Skip to content

Commit a14cbeb

Browse files
committed
feat: Update tree when adding and deleting stuff
1 parent f50e2d8 commit a14cbeb

File tree

7 files changed

+85
-36
lines changed

7 files changed

+85
-36
lines changed

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

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,27 @@ import type { DirectoryEntry } from '@/features/instance/applications/context/di
77
import type { FileEntry } from '@/features/instance/applications/context/fileEntry';
88
import { isDirectory } from '@/features/instance/applications/context/isDirectory';
99
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
10-
import { useSessionStorage } from '@/hooks/useSessionStorage';
11-
import { useParams } from '@tanstack/react-router';
1210
import { useEffect, useMemo } from 'react';
1311
import { ControlledTreeEnvironment, Tree } from 'react-complex-tree';
1412
import './file-explorer-modern.css';
15-
import { TreeItemIndex } from 'react-complex-tree/src/types';
1613
import { FileTypeIcon } from './FileTreeExplorer/FileTypeIcon';
1714

1815
export function ApplicationsSidebar() {
19-
const { rootEntries, openedEntry, setOpenedEntry } = useEditorView();
20-
const { clusterId, instanceId }: { clusterId?: string; instanceId?: string } = useParams({ strict: false });
21-
22-
const defaultFolderExpansions = rootEntries.filter(rootEntry => !rootEntry.package).map<TreeItemIndex>(rootEntry => rootEntry.name);
23-
const defaultFocusedItem = defaultFolderExpansions[0];
24-
const defaultSelectedItem = defaultFolderExpansions.slice(0, 1);
25-
const [focusedItem, setFocusedItem] = useSessionStorage(`FileFocused/${instanceId || clusterId}` as 'FileFocused/{instanceId}', defaultFocusedItem);
26-
const [expandedItems, setExpandedItems] = useSessionStorage(`FolderOpened/${instanceId || clusterId}` as 'FolderOpened/{instanceId}', defaultFolderExpansions);
27-
const [selectedItems, setSelectedItems] = useSessionStorage(`FileSelected/${instanceId || clusterId}` as 'FileSelected/{instanceId}', defaultSelectedItem);
16+
const {
17+
rootEntries,
18+
openedEntry,
19+
setOpenedEntry,
20+
focusedItem,
21+
setFocusedItem,
22+
expandedItems,
23+
setExpandedItems,
24+
selectedItems,
25+
setSelectedItems,
26+
} = useEditorView();
2827

2928
const { items, rootId } = useMemo(() => buildItems(rootEntries), [rootEntries]);
3029

31-
useEffect(() => {
30+
useEffect(function setOpenedEntryFromFocusedItem() {
3231
if (openedEntry?.path !== focusedItem && focusedItem) {
3332
const item = items[focusedItem];
3433
const entry = item?.data as DirectoryEntry | FileEntry | undefined;
@@ -38,7 +37,6 @@ export function ApplicationsSidebar() {
3837
}
3938
}, [openedEntry, focusedItem, items]);
4039

41-
// TODO: consistently drive file and folder selection through context (particularly during creation and deletion)
4240
// TODO: onRenameItem f2 handling
4341
// TODO: on drag item from one folder to another
4442
// TODO: Split all this logic up into smaller files, right?

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -288,14 +288,6 @@ export function TextEditorView() {
288288

289289
<div className="grow"></div>
290290

291-
{!restrictPackageModification && canManageBrowseInstance && <Button
292-
variant="destructiveGhost"
293-
className="rounded-none"
294-
onClick={onDeleteClick}
295-
>
296-
<TrashIcon />
297-
<span className="hidden xl:inline-block"><u>D</u>elete</span>
298-
</Button>}
299291

300292
{!isDirectory(openedEntry) && !openedEntry.package && canManageBrowseInstance && <Button
301293
variant="ghost"
@@ -311,6 +303,14 @@ export function TextEditorView() {
311303
<span className="hidden xl:inline-block">Revert Changes</span>
312304
</Button>}
313305

306+
{!restrictPackageModification && canManageBrowseInstance && <Button
307+
variant="destructiveGhost"
308+
className="rounded-none"
309+
onClick={onDeleteClick}
310+
>
311+
<TrashIcon />
312+
<span className="hidden xl:inline-block"><u>D</u>elete</span>
313+
</Button>}
314314
</div>
315315

316316
<AddDirectoryOrFileModal

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { SetComponentFileRequest } from '@/features/instance/operations/mutations/updateComponentFile';
22
import { createContext } from 'react';
3+
import { TreeItemIndex } from 'react-complex-tree/src/types';
34
import { DirectoryEntry } from './directoryEntry';
45
import { FileEntry } from './fileEntry';
56

@@ -13,6 +14,13 @@ export type EditorViewContextValue = {
1314
openedEntryContents: string | null;
1415
setOpenedEntryContents: (contents: string | null) => void;
1516

17+
focusedItem: TreeItemIndex | undefined,
18+
setFocusedItem: (entry: TreeItemIndex | undefined | ((prevState: TreeItemIndex | undefined) => TreeItemIndex | undefined)) => void,
19+
expandedItems: TreeItemIndex[],
20+
setExpandedItems: (entries: TreeItemIndex[] | ((prevState: TreeItemIndex[]) => TreeItemIndex[])) => void,
21+
selectedItems: TreeItemIndex[],
22+
setSelectedItems: (entries: TreeItemIndex[] | ((prevState: TreeItemIndex[]) => TreeItemIndex[])) => void,
23+
1624
saveFile: (data: SetComponentFileRequest, filePath: string) => void;
1725
isSavingFile: boolean;
1826
};

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import {
99
APIFileEntry,
1010
getComponentsQueryOptions,
1111
} from '@/features/instance/operations/queries/getComponents';
12+
import { useSessionStorage } from '@/hooks/useSessionStorage';
1213
import { transformNodes } from '@/lib/arrays/transformNodes';
1314
import { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
1415
import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react';
16+
import { TreeItemIndex } from 'react-complex-tree/src/types';
1517
import { DirectoryEntry } from './directoryEntry';
1618
import { EditorViewContext, EditorViewContextValue } from './EditorViewContext';
1719
import { FileEntry } from './fileEntry';
@@ -58,9 +60,15 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
5860
} satisfies DirectoryEntry | FileEntry;
5961
},
6062
);
61-
6263
}, [apiComponents]);
6364

65+
const defaultFolderExpansions = rootEntries.filter(rootEntry => !rootEntry.package).map<TreeItemIndex>(rootEntry => rootEntry.name);
66+
const defaultFocusedItem = defaultFolderExpansions[0] as TreeItemIndex | undefined;
67+
const defaultSelectedItem = defaultFolderExpansions.slice(0, 1);
68+
const [focusedItem, setFocusedItem] = useSessionStorage(`FileFocused/${instanceParams.entityId}` as 'FileFocused/{entityId}', defaultFocusedItem);
69+
const [expandedItems, setExpandedItems] = useSessionStorage(`FolderOpened/${instanceParams.entityId}` as 'FolderOpened/{entityId}', defaultFolderExpansions);
70+
const [selectedItems, setSelectedItems] = useSessionStorage(`FileSelected/${instanceParams.entityId}` as 'FileSelected/{entityId}', defaultSelectedItem);
71+
6472
/*
6573
Load the selected file contents.
6674
*/
@@ -95,7 +103,7 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
95103
(data: SetComponentFileRequest, filePath: string) => {
96104
saveComponentFile(data, {
97105
onSuccess: () => {
98-
if (openedEntry?.path === filePath && data.payload) {
106+
if (openedEntry?.path === filePath && data.payload !== undefined) {
99107
setOpenedEntryContents(data.payload);
100108
}
101109
},
@@ -116,6 +124,13 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
116124
rootEntries,
117125
reloadRootEntries,
118126

127+
focusedItem,
128+
setFocusedItem,
129+
expandedItems,
130+
setExpandedItems,
131+
selectedItems,
132+
setSelectedItems,
133+
119134
openedEntry,
120135
setOpenedEntry,
121136

@@ -126,6 +141,25 @@ export function EditorViewProvider({ children }: PropsWithChildren) {
126141
isSavingFile,
127142

128143
};
129-
}, [rootEntries, openedEntry, openedEntryContents, isSavingFile]);
144+
}, [
145+
rootEntries,
146+
reloadRootEntries,
147+
148+
focusedItem,
149+
setFocusedItem,
150+
expandedItems,
151+
setExpandedItems,
152+
selectedItems,
153+
setSelectedItems,
154+
155+
openedEntry,
156+
setOpenedEntry,
157+
158+
openedEntryContents,
159+
setOpenedEntryContents,
160+
161+
saveFile,
162+
isSavingFile,
163+
]);
130164
return <EditorViewContext.Provider value={value}>{children}</EditorViewContext.Provider>;
131165
}

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useInstanceClientIdParams } from '@/config/useInstanceClient';
1818
import { isDirectory } from '@/features/instance/applications/context/isDirectory';
1919
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
2020
import { useUpdateComponentFile } from '@/features/instance/operations/mutations/updateComponentFile';
21+
import { excludeFalsy } from '@/lib/arrays/excludeFalsy';
2122
import { zodResolver } from '@hookform/resolvers/zod';
2223
import { Ban, Plus } from 'lucide-react';
2324
import { useCallback } from 'react';
@@ -33,7 +34,7 @@ export function AddDirectoryOrFileModal({
3334
readonly hideModal: () => void;
3435
readonly type: 'directory' | 'file';
3536
}) {
36-
const { openedEntry, reloadRootEntries } = useEditorView();
37+
const { openedEntry, reloadRootEntries, setFocusedItem, setSelectedItems, setExpandedItems } = useEditorView();
3738
const instanceParams = useInstanceClientIdParams();
3839
const { mutate: addFolderFile, isPending } = useUpdateComponentFile();
3940
const NewFileFolderSchema = z.object({
@@ -59,14 +60,14 @@ export function AddDirectoryOrFileModal({
5960
return;
6061
}
6162
const splitPath = openedEntry.path.split('/');
62-
const intoPath = (
63+
const filePath = (
6364
isDirectory(openedEntry)
6465
? splitPath.slice(1)
6566
: splitPath.slice(1, -1)
6667
).join('/');
6768
addFolderFile(
6869
{
69-
file: `${intoPath}/${data.name}`,
70+
file: `${filePath}/${data.name}`,
7071
project: openedEntry.project,
7172
payload: type === 'directory' ? undefined : '',
7273
...instanceParams,
@@ -76,10 +77,16 @@ export function AddDirectoryOrFileModal({
7677
reloadRootEntries();
7778
hideModal();
7879
form.reset();
80+
const treeId = [openedEntry.project, filePath, data.name].filter(excludeFalsy).join('/');
81+
setFocusedItem(treeId);
82+
setSelectedItems([treeId]);
83+
if (type === 'directory') {
84+
setExpandedItems(expandedItems => [...expandedItems, treeId]);
85+
}
7986
},
8087
},
8188
);
82-
}, [addFolderFile, instanceParams, type, openedEntry, reloadRootEntries]);
89+
}, [addFolderFile, setFocusedItem, setSelectedItems, setExpandedItems, instanceParams, type, openedEntry, reloadRootEntries]);
8390

8491
const onCancelClick = useCallback(() => {
8592
hideModal();

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ export function DeleteDirectoryOrFileModal({
1515
readonly setIsModalOpen: (value: boolean) => void;
1616
}) {
1717
const instanceParams = useInstanceClientIdParams();
18-
const { openedEntry, reloadRootEntries, setOpenedEntry } = useEditorView();
19-
const isFolderSelected = isDirectory(openedEntry);
18+
const { openedEntry, reloadRootEntries, setFocusedItem, setSelectedItems } = useEditorView();
19+
const isDirectorySelected = isDirectory(openedEntry);
2020
const isPackageSelected = !!openedEntry?.package;
21-
const thing = isPackageSelected ? 'Package' : isFolderSelected ? 'Folder' : 'File';
21+
const thing = isPackageSelected ? 'Package' : isDirectorySelected ? 'Directory' : 'File';
2222
const { mutate: deleteFolderFile, isPending, isSuccess } = useDropComponent();
2323

2424
const handleDeleteFolderOrFile = useCallback(() => {
@@ -37,7 +37,9 @@ export function DeleteDirectoryOrFileModal({
3737
{
3838
onSuccess: () => {
3939
setIsModalOpen(false);
40-
setOpenedEntry(null);
40+
const itemToFocus = !openedEntry.package && openedEntry.path.split('/').slice(0, -1).join('/');
41+
setFocusedItem(itemToFocus || undefined);
42+
setSelectedItems(itemToFocus ? [itemToFocus] : []);
4143
reloadRootEntries();
4244
},
4345
},
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export interface SessionStorageKeys {
2-
'FileFocused/{instanceId}': true;
3-
'FolderOpened/{instanceId}': true;
4-
'FileSelected/{instanceId}': true;
2+
'FileFocused/{entityId}': true;
3+
'FolderOpened/{entityId}': true;
4+
'FileSelected/{entityId}': true;
55
'ColumnDisplayed/{database}/{table}': true;
66
}

0 commit comments

Comments
 (0)