@tutti-os/workspace-file-manager 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2802 @@
1
+ import {
2
+ buildWorkspaceFileBreadcrumbs,
3
+ classifyWorkspaceFilePreviewKind,
4
+ createWorkspaceFileManagerService,
5
+ decodeWorkspaceTextFile,
6
+ formatWorkspaceFileBytes,
7
+ formatWorkspaceFileModifiedTime,
8
+ isWorkspaceFileBrowserOpenable,
9
+ isWorkspaceTextFileTooLarge,
10
+ looksLikeBinaryText,
11
+ normalizeWorkspaceFilePath,
12
+ resolveWorkspaceFileActivationTarget,
13
+ resolveWorkspaceFileExtension,
14
+ resolveWorkspaceFileVisualKind,
15
+ resolveWorkspaceImageMimeType,
16
+ splitWorkspaceFileName,
17
+ workspaceFilePreviewMaxBytes,
18
+ workspaceFileTextMaxBytes
19
+ } from "./chunk-IMOFXYBB.js";
20
+ import {
21
+ createWorkspaceFileManagerI18nRuntime,
22
+ resolveRevealInFolderLabel,
23
+ workspaceFileManagerI18nNamespace,
24
+ workspaceFileManagerI18nResources
25
+ } from "./chunk-2AG4ND4D.js";
26
+
27
+ // src/ui/WorkspaceFileManager.tsx
28
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
29
+ import { cn as cn4 } from "@tutti-os/ui-system";
30
+
31
+ // src/ui/WorkspaceFileManagerMenus.tsx
32
+ import {
33
+ ArrowRightIcon,
34
+ Button,
35
+ ConfirmationDialog,
36
+ CopyIcon,
37
+ DeleteIcon,
38
+ DownloadIcon,
39
+ EditIcon,
40
+ Dialog,
41
+ DialogContent,
42
+ DialogDescription,
43
+ DialogFooter,
44
+ DialogHeader,
45
+ DialogTitle,
46
+ EyeIcon,
47
+ FileLinedIcon,
48
+ Input,
49
+ LaunchIcon,
50
+ LocateFolderIcon,
51
+ MenuSurface,
52
+ NewWorkspaceLinedIcon,
53
+ UploadIcon as ImportIcon,
54
+ ViewportMenuSurface,
55
+ WebIcon,
56
+ cn
57
+ } from "@tutti-os/ui-system";
58
+ import {
59
+ useCallback,
60
+ useEffect,
61
+ useLayoutEffect,
62
+ useRef,
63
+ useState
64
+ } from "react";
65
+
66
+ // src/ui/contextMenuPlacement.ts
67
+ var CONTEXT_MENU_ITEM_HEIGHT_PX = 32;
68
+ var CONTEXT_MENU_PADDING_PX = 8;
69
+ var CONTEXT_MENU_SUBMENU_GAP_PX = 4;
70
+ function clampContextMenuPosition(input) {
71
+ const padding = input.padding ?? CONTEXT_MENU_PADDING_PX;
72
+ const maxX = Math.max(
73
+ padding,
74
+ input.boundaryWidth - input.menuWidth - padding
75
+ );
76
+ const maxY = Math.max(
77
+ padding,
78
+ input.boundaryHeight - input.menuHeight - padding
79
+ );
80
+ return {
81
+ x: Math.min(Math.max(input.x, padding), maxX),
82
+ y: Math.min(Math.max(input.y, padding), maxY)
83
+ };
84
+ }
85
+ function estimateOpenWithSubmenuHeight(input) {
86
+ let itemCount = 0;
87
+ if (input.showOpenInAppBrowser) {
88
+ itemCount += 1;
89
+ }
90
+ if (input.showExternalSection) {
91
+ itemCount += 1;
92
+ }
93
+ if (input.isLoading) {
94
+ itemCount += 1;
95
+ }
96
+ itemCount += input.applicationCount;
97
+ if (input.showOpenInDefaultBrowser) {
98
+ itemCount += 1;
99
+ }
100
+ if (input.showOpenWithOther) {
101
+ itemCount += 2;
102
+ }
103
+ return itemCount * CONTEXT_MENU_ITEM_HEIGHT_PX + CONTEXT_MENU_PADDING_PX * 2;
104
+ }
105
+
106
+ // src/ui/WorkspaceFileManagerMenus.tsx
107
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
108
+ function WorkspaceFileManagerContextMenu({
109
+ busy,
110
+ copy,
111
+ contextMenu,
112
+ contextMenuRef,
113
+ openInAppBrowserIcon,
114
+ showCopyAction,
115
+ showImportAction,
116
+ showExportAction,
117
+ showOpenInAppBrowserAction,
118
+ showOpenInDefaultBrowserAction,
119
+ showOpenWithAction,
120
+ showOpenWithOtherAction,
121
+ showRevealInFolderAction,
122
+ showRenameAction,
123
+ revealInFolderLabel,
124
+ openWithApplications,
125
+ openWithLoading,
126
+ resolveOpenWithApplicationIcon,
127
+ onClose,
128
+ onCreateDirectory,
129
+ onCreateFile,
130
+ onCopy,
131
+ onCopyPath,
132
+ onDelete,
133
+ onExport,
134
+ onOpen,
135
+ onOpenInAppBrowser,
136
+ onOpenInDefaultBrowser,
137
+ onOpenWithApplication,
138
+ onOpenWithOtherApplication,
139
+ onImport,
140
+ onRevealInFolder,
141
+ onRename
142
+ }) {
143
+ const [position, setPosition] = useState({
144
+ x: contextMenu?.x ?? 0,
145
+ y: contextMenu?.y ?? 0
146
+ });
147
+ useLayoutEffect(() => {
148
+ if (!contextMenu) {
149
+ return;
150
+ }
151
+ setPosition({ x: contextMenu.x, y: contextMenu.y });
152
+ }, [contextMenu?.x, contextMenu?.y]);
153
+ useLayoutEffect(() => {
154
+ if (!contextMenu) {
155
+ return;
156
+ }
157
+ const menu = contextMenuRef.current;
158
+ const boundary = menu?.closest("[data-workspace-file-manager]");
159
+ if (!menu || !boundary) {
160
+ return;
161
+ }
162
+ const boundaryRect = boundary.getBoundingClientRect();
163
+ const menuRect = menu.getBoundingClientRect();
164
+ setPosition(
165
+ clampContextMenuPosition({
166
+ boundaryHeight: boundaryRect.height,
167
+ boundaryWidth: boundaryRect.width,
168
+ menuHeight: menuRect.height,
169
+ menuWidth: menuRect.width,
170
+ x: contextMenu.x,
171
+ y: contextMenu.y
172
+ })
173
+ );
174
+ }, [
175
+ contextMenu,
176
+ contextMenuRef,
177
+ openWithApplications.length,
178
+ openWithLoading,
179
+ showCopyAction,
180
+ showExportAction,
181
+ showImportAction,
182
+ showOpenInAppBrowserAction,
183
+ showOpenInDefaultBrowserAction,
184
+ showOpenWithAction,
185
+ showOpenWithOtherAction,
186
+ showRevealInFolderAction,
187
+ showRenameAction
188
+ ]);
189
+ if (!contextMenu) {
190
+ return null;
191
+ }
192
+ const entry = contextMenu.entry;
193
+ const isDirectory = entry?.kind === "directory";
194
+ const editItems = [];
195
+ const transferItems = [];
196
+ const dangerItems = [];
197
+ const createItems = [];
198
+ if (entry) {
199
+ if (showRenameAction) {
200
+ editItems.push({
201
+ action: onRename,
202
+ disabled: busy,
203
+ icon: /* @__PURE__ */ jsx(EditIcon, { className: "size-4" }),
204
+ key: "rename",
205
+ label: copy.t("renameLabel")
206
+ });
207
+ }
208
+ if (showCopyAction) {
209
+ editItems.push({
210
+ action: onCopy,
211
+ disabled: busy,
212
+ icon: /* @__PURE__ */ jsx(CopyIcon, { className: "size-4" }),
213
+ key: "copy",
214
+ label: copy.t("copyLabel")
215
+ });
216
+ }
217
+ editItems.push({
218
+ action: onCopyPath,
219
+ disabled: busy,
220
+ icon: /* @__PURE__ */ jsx(CopyIcon, { className: "size-4" }),
221
+ key: "copy-path",
222
+ label: copy.t("copyPathLabel")
223
+ });
224
+ if (showRevealInFolderAction) {
225
+ editItems.push({
226
+ action: onRevealInFolder,
227
+ disabled: busy,
228
+ icon: /* @__PURE__ */ jsx(LocateFolderIcon, { className: "size-4" }),
229
+ key: "reveal-in-folder",
230
+ label: revealInFolderLabel
231
+ });
232
+ }
233
+ if (isDirectory && showImportAction) {
234
+ transferItems.push({
235
+ action: onImport,
236
+ disabled: busy,
237
+ icon: /* @__PURE__ */ jsx(ImportIcon, { className: "size-4" }),
238
+ key: "import",
239
+ label: copy.t("importLabel")
240
+ });
241
+ }
242
+ if (showExportAction) {
243
+ transferItems.push({
244
+ action: onExport,
245
+ disabled: busy,
246
+ icon: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-4" }),
247
+ key: "export",
248
+ label: copy.t("downloadLabel")
249
+ });
250
+ }
251
+ dangerItems.push({
252
+ action: onDelete,
253
+ disabled: busy,
254
+ danger: true,
255
+ icon: /* @__PURE__ */ jsx(DeleteIcon, { className: "size-4" }),
256
+ key: "delete",
257
+ label: copy.t("deleteLabel")
258
+ });
259
+ } else {
260
+ createItems.push({
261
+ action: onCreateFile,
262
+ disabled: busy,
263
+ icon: /* @__PURE__ */ jsx(NewWorkspaceLinedIcon, { className: "size-4" }),
264
+ key: "create-file",
265
+ label: copy.t("createFileLabel")
266
+ });
267
+ createItems.push({
268
+ action: onCreateDirectory,
269
+ disabled: busy,
270
+ icon: /* @__PURE__ */ jsx(FileLinedIcon, { className: "size-4" }),
271
+ key: "create-directory",
272
+ label: copy.t("createDirectoryLabel")
273
+ });
274
+ if (showImportAction) {
275
+ transferItems.push({
276
+ action: onImport,
277
+ disabled: busy,
278
+ icon: /* @__PURE__ */ jsx(ImportIcon, { className: "size-4" }),
279
+ key: "import",
280
+ label: copy.t("importLabel")
281
+ });
282
+ }
283
+ }
284
+ const menuGroups = entry ? [
285
+ { items: editItems, key: "edit" },
286
+ { items: transferItems, key: "transfer" },
287
+ { items: dangerItems, key: "danger" }
288
+ ] : [
289
+ { items: createItems, key: "create" },
290
+ { items: transferItems, key: "transfer" }
291
+ ];
292
+ const visibleMenuGroups = menuGroups.filter(
293
+ (group) => group.items.length > 0
294
+ );
295
+ return /* @__PURE__ */ jsxs(
296
+ MenuSurface,
297
+ {
298
+ ref: contextMenuRef,
299
+ className: "absolute w-[220px] overflow-visible p-1",
300
+ role: "menu",
301
+ style: {
302
+ left: `${position.x}px`,
303
+ top: `${position.y}px`,
304
+ zIndex: "calc(var(--workspace-file-manager-dialog-overlay-z-index) - 1)"
305
+ },
306
+ onContextMenu: (event) => {
307
+ event.preventDefault();
308
+ },
309
+ children: [
310
+ entry ? /* @__PURE__ */ jsx(
311
+ ContextMenuActionButton,
312
+ {
313
+ disabled: busy,
314
+ icon: /* @__PURE__ */ jsx(EyeIcon, { className: "size-4" }),
315
+ label: copy.t("openLabel"),
316
+ onClick: () => {
317
+ onClose();
318
+ void onOpen();
319
+ }
320
+ }
321
+ ) : null,
322
+ entry && showOpenWithAction ? /* @__PURE__ */ jsx(
323
+ OpenWithMenuItem,
324
+ {
325
+ applications: openWithApplications,
326
+ busy,
327
+ copy,
328
+ isLoading: openWithLoading,
329
+ openInAppBrowserIcon,
330
+ resolveOpenWithApplicationIcon,
331
+ showOpenInAppBrowser: showOpenInAppBrowserAction,
332
+ showOpenInDefaultBrowser: showOpenInDefaultBrowserAction,
333
+ showOpenWithOther: showOpenWithOtherAction,
334
+ onClose,
335
+ onOpenInAppBrowser,
336
+ onOpenInDefaultBrowser,
337
+ onOpenWithApplication,
338
+ onOpenWithOtherApplication
339
+ }
340
+ ) : null,
341
+ visibleMenuGroups.map((group, groupIndex) => /* @__PURE__ */ jsx(
342
+ ContextMenuActionGroup,
343
+ {
344
+ items: group.items,
345
+ showDivider: entry !== null || groupIndex > 0,
346
+ onClose
347
+ },
348
+ group.key
349
+ ))
350
+ ]
351
+ }
352
+ );
353
+ }
354
+ function ContextMenuActionGroup({
355
+ items,
356
+ onClose,
357
+ showDivider
358
+ }) {
359
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
360
+ showDivider ? /* @__PURE__ */ jsx(ContextMenuDivider, {}) : null,
361
+ items.map((item) => /* @__PURE__ */ jsx(
362
+ ContextMenuActionButton,
363
+ {
364
+ danger: item.danger,
365
+ disabled: item.disabled,
366
+ icon: item.icon,
367
+ label: item.label,
368
+ onClick: () => {
369
+ onClose();
370
+ void item.action();
371
+ }
372
+ },
373
+ item.key
374
+ ))
375
+ ] });
376
+ }
377
+ function ContextMenuActionButton({
378
+ danger = false,
379
+ disabled = false,
380
+ icon,
381
+ label,
382
+ onClick
383
+ }) {
384
+ return /* @__PURE__ */ jsxs(
385
+ "button",
386
+ {
387
+ className: cn(
388
+ "flex h-8 w-full items-center gap-2 rounded-md border-0 bg-transparent px-2 text-left text-sm transition-colors disabled:cursor-default disabled:opacity-50",
389
+ danger ? "text-[var(--state-danger)] hover:bg-[var(--on-danger)]" : "text-[var(--text-primary)] hover:bg-transparency-block"
390
+ ),
391
+ disabled,
392
+ role: "menuitem",
393
+ type: "button",
394
+ onClick,
395
+ children: [
396
+ /* @__PURE__ */ jsx(
397
+ "span",
398
+ {
399
+ className: cn(
400
+ "grid size-4 flex-none place-items-center",
401
+ danger ? "text-[var(--state-danger)]" : "text-[var(--text-secondary)]"
402
+ ),
403
+ children: icon
404
+ }
405
+ ),
406
+ label
407
+ ]
408
+ }
409
+ );
410
+ }
411
+ function ContextMenuDivider() {
412
+ return /* @__PURE__ */ jsx("div", { className: "mx-2 my-0.5 h-px bg-[var(--border-1)]", role: "separator" });
413
+ }
414
+ function OpenWithMenuItem({
415
+ applications,
416
+ busy,
417
+ copy,
418
+ isLoading,
419
+ openInAppBrowserIcon,
420
+ resolveOpenWithApplicationIcon,
421
+ showOpenInAppBrowser,
422
+ showOpenInDefaultBrowser,
423
+ showOpenWithOther,
424
+ onClose,
425
+ onOpenInAppBrowser,
426
+ onOpenInDefaultBrowser,
427
+ onOpenWithApplication,
428
+ onOpenWithOtherApplication
429
+ }) {
430
+ const triggerRef = useRef(null);
431
+ const [open, setOpen] = useState(false);
432
+ const [placementPoint, setPlacementPoint] = useState({ x: 0, y: 0 });
433
+ const closeTimerRef = useRef(null);
434
+ const showExternalSection = showOpenInDefaultBrowser || showOpenWithOther || isLoading || applications.length > 0;
435
+ const estimatedSubmenuHeight = estimateOpenWithSubmenuHeight({
436
+ applicationCount: applications.length,
437
+ isLoading,
438
+ showExternalSection,
439
+ showOpenInAppBrowser,
440
+ showOpenInDefaultBrowser,
441
+ showOpenWithOther
442
+ });
443
+ const cancelClose = useCallback(() => {
444
+ if (closeTimerRef.current !== null) {
445
+ window.clearTimeout(closeTimerRef.current);
446
+ closeTimerRef.current = null;
447
+ }
448
+ }, []);
449
+ const scheduleClose = useCallback(() => {
450
+ cancelClose();
451
+ closeTimerRef.current = window.setTimeout(() => {
452
+ setOpen(false);
453
+ closeTimerRef.current = null;
454
+ }, 120);
455
+ }, [cancelClose]);
456
+ const openSubmenu = useCallback(() => {
457
+ cancelClose();
458
+ setOpen(true);
459
+ }, [cancelClose]);
460
+ useEffect(() => {
461
+ return () => {
462
+ cancelClose();
463
+ };
464
+ }, [cancelClose]);
465
+ useLayoutEffect(() => {
466
+ if (!open) {
467
+ return;
468
+ }
469
+ const trigger = triggerRef.current;
470
+ if (!trigger) {
471
+ return;
472
+ }
473
+ const rect = trigger.getBoundingClientRect();
474
+ setPlacementPoint({
475
+ x: rect.right + CONTEXT_MENU_SUBMENU_GAP_PX,
476
+ y: rect.top
477
+ });
478
+ }, [open, estimatedSubmenuHeight]);
479
+ return /* @__PURE__ */ jsxs(
480
+ "div",
481
+ {
482
+ ref: triggerRef,
483
+ className: "relative",
484
+ onPointerEnter: openSubmenu,
485
+ onPointerLeave: scheduleClose,
486
+ children: [
487
+ /* @__PURE__ */ jsxs(
488
+ "button",
489
+ {
490
+ "aria-expanded": open,
491
+ "aria-haspopup": "menu",
492
+ className: cn(
493
+ "flex h-8 w-full items-center gap-2 rounded-md border-0 bg-transparent px-2 text-left text-sm text-[var(--text-primary)] transition-colors hover:bg-transparency-block disabled:cursor-default disabled:opacity-50",
494
+ open && "bg-transparency-block"
495
+ ),
496
+ disabled: busy,
497
+ role: "menuitem",
498
+ type: "button",
499
+ onClick: () => {
500
+ setOpen((current) => {
501
+ const next = !current;
502
+ if (next) {
503
+ cancelClose();
504
+ }
505
+ return next;
506
+ });
507
+ },
508
+ children: [
509
+ /* @__PURE__ */ jsx("span", { className: "grid size-4 flex-none place-items-center text-[var(--text-secondary)]", children: /* @__PURE__ */ jsx(LaunchIcon, { className: "size-4" }) }),
510
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: copy.t("openWithLabel") }),
511
+ /* @__PURE__ */ jsx(
512
+ "span",
513
+ {
514
+ "aria-hidden": "true",
515
+ className: "grid size-4 flex-none place-items-center text-[var(--text-tertiary)]",
516
+ children: /* @__PURE__ */ jsx(ArrowRightIcon, { className: "size-4" })
517
+ }
518
+ )
519
+ ]
520
+ }
521
+ ),
522
+ /* @__PURE__ */ jsxs(
523
+ ViewportMenuSurface,
524
+ {
525
+ "data-workspace-file-manager-submenu": "",
526
+ open,
527
+ className: "w-[220px] max-h-[min(480px,calc(100vh-24px))] overflow-y-auto p-1",
528
+ dismissOnEscape: false,
529
+ dismissOnPointerDownOutside: false,
530
+ dismissOnScroll: false,
531
+ placement: {
532
+ type: "point",
533
+ point: placementPoint,
534
+ alignX: "start",
535
+ alignY: "auto",
536
+ estimatedSize: {
537
+ width: 220,
538
+ height: estimatedSubmenuHeight
539
+ }
540
+ },
541
+ role: "menu",
542
+ onPointerEnter: openSubmenu,
543
+ onPointerLeave: scheduleClose,
544
+ children: [
545
+ showOpenInAppBrowser ? /* @__PURE__ */ jsx(
546
+ ContextMenuActionButton,
547
+ {
548
+ disabled: busy,
549
+ icon: openInAppBrowserIcon ?? /* @__PURE__ */ jsx(WebIcon, { className: "size-4" }),
550
+ label: copy.t("openInAppBrowserLabel"),
551
+ onClick: () => {
552
+ onClose();
553
+ void onOpenInAppBrowser();
554
+ }
555
+ }
556
+ ) : null,
557
+ showExternalSection ? /* @__PURE__ */ jsx(ContextMenuDivider, {}) : null,
558
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs text-[var(--text-tertiary)]", children: copy.t("openWithLoadingLabel") }) : null,
559
+ applications.map((application) => {
560
+ const resolvedIcon = resolveOpenWithApplicationIcon?.(application);
561
+ return /* @__PURE__ */ jsx(
562
+ ContextMenuActionButton,
563
+ {
564
+ disabled: busy,
565
+ icon: resolvedIcon ?? (application.iconDataUrl ? /* @__PURE__ */ jsx(
566
+ "img",
567
+ {
568
+ alt: "",
569
+ className: "size-4 rounded-[4px] object-contain",
570
+ src: application.iconDataUrl
571
+ }
572
+ ) : /* @__PURE__ */ jsx(EyeIcon, { className: "size-4" })),
573
+ label: application.name,
574
+ onClick: () => {
575
+ onClose();
576
+ void onOpenWithApplication(application.applicationPath);
577
+ }
578
+ },
579
+ application.applicationPath
580
+ );
581
+ }),
582
+ showOpenInDefaultBrowser ? /* @__PURE__ */ jsx(
583
+ ContextMenuActionButton,
584
+ {
585
+ disabled: busy,
586
+ icon: /* @__PURE__ */ jsx(WebIcon, { className: "size-4" }),
587
+ label: copy.t("openInDefaultBrowserLabel"),
588
+ onClick: () => {
589
+ onClose();
590
+ void onOpenInDefaultBrowser();
591
+ }
592
+ }
593
+ ) : null,
594
+ showOpenWithOther ? /* @__PURE__ */ jsx(
595
+ ContextMenuActionButton,
596
+ {
597
+ disabled: busy,
598
+ icon: /* @__PURE__ */ jsx(LaunchIcon, { className: "size-4" }),
599
+ label: copy.t("openWithOtherLabel"),
600
+ onClick: () => {
601
+ onClose();
602
+ void onOpenWithOtherApplication();
603
+ }
604
+ }
605
+ ) : null
606
+ ]
607
+ }
608
+ )
609
+ ]
610
+ }
611
+ );
612
+ }
613
+ function WorkspaceFileManagerCreateDialog({
614
+ busy,
615
+ copy,
616
+ dialog,
617
+ onClose,
618
+ onConfirm,
619
+ onNameChange
620
+ }) {
621
+ if (!dialog) {
622
+ return null;
623
+ }
624
+ return /* @__PURE__ */ jsx(
625
+ Dialog,
626
+ {
627
+ open: true,
628
+ onOpenChange: (nextOpen) => {
629
+ if (!nextOpen) {
630
+ onClose();
631
+ }
632
+ },
633
+ children: /* @__PURE__ */ jsx(DialogContent, { "aria-busy": busy, showCloseButton: false, children: /* @__PURE__ */ jsxs(
634
+ "form",
635
+ {
636
+ className: "grid gap-3",
637
+ onSubmit: (event) => {
638
+ event.preventDefault();
639
+ onConfirm();
640
+ },
641
+ children: [
642
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: dialog.kind === "directory" ? copy.t("createDirectoryLabel") : copy.t("createFileLabel") }) }),
643
+ /* @__PURE__ */ jsx(
644
+ Input,
645
+ {
646
+ autoFocus: true,
647
+ placeholder: dialog.kind === "directory" ? copy.t("createDirectoryPlaceholder") : copy.t("createFilePlaceholder"),
648
+ value: dialog.name,
649
+ onChange: (event) => {
650
+ onNameChange(event.currentTarget.value);
651
+ }
652
+ }
653
+ ),
654
+ dialog.errorMessage ? /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--state-danger)]", children: dialog.errorMessage }) : null,
655
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
656
+ /* @__PURE__ */ jsx(
657
+ Button,
658
+ {
659
+ disabled: busy,
660
+ size: "dialog",
661
+ type: "button",
662
+ variant: "ghost",
663
+ onClick: onClose,
664
+ children: copy.t("cancelLabel")
665
+ }
666
+ ),
667
+ /* @__PURE__ */ jsx(Button, { disabled: busy, size: "dialog", type: "submit", children: copy.t("createActionLabel") })
668
+ ] })
669
+ ]
670
+ }
671
+ ) })
672
+ }
673
+ );
674
+ }
675
+ function WorkspaceFileManagerDeleteDialog({
676
+ busy,
677
+ copy,
678
+ entry,
679
+ onClose,
680
+ onConfirm
681
+ }) {
682
+ if (!entry) {
683
+ return null;
684
+ }
685
+ return /* @__PURE__ */ jsx(
686
+ ConfirmationDialog,
687
+ {
688
+ cancelLabel: copy.t("cancelLabel"),
689
+ confirmBusy: busy,
690
+ confirmLabel: busy ? copy.t("deletingLabel") : copy.t("deleteLabel"),
691
+ description: copy.t("deleteConfirmDescription", { name: entry.name }),
692
+ open: true,
693
+ title: copy.t("deleteLabel"),
694
+ tone: "destructive",
695
+ onConfirm,
696
+ onOpenChange: (nextOpen) => {
697
+ if (!nextOpen) {
698
+ onClose();
699
+ }
700
+ }
701
+ }
702
+ );
703
+ }
704
+ function WorkspaceFileManagerUnsupportedDialog({
705
+ copy,
706
+ dialog,
707
+ isViewing,
708
+ onAction,
709
+ onClose
710
+ }) {
711
+ if (!dialog) {
712
+ return null;
713
+ }
714
+ const title = dialog.title ?? (dialog.kind === "import" ? copy.t("unsupportedImportTitle") : copy.t("unsupportedViewTitle"));
715
+ const body = dialog.message ?? (dialog.kind === "import" ? copy.t("unsupportedImportBody") : copy.t("unsupportedViewBody", { name: dialog.entry?.name ?? "" }));
716
+ const actions = dialog.actions?.filter((action) => action.kind !== "none") ?? [];
717
+ return /* @__PURE__ */ jsx(
718
+ Dialog,
719
+ {
720
+ open: true,
721
+ onOpenChange: (nextOpen) => {
722
+ if (!nextOpen) {
723
+ onClose();
724
+ }
725
+ },
726
+ children: /* @__PURE__ */ jsxs(
727
+ DialogContent,
728
+ {
729
+ showCloseButton: false,
730
+ onEscapeKeyDown: (event) => {
731
+ if (isViewing) {
732
+ event.preventDefault();
733
+ }
734
+ },
735
+ onInteractOutside: (event) => {
736
+ if (isViewing) {
737
+ event.preventDefault();
738
+ }
739
+ },
740
+ children: [
741
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
742
+ /* @__PURE__ */ jsx(DialogTitle, { children: title }),
743
+ /* @__PURE__ */ jsx(DialogDescription, { children: body })
744
+ ] }),
745
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
746
+ /* @__PURE__ */ jsx(
747
+ Button,
748
+ {
749
+ disabled: isViewing,
750
+ size: "dialog",
751
+ type: "button",
752
+ variant: "ghost",
753
+ onClick: onClose,
754
+ children: copy.t("closeLabel")
755
+ }
756
+ ),
757
+ actions.map((action) => /* @__PURE__ */ jsx(
758
+ Button,
759
+ {
760
+ disabled: isViewing,
761
+ size: "dialog",
762
+ type: "button",
763
+ className: "shadow-none",
764
+ onClick: () => {
765
+ onAction(action);
766
+ },
767
+ children: action.label ?? (action.kind === "download" ? copy.t("downloadLabel") : copy.t("openLabel"))
768
+ },
769
+ action.kind
770
+ ))
771
+ ] })
772
+ ]
773
+ }
774
+ )
775
+ }
776
+ );
777
+ }
778
+ function WorkspaceFileManagerImportConflictDialog({
779
+ busy,
780
+ copy,
781
+ dialog,
782
+ onClose,
783
+ onConfirm
784
+ }) {
785
+ if (!dialog) {
786
+ return null;
787
+ }
788
+ const hasBlockedConflict = dialog.conflicts.some(
789
+ (conflict) => conflict.conflictKind === "type_mismatch"
790
+ );
791
+ return /* @__PURE__ */ jsxs(
792
+ ConfirmationDialog,
793
+ {
794
+ cancelLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("cancelLabel"),
795
+ className: "max-w-lg",
796
+ confirmBusy: busy,
797
+ confirmLabel: hasBlockedConflict ? copy.t("closeLabel") : copy.t("importConflictReplaceLabel"),
798
+ description: hasBlockedConflict ? copy.t("importTypeConflictDescription", {
799
+ count: dialog.conflicts.length
800
+ }) : copy.t("importConflictDescription", {
801
+ count: dialog.conflicts.length
802
+ }),
803
+ hideConfirmButton: hasBlockedConflict,
804
+ open: true,
805
+ title: hasBlockedConflict ? copy.t("importTypeConflictTitle") : copy.t("importConflictTitle"),
806
+ tone: hasBlockedConflict ? "default" : "destructive",
807
+ onConfirm: hasBlockedConflict ? onClose : onConfirm,
808
+ onOpenChange: (nextOpen) => {
809
+ if (!nextOpen) {
810
+ onClose();
811
+ }
812
+ },
813
+ children: [
814
+ /* @__PURE__ */ jsx(ImportConflictSummary, { copy, dialog }),
815
+ /* @__PURE__ */ jsx("div", { className: "max-h-60 overflow-auto rounded-lg border border-[var(--border-1)] bg-transparency-block", children: /* @__PURE__ */ jsx("div", { className: "divide-y divide-[var(--border-1)]", children: dialog.conflicts.map((conflict) => /* @__PURE__ */ jsxs(
816
+ "div",
817
+ {
818
+ className: "flex flex-col gap-1 px-4 py-3 text-sm",
819
+ children: [
820
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: conflict.name }),
821
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-[var(--text-secondary)]", children: [
822
+ copy.t("importConflictReviewLabel"),
823
+ ":",
824
+ " ",
825
+ conflict.destinationPath
826
+ ] })
827
+ ]
828
+ },
829
+ `${conflict.destinationPath}:${conflict.sourcePath}`
830
+ )) }) })
831
+ ]
832
+ }
833
+ );
834
+ }
835
+ function ImportConflictSummary({
836
+ copy,
837
+ dialog
838
+ }) {
839
+ const summaryItems = [];
840
+ const hasReasonBreakdown = dialog.summary?.reasonBreakdown?.some((reason) => reason.count > 0) ?? false;
841
+ if (typeof dialog.summary?.selectedCount === "number" && dialog.summary.selectedCount > 0) {
842
+ summaryItems.push(
843
+ copy.t("importConflictSummarySelected", {
844
+ count: dialog.summary.selectedCount
845
+ })
846
+ );
847
+ }
848
+ if (!hasReasonBreakdown && typeof dialog.summary?.filteredCount === "number" && dialog.summary.filteredCount > 0) {
849
+ summaryItems.push(
850
+ copy.t("importConflictSummaryFiltered", {
851
+ count: dialog.summary.filteredCount
852
+ })
853
+ );
854
+ }
855
+ if (!hasReasonBreakdown && typeof dialog.summary?.ignoredCount === "number" && dialog.summary.ignoredCount > 0) {
856
+ summaryItems.push(
857
+ copy.t("importConflictSummaryIgnored", {
858
+ count: dialog.summary.ignoredCount
859
+ })
860
+ );
861
+ }
862
+ for (const reason of dialog.summary?.reasonBreakdown ?? []) {
863
+ if (reason.count <= 0) {
864
+ continue;
865
+ }
866
+ const copyKey = importSummaryReasonCopyKey(reason.reason);
867
+ if (!copyKey) {
868
+ continue;
869
+ }
870
+ summaryItems.push(copy.t(copyKey, { count: reason.count }));
871
+ }
872
+ if (summaryItems.length === 0) {
873
+ return null;
874
+ }
875
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 text-xs text-[var(--text-secondary)]", children: summaryItems.map((item) => /* @__PURE__ */ jsx(
876
+ "span",
877
+ {
878
+ className: "rounded-md border border-[var(--border-1)] px-2 py-1",
879
+ children: item
880
+ },
881
+ item
882
+ )) });
883
+ }
884
+ function importSummaryReasonCopyKey(reason) {
885
+ switch (reason) {
886
+ case "ignored":
887
+ return "importConflictSummaryReasonIgnored";
888
+ case "symlink":
889
+ return "importConflictSummaryReasonSymlink";
890
+ case "system_metadata":
891
+ return "importConflictSummaryReasonSystemMetadata";
892
+ }
893
+ }
894
+
895
+ // src/ui/WorkspaceFileManagerPanels.tsx
896
+ import {
897
+ FileCodeIcon,
898
+ FileTextIcon,
899
+ FolderFilledIcon,
900
+ ImageFileIcon,
901
+ LoadingIcon,
902
+ ScrollArea,
903
+ VideoFileIcon,
904
+ cn as cn2
905
+ } from "@tutti-os/ui-system";
906
+ import { WorkspaceFilePreviewSurface as SharedWorkspaceFilePreviewSurface } from "@tutti-os/workspace-file-preview/react";
907
+ import {
908
+ useCallback as useCallback2,
909
+ useEffect as useEffect2,
910
+ useLayoutEffect as useLayoutEffect2,
911
+ useRef as useRef2,
912
+ useState as useState2
913
+ } from "react";
914
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
915
+ var workspaceFileManagerTableGridClassName = "grid-cols-[minmax(0,_1fr)_148px_96px]";
916
+ var workspaceFileManagerTableGridStyle = {
917
+ gridTemplateColumns: "minmax(0, 1fr) 148px 96px"
918
+ };
919
+ var workspaceFileManagerCompactTableGridClassName = "grid-cols-[minmax(0,_1fr)_96px_72px]";
920
+ var workspaceFileManagerCompactTableGridStyle = {
921
+ gridTemplateColumns: "minmax(0, 1fr) 96px 72px"
922
+ };
923
+ var workspaceFileManagerPreviewDetailGridStyle = {
924
+ gridTemplateColumns: "minmax(82px, 0.8fr) minmax(0, 1.2fr)"
925
+ };
926
+ var workspaceFileManagerStackedBreakpoint = 600;
927
+ var workspaceFileManagerPreviewDefaultWidth = 280;
928
+ var workspaceFileManagerPreviewMinWidth = 220;
929
+ var workspaceFileManagerTableMinWidth = 360;
930
+ var workspaceFileManagerMoveDragThreshold = 4;
931
+ var workspaceFileManagerMoveDragAutoScrollDelayMs = 500;
932
+ var workspaceFileManagerMoveDragAutoScrollEdgePx = 48;
933
+ var workspaceFileManagerMoveDragAutoScrollStepPx = 12;
934
+ var workspaceFileManagerEntryOpenClickIntervalMs = 500;
935
+ function WorkspaceFileManagerPanels({
936
+ canMove,
937
+ contextMenuEntryPath,
938
+ dateLocale,
939
+ entryDragMode,
940
+ entrySelectionEnabled = true,
941
+ copy,
942
+ inlineRenameEntryPath,
943
+ inlineRenameValidation,
944
+ isRenaming,
945
+ pendingDirectoryPath,
946
+ previewState,
947
+ selectedEntry,
948
+ selectedPath,
949
+ state,
950
+ showDropOverlay,
951
+ onBlankContextMenu,
952
+ onCancelInlineRename,
953
+ onClearInlineRenameValidation,
954
+ onConfirmInlineRename,
955
+ onEntryContextMenu,
956
+ onEntryDragStart,
957
+ onMoveEntry,
958
+ onOpenEntry,
959
+ onSelect
960
+ }) {
961
+ const { isStacked, rootRef } = useWorkspaceFileManagerStackedLayout(
962
+ workspaceFileManagerStackedBreakpoint
963
+ );
964
+ const previewResizeRef = useRef2(null);
965
+ const [previewPaneWidth, setPreviewPaneWidth] = useState2(
966
+ workspaceFileManagerPreviewDefaultWidth
967
+ );
968
+ const [moveDragPreview, setMoveDragPreview] = useState2(null);
969
+ const lastEntryClickRef = useRef2(
970
+ null
971
+ );
972
+ const moveDragAutoScrollRef = useRef2(null);
973
+ const moveDragRef = useRef2(null);
974
+ const suppressNextClickRef = useRef2(false);
975
+ const entriesRef = useRef2(state.entries);
976
+ const nativeEntryDragEnabled = onEntryDragStart !== void 0 && (entryDragMode === "external" || entryDragMode === void 0 && !canMove);
977
+ const internalMoveEnabled = canMove && !nativeEntryDragEnabled;
978
+ const stopMoveDragAutoScroll = useCallback2(() => {
979
+ const autoScroll = moveDragAutoScrollRef.current;
980
+ if (autoScroll?.frameId !== null && autoScroll?.frameId !== void 0) {
981
+ window.cancelAnimationFrame(autoScroll.frameId);
982
+ }
983
+ moveDragAutoScrollRef.current = null;
984
+ }, []);
985
+ const updateMoveDragAutoScroll = useCallback2(
986
+ (clientY) => {
987
+ const scrollViewport = resolveWorkspaceFileManagerTableScrollViewport(
988
+ rootRef.current
989
+ );
990
+ const direction = resolveWorkspaceFileManagerMoveDragAutoScrollDirection(
991
+ scrollViewport,
992
+ clientY
993
+ );
994
+ if (!scrollViewport || direction === null) {
995
+ stopMoveDragAutoScroll();
996
+ return;
997
+ }
998
+ const current = moveDragAutoScrollRef.current;
999
+ if (current?.scrollViewport === scrollViewport && current.direction === direction) {
1000
+ current.lastClientY = clientY;
1001
+ return;
1002
+ }
1003
+ stopMoveDragAutoScroll();
1004
+ const nextAutoScroll = {
1005
+ direction,
1006
+ enteredAtMs: window.performance.now(),
1007
+ frameId: null,
1008
+ lastClientY: clientY,
1009
+ scrollViewport
1010
+ };
1011
+ const step = (timestamp) => {
1012
+ if (moveDragAutoScrollRef.current !== nextAutoScroll) {
1013
+ return;
1014
+ }
1015
+ const nextDirection = resolveWorkspaceFileManagerMoveDragAutoScrollDirection(
1016
+ nextAutoScroll.scrollViewport,
1017
+ nextAutoScroll.lastClientY
1018
+ );
1019
+ if (nextDirection !== nextAutoScroll.direction) {
1020
+ stopMoveDragAutoScroll();
1021
+ return;
1022
+ }
1023
+ if (timestamp - nextAutoScroll.enteredAtMs >= workspaceFileManagerMoveDragAutoScrollDelayMs) {
1024
+ nextAutoScroll.scrollViewport.scrollTop += nextAutoScroll.direction * workspaceFileManagerMoveDragAutoScrollStepPx;
1025
+ }
1026
+ nextAutoScroll.frameId = window.requestAnimationFrame(step);
1027
+ };
1028
+ moveDragAutoScrollRef.current = nextAutoScroll;
1029
+ nextAutoScroll.frameId = window.requestAnimationFrame(step);
1030
+ },
1031
+ [rootRef, stopMoveDragAutoScroll]
1032
+ );
1033
+ const handleEntryPointerDown = useCallback2(
1034
+ (entry, event) => {
1035
+ if (!internalMoveEnabled || event.button !== 0) {
1036
+ return;
1037
+ }
1038
+ moveDragRef.current = {
1039
+ active: false,
1040
+ entry,
1041
+ startX: event.clientX,
1042
+ startY: event.clientY
1043
+ };
1044
+ },
1045
+ [internalMoveEnabled]
1046
+ );
1047
+ const shouldSuppressEntryClick = useCallback2(() => {
1048
+ if (!suppressNextClickRef.current) {
1049
+ return false;
1050
+ }
1051
+ suppressNextClickRef.current = false;
1052
+ return true;
1053
+ }, []);
1054
+ const handleTablePanelContextMenu = useCallback2(
1055
+ (event) => {
1056
+ const target = event.target;
1057
+ if (target instanceof Element && target.closest("[data-workspace-file-entry-path]")) {
1058
+ return;
1059
+ }
1060
+ onBlankContextMenu(event);
1061
+ },
1062
+ [onBlankContextMenu]
1063
+ );
1064
+ const handleEntryClick = useCallback2(
1065
+ (entry) => {
1066
+ if (shouldSuppressEntryClick()) {
1067
+ lastEntryClickRef.current = null;
1068
+ return;
1069
+ }
1070
+ const now = Date.now();
1071
+ const lastClick = lastEntryClickRef.current;
1072
+ if (lastClick?.path === entry.path && now - lastClick.timeMs <= workspaceFileManagerEntryOpenClickIntervalMs) {
1073
+ lastEntryClickRef.current = null;
1074
+ onOpenEntry(entry);
1075
+ return;
1076
+ }
1077
+ lastEntryClickRef.current = { path: entry.path, timeMs: now };
1078
+ if (!entrySelectionEnabled) {
1079
+ return;
1080
+ }
1081
+ onSelect(entry.path);
1082
+ },
1083
+ [entrySelectionEnabled, onOpenEntry, onSelect, shouldSuppressEntryClick]
1084
+ );
1085
+ useEffect2(() => {
1086
+ lastEntryClickRef.current = null;
1087
+ entriesRef.current = state.entries;
1088
+ }, [state.entries]);
1089
+ useEffect2(() => {
1090
+ function handleDocumentPointerMove(event) {
1091
+ const drag = moveDragRef.current;
1092
+ if (!drag) {
1093
+ return;
1094
+ }
1095
+ const deltaX = event.clientX - drag.startX;
1096
+ const deltaY = event.clientY - drag.startY;
1097
+ if (!drag.active && Math.hypot(deltaX, deltaY) >= workspaceFileManagerMoveDragThreshold) {
1098
+ drag.active = true;
1099
+ }
1100
+ if (drag.active) {
1101
+ const targetEntry = resolveMoveDragTargetEntry(
1102
+ entriesRef.current,
1103
+ drag.entry,
1104
+ event.clientX,
1105
+ event.clientY
1106
+ );
1107
+ setMoveDragPreview({
1108
+ entry: drag.entry,
1109
+ targetDirectoryPath: targetEntry?.path ?? null,
1110
+ x: event.clientX,
1111
+ y: event.clientY
1112
+ });
1113
+ updateMoveDragAutoScroll(event.clientY);
1114
+ event.preventDefault();
1115
+ }
1116
+ }
1117
+ function handleDocumentPointerUp(event) {
1118
+ const drag = moveDragRef.current;
1119
+ moveDragRef.current = null;
1120
+ stopMoveDragAutoScroll();
1121
+ setMoveDragPreview(null);
1122
+ if (!drag?.active) {
1123
+ return;
1124
+ }
1125
+ suppressNextClickRef.current = true;
1126
+ event.preventDefault();
1127
+ if (!internalMoveEnabled) {
1128
+ return;
1129
+ }
1130
+ const targetEntry = resolveMoveDragTargetEntry(
1131
+ entriesRef.current,
1132
+ drag.entry,
1133
+ event.clientX,
1134
+ event.clientY
1135
+ );
1136
+ if (targetEntry) {
1137
+ onMoveEntry(drag.entry, targetEntry.path);
1138
+ }
1139
+ }
1140
+ document.addEventListener("pointermove", handleDocumentPointerMove, true);
1141
+ document.addEventListener("mousemove", handleDocumentPointerMove, true);
1142
+ document.addEventListener("pointerup", handleDocumentPointerUp, true);
1143
+ document.addEventListener("mouseup", handleDocumentPointerUp, true);
1144
+ return () => {
1145
+ document.removeEventListener(
1146
+ "pointermove",
1147
+ handleDocumentPointerMove,
1148
+ true
1149
+ );
1150
+ document.removeEventListener(
1151
+ "mousemove",
1152
+ handleDocumentPointerMove,
1153
+ true
1154
+ );
1155
+ document.removeEventListener("pointerup", handleDocumentPointerUp, true);
1156
+ document.removeEventListener("mouseup", handleDocumentPointerUp, true);
1157
+ stopMoveDragAutoScroll();
1158
+ };
1159
+ }, [
1160
+ internalMoveEnabled,
1161
+ onMoveEntry,
1162
+ stopMoveDragAutoScroll,
1163
+ updateMoveDragAutoScroll
1164
+ ]);
1165
+ const previewPaneClassName = isStacked ? "min-w-0 border-t border-[var(--border-1)]" : "min-w-[220px] border-l border-[var(--border-1)]";
1166
+ const tableGridClassName = isStacked ? workspaceFileManagerCompactTableGridClassName : workspaceFileManagerTableGridClassName;
1167
+ const tableGridStyle = isStacked ? workspaceFileManagerCompactTableGridStyle : workspaceFileManagerTableGridStyle;
1168
+ const tableCellPaddingClassName = "px-4";
1169
+ const clampPreviewPaneWidth = useCallback2(
1170
+ (width, maxWidth) => {
1171
+ const resolvedMaxWidth = typeof maxWidth === "number" ? Math.max(workspaceFileManagerPreviewMinWidth, maxWidth) : Number.POSITIVE_INFINITY;
1172
+ return Math.min(
1173
+ Math.max(width, workspaceFileManagerPreviewMinWidth),
1174
+ resolvedMaxWidth
1175
+ );
1176
+ },
1177
+ []
1178
+ );
1179
+ useLayoutEffect2(() => {
1180
+ const element = rootRef.current;
1181
+ if (!element || isStacked) {
1182
+ return;
1183
+ }
1184
+ const publishLayout = () => {
1185
+ const containerWidth = Math.round(element.getBoundingClientRect().width);
1186
+ const maxWidth = Math.max(
1187
+ workspaceFileManagerPreviewMinWidth,
1188
+ containerWidth - workspaceFileManagerTableMinWidth
1189
+ );
1190
+ setPreviewPaneWidth(
1191
+ (currentWidth) => clampPreviewPaneWidth(currentWidth, maxWidth)
1192
+ );
1193
+ };
1194
+ publishLayout();
1195
+ if (typeof ResizeObserver === "undefined") {
1196
+ window.addEventListener("resize", publishLayout);
1197
+ return () => {
1198
+ window.removeEventListener("resize", publishLayout);
1199
+ };
1200
+ }
1201
+ const observer = new ResizeObserver(publishLayout);
1202
+ observer.observe(element);
1203
+ return () => {
1204
+ observer.disconnect();
1205
+ };
1206
+ }, [clampPreviewPaneWidth, isStacked, rootRef]);
1207
+ const handlePreviewResizePointerDown = useCallback2(
1208
+ (event) => {
1209
+ if (event.button !== 0) {
1210
+ return;
1211
+ }
1212
+ const containerWidth = rootRef.current?.getBoundingClientRect().width ?? 0;
1213
+ const maxWidth = Math.max(
1214
+ workspaceFileManagerPreviewMinWidth,
1215
+ containerWidth - workspaceFileManagerTableMinWidth
1216
+ );
1217
+ event.preventDefault();
1218
+ event.stopPropagation();
1219
+ event.currentTarget.setPointerCapture(event.pointerId);
1220
+ previewResizeRef.current = {
1221
+ pointerId: event.pointerId,
1222
+ startX: event.clientX,
1223
+ startWidth: previewPaneWidth,
1224
+ maxWidth
1225
+ };
1226
+ },
1227
+ [previewPaneWidth, rootRef]
1228
+ );
1229
+ const handlePreviewResizePointerMove = useCallback2(
1230
+ (event) => {
1231
+ const resize = previewResizeRef.current;
1232
+ if (!resize || resize.pointerId !== event.pointerId) {
1233
+ return;
1234
+ }
1235
+ const deltaX = event.clientX - resize.startX;
1236
+ setPreviewPaneWidth(
1237
+ clampPreviewPaneWidth(resize.startWidth - deltaX, resize.maxWidth)
1238
+ );
1239
+ },
1240
+ [clampPreviewPaneWidth]
1241
+ );
1242
+ const handlePreviewResizePointerEnd = useCallback2(
1243
+ (event) => {
1244
+ const resize = previewResizeRef.current;
1245
+ if (!resize || resize.pointerId !== event.pointerId) {
1246
+ return;
1247
+ }
1248
+ previewResizeRef.current = null;
1249
+ event.currentTarget.releasePointerCapture(event.pointerId);
1250
+ },
1251
+ []
1252
+ );
1253
+ const tablePanel = /* @__PURE__ */ jsx2(
1254
+ "div",
1255
+ {
1256
+ className: cn2(
1257
+ "flex min-h-0 min-w-0 flex-col overflow-hidden",
1258
+ isStacked ? "h-[44%] max-h-[48%] flex-none" : "flex-1"
1259
+ ),
1260
+ onContextMenu: handleTablePanelContextMenu,
1261
+ children: state.error ? /* @__PURE__ */ jsx2(FeedbackState, { message: state.error }) : state.isLoading && state.entries.length === 0 ? /* @__PURE__ */ jsx2(FeedbackState, { message: copy.t("loading") }) : state.entries.length === 0 ? /* @__PURE__ */ jsx2(FeedbackState, { message: copy.t("emptyDirectory") }) : /* @__PURE__ */ jsx2(ScrollArea, { className: "min-h-0 flex-1 [&_[data-orientation=vertical][data-slot=scroll-area-scrollbar]]:opacity-100", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col", children: [
1262
+ /* @__PURE__ */ jsxs2(
1263
+ "div",
1264
+ {
1265
+ className: cn2(
1266
+ "grid h-9 min-h-9 items-center gap-x-6 border-b border-[var(--border-1)] text-xs font-normal text-[var(--text-secondary)]",
1267
+ tableGridClassName
1268
+ ),
1269
+ style: tableGridStyle,
1270
+ children: [
1271
+ /* @__PURE__ */ jsx2("span", { className: tableCellPaddingClassName, children: copy.t("nameLabel") }),
1272
+ /* @__PURE__ */ jsx2(
1273
+ "span",
1274
+ {
1275
+ className: cn2("whitespace-nowrap", tableCellPaddingClassName),
1276
+ children: copy.t("modifiedLabel")
1277
+ }
1278
+ ),
1279
+ /* @__PURE__ */ jsx2(
1280
+ "span",
1281
+ {
1282
+ className: cn2("whitespace-nowrap", tableCellPaddingClassName),
1283
+ children: copy.t("sizeLabel")
1284
+ }
1285
+ )
1286
+ ]
1287
+ }
1288
+ ),
1289
+ state.entries.map((entry) => /* @__PURE__ */ jsx2(
1290
+ EntryRow,
1291
+ {
1292
+ contextMenuActive: contextMenuEntryPath === entry.path,
1293
+ copy,
1294
+ dateLocale,
1295
+ entry,
1296
+ canMove: internalMoveEnabled,
1297
+ draggable: nativeEntryDragEnabled,
1298
+ gridClassName: tableGridClassName,
1299
+ gridStyle: tableGridStyle,
1300
+ inlineRenameValidation: inlineRenameEntryPath === entry.path ? inlineRenameValidation : null,
1301
+ isEnteringDirectory: pendingDirectoryPath === entry.path,
1302
+ isInlineRenaming: inlineRenameEntryPath === entry.path,
1303
+ isRenaming,
1304
+ moveDragActive: moveDragPreview !== null,
1305
+ moveDragSource: moveDragPreview?.entry.path === entry.path,
1306
+ moveDragTarget: moveDragPreview?.targetDirectoryPath === entry.path,
1307
+ tableCellPaddingClassName,
1308
+ selected: selectedPath === entry.path,
1309
+ onCancelInlineRename,
1310
+ onClearInlineRenameValidation,
1311
+ onConfirmInlineRename,
1312
+ onContextMenu: onEntryContextMenu,
1313
+ onDragStart: onEntryDragStart,
1314
+ onClick: handleEntryClick,
1315
+ onPointerDown: handleEntryPointerDown
1316
+ },
1317
+ entry.path
1318
+ ))
1319
+ ] }) })
1320
+ }
1321
+ );
1322
+ const previewPanel = /* @__PURE__ */ jsx2(
1323
+ "aside",
1324
+ {
1325
+ className: cn2(
1326
+ "relative flex h-full min-h-0 flex-col gap-[14px] overflow-auto p-4",
1327
+ isStacked ? "max-h-[44%] flex-none" : "flex-none",
1328
+ previewPaneClassName
1329
+ ),
1330
+ style: isStacked ? void 0 : { width: previewPaneWidth },
1331
+ children: /* @__PURE__ */ jsx2(
1332
+ PreviewPane,
1333
+ {
1334
+ copy,
1335
+ dateLocale,
1336
+ entry: selectedEntry,
1337
+ previewState
1338
+ }
1339
+ )
1340
+ }
1341
+ );
1342
+ return /* @__PURE__ */ jsxs2(
1343
+ "div",
1344
+ {
1345
+ className: "relative min-h-0 min-w-0 flex-1 overflow-hidden",
1346
+ ref: rootRef,
1347
+ children: [
1348
+ /* @__PURE__ */ jsxs2(
1349
+ "div",
1350
+ {
1351
+ className: cn2(
1352
+ "h-full min-h-0 min-w-0 bg-transparent",
1353
+ isStacked ? "flex flex-col gap-3" : "flex"
1354
+ ),
1355
+ children: [
1356
+ tablePanel,
1357
+ isStacked ? null : /* @__PURE__ */ jsx2(
1358
+ "div",
1359
+ {
1360
+ "aria-orientation": "vertical",
1361
+ className: "nodrag absolute top-0 bottom-0 z-[1] w-2 cursor-col-resize touch-none",
1362
+ role: "separator",
1363
+ style: {
1364
+ right: previewPaneWidth - 4
1365
+ },
1366
+ onPointerCancel: handlePreviewResizePointerEnd,
1367
+ onPointerDown: handlePreviewResizePointerDown,
1368
+ onPointerMove: handlePreviewResizePointerMove,
1369
+ onPointerUp: handlePreviewResizePointerEnd
1370
+ }
1371
+ ),
1372
+ previewPanel
1373
+ ]
1374
+ }
1375
+ ),
1376
+ /* @__PURE__ */ jsx2(
1377
+ "div",
1378
+ {
1379
+ "aria-hidden": "true",
1380
+ className: cn2(
1381
+ "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",
1382
+ showDropOverlay && "opacity-100"
1383
+ ),
1384
+ children: /* @__PURE__ */ jsx2("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") })
1385
+ }
1386
+ ),
1387
+ moveDragPreview ? /* @__PURE__ */ jsx2(MoveDragPreview, { preview: moveDragPreview }) : null
1388
+ ]
1389
+ }
1390
+ );
1391
+ }
1392
+ function useWorkspaceFileManagerStackedLayout(breakpoint) {
1393
+ const rootRef = useRef2(null);
1394
+ const [isStacked, setIsStacked] = useState2(false);
1395
+ useLayoutEffect2(() => {
1396
+ const element = rootRef.current;
1397
+ if (!element) {
1398
+ return;
1399
+ }
1400
+ const publishLayout = () => {
1401
+ const nextIsStacked = Math.round(element.getBoundingClientRect().width) <= breakpoint;
1402
+ setIsStacked(
1403
+ (current) => current === nextIsStacked ? current : nextIsStacked
1404
+ );
1405
+ };
1406
+ publishLayout();
1407
+ if (typeof ResizeObserver === "undefined") {
1408
+ window.addEventListener("resize", publishLayout);
1409
+ return () => {
1410
+ window.removeEventListener("resize", publishLayout);
1411
+ };
1412
+ }
1413
+ const observer = new ResizeObserver(publishLayout);
1414
+ observer.observe(element);
1415
+ return () => {
1416
+ observer.disconnect();
1417
+ };
1418
+ }, [breakpoint]);
1419
+ return { isStacked, rootRef };
1420
+ }
1421
+ function EntryRow({
1422
+ canMove,
1423
+ contextMenuActive,
1424
+ copy,
1425
+ dateLocale,
1426
+ draggable,
1427
+ entry,
1428
+ gridClassName,
1429
+ gridStyle,
1430
+ inlineRenameValidation,
1431
+ isEnteringDirectory,
1432
+ isInlineRenaming,
1433
+ isRenaming,
1434
+ moveDragActive,
1435
+ moveDragSource,
1436
+ moveDragTarget,
1437
+ selected,
1438
+ tableCellPaddingClassName,
1439
+ onCancelInlineRename,
1440
+ onClearInlineRenameValidation,
1441
+ onConfirmInlineRename,
1442
+ onContextMenu,
1443
+ onDragStart,
1444
+ onClick,
1445
+ onPointerDown
1446
+ }) {
1447
+ const buttonRowRef = useRef2(null);
1448
+ const divRowRef = useRef2(null);
1449
+ useLayoutEffect2(() => {
1450
+ if (!selected) {
1451
+ return;
1452
+ }
1453
+ (buttonRowRef.current ?? divRowRef.current)?.scrollIntoView({
1454
+ block: "nearest",
1455
+ inline: "nearest"
1456
+ });
1457
+ }, [selected]);
1458
+ const rowClassName = cn2(
1459
+ "grid min-h-10 w-full items-center gap-x-6 border-b border-[var(--border-1)] px-0 text-left transition-colors",
1460
+ gridClassName,
1461
+ isInlineRenaming ? "cursor-default" : "cursor-pointer hover:bg-transparency-block",
1462
+ canMove && moveDragActive && "cursor-grabbing",
1463
+ selected || contextMenuActive || isInlineRenaming ? "bg-transparency-block text-[var(--text-primary)]" : "text-[var(--text-secondary)]",
1464
+ moveDragSource && "opacity-55",
1465
+ moveDragTarget && "bg-[var(--accent-bg)] text-[var(--text-primary)] outline outline-1 -outline-offset-1 outline-[var(--border-focus)]",
1466
+ moveDragActive && !moveDragSource && !moveDragTarget && "opacity-90"
1467
+ );
1468
+ const rowProps = {
1469
+ "aria-label": entry.name,
1470
+ className: rowClassName,
1471
+ "data-workspace-file-entry-path": entry.path,
1472
+ draggable: isInlineRenaming ? false : draggable,
1473
+ style: gridStyle,
1474
+ onContextMenu: (event) => {
1475
+ onContextMenu(event, entry);
1476
+ }
1477
+ };
1478
+ const nameCell = /* @__PURE__ */ jsx2("span", { className: tableCellPaddingClassName, children: /* @__PURE__ */ jsx2(
1479
+ EntryNameCell,
1480
+ {
1481
+ copy,
1482
+ entry,
1483
+ inlineRenameValidation,
1484
+ isEnteringDirectory,
1485
+ isInlineRenaming,
1486
+ isRenaming,
1487
+ onCancelInlineRename,
1488
+ onClearInlineRenameValidation,
1489
+ onConfirmInlineRename
1490
+ }
1491
+ ) });
1492
+ const modifiedCell = /* @__PURE__ */ jsx2(
1493
+ "span",
1494
+ {
1495
+ className: cn2(
1496
+ "truncate text-xs text-[var(--text-secondary)]",
1497
+ tableCellPaddingClassName
1498
+ ),
1499
+ children: formatWorkspaceFileModifiedTime(entry.mtimeMs, dateLocale)
1500
+ }
1501
+ );
1502
+ const sizeCell = /* @__PURE__ */ jsx2(
1503
+ "span",
1504
+ {
1505
+ className: cn2(
1506
+ "truncate text-xs text-[var(--text-secondary)]",
1507
+ tableCellPaddingClassName
1508
+ ),
1509
+ children: entry.kind === "directory" ? "--" : formatWorkspaceFileBytes(entry.sizeBytes)
1510
+ }
1511
+ );
1512
+ if (isInlineRenaming) {
1513
+ return /* @__PURE__ */ jsxs2("div", { ...rowProps, ref: divRowRef, children: [
1514
+ nameCell,
1515
+ modifiedCell,
1516
+ sizeCell
1517
+ ] });
1518
+ }
1519
+ return /* @__PURE__ */ jsxs2(
1520
+ "button",
1521
+ {
1522
+ ...rowProps,
1523
+ ref: buttonRowRef,
1524
+ type: "button",
1525
+ onDragStart: (event) => {
1526
+ if (!draggable) {
1527
+ event.preventDefault();
1528
+ return;
1529
+ }
1530
+ onDragStart?.(entry, event.dataTransfer);
1531
+ },
1532
+ onPointerDown: (event) => {
1533
+ onPointerDown(entry, event);
1534
+ },
1535
+ onClick: () => {
1536
+ onClick(entry);
1537
+ },
1538
+ children: [
1539
+ nameCell,
1540
+ modifiedCell,
1541
+ sizeCell
1542
+ ]
1543
+ }
1544
+ );
1545
+ }
1546
+ function MoveDragPreview({
1547
+ preview
1548
+ }) {
1549
+ const visualKind = resolveWorkspaceFileVisualKind(preview.entry);
1550
+ return /* @__PURE__ */ jsxs2(
1551
+ "div",
1552
+ {
1553
+ "aria-hidden": "true",
1554
+ className: "pointer-events-none fixed z-[60] flex max-w-[280px] items-center gap-2 rounded-md border border-[var(--line-1)] bg-[var(--background-fronted)] px-3 py-2 text-sm font-normal text-[var(--text-primary)] shadow-panel",
1555
+ style: {
1556
+ left: preview.x,
1557
+ top: preview.y,
1558
+ transform: "translate(12px, 12px)"
1559
+ },
1560
+ children: [
1561
+ /* @__PURE__ */ jsx2(
1562
+ "span",
1563
+ {
1564
+ className: cn2(
1565
+ "grid size-[18px] flex-none place-items-center",
1566
+ entryIconColorClassName(visualKind)
1567
+ ),
1568
+ children: /* @__PURE__ */ jsx2(EntryIcon, { visualKind })
1569
+ }
1570
+ ),
1571
+ /* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate", children: preview.entry.name })
1572
+ ]
1573
+ }
1574
+ );
1575
+ }
1576
+ function resolveWorkspaceFileManagerTableScrollViewport(rootElement) {
1577
+ return rootElement?.querySelector(
1578
+ '[data-slot="scroll-area-viewport"]'
1579
+ ) ?? null;
1580
+ }
1581
+ function resolveWorkspaceFileManagerMoveDragAutoScrollDirection(scrollViewport, clientY) {
1582
+ if (!scrollViewport) {
1583
+ return null;
1584
+ }
1585
+ const { bottom, top } = scrollViewport.getBoundingClientRect();
1586
+ const canScrollUp = scrollViewport.scrollTop > 0;
1587
+ const canScrollDown = scrollViewport.scrollTop + scrollViewport.clientHeight < scrollViewport.scrollHeight;
1588
+ if (canScrollUp && clientY <= top + workspaceFileManagerMoveDragAutoScrollEdgePx) {
1589
+ return -1;
1590
+ }
1591
+ if (canScrollDown && clientY >= bottom - workspaceFileManagerMoveDragAutoScrollEdgePx) {
1592
+ return 1;
1593
+ }
1594
+ return null;
1595
+ }
1596
+ function resolveMoveDragTargetEntry(entries, movedEntry, x, y) {
1597
+ const targetElement = document.elementFromPoint(x, y);
1598
+ const rowElement = targetElement?.closest(
1599
+ "[data-workspace-file-entry-path]"
1600
+ );
1601
+ const targetPath = rowElement?.dataset.workspaceFileEntryPath;
1602
+ const targetEntry = targetPath ? entries.find((entry) => entry.path === targetPath) : null;
1603
+ if (!targetEntry || !canMoveEntryToDirectory(movedEntry, targetEntry)) {
1604
+ return null;
1605
+ }
1606
+ return targetEntry;
1607
+ }
1608
+ function canMoveEntryToDirectory(movedEntry, targetEntry) {
1609
+ return movedEntry.kind !== "unknown" && targetEntry.kind === "directory" && movedEntry.path !== targetEntry.path && !targetEntry.path.startsWith(`${movedEntry.path}/`);
1610
+ }
1611
+ function EntryNameCell({
1612
+ copy,
1613
+ entry,
1614
+ inlineRenameValidation = null,
1615
+ isEnteringDirectory = false,
1616
+ isInlineRenaming = false,
1617
+ isRenaming = false,
1618
+ onCancelInlineRename,
1619
+ onClearInlineRenameValidation,
1620
+ onConfirmInlineRename
1621
+ }) {
1622
+ const visualKind = resolveWorkspaceFileVisualKind(entry);
1623
+ const nameParts = splitWorkspaceFileName(entry.name);
1624
+ const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
1625
+ const [name, setName] = useState2(entry.name);
1626
+ const inputRef = useRef2(null);
1627
+ const skipBlurRef = useRef2(false);
1628
+ useEffect2(() => {
1629
+ if (!isInlineRenaming) {
1630
+ return;
1631
+ }
1632
+ setName(entry.name);
1633
+ skipBlurRef.current = false;
1634
+ }, [entry.name, entry.path, isInlineRenaming]);
1635
+ useLayoutEffect2(() => {
1636
+ if (!isInlineRenaming) {
1637
+ return;
1638
+ }
1639
+ const input = inputRef.current;
1640
+ if (!input) {
1641
+ return;
1642
+ }
1643
+ input.focus();
1644
+ if (hasFileExtension) {
1645
+ input.setSelectionRange(0, nameParts.start.length);
1646
+ } else {
1647
+ input.select();
1648
+ }
1649
+ }, [hasFileExtension, isInlineRenaming, nameParts.start.length]);
1650
+ const validationMessage = inlineRenameValidation === "required" ? copy.t("createNameRequired") : inlineRenameValidation === "invalid" ? copy.t("createNameInvalid") : null;
1651
+ const handleConfirm = useCallback2(async () => {
1652
+ const confirmed = await onConfirmInlineRename(name);
1653
+ if (!confirmed) {
1654
+ inputRef.current?.focus();
1655
+ }
1656
+ }, [name, onConfirmInlineRename]);
1657
+ if (isInlineRenaming) {
1658
+ return /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-2", children: [
1659
+ /* @__PURE__ */ jsx2(
1660
+ "span",
1661
+ {
1662
+ className: cn2(
1663
+ "grid size-[18px] flex-none place-items-center",
1664
+ entryIconColorClassName(visualKind)
1665
+ ),
1666
+ children: /* @__PURE__ */ jsx2(EntryIcon, { visualKind })
1667
+ }
1668
+ ),
1669
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
1670
+ /* @__PURE__ */ jsx2(
1671
+ "input",
1672
+ {
1673
+ "aria-invalid": inlineRenameValidation !== null,
1674
+ "aria-label": copy.t("renameLabel"),
1675
+ className: cn2(
1676
+ "min-w-0 max-w-full rounded-md border border-transparent bg-[var(--transparency-block)] px-1.5 py-0.5 text-sm text-[var(--text-primary)] outline-none transition-colors duration-200 selection:bg-[var(--transparency-active)] selection:text-[var(--text-primary)] hover:bg-[var(--transparency-hover)] focus:bg-[var(--transparency-hover)] focus-visible:border-transparent focus-visible:bg-[var(--transparency-hover)] disabled:bg-[var(--transparency-block)] disabled:text-[var(--text-disabled)] disabled:opacity-100",
1677
+ inlineRenameValidation !== null && "border-[var(--state-danger)]"
1678
+ ),
1679
+ disabled: isRenaming,
1680
+ ref: inputRef,
1681
+ value: name,
1682
+ onBlur: () => {
1683
+ if (skipBlurRef.current) {
1684
+ skipBlurRef.current = false;
1685
+ return;
1686
+ }
1687
+ void handleConfirm();
1688
+ },
1689
+ onChange: (event) => {
1690
+ setName(event.currentTarget.value);
1691
+ onClearInlineRenameValidation();
1692
+ },
1693
+ onFocus: (event) => {
1694
+ if (hasFileExtension) {
1695
+ event.currentTarget.setSelectionRange(
1696
+ 0,
1697
+ nameParts.start.length
1698
+ );
1699
+ } else {
1700
+ event.currentTarget.select();
1701
+ }
1702
+ },
1703
+ onKeyDown: (event) => {
1704
+ if (event.key === "Enter") {
1705
+ event.preventDefault();
1706
+ void handleConfirm();
1707
+ return;
1708
+ }
1709
+ if (event.key === "Escape") {
1710
+ event.preventDefault();
1711
+ skipBlurRef.current = true;
1712
+ onCancelInlineRename();
1713
+ }
1714
+ }
1715
+ }
1716
+ ),
1717
+ validationMessage ? /* @__PURE__ */ jsx2("span", { className: "text-xs text-[var(--state-danger)]", children: validationMessage }) : null
1718
+ ] })
1719
+ ] });
1720
+ }
1721
+ return /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-2", children: [
1722
+ /* @__PURE__ */ jsx2(
1723
+ "span",
1724
+ {
1725
+ className: cn2(
1726
+ "grid size-[18px] flex-none place-items-center",
1727
+ isEnteringDirectory ? "text-[var(--text-tertiary)]" : entryIconColorClassName(visualKind)
1728
+ ),
1729
+ children: isEnteringDirectory ? /* @__PURE__ */ jsx2(LoadingIcon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx2(EntryIcon, { visualKind })
1730
+ }
1731
+ ),
1732
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
1733
+ /* @__PURE__ */ jsx2("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: nameParts.start }),
1734
+ nameParts.end ? /* @__PURE__ */ jsx2("span", { className: "flex-none overflow-hidden text-ellipsis", children: nameParts.end }) : null
1735
+ ] })
1736
+ ] });
1737
+ }
1738
+ function entryIconColorClassName(visualKind) {
1739
+ return visualKind === "directory" ? "text-[var(--rich-text-mention-file)]" : "text-[var(--text-tertiary)]";
1740
+ }
1741
+ function EntryIcon({
1742
+ className = "size-4",
1743
+ visualKind
1744
+ }) {
1745
+ switch (visualKind) {
1746
+ case "directory":
1747
+ return /* @__PURE__ */ jsx2(FolderFilledIcon, { className });
1748
+ case "image":
1749
+ return /* @__PURE__ */ jsx2(ImageFileIcon, { className });
1750
+ case "video":
1751
+ return /* @__PURE__ */ jsx2(VideoFileIcon, { className });
1752
+ case "markdown":
1753
+ case "document":
1754
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className });
1755
+ case "code":
1756
+ return /* @__PURE__ */ jsx2(FileCodeIcon, { className });
1757
+ case "binary":
1758
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className });
1759
+ default:
1760
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className });
1761
+ }
1762
+ }
1763
+ function PreviewPane({
1764
+ copy,
1765
+ dateLocale,
1766
+ entry,
1767
+ previewState
1768
+ }) {
1769
+ if (!entry || previewState.status === "empty") {
1770
+ return /* @__PURE__ */ jsx2("div", { className: "grid h-full min-h-[180px] place-items-center overflow-hidden p-8 text-center text-sm leading-5 text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx2("span", { className: "max-w-[24ch] [overflow-wrap:anywhere]", children: copy.t("previewEmptyLabel") }) });
1771
+ }
1772
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1773
+ /* @__PURE__ */ jsx2(PreviewSurface, { copy, previewState }),
1774
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-[14px]", children: [
1775
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-[3px]", children: [
1776
+ /* @__PURE__ */ jsx2("strong", { className: "min-w-0 truncate text-[15px] font-semibold text-[var(--text-primary)]", children: entry.name }),
1777
+ /* @__PURE__ */ jsx2("p", { className: "min-w-0 truncate text-xs text-[var(--text-secondary)]", children: entry.path })
1778
+ ] }),
1779
+ /* @__PURE__ */ jsxs2("dl", { className: "border-t border-[var(--border-1)]", children: [
1780
+ /* @__PURE__ */ jsx2(
1781
+ PreviewDetail,
1782
+ {
1783
+ label: copy.t("modifiedLabel"),
1784
+ value: formatWorkspaceFileModifiedTime(entry.mtimeMs, dateLocale)
1785
+ }
1786
+ ),
1787
+ /* @__PURE__ */ jsx2(
1788
+ PreviewDetail,
1789
+ {
1790
+ label: copy.t("sizeLabel"),
1791
+ value: entry.kind === "directory" ? "--" : formatWorkspaceFileBytes(entry.sizeBytes)
1792
+ }
1793
+ )
1794
+ ] })
1795
+ ] })
1796
+ ] });
1797
+ }
1798
+ function PreviewSurface({
1799
+ copy,
1800
+ previewState
1801
+ }) {
1802
+ return /* @__PURE__ */ jsx2(
1803
+ SharedWorkspaceFilePreviewSurface,
1804
+ {
1805
+ directoryMessage: copy.t("previewDirectoryLabel"),
1806
+ emptyMessage: copy.t("previewEmptyLabel"),
1807
+ frameClassName: "flex h-60 min-h-60 max-h-60 items-center justify-center overflow-hidden rounded-lg border border-[var(--border-1)] bg-[var(--transparency-block)]",
1808
+ imageAlt: (entry) => entry.name,
1809
+ imageFrameClassName: "p-4",
1810
+ loadingIndicator: /* @__PURE__ */ jsx2("span", { className: "mx-auto grid size-11 place-items-center rounded-lg bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx2(LoadingIcon, { className: "size-4 animate-spin" }) }),
1811
+ loadingMessage: copy.t("previewLoadingLabel"),
1812
+ messageClassName: "max-w-[24ch] [overflow-wrap:anywhere]",
1813
+ renderIcon: (entry) => /* @__PURE__ */ jsx2(
1814
+ EntryIcon,
1815
+ {
1816
+ className: "mx-auto size-7",
1817
+ visualKind: resolveWorkspaceFilePreviewIconKind(entry)
1818
+ }
1819
+ ),
1820
+ state: previewState,
1821
+ textFrameClassName: "items-stretch justify-stretch"
1822
+ }
1823
+ );
1824
+ }
1825
+ function resolveWorkspaceFilePreviewIconKind(entry) {
1826
+ if ("kind" in entry) {
1827
+ return resolveWorkspaceFileVisualKind(entry);
1828
+ }
1829
+ return entry.fileKind === "image" ? "image" : "document";
1830
+ }
1831
+ function PreviewDetail({
1832
+ label,
1833
+ value
1834
+ }) {
1835
+ return /* @__PURE__ */ jsxs2(
1836
+ "div",
1837
+ {
1838
+ className: "@max-[600px]/workspace-file-manager:grid-cols-1 @max-[600px]/workspace-file-manager:gap-0.5 grid gap-2.5 border-b border-[var(--border-1)] py-2.5 text-xs",
1839
+ style: workspaceFileManagerPreviewDetailGridStyle,
1840
+ children: [
1841
+ /* @__PURE__ */ jsx2("dt", { className: "truncate text-[var(--text-secondary)]", children: label }),
1842
+ /* @__PURE__ */ jsx2("dd", { className: "@max-[600px]/workspace-file-manager:text-left truncate text-right text-[var(--text-primary)]", children: value })
1843
+ ]
1844
+ }
1845
+ );
1846
+ }
1847
+ function FeedbackState({ message }) {
1848
+ return /* @__PURE__ */ jsx2("div", { className: "grid min-h-0 flex-1 place-items-center p-6 text-center text-sm text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx2("span", { className: "max-w-[34ch] [overflow-wrap:anywhere]", children: message }) });
1849
+ }
1850
+ function hasFileDragPayload(dataTransfer) {
1851
+ return Array.from(dataTransfer.types).includes("Files");
1852
+ }
1853
+
1854
+ // src/ui/WorkspaceFileManagerToolbar.tsx
1855
+ import {
1856
+ ArrowLeftIcon,
1857
+ ArrowRightIcon as ArrowRightIcon2,
1858
+ Button as Button2,
1859
+ LoadingIcon as LoadingIcon2,
1860
+ RefreshIcon,
1861
+ cn as cn3
1862
+ } from "@tutti-os/ui-system";
1863
+ import { useState as useState3 } from "react";
1864
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1865
+ function WorkspaceFileManagerToolbar({
1866
+ breadcrumbs,
1867
+ canGoBack,
1868
+ canGoForward,
1869
+ copy,
1870
+ currentDirectoryPath,
1871
+ isBusy,
1872
+ isLoading,
1873
+ isMutating,
1874
+ onGoBack,
1875
+ onGoForward,
1876
+ onLoadDirectory,
1877
+ onRefresh
1878
+ }) {
1879
+ const [refreshAnimationKey, setRefreshAnimationKey] = useState3(0);
1880
+ const handleRefresh = () => {
1881
+ setRefreshAnimationKey((currentKey) => currentKey + 1);
1882
+ onRefresh();
1883
+ };
1884
+ return /* @__PURE__ */ jsxs3("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: [
1885
+ /* @__PURE__ */ jsx3(
1886
+ ToolbarIconButton,
1887
+ {
1888
+ ariaLabel: copy.t("backLabel"),
1889
+ disabled: !canGoBack || isLoading || isBusy,
1890
+ title: copy.t("backLabel"),
1891
+ onClick: onGoBack,
1892
+ children: /* @__PURE__ */ jsx3(ArrowLeftIcon, { className: "size-4" })
1893
+ }
1894
+ ),
1895
+ /* @__PURE__ */ jsx3(
1896
+ ToolbarIconButton,
1897
+ {
1898
+ ariaLabel: copy.t("forwardLabel"),
1899
+ disabled: !canGoForward || isLoading || isBusy,
1900
+ title: copy.t("forwardLabel"),
1901
+ onClick: onGoForward,
1902
+ children: /* @__PURE__ */ jsx3(ArrowRightIcon2, { className: "size-4" })
1903
+ }
1904
+ ),
1905
+ /* @__PURE__ */ jsx3(
1906
+ "nav",
1907
+ {
1908
+ "aria-label": currentDirectoryPath,
1909
+ className: "@max-[600px]/workspace-file-manager:flex-auto flex min-w-0 flex-1 overflow-hidden pr-2",
1910
+ "data-workspace-file-manager-path": "",
1911
+ children: /* @__PURE__ */ jsx3("ol", { className: "flex min-w-0 max-w-full flex-1 items-center gap-2 overflow-hidden", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsx3(
1912
+ BreadcrumbButton,
1913
+ {
1914
+ active: index === breadcrumbs.length - 1,
1915
+ label: crumb.label,
1916
+ showSeparator: index > 0,
1917
+ onClick: () => {
1918
+ onLoadDirectory(crumb.path);
1919
+ }
1920
+ },
1921
+ crumb.path
1922
+ )) })
1923
+ }
1924
+ ),
1925
+ /* @__PURE__ */ jsx3("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: /* @__PURE__ */ jsxs3(
1926
+ ToolbarActionButton,
1927
+ {
1928
+ disabled: isLoading || isMutating || isBusy,
1929
+ onClick: handleRefresh,
1930
+ children: [
1931
+ isLoading ? /* @__PURE__ */ jsx3(LoadingIcon2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx3(
1932
+ RefreshIcon,
1933
+ {
1934
+ className: cn3(
1935
+ "size-4",
1936
+ refreshAnimationKey > 0 && "motion-safe:animate-[spin_520ms_cubic-bezier(0.4,0,0.2,1)_1_reverse]"
1937
+ )
1938
+ },
1939
+ refreshAnimationKey
1940
+ ),
1941
+ /* @__PURE__ */ jsx3("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
1942
+ ]
1943
+ }
1944
+ ) })
1945
+ ] });
1946
+ }
1947
+ function ToolbarIconButton({
1948
+ ariaLabel,
1949
+ children,
1950
+ disabled,
1951
+ onClick,
1952
+ title
1953
+ }) {
1954
+ return /* @__PURE__ */ jsx3(
1955
+ Button2,
1956
+ {
1957
+ "aria-label": ariaLabel,
1958
+ className: "size-7 min-w-7 rounded-sm p-0 text-[var(--text-primary)]",
1959
+ disabled,
1960
+ size: "icon-sm",
1961
+ title,
1962
+ type: "button",
1963
+ variant: "ghost",
1964
+ onClick,
1965
+ children
1966
+ }
1967
+ );
1968
+ }
1969
+ function ToolbarActionButton({
1970
+ children,
1971
+ disabled,
1972
+ onClick
1973
+ }) {
1974
+ return /* @__PURE__ */ jsx3(
1975
+ Button2,
1976
+ {
1977
+ 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",
1978
+ disabled,
1979
+ size: "sm",
1980
+ type: "button",
1981
+ variant: "ghost",
1982
+ onClick,
1983
+ children
1984
+ }
1985
+ );
1986
+ }
1987
+ function BreadcrumbButton({
1988
+ active,
1989
+ label,
1990
+ onClick,
1991
+ showSeparator
1992
+ }) {
1993
+ return /* @__PURE__ */ jsxs3("li", { className: "flex min-w-0 items-center gap-2", children: [
1994
+ showSeparator ? /* @__PURE__ */ jsx3("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
1995
+ /* @__PURE__ */ jsx3(
1996
+ "button",
1997
+ {
1998
+ className: cn3(
1999
+ "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap border-0 bg-transparent text-sm font-normal transition-colors",
2000
+ active ? "font-semibold text-[var(--text-primary)]" : "text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
2001
+ ),
2002
+ type: "button",
2003
+ onClick,
2004
+ children: label
2005
+ }
2006
+ )
2007
+ ] });
2008
+ }
2009
+
2010
+ // src/ui/workspaceFileManagerAnalytics.ts
2011
+ function shouldTrackDirectoryExpanded(input) {
2012
+ return normalizeDirectoryPath(input.currentDirectoryPath) !== normalizeDirectoryPath(input.nextDirectoryPath);
2013
+ }
2014
+ function normalizeDirectoryPath(path) {
2015
+ return path.trim().replaceAll("\\", "/").replace(/\/+$/u, "");
2016
+ }
2017
+
2018
+ // src/ui/useWorkspaceFileManagerService.ts
2019
+ import { useSnapshot } from "valtio";
2020
+
2021
+ // src/services/internal/workspaceFileManagerViewModel.ts
2022
+ function resolveWorkspaceFileManagerRootViewState(input) {
2023
+ const { state } = input;
2024
+ return {
2025
+ canImportFromDrop: state.capabilities.canImportFromDrop,
2026
+ currentDirectoryPath: state.currentDirectoryPath,
2027
+ isBusy: state.busyAction !== null
2028
+ };
2029
+ }
2030
+ function resolveWorkspaceFileManagerToolbarViewState(input) {
2031
+ const { copy, state } = input;
2032
+ const currentDirectoryPath = normalizeWorkspaceFilePath(
2033
+ state.currentDirectoryPath,
2034
+ state.root
2035
+ );
2036
+ return {
2037
+ breadcrumbs: buildWorkspaceFileBreadcrumbs(
2038
+ currentDirectoryPath,
2039
+ copy.t("breadcrumbRootLabel"),
2040
+ state.root
2041
+ ),
2042
+ canGoBack: currentDirectoryPath !== normalizeWorkspaceFilePath(state.root) && state.navigationBackStack.length > 0,
2043
+ canGoForward: state.navigationForwardStack.length > 0,
2044
+ currentDirectoryPath,
2045
+ isBusy: state.busyAction !== null,
2046
+ isLoading: state.isLoading,
2047
+ isMutating: state.isMutating,
2048
+ isImporting: state.busyAction === "import",
2049
+ showImportAction: state.capabilities.canImportFromPicker
2050
+ };
2051
+ }
2052
+ function resolveWorkspaceFileManagerPanelsViewState(input) {
2053
+ const { state } = input;
2054
+ return {
2055
+ canMove: state.capabilities.canMove,
2056
+ contextMenuEntryPath: state.contextMenuEntryPath,
2057
+ entries: state.entries,
2058
+ error: state.error,
2059
+ inlineRenameEntryPath: state.inlineRenameEntryPath,
2060
+ inlineRenameValidation: state.inlineRenameValidation,
2061
+ isLoading: state.isLoading,
2062
+ isRenaming: state.busyAction === "rename",
2063
+ pendingDirectoryPath: state.pendingDirectoryPath,
2064
+ previewState: state.previewState,
2065
+ selectedEntry: findSelectedEntry(state),
2066
+ selectedPath: state.selectedPath,
2067
+ showDropOverlay: state.capabilities.canImportFromDrop && state.dragDepth > 0 && state.busyAction === null
2068
+ };
2069
+ }
2070
+ function resolveWorkspaceFileManagerDialogsViewState(input) {
2071
+ const { state } = input;
2072
+ const unsupportedDialogEntry = state.unsupportedDialog?.entryPath ? findEntry(state, state.unsupportedDialog.entryPath) : null;
2073
+ return {
2074
+ createDialog: state.createDialog,
2075
+ deleteDialogEntry: state.deleteDialog ? findEntry(state, state.deleteDialog.entryPath) : null,
2076
+ isBusy: state.busyAction !== null,
2077
+ isDeleting: state.busyAction === "delete",
2078
+ isImporting: state.busyAction === "import",
2079
+ isRenaming: state.busyAction === "rename",
2080
+ isViewing: state.busyAction === "view",
2081
+ unsupportedDialog: state.unsupportedDialog ? {
2082
+ actions: state.unsupportedDialog.actions,
2083
+ entry: unsupportedDialogEntry ?? void 0,
2084
+ kind: state.unsupportedDialog.kind,
2085
+ message: state.unsupportedDialog.message,
2086
+ title: state.unsupportedDialog.title
2087
+ } : null,
2088
+ importConflictDialog: state.importConflictDialog
2089
+ };
2090
+ }
2091
+ function resolveWorkspaceFileManagerContextMenuViewState(input) {
2092
+ const { state } = input;
2093
+ const contextMenuEntry = state.contextMenu?.entryPath ? findEntry(state, state.contextMenu.entryPath) : null;
2094
+ const isContextMenuFile = contextMenuEntry?.kind === "file";
2095
+ return {
2096
+ contextMenu: state.contextMenu ? {
2097
+ entry: contextMenuEntry,
2098
+ x: state.contextMenu.x,
2099
+ y: state.contextMenu.y
2100
+ } : null,
2101
+ currentDirectoryPath: state.currentDirectoryPath,
2102
+ isBusy: state.busyAction !== null,
2103
+ isLoading: state.isLoading,
2104
+ isMutating: state.isMutating,
2105
+ showCopyAction: state.capabilities.canCopy,
2106
+ showExportAction: state.capabilities.canExport,
2107
+ showImportAction: state.capabilities.canImportFromPicker,
2108
+ showMoveAction: state.capabilities.canMove,
2109
+ showOpenInAppBrowserAction: state.capabilities.canOpenInAppBrowser,
2110
+ showOpenInDefaultBrowserAction: state.capabilities.canOpenInDefaultBrowser,
2111
+ showOpenWithAction: state.capabilities.canOpenWith && isContextMenuFile,
2112
+ showOpenWithOtherAction: state.capabilities.canPickOtherOpenWithApplication && isContextMenuFile,
2113
+ showRevealInFolderAction: state.capabilities.canRevealInFolder,
2114
+ showRenameAction: state.capabilities.canRename
2115
+ };
2116
+ }
2117
+ function findSelectedEntry(state) {
2118
+ return state.entries.find((entry) => entry.path === state.selectedPath) ?? null;
2119
+ }
2120
+ function findEntry(state, entryPath) {
2121
+ return state.entries.find((entry) => entry.path === entryPath) ?? null;
2122
+ }
2123
+
2124
+ // src/ui/useWorkspaceFileManagerService.ts
2125
+ function useWorkspaceFileManagerRootView(session) {
2126
+ const state = useSnapshot(
2127
+ session.store
2128
+ );
2129
+ return resolveWorkspaceFileManagerRootViewState({ state });
2130
+ }
2131
+ function useWorkspaceFileManagerToolbarView(session, i18n) {
2132
+ const state = useSnapshot(
2133
+ session.store
2134
+ );
2135
+ return {
2136
+ view: resolveWorkspaceFileManagerToolbarViewState({
2137
+ copy: i18n,
2138
+ state
2139
+ })
2140
+ };
2141
+ }
2142
+ function useWorkspaceFileManagerPanelsView(session) {
2143
+ const state = useSnapshot(
2144
+ session.store
2145
+ );
2146
+ return {
2147
+ state,
2148
+ view: resolveWorkspaceFileManagerPanelsViewState({
2149
+ state
2150
+ })
2151
+ };
2152
+ }
2153
+ function useWorkspaceFileManagerDialogsView(session) {
2154
+ const state = useSnapshot(
2155
+ session.store
2156
+ );
2157
+ return {
2158
+ state,
2159
+ view: resolveWorkspaceFileManagerDialogsViewState({
2160
+ state
2161
+ })
2162
+ };
2163
+ }
2164
+ function useWorkspaceFileManagerContextMenuView(session) {
2165
+ const state = useSnapshot(
2166
+ session.store
2167
+ );
2168
+ return {
2169
+ state,
2170
+ view: resolveWorkspaceFileManagerContextMenuViewState({
2171
+ state
2172
+ })
2173
+ };
2174
+ }
2175
+
2176
+ // src/ui/WorkspaceFileManager.tsx
2177
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2178
+ function WorkspaceFileManager({
2179
+ className,
2180
+ dateLocale,
2181
+ entryDragMode,
2182
+ i18n,
2183
+ onCopyEntry,
2184
+ onCopyPath,
2185
+ onDirectoryExpanded,
2186
+ onEntryDragStart,
2187
+ openInAppBrowserIcon,
2188
+ resolveOpenWithApplicationIcon,
2189
+ hostOs = "linux",
2190
+ session,
2191
+ surface = "card"
2192
+ }) {
2193
+ const rootRef = useRef3(null);
2194
+ const rootView = useWorkspaceFileManagerRootView(session);
2195
+ const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
2196
+ useEffect3(() => {
2197
+ function handleCopyShortcut(event) {
2198
+ if (!(event.metaKey || event.ctrlKey) || event.key !== "c" || event.shiftKey) {
2199
+ return;
2200
+ }
2201
+ if (isEditableKeyboardTarget(event.target)) {
2202
+ return;
2203
+ }
2204
+ if (!rootRef.current?.contains(event.target)) {
2205
+ return;
2206
+ }
2207
+ if (!panelsState.capabilities.canCopy || panelsState.busyAction !== null || panelsState.isLoading || panelsState.isMutating) {
2208
+ return;
2209
+ }
2210
+ const entry = panelsView.selectedEntry;
2211
+ if (!entry) {
2212
+ return;
2213
+ }
2214
+ event.preventDefault();
2215
+ void (async () => {
2216
+ await session.copyToClipboard(entry);
2217
+ await onCopyEntry?.();
2218
+ })();
2219
+ }
2220
+ window.addEventListener("keydown", handleCopyShortcut);
2221
+ return () => {
2222
+ window.removeEventListener("keydown", handleCopyShortcut);
2223
+ };
2224
+ }, [onCopyEntry, panelsState, panelsView.selectedEntry, session]);
2225
+ useEffect3(() => {
2226
+ function handleRenameShortcut(event) {
2227
+ if (event.key !== "Enter" || event.metaKey || event.ctrlKey || event.altKey) {
2228
+ return;
2229
+ }
2230
+ if (isEditableKeyboardTarget(event.target)) {
2231
+ return;
2232
+ }
2233
+ if (!rootRef.current?.contains(event.target)) {
2234
+ return;
2235
+ }
2236
+ if (!panelsState.capabilities.canRename || panelsState.busyAction !== null || panelsState.isLoading || panelsState.isMutating || panelsState.inlineRenameEntryPath !== null) {
2237
+ return;
2238
+ }
2239
+ const entry = panelsView.selectedEntry;
2240
+ if (!entry) {
2241
+ return;
2242
+ }
2243
+ event.preventDefault();
2244
+ session.startInlineRename(entry);
2245
+ }
2246
+ window.addEventListener("keydown", handleRenameShortcut);
2247
+ return () => {
2248
+ window.removeEventListener("keydown", handleRenameShortcut);
2249
+ };
2250
+ }, [panelsState, panelsView.selectedEntry, session]);
2251
+ useEffect3(() => {
2252
+ function resetDropOverlay() {
2253
+ session.resetDragDepth();
2254
+ }
2255
+ function handleDocumentDragOver(event) {
2256
+ if (isPointInsideElement(rootRef.current, event.clientX, event.clientY)) {
2257
+ return;
2258
+ }
2259
+ session.resetDragDepth();
2260
+ }
2261
+ window.addEventListener("blur", resetDropOverlay);
2262
+ window.addEventListener("dragend", resetDropOverlay);
2263
+ window.addEventListener("drop", resetDropOverlay);
2264
+ document.addEventListener("dragover", handleDocumentDragOver, true);
2265
+ return () => {
2266
+ window.removeEventListener("blur", resetDropOverlay);
2267
+ window.removeEventListener("dragend", resetDropOverlay);
2268
+ window.removeEventListener("drop", resetDropOverlay);
2269
+ document.removeEventListener("dragover", handleDocumentDragOver, true);
2270
+ };
2271
+ }, [session]);
2272
+ function openContextMenu(event, entry) {
2273
+ event.preventDefault();
2274
+ event.stopPropagation();
2275
+ const rootBounds = rootRef.current?.getBoundingClientRect();
2276
+ if (!rootBounds) {
2277
+ return;
2278
+ }
2279
+ const menuWidth = 220;
2280
+ const menuHeight = 280;
2281
+ const x = clampContextMenuCoordinate(
2282
+ event.clientX - rootBounds.left,
2283
+ rootBounds.width,
2284
+ menuWidth
2285
+ );
2286
+ const y = clampContextMenuCoordinate(
2287
+ event.clientY - rootBounds.top,
2288
+ rootBounds.height,
2289
+ menuHeight
2290
+ );
2291
+ session.openContextMenu({
2292
+ entryPath: entry?.path ?? null,
2293
+ x,
2294
+ y
2295
+ });
2296
+ }
2297
+ function handleDragEnter(event) {
2298
+ if (!rootView.canImportFromDrop || rootView.isBusy || !hasFileDragPayload(event.dataTransfer)) {
2299
+ return;
2300
+ }
2301
+ event.preventDefault();
2302
+ session.incrementDragDepth();
2303
+ }
2304
+ function handleDragOver(event) {
2305
+ if (!rootView.canImportFromDrop || rootView.isBusy || !hasFileDragPayload(event.dataTransfer)) {
2306
+ return;
2307
+ }
2308
+ event.preventDefault();
2309
+ event.dataTransfer.dropEffect = "copy";
2310
+ }
2311
+ function handleDragLeave(event) {
2312
+ if (!rootView.canImportFromDrop || !hasFileDragPayload(event.dataTransfer)) {
2313
+ return;
2314
+ }
2315
+ event.preventDefault();
2316
+ const nextTarget = event.relatedTarget;
2317
+ if (nextTarget instanceof Node && rootRef.current?.contains(nextTarget)) {
2318
+ return;
2319
+ }
2320
+ session.resetDragDepth();
2321
+ }
2322
+ function handleDrop(event) {
2323
+ if (!rootView.canImportFromDrop || rootView.isBusy || !hasFileDragPayload(event.dataTransfer)) {
2324
+ return;
2325
+ }
2326
+ event.preventDefault();
2327
+ event.stopPropagation();
2328
+ session.resetDragDepth();
2329
+ void session.importDroppedFiles(
2330
+ event.dataTransfer,
2331
+ rootView.currentDirectoryPath
2332
+ );
2333
+ }
2334
+ return /* @__PURE__ */ jsxs4(
2335
+ "section",
2336
+ {
2337
+ className: cn4(
2338
+ "@container/workspace-file-manager relative flex h-full min-h-0 w-full overflow-hidden text-[14px] text-[var(--text-primary)]",
2339
+ surface === "card" ? "rounded-lg border border-[var(--border-1)] bg-[var(--background-panel)]" : "rounded-none border-0 bg-transparent",
2340
+ className
2341
+ ),
2342
+ "data-slot": "viewport-menu-boundary",
2343
+ "data-workspace-file-manager": "",
2344
+ onDragEnter: handleDragEnter,
2345
+ onDragLeave: handleDragLeave,
2346
+ onDragOver: handleDragOver,
2347
+ onDrop: handleDrop,
2348
+ ref: rootRef,
2349
+ children: [
2350
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
2351
+ /* @__PURE__ */ jsx4(
2352
+ WorkspaceFileManagerToolbarContainer,
2353
+ {
2354
+ i18n,
2355
+ onDirectoryExpanded,
2356
+ session
2357
+ }
2358
+ ),
2359
+ /* @__PURE__ */ jsx4(
2360
+ "div",
2361
+ {
2362
+ 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",
2363
+ style: {
2364
+ "--workspace-file-manager-dialog-overlay-z-index": "20"
2365
+ },
2366
+ children: /* @__PURE__ */ jsx4(
2367
+ WorkspaceFileManagerPanelsContainer,
2368
+ {
2369
+ dateLocale,
2370
+ entryDragMode,
2371
+ i18n,
2372
+ onDirectoryExpanded,
2373
+ onEntryDragStart,
2374
+ onOpenContextMenu: openContextMenu,
2375
+ session
2376
+ }
2377
+ )
2378
+ }
2379
+ )
2380
+ ] }),
2381
+ /* @__PURE__ */ jsx4(WorkspaceFileManagerDialogsContainer, { i18n, session }),
2382
+ /* @__PURE__ */ jsx4(
2383
+ WorkspaceFileManagerContextMenuContainer,
2384
+ {
2385
+ hostOs,
2386
+ i18n,
2387
+ onCopyEntry,
2388
+ onCopyPath,
2389
+ openInAppBrowserIcon,
2390
+ resolveOpenWithApplicationIcon,
2391
+ session
2392
+ }
2393
+ )
2394
+ ]
2395
+ }
2396
+ );
2397
+ }
2398
+ function WorkspaceFileManagerToolbarContainer({
2399
+ i18n,
2400
+ onDirectoryExpanded,
2401
+ session
2402
+ }) {
2403
+ const { view } = useWorkspaceFileManagerToolbarView(session, i18n);
2404
+ return /* @__PURE__ */ jsx4(
2405
+ WorkspaceFileManagerToolbar,
2406
+ {
2407
+ breadcrumbs: view.breadcrumbs,
2408
+ canGoBack: view.canGoBack,
2409
+ canGoForward: view.canGoForward,
2410
+ copy: i18n,
2411
+ currentDirectoryPath: view.currentDirectoryPath,
2412
+ isBusy: view.isBusy,
2413
+ isLoading: view.isLoading,
2414
+ isMutating: view.isMutating,
2415
+ onGoBack: () => {
2416
+ void session.goBack();
2417
+ },
2418
+ onGoForward: () => {
2419
+ void session.goForward();
2420
+ },
2421
+ onLoadDirectory: (path) => {
2422
+ if (shouldTrackDirectoryExpanded({
2423
+ currentDirectoryPath: view.currentDirectoryPath,
2424
+ nextDirectoryPath: path
2425
+ })) {
2426
+ onDirectoryExpanded?.(path);
2427
+ }
2428
+ void session.loadDirectory(path);
2429
+ },
2430
+ onRefresh: () => {
2431
+ void session.refresh();
2432
+ }
2433
+ }
2434
+ );
2435
+ }
2436
+ function WorkspaceFileManagerPanelsContainer({
2437
+ dateLocale,
2438
+ entryDragMode,
2439
+ i18n,
2440
+ onDirectoryExpanded,
2441
+ onEntryDragStart,
2442
+ onOpenContextMenu,
2443
+ session
2444
+ }) {
2445
+ const { state, view } = useWorkspaceFileManagerPanelsView(session);
2446
+ return /* @__PURE__ */ jsx4(
2447
+ WorkspaceFileManagerPanels,
2448
+ {
2449
+ canMove: view.canMove,
2450
+ contextMenuEntryPath: view.contextMenuEntryPath,
2451
+ copy: i18n,
2452
+ dateLocale,
2453
+ entryDragMode,
2454
+ inlineRenameEntryPath: view.inlineRenameEntryPath,
2455
+ inlineRenameValidation: view.inlineRenameValidation,
2456
+ isRenaming: view.isRenaming,
2457
+ pendingDirectoryPath: view.pendingDirectoryPath,
2458
+ previewState: view.previewState,
2459
+ selectedEntry: view.selectedEntry,
2460
+ selectedPath: view.selectedPath,
2461
+ showDropOverlay: view.showDropOverlay,
2462
+ state: {
2463
+ entries: state.entries,
2464
+ error: state.error,
2465
+ isLoading: state.isLoading
2466
+ },
2467
+ onBlankContextMenu: (event) => {
2468
+ onOpenContextMenu(event, null);
2469
+ },
2470
+ onCancelInlineRename: () => {
2471
+ session.cancelInlineRename();
2472
+ },
2473
+ onClearInlineRenameValidation: () => {
2474
+ session.clearInlineRenameValidation();
2475
+ },
2476
+ onConfirmInlineRename: (newName) => {
2477
+ return session.confirmInlineRename(newName);
2478
+ },
2479
+ onEntryContextMenu: onOpenContextMenu,
2480
+ onEntryDragStart,
2481
+ onMoveEntry: (entry, targetDirectoryPath) => {
2482
+ void session.moveEntry(entry, targetDirectoryPath);
2483
+ },
2484
+ onOpenEntry: (entry) => {
2485
+ if (entry.kind === "directory") {
2486
+ onDirectoryExpanded?.(entry.path);
2487
+ }
2488
+ void session.openEntry(entry);
2489
+ },
2490
+ onSelect: (path) => {
2491
+ session.select(path);
2492
+ }
2493
+ }
2494
+ );
2495
+ }
2496
+ function WorkspaceFileManagerDialogsContainer({
2497
+ i18n,
2498
+ session
2499
+ }) {
2500
+ const { state, view } = useWorkspaceFileManagerDialogsView(session);
2501
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
2502
+ /* @__PURE__ */ jsx4(
2503
+ WorkspaceFileManagerCreateDialog,
2504
+ {
2505
+ busy: view.isBusy && state.busyAction === "create",
2506
+ copy: i18n,
2507
+ dialog: view.createDialog,
2508
+ onClose: () => {
2509
+ session.closeCreateDialog();
2510
+ },
2511
+ onConfirm: () => {
2512
+ void session.confirmCreateDialog();
2513
+ },
2514
+ onNameChange: (name) => {
2515
+ session.updateCreateDialogName(name);
2516
+ }
2517
+ }
2518
+ ),
2519
+ /* @__PURE__ */ jsx4(
2520
+ WorkspaceFileManagerDeleteDialog,
2521
+ {
2522
+ busy: view.isDeleting,
2523
+ copy: i18n,
2524
+ entry: view.deleteDialogEntry,
2525
+ onClose: () => {
2526
+ session.closeDeleteDialog();
2527
+ },
2528
+ onConfirm: () => {
2529
+ void session.confirmDeleteDialog();
2530
+ }
2531
+ }
2532
+ ),
2533
+ /* @__PURE__ */ jsx4(
2534
+ WorkspaceFileManagerImportConflictDialog,
2535
+ {
2536
+ busy: view.isImporting,
2537
+ copy: i18n,
2538
+ dialog: view.importConflictDialog,
2539
+ onClose: () => {
2540
+ session.closeImportConflictDialog();
2541
+ },
2542
+ onConfirm: () => {
2543
+ void session.confirmImportConflict();
2544
+ }
2545
+ }
2546
+ ),
2547
+ /* @__PURE__ */ jsx4(
2548
+ WorkspaceFileManagerUnsupportedDialog,
2549
+ {
2550
+ copy: i18n,
2551
+ dialog: view.unsupportedDialog,
2552
+ isViewing: view.isViewing,
2553
+ onAction: (action) => {
2554
+ void session.handleActivationFallbackAction(action);
2555
+ },
2556
+ onClose: () => {
2557
+ session.closeUnsupportedDialog();
2558
+ }
2559
+ }
2560
+ )
2561
+ ] });
2562
+ }
2563
+ function WorkspaceFileManagerContextMenuContainer({
2564
+ hostOs,
2565
+ i18n,
2566
+ onCopyEntry,
2567
+ onCopyPath,
2568
+ openInAppBrowserIcon,
2569
+ resolveOpenWithApplicationIcon,
2570
+ session
2571
+ }) {
2572
+ const contextMenuRef = useRef3(null);
2573
+ const { view } = useWorkspaceFileManagerContextMenuView(session);
2574
+ const [openWithApplications, setOpenWithApplications] = useState4([]);
2575
+ const [openWithLoading, setOpenWithLoading] = useState4(false);
2576
+ useEffect3(() => {
2577
+ const entry = view.contextMenu?.entry;
2578
+ if (!entry || !view.showOpenWithAction) {
2579
+ return;
2580
+ }
2581
+ const cachedApplications = session.getCachedOpenWithApplications(entry);
2582
+ if (cachedApplications) {
2583
+ setOpenWithApplications(cachedApplications);
2584
+ setOpenWithLoading(false);
2585
+ return;
2586
+ }
2587
+ let cancelled = false;
2588
+ setOpenWithLoading(true);
2589
+ void session.listOpenWithApplications(entry).then((applications) => {
2590
+ if (cancelled) {
2591
+ return;
2592
+ }
2593
+ setOpenWithApplications(applications);
2594
+ setOpenWithLoading(false);
2595
+ }).catch(() => {
2596
+ if (cancelled) {
2597
+ return;
2598
+ }
2599
+ setOpenWithApplications([]);
2600
+ setOpenWithLoading(false);
2601
+ });
2602
+ return () => {
2603
+ cancelled = true;
2604
+ };
2605
+ }, [session, view.contextMenu?.entry?.path, view.showOpenWithAction]);
2606
+ useCloseContextMenuOnOutsideInteraction({
2607
+ contextMenuRef,
2608
+ isOpen: view.contextMenu !== null,
2609
+ session
2610
+ });
2611
+ return /* @__PURE__ */ jsx4(
2612
+ WorkspaceFileManagerContextMenu,
2613
+ {
2614
+ busy: view.isBusy || view.isLoading || view.isMutating,
2615
+ copy: i18n,
2616
+ contextMenu: view.contextMenu,
2617
+ contextMenuRef,
2618
+ showCopyAction: view.showCopyAction,
2619
+ showImportAction: view.showImportAction,
2620
+ showExportAction: view.showExportAction,
2621
+ showOpenInAppBrowserAction: view.showOpenInAppBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
2622
+ showOpenInDefaultBrowserAction: view.showOpenInDefaultBrowserAction && !!view.contextMenu?.entry && isWorkspaceFileBrowserOpenable(view.contextMenu.entry),
2623
+ showOpenWithAction: view.showOpenWithAction,
2624
+ showOpenWithOtherAction: view.showOpenWithOtherAction,
2625
+ showRevealInFolderAction: view.showRevealInFolderAction,
2626
+ showRenameAction: view.showRenameAction,
2627
+ revealInFolderLabel: resolveRevealInFolderLabel(i18n, hostOs),
2628
+ openInAppBrowserIcon,
2629
+ openWithApplications,
2630
+ openWithLoading,
2631
+ resolveOpenWithApplicationIcon,
2632
+ onClose: () => {
2633
+ session.closeContextMenu();
2634
+ },
2635
+ onCreateDirectory: () => {
2636
+ session.openCreateDirectoryDialog();
2637
+ },
2638
+ onCreateFile: () => {
2639
+ session.openCreateFileDialog();
2640
+ },
2641
+ onCopy: async () => {
2642
+ const entry = view.contextMenu?.entry;
2643
+ if (!entry) {
2644
+ return;
2645
+ }
2646
+ session.closeContextMenu();
2647
+ await session.copyToClipboard(entry);
2648
+ if (onCopyEntry) {
2649
+ await onCopyEntry();
2650
+ }
2651
+ },
2652
+ onCopyPath: async () => {
2653
+ const path = view.contextMenu?.entry?.path;
2654
+ if (!path) {
2655
+ return;
2656
+ }
2657
+ session.closeContextMenu();
2658
+ if (onCopyPath) {
2659
+ await onCopyPath(path);
2660
+ return;
2661
+ }
2662
+ await navigator.clipboard.writeText(path);
2663
+ },
2664
+ onDelete: () => {
2665
+ const entry = view.contextMenu?.entry;
2666
+ if (!entry) {
2667
+ return;
2668
+ }
2669
+ session.openDeleteDialog(entry);
2670
+ },
2671
+ onRename: () => {
2672
+ const entry = view.contextMenu?.entry;
2673
+ if (!entry) {
2674
+ return;
2675
+ }
2676
+ session.startInlineRename(entry);
2677
+ },
2678
+ onExport: async () => {
2679
+ const entry = view.contextMenu?.entry;
2680
+ if (!entry) {
2681
+ return;
2682
+ }
2683
+ await session.exportEntry(entry);
2684
+ },
2685
+ onOpen: async () => {
2686
+ const entry = view.contextMenu?.entry;
2687
+ if (!entry) {
2688
+ return;
2689
+ }
2690
+ await session.openEntry(entry);
2691
+ },
2692
+ onOpenInAppBrowser: async () => {
2693
+ const entry = view.contextMenu?.entry;
2694
+ if (!entry) {
2695
+ return;
2696
+ }
2697
+ await session.openFileInAppBrowser(entry);
2698
+ },
2699
+ onOpenInDefaultBrowser: async () => {
2700
+ const entry = view.contextMenu?.entry;
2701
+ if (!entry) {
2702
+ return;
2703
+ }
2704
+ await session.openFileInDefaultBrowser(entry);
2705
+ },
2706
+ onOpenWithApplication: async (applicationPath) => {
2707
+ const entry = view.contextMenu?.entry;
2708
+ if (!entry) {
2709
+ return;
2710
+ }
2711
+ await session.openFileWithApplication(entry, applicationPath);
2712
+ },
2713
+ onOpenWithOtherApplication: async () => {
2714
+ const entry = view.contextMenu?.entry;
2715
+ if (!entry) {
2716
+ return;
2717
+ }
2718
+ await session.openFileWithOtherApplication(entry);
2719
+ },
2720
+ onRevealInFolder: async () => {
2721
+ const entry = view.contextMenu?.entry;
2722
+ if (!entry) {
2723
+ return;
2724
+ }
2725
+ await session.revealEntry(entry);
2726
+ },
2727
+ onImport: async () => {
2728
+ session.closeContextMenu();
2729
+ await session.importFiles(
2730
+ view.contextMenu?.entry?.kind === "directory" ? view.contextMenu.entry.path : view.currentDirectoryPath
2731
+ );
2732
+ }
2733
+ }
2734
+ );
2735
+ }
2736
+ function useCloseContextMenuOnOutsideInteraction(input) {
2737
+ const { contextMenuRef, isOpen, session } = input;
2738
+ useEffect3(() => {
2739
+ if (!isOpen) {
2740
+ return;
2741
+ }
2742
+ function handlePointerDown(event) {
2743
+ const target = event.target;
2744
+ if (target instanceof Node && contextMenuRef.current?.contains(target)) {
2745
+ return;
2746
+ }
2747
+ if (target instanceof Element && target.closest("[data-workspace-file-manager-submenu]")) {
2748
+ return;
2749
+ }
2750
+ session.closeContextMenu();
2751
+ }
2752
+ function handleKeyDown(event) {
2753
+ if (event.key === "Escape") {
2754
+ session.closeContextMenu();
2755
+ }
2756
+ }
2757
+ window.addEventListener("pointerdown", handlePointerDown);
2758
+ window.addEventListener("keydown", handleKeyDown);
2759
+ return () => {
2760
+ window.removeEventListener("pointerdown", handlePointerDown);
2761
+ window.removeEventListener("keydown", handleKeyDown);
2762
+ };
2763
+ }, [contextMenuRef, isOpen, session]);
2764
+ }
2765
+ function isEditableKeyboardTarget(target) {
2766
+ if (!(target instanceof HTMLElement)) {
2767
+ return false;
2768
+ }
2769
+ if (target.isContentEditable) {
2770
+ return true;
2771
+ }
2772
+ const tagName = target.tagName;
2773
+ return tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT";
2774
+ }
2775
+ function clampContextMenuCoordinate(coordinate, boundarySize, menuSize) {
2776
+ const max = Math.max(8, boundarySize - menuSize - 8);
2777
+ return Math.min(Math.max(coordinate, 8), max);
2778
+ }
2779
+ function isPointInsideElement(element, clientX, clientY) {
2780
+ if (!element) {
2781
+ return false;
2782
+ }
2783
+ const bounds = element.getBoundingClientRect();
2784
+ return clientX >= bounds.left && clientX <= bounds.right && clientY >= bounds.top && clientY <= bounds.bottom;
2785
+ }
2786
+ export {
2787
+ WorkspaceFileManager,
2788
+ classifyWorkspaceFilePreviewKind,
2789
+ createWorkspaceFileManagerI18nRuntime,
2790
+ createWorkspaceFileManagerService,
2791
+ decodeWorkspaceTextFile,
2792
+ isWorkspaceTextFileTooLarge,
2793
+ looksLikeBinaryText,
2794
+ resolveWorkspaceFileActivationTarget,
2795
+ resolveWorkspaceFileExtension,
2796
+ resolveWorkspaceImageMimeType,
2797
+ workspaceFileManagerI18nNamespace,
2798
+ workspaceFileManagerI18nResources,
2799
+ workspaceFilePreviewMaxBytes,
2800
+ workspaceFileTextMaxBytes
2801
+ };
2802
+ //# sourceMappingURL=index.js.map