@tutti-os/workspace-file-manager 0.0.230 → 0.0.231

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
@@ -31,10 +31,10 @@ import {
31
31
 
32
32
  // src/ui/WorkspaceFileManager.tsx
33
33
  import {
34
- useCallback as useCallback6,
34
+ useCallback as useCallback7,
35
35
  useEffect as useEffect7,
36
36
  useLayoutEffect as useLayoutEffect5,
37
- useMemo as useMemo3,
37
+ useMemo as useMemo4,
38
38
  useRef as useRef7,
39
39
  useState as useState8
40
40
  } from "react";
@@ -1127,6 +1127,12 @@ function WorkspaceFileManagerUnsupportedDialog({
1127
1127
  );
1128
1128
  }
1129
1129
 
1130
+ // src/ui/WorkspaceFileManagerPanelsContainer.tsx
1131
+ import {
1132
+ useCallback as useCallback4,
1133
+ useMemo as useMemo3
1134
+ } from "react";
1135
+
1130
1136
  // src/ui/WorkspaceFileManagerPanels.tsx
1131
1137
  import {
1132
1138
  ArrowRightIcon as ArrowRightIcon2,
@@ -1140,6 +1146,7 @@ import {
1140
1146
  import { resolveWorkspaceFileVisualKind as resolveWorkspaceFileVisualKind2 } from "@tutti-os/workspace-file-preview";
1141
1147
  import { WorkspaceFilePreviewSurface as SharedWorkspaceFilePreviewSurface } from "@tutti-os/workspace-file-preview/react";
1142
1148
  import {
1149
+ memo,
1143
1150
  useCallback as useCallback2,
1144
1151
  useEffect as useEffect4,
1145
1152
  useLayoutEffect as useLayoutEffect4,
@@ -2366,7 +2373,7 @@ function resolveWorkspaceFileManagerDateColumnLabel(copy, arrangeMode) {
2366
2373
  return copy.t("modifiedLabel");
2367
2374
  }
2368
2375
  }
2369
- function EntryRow({
2376
+ var EntryRow = memo(function EntryRow2({
2370
2377
  arrangeMode,
2371
2378
  canMove,
2372
2379
  contextMenuActive,
@@ -2540,7 +2547,7 @@ function EntryRow({
2540
2547
  ]
2541
2548
  }
2542
2549
  );
2543
- }
2550
+ });
2544
2551
  function MoveDragPreview({
2545
2552
  iconUrlByCacheKey,
2546
2553
  preview,
@@ -2976,231 +2983,700 @@ function FeedbackState({ message }) {
2976
2983
  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 }) });
2977
2984
  }
2978
2985
 
2979
- // src/ui/WorkspaceFileManagerToolbar.tsx
2980
- import {
2981
- ArrowLeftIcon as ArrowLeftIcon2,
2982
- ArrowRightIcon as ArrowRightIcon3,
2983
- Button as Button2,
2984
- ChevronDownIcon,
2985
- CloseIcon,
2986
- DropdownMenu,
2987
- DropdownMenuContent,
2988
- DropdownMenuRadioGroup,
2989
- DropdownMenuRadioItem,
2990
- DropdownMenuSeparator,
2991
- DropdownMenuTrigger,
2992
- Input as Input2,
2993
- LoadingIcon as LoadingIcon3,
2994
- RefreshIcon,
2995
- SearchIcon,
2996
- Tooltip,
2997
- TooltipContent,
2998
- TooltipProvider,
2999
- TooltipTrigger,
3000
- ViewGridLinedIcon,
3001
- ViewListLinedIcon,
3002
- cn as cn5
3003
- } from "@tutti-os/ui-system";
3004
- import { useEffect as useEffect5, useRef as useRef6, useState as useState4 } from "react";
3005
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3006
- function WorkspaceFileManagerToolbar({
3007
- breadcrumbs,
3008
- canSearch,
3009
- canGoBack,
3010
- canGoForward,
3011
- copy,
3012
- currentDirectoryPath,
3013
- isBusy,
3014
- isLoading,
3015
- isMutating,
3016
- isSearching,
3017
- arrangeMode,
3018
- layoutMode,
3019
- renderToolbarTrailingActions,
3020
- searchQuery,
3021
- onGoBack,
3022
- onGoForward,
3023
- onArrangeModeChange,
3024
- onLayoutModeChange,
3025
- onLoadDirectory,
3026
- onRefresh,
3027
- onSearchClear,
3028
- onSearchQueryChange
3029
- }) {
3030
- const [refreshAnimationKey, setRefreshAnimationKey] = useState4(0);
3031
- const handleRefresh = () => {
3032
- setRefreshAnimationKey((currentKey) => currentKey + 1);
3033
- onRefresh();
3034
- };
3035
- 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: [
3036
- /* @__PURE__ */ jsx7(
3037
- ToolbarIconButton,
3038
- {
3039
- ariaLabel: copy.t("backLabel"),
3040
- disabled: !canGoBack || isLoading || isBusy,
3041
- title: copy.t("backLabel"),
3042
- onClick: onGoBack,
3043
- children: /* @__PURE__ */ jsx7(ArrowLeftIcon2, { className: "size-4" })
3044
- }
3045
- ),
3046
- /* @__PURE__ */ jsx7(
3047
- ToolbarIconButton,
3048
- {
3049
- ariaLabel: copy.t("forwardLabel"),
3050
- disabled: !canGoForward || isLoading || isBusy,
3051
- title: copy.t("forwardLabel"),
3052
- onClick: onGoForward,
3053
- children: /* @__PURE__ */ jsx7(ArrowRightIcon3, { className: "size-4" })
3054
- }
3055
- ),
3056
- /* @__PURE__ */ jsx7(
3057
- "nav",
3058
- {
3059
- "aria-label": currentDirectoryPath,
3060
- className: "@max-[600px]/workspace-file-manager:flex-auto flex min-w-0 flex-1 overflow-hidden pr-2",
3061
- "data-workspace-file-manager-path": "",
3062
- 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(
3063
- BreadcrumbButton,
3064
- {
3065
- active: index === breadcrumbs.length - 1,
3066
- label: crumb.label,
3067
- showSeparator: index > 0,
3068
- onClick: () => {
3069
- onLoadDirectory(crumb.path);
3070
- }
3071
- },
3072
- crumb.path
3073
- )) })
3074
- }
3075
- ),
3076
- /* @__PURE__ */ jsxs6("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: [
3077
- /* @__PURE__ */ jsx7(
3078
- LayoutModeToggle,
3079
- {
3080
- arrangeMode,
3081
- copy,
3082
- layoutMode,
3083
- onArrangeModeChange,
3084
- onLayoutModeChange
3085
- }
3086
- ),
3087
- /* @__PURE__ */ jsxs6(
3088
- ToolbarActionButton,
3089
- {
3090
- disabled: isLoading || isMutating || isBusy,
3091
- onClick: handleRefresh,
3092
- children: [
3093
- isLoading ? /* @__PURE__ */ jsx7(LoadingIcon3, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx7(
3094
- RefreshIcon,
3095
- {
3096
- className: cn5(
3097
- "size-4",
3098
- refreshAnimationKey > 0 && "motion-safe:animate-[spin_520ms_cubic-bezier(0.4,0,0.2,1)_1_reverse]"
3099
- )
3100
- },
3101
- refreshAnimationKey
3102
- ),
3103
- /* @__PURE__ */ jsx7("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
3104
- ]
3105
- }
3106
- ),
3107
- renderToolbarTrailingActions?.({
3108
- currentDirectoryPath,
3109
- isBusy,
3110
- isLoading,
3111
- isMutating
3112
- }) ?? null,
3113
- /* @__PURE__ */ jsx7(
3114
- WorkspaceFileManagerToolbarSearch,
3115
- {
3116
- canSearch,
3117
- copy,
3118
- isSearching,
3119
- searchQuery,
3120
- onClear: onSearchClear,
3121
- onSearchQueryChange
3122
- }
3123
- )
3124
- ] })
3125
- ] });
2986
+ // src/ui/useWorkspaceFileEntryIconUrls.ts
2987
+ import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo2, useState as useState4 } from "react";
2988
+
2989
+ // src/ui/workspaceFileEntryIconUrlQueue.ts
2990
+ var defaultMaxConcurrentIconRequests = 3;
2991
+ var imageThumbnailCacheKeyPrefix = "image-thumbnail:";
2992
+ function shouldKeepResolvedIconAfterViewportLeave(cacheKey) {
2993
+ return cacheKey.startsWith(imageThumbnailCacheKeyPrefix);
3126
2994
  }
3127
- function WorkspaceFileManagerToolbarSearch({
3128
- canSearch,
3129
- copy,
3130
- isSearching,
3131
- searchQuery,
3132
- onClear,
3133
- onSearchQueryChange
3134
- }) {
3135
- const [expanded, setExpanded] = useState4(searchQuery.trim().length > 0);
3136
- const [inputValue, setInputValue] = useState4(searchQuery);
3137
- const inputRef = useRef6(null);
3138
- const isComposingRef = useRef6(false);
3139
- const active = expanded || searchQuery.trim().length > 0;
3140
- useEffect5(() => {
3141
- if (searchQuery.trim().length > 0) {
3142
- setExpanded(true);
2995
+ function createWorkspaceFileEntryIconUrlQueue(options) {
2996
+ const maxConcurrent = Math.max(
2997
+ 1,
2998
+ Math.floor(options.maxConcurrent ?? defaultMaxConcurrentIconRequests)
2999
+ );
3000
+ const iconUrlByCacheKey = /* @__PURE__ */ new Map();
3001
+ const inFlightCacheKeys = /* @__PURE__ */ new Set();
3002
+ const listeners = /* @__PURE__ */ new Set();
3003
+ const queuedEntries = /* @__PURE__ */ new Map();
3004
+ const visibleReferenceCountByCacheKey = /* @__PURE__ */ new Map();
3005
+ let activeCount = 0;
3006
+ let disposed = false;
3007
+ let retainedCacheKeys = null;
3008
+ const policyOptions = {
3009
+ includeImageThumbnails: options.includeImageThumbnails
3010
+ };
3011
+ function publish() {
3012
+ for (const listener of listeners) {
3013
+ listener();
3143
3014
  }
3144
- }, [searchQuery]);
3145
- useEffect5(() => {
3146
- if (!expanded) {
3015
+ }
3016
+ function isRetained(cacheKey) {
3017
+ return retainedCacheKeys === null || retainedCacheKeys.has(cacheKey);
3018
+ }
3019
+ function isVisible(cacheKey) {
3020
+ return (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) > 0;
3021
+ }
3022
+ function shouldResolveCacheKey(cacheKey) {
3023
+ return isVisible(cacheKey) || shouldKeepResolvedIconAfterViewportLeave(cacheKey);
3024
+ }
3025
+ function retainVisibleReference(cacheKey) {
3026
+ visibleReferenceCountByCacheKey.set(
3027
+ cacheKey,
3028
+ (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) + 1
3029
+ );
3030
+ }
3031
+ function releaseVisibleReference(cacheKey) {
3032
+ const nextCount = (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) - 1;
3033
+ if (nextCount > 0) {
3034
+ visibleReferenceCountByCacheKey.set(cacheKey, nextCount);
3147
3035
  return;
3148
3036
  }
3149
- inputRef.current?.focus();
3150
- }, [expanded]);
3151
- useEffect5(() => {
3152
- if (isComposingRef.current) {
3037
+ visibleReferenceCountByCacheKey.delete(cacheKey);
3038
+ if (shouldKeepResolvedIconAfterViewportLeave(cacheKey)) {
3153
3039
  return;
3154
3040
  }
3155
- setInputValue(searchQuery);
3156
- }, [searchQuery]);
3157
- const commitInputValue = (value) => {
3158
- setInputValue(value);
3159
- onSearchQueryChange(value);
3160
- };
3161
- const handleClear = () => {
3162
- isComposingRef.current = false;
3163
- setInputValue("");
3164
- onClear();
3165
- setExpanded(false);
3166
- };
3167
- if (!active) {
3168
- return /* @__PURE__ */ jsx7(
3169
- ToolbarIconButton,
3170
- {
3171
- ariaLabel: copy.t("searchPlaceholder"),
3172
- disabled: !canSearch,
3173
- title: copy.t("searchPlaceholder"),
3174
- onClick: () => {
3175
- setExpanded(true);
3176
- },
3177
- children: /* @__PURE__ */ jsx7(SearchIcon, { className: "size-4" })
3178
- }
3179
- );
3041
+ queuedEntries.delete(cacheKey);
3042
+ const hadCachedIcon = iconUrlByCacheKey.delete(cacheKey);
3043
+ if (hadCachedIcon) {
3044
+ publish();
3045
+ }
3180
3046
  }
3181
- return /* @__PURE__ */ jsxs6(
3182
- "div",
3183
- {
3184
- className: cn5(
3185
- "relative h-7 w-[min(220px,34vw)] flex-none overflow-hidden rounded-md bg-[var(--transparency-block)] transition-[width,opacity,background-color] duration-200 ease-out",
3186
- "@max-[600px]/workspace-file-manager:w-[min(170px,42vw)]",
3187
- !canSearch && "opacity-60"
3188
- ),
3189
- children: [
3190
- /* @__PURE__ */ jsx7(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 text-[var(--text-tertiary)]" }),
3191
- /* @__PURE__ */ jsx7(
3192
- Input2,
3193
- {
3194
- "aria-label": copy.t("searchPlaceholder"),
3195
- className: "h-full border-0 bg-transparent pr-8 pl-8 text-sm shadow-none focus-visible:ring-0",
3196
- disabled: !canSearch,
3197
- placeholder: copy.t("searchPlaceholder"),
3198
- ref: inputRef,
3199
- value: inputValue,
3200
- onBlur: (event) => {
3201
- commitInputValue(event.currentTarget.value);
3202
- },
3203
- onChange: (event) => {
3047
+ function drain() {
3048
+ if (disposed || !options.resolveEntryIconUrl) {
3049
+ return;
3050
+ }
3051
+ while (activeCount < maxConcurrent && queuedEntries.size > 0) {
3052
+ const next = queuedEntries.entries().next().value;
3053
+ if (!next) {
3054
+ return;
3055
+ }
3056
+ const [cacheKey, entry] = next;
3057
+ queuedEntries.delete(cacheKey);
3058
+ if (!shouldResolveCacheKey(cacheKey) || iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey)) {
3059
+ continue;
3060
+ }
3061
+ activeCount += 1;
3062
+ inFlightCacheKeys.add(cacheKey);
3063
+ void options.resolveEntryIconUrl(entry).then((iconUrl) => {
3064
+ const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
3065
+ if (shouldStore) {
3066
+ iconUrlByCacheKey.set(cacheKey, iconUrl?.trim() || null);
3067
+ publish();
3068
+ }
3069
+ }).catch(() => {
3070
+ const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
3071
+ if (shouldStore) {
3072
+ iconUrlByCacheKey.set(cacheKey, null);
3073
+ publish();
3074
+ }
3075
+ }).finally(() => {
3076
+ activeCount -= 1;
3077
+ inFlightCacheKeys.delete(cacheKey);
3078
+ drain();
3079
+ });
3080
+ }
3081
+ }
3082
+ return {
3083
+ activate() {
3084
+ disposed = false;
3085
+ },
3086
+ dispose() {
3087
+ disposed = true;
3088
+ queuedEntries.clear();
3089
+ visibleReferenceCountByCacheKey.clear();
3090
+ listeners.clear();
3091
+ },
3092
+ leaveViewport(entry) {
3093
+ if (disposed || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
3094
+ return;
3095
+ }
3096
+ const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
3097
+ releaseVisibleReference(cacheKey);
3098
+ },
3099
+ enterViewport(entry) {
3100
+ if (disposed || !options.resolveEntryIconUrl || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
3101
+ return;
3102
+ }
3103
+ const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
3104
+ retainVisibleReference(cacheKey);
3105
+ if (iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey) || queuedEntries.has(cacheKey)) {
3106
+ return;
3107
+ }
3108
+ queuedEntries.set(cacheKey, entry);
3109
+ drain();
3110
+ },
3111
+ retainEntries(entries) {
3112
+ retainedCacheKeys = new Set(
3113
+ entries.filter(
3114
+ (entry) => shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)
3115
+ ).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry))
3116
+ );
3117
+ let changed = false;
3118
+ for (const cacheKey of iconUrlByCacheKey.keys()) {
3119
+ if (!isRetained(cacheKey)) {
3120
+ iconUrlByCacheKey.delete(cacheKey);
3121
+ changed = true;
3122
+ continue;
3123
+ }
3124
+ if (iconUrlByCacheKey.get(cacheKey) === null) {
3125
+ iconUrlByCacheKey.delete(cacheKey);
3126
+ changed = true;
3127
+ }
3128
+ }
3129
+ for (const cacheKey of queuedEntries.keys()) {
3130
+ if (!isRetained(cacheKey)) {
3131
+ queuedEntries.delete(cacheKey);
3132
+ }
3133
+ }
3134
+ for (const cacheKey of visibleReferenceCountByCacheKey.keys()) {
3135
+ if (!isRetained(cacheKey)) {
3136
+ visibleReferenceCountByCacheKey.delete(cacheKey);
3137
+ }
3138
+ }
3139
+ if (changed) {
3140
+ publish();
3141
+ }
3142
+ },
3143
+ snapshot() {
3144
+ return new Map(iconUrlByCacheKey);
3145
+ },
3146
+ subscribe(listener) {
3147
+ if (disposed) {
3148
+ return () => {
3149
+ };
3150
+ }
3151
+ listeners.add(listener);
3152
+ return () => {
3153
+ listeners.delete(listener);
3154
+ };
3155
+ }
3156
+ };
3157
+ }
3158
+
3159
+ // src/ui/useWorkspaceFileEntryIconUrls.ts
3160
+ function buildIconTargetSignature(entries, options) {
3161
+ return entries.filter((entry) => shouldResolveWorkspaceFileEntryIcon(entry, options)).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
3162
+ }
3163
+ function useWorkspaceFileEntryIconUrls(input) {
3164
+ const { entries, includeImageThumbnails, resolveEntryIconUrl } = input;
3165
+ const queue = useMemo2(
3166
+ () => createWorkspaceFileEntryIconUrlQueue({
3167
+ includeImageThumbnails,
3168
+ resolveEntryIconUrl
3169
+ }),
3170
+ [includeImageThumbnails, resolveEntryIconUrl]
3171
+ );
3172
+ const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState4(() => queue.snapshot());
3173
+ const iconTargetSignature = useMemo2(
3174
+ () => buildIconTargetSignature(entries, { includeImageThumbnails }),
3175
+ [entries, includeImageThumbnails]
3176
+ );
3177
+ useEffect5(() => {
3178
+ queue.activate();
3179
+ return () => {
3180
+ queue.dispose();
3181
+ };
3182
+ }, [queue]);
3183
+ useEffect5(() => {
3184
+ queue.retainEntries(entries);
3185
+ }, [entries, iconTargetSignature, queue]);
3186
+ useEffect5(() => {
3187
+ setIconUrlByCacheKey(queue.snapshot());
3188
+ return queue.subscribe(() => {
3189
+ setIconUrlByCacheKey(queue.snapshot());
3190
+ });
3191
+ }, [queue]);
3192
+ const reportEntryIconViewportEnter = useCallback3(
3193
+ (entry) => {
3194
+ queue.enterViewport(entry);
3195
+ },
3196
+ [queue]
3197
+ );
3198
+ const reportEntryIconViewportLeave = useCallback3(
3199
+ (entry) => {
3200
+ queue.leaveViewport(entry);
3201
+ },
3202
+ [queue]
3203
+ );
3204
+ return {
3205
+ iconUrlByCacheKey,
3206
+ reportEntryIconViewportEnter,
3207
+ reportEntryIconViewportLeave
3208
+ };
3209
+ }
3210
+
3211
+ // src/ui/workspaceFileManagerVisibleTree.ts
3212
+ function buildWorkspaceFileManagerVisibleTreeRows(input) {
3213
+ return appendWorkspaceFileManagerVisibleTreeRows({
3214
+ ...input,
3215
+ depth: 0
3216
+ });
3217
+ }
3218
+ function collectWorkspaceFileManagerVisibleTreeEntries(rows) {
3219
+ return rows.flatMap((row) => row.kind === "entry" ? [row.entry] : []);
3220
+ }
3221
+ function appendWorkspaceFileManagerVisibleTreeRows(input) {
3222
+ const sortedEntries = sortWorkspaceFileEntriesForArrangeMode(
3223
+ input.entries,
3224
+ input.arrangeMode
3225
+ );
3226
+ const rows = [];
3227
+ for (const entry of sortedEntries) {
3228
+ const expandable = entry.kind === "directory" && entry.hasChildren;
3229
+ const expanded = expandable ? input.expandedDirectoryPaths[entry.path] === true : false;
3230
+ const childState = input.directoryExpansionByPath[entry.path];
3231
+ rows.push({
3232
+ depth: input.depth,
3233
+ entry,
3234
+ expanded,
3235
+ expandable,
3236
+ kind: "entry",
3237
+ loadingChildren: childState?.isLoading ?? false
3238
+ });
3239
+ if (!expanded) {
3240
+ continue;
3241
+ }
3242
+ if (!childState || childState.isLoading) {
3243
+ rows.push({
3244
+ depth: input.depth + 1,
3245
+ key: `${entry.path}:loading`,
3246
+ kind: "feedback",
3247
+ parentPath: entry.path,
3248
+ status: "loading"
3249
+ });
3250
+ continue;
3251
+ }
3252
+ if (childState.error) {
3253
+ rows.push({
3254
+ depth: input.depth + 1,
3255
+ key: `${entry.path}:error`,
3256
+ kind: "feedback",
3257
+ message: childState.error,
3258
+ parentPath: entry.path,
3259
+ status: "error"
3260
+ });
3261
+ continue;
3262
+ }
3263
+ if (childState.loaded && childState.entries.length === 0) {
3264
+ rows.push({
3265
+ depth: input.depth + 1,
3266
+ key: `${entry.path}:empty`,
3267
+ kind: "feedback",
3268
+ parentPath: entry.path,
3269
+ status: "empty"
3270
+ });
3271
+ continue;
3272
+ }
3273
+ rows.push(
3274
+ ...appendWorkspaceFileManagerVisibleTreeRows({
3275
+ ...input,
3276
+ depth: input.depth + 1,
3277
+ entries: childState.entries
3278
+ })
3279
+ );
3280
+ }
3281
+ return rows;
3282
+ }
3283
+
3284
+ // src/ui/WorkspaceFileManagerPanelsContainer.tsx
3285
+ import { jsx as jsx7 } from "react/jsx-runtime";
3286
+ function WorkspaceFileManagerPanelsContainer({
3287
+ arrangeMode,
3288
+ dateLocale,
3289
+ entryDragMode,
3290
+ i18n,
3291
+ layoutMode,
3292
+ onDirectoryExpanded,
3293
+ onEntryDragStart,
3294
+ onOpenContextMenu,
3295
+ resolveEntryIconUrl,
3296
+ session,
3297
+ showPreviewPanel
3298
+ }) {
3299
+ const { state, view } = useWorkspaceFileManagerPanelsView(session);
3300
+ const arrangedEntries = useMemo3(
3301
+ () => sortWorkspaceFileEntriesForArrangeMode(state.entries, arrangeMode),
3302
+ [arrangeMode, state.entries]
3303
+ );
3304
+ const searchEntries = useMemo3(
3305
+ () => view.searchEntries.map(workspaceFileSearchEntryToEntry),
3306
+ [view.searchEntries]
3307
+ );
3308
+ const searchEntryContextByPath = useMemo3(() => {
3309
+ const contextByPath = /* @__PURE__ */ new Map();
3310
+ for (const entry of view.searchEntries) {
3311
+ contextByPath.set(entry.path, entry.directoryPath);
3312
+ }
3313
+ return contextByPath;
3314
+ }, [view.searchEntries]);
3315
+ const treeRows = useMemo3(
3316
+ () => buildWorkspaceFileManagerVisibleTreeRows({
3317
+ arrangeMode,
3318
+ directoryExpansionByPath: state.directoryExpansionByPath,
3319
+ entries: arrangedEntries,
3320
+ expandedDirectoryPaths: state.expandedDirectoryPaths
3321
+ }),
3322
+ [
3323
+ arrangeMode,
3324
+ arrangedEntries,
3325
+ state.directoryExpansionByPath,
3326
+ state.expandedDirectoryPaths
3327
+ ]
3328
+ );
3329
+ const searchTreeRows = useMemo3(
3330
+ () => searchEntries.map((entry) => ({
3331
+ depth: 0,
3332
+ entry,
3333
+ expanded: false,
3334
+ expandable: false,
3335
+ kind: "entry",
3336
+ loadingChildren: false
3337
+ })),
3338
+ [searchEntries]
3339
+ );
3340
+ const displayedEntries = view.isSearchMode ? searchEntries : arrangedEntries;
3341
+ const displayedTreeRows = view.isSearchMode ? searchTreeRows : treeRows;
3342
+ const visibleTreeEntries = useMemo3(
3343
+ () => collectWorkspaceFileManagerVisibleTreeEntries(displayedTreeRows),
3344
+ [displayedTreeRows]
3345
+ );
3346
+ const panelState = useMemo3(
3347
+ () => ({
3348
+ entries: displayedEntries,
3349
+ error: view.isSearchMode ? view.searchError : state.error,
3350
+ isLoading: view.isSearchMode ? view.isSearching : state.isLoading,
3351
+ isSearchMode: view.isSearchMode
3352
+ }),
3353
+ [
3354
+ displayedEntries,
3355
+ state.error,
3356
+ state.isLoading,
3357
+ view.isSearchMode,
3358
+ view.isSearching,
3359
+ view.searchError
3360
+ ]
3361
+ );
3362
+ const {
3363
+ iconUrlByCacheKey,
3364
+ reportEntryIconViewportEnter,
3365
+ reportEntryIconViewportLeave
3366
+ } = useWorkspaceFileEntryIconUrls({
3367
+ entries: layoutMode === "list" ? visibleTreeEntries : displayedEntries,
3368
+ includeImageThumbnails: true,
3369
+ resolveEntryIconUrl
3370
+ });
3371
+ const handleBlankContextMenu = useCallback4(
3372
+ (event) => {
3373
+ onOpenContextMenu(event, null);
3374
+ },
3375
+ [onOpenContextMenu]
3376
+ );
3377
+ const handleCancelInlineRename = useCallback4(() => {
3378
+ session.cancelInlineRename();
3379
+ }, [session]);
3380
+ const handleClearInlineRenameValidation = useCallback4(() => {
3381
+ session.clearInlineRenameValidation();
3382
+ }, [session]);
3383
+ const handleConfirmInlineRename = useCallback4(
3384
+ (newName) => session.confirmInlineRename(newName),
3385
+ [session]
3386
+ );
3387
+ const handleMoveEntry = useCallback4(
3388
+ (entry, targetDirectoryPath) => {
3389
+ void session.moveEntry(entry, targetDirectoryPath);
3390
+ },
3391
+ [session]
3392
+ );
3393
+ const handleOpenEntry = useCallback4(
3394
+ (entry) => {
3395
+ if (entry.kind === "directory") {
3396
+ onDirectoryExpanded?.(entry.path);
3397
+ }
3398
+ void session.openEntry(entry);
3399
+ },
3400
+ [onDirectoryExpanded, session]
3401
+ );
3402
+ const handleSelect = useCallback4(
3403
+ (path) => {
3404
+ session.select(path);
3405
+ },
3406
+ [session]
3407
+ );
3408
+ const handleToggleDirectoryExpanded = useCallback4(
3409
+ (entry, expanded) => {
3410
+ if (!expanded) {
3411
+ onDirectoryExpanded?.(entry.path);
3412
+ }
3413
+ void session.toggleDirectoryExpanded(entry);
3414
+ },
3415
+ [onDirectoryExpanded, session]
3416
+ );
3417
+ return /* @__PURE__ */ jsx7(
3418
+ WorkspaceFileManagerPanels,
3419
+ {
3420
+ arrangeMode,
3421
+ canMove: view.isSearchMode ? false : view.canMove,
3422
+ contextMenuEntryPath: view.contextMenuEntryPath,
3423
+ copy: i18n,
3424
+ dateLocale,
3425
+ entryContextByPath: view.isSearchMode ? searchEntryContextByPath : null,
3426
+ entryDragMode,
3427
+ iconUrlByCacheKey,
3428
+ inlineRenameEntryPath: view.inlineRenameEntryPath,
3429
+ inlineRenameValidation: view.inlineRenameValidation,
3430
+ isRenaming: view.isRenaming,
3431
+ layoutMode,
3432
+ pendingDirectoryPath: view.pendingDirectoryPath,
3433
+ previewState: view.previewState,
3434
+ selectedEntry: view.selectedEntry,
3435
+ selectedPath: view.selectedPath,
3436
+ showPreviewPanel,
3437
+ state: panelState,
3438
+ treeRows: displayedTreeRows,
3439
+ onBlankContextMenu: handleBlankContextMenu,
3440
+ onCancelInlineRename: handleCancelInlineRename,
3441
+ onClearInlineRenameValidation: handleClearInlineRenameValidation,
3442
+ onConfirmInlineRename: handleConfirmInlineRename,
3443
+ onEntryContextMenu: onOpenContextMenu,
3444
+ onEntryDragStart,
3445
+ onEntryIconViewportEnter: reportEntryIconViewportEnter,
3446
+ onEntryIconViewportLeave: reportEntryIconViewportLeave,
3447
+ onMoveEntry: handleMoveEntry,
3448
+ onOpenEntry: handleOpenEntry,
3449
+ onSelect: handleSelect,
3450
+ onToggleDirectoryExpanded: handleToggleDirectoryExpanded
3451
+ }
3452
+ );
3453
+ }
3454
+
3455
+ // src/ui/WorkspaceFileManagerToolbar.tsx
3456
+ import {
3457
+ ArrowLeftIcon as ArrowLeftIcon2,
3458
+ ArrowRightIcon as ArrowRightIcon3,
3459
+ Button as Button2,
3460
+ ChevronDownIcon,
3461
+ CloseIcon,
3462
+ DropdownMenu,
3463
+ DropdownMenuContent,
3464
+ DropdownMenuRadioGroup,
3465
+ DropdownMenuRadioItem,
3466
+ DropdownMenuSeparator,
3467
+ DropdownMenuTrigger,
3468
+ Input as Input2,
3469
+ LoadingIcon as LoadingIcon3,
3470
+ RefreshIcon,
3471
+ SearchIcon,
3472
+ Tooltip,
3473
+ TooltipContent,
3474
+ TooltipProvider,
3475
+ TooltipTrigger,
3476
+ ViewGridLinedIcon,
3477
+ ViewListLinedIcon,
3478
+ cn as cn5
3479
+ } from "@tutti-os/ui-system";
3480
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState5 } from "react";
3481
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3482
+ function WorkspaceFileManagerToolbar({
3483
+ breadcrumbs,
3484
+ canSearch,
3485
+ canGoBack,
3486
+ canGoForward,
3487
+ copy,
3488
+ currentDirectoryPath,
3489
+ isBusy,
3490
+ isLoading,
3491
+ isMutating,
3492
+ isSearching,
3493
+ arrangeMode,
3494
+ layoutMode,
3495
+ renderToolbarTrailingActions,
3496
+ searchQuery,
3497
+ onGoBack,
3498
+ onGoForward,
3499
+ onArrangeModeChange,
3500
+ onLayoutModeChange,
3501
+ onLoadDirectory,
3502
+ onRefresh,
3503
+ onSearchClear,
3504
+ onSearchQueryChange
3505
+ }) {
3506
+ const [refreshAnimationKey, setRefreshAnimationKey] = useState5(0);
3507
+ const handleRefresh = () => {
3508
+ setRefreshAnimationKey((currentKey) => currentKey + 1);
3509
+ onRefresh();
3510
+ };
3511
+ 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: [
3512
+ /* @__PURE__ */ jsx8(
3513
+ ToolbarIconButton,
3514
+ {
3515
+ ariaLabel: copy.t("backLabel"),
3516
+ disabled: !canGoBack || isLoading || isBusy,
3517
+ title: copy.t("backLabel"),
3518
+ onClick: onGoBack,
3519
+ children: /* @__PURE__ */ jsx8(ArrowLeftIcon2, { className: "size-4" })
3520
+ }
3521
+ ),
3522
+ /* @__PURE__ */ jsx8(
3523
+ ToolbarIconButton,
3524
+ {
3525
+ ariaLabel: copy.t("forwardLabel"),
3526
+ disabled: !canGoForward || isLoading || isBusy,
3527
+ title: copy.t("forwardLabel"),
3528
+ onClick: onGoForward,
3529
+ children: /* @__PURE__ */ jsx8(ArrowRightIcon3, { className: "size-4" })
3530
+ }
3531
+ ),
3532
+ /* @__PURE__ */ jsx8(
3533
+ "nav",
3534
+ {
3535
+ "aria-label": currentDirectoryPath,
3536
+ className: "@max-[600px]/workspace-file-manager:flex-auto flex min-w-0 flex-1 overflow-hidden pr-2",
3537
+ "data-workspace-file-manager-path": "",
3538
+ children: /* @__PURE__ */ jsx8("ol", { className: "flex min-w-0 max-w-full flex-1 items-center gap-2 overflow-hidden", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsx8(
3539
+ BreadcrumbButton,
3540
+ {
3541
+ active: index === breadcrumbs.length - 1,
3542
+ label: crumb.label,
3543
+ showSeparator: index > 0,
3544
+ onClick: () => {
3545
+ onLoadDirectory(crumb.path);
3546
+ }
3547
+ },
3548
+ crumb.path
3549
+ )) })
3550
+ }
3551
+ ),
3552
+ /* @__PURE__ */ jsxs6("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: [
3553
+ /* @__PURE__ */ jsx8(
3554
+ LayoutModeToggle,
3555
+ {
3556
+ arrangeMode,
3557
+ copy,
3558
+ layoutMode,
3559
+ onArrangeModeChange,
3560
+ onLayoutModeChange
3561
+ }
3562
+ ),
3563
+ /* @__PURE__ */ jsxs6(
3564
+ ToolbarActionButton,
3565
+ {
3566
+ disabled: isLoading || isMutating || isBusy,
3567
+ onClick: handleRefresh,
3568
+ children: [
3569
+ isLoading ? /* @__PURE__ */ jsx8(LoadingIcon3, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx8(
3570
+ RefreshIcon,
3571
+ {
3572
+ className: cn5(
3573
+ "size-4",
3574
+ refreshAnimationKey > 0 && "motion-safe:animate-[spin_520ms_cubic-bezier(0.4,0,0.2,1)_1_reverse]"
3575
+ )
3576
+ },
3577
+ refreshAnimationKey
3578
+ ),
3579
+ /* @__PURE__ */ jsx8("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
3580
+ ]
3581
+ }
3582
+ ),
3583
+ renderToolbarTrailingActions?.({
3584
+ currentDirectoryPath,
3585
+ isBusy,
3586
+ isLoading,
3587
+ isMutating
3588
+ }) ?? null,
3589
+ /* @__PURE__ */ jsx8(
3590
+ WorkspaceFileManagerToolbarSearch,
3591
+ {
3592
+ canSearch,
3593
+ copy,
3594
+ isSearching,
3595
+ searchQuery,
3596
+ onClear: onSearchClear,
3597
+ onSearchQueryChange
3598
+ }
3599
+ )
3600
+ ] })
3601
+ ] });
3602
+ }
3603
+ function WorkspaceFileManagerToolbarSearch({
3604
+ canSearch,
3605
+ copy,
3606
+ isSearching,
3607
+ searchQuery,
3608
+ onClear,
3609
+ onSearchQueryChange
3610
+ }) {
3611
+ const [expanded, setExpanded] = useState5(searchQuery.trim().length > 0);
3612
+ const [inputValue, setInputValue] = useState5(searchQuery);
3613
+ const inputRef = useRef6(null);
3614
+ const isComposingRef = useRef6(false);
3615
+ const active = expanded || searchQuery.trim().length > 0;
3616
+ useEffect6(() => {
3617
+ if (searchQuery.trim().length > 0) {
3618
+ setExpanded(true);
3619
+ }
3620
+ }, [searchQuery]);
3621
+ useEffect6(() => {
3622
+ if (!expanded) {
3623
+ return;
3624
+ }
3625
+ inputRef.current?.focus();
3626
+ }, [expanded]);
3627
+ useEffect6(() => {
3628
+ if (isComposingRef.current) {
3629
+ return;
3630
+ }
3631
+ setInputValue(searchQuery);
3632
+ }, [searchQuery]);
3633
+ const commitInputValue = (value) => {
3634
+ setInputValue(value);
3635
+ onSearchQueryChange(value);
3636
+ };
3637
+ const handleClear = () => {
3638
+ isComposingRef.current = false;
3639
+ setInputValue("");
3640
+ onClear();
3641
+ setExpanded(false);
3642
+ };
3643
+ if (!active) {
3644
+ return /* @__PURE__ */ jsx8(
3645
+ ToolbarIconButton,
3646
+ {
3647
+ ariaLabel: copy.t("searchPlaceholder"),
3648
+ disabled: !canSearch,
3649
+ title: copy.t("searchPlaceholder"),
3650
+ onClick: () => {
3651
+ setExpanded(true);
3652
+ },
3653
+ children: /* @__PURE__ */ jsx8(SearchIcon, { className: "size-4" })
3654
+ }
3655
+ );
3656
+ }
3657
+ return /* @__PURE__ */ jsxs6(
3658
+ "div",
3659
+ {
3660
+ className: cn5(
3661
+ "relative h-7 w-[min(220px,34vw)] flex-none overflow-hidden rounded-md bg-[var(--transparency-block)] transition-[width,opacity,background-color] duration-200 ease-out",
3662
+ "@max-[600px]/workspace-file-manager:w-[min(170px,42vw)]",
3663
+ !canSearch && "opacity-60"
3664
+ ),
3665
+ children: [
3666
+ /* @__PURE__ */ jsx8(SearchIcon, { className: "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 text-[var(--text-tertiary)]" }),
3667
+ /* @__PURE__ */ jsx8(
3668
+ Input2,
3669
+ {
3670
+ "aria-label": copy.t("searchPlaceholder"),
3671
+ className: "h-full border-0 bg-transparent pr-8 pl-8 text-sm shadow-none focus-visible:ring-0",
3672
+ disabled: !canSearch,
3673
+ placeholder: copy.t("searchPlaceholder"),
3674
+ ref: inputRef,
3675
+ value: inputValue,
3676
+ onBlur: (event) => {
3677
+ commitInputValue(event.currentTarget.value);
3678
+ },
3679
+ onChange: (event) => {
3204
3680
  const nextValue = event.currentTarget.value;
3205
3681
  if (isComposingRef.current) {
3206
3682
  setInputValue(nextValue);
@@ -3227,14 +3703,14 @@ function WorkspaceFileManagerToolbarSearch({
3227
3703
  }
3228
3704
  }
3229
3705
  ),
3230
- /* @__PURE__ */ jsx7(
3706
+ /* @__PURE__ */ jsx8(
3231
3707
  "button",
3232
3708
  {
3233
3709
  "aria-label": copy.t("clearSearchLabel"),
3234
3710
  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)]",
3235
3711
  type: "button",
3236
3712
  onClick: handleClear,
3237
- children: isSearching ? /* @__PURE__ */ jsx7(LoadingIcon3, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx7(CloseIcon, { className: "size-3" })
3713
+ children: isSearching ? /* @__PURE__ */ jsx8(LoadingIcon3, { className: "size-3 animate-spin" }) : /* @__PURE__ */ jsx8(CloseIcon, { className: "size-3" })
3238
3714
  }
3239
3715
  )
3240
3716
  ]
@@ -3259,13 +3735,13 @@ function LayoutModeToggle({
3259
3735
  { label: copy.t("arrangeCreatedLabel"), mode: "created" },
3260
3736
  { label: copy.t("sizeLabel"), mode: "size" }
3261
3737
  ];
3262
- return /* @__PURE__ */ jsx7(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs6(
3738
+ return /* @__PURE__ */ jsx8(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs6(
3263
3739
  "div",
3264
3740
  {
3265
3741
  className: "flex items-center rounded-md border border-[var(--border-1)] bg-[var(--transparency-block)] p-0.5",
3266
3742
  role: "group",
3267
3743
  children: [
3268
- /* @__PURE__ */ jsx7(
3744
+ /* @__PURE__ */ jsx8(
3269
3745
  LayoutModeButton,
3270
3746
  {
3271
3747
  active: layoutMode === "icon",
@@ -3274,10 +3750,10 @@ function LayoutModeToggle({
3274
3750
  onClick: () => {
3275
3751
  onLayoutModeChange("icon");
3276
3752
  },
3277
- children: /* @__PURE__ */ jsx7(ViewGridLinedIcon, { className: "size-4" })
3753
+ children: /* @__PURE__ */ jsx8(ViewGridLinedIcon, { className: "size-4" })
3278
3754
  }
3279
3755
  ),
3280
- /* @__PURE__ */ jsx7(
3756
+ /* @__PURE__ */ jsx8(
3281
3757
  LayoutModeButton,
3282
3758
  {
3283
3759
  active: layoutMode === "list",
@@ -3286,12 +3762,12 @@ function LayoutModeToggle({
3286
3762
  onClick: () => {
3287
3763
  onLayoutModeChange("list");
3288
3764
  },
3289
- children: /* @__PURE__ */ jsx7(ViewListLinedIcon, { className: "size-4" })
3765
+ children: /* @__PURE__ */ jsx8(ViewListLinedIcon, { className: "size-4" })
3290
3766
  }
3291
3767
  ),
3292
- /* @__PURE__ */ jsx7("span", { className: "mx-0.5 h-4 w-px bg-[var(--border-1)]", "aria-hidden": true }),
3768
+ /* @__PURE__ */ jsx8("span", { className: "mx-0.5 h-4 w-px bg-[var(--border-1)]", "aria-hidden": true }),
3293
3769
  /* @__PURE__ */ jsxs6(DropdownMenu, { children: [
3294
- /* @__PURE__ */ jsx7(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
3770
+ /* @__PURE__ */ jsx8(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx8(
3295
3771
  Button2,
3296
3772
  {
3297
3773
  "aria-label": copy.t("arrangeMenuLabel"),
@@ -3299,17 +3775,17 @@ function LayoutModeToggle({
3299
3775
  size: "icon-sm",
3300
3776
  type: "button",
3301
3777
  variant: "ghost",
3302
- children: /* @__PURE__ */ jsx7(ChevronDownIcon, { className: "size-3.5" })
3778
+ children: /* @__PURE__ */ jsx8(ChevronDownIcon, { className: "size-3.5" })
3303
3779
  }
3304
3780
  ) }),
3305
- /* @__PURE__ */ jsx7(
3781
+ /* @__PURE__ */ jsx8(
3306
3782
  DropdownMenuContent,
3307
3783
  {
3308
3784
  align: "end",
3309
3785
  className: "min-w-[236px] px-1 py-1",
3310
3786
  sideOffset: 7,
3311
3787
  style: { zIndex: "var(--z-panel-popover)" },
3312
- children: /* @__PURE__ */ jsx7(
3788
+ children: /* @__PURE__ */ jsx8(
3313
3789
  DropdownMenuRadioGroup,
3314
3790
  {
3315
3791
  value: arrangeMode,
@@ -3319,8 +3795,8 @@ function LayoutModeToggle({
3319
3795
  );
3320
3796
  },
3321
3797
  children: arrangeOptions.map((option, index) => /* @__PURE__ */ jsxs6("div", { children: [
3322
- index === 1 ? /* @__PURE__ */ jsx7(DropdownMenuSeparator, {}) : null,
3323
- /* @__PURE__ */ jsx7(
3798
+ index === 1 ? /* @__PURE__ */ jsx8(DropdownMenuSeparator, {}) : null,
3799
+ /* @__PURE__ */ jsx8(
3324
3800
  DropdownMenuRadioItem,
3325
3801
  {
3326
3802
  className: "h-8 text-sm font-normal",
@@ -3346,7 +3822,7 @@ function LayoutModeButton({
3346
3822
  tooltipLabel
3347
3823
  }) {
3348
3824
  return /* @__PURE__ */ jsxs6(Tooltip, { children: [
3349
- /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7(
3825
+ /* @__PURE__ */ jsx8(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx8(
3350
3826
  Button2,
3351
3827
  {
3352
3828
  "aria-label": ariaLabel,
@@ -3362,7 +3838,7 @@ function LayoutModeButton({
3362
3838
  children
3363
3839
  }
3364
3840
  ) }),
3365
- /* @__PURE__ */ jsx7(TooltipContent, { side: "bottom", children: tooltipLabel })
3841
+ /* @__PURE__ */ jsx8(TooltipContent, { side: "bottom", children: tooltipLabel })
3366
3842
  ] });
3367
3843
  }
3368
3844
  function ToolbarIconButton({
@@ -3372,7 +3848,7 @@ function ToolbarIconButton({
3372
3848
  onClick,
3373
3849
  title
3374
3850
  }) {
3375
- return /* @__PURE__ */ jsx7(
3851
+ return /* @__PURE__ */ jsx8(
3376
3852
  Button2,
3377
3853
  {
3378
3854
  "aria-label": ariaLabel,
@@ -3392,7 +3868,7 @@ function ToolbarActionButton({
3392
3868
  disabled,
3393
3869
  onClick
3394
3870
  }) {
3395
- return /* @__PURE__ */ jsx7(
3871
+ return /* @__PURE__ */ jsx8(
3396
3872
  Button2,
3397
3873
  {
3398
3874
  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",
@@ -3412,8 +3888,8 @@ function BreadcrumbButton({
3412
3888
  showSeparator
3413
3889
  }) {
3414
3890
  return /* @__PURE__ */ jsxs6("li", { className: "flex min-w-0 items-center gap-2", children: [
3415
- showSeparator ? /* @__PURE__ */ jsx7("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
3416
- /* @__PURE__ */ jsx7(
3891
+ showSeparator ? /* @__PURE__ */ jsx8("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
3892
+ /* @__PURE__ */ jsx8(
3417
3893
  "button",
3418
3894
  {
3419
3895
  className: cn5(
@@ -3439,7 +3915,7 @@ import {
3439
3915
  TooltipTrigger as TooltipTrigger2,
3440
3916
  cn as cn6
3441
3917
  } from "@tutti-os/ui-system";
3442
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3918
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
3443
3919
  function WorkspaceFileManagerSidebar({
3444
3920
  disabled,
3445
3921
  locationSections,
@@ -3453,14 +3929,14 @@ function WorkspaceFileManagerSidebar({
3453
3929
  if (visibleSections.length === 0) {
3454
3930
  return null;
3455
3931
  }
3456
- return /* @__PURE__ */ jsx8(
3932
+ return /* @__PURE__ */ jsx9(
3457
3933
  "aside",
3458
3934
  {
3459
3935
  className: "@max-[600px]/workspace-file-manager:hidden flex shrink-0 flex-col border-r border-[var(--border-1)] bg-[var(--background-panel)]",
3460
3936
  style: { minWidth: width, width },
3461
- 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: [
3462
- /* @__PURE__ */ jsx8("h2", { className: "mb-1 px-2 text-[11px] font-medium uppercase tracking-normal text-[var(--text-tertiary)]", children: section.label }),
3463
- /* @__PURE__ */ jsx8("div", { className: "flex flex-col gap-0.5", children: section.locations.map((location) => /* @__PURE__ */ jsx8(
3937
+ children: /* @__PURE__ */ jsx9(ScrollArea2, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx9(TooltipProvider2, { delayDuration: 350, children: /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-3 px-2 py-3", children: visibleSections.map((section) => /* @__PURE__ */ jsxs7("section", { className: "min-w-0", children: [
3938
+ /* @__PURE__ */ jsx9("h2", { className: "mb-1 px-2 text-[11px] font-medium uppercase tracking-normal text-[var(--text-tertiary)]", children: section.label }),
3939
+ /* @__PURE__ */ jsx9("div", { className: "flex flex-col gap-0.5", children: section.locations.map((location) => /* @__PURE__ */ jsx9(
3464
3940
  WorkspaceFileManagerSidebarLocation,
3465
3941
  {
3466
3942
  active: location.id === selectedLocationId,
@@ -3496,8 +3972,8 @@ function WorkspaceFileManagerSidebarLocation({
3496
3972
  onSelectLocation(location);
3497
3973
  },
3498
3974
  children: [
3499
- location.kind === "external" && location.iconUrl ? /* @__PURE__ */ jsx8(ExternalLocationIcon, { location }) : /* @__PURE__ */ jsx8(Icon, { className: "size-4 flex-none" }),
3500
- /* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: location.label })
3975
+ location.kind === "external" && location.iconUrl ? /* @__PURE__ */ jsx9(ExternalLocationIcon, { location }) : /* @__PURE__ */ jsx9(Icon, { className: "size-4 flex-none" }),
3976
+ /* @__PURE__ */ jsx9("span", { className: "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap", children: location.label })
3501
3977
  ]
3502
3978
  }
3503
3979
  );
@@ -3505,14 +3981,14 @@ function WorkspaceFileManagerSidebarLocation({
3505
3981
  return content;
3506
3982
  }
3507
3983
  return /* @__PURE__ */ jsxs7(Tooltip2, { children: [
3508
- /* @__PURE__ */ jsx8(TooltipTrigger2, { asChild: true, children: content }),
3509
- /* @__PURE__ */ jsx8(TooltipContent2, { side: "right", children: location.contextLabel })
3984
+ /* @__PURE__ */ jsx9(TooltipTrigger2, { asChild: true, children: content }),
3985
+ /* @__PURE__ */ jsx9(TooltipContent2, { side: "right", children: location.contextLabel })
3510
3986
  ] });
3511
3987
  }
3512
3988
  function ExternalLocationIcon({
3513
3989
  location
3514
3990
  }) {
3515
- return /* @__PURE__ */ jsx8(
3991
+ return /* @__PURE__ */ jsx9(
3516
3992
  "img",
3517
3993
  {
3518
3994
  alt: "",
@@ -3523,12 +3999,12 @@ function ExternalLocationIcon({
3523
3999
  }
3524
4000
 
3525
4001
  // src/ui/useWorkspaceFileManagerArrangeMode.ts
3526
- import { useCallback as useCallback3, useState as useState5 } from "react";
4002
+ import { useCallback as useCallback5, useState as useState6 } from "react";
3527
4003
  function useWorkspaceFileManagerArrangeMode() {
3528
- const [arrangeMode, setArrangeModeState] = useState5(
4004
+ const [arrangeMode, setArrangeModeState] = useState6(
3529
4005
  readWorkspaceFileManagerArrangeMode
3530
4006
  );
3531
- const setArrangeMode = useCallback3(
4007
+ const setArrangeMode = useCallback5(
3532
4008
  (nextArrangeMode) => {
3533
4009
  setArrangeModeState(nextArrangeMode);
3534
4010
  writeWorkspaceFileManagerArrangeMode(nextArrangeMode);
@@ -3536,270 +4012,45 @@ function useWorkspaceFileManagerArrangeMode() {
3536
4012
  []
3537
4013
  );
3538
4014
  return { arrangeMode, setArrangeMode };
3539
- }
3540
-
3541
- // src/ui/useWorkspaceFileManagerLayoutMode.ts
3542
- import { useCallback as useCallback4, useState as useState6 } from "react";
3543
-
3544
- // src/ui/workspaceFileManagerLayoutMode.ts
3545
- var workspaceFileManagerLayoutModeStorageKey = "tutti.workspace-file-manager.layout-mode";
3546
- function readWorkspaceFileManagerLayoutMode() {
3547
- if (typeof window === "undefined") {
3548
- return "list";
3549
- }
3550
- const stored = window.localStorage.getItem(
3551
- workspaceFileManagerLayoutModeStorageKey
3552
- );
3553
- return stored === "icon" ? "icon" : "list";
3554
- }
3555
- function writeWorkspaceFileManagerLayoutMode(layoutMode) {
3556
- if (typeof window === "undefined") {
3557
- return;
3558
- }
3559
- window.localStorage.setItem(
3560
- workspaceFileManagerLayoutModeStorageKey,
3561
- layoutMode
3562
- );
3563
- }
3564
-
3565
- // src/ui/useWorkspaceFileManagerLayoutMode.ts
3566
- function useWorkspaceFileManagerLayoutMode() {
3567
- const [layoutMode, setLayoutModeState] = useState6(
3568
- readWorkspaceFileManagerLayoutMode
3569
- );
3570
- const setLayoutMode = useCallback4(
3571
- (nextLayoutMode) => {
3572
- setLayoutModeState(nextLayoutMode);
3573
- writeWorkspaceFileManagerLayoutMode(nextLayoutMode);
3574
- },
3575
- []
3576
- );
3577
- return { layoutMode, setLayoutMode };
3578
- }
3579
-
3580
- // src/ui/useWorkspaceFileEntryIconUrls.ts
3581
- import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo2, useState as useState7 } from "react";
3582
-
3583
- // src/ui/workspaceFileEntryIconUrlQueue.ts
3584
- var defaultMaxConcurrentIconRequests = 3;
3585
- var imageThumbnailCacheKeyPrefix = "image-thumbnail:";
3586
- function shouldKeepResolvedIconAfterViewportLeave(cacheKey) {
3587
- return cacheKey.startsWith(imageThumbnailCacheKeyPrefix);
3588
- }
3589
- function createWorkspaceFileEntryIconUrlQueue(options) {
3590
- const maxConcurrent = Math.max(
3591
- 1,
3592
- Math.floor(options.maxConcurrent ?? defaultMaxConcurrentIconRequests)
3593
- );
3594
- const iconUrlByCacheKey = /* @__PURE__ */ new Map();
3595
- const inFlightCacheKeys = /* @__PURE__ */ new Set();
3596
- const listeners = /* @__PURE__ */ new Set();
3597
- const queuedEntries = /* @__PURE__ */ new Map();
3598
- const visibleReferenceCountByCacheKey = /* @__PURE__ */ new Map();
3599
- let activeCount = 0;
3600
- let disposed = false;
3601
- let retainedCacheKeys = null;
3602
- const policyOptions = {
3603
- includeImageThumbnails: options.includeImageThumbnails
3604
- };
3605
- function publish() {
3606
- for (const listener of listeners) {
3607
- listener();
3608
- }
3609
- }
3610
- function isRetained(cacheKey) {
3611
- return retainedCacheKeys === null || retainedCacheKeys.has(cacheKey);
3612
- }
3613
- function isVisible(cacheKey) {
3614
- return (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) > 0;
3615
- }
3616
- function shouldResolveCacheKey(cacheKey) {
3617
- return isVisible(cacheKey) || shouldKeepResolvedIconAfterViewportLeave(cacheKey);
3618
- }
3619
- function retainVisibleReference(cacheKey) {
3620
- visibleReferenceCountByCacheKey.set(
3621
- cacheKey,
3622
- (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) + 1
3623
- );
3624
- }
3625
- function releaseVisibleReference(cacheKey) {
3626
- const nextCount = (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) - 1;
3627
- if (nextCount > 0) {
3628
- visibleReferenceCountByCacheKey.set(cacheKey, nextCount);
3629
- return;
3630
- }
3631
- visibleReferenceCountByCacheKey.delete(cacheKey);
3632
- if (shouldKeepResolvedIconAfterViewportLeave(cacheKey)) {
3633
- return;
3634
- }
3635
- queuedEntries.delete(cacheKey);
3636
- const hadCachedIcon = iconUrlByCacheKey.delete(cacheKey);
3637
- if (hadCachedIcon) {
3638
- publish();
3639
- }
3640
- }
3641
- function drain() {
3642
- if (disposed || !options.resolveEntryIconUrl) {
3643
- return;
3644
- }
3645
- while (activeCount < maxConcurrent && queuedEntries.size > 0) {
3646
- const next = queuedEntries.entries().next().value;
3647
- if (!next) {
3648
- return;
3649
- }
3650
- const [cacheKey, entry] = next;
3651
- queuedEntries.delete(cacheKey);
3652
- if (!shouldResolveCacheKey(cacheKey) || iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey)) {
3653
- continue;
3654
- }
3655
- activeCount += 1;
3656
- inFlightCacheKeys.add(cacheKey);
3657
- void options.resolveEntryIconUrl(entry).then((iconUrl) => {
3658
- const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
3659
- if (shouldStore) {
3660
- iconUrlByCacheKey.set(cacheKey, iconUrl?.trim() || null);
3661
- publish();
3662
- }
3663
- }).catch(() => {
3664
- const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
3665
- if (shouldStore) {
3666
- iconUrlByCacheKey.set(cacheKey, null);
3667
- publish();
3668
- }
3669
- }).finally(() => {
3670
- activeCount -= 1;
3671
- inFlightCacheKeys.delete(cacheKey);
3672
- drain();
3673
- });
3674
- }
3675
- }
3676
- return {
3677
- activate() {
3678
- disposed = false;
3679
- },
3680
- dispose() {
3681
- disposed = true;
3682
- queuedEntries.clear();
3683
- visibleReferenceCountByCacheKey.clear();
3684
- listeners.clear();
3685
- },
3686
- leaveViewport(entry) {
3687
- if (disposed || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
3688
- return;
3689
- }
3690
- const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
3691
- releaseVisibleReference(cacheKey);
3692
- },
3693
- enterViewport(entry) {
3694
- if (disposed || !options.resolveEntryIconUrl || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
3695
- return;
3696
- }
3697
- const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
3698
- retainVisibleReference(cacheKey);
3699
- if (iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey) || queuedEntries.has(cacheKey)) {
3700
- return;
3701
- }
3702
- queuedEntries.set(cacheKey, entry);
3703
- drain();
3704
- },
3705
- retainEntries(entries) {
3706
- retainedCacheKeys = new Set(
3707
- entries.filter(
3708
- (entry) => shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)
3709
- ).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry))
3710
- );
3711
- let changed = false;
3712
- for (const cacheKey of iconUrlByCacheKey.keys()) {
3713
- if (!isRetained(cacheKey)) {
3714
- iconUrlByCacheKey.delete(cacheKey);
3715
- changed = true;
3716
- continue;
3717
- }
3718
- if (iconUrlByCacheKey.get(cacheKey) === null) {
3719
- iconUrlByCacheKey.delete(cacheKey);
3720
- changed = true;
3721
- }
3722
- }
3723
- for (const cacheKey of queuedEntries.keys()) {
3724
- if (!isRetained(cacheKey)) {
3725
- queuedEntries.delete(cacheKey);
3726
- }
3727
- }
3728
- for (const cacheKey of visibleReferenceCountByCacheKey.keys()) {
3729
- if (!isRetained(cacheKey)) {
3730
- visibleReferenceCountByCacheKey.delete(cacheKey);
3731
- }
3732
- }
3733
- if (changed) {
3734
- publish();
3735
- }
3736
- },
3737
- snapshot() {
3738
- return new Map(iconUrlByCacheKey);
3739
- },
3740
- subscribe(listener) {
3741
- if (disposed) {
3742
- return () => {
3743
- };
3744
- }
3745
- listeners.add(listener);
3746
- return () => {
3747
- listeners.delete(listener);
3748
- };
3749
- }
3750
- };
3751
- }
3752
-
3753
- // src/ui/useWorkspaceFileEntryIconUrls.ts
3754
- function buildIconTargetSignature(entries, options) {
3755
- return entries.filter((entry) => shouldResolveWorkspaceFileEntryIcon(entry, options)).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
3756
- }
3757
- function useWorkspaceFileEntryIconUrls(input) {
3758
- const { entries, includeImageThumbnails, resolveEntryIconUrl } = input;
3759
- const queue = useMemo2(
3760
- () => createWorkspaceFileEntryIconUrlQueue({
3761
- includeImageThumbnails,
3762
- resolveEntryIconUrl
3763
- }),
3764
- [includeImageThumbnails, resolveEntryIconUrl]
4015
+ }
4016
+
4017
+ // src/ui/useWorkspaceFileManagerLayoutMode.ts
4018
+ import { useCallback as useCallback6, useState as useState7 } from "react";
4019
+
4020
+ // src/ui/workspaceFileManagerLayoutMode.ts
4021
+ var workspaceFileManagerLayoutModeStorageKey = "tutti.workspace-file-manager.layout-mode";
4022
+ function readWorkspaceFileManagerLayoutMode() {
4023
+ if (typeof window === "undefined") {
4024
+ return "list";
4025
+ }
4026
+ const stored = window.localStorage.getItem(
4027
+ workspaceFileManagerLayoutModeStorageKey
3765
4028
  );
3766
- const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState7(() => queue.snapshot());
3767
- const iconTargetSignature = useMemo2(
3768
- () => buildIconTargetSignature(entries, { includeImageThumbnails }),
3769
- [entries, includeImageThumbnails]
4029
+ return stored === "icon" ? "icon" : "list";
4030
+ }
4031
+ function writeWorkspaceFileManagerLayoutMode(layoutMode) {
4032
+ if (typeof window === "undefined") {
4033
+ return;
4034
+ }
4035
+ window.localStorage.setItem(
4036
+ workspaceFileManagerLayoutModeStorageKey,
4037
+ layoutMode
3770
4038
  );
3771
- useEffect6(() => {
3772
- queue.activate();
3773
- return () => {
3774
- queue.dispose();
3775
- };
3776
- }, [queue]);
3777
- useEffect6(() => {
3778
- queue.retainEntries(entries);
3779
- }, [entries, iconTargetSignature, queue]);
3780
- useEffect6(() => {
3781
- setIconUrlByCacheKey(queue.snapshot());
3782
- return queue.subscribe(() => {
3783
- setIconUrlByCacheKey(queue.snapshot());
3784
- });
3785
- }, [queue]);
3786
- const reportEntryIconViewportEnter = useCallback5(
3787
- (entry) => {
3788
- queue.enterViewport(entry);
3789
- },
3790
- [queue]
4039
+ }
4040
+
4041
+ // src/ui/useWorkspaceFileManagerLayoutMode.ts
4042
+ function useWorkspaceFileManagerLayoutMode() {
4043
+ const [layoutMode, setLayoutModeState] = useState7(
4044
+ readWorkspaceFileManagerLayoutMode
3791
4045
  );
3792
- const reportEntryIconViewportLeave = useCallback5(
3793
- (entry) => {
3794
- queue.leaveViewport(entry);
4046
+ const setLayoutMode = useCallback6(
4047
+ (nextLayoutMode) => {
4048
+ setLayoutModeState(nextLayoutMode);
4049
+ writeWorkspaceFileManagerLayoutMode(nextLayoutMode);
3795
4050
  },
3796
- [queue]
4051
+ []
3797
4052
  );
3798
- return {
3799
- iconUrlByCacheKey,
3800
- reportEntryIconViewportEnter,
3801
- reportEntryIconViewportLeave
3802
- };
4053
+ return { layoutMode, setLayoutMode };
3803
4054
  }
3804
4055
 
3805
4056
  // src/ui/workspaceFileManagerAnalytics.ts
@@ -3810,81 +4061,8 @@ function normalizeDirectoryPath(path) {
3810
4061
  return path.trim().replaceAll("\\", "/").replace(/\/+$/u, "");
3811
4062
  }
3812
4063
 
3813
- // src/ui/workspaceFileManagerVisibleTree.ts
3814
- function buildWorkspaceFileManagerVisibleTreeRows(input) {
3815
- return appendWorkspaceFileManagerVisibleTreeRows({
3816
- ...input,
3817
- depth: 0
3818
- });
3819
- }
3820
- function collectWorkspaceFileManagerVisibleTreeEntries(rows) {
3821
- return rows.flatMap((row) => row.kind === "entry" ? [row.entry] : []);
3822
- }
3823
- function appendWorkspaceFileManagerVisibleTreeRows(input) {
3824
- const sortedEntries = sortWorkspaceFileEntriesForArrangeMode(
3825
- input.entries,
3826
- input.arrangeMode
3827
- );
3828
- const rows = [];
3829
- for (const entry of sortedEntries) {
3830
- const expandable = entry.kind === "directory" && entry.hasChildren;
3831
- const expanded = expandable ? input.expandedDirectoryPaths[entry.path] === true : false;
3832
- const childState = input.directoryExpansionByPath[entry.path];
3833
- rows.push({
3834
- depth: input.depth,
3835
- entry,
3836
- expanded,
3837
- expandable,
3838
- kind: "entry",
3839
- loadingChildren: childState?.isLoading ?? false
3840
- });
3841
- if (!expanded) {
3842
- continue;
3843
- }
3844
- if (!childState || childState.isLoading) {
3845
- rows.push({
3846
- depth: input.depth + 1,
3847
- key: `${entry.path}:loading`,
3848
- kind: "feedback",
3849
- parentPath: entry.path,
3850
- status: "loading"
3851
- });
3852
- continue;
3853
- }
3854
- if (childState.error) {
3855
- rows.push({
3856
- depth: input.depth + 1,
3857
- key: `${entry.path}:error`,
3858
- kind: "feedback",
3859
- message: childState.error,
3860
- parentPath: entry.path,
3861
- status: "error"
3862
- });
3863
- continue;
3864
- }
3865
- if (childState.loaded && childState.entries.length === 0) {
3866
- rows.push({
3867
- depth: input.depth + 1,
3868
- key: `${entry.path}:empty`,
3869
- kind: "feedback",
3870
- parentPath: entry.path,
3871
- status: "empty"
3872
- });
3873
- continue;
3874
- }
3875
- rows.push(
3876
- ...appendWorkspaceFileManagerVisibleTreeRows({
3877
- ...input,
3878
- depth: input.depth + 1,
3879
- entries: childState.entries
3880
- })
3881
- );
3882
- }
3883
- return rows;
3884
- }
3885
-
3886
4064
  // src/ui/WorkspaceFileManager.tsx
3887
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
4065
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
3888
4066
  var workspaceFileManagerSearchDebounceMs = 180;
3889
4067
  function WorkspaceFileManager({
3890
4068
  className,
@@ -3913,7 +4091,7 @@ function WorkspaceFileManager({
3913
4091
  const { layoutMode, setLayoutMode } = useWorkspaceFileManagerLayoutMode();
3914
4092
  const rootView = useWorkspaceFileManagerRootView(session);
3915
4093
  const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
3916
- const selectedExternalLocation = useMemo3(() => {
4094
+ const selectedExternalLocation = useMemo4(() => {
3917
4095
  const location = findWorkspaceFileLocationById(
3918
4096
  rootView.locationSections,
3919
4097
  rootView.selectedLocationId
@@ -3926,7 +4104,7 @@ function WorkspaceFileManager({
3926
4104
  containerWidth,
3927
4105
  sidebarContentMinWidth
3928
4106
  ) : workspaceFileManagerSidebarDefaultWidth;
3929
- const updateSidebarWidth = useCallback6(
4107
+ const updateSidebarWidth = useCallback7(
3930
4108
  (width) => {
3931
4109
  const nextWidth = clampWorkspaceFileManagerSidebarWidth({
3932
4110
  containerWidth: rootRef.current?.getBoundingClientRect().width ?? 0,
@@ -3969,7 +4147,7 @@ function WorkspaceFileManager({
3969
4147
  observer.disconnect();
3970
4148
  };
3971
4149
  }, [hasLocationSidebar, sidebarContentMinWidth]);
3972
- const handleSidebarResizePointerDown = useCallback6(
4150
+ const handleSidebarResizePointerDown = useCallback7(
3973
4151
  (event) => {
3974
4152
  if (event.button !== 0) {
3975
4153
  return;
@@ -3998,7 +4176,7 @@ function WorkspaceFileManager({
3998
4176
  },
3999
4177
  [sidebarWidth]
4000
4178
  );
4001
- const handleSidebarResizePointerMove = useCallback6(
4179
+ const handleSidebarResizePointerMove = useCallback7(
4002
4180
  (event) => {
4003
4181
  const resize = sidebarResizeRef.current;
4004
4182
  if (!resize || resize.pointerId !== event.pointerId) {
@@ -4010,7 +4188,7 @@ function WorkspaceFileManager({
4010
4188
  },
4011
4189
  [updateSidebarWidth]
4012
4190
  );
4013
- const handleSidebarResizePointerEnd = useCallback6(
4191
+ const handleSidebarResizePointerEnd = useCallback7(
4014
4192
  (event) => {
4015
4193
  const resize = sidebarResizeRef.current;
4016
4194
  if (!resize || resize.pointerId !== event.pointerId) {
@@ -4022,7 +4200,7 @@ function WorkspaceFileManager({
4022
4200
  },
4023
4201
  []
4024
4202
  );
4025
- const handleSidebarResizeKeyDown = useCallback6(
4203
+ const handleSidebarResizeKeyDown = useCallback7(
4026
4204
  (event) => {
4027
4205
  let nextWidth = null;
4028
4206
  if (event.key === "Home") {
@@ -4115,27 +4293,30 @@ function WorkspaceFileManager({
4115
4293
  selectedExternalLocation,
4116
4294
  session
4117
4295
  ]);
4118
- function openContextMenu(event, entry) {
4119
- event.preventDefault();
4120
- event.stopPropagation();
4121
- const menuWidth = 220;
4122
- const menuHeight = 280;
4123
- const x = clampContextMenuCoordinate(
4124
- event.clientX,
4125
- window.innerWidth,
4126
- menuWidth
4127
- );
4128
- const y = clampContextMenuCoordinate(
4129
- event.clientY,
4130
- window.innerHeight,
4131
- menuHeight
4132
- );
4133
- session.openContextMenu({
4134
- entryPath: entry?.path ?? null,
4135
- x,
4136
- y
4137
- });
4138
- }
4296
+ const openContextMenu = useCallback7(
4297
+ (event, entry) => {
4298
+ event.preventDefault();
4299
+ event.stopPropagation();
4300
+ const menuWidth = 220;
4301
+ const menuHeight = 280;
4302
+ const x = clampContextMenuCoordinate(
4303
+ event.clientX,
4304
+ window.innerWidth,
4305
+ menuWidth
4306
+ );
4307
+ const y = clampContextMenuCoordinate(
4308
+ event.clientY,
4309
+ window.innerHeight,
4310
+ menuHeight
4311
+ );
4312
+ session.openContextMenu({
4313
+ entryPath: entry?.path ?? null,
4314
+ x,
4315
+ y
4316
+ });
4317
+ },
4318
+ [session]
4319
+ );
4139
4320
  return /* @__PURE__ */ jsxs8(
4140
4321
  "section",
4141
4322
  {
@@ -4154,7 +4335,7 @@ function WorkspaceFileManager({
4154
4335
  "--workspace-file-manager-dialog-overlay-z-index": "20"
4155
4336
  },
4156
4337
  children: [
4157
- hasLocationSidebar ? /* @__PURE__ */ jsx9(
4338
+ hasLocationSidebar ? /* @__PURE__ */ jsx10(
4158
4339
  WorkspaceFileManagerSidebar,
4159
4340
  {
4160
4341
  disabled: rootView.isBusy || panelsState.isLoading,
@@ -4169,7 +4350,7 @@ function WorkspaceFileManager({
4169
4350
  }
4170
4351
  }
4171
4352
  ) : null,
4172
- hasLocationSidebar ? /* @__PURE__ */ jsx9(
4353
+ hasLocationSidebar ? /* @__PURE__ */ jsx10(
4173
4354
  "div",
4174
4355
  {
4175
4356
  "aria-label": i18n.t("resizeLocationsSidebar"),
@@ -4187,8 +4368,8 @@ function WorkspaceFileManager({
4187
4368
  onPointerUp: handleSidebarResizePointerEnd
4188
4369
  }
4189
4370
  ) : null,
4190
- /* @__PURE__ */ jsx9("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: selectedExternalLocation ? renderExternalLocationContent?.(selectedExternalLocation) ?? null : /* @__PURE__ */ jsxs8(Fragment3, { children: [
4191
- /* @__PURE__ */ jsx9(
4371
+ /* @__PURE__ */ jsx10("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: selectedExternalLocation ? renderExternalLocationContent?.(selectedExternalLocation) ?? null : /* @__PURE__ */ jsxs8(Fragment3, { children: [
4372
+ /* @__PURE__ */ jsx10(
4192
4373
  WorkspaceFileManagerToolbarContainer,
4193
4374
  {
4194
4375
  i18n,
@@ -4201,7 +4382,7 @@ function WorkspaceFileManager({
4201
4382
  session
4202
4383
  }
4203
4384
  ),
4204
- /* @__PURE__ */ jsx9("div", { 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", children: /* @__PURE__ */ jsx9(
4385
+ /* @__PURE__ */ jsx10("div", { 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", children: /* @__PURE__ */ jsx10(
4205
4386
  WorkspaceFileManagerPanelsContainer,
4206
4387
  {
4207
4388
  dateLocale,
@@ -4219,8 +4400,8 @@ function WorkspaceFileManager({
4219
4400
  ) })
4220
4401
  ] }) }),
4221
4402
  !selectedExternalLocation ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
4222
- /* @__PURE__ */ jsx9(WorkspaceFileManagerDialogsContainer, { i18n, session }),
4223
- /* @__PURE__ */ jsx9(
4403
+ /* @__PURE__ */ jsx10(WorkspaceFileManagerDialogsContainer, { i18n, session }),
4404
+ /* @__PURE__ */ jsx10(
4224
4405
  WorkspaceFileManagerContextMenuContainer,
4225
4406
  {
4226
4407
  resolveContextMenu,
@@ -4245,7 +4426,7 @@ function WorkspaceFileManagerToolbarContainer({
4245
4426
  const { view } = useWorkspaceFileManagerToolbarView(session, i18n);
4246
4427
  const [searchQuery, setSearchQuery] = useState8(view.searchQuery);
4247
4428
  const submittedSearchQueryRef = useRef7(view.searchQuery);
4248
- const submitSearchQuery = useCallback6(
4429
+ const submitSearchQuery = useCallback7(
4249
4430
  (query) => {
4250
4431
  submittedSearchQueryRef.current = query;
4251
4432
  void session.search(query);
@@ -4270,11 +4451,11 @@ function WorkspaceFileManagerToolbarContainer({
4270
4451
  clearTimeout(timer);
4271
4452
  };
4272
4453
  }, [searchQuery, submitSearchQuery, view.canSearch]);
4273
- const handleSearchClear = useCallback6(() => {
4454
+ const handleSearchClear = useCallback7(() => {
4274
4455
  setSearchQuery("");
4275
4456
  submitSearchQuery("");
4276
4457
  }, [submitSearchQuery]);
4277
- return /* @__PURE__ */ jsx9(
4458
+ return /* @__PURE__ */ jsx10(
4278
4459
  WorkspaceFileManagerToolbar,
4279
4460
  {
4280
4461
  breadcrumbs: view.breadcrumbs,
@@ -4316,146 +4497,13 @@ function WorkspaceFileManagerToolbarContainer({
4316
4497
  }
4317
4498
  );
4318
4499
  }
4319
- function WorkspaceFileManagerPanelsContainer({
4320
- arrangeMode,
4321
- dateLocale,
4322
- entryDragMode,
4323
- i18n,
4324
- layoutMode,
4325
- onDirectoryExpanded,
4326
- onEntryDragStart,
4327
- onOpenContextMenu,
4328
- resolveEntryIconUrl,
4329
- session,
4330
- showPreviewPanel
4331
- }) {
4332
- const { state, view } = useWorkspaceFileManagerPanelsView(session);
4333
- const arrangedEntries = useMemo3(
4334
- () => sortWorkspaceFileEntriesForArrangeMode(state.entries, arrangeMode),
4335
- [arrangeMode, state.entries]
4336
- );
4337
- const searchEntries = useMemo3(
4338
- () => view.searchEntries.map(workspaceFileSearchEntryToEntry),
4339
- [view.searchEntries]
4340
- );
4341
- const searchEntryContextByPath = useMemo3(() => {
4342
- const contextByPath = /* @__PURE__ */ new Map();
4343
- for (const entry of view.searchEntries) {
4344
- contextByPath.set(entry.path, entry.directoryPath);
4345
- }
4346
- return contextByPath;
4347
- }, [view.searchEntries]);
4348
- const treeRows = useMemo3(
4349
- () => buildWorkspaceFileManagerVisibleTreeRows({
4350
- arrangeMode,
4351
- directoryExpansionByPath: state.directoryExpansionByPath,
4352
- entries: arrangedEntries,
4353
- expandedDirectoryPaths: state.expandedDirectoryPaths
4354
- }),
4355
- [
4356
- arrangeMode,
4357
- arrangedEntries,
4358
- state.directoryExpansionByPath,
4359
- state.expandedDirectoryPaths
4360
- ]
4361
- );
4362
- const searchTreeRows = useMemo3(
4363
- () => searchEntries.map((entry) => ({
4364
- depth: 0,
4365
- entry,
4366
- expanded: false,
4367
- expandable: false,
4368
- kind: "entry",
4369
- loadingChildren: false
4370
- })),
4371
- [searchEntries]
4372
- );
4373
- const displayedEntries = view.isSearchMode ? searchEntries : arrangedEntries;
4374
- const displayedTreeRows = view.isSearchMode ? searchTreeRows : treeRows;
4375
- const visibleTreeEntries = useMemo3(
4376
- () => collectWorkspaceFileManagerVisibleTreeEntries(displayedTreeRows),
4377
- [displayedTreeRows]
4378
- );
4379
- const {
4380
- iconUrlByCacheKey,
4381
- reportEntryIconViewportEnter,
4382
- reportEntryIconViewportLeave
4383
- } = useWorkspaceFileEntryIconUrls({
4384
- entries: layoutMode === "list" ? visibleTreeEntries : displayedEntries,
4385
- includeImageThumbnails: true,
4386
- resolveEntryIconUrl
4387
- });
4388
- return /* @__PURE__ */ jsx9(
4389
- WorkspaceFileManagerPanels,
4390
- {
4391
- arrangeMode,
4392
- canMove: view.isSearchMode ? false : view.canMove,
4393
- contextMenuEntryPath: view.contextMenuEntryPath,
4394
- copy: i18n,
4395
- dateLocale,
4396
- entryDragMode,
4397
- iconUrlByCacheKey,
4398
- inlineRenameEntryPath: view.inlineRenameEntryPath,
4399
- inlineRenameValidation: view.inlineRenameValidation,
4400
- isRenaming: view.isRenaming,
4401
- layoutMode,
4402
- pendingDirectoryPath: view.pendingDirectoryPath,
4403
- previewState: view.previewState,
4404
- entryContextByPath: view.isSearchMode ? searchEntryContextByPath : null,
4405
- treeRows: displayedTreeRows,
4406
- onEntryIconViewportEnter: reportEntryIconViewportEnter,
4407
- onEntryIconViewportLeave: reportEntryIconViewportLeave,
4408
- selectedEntry: view.selectedEntry,
4409
- selectedPath: view.selectedPath,
4410
- showPreviewPanel,
4411
- state: {
4412
- entries: displayedEntries,
4413
- error: view.isSearchMode ? view.searchError : state.error,
4414
- isLoading: view.isSearchMode ? view.isSearching : state.isLoading,
4415
- isSearchMode: view.isSearchMode
4416
- },
4417
- onBlankContextMenu: (event) => {
4418
- onOpenContextMenu(event, null);
4419
- },
4420
- onCancelInlineRename: () => {
4421
- session.cancelInlineRename();
4422
- },
4423
- onClearInlineRenameValidation: () => {
4424
- session.clearInlineRenameValidation();
4425
- },
4426
- onConfirmInlineRename: (newName) => {
4427
- return session.confirmInlineRename(newName);
4428
- },
4429
- onEntryContextMenu: onOpenContextMenu,
4430
- onEntryDragStart,
4431
- onMoveEntry: (entry, targetDirectoryPath) => {
4432
- void session.moveEntry(entry, targetDirectoryPath);
4433
- },
4434
- onOpenEntry: (entry) => {
4435
- if (entry.kind === "directory") {
4436
- onDirectoryExpanded?.(entry.path);
4437
- }
4438
- void session.openEntry(entry);
4439
- },
4440
- onSelect: (path) => {
4441
- session.select(path);
4442
- },
4443
- onToggleDirectoryExpanded: (entry, expanded) => {
4444
- if (!expanded) {
4445
- onDirectoryExpanded?.(entry.path);
4446
- }
4447
- void session.toggleDirectoryExpanded(entry);
4448
- }
4449
- }
4450
- );
4451
- }
4452
4500
  function WorkspaceFileManagerDialogsContainer({
4453
4501
  i18n,
4454
4502
  session
4455
4503
  }) {
4456
4504
  const { state, view } = useWorkspaceFileManagerDialogsView(session);
4457
4505
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
4458
- /* @__PURE__ */ jsx9(
4506
+ /* @__PURE__ */ jsx10(
4459
4507
  WorkspaceFileManagerCreateDialog,
4460
4508
  {
4461
4509
  busy: view.isBusy && state.busyAction === "create",
@@ -4472,7 +4520,7 @@ function WorkspaceFileManagerDialogsContainer({
4472
4520
  }
4473
4521
  }
4474
4522
  ),
4475
- /* @__PURE__ */ jsx9(
4523
+ /* @__PURE__ */ jsx10(
4476
4524
  WorkspaceFileManagerDeleteDialog,
4477
4525
  {
4478
4526
  busy: view.isDeleting,
@@ -4486,7 +4534,7 @@ function WorkspaceFileManagerDialogsContainer({
4486
4534
  }
4487
4535
  }
4488
4536
  ),
4489
- /* @__PURE__ */ jsx9(
4537
+ /* @__PURE__ */ jsx10(
4490
4538
  WorkspaceFileManagerUnsupportedDialog,
4491
4539
  {
4492
4540
  copy: i18n,