@bendyline/docblocks-react 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/dist/ExportDialog-VYATD5UC.js +9 -0
- package/dist/{ExportToolbarControls-D2YDWOXW.js → ExportToolbarControls-5HUE3LSX.js} +206 -42
- package/dist/{GitToolbarControl-LMIR4R7I.js → GitToolbarControl-FW3BZFXV.js} +62 -44
- package/dist/{GitUI-KGF5MSNV.js → GitUI-GMH3PAYL.js} +225 -141
- package/dist/{LazyEditorShell-EF2BWRXU.js → LazyEditorShell-RF54B5UD.js} +0 -1
- package/dist/ShareDialog-PAMOQQFB.js +212 -0
- package/dist/chunk-3CZ27FOA.js +359 -0
- package/dist/chunk-3QSZY74C.js +255 -0
- package/{src/Export/export-destination.ts → dist/chunk-BI7NVU6T.js} +16 -15
- package/dist/{chunk-VHDP4IPL.js → chunk-BPGEBGAN.js} +0 -1
- package/dist/{chunk-DD4VFJQI.js → chunk-EBWYGTN7.js} +2 -1
- package/dist/{chunk-ZI56BNB7.js → chunk-GHQ4X7KM.js} +27 -7
- package/dist/{chunk-5A4KEWKT.js → chunk-HFYPTTCH.js} +0 -1
- package/dist/chunk-LG6HAWCK.js +109 -0
- package/dist/chunk-TFMO7EVO.js +88 -0
- package/dist/{chunk-4XXSWN3T.js → chunk-YBEYTVU2.js} +0 -1
- package/dist/editor.d.ts +5 -4
- package/dist/editor.js +8 -70
- package/dist/export/index.d.ts +24 -8
- package/dist/export/index.js +10 -6
- package/dist/index.d.ts +355 -21
- package/dist/index.js +6419 -21
- package/dist/run-export-Z5BVFLIR.js +9 -0
- package/dist/settings/index.d.ts +9 -1
- package/dist/settings/index.js +6 -5
- package/package.json +18 -13
- package/src/styles/docblocks.css +985 -35
- package/dist/AppMenu/AppMenu.d.ts +0 -53
- package/dist/AppMenu/AppMenu.d.ts.map +0 -1
- package/dist/AppMenu/AppMenu.js +0 -67
- package/dist/AppMenu/AppMenu.js.map +0 -1
- package/dist/AppMenu/index.d.ts +0 -3
- package/dist/AppMenu/index.d.ts.map +0 -1
- package/dist/AppMenu/index.js +0 -2
- package/dist/AppMenu/index.js.map +0 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts +0 -49
- package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +0 -1
- package/dist/DocBlocksShell/DocBlocksShell.js +0 -2730
- package/dist/DocBlocksShell/DocBlocksShell.js.map +0 -1
- package/dist/DocBlocksShell/LazyEditorShell.d.ts +0 -3
- package/dist/DocBlocksShell/LazyEditorShell.d.ts.map +0 -1
- package/dist/DocBlocksShell/LazyEditorShell.js +0 -3
- package/dist/DocBlocksShell/LazyEditorShell.js.map +0 -1
- package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts +0 -13
- package/dist/DocBlocksShell/UpdateAvailableNotice.d.ts.map +0 -1
- package/dist/DocBlocksShell/UpdateAvailableNotice.js +0 -16
- package/dist/DocBlocksShell/UpdateAvailableNotice.js.map +0 -1
- package/dist/DocBlocksShell/document-title.d.ts +0 -3
- package/dist/DocBlocksShell/document-title.d.ts.map +0 -1
- package/dist/DocBlocksShell/document-title.js +0 -17
- package/dist/DocBlocksShell/document-title.js.map +0 -1
- package/dist/DocBlocksShell/index.d.ts +0 -3
- package/dist/DocBlocksShell/index.d.ts.map +0 -1
- package/dist/DocBlocksShell/index.js +0 -2
- package/dist/DocBlocksShell/index.js.map +0 -1
- package/dist/DocBlocksShell/workspace-authority-barrier.d.ts +0 -14
- package/dist/DocBlocksShell/workspace-authority-barrier.d.ts.map +0 -1
- package/dist/DocBlocksShell/workspace-authority-barrier.js +0 -23
- package/dist/DocBlocksShell/workspace-authority-barrier.js.map +0 -1
- package/dist/Export/DeferredExportToolbarControls.d.ts +0 -5
- package/dist/Export/DeferredExportToolbarControls.d.ts.map +0 -1
- package/dist/Export/DeferredExportToolbarControls.js +0 -10
- package/dist/Export/DeferredExportToolbarControls.js.map +0 -1
- package/dist/Export/ExportDialog.d.ts +0 -25
- package/dist/Export/ExportDialog.d.ts.map +0 -1
- package/dist/Export/ExportDialog.js +0 -160
- package/dist/Export/ExportDialog.js.map +0 -1
- package/dist/Export/ExportToolbarControls.d.ts +0 -37
- package/dist/Export/ExportToolbarControls.d.ts.map +0 -1
- package/dist/Export/ExportToolbarControls.js +0 -285
- package/dist/Export/ExportToolbarControls.js.map +0 -1
- package/dist/Export/export-destination.d.ts +0 -3
- package/dist/Export/export-destination.d.ts.map +0 -1
- package/dist/Export/export-destination.js +0 -32
- package/dist/Export/export-destination.js.map +0 -1
- package/dist/Export/export-options.d.ts +0 -53
- package/dist/Export/export-options.d.ts.map +0 -1
- package/dist/Export/export-options.js +0 -49
- package/dist/Export/export-options.js.map +0 -1
- package/dist/Export/index.d.ts +0 -10
- package/dist/Export/index.d.ts.map +0 -1
- package/dist/Export/index.js +0 -6
- package/dist/Export/index.js.map +0 -1
- package/dist/Export/public-api.d.ts +0 -12
- package/dist/Export/public-api.d.ts.map +0 -1
- package/dist/Export/public-api.js +0 -9
- package/dist/Export/public-api.js.map +0 -1
- package/dist/Export/run-export.d.ts +0 -10
- package/dist/Export/run-export.d.ts.map +0 -1
- package/dist/Export/run-export.js +0 -313
- package/dist/Export/run-export.js.map +0 -1
- package/dist/Export/transform-summaries.d.ts +0 -7
- package/dist/Export/transform-summaries.d.ts.map +0 -1
- package/dist/Export/transform-summaries.js +0 -6
- package/dist/Export/transform-summaries.js.map +0 -1
- package/dist/ExportDialog-MEUISWZX.js +0 -9
- package/dist/ExportDialog-MEUISWZX.js.map +0 -1
- package/dist/ExportToolbarControls-D2YDWOXW.js.map +0 -1
- package/dist/FileExplorer/FileExplorer.d.ts +0 -43
- package/dist/FileExplorer/FileExplorer.d.ts.map +0 -1
- package/dist/FileExplorer/FileExplorer.js +0 -315
- package/dist/FileExplorer/FileExplorer.js.map +0 -1
- package/dist/FileExplorer/FileTreeNode.d.ts +0 -39
- package/dist/FileExplorer/FileTreeNode.d.ts.map +0 -1
- package/dist/FileExplorer/FileTreeNode.js +0 -131
- package/dist/FileExplorer/FileTreeNode.js.map +0 -1
- package/dist/FileExplorer/index.d.ts +0 -6
- package/dist/FileExplorer/index.d.ts.map +0 -1
- package/dist/FileExplorer/index.js +0 -4
- package/dist/FileExplorer/index.js.map +0 -1
- package/dist/FileExplorer/useFileTree.d.ts +0 -37
- package/dist/FileExplorer/useFileTree.d.ts.map +0 -1
- package/dist/FileExplorer/useFileTree.js +0 -284
- package/dist/FileExplorer/useFileTree.js.map +0 -1
- package/dist/Git/GitAuthGuidanceDialog.d.ts +0 -18
- package/dist/Git/GitAuthGuidanceDialog.d.ts.map +0 -1
- package/dist/Git/GitAuthGuidanceDialog.js +0 -27
- package/dist/Git/GitAuthGuidanceDialog.js.map +0 -1
- package/dist/Git/GitBranchesDialog.d.ts +0 -13
- package/dist/Git/GitBranchesDialog.d.ts.map +0 -1
- package/dist/Git/GitBranchesDialog.js +0 -59
- package/dist/Git/GitBranchesDialog.js.map +0 -1
- package/dist/Git/GitCloneDialog.d.ts +0 -14
- package/dist/Git/GitCloneDialog.d.ts.map +0 -1
- package/dist/Git/GitCloneDialog.js +0 -79
- package/dist/Git/GitCloneDialog.js.map +0 -1
- package/dist/Git/GitCommitDialog.d.ts +0 -12
- package/dist/Git/GitCommitDialog.d.ts.map +0 -1
- package/dist/Git/GitCommitDialog.js +0 -38
- package/dist/Git/GitCommitDialog.js.map +0 -1
- package/dist/Git/GitConflictsDialog.d.ts +0 -12
- package/dist/Git/GitConflictsDialog.d.ts.map +0 -1
- package/dist/Git/GitConflictsDialog.js +0 -17
- package/dist/Git/GitConflictsDialog.js.map +0 -1
- package/dist/Git/GitContext.d.ts +0 -89
- package/dist/Git/GitContext.d.ts.map +0 -1
- package/dist/Git/GitContext.js +0 -15
- package/dist/Git/GitContext.js.map +0 -1
- package/dist/Git/GitDiffDialog.d.ts +0 -20
- package/dist/Git/GitDiffDialog.d.ts.map +0 -1
- package/dist/Git/GitDiffDialog.js +0 -100
- package/dist/Git/GitDiffDialog.js.map +0 -1
- package/dist/Git/GitDiffView.d.ts +0 -23
- package/dist/Git/GitDiffView.d.ts.map +0 -1
- package/dist/Git/GitDiffView.js +0 -77
- package/dist/Git/GitDiffView.js.map +0 -1
- package/dist/Git/GitHistoryDialog.d.ts +0 -15
- package/dist/Git/GitHistoryDialog.d.ts.map +0 -1
- package/dist/Git/GitHistoryDialog.js +0 -107
- package/dist/Git/GitHistoryDialog.js.map +0 -1
- package/dist/Git/GitStatusBar.d.ts +0 -8
- package/dist/Git/GitStatusBar.d.ts.map +0 -1
- package/dist/Git/GitStatusBar.js +0 -49
- package/dist/Git/GitStatusBar.js.map +0 -1
- package/dist/Git/GitToolbarControl.d.ts +0 -15
- package/dist/Git/GitToolbarControl.d.ts.map +0 -1
- package/dist/Git/GitToolbarControl.js +0 -51
- package/dist/Git/GitToolbarControl.js.map +0 -1
- package/dist/Git/GitUI.d.ts +0 -14
- package/dist/Git/GitUI.d.ts.map +0 -1
- package/dist/Git/GitUI.js +0 -21
- package/dist/Git/GitUI.js.map +0 -1
- package/dist/Git/commit-selection.d.ts +0 -22
- package/dist/Git/commit-selection.d.ts.map +0 -1
- package/dist/Git/commit-selection.js +0 -47
- package/dist/Git/commit-selection.js.map +0 -1
- package/dist/Git/git-clone-utils.d.ts +0 -8
- package/dist/Git/git-clone-utils.d.ts.map +0 -1
- package/dist/Git/git-clone-utils.js +0 -26
- package/dist/Git/git-clone-utils.js.map +0 -1
- package/dist/Git/git-status.d.ts +0 -24
- package/dist/Git/git-status.d.ts.map +0 -1
- package/dist/Git/git-status.js +0 -105
- package/dist/Git/git-status.js.map +0 -1
- package/dist/Git/useGit.d.ts +0 -15
- package/dist/Git/useGit.d.ts.map +0 -1
- package/dist/Git/useGit.js +0 -206
- package/dist/Git/useGit.js.map +0 -1
- package/dist/Git/useGitStatus.d.ts +0 -17
- package/dist/Git/useGitStatus.d.ts.map +0 -1
- package/dist/Git/useGitStatus.js +0 -63
- package/dist/Git/useGitStatus.js.map +0 -1
- package/dist/GitToolbarControl-LMIR4R7I.js.map +0 -1
- package/dist/GitUI-KGF5MSNV.js.map +0 -1
- package/dist/LazyEditorShell-EF2BWRXU.js.map +0 -1
- package/dist/Settings/Settings.d.ts +0 -23
- package/dist/Settings/Settings.d.ts.map +0 -1
- package/dist/Settings/Settings.js +0 -22
- package/dist/Settings/Settings.js.map +0 -1
- package/dist/Settings/public-api.d.ts +0 -3
- package/dist/Settings/public-api.d.ts.map +0 -1
- package/dist/Settings/public-api.js +0 -2
- package/dist/Settings/public-api.js.map +0 -1
- package/dist/WorkspacePicker/WorkspacePicker.d.ts +0 -22
- package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +0 -1
- package/dist/WorkspacePicker/WorkspacePicker.js +0 -74
- package/dist/WorkspacePicker/WorkspacePicker.js.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +0 -11
- package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsButton.js +0 -27
- package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts +0 -20
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts.map +0 -1
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.js +0 -36
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.js.map +0 -1
- package/dist/WorkspacePicker/index.d.ts +0 -3
- package/dist/WorkspacePicker/index.d.ts.map +0 -1
- package/dist/WorkspacePicker/index.js +0 -2
- package/dist/WorkspacePicker/index.js.map +0 -1
- package/dist/chunk-3SAXDU2U.js +0 -157
- package/dist/chunk-3SAXDU2U.js.map +0 -1
- package/dist/chunk-4XXSWN3T.js.map +0 -1
- package/dist/chunk-5A4KEWKT.js.map +0 -1
- package/dist/chunk-AOVTHZU7.js +0 -321
- package/dist/chunk-AOVTHZU7.js.map +0 -1
- package/dist/chunk-DD4VFJQI.js.map +0 -1
- package/dist/chunk-GSNZSCIK.js +0 -55
- package/dist/chunk-GSNZSCIK.js.map +0 -1
- package/dist/chunk-VHDP4IPL.js.map +0 -1
- package/dist/chunk-ZI56BNB7.js.map +0 -1
- package/dist/components/Dialog.d.ts +0 -22
- package/dist/components/Dialog.d.ts.map +0 -1
- package/dist/components/Dialog.js +0 -33
- package/dist/components/Dialog.js.map +0 -1
- package/dist/editor.d.ts.map +0 -1
- package/dist/editor.js.map +0 -1
- package/dist/export/index.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -3
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/useDocumentSession.d.ts +0 -16
- package/dist/hooks/useDocumentSession.d.ts.map +0 -1
- package/dist/hooks/useDocumentSession.js +0 -18
- package/dist/hooks/useDocumentSession.js.map +0 -1
- package/dist/icons.d.ts +0 -12
- package/dist/icons.d.ts.map +0 -1
- package/dist/icons.js +0 -28
- package/dist/icons.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/preferences/theme.d.ts +0 -18
- package/dist/preferences/theme.d.ts.map +0 -1
- package/dist/preferences/theme.js +0 -63
- package/dist/preferences/theme.js.map +0 -1
- package/dist/preferences/versioning.d.ts +0 -27
- package/dist/preferences/versioning.d.ts.map +0 -1
- package/dist/preferences/versioning.js +0 -62
- package/dist/preferences/versioning.js.map +0 -1
- package/dist/provider-lease.d.ts +0 -10
- package/dist/provider-lease.d.ts.map +0 -1
- package/dist/provider-lease.js +0 -42
- package/dist/provider-lease.js.map +0 -1
- package/dist/run-export-ICTZITXC.js +0 -10
- package/dist/run-export-ICTZITXC.js.map +0 -1
- package/dist/settings/index.js.map +0 -1
- package/src/AppMenu/AppMenu.tsx +0 -334
- package/src/AppMenu/index.ts +0 -2
- package/src/DocBlocksShell/DocBlocksShell.tsx +0 -3398
- package/src/DocBlocksShell/LazyEditorShell.ts +0 -2
- package/src/DocBlocksShell/UpdateAvailableNotice.tsx +0 -60
- package/src/DocBlocksShell/document-title.ts +0 -19
- package/src/DocBlocksShell/index.ts +0 -2
- package/src/DocBlocksShell/workspace-authority-barrier.ts +0 -26
- package/src/Export/DeferredExportToolbarControls.tsx +0 -23
- package/src/Export/ExportDialog.tsx +0 -437
- package/src/Export/ExportToolbarControls.tsx +0 -511
- package/src/Export/export-options.ts +0 -99
- package/src/Export/index.ts +0 -19
- package/src/Export/public-api.ts +0 -17
- package/src/Export/run-export.ts +0 -374
- package/src/Export/transform-summaries.ts +0 -14
- package/src/FileExplorer/FileExplorer.tsx +0 -528
- package/src/FileExplorer/FileTreeNode.tsx +0 -306
- package/src/FileExplorer/index.ts +0 -5
- package/src/FileExplorer/useFileTree.ts +0 -352
- package/src/Git/GitAuthGuidanceDialog.tsx +0 -84
- package/src/Git/GitBranchesDialog.tsx +0 -144
- package/src/Git/GitCloneDialog.tsx +0 -200
- package/src/Git/GitCommitDialog.tsx +0 -119
- package/src/Git/GitConflictsDialog.tsx +0 -62
- package/src/Git/GitContext.tsx +0 -88
- package/src/Git/GitDiffDialog.tsx +0 -147
- package/src/Git/GitDiffView.tsx +0 -105
- package/src/Git/GitHistoryDialog.tsx +0 -195
- package/src/Git/GitStatusBar.tsx +0 -157
- package/src/Git/GitToolbarControl.tsx +0 -123
- package/src/Git/GitUI.tsx +0 -68
- package/src/Git/commit-selection.ts +0 -63
- package/src/Git/git-clone-utils.ts +0 -23
- package/src/Git/git-status.ts +0 -113
- package/src/Git/useGit.ts +0 -279
- package/src/Git/useGitStatus.ts +0 -78
- package/src/Settings/Settings.tsx +0 -118
- package/src/Settings/public-api.ts +0 -8
- package/src/WorkspacePicker/WorkspacePicker.tsx +0 -191
- package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +0 -88
- package/src/WorkspacePicker/WorkspaceSettingsDialog.tsx +0 -121
- package/src/WorkspacePicker/index.ts +0 -2
- package/src/components/Dialog.tsx +0 -81
- package/src/editor.ts +0 -72
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useDocumentSession.ts +0 -37
- package/src/icons.tsx +0 -48
- package/src/index.ts +0 -61
- package/src/preferences/theme.ts +0 -67
- package/src/preferences/versioning.ts +0 -69
- package/src/provider-lease.ts +0 -52
- package/src/types/pwa-dom.d.ts +0 -31
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileTreeNode — recursive tree node for the file explorer.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useCallback, useState, useRef, useEffect, useLayoutEffect } from 'react';
|
|
6
|
-
import { createPortal } from 'react-dom';
|
|
7
|
-
import type { FileSystemEntry } from '@bendyline/docblocks/filesystem';
|
|
8
|
-
import { MoreIcon } from '../icons.js';
|
|
9
|
-
|
|
10
|
-
/** Git decoration for a row — precomputed by FileExplorer so this node stays context-free. */
|
|
11
|
-
export interface FileTreeNodeBadge {
|
|
12
|
-
kind: string;
|
|
13
|
-
glyph: string;
|
|
14
|
-
label: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Git context-menu actions, prebound to this entry's path. */
|
|
18
|
-
export interface FileTreeNodeGitActions {
|
|
19
|
-
viewChanges?: () => void;
|
|
20
|
-
fileHistory?: () => void;
|
|
21
|
-
openOnRemote?: () => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface FileTreeNodeProps {
|
|
25
|
-
entry: FileSystemEntry;
|
|
26
|
-
depth: number;
|
|
27
|
-
expanded: boolean;
|
|
28
|
-
selected: boolean;
|
|
29
|
-
badge?: FileTreeNodeBadge;
|
|
30
|
-
gitActions?: FileTreeNodeGitActions;
|
|
31
|
-
children?: FileSystemEntry[];
|
|
32
|
-
onToggle: (path: string) => void;
|
|
33
|
-
onSelect: (path: string) => void;
|
|
34
|
-
onDelete: (path: string, kind: 'file' | 'directory') => Promise<void>;
|
|
35
|
-
onRename: (oldPath: string, newPath: string, kind: 'file' | 'directory') => Promise<void>;
|
|
36
|
-
draggable?: boolean;
|
|
37
|
-
dragging?: boolean;
|
|
38
|
-
dropTarget?: boolean;
|
|
39
|
-
onDragStart?: (event: React.DragEvent, entry: FileSystemEntry) => void;
|
|
40
|
-
onDragEnd?: () => void;
|
|
41
|
-
onDragOverEntry?: (event: React.DragEvent, entry: FileSystemEntry) => void;
|
|
42
|
-
onDropEntry?: (event: React.DragEvent, entry: FileSystemEntry) => void;
|
|
43
|
-
renderChildren?: (dirPath: string) => React.ReactNode;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function FileTreeNode({
|
|
47
|
-
entry,
|
|
48
|
-
depth,
|
|
49
|
-
expanded,
|
|
50
|
-
selected,
|
|
51
|
-
badge,
|
|
52
|
-
gitActions,
|
|
53
|
-
onToggle,
|
|
54
|
-
onSelect,
|
|
55
|
-
onDelete,
|
|
56
|
-
onRename,
|
|
57
|
-
draggable = false,
|
|
58
|
-
dragging = false,
|
|
59
|
-
dropTarget = false,
|
|
60
|
-
onDragStart,
|
|
61
|
-
onDragEnd,
|
|
62
|
-
onDragOverEntry,
|
|
63
|
-
onDropEntry,
|
|
64
|
-
renderChildren,
|
|
65
|
-
}: FileTreeNodeProps) {
|
|
66
|
-
const [renaming, setRenaming] = useState(false);
|
|
67
|
-
const [renameValue, setRenameValue] = useState(entry.name);
|
|
68
|
-
const [showContext, setShowContext] = useState(false);
|
|
69
|
-
const [contextPos, setContextPos] = useState({ x: 0, y: 0 });
|
|
70
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
71
|
-
const contextRef = useRef<HTMLDivElement>(null);
|
|
72
|
-
const nodeRef = useRef<HTMLDivElement>(null);
|
|
73
|
-
|
|
74
|
-
const isDir = entry.kind === 'directory';
|
|
75
|
-
const icon = isDir ? (expanded ? '\u25BE' : '\u25B8') : '\u00A0\u00A0';
|
|
76
|
-
|
|
77
|
-
const handleClick = useCallback(() => {
|
|
78
|
-
if (isDir) {
|
|
79
|
-
onToggle(entry.path);
|
|
80
|
-
}
|
|
81
|
-
onSelect(entry.path);
|
|
82
|
-
}, [isDir, entry.path, onToggle, onSelect]);
|
|
83
|
-
|
|
84
|
-
const handleContextMenu = useCallback((e: React.MouseEvent) => {
|
|
85
|
-
e.preventDefault();
|
|
86
|
-
e.stopPropagation();
|
|
87
|
-
setContextPos({ x: e.clientX, y: e.clientY });
|
|
88
|
-
setShowContext(true);
|
|
89
|
-
}, []);
|
|
90
|
-
|
|
91
|
-
const handleMoreClick = useCallback((e: React.MouseEvent) => {
|
|
92
|
-
e.preventDefault();
|
|
93
|
-
e.stopPropagation();
|
|
94
|
-
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
95
|
-
setContextPos({ x: rect.right, y: rect.bottom });
|
|
96
|
-
setShowContext(true);
|
|
97
|
-
}, []);
|
|
98
|
-
|
|
99
|
-
const handleRenameStart = useCallback(() => {
|
|
100
|
-
setRenameValue(entry.name);
|
|
101
|
-
setRenaming(true);
|
|
102
|
-
setShowContext(false);
|
|
103
|
-
}, [entry.name]);
|
|
104
|
-
|
|
105
|
-
const handleRenameSubmit = useCallback(async () => {
|
|
106
|
-
if (renameValue && renameValue !== entry.name) {
|
|
107
|
-
const parentPath = entry.path.includes('/')
|
|
108
|
-
? entry.path.slice(0, entry.path.lastIndexOf('/'))
|
|
109
|
-
: '';
|
|
110
|
-
const newPath = parentPath ? `${parentPath}/${renameValue}` : renameValue;
|
|
111
|
-
await onRename(entry.path, newPath, entry.kind);
|
|
112
|
-
}
|
|
113
|
-
setRenaming(false);
|
|
114
|
-
}, [renameValue, entry.name, entry.path, entry.kind, onRename]);
|
|
115
|
-
|
|
116
|
-
const handleDeleteClick = useCallback(async () => {
|
|
117
|
-
setShowContext(false);
|
|
118
|
-
const description = entry.kind === 'directory' ? 'folder and everything inside it' : 'document';
|
|
119
|
-
if (!window.confirm(`Delete the ${description} "${entry.name}"? This cannot be undone.`)) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
await onDelete(entry.path, entry.kind);
|
|
123
|
-
}, [entry.name, entry.path, entry.kind, onDelete]);
|
|
124
|
-
|
|
125
|
-
// Close context menu on outside click or scroll
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (!showContext) return;
|
|
128
|
-
function handleClose(e: Event) {
|
|
129
|
-
if (contextRef.current && !contextRef.current.contains(e.target as Node)) {
|
|
130
|
-
setShowContext(false);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Use click (not mousedown) so menu button clicks register first
|
|
134
|
-
document.addEventListener('click', handleClose, true);
|
|
135
|
-
document.addEventListener('contextmenu', handleClose, true);
|
|
136
|
-
document.addEventListener('scroll', () => setShowContext(false), { capture: true, once: true });
|
|
137
|
-
return () => {
|
|
138
|
-
document.removeEventListener('click', handleClose, true);
|
|
139
|
-
document.removeEventListener('contextmenu', handleClose, true);
|
|
140
|
-
};
|
|
141
|
-
}, [showContext]);
|
|
142
|
-
|
|
143
|
-
// Focus input when renaming
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (renaming && inputRef.current) {
|
|
146
|
-
inputRef.current.focus();
|
|
147
|
-
inputRef.current.select();
|
|
148
|
-
}
|
|
149
|
-
}, [renaming]);
|
|
150
|
-
|
|
151
|
-
// Active documents can be revealed after asynchronous ancestor loads. Keep
|
|
152
|
-
// the selected row inside the explorer's scroll viewport once it mounts.
|
|
153
|
-
useEffect(() => {
|
|
154
|
-
if (selected) nodeRef.current?.scrollIntoView?.({ block: 'nearest' });
|
|
155
|
-
}, [selected]);
|
|
156
|
-
|
|
157
|
-
// Clamp menu inside viewport after it renders
|
|
158
|
-
useLayoutEffect(() => {
|
|
159
|
-
if (!showContext || !contextRef.current) return;
|
|
160
|
-
const menu = contextRef.current;
|
|
161
|
-
const rect = menu.getBoundingClientRect();
|
|
162
|
-
const margin = 4;
|
|
163
|
-
let left = contextPos.x;
|
|
164
|
-
let top = contextPos.y;
|
|
165
|
-
if (rect.right > window.innerWidth - margin) {
|
|
166
|
-
left = Math.max(margin, window.innerWidth - rect.width - margin);
|
|
167
|
-
}
|
|
168
|
-
if (rect.bottom > window.innerHeight - margin) {
|
|
169
|
-
top = Math.max(margin, window.innerHeight - rect.height - margin);
|
|
170
|
-
}
|
|
171
|
-
if (left !== contextPos.x || top !== contextPos.y) {
|
|
172
|
-
menu.style.left = `${left}px`;
|
|
173
|
-
menu.style.top = `${top}px`;
|
|
174
|
-
}
|
|
175
|
-
}, [showContext, contextPos]);
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<div className="db-tree-node" ref={nodeRef}>
|
|
179
|
-
<div
|
|
180
|
-
className={`db-tree-row ${selected ? 'db-tree-row--selected' : ''} ${dragging ? 'db-tree-row--dragging' : ''} ${dropTarget ? 'db-tree-row--drop-target' : ''}`}
|
|
181
|
-
style={{ paddingLeft: depth * 16 + 4 }}
|
|
182
|
-
onClick={handleClick}
|
|
183
|
-
onContextMenu={handleContextMenu}
|
|
184
|
-
draggable={draggable && !renaming}
|
|
185
|
-
onDragStart={(event) => onDragStart?.(event, entry)}
|
|
186
|
-
onDragEnd={onDragEnd}
|
|
187
|
-
onDragOver={(event) => onDragOverEntry?.(event, entry)}
|
|
188
|
-
onDrop={(event) => onDropEntry?.(event, entry)}
|
|
189
|
-
role="treeitem"
|
|
190
|
-
aria-expanded={isDir ? expanded : undefined}
|
|
191
|
-
aria-selected={selected}
|
|
192
|
-
aria-label={badge ? `${entry.name}, ${badge.label}` : undefined}
|
|
193
|
-
tabIndex={0}
|
|
194
|
-
onKeyDown={(e) => {
|
|
195
|
-
if (e.key === 'Enter') handleClick();
|
|
196
|
-
}}
|
|
197
|
-
>
|
|
198
|
-
<span className="db-tree-icon">{icon}</span>
|
|
199
|
-
{renaming ? (
|
|
200
|
-
<input
|
|
201
|
-
ref={inputRef}
|
|
202
|
-
className="db-tree-rename-input"
|
|
203
|
-
value={renameValue}
|
|
204
|
-
onChange={(e) => setRenameValue(e.target.value)}
|
|
205
|
-
onBlur={handleRenameSubmit}
|
|
206
|
-
onKeyDown={(e) => {
|
|
207
|
-
if (e.key === 'Enter') handleRenameSubmit();
|
|
208
|
-
if (e.key === 'Escape') setRenaming(false);
|
|
209
|
-
}}
|
|
210
|
-
onClick={(e) => e.stopPropagation()}
|
|
211
|
-
/>
|
|
212
|
-
) : (
|
|
213
|
-
<>
|
|
214
|
-
<span className="db-tree-label">
|
|
215
|
-
{entry.name.endsWith('.md') ? entry.name.slice(0, -3) : entry.name}
|
|
216
|
-
</span>
|
|
217
|
-
{badge && (
|
|
218
|
-
<span className={`db-git-badge db-git-badge--${badge.kind}`} aria-hidden="true">
|
|
219
|
-
{badge.glyph}
|
|
220
|
-
</span>
|
|
221
|
-
)}
|
|
222
|
-
<button
|
|
223
|
-
type="button"
|
|
224
|
-
className={`db-tree-more${showContext ? ' db-tree-more--active' : ''}`}
|
|
225
|
-
onClick={handleMoreClick}
|
|
226
|
-
onContextMenu={handleMoreClick}
|
|
227
|
-
aria-label="More actions"
|
|
228
|
-
aria-haspopup="menu"
|
|
229
|
-
aria-expanded={showContext}
|
|
230
|
-
tabIndex={-1}
|
|
231
|
-
>
|
|
232
|
-
<MoreIcon />
|
|
233
|
-
</button>
|
|
234
|
-
</>
|
|
235
|
-
)}
|
|
236
|
-
</div>
|
|
237
|
-
|
|
238
|
-
{/* Context menu — portaled so ancestor overflow:hidden doesn't clip it */}
|
|
239
|
-
{showContext &&
|
|
240
|
-
createPortal(
|
|
241
|
-
<div
|
|
242
|
-
ref={contextRef}
|
|
243
|
-
className="db-tree-context"
|
|
244
|
-
style={{ left: contextPos.x, top: contextPos.y }}
|
|
245
|
-
>
|
|
246
|
-
<button className="db-tree-context-item" onClick={handleRenameStart}>
|
|
247
|
-
Rename
|
|
248
|
-
</button>
|
|
249
|
-
{gitActions && (gitActions.viewChanges || gitActions.fileHistory) && (
|
|
250
|
-
<>
|
|
251
|
-
<div className="db-tree-context-divider" role="separator" />
|
|
252
|
-
{gitActions.viewChanges && (
|
|
253
|
-
<button
|
|
254
|
-
className="db-tree-context-item"
|
|
255
|
-
onClick={() => {
|
|
256
|
-
setShowContext(false);
|
|
257
|
-
gitActions.viewChanges?.();
|
|
258
|
-
}}
|
|
259
|
-
>
|
|
260
|
-
View changes
|
|
261
|
-
</button>
|
|
262
|
-
)}
|
|
263
|
-
{gitActions.fileHistory && (
|
|
264
|
-
<button
|
|
265
|
-
className="db-tree-context-item"
|
|
266
|
-
onClick={() => {
|
|
267
|
-
setShowContext(false);
|
|
268
|
-
gitActions.fileHistory?.();
|
|
269
|
-
}}
|
|
270
|
-
>
|
|
271
|
-
File history…
|
|
272
|
-
</button>
|
|
273
|
-
)}
|
|
274
|
-
{gitActions.openOnRemote && (
|
|
275
|
-
<button
|
|
276
|
-
className="db-tree-context-item"
|
|
277
|
-
onClick={() => {
|
|
278
|
-
setShowContext(false);
|
|
279
|
-
gitActions.openOnRemote?.();
|
|
280
|
-
}}
|
|
281
|
-
>
|
|
282
|
-
Open on remote
|
|
283
|
-
</button>
|
|
284
|
-
)}
|
|
285
|
-
<div className="db-tree-context-divider" role="separator" />
|
|
286
|
-
</>
|
|
287
|
-
)}
|
|
288
|
-
<button
|
|
289
|
-
className="db-tree-context-item db-tree-context-item--danger"
|
|
290
|
-
onClick={handleDeleteClick}
|
|
291
|
-
>
|
|
292
|
-
Delete
|
|
293
|
-
</button>
|
|
294
|
-
</div>,
|
|
295
|
-
document.body,
|
|
296
|
-
)}
|
|
297
|
-
|
|
298
|
-
{/* Render children if directory is expanded */}
|
|
299
|
-
{isDir && expanded && renderChildren && (
|
|
300
|
-
<div className="db-tree-children" role="group">
|
|
301
|
-
{renderChildren(entry.path)}
|
|
302
|
-
</div>
|
|
303
|
-
)}
|
|
304
|
-
</div>
|
|
305
|
-
);
|
|
306
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { FileExplorer } from './FileExplorer.js';
|
|
2
|
-
export type { FileExplorerProps, FileTreeChange, FileTreeMutationHandler } from './FileExplorer.js';
|
|
3
|
-
export { FileTreeNode } from './FileTreeNode.js';
|
|
4
|
-
export type { FileTreeNodeProps } from './FileTreeNode.js';
|
|
5
|
-
export { useFileTree } from './useFileTree.js';
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useFileTree — hook that reads from a FileSystemProvider,
|
|
3
|
-
* maintains expanded/collapsed state, and provides CRUD operations.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
7
|
-
import {
|
|
8
|
-
getFileSystemProviderV2,
|
|
9
|
-
moveFileSystemEntry,
|
|
10
|
-
parseWorkspacePath,
|
|
11
|
-
type FileSystemProvider,
|
|
12
|
-
type FileSystemEntry,
|
|
13
|
-
} from '@bendyline/docblocks/filesystem';
|
|
14
|
-
|
|
15
|
-
function normalisePath(path: string): string {
|
|
16
|
-
return parseWorkspacePath(path);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function relocatePath(path: string, oldPath: string, newPath: string): string {
|
|
20
|
-
const current = normalisePath(path);
|
|
21
|
-
const oldNormalised = normalisePath(oldPath);
|
|
22
|
-
if (current !== oldNormalised && !current.startsWith(`${oldNormalised}/`)) return path;
|
|
23
|
-
const suffix = current.slice(oldNormalised.length);
|
|
24
|
-
return `${newPath.replace(/\/+$/, '')}${suffix}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function ancestorPaths(path: string): string[] {
|
|
28
|
-
const canonical = normalisePath(path);
|
|
29
|
-
if (!canonical) return [];
|
|
30
|
-
const segments = canonical.split('/');
|
|
31
|
-
const prefix = /^[\\/]/u.test(path) ? '/' : '';
|
|
32
|
-
return segments
|
|
33
|
-
.slice(0, -1)
|
|
34
|
-
.map((_, index) => `${prefix}${segments.slice(0, index + 1).join('/')}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function equivalentPathKeys(path: string): string[] {
|
|
38
|
-
const canonical = normalisePath(path);
|
|
39
|
-
return canonical ? [path, canonical, `/${canonical}`] : [path, canonical];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function hasEquivalentPath(paths: ReadonlySet<string>, path: string): boolean {
|
|
43
|
-
return equivalentPathKeys(path).some((candidate) => paths.has(candidate));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function readProviderDirectory(
|
|
47
|
-
provider: FileSystemProvider,
|
|
48
|
-
path: string,
|
|
49
|
-
): Promise<FileSystemEntry[]> {
|
|
50
|
-
const providerV2 = getFileSystemProviderV2(provider);
|
|
51
|
-
if (!providerV2) return provider.readDirectory(path);
|
|
52
|
-
const entries = await providerV2.readDirectory(parseWorkspacePath(path));
|
|
53
|
-
return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface FileTreeState {
|
|
57
|
-
/** Flat list of entries for the current directory view. */
|
|
58
|
-
entries: FileSystemEntry[];
|
|
59
|
-
/** Set of expanded directory paths. */
|
|
60
|
-
expanded: Set<string>;
|
|
61
|
-
/** Currently selected path — file or directory (null if none). */
|
|
62
|
-
selectedPath: string | null;
|
|
63
|
-
/** Kind of the selected entry. */
|
|
64
|
-
selectedKind: 'file' | 'directory' | null;
|
|
65
|
-
/** Whether the tree is loading. */
|
|
66
|
-
loading: boolean;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface FileTreeActions {
|
|
70
|
-
/** Toggle a directory's expanded state. */
|
|
71
|
-
toggleExpand: (path: string) => void;
|
|
72
|
-
/** Select a file or directory. */
|
|
73
|
-
select: (path: string, kind: 'file' | 'directory') => void;
|
|
74
|
-
/** Select an entry and expand each directory needed to reveal it. */
|
|
75
|
-
reveal: (path: string, kind: 'file' | 'directory') => void;
|
|
76
|
-
/** Create a new file with optional initial content. */
|
|
77
|
-
createFile: (path: string, content?: string) => Promise<void>;
|
|
78
|
-
/** Create a new directory. */
|
|
79
|
-
createDirectory: (path: string) => Promise<void>;
|
|
80
|
-
/** Delete a file or directory. */
|
|
81
|
-
deleteEntry: (path: string) => Promise<void>;
|
|
82
|
-
/** Rename or move a file or directory, including a markdown companion folder. */
|
|
83
|
-
renameEntry: (oldPath: string, newPath: string, kind?: 'file' | 'directory') => Promise<void>;
|
|
84
|
-
/** Force refresh the tree. */
|
|
85
|
-
refresh: () => Promise<void>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function useFileTree(provider: FileSystemProvider | null): FileTreeState & FileTreeActions {
|
|
89
|
-
const [entries, setEntries] = useState<FileSystemEntry[]>([]);
|
|
90
|
-
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
91
|
-
const [selectedPath, setSelectedPath] = useState<string | null>(null);
|
|
92
|
-
const [selectedKind, setSelectedKind] = useState<'file' | 'directory' | null>(null);
|
|
93
|
-
const [loading, setLoading] = useState(false);
|
|
94
|
-
|
|
95
|
-
// Track child entries for expanded directories
|
|
96
|
-
const [childEntries, setChildEntries] = useState<Map<string, FileSystemEntry[]>>(new Map());
|
|
97
|
-
|
|
98
|
-
const providerRef = useRef(provider);
|
|
99
|
-
providerRef.current = provider;
|
|
100
|
-
|
|
101
|
-
const loadRoot = useCallback(async () => {
|
|
102
|
-
if (!providerRef.current) {
|
|
103
|
-
setEntries([]);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
setLoading(true);
|
|
107
|
-
try {
|
|
108
|
-
const root = await readProviderDirectory(providerRef.current, '');
|
|
109
|
-
setEntries(root);
|
|
110
|
-
} finally {
|
|
111
|
-
setLoading(false);
|
|
112
|
-
}
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
const loadChildren = useCallback(async (dirPath: string) => {
|
|
116
|
-
if (!providerRef.current) return;
|
|
117
|
-
const children = await readProviderDirectory(providerRef.current, dirPath);
|
|
118
|
-
setChildEntries((prev) => {
|
|
119
|
-
const next = new Map(prev);
|
|
120
|
-
next.set(dirPath, children);
|
|
121
|
-
return next;
|
|
122
|
-
});
|
|
123
|
-
}, []);
|
|
124
|
-
|
|
125
|
-
// Load root on provider change
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
setExpanded(new Set());
|
|
128
|
-
setChildEntries(new Map());
|
|
129
|
-
setSelectedPath(null);
|
|
130
|
-
setSelectedKind(null);
|
|
131
|
-
loadRoot();
|
|
132
|
-
}, [provider, loadRoot]);
|
|
133
|
-
|
|
134
|
-
const toggleExpand = useCallback(
|
|
135
|
-
(path: string) => {
|
|
136
|
-
setExpanded((prev) => {
|
|
137
|
-
const next = new Set(prev);
|
|
138
|
-
if (hasEquivalentPath(next, path)) {
|
|
139
|
-
for (const candidate of equivalentPathKeys(path)) next.delete(candidate);
|
|
140
|
-
} else {
|
|
141
|
-
next.add(path);
|
|
142
|
-
// Load children when expanding
|
|
143
|
-
loadChildren(path);
|
|
144
|
-
}
|
|
145
|
-
return next;
|
|
146
|
-
});
|
|
147
|
-
},
|
|
148
|
-
[loadChildren],
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const select = useCallback((path: string, kind: 'file' | 'directory') => {
|
|
152
|
-
setSelectedPath(path);
|
|
153
|
-
setSelectedKind(kind);
|
|
154
|
-
}, []);
|
|
155
|
-
|
|
156
|
-
const reveal = useCallback(
|
|
157
|
-
(path: string, kind: 'file' | 'directory') => {
|
|
158
|
-
const ancestors = ancestorPaths(path);
|
|
159
|
-
setSelectedPath(path);
|
|
160
|
-
setSelectedKind(kind);
|
|
161
|
-
setExpanded((prev) => {
|
|
162
|
-
const next = new Set(prev);
|
|
163
|
-
for (const ancestor of ancestors) {
|
|
164
|
-
if (!hasEquivalentPath(next, ancestor)) next.add(ancestor);
|
|
165
|
-
}
|
|
166
|
-
return next;
|
|
167
|
-
});
|
|
168
|
-
for (const ancestor of ancestors) {
|
|
169
|
-
void loadChildren(ancestor);
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
[loadChildren],
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
const refresh = useCallback(async () => {
|
|
176
|
-
await loadRoot();
|
|
177
|
-
// Reload all expanded directories
|
|
178
|
-
const expandedPaths = [...expanded];
|
|
179
|
-
for (const dirPath of expandedPaths) {
|
|
180
|
-
await loadChildren(dirPath);
|
|
181
|
-
}
|
|
182
|
-
}, [loadRoot, loadChildren, expanded]);
|
|
183
|
-
|
|
184
|
-
const refreshRef = useRef(refresh);
|
|
185
|
-
refreshRef.current = refresh;
|
|
186
|
-
|
|
187
|
-
// Keep the explorer synchronized with provider changes. Watch-capable
|
|
188
|
-
// providers (including Electron's chokidar-backed provider) update in real
|
|
189
|
-
// time. Calls are serialized and coalesced so event bursts cannot publish
|
|
190
|
-
// directory reads out of order.
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
if (!provider) return;
|
|
193
|
-
const providerV2 = getFileSystemProviderV2(provider);
|
|
194
|
-
if (!providerV2?.capabilities.watch) return;
|
|
195
|
-
|
|
196
|
-
let disposed = false;
|
|
197
|
-
let refreshing = false;
|
|
198
|
-
let refreshAgain = false;
|
|
199
|
-
|
|
200
|
-
const requestRefresh = () => {
|
|
201
|
-
if (disposed) return;
|
|
202
|
-
if (refreshing) {
|
|
203
|
-
refreshAgain = true;
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
refreshing = true;
|
|
207
|
-
void (async () => {
|
|
208
|
-
try {
|
|
209
|
-
do {
|
|
210
|
-
refreshAgain = false;
|
|
211
|
-
await refreshRef.current();
|
|
212
|
-
} while (refreshAgain && !disposed);
|
|
213
|
-
} catch {
|
|
214
|
-
// A later watcher event or window resume will retry the read.
|
|
215
|
-
} finally {
|
|
216
|
-
refreshing = false;
|
|
217
|
-
if (refreshAgain && !disposed) requestRefresh();
|
|
218
|
-
}
|
|
219
|
-
})();
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const subscription = providerV2.watch(requestRefresh, { onError: requestRefresh });
|
|
223
|
-
void subscription.ready.catch(() => undefined);
|
|
224
|
-
return () => {
|
|
225
|
-
disposed = true;
|
|
226
|
-
void subscription.dispose();
|
|
227
|
-
};
|
|
228
|
-
}, [provider]);
|
|
229
|
-
|
|
230
|
-
// File System Access and IndexedDB do not expose a dependable external
|
|
231
|
-
// watcher. Refreshing on resume catches changes made while this surface was
|
|
232
|
-
// in the background without continuously polling the filesystem.
|
|
233
|
-
useEffect(() => {
|
|
234
|
-
if (!provider) return;
|
|
235
|
-
const refreshOnFocus = () => {
|
|
236
|
-
void refreshRef.current().catch(() => undefined);
|
|
237
|
-
};
|
|
238
|
-
const refreshOnVisibility = () => {
|
|
239
|
-
if (document.visibilityState === 'visible') refreshOnFocus();
|
|
240
|
-
};
|
|
241
|
-
window.addEventListener('focus', refreshOnFocus);
|
|
242
|
-
document.addEventListener('visibilitychange', refreshOnVisibility);
|
|
243
|
-
return () => {
|
|
244
|
-
window.removeEventListener('focus', refreshOnFocus);
|
|
245
|
-
document.removeEventListener('visibilitychange', refreshOnVisibility);
|
|
246
|
-
};
|
|
247
|
-
}, [provider]);
|
|
248
|
-
|
|
249
|
-
const createFile = useCallback(
|
|
250
|
-
async (path: string, content = '') => {
|
|
251
|
-
if (!providerRef.current) return;
|
|
252
|
-
const providerV2 = getFileSystemProviderV2(providerRef.current);
|
|
253
|
-
if (providerV2) {
|
|
254
|
-
await providerV2.writeFile(parseWorkspacePath(path), new TextEncoder().encode(content), {
|
|
255
|
-
mode: 'create',
|
|
256
|
-
});
|
|
257
|
-
} else {
|
|
258
|
-
await providerRef.current.writeFile(path, content);
|
|
259
|
-
}
|
|
260
|
-
await refresh();
|
|
261
|
-
},
|
|
262
|
-
[refresh],
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
const createDirectory = useCallback(
|
|
266
|
-
async (path: string) => {
|
|
267
|
-
if (!providerRef.current) return;
|
|
268
|
-
const providerV2 = getFileSystemProviderV2(providerRef.current);
|
|
269
|
-
if (providerV2) {
|
|
270
|
-
await providerV2.createDirectory(parseWorkspacePath(path), { mode: 'create' });
|
|
271
|
-
} else {
|
|
272
|
-
await providerRef.current.createDirectory(path);
|
|
273
|
-
}
|
|
274
|
-
await refresh();
|
|
275
|
-
},
|
|
276
|
-
[refresh],
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
const deleteEntry = useCallback(
|
|
280
|
-
async (path: string) => {
|
|
281
|
-
if (!providerRef.current) return;
|
|
282
|
-
const providerV2 = getFileSystemProviderV2(providerRef.current);
|
|
283
|
-
if (providerV2) {
|
|
284
|
-
const canonical = parseWorkspacePath(path);
|
|
285
|
-
const entry = await providerV2.stat(canonical);
|
|
286
|
-
if (entry) {
|
|
287
|
-
await providerV2.remove(canonical, {
|
|
288
|
-
recursive: entry.kind === 'directory',
|
|
289
|
-
missing: 'error',
|
|
290
|
-
expectedVersion: entry.version,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
await providerRef.current.delete(path);
|
|
295
|
-
}
|
|
296
|
-
if (selectedPath === path) {
|
|
297
|
-
setSelectedPath(null);
|
|
298
|
-
setSelectedKind(null);
|
|
299
|
-
}
|
|
300
|
-
await refresh();
|
|
301
|
-
},
|
|
302
|
-
[refresh, selectedPath],
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
const renameEntry = useCallback(
|
|
306
|
-
async (oldPath: string, newPath: string, kind?: 'file' | 'directory') => {
|
|
307
|
-
if (!providerRef.current) return;
|
|
308
|
-
const oldNormalised = normalisePath(oldPath);
|
|
309
|
-
const knownEntry = [...entries, ...childEntries.values()]
|
|
310
|
-
.flat()
|
|
311
|
-
.find((entry) => normalisePath(entry.path) === oldNormalised);
|
|
312
|
-
const entryKind = kind ?? knownEntry?.kind ?? 'file';
|
|
313
|
-
await moveFileSystemEntry(providerRef.current, oldPath, newPath, entryKind);
|
|
314
|
-
|
|
315
|
-
if (selectedPath) {
|
|
316
|
-
setSelectedPath(relocatePath(selectedPath, oldPath, newPath));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const nextExpanded = new Set(
|
|
320
|
-
[...expanded].map((path) => relocatePath(path, oldPath, newPath)),
|
|
321
|
-
);
|
|
322
|
-
setExpanded(nextExpanded);
|
|
323
|
-
setChildEntries(new Map());
|
|
324
|
-
await loadRoot();
|
|
325
|
-
for (const dirPath of nextExpanded) {
|
|
326
|
-
await loadChildren(dirPath);
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
[selectedPath, entries, childEntries, expanded, loadRoot, loadChildren],
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
// Merge root entries with child entries for a flat tree representation
|
|
333
|
-
// (consumers use `expanded` and `childEntries` to render recursively)
|
|
334
|
-
|
|
335
|
-
return {
|
|
336
|
-
entries,
|
|
337
|
-
expanded,
|
|
338
|
-
selectedPath,
|
|
339
|
-
selectedKind,
|
|
340
|
-
loading,
|
|
341
|
-
toggleExpand,
|
|
342
|
-
select,
|
|
343
|
-
reveal,
|
|
344
|
-
createFile,
|
|
345
|
-
createDirectory,
|
|
346
|
-
deleteEntry,
|
|
347
|
-
renameEntry,
|
|
348
|
-
refresh,
|
|
349
|
-
// Expose child entries for rendering
|
|
350
|
-
...{ childEntries },
|
|
351
|
-
} as FileTreeState & FileTreeActions & { childEntries: Map<string, FileSystemEntry[]> };
|
|
352
|
-
}
|