@tutti-os/workspace-file-manager 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,17 +16,18 @@ import {
16
16
  resolveWorkspaceFileVisualKind,
17
17
  resolveWorkspaceImageMimeType,
18
18
  shouldResolveWorkspaceFileEntryIcon,
19
+ shouldUseWorkspaceFileArchiveIcon,
19
20
  shouldUseWorkspaceFileExtensionDocumentIcon,
20
21
  splitWorkspaceFileName,
21
22
  workspaceFilePreviewMaxBytes,
22
23
  workspaceFileTextMaxBytes
23
- } from "./chunk-DXLDUMVG.js";
24
+ } from "./chunk-G2T6CZ2T.js";
24
25
  import {
25
26
  createWorkspaceFileManagerI18nRuntime,
26
27
  resolveRevealInFolderLabel,
27
28
  workspaceFileManagerI18nNamespace,
28
29
  workspaceFileManagerI18nResources
29
- } from "./chunk-XOFPTWRV.js";
30
+ } from "./chunk-MDBT2GS5.js";
30
31
 
31
32
  // src/ui/WorkspaceFileManager.tsx
32
33
  import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5, useState as useState7 } from "react";
@@ -900,8 +901,6 @@ function importSummaryReasonCopyKey(reason) {
900
901
  import {
901
902
  FileCodeIcon as FileCodeIcon2,
902
903
  FileTextIcon as FileTextIcon2,
903
- FolderFilledIcon as FolderFilledIcon2,
904
- ImageFileIcon as ImageFileIcon2,
905
904
  LoadingIcon as LoadingIcon2,
906
905
  ScrollArea,
907
906
  VideoFileIcon as VideoFileIcon2,
@@ -924,20 +923,31 @@ import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
924
923
  import {
925
924
  FileCodeIcon,
926
925
  FileTextIcon,
927
- FolderFilledIcon,
928
- ImageFileIcon,
929
926
  LoadingIcon,
930
927
  VideoFileIcon,
931
928
  cn as cn2
932
929
  } from "@tutti-os/ui-system";
933
930
  import { useEffect as useEffect2, useRef as useRef2 } from "react";
934
931
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
932
+ var workspaceArchiveFallbackIconUrl = new URL(
933
+ "../assets/workspace-archive-fallback.png",
934
+ import.meta.url
935
+ ).toString();
936
+ var workspaceFolderFallbackIconUrl = new URL(
937
+ "../assets/workspace-folder-fallback.png",
938
+ import.meta.url
939
+ ).toString();
940
+ var workspaceImageFallbackIconUrl = new URL(
941
+ "../assets/workspace-image-fallback.png",
942
+ import.meta.url
943
+ ).toString();
935
944
  function WorkspaceFileEntryIcon({
936
945
  entry,
937
946
  frameClassName,
938
947
  iconClassName = "size-4",
939
948
  iconUrlByCacheKey,
940
949
  isEnteringDirectory = false,
950
+ loadingIconClassName,
941
951
  onViewportLeave,
942
952
  onViewportEnter
943
953
  }) {
@@ -981,9 +991,10 @@ function WorkspaceFileEntryIcon({
981
991
  const viewportRoot = resolveWorkspaceFileEntryIconViewport(element);
982
992
  const observer = new IntersectionObserver(
983
993
  (records) => {
984
- if (records.some(
994
+ const intersecting = records.some(
985
995
  (record) => record.isIntersecting && record.intersectionRatio > 0
986
- )) {
996
+ );
997
+ if (intersecting) {
987
998
  reportViewportEnter();
988
999
  return;
989
1000
  }
@@ -992,7 +1003,11 @@ function WorkspaceFileEntryIcon({
992
1003
  { root: viewportRoot, rootMargin: "0px", threshold: 0 }
993
1004
  );
994
1005
  observer.observe(element);
995
- if (isWorkspaceFileEntryIconVisible(element, viewportRoot)) {
1006
+ const initiallyVisible = isWorkspaceFileEntryIconVisible(
1007
+ element,
1008
+ viewportRoot
1009
+ );
1010
+ if (initiallyVisible) {
996
1011
  reportViewportEnter();
997
1012
  } else {
998
1013
  reportViewportLeave();
@@ -1011,12 +1026,22 @@ function WorkspaceFileEntryIcon({
1011
1026
  frameClassName,
1012
1027
  isEnteringDirectory ? "text-[var(--text-tertiary)]" : entryIconColorClassName(visualKind, isAppBundle)
1013
1028
  ),
1014
- children: isEnteringDirectory ? /* @__PURE__ */ jsx2(LoadingIcon, { className: iconClassName + " animate-spin" }) : iconUrl ? /* @__PURE__ */ jsx2(
1029
+ children: isEnteringDirectory ? /* @__PURE__ */ jsx2(
1030
+ LoadingIcon,
1031
+ {
1032
+ className: cn2(loadingIconClassName ?? iconClassName, "animate-spin")
1033
+ }
1034
+ ) : iconUrl ? /* @__PURE__ */ jsx2(
1015
1035
  "img",
1016
1036
  {
1017
1037
  alt: "",
1018
- className: cn2(iconClassName, "rounded-[4px] object-contain"),
1038
+ className: cn2(
1039
+ iconClassName,
1040
+ visualKind === "image" ? "rounded-[6px] border border-[var(--border-1)] bg-[var(--transparency-block)] object-contain" : "rounded-[4px] object-contain"
1041
+ ),
1042
+ decoding: "async",
1019
1043
  draggable: false,
1044
+ loading: "lazy",
1020
1045
  src: iconUrl
1021
1046
  }
1022
1047
  ) : /* @__PURE__ */ jsx2(
@@ -1048,44 +1073,102 @@ function DefaultEntryIcon({
1048
1073
  iconClassName,
1049
1074
  visualKind
1050
1075
  }) {
1076
+ const vectorIconClassName = vectorFallbackIconClassName(iconClassName);
1051
1077
  if (isWorkspaceApplicationBundle(entry)) {
1052
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1078
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1079
+ }
1080
+ if (shouldUseWorkspaceFileArchiveIcon(entry)) {
1081
+ return /* @__PURE__ */ jsx2(WorkspaceArchiveFallbackIcon, { className: iconClassName });
1053
1082
  }
1054
1083
  if (shouldUseWorkspaceFileExtensionDocumentIcon(entry)) {
1055
- return /* @__PURE__ */ jsx2(ExtensionDocumentIcon, { entry, iconClassName });
1084
+ return /* @__PURE__ */ jsx2(
1085
+ ExtensionDocumentIcon,
1086
+ {
1087
+ entry,
1088
+ iconClassName: vectorIconClassName
1089
+ }
1090
+ );
1056
1091
  }
1057
1092
  switch (visualKind) {
1058
1093
  case "directory":
1059
- return /* @__PURE__ */ jsx2(FolderFilledIcon, { className: iconClassName });
1094
+ return /* @__PURE__ */ jsx2(WorkspaceFolderFallbackIcon, { className: iconClassName });
1060
1095
  case "image":
1061
- return /* @__PURE__ */ jsx2(ImageFileIcon, { className: iconClassName });
1096
+ return /* @__PURE__ */ jsx2(WorkspaceImageFallbackIcon, { className: iconClassName });
1062
1097
  case "video":
1063
- return /* @__PURE__ */ jsx2(VideoFileIcon, { className: iconClassName });
1098
+ return /* @__PURE__ */ jsx2(VideoFileIcon, { className: vectorIconClassName });
1064
1099
  case "markdown":
1065
1100
  case "document":
1066
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1101
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1067
1102
  case "code":
1068
- return /* @__PURE__ */ jsx2(FileCodeIcon, { className: iconClassName });
1103
+ return /* @__PURE__ */ jsx2(FileCodeIcon, { className: vectorIconClassName });
1069
1104
  case "binary":
1070
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1105
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1071
1106
  default:
1072
- return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1107
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: vectorIconClassName });
1073
1108
  }
1074
1109
  }
1110
+ function vectorFallbackIconClassName(iconClassName) {
1111
+ return iconClassName.includes("size-[84px]") ? "size-[64px]" : iconClassName;
1112
+ }
1113
+ function WorkspaceFolderFallbackIcon({
1114
+ className
1115
+ }) {
1116
+ return /* @__PURE__ */ jsx2(
1117
+ "img",
1118
+ {
1119
+ alt: "",
1120
+ "aria-hidden": "true",
1121
+ className: cn2("object-contain", className),
1122
+ decoding: "async",
1123
+ draggable: false,
1124
+ src: workspaceFolderFallbackIconUrl
1125
+ }
1126
+ );
1127
+ }
1128
+ function WorkspaceArchiveFallbackIcon({
1129
+ className
1130
+ }) {
1131
+ return /* @__PURE__ */ jsx2(
1132
+ "img",
1133
+ {
1134
+ alt: "",
1135
+ "aria-hidden": "true",
1136
+ className: cn2("object-contain", className),
1137
+ decoding: "async",
1138
+ draggable: false,
1139
+ src: workspaceArchiveFallbackIconUrl
1140
+ }
1141
+ );
1142
+ }
1143
+ function WorkspaceImageFallbackIcon({
1144
+ className
1145
+ }) {
1146
+ return /* @__PURE__ */ jsx2(
1147
+ "img",
1148
+ {
1149
+ alt: "",
1150
+ "aria-hidden": "true",
1151
+ className: cn2("object-contain", className),
1152
+ decoding: "async",
1153
+ draggable: false,
1154
+ src: workspaceImageFallbackIconUrl
1155
+ }
1156
+ );
1157
+ }
1075
1158
  function ExtensionDocumentIcon({
1076
1159
  entry,
1077
1160
  iconClassName
1078
1161
  }) {
1079
1162
  const extension = resolveWorkspaceFileExtension(entry.name).slice(0, 5).toUpperCase();
1080
- const showExtension = extension.length > 0 && iconClassName.includes("52px");
1163
+ const showExtension = extension.length > 0 && !iconClassName.includes("size-4");
1081
1164
  return /* @__PURE__ */ jsxs2(
1082
1165
  "span",
1083
1166
  {
1084
1167
  "aria-hidden": "true",
1085
1168
  className: cn2("relative inline-block overflow-visible", iconClassName),
1086
1169
  children: [
1087
- /* @__PURE__ */ jsx2("span", { className: "absolute inset-[5%] rounded-[6px] border border-black/10 bg-linear-to-br from-white via-[#f8f8f8] to-[#ececec] shadow-[0_8px_16px_rgba(0,0,0,0.18),inset_0_1px_0_rgba(255,255,255,0.85)]" }),
1088
- /* @__PURE__ */ jsx2("span", { className: "absolute top-[5%] right-[5%] h-[28%] w-[28%] overflow-hidden rounded-tr-[6px]", children: /* @__PURE__ */ jsx2("span", { className: "absolute top-0 right-0 h-full w-full origin-top-right -skew-x-3 rounded-bl-[4px] border-b border-l border-black/10 bg-linear-to-br from-white to-[#d9d9d9] shadow-[-2px_3px_5px_rgba(0,0,0,0.18)]" }) }),
1170
+ /* @__PURE__ */ jsx2("span", { className: "absolute inset-[5%] rounded-[6px] border border-black/10 bg-linear-to-br from-white via-[#f8f8f8] to-[#ececec]" }),
1171
+ /* @__PURE__ */ jsx2("span", { className: "absolute top-[5%] right-[5%] h-[28%] w-[28%] overflow-hidden rounded-tr-[6px]", children: /* @__PURE__ */ jsx2("span", { className: "absolute top-0 right-0 h-full w-full origin-top-right -skew-x-3 rounded-bl-[4px] border-b border-l border-black/10 bg-linear-to-br from-white to-[#d9d9d9]" }) }),
1089
1172
  showExtension ? /* @__PURE__ */ jsx2("span", { className: "absolute right-[12%] bottom-[14%] left-[12%] truncate text-center text-[10px] leading-none font-semibold tracking-wide text-[#7a7a7a]", children: extension }) : null
1090
1173
  ]
1091
1174
  }
@@ -1099,19 +1182,19 @@ function entryIconColorClassName(visualKind, isAppBundle) {
1099
1182
  }
1100
1183
 
1101
1184
  // src/ui/workspaceFileManagerIconGridLayout.ts
1102
- var workspaceFileManagerIconGridIconSizePx = 52;
1103
- var workspaceFileManagerIconGridTileMinWidthPx = 108;
1104
- var workspaceFileManagerIconGridTileMaxWidthPx = 120;
1185
+ var workspaceFileManagerIconGridIconSizePx = 84;
1186
+ var workspaceFileManagerIconGridTileMinWidthPx = 136;
1187
+ var workspaceFileManagerIconGridTileMaxWidthPx = 148;
1105
1188
  var workspaceFileManagerIconGridLayout = {
1106
1189
  iconSizePx: workspaceFileManagerIconGridIconSizePx,
1107
1190
  tileMaxWidthPx: workspaceFileManagerIconGridTileMaxWidthPx,
1108
1191
  tileMinWidthPx: workspaceFileManagerIconGridTileMinWidthPx
1109
1192
  };
1110
1193
  function workspaceFileManagerIconGridIconClassName() {
1111
- return "size-[52px]";
1194
+ return "size-[84px]";
1112
1195
  }
1113
1196
  function workspaceFileManagerIconGridFrameClassName() {
1114
- return "size-[60px]";
1197
+ return "size-[92px]";
1115
1198
  }
1116
1199
 
1117
1200
  // src/ui/WorkspaceFileManagerIconGrid.tsx
@@ -1214,7 +1297,7 @@ function WorkspaceFileManagerIconTile({
1214
1297
  });
1215
1298
  }, [selected]);
1216
1299
  const tileClassName = cn3(
1217
- "flex min-w-0 max-w-[120px] flex-col items-center gap-1.5 rounded-md border border-transparent px-2 py-2 text-center transition-colors",
1300
+ "flex min-w-0 max-w-[148px] flex-col items-center gap-1.5 rounded-md border border-transparent px-2 py-2 text-center transition-colors",
1218
1301
  isInlineRenaming ? "cursor-default" : "cursor-pointer hover:bg-transparency-block",
1219
1302
  moveDragActive && "cursor-grabbing",
1220
1303
  selected || contextMenuActive || isInlineRenaming ? "border-[var(--border-focus)] bg-transparency-block text-[var(--text-primary)]" : "text-[var(--text-secondary)]",
@@ -1229,6 +1312,7 @@ function WorkspaceFileManagerIconTile({
1229
1312
  iconClassName: workspaceFileManagerIconGridIconClassName(),
1230
1313
  iconUrlByCacheKey,
1231
1314
  isEnteringDirectory,
1315
+ loadingIconClassName: "size-7",
1232
1316
  onViewportLeave: onEntryIconViewportLeave,
1233
1317
  onViewportEnter: onEntryIconViewportEnter
1234
1318
  }
@@ -1325,7 +1409,7 @@ function IconTileRenameInput({
1325
1409
  "aria-invalid": inlineRenameValidation !== null,
1326
1410
  "aria-label": copy.t("renameLabel"),
1327
1411
  className: cn3(
1328
- "w-full min-w-0 rounded-md border border-transparent bg-[var(--transparency-block)] px-1 py-0.5 text-center text-xs text-[var(--text-primary)] outline-none",
1412
+ "w-full min-w-0 rounded-[4px] border border-transparent bg-[var(--transparency-block)] px-1 py-0.5 text-center text-xs text-[var(--text-primary)] outline-none",
1329
1413
  inlineRenameValidation !== null && "border-[var(--state-danger)]"
1330
1414
  ),
1331
1415
  defaultValue: entry.name,
@@ -1365,8 +1449,7 @@ var workspaceFileManagerArrangeModes = /* @__PURE__ */ new Set([
1365
1449
  "dateAdded",
1366
1450
  "modified",
1367
1451
  "created",
1368
- "size",
1369
- "tags"
1452
+ "size"
1370
1453
  ]);
1371
1454
  function readWorkspaceFileManagerArrangeMode() {
1372
1455
  if (typeof window === "undefined") {
@@ -1414,8 +1497,6 @@ function sortWorkspaceFileEntriesForArrangeMode(entries, arrangeMode) {
1414
1497
  return compareDateDescending(left, right, "created") || compareEntryName(left, right);
1415
1498
  case "size":
1416
1499
  return compareSizeDescending(left, right) || compareEntryName(left, right);
1417
- case "tags":
1418
- return compareDirectoryFirst(left, right) || compareEntryName(left, right);
1419
1500
  }
1420
1501
  });
1421
1502
  }
@@ -2198,8 +2279,8 @@ function MoveDragPreview({
2198
2279
  WorkspaceFileEntryIcon,
2199
2280
  {
2200
2281
  entry: preview.entry,
2201
- frameClassName: "size-[18px]",
2202
- iconClassName: "size-4",
2282
+ frameClassName: "size-7",
2283
+ iconClassName: "size-6",
2203
2284
  iconUrlByCacheKey,
2204
2285
  onViewportLeave: onEntryIconViewportLeave,
2205
2286
  onViewportEnter: onEntryIconViewportEnter
@@ -2299,8 +2380,8 @@ function EntryNameCell({
2299
2380
  WorkspaceFileEntryIcon,
2300
2381
  {
2301
2382
  entry,
2302
- frameClassName: "size-[18px]",
2303
- iconClassName: "size-4",
2383
+ frameClassName: "size-7",
2384
+ iconClassName: "size-6",
2304
2385
  iconUrlByCacheKey,
2305
2386
  onViewportLeave: onEntryIconViewportLeave,
2306
2387
  onViewportEnter: onEntryIconViewportEnter
@@ -2363,8 +2444,8 @@ function EntryNameCell({
2363
2444
  WorkspaceFileEntryIcon,
2364
2445
  {
2365
2446
  entry,
2366
- frameClassName: "size-[18px]",
2367
- iconClassName: "size-4",
2447
+ frameClassName: "size-7",
2448
+ iconClassName: "size-6",
2368
2449
  iconUrlByCacheKey,
2369
2450
  isEnteringDirectory,
2370
2451
  onViewportLeave: onEntryIconViewportLeave,
@@ -2383,9 +2464,9 @@ function EntryIcon({
2383
2464
  }) {
2384
2465
  switch (visualKind) {
2385
2466
  case "directory":
2386
- return /* @__PURE__ */ jsx4(FolderFilledIcon2, { className });
2467
+ return /* @__PURE__ */ jsx4(WorkspaceFolderFallbackIcon, { className });
2387
2468
  case "image":
2388
- return /* @__PURE__ */ jsx4(ImageFileIcon2, { className });
2469
+ return /* @__PURE__ */ jsx4(WorkspaceImageFallbackIcon, { className });
2389
2470
  case "video":
2390
2471
  return /* @__PURE__ */ jsx4(VideoFileIcon2, { className });
2391
2472
  case "markdown":
@@ -2504,6 +2585,10 @@ import {
2504
2585
  DropdownMenuTrigger,
2505
2586
  LoadingIcon as LoadingIcon3,
2506
2587
  RefreshIcon,
2588
+ Tooltip,
2589
+ TooltipContent,
2590
+ TooltipProvider,
2591
+ TooltipTrigger,
2507
2592
  ViewGridLinedIcon,
2508
2593
  ViewListLinedIcon,
2509
2594
  cn as cn5
@@ -2624,10 +2709,9 @@ function LayoutModeToggle({
2624
2709
  { label: copy.t("arrangeDateAddedLabel"), mode: "dateAdded" },
2625
2710
  { label: copy.t("modifiedLabel"), mode: "modified" },
2626
2711
  { label: copy.t("arrangeCreatedLabel"), mode: "created" },
2627
- { label: copy.t("sizeLabel"), mode: "size" },
2628
- { label: copy.t("arrangeTagsLabel"), mode: "tags" }
2712
+ { label: copy.t("sizeLabel"), mode: "size" }
2629
2713
  ];
2630
- return /* @__PURE__ */ jsxs5(
2714
+ return /* @__PURE__ */ jsx5(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs5(
2631
2715
  "div",
2632
2716
  {
2633
2717
  className: "flex items-center rounded-md border border-[var(--border-1)] bg-[var(--transparency-block)] p-0.5",
@@ -2638,6 +2722,7 @@ function LayoutModeToggle({
2638
2722
  {
2639
2723
  active: layoutMode === "icon",
2640
2724
  ariaLabel: copy.t("layoutIconViewLabel"),
2725
+ tooltipLabel: copy.t("layoutIconViewTooltipLabel"),
2641
2726
  onClick: () => {
2642
2727
  onLayoutModeChange("icon");
2643
2728
  },
@@ -2649,6 +2734,7 @@ function LayoutModeToggle({
2649
2734
  {
2650
2735
  active: layoutMode === "list",
2651
2736
  ariaLabel: copy.t("layoutListViewLabel"),
2737
+ tooltipLabel: copy.t("layoutListViewTooltipLabel"),
2652
2738
  onClick: () => {
2653
2739
  onLayoutModeChange("list");
2654
2740
  },
@@ -2663,7 +2749,6 @@ function LayoutModeToggle({
2663
2749
  "aria-label": copy.t("arrangeMenuLabel"),
2664
2750
  className: "size-6 min-w-6 rounded-[4px] p-0 text-[var(--text-secondary)] data-[state=open]:bg-[var(--background-fronted)] data-[state=open]:text-[var(--text-primary)] data-[state=open]:shadow-sm",
2665
2751
  size: "icon-sm",
2666
- title: copy.t("arrangeMenuLabel"),
2667
2752
  type: "button",
2668
2753
  variant: "ghost",
2669
2754
  children: /* @__PURE__ */ jsx5(ChevronDownIcon, { className: "size-3.5" })
@@ -2680,7 +2765,9 @@ function LayoutModeToggle({
2680
2765
  {
2681
2766
  value: arrangeMode,
2682
2767
  onValueChange: (nextMode) => {
2683
- onArrangeModeChange(nextMode);
2768
+ onArrangeModeChange(
2769
+ nextMode
2770
+ );
2684
2771
  },
2685
2772
  children: arrangeOptions.map((option, index) => /* @__PURE__ */ jsxs5("div", { children: [
2686
2773
  index === 1 ? /* @__PURE__ */ jsx5(DropdownMenuSeparator, {}) : null,
@@ -2700,31 +2787,34 @@ function LayoutModeToggle({
2700
2787
  ] })
2701
2788
  ]
2702
2789
  }
2703
- );
2790
+ ) });
2704
2791
  }
2705
2792
  function LayoutModeButton({
2706
2793
  active,
2707
2794
  ariaLabel,
2708
2795
  children,
2709
- onClick
2796
+ onClick,
2797
+ tooltipLabel
2710
2798
  }) {
2711
- return /* @__PURE__ */ jsx5(
2712
- Button2,
2713
- {
2714
- "aria-label": ariaLabel,
2715
- "aria-pressed": active,
2716
- className: cn5(
2717
- "size-6 min-w-6 rounded-[4px] p-0 text-text-secondary",
2718
- active && "!bg-background-fronted text-foreground hover:!bg-background-fronted"
2719
- ),
2720
- size: "icon-sm",
2721
- title: ariaLabel,
2722
- type: "button",
2723
- variant: "ghost",
2724
- onClick,
2725
- children
2726
- }
2727
- );
2799
+ return /* @__PURE__ */ jsxs5(Tooltip, { children: [
2800
+ /* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(
2801
+ Button2,
2802
+ {
2803
+ "aria-label": ariaLabel,
2804
+ "aria-pressed": active,
2805
+ className: cn5(
2806
+ "size-6 min-w-6 rounded-[4px] p-0 text-text-secondary",
2807
+ active && "!bg-background-fronted text-foreground hover:!bg-background-fronted"
2808
+ ),
2809
+ size: "icon-sm",
2810
+ type: "button",
2811
+ variant: "ghost",
2812
+ onClick,
2813
+ children
2814
+ }
2815
+ ) }),
2816
+ /* @__PURE__ */ jsx5(TooltipContent, { side: "bottom", children: tooltipLabel })
2817
+ ] });
2728
2818
  }
2729
2819
  function ToolbarIconButton({
2730
2820
  ariaLabel,
@@ -2849,6 +2939,10 @@ import { useCallback as useCallback5, useEffect as useEffect4, useMemo, useState
2849
2939
 
2850
2940
  // src/ui/workspaceFileEntryIconUrlQueue.ts
2851
2941
  var defaultMaxConcurrentIconRequests = 3;
2942
+ var imageThumbnailCacheKeyPrefix = "image-thumbnail:";
2943
+ function shouldKeepResolvedIconAfterViewportLeave(cacheKey) {
2944
+ return cacheKey.startsWith(imageThumbnailCacheKeyPrefix);
2945
+ }
2852
2946
  function createWorkspaceFileEntryIconUrlQueue(options) {
2853
2947
  const maxConcurrent = Math.max(
2854
2948
  1,
@@ -2862,6 +2956,9 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2862
2956
  let activeCount = 0;
2863
2957
  let disposed = false;
2864
2958
  let retainedCacheKeys = null;
2959
+ const policyOptions = {
2960
+ includeImageThumbnails: options.includeImageThumbnails
2961
+ };
2865
2962
  function publish() {
2866
2963
  for (const listener of listeners) {
2867
2964
  listener();
@@ -2873,6 +2970,9 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2873
2970
  function isVisible(cacheKey) {
2874
2971
  return (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) > 0;
2875
2972
  }
2973
+ function shouldResolveCacheKey(cacheKey) {
2974
+ return isVisible(cacheKey) || shouldKeepResolvedIconAfterViewportLeave(cacheKey);
2975
+ }
2876
2976
  function retainVisibleReference(cacheKey) {
2877
2977
  visibleReferenceCountByCacheKey.set(
2878
2978
  cacheKey,
@@ -2883,15 +2983,17 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2883
2983
  const nextCount = (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) - 1;
2884
2984
  if (nextCount > 0) {
2885
2985
  visibleReferenceCountByCacheKey.set(cacheKey, nextCount);
2886
- return false;
2986
+ return;
2887
2987
  }
2888
2988
  visibleReferenceCountByCacheKey.delete(cacheKey);
2989
+ if (shouldKeepResolvedIconAfterViewportLeave(cacheKey)) {
2990
+ return;
2991
+ }
2889
2992
  queuedEntries.delete(cacheKey);
2890
2993
  const hadCachedIcon = iconUrlByCacheKey.delete(cacheKey);
2891
2994
  if (hadCachedIcon) {
2892
2995
  publish();
2893
2996
  }
2894
- return true;
2895
2997
  }
2896
2998
  function drain() {
2897
2999
  if (disposed || !options.resolveEntryIconUrl) {
@@ -2904,18 +3006,20 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2904
3006
  }
2905
3007
  const [cacheKey, entry] = next;
2906
3008
  queuedEntries.delete(cacheKey);
2907
- if (!isVisible(cacheKey) || iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey)) {
3009
+ if (!shouldResolveCacheKey(cacheKey) || iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey)) {
2908
3010
  continue;
2909
3011
  }
2910
3012
  activeCount += 1;
2911
3013
  inFlightCacheKeys.add(cacheKey);
2912
3014
  void options.resolveEntryIconUrl(entry).then((iconUrl) => {
2913
- if (!disposed && isRetained(cacheKey) && isVisible(cacheKey)) {
3015
+ const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
3016
+ if (shouldStore) {
2914
3017
  iconUrlByCacheKey.set(cacheKey, iconUrl?.trim() || null);
2915
3018
  publish();
2916
3019
  }
2917
3020
  }).catch(() => {
2918
- if (!disposed && isRetained(cacheKey) && isVisible(cacheKey)) {
3021
+ const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
3022
+ if (shouldStore) {
2919
3023
  iconUrlByCacheKey.set(cacheKey, null);
2920
3024
  publish();
2921
3025
  }
@@ -2927,6 +3031,9 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2927
3031
  }
2928
3032
  }
2929
3033
  return {
3034
+ activate() {
3035
+ disposed = false;
3036
+ },
2930
3037
  dispose() {
2931
3038
  disposed = true;
2932
3039
  queuedEntries.clear();
@@ -2934,13 +3041,14 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2934
3041
  listeners.clear();
2935
3042
  },
2936
3043
  leaveViewport(entry) {
2937
- if (disposed || !shouldResolveWorkspaceFileEntryIcon(entry)) {
3044
+ if (disposed || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
2938
3045
  return;
2939
3046
  }
2940
- releaseVisibleReference(resolveWorkspaceFileEntryIconCacheKey(entry));
3047
+ const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
3048
+ releaseVisibleReference(cacheKey);
2941
3049
  },
2942
3050
  enterViewport(entry) {
2943
- if (disposed || !options.resolveEntryIconUrl || !shouldResolveWorkspaceFileEntryIcon(entry)) {
3051
+ if (disposed || !options.resolveEntryIconUrl || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
2944
3052
  return;
2945
3053
  }
2946
3054
  const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
@@ -2953,7 +3061,9 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2953
3061
  },
2954
3062
  retainEntries(entries) {
2955
3063
  retainedCacheKeys = new Set(
2956
- entries.filter(shouldResolveWorkspaceFileEntryIcon).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry))
3064
+ entries.filter(
3065
+ (entry) => shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)
3066
+ ).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry))
2957
3067
  );
2958
3068
  let changed = false;
2959
3069
  for (const cacheKey of iconUrlByCacheKey.keys()) {
@@ -2998,22 +3108,29 @@ function createWorkspaceFileEntryIconUrlQueue(options) {
2998
3108
  }
2999
3109
 
3000
3110
  // src/ui/useWorkspaceFileEntryIconUrls.ts
3001
- function buildIconTargetSignature(entries) {
3002
- return entries.filter(shouldResolveWorkspaceFileEntryIcon).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
3111
+ function buildIconTargetSignature(entries, options) {
3112
+ return entries.filter((entry) => shouldResolveWorkspaceFileEntryIcon(entry, options)).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
3003
3113
  }
3004
3114
  function useWorkspaceFileEntryIconUrls(input) {
3005
- const { entries, resolveEntryIconUrl } = input;
3115
+ const { entries, includeImageThumbnails, resolveEntryIconUrl } = input;
3006
3116
  const queue = useMemo(
3007
3117
  () => createWorkspaceFileEntryIconUrlQueue({
3118
+ includeImageThumbnails,
3008
3119
  resolveEntryIconUrl
3009
3120
  }),
3010
- [resolveEntryIconUrl]
3121
+ [includeImageThumbnails, resolveEntryIconUrl]
3011
3122
  );
3012
3123
  const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState6(() => queue.snapshot());
3013
3124
  const iconTargetSignature = useMemo(
3014
- () => buildIconTargetSignature(entries),
3015
- [entries]
3125
+ () => buildIconTargetSignature(entries, { includeImageThumbnails }),
3126
+ [entries, includeImageThumbnails]
3016
3127
  );
3128
+ useEffect4(() => {
3129
+ queue.activate();
3130
+ return () => {
3131
+ queue.dispose();
3132
+ };
3133
+ }, [queue]);
3017
3134
  useEffect4(() => {
3018
3135
  queue.retainEntries(entries);
3019
3136
  }, [entries, iconTargetSignature, queue]);
@@ -3023,12 +3140,6 @@ function useWorkspaceFileEntryIconUrls(input) {
3023
3140
  setIconUrlByCacheKey(queue.snapshot());
3024
3141
  });
3025
3142
  }, [queue]);
3026
- useEffect4(
3027
- () => () => {
3028
- queue.dispose();
3029
- },
3030
- [queue]
3031
- );
3032
3143
  const reportEntryIconViewportEnter = useCallback5(
3033
3144
  (entry) => {
3034
3145
  queue.enterViewport(entry);
@@ -3515,6 +3626,7 @@ function WorkspaceFileManagerPanelsContainer({
3515
3626
  reportEntryIconViewportLeave
3516
3627
  } = useWorkspaceFileEntryIconUrls({
3517
3628
  entries: arrangedEntries,
3629
+ includeImageThumbnails: true,
3518
3630
  resolveEntryIconUrl
3519
3631
  });
3520
3632
  return /* @__PURE__ */ jsx6(