@tutti-os/workspace-file-manager 0.0.23 → 0.0.24

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
@@ -4,10 +4,13 @@ import {
4
4
  createWorkspaceFileManagerService,
5
5
  decodeWorkspaceTextFile,
6
6
  findWorkspaceFileEntry,
7
+ findWorkspaceFileLocationById,
8
+ flattenWorkspaceFileLocations,
7
9
  formatWorkspaceFileBytes,
8
10
  formatWorkspaceFileModifiedTime,
9
11
  isWorkspaceApplicationBundle,
10
12
  isWorkspaceFileBrowserOpenable,
13
+ isWorkspaceFileRecentLocation,
11
14
  isWorkspaceTextFileTooLarge,
12
15
  looksLikeBinaryText,
13
16
  normalizeWorkspaceFilePath,
@@ -16,27 +19,30 @@ import {
16
19
  resolveWorkspaceFileEntryArrangeDateMs,
17
20
  resolveWorkspaceFileEntryIconCacheKey,
18
21
  resolveWorkspaceFileExtension,
22
+ resolveWorkspaceFileLocationDefaultId,
19
23
  resolveWorkspaceFileVisualKind,
20
24
  resolveWorkspaceImageMimeType,
25
+ resolveWorkspaceVideoMimeType,
21
26
  shouldResolveWorkspaceFileEntryIcon,
22
27
  shouldUseWorkspaceFileArchiveIcon,
23
28
  shouldUseWorkspaceFileExtensionDocumentIcon,
24
29
  sortWorkspaceFileEntriesForArrangeMode,
25
30
  splitWorkspaceFileName,
26
31
  workspaceFilePreviewMaxBytes,
32
+ workspaceFileSearchEntryToEntry,
27
33
  workspaceFileTextMaxBytes,
28
34
  writeWorkspaceFileManagerArrangeMode
29
- } from "./chunk-VCW64U6E.js";
35
+ } from "./chunk-6X2KFAFG.js";
30
36
  import {
31
37
  createWorkspaceFileManagerI18nRuntime,
32
38
  resolveRevealInFolderLabel,
33
39
  workspaceFileManagerI18nNamespace,
34
40
  workspaceFileManagerI18nResources
35
- } from "./chunk-V2LNXNYT.js";
41
+ } from "./chunk-HIYT2WOT.js";
36
42
 
37
43
  // src/ui/WorkspaceFileManager.tsx
38
- import { useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6 } from "react";
39
- import { cn as cn6 } from "@tutti-os/ui-system";
44
+ import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef7, useState as useState8 } from "react";
45
+ import { cn as cn7 } from "@tutti-os/ui-system";
40
46
 
41
47
  // src/ui/WorkspaceFileManagerContextMenuContainer.tsx
42
48
  import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
@@ -119,6 +125,8 @@ function WorkspaceFileManagerContextMenu({
119
125
  contextMenuRef,
120
126
  openInAppBrowserIcon,
121
127
  showCopyAction,
128
+ showCreateAction,
129
+ showDeleteAction,
122
130
  showImportAction,
123
131
  showExportAction,
124
132
  showOpenInAppBrowserAction,
@@ -258,29 +266,33 @@ function WorkspaceFileManagerContextMenu({
258
266
  label: copy.t("downloadLabel")
259
267
  });
260
268
  }
261
- dangerItems.push({
262
- action: onDelete,
263
- disabled: busy,
264
- danger: true,
265
- icon: /* @__PURE__ */ jsx(DeleteIcon, { className: "size-4" }),
266
- key: "delete",
267
- label: copy.t("deleteLabel")
268
- });
269
+ if (showDeleteAction) {
270
+ dangerItems.push({
271
+ action: onDelete,
272
+ disabled: busy,
273
+ danger: true,
274
+ icon: /* @__PURE__ */ jsx(DeleteIcon, { className: "size-4" }),
275
+ key: "delete",
276
+ label: copy.t("deleteLabel")
277
+ });
278
+ }
269
279
  } else {
270
- createItems.push({
271
- action: onCreateFile,
272
- disabled: busy,
273
- icon: /* @__PURE__ */ jsx(NewWorkspaceLinedIcon, { className: "size-4" }),
274
- key: "create-file",
275
- label: copy.t("createFileLabel")
276
- });
277
- createItems.push({
278
- action: onCreateDirectory,
279
- disabled: busy,
280
- icon: /* @__PURE__ */ jsx(FileLinedIcon, { className: "size-4" }),
281
- key: "create-directory",
282
- label: copy.t("createDirectoryLabel")
283
- });
280
+ if (showCreateAction) {
281
+ createItems.push({
282
+ action: onCreateFile,
283
+ disabled: busy,
284
+ icon: /* @__PURE__ */ jsx(NewWorkspaceLinedIcon, { className: "size-4" }),
285
+ key: "create-file",
286
+ label: copy.t("createFileLabel")
287
+ });
288
+ createItems.push({
289
+ action: onCreateDirectory,
290
+ disabled: busy,
291
+ icon: /* @__PURE__ */ jsx(FileLinedIcon, { className: "size-4" }),
292
+ key: "create-directory",
293
+ label: copy.t("createDirectoryLabel")
294
+ });
295
+ }
284
296
  if (showImportAction) {
285
297
  transferItems.push({
286
298
  action: onImport,
@@ -644,10 +656,19 @@ import { useSnapshot } from "valtio";
644
656
  // src/services/internal/workspaceFileManagerViewModel.ts
645
657
  function resolveWorkspaceFileManagerRootViewState(input) {
646
658
  const { state } = input;
659
+ const isRecentLocation = isWorkspaceFileRecentLocation(
660
+ findWorkspaceFileLocationById(
661
+ state.locationSections,
662
+ state.selectedLocationId
663
+ )
664
+ );
665
+ const isSearchMode = state.searchQuery.trim().length > 0;
647
666
  return {
648
- canImportFromDrop: state.capabilities.canImportFromDrop,
667
+ canImportFromDrop: state.capabilities.canImportFromDrop && !isRecentLocation && !isSearchMode,
649
668
  currentDirectoryPath: state.currentDirectoryPath,
650
- isBusy: state.busyAction !== null
669
+ isBusy: state.busyAction !== null,
670
+ locationSections: state.locationSections,
671
+ selectedLocationId: state.selectedLocationId
651
672
  };
652
673
  }
653
674
  function resolveWorkspaceFileManagerToolbarViewState(input) {
@@ -662,29 +683,43 @@ function resolveWorkspaceFileManagerToolbarViewState(input) {
662
683
  copy.t("breadcrumbRootLabel"),
663
684
  state.root
664
685
  ),
686
+ canSearch: state.capabilities.canSearch,
665
687
  canGoBack: currentDirectoryPath !== normalizeWorkspaceFilePath(state.root) && state.navigationBackStack.length > 0,
666
688
  canGoForward: state.navigationForwardStack.length > 0,
667
689
  currentDirectoryPath,
668
690
  isBusy: state.busyAction !== null,
669
691
  isLoading: state.isLoading,
670
692
  isMutating: state.isMutating,
693
+ isSearching: state.isSearching,
694
+ searchQuery: state.searchQuery,
671
695
  isImporting: state.busyAction === "import",
672
696
  showImportAction: state.capabilities.canImportFromPicker
673
697
  };
674
698
  }
675
699
  function resolveWorkspaceFileManagerPanelsViewState(input) {
676
700
  const { state } = input;
701
+ const isRecentLocation = isWorkspaceFileRecentLocation(
702
+ findWorkspaceFileLocationById(
703
+ state.locationSections,
704
+ state.selectedLocationId
705
+ )
706
+ );
677
707
  return {
678
- canMove: state.capabilities.canMove,
708
+ canMove: state.capabilities.canMove && !isRecentLocation,
679
709
  contextMenuEntryPath: state.contextMenuEntryPath,
680
710
  entries: state.entries,
681
711
  error: state.error,
712
+ isSearchMode: state.searchQuery.trim().length > 0,
682
713
  inlineRenameEntryPath: state.inlineRenameEntryPath,
683
714
  inlineRenameValidation: state.inlineRenameValidation,
684
715
  isLoading: state.isLoading,
685
716
  isRenaming: state.busyAction === "rename",
717
+ isSearching: state.isSearching,
686
718
  pendingDirectoryPath: state.pendingDirectoryPath,
687
719
  previewState: state.previewState,
720
+ searchEntries: state.searchEntries,
721
+ searchError: state.searchError,
722
+ searchQuery: state.searchQuery,
688
723
  selectedEntry: findSelectedEntry(state),
689
724
  selectedPath: state.selectedPath,
690
725
  showDropOverlay: state.capabilities.canImportFromDrop && state.dragDepth > 0 && state.busyAction === null
@@ -713,6 +748,13 @@ function resolveWorkspaceFileManagerDialogsViewState(input) {
713
748
  }
714
749
  function resolveWorkspaceFileManagerContextMenuViewState(input) {
715
750
  const { state } = input;
751
+ const isRecentLocation = isWorkspaceFileRecentLocation(
752
+ findWorkspaceFileLocationById(
753
+ state.locationSections,
754
+ state.selectedLocationId
755
+ )
756
+ );
757
+ const isSearchMode = state.searchQuery.trim().length > 0;
716
758
  const contextMenuEntry = state.contextMenu?.entryPath ? findEntry(state, state.contextMenu.entryPath) : null;
717
759
  const isContextMenuFile = contextMenuEntry?.kind === "file";
718
760
  return {
@@ -725,17 +767,19 @@ function resolveWorkspaceFileManagerContextMenuViewState(input) {
725
767
  isBusy: state.busyAction !== null,
726
768
  isLoading: state.isLoading,
727
769
  isMutating: state.isMutating,
770
+ showCreateAction: !isRecentLocation && !isSearchMode && (state.capabilities.canCreateDirectory || state.capabilities.canCreateFile),
728
771
  showCopyAction: state.capabilities.canCopy,
772
+ showDeleteAction: state.capabilities.canDelete && !isRecentLocation && !isSearchMode,
729
773
  showExportAction: state.capabilities.canExport,
730
- showImportAction: state.capabilities.canImportFromPicker,
731
- showMoveAction: state.capabilities.canMove,
774
+ showImportAction: state.capabilities.canImportFromPicker && !isRecentLocation && !isSearchMode,
775
+ showMoveAction: state.capabilities.canMove && !isRecentLocation && !isSearchMode,
732
776
  showOpenInAppBrowserAction: state.capabilities.canOpenInAppBrowser,
733
777
  showOpenInDefaultBrowserAction: state.capabilities.canOpenInDefaultBrowser,
734
778
  showOpenInFileViewerAction: contextMenuEntry !== null && isContextMenuFile && resolveWorkspaceFileActivationTarget(contextMenuEntry) !== null,
735
779
  showOpenWithAction: state.capabilities.canOpenWith && isContextMenuFile,
736
780
  showOpenWithOtherAction: state.capabilities.canPickOtherOpenWithApplication && isContextMenuFile,
737
781
  showRevealInFolderAction: state.capabilities.canRevealInFolder,
738
- showRenameAction: state.capabilities.canRename
782
+ showRenameAction: state.capabilities.canRename && !isRecentLocation && !isSearchMode
739
783
  };
740
784
  }
741
785
  function findSelectedEntry(state) {
@@ -858,6 +902,8 @@ function WorkspaceFileManagerContextMenuContainer({
858
902
  contextMenu: view.contextMenu,
859
903
  contextMenuRef,
860
904
  showCopyAction: view.showCopyAction,
905
+ showCreateAction: view.showCreateAction,
906
+ showDeleteAction: view.showDeleteAction,
861
907
  showImportAction: view.showImportAction,
862
908
  showExportAction: view.showExportAction,
863
909
  showOpenInAppBrowserAction: view.showOpenInAppBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
@@ -1880,6 +1926,7 @@ function WorkspaceFileManagerPanels({
1880
1926
  dateLocale,
1881
1927
  entryDragMode,
1882
1928
  entrySelectionEnabled = true,
1929
+ entryContextByPath = null,
1883
1930
  copy,
1884
1931
  iconUrlByCacheKey,
1885
1932
  inlineRenameEntryPath,
@@ -2216,7 +2263,14 @@ function WorkspaceFileManagerPanels({
2216
2263
  useStackedPreview ? "h-[44%] max-h-[48%] flex-none" : "flex-1"
2217
2264
  ),
2218
2265
  onContextMenu: handleTablePanelContextMenu,
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(
2266
+ 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(
2267
+ FeedbackState,
2268
+ {
2269
+ message: copy.t(
2270
+ state.isSearchMode ? "noSearchResults" : "emptyDirectory"
2271
+ )
2272
+ }
2273
+ ) : /* @__PURE__ */ jsx6(ScrollArea, { className: "min-h-0 flex-1 [&_[data-orientation=vertical][data-slot=scroll-area-scrollbar]]:opacity-100", children: layoutMode === "icon" ? /* @__PURE__ */ jsx6(
2220
2274
  WorkspaceFileManagerIconGrid,
2221
2275
  {
2222
2276
  contextMenuEntryPath,
@@ -2292,6 +2346,7 @@ function WorkspaceFileManagerPanels({
2292
2346
  dateLocale,
2293
2347
  depth: row.depth,
2294
2348
  entry: row.entry,
2349
+ contextLabel: entryContextByPath?.get(row.entry.path),
2295
2350
  expanded: row.expanded,
2296
2351
  expandable: row.expandable,
2297
2352
  iconUrlByCacheKey,
@@ -2450,6 +2505,7 @@ function EntryRow({
2450
2505
  arrangeMode,
2451
2506
  canMove,
2452
2507
  contextMenuActive,
2508
+ contextLabel,
2453
2509
  copy,
2454
2510
  dateLocale,
2455
2511
  depth,
@@ -2516,6 +2572,7 @@ function EntryRow({
2516
2572
  EntryNameCell,
2517
2573
  {
2518
2574
  copy,
2575
+ contextLabel,
2519
2576
  entry,
2520
2577
  iconUrlByCacheKey,
2521
2578
  inlineRenameValidation,
@@ -2743,6 +2800,7 @@ function workspaceFileManagerTreeIndentStyle(depth) {
2743
2800
  };
2744
2801
  }
2745
2802
  function EntryNameCell({
2803
+ contextLabel = null,
2746
2804
  copy,
2747
2805
  entry,
2748
2806
  iconUrlByCacheKey,
@@ -2907,9 +2965,12 @@ function EntryNameCell({
2907
2965
  onViewportEnter: onEntryIconViewportEnter
2908
2966
  }
2909
2967
  ),
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
2968
+ /* @__PURE__ */ jsxs5("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
2969
+ /* @__PURE__ */ jsxs5("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
2970
+ /* @__PURE__ */ jsx6("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: nameParts.start }),
2971
+ nameParts.end ? /* @__PURE__ */ jsx6("span", { className: "flex-none overflow-hidden text-ellipsis", children: nameParts.end }) : null
2972
+ ] }),
2973
+ contextLabel ? /* @__PURE__ */ jsx6("span", { className: "block max-w-full truncate text-[11px] leading-3 text-[var(--text-tertiary)]", children: contextLabel }) : null
2913
2974
  ] })
2914
2975
  ]
2915
2976
  }
@@ -3034,14 +3095,17 @@ import {
3034
3095
  ArrowRightIcon as ArrowRightIcon3,
3035
3096
  Button as Button2,
3036
3097
  ChevronDownIcon,
3098
+ CloseIcon,
3037
3099
  DropdownMenu,
3038
3100
  DropdownMenuContent,
3039
3101
  DropdownMenuRadioGroup,
3040
3102
  DropdownMenuRadioItem,
3041
3103
  DropdownMenuSeparator,
3042
3104
  DropdownMenuTrigger,
3105
+ Input as Input2,
3043
3106
  LoadingIcon as LoadingIcon3,
3044
3107
  RefreshIcon,
3108
+ SearchIcon,
3045
3109
  Tooltip,
3046
3110
  TooltipContent,
3047
3111
  TooltipProvider,
@@ -3050,10 +3114,11 @@ import {
3050
3114
  ViewListLinedIcon,
3051
3115
  cn as cn5
3052
3116
  } from "@tutti-os/ui-system";
3053
- import { useState as useState4 } from "react";
3117
+ import { useEffect as useEffect5, useRef as useRef6, useState as useState4 } from "react";
3054
3118
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3055
3119
  function WorkspaceFileManagerToolbar({
3056
3120
  breadcrumbs,
3121
+ canSearch,
3057
3122
  canGoBack,
3058
3123
  canGoForward,
3059
3124
  copy,
@@ -3061,14 +3126,18 @@ function WorkspaceFileManagerToolbar({
3061
3126
  isBusy,
3062
3127
  isLoading,
3063
3128
  isMutating,
3129
+ isSearching,
3064
3130
  arrangeMode,
3065
3131
  layoutMode,
3132
+ searchQuery,
3066
3133
  onGoBack,
3067
3134
  onGoForward,
3068
3135
  onArrangeModeChange,
3069
3136
  onLayoutModeChange,
3070
3137
  onLoadDirectory,
3071
- onRefresh
3138
+ onRefresh,
3139
+ onSearchClear,
3140
+ onSearchQueryChange
3072
3141
  }) {
3073
3142
  const [refreshAnimationKey, setRefreshAnimationKey] = useState4(0);
3074
3143
  const handleRefresh = () => {
@@ -3146,10 +3215,138 @@ function WorkspaceFileManagerToolbar({
3146
3215
  /* @__PURE__ */ jsx7("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
3147
3216
  ]
3148
3217
  }
3218
+ ),
3219
+ /* @__PURE__ */ jsx7(
3220
+ WorkspaceFileManagerToolbarSearch,
3221
+ {
3222
+ canSearch,
3223
+ copy,
3224
+ isSearching,
3225
+ searchQuery,
3226
+ onClear: onSearchClear,
3227
+ onSearchQueryChange
3228
+ }
3149
3229
  )
3150
3230
  ] })
3151
3231
  ] });
3152
3232
  }
3233
+ function WorkspaceFileManagerToolbarSearch({
3234
+ canSearch,
3235
+ copy,
3236
+ isSearching,
3237
+ searchQuery,
3238
+ onClear,
3239
+ onSearchQueryChange
3240
+ }) {
3241
+ const [expanded, setExpanded] = useState4(searchQuery.trim().length > 0);
3242
+ const [inputValue, setInputValue] = useState4(searchQuery);
3243
+ const inputRef = useRef6(null);
3244
+ const isComposingRef = useRef6(false);
3245
+ const active = expanded || searchQuery.trim().length > 0;
3246
+ useEffect5(() => {
3247
+ if (searchQuery.trim().length > 0) {
3248
+ setExpanded(true);
3249
+ }
3250
+ }, [searchQuery]);
3251
+ useEffect5(() => {
3252
+ if (!expanded) {
3253
+ return;
3254
+ }
3255
+ inputRef.current?.focus();
3256
+ }, [expanded]);
3257
+ useEffect5(() => {
3258
+ if (isComposingRef.current) {
3259
+ return;
3260
+ }
3261
+ setInputValue(searchQuery);
3262
+ }, [searchQuery]);
3263
+ const commitInputValue = (value) => {
3264
+ setInputValue(value);
3265
+ onSearchQueryChange(value);
3266
+ };
3267
+ const handleClear = () => {
3268
+ isComposingRef.current = false;
3269
+ setInputValue("");
3270
+ onClear();
3271
+ setExpanded(false);
3272
+ };
3273
+ if (!active) {
3274
+ return /* @__PURE__ */ jsx7(
3275
+ ToolbarIconButton,
3276
+ {
3277
+ ariaLabel: copy.t("searchPlaceholder"),
3278
+ disabled: !canSearch,
3279
+ title: copy.t("searchPlaceholder"),
3280
+ onClick: () => {
3281
+ setExpanded(true);
3282
+ },
3283
+ children: /* @__PURE__ */ jsx7(SearchIcon, { className: "size-4" })
3284
+ }
3285
+ );
3286
+ }
3287
+ return /* @__PURE__ */ jsxs6(
3288
+ "div",
3289
+ {
3290
+ className: cn5(
3291
+ "relative h-7 w-[min(220px,34vw)] flex-none overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--background-fronted)] shadow-sm transition-[width,opacity,border-color,background-color] duration-200 ease-out",
3292
+ "@max-[600px]/workspace-file-manager:w-[min(170px,42vw)]",
3293
+ !canSearch && "opacity-60"
3294
+ ),
3295
+ children: [
3296
+ /* @__PURE__ */ jsx7(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 text-[var(--text-tertiary)]" }),
3297
+ /* @__PURE__ */ jsx7(
3298
+ Input2,
3299
+ {
3300
+ "aria-label": copy.t("searchPlaceholder"),
3301
+ className: "h-full border-0 bg-transparent pr-8 pl-8 text-sm shadow-none focus-visible:ring-0",
3302
+ disabled: !canSearch,
3303
+ placeholder: copy.t("searchPlaceholder"),
3304
+ ref: inputRef,
3305
+ value: inputValue,
3306
+ onBlur: (event) => {
3307
+ commitInputValue(event.currentTarget.value);
3308
+ },
3309
+ onChange: (event) => {
3310
+ const nextValue = event.currentTarget.value;
3311
+ if (isComposingRef.current) {
3312
+ setInputValue(nextValue);
3313
+ return;
3314
+ }
3315
+ commitInputValue(nextValue);
3316
+ },
3317
+ onCompositionEnd: (event) => {
3318
+ isComposingRef.current = false;
3319
+ commitInputValue(event.currentTarget.value);
3320
+ },
3321
+ onCompositionStart: () => {
3322
+ isComposingRef.current = true;
3323
+ },
3324
+ onKeyDown: (event) => {
3325
+ if (event.key !== "Escape") {
3326
+ return;
3327
+ }
3328
+ if (isComposingRef.current || event.nativeEvent.isComposing) {
3329
+ return;
3330
+ }
3331
+ event.preventDefault();
3332
+ handleClear();
3333
+ }
3334
+ }
3335
+ ),
3336
+ /* @__PURE__ */ jsx7(
3337
+ "button",
3338
+ {
3339
+ "aria-label": copy.t("clearSearchLabel"),
3340
+ className: "absolute top-1/2 right-1 grid size-5 -translate-y-1/2 place-items-center rounded-[4px] text-[var(--text-tertiary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]",
3341
+ type: "button",
3342
+ onClick: handleClear,
3343
+ children: isSearching ? /* @__PURE__ */ jsx7(LoadingIcon3, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx7(CloseIcon, { className: "size-3" })
3344
+ }
3345
+ )
3346
+ ]
3347
+ }
3348
+ );
3349
+ }
3153
3350
  function LayoutModeToggle({
3154
3351
  arrangeMode,
3155
3352
  copy,
@@ -3336,6 +3533,80 @@ function BreadcrumbButton({
3336
3533
  ] });
3337
3534
  }
3338
3535
 
3536
+ // src/ui/WorkspaceFileManagerSidebar.tsx
3537
+ import {
3538
+ FileIcon,
3539
+ FolderIcon,
3540
+ ScrollArea as ScrollArea2,
3541
+ Tooltip as Tooltip2,
3542
+ TooltipContent as TooltipContent2,
3543
+ TooltipProvider as TooltipProvider2,
3544
+ TooltipTrigger as TooltipTrigger2,
3545
+ cn as cn6
3546
+ } from "@tutti-os/ui-system";
3547
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3548
+ function WorkspaceFileManagerSidebar({
3549
+ disabled,
3550
+ locationSections,
3551
+ selectedLocationId,
3552
+ onSelectLocation
3553
+ }) {
3554
+ const visibleSections = locationSections.filter(
3555
+ (section) => section.locations.length > 0
3556
+ );
3557
+ if (visibleSections.length === 0) {
3558
+ return null;
3559
+ }
3560
+ return /* @__PURE__ */ jsx8("aside", { className: "@max-[600px]/workspace-file-manager:hidden flex w-[188px] min-w-[188px] flex-col border-r border-[var(--border-1)] bg-[var(--background-panel)]", children: /* @__PURE__ */ jsx8(ScrollArea2, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx8(TooltipProvider2, { delayDuration: 350, children: /* @__PURE__ */ jsx8("div", { className: "flex flex-col gap-3 px-2 py-3", children: visibleSections.map((section) => /* @__PURE__ */ jsxs7("section", { className: "min-w-0", children: [
3561
+ /* @__PURE__ */ jsx8("h2", { className: "mb-1 px-2 text-[11px] font-medium uppercase tracking-normal text-[var(--text-tertiary)]", children: section.label }),
3562
+ /* @__PURE__ */ jsx8("div", { className: "flex flex-col gap-0.5", children: section.locations.map((location) => /* @__PURE__ */ jsx8(
3563
+ WorkspaceFileManagerSidebarLocation,
3564
+ {
3565
+ active: location.id === selectedLocationId,
3566
+ disabled,
3567
+ location,
3568
+ onSelectLocation
3569
+ },
3570
+ location.id
3571
+ )) })
3572
+ ] }, section.id)) }) }) }) });
3573
+ }
3574
+ function WorkspaceFileManagerSidebarLocation({
3575
+ active,
3576
+ disabled,
3577
+ location,
3578
+ onSelectLocation
3579
+ }) {
3580
+ const Icon = location.kind === "recent" ? FileIcon : FolderIcon;
3581
+ const content = /* @__PURE__ */ jsxs7(
3582
+ "button",
3583
+ {
3584
+ "aria-current": active ? "page" : void 0,
3585
+ className: cn6(
3586
+ "flex h-8 min-w-0 items-center gap-2 rounded-md px-2 text-left text-sm text-[var(--text-secondary)] transition-colors",
3587
+ active ? "bg-[var(--background-fronted)] text-[var(--text-primary)] shadow-sm" : "hover:bg-[var(--transparency-block)] hover:text-[var(--text-primary)]",
3588
+ disabled && "pointer-events-none opacity-60"
3589
+ ),
3590
+ disabled,
3591
+ type: "button",
3592
+ onClick: () => {
3593
+ onSelectLocation(location);
3594
+ },
3595
+ children: [
3596
+ /* @__PURE__ */ jsx8(Icon, { className: "size-4 flex-none" }),
3597
+ /* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: location.label })
3598
+ ]
3599
+ }
3600
+ );
3601
+ if (!location.contextLabel) {
3602
+ return content;
3603
+ }
3604
+ return /* @__PURE__ */ jsxs7(Tooltip2, { children: [
3605
+ /* @__PURE__ */ jsx8(TooltipTrigger2, { asChild: true, children: content }),
3606
+ /* @__PURE__ */ jsx8(TooltipContent2, { side: "right", children: location.contextLabel })
3607
+ ] });
3608
+ }
3609
+
3339
3610
  // src/ui/useWorkspaceFileManagerArrangeMode.ts
3340
3611
  import { useCallback as useCallback3, useState as useState5 } from "react";
3341
3612
  function useWorkspaceFileManagerArrangeMode() {
@@ -3392,7 +3663,7 @@ function useWorkspaceFileManagerLayoutMode() {
3392
3663
  }
3393
3664
 
3394
3665
  // src/ui/useWorkspaceFileEntryIconUrls.ts
3395
- import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo2, useState as useState7 } from "react";
3666
+ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo2, useState as useState7 } from "react";
3396
3667
 
3397
3668
  // src/ui/workspaceFileEntryIconUrlQueue.ts
3398
3669
  var defaultMaxConcurrentIconRequests = 3;
@@ -3582,16 +3853,16 @@ function useWorkspaceFileEntryIconUrls(input) {
3582
3853
  () => buildIconTargetSignature(entries, { includeImageThumbnails }),
3583
3854
  [entries, includeImageThumbnails]
3584
3855
  );
3585
- useEffect5(() => {
3856
+ useEffect6(() => {
3586
3857
  queue.activate();
3587
3858
  return () => {
3588
3859
  queue.dispose();
3589
3860
  };
3590
3861
  }, [queue]);
3591
- useEffect5(() => {
3862
+ useEffect6(() => {
3592
3863
  queue.retainEntries(entries);
3593
3864
  }, [entries, iconTargetSignature, queue]);
3594
- useEffect5(() => {
3865
+ useEffect6(() => {
3595
3866
  setIconUrlByCacheKey(queue.snapshot());
3596
3867
  return queue.subscribe(() => {
3597
3868
  setIconUrlByCacheKey(queue.snapshot());
@@ -3698,7 +3969,8 @@ function appendWorkspaceFileManagerVisibleTreeRows(input) {
3698
3969
  }
3699
3970
 
3700
3971
  // src/ui/WorkspaceFileManager.tsx
3701
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3972
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3973
+ var workspaceFileManagerSearchDebounceMs = 180;
3702
3974
  function WorkspaceFileManager({
3703
3975
  className,
3704
3976
  dateLocale,
@@ -3715,12 +3987,12 @@ function WorkspaceFileManager({
3715
3987
  session,
3716
3988
  surface = "card"
3717
3989
  }) {
3718
- const rootRef = useRef6(null);
3990
+ const rootRef = useRef7(null);
3719
3991
  const { arrangeMode, setArrangeMode } = useWorkspaceFileManagerArrangeMode();
3720
3992
  const { layoutMode, setLayoutMode } = useWorkspaceFileManagerLayoutMode();
3721
3993
  const rootView = useWorkspaceFileManagerRootView(session);
3722
3994
  const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
3723
- useEffect6(() => {
3995
+ useEffect7(() => {
3724
3996
  function handleCopyShortcut(event) {
3725
3997
  if (!(event.metaKey || event.ctrlKey) || event.key !== "c" || event.shiftKey) {
3726
3998
  return;
@@ -3749,7 +4021,7 @@ function WorkspaceFileManager({
3749
4021
  window.removeEventListener("keydown", handleCopyShortcut);
3750
4022
  };
3751
4023
  }, [onCopyEntry, panelsState, panelsView.selectedEntry, session]);
3752
- useEffect6(() => {
4024
+ useEffect7(() => {
3753
4025
  function handleRenameShortcut(event) {
3754
4026
  if (event.key !== "Enter" || event.metaKey || event.ctrlKey || event.altKey) {
3755
4027
  return;
@@ -3760,7 +4032,7 @@ function WorkspaceFileManager({
3760
4032
  if (!rootRef.current?.contains(event.target)) {
3761
4033
  return;
3762
4034
  }
3763
- if (!panelsState.capabilities.canRename || panelsState.busyAction !== null || panelsState.isLoading || panelsState.isMutating || panelsState.inlineRenameEntryPath !== null) {
4035
+ if (!panelsState.capabilities.canRename || panelsState.busyAction !== null || panelsState.isLoading || panelsState.isMutating || panelsState.inlineRenameEntryPath !== null || panelsState.searchQuery.trim().length > 0) {
3764
4036
  return;
3765
4037
  }
3766
4038
  const entry = panelsView.selectedEntry;
@@ -3775,7 +4047,7 @@ function WorkspaceFileManager({
3775
4047
  window.removeEventListener("keydown", handleRenameShortcut);
3776
4048
  };
3777
4049
  }, [panelsState, panelsView.selectedEntry, session]);
3778
- useEffect6(() => {
4050
+ useEffect7(() => {
3779
4051
  function resetDropOverlay() {
3780
4052
  session.resetDragDepth();
3781
4053
  }
@@ -3858,10 +4130,10 @@ function WorkspaceFileManager({
3858
4130
  rootView.currentDirectoryPath
3859
4131
  );
3860
4132
  }
3861
- return /* @__PURE__ */ jsxs7(
4133
+ return /* @__PURE__ */ jsxs8(
3862
4134
  "section",
3863
4135
  {
3864
- className: cn6(
4136
+ className: cn7(
3865
4137
  "@container/workspace-file-manager relative flex h-full min-h-0 w-full overflow-hidden text-[14px] text-[var(--text-primary)]",
3866
4138
  surface === "card" ? "rounded-lg border border-[var(--border-1)] bg-[var(--background-panel)]" : "rounded-none border-0 bg-transparent",
3867
4139
  className
@@ -3874,8 +4146,22 @@ function WorkspaceFileManager({
3874
4146
  onDrop: handleDrop,
3875
4147
  ref: rootRef,
3876
4148
  children: [
3877
- /* @__PURE__ */ jsxs7("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
3878
- /* @__PURE__ */ jsx8(
4149
+ /* @__PURE__ */ jsx9(
4150
+ WorkspaceFileManagerSidebar,
4151
+ {
4152
+ disabled: rootView.isBusy || panelsState.isLoading,
4153
+ locationSections: rootView.locationSections,
4154
+ selectedLocationId: rootView.selectedLocationId,
4155
+ onSelectLocation: (location) => {
4156
+ if (location.kind === "directory") {
4157
+ onDirectoryExpanded?.(location.path);
4158
+ }
4159
+ void session.selectLocation(location.id);
4160
+ }
4161
+ }
4162
+ ),
4163
+ /* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
4164
+ /* @__PURE__ */ jsx9(
3879
4165
  WorkspaceFileManagerToolbarContainer,
3880
4166
  {
3881
4167
  i18n,
@@ -3887,14 +4173,14 @@ function WorkspaceFileManager({
3887
4173
  session
3888
4174
  }
3889
4175
  ),
3890
- /* @__PURE__ */ jsx8(
4176
+ /* @__PURE__ */ jsx9(
3891
4177
  "div",
3892
4178
  {
3893
4179
  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",
3894
4180
  style: {
3895
4181
  "--workspace-file-manager-dialog-overlay-z-index": "20"
3896
4182
  },
3897
- children: /* @__PURE__ */ jsx8(
4183
+ children: /* @__PURE__ */ jsx9(
3898
4184
  WorkspaceFileManagerPanelsContainer,
3899
4185
  {
3900
4186
  dateLocale,
@@ -3912,8 +4198,8 @@ function WorkspaceFileManager({
3912
4198
  }
3913
4199
  )
3914
4200
  ] }),
3915
- /* @__PURE__ */ jsx8(WorkspaceFileManagerDialogsContainer, { i18n, session }),
3916
- /* @__PURE__ */ jsx8(
4201
+ /* @__PURE__ */ jsx9(WorkspaceFileManagerDialogsContainer, { i18n, session }),
4202
+ /* @__PURE__ */ jsx9(
3917
4203
  WorkspaceFileManagerContextMenuContainer,
3918
4204
  {
3919
4205
  hostOs,
@@ -3939,10 +4225,42 @@ function WorkspaceFileManagerToolbarContainer({
3939
4225
  session
3940
4226
  }) {
3941
4227
  const { view } = useWorkspaceFileManagerToolbarView(session, i18n);
3942
- return /* @__PURE__ */ jsx8(
4228
+ const [searchQuery, setSearchQuery] = useState8(view.searchQuery);
4229
+ const submittedSearchQueryRef = useRef7(view.searchQuery);
4230
+ const submitSearchQuery = useCallback6(
4231
+ (query) => {
4232
+ submittedSearchQueryRef.current = query;
4233
+ void session.search(query);
4234
+ },
4235
+ [session]
4236
+ );
4237
+ useEffect7(() => {
4238
+ if (view.searchQuery === submittedSearchQueryRef.current) {
4239
+ return;
4240
+ }
4241
+ submittedSearchQueryRef.current = view.searchQuery;
4242
+ setSearchQuery(view.searchQuery);
4243
+ }, [view.searchQuery]);
4244
+ useEffect7(() => {
4245
+ if (!view.canSearch || searchQuery === submittedSearchQueryRef.current) {
4246
+ return;
4247
+ }
4248
+ const timer = setTimeout(() => {
4249
+ submitSearchQuery(searchQuery);
4250
+ }, workspaceFileManagerSearchDebounceMs);
4251
+ return () => {
4252
+ clearTimeout(timer);
4253
+ };
4254
+ }, [searchQuery, submitSearchQuery, view.canSearch]);
4255
+ const handleSearchClear = useCallback6(() => {
4256
+ setSearchQuery("");
4257
+ submitSearchQuery("");
4258
+ }, [submitSearchQuery]);
4259
+ return /* @__PURE__ */ jsx9(
3943
4260
  WorkspaceFileManagerToolbar,
3944
4261
  {
3945
4262
  breadcrumbs: view.breadcrumbs,
4263
+ canSearch: view.canSearch,
3946
4264
  canGoBack: view.canGoBack,
3947
4265
  canGoForward: view.canGoForward,
3948
4266
  copy: i18n,
@@ -3950,8 +4268,10 @@ function WorkspaceFileManagerToolbarContainer({
3950
4268
  isBusy: view.isBusy,
3951
4269
  isLoading: view.isLoading,
3952
4270
  isMutating: view.isMutating,
4271
+ isSearching: view.isSearching,
3953
4272
  arrangeMode,
3954
4273
  layoutMode,
4274
+ searchQuery,
3955
4275
  onArrangeModeChange,
3956
4276
  onGoBack: () => {
3957
4277
  void session.goBack();
@@ -3971,7 +4291,9 @@ function WorkspaceFileManagerToolbarContainer({
3971
4291
  },
3972
4292
  onRefresh: () => {
3973
4293
  void session.refresh();
3974
- }
4294
+ },
4295
+ onSearchClear: handleSearchClear,
4296
+ onSearchQueryChange: setSearchQuery
3975
4297
  }
3976
4298
  );
3977
4299
  }
@@ -3992,6 +4314,17 @@ function WorkspaceFileManagerPanelsContainer({
3992
4314
  () => sortWorkspaceFileEntriesForArrangeMode(state.entries, arrangeMode),
3993
4315
  [arrangeMode, state.entries]
3994
4316
  );
4317
+ const searchEntries = useMemo3(
4318
+ () => view.searchEntries.map(workspaceFileSearchEntryToEntry),
4319
+ [view.searchEntries]
4320
+ );
4321
+ const searchEntryContextByPath = useMemo3(() => {
4322
+ const contextByPath = /* @__PURE__ */ new Map();
4323
+ for (const entry of view.searchEntries) {
4324
+ contextByPath.set(entry.path, entry.directoryPath);
4325
+ }
4326
+ return contextByPath;
4327
+ }, [view.searchEntries]);
3995
4328
  const treeRows = useMemo3(
3996
4329
  () => buildWorkspaceFileManagerVisibleTreeRows({
3997
4330
  arrangeMode,
@@ -4006,24 +4339,37 @@ function WorkspaceFileManagerPanelsContainer({
4006
4339
  state.expandedDirectoryPaths
4007
4340
  ]
4008
4341
  );
4342
+ const searchTreeRows = useMemo3(
4343
+ () => searchEntries.map((entry) => ({
4344
+ depth: 0,
4345
+ entry,
4346
+ expanded: false,
4347
+ expandable: false,
4348
+ kind: "entry",
4349
+ loadingChildren: false
4350
+ })),
4351
+ [searchEntries]
4352
+ );
4353
+ const displayedEntries = view.isSearchMode ? searchEntries : arrangedEntries;
4354
+ const displayedTreeRows = view.isSearchMode ? searchTreeRows : treeRows;
4009
4355
  const visibleTreeEntries = useMemo3(
4010
- () => collectWorkspaceFileManagerVisibleTreeEntries(treeRows),
4011
- [treeRows]
4356
+ () => collectWorkspaceFileManagerVisibleTreeEntries(displayedTreeRows),
4357
+ [displayedTreeRows]
4012
4358
  );
4013
4359
  const {
4014
4360
  iconUrlByCacheKey,
4015
4361
  reportEntryIconViewportEnter,
4016
4362
  reportEntryIconViewportLeave
4017
4363
  } = useWorkspaceFileEntryIconUrls({
4018
- entries: layoutMode === "list" ? visibleTreeEntries : arrangedEntries,
4364
+ entries: layoutMode === "list" ? visibleTreeEntries : displayedEntries,
4019
4365
  includeImageThumbnails: true,
4020
4366
  resolveEntryIconUrl
4021
4367
  });
4022
- return /* @__PURE__ */ jsx8(
4368
+ return /* @__PURE__ */ jsx9(
4023
4369
  WorkspaceFileManagerPanels,
4024
4370
  {
4025
4371
  arrangeMode,
4026
- canMove: view.canMove,
4372
+ canMove: view.isSearchMode ? false : view.canMove,
4027
4373
  contextMenuEntryPath: view.contextMenuEntryPath,
4028
4374
  copy: i18n,
4029
4375
  dateLocale,
@@ -4035,16 +4381,18 @@ function WorkspaceFileManagerPanelsContainer({
4035
4381
  layoutMode,
4036
4382
  pendingDirectoryPath: view.pendingDirectoryPath,
4037
4383
  previewState: view.previewState,
4038
- treeRows,
4384
+ entryContextByPath: view.isSearchMode ? searchEntryContextByPath : null,
4385
+ treeRows: displayedTreeRows,
4039
4386
  onEntryIconViewportEnter: reportEntryIconViewportEnter,
4040
4387
  onEntryIconViewportLeave: reportEntryIconViewportLeave,
4041
4388
  selectedEntry: view.selectedEntry,
4042
4389
  selectedPath: view.selectedPath,
4043
4390
  showDropOverlay: view.showDropOverlay,
4044
4391
  state: {
4045
- entries: arrangedEntries,
4046
- error: state.error,
4047
- isLoading: state.isLoading
4392
+ entries: displayedEntries,
4393
+ error: view.isSearchMode ? view.searchError : state.error,
4394
+ isLoading: view.isSearchMode ? view.isSearching : state.isLoading,
4395
+ isSearchMode: view.isSearchMode
4048
4396
  },
4049
4397
  onBlankContextMenu: (event) => {
4050
4398
  onOpenContextMenu(event, null);
@@ -4086,8 +4434,8 @@ function WorkspaceFileManagerDialogsContainer({
4086
4434
  session
4087
4435
  }) {
4088
4436
  const { state, view } = useWorkspaceFileManagerDialogsView(session);
4089
- return /* @__PURE__ */ jsxs7(Fragment3, { children: [
4090
- /* @__PURE__ */ jsx8(
4437
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
4438
+ /* @__PURE__ */ jsx9(
4091
4439
  WorkspaceFileManagerCreateDialog,
4092
4440
  {
4093
4441
  busy: view.isBusy && state.busyAction === "create",
@@ -4104,7 +4452,7 @@ function WorkspaceFileManagerDialogsContainer({
4104
4452
  }
4105
4453
  }
4106
4454
  ),
4107
- /* @__PURE__ */ jsx8(
4455
+ /* @__PURE__ */ jsx9(
4108
4456
  WorkspaceFileManagerDeleteDialog,
4109
4457
  {
4110
4458
  busy: view.isDeleting,
@@ -4118,7 +4466,7 @@ function WorkspaceFileManagerDialogsContainer({
4118
4466
  }
4119
4467
  }
4120
4468
  ),
4121
- /* @__PURE__ */ jsx8(
4469
+ /* @__PURE__ */ jsx9(
4122
4470
  WorkspaceFileManagerImportConflictDialog,
4123
4471
  {
4124
4472
  busy: view.isImporting,
@@ -4132,7 +4480,7 @@ function WorkspaceFileManagerDialogsContainer({
4132
4480
  }
4133
4481
  }
4134
4482
  ),
4135
- /* @__PURE__ */ jsx8(
4483
+ /* @__PURE__ */ jsx9(
4136
4484
  WorkspaceFileManagerUnsupportedDialog,
4137
4485
  {
4138
4486
  copy: i18n,
@@ -4175,11 +4523,16 @@ export {
4175
4523
  createWorkspaceFileManagerI18nRuntime,
4176
4524
  createWorkspaceFileManagerService,
4177
4525
  decodeWorkspaceTextFile,
4526
+ findWorkspaceFileLocationById,
4527
+ flattenWorkspaceFileLocations,
4528
+ isWorkspaceFileRecentLocation,
4178
4529
  isWorkspaceTextFileTooLarge,
4179
4530
  looksLikeBinaryText,
4180
4531
  resolveWorkspaceFileActivationTarget,
4181
4532
  resolveWorkspaceFileExtension,
4533
+ resolveWorkspaceFileLocationDefaultId,
4182
4534
  resolveWorkspaceImageMimeType,
4535
+ resolveWorkspaceVideoMimeType,
4183
4536
  workspaceFileManagerI18nNamespace,
4184
4537
  workspaceFileManagerI18nResources,
4185
4538
  workspaceFilePreviewMaxBytes,