@tutti-os/workspace-file-manager 0.0.15 → 0.0.16

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/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  classifyWorkspaceFilePreviewKind,
4
4
  createWorkspaceFileManagerService,
5
5
  decodeWorkspaceTextFile,
6
+ findWorkspaceFileEntry,
6
7
  formatWorkspaceFileBytes,
7
8
  formatWorkspaceFileModifiedTime,
8
9
  isWorkspaceApplicationBundle,
@@ -25,41 +26,35 @@ import {
25
26
  workspaceFilePreviewMaxBytes,
26
27
  workspaceFileTextMaxBytes,
27
28
  writeWorkspaceFileManagerArrangeMode
28
- } from "./chunk-5C34NYLK.js";
29
+ } from "./chunk-ZN7X24RU.js";
29
30
  import {
30
31
  createWorkspaceFileManagerI18nRuntime,
31
32
  resolveRevealInFolderLabel,
32
33
  workspaceFileManagerI18nNamespace,
33
34
  workspaceFileManagerI18nResources
34
- } from "./chunk-MDBT2GS5.js";
35
+ } from "./chunk-V2LNXNYT.js";
35
36
 
36
37
  // src/ui/WorkspaceFileManager.tsx
37
- import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5, useState as useState7 } from "react";
38
+ import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6 } from "react";
38
39
  import { cn as cn6 } from "@tutti-os/ui-system";
39
40
 
40
- // src/ui/WorkspaceFileManagerMenus.tsx
41
+ // src/ui/WorkspaceFileManagerContextMenuContainer.tsx
42
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
43
+
44
+ // src/ui/WorkspaceFileManagerContextMenu.tsx
41
45
  import {
42
46
  ArrowRightIcon,
43
- Button,
44
- ConfirmationDialog,
45
47
  CopyIcon,
46
48
  DeleteIcon,
47
49
  DownloadIcon,
48
50
  EditIcon,
49
- Dialog,
50
- DialogContent,
51
- DialogDescription,
52
- DialogFooter,
53
- DialogHeader,
54
- DialogTitle,
55
51
  EyeIcon,
56
52
  FileLinedIcon,
57
- Input,
53
+ ImportLinedIcon as ImportIcon,
58
54
  LaunchIcon,
59
55
  LocateFolderIcon,
60
56
  MenuSurface,
61
57
  NewWorkspaceLinedIcon,
62
- UploadIcon as ImportIcon,
63
58
  ViewportMenuSurface,
64
59
  WebIcon,
65
60
  cn
@@ -93,6 +88,9 @@ function clampContextMenuPosition(input) {
93
88
  }
94
89
  function estimateOpenWithSubmenuHeight(input) {
95
90
  let itemCount = 0;
91
+ if (input.showOpenInFileViewer) {
92
+ itemCount += 1;
93
+ }
96
94
  if (input.showOpenInAppBrowser) {
97
95
  itemCount += 1;
98
96
  }
@@ -112,7 +110,7 @@ function estimateOpenWithSubmenuHeight(input) {
112
110
  return itemCount * CONTEXT_MENU_ITEM_HEIGHT_PX + CONTEXT_MENU_PADDING_PX * 2;
113
111
  }
114
112
 
115
- // src/ui/WorkspaceFileManagerMenus.tsx
113
+ // src/ui/WorkspaceFileManagerContextMenu.tsx
116
114
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
117
115
  function WorkspaceFileManagerContextMenu({
118
116
  busy,
@@ -125,6 +123,7 @@ function WorkspaceFileManagerContextMenu({
125
123
  showExportAction,
126
124
  showOpenInAppBrowserAction,
127
125
  showOpenInDefaultBrowserAction,
126
+ showOpenInFileViewerAction,
128
127
  showOpenWithAction,
129
128
  showOpenWithOtherAction,
130
129
  showRevealInFolderAction,
@@ -143,6 +142,7 @@ function WorkspaceFileManagerContextMenu({
143
142
  onOpen,
144
143
  onOpenInAppBrowser,
145
144
  onOpenInDefaultBrowser,
145
+ onOpenInFileViewer,
146
146
  onOpenWithApplication,
147
147
  onOpenWithOtherApplication,
148
148
  onImport,
@@ -190,6 +190,7 @@ function WorkspaceFileManagerContextMenu({
190
190
  showImportAction,
191
191
  showOpenInAppBrowserAction,
192
192
  showOpenInDefaultBrowserAction,
193
+ showOpenInFileViewerAction,
193
194
  showOpenWithAction,
194
195
  showOpenWithOtherAction,
195
196
  showRevealInFolderAction,
@@ -339,10 +340,12 @@ function WorkspaceFileManagerContextMenu({
339
340
  resolveOpenWithApplicationIcon,
340
341
  showOpenInAppBrowser: showOpenInAppBrowserAction,
341
342
  showOpenInDefaultBrowser: showOpenInDefaultBrowserAction,
343
+ showOpenInFileViewer: showOpenInFileViewerAction,
342
344
  showOpenWithOther: showOpenWithOtherAction,
343
345
  onClose,
344
346
  onOpenInAppBrowser,
345
347
  onOpenInDefaultBrowser,
348
+ onOpenInFileViewer,
346
349
  onOpenWithApplication,
347
350
  onOpenWithOtherApplication
348
351
  }
@@ -429,10 +432,12 @@ function OpenWithMenuItem({
429
432
  resolveOpenWithApplicationIcon,
430
433
  showOpenInAppBrowser,
431
434
  showOpenInDefaultBrowser,
435
+ showOpenInFileViewer,
432
436
  showOpenWithOther,
433
437
  onClose,
434
438
  onOpenInAppBrowser,
435
439
  onOpenInDefaultBrowser,
440
+ onOpenInFileViewer,
436
441
  onOpenWithApplication,
437
442
  onOpenWithOtherApplication
438
443
  }) {
@@ -447,6 +452,7 @@ function OpenWithMenuItem({
447
452
  showExternalSection,
448
453
  showOpenInAppBrowser,
449
454
  showOpenInDefaultBrowser,
455
+ showOpenInFileViewer,
450
456
  showOpenWithOther
451
457
  });
452
458
  const cancelClose = useCallback(() => {
@@ -551,6 +557,18 @@ function OpenWithMenuItem({
551
557
  onPointerEnter: openSubmenu,
552
558
  onPointerLeave: scheduleClose,
553
559
  children: [
560
+ showOpenInFileViewer ? /* @__PURE__ */ jsx(
561
+ ContextMenuActionButton,
562
+ {
563
+ disabled: busy,
564
+ icon: /* @__PURE__ */ jsx(EyeIcon, { className: "size-4" }),
565
+ label: copy.t("openInFileViewerLabel"),
566
+ onClick: () => {
567
+ onClose();
568
+ void onOpenInFileViewer();
569
+ }
570
+ }
571
+ ) : null,
554
572
  showOpenInAppBrowser ? /* @__PURE__ */ jsx(
555
573
  ContextMenuActionButton,
556
574
  {
@@ -619,215 +637,604 @@ function OpenWithMenuItem({
619
637
  }
620
638
  );
621
639
  }
622
- function WorkspaceFileManagerCreateDialog({
623
- busy,
624
- copy,
625
- dialog,
626
- onClose,
627
- onConfirm,
628
- onNameChange
629
- }) {
630
- if (!dialog) {
631
- return null;
632
- }
633
- return /* @__PURE__ */ jsx(
634
- Dialog,
635
- {
636
- open: true,
637
- onOpenChange: (nextOpen) => {
638
- if (!nextOpen) {
639
- onClose();
640
- }
641
- },
642
- children: /* @__PURE__ */ jsx(DialogContent, { "aria-busy": busy, showCloseButton: false, children: /* @__PURE__ */ jsxs(
643
- "form",
644
- {
645
- className: "grid gap-3",
646
- onSubmit: (event) => {
647
- event.preventDefault();
648
- onConfirm();
649
- },
650
- children: [
651
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: dialog.kind === "directory" ? copy.t("createDirectoryLabel") : copy.t("createFileLabel") }) }),
652
- /* @__PURE__ */ jsx(
653
- Input,
654
- {
655
- autoFocus: true,
656
- placeholder: dialog.kind === "directory" ? copy.t("createDirectoryPlaceholder") : copy.t("createFilePlaceholder"),
657
- value: dialog.name,
658
- onChange: (event) => {
659
- onNameChange(event.currentTarget.value);
660
- }
661
- }
662
- ),
663
- dialog.errorMessage ? /* @__PURE__ */ jsx("p", { className: "text-[13px] text-[var(--state-danger)]", children: dialog.errorMessage }) : null,
664
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
665
- /* @__PURE__ */ jsx(
666
- Button,
667
- {
668
- disabled: busy,
669
- size: "dialog",
670
- type: "button",
671
- variant: "ghost",
672
- onClick: onClose,
673
- children: copy.t("cancelLabel")
674
- }
675
- ),
676
- /* @__PURE__ */ jsx(Button, { disabled: busy, size: "dialog", type: "submit", children: copy.t("createActionLabel") })
677
- ] })
678
- ]
679
- }
680
- ) })
681
- }
682
- );
640
+
641
+ // src/ui/useWorkspaceFileManagerService.ts
642
+ import { useSnapshot } from "valtio";
643
+
644
+ // src/services/internal/workspaceFileManagerViewModel.ts
645
+ function resolveWorkspaceFileManagerRootViewState(input) {
646
+ const { state } = input;
647
+ return {
648
+ canImportFromDrop: state.capabilities.canImportFromDrop,
649
+ currentDirectoryPath: state.currentDirectoryPath,
650
+ isBusy: state.busyAction !== null
651
+ };
683
652
  }
684
- function WorkspaceFileManagerDeleteDialog({
685
- busy,
686
- copy,
687
- entry,
688
- onClose,
689
- onConfirm
690
- }) {
691
- if (!entry) {
692
- return null;
693
- }
694
- return /* @__PURE__ */ jsx(
695
- ConfirmationDialog,
696
- {
697
- cancelLabel: copy.t("cancelLabel"),
698
- confirmBusy: busy,
699
- confirmLabel: busy ? copy.t("deletingLabel") : copy.t("deleteLabel"),
700
- description: copy.t("deleteConfirmDescription", { name: entry.name }),
701
- open: true,
702
- title: copy.t("deleteLabel"),
703
- tone: "destructive",
704
- onConfirm,
705
- onOpenChange: (nextOpen) => {
706
- if (!nextOpen) {
707
- onClose();
708
- }
709
- }
710
- }
653
+ function resolveWorkspaceFileManagerToolbarViewState(input) {
654
+ const { copy, state } = input;
655
+ const currentDirectoryPath = normalizeWorkspaceFilePath(
656
+ state.currentDirectoryPath,
657
+ state.root
711
658
  );
659
+ return {
660
+ breadcrumbs: buildWorkspaceFileBreadcrumbs(
661
+ currentDirectoryPath,
662
+ copy.t("breadcrumbRootLabel"),
663
+ state.root
664
+ ),
665
+ canGoBack: currentDirectoryPath !== normalizeWorkspaceFilePath(state.root) && state.navigationBackStack.length > 0,
666
+ canGoForward: state.navigationForwardStack.length > 0,
667
+ currentDirectoryPath,
668
+ isBusy: state.busyAction !== null,
669
+ isLoading: state.isLoading,
670
+ isMutating: state.isMutating,
671
+ isImporting: state.busyAction === "import",
672
+ showImportAction: state.capabilities.canImportFromPicker
673
+ };
712
674
  }
713
- function WorkspaceFileManagerUnsupportedDialog({
714
- copy,
715
- dialog,
716
- isViewing,
717
- onAction,
718
- onClose
719
- }) {
720
- if (!dialog) {
721
- return null;
722
- }
723
- const title = dialog.title ?? (dialog.kind === "import" ? copy.t("unsupportedImportTitle") : copy.t("unsupportedViewTitle"));
724
- const body = dialog.message ?? (dialog.kind === "import" ? copy.t("unsupportedImportBody") : copy.t("unsupportedViewBody", { name: dialog.entry?.name ?? "" }));
725
- const actions = dialog.actions?.filter((action) => action.kind !== "none") ?? [];
726
- return /* @__PURE__ */ jsx(
727
- Dialog,
728
- {
729
- open: true,
730
- onOpenChange: (nextOpen) => {
731
- if (!nextOpen) {
732
- onClose();
733
- }
734
- },
735
- children: /* @__PURE__ */ jsxs(
736
- DialogContent,
737
- {
738
- showCloseButton: false,
739
- onEscapeKeyDown: (event) => {
740
- if (isViewing) {
741
- event.preventDefault();
742
- }
743
- },
744
- onInteractOutside: (event) => {
745
- if (isViewing) {
746
- event.preventDefault();
747
- }
748
- },
749
- children: [
750
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
751
- /* @__PURE__ */ jsx(DialogTitle, { children: title }),
752
- /* @__PURE__ */ jsx(DialogDescription, { children: body })
753
- ] }),
754
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
755
- /* @__PURE__ */ jsx(
756
- Button,
757
- {
758
- disabled: isViewing,
759
- size: "dialog",
760
- type: "button",
761
- variant: "ghost",
762
- onClick: onClose,
763
- children: copy.t("closeLabel")
764
- }
765
- ),
766
- actions.map((action) => /* @__PURE__ */ jsx(
767
- Button,
768
- {
769
- disabled: isViewing,
770
- size: "dialog",
771
- type: "button",
772
- className: "shadow-none",
773
- onClick: () => {
774
- onAction(action);
775
- },
776
- children: action.label ?? (action.kind === "download" ? copy.t("downloadLabel") : copy.t("openLabel"))
777
- },
778
- action.kind
779
- ))
780
- ] })
781
- ]
782
- }
783
- )
784
- }
785
- );
675
+ function resolveWorkspaceFileManagerPanelsViewState(input) {
676
+ const { state } = input;
677
+ return {
678
+ canMove: state.capabilities.canMove,
679
+ contextMenuEntryPath: state.contextMenuEntryPath,
680
+ entries: state.entries,
681
+ error: state.error,
682
+ inlineRenameEntryPath: state.inlineRenameEntryPath,
683
+ inlineRenameValidation: state.inlineRenameValidation,
684
+ isLoading: state.isLoading,
685
+ isRenaming: state.busyAction === "rename",
686
+ pendingDirectoryPath: state.pendingDirectoryPath,
687
+ previewState: state.previewState,
688
+ selectedEntry: findSelectedEntry(state),
689
+ selectedPath: state.selectedPath,
690
+ showDropOverlay: state.capabilities.canImportFromDrop && state.dragDepth > 0 && state.busyAction === null
691
+ };
786
692
  }
787
- function WorkspaceFileManagerImportConflictDialog({
788
- busy,
789
- copy,
790
- dialog,
791
- onClose,
792
- onConfirm
793
- }) {
794
- if (!dialog) {
795
- return null;
796
- }
797
- const hasBlockedConflict = dialog.conflicts.some(
798
- (conflict) => conflict.conflictKind === "type_mismatch"
799
- );
800
- return /* @__PURE__ */ jsxs(
801
- ConfirmationDialog,
802
- {
803
- cancelLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("cancelLabel"),
804
- className: "max-w-lg",
805
- confirmBusy: busy,
806
- confirmLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("importConflictReplaceLabel"),
807
- description: hasBlockedConflict ? copy.t("importTypeConflictDescription", {
808
- count: dialog.conflicts.length
809
- }) : copy.t("importConflictDescription", {
810
- count: dialog.conflicts.length
811
- }),
812
- hideConfirmButton: hasBlockedConflict,
813
- open: true,
814
- title: hasBlockedConflict ? copy.t("importTypeConflictTitle") : copy.t("importConflictTitle"),
815
- tone: hasBlockedConflict ? "default" : "destructive",
816
- onConfirm: hasBlockedConflict ? onClose : onConfirm,
817
- onOpenChange: (nextOpen) => {
818
- if (!nextOpen) {
819
- onClose();
820
- }
821
- },
822
- children: [
823
- /* @__PURE__ */ jsx(ImportConflictSummary, { copy, dialog }),
824
- /* @__PURE__ */ jsx("div", { className: "max-h-60 overflow-auto rounded-lg border border-[var(--border-1)] bg-transparency-block", children: /* @__PURE__ */ jsx("div", { className: "divide-y divide-[var(--border-1)]", children: dialog.conflicts.map((conflict) => /* @__PURE__ */ jsxs(
825
- "div",
826
- {
827
- className: "flex flex-col gap-1 px-4 py-3 text-[13px]",
828
- children: [
829
- /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: conflict.name }),
830
- /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-[var(--text-secondary)]", children: [
693
+ function resolveWorkspaceFileManagerDialogsViewState(input) {
694
+ const { state } = input;
695
+ const unsupportedDialogEntry = state.unsupportedDialog?.entryPath ? findEntry(state, state.unsupportedDialog.entryPath) : null;
696
+ return {
697
+ createDialog: state.createDialog,
698
+ deleteDialogEntry: state.deleteDialog ? findEntry(state, state.deleteDialog.entryPath) : null,
699
+ isBusy: state.busyAction !== null,
700
+ isDeleting: state.busyAction === "delete",
701
+ isImporting: state.busyAction === "import",
702
+ isRenaming: state.busyAction === "rename",
703
+ isViewing: state.busyAction === "view",
704
+ unsupportedDialog: state.unsupportedDialog ? {
705
+ actions: state.unsupportedDialog.actions,
706
+ entry: unsupportedDialogEntry ?? void 0,
707
+ kind: state.unsupportedDialog.kind,
708
+ message: state.unsupportedDialog.message,
709
+ title: state.unsupportedDialog.title
710
+ } : null,
711
+ importConflictDialog: state.importConflictDialog
712
+ };
713
+ }
714
+ function resolveWorkspaceFileManagerContextMenuViewState(input) {
715
+ const { state } = input;
716
+ const contextMenuEntry = state.contextMenu?.entryPath ? findEntry(state, state.contextMenu.entryPath) : null;
717
+ const isContextMenuFile = contextMenuEntry?.kind === "file";
718
+ return {
719
+ contextMenu: state.contextMenu ? {
720
+ entry: contextMenuEntry,
721
+ x: state.contextMenu.x,
722
+ y: state.contextMenu.y
723
+ } : null,
724
+ currentDirectoryPath: state.currentDirectoryPath,
725
+ isBusy: state.busyAction !== null,
726
+ isLoading: state.isLoading,
727
+ isMutating: state.isMutating,
728
+ showCopyAction: state.capabilities.canCopy,
729
+ showExportAction: state.capabilities.canExport,
730
+ showImportAction: state.capabilities.canImportFromPicker,
731
+ showMoveAction: state.capabilities.canMove,
732
+ showOpenInAppBrowserAction: state.capabilities.canOpenInAppBrowser,
733
+ showOpenInDefaultBrowserAction: state.capabilities.canOpenInDefaultBrowser,
734
+ showOpenInFileViewerAction: contextMenuEntry !== null && isContextMenuFile && resolveWorkspaceFileActivationTarget(contextMenuEntry) !== null,
735
+ showOpenWithAction: state.capabilities.canOpenWith && isContextMenuFile,
736
+ showOpenWithOtherAction: state.capabilities.canPickOtherOpenWithApplication && isContextMenuFile,
737
+ showRevealInFolderAction: state.capabilities.canRevealInFolder,
738
+ showRenameAction: state.capabilities.canRename
739
+ };
740
+ }
741
+ function findSelectedEntry(state) {
742
+ return findWorkspaceFileEntry(state, state.selectedPath);
743
+ }
744
+ function findEntry(state, entryPath) {
745
+ return findWorkspaceFileEntry(state, entryPath);
746
+ }
747
+
748
+ // src/ui/useWorkspaceFileManagerService.ts
749
+ function useWorkspaceFileManagerRootView(session) {
750
+ const state = useSnapshot(
751
+ session.store
752
+ );
753
+ return resolveWorkspaceFileManagerRootViewState({ state });
754
+ }
755
+ function useWorkspaceFileManagerToolbarView(session, i18n) {
756
+ const state = useSnapshot(
757
+ session.store
758
+ );
759
+ return {
760
+ view: resolveWorkspaceFileManagerToolbarViewState({
761
+ copy: i18n,
762
+ state
763
+ })
764
+ };
765
+ }
766
+ function useWorkspaceFileManagerPanelsView(session) {
767
+ const state = useSnapshot(
768
+ session.store
769
+ );
770
+ return {
771
+ state,
772
+ view: resolveWorkspaceFileManagerPanelsViewState({
773
+ state
774
+ })
775
+ };
776
+ }
777
+ function useWorkspaceFileManagerDialogsView(session) {
778
+ const state = useSnapshot(
779
+ session.store
780
+ );
781
+ return {
782
+ state,
783
+ view: resolveWorkspaceFileManagerDialogsViewState({
784
+ state
785
+ })
786
+ };
787
+ }
788
+ function useWorkspaceFileManagerContextMenuView(session) {
789
+ const state = useSnapshot(
790
+ session.store
791
+ );
792
+ return {
793
+ state,
794
+ view: resolveWorkspaceFileManagerContextMenuViewState({
795
+ state
796
+ })
797
+ };
798
+ }
799
+
800
+ // src/ui/WorkspaceFileManagerContextMenuContainer.tsx
801
+ import { jsx as jsx2 } from "react/jsx-runtime";
802
+ function WorkspaceFileManagerContextMenuContainer({
803
+ hostOs,
804
+ i18n,
805
+ onCopyEntry,
806
+ onCopyPath,
807
+ openInAppBrowserIcon,
808
+ resolveOpenWithApplicationIcon,
809
+ session
810
+ }) {
811
+ const contextMenuRef = useRef2(null);
812
+ const { view } = useWorkspaceFileManagerContextMenuView(session);
813
+ const [openWithApplications, setOpenWithApplications] = useState2([]);
814
+ const [openWithLoading, setOpenWithLoading] = useState2(false);
815
+ useEffect2(() => {
816
+ const entry = view.contextMenu?.entry;
817
+ if (!entry || !view.showOpenWithAction) {
818
+ setOpenWithApplications([]);
819
+ setOpenWithLoading(false);
820
+ return;
821
+ }
822
+ const cachedApplications = session.getCachedOpenWithApplications(entry);
823
+ if (cachedApplications) {
824
+ setOpenWithApplications(cachedApplications);
825
+ setOpenWithLoading(false);
826
+ return;
827
+ }
828
+ let cancelled = false;
829
+ setOpenWithApplications([]);
830
+ setOpenWithLoading(true);
831
+ void session.listOpenWithApplications(entry).then((applications) => {
832
+ if (cancelled) {
833
+ return;
834
+ }
835
+ setOpenWithApplications(applications);
836
+ setOpenWithLoading(false);
837
+ }).catch(() => {
838
+ if (cancelled) {
839
+ return;
840
+ }
841
+ setOpenWithApplications([]);
842
+ setOpenWithLoading(false);
843
+ });
844
+ return () => {
845
+ cancelled = true;
846
+ };
847
+ }, [session, view.contextMenu?.entry?.path, view.showOpenWithAction]);
848
+ useCloseContextMenuOnOutsideInteraction({
849
+ contextMenuRef,
850
+ isOpen: view.contextMenu !== null,
851
+ session
852
+ });
853
+ return /* @__PURE__ */ jsx2(
854
+ WorkspaceFileManagerContextMenu,
855
+ {
856
+ busy: view.isBusy || view.isLoading || view.isMutating,
857
+ copy: i18n,
858
+ contextMenu: view.contextMenu,
859
+ contextMenuRef,
860
+ showCopyAction: view.showCopyAction,
861
+ showImportAction: view.showImportAction,
862
+ showExportAction: view.showExportAction,
863
+ showOpenInAppBrowserAction: view.showOpenInAppBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
864
+ showOpenInDefaultBrowserAction: view.showOpenInDefaultBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
865
+ showOpenInFileViewerAction: view.showOpenInFileViewerAction,
866
+ showOpenWithAction: view.showOpenWithAction,
867
+ showOpenWithOtherAction: view.showOpenWithOtherAction,
868
+ showRevealInFolderAction: view.showRevealInFolderAction,
869
+ showRenameAction: view.showRenameAction,
870
+ revealInFolderLabel: resolveRevealInFolderLabel(i18n, hostOs),
871
+ openInAppBrowserIcon,
872
+ openWithApplications,
873
+ openWithLoading,
874
+ resolveOpenWithApplicationIcon,
875
+ onClose: () => {
876
+ session.closeContextMenu();
877
+ },
878
+ onCreateDirectory: () => {
879
+ session.openCreateDirectoryDialog();
880
+ },
881
+ onCreateFile: () => {
882
+ session.openCreateFileDialog();
883
+ },
884
+ onCopy: async () => {
885
+ const entry = view.contextMenu?.entry;
886
+ if (!entry) {
887
+ return;
888
+ }
889
+ session.closeContextMenu();
890
+ await session.copyToClipboard(entry);
891
+ if (onCopyEntry) {
892
+ await onCopyEntry();
893
+ }
894
+ },
895
+ onCopyPath: async () => {
896
+ const path = view.contextMenu?.entry?.path;
897
+ if (!path) {
898
+ return;
899
+ }
900
+ session.closeContextMenu();
901
+ if (onCopyPath) {
902
+ await onCopyPath(path);
903
+ return;
904
+ }
905
+ await navigator.clipboard.writeText(path);
906
+ },
907
+ onDelete: () => {
908
+ const entry = view.contextMenu?.entry;
909
+ if (!entry) {
910
+ return;
911
+ }
912
+ session.openDeleteDialog(entry);
913
+ },
914
+ onRename: () => {
915
+ const entry = view.contextMenu?.entry;
916
+ if (!entry) {
917
+ return;
918
+ }
919
+ session.startInlineRename(entry);
920
+ },
921
+ onExport: async () => {
922
+ const entry = view.contextMenu?.entry;
923
+ if (!entry) {
924
+ return;
925
+ }
926
+ await session.exportEntry(entry);
927
+ },
928
+ onOpen: async () => {
929
+ const entry = view.contextMenu?.entry;
930
+ if (!entry) {
931
+ return;
932
+ }
933
+ await session.openEntry(entry);
934
+ },
935
+ onOpenInAppBrowser: async () => {
936
+ const entry = view.contextMenu?.entry;
937
+ if (!entry) {
938
+ return;
939
+ }
940
+ await session.openFileInAppBrowser(entry);
941
+ },
942
+ onOpenInDefaultBrowser: async () => {
943
+ const entry = view.contextMenu?.entry;
944
+ if (!entry) {
945
+ return;
946
+ }
947
+ await session.openFileInDefaultBrowser(entry);
948
+ },
949
+ onOpenInFileViewer: async () => {
950
+ const entry = view.contextMenu?.entry;
951
+ if (!entry) {
952
+ return;
953
+ }
954
+ await session.openFileInFileViewer(entry);
955
+ },
956
+ onOpenWithApplication: async (applicationPath) => {
957
+ const entry = view.contextMenu?.entry;
958
+ if (!entry) {
959
+ return;
960
+ }
961
+ await session.openFileWithApplication(entry, applicationPath);
962
+ },
963
+ onOpenWithOtherApplication: async () => {
964
+ const entry = view.contextMenu?.entry;
965
+ if (!entry) {
966
+ return;
967
+ }
968
+ await session.openFileWithOtherApplication(entry);
969
+ },
970
+ onRevealInFolder: async () => {
971
+ const entry = view.contextMenu?.entry;
972
+ if (!entry) {
973
+ return;
974
+ }
975
+ await session.revealEntry(entry);
976
+ },
977
+ onImport: async () => {
978
+ session.closeContextMenu();
979
+ await session.importFiles(
980
+ view.contextMenu?.entry?.kind === "directory" ? view.contextMenu.entry.path : view.currentDirectoryPath
981
+ );
982
+ }
983
+ }
984
+ );
985
+ }
986
+ function useCloseContextMenuOnOutsideInteraction(input) {
987
+ const { contextMenuRef, isOpen, session } = input;
988
+ useEffect2(() => {
989
+ if (!isOpen) {
990
+ return;
991
+ }
992
+ function handlePointerDown(event) {
993
+ const target = event.target;
994
+ if (target instanceof Node && contextMenuRef.current?.contains(target)) {
995
+ return;
996
+ }
997
+ if (target instanceof Element && target.closest("[data-workspace-file-manager-submenu]")) {
998
+ return;
999
+ }
1000
+ session.closeContextMenu();
1001
+ }
1002
+ function handleKeyDown(event) {
1003
+ if (event.key === "Escape") {
1004
+ session.closeContextMenu();
1005
+ }
1006
+ }
1007
+ window.addEventListener("pointerdown", handlePointerDown);
1008
+ window.addEventListener("keydown", handleKeyDown);
1009
+ return () => {
1010
+ window.removeEventListener("pointerdown", handlePointerDown);
1011
+ window.removeEventListener("keydown", handleKeyDown);
1012
+ };
1013
+ }, [contextMenuRef, isOpen, session]);
1014
+ }
1015
+
1016
+ // src/ui/WorkspaceFileManagerMenus.tsx
1017
+ import {
1018
+ Button,
1019
+ ConfirmationDialog,
1020
+ Dialog,
1021
+ DialogContent,
1022
+ DialogDescription,
1023
+ DialogFooter,
1024
+ DialogHeader,
1025
+ DialogTitle,
1026
+ Input
1027
+ } from "@tutti-os/ui-system";
1028
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1029
+ function WorkspaceFileManagerCreateDialog({
1030
+ busy,
1031
+ copy,
1032
+ dialog,
1033
+ onClose,
1034
+ onConfirm,
1035
+ onNameChange
1036
+ }) {
1037
+ if (!dialog) {
1038
+ return null;
1039
+ }
1040
+ return /* @__PURE__ */ jsx3(
1041
+ Dialog,
1042
+ {
1043
+ open: true,
1044
+ onOpenChange: (nextOpen) => {
1045
+ if (!nextOpen) {
1046
+ onClose();
1047
+ }
1048
+ },
1049
+ children: /* @__PURE__ */ jsx3(DialogContent, { "aria-busy": busy, showCloseButton: false, children: /* @__PURE__ */ jsxs2(
1050
+ "form",
1051
+ {
1052
+ className: "grid gap-3",
1053
+ onSubmit: (event) => {
1054
+ event.preventDefault();
1055
+ onConfirm();
1056
+ },
1057
+ children: [
1058
+ /* @__PURE__ */ jsx3(DialogHeader, { children: /* @__PURE__ */ jsx3(DialogTitle, { children: dialog.kind === "directory" ? copy.t("createDirectoryLabel") : copy.t("createFileLabel") }) }),
1059
+ /* @__PURE__ */ jsx3(
1060
+ Input,
1061
+ {
1062
+ autoFocus: true,
1063
+ placeholder: dialog.kind === "directory" ? copy.t("createDirectoryPlaceholder") : copy.t("createFilePlaceholder"),
1064
+ value: dialog.name,
1065
+ onChange: (event) => {
1066
+ onNameChange(event.currentTarget.value);
1067
+ }
1068
+ }
1069
+ ),
1070
+ dialog.errorMessage ? /* @__PURE__ */ jsx3("p", { className: "text-[13px] text-[var(--state-danger)]", children: dialog.errorMessage }) : null,
1071
+ /* @__PURE__ */ jsxs2(DialogFooter, { children: [
1072
+ /* @__PURE__ */ jsx3(
1073
+ Button,
1074
+ {
1075
+ disabled: busy,
1076
+ size: "dialog",
1077
+ type: "button",
1078
+ variant: "ghost",
1079
+ onClick: onClose,
1080
+ children: copy.t("cancelLabel")
1081
+ }
1082
+ ),
1083
+ /* @__PURE__ */ jsx3(Button, { disabled: busy, size: "dialog", type: "submit", children: copy.t("createActionLabel") })
1084
+ ] })
1085
+ ]
1086
+ }
1087
+ ) })
1088
+ }
1089
+ );
1090
+ }
1091
+ function WorkspaceFileManagerDeleteDialog({
1092
+ busy,
1093
+ copy,
1094
+ entry,
1095
+ onClose,
1096
+ onConfirm
1097
+ }) {
1098
+ if (!entry) {
1099
+ return null;
1100
+ }
1101
+ return /* @__PURE__ */ jsx3(
1102
+ ConfirmationDialog,
1103
+ {
1104
+ cancelLabel: copy.t("cancelLabel"),
1105
+ confirmBusy: busy,
1106
+ confirmLabel: busy ? copy.t("deletingLabel") : copy.t("deleteLabel"),
1107
+ description: copy.t("deleteConfirmDescription", { name: entry.name }),
1108
+ open: true,
1109
+ title: copy.t("deleteLabel"),
1110
+ tone: "destructive",
1111
+ onConfirm,
1112
+ onOpenChange: (nextOpen) => {
1113
+ if (!nextOpen) {
1114
+ onClose();
1115
+ }
1116
+ }
1117
+ }
1118
+ );
1119
+ }
1120
+ function WorkspaceFileManagerUnsupportedDialog({
1121
+ copy,
1122
+ dialog,
1123
+ isViewing,
1124
+ onAction,
1125
+ onClose
1126
+ }) {
1127
+ if (!dialog) {
1128
+ return null;
1129
+ }
1130
+ const title = dialog.title ?? (dialog.kind === "import" ? copy.t("unsupportedImportTitle") : copy.t("unsupportedViewTitle"));
1131
+ const body = dialog.message ?? (dialog.kind === "import" ? copy.t("unsupportedImportBody") : copy.t("unsupportedViewBody", { name: dialog.entry?.name ?? "" }));
1132
+ const actions = dialog.actions?.filter((action) => action.kind !== "none") ?? [];
1133
+ return /* @__PURE__ */ jsx3(
1134
+ Dialog,
1135
+ {
1136
+ open: true,
1137
+ onOpenChange: (nextOpen) => {
1138
+ if (!nextOpen) {
1139
+ onClose();
1140
+ }
1141
+ },
1142
+ children: /* @__PURE__ */ jsxs2(
1143
+ DialogContent,
1144
+ {
1145
+ showCloseButton: false,
1146
+ onEscapeKeyDown: (event) => {
1147
+ if (isViewing) {
1148
+ event.preventDefault();
1149
+ }
1150
+ },
1151
+ onInteractOutside: (event) => {
1152
+ if (isViewing) {
1153
+ event.preventDefault();
1154
+ }
1155
+ },
1156
+ children: [
1157
+ /* @__PURE__ */ jsxs2(DialogHeader, { children: [
1158
+ /* @__PURE__ */ jsx3(DialogTitle, { children: title }),
1159
+ /* @__PURE__ */ jsx3(DialogDescription, { children: body })
1160
+ ] }),
1161
+ /* @__PURE__ */ jsxs2(DialogFooter, { children: [
1162
+ /* @__PURE__ */ jsx3(
1163
+ Button,
1164
+ {
1165
+ disabled: isViewing,
1166
+ size: "dialog",
1167
+ type: "button",
1168
+ variant: "ghost",
1169
+ onClick: onClose,
1170
+ children: copy.t("closeLabel")
1171
+ }
1172
+ ),
1173
+ actions.map((action) => /* @__PURE__ */ jsx3(
1174
+ Button,
1175
+ {
1176
+ disabled: isViewing,
1177
+ size: "dialog",
1178
+ type: "button",
1179
+ className: "shadow-none",
1180
+ onClick: () => {
1181
+ onAction(action);
1182
+ },
1183
+ children: action.label ?? (action.kind === "download" ? copy.t("downloadLabel") : copy.t("openLabel"))
1184
+ },
1185
+ action.kind
1186
+ ))
1187
+ ] })
1188
+ ]
1189
+ }
1190
+ )
1191
+ }
1192
+ );
1193
+ }
1194
+ function WorkspaceFileManagerImportConflictDialog({
1195
+ busy,
1196
+ copy,
1197
+ dialog,
1198
+ onClose,
1199
+ onConfirm
1200
+ }) {
1201
+ if (!dialog) {
1202
+ return null;
1203
+ }
1204
+ const hasBlockedConflict = dialog.conflicts.some(
1205
+ (conflict) => conflict.conflictKind === "type_mismatch"
1206
+ );
1207
+ return /* @__PURE__ */ jsxs2(
1208
+ ConfirmationDialog,
1209
+ {
1210
+ cancelLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("cancelLabel"),
1211
+ className: "max-w-lg",
1212
+ confirmBusy: busy,
1213
+ confirmLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("importConflictReplaceLabel"),
1214
+ description: hasBlockedConflict ? copy.t("importTypeConflictDescription", {
1215
+ count: dialog.conflicts.length
1216
+ }) : copy.t("importConflictDescription", {
1217
+ count: dialog.conflicts.length
1218
+ }),
1219
+ hideConfirmButton: hasBlockedConflict,
1220
+ open: true,
1221
+ title: hasBlockedConflict ? copy.t("importTypeConflictTitle") : copy.t("importConflictTitle"),
1222
+ tone: hasBlockedConflict ? "default" : "destructive",
1223
+ onConfirm: hasBlockedConflict ? onClose : onConfirm,
1224
+ onOpenChange: (nextOpen) => {
1225
+ if (!nextOpen) {
1226
+ onClose();
1227
+ }
1228
+ },
1229
+ children: [
1230
+ /* @__PURE__ */ jsx3(ImportConflictSummary, { copy, dialog }),
1231
+ /* @__PURE__ */ jsx3("div", { className: "max-h-60 overflow-auto rounded-lg border border-[var(--border-1)] bg-transparency-block", children: /* @__PURE__ */ jsx3("div", { className: "divide-y divide-[var(--border-1)]", children: dialog.conflicts.map((conflict) => /* @__PURE__ */ jsxs2(
1232
+ "div",
1233
+ {
1234
+ className: "flex flex-col gap-1 px-4 py-3 text-[13px]",
1235
+ children: [
1236
+ /* @__PURE__ */ jsx3("span", { className: "font-medium text-[var(--text-primary)]", children: conflict.name }),
1237
+ /* @__PURE__ */ jsxs2("span", { className: "text-[11px] text-[var(--text-secondary)]", children: [
831
1238
  copy.t("importConflictReviewLabel"),
832
1239
  ":",
833
1240
  " ",
@@ -881,7 +1288,7 @@ function ImportConflictSummary({
881
1288
  if (summaryItems.length === 0) {
882
1289
  return null;
883
1290
  }
884
- return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 text-[11px] text-[var(--text-secondary)]", children: summaryItems.map((item) => /* @__PURE__ */ jsx(
1291
+ return /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap gap-2 text-[11px] text-[var(--text-secondary)]", children: summaryItems.map((item) => /* @__PURE__ */ jsx3(
885
1292
  "span",
886
1293
  {
887
1294
  className: "rounded-md border border-[var(--border-1)] px-2 py-1",
@@ -903,6 +1310,7 @@ function importSummaryReasonCopyKey(reason) {
903
1310
 
904
1311
  // src/ui/WorkspaceFileManagerPanels.tsx
905
1312
  import {
1313
+ ArrowRightIcon as ArrowRightIcon2,
906
1314
  FileCodeIcon as FileCodeIcon2,
907
1315
  FileTextIcon as FileTextIcon2,
908
1316
  LoadingIcon as LoadingIcon2,
@@ -913,15 +1321,16 @@ import {
913
1321
  import { WorkspaceFilePreviewSurface as SharedWorkspaceFilePreviewSurface } from "@tutti-os/workspace-file-preview/react";
914
1322
  import {
915
1323
  useCallback as useCallback2,
916
- useEffect as useEffect3,
1324
+ useEffect as useEffect4,
917
1325
  useLayoutEffect as useLayoutEffect3,
918
- useRef as useRef4,
919
- useState as useState2
1326
+ useMemo,
1327
+ useRef as useRef5,
1328
+ useState as useState3
920
1329
  } from "react";
921
1330
 
922
1331
  // src/ui/WorkspaceFileManagerIconGrid.tsx
923
1332
  import { cn as cn3 } from "@tutti-os/ui-system";
924
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
1333
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef4 } from "react";
925
1334
 
926
1335
  // src/ui/WorkspaceFileEntryIcon.tsx
927
1336
  import {
@@ -931,8 +1340,8 @@ import {
931
1340
  VideoFileIcon,
932
1341
  cn as cn2
933
1342
  } from "@tutti-os/ui-system";
934
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
935
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1343
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
1344
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
936
1345
  var workspaceArchiveFallbackIconUrl = new URL(
937
1346
  "../assets/workspace-archive-fallback.png",
938
1347
  import.meta.url
@@ -959,10 +1368,10 @@ function WorkspaceFileEntryIcon({
959
1368
  const isAppBundle = isWorkspaceApplicationBundle(entry);
960
1369
  const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
961
1370
  const iconUrl = iconUrlByCacheKey?.get(cacheKey) ?? null;
962
- const frameRef = useRef2(null);
963
- const visibleEntryRef = useRef2(null);
964
- const visibleEntryCacheKeyRef = useRef2(null);
965
- useEffect2(() => {
1371
+ const frameRef = useRef3(null);
1372
+ const visibleEntryRef = useRef3(null);
1373
+ const visibleEntryCacheKeyRef = useRef3(null);
1374
+ useEffect3(() => {
966
1375
  function reportViewportLeave() {
967
1376
  const visibleEntry = visibleEntryRef.current;
968
1377
  if (!visibleEntry) {
@@ -1021,7 +1430,7 @@ function WorkspaceFileEntryIcon({
1021
1430
  reportViewportLeave();
1022
1431
  };
1023
1432
  }, [cacheKey, entry, isEnteringDirectory, onViewportLeave, onViewportEnter]);
1024
- return /* @__PURE__ */ jsx2(
1433
+ return /* @__PURE__ */ jsx4(
1025
1434
  "span",
1026
1435
  {
1027
1436
  ref: frameRef,
@@ -1030,12 +1439,12 @@ function WorkspaceFileEntryIcon({
1030
1439
  frameClassName,
1031
1440
  isEnteringDirectory ? "text-[var(--text-tertiary)]" : entryIconColorClassName(visualKind, isAppBundle)
1032
1441
  ),
1033
- children: isEnteringDirectory ? /* @__PURE__ */ jsx2(
1442
+ children: isEnteringDirectory ? /* @__PURE__ */ jsx4(
1034
1443
  LoadingIcon,
1035
1444
  {
1036
1445
  className: cn2(loadingIconClassName ?? iconClassName, "animate-spin")
1037
1446
  }
1038
- ) : iconUrl ? /* @__PURE__ */ jsx2(
1447
+ ) : iconUrl ? /* @__PURE__ */ jsx4(
1039
1448
  "img",
1040
1449
  {
1041
1450
  alt: "",
@@ -1048,7 +1457,7 @@ function WorkspaceFileEntryIcon({
1048
1457
  loading: "lazy",
1049
1458
  src: iconUrl
1050
1459
  }
1051
- ) : /* @__PURE__ */ jsx2(
1460
+ ) : /* @__PURE__ */ jsx4(
1052
1461
  DefaultEntryIcon,
1053
1462
  {
1054
1463
  entry,
@@ -1079,13 +1488,13 @@ function DefaultEntryIcon({
1079
1488
  }) {
1080
1489
  const vectorIconClassName = vectorFallbackIconClassName(iconClassName);
1081
1490
  if (isWorkspaceApplicationBundle(entry)) {
1082
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1491
+ return /* @__PURE__ */ jsx4(FileTextIcon, { className: vectorIconClassName });
1083
1492
  }
1084
1493
  if (shouldUseWorkspaceFileArchiveIcon(entry)) {
1085
- return /* @__PURE__ */ jsx2(WorkspaceArchiveFallbackIcon, { className: iconClassName });
1494
+ return /* @__PURE__ */ jsx4(WorkspaceArchiveFallbackIcon, { className: iconClassName });
1086
1495
  }
1087
1496
  if (shouldUseWorkspaceFileExtensionDocumentIcon(entry)) {
1088
- return /* @__PURE__ */ jsx2(
1497
+ return /* @__PURE__ */ jsx4(
1089
1498
  ExtensionDocumentIcon,
1090
1499
  {
1091
1500
  entry,
@@ -1095,20 +1504,20 @@ function DefaultEntryIcon({
1095
1504
  }
1096
1505
  switch (visualKind) {
1097
1506
  case "directory":
1098
- return /* @__PURE__ */ jsx2(WorkspaceFolderFallbackIcon, { className: iconClassName });
1507
+ return /* @__PURE__ */ jsx4(WorkspaceFolderFallbackIcon, { className: iconClassName });
1099
1508
  case "image":
1100
- return /* @__PURE__ */ jsx2(WorkspaceImageFallbackIcon, { className: iconClassName });
1509
+ return /* @__PURE__ */ jsx4(WorkspaceImageFallbackIcon, { className: iconClassName });
1101
1510
  case "video":
1102
- return /* @__PURE__ */ jsx2(VideoFileIcon, { className: vectorIconClassName });
1511
+ return /* @__PURE__ */ jsx4(VideoFileIcon, { className: vectorIconClassName });
1103
1512
  case "markdown":
1104
1513
  case "document":
1105
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1514
+ return /* @__PURE__ */ jsx4(FileTextIcon, { className: vectorIconClassName });
1106
1515
  case "code":
1107
- return /* @__PURE__ */ jsx2(FileCodeIcon, { className: vectorIconClassName });
1516
+ return /* @__PURE__ */ jsx4(FileCodeIcon, { className: vectorIconClassName });
1108
1517
  case "binary":
1109
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1518
+ return /* @__PURE__ */ jsx4(FileTextIcon, { className: vectorIconClassName });
1110
1519
  default:
1111
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1520
+ return /* @__PURE__ */ jsx4(FileTextIcon, { className: vectorIconClassName });
1112
1521
  }
1113
1522
  }
1114
1523
  function vectorFallbackIconClassName(iconClassName) {
@@ -1117,7 +1526,7 @@ function vectorFallbackIconClassName(iconClassName) {
1117
1526
  function WorkspaceFolderFallbackIcon({
1118
1527
  className
1119
1528
  }) {
1120
- return /* @__PURE__ */ jsx2(
1529
+ return /* @__PURE__ */ jsx4(
1121
1530
  "img",
1122
1531
  {
1123
1532
  alt: "",
@@ -1132,7 +1541,7 @@ function WorkspaceFolderFallbackIcon({
1132
1541
  function WorkspaceArchiveFallbackIcon({
1133
1542
  className
1134
1543
  }) {
1135
- return /* @__PURE__ */ jsx2(
1544
+ return /* @__PURE__ */ jsx4(
1136
1545
  "img",
1137
1546
  {
1138
1547
  alt: "",
@@ -1147,7 +1556,7 @@ function WorkspaceArchiveFallbackIcon({
1147
1556
  function WorkspaceImageFallbackIcon({
1148
1557
  className
1149
1558
  }) {
1150
- return /* @__PURE__ */ jsx2(
1559
+ return /* @__PURE__ */ jsx4(
1151
1560
  "img",
1152
1561
  {
1153
1562
  alt: "",
@@ -1165,15 +1574,15 @@ function ExtensionDocumentIcon({
1165
1574
  }) {
1166
1575
  const extension = resolveWorkspaceFileExtension(entry.name).slice(0, 5).toUpperCase();
1167
1576
  const showExtension = extension.length > 0 && !iconClassName.includes("size-4");
1168
- return /* @__PURE__ */ jsxs2(
1577
+ return /* @__PURE__ */ jsxs3(
1169
1578
  "span",
1170
1579
  {
1171
1580
  "aria-hidden": "true",
1172
1581
  className: cn2("relative inline-block overflow-visible", iconClassName),
1173
1582
  children: [
1174
- /* @__PURE__ */ jsx2("span", { className: "absolute inset-[5%] rounded-[6px] border border-black/10 bg-linear-to-br from-white via-[#f8f8f8] to-[#ececec]" }),
1175
- /* @__PURE__ */ jsx2("span", { className: "absolute top-[5%] right-[5%] h-[28%] w-[28%] overflow-hidden rounded-tr-[6px]", children: /* @__PURE__ */ jsx2("span", { className: "absolute top-0 right-0 h-full w-full origin-top-right -skew-x-3 rounded-bl-[4px] border-b border-l border-black/10 bg-linear-to-br from-white to-[#d9d9d9]" }) }),
1176
- showExtension ? /* @__PURE__ */ jsx2("span", { className: "absolute right-[12%] bottom-[14%] left-[12%] truncate text-center text-[10px] leading-none font-semibold tracking-wide text-[#7a7a7a]", children: extension }) : null
1583
+ /* @__PURE__ */ jsx4("span", { className: "absolute inset-[5%] rounded-[6px] border border-black/10 bg-linear-to-br from-white via-[#f8f8f8] to-[#ececec]" }),
1584
+ /* @__PURE__ */ jsx4("span", { className: "absolute top-[5%] right-[5%] h-[28%] w-[28%] overflow-hidden rounded-tr-[6px]", children: /* @__PURE__ */ jsx4("span", { className: "absolute top-0 right-0 h-full w-full origin-top-right -skew-x-3 rounded-bl-[4px] border-b border-l border-black/10 bg-linear-to-br from-white to-[#d9d9d9]" }) }),
1585
+ showExtension ? /* @__PURE__ */ jsx4("span", { className: "absolute right-[12%] bottom-[14%] left-[12%] truncate text-center text-[10px] leading-none font-semibold tracking-wide text-[#7a7a7a]", children: extension }) : null
1177
1586
  ]
1178
1587
  }
1179
1588
  );
@@ -1202,7 +1611,7 @@ function workspaceFileManagerIconGridFrameClassName() {
1202
1611
  }
1203
1612
 
1204
1613
  // src/ui/WorkspaceFileManagerIconGrid.tsx
1205
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1614
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1206
1615
  function WorkspaceFileManagerIconGrid({
1207
1616
  contextMenuEntryPath,
1208
1617
  copy,
@@ -1227,14 +1636,14 @@ function WorkspaceFileManagerIconGrid({
1227
1636
  onEntryClick,
1228
1637
  onEntryPointerDown
1229
1638
  }) {
1230
- return /* @__PURE__ */ jsx3(
1639
+ return /* @__PURE__ */ jsx5(
1231
1640
  "div",
1232
1641
  {
1233
1642
  className: "grid auto-rows-min content-start items-start gap-x-2 gap-y-6 px-4 py-4",
1234
1643
  style: {
1235
1644
  gridTemplateColumns: `repeat(auto-fill, minmax(${workspaceFileManagerIconGridLayout.tileMinWidthPx}px, 1fr))`
1236
1645
  },
1237
- children: entries.map((entry) => /* @__PURE__ */ jsx3(
1646
+ children: entries.map((entry) => /* @__PURE__ */ jsx5(
1238
1647
  WorkspaceFileManagerIconTile,
1239
1648
  {
1240
1649
  contextMenuActive: contextMenuEntryPath === entry.path,
@@ -1289,7 +1698,7 @@ function WorkspaceFileManagerIconTile({
1289
1698
  onClick,
1290
1699
  onPointerDown
1291
1700
  }) {
1292
- const buttonRef = useRef3(null);
1701
+ const buttonRef = useRef4(null);
1293
1702
  const nameParts = splitWorkspaceFileName(entry.name);
1294
1703
  useLayoutEffect2(() => {
1295
1704
  if (!selected) {
@@ -1308,7 +1717,7 @@ function WorkspaceFileManagerIconTile({
1308
1717
  moveDragSource && "opacity-55",
1309
1718
  moveDragTarget && "bg-[var(--accent-bg)] text-[var(--text-primary)] outline outline-1 -outline-offset-1 outline-[var(--border-focus)]"
1310
1719
  );
1311
- const iconGraphic = /* @__PURE__ */ jsx3(
1720
+ const iconGraphic = /* @__PURE__ */ jsx5(
1312
1721
  WorkspaceFileEntryIcon,
1313
1722
  {
1314
1723
  entry,
@@ -1322,7 +1731,7 @@ function WorkspaceFileManagerIconTile({
1322
1731
  }
1323
1732
  );
1324
1733
  if (isInlineRenaming) {
1325
- return /* @__PURE__ */ jsxs3(
1734
+ return /* @__PURE__ */ jsxs4(
1326
1735
  "div",
1327
1736
  {
1328
1737
  "aria-label": entry.name,
@@ -1330,7 +1739,7 @@ function WorkspaceFileManagerIconTile({
1330
1739
  "data-workspace-file-entry-path": entry.path,
1331
1740
  children: [
1332
1741
  iconGraphic,
1333
- /* @__PURE__ */ jsx3(
1742
+ /* @__PURE__ */ jsx5(
1334
1743
  IconTileRenameInput,
1335
1744
  {
1336
1745
  copy,
@@ -1346,7 +1755,7 @@ function WorkspaceFileManagerIconTile({
1346
1755
  }
1347
1756
  );
1348
1757
  }
1349
- return /* @__PURE__ */ jsxs3(
1758
+ return /* @__PURE__ */ jsxs4(
1350
1759
  "button",
1351
1760
  {
1352
1761
  "aria-label": entry.name,
@@ -1373,9 +1782,9 @@ function WorkspaceFileManagerIconTile({
1373
1782
  },
1374
1783
  children: [
1375
1784
  iconGraphic,
1376
- /* @__PURE__ */ jsxs3("span", { className: "line-clamp-2 w-full break-all text-[13px] leading-[18px] text-[var(--text-primary)]", children: [
1377
- /* @__PURE__ */ jsx3("span", { children: nameParts.start }),
1378
- nameParts.end ? /* @__PURE__ */ jsx3("span", { children: nameParts.end }) : null
1785
+ /* @__PURE__ */ jsxs4("span", { className: "line-clamp-2 w-full break-all text-[13px] leading-[18px] text-[var(--text-primary)]", children: [
1786
+ /* @__PURE__ */ jsx5("span", { children: nameParts.start }),
1787
+ nameParts.end ? /* @__PURE__ */ jsx5("span", { children: nameParts.end }) : null
1379
1788
  ] })
1380
1789
  ]
1381
1790
  }
@@ -1392,7 +1801,7 @@ function IconTileRenameInput({
1392
1801
  }) {
1393
1802
  const nameParts = splitWorkspaceFileName(entry.name);
1394
1803
  const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
1395
- const inputRef = useRef3(null);
1804
+ const inputRef = useRef4(null);
1396
1805
  useLayoutEffect2(() => {
1397
1806
  const input = inputRef.current;
1398
1807
  if (!input) {
@@ -1406,8 +1815,8 @@ function IconTileRenameInput({
1406
1815
  }
1407
1816
  }, [hasFileExtension, nameParts.start.length]);
1408
1817
  const validationMessage = inlineRenameValidation === "required" ? copy.t("createNameRequired") : inlineRenameValidation === "invalid" ? copy.t("createNameInvalid") : null;
1409
- return /* @__PURE__ */ jsxs3("span", { className: "flex w-full min-w-0 flex-col gap-0.5", children: [
1410
- /* @__PURE__ */ jsx3(
1818
+ return /* @__PURE__ */ jsxs4("span", { className: "flex w-full min-w-0 flex-col gap-0.5", children: [
1819
+ /* @__PURE__ */ jsx5(
1411
1820
  "input",
1412
1821
  {
1413
1822
  "aria-invalid": inlineRenameValidation !== null,
@@ -1438,12 +1847,12 @@ function IconTileRenameInput({
1438
1847
  }
1439
1848
  }
1440
1849
  ),
1441
- validationMessage ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] leading-3 text-[var(--state-danger)]", children: validationMessage }) : null
1850
+ validationMessage ? /* @__PURE__ */ jsx5("span", { className: "text-[10px] leading-3 text-[var(--state-danger)]", children: validationMessage }) : null
1442
1851
  ] });
1443
1852
  }
1444
1853
 
1445
1854
  // src/ui/WorkspaceFileManagerPanels.tsx
1446
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1855
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1447
1856
  var workspaceFileManagerTableGridClassName = "grid-cols-[minmax(0,_1fr)_148px_96px]";
1448
1857
  var workspaceFileManagerTableGridStyle = {
1449
1858
  gridTemplateColumns: "minmax(0, 1fr) 148px 96px"
@@ -1485,6 +1894,7 @@ function WorkspaceFileManagerPanels({
1485
1894
  selectedPath,
1486
1895
  state,
1487
1896
  showDropOverlay,
1897
+ treeRows,
1488
1898
  onBlankContextMenu,
1489
1899
  onCancelInlineRename,
1490
1900
  onClearInlineRenameValidation,
@@ -1493,23 +1903,28 @@ function WorkspaceFileManagerPanels({
1493
1903
  onEntryDragStart,
1494
1904
  onMoveEntry,
1495
1905
  onOpenEntry,
1496
- onSelect
1906
+ onSelect,
1907
+ onToggleDirectoryExpanded
1497
1908
  }) {
1498
1909
  const { rootRef } = useWorkspaceFileManagerStackedLayout(
1499
1910
  workspaceFileManagerStackedBreakpoint
1500
1911
  );
1501
- const previewResizeRef = useRef4(null);
1502
- const [previewPaneWidth, setPreviewPaneWidth] = useState2(
1912
+ const previewResizeRef = useRef5(null);
1913
+ const [previewPaneWidth, setPreviewPaneWidth] = useState3(
1503
1914
  workspaceFileManagerPreviewDefaultWidth
1504
1915
  );
1505
- const [moveDragPreview, setMoveDragPreview] = useState2(null);
1506
- const lastEntryClickRef = useRef4(
1916
+ const [moveDragPreview, setMoveDragPreview] = useState3(null);
1917
+ const lastEntryClickRef = useRef5(
1507
1918
  null
1508
1919
  );
1509
- const moveDragAutoScrollRef = useRef4(null);
1510
- const moveDragRef = useRef4(null);
1511
- const suppressNextClickRef = useRef4(false);
1512
- const entriesRef = useRef4(state.entries);
1920
+ const moveDragAutoScrollRef = useRef5(null);
1921
+ const moveDragRef = useRef5(null);
1922
+ const suppressNextClickRef = useRef5(false);
1923
+ const moveTargetEntries = useMemo(
1924
+ () => layoutMode === "list" ? treeRows.flatMap((row) => row.kind === "entry" ? [row.entry] : []) : state.entries,
1925
+ [layoutMode, state.entries, treeRows]
1926
+ );
1927
+ const entriesRef = useRef5(moveTargetEntries);
1513
1928
  const nativeEntryDragEnabled = onEntryDragStart !== void 0 && (entryDragMode === "external" || entryDragMode === void 0 && !canMove);
1514
1929
  const internalMoveEnabled = canMove && !nativeEntryDragEnabled;
1515
1930
  const dateColumnLabel = resolveWorkspaceFileManagerDateColumnLabel(
@@ -1623,11 +2038,11 @@ function WorkspaceFileManagerPanels({
1623
2038
  },
1624
2039
  [entrySelectionEnabled, onOpenEntry, onSelect, shouldSuppressEntryClick]
1625
2040
  );
1626
- useEffect3(() => {
2041
+ useEffect4(() => {
1627
2042
  lastEntryClickRef.current = null;
1628
- entriesRef.current = state.entries;
1629
- }, [state.entries]);
1630
- useEffect3(() => {
2043
+ entriesRef.current = moveTargetEntries;
2044
+ }, [moveTargetEntries]);
2045
+ useEffect4(() => {
1631
2046
  function handleDocumentPointerMove(event) {
1632
2047
  const drag = moveDragRef.current;
1633
2048
  if (!drag) {
@@ -1793,7 +2208,7 @@ function WorkspaceFileManagerPanels({
1793
2208
  },
1794
2209
  []
1795
2210
  );
1796
- const tablePanel = /* @__PURE__ */ jsx4(
2211
+ const tablePanel = /* @__PURE__ */ jsx6(
1797
2212
  "div",
1798
2213
  {
1799
2214
  className: cn4(
@@ -1801,7 +2216,7 @@ function WorkspaceFileManagerPanels({
1801
2216
  useStackedPreview ? "h-[44%] max-h-[48%] flex-none" : "flex-1"
1802
2217
  ),
1803
2218
  onContextMenu: handleTablePanelContextMenu,
1804
- children: state.error ? /* @__PURE__ */ jsx4(FeedbackState, { message: state.error }) : state.isLoading && state.entries.length === 0 ? /* @__PURE__ */ jsx4(FeedbackState, { message: copy.t("loading") }) : state.entries.length === 0 ? /* @__PURE__ */ jsx4(FeedbackState, { message: copy.t("emptyDirectory") }) : /* @__PURE__ */ jsx4(ScrollArea, { className: "min-h-0 flex-1 [&_[data-orientation=vertical][data-slot=scroll-area-scrollbar]]:opacity-100", children: layoutMode === "icon" ? /* @__PURE__ */ jsx4(
2219
+ children: state.error ? /* @__PURE__ */ jsx6(FeedbackState, { message: state.error }) : state.isLoading && state.entries.length === 0 ? /* @__PURE__ */ jsx6(FeedbackState, { message: copy.t("loading") }) : state.entries.length === 0 ? /* @__PURE__ */ jsx6(FeedbackState, { message: copy.t("emptyDirectory") }) : /* @__PURE__ */ jsx6(ScrollArea, { className: "min-h-0 flex-1 [&_[data-orientation=vertical][data-slot=scroll-area-scrollbar]]:opacity-100", children: layoutMode === "icon" ? /* @__PURE__ */ jsx6(
1805
2220
  WorkspaceFileManagerIconGrid,
1806
2221
  {
1807
2222
  contextMenuEntryPath,
@@ -1827,8 +2242,8 @@ function WorkspaceFileManagerPanels({
1827
2242
  onEntryClick: handleEntryClick,
1828
2243
  onEntryPointerDown: handleEntryPointerDown
1829
2244
  }
1830
- ) : /* @__PURE__ */ jsxs4("div", { className: "flex flex-col", children: [
1831
- /* @__PURE__ */ jsxs4(
2245
+ ) : /* @__PURE__ */ jsxs5("div", { className: "flex flex-col", children: [
2246
+ /* @__PURE__ */ jsxs5(
1832
2247
  "div",
1833
2248
  {
1834
2249
  className: cn4(
@@ -1837,15 +2252,15 @@ function WorkspaceFileManagerPanels({
1837
2252
  ),
1838
2253
  style: tableGridStyle,
1839
2254
  children: [
1840
- /* @__PURE__ */ jsx4("span", { className: tableCellPaddingClassName, children: copy.t("nameLabel") }),
1841
- /* @__PURE__ */ jsx4(
2255
+ /* @__PURE__ */ jsx6("span", { className: tableCellPaddingClassName, children: copy.t("nameLabel") }),
2256
+ /* @__PURE__ */ jsx6(
1842
2257
  "span",
1843
2258
  {
1844
2259
  className: cn4("whitespace-nowrap", tableCellPaddingClassName),
1845
2260
  children: dateColumnLabel
1846
2261
  }
1847
2262
  ),
1848
- /* @__PURE__ */ jsx4(
2263
+ /* @__PURE__ */ jsx6(
1849
2264
  "span",
1850
2265
  {
1851
2266
  className: cn4("whitespace-nowrap", tableCellPaddingClassName),
@@ -1855,44 +2270,63 @@ function WorkspaceFileManagerPanels({
1855
2270
  ]
1856
2271
  }
1857
2272
  ),
1858
- state.entries.map((entry) => /* @__PURE__ */ jsx4(
1859
- EntryRow,
1860
- {
1861
- contextMenuActive: contextMenuEntryPath === entry.path,
1862
- copy,
1863
- arrangeMode,
1864
- dateLocale,
1865
- entry,
1866
- iconUrlByCacheKey,
1867
- canMove: internalMoveEnabled,
1868
- draggable: nativeEntryDragEnabled,
1869
- gridClassName: tableGridClassName,
1870
- gridStyle: tableGridStyle,
1871
- inlineRenameValidation: inlineRenameEntryPath === entry.path ? inlineRenameValidation : null,
1872
- isEnteringDirectory: pendingDirectoryPath === entry.path,
1873
- isInlineRenaming: inlineRenameEntryPath === entry.path,
1874
- isRenaming,
1875
- moveDragActive: moveDragPreview !== null,
1876
- moveDragSource: moveDragPreview?.entry.path === entry.path,
1877
- moveDragTarget: moveDragPreview?.targetDirectoryPath === entry.path,
1878
- tableCellPaddingClassName,
1879
- selected: selectedPath === entry.path,
1880
- onEntryIconViewportLeave,
1881
- onEntryIconViewportEnter,
1882
- onCancelInlineRename,
1883
- onClearInlineRenameValidation,
1884
- onConfirmInlineRename,
1885
- onContextMenu: onEntryContextMenu,
1886
- onDragStart: onEntryDragStart,
1887
- onClick: handleEntryClick,
1888
- onPointerDown: handleEntryPointerDown
1889
- },
1890
- entry.path
1891
- ))
2273
+ treeRows.map(
2274
+ (row) => row.kind === "feedback" ? /* @__PURE__ */ jsx6(
2275
+ TreeFeedbackRow,
2276
+ {
2277
+ copy,
2278
+ depth: row.depth,
2279
+ gridClassName: tableGridClassName,
2280
+ gridStyle: tableGridStyle,
2281
+ message: row.message,
2282
+ status: row.status,
2283
+ tableCellPaddingClassName
2284
+ },
2285
+ row.key
2286
+ ) : /* @__PURE__ */ jsx6(
2287
+ EntryRow,
2288
+ {
2289
+ contextMenuActive: contextMenuEntryPath === row.entry.path,
2290
+ copy,
2291
+ arrangeMode,
2292
+ dateLocale,
2293
+ depth: row.depth,
2294
+ entry: row.entry,
2295
+ expanded: row.expanded,
2296
+ expandable: row.expandable,
2297
+ iconUrlByCacheKey,
2298
+ canMove: internalMoveEnabled,
2299
+ draggable: nativeEntryDragEnabled,
2300
+ gridClassName: tableGridClassName,
2301
+ gridStyle: tableGridStyle,
2302
+ inlineRenameValidation: inlineRenameEntryPath === row.entry.path ? inlineRenameValidation : null,
2303
+ isEnteringDirectory: pendingDirectoryPath === row.entry.path,
2304
+ isInlineRenaming: inlineRenameEntryPath === row.entry.path,
2305
+ isLoadingChildren: row.loadingChildren,
2306
+ isRenaming,
2307
+ moveDragActive: moveDragPreview !== null,
2308
+ moveDragSource: moveDragPreview?.entry.path === row.entry.path,
2309
+ moveDragTarget: moveDragPreview?.targetDirectoryPath === row.entry.path,
2310
+ tableCellPaddingClassName,
2311
+ selected: selectedPath === row.entry.path,
2312
+ onEntryIconViewportLeave,
2313
+ onEntryIconViewportEnter,
2314
+ onCancelInlineRename,
2315
+ onClearInlineRenameValidation,
2316
+ onConfirmInlineRename,
2317
+ onContextMenu: onEntryContextMenu,
2318
+ onDragStart: onEntryDragStart,
2319
+ onClick: handleEntryClick,
2320
+ onPointerDown: handleEntryPointerDown,
2321
+ onToggleDirectoryExpanded
2322
+ },
2323
+ row.entry.path
2324
+ )
2325
+ )
1892
2326
  ] }) })
1893
2327
  }
1894
2328
  );
1895
- const previewPanel = showPreviewPanel ? /* @__PURE__ */ jsx4(
2329
+ const previewPanel = showPreviewPanel ? /* @__PURE__ */ jsx6(
1896
2330
  "aside",
1897
2331
  {
1898
2332
  className: cn4(
@@ -1901,7 +2335,7 @@ function WorkspaceFileManagerPanels({
1901
2335
  previewPaneClassName
1902
2336
  ),
1903
2337
  style: useStackedPreview ? void 0 : { width: previewPaneWidth },
1904
- children: /* @__PURE__ */ jsx4(
2338
+ children: /* @__PURE__ */ jsx6(
1905
2339
  PreviewPane,
1906
2340
  {
1907
2341
  copy,
@@ -1912,13 +2346,13 @@ function WorkspaceFileManagerPanels({
1912
2346
  )
1913
2347
  }
1914
2348
  ) : null;
1915
- return /* @__PURE__ */ jsxs4(
2349
+ return /* @__PURE__ */ jsxs5(
1916
2350
  "div",
1917
2351
  {
1918
2352
  className: "relative min-h-0 min-w-0 flex-1 overflow-hidden",
1919
2353
  ref: rootRef,
1920
2354
  children: [
1921
- /* @__PURE__ */ jsxs4(
2355
+ /* @__PURE__ */ jsxs5(
1922
2356
  "div",
1923
2357
  {
1924
2358
  className: cn4(
@@ -1927,7 +2361,7 @@ function WorkspaceFileManagerPanels({
1927
2361
  ),
1928
2362
  children: [
1929
2363
  tablePanel,
1930
- showPreviewPanel && !useStackedPreview ? /* @__PURE__ */ jsx4(
2364
+ showPreviewPanel && !useStackedPreview ? /* @__PURE__ */ jsx6(
1931
2365
  "div",
1932
2366
  {
1933
2367
  "aria-orientation": "vertical",
@@ -1946,7 +2380,7 @@ function WorkspaceFileManagerPanels({
1946
2380
  ]
1947
2381
  }
1948
2382
  ),
1949
- /* @__PURE__ */ jsx4(
2383
+ /* @__PURE__ */ jsx6(
1950
2384
  "div",
1951
2385
  {
1952
2386
  "aria-hidden": "true",
@@ -1954,10 +2388,10 @@ function WorkspaceFileManagerPanels({
1954
2388
  "pointer-events-none absolute inset-0 grid place-items-center rounded-[var(--workbench-window-radius,8px)] border border-dashed border-[var(--border-focus)] bg-[var(--accent-bg)] opacity-0 transition-opacity duration-150 ease-out",
1955
2389
  showDropOverlay && "opacity-100"
1956
2390
  ),
1957
- children: /* @__PURE__ */ jsx4("div", { className: "rounded-lg border border-[var(--border-1)] bg-[var(--background-fronted)] px-5 py-3 text-sm font-normal text-[var(--text-primary)] shadow-panel", children: copy.t("dropToImportLabel") })
2391
+ children: /* @__PURE__ */ jsx6("div", { className: "rounded-lg border border-[var(--border-1)] bg-[var(--background-fronted)] px-5 py-3 text-sm font-normal text-[var(--text-primary)] shadow-panel", children: copy.t("dropToImportLabel") })
1958
2392
  }
1959
2393
  ),
1960
- moveDragPreview ? /* @__PURE__ */ jsx4(
2394
+ moveDragPreview ? /* @__PURE__ */ jsx6(
1961
2395
  MoveDragPreview,
1962
2396
  {
1963
2397
  iconUrlByCacheKey,
@@ -1971,8 +2405,8 @@ function WorkspaceFileManagerPanels({
1971
2405
  );
1972
2406
  }
1973
2407
  function useWorkspaceFileManagerStackedLayout(breakpoint) {
1974
- const rootRef = useRef4(null);
1975
- const [isStacked, setIsStacked] = useState2(false);
2408
+ const rootRef = useRef5(null);
2409
+ const [isStacked, setIsStacked] = useState3(false);
1976
2410
  useLayoutEffect3(() => {
1977
2411
  const element = rootRef.current;
1978
2412
  if (!element) {
@@ -2018,14 +2452,18 @@ function EntryRow({
2018
2452
  contextMenuActive,
2019
2453
  copy,
2020
2454
  dateLocale,
2455
+ depth,
2021
2456
  draggable,
2022
2457
  entry,
2458
+ expanded,
2459
+ expandable,
2023
2460
  gridClassName,
2024
2461
  gridStyle,
2025
2462
  iconUrlByCacheKey,
2026
2463
  inlineRenameValidation,
2027
2464
  isEnteringDirectory,
2028
2465
  isInlineRenaming,
2466
+ isLoadingChildren,
2029
2467
  isRenaming,
2030
2468
  moveDragActive,
2031
2469
  moveDragSource,
@@ -2040,10 +2478,11 @@ function EntryRow({
2040
2478
  onContextMenu,
2041
2479
  onDragStart,
2042
2480
  onClick,
2043
- onPointerDown
2481
+ onPointerDown,
2482
+ onToggleDirectoryExpanded
2044
2483
  }) {
2045
- const buttonRowRef = useRef4(null);
2046
- const divRowRef = useRef4(null);
2484
+ const buttonRowRef = useRef5(null);
2485
+ const divRowRef = useRef5(null);
2047
2486
  useLayoutEffect3(() => {
2048
2487
  if (!selected) {
2049
2488
  return;
@@ -2073,7 +2512,7 @@ function EntryRow({
2073
2512
  onContextMenu(event, entry);
2074
2513
  }
2075
2514
  };
2076
- const nameCell = /* @__PURE__ */ jsx4("span", { className: tableCellPaddingClassName, children: /* @__PURE__ */ jsx4(
2515
+ const nameCell = /* @__PURE__ */ jsx6("span", { className: tableCellPaddingClassName, children: /* @__PURE__ */ jsx6(
2077
2516
  EntryNameCell,
2078
2517
  {
2079
2518
  copy,
@@ -2081,16 +2520,21 @@ function EntryRow({
2081
2520
  iconUrlByCacheKey,
2082
2521
  inlineRenameValidation,
2083
2522
  isEnteringDirectory,
2523
+ isExpanded: expanded,
2524
+ isExpandable: expandable,
2084
2525
  isInlineRenaming,
2526
+ isLoadingChildren,
2085
2527
  isRenaming,
2528
+ treeDepth: depth,
2086
2529
  onEntryIconViewportLeave,
2087
2530
  onEntryIconViewportEnter,
2088
2531
  onCancelInlineRename,
2089
2532
  onClearInlineRenameValidation,
2090
- onConfirmInlineRename
2533
+ onConfirmInlineRename,
2534
+ onToggleDirectoryExpanded
2091
2535
  }
2092
2536
  ) });
2093
- const modifiedCell = /* @__PURE__ */ jsx4(
2537
+ const modifiedCell = /* @__PURE__ */ jsx6(
2094
2538
  "span",
2095
2539
  {
2096
2540
  className: cn4(
@@ -2103,7 +2547,7 @@ function EntryRow({
2103
2547
  )
2104
2548
  }
2105
2549
  );
2106
- const sizeCell = /* @__PURE__ */ jsx4(
2550
+ const sizeCell = /* @__PURE__ */ jsx6(
2107
2551
  "span",
2108
2552
  {
2109
2553
  className: cn4(
@@ -2114,13 +2558,13 @@ function EntryRow({
2114
2558
  }
2115
2559
  );
2116
2560
  if (isInlineRenaming) {
2117
- return /* @__PURE__ */ jsxs4("div", { ...rowProps, ref: divRowRef, children: [
2561
+ return /* @__PURE__ */ jsxs5("div", { ...rowProps, ref: divRowRef, children: [
2118
2562
  nameCell,
2119
2563
  modifiedCell,
2120
2564
  sizeCell
2121
2565
  ] });
2122
2566
  }
2123
- return /* @__PURE__ */ jsxs4(
2567
+ return /* @__PURE__ */ jsxs5(
2124
2568
  "button",
2125
2569
  {
2126
2570
  ...rowProps,
@@ -2153,7 +2597,7 @@ function MoveDragPreview({
2153
2597
  onEntryIconViewportLeave,
2154
2598
  onEntryIconViewportEnter
2155
2599
  }) {
2156
- return /* @__PURE__ */ jsxs4(
2600
+ return /* @__PURE__ */ jsxs5(
2157
2601
  "div",
2158
2602
  {
2159
2603
  "aria-hidden": "true",
@@ -2164,7 +2608,7 @@ function MoveDragPreview({
2164
2608
  transform: "translate(12px, 12px)"
2165
2609
  },
2166
2610
  children: [
2167
- /* @__PURE__ */ jsx4(
2611
+ /* @__PURE__ */ jsx6(
2168
2612
  WorkspaceFileEntryIcon,
2169
2613
  {
2170
2614
  entry: preview.entry,
@@ -2175,7 +2619,7 @@ function MoveDragPreview({
2175
2619
  onViewportEnter: onEntryIconViewportEnter
2176
2620
  }
2177
2621
  ),
2178
- /* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: preview.entry.name })
2622
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 truncate", children: preview.entry.name })
2179
2623
  ]
2180
2624
  }
2181
2625
  );
@@ -2215,26 +2659,114 @@ function resolveMoveDragTargetEntry(entries, movedEntry, x, y) {
2215
2659
  function canMoveEntryToDirectory(movedEntry, targetEntry) {
2216
2660
  return movedEntry.kind !== "unknown" && targetEntry.kind === "directory" && movedEntry.path !== targetEntry.path && !targetEntry.path.startsWith(`${movedEntry.path}/`);
2217
2661
  }
2662
+ function TreeFeedbackRow({
2663
+ copy,
2664
+ depth,
2665
+ gridClassName,
2666
+ gridStyle,
2667
+ message,
2668
+ status,
2669
+ tableCellPaddingClassName
2670
+ }) {
2671
+ const resolvedMessage = status === "loading" ? copy.t("loading") : status === "empty" ? copy.t("emptyDirectory") : message ?? copy.t("unknownErrorMessage");
2672
+ return /* @__PURE__ */ jsxs5(
2673
+ "div",
2674
+ {
2675
+ className: cn4(
2676
+ "grid min-h-9 w-full items-center gap-x-6 border-b border-[var(--border-1)] text-left text-xs text-[var(--text-tertiary)]",
2677
+ gridClassName
2678
+ ),
2679
+ style: gridStyle,
2680
+ children: [
2681
+ /* @__PURE__ */ jsx6("span", { className: tableCellPaddingClassName, children: /* @__PURE__ */ jsxs5(
2682
+ "span",
2683
+ {
2684
+ className: "flex min-w-0 items-center gap-2",
2685
+ style: workspaceFileManagerTreeIndentStyle(depth),
2686
+ children: [
2687
+ /* @__PURE__ */ jsx6("span", { className: "size-5 shrink-0" }),
2688
+ status === "loading" ? /* @__PURE__ */ jsx6(LoadingIcon2, { className: "size-3.5 shrink-0 animate-spin" }) : null,
2689
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 truncate", children: resolvedMessage })
2690
+ ]
2691
+ }
2692
+ ) }),
2693
+ /* @__PURE__ */ jsx6("span", { "aria-hidden": "true" }),
2694
+ /* @__PURE__ */ jsx6("span", { "aria-hidden": "true" })
2695
+ ]
2696
+ }
2697
+ );
2698
+ }
2699
+ function DirectoryDisclosureButton({
2700
+ copy,
2701
+ entry,
2702
+ expanded,
2703
+ isLoading,
2704
+ show,
2705
+ onToggle
2706
+ }) {
2707
+ if (!show) {
2708
+ return /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", className: "size-5 shrink-0" });
2709
+ }
2710
+ return /* @__PURE__ */ jsx6(
2711
+ "button",
2712
+ {
2713
+ "aria-label": copy.t(
2714
+ expanded ? "collapseFolderLabel" : "expandFolderLabel"
2715
+ ),
2716
+ "aria-expanded": expanded,
2717
+ className: "grid size-5 shrink-0 place-items-center rounded text-[var(--text-tertiary)] transition-colors hover:bg-transparency-block hover:text-[var(--text-primary)]",
2718
+ disabled: isLoading,
2719
+ type: "button",
2720
+ onClick: (event) => {
2721
+ event.preventDefault();
2722
+ event.stopPropagation();
2723
+ onToggle(entry, expanded);
2724
+ },
2725
+ onPointerDown: (event) => {
2726
+ event.stopPropagation();
2727
+ },
2728
+ children: isLoading ? /* @__PURE__ */ jsx6(LoadingIcon2, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx6(
2729
+ ArrowRightIcon2,
2730
+ {
2731
+ className: cn4(
2732
+ "size-3.5 transition-transform",
2733
+ expanded && "rotate-90"
2734
+ )
2735
+ }
2736
+ )
2737
+ }
2738
+ );
2739
+ }
2740
+ function workspaceFileManagerTreeIndentStyle(depth) {
2741
+ return {
2742
+ paddingLeft: `${Math.max(0, depth) * 20}px`
2743
+ };
2744
+ }
2218
2745
  function EntryNameCell({
2219
2746
  copy,
2220
2747
  entry,
2221
2748
  iconUrlByCacheKey,
2222
2749
  inlineRenameValidation = null,
2223
2750
  isEnteringDirectory = false,
2751
+ isExpanded = false,
2752
+ isExpandable = false,
2224
2753
  isInlineRenaming = false,
2754
+ isLoadingChildren = false,
2225
2755
  isRenaming = false,
2756
+ treeDepth = 0,
2226
2757
  onEntryIconViewportLeave,
2227
2758
  onEntryIconViewportEnter,
2228
2759
  onCancelInlineRename,
2229
2760
  onClearInlineRenameValidation,
2230
- onConfirmInlineRename
2761
+ onConfirmInlineRename,
2762
+ onToggleDirectoryExpanded
2231
2763
  }) {
2232
2764
  const nameParts = splitWorkspaceFileName(entry.name);
2233
2765
  const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
2234
- const [name, setName] = useState2(entry.name);
2235
- const inputRef = useRef4(null);
2236
- const skipBlurRef = useRef4(false);
2237
- useEffect3(() => {
2766
+ const [name, setName] = useState3(entry.name);
2767
+ const inputRef = useRef5(null);
2768
+ const skipBlurRef = useRef5(false);
2769
+ useEffect4(() => {
2238
2770
  if (!isInlineRenaming) {
2239
2771
  return;
2240
2772
  }
@@ -2253,99 +2785,135 @@ function EntryNameCell({
2253
2785
  if (hasFileExtension) {
2254
2786
  input.setSelectionRange(0, nameParts.start.length);
2255
2787
  } else {
2256
- input.select();
2257
- }
2258
- }, [hasFileExtension, isInlineRenaming, nameParts.start.length]);
2259
- const validationMessage = inlineRenameValidation === "required" ? copy.t("createNameRequired") : inlineRenameValidation === "invalid" ? copy.t("createNameInvalid") : null;
2260
- const handleConfirm = useCallback2(async () => {
2261
- const confirmed = await onConfirmInlineRename(name);
2262
- if (!confirmed) {
2263
- inputRef.current?.focus();
2264
- }
2265
- }, [name, onConfirmInlineRename]);
2266
- if (isInlineRenaming) {
2267
- return /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2", children: [
2268
- /* @__PURE__ */ jsx4(
2269
- WorkspaceFileEntryIcon,
2270
- {
2271
- entry,
2272
- frameClassName: "size-7",
2273
- iconClassName: "size-6",
2274
- iconUrlByCacheKey,
2275
- onViewportLeave: onEntryIconViewportLeave,
2276
- onViewportEnter: onEntryIconViewportEnter
2277
- }
2278
- ),
2279
- /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2280
- /* @__PURE__ */ jsx4(
2281
- "input",
2282
- {
2283
- "aria-invalid": inlineRenameValidation !== null,
2284
- "aria-label": copy.t("renameLabel"),
2285
- className: cn4(
2286
- "min-w-0 max-w-full rounded-md border border-transparent bg-[var(--transparency-block)] px-1.5 py-0.5 text-sm text-[var(--text-primary)] outline-none transition-colors duration-200 selection:bg-[var(--transparency-active)] selection:text-[var(--text-primary)] hover:bg-[var(--transparency-hover)] focus:bg-[var(--transparency-hover)] focus-visible:border-transparent focus-visible:bg-[var(--transparency-hover)] disabled:bg-[var(--transparency-block)] disabled:text-[var(--text-disabled)] disabled:opacity-100",
2287
- inlineRenameValidation !== null && "border-[var(--state-danger)]"
2288
- ),
2289
- disabled: isRenaming,
2290
- ref: inputRef,
2291
- value: name,
2292
- onBlur: () => {
2293
- if (skipBlurRef.current) {
2294
- skipBlurRef.current = false;
2295
- return;
2296
- }
2297
- void handleConfirm();
2298
- },
2299
- onChange: (event) => {
2300
- setName(event.currentTarget.value);
2301
- onClearInlineRenameValidation();
2302
- },
2303
- onFocus: (event) => {
2304
- if (hasFileExtension) {
2305
- event.currentTarget.setSelectionRange(
2306
- 0,
2307
- nameParts.start.length
2308
- );
2309
- } else {
2310
- event.currentTarget.select();
2311
- }
2312
- },
2313
- onKeyDown: (event) => {
2314
- if (event.key === "Enter") {
2315
- event.preventDefault();
2316
- void handleConfirm();
2317
- return;
2318
- }
2319
- if (event.key === "Escape") {
2320
- event.preventDefault();
2321
- skipBlurRef.current = true;
2322
- onCancelInlineRename();
2323
- }
2788
+ input.select();
2789
+ }
2790
+ }, [hasFileExtension, isInlineRenaming, nameParts.start.length]);
2791
+ const validationMessage = inlineRenameValidation === "required" ? copy.t("createNameRequired") : inlineRenameValidation === "invalid" ? copy.t("createNameInvalid") : null;
2792
+ const handleConfirm = useCallback2(async () => {
2793
+ const confirmed = await onConfirmInlineRename(name);
2794
+ if (!confirmed) {
2795
+ inputRef.current?.focus();
2796
+ }
2797
+ }, [name, onConfirmInlineRename]);
2798
+ if (isInlineRenaming) {
2799
+ return /* @__PURE__ */ jsxs5(
2800
+ "span",
2801
+ {
2802
+ className: "flex min-w-0 items-center gap-1.5",
2803
+ style: workspaceFileManagerTreeIndentStyle(treeDepth),
2804
+ children: [
2805
+ /* @__PURE__ */ jsx6(
2806
+ DirectoryDisclosureButton,
2807
+ {
2808
+ copy,
2809
+ entry,
2810
+ expanded: isExpanded,
2811
+ isLoading: isLoadingChildren,
2812
+ show: isExpandable,
2813
+ onToggle: onToggleDirectoryExpanded
2814
+ }
2815
+ ),
2816
+ /* @__PURE__ */ jsx6(
2817
+ WorkspaceFileEntryIcon,
2818
+ {
2819
+ entry,
2820
+ frameClassName: "size-7",
2821
+ iconClassName: "size-6",
2822
+ iconUrlByCacheKey,
2823
+ onViewportLeave: onEntryIconViewportLeave,
2824
+ onViewportEnter: onEntryIconViewportEnter
2324
2825
  }
2826
+ ),
2827
+ /* @__PURE__ */ jsxs5("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2828
+ /* @__PURE__ */ jsx6(
2829
+ "input",
2830
+ {
2831
+ "aria-invalid": inlineRenameValidation !== null,
2832
+ "aria-label": copy.t("renameLabel"),
2833
+ className: cn4(
2834
+ "min-w-0 max-w-full rounded-md border border-transparent bg-[var(--transparency-block)] px-1.5 py-0.5 text-sm text-[var(--text-primary)] outline-none transition-colors duration-200 selection:bg-[var(--transparency-active)] selection:text-[var(--text-primary)] hover:bg-[var(--transparency-hover)] focus:bg-[var(--transparency-hover)] focus-visible:border-transparent focus-visible:bg-[var(--transparency-hover)] disabled:bg-[var(--transparency-block)] disabled:text-[var(--text-disabled)] disabled:opacity-100",
2835
+ inlineRenameValidation !== null && "border-[var(--state-danger)]"
2836
+ ),
2837
+ disabled: isRenaming,
2838
+ ref: inputRef,
2839
+ value: name,
2840
+ onBlur: () => {
2841
+ if (skipBlurRef.current) {
2842
+ skipBlurRef.current = false;
2843
+ return;
2844
+ }
2845
+ void handleConfirm();
2846
+ },
2847
+ onChange: (event) => {
2848
+ setName(event.currentTarget.value);
2849
+ onClearInlineRenameValidation();
2850
+ },
2851
+ onFocus: (event) => {
2852
+ if (hasFileExtension) {
2853
+ event.currentTarget.setSelectionRange(
2854
+ 0,
2855
+ nameParts.start.length
2856
+ );
2857
+ } else {
2858
+ event.currentTarget.select();
2859
+ }
2860
+ },
2861
+ onKeyDown: (event) => {
2862
+ if (event.key === "Enter") {
2863
+ event.preventDefault();
2864
+ void handleConfirm();
2865
+ return;
2866
+ }
2867
+ if (event.key === "Escape") {
2868
+ event.preventDefault();
2869
+ skipBlurRef.current = true;
2870
+ onCancelInlineRename();
2871
+ }
2872
+ }
2873
+ }
2874
+ ),
2875
+ validationMessage ? /* @__PURE__ */ jsx6("span", { className: "text-xs text-[var(--state-danger)]", children: validationMessage }) : null
2876
+ ] })
2877
+ ]
2878
+ }
2879
+ );
2880
+ }
2881
+ return /* @__PURE__ */ jsxs5(
2882
+ "span",
2883
+ {
2884
+ className: "flex min-w-0 items-center gap-1.5",
2885
+ style: workspaceFileManagerTreeIndentStyle(treeDepth),
2886
+ children: [
2887
+ /* @__PURE__ */ jsx6(
2888
+ DirectoryDisclosureButton,
2889
+ {
2890
+ copy,
2891
+ entry,
2892
+ expanded: isExpanded,
2893
+ isLoading: isLoadingChildren,
2894
+ show: isExpandable,
2895
+ onToggle: onToggleDirectoryExpanded
2325
2896
  }
2326
2897
  ),
2327
- validationMessage ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-[var(--state-danger)]", children: validationMessage }) : null
2328
- ] })
2329
- ] });
2330
- }
2331
- return /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2", children: [
2332
- /* @__PURE__ */ jsx4(
2333
- WorkspaceFileEntryIcon,
2334
- {
2335
- entry,
2336
- frameClassName: "size-7",
2337
- iconClassName: "size-6",
2338
- iconUrlByCacheKey,
2339
- isEnteringDirectory,
2340
- onViewportLeave: onEntryIconViewportLeave,
2341
- onViewportEnter: onEntryIconViewportEnter
2342
- }
2343
- ),
2344
- /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
2345
- /* @__PURE__ */ jsx4("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: nameParts.start }),
2346
- nameParts.end ? /* @__PURE__ */ jsx4("span", { className: "flex-none overflow-hidden text-ellipsis", children: nameParts.end }) : null
2347
- ] })
2348
- ] });
2898
+ /* @__PURE__ */ jsx6(
2899
+ WorkspaceFileEntryIcon,
2900
+ {
2901
+ entry,
2902
+ frameClassName: "size-7",
2903
+ iconClassName: "size-6",
2904
+ iconUrlByCacheKey,
2905
+ isEnteringDirectory,
2906
+ onViewportLeave: onEntryIconViewportLeave,
2907
+ onViewportEnter: onEntryIconViewportEnter
2908
+ }
2909
+ ),
2910
+ /* @__PURE__ */ jsxs5("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
2911
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: nameParts.start }),
2912
+ nameParts.end ? /* @__PURE__ */ jsx6("span", { className: "flex-none overflow-hidden text-ellipsis", children: nameParts.end }) : null
2913
+ ] })
2914
+ ]
2915
+ }
2916
+ );
2349
2917
  }
2350
2918
  function EntryIcon({
2351
2919
  className = "size-4",
@@ -2353,20 +2921,20 @@ function EntryIcon({
2353
2921
  }) {
2354
2922
  switch (visualKind) {
2355
2923
  case "directory":
2356
- return /* @__PURE__ */ jsx4(WorkspaceFolderFallbackIcon, { className });
2924
+ return /* @__PURE__ */ jsx6(WorkspaceFolderFallbackIcon, { className });
2357
2925
  case "image":
2358
- return /* @__PURE__ */ jsx4(WorkspaceImageFallbackIcon, { className });
2926
+ return /* @__PURE__ */ jsx6(WorkspaceImageFallbackIcon, { className });
2359
2927
  case "video":
2360
- return /* @__PURE__ */ jsx4(VideoFileIcon2, { className });
2928
+ return /* @__PURE__ */ jsx6(VideoFileIcon2, { className });
2361
2929
  case "markdown":
2362
2930
  case "document":
2363
- return /* @__PURE__ */ jsx4(FileTextIcon2, { className });
2931
+ return /* @__PURE__ */ jsx6(FileTextIcon2, { className });
2364
2932
  case "code":
2365
- return /* @__PURE__ */ jsx4(FileCodeIcon2, { className });
2933
+ return /* @__PURE__ */ jsx6(FileCodeIcon2, { className });
2366
2934
  case "binary":
2367
- return /* @__PURE__ */ jsx4(FileTextIcon2, { className });
2935
+ return /* @__PURE__ */ jsx6(FileTextIcon2, { className });
2368
2936
  default:
2369
- return /* @__PURE__ */ jsx4(FileTextIcon2, { className });
2937
+ return /* @__PURE__ */ jsx6(FileTextIcon2, { className });
2370
2938
  }
2371
2939
  }
2372
2940
  function PreviewPane({
@@ -2376,24 +2944,24 @@ function PreviewPane({
2376
2944
  previewState
2377
2945
  }) {
2378
2946
  if (!entry || previewState.status === "empty") {
2379
- return /* @__PURE__ */ jsx4("div", { className: "grid h-full min-h-[180px] place-items-center overflow-hidden p-8 text-center text-sm leading-5 text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx4("span", { className: "max-w-[24ch] [overflow-wrap:anywhere]", children: copy.t("previewEmptyLabel") }) });
2947
+ return /* @__PURE__ */ jsx6("div", { className: "grid h-full min-h-[180px] place-items-center overflow-hidden p-8 text-center text-sm leading-5 text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx6("span", { className: "max-w-[24ch] [overflow-wrap:anywhere]", children: copy.t("previewEmptyLabel") }) });
2380
2948
  }
2381
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
2382
- /* @__PURE__ */ jsx4(PreviewSurface, { copy, previewState }),
2383
- /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-col gap-[14px]", children: [
2384
- /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-col gap-[3px]", children: [
2385
- /* @__PURE__ */ jsx4("strong", { className: "min-w-0 truncate text-[15px] font-semibold text-[var(--text-primary)]", children: entry.name }),
2386
- /* @__PURE__ */ jsx4("p", { className: "min-w-0 truncate text-xs text-[var(--text-secondary)]", children: entry.path })
2949
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2950
+ /* @__PURE__ */ jsx6(PreviewSurface, { copy, previewState }),
2951
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 flex-col gap-[14px]", children: [
2952
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 flex-col gap-[3px]", children: [
2953
+ /* @__PURE__ */ jsx6("strong", { className: "min-w-0 truncate text-[15px] font-semibold text-[var(--text-primary)]", children: entry.name }),
2954
+ /* @__PURE__ */ jsx6("p", { className: "min-w-0 truncate text-xs text-[var(--text-secondary)]", children: entry.path })
2387
2955
  ] }),
2388
- /* @__PURE__ */ jsxs4("dl", { className: "border-t border-[var(--border-1)]", children: [
2389
- /* @__PURE__ */ jsx4(
2956
+ /* @__PURE__ */ jsxs5("dl", { className: "border-t border-[var(--border-1)]", children: [
2957
+ /* @__PURE__ */ jsx6(
2390
2958
  PreviewDetail,
2391
2959
  {
2392
2960
  label: copy.t("modifiedLabel"),
2393
2961
  value: formatWorkspaceFileModifiedTime(entry.mtimeMs, dateLocale)
2394
2962
  }
2395
2963
  ),
2396
- /* @__PURE__ */ jsx4(
2964
+ /* @__PURE__ */ jsx6(
2397
2965
  PreviewDetail,
2398
2966
  {
2399
2967
  label: copy.t("sizeLabel"),
@@ -2408,7 +2976,7 @@ function PreviewSurface({
2408
2976
  copy,
2409
2977
  previewState
2410
2978
  }) {
2411
- return /* @__PURE__ */ jsx4(
2979
+ return /* @__PURE__ */ jsx6(
2412
2980
  SharedWorkspaceFilePreviewSurface,
2413
2981
  {
2414
2982
  directoryMessage: copy.t("previewDirectoryLabel"),
@@ -2416,10 +2984,10 @@ function PreviewSurface({
2416
2984
  frameClassName: "flex h-60 min-h-60 max-h-60 items-center justify-center overflow-hidden rounded-lg border border-[var(--border-1)] bg-[var(--transparency-block)]",
2417
2985
  imageAlt: (entry) => entry.name,
2418
2986
  imageFrameClassName: "p-4",
2419
- loadingIndicator: /* @__PURE__ */ jsx4("span", { className: "mx-auto grid size-11 place-items-center rounded-lg bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx4(LoadingIcon2, { className: "size-4 animate-spin" }) }),
2987
+ loadingIndicator: /* @__PURE__ */ jsx6("span", { className: "mx-auto grid size-11 place-items-center rounded-lg bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx6(LoadingIcon2, { className: "size-4 animate-spin" }) }),
2420
2988
  loadingMessage: copy.t("previewLoadingLabel"),
2421
2989
  messageClassName: "max-w-[24ch] [overflow-wrap:anywhere]",
2422
- renderIcon: (entry) => /* @__PURE__ */ jsx4(
2990
+ renderIcon: (entry) => /* @__PURE__ */ jsx6(
2423
2991
  EntryIcon,
2424
2992
  {
2425
2993
  className: "mx-auto size-7",
@@ -2435,26 +3003,26 @@ function resolveWorkspaceFilePreviewIconKind(entry) {
2435
3003
  if ("kind" in entry) {
2436
3004
  return resolveWorkspaceFileVisualKind(entry);
2437
3005
  }
2438
- return entry.fileKind === "image" ? "image" : "document";
3006
+ return entry.fileKind === "image" ? "image" : entry.fileKind === "video" ? "video" : "document";
2439
3007
  }
2440
3008
  function PreviewDetail({
2441
3009
  label,
2442
3010
  value
2443
3011
  }) {
2444
- return /* @__PURE__ */ jsxs4(
3012
+ return /* @__PURE__ */ jsxs5(
2445
3013
  "div",
2446
3014
  {
2447
3015
  className: "@max-[600px]/workspace-file-manager:grid-cols-1 @max-[600px]/workspace-file-manager:gap-0.5 grid gap-2.5 border-b border-[var(--border-1)] py-2.5 text-xs",
2448
3016
  style: workspaceFileManagerPreviewDetailGridStyle,
2449
3017
  children: [
2450
- /* @__PURE__ */ jsx4("dt", { className: "truncate text-[var(--text-secondary)]", children: label }),
2451
- /* @__PURE__ */ jsx4("dd", { className: "@max-[600px]/workspace-file-manager:text-left truncate text-right text-[var(--text-primary)]", children: value })
3018
+ /* @__PURE__ */ jsx6("dt", { className: "truncate text-[var(--text-secondary)]", children: label }),
3019
+ /* @__PURE__ */ jsx6("dd", { className: "@max-[600px]/workspace-file-manager:text-left truncate text-right text-[var(--text-primary)]", children: value })
2452
3020
  ]
2453
3021
  }
2454
3022
  );
2455
3023
  }
2456
3024
  function FeedbackState({ message }) {
2457
- return /* @__PURE__ */ jsx4("div", { className: "grid min-h-0 flex-1 place-items-center p-6 text-center text-sm text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx4("span", { className: "max-w-[34ch] [overflow-wrap:anywhere]", children: message }) });
3025
+ return /* @__PURE__ */ jsx6("div", { className: "grid min-h-0 flex-1 place-items-center p-6 text-center text-sm text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx6("span", { className: "max-w-[34ch] [overflow-wrap:anywhere]", children: message }) });
2458
3026
  }
2459
3027
  function hasFileDragPayload(dataTransfer) {
2460
3028
  return Array.from(dataTransfer.types).includes("Files");
@@ -2463,7 +3031,7 @@ function hasFileDragPayload(dataTransfer) {
2463
3031
  // src/ui/WorkspaceFileManagerToolbar.tsx
2464
3032
  import {
2465
3033
  ArrowLeftIcon,
2466
- ArrowRightIcon as ArrowRightIcon2,
3034
+ ArrowRightIcon as ArrowRightIcon3,
2467
3035
  Button as Button2,
2468
3036
  ChevronDownIcon,
2469
3037
  DropdownMenu,
@@ -2482,8 +3050,8 @@ import {
2482
3050
  ViewListLinedIcon,
2483
3051
  cn as cn5
2484
3052
  } from "@tutti-os/ui-system";
2485
- import { useState as useState3 } from "react";
2486
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3053
+ import { useState as useState4 } from "react";
3054
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2487
3055
  function WorkspaceFileManagerToolbar({
2488
3056
  breadcrumbs,
2489
3057
  canGoBack,
@@ -2502,39 +3070,39 @@ function WorkspaceFileManagerToolbar({
2502
3070
  onLoadDirectory,
2503
3071
  onRefresh
2504
3072
  }) {
2505
- const [refreshAnimationKey, setRefreshAnimationKey] = useState3(0);
3073
+ const [refreshAnimationKey, setRefreshAnimationKey] = useState4(0);
2506
3074
  const handleRefresh = () => {
2507
3075
  setRefreshAnimationKey((currentKey) => currentKey + 1);
2508
3076
  onRefresh();
2509
3077
  };
2510
- return /* @__PURE__ */ jsxs5("header", { className: "@max-[600px]/workspace-file-manager:flex-nowrap flex h-10 min-h-10 w-full min-w-0 items-center gap-2 border-b border-[var(--border-1)] px-2 py-1", children: [
2511
- /* @__PURE__ */ jsx5(
3078
+ return /* @__PURE__ */ jsxs6("header", { className: "@max-[600px]/workspace-file-manager:flex-nowrap flex h-10 min-h-10 w-full min-w-0 items-center gap-2 border-b border-[var(--border-1)] px-2 py-1", children: [
3079
+ /* @__PURE__ */ jsx7(
2512
3080
  ToolbarIconButton,
2513
3081
  {
2514
3082
  ariaLabel: copy.t("backLabel"),
2515
3083
  disabled: !canGoBack || isLoading || isBusy,
2516
3084
  title: copy.t("backLabel"),
2517
3085
  onClick: onGoBack,
2518
- children: /* @__PURE__ */ jsx5(ArrowLeftIcon, { className: "size-4" })
3086
+ children: /* @__PURE__ */ jsx7(ArrowLeftIcon, { className: "size-4" })
2519
3087
  }
2520
3088
  ),
2521
- /* @__PURE__ */ jsx5(
3089
+ /* @__PURE__ */ jsx7(
2522
3090
  ToolbarIconButton,
2523
3091
  {
2524
3092
  ariaLabel: copy.t("forwardLabel"),
2525
3093
  disabled: !canGoForward || isLoading || isBusy,
2526
3094
  title: copy.t("forwardLabel"),
2527
3095
  onClick: onGoForward,
2528
- children: /* @__PURE__ */ jsx5(ArrowRightIcon2, { className: "size-4" })
3096
+ children: /* @__PURE__ */ jsx7(ArrowRightIcon3, { className: "size-4" })
2529
3097
  }
2530
3098
  ),
2531
- /* @__PURE__ */ jsx5(
3099
+ /* @__PURE__ */ jsx7(
2532
3100
  "nav",
2533
3101
  {
2534
3102
  "aria-label": currentDirectoryPath,
2535
3103
  className: "@max-[600px]/workspace-file-manager:flex-auto flex min-w-0 flex-1 overflow-hidden pr-2",
2536
3104
  "data-workspace-file-manager-path": "",
2537
- children: /* @__PURE__ */ jsx5("ol", { className: "flex min-w-0 max-w-full flex-1 items-center gap-2 overflow-hidden", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsx5(
3105
+ children: /* @__PURE__ */ jsx7("ol", { className: "flex min-w-0 max-w-full flex-1 items-center gap-2 overflow-hidden", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsx7(
2538
3106
  BreadcrumbButton,
2539
3107
  {
2540
3108
  active: index === breadcrumbs.length - 1,
@@ -2548,8 +3116,8 @@ function WorkspaceFileManagerToolbar({
2548
3116
  )) })
2549
3117
  }
2550
3118
  ),
2551
- /* @__PURE__ */ jsxs5("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: [
2552
- /* @__PURE__ */ jsx5(
3119
+ /* @__PURE__ */ jsxs6("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: [
3120
+ /* @__PURE__ */ jsx7(
2553
3121
  LayoutModeToggle,
2554
3122
  {
2555
3123
  arrangeMode,
@@ -2559,13 +3127,13 @@ function WorkspaceFileManagerToolbar({
2559
3127
  onLayoutModeChange
2560
3128
  }
2561
3129
  ),
2562
- /* @__PURE__ */ jsxs5(
3130
+ /* @__PURE__ */ jsxs6(
2563
3131
  ToolbarActionButton,
2564
3132
  {
2565
3133
  disabled: isLoading || isMutating || isBusy,
2566
3134
  onClick: handleRefresh,
2567
3135
  children: [
2568
- isLoading ? /* @__PURE__ */ jsx5(LoadingIcon3, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx5(
3136
+ isLoading ? /* @__PURE__ */ jsx7(LoadingIcon3, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx7(
2569
3137
  RefreshIcon,
2570
3138
  {
2571
3139
  className: cn5(
@@ -2575,7 +3143,7 @@ function WorkspaceFileManagerToolbar({
2575
3143
  },
2576
3144
  refreshAnimationKey
2577
3145
  ),
2578
- /* @__PURE__ */ jsx5("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
3146
+ /* @__PURE__ */ jsx7("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
2579
3147
  ]
2580
3148
  }
2581
3149
  )
@@ -2600,13 +3168,13 @@ function LayoutModeToggle({
2600
3168
  { label: copy.t("arrangeCreatedLabel"), mode: "created" },
2601
3169
  { label: copy.t("sizeLabel"), mode: "size" }
2602
3170
  ];
2603
- return /* @__PURE__ */ jsx5(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs5(
3171
+ return /* @__PURE__ */ jsx7(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs6(
2604
3172
  "div",
2605
3173
  {
2606
3174
  className: "flex items-center rounded-md border border-[var(--border-1)] bg-[var(--transparency-block)] p-0.5",
2607
3175
  role: "group",
2608
3176
  children: [
2609
- /* @__PURE__ */ jsx5(
3177
+ /* @__PURE__ */ jsx7(
2610
3178
  LayoutModeButton,
2611
3179
  {
2612
3180
  active: layoutMode === "icon",
@@ -2615,10 +3183,10 @@ function LayoutModeToggle({
2615
3183
  onClick: () => {
2616
3184
  onLayoutModeChange("icon");
2617
3185
  },
2618
- children: /* @__PURE__ */ jsx5(ViewGridLinedIcon, { className: "size-4" })
3186
+ children: /* @__PURE__ */ jsx7(ViewGridLinedIcon, { className: "size-4" })
2619
3187
  }
2620
3188
  ),
2621
- /* @__PURE__ */ jsx5(
3189
+ /* @__PURE__ */ jsx7(
2622
3190
  LayoutModeButton,
2623
3191
  {
2624
3192
  active: layoutMode === "list",
@@ -2627,12 +3195,12 @@ function LayoutModeToggle({
2627
3195
  onClick: () => {
2628
3196
  onLayoutModeChange("list");
2629
3197
  },
2630
- children: /* @__PURE__ */ jsx5(ViewListLinedIcon, { className: "size-4" })
3198
+ children: /* @__PURE__ */ jsx7(ViewListLinedIcon, { className: "size-4" })
2631
3199
  }
2632
3200
  ),
2633
- /* @__PURE__ */ jsx5("span", { className: "mx-0.5 h-4 w-px bg-[var(--border-1)]", "aria-hidden": true }),
2634
- /* @__PURE__ */ jsxs5(DropdownMenu, { children: [
2635
- /* @__PURE__ */ jsx5(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(
3201
+ /* @__PURE__ */ jsx7("span", { className: "mx-0.5 h-4 w-px bg-[var(--border-1)]", "aria-hidden": true }),
3202
+ /* @__PURE__ */ jsxs6(DropdownMenu, { children: [
3203
+ /* @__PURE__ */ jsx7(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
2636
3204
  Button2,
2637
3205
  {
2638
3206
  "aria-label": copy.t("arrangeMenuLabel"),
@@ -2640,16 +3208,16 @@ function LayoutModeToggle({
2640
3208
  size: "icon-sm",
2641
3209
  type: "button",
2642
3210
  variant: "ghost",
2643
- children: /* @__PURE__ */ jsx5(ChevronDownIcon, { className: "size-3.5" })
3211
+ children: /* @__PURE__ */ jsx7(ChevronDownIcon, { className: "size-3.5" })
2644
3212
  }
2645
3213
  ) }),
2646
- /* @__PURE__ */ jsx5(
3214
+ /* @__PURE__ */ jsx7(
2647
3215
  DropdownMenuContent,
2648
3216
  {
2649
3217
  align: "end",
2650
3218
  className: "min-w-[236px] px-1 py-1",
2651
3219
  sideOffset: 7,
2652
- children: /* @__PURE__ */ jsx5(
3220
+ children: /* @__PURE__ */ jsx7(
2653
3221
  DropdownMenuRadioGroup,
2654
3222
  {
2655
3223
  value: arrangeMode,
@@ -2658,9 +3226,9 @@ function LayoutModeToggle({
2658
3226
  nextMode
2659
3227
  );
2660
3228
  },
2661
- children: arrangeOptions.map((option, index) => /* @__PURE__ */ jsxs5("div", { children: [
2662
- index === 1 ? /* @__PURE__ */ jsx5(DropdownMenuSeparator, {}) : null,
2663
- /* @__PURE__ */ jsx5(
3229
+ children: arrangeOptions.map((option, index) => /* @__PURE__ */ jsxs6("div", { children: [
3230
+ index === 1 ? /* @__PURE__ */ jsx7(DropdownMenuSeparator, {}) : null,
3231
+ /* @__PURE__ */ jsx7(
2664
3232
  DropdownMenuRadioItem,
2665
3233
  {
2666
3234
  className: "h-8 text-sm font-normal",
@@ -2685,8 +3253,8 @@ function LayoutModeButton({
2685
3253
  onClick,
2686
3254
  tooltipLabel
2687
3255
  }) {
2688
- return /* @__PURE__ */ jsxs5(Tooltip, { children: [
2689
- /* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(
3256
+ return /* @__PURE__ */ jsxs6(Tooltip, { children: [
3257
+ /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
2690
3258
  Button2,
2691
3259
  {
2692
3260
  "aria-label": ariaLabel,
@@ -2702,7 +3270,7 @@ function LayoutModeButton({
2702
3270
  children
2703
3271
  }
2704
3272
  ) }),
2705
- /* @__PURE__ */ jsx5(TooltipContent, { side: "bottom", children: tooltipLabel })
3273
+ /* @__PURE__ */ jsx7(TooltipContent, { side: "bottom", children: tooltipLabel })
2706
3274
  ] });
2707
3275
  }
2708
3276
  function ToolbarIconButton({
@@ -2712,7 +3280,7 @@ function ToolbarIconButton({
2712
3280
  onClick,
2713
3281
  title
2714
3282
  }) {
2715
- return /* @__PURE__ */ jsx5(
3283
+ return /* @__PURE__ */ jsx7(
2716
3284
  Button2,
2717
3285
  {
2718
3286
  "aria-label": ariaLabel,
@@ -2732,7 +3300,7 @@ function ToolbarActionButton({
2732
3300
  disabled,
2733
3301
  onClick
2734
3302
  }) {
2735
- return /* @__PURE__ */ jsx5(
3303
+ return /* @__PURE__ */ jsx7(
2736
3304
  Button2,
2737
3305
  {
2738
3306
  className: "@max-[600px]/workspace-file-manager:size-7 @max-[600px]/workspace-file-manager:min-w-7 @max-[600px]/workspace-file-manager:px-0 cursor-pointer",
@@ -2751,9 +3319,9 @@ function BreadcrumbButton({
2751
3319
  onClick,
2752
3320
  showSeparator
2753
3321
  }) {
2754
- return /* @__PURE__ */ jsxs5("li", { className: "flex min-w-0 items-center gap-2", children: [
2755
- showSeparator ? /* @__PURE__ */ jsx5("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
2756
- /* @__PURE__ */ jsx5(
3322
+ return /* @__PURE__ */ jsxs6("li", { className: "flex min-w-0 items-center gap-2", children: [
3323
+ showSeparator ? /* @__PURE__ */ jsx7("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
3324
+ /* @__PURE__ */ jsx7(
2757
3325
  "button",
2758
3326
  {
2759
3327
  className: cn5(
@@ -2769,9 +3337,9 @@ function BreadcrumbButton({
2769
3337
  }
2770
3338
 
2771
3339
  // src/ui/useWorkspaceFileManagerArrangeMode.ts
2772
- import { useCallback as useCallback3, useState as useState4 } from "react";
3340
+ import { useCallback as useCallback3, useState as useState5 } from "react";
2773
3341
  function useWorkspaceFileManagerArrangeMode() {
2774
- const [arrangeMode, setArrangeModeState] = useState4(
3342
+ const [arrangeMode, setArrangeModeState] = useState5(
2775
3343
  readWorkspaceFileManagerArrangeMode
2776
3344
  );
2777
3345
  const setArrangeMode = useCallback3(
@@ -2785,7 +3353,7 @@ function useWorkspaceFileManagerArrangeMode() {
2785
3353
  }
2786
3354
 
2787
3355
  // src/ui/useWorkspaceFileManagerLayoutMode.ts
2788
- import { useCallback as useCallback4, useState as useState5 } from "react";
3356
+ import { useCallback as useCallback4, useState as useState6 } from "react";
2789
3357
 
2790
3358
  // src/ui/workspaceFileManagerLayoutMode.ts
2791
3359
  var workspaceFileManagerLayoutModeStorageKey = "tutti.workspace-file-manager.layout-mode";
@@ -2810,7 +3378,7 @@ function writeWorkspaceFileManagerLayoutMode(layoutMode) {
2810
3378
 
2811
3379
  // src/ui/useWorkspaceFileManagerLayoutMode.ts
2812
3380
  function useWorkspaceFileManagerLayoutMode() {
2813
- const [layoutMode, setLayoutModeState] = useState5(
3381
+ const [layoutMode, setLayoutModeState] = useState6(
2814
3382
  readWorkspaceFileManagerLayoutMode
2815
3383
  );
2816
3384
  const setLayoutMode = useCallback4(
@@ -2824,7 +3392,7 @@ function useWorkspaceFileManagerLayoutMode() {
2824
3392
  }
2825
3393
 
2826
3394
  // src/ui/useWorkspaceFileEntryIconUrls.ts
2827
- import { useCallback as useCallback5, useEffect as useEffect4, useMemo, useState as useState6 } from "react";
3395
+ import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo2, useState as useState7 } from "react";
2828
3396
 
2829
3397
  // src/ui/workspaceFileEntryIconUrlQueue.ts
2830
3398
  var defaultMaxConcurrentIconRequests = 3;
@@ -3002,28 +3570,28 @@ function buildIconTargetSignature(entries, options) {
3002
3570
  }
3003
3571
  function useWorkspaceFileEntryIconUrls(input) {
3004
3572
  const { entries, includeImageThumbnails, resolveEntryIconUrl } = input;
3005
- const queue = useMemo(
3573
+ const queue = useMemo2(
3006
3574
  () => createWorkspaceFileEntryIconUrlQueue({
3007
3575
  includeImageThumbnails,
3008
3576
  resolveEntryIconUrl
3009
3577
  }),
3010
3578
  [includeImageThumbnails, resolveEntryIconUrl]
3011
3579
  );
3012
- const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState6(() => queue.snapshot());
3013
- const iconTargetSignature = useMemo(
3580
+ const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState7(() => queue.snapshot());
3581
+ const iconTargetSignature = useMemo2(
3014
3582
  () => buildIconTargetSignature(entries, { includeImageThumbnails }),
3015
3583
  [entries, includeImageThumbnails]
3016
3584
  );
3017
- useEffect4(() => {
3585
+ useEffect5(() => {
3018
3586
  queue.activate();
3019
3587
  return () => {
3020
3588
  queue.dispose();
3021
3589
  };
3022
3590
  }, [queue]);
3023
- useEffect4(() => {
3591
+ useEffect5(() => {
3024
3592
  queue.retainEntries(entries);
3025
3593
  }, [entries, iconTargetSignature, queue]);
3026
- useEffect4(() => {
3594
+ useEffect5(() => {
3027
3595
  setIconUrlByCacheKey(queue.snapshot());
3028
3596
  return queue.subscribe(() => {
3029
3597
  setIconUrlByCacheKey(queue.snapshot());
@@ -3037,185 +3605,100 @@ function useWorkspaceFileEntryIconUrls(input) {
3037
3605
  );
3038
3606
  const reportEntryIconViewportLeave = useCallback5(
3039
3607
  (entry) => {
3040
- queue.leaveViewport(entry);
3041
- },
3042
- [queue]
3043
- );
3044
- return {
3045
- iconUrlByCacheKey,
3046
- reportEntryIconViewportEnter,
3047
- reportEntryIconViewportLeave
3048
- };
3049
- }
3050
-
3051
- // src/ui/workspaceFileManagerAnalytics.ts
3052
- function shouldTrackDirectoryExpanded(input) {
3053
- return normalizeDirectoryPath(input.currentDirectoryPath) !== normalizeDirectoryPath(input.nextDirectoryPath);
3054
- }
3055
- function normalizeDirectoryPath(path) {
3056
- return path.trim().replaceAll("\\", "/").replace(/\/+$/u, "");
3057
- }
3058
-
3059
- // src/ui/useWorkspaceFileManagerService.ts
3060
- import { useSnapshot } from "valtio";
3061
-
3062
- // src/services/internal/workspaceFileManagerViewModel.ts
3063
- function resolveWorkspaceFileManagerRootViewState(input) {
3064
- const { state } = input;
3065
- return {
3066
- canImportFromDrop: state.capabilities.canImportFromDrop,
3067
- currentDirectoryPath: state.currentDirectoryPath,
3068
- isBusy: state.busyAction !== null
3069
- };
3070
- }
3071
- function resolveWorkspaceFileManagerToolbarViewState(input) {
3072
- const { copy, state } = input;
3073
- const currentDirectoryPath = normalizeWorkspaceFilePath(
3074
- state.currentDirectoryPath,
3075
- state.root
3076
- );
3077
- return {
3078
- breadcrumbs: buildWorkspaceFileBreadcrumbs(
3079
- currentDirectoryPath,
3080
- copy.t("breadcrumbRootLabel"),
3081
- state.root
3082
- ),
3083
- canGoBack: currentDirectoryPath !== normalizeWorkspaceFilePath(state.root) && state.navigationBackStack.length > 0,
3084
- canGoForward: state.navigationForwardStack.length > 0,
3085
- currentDirectoryPath,
3086
- isBusy: state.busyAction !== null,
3087
- isLoading: state.isLoading,
3088
- isMutating: state.isMutating,
3089
- isImporting: state.busyAction === "import",
3090
- showImportAction: state.capabilities.canImportFromPicker
3091
- };
3092
- }
3093
- function resolveWorkspaceFileManagerPanelsViewState(input) {
3094
- const { state } = input;
3095
- return {
3096
- canMove: state.capabilities.canMove,
3097
- contextMenuEntryPath: state.contextMenuEntryPath,
3098
- entries: state.entries,
3099
- error: state.error,
3100
- inlineRenameEntryPath: state.inlineRenameEntryPath,
3101
- inlineRenameValidation: state.inlineRenameValidation,
3102
- isLoading: state.isLoading,
3103
- isRenaming: state.busyAction === "rename",
3104
- pendingDirectoryPath: state.pendingDirectoryPath,
3105
- previewState: state.previewState,
3106
- selectedEntry: findSelectedEntry(state),
3107
- selectedPath: state.selectedPath,
3108
- showDropOverlay: state.capabilities.canImportFromDrop && state.dragDepth > 0 && state.busyAction === null
3109
- };
3110
- }
3111
- function resolveWorkspaceFileManagerDialogsViewState(input) {
3112
- const { state } = input;
3113
- const unsupportedDialogEntry = state.unsupportedDialog?.entryPath ? findEntry(state, state.unsupportedDialog.entryPath) : null;
3114
- return {
3115
- createDialog: state.createDialog,
3116
- deleteDialogEntry: state.deleteDialog ? findEntry(state, state.deleteDialog.entryPath) : null,
3117
- isBusy: state.busyAction !== null,
3118
- isDeleting: state.busyAction === "delete",
3119
- isImporting: state.busyAction === "import",
3120
- isRenaming: state.busyAction === "rename",
3121
- isViewing: state.busyAction === "view",
3122
- unsupportedDialog: state.unsupportedDialog ? {
3123
- actions: state.unsupportedDialog.actions,
3124
- entry: unsupportedDialogEntry ?? void 0,
3125
- kind: state.unsupportedDialog.kind,
3126
- message: state.unsupportedDialog.message,
3127
- title: state.unsupportedDialog.title
3128
- } : null,
3129
- importConflictDialog: state.importConflictDialog
3130
- };
3131
- }
3132
- function resolveWorkspaceFileManagerContextMenuViewState(input) {
3133
- const { state } = input;
3134
- const contextMenuEntry = state.contextMenu?.entryPath ? findEntry(state, state.contextMenu.entryPath) : null;
3135
- const isContextMenuFile = contextMenuEntry?.kind === "file";
3136
- return {
3137
- contextMenu: state.contextMenu ? {
3138
- entry: contextMenuEntry,
3139
- x: state.contextMenu.x,
3140
- y: state.contextMenu.y
3141
- } : null,
3142
- currentDirectoryPath: state.currentDirectoryPath,
3143
- isBusy: state.busyAction !== null,
3144
- isLoading: state.isLoading,
3145
- isMutating: state.isMutating,
3146
- showCopyAction: state.capabilities.canCopy,
3147
- showExportAction: state.capabilities.canExport,
3148
- showImportAction: state.capabilities.canImportFromPicker,
3149
- showMoveAction: state.capabilities.canMove,
3150
- showOpenInAppBrowserAction: state.capabilities.canOpenInAppBrowser,
3151
- showOpenInDefaultBrowserAction: state.capabilities.canOpenInDefaultBrowser,
3152
- showOpenWithAction: state.capabilities.canOpenWith && isContextMenuFile,
3153
- showOpenWithOtherAction: state.capabilities.canPickOtherOpenWithApplication && isContextMenuFile,
3154
- showRevealInFolderAction: state.capabilities.canRevealInFolder,
3155
- showRenameAction: state.capabilities.canRename
3156
- };
3157
- }
3158
- function findSelectedEntry(state) {
3159
- return state.entries.find((entry) => entry.path === state.selectedPath) ?? null;
3160
- }
3161
- function findEntry(state, entryPath) {
3162
- return state.entries.find((entry) => entry.path === entryPath) ?? null;
3163
- }
3164
-
3165
- // src/ui/useWorkspaceFileManagerService.ts
3166
- function useWorkspaceFileManagerRootView(session) {
3167
- const state = useSnapshot(
3168
- session.store
3169
- );
3170
- return resolveWorkspaceFileManagerRootViewState({ state });
3171
- }
3172
- function useWorkspaceFileManagerToolbarView(session, i18n) {
3173
- const state = useSnapshot(
3174
- session.store
3175
- );
3176
- return {
3177
- view: resolveWorkspaceFileManagerToolbarViewState({
3178
- copy: i18n,
3179
- state
3180
- })
3181
- };
3182
- }
3183
- function useWorkspaceFileManagerPanelsView(session) {
3184
- const state = useSnapshot(
3185
- session.store
3186
- );
3187
- return {
3188
- state,
3189
- view: resolveWorkspaceFileManagerPanelsViewState({
3190
- state
3191
- })
3192
- };
3193
- }
3194
- function useWorkspaceFileManagerDialogsView(session) {
3195
- const state = useSnapshot(
3196
- session.store
3197
- );
3198
- return {
3199
- state,
3200
- view: resolveWorkspaceFileManagerDialogsViewState({
3201
- state
3202
- })
3203
- };
3204
- }
3205
- function useWorkspaceFileManagerContextMenuView(session) {
3206
- const state = useSnapshot(
3207
- session.store
3608
+ queue.leaveViewport(entry);
3609
+ },
3610
+ [queue]
3208
3611
  );
3209
3612
  return {
3210
- state,
3211
- view: resolveWorkspaceFileManagerContextMenuViewState({
3212
- state
3213
- })
3613
+ iconUrlByCacheKey,
3614
+ reportEntryIconViewportEnter,
3615
+ reportEntryIconViewportLeave
3214
3616
  };
3215
3617
  }
3216
3618
 
3619
+ // src/ui/workspaceFileManagerAnalytics.ts
3620
+ function shouldTrackDirectoryExpanded(input) {
3621
+ return normalizeDirectoryPath(input.currentDirectoryPath) !== normalizeDirectoryPath(input.nextDirectoryPath);
3622
+ }
3623
+ function normalizeDirectoryPath(path) {
3624
+ return path.trim().replaceAll("\\", "/").replace(/\/+$/u, "");
3625
+ }
3626
+
3627
+ // src/ui/workspaceFileManagerVisibleTree.ts
3628
+ function buildWorkspaceFileManagerVisibleTreeRows(input) {
3629
+ return appendWorkspaceFileManagerVisibleTreeRows({
3630
+ ...input,
3631
+ depth: 0
3632
+ });
3633
+ }
3634
+ function collectWorkspaceFileManagerVisibleTreeEntries(rows) {
3635
+ return rows.flatMap((row) => row.kind === "entry" ? [row.entry] : []);
3636
+ }
3637
+ function appendWorkspaceFileManagerVisibleTreeRows(input) {
3638
+ const sortedEntries = sortWorkspaceFileEntriesForArrangeMode(
3639
+ input.entries,
3640
+ input.arrangeMode
3641
+ );
3642
+ const rows = [];
3643
+ for (const entry of sortedEntries) {
3644
+ const expandable = entry.kind === "directory" && entry.hasChildren;
3645
+ const expanded = expandable ? input.expandedDirectoryPaths[entry.path] === true : false;
3646
+ const childState = input.directoryExpansionByPath[entry.path];
3647
+ rows.push({
3648
+ depth: input.depth,
3649
+ entry,
3650
+ expanded,
3651
+ expandable,
3652
+ kind: "entry",
3653
+ loadingChildren: childState?.isLoading ?? false
3654
+ });
3655
+ if (!expanded) {
3656
+ continue;
3657
+ }
3658
+ if (!childState || childState.isLoading) {
3659
+ rows.push({
3660
+ depth: input.depth + 1,
3661
+ key: `${entry.path}:loading`,
3662
+ kind: "feedback",
3663
+ parentPath: entry.path,
3664
+ status: "loading"
3665
+ });
3666
+ continue;
3667
+ }
3668
+ if (childState.error) {
3669
+ rows.push({
3670
+ depth: input.depth + 1,
3671
+ key: `${entry.path}:error`,
3672
+ kind: "feedback",
3673
+ message: childState.error,
3674
+ parentPath: entry.path,
3675
+ status: "error"
3676
+ });
3677
+ continue;
3678
+ }
3679
+ if (childState.loaded && childState.entries.length === 0) {
3680
+ rows.push({
3681
+ depth: input.depth + 1,
3682
+ key: `${entry.path}:empty`,
3683
+ kind: "feedback",
3684
+ parentPath: entry.path,
3685
+ status: "empty"
3686
+ });
3687
+ continue;
3688
+ }
3689
+ rows.push(
3690
+ ...appendWorkspaceFileManagerVisibleTreeRows({
3691
+ ...input,
3692
+ depth: input.depth + 1,
3693
+ entries: childState.entries
3694
+ })
3695
+ );
3696
+ }
3697
+ return rows;
3698
+ }
3699
+
3217
3700
  // src/ui/WorkspaceFileManager.tsx
3218
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3701
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3219
3702
  function WorkspaceFileManager({
3220
3703
  className,
3221
3704
  dateLocale,
@@ -3232,12 +3715,12 @@ function WorkspaceFileManager({
3232
3715
  session,
3233
3716
  surface = "card"
3234
3717
  }) {
3235
- const rootRef = useRef5(null);
3718
+ const rootRef = useRef6(null);
3236
3719
  const { arrangeMode, setArrangeMode } = useWorkspaceFileManagerArrangeMode();
3237
3720
  const { layoutMode, setLayoutMode } = useWorkspaceFileManagerLayoutMode();
3238
3721
  const rootView = useWorkspaceFileManagerRootView(session);
3239
3722
  const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
3240
- useEffect5(() => {
3723
+ useEffect6(() => {
3241
3724
  function handleCopyShortcut(event) {
3242
3725
  if (!(event.metaKey || event.ctrlKey) || event.key !== "c" || event.shiftKey) {
3243
3726
  return;
@@ -3266,7 +3749,7 @@ function WorkspaceFileManager({
3266
3749
  window.removeEventListener("keydown", handleCopyShortcut);
3267
3750
  };
3268
3751
  }, [onCopyEntry, panelsState, panelsView.selectedEntry, session]);
3269
- useEffect5(() => {
3752
+ useEffect6(() => {
3270
3753
  function handleRenameShortcut(event) {
3271
3754
  if (event.key !== "Enter" || event.metaKey || event.ctrlKey || event.altKey) {
3272
3755
  return;
@@ -3292,7 +3775,7 @@ function WorkspaceFileManager({
3292
3775
  window.removeEventListener("keydown", handleRenameShortcut);
3293
3776
  };
3294
3777
  }, [panelsState, panelsView.selectedEntry, session]);
3295
- useEffect5(() => {
3778
+ useEffect6(() => {
3296
3779
  function resetDropOverlay() {
3297
3780
  session.resetDragDepth();
3298
3781
  }
@@ -3375,7 +3858,7 @@ function WorkspaceFileManager({
3375
3858
  rootView.currentDirectoryPath
3376
3859
  );
3377
3860
  }
3378
- return /* @__PURE__ */ jsxs6(
3861
+ return /* @__PURE__ */ jsxs7(
3379
3862
  "section",
3380
3863
  {
3381
3864
  className: cn6(
@@ -3391,8 +3874,8 @@ function WorkspaceFileManager({
3391
3874
  onDrop: handleDrop,
3392
3875
  ref: rootRef,
3393
3876
  children: [
3394
- /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
3395
- /* @__PURE__ */ jsx6(
3877
+ /* @__PURE__ */ jsxs7("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
3878
+ /* @__PURE__ */ jsx8(
3396
3879
  WorkspaceFileManagerToolbarContainer,
3397
3880
  {
3398
3881
  i18n,
@@ -3404,14 +3887,14 @@ function WorkspaceFileManager({
3404
3887
  session
3405
3888
  }
3406
3889
  ),
3407
- /* @__PURE__ */ jsx6(
3890
+ /* @__PURE__ */ jsx8(
3408
3891
  "div",
3409
3892
  {
3410
3893
  className: "@max-[600px]/workspace-file-manager:flex-col @max-[600px]/workspace-file-manager:gap-3 flex min-h-0 min-w-0 flex-1 overflow-hidden",
3411
3894
  style: {
3412
3895
  "--workspace-file-manager-dialog-overlay-z-index": "20"
3413
3896
  },
3414
- children: /* @__PURE__ */ jsx6(
3897
+ children: /* @__PURE__ */ jsx8(
3415
3898
  WorkspaceFileManagerPanelsContainer,
3416
3899
  {
3417
3900
  dateLocale,
@@ -3429,8 +3912,8 @@ function WorkspaceFileManager({
3429
3912
  }
3430
3913
  )
3431
3914
  ] }),
3432
- /* @__PURE__ */ jsx6(WorkspaceFileManagerDialogsContainer, { i18n, session }),
3433
- /* @__PURE__ */ jsx6(
3915
+ /* @__PURE__ */ jsx8(WorkspaceFileManagerDialogsContainer, { i18n, session }),
3916
+ /* @__PURE__ */ jsx8(
3434
3917
  WorkspaceFileManagerContextMenuContainer,
3435
3918
  {
3436
3919
  hostOs,
@@ -3456,7 +3939,7 @@ function WorkspaceFileManagerToolbarContainer({
3456
3939
  session
3457
3940
  }) {
3458
3941
  const { view } = useWorkspaceFileManagerToolbarView(session, i18n);
3459
- return /* @__PURE__ */ jsx6(
3942
+ return /* @__PURE__ */ jsx8(
3460
3943
  WorkspaceFileManagerToolbar,
3461
3944
  {
3462
3945
  breadcrumbs: view.breadcrumbs,
@@ -3505,20 +3988,38 @@ function WorkspaceFileManagerPanelsContainer({
3505
3988
  session
3506
3989
  }) {
3507
3990
  const { state, view } = useWorkspaceFileManagerPanelsView(session);
3508
- const arrangedEntries = useMemo2(
3991
+ const arrangedEntries = useMemo3(
3509
3992
  () => sortWorkspaceFileEntriesForArrangeMode(state.entries, arrangeMode),
3510
3993
  [arrangeMode, state.entries]
3511
3994
  );
3995
+ const treeRows = useMemo3(
3996
+ () => buildWorkspaceFileManagerVisibleTreeRows({
3997
+ arrangeMode,
3998
+ directoryExpansionByPath: state.directoryExpansionByPath,
3999
+ entries: arrangedEntries,
4000
+ expandedDirectoryPaths: state.expandedDirectoryPaths
4001
+ }),
4002
+ [
4003
+ arrangeMode,
4004
+ arrangedEntries,
4005
+ state.directoryExpansionByPath,
4006
+ state.expandedDirectoryPaths
4007
+ ]
4008
+ );
4009
+ const visibleTreeEntries = useMemo3(
4010
+ () => collectWorkspaceFileManagerVisibleTreeEntries(treeRows),
4011
+ [treeRows]
4012
+ );
3512
4013
  const {
3513
4014
  iconUrlByCacheKey,
3514
4015
  reportEntryIconViewportEnter,
3515
4016
  reportEntryIconViewportLeave
3516
4017
  } = useWorkspaceFileEntryIconUrls({
3517
- entries: arrangedEntries,
4018
+ entries: layoutMode === "list" ? visibleTreeEntries : arrangedEntries,
3518
4019
  includeImageThumbnails: true,
3519
4020
  resolveEntryIconUrl
3520
4021
  });
3521
- return /* @__PURE__ */ jsx6(
4022
+ return /* @__PURE__ */ jsx8(
3522
4023
  WorkspaceFileManagerPanels,
3523
4024
  {
3524
4025
  arrangeMode,
@@ -3534,6 +4035,7 @@ function WorkspaceFileManagerPanelsContainer({
3534
4035
  layoutMode,
3535
4036
  pendingDirectoryPath: view.pendingDirectoryPath,
3536
4037
  previewState: view.previewState,
4038
+ treeRows,
3537
4039
  onEntryIconViewportEnter: reportEntryIconViewportEnter,
3538
4040
  onEntryIconViewportLeave: reportEntryIconViewportLeave,
3539
4041
  selectedEntry: view.selectedEntry,
@@ -3569,6 +4071,12 @@ function WorkspaceFileManagerPanelsContainer({
3569
4071
  },
3570
4072
  onSelect: (path) => {
3571
4073
  session.select(path);
4074
+ },
4075
+ onToggleDirectoryExpanded: (entry, expanded) => {
4076
+ if (!expanded) {
4077
+ onDirectoryExpanded?.(entry.path);
4078
+ }
4079
+ void session.toggleDirectoryExpanded(entry);
3572
4080
  }
3573
4081
  }
3574
4082
  );
@@ -3578,8 +4086,8 @@ function WorkspaceFileManagerDialogsContainer({
3578
4086
  session
3579
4087
  }) {
3580
4088
  const { state, view } = useWorkspaceFileManagerDialogsView(session);
3581
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
3582
- /* @__PURE__ */ jsx6(
4089
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
4090
+ /* @__PURE__ */ jsx8(
3583
4091
  WorkspaceFileManagerCreateDialog,
3584
4092
  {
3585
4093
  busy: view.isBusy && state.busyAction === "create",
@@ -3596,7 +4104,7 @@ function WorkspaceFileManagerDialogsContainer({
3596
4104
  }
3597
4105
  }
3598
4106
  ),
3599
- /* @__PURE__ */ jsx6(
4107
+ /* @__PURE__ */ jsx8(
3600
4108
  WorkspaceFileManagerDeleteDialog,
3601
4109
  {
3602
4110
  busy: view.isDeleting,
@@ -3610,7 +4118,7 @@ function WorkspaceFileManagerDialogsContainer({
3610
4118
  }
3611
4119
  }
3612
4120
  ),
3613
- /* @__PURE__ */ jsx6(
4121
+ /* @__PURE__ */ jsx8(
3614
4122
  WorkspaceFileManagerImportConflictDialog,
3615
4123
  {
3616
4124
  busy: view.isImporting,
@@ -3624,7 +4132,7 @@ function WorkspaceFileManagerDialogsContainer({
3624
4132
  }
3625
4133
  }
3626
4134
  ),
3627
- /* @__PURE__ */ jsx6(
4135
+ /* @__PURE__ */ jsx8(
3628
4136
  WorkspaceFileManagerUnsupportedDialog,
3629
4137
  {
3630
4138
  copy: i18n,
@@ -3640,208 +4148,6 @@ function WorkspaceFileManagerDialogsContainer({
3640
4148
  )
3641
4149
  ] });
3642
4150
  }
3643
- function WorkspaceFileManagerContextMenuContainer({
3644
- hostOs,
3645
- i18n,
3646
- onCopyEntry,
3647
- onCopyPath,
3648
- openInAppBrowserIcon,
3649
- resolveOpenWithApplicationIcon,
3650
- session
3651
- }) {
3652
- const contextMenuRef = useRef5(null);
3653
- const { view } = useWorkspaceFileManagerContextMenuView(session);
3654
- const [openWithApplications, setOpenWithApplications] = useState7([]);
3655
- const [openWithLoading, setOpenWithLoading] = useState7(false);
3656
- useEffect5(() => {
3657
- const entry = view.contextMenu?.entry;
3658
- if (!entry || !view.showOpenWithAction) {
3659
- return;
3660
- }
3661
- const cachedApplications = session.getCachedOpenWithApplications(entry);
3662
- if (cachedApplications) {
3663
- setOpenWithApplications(cachedApplications);
3664
- setOpenWithLoading(false);
3665
- return;
3666
- }
3667
- let cancelled = false;
3668
- setOpenWithLoading(true);
3669
- void session.listOpenWithApplications(entry).then((applications) => {
3670
- if (cancelled) {
3671
- return;
3672
- }
3673
- setOpenWithApplications(applications);
3674
- setOpenWithLoading(false);
3675
- }).catch(() => {
3676
- if (cancelled) {
3677
- return;
3678
- }
3679
- setOpenWithApplications([]);
3680
- setOpenWithLoading(false);
3681
- });
3682
- return () => {
3683
- cancelled = true;
3684
- };
3685
- }, [session, view.contextMenu?.entry?.path, view.showOpenWithAction]);
3686
- useCloseContextMenuOnOutsideInteraction({
3687
- contextMenuRef,
3688
- isOpen: view.contextMenu !== null,
3689
- session
3690
- });
3691
- return /* @__PURE__ */ jsx6(
3692
- WorkspaceFileManagerContextMenu,
3693
- {
3694
- busy: view.isBusy || view.isLoading || view.isMutating,
3695
- copy: i18n,
3696
- contextMenu: view.contextMenu,
3697
- contextMenuRef,
3698
- showCopyAction: view.showCopyAction,
3699
- showImportAction: view.showImportAction,
3700
- showExportAction: view.showExportAction,
3701
- showOpenInAppBrowserAction: view.showOpenInAppBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
3702
- showOpenInDefaultBrowserAction: view.showOpenInDefaultBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
3703
- showOpenWithAction: view.showOpenWithAction,
3704
- showOpenWithOtherAction: view.showOpenWithOtherAction,
3705
- showRevealInFolderAction: view.showRevealInFolderAction,
3706
- showRenameAction: view.showRenameAction,
3707
- revealInFolderLabel: resolveRevealInFolderLabel(i18n, hostOs),
3708
- openInAppBrowserIcon,
3709
- openWithApplications,
3710
- openWithLoading,
3711
- resolveOpenWithApplicationIcon,
3712
- onClose: () => {
3713
- session.closeContextMenu();
3714
- },
3715
- onCreateDirectory: () => {
3716
- session.openCreateDirectoryDialog();
3717
- },
3718
- onCreateFile: () => {
3719
- session.openCreateFileDialog();
3720
- },
3721
- onCopy: async () => {
3722
- const entry = view.contextMenu?.entry;
3723
- if (!entry) {
3724
- return;
3725
- }
3726
- session.closeContextMenu();
3727
- await session.copyToClipboard(entry);
3728
- if (onCopyEntry) {
3729
- await onCopyEntry();
3730
- }
3731
- },
3732
- onCopyPath: async () => {
3733
- const path = view.contextMenu?.entry?.path;
3734
- if (!path) {
3735
- return;
3736
- }
3737
- session.closeContextMenu();
3738
- if (onCopyPath) {
3739
- await onCopyPath(path);
3740
- return;
3741
- }
3742
- await navigator.clipboard.writeText(path);
3743
- },
3744
- onDelete: () => {
3745
- const entry = view.contextMenu?.entry;
3746
- if (!entry) {
3747
- return;
3748
- }
3749
- session.openDeleteDialog(entry);
3750
- },
3751
- onRename: () => {
3752
- const entry = view.contextMenu?.entry;
3753
- if (!entry) {
3754
- return;
3755
- }
3756
- session.startInlineRename(entry);
3757
- },
3758
- onExport: async () => {
3759
- const entry = view.contextMenu?.entry;
3760
- if (!entry) {
3761
- return;
3762
- }
3763
- await session.exportEntry(entry);
3764
- },
3765
- onOpen: async () => {
3766
- const entry = view.contextMenu?.entry;
3767
- if (!entry) {
3768
- return;
3769
- }
3770
- await session.openEntry(entry);
3771
- },
3772
- onOpenInAppBrowser: async () => {
3773
- const entry = view.contextMenu?.entry;
3774
- if (!entry) {
3775
- return;
3776
- }
3777
- await session.openFileInAppBrowser(entry);
3778
- },
3779
- onOpenInDefaultBrowser: async () => {
3780
- const entry = view.contextMenu?.entry;
3781
- if (!entry) {
3782
- return;
3783
- }
3784
- await session.openFileInDefaultBrowser(entry);
3785
- },
3786
- onOpenWithApplication: async (applicationPath) => {
3787
- const entry = view.contextMenu?.entry;
3788
- if (!entry) {
3789
- return;
3790
- }
3791
- await session.openFileWithApplication(entry, applicationPath);
3792
- },
3793
- onOpenWithOtherApplication: async () => {
3794
- const entry = view.contextMenu?.entry;
3795
- if (!entry) {
3796
- return;
3797
- }
3798
- await session.openFileWithOtherApplication(entry);
3799
- },
3800
- onRevealInFolder: async () => {
3801
- const entry = view.contextMenu?.entry;
3802
- if (!entry) {
3803
- return;
3804
- }
3805
- await session.revealEntry(entry);
3806
- },
3807
- onImport: async () => {
3808
- session.closeContextMenu();
3809
- await session.importFiles(
3810
- view.contextMenu?.entry?.kind === "directory" ? view.contextMenu.entry.path : view.currentDirectoryPath
3811
- );
3812
- }
3813
- }
3814
- );
3815
- }
3816
- function useCloseContextMenuOnOutsideInteraction(input) {
3817
- const { contextMenuRef, isOpen, session } = input;
3818
- useEffect5(() => {
3819
- if (!isOpen) {
3820
- return;
3821
- }
3822
- function handlePointerDown(event) {
3823
- const target = event.target;
3824
- if (target instanceof Node && contextMenuRef.current?.contains(target)) {
3825
- return;
3826
- }
3827
- if (target instanceof Element && target.closest("[data-workspace-file-manager-submenu]")) {
3828
- return;
3829
- }
3830
- session.closeContextMenu();
3831
- }
3832
- function handleKeyDown(event) {
3833
- if (event.key === "Escape") {
3834
- session.closeContextMenu();
3835
- }
3836
- }
3837
- window.addEventListener("pointerdown", handlePointerDown);
3838
- window.addEventListener("keydown", handleKeyDown);
3839
- return () => {
3840
- window.removeEventListener("pointerdown", handlePointerDown);
3841
- window.removeEventListener("keydown", handleKeyDown);
3842
- };
3843
- }, [contextMenuRef, isOpen, session]);
3844
- }
3845
4151
  function isEditableKeyboardTarget(target) {
3846
4152
  if (!(target instanceof HTMLElement)) {
3847
4153
  return false;