Skip to content

Commit 392234d

Browse files
Added reveal functionality for active tab in file explorer and enhanced studio shortcuts (#491)
1 parent ac067af commit 392234d

12 files changed

Lines changed: 772 additions & 36 deletions

File tree

src/main/frontend/app/components/file-structure/editor-file-structure.tsx

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React, { type JSX, useCallback, useEffect, useRef, useState } from 'react'
1+
import React, { type JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import Search from '~/components/search/search'
33
import LoadingSpinner from '~/components/loading-spinner'
44
import FolderIcon from '../../../icons/solar/Folder.svg?react'
55
import FolderOpenIcon from '../../../icons/solar/Folder Open.svg?react'
6+
import ListDown from '../../../icons/solar/List Down.svg?react'
67
import '/styles/editor-files.css'
78
import AltArrowRightIcon from '../../../icons/solar/Alt Arrow Right.svg?react'
89
import AltArrowDownIcon from '../../../icons/solar/Alt Arrow Down.svg?react'
@@ -11,6 +12,7 @@ import CodeFileIcon from '../../../icons/solar/Code File.svg?react'
1112
import TrashBinIcon from '../../../icons/solar/Trash Bin.svg?react'
1213
import Pen from '../../../icons/solar/Pen.svg?react'
1314
import { useShortcut } from '~/hooks/use-shortcut'
15+
import { getAncestorIds, isVisibleInTree, selectAndReveal, toTreeItemId } from './tree-utilities'
1416
import type { ContextMenuState } from './use-file-tree-context-menu'
1517

1618
import {
@@ -51,16 +53,38 @@ export default function EditorFileStructure() {
5153
const getTab = useEditorTabStore((state) => state.getTab)
5254
const removeTab = useEditorTabStore((state) => state.removeTab)
5355
const removeTabAndSelectFallback = useEditorTabStore((state) => state.removeTabAndSelectFallback)
56+
const activeTabFilePath = useEditorTabStore((state) => state.activeTabFilePath)
5457

5558
const [dataProvider, setDataProvider] = useState<EditorFilesDataProvider | null>(null)
5659
const [selectedItemId, setSelectedItemId] = useState<TreeItemIndex | null>(null)
60+
const [rootPath, setRootPath] = useState<string | null>(null)
5761

5862
const expandedItemsRef = useRef(editorExpandedItems)
5963

6064
useEffect(() => {
6165
expandedItemsRef.current = editorExpandedItems
6266
}, [editorExpandedItems])
6367

68+
useEffect(() => {
69+
if (!dataProvider) {
70+
setRootPath(null)
71+
return
72+
}
73+
void dataProvider.getTreeItem('root').then((root) => {
74+
if (root) setRootPath((root.data as FileNode).path)
75+
})
76+
}, [dataProvider])
77+
78+
const activeTabItemId = useMemo(
79+
() => (rootPath && activeTabFilePath ? toTreeItemId(activeTabFilePath, rootPath) : null),
80+
[rootPath, activeTabFilePath],
81+
)
82+
83+
const isActiveItemVisible = useMemo(
84+
() => isVisibleInTree(activeTabItemId, editorExpandedItems),
85+
[activeTabItemId, editorExpandedItems],
86+
)
87+
6488
const onAfterRename = useCallback(
6589
(oldPath: string, newName: string) => {
6690
const tab = getTab(oldPath)
@@ -126,12 +150,31 @@ export default function EditorFileStructure() {
126150
[buildContextForItem],
127151
)
128152

153+
const revealActiveFile = useCallback(async () => {
154+
if (!dataProvider || !activeTabFilePath || !rootPath || !tree.current) return
155+
156+
const itemId = toTreeItemId(activeTabFilePath, rootPath)
157+
158+
for (const ancestorId of getAncestorIds(itemId)) {
159+
await dataProvider.loadDirectory(ancestorId)
160+
tree.current.expandItem(ancestorId)
161+
}
162+
163+
selectAndReveal(tree.current, itemId)
164+
}, [dataProvider, activeTabFilePath, rootPath])
165+
129166
useShortcut({
130167
'explorer.new-file': () => triggerExplorerAction(editorContextMenu.handleNewFile, false),
131168
'explorer.new-folder': () => triggerExplorerAction(editorContextMenu.handleNewFolder, false),
132-
'explorer.rename': () => triggerExplorerAction(editorContextMenu.handleRename, true),
133-
'explorer.delete': () => triggerExplorerAction(editorContextMenu.handleDelete, true),
134-
'explorer.delete-mac': () => triggerExplorerAction(editorContextMenu.handleDelete, true),
169+
'explorer.rename': () => {
170+
if (!selectedItemId) return false
171+
triggerExplorerAction(editorContextMenu.handleRename, true)
172+
},
173+
'explorer.delete': () => {
174+
if (!selectedItemId) return false
175+
triggerExplorerAction(editorContextMenu.handleDelete, true)
176+
},
177+
'explorer.reveal': () => void revealActiveFile(),
135178
})
136179

137180
useEffect(() => {
@@ -421,6 +464,14 @@ export default function EditorFileStructure() {
421464
<div className="border-border flex items-center justify-between border-b px-2 py-1">
422465
<span className="text-foreground/50 text-xs font-semibold tracking-wider uppercase">Explorer</span>
423466
<div className="flex items-center gap-0.5">
467+
<button
468+
className={`${toolbarBtnClass} ${!activeTabFilePath || isActiveItemVisible ? 'cursor-not-allowed opacity-40' : ''}`}
469+
title="Open File Tree to Active Tab"
470+
disabled={!activeTabFilePath || isActiveItemVisible}
471+
onClick={() => void revealActiveFile()}
472+
>
473+
<ListDown className="fill-foreground h-5 w-5" />
474+
</button>
424475
<button
425476
className={toolbarBtnClass}
426477
title="New File"
@@ -458,6 +509,9 @@ export default function EditorFileStructure() {
458509
}}
459510
onCollapseItem={(item) => {
460511
removeEditorExpandedItem(String(item.index))
512+
setSelectedItemId((previous) =>
513+
previous && String(previous).startsWith(`${String(item.index)}/`) ? null : previous,
514+
)
461515
}}
462516
getItemTitle={getItemTitle}
463517
dataProvider={dataProvider}

0 commit comments

Comments
 (0)