Skip to content

Commit 1c54c53

Browse files
committed
feat: Rename file from editor too
1 parent e9f9965 commit 1c54c53

File tree

5 files changed

+153
-20
lines changed

5 files changed

+153
-20
lines changed

src/components/RestartButton.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface RestartButtonParams extends InstanceClientConfig, VariantProps<typeof
1111
targetNoun: 'Instance' | 'Cluster';
1212
operation: 'restart_service' | 'restart';
1313
className?: string;
14+
disabled?: boolean;
1415
}
1516

1617
export function RestartButton({
@@ -19,6 +20,7 @@ export function RestartButton({
1920
operation,
2021
variant,
2122
className,
23+
disabled,
2224
}: RestartButtonParams) {
2325
const {
2426
onRestartClick: onRestartClusterClick,
@@ -31,7 +33,7 @@ export function RestartButton({
3133
variant={variant || 'positiveOutline'}
3234
className={cx('mx-0 md:mx-4 rounded-full', className)}
3335
onClick={targetNoun === 'Cluster' && operation === 'restart' ? onRestartClusterClick : onRestartClick}
34-
disabled={isRestartPending || isRestartClusterPending}
36+
disabled={disabled || isRestartPending || isRestartClusterPending}
3537
>
3638
<RotateCcwIcon /> Restart {targetNoun}
3739
</Button>

src/features/instance/applications/components/ApplicationsSidebar/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export function ApplicationsSidebar() {
3838
}
3939
}, [openedEntry, focusedItem, items]);
4040

41-
// TODO: rename from editor handling
4241
// TODO: Split all this logic up into smaller files, right?
4342

4443
// TODO: keyboard shortcuts when the editor isn't focused (creating files and folders, deleting, can we

src/features/instance/applications/components/TextEditorView/index.tsx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useEditorView } from '@/features/instance/applications/hooks/useEditorV
77
import { AddDirectoryOrFileModal } from '@/features/instance/applications/modals/AddDirectoryOrFileModal';
88
import { DeleteDirectoryOrFileModal } from '@/features/instance/applications/modals/DeleteDirectoryOrFileModal';
99
import { RedeployApplicationModal } from '@/features/instance/applications/modals/RedeployApplicationModal';
10+
import { RenameFileModal } from '@/features/instance/applications/modals/RenameFileModal';
1011
import { useDeployComponentMutation } from '@/features/instance/operations/mutations/deployComponent';
1112
import { useEffectedState } from '@/hooks/useEffectedState';
1213
import { useInstanceBrowseManagePermission } from '@/hooks/usePermissions';
@@ -91,6 +92,7 @@ export function TextEditorView() {
9192
toggleOff: hideAddingDirectory,
9293
} = useToggler(false);
9394
const { toggled: isAddingFile, toggleOn: onAddFileClicked, toggleOff: hideAddingFile } = useToggler(false);
95+
const { toggled: isRenamingFile, toggleOn: onRenameClick, toggleOff: onHideRenamingFileModal } = useToggler(false);
9496
const {
9597
toggled: isDeleteDirectoryOrFileClicked,
9698
toggleOn: onDeleteClick,
@@ -161,6 +163,12 @@ export function TextEditorView() {
161163
keybindings: [monaco.KeyMod.WinCtrl | monaco.KeyMod.Alt | monaco.KeyCode.KeyN],
162164
run: onAddFileClicked,
163165
}),
166+
editor.addAction({
167+
id: 'rename-file',
168+
label: 'Rename File',
169+
keybindings: [monaco.KeyMod.WinCtrl | monaco.KeyMod.Alt | monaco.KeyCode.KeyR],
170+
run: onRenameClick,
171+
}),
164172
editor.addAction({
165173
id: 'new-directory',
166174
label: 'New Directory',
@@ -189,12 +197,14 @@ export function TextEditorView() {
189197
disposable?.dispose();
190198
}
191199
};
192-
}, [mountedRef, canManageBrowseInstance, onAddFileClicked, onAddDirectoryClicked, onSaveClick, onRevertChangesClicked, onDeleteClick]);
200+
}, [mountedRef, canManageBrowseInstance, onAddFileClicked, onRenameClick, onAddDirectoryClicked, onSaveClick, onRevertChangesClicked, onDeleteClick]);
193201

194202
if (!openedEntry) {
195203
return null;
196204
}
197205

206+
const fileIsClean = updateFileContent === undefined || updateFileContent === openedEntryContents;
207+
198208
return (
199209
<>
200210
{openedEntryContents !== undefined && <>
@@ -227,11 +237,7 @@ export function TextEditorView() {
227237
variant="default"
228238
className="rounded-none"
229239
onClick={onSaveClick}
230-
disabled={
231-
updateFileContent === undefined ||
232-
updateFileContent === openedEntryContents ||
233-
isSavingFile
234-
}
240+
disabled={fileIsClean || isSavingFile}
235241
>
236242
<SaveIcon />
237243
<span className="hidden lg:inline-block"><u>S</u>ave</span>
@@ -240,12 +246,8 @@ export function TextEditorView() {
240246
{!openedEntry.package && canManageBrowseInstance && <Button
241247
variant="ghost"
242248
className="rounded-none"
243-
// onClick={onRenameClick}
244-
disabled={
245-
updateFileContent === undefined ||
246-
updateFileContent === openedEntryContents ||
247-
isSavingFile
248-
}
249+
onClick={onRenameClick}
250+
disabled={!fileIsClean || isSavingFile}
249251
>
250252
<PencilIcon />
251253
<span className="hidden lg:inline-block"><u>R</u>ename</span>
@@ -284,6 +286,7 @@ export function TextEditorView() {
284286
operation="restart_service"
285287
variant="ghost"
286288
className="rounded-none"
289+
disabled={!fileIsClean || isSavingFile}
287290
/>}
288291

289292
<div className="grow"></div>
@@ -293,11 +296,7 @@ export function TextEditorView() {
293296
variant="ghost"
294297
className="rounded-none"
295298
onClick={onRevertChangesClicked}
296-
disabled={
297-
updateFileContent === undefined ||
298-
updateFileContent === openedEntryContents ||
299-
isSavingFile
300-
}
299+
disabled={fileIsClean || isSavingFile}
301300
>
302301
<Undo2Icon />
303302
<span className="hidden xl:inline-block">Revert Changes</span>
@@ -318,6 +317,10 @@ export function TextEditorView() {
318317
hideModal={onHideAddDirectoryModal}
319318
type={isAddingDirectory ? 'directory' : 'file'}
320319
/>
320+
<RenameFileModal
321+
isModalOpen={isRenamingFile}
322+
hideModal={onHideRenamingFileModal}
323+
/>
321324
<DeleteDirectoryOrFileModal
322325
isModalOpen={isDeleteDirectoryOrFileClicked}
323326
setIsModalOpen={setIsDeleteDirectoryOrFileClicked}

src/features/instance/applications/hooks/useRenameFile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function useRenameFile() {
6262

6363
reloadRootEntries();
6464

65-
const existingIndex = `${data.project}/${data.path}`;
65+
const existingIndex = data.path;
6666
const newIndex = `${existingIndex.split('/').slice(0, -1).join('/')}/${name}`;
6767

6868
setSelectedItems(selectedItems => {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Button } from '@/components/ui/button';
2+
import {
3+
Dialog,
4+
DialogContent,
5+
DialogDescription,
6+
DialogFooter,
7+
DialogHeader,
8+
DialogTitle,
9+
} from '@/components/ui/dialog';
10+
import { Form } from '@/components/ui/form/Form';
11+
import { FormControl } from '@/components/ui/form/FormControl';
12+
import { FormField } from '@/components/ui/form/FormField';
13+
import { FormItem } from '@/components/ui/form/FormItem';
14+
import { FormLabel } from '@/components/ui/form/FormLabel';
15+
import { FormMessage } from '@/components/ui/form/FormMessage';
16+
import { Input } from '@/components/ui/input';
17+
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
18+
import { useRenameFile } from '@/features/instance/applications/hooks/useRenameFile';
19+
import { zodResolver } from '@hookform/resolvers/zod';
20+
import { Ban, PencilIcon } from 'lucide-react';
21+
import { useCallback, useEffect, useState } from 'react';
22+
import { useForm } from 'react-hook-form';
23+
import z from 'zod';
24+
25+
export function RenameFileModal({
26+
isModalOpen = false,
27+
hideModal,
28+
}: {
29+
readonly isModalOpen?: boolean;
30+
readonly hideModal: () => void;
31+
}) {
32+
const { openedEntry } = useEditorView();
33+
const RenameFileSchema = z.object({
34+
name: z
35+
.string()
36+
.nonempty({ error: 'Please enter a valid name.' })
37+
.regex(/^[a-zA-Z0-9_\- .]*$/, {
38+
error: 'Names can only contain letters, numbers, underscores, hyphens, periods, and spaces.',
39+
})
40+
.max(50, { error: 'Names cannot be longer than 50 characters.' })
41+
.trim()
42+
.refine((name) => name !== openedEntry?.name, {
43+
error: 'Please enter a new name.',
44+
path: ['name'],
45+
}),
46+
});
47+
const [isPending, setIsPending] = useState(false);
48+
const renameFile = useRenameFile();
49+
50+
const form = useForm({
51+
resolver: zodResolver(RenameFileSchema),
52+
});
53+
54+
useEffect(() => {
55+
if (openedEntry?.name) {
56+
form.reset({ name: openedEntry?.name });
57+
}
58+
}, [openedEntry?.name]);
59+
60+
const submitForm = useCallback(async (data: z.infer<typeof RenameFileSchema>) => {
61+
if (!openedEntry) {
62+
return;
63+
}
64+
65+
setIsPending(true);
66+
await renameFile(openedEntry, data.name);
67+
hideModal();
68+
form.reset();
69+
setIsPending(false);
70+
}, [setIsPending, openedEntry]);
71+
72+
const onCancelClick = useCallback(() => {
73+
hideModal();
74+
form.reset();
75+
}, [hideModal, form]);
76+
77+
return (
78+
<Dialog onOpenChange={hideModal} open={isModalOpen}>
79+
<DialogContent aria-describedby={undefined} className="text-white">
80+
<Form {...form}>
81+
<form onSubmit={form.handleSubmit(submitForm)}>
82+
<DialogHeader>
83+
<DialogTitle>Rename File</DialogTitle>
84+
<DialogDescription>
85+
{openedEntry?.path}
86+
</DialogDescription>
87+
</DialogHeader>
88+
89+
<FormField
90+
control={form.control}
91+
name="name"
92+
render={({ field }) => (
93+
<FormItem className="my-2">
94+
<FormLabel>Name</FormLabel>
95+
<FormControl>
96+
<Input
97+
disabled={isPending}
98+
type="text"
99+
autoComplete="off"
100+
autoCapitalize="off"
101+
{...field}
102+
/>
103+
</FormControl>
104+
<FormMessage />
105+
</FormItem>
106+
)}
107+
/>
108+
109+
<DialogFooter>
110+
<div className="flex justify-between w-full">
111+
<Button type="button" variant="destructiveOutline" className="rounded-full" onClick={onCancelClick}>
112+
<Ban /> Cancel
113+
</Button>
114+
<Button
115+
variant="positiveOutline"
116+
type="submit"
117+
className="rounded-full"
118+
disabled={isPending || !form.formState.isDirty || !form.formState.isValid}
119+
>
120+
<PencilIcon /> Rename
121+
</Button>
122+
</div>
123+
</DialogFooter>
124+
</form>
125+
</Form>
126+
</DialogContent>
127+
</Dialog>
128+
);
129+
}

0 commit comments

Comments
 (0)