@tutti-os/workspace-file-manager 0.0.196 → 0.0.198

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
@@ -7,12 +7,10 @@ import {
7
7
  formatWorkspaceFileBytes,
8
8
  formatWorkspaceFileModifiedTime,
9
9
  isWorkspaceApplicationBundle,
10
- isWorkspaceFileBrowserOpenable,
11
10
  isWorkspaceFileExternalLocation,
12
11
  isWorkspaceFileRecentLocation,
13
12
  normalizeWorkspaceFilePath,
14
13
  readWorkspaceFileManagerArrangeMode,
15
- resolveWorkspaceFileActivationTarget,
16
14
  resolveWorkspaceFileEntryArrangeDateMs,
17
15
  resolveWorkspaceFileEntryIconCacheKey,
18
16
  resolveWorkspaceFileLocationDefaultId,
@@ -23,19 +21,19 @@ import {
23
21
  splitWorkspaceFileName,
24
22
  workspaceFileSearchEntryToEntry,
25
23
  writeWorkspaceFileManagerArrangeMode
26
- } from "./chunk-P4SNEQWG.js";
24
+ } from "./chunk-TNFCNCZV.js";
27
25
  import {
28
26
  createWorkspaceFileManagerI18nRuntime,
29
27
  resolveRevealInFolderLabel,
30
28
  workspaceFileManagerI18nNamespace,
31
29
  workspaceFileManagerI18nResources
32
- } from "./chunk-KKKQGRC5.js";
30
+ } from "./chunk-EI4ICYY4.js";
33
31
 
34
32
  // src/ui/WorkspaceFileManager.tsx
35
33
  import {
36
34
  useCallback as useCallback6,
37
35
  useEffect as useEffect7,
38
- useLayoutEffect as useLayoutEffect4,
36
+ useLayoutEffect as useLayoutEffect5,
39
37
  useMemo as useMemo3,
40
38
  useRef as useRef7,
41
39
  useState as useState8
@@ -43,25 +41,15 @@ import {
43
41
  import { cn as cn7 } from "@tutti-os/ui-system";
44
42
 
45
43
  // src/ui/WorkspaceFileManagerContextMenuContainer.tsx
46
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
44
+ import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState2 } from "react";
47
45
 
48
46
  // src/ui/WorkspaceFileManagerContextMenu.tsx
49
47
  import {
50
48
  ArrowLeftIcon,
51
49
  ArrowRightIcon,
52
- CopyIcon,
53
- DeleteIcon,
54
- DownloadIcon,
55
- EditIcon,
56
- EyeIcon,
57
- FileLinedIcon,
58
- ImportLinedIcon as ImportIcon,
59
50
  LaunchIcon,
60
- LocateFolderIcon,
61
51
  MenuSurface,
62
- NewWorkspaceLinedIcon,
63
52
  ViewportMenuSurface,
64
- WebIcon,
65
53
  cn
66
54
  } from "@tutti-os/ui-system";
67
55
  import {
@@ -153,47 +141,11 @@ function shouldShowOpenWithSectionDivider(input) {
153
141
  // src/ui/WorkspaceFileManagerContextMenu.tsx
154
142
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
155
143
  function WorkspaceFileManagerContextMenu({
156
- busy,
157
- copy,
158
144
  contextMenu,
159
145
  contextMenuRef,
160
- openInAppBrowserIcon,
146
+ items,
161
147
  positionMode = "local",
162
- showCopyAction,
163
- showCopyPathAction,
164
- showCreateDirectoryAction,
165
- showCreateFileAction,
166
- showDeleteAction,
167
- showImportAction,
168
- showExportAction,
169
- showOpenInAppBrowserAction,
170
- showOpenInDefaultBrowserAction,
171
- showOpenInFileViewerAction,
172
- showOpenAction,
173
- showOpenWithAction,
174
- showOpenWithOtherAction,
175
- showRevealInFolderAction,
176
- showRenameAction,
177
- revealInFolderLabel,
178
- openWithApplications,
179
- openWithLoading,
180
- resolveOpenWithApplicationIcon,
181
- onClose,
182
- onCreateDirectory,
183
- onCreateFile,
184
- onCopy,
185
- onCopyPath,
186
- onDelete,
187
- onExport,
188
- onOpen,
189
- onOpenInAppBrowser,
190
- onOpenInDefaultBrowser,
191
- onOpenInFileViewer,
192
- onOpenWithApplication,
193
- onOpenWithOtherApplication,
194
- onImport,
195
- onRevealInFolder,
196
- onRename
148
+ onClose
197
149
  }) {
198
150
  const [position, setPosition] = useState({
199
151
  x: contextMenu?.x ?? 0,
@@ -231,145 +183,11 @@ function WorkspaceFileManagerContextMenu({
231
183
  y: contextMenu.y
232
184
  })
233
185
  );
234
- }, [
235
- contextMenu,
236
- contextMenuRef,
237
- openWithApplications.length,
238
- openWithLoading,
239
- positionMode,
240
- showCopyAction,
241
- showCopyPathAction,
242
- showCreateDirectoryAction,
243
- showCreateFileAction,
244
- showExportAction,
245
- showImportAction,
246
- showOpenInAppBrowserAction,
247
- showOpenInDefaultBrowserAction,
248
- showOpenInFileViewerAction,
249
- showOpenAction,
250
- showOpenWithAction,
251
- showOpenWithOtherAction,
252
- showRevealInFolderAction,
253
- showRenameAction
254
- ]);
255
- if (!contextMenu) {
186
+ }, [contextMenu, contextMenuRef, items, positionMode]);
187
+ if (!contextMenu || items.length === 0) {
256
188
  return null;
257
189
  }
258
- const entry = contextMenu.entry;
259
- const isDirectory = entry?.kind === "directory";
260
- const editItems = [];
261
- const transferItems = [];
262
- const dangerItems = [];
263
- const createItems = [];
264
- if (!entry || isDirectory) {
265
- if (showCreateFileAction) {
266
- createItems.push({
267
- action: onCreateFile,
268
- disabled: busy,
269
- icon: /* @__PURE__ */ jsx(NewWorkspaceLinedIcon, { className: "size-4" }),
270
- key: "create-file",
271
- label: copy.t("createFileLabel")
272
- });
273
- }
274
- if (showCreateDirectoryAction) {
275
- createItems.push({
276
- action: onCreateDirectory,
277
- disabled: busy,
278
- icon: /* @__PURE__ */ jsx(FileLinedIcon, { className: "size-4" }),
279
- key: "create-directory",
280
- label: copy.t("createDirectoryLabel")
281
- });
282
- }
283
- }
284
- if (entry) {
285
- if (showRenameAction) {
286
- editItems.push({
287
- action: onRename,
288
- disabled: busy,
289
- icon: /* @__PURE__ */ jsx(EditIcon, { className: "size-4" }),
290
- key: "rename",
291
- label: copy.t("renameLabel")
292
- });
293
- }
294
- if (showCopyAction) {
295
- editItems.push({
296
- action: onCopy,
297
- disabled: busy,
298
- icon: /* @__PURE__ */ jsx(CopyIcon, { className: "size-4" }),
299
- key: "copy",
300
- label: copy.t("copyLabel")
301
- });
302
- }
303
- if (showCopyPathAction) {
304
- editItems.push({
305
- action: onCopyPath,
306
- disabled: busy,
307
- icon: /* @__PURE__ */ jsx(CopyIcon, { className: "size-4" }),
308
- key: "copy-path",
309
- label: copy.t("copyPathLabel")
310
- });
311
- }
312
- if (showRevealInFolderAction) {
313
- editItems.push({
314
- action: onRevealInFolder,
315
- disabled: busy,
316
- icon: /* @__PURE__ */ jsx(LocateFolderIcon, { className: "size-4" }),
317
- key: "reveal-in-folder",
318
- label: revealInFolderLabel
319
- });
320
- }
321
- if (isDirectory && showImportAction) {
322
- transferItems.push({
323
- action: onImport,
324
- disabled: busy,
325
- icon: /* @__PURE__ */ jsx(ImportIcon, { className: "size-4" }),
326
- key: "import",
327
- label: copy.t("importLabel")
328
- });
329
- }
330
- if (showExportAction) {
331
- transferItems.push({
332
- action: onExport,
333
- disabled: busy,
334
- icon: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-4" }),
335
- key: "export",
336
- label: copy.t("downloadLabel")
337
- });
338
- }
339
- if (showDeleteAction) {
340
- dangerItems.push({
341
- action: onDelete,
342
- disabled: busy,
343
- danger: true,
344
- icon: /* @__PURE__ */ jsx(DeleteIcon, { className: "size-4" }),
345
- key: "delete",
346
- label: copy.t("deleteLabel")
347
- });
348
- }
349
- } else {
350
- if (showImportAction) {
351
- transferItems.push({
352
- action: onImport,
353
- disabled: busy,
354
- icon: /* @__PURE__ */ jsx(ImportIcon, { className: "size-4" }),
355
- key: "import",
356
- label: copy.t("importLabel")
357
- });
358
- }
359
- }
360
- const menuGroups = entry ? [
361
- { items: createItems, key: "create" },
362
- { items: editItems, key: "edit" },
363
- { items: transferItems, key: "transfer" },
364
- { items: dangerItems, key: "danger" }
365
- ] : [
366
- { items: createItems, key: "create" },
367
- { items: transferItems, key: "transfer" }
368
- ];
369
- const visibleMenuGroups = menuGroups.filter(
370
- (group) => group.items.length > 0
371
- );
372
- return /* @__PURE__ */ jsxs(
190
+ return /* @__PURE__ */ jsx(
373
191
  MenuSurface,
374
192
  {
375
193
  ref: contextMenuRef,
@@ -387,75 +205,38 @@ function WorkspaceFileManagerContextMenu({
387
205
  onContextMenu: (event) => {
388
206
  event.preventDefault();
389
207
  },
390
- children: [
391
- entry && showOpenAction ? /* @__PURE__ */ jsx(
392
- ContextMenuActionButton,
393
- {
394
- disabled: busy,
395
- icon: /* @__PURE__ */ jsx(EyeIcon, { className: "size-4" }),
396
- label: copy.t("openLabel"),
397
- onClick: () => {
398
- onClose();
399
- void onOpen();
400
- }
401
- }
402
- ) : null,
403
- entry && showOpenWithAction ? /* @__PURE__ */ jsx(
404
- OpenWithMenuItem,
405
- {
406
- applications: openWithApplications,
407
- busy,
408
- copy,
409
- isLoading: openWithLoading,
410
- openInAppBrowserIcon,
411
- resolveOpenWithApplicationIcon,
412
- showOpenInAppBrowser: showOpenInAppBrowserAction,
413
- showOpenInDefaultBrowser: showOpenInDefaultBrowserAction,
414
- showOpenInFileViewer: showOpenInFileViewerAction,
415
- showOpenWithOther: showOpenWithOtherAction,
416
- onClose,
417
- onOpenInAppBrowser,
418
- onOpenInDefaultBrowser,
419
- onOpenInFileViewer,
420
- onOpenWithApplication,
421
- onOpenWithOtherApplication
422
- }
423
- ) : null,
424
- visibleMenuGroups.map((group, groupIndex) => /* @__PURE__ */ jsx(
425
- ContextMenuActionGroup,
426
- {
427
- items: group.items,
428
- showDivider: groupIndex > 0 || entry !== null && (showOpenAction || showOpenWithAction),
429
- onClose
430
- },
431
- group.key
432
- ))
433
- ]
208
+ children: items.map((item) => /* @__PURE__ */ jsx(ContextMenuItemRenderer, { item, onClose }, item.id))
434
209
  }
435
210
  );
436
211
  }
437
- function ContextMenuActionGroup({
438
- items,
212
+ function ContextMenuItemRenderer({
213
+ item,
439
214
  onClose,
440
- showDivider
215
+ activateOnPointerDown = false
441
216
  }) {
442
- return /* @__PURE__ */ jsxs(Fragment, { children: [
443
- showDivider ? /* @__PURE__ */ jsx(ContextMenuDivider, {}) : null,
444
- items.map((item) => /* @__PURE__ */ jsx(
445
- ContextMenuActionButton,
446
- {
447
- danger: item.danger,
448
- disabled: item.disabled,
449
- icon: item.icon,
450
- label: item.label,
451
- onClick: () => {
452
- onClose();
453
- void item.action();
217
+ switch (item.type) {
218
+ case "separator":
219
+ return /* @__PURE__ */ jsx(ContextMenuDivider, {});
220
+ case "submenu":
221
+ return /* @__PURE__ */ jsx(ContextMenuSubmenu, { item, onClose });
222
+ case "item":
223
+ return /* @__PURE__ */ jsx(
224
+ ContextMenuActionButton,
225
+ {
226
+ activateOnPointerDown,
227
+ danger: item.danger,
228
+ disabled: item.disabled,
229
+ icon: item.icon ?? /* @__PURE__ */ jsx(LaunchIcon, { className: "size-4" }),
230
+ label: item.label,
231
+ onClick: () => {
232
+ onClose();
233
+ void item.onSelect();
234
+ }
454
235
  }
455
- },
456
- item.key
457
- ))
458
- ] });
236
+ );
237
+ default:
238
+ return null;
239
+ }
459
240
  }
460
241
  function ContextMenuActionButton({
461
242
  activateOnPointerDown = false,
@@ -530,29 +311,18 @@ function ContextMenuActionButton({
530
311
  function ContextMenuDivider() {
531
312
  return /* @__PURE__ */ jsx("div", { className: "mx-2 my-0.5 h-px bg-[var(--border-1)]", role: "separator" });
532
313
  }
533
- function OpenWithMenuItem({
534
- applications,
535
- busy,
536
- copy,
537
- isLoading,
538
- openInAppBrowserIcon,
539
- resolveOpenWithApplicationIcon,
540
- showOpenInAppBrowser,
541
- showOpenInDefaultBrowser,
542
- showOpenInFileViewer,
543
- showOpenWithOther,
544
- onClose,
545
- onOpenInAppBrowser,
546
- onOpenInDefaultBrowser,
547
- onOpenInFileViewer,
548
- onOpenWithApplication,
549
- onOpenWithOtherApplication
314
+ function ContextMenuSubmenu({
315
+ item,
316
+ onClose
550
317
  }) {
551
318
  const triggerRef = useRef(null);
552
319
  const triggerButtonRef = useRef(null);
553
320
  const submenuRef = useRef(null);
554
321
  const focusSubmenuOnOpenRef = useRef(false);
555
322
  const [open, setOpen] = useState(false);
323
+ const controlledChildren = item.children;
324
+ const [asyncChildren, setAsyncChildren] = useState(null);
325
+ const [asyncLoading, setAsyncLoading] = useState(false);
556
326
  const [submenuPosition, setSubmenuPosition] = useState({
557
327
  left: 0,
558
328
  mode: "right",
@@ -560,21 +330,24 @@ function OpenWithMenuItem({
560
330
  width: OPEN_WITH_SUBMENU_WIDTH_PX
561
331
  });
562
332
  const closeTimerRef = useRef(null);
563
- const showExternalSection = showOpenInDefaultBrowser || showOpenWithOther || isLoading || applications.length > 0;
564
- const showSectionDivider = shouldShowOpenWithSectionDivider({
565
- showExternalSection,
566
- showOpenInAppBrowser,
567
- showOpenInFileViewer
568
- });
333
+ const children = controlledChildren ?? asyncChildren ?? [];
334
+ const isLoading = Boolean(item.loading) || asyncLoading;
335
+ const childActionCount = countRenderableMenuActions(children);
569
336
  const estimatedSubmenuHeight = estimateOpenWithSubmenuHeight({
570
- applicationCount: applications.length,
337
+ applicationCount: Math.max(0, childActionCount - 2),
571
338
  isLoading,
572
- showExternalSection,
573
- showOpenInAppBrowser,
574
- showOpenInDefaultBrowser,
575
- showOpenInFileViewer,
576
- showOpenWithOther
339
+ showExternalSection: childActionCount > 0 || isLoading,
340
+ showOpenInAppBrowser: false,
341
+ showOpenInDefaultBrowser: false,
342
+ showOpenInFileViewer: false,
343
+ showOpenWithOther: false
577
344
  });
345
+ useEffect(() => {
346
+ if (controlledChildren !== void 0) {
347
+ setAsyncChildren(null);
348
+ setAsyncLoading(false);
349
+ }
350
+ }, [controlledChildren]);
578
351
  const cancelClose = useCallback(() => {
579
352
  if (closeTimerRef.current !== null) {
580
353
  window.clearTimeout(closeTimerRef.current);
@@ -588,11 +361,25 @@ function OpenWithMenuItem({
588
361
  closeTimerRef.current = null;
589
362
  }, 120);
590
363
  }, [cancelClose]);
364
+ const ensureChildrenLoaded = useCallback(() => {
365
+ if (controlledChildren !== void 0 || asyncChildren !== null || !item.loadChildren || asyncLoading) {
366
+ return;
367
+ }
368
+ setAsyncLoading(true);
369
+ void item.loadChildren().then((next) => {
370
+ setAsyncChildren(next);
371
+ }).catch(() => {
372
+ setAsyncChildren([]);
373
+ }).finally(() => {
374
+ setAsyncLoading(false);
375
+ });
376
+ }, [asyncChildren, asyncLoading, controlledChildren, item]);
591
377
  const openSubmenu = useCallback(() => {
592
378
  focusSubmenuOnOpenRef.current = false;
593
379
  cancelClose();
380
+ ensureChildrenLoaded();
594
381
  setOpen(true);
595
- }, [cancelClose]);
382
+ }, [cancelClose, ensureChildrenLoaded]);
596
383
  const closeSubmenuToTrigger = useCallback(() => {
597
384
  cancelClose();
598
385
  setOpen(false);
@@ -633,7 +420,7 @@ function OpenWithMenuItem({
633
420
  return;
634
421
  }
635
422
  updateSubmenuPosition();
636
- }, [open, updateSubmenuPosition]);
423
+ }, [open, updateSubmenuPosition, children.length, isLoading]);
637
424
  useEffect(() => {
638
425
  if (!open) {
639
426
  return;
@@ -675,7 +462,7 @@ function OpenWithMenuItem({
675
462
  "flex h-8 w-full items-center gap-2 rounded-md border-0 bg-transparent px-2 text-left text-[13px] text-[var(--text-primary)] transition-colors hover:bg-transparency-block disabled:cursor-default disabled:opacity-50",
676
463
  open && "bg-transparency-block"
677
464
  ),
678
- disabled: busy,
465
+ disabled: item.disabled,
679
466
  role: "menuitem",
680
467
  type: "button",
681
468
  onClick: (event) => {
@@ -684,6 +471,7 @@ function OpenWithMenuItem({
684
471
  const next = !current;
685
472
  if (next) {
686
473
  cancelClose();
474
+ ensureChildrenLoaded();
687
475
  } else {
688
476
  focusSubmenuOnOpenRef.current = false;
689
477
  }
@@ -698,11 +486,12 @@ function OpenWithMenuItem({
698
486
  event.stopPropagation();
699
487
  focusSubmenuOnOpenRef.current = true;
700
488
  cancelClose();
489
+ ensureChildrenLoaded();
701
490
  setOpen(true);
702
491
  },
703
492
  children: [
704
- /* @__PURE__ */ jsx("span", { className: "grid size-4 flex-none place-items-center text-[var(--text-secondary)]", children: /* @__PURE__ */ jsx(LaunchIcon, { className: "size-4" }) }),
705
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: copy.t("openWithLabel") }),
493
+ /* @__PURE__ */ jsx("span", { className: "grid size-4 flex-none place-items-center text-[var(--text-secondary)]", children: item.icon ?? /* @__PURE__ */ jsx(LaunchIcon, { className: "size-4" }) }),
494
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
706
495
  /* @__PURE__ */ jsx(
707
496
  "span",
708
497
  {
@@ -718,7 +507,8 @@ function OpenWithMenuItem({
718
507
  ViewportMenuSurface,
719
508
  {
720
509
  ref: submenuRef,
721
- "aria-label": copy.t("openWithLabel"),
510
+ "aria-label": item.label,
511
+ "data-workspace-file-manager-submenu": "",
722
512
  "data-workspace-file-manager-submenu-mode": submenuPosition.mode,
723
513
  open,
724
514
  className: "max-h-[min(480px,calc(100vh-24px))] max-w-[calc(100vw-24px)] overflow-y-auto p-1",
@@ -748,22 +538,22 @@ function OpenWithMenuItem({
748
538
  if (event.key !== "ArrowDown" && event.key !== "ArrowUp") {
749
539
  return;
750
540
  }
751
- const items = Array.from(
541
+ const buttons = Array.from(
752
542
  event.currentTarget.querySelectorAll(
753
543
  "button:not(:disabled)"
754
544
  )
755
545
  );
756
- if (items.length === 0) {
546
+ if (buttons.length === 0) {
757
547
  return;
758
548
  }
759
549
  event.preventDefault();
760
550
  event.stopPropagation();
761
- const currentIndex = items.indexOf(
551
+ const currentIndex = buttons.indexOf(
762
552
  document.activeElement
763
553
  );
764
554
  const offset = event.key === "ArrowDown" ? 1 : -1;
765
- const nextIndex = currentIndex < 0 ? offset > 0 ? 0 : items.length - 1 : (currentIndex + offset + items.length) % items.length;
766
- items[nextIndex]?.focus();
555
+ const nextIndex = currentIndex < 0 ? offset > 0 ? 0 : buttons.length - 1 : (currentIndex + offset + buttons.length) % buttons.length;
556
+ buttons[nextIndex]?.focus();
767
557
  },
768
558
  children: [
769
559
  submenuPosition.mode === "overlay" ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -772,97 +562,22 @@ function OpenWithMenuItem({
772
562
  {
773
563
  activateOnPointerDown: true,
774
564
  icon: /* @__PURE__ */ jsx(ArrowLeftIcon, { className: "size-4" }),
775
- label: copy.t("openWithLabel"),
565
+ label: item.label,
776
566
  onClick: closeSubmenuToTrigger
777
567
  }
778
568
  ),
779
569
  /* @__PURE__ */ jsx(ContextMenuDivider, {})
780
570
  ] }) : null,
781
- showOpenInFileViewer ? /* @__PURE__ */ jsx(
782
- ContextMenuActionButton,
783
- {
784
- activateOnPointerDown: true,
785
- disabled: busy,
786
- icon: /* @__PURE__ */ jsx(EyeIcon, { className: "size-4" }),
787
- label: copy.t("openInFileViewerLabel"),
788
- onClick: () => {
789
- const openPromise = onOpenInFileViewer();
790
- onClose();
791
- void openPromise;
792
- }
793
- }
794
- ) : null,
795
- showOpenInAppBrowser ? /* @__PURE__ */ jsx(
796
- ContextMenuActionButton,
571
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-[11px] text-[var(--text-tertiary)]", children: item.loadingLabel ?? "\u2026" }) : null,
572
+ children.map((child) => /* @__PURE__ */ jsx(
573
+ ContextMenuItemRenderer,
797
574
  {
798
575
  activateOnPointerDown: true,
799
- disabled: busy,
800
- icon: openInAppBrowserIcon ?? /* @__PURE__ */ jsx(WebIcon, { className: "size-4" }),
801
- label: copy.t("openInAppBrowserLabel"),
802
- onClick: () => {
803
- const openPromise = onOpenInAppBrowser();
804
- onClose();
805
- void openPromise;
806
- }
807
- }
808
- ) : null,
809
- showSectionDivider ? /* @__PURE__ */ jsx(ContextMenuDivider, {}) : null,
810
- isLoading ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-[11px] text-[var(--text-tertiary)]", children: copy.t("openWithLoadingLabel") }) : null,
811
- applications.map((application) => {
812
- const resolvedIcon = resolveOpenWithApplicationIcon?.(application);
813
- return /* @__PURE__ */ jsx(
814
- ContextMenuActionButton,
815
- {
816
- activateOnPointerDown: true,
817
- disabled: busy,
818
- icon: resolvedIcon ?? (application.iconDataUrl ? /* @__PURE__ */ jsx(
819
- "img",
820
- {
821
- alt: "",
822
- className: "size-4 rounded-[4px] object-contain",
823
- src: application.iconDataUrl
824
- }
825
- ) : /* @__PURE__ */ jsx(EyeIcon, { className: "size-4" })),
826
- label: application.name,
827
- onClick: () => {
828
- const openPromise = onOpenWithApplication(
829
- application.applicationPath
830
- );
831
- onClose();
832
- void openPromise;
833
- }
834
- },
835
- application.applicationPath
836
- );
837
- }),
838
- showOpenInDefaultBrowser ? /* @__PURE__ */ jsx(
839
- ContextMenuActionButton,
840
- {
841
- activateOnPointerDown: true,
842
- disabled: busy,
843
- icon: /* @__PURE__ */ jsx(WebIcon, { className: "size-4" }),
844
- label: copy.t("openInDefaultBrowserLabel"),
845
- onClick: () => {
846
- const openPromise = onOpenInDefaultBrowser();
847
- onClose();
848
- void openPromise;
849
- }
850
- }
851
- ) : null,
852
- showOpenWithOther ? /* @__PURE__ */ jsx(
853
- ContextMenuActionButton,
854
- {
855
- activateOnPointerDown: true,
856
- disabled: busy,
857
- icon: /* @__PURE__ */ jsx(LaunchIcon, { className: "size-4" }),
858
- label: copy.t("openWithOtherLabel"),
859
- onClick: () => {
860
- const openPromise = onOpenWithOtherApplication();
861
- onClose();
862
- void openPromise;
863
- }
864
- }
865
- ) : null
576
+ item: child,
577
+ onClose
578
+ },
579
+ child.id
580
+ ))
866
581
  ]
867
582
  }
868
583
  )
@@ -870,6 +585,17 @@ function OpenWithMenuItem({
870
585
  }
871
586
  );
872
587
  }
588
+ function countRenderableMenuActions(items) {
589
+ let count = 0;
590
+ for (const item of items) {
591
+ if (item.type === "item") {
592
+ count += 1;
593
+ } else if (item.type === "submenu") {
594
+ count += 1 + countRenderableMenuActions(item.children ?? []);
595
+ }
596
+ }
597
+ return count;
598
+ }
873
599
 
874
600
  // src/ui/useWorkspaceFileManagerService.ts
875
601
  import { useSnapshot } from "valtio";
@@ -877,21 +603,7 @@ import { useSnapshot } from "valtio";
877
603
  // src/services/internal/workspaceFileManagerViewModel.ts
878
604
  function resolveWorkspaceFileManagerRootViewState(input) {
879
605
  const { state } = input;
880
- const isRecentLocation = isWorkspaceFileRecentLocation(
881
- findWorkspaceFileLocationById(
882
- state.locationSections,
883
- state.selectedLocationId
884
- )
885
- );
886
- const isExternalLocation = isWorkspaceFileExternalLocation(
887
- findWorkspaceFileLocationById(
888
- state.locationSections,
889
- state.selectedLocationId
890
- )
891
- );
892
- const isSearchMode = state.searchQuery.trim().length > 0;
893
606
  return {
894
- canImportFromDrop: state.capabilities.canImportFromDrop && !isRecentLocation && !isExternalLocation && !isSearchMode,
895
607
  currentDirectoryPath: state.currentDirectoryPath,
896
608
  isBusy: state.busyAction !== null,
897
609
  locationSections: state.locationSections,
@@ -918,9 +630,7 @@ function resolveWorkspaceFileManagerToolbarViewState(input) {
918
630
  isLoading: state.isLoading,
919
631
  isMutating: state.isMutating,
920
632
  isSearching: state.isSearching,
921
- searchQuery: state.searchQuery,
922
- isImporting: state.busyAction === "import",
923
- showImportAction: state.capabilities.canImportFromPicker
633
+ searchQuery: state.searchQuery
924
634
  };
925
635
  }
926
636
  function resolveWorkspaceFileManagerPanelsViewState(input) {
@@ -954,8 +664,7 @@ function resolveWorkspaceFileManagerPanelsViewState(input) {
954
664
  searchError: state.searchError,
955
665
  searchQuery: state.searchQuery,
956
666
  selectedEntry: findSelectedEntry(state),
957
- selectedPath: state.selectedPath,
958
- showDropOverlay: state.capabilities.canImportFromDrop && !isExternalLocation && state.dragDepth > 0 && state.busyAction === null
667
+ selectedPath: state.selectedPath
959
668
  };
960
669
  }
961
670
  function resolveWorkspaceFileManagerDialogsViewState(input) {
@@ -966,7 +675,6 @@ function resolveWorkspaceFileManagerDialogsViewState(input) {
966
675
  deleteDialogEntry: state.deleteDialog ? findEntry(state, state.deleteDialog.entryPath) : null,
967
676
  isBusy: state.busyAction !== null,
968
677
  isDeleting: state.busyAction === "delete",
969
- isImporting: state.busyAction === "import",
970
678
  isRenaming: state.busyAction === "rename",
971
679
  isViewing: state.busyAction === "view",
972
680
  unsupportedDialog: state.unsupportedDialog ? {
@@ -975,27 +683,12 @@ function resolveWorkspaceFileManagerDialogsViewState(input) {
975
683
  kind: state.unsupportedDialog.kind,
976
684
  message: state.unsupportedDialog.message,
977
685
  title: state.unsupportedDialog.title
978
- } : null,
979
- importConflictDialog: state.importConflictDialog
686
+ } : null
980
687
  };
981
688
  }
982
689
  function resolveWorkspaceFileManagerContextMenuViewState(input) {
983
690
  const { state } = input;
984
- const isRecentLocation = isWorkspaceFileRecentLocation(
985
- findWorkspaceFileLocationById(
986
- state.locationSections,
987
- state.selectedLocationId
988
- )
989
- );
990
- const isExternalLocation = isWorkspaceFileExternalLocation(
991
- findWorkspaceFileLocationById(
992
- state.locationSections,
993
- state.selectedLocationId
994
- )
995
- );
996
- const isSearchMode = state.searchQuery.trim().length > 0;
997
691
  const contextMenuEntry = state.contextMenu?.entryPath ? findEntry(state, state.contextMenu.entryPath) : null;
998
- const isContextMenuFile = contextMenuEntry?.kind === "file";
999
692
  return {
1000
693
  contextMenu: state.contextMenu ? {
1001
694
  entry: contextMenuEntry,
@@ -1005,21 +698,7 @@ function resolveWorkspaceFileManagerContextMenuViewState(input) {
1005
698
  currentDirectoryPath: state.currentDirectoryPath,
1006
699
  isBusy: state.busyAction !== null,
1007
700
  isLoading: state.isLoading,
1008
- isMutating: state.isMutating,
1009
- showCreateDirectoryAction: !isExternalLocation && !isRecentLocation && !isSearchMode && state.capabilities.canCreateDirectory,
1010
- showCreateFileAction: !isExternalLocation && !isRecentLocation && !isSearchMode && state.capabilities.canCreateFile,
1011
- showCopyAction: state.capabilities.canCopy,
1012
- showDeleteAction: state.capabilities.canDelete && !isExternalLocation && !isRecentLocation && !isSearchMode,
1013
- showExportAction: state.capabilities.canExport && !isExternalLocation,
1014
- showImportAction: state.capabilities.canImportFromPicker && !isExternalLocation && !isRecentLocation && !isSearchMode,
1015
- showMoveAction: state.capabilities.canMove && !isExternalLocation && !isRecentLocation && !isSearchMode,
1016
- showOpenInAppBrowserAction: state.capabilities.canOpenInAppBrowser && !isExternalLocation,
1017
- showOpenInDefaultBrowserAction: state.capabilities.canOpenInDefaultBrowser,
1018
- showOpenInFileViewerAction: contextMenuEntry !== null && isContextMenuFile && resolveWorkspaceFileActivationTarget(contextMenuEntry) !== null,
1019
- showOpenWithAction: state.capabilities.canOpenWith && isContextMenuFile,
1020
- showOpenWithOtherAction: state.capabilities.canPickOtherOpenWithApplication && isContextMenuFile,
1021
- showRevealInFolderAction: state.capabilities.canRevealInFolder && !isExternalLocation,
1022
- showRenameAction: state.capabilities.canRename && !isExternalLocation && !isRecentLocation && !isSearchMode
701
+ isMutating: state.isMutating
1023
702
  };
1024
703
  }
1025
704
  function findSelectedEntry(state) {
@@ -1081,198 +760,112 @@ function useWorkspaceFileManagerContextMenuView(session) {
1081
760
  };
1082
761
  }
1083
762
 
763
+ // src/ui/workspaceFileManagerContextMenuTypes.ts
764
+ function resolveWorkspaceFileManagerContextMenuTarget(input) {
765
+ if (!input.entry) {
766
+ return {
767
+ kind: "blank",
768
+ directoryPath: input.currentDirectoryPath
769
+ };
770
+ }
771
+ if (input.entry.kind === "directory") {
772
+ return {
773
+ kind: "directory",
774
+ directoryPath: input.currentDirectoryPath,
775
+ entry: input.entry
776
+ };
777
+ }
778
+ return {
779
+ kind: "file",
780
+ directoryPath: input.currentDirectoryPath,
781
+ entry: input.entry
782
+ };
783
+ }
784
+
1084
785
  // src/ui/WorkspaceFileManagerContextMenuContainer.tsx
1085
786
  import { jsx as jsx2 } from "react/jsx-runtime";
1086
787
  function WorkspaceFileManagerContextMenuContainer({
1087
- hostOs,
1088
- i18n,
1089
- onCopyEntry,
1090
- onCopyPath,
1091
- openInAppBrowserIcon,
1092
- resolveOpenWithApplicationIcon,
1093
- session,
1094
- showInternalOpenWithActions
788
+ resolveContextMenu,
789
+ session
1095
790
  }) {
1096
791
  const contextMenuRef = useRef2(null);
1097
792
  const { view } = useWorkspaceFileManagerContextMenuView(session);
1098
- const [openWithApplications, setOpenWithApplications] = useState2([]);
1099
- const [openWithLoading, setOpenWithLoading] = useState2(false);
1100
- useEffect2(() => {
1101
- const entry = view.contextMenu?.entry;
1102
- if (!entry || !view.showOpenWithAction) {
1103
- setOpenWithApplications([]);
1104
- setOpenWithLoading(false);
793
+ const [items, setItems] = useState2([]);
794
+ useLayoutEffect2(() => {
795
+ if (!view.contextMenu) {
796
+ setItems([]);
1105
797
  return;
1106
798
  }
1107
- const cachedApplications = session.getCachedOpenWithApplications(entry);
1108
- if (cachedApplications) {
1109
- setOpenWithApplications(cachedApplications);
1110
- setOpenWithLoading(false);
799
+ const request = buildContextMenuRequest({
800
+ session,
801
+ view
802
+ });
803
+ const resolved = resolveContextMenu(request);
804
+ if (!isPromiseLike(resolved)) {
805
+ setItems(resolved);
1111
806
  return;
1112
807
  }
1113
808
  let cancelled = false;
1114
- setOpenWithApplications([]);
1115
- setOpenWithLoading(true);
1116
- void session.listOpenWithApplications(entry).then((applications) => {
1117
- if (cancelled) {
1118
- return;
809
+ setItems([]);
810
+ void resolved.then((nextItems) => {
811
+ if (!cancelled) {
812
+ setItems(nextItems);
1119
813
  }
1120
- setOpenWithApplications(applications);
1121
- setOpenWithLoading(false);
1122
- }).catch(() => {
1123
- if (cancelled) {
1124
- return;
1125
- }
1126
- setOpenWithApplications([]);
1127
- setOpenWithLoading(false);
1128
814
  });
1129
815
  return () => {
1130
816
  cancelled = true;
1131
817
  };
1132
- }, [session, view.contextMenu?.entry?.path, view.showOpenWithAction]);
818
+ }, [
819
+ resolveContextMenu,
820
+ session,
821
+ view.contextMenu,
822
+ view.currentDirectoryPath,
823
+ view.isBusy,
824
+ view.isLoading,
825
+ view.isMutating
826
+ ]);
1133
827
  useCloseContextMenuOnOutsideInteraction({
1134
828
  contextMenuRef,
1135
829
  isOpen: view.contextMenu !== null,
1136
830
  session
1137
831
  });
832
+ if (!view.contextMenu) {
833
+ return null;
834
+ }
1138
835
  return /* @__PURE__ */ jsx2(
1139
836
  WorkspaceFileManagerContextMenu,
1140
837
  {
1141
- busy: view.isBusy || view.isLoading || view.isMutating,
1142
- copy: i18n,
1143
- contextMenu: view.contextMenu,
838
+ contextMenu: { x: view.contextMenu.x, y: view.contextMenu.y },
1144
839
  contextMenuRef,
1145
- showCopyAction: view.showCopyAction,
1146
- showCopyPathAction: true,
1147
- showCreateDirectoryAction: view.contextMenu?.entry === null && view.showCreateDirectoryAction,
1148
- showCreateFileAction: view.contextMenu?.entry === null && view.showCreateFileAction,
1149
- showDeleteAction: view.showDeleteAction,
1150
- showImportAction: view.showImportAction,
1151
- showExportAction: view.showExportAction,
1152
- showOpenInAppBrowserAction: showInternalOpenWithActions && view.showOpenInAppBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
1153
- showOpenInDefaultBrowserAction: view.showOpenInDefaultBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
1154
- showOpenInFileViewerAction: showInternalOpenWithActions && view.showOpenInFileViewerAction,
1155
- showOpenAction: true,
1156
- showOpenWithAction: view.showOpenWithAction,
1157
- showOpenWithOtherAction: view.showOpenWithOtherAction,
1158
- showRevealInFolderAction: view.showRevealInFolderAction,
1159
- showRenameAction: view.showRenameAction,
1160
- revealInFolderLabel: resolveRevealInFolderLabel(i18n, hostOs),
1161
- openInAppBrowserIcon,
1162
- openWithApplications,
1163
- openWithLoading,
1164
- resolveOpenWithApplicationIcon,
840
+ items,
1165
841
  onClose: () => {
1166
842
  session.closeContextMenu();
1167
- },
1168
- onCreateDirectory: () => {
1169
- session.openCreateDirectoryDialog();
1170
- },
1171
- onCreateFile: () => {
1172
- session.openCreateFileDialog();
1173
- },
1174
- onCopy: async () => {
1175
- const entry = view.contextMenu?.entry;
1176
- if (!entry) {
1177
- return;
1178
- }
1179
- session.closeContextMenu();
1180
- await session.copyToClipboard(entry);
1181
- if (onCopyEntry) {
1182
- await onCopyEntry();
1183
- }
1184
- },
1185
- onCopyPath: async () => {
1186
- const path = view.contextMenu?.entry?.path;
1187
- if (!path) {
1188
- return;
1189
- }
1190
- session.closeContextMenu();
1191
- if (onCopyPath) {
1192
- await onCopyPath(path);
1193
- return;
1194
- }
1195
- await navigator.clipboard.writeText(path);
1196
- },
1197
- onDelete: () => {
1198
- const entry = view.contextMenu?.entry;
1199
- if (!entry) {
1200
- return;
1201
- }
1202
- session.openDeleteDialog(entry);
1203
- },
1204
- onRename: () => {
1205
- const entry = view.contextMenu?.entry;
1206
- if (!entry) {
1207
- return;
1208
- }
1209
- session.startInlineRename(entry);
1210
- },
1211
- onExport: async () => {
1212
- const entry = view.contextMenu?.entry;
1213
- if (!entry) {
1214
- return;
1215
- }
1216
- await session.exportEntry(entry);
1217
- },
1218
- onOpen: async () => {
1219
- const entry = view.contextMenu?.entry;
1220
- if (!entry) {
1221
- return;
1222
- }
1223
- await session.openEntry(entry);
1224
- },
1225
- onOpenInAppBrowser: async () => {
1226
- const entry = view.contextMenu?.entry;
1227
- if (!entry) {
1228
- return;
1229
- }
1230
- await session.openFileInAppBrowser(entry);
1231
- },
1232
- onOpenInDefaultBrowser: async () => {
1233
- const entry = view.contextMenu?.entry;
1234
- if (!entry) {
1235
- return;
1236
- }
1237
- await session.openFileInDefaultBrowser(entry);
1238
- },
1239
- onOpenInFileViewer: async () => {
1240
- const entry = view.contextMenu?.entry;
1241
- if (!entry) {
1242
- return;
1243
- }
1244
- await session.openFileInFileViewer(entry);
1245
- },
1246
- onOpenWithApplication: async (applicationPath) => {
1247
- const entry = view.contextMenu?.entry;
1248
- if (!entry) {
1249
- return;
1250
- }
1251
- await session.openFileWithApplication(entry, applicationPath);
1252
- },
1253
- onOpenWithOtherApplication: async () => {
1254
- const entry = view.contextMenu?.entry;
1255
- if (!entry) {
1256
- return;
1257
- }
1258
- await session.openFileWithOtherApplication(entry);
1259
- },
1260
- onRevealInFolder: async () => {
1261
- const entry = view.contextMenu?.entry;
1262
- if (!entry) {
1263
- return;
1264
- }
1265
- await session.revealEntry(entry);
1266
- },
1267
- onImport: async () => {
1268
- session.closeContextMenu();
1269
- await session.importFiles(
1270
- view.contextMenu?.entry?.kind === "directory" ? view.contextMenu.entry.path : view.currentDirectoryPath
1271
- );
1272
843
  }
1273
844
  }
1274
845
  );
1275
846
  }
847
+ function buildContextMenuRequest(input) {
848
+ const { session, view } = input;
849
+ const location = findWorkspaceFileLocationById(
850
+ session.store.locationSections,
851
+ session.store.selectedLocationId
852
+ );
853
+ return {
854
+ currentDirectoryPath: view.currentDirectoryPath,
855
+ isBusy: view.isBusy || view.isLoading || view.isMutating,
856
+ isExternalLocation: isWorkspaceFileExternalLocation(location),
857
+ isRecentLocation: isWorkspaceFileRecentLocation(location),
858
+ isSearchMode: session.store.searchQuery.trim().length > 0,
859
+ selectedLocationId: session.store.selectedLocationId,
860
+ target: resolveWorkspaceFileManagerContextMenuTarget({
861
+ currentDirectoryPath: view.currentDirectoryPath,
862
+ entry: view.contextMenu?.entry ?? null
863
+ })
864
+ };
865
+ }
866
+ function isPromiseLike(value) {
867
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
868
+ }
1276
869
  function useCloseContextMenuOnOutsideInteraction(input) {
1277
870
  const { contextMenuRef, isOpen, session } = input;
1278
871
  useEffect2(() => {
@@ -1417,8 +1010,8 @@ function WorkspaceFileManagerUnsupportedDialog({
1417
1010
  if (!dialog) {
1418
1011
  return null;
1419
1012
  }
1420
- const title = dialog.title ?? (dialog.kind === "import" ? copy.t("unsupportedImportTitle") : copy.t("unsupportedViewTitle"));
1421
- const body = dialog.message ?? (dialog.kind === "import" ? copy.t("unsupportedImportBody") : copy.t("unsupportedViewBody", { name: dialog.entry?.name ?? "" }));
1013
+ const title = dialog.title ?? copy.t("unsupportedViewTitle");
1014
+ const body = dialog.message ?? copy.t("unsupportedViewBody", { name: dialog.entry?.name ?? "" });
1422
1015
  const actions = dialog.actions?.filter((action) => action.kind !== "none") ?? [];
1423
1016
  return /* @__PURE__ */ jsx3(
1424
1017
  Dialog,
@@ -1481,122 +1074,6 @@ function WorkspaceFileManagerUnsupportedDialog({
1481
1074
  }
1482
1075
  );
1483
1076
  }
1484
- function WorkspaceFileManagerImportConflictDialog({
1485
- busy,
1486
- copy,
1487
- dialog,
1488
- onClose,
1489
- onConfirm
1490
- }) {
1491
- if (!dialog) {
1492
- return null;
1493
- }
1494
- const hasBlockedConflict = dialog.conflicts.some(
1495
- (conflict) => conflict.conflictKind === "type_mismatch"
1496
- );
1497
- return /* @__PURE__ */ jsxs2(
1498
- ConfirmationDialog,
1499
- {
1500
- cancelLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("cancelLabel"),
1501
- className: "max-w-lg",
1502
- confirmBusy: busy,
1503
- confirmLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("importConflictReplaceLabel"),
1504
- description: hasBlockedConflict ? copy.t("importTypeConflictDescription", {
1505
- count: dialog.conflicts.length
1506
- }) : copy.t("importConflictDescription", {
1507
- count: dialog.conflicts.length
1508
- }),
1509
- hideConfirmButton: hasBlockedConflict,
1510
- open: true,
1511
- title: hasBlockedConflict ? copy.t("importTypeConflictTitle") : copy.t("importConflictTitle"),
1512
- tone: hasBlockedConflict ? "default" : "destructive",
1513
- onConfirm: hasBlockedConflict ? onClose : onConfirm,
1514
- onOpenChange: (nextOpen) => {
1515
- if (!nextOpen) {
1516
- onClose();
1517
- }
1518
- },
1519
- children: [
1520
- /* @__PURE__ */ jsx3(ImportConflictSummary, { copy, dialog }),
1521
- /* @__PURE__ */ jsx3("div", { className: "max-h-60 overflow-auto rounded-lg border border-[var(--border-1)] bg-transparency-block", children: /* @__PURE__ */ jsx3("div", { className: "divide-y divide-[var(--border-1)]", children: dialog.conflicts.map((conflict) => /* @__PURE__ */ jsxs2(
1522
- "div",
1523
- {
1524
- className: "flex flex-col gap-1 px-4 py-3 text-[13px]",
1525
- children: [
1526
- /* @__PURE__ */ jsx3("span", { className: "font-medium text-[var(--text-primary)]", children: conflict.name }),
1527
- /* @__PURE__ */ jsxs2("span", { className: "text-[11px] text-[var(--text-secondary)]", children: [
1528
- copy.t("importConflictReviewLabel"),
1529
- ":",
1530
- " ",
1531
- conflict.destinationPath
1532
- ] })
1533
- ]
1534
- },
1535
- `${conflict.destinationPath}:${conflict.sourcePath}`
1536
- )) }) })
1537
- ]
1538
- }
1539
- );
1540
- }
1541
- function ImportConflictSummary({
1542
- copy,
1543
- dialog
1544
- }) {
1545
- const summaryItems = [];
1546
- const hasReasonBreakdown = dialog.summary?.reasonBreakdown?.some((reason) => reason.count > 0) ?? false;
1547
- if (typeof dialog.summary?.selectedCount === "number" && dialog.summary.selectedCount > 0) {
1548
- summaryItems.push(
1549
- copy.t("importConflictSummarySelected", {
1550
- count: dialog.summary.selectedCount
1551
- })
1552
- );
1553
- }
1554
- if (!hasReasonBreakdown && typeof dialog.summary?.filteredCount === "number" && dialog.summary.filteredCount > 0) {
1555
- summaryItems.push(
1556
- copy.t("importConflictSummaryFiltered", {
1557
- count: dialog.summary.filteredCount
1558
- })
1559
- );
1560
- }
1561
- if (!hasReasonBreakdown && typeof dialog.summary?.ignoredCount === "number" && dialog.summary.ignoredCount > 0) {
1562
- summaryItems.push(
1563
- copy.t("importConflictSummaryIgnored", {
1564
- count: dialog.summary.ignoredCount
1565
- })
1566
- );
1567
- }
1568
- for (const reason of dialog.summary?.reasonBreakdown ?? []) {
1569
- if (reason.count <= 0) {
1570
- continue;
1571
- }
1572
- const copyKey = importSummaryReasonCopyKey(reason.reason);
1573
- if (!copyKey) {
1574
- continue;
1575
- }
1576
- summaryItems.push(copy.t(copyKey, { count: reason.count }));
1577
- }
1578
- if (summaryItems.length === 0) {
1579
- return null;
1580
- }
1581
- return /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap gap-2 text-[11px] text-[var(--text-secondary)]", children: summaryItems.map((item) => /* @__PURE__ */ jsx3(
1582
- "span",
1583
- {
1584
- className: "rounded-md border border-[var(--border-1)] px-2 py-1",
1585
- children: item
1586
- },
1587
- item
1588
- )) });
1589
- }
1590
- function importSummaryReasonCopyKey(reason) {
1591
- switch (reason) {
1592
- case "ignored":
1593
- return "importConflictSummaryReasonIgnored";
1594
- case "symlink":
1595
- return "importConflictSummaryReasonSymlink";
1596
- case "system_metadata":
1597
- return "importConflictSummaryReasonSystemMetadata";
1598
- }
1599
- }
1600
1077
 
1601
1078
  // src/ui/WorkspaceFileManagerPanels.tsx
1602
1079
  import {
@@ -1613,7 +1090,7 @@ import { WorkspaceFilePreviewSurface as SharedWorkspaceFilePreviewSurface } from
1613
1090
  import {
1614
1091
  useCallback as useCallback2,
1615
1092
  useEffect as useEffect4,
1616
- useLayoutEffect as useLayoutEffect3,
1093
+ useLayoutEffect as useLayoutEffect4,
1617
1094
  useMemo,
1618
1095
  useRef as useRef5,
1619
1096
  useState as useState3
@@ -1621,7 +1098,7 @@ import {
1621
1098
 
1622
1099
  // src/ui/WorkspaceFileManagerIconGrid.tsx
1623
1100
  import { cn as cn3 } from "@tutti-os/ui-system";
1624
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef4 } from "react";
1101
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef4 } from "react";
1625
1102
 
1626
1103
  // src/ui/WorkspaceFileEntryIcon.tsx
1627
1104
  import {
@@ -1986,7 +1463,7 @@ function WorkspaceFileManagerIconTile({
1986
1463
  }) {
1987
1464
  const buttonRef = useRef4(null);
1988
1465
  const nameParts = splitWorkspaceFileName(entry.name);
1989
- useLayoutEffect2(() => {
1466
+ useLayoutEffect3(() => {
1990
1467
  if (!selected) {
1991
1468
  return;
1992
1469
  }
@@ -2088,7 +1565,7 @@ function IconTileRenameInput({
2088
1565
  const nameParts = splitWorkspaceFileName(entry.name);
2089
1566
  const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
2090
1567
  const inputRef = useRef4(null);
2091
- useLayoutEffect2(() => {
1568
+ useLayoutEffect3(() => {
2092
1569
  const input = inputRef.current;
2093
1570
  if (!input) {
2094
1571
  return;
@@ -2266,7 +1743,6 @@ function WorkspaceFileManagerPanels({
2266
1743
  selectedPath,
2267
1744
  showPreviewPanel: showPreviewPanelEnabled = true,
2268
1745
  state,
2269
- showDropOverlay,
2270
1746
  treeRows,
2271
1747
  onBlankContextMenu,
2272
1748
  onCancelInlineRename,
@@ -2517,7 +1993,7 @@ function WorkspaceFileManagerPanels({
2517
1993
  containerWidth - workspaceFileManagerTableMinWidth
2518
1994
  );
2519
1995
  }, [rootRef]);
2520
- useLayoutEffect3(() => {
1996
+ useLayoutEffect4(() => {
2521
1997
  const element = rootRef.current;
2522
1998
  if (!element || useStackedPreview || !showPreviewPanel) {
2523
1999
  return;
@@ -2813,17 +2289,6 @@ function WorkspaceFileManagerPanels({
2813
2289
  ]
2814
2290
  }
2815
2291
  ),
2816
- /* @__PURE__ */ jsx6(
2817
- "div",
2818
- {
2819
- "aria-hidden": "true",
2820
- className: cn4(
2821
- "pointer-events-none absolute inset-0 grid place-items-center rounded-[var(--workbench-window-radius,8px)] border border-dashed border-[var(--border-focus)] bg-[var(--accent-bg)] opacity-0 transition-opacity duration-150 ease-out",
2822
- showDropOverlay && "opacity-100"
2823
- ),
2824
- children: /* @__PURE__ */ jsx6("div", { className: "rounded-lg border border-[var(--border-1)] bg-[var(--background-fronted)] px-5 py-3 text-sm font-normal text-[var(--text-primary)] shadow-panel", children: copy.t("dropToImportLabel") })
2825
- }
2826
- ),
2827
2292
  moveDragPreview ? /* @__PURE__ */ jsx6(
2828
2293
  MoveDragPreview,
2829
2294
  {
@@ -2840,7 +2305,7 @@ function WorkspaceFileManagerPanels({
2840
2305
  function useWorkspaceFileManagerStackedLayout(breakpoint) {
2841
2306
  const rootRef = useRef5(null);
2842
2307
  const [isStacked, setIsStacked] = useState3(false);
2843
- useLayoutEffect3(() => {
2308
+ useLayoutEffect4(() => {
2844
2309
  const element = rootRef.current;
2845
2310
  if (!element) {
2846
2311
  return;
@@ -2917,7 +2382,7 @@ function EntryRow({
2917
2382
  }) {
2918
2383
  const buttonRowRef = useRef5(null);
2919
2384
  const divRowRef = useRef5(null);
2920
- useLayoutEffect3(() => {
2385
+ useLayoutEffect4(() => {
2921
2386
  if (!selected) {
2922
2387
  return;
2923
2388
  }
@@ -3209,7 +2674,7 @@ function EntryNameCell({
3209
2674
  setName(entry.name);
3210
2675
  skipBlurRef.current = false;
3211
2676
  }, [entry.name, entry.path, isInlineRenaming]);
3212
- useLayoutEffect3(() => {
2677
+ useLayoutEffect4(() => {
3213
2678
  if (!isInlineRenaming) {
3214
2679
  return;
3215
2680
  }
@@ -3460,9 +2925,6 @@ function PreviewDetail({
3460
2925
  function FeedbackState({ message }) {
3461
2926
  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 }) });
3462
2927
  }
3463
- function hasFileDragPayload(dataTransfer) {
3464
- return Array.from(dataTransfer.types).includes("Files");
3465
- }
3466
2928
 
3467
2929
  // src/ui/WorkspaceFileManagerToolbar.tsx
3468
2930
  import {
@@ -4373,16 +3835,12 @@ function WorkspaceFileManager({
4373
3835
  entryDragMode,
4374
3836
  i18n,
4375
3837
  onCopyEntry,
4376
- onCopyPath,
4377
3838
  onDirectoryExpanded,
4378
3839
  onEntryDragStart,
4379
- openInAppBrowserIcon,
4380
- resolveOpenWithApplicationIcon,
3840
+ resolveContextMenu,
4381
3841
  resolveEntryIconUrl,
4382
3842
  renderExternalLocationContent,
4383
- hostOs = "linux",
4384
3843
  session,
4385
- showInternalOpenWithActions = true,
4386
3844
  showPreviewPanel = true,
4387
3845
  surface = "card"
4388
3846
  }) {
@@ -4423,7 +3881,7 @@ function WorkspaceFileManager({
4423
3881
  },
4424
3882
  [sidebarContentMinWidth]
4425
3883
  );
4426
- useLayoutEffect4(() => {
3884
+ useLayoutEffect5(() => {
4427
3885
  const element = rootRef.current;
4428
3886
  if (!element || !hasLocationSidebar) {
4429
3887
  return;
@@ -4600,27 +4058,6 @@ function WorkspaceFileManager({
4600
4058
  selectedExternalLocation,
4601
4059
  session
4602
4060
  ]);
4603
- useEffect7(() => {
4604
- function resetDropOverlay() {
4605
- session.resetDragDepth();
4606
- }
4607
- function handleDocumentDragOver(event) {
4608
- if (isPointInsideElement(rootRef.current, event.clientX, event.clientY)) {
4609
- return;
4610
- }
4611
- session.resetDragDepth();
4612
- }
4613
- window.addEventListener("blur", resetDropOverlay);
4614
- window.addEventListener("dragend", resetDropOverlay);
4615
- window.addEventListener("drop", resetDropOverlay);
4616
- document.addEventListener("dragover", handleDocumentDragOver, true);
4617
- return () => {
4618
- window.removeEventListener("blur", resetDropOverlay);
4619
- window.removeEventListener("dragend", resetDropOverlay);
4620
- window.removeEventListener("drop", resetDropOverlay);
4621
- document.removeEventListener("dragover", handleDocumentDragOver, true);
4622
- };
4623
- }, [session]);
4624
4061
  function openContextMenu(event, entry) {
4625
4062
  event.preventDefault();
4626
4063
  event.stopPropagation();
@@ -4646,43 +4083,6 @@ function WorkspaceFileManager({
4646
4083
  y
4647
4084
  });
4648
4085
  }
4649
- function handleDragEnter(event) {
4650
- if (!rootView.canImportFromDrop || rootView.isBusy || !hasFileDragPayload(event.dataTransfer)) {
4651
- return;
4652
- }
4653
- event.preventDefault();
4654
- session.incrementDragDepth();
4655
- }
4656
- function handleDragOver(event) {
4657
- if (!rootView.canImportFromDrop || rootView.isBusy || !hasFileDragPayload(event.dataTransfer)) {
4658
- return;
4659
- }
4660
- event.preventDefault();
4661
- event.dataTransfer.dropEffect = "copy";
4662
- }
4663
- function handleDragLeave(event) {
4664
- if (!rootView.canImportFromDrop || !hasFileDragPayload(event.dataTransfer)) {
4665
- return;
4666
- }
4667
- event.preventDefault();
4668
- const nextTarget = event.relatedTarget;
4669
- if (nextTarget instanceof Node && rootRef.current?.contains(nextTarget)) {
4670
- return;
4671
- }
4672
- session.resetDragDepth();
4673
- }
4674
- function handleDrop(event) {
4675
- if (!rootView.canImportFromDrop || rootView.isBusy || !hasFileDragPayload(event.dataTransfer)) {
4676
- return;
4677
- }
4678
- event.preventDefault();
4679
- event.stopPropagation();
4680
- session.resetDragDepth();
4681
- void session.importDroppedFiles(
4682
- event.dataTransfer,
4683
- rootView.currentDirectoryPath
4684
- );
4685
- }
4686
4086
  return /* @__PURE__ */ jsxs8(
4687
4087
  "section",
4688
4088
  {
@@ -4693,10 +4093,6 @@ function WorkspaceFileManager({
4693
4093
  ),
4694
4094
  "data-slot": "viewport-menu-boundary",
4695
4095
  "data-workspace-file-manager": "",
4696
- onDragEnter: handleDragEnter,
4697
- onDragLeave: handleDragLeave,
4698
- onDragOver: handleDragOver,
4699
- onDrop: handleDrop,
4700
4096
  ref: rootRef,
4701
4097
  children: [
4702
4098
  /* @__PURE__ */ jsx9(
@@ -4776,14 +4172,8 @@ function WorkspaceFileManager({
4776
4172
  /* @__PURE__ */ jsx9(
4777
4173
  WorkspaceFileManagerContextMenuContainer,
4778
4174
  {
4779
- hostOs,
4780
- i18n,
4781
- onCopyEntry,
4782
- onCopyPath,
4783
- openInAppBrowserIcon,
4784
- resolveOpenWithApplicationIcon,
4785
- session,
4786
- showInternalOpenWithActions
4175
+ resolveContextMenu,
4176
+ session
4787
4177
  }
4788
4178
  )
4789
4179
  ] }) : null
@@ -4965,7 +4355,6 @@ function WorkspaceFileManagerPanelsContainer({
4965
4355
  selectedEntry: view.selectedEntry,
4966
4356
  selectedPath: view.selectedPath,
4967
4357
  showPreviewPanel,
4968
- showDropOverlay: view.showDropOverlay,
4969
4358
  state: {
4970
4359
  entries: displayedEntries,
4971
4360
  error: view.isSearchMode ? view.searchError : state.error,
@@ -5044,20 +4433,6 @@ function WorkspaceFileManagerDialogsContainer({
5044
4433
  }
5045
4434
  }
5046
4435
  ),
5047
- /* @__PURE__ */ jsx9(
5048
- WorkspaceFileManagerImportConflictDialog,
5049
- {
5050
- busy: view.isImporting,
5051
- copy: i18n,
5052
- dialog: view.importConflictDialog,
5053
- onClose: () => {
5054
- session.closeImportConflictDialog();
5055
- },
5056
- onConfirm: () => {
5057
- void session.confirmImportConflict();
5058
- }
5059
- }
5060
- ),
5061
4436
  /* @__PURE__ */ jsx9(
5062
4437
  WorkspaceFileManagerUnsupportedDialog,
5063
4438
  {
@@ -5088,13 +4463,6 @@ function clampContextMenuCoordinate(coordinate, boundarySize, menuSize) {
5088
4463
  const max = Math.max(8, boundarySize - menuSize - 8);
5089
4464
  return Math.min(Math.max(coordinate, 8), max);
5090
4465
  }
5091
- function isPointInsideElement(element, clientX, clientY) {
5092
- if (!element) {
5093
- return false;
5094
- }
5095
- const bounds = element.getBoundingClientRect();
5096
- return clientX >= bounds.left && clientX <= bounds.right && clientY >= bounds.top && clientY <= bounds.bottom;
5097
- }
5098
4466
  export {
5099
4467
  WorkspaceFileEntryIcon,
5100
4468
  WorkspaceFileManager,
@@ -5108,6 +4476,7 @@ export {
5108
4476
  isWorkspaceFileRecentLocation,
5109
4477
  resolveRevealInFolderLabel,
5110
4478
  resolveWorkspaceFileLocationDefaultId,
4479
+ resolveWorkspaceFileManagerContextMenuTarget,
5111
4480
  useWorkspaceFileEntryIconUrls,
5112
4481
  workspaceFileManagerI18nNamespace,
5113
4482
  workspaceFileManagerI18nResources