Skip to content

Commit 7b40ade

Browse files
committed
feat: Rename from tree
1 parent 012554c commit 7b40ade

File tree

8 files changed

+117
-33
lines changed

8 files changed

+117
-33
lines changed

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ export function buildItems(rootEntries: Array<DirectoryEntry | FileEntry>): {
2626
isFolder: true,
2727
children: [...directoryIds, ...fileIds],
2828
data: null,
29-
};
29+
canMove: false,
30+
canRename: false,
31+
} satisfies TreeItem<null>;
3032

3133
return { items, rootId };
3234
}
@@ -54,7 +56,7 @@ function addEntry(
5456
data: entry,
5557
canMove: false,
5658
canRename: false,
57-
} satisfies TreeItem<DirectoryEntry | FileEntry>;
59+
} satisfies TreeItem<DirectoryEntry>;
5860
for (const child of dir.entries) {
5961
addEntry(items, child);
6062
}
@@ -65,6 +67,6 @@ function addEntry(
6567
data: entry,
6668
canMove: true,
6769
canRename: true,
68-
};
70+
} satisfies TreeItem<FileEntry>;
6971
}
7072
}

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

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useInstanceClientIdParams } from '@/config/useInstanceClient';
12
import { buildItems } from '@/features/instance/applications/components/ApplicationsSidebar/buildItems';
23
import {
34
DirectoryIcon,
@@ -7,14 +8,19 @@ import type { DirectoryEntry } from '@/features/instance/applications/context/di
78
import type { FileEntry } from '@/features/instance/applications/context/fileEntry';
89
import { isDirectory } from '@/features/instance/applications/context/isDirectory';
910
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
10-
import { useEffect, useMemo } from 'react';
11-
import { ControlledTreeEnvironment, Tree } from 'react-complex-tree';
11+
import { dropComponent } from '@/features/instance/operations/mutations/dropComponent';
12+
import { setComponentFile } from '@/features/instance/operations/mutations/setComponentFile';
13+
import { getComponentFile } from '@/features/instance/operations/queries/getComponentFile';
14+
import { useCallback, useEffect, useMemo } from 'react';
15+
import { ControlledTreeEnvironment, Tree, TreeItem } from 'react-complex-tree';
1216
import './file-explorer-modern.css';
17+
import { toast } from 'sonner';
1318
import { FileTypeIcon } from './FileTreeExplorer/FileTypeIcon';
1419

1520
export function ApplicationsSidebar() {
1621
const {
1722
rootEntries,
23+
reloadRootEntries,
1824
openedEntry,
1925
setOpenedEntry,
2026
focusedItem,
@@ -25,6 +31,7 @@ export function ApplicationsSidebar() {
2531
setSelectedItems,
2632
} = useEditorView();
2733

34+
const instanceParams = useInstanceClientIdParams();
2835
const { items, rootId } = useMemo(() => buildItems(rootEntries), [rootEntries]);
2936

3037
useEffect(function setOpenedEntryFromFocusedItem() {
@@ -37,7 +44,7 @@ export function ApplicationsSidebar() {
3744
}
3845
}, [openedEntry, focusedItem, items]);
3946

40-
// TODO: onRenameItem f2 handling
47+
// TODO: rename from editor handling
4148
// TODO: Split all this logic up into smaller files, right?
4249

4350
// TODO: keyboard shortcuts when the editor isn't focused (creating files and folders, deleting, can we
@@ -54,6 +61,75 @@ export function ApplicationsSidebar() {
5461
// TODO: open file after creating it
5562
// TODO: select folder after creating it
5663

64+
const onRenameItem = useCallback(async (item: TreeItem<FileEntry | DirectoryEntry | null>, name: string) => {
65+
if (item.data && item.data?.name !== name) {
66+
const oldFile = item.data.path.split('/').slice(1).join('/');
67+
const newFile = item.data.path.split('/').slice(1, -1).join('/') + '/' + name;
68+
69+
const toastId = toast.loading('Renaming', {
70+
description: 'Loading existing contents...',
71+
duration: 0,
72+
});
73+
const fileContents = await getComponentFile({
74+
...instanceParams,
75+
file: oldFile,
76+
project: item.data.project,
77+
});
78+
79+
toast.loading('Renaming', {
80+
id: toastId,
81+
description: 'Copying to new name...',
82+
duration: 0,
83+
});
84+
await setComponentFile({
85+
...instanceParams,
86+
file: newFile,
87+
project: item.data.project,
88+
payload: fileContents.message,
89+
});
90+
91+
toast.loading('Renaming', {
92+
id: toastId,
93+
description: 'Removing old copy...',
94+
duration: 0,
95+
});
96+
await dropComponent({
97+
...instanceParams,
98+
file: oldFile,
99+
project: item.data.project,
100+
});
101+
toast.success('Renamed!', {
102+
id: toastId,
103+
description: 'All done!',
104+
duration: 3000,
105+
});
106+
107+
reloadRootEntries();
108+
109+
const existingIndex = item.index;
110+
const newIndex = (item.index as string).split('/').slice(0, -1).join('/') + '/' + name;
111+
112+
setSelectedItems(selectedItems => {
113+
const selectedIndex = selectedItems.indexOf(existingIndex);
114+
if (selectedIndex >= 0) {
115+
return [
116+
...selectedItems.slice(0, selectedIndex),
117+
...selectedItems.slice(selectedIndex + 1),
118+
newIndex,
119+
];
120+
}
121+
return selectedItems;
122+
});
123+
124+
setFocusedItem(focusedItem => {
125+
if (focusedItem === existingIndex) {
126+
return newIndex;
127+
}
128+
return focusedItem;
129+
});
130+
}
131+
}, [openedEntry]);
132+
57133
return (
58134
<div className="h-full overflow-auto pr-1.5">
59135
<ControlledTreeEnvironment
@@ -62,6 +138,7 @@ export function ApplicationsSidebar() {
62138
canDragAndDrop={true}
63139
canReorderItems={false}
64140
canSearch={true}
141+
onRenameItem={onRenameItem}
65142
renderItemTitle={({ title, item, context }) => {
66143
const data = item.data as DirectoryEntry | FileEntry | null;
67144
const isDir = isDirectory(data);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SetComponentFileRequest } from '@/features/instance/operations/mutations/updateComponentFile';
1+
import { SetComponentFileRequest } from '@/features/instance/operations/mutations/setComponentFile';
22
import { createContext } from 'react';
33
import { TreeItemIndex } from 'react-complex-tree/src/types';
44
import { DirectoryEntry } from './directoryEntry';

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useInstanceClientIdParams } from '@/config/useInstanceClient';
22
import {
33
SetComponentFileRequest,
4-
useUpdateComponentFile,
5-
} from '@/features/instance/operations/mutations/updateComponentFile';
4+
useSetComponentFile,
5+
} from '@/features/instance/operations/mutations/setComponentFile';
66
import { getComponentFileQueryOptions } from '@/features/instance/operations/queries/getComponentFile';
77
import {
88
APIDirectoryEntry,
@@ -98,7 +98,7 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
9898
/*
9999
Save changes.
100100
*/
101-
const { mutate: saveComponentFile, isPending: isSavingFile } = useUpdateComponentFile();
101+
const { mutate: saveComponentFile, isPending: isSavingFile } = useSetComponentFile();
102102
const saveFile = useCallback(
103103
(data: SetComponentFileRequest, filePath: string) => {
104104
saveComponentFile(data, {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Input } from '@/components/ui/input';
1717
import { useInstanceClientIdParams } from '@/config/useInstanceClient';
1818
import { isDirectory } from '@/features/instance/applications/context/isDirectory';
1919
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
20-
import { useUpdateComponentFile } from '@/features/instance/operations/mutations/updateComponentFile';
20+
import { useSetComponentFile } from '@/features/instance/operations/mutations/setComponentFile';
2121
import { excludeFalsy } from '@/lib/arrays/excludeFalsy';
2222
import { zodResolver } from '@hookform/resolvers/zod';
2323
import { Ban, Plus } from 'lucide-react';
@@ -36,7 +36,7 @@ export function AddDirectoryOrFileModal({
3636
}) {
3737
const { openedEntry, reloadRootEntries, setFocusedItem, setSelectedItems, setExpandedItems } = useEditorView();
3838
const instanceParams = useInstanceClientIdParams();
39-
const { mutate: addFolderFile, isPending } = useUpdateComponentFile();
39+
const { mutate: addFolderFile, isPending } = useSetComponentFile();
4040
const NewFileFolderSchema = z.object({
4141
name: z
4242
.string()

src/features/instance/operations/mutations/dropComponent.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1-
import { InstanceClientConfig } from '@/config/instanceClientConfig';
1+
import { InstanceClientConfig, InstanceTypeConfig } from '@/config/instanceClientConfig';
22
import { ReplicatedResponse } from '@/lib/api/replication';
33
import { useMutation } from '@tanstack/react-query';
44

5-
interface DropComponentRequest extends InstanceClientConfig {
5+
interface DropComponentRequest extends InstanceClientConfig, InstanceTypeConfig {
66
project: string;
77
file: string | undefined;
8-
replicated: boolean;
98
}
109

11-
async function dropComponent({
10+
export async function dropComponent({
1211
file,
1312
project,
14-
replicated,
13+
entityType,
1514
instanceClient,
1615
}: DropComponentRequest): Promise<ReplicatedResponse> {
1716
const { data } = await instanceClient.post('/', {
1817
operation: 'drop_component',
1918
file: file || undefined,
2019
project,
21-
replicated,
20+
replicated: entityType === 'cluster',
2221
});
2322
return data;
2423
}

src/features/instance/operations/mutations/updateComponentFile.ts renamed to src/features/instance/operations/mutations/setComponentFile.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface SetComponentFileRequest extends InstanceClientConfig, InstanceT
77
project: string;
88
}
99

10-
async function onUpdateComponentFile({ file, payload, project, entityType, instanceClient }: SetComponentFileRequest) {
10+
export async function setComponentFile({ file, payload, project, entityType, instanceClient }: SetComponentFileRequest) {
1111
const { data } = await instanceClient.post('/', {
1212
operation: 'set_component_file',
1313
file,
@@ -18,8 +18,8 @@ async function onUpdateComponentFile({ file, payload, project, entityType, insta
1818
return data;
1919
}
2020

21-
export function useUpdateComponentFile() {
21+
export function useSetComponentFile() {
2222
return useMutation({
23-
mutationFn: onUpdateComponentFile,
23+
mutationFn: setComponentFile,
2424
});
2525
}

src/features/instance/operations/queries/getComponentFile.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@ interface GetComponentFileResponse {
1616
size: number;
1717
}
1818

19+
export async function getComponentFile({
20+
instanceClient,
21+
file,
22+
project,
23+
}: GetComponentFileRequest): Promise<GetComponentFileResponse> {
24+
const { data } = await instanceClient.post('/', {
25+
operation: 'get_component_file',
26+
file,
27+
project,
28+
});
29+
return {
30+
file,
31+
project,
32+
...data,
33+
};
34+
}
35+
1936
export function getComponentFileQueryOptions({ entityId, instanceClient, file, project }: GetComponentFileRequest) {
2037
return queryOptions({
2138
queryKey: [
@@ -24,18 +41,7 @@ export function getComponentFileQueryOptions({ entityId, instanceClient, file, p
2441
file,
2542
project,
2643
] as const,
27-
queryFn: async (): Promise<GetComponentFileResponse> => {
28-
const { data } = await instanceClient.post('/', {
29-
operation: 'get_component_file',
30-
file,
31-
project,
32-
});
33-
return {
34-
file,
35-
project,
36-
...data,
37-
};
38-
},
44+
queryFn: () => getComponentFile({ entityId, instanceClient, file, project }),
3945
enabled: !!file && !!project,
4046
retry: false,
4147
});

0 commit comments

Comments
 (0)