Skip to content

Commit a9a4991

Browse files
committed
feat: Adding files
1 parent ebcbb17 commit a9a4991

File tree

4 files changed

+106
-72
lines changed

4 files changed

+106
-72
lines changed

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

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,20 @@ import { RestartButton } from '@/components/RestartButton';
22
import { Button } from '@/components/ui/button';
33
import { isLocalStudio } from '@/config/constants';
44
import { useInstanceClientIdParams } from '@/config/useInstanceClient';
5-
import {
6-
DirectoryIcon,
7-
} from '@/features/instance/applications/components/ApplicationsSidebar/FileTreeExplorer/DirectoryIcon';
85
import { isDirectory } from '@/features/instance/applications/context/isDirectory';
96
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
107
import { AddFolderFileModal } from '@/features/instance/applications/modals/AddFolderFileModal';
118
import { DeleteFolderFileModal } from '@/features/instance/applications/modals/DeleteFolderFileModal';
129
import { RedeployApplicationModal } from '@/features/instance/applications/modals/RedeployApplicationModal';
1310
import { useDeleteComponentFolderFile } from '@/features/instance/operations/mutations/deleteComponentFolderFile';
1411
import { useDeployComponentMutation } from '@/features/instance/operations/mutations/deployComponent';
15-
import { useUpdateComponentFile } from '@/features/instance/operations/mutations/updateComponentFile';
1612
import { useEffectedState } from '@/hooks/useEffectedState';
1713
import { useToggler } from '@/hooks/useToggler';
1814
import { parseFileExtension } from '@/lib/string/parseFileExtension';
1915
import { Editor, EditorProps, OnMount } from '@monaco-editor/react';
2016
import { useQueryClient } from '@tanstack/react-query';
2117
import { useParams } from '@tanstack/react-router';
22-
import { FileIcon, PackageIcon, PencilIcon, SaveIcon, TrashIcon } from 'lucide-react';
18+
import { FileIcon, FolderIcon, PackageIcon, PencilIcon, SaveIcon, TrashIcon, Undo2Icon } from 'lucide-react';
2319
import { useCallback, useEffect, useRef, useState } from 'react';
2420
import './directory-read-me.css';
2521
import Markdown from 'react-markdown';
@@ -52,14 +48,10 @@ export function TextEditorView() {
5248
);
5349
const targetNoun = instanceId || isLocalStudio ? 'Instance' : 'Cluster';
5450

55-
const editorRef = useRef<Parameters<OnMount>[0] | null>(null);
51+
const mountedRef = useRef<Parameters<OnMount> | null>(null);
5652

5753
// TODO: Split all this logic up into smaller files, right?
5854

59-
const handleEditorDidMount: EditorProps['onMount'] = useCallback<OnMount>((editor) => {
60-
editorRef.current = editor;
61-
}, [editorRef]);
62-
6355
useEffect(() => {
6456
const extension = parseFileExtension(openedEntry?.path);
6557
const updatedLanguage = extensionToLanguageMap[extension] || 'plaintext';
@@ -84,38 +76,51 @@ export function TextEditorView() {
8476
if (openedEntryContents) {
8577
setUpdateFileContent(openedEntryContents);
8678
}
87-
if (editorRef) {
88-
editorRef.current?.setValue(openedEntryContents || '');
79+
if (mountedRef.current) {
80+
const [editor] = mountedRef.current;
81+
editor.setValue(openedEntryContents || '');
8982
}
9083
}, [openedEntryContents]);
9184

92-
const [isAddFolderOrFileClicked, setIsAddFolderOrFileClicked] = useState(false);
85+
const handleEditorDidMount: EditorProps['onMount'] = useCallback<OnMount>((editor, monaco) => {
86+
mountedRef.current = [editor, monaco];
87+
}, [mountedRef, onSaveClick]);
88+
89+
useEffect(() => {
90+
if (!mountedRef.current) {
91+
return;
92+
}
93+
const [editor, monaco] = mountedRef.current;
94+
const disposables = [
95+
editor.addAction({
96+
id: 'save-file',
97+
label: 'Save Changes',
98+
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
99+
run: onSaveClick,
100+
}),
101+
editor.addAction({
102+
id: 'revert-file',
103+
label: 'Revert File',
104+
run: onDiscardClick,
105+
}),
106+
];
107+
return () => {
108+
for (const disposable of disposables) {
109+
disposable?.dispose();
110+
}
111+
};
112+
}, [mountedRef, onSaveClick, onDiscardClick]);
113+
114+
const {
115+
toggled: isAddFolderOrFileClicked,
116+
toggleOn: onNewFileClick,
117+
setToggled: setIsAddFolderOrFileClicked,
118+
} = useToggler(false);
93119
const { toggled: isAddingFolder, toggleOn: setIsAddingFolder } = useToggler(false);
94120
const { toggled: isDeleteFolderOrFileClicked, setToggled: setIsDeleteFolderOrFileClicked } = useToggler(false);
95121
const { toggled: isRedeployApplicationClicked, setToggled: setIsRedeployApplicationClicked } = useToggler(false);
96-
const { mutate: addFolderFile, isPending: isAddFolderFilePending } = useUpdateComponentFile();
97122
const { mutate: deleteFolderFile, isPending: isDeleteFolderFilePending } = useDeleteComponentFolderFile();
98123

99-
const handleAddFolderOrFile = useCallback(async (name: string) => {
100-
if (!openedEntry) {
101-
return;
102-
}
103-
addFolderFile(
104-
{
105-
file: `${openedEntry.path.split('/').slice(1).join('/')}/${name}`,
106-
project: openedEntry.project,
107-
payload: isAddingFolder ? undefined : '',
108-
...instanceParams,
109-
},
110-
{
111-
onSuccess: () => {
112-
// TODO: refetchComponents();
113-
setIsAddFolderOrFileClicked(false);
114-
},
115-
},
116-
);
117-
}, [addFolderFile, instanceParams, isAddingFolder, openedEntry]);
118-
119124
const handleDeleteFolderOrFile = useCallback(async () => {
120125
if (!openedEntry) {
121126
return;
@@ -196,7 +201,7 @@ export function TextEditorView() {
196201

197202
return (
198203
<>
199-
{openedEntryContents && <>
204+
{openedEntryContents !== undefined && <>
200205
{!isDirectory(openedEntry)
201206
? (<Editor
202207
className="w-full min-h-full h-80"
@@ -234,7 +239,7 @@ export function TextEditorView() {
234239
accessKey="s"
235240
>
236241
<SaveIcon />
237-
<span><u>S</u>ave</span>
242+
<span className="hidden lg:inline-block"><u>S</u>ave</span>
238243
</Button>}
239244

240245
<Button
@@ -249,28 +254,28 @@ export function TextEditorView() {
249254
accessKey="s"
250255
>
251256
<PencilIcon />
252-
<span><u>R</u>ename</span>
257+
<span className="hidden lg:inline-block"><u>R</u>ename</span>
253258
</Button>
254259

255-
{isDirectory(openedEntry) && <Button
260+
<Button
256261
variant="ghost"
257262
className="rounded-none"
258-
// onClick={onNewFileClick}
263+
onClick={onNewFileClick}
259264
accessKey="n"
260265
>
261266
<FileIcon />
262-
<span><u>N</u>ew File</span>
263-
</Button>}
267+
<span className="hidden lg:inline-block"><u>N</u>ew File</span>
268+
</Button>
264269

265-
{isDirectory(openedEntry) && <Button
270+
<Button
266271
variant="ghost"
267272
className="rounded-none"
268273
onClick={setIsAddingFolder}
269274
accessKey="n"
270275
>
271-
<DirectoryIcon />
272-
<span><u>A</u>dd Directory</span>
273-
</Button>}
276+
<FolderIcon />
277+
<span className="hidden lg:inline-block"><u>A</u>dd Directory</span>
278+
</Button>
274279

275280
{openedEntry.package && <Button
276281
variant="ghost"
@@ -299,7 +304,7 @@ export function TextEditorView() {
299304
accessKey="n"
300305
>
301306
<TrashIcon />
302-
<span><u>D</u>elete</span>
307+
<span className="hidden xl:inline-block"><u>D</u>elete</span>
303308
</Button>
304309

305310
{!isDirectory(openedEntry) && <Button
@@ -313,8 +318,8 @@ export function TextEditorView() {
313318
}
314319
accessKey="d"
315320
>
316-
<SaveIcon />
317-
<span><u>D</u>iscard Changes</span>
321+
<Undo2Icon />
322+
<span className="hidden xl:inline-block"><u>D</u>iscard Changes</span>
318323
</Button>}
319324

320325
</div>
@@ -323,8 +328,6 @@ export function TextEditorView() {
323328
isModalOpen={isAddFolderOrFileClicked}
324329
setIsModalOpen={setIsAddFolderOrFileClicked}
325330
isAddingFolder={isAddingFolder}
326-
handleAddFolderOrFile={handleAddFolderOrFile}
327-
isPending={isAddFolderFilePending}
328331
/>
329332
<DeleteFolderFileModal
330333
isModalOpen={isDeleteFolderOrFileClicked}

src/features/instance/applications/context/EditorViewContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { FileEntry } from './fileEntry';
55

66
export type EditorViewContextValue = {
77
rootEntries: Array<DirectoryEntry | FileEntry>;
8+
reloadRootEntries: () => void;
89

910
openedEntry: DirectoryEntry | FileEntry | null;
1011
setOpenedEntry: (entry: DirectoryEntry | FileEntry | null) => void;

src/features/instance/applications/context/EditorViewProvider.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import {
1010
getComponentsQueryOptions,
1111
} from '@/features/instance/operations/queries/getComponents';
1212
import { transformNodes } from '@/lib/arrays/transformNodes';
13-
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
13+
import { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
1414
import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react';
15-
import { toast } from 'sonner';
1615
import { DirectoryEntry } from './directoryEntry';
1716
import { EditorViewContext, EditorViewContextValue } from './EditorViewContext';
1817
import { FileEntry } from './fileEntry';
@@ -22,6 +21,14 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
2221
const [openedEntry, setOpenedEntry] = useState<DirectoryEntry | FileEntry | null>(null);
2322
const [openedEntryContents, setOpenedEntryContents] = useState<string | null>(null);
2423
const instanceParams = useInstanceClientIdParams();
24+
const queryClient = useQueryClient();
25+
26+
const reloadRootEntries = useCallback(() => {
27+
void queryClient.invalidateQueries({
28+
queryKey: [instanceParams.entityId, 'get_components'],
29+
refetchType: 'active',
30+
});
31+
}, [queryClient, instanceParams]);
2532

2633
/*
2734
Create our structured view from the relational API data.
@@ -91,13 +98,6 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
9198
if (openedEntry?.path === filePath && data.payload) {
9299
setOpenedEntryContents(data.payload);
93100
}
94-
toast.success('Success', {
95-
description: `${data.file.split('/').pop()} saved successfully. A restart is required to see changes.`,
96-
action: {
97-
label: 'Dismiss',
98-
onClick: () => toast.dismiss(),
99-
},
100-
});
101101
},
102102
onError: (error) => {
103103
console.error('Error saving file:', error);
@@ -114,6 +114,7 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
114114
return {
115115

116116
rootEntries,
117+
reloadRootEntries,
117118

118119
openedEntry,
119120
setOpenedEntry,

src/features/instance/applications/modals/AddFolderFileModal.tsx

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,28 @@ import { FormItem } from '@/components/ui/form/FormItem';
1414
import { FormLabel } from '@/components/ui/form/FormLabel';
1515
import { FormMessage } from '@/components/ui/form/FormMessage';
1616
import { Input } from '@/components/ui/input';
17+
import { useInstanceClientIdParams } from '@/config/useInstanceClient';
18+
import { isDirectory } from '@/features/instance/applications/context/isDirectory';
19+
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
20+
import { useUpdateComponentFile } from '@/features/instance/operations/mutations/updateComponentFile';
1721
import { zodResolver } from '@hookform/resolvers/zod';
1822
import { Ban, Plus } from 'lucide-react';
23+
import { useCallback } from 'react';
1924
import { useForm } from 'react-hook-form';
2025
import z from 'zod';
2126

2227
export function AddFolderFileModal({
2328
isModalOpen = false,
2429
setIsModalOpen,
25-
isPending,
2630
isAddingFolder,
27-
handleAddFolderOrFile,
2831
}: {
2932
readonly isModalOpen?: boolean;
3033
readonly setIsModalOpen: (value: boolean) => void;
31-
readonly isPending?: boolean;
3234
readonly isAddingFolder?: boolean;
33-
readonly handleAddFolderOrFile?: (name: string) => void;
3435
}) {
36+
const { openedEntry, reloadRootEntries } = useEditorView();
37+
const instanceParams = useInstanceClientIdParams();
38+
const { mutate: addFolderFile, isPending } = useUpdateComponentFile();
3539
const NewFileFolderSchema = z.object({
3640
name: z
3741
.string()
@@ -50,11 +54,43 @@ export function AddFolderFileModal({
5054
},
5155
});
5256

57+
const submitForm = useCallback((data: z.infer<typeof NewFileFolderSchema>) => {
58+
if (!openedEntry) {
59+
return;
60+
}
61+
const splitPath = openedEntry.path.split('/');
62+
const intoPath = (
63+
isDirectory(openedEntry)
64+
? splitPath.slice(1)
65+
: splitPath.slice(1, -1)
66+
).join('/');
67+
addFolderFile(
68+
{
69+
file: `${intoPath}/${data.name}`,
70+
project: openedEntry.project,
71+
payload: isAddingFolder ? undefined : '',
72+
...instanceParams,
73+
},
74+
{
75+
onSuccess: () => {
76+
reloadRootEntries();
77+
setIsModalOpen(false);
78+
form.reset();
79+
},
80+
},
81+
);
82+
}, [addFolderFile, instanceParams, isAddingFolder, openedEntry, reloadRootEntries]);
83+
84+
const onCancelClick = useCallback(() => {
85+
setIsModalOpen(false);
86+
form.reset();
87+
}, [setIsModalOpen, form]);
88+
5389
return (
5490
<Dialog onOpenChange={setIsModalOpen} open={isModalOpen}>
5591
<DialogContent aria-describedby={undefined} className="text-white">
5692
<Form {...form}>
57-
<form>
93+
<form onSubmit={form.handleSubmit(submitForm)}>
5894
<DialogHeader>
5995
<DialogTitle>Add {isAddingFolder ? 'Folder' : 'File'}</DialogTitle>
6096
<DialogDescription>
@@ -84,21 +120,14 @@ export function AddFolderFileModal({
84120

85121
<DialogFooter>
86122
<div className="flex justify-between w-full">
87-
<Button type="button" variant="destructiveOutline" className="rounded-full" onClick={() => setIsModalOpen(false)}>
123+
<Button type="button" variant="destructiveOutline" className="rounded-full" onClick={onCancelClick}>
88124
<Ban /> Cancel
89125
</Button>
90126
<Button
91127
variant="positiveOutline"
92128
type="submit"
93129
className="rounded-full"
94130
disabled={isPending}
95-
onClick={(e) => {
96-
e.preventDefault();
97-
form.handleSubmit((data) => {
98-
handleAddFolderOrFile?.(data.name);
99-
form.reset();
100-
})();
101-
}}
102131
>
103132
<Plus /> Add {isAddingFolder ? 'Folder' : 'File'}
104133
</Button>

0 commit comments

Comments
 (0)