@tutti-os/workspace-file-manager 0.0.10 → 0.0.12
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/{chunk-H3SDVOT7.js → chunk-CSFEGRX7.js} +77 -2
- package/dist/chunk-CSFEGRX7.js.map +1 -0
- package/dist/{chunk-XOFPTWRV.js → chunk-L6SAJEFE.js} +7 -3
- package/dist/chunk-L6SAJEFE.js.map +1 -0
- package/dist/{fileKinds-Djl-phx5.d.ts → fileKinds-CMKjSrBx.d.ts} +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/{index-6Z1LmfOL.d.ts → index-CppyxTLd.d.ts} +2 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +420 -156
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +19 -3
- package/dist/services/index.js +12 -2
- package/package.json +4 -4
- package/dist/chunk-H3SDVOT7.js.map +0 -1
- package/dist/chunk-XOFPTWRV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,27 +5,31 @@ import {
|
|
|
5
5
|
decodeWorkspaceTextFile,
|
|
6
6
|
formatWorkspaceFileBytes,
|
|
7
7
|
formatWorkspaceFileModifiedTime,
|
|
8
|
+
isWorkspaceApplicationBundle,
|
|
8
9
|
isWorkspaceFileBrowserOpenable,
|
|
9
10
|
isWorkspaceTextFileTooLarge,
|
|
10
11
|
looksLikeBinaryText,
|
|
11
12
|
normalizeWorkspaceFilePath,
|
|
12
13
|
resolveWorkspaceFileActivationTarget,
|
|
14
|
+
resolveWorkspaceFileEntryIconCacheKey,
|
|
13
15
|
resolveWorkspaceFileExtension,
|
|
14
16
|
resolveWorkspaceFileVisualKind,
|
|
15
17
|
resolveWorkspaceImageMimeType,
|
|
18
|
+
shouldResolveWorkspaceFileEntryIcon,
|
|
19
|
+
shouldUseWorkspaceFileExtensionDocumentIcon,
|
|
16
20
|
splitWorkspaceFileName,
|
|
17
21
|
workspaceFilePreviewMaxBytes,
|
|
18
22
|
workspaceFileTextMaxBytes
|
|
19
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-CSFEGRX7.js";
|
|
20
24
|
import {
|
|
21
25
|
createWorkspaceFileManagerI18nRuntime,
|
|
22
26
|
resolveRevealInFolderLabel,
|
|
23
27
|
workspaceFileManagerI18nNamespace,
|
|
24
28
|
workspaceFileManagerI18nResources
|
|
25
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-L6SAJEFE.js";
|
|
26
30
|
|
|
27
31
|
// src/ui/WorkspaceFileManager.tsx
|
|
28
|
-
import { useEffect as
|
|
32
|
+
import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5, useState as useState7 } from "react";
|
|
29
33
|
import { cn as cn6 } from "@tutti-os/ui-system";
|
|
30
34
|
|
|
31
35
|
// src/ui/WorkspaceFileManagerMenus.tsx
|
|
@@ -906,15 +910,15 @@ import {
|
|
|
906
910
|
import { WorkspaceFilePreviewSurface as SharedWorkspaceFilePreviewSurface } from "@tutti-os/workspace-file-preview/react";
|
|
907
911
|
import {
|
|
908
912
|
useCallback as useCallback2,
|
|
909
|
-
useEffect as
|
|
913
|
+
useEffect as useEffect3,
|
|
910
914
|
useLayoutEffect as useLayoutEffect3,
|
|
911
|
-
useRef as
|
|
915
|
+
useRef as useRef4,
|
|
912
916
|
useState as useState2
|
|
913
917
|
} from "react";
|
|
914
918
|
|
|
915
919
|
// src/ui/WorkspaceFileManagerIconGrid.tsx
|
|
916
920
|
import { cn as cn3 } from "@tutti-os/ui-system";
|
|
917
|
-
import { useLayoutEffect as useLayoutEffect2, useRef as
|
|
921
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
|
|
918
922
|
|
|
919
923
|
// src/ui/WorkspaceFileEntryIcon.tsx
|
|
920
924
|
import {
|
|
@@ -926,46 +930,87 @@ import {
|
|
|
926
930
|
VideoFileIcon,
|
|
927
931
|
cn as cn2
|
|
928
932
|
} from "@tutti-os/ui-system";
|
|
929
|
-
|
|
930
|
-
// src/ui/workspaceFileEntryIconPolicy.ts
|
|
931
|
-
function shouldResolveWorkspaceFileEntryIcon(entry) {
|
|
932
|
-
if (isWorkspaceApplicationBundle(entry)) {
|
|
933
|
-
return true;
|
|
934
|
-
}
|
|
935
|
-
if (shouldUseWorkspaceFileExtensionDocumentIcon(entry)) {
|
|
936
|
-
return false;
|
|
937
|
-
}
|
|
938
|
-
return entry.kind === "file";
|
|
939
|
-
}
|
|
940
|
-
function shouldUseWorkspaceFileExtensionDocumentIcon(entry) {
|
|
941
|
-
if (entry.kind !== "file") {
|
|
942
|
-
return false;
|
|
943
|
-
}
|
|
944
|
-
const visualKind = resolveWorkspaceFileVisualKind(entry);
|
|
945
|
-
return visualKind === "code" || visualKind === "markdown" || classifyWorkspaceFilePreviewKind(entry) === "text";
|
|
946
|
-
}
|
|
947
|
-
function isWorkspaceApplicationBundle(entry) {
|
|
948
|
-
return entry.name.trim().toLowerCase().endsWith(".app");
|
|
949
|
-
}
|
|
950
|
-
function resolveWorkspaceFileEntryIconCacheKey(entry) {
|
|
951
|
-
return `${entry.path}:${entry.mtimeMs ?? 0}`;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// src/ui/WorkspaceFileEntryIcon.tsx
|
|
933
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
955
934
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
956
935
|
function WorkspaceFileEntryIcon({
|
|
957
936
|
entry,
|
|
958
937
|
frameClassName,
|
|
959
938
|
iconClassName = "size-4",
|
|
960
939
|
iconUrlByCacheKey,
|
|
961
|
-
isEnteringDirectory = false
|
|
940
|
+
isEnteringDirectory = false,
|
|
941
|
+
onViewportLeave,
|
|
942
|
+
onViewportEnter
|
|
962
943
|
}) {
|
|
963
944
|
const visualKind = resolveWorkspaceFileVisualKind(entry);
|
|
964
945
|
const isAppBundle = isWorkspaceApplicationBundle(entry);
|
|
965
|
-
const
|
|
946
|
+
const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
|
|
947
|
+
const iconUrl = iconUrlByCacheKey?.get(cacheKey) ?? null;
|
|
948
|
+
const frameRef = useRef2(null);
|
|
949
|
+
const visibleEntryRef = useRef2(null);
|
|
950
|
+
const visibleEntryCacheKeyRef = useRef2(null);
|
|
951
|
+
useEffect2(() => {
|
|
952
|
+
function reportViewportLeave() {
|
|
953
|
+
const visibleEntry = visibleEntryRef.current;
|
|
954
|
+
if (!visibleEntry) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
visibleEntryRef.current = null;
|
|
958
|
+
visibleEntryCacheKeyRef.current = null;
|
|
959
|
+
onViewportLeave?.(visibleEntry);
|
|
960
|
+
}
|
|
961
|
+
function reportViewportEnter() {
|
|
962
|
+
if (visibleEntryCacheKeyRef.current === cacheKey) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
reportViewportLeave();
|
|
966
|
+
visibleEntryRef.current = entry;
|
|
967
|
+
visibleEntryCacheKeyRef.current = cacheKey;
|
|
968
|
+
onViewportEnter?.(entry);
|
|
969
|
+
}
|
|
970
|
+
if (!onViewportEnter || isEnteringDirectory) {
|
|
971
|
+
reportViewportLeave();
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const element = frameRef.current;
|
|
975
|
+
if (!element || typeof IntersectionObserver === "undefined") {
|
|
976
|
+
reportViewportEnter();
|
|
977
|
+
return () => {
|
|
978
|
+
reportViewportLeave();
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
const viewportRoot = resolveWorkspaceFileEntryIconViewport(element);
|
|
982
|
+
const observer = new IntersectionObserver(
|
|
983
|
+
(records) => {
|
|
984
|
+
const intersecting = records.some(
|
|
985
|
+
(record) => record.isIntersecting && record.intersectionRatio > 0
|
|
986
|
+
);
|
|
987
|
+
if (intersecting) {
|
|
988
|
+
reportViewportEnter();
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
reportViewportLeave();
|
|
992
|
+
},
|
|
993
|
+
{ root: viewportRoot, rootMargin: "0px", threshold: 0 }
|
|
994
|
+
);
|
|
995
|
+
observer.observe(element);
|
|
996
|
+
const initiallyVisible = isWorkspaceFileEntryIconVisible(
|
|
997
|
+
element,
|
|
998
|
+
viewportRoot
|
|
999
|
+
);
|
|
1000
|
+
if (initiallyVisible) {
|
|
1001
|
+
reportViewportEnter();
|
|
1002
|
+
} else {
|
|
1003
|
+
reportViewportLeave();
|
|
1004
|
+
}
|
|
1005
|
+
return () => {
|
|
1006
|
+
observer.disconnect();
|
|
1007
|
+
reportViewportLeave();
|
|
1008
|
+
};
|
|
1009
|
+
}, [cacheKey, entry, isEnteringDirectory, onViewportLeave, onViewportEnter]);
|
|
966
1010
|
return /* @__PURE__ */ jsx2(
|
|
967
1011
|
"span",
|
|
968
1012
|
{
|
|
1013
|
+
ref: frameRef,
|
|
969
1014
|
className: cn2(
|
|
970
1015
|
"grid flex-none place-items-center overflow-hidden",
|
|
971
1016
|
frameClassName,
|
|
@@ -975,8 +1020,13 @@ function WorkspaceFileEntryIcon({
|
|
|
975
1020
|
"img",
|
|
976
1021
|
{
|
|
977
1022
|
alt: "",
|
|
978
|
-
className: cn2(
|
|
1023
|
+
className: cn2(
|
|
1024
|
+
iconClassName,
|
|
1025
|
+
visualKind === "image" ? "rounded-[6px] border border-[var(--border-1)] bg-[var(--transparency-block)] object-contain shadow-sm" : "rounded-[4px] object-contain"
|
|
1026
|
+
),
|
|
1027
|
+
decoding: "async",
|
|
979
1028
|
draggable: false,
|
|
1029
|
+
loading: "lazy",
|
|
980
1030
|
src: iconUrl
|
|
981
1031
|
}
|
|
982
1032
|
) : /* @__PURE__ */ jsx2(
|
|
@@ -990,6 +1040,19 @@ function WorkspaceFileEntryIcon({
|
|
|
990
1040
|
}
|
|
991
1041
|
);
|
|
992
1042
|
}
|
|
1043
|
+
function resolveWorkspaceFileEntryIconViewport(element) {
|
|
1044
|
+
return element.closest('[data-slot="scroll-area-viewport"]');
|
|
1045
|
+
}
|
|
1046
|
+
function isWorkspaceFileEntryIconVisible(element, viewportRoot) {
|
|
1047
|
+
const elementRect = element.getBoundingClientRect();
|
|
1048
|
+
const viewportRect = viewportRoot?.getBoundingClientRect() ?? {
|
|
1049
|
+
bottom: window.innerHeight,
|
|
1050
|
+
left: 0,
|
|
1051
|
+
right: window.innerWidth,
|
|
1052
|
+
top: 0
|
|
1053
|
+
};
|
|
1054
|
+
return elementRect.bottom > viewportRect.top && elementRect.top < viewportRect.bottom && elementRect.right > viewportRect.left && elementRect.left < viewportRect.right;
|
|
1055
|
+
}
|
|
993
1056
|
function DefaultEntryIcon({
|
|
994
1057
|
entry,
|
|
995
1058
|
iconClassName,
|
|
@@ -1024,7 +1087,7 @@ function ExtensionDocumentIcon({
|
|
|
1024
1087
|
iconClassName
|
|
1025
1088
|
}) {
|
|
1026
1089
|
const extension = resolveWorkspaceFileExtension(entry.name).slice(0, 5).toUpperCase();
|
|
1027
|
-
const showExtension = extension.length > 0 && iconClassName.includes("
|
|
1090
|
+
const showExtension = extension.length > 0 && !iconClassName.includes("size-4");
|
|
1028
1091
|
return /* @__PURE__ */ jsxs2(
|
|
1029
1092
|
"span",
|
|
1030
1093
|
{
|
|
@@ -1046,19 +1109,19 @@ function entryIconColorClassName(visualKind, isAppBundle) {
|
|
|
1046
1109
|
}
|
|
1047
1110
|
|
|
1048
1111
|
// src/ui/workspaceFileManagerIconGridLayout.ts
|
|
1049
|
-
var workspaceFileManagerIconGridIconSizePx =
|
|
1050
|
-
var workspaceFileManagerIconGridTileMinWidthPx =
|
|
1051
|
-
var workspaceFileManagerIconGridTileMaxWidthPx =
|
|
1112
|
+
var workspaceFileManagerIconGridIconSizePx = 84;
|
|
1113
|
+
var workspaceFileManagerIconGridTileMinWidthPx = 136;
|
|
1114
|
+
var workspaceFileManagerIconGridTileMaxWidthPx = 148;
|
|
1052
1115
|
var workspaceFileManagerIconGridLayout = {
|
|
1053
1116
|
iconSizePx: workspaceFileManagerIconGridIconSizePx,
|
|
1054
1117
|
tileMaxWidthPx: workspaceFileManagerIconGridTileMaxWidthPx,
|
|
1055
1118
|
tileMinWidthPx: workspaceFileManagerIconGridTileMinWidthPx
|
|
1056
1119
|
};
|
|
1057
1120
|
function workspaceFileManagerIconGridIconClassName() {
|
|
1058
|
-
return "size-[
|
|
1121
|
+
return "size-[84px]";
|
|
1059
1122
|
}
|
|
1060
1123
|
function workspaceFileManagerIconGridFrameClassName() {
|
|
1061
|
-
return "size-[
|
|
1124
|
+
return "size-[92px]";
|
|
1062
1125
|
}
|
|
1063
1126
|
|
|
1064
1127
|
// src/ui/WorkspaceFileManagerIconGrid.tsx
|
|
@@ -1076,6 +1139,8 @@ function WorkspaceFileManagerIconGrid({
|
|
|
1076
1139
|
moveDragPreviewEntryPath,
|
|
1077
1140
|
moveDragTargetEntryPath,
|
|
1078
1141
|
pendingDirectoryPath,
|
|
1142
|
+
onEntryIconViewportLeave,
|
|
1143
|
+
onEntryIconViewportEnter,
|
|
1079
1144
|
selectedPath,
|
|
1080
1145
|
onCancelInlineRename,
|
|
1081
1146
|
onClearInlineRenameValidation,
|
|
@@ -1107,6 +1172,8 @@ function WorkspaceFileManagerIconGrid({
|
|
|
1107
1172
|
moveDragActive,
|
|
1108
1173
|
moveDragSource: moveDragPreviewEntryPath === entry.path,
|
|
1109
1174
|
moveDragTarget: moveDragTargetEntryPath === entry.path,
|
|
1175
|
+
onEntryIconViewportLeave,
|
|
1176
|
+
onEntryIconViewportEnter,
|
|
1110
1177
|
selected: selectedPath === entry.path,
|
|
1111
1178
|
onCancelInlineRename,
|
|
1112
1179
|
onClearInlineRenameValidation,
|
|
@@ -1134,6 +1201,8 @@ function WorkspaceFileManagerIconTile({
|
|
|
1134
1201
|
moveDragActive,
|
|
1135
1202
|
moveDragSource,
|
|
1136
1203
|
moveDragTarget,
|
|
1204
|
+
onEntryIconViewportLeave,
|
|
1205
|
+
onEntryIconViewportEnter,
|
|
1137
1206
|
selected,
|
|
1138
1207
|
onCancelInlineRename,
|
|
1139
1208
|
onClearInlineRenameValidation,
|
|
@@ -1143,7 +1212,7 @@ function WorkspaceFileManagerIconTile({
|
|
|
1143
1212
|
onClick,
|
|
1144
1213
|
onPointerDown
|
|
1145
1214
|
}) {
|
|
1146
|
-
const buttonRef =
|
|
1215
|
+
const buttonRef = useRef3(null);
|
|
1147
1216
|
const nameParts = splitWorkspaceFileName(entry.name);
|
|
1148
1217
|
useLayoutEffect2(() => {
|
|
1149
1218
|
if (!selected) {
|
|
@@ -1155,7 +1224,7 @@ function WorkspaceFileManagerIconTile({
|
|
|
1155
1224
|
});
|
|
1156
1225
|
}, [selected]);
|
|
1157
1226
|
const tileClassName = cn3(
|
|
1158
|
-
"flex min-w-0 max-w-[
|
|
1227
|
+
"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",
|
|
1159
1228
|
isInlineRenaming ? "cursor-default" : "cursor-pointer hover:bg-transparency-block",
|
|
1160
1229
|
moveDragActive && "cursor-grabbing",
|
|
1161
1230
|
selected || contextMenuActive || isInlineRenaming ? "border-[var(--border-focus)] bg-transparency-block text-[var(--text-primary)]" : "text-[var(--text-secondary)]",
|
|
@@ -1169,7 +1238,9 @@ function WorkspaceFileManagerIconTile({
|
|
|
1169
1238
|
frameClassName: workspaceFileManagerIconGridFrameClassName(),
|
|
1170
1239
|
iconClassName: workspaceFileManagerIconGridIconClassName(),
|
|
1171
1240
|
iconUrlByCacheKey,
|
|
1172
|
-
isEnteringDirectory
|
|
1241
|
+
isEnteringDirectory,
|
|
1242
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
1243
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
1173
1244
|
}
|
|
1174
1245
|
);
|
|
1175
1246
|
if (isInlineRenaming) {
|
|
@@ -1243,7 +1314,7 @@ function IconTileRenameInput({
|
|
|
1243
1314
|
}) {
|
|
1244
1315
|
const nameParts = splitWorkspaceFileName(entry.name);
|
|
1245
1316
|
const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
|
|
1246
|
-
const inputRef =
|
|
1317
|
+
const inputRef = useRef3(null);
|
|
1247
1318
|
useLayoutEffect2(() => {
|
|
1248
1319
|
const input = inputRef.current;
|
|
1249
1320
|
if (!input) {
|
|
@@ -1448,6 +1519,8 @@ function WorkspaceFileManagerPanels({
|
|
|
1448
1519
|
layoutMode,
|
|
1449
1520
|
pendingDirectoryPath,
|
|
1450
1521
|
previewState,
|
|
1522
|
+
onEntryIconViewportLeave,
|
|
1523
|
+
onEntryIconViewportEnter,
|
|
1451
1524
|
selectedEntry,
|
|
1452
1525
|
selectedPath,
|
|
1453
1526
|
state,
|
|
@@ -1465,18 +1538,18 @@ function WorkspaceFileManagerPanels({
|
|
|
1465
1538
|
const { rootRef } = useWorkspaceFileManagerStackedLayout(
|
|
1466
1539
|
workspaceFileManagerStackedBreakpoint
|
|
1467
1540
|
);
|
|
1468
|
-
const previewResizeRef =
|
|
1541
|
+
const previewResizeRef = useRef4(null);
|
|
1469
1542
|
const [previewPaneWidth, setPreviewPaneWidth] = useState2(
|
|
1470
1543
|
workspaceFileManagerPreviewDefaultWidth
|
|
1471
1544
|
);
|
|
1472
1545
|
const [moveDragPreview, setMoveDragPreview] = useState2(null);
|
|
1473
|
-
const lastEntryClickRef =
|
|
1546
|
+
const lastEntryClickRef = useRef4(
|
|
1474
1547
|
null
|
|
1475
1548
|
);
|
|
1476
|
-
const moveDragAutoScrollRef =
|
|
1477
|
-
const moveDragRef =
|
|
1478
|
-
const suppressNextClickRef =
|
|
1479
|
-
const entriesRef =
|
|
1549
|
+
const moveDragAutoScrollRef = useRef4(null);
|
|
1550
|
+
const moveDragRef = useRef4(null);
|
|
1551
|
+
const suppressNextClickRef = useRef4(false);
|
|
1552
|
+
const entriesRef = useRef4(state.entries);
|
|
1480
1553
|
const nativeEntryDragEnabled = onEntryDragStart !== void 0 && (entryDragMode === "external" || entryDragMode === void 0 && !canMove);
|
|
1481
1554
|
const internalMoveEnabled = canMove && !nativeEntryDragEnabled;
|
|
1482
1555
|
const dateColumnLabel = resolveWorkspaceFileManagerDateColumnLabel(
|
|
@@ -1590,11 +1663,11 @@ function WorkspaceFileManagerPanels({
|
|
|
1590
1663
|
},
|
|
1591
1664
|
[entrySelectionEnabled, onOpenEntry, onSelect, shouldSuppressEntryClick]
|
|
1592
1665
|
);
|
|
1593
|
-
|
|
1666
|
+
useEffect3(() => {
|
|
1594
1667
|
lastEntryClickRef.current = null;
|
|
1595
1668
|
entriesRef.current = state.entries;
|
|
1596
1669
|
}, [state.entries]);
|
|
1597
|
-
|
|
1670
|
+
useEffect3(() => {
|
|
1598
1671
|
function handleDocumentPointerMove(event) {
|
|
1599
1672
|
const drag = moveDragRef.current;
|
|
1600
1673
|
if (!drag) {
|
|
@@ -1783,6 +1856,8 @@ function WorkspaceFileManagerPanels({
|
|
|
1783
1856
|
moveDragPreviewEntryPath: moveDragPreview?.entry.path ?? null,
|
|
1784
1857
|
moveDragTargetEntryPath: moveDragPreview?.targetDirectoryPath ?? null,
|
|
1785
1858
|
pendingDirectoryPath,
|
|
1859
|
+
onEntryIconViewportLeave,
|
|
1860
|
+
onEntryIconViewportEnter,
|
|
1786
1861
|
selectedPath,
|
|
1787
1862
|
onCancelInlineRename,
|
|
1788
1863
|
onClearInlineRenameValidation,
|
|
@@ -1842,6 +1917,8 @@ function WorkspaceFileManagerPanels({
|
|
|
1842
1917
|
moveDragTarget: moveDragPreview?.targetDirectoryPath === entry.path,
|
|
1843
1918
|
tableCellPaddingClassName,
|
|
1844
1919
|
selected: selectedPath === entry.path,
|
|
1920
|
+
onEntryIconViewportLeave,
|
|
1921
|
+
onEntryIconViewportEnter,
|
|
1845
1922
|
onCancelInlineRename,
|
|
1846
1923
|
onClearInlineRenameValidation,
|
|
1847
1924
|
onConfirmInlineRename,
|
|
@@ -1924,7 +2001,9 @@ function WorkspaceFileManagerPanels({
|
|
|
1924
2001
|
MoveDragPreview,
|
|
1925
2002
|
{
|
|
1926
2003
|
iconUrlByCacheKey,
|
|
1927
|
-
preview: moveDragPreview
|
|
2004
|
+
preview: moveDragPreview,
|
|
2005
|
+
onEntryIconViewportLeave,
|
|
2006
|
+
onEntryIconViewportEnter
|
|
1928
2007
|
}
|
|
1929
2008
|
) : null
|
|
1930
2009
|
]
|
|
@@ -1932,7 +2011,7 @@ function WorkspaceFileManagerPanels({
|
|
|
1932
2011
|
);
|
|
1933
2012
|
}
|
|
1934
2013
|
function useWorkspaceFileManagerStackedLayout(breakpoint) {
|
|
1935
|
-
const rootRef =
|
|
2014
|
+
const rootRef = useRef4(null);
|
|
1936
2015
|
const [isStacked, setIsStacked] = useState2(false);
|
|
1937
2016
|
useLayoutEffect3(() => {
|
|
1938
2017
|
const element = rootRef.current;
|
|
@@ -1991,6 +2070,8 @@ function EntryRow({
|
|
|
1991
2070
|
moveDragActive,
|
|
1992
2071
|
moveDragSource,
|
|
1993
2072
|
moveDragTarget,
|
|
2073
|
+
onEntryIconViewportLeave,
|
|
2074
|
+
onEntryIconViewportEnter,
|
|
1994
2075
|
selected,
|
|
1995
2076
|
tableCellPaddingClassName,
|
|
1996
2077
|
onCancelInlineRename,
|
|
@@ -2001,8 +2082,8 @@ function EntryRow({
|
|
|
2001
2082
|
onClick,
|
|
2002
2083
|
onPointerDown
|
|
2003
2084
|
}) {
|
|
2004
|
-
const buttonRowRef =
|
|
2005
|
-
const divRowRef =
|
|
2085
|
+
const buttonRowRef = useRef4(null);
|
|
2086
|
+
const divRowRef = useRef4(null);
|
|
2006
2087
|
useLayoutEffect3(() => {
|
|
2007
2088
|
if (!selected) {
|
|
2008
2089
|
return;
|
|
@@ -2042,6 +2123,8 @@ function EntryRow({
|
|
|
2042
2123
|
isEnteringDirectory,
|
|
2043
2124
|
isInlineRenaming,
|
|
2044
2125
|
isRenaming,
|
|
2126
|
+
onEntryIconViewportLeave,
|
|
2127
|
+
onEntryIconViewportEnter,
|
|
2045
2128
|
onCancelInlineRename,
|
|
2046
2129
|
onClearInlineRenameValidation,
|
|
2047
2130
|
onConfirmInlineRename
|
|
@@ -2106,7 +2189,9 @@ function EntryRow({
|
|
|
2106
2189
|
}
|
|
2107
2190
|
function MoveDragPreview({
|
|
2108
2191
|
iconUrlByCacheKey,
|
|
2109
|
-
preview
|
|
2192
|
+
preview,
|
|
2193
|
+
onEntryIconViewportLeave,
|
|
2194
|
+
onEntryIconViewportEnter
|
|
2110
2195
|
}) {
|
|
2111
2196
|
return /* @__PURE__ */ jsxs4(
|
|
2112
2197
|
"div",
|
|
@@ -2123,9 +2208,11 @@ function MoveDragPreview({
|
|
|
2123
2208
|
WorkspaceFileEntryIcon,
|
|
2124
2209
|
{
|
|
2125
2210
|
entry: preview.entry,
|
|
2126
|
-
frameClassName: "size-
|
|
2127
|
-
iconClassName: "size-
|
|
2128
|
-
iconUrlByCacheKey
|
|
2211
|
+
frameClassName: "size-7",
|
|
2212
|
+
iconClassName: "size-6",
|
|
2213
|
+
iconUrlByCacheKey,
|
|
2214
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
2215
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
2129
2216
|
}
|
|
2130
2217
|
),
|
|
2131
2218
|
/* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: preview.entry.name })
|
|
@@ -2176,6 +2263,8 @@ function EntryNameCell({
|
|
|
2176
2263
|
isEnteringDirectory = false,
|
|
2177
2264
|
isInlineRenaming = false,
|
|
2178
2265
|
isRenaming = false,
|
|
2266
|
+
onEntryIconViewportLeave,
|
|
2267
|
+
onEntryIconViewportEnter,
|
|
2179
2268
|
onCancelInlineRename,
|
|
2180
2269
|
onClearInlineRenameValidation,
|
|
2181
2270
|
onConfirmInlineRename
|
|
@@ -2183,9 +2272,9 @@ function EntryNameCell({
|
|
|
2183
2272
|
const nameParts = splitWorkspaceFileName(entry.name);
|
|
2184
2273
|
const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
|
|
2185
2274
|
const [name, setName] = useState2(entry.name);
|
|
2186
|
-
const inputRef =
|
|
2187
|
-
const skipBlurRef =
|
|
2188
|
-
|
|
2275
|
+
const inputRef = useRef4(null);
|
|
2276
|
+
const skipBlurRef = useRef4(false);
|
|
2277
|
+
useEffect3(() => {
|
|
2189
2278
|
if (!isInlineRenaming) {
|
|
2190
2279
|
return;
|
|
2191
2280
|
}
|
|
@@ -2220,9 +2309,11 @@ function EntryNameCell({
|
|
|
2220
2309
|
WorkspaceFileEntryIcon,
|
|
2221
2310
|
{
|
|
2222
2311
|
entry,
|
|
2223
|
-
frameClassName: "size-
|
|
2224
|
-
iconClassName: "size-
|
|
2225
|
-
iconUrlByCacheKey
|
|
2312
|
+
frameClassName: "size-7",
|
|
2313
|
+
iconClassName: "size-6",
|
|
2314
|
+
iconUrlByCacheKey,
|
|
2315
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
2316
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
2226
2317
|
}
|
|
2227
2318
|
),
|
|
2228
2319
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
|
|
@@ -2282,10 +2373,12 @@ function EntryNameCell({
|
|
|
2282
2373
|
WorkspaceFileEntryIcon,
|
|
2283
2374
|
{
|
|
2284
2375
|
entry,
|
|
2285
|
-
frameClassName: "size-
|
|
2286
|
-
iconClassName: "size-
|
|
2376
|
+
frameClassName: "size-7",
|
|
2377
|
+
iconClassName: "size-6",
|
|
2287
2378
|
iconUrlByCacheKey,
|
|
2288
|
-
isEnteringDirectory
|
|
2379
|
+
isEnteringDirectory,
|
|
2380
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
2381
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
2289
2382
|
}
|
|
2290
2383
|
),
|
|
2291
2384
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
|
|
@@ -2421,6 +2514,10 @@ import {
|
|
|
2421
2514
|
DropdownMenuTrigger,
|
|
2422
2515
|
LoadingIcon as LoadingIcon3,
|
|
2423
2516
|
RefreshIcon,
|
|
2517
|
+
Tooltip,
|
|
2518
|
+
TooltipContent,
|
|
2519
|
+
TooltipProvider,
|
|
2520
|
+
TooltipTrigger,
|
|
2424
2521
|
ViewGridLinedIcon,
|
|
2425
2522
|
ViewListLinedIcon,
|
|
2426
2523
|
cn as cn5
|
|
@@ -2544,7 +2641,7 @@ function LayoutModeToggle({
|
|
|
2544
2641
|
{ label: copy.t("sizeLabel"), mode: "size" },
|
|
2545
2642
|
{ label: copy.t("arrangeTagsLabel"), mode: "tags" }
|
|
2546
2643
|
];
|
|
2547
|
-
return /* @__PURE__ */ jsxs5(
|
|
2644
|
+
return /* @__PURE__ */ jsx5(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs5(
|
|
2548
2645
|
"div",
|
|
2549
2646
|
{
|
|
2550
2647
|
className: "flex items-center rounded-md border border-[var(--border-1)] bg-[var(--transparency-block)] p-0.5",
|
|
@@ -2555,6 +2652,7 @@ function LayoutModeToggle({
|
|
|
2555
2652
|
{
|
|
2556
2653
|
active: layoutMode === "icon",
|
|
2557
2654
|
ariaLabel: copy.t("layoutIconViewLabel"),
|
|
2655
|
+
tooltipLabel: copy.t("layoutIconViewTooltipLabel"),
|
|
2558
2656
|
onClick: () => {
|
|
2559
2657
|
onLayoutModeChange("icon");
|
|
2560
2658
|
},
|
|
@@ -2566,6 +2664,7 @@ function LayoutModeToggle({
|
|
|
2566
2664
|
{
|
|
2567
2665
|
active: layoutMode === "list",
|
|
2568
2666
|
ariaLabel: copy.t("layoutListViewLabel"),
|
|
2667
|
+
tooltipLabel: copy.t("layoutListViewTooltipLabel"),
|
|
2569
2668
|
onClick: () => {
|
|
2570
2669
|
onLayoutModeChange("list");
|
|
2571
2670
|
},
|
|
@@ -2580,7 +2679,6 @@ function LayoutModeToggle({
|
|
|
2580
2679
|
"aria-label": copy.t("arrangeMenuLabel"),
|
|
2581
2680
|
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",
|
|
2582
2681
|
size: "icon-sm",
|
|
2583
|
-
title: copy.t("arrangeMenuLabel"),
|
|
2584
2682
|
type: "button",
|
|
2585
2683
|
variant: "ghost",
|
|
2586
2684
|
children: /* @__PURE__ */ jsx5(ChevronDownIcon, { className: "size-3.5" })
|
|
@@ -2597,7 +2695,9 @@ function LayoutModeToggle({
|
|
|
2597
2695
|
{
|
|
2598
2696
|
value: arrangeMode,
|
|
2599
2697
|
onValueChange: (nextMode) => {
|
|
2600
|
-
onArrangeModeChange(
|
|
2698
|
+
onArrangeModeChange(
|
|
2699
|
+
nextMode
|
|
2700
|
+
);
|
|
2601
2701
|
},
|
|
2602
2702
|
children: arrangeOptions.map((option, index) => /* @__PURE__ */ jsxs5("div", { children: [
|
|
2603
2703
|
index === 1 ? /* @__PURE__ */ jsx5(DropdownMenuSeparator, {}) : null,
|
|
@@ -2617,31 +2717,34 @@ function LayoutModeToggle({
|
|
|
2617
2717
|
] })
|
|
2618
2718
|
]
|
|
2619
2719
|
}
|
|
2620
|
-
);
|
|
2720
|
+
) });
|
|
2621
2721
|
}
|
|
2622
2722
|
function LayoutModeButton({
|
|
2623
2723
|
active,
|
|
2624
2724
|
ariaLabel,
|
|
2625
2725
|
children,
|
|
2626
|
-
onClick
|
|
2726
|
+
onClick,
|
|
2727
|
+
tooltipLabel
|
|
2627
2728
|
}) {
|
|
2628
|
-
return /* @__PURE__ */
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2729
|
+
return /* @__PURE__ */ jsxs5(Tooltip, { children: [
|
|
2730
|
+
/* @__PURE__ */ jsx5(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(
|
|
2731
|
+
Button2,
|
|
2732
|
+
{
|
|
2733
|
+
"aria-label": ariaLabel,
|
|
2734
|
+
"aria-pressed": active,
|
|
2735
|
+
className: cn5(
|
|
2736
|
+
"size-6 min-w-6 rounded-[4px] p-0 text-text-secondary",
|
|
2737
|
+
active && "!bg-background-fronted text-foreground hover:!bg-background-fronted"
|
|
2738
|
+
),
|
|
2739
|
+
size: "icon-sm",
|
|
2740
|
+
type: "button",
|
|
2741
|
+
variant: "ghost",
|
|
2742
|
+
onClick,
|
|
2743
|
+
children
|
|
2744
|
+
}
|
|
2745
|
+
) }),
|
|
2746
|
+
/* @__PURE__ */ jsx5(TooltipContent, { side: "bottom", children: tooltipLabel })
|
|
2747
|
+
] });
|
|
2645
2748
|
}
|
|
2646
2749
|
function ToolbarIconButton({
|
|
2647
2750
|
ariaLabel,
|
|
@@ -2762,74 +2865,228 @@ function useWorkspaceFileManagerLayoutMode() {
|
|
|
2762
2865
|
}
|
|
2763
2866
|
|
|
2764
2867
|
// src/ui/useWorkspaceFileEntryIconUrls.ts
|
|
2765
|
-
import { useEffect as
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2868
|
+
import { useCallback as useCallback5, useEffect as useEffect4, useMemo, useState as useState6 } from "react";
|
|
2869
|
+
|
|
2870
|
+
// src/ui/workspaceFileEntryIconUrlQueue.ts
|
|
2871
|
+
var defaultMaxConcurrentIconRequests = 3;
|
|
2872
|
+
var imageThumbnailCacheKeyPrefix = "image-thumbnail:";
|
|
2873
|
+
function shouldKeepResolvedIconAfterViewportLeave(cacheKey) {
|
|
2874
|
+
return cacheKey.startsWith(imageThumbnailCacheKeyPrefix);
|
|
2875
|
+
}
|
|
2876
|
+
function createWorkspaceFileEntryIconUrlQueue(options) {
|
|
2877
|
+
const maxConcurrent = Math.max(
|
|
2878
|
+
1,
|
|
2879
|
+
Math.floor(options.maxConcurrent ?? defaultMaxConcurrentIconRequests)
|
|
2880
|
+
);
|
|
2881
|
+
const iconUrlByCacheKey = /* @__PURE__ */ new Map();
|
|
2882
|
+
const inFlightCacheKeys = /* @__PURE__ */ new Set();
|
|
2883
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2884
|
+
const queuedEntries = /* @__PURE__ */ new Map();
|
|
2885
|
+
const visibleReferenceCountByCacheKey = /* @__PURE__ */ new Map();
|
|
2886
|
+
let activeCount = 0;
|
|
2887
|
+
let disposed = false;
|
|
2888
|
+
let retainedCacheKeys = null;
|
|
2889
|
+
const policyOptions = {
|
|
2890
|
+
includeImageThumbnails: options.includeImageThumbnails
|
|
2891
|
+
};
|
|
2892
|
+
function publish() {
|
|
2893
|
+
for (const listener of listeners) {
|
|
2894
|
+
listener();
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
function isRetained(cacheKey) {
|
|
2898
|
+
return retainedCacheKeys === null || retainedCacheKeys.has(cacheKey);
|
|
2899
|
+
}
|
|
2900
|
+
function isVisible(cacheKey) {
|
|
2901
|
+
return (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) > 0;
|
|
2902
|
+
}
|
|
2903
|
+
function shouldResolveCacheKey(cacheKey) {
|
|
2904
|
+
return isVisible(cacheKey) || shouldKeepResolvedIconAfterViewportLeave(cacheKey);
|
|
2905
|
+
}
|
|
2906
|
+
function retainVisibleReference(cacheKey) {
|
|
2907
|
+
visibleReferenceCountByCacheKey.set(
|
|
2908
|
+
cacheKey,
|
|
2909
|
+
(visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) + 1
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
function releaseVisibleReference(cacheKey) {
|
|
2913
|
+
const nextCount = (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) - 1;
|
|
2914
|
+
if (nextCount > 0) {
|
|
2915
|
+
visibleReferenceCountByCacheKey.set(cacheKey, nextCount);
|
|
2781
2916
|
return;
|
|
2782
2917
|
}
|
|
2783
|
-
|
|
2784
|
-
if (
|
|
2785
|
-
setIconUrlByCacheKey(
|
|
2786
|
-
(current) => current.size === 0 ? current : /* @__PURE__ */ new Map()
|
|
2787
|
-
);
|
|
2918
|
+
visibleReferenceCountByCacheKey.delete(cacheKey);
|
|
2919
|
+
if (shouldKeepResolvedIconAfterViewportLeave(cacheKey)) {
|
|
2788
2920
|
return;
|
|
2789
2921
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2922
|
+
queuedEntries.delete(cacheKey);
|
|
2923
|
+
const hadCachedIcon = iconUrlByCacheKey.delete(cacheKey);
|
|
2924
|
+
if (hadCachedIcon) {
|
|
2925
|
+
publish();
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
function drain() {
|
|
2929
|
+
if (disposed || !options.resolveEntryIconUrl) {
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
while (activeCount < maxConcurrent && queuedEntries.size > 0) {
|
|
2933
|
+
const next = queuedEntries.entries().next().value;
|
|
2934
|
+
if (!next) {
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
const [cacheKey, entry] = next;
|
|
2938
|
+
queuedEntries.delete(cacheKey);
|
|
2939
|
+
if (!shouldResolveCacheKey(cacheKey) || iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey)) {
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
activeCount += 1;
|
|
2943
|
+
inFlightCacheKeys.add(cacheKey);
|
|
2944
|
+
void options.resolveEntryIconUrl(entry).then((iconUrl) => {
|
|
2945
|
+
const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
|
|
2946
|
+
if (shouldStore) {
|
|
2947
|
+
iconUrlByCacheKey.set(cacheKey, iconUrl?.trim() || null);
|
|
2948
|
+
publish();
|
|
2799
2949
|
}
|
|
2800
|
-
})
|
|
2801
|
-
|
|
2802
|
-
|
|
2950
|
+
}).catch(() => {
|
|
2951
|
+
const shouldStore = !disposed && isRetained(cacheKey) && shouldResolveCacheKey(cacheKey);
|
|
2952
|
+
if (shouldStore) {
|
|
2953
|
+
iconUrlByCacheKey.set(cacheKey, null);
|
|
2954
|
+
publish();
|
|
2955
|
+
}
|
|
2956
|
+
}).finally(() => {
|
|
2957
|
+
activeCount -= 1;
|
|
2958
|
+
inFlightCacheKeys.delete(cacheKey);
|
|
2959
|
+
drain();
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
return {
|
|
2964
|
+
activate() {
|
|
2965
|
+
disposed = false;
|
|
2966
|
+
},
|
|
2967
|
+
dispose() {
|
|
2968
|
+
disposed = true;
|
|
2969
|
+
queuedEntries.clear();
|
|
2970
|
+
visibleReferenceCountByCacheKey.clear();
|
|
2971
|
+
listeners.clear();
|
|
2972
|
+
},
|
|
2973
|
+
leaveViewport(entry) {
|
|
2974
|
+
if (disposed || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
|
|
2803
2975
|
return;
|
|
2804
2976
|
}
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2977
|
+
const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
|
|
2978
|
+
releaseVisibleReference(cacheKey);
|
|
2979
|
+
},
|
|
2980
|
+
enterViewport(entry) {
|
|
2981
|
+
if (disposed || !options.resolveEntryIconUrl || !shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)) {
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
|
|
2985
|
+
retainVisibleReference(cacheKey);
|
|
2986
|
+
if (iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey) || queuedEntries.has(cacheKey)) {
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
queuedEntries.set(cacheKey, entry);
|
|
2990
|
+
drain();
|
|
2991
|
+
},
|
|
2992
|
+
retainEntries(entries) {
|
|
2993
|
+
retainedCacheKeys = new Set(
|
|
2994
|
+
entries.filter(
|
|
2995
|
+
(entry) => shouldResolveWorkspaceFileEntryIcon(entry, policyOptions)
|
|
2996
|
+
).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry))
|
|
2997
|
+
);
|
|
2998
|
+
let changed = false;
|
|
2999
|
+
for (const cacheKey of iconUrlByCacheKey.keys()) {
|
|
3000
|
+
if (!isRetained(cacheKey)) {
|
|
3001
|
+
iconUrlByCacheKey.delete(cacheKey);
|
|
3002
|
+
changed = true;
|
|
3003
|
+
continue;
|
|
2814
3004
|
}
|
|
2815
|
-
if (
|
|
3005
|
+
if (iconUrlByCacheKey.get(cacheKey) === null) {
|
|
3006
|
+
iconUrlByCacheKey.delete(cacheKey);
|
|
2816
3007
|
changed = true;
|
|
2817
|
-
} else if (!changed) {
|
|
2818
|
-
for (const cacheKey of current.keys()) {
|
|
2819
|
-
if (!nextCacheKeys.has(cacheKey)) {
|
|
2820
|
-
changed = true;
|
|
2821
|
-
break;
|
|
2822
|
-
}
|
|
2823
|
-
}
|
|
2824
3008
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
3009
|
+
}
|
|
3010
|
+
for (const cacheKey of queuedEntries.keys()) {
|
|
3011
|
+
if (!isRetained(cacheKey)) {
|
|
3012
|
+
queuedEntries.delete(cacheKey);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
for (const cacheKey of visibleReferenceCountByCacheKey.keys()) {
|
|
3016
|
+
if (!isRetained(cacheKey)) {
|
|
3017
|
+
visibleReferenceCountByCacheKey.delete(cacheKey);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
if (changed) {
|
|
3021
|
+
publish();
|
|
3022
|
+
}
|
|
3023
|
+
},
|
|
3024
|
+
snapshot() {
|
|
3025
|
+
return new Map(iconUrlByCacheKey);
|
|
3026
|
+
},
|
|
3027
|
+
subscribe(listener) {
|
|
3028
|
+
if (disposed) {
|
|
3029
|
+
return () => {
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
listeners.add(listener);
|
|
3033
|
+
return () => {
|
|
3034
|
+
listeners.delete(listener);
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
// src/ui/useWorkspaceFileEntryIconUrls.ts
|
|
3041
|
+
function buildIconTargetSignature(entries, options) {
|
|
3042
|
+
return entries.filter((entry) => shouldResolveWorkspaceFileEntryIcon(entry, options)).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
|
|
3043
|
+
}
|
|
3044
|
+
function useWorkspaceFileEntryIconUrls(input) {
|
|
3045
|
+
const { entries, includeImageThumbnails, resolveEntryIconUrl } = input;
|
|
3046
|
+
const queue = useMemo(
|
|
3047
|
+
() => createWorkspaceFileEntryIconUrlQueue({
|
|
3048
|
+
includeImageThumbnails,
|
|
3049
|
+
resolveEntryIconUrl
|
|
3050
|
+
}),
|
|
3051
|
+
[includeImageThumbnails, resolveEntryIconUrl]
|
|
3052
|
+
);
|
|
3053
|
+
const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState6(() => queue.snapshot());
|
|
3054
|
+
const iconTargetSignature = useMemo(
|
|
3055
|
+
() => buildIconTargetSignature(entries, { includeImageThumbnails }),
|
|
3056
|
+
[entries, includeImageThumbnails]
|
|
3057
|
+
);
|
|
3058
|
+
useEffect4(() => {
|
|
3059
|
+
queue.activate();
|
|
2828
3060
|
return () => {
|
|
2829
|
-
|
|
3061
|
+
queue.dispose();
|
|
2830
3062
|
};
|
|
2831
|
-
}, [
|
|
2832
|
-
|
|
3063
|
+
}, [queue]);
|
|
3064
|
+
useEffect4(() => {
|
|
3065
|
+
queue.retainEntries(entries);
|
|
3066
|
+
}, [entries, iconTargetSignature, queue]);
|
|
3067
|
+
useEffect4(() => {
|
|
3068
|
+
setIconUrlByCacheKey(queue.snapshot());
|
|
3069
|
+
return queue.subscribe(() => {
|
|
3070
|
+
setIconUrlByCacheKey(queue.snapshot());
|
|
3071
|
+
});
|
|
3072
|
+
}, [queue]);
|
|
3073
|
+
const reportEntryIconViewportEnter = useCallback5(
|
|
3074
|
+
(entry) => {
|
|
3075
|
+
queue.enterViewport(entry);
|
|
3076
|
+
},
|
|
3077
|
+
[queue]
|
|
3078
|
+
);
|
|
3079
|
+
const reportEntryIconViewportLeave = useCallback5(
|
|
3080
|
+
(entry) => {
|
|
3081
|
+
queue.leaveViewport(entry);
|
|
3082
|
+
},
|
|
3083
|
+
[queue]
|
|
3084
|
+
);
|
|
3085
|
+
return {
|
|
3086
|
+
iconUrlByCacheKey,
|
|
3087
|
+
reportEntryIconViewportEnter,
|
|
3088
|
+
reportEntryIconViewportLeave
|
|
3089
|
+
};
|
|
2833
3090
|
}
|
|
2834
3091
|
|
|
2835
3092
|
// src/ui/workspaceFileManagerAnalytics.ts
|
|
@@ -3016,12 +3273,12 @@ function WorkspaceFileManager({
|
|
|
3016
3273
|
session,
|
|
3017
3274
|
surface = "card"
|
|
3018
3275
|
}) {
|
|
3019
|
-
const rootRef =
|
|
3276
|
+
const rootRef = useRef5(null);
|
|
3020
3277
|
const { arrangeMode, setArrangeMode } = useWorkspaceFileManagerArrangeMode();
|
|
3021
3278
|
const { layoutMode, setLayoutMode } = useWorkspaceFileManagerLayoutMode();
|
|
3022
3279
|
const rootView = useWorkspaceFileManagerRootView(session);
|
|
3023
3280
|
const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
|
|
3024
|
-
|
|
3281
|
+
useEffect5(() => {
|
|
3025
3282
|
function handleCopyShortcut(event) {
|
|
3026
3283
|
if (!(event.metaKey || event.ctrlKey) || event.key !== "c" || event.shiftKey) {
|
|
3027
3284
|
return;
|
|
@@ -3050,7 +3307,7 @@ function WorkspaceFileManager({
|
|
|
3050
3307
|
window.removeEventListener("keydown", handleCopyShortcut);
|
|
3051
3308
|
};
|
|
3052
3309
|
}, [onCopyEntry, panelsState, panelsView.selectedEntry, session]);
|
|
3053
|
-
|
|
3310
|
+
useEffect5(() => {
|
|
3054
3311
|
function handleRenameShortcut(event) {
|
|
3055
3312
|
if (event.key !== "Enter" || event.metaKey || event.ctrlKey || event.altKey) {
|
|
3056
3313
|
return;
|
|
@@ -3076,7 +3333,7 @@ function WorkspaceFileManager({
|
|
|
3076
3333
|
window.removeEventListener("keydown", handleRenameShortcut);
|
|
3077
3334
|
};
|
|
3078
3335
|
}, [panelsState, panelsView.selectedEntry, session]);
|
|
3079
|
-
|
|
3336
|
+
useEffect5(() => {
|
|
3080
3337
|
function resetDropOverlay() {
|
|
3081
3338
|
session.resetDragDepth();
|
|
3082
3339
|
}
|
|
@@ -3293,8 +3550,13 @@ function WorkspaceFileManagerPanelsContainer({
|
|
|
3293
3550
|
() => sortWorkspaceFileEntriesForArrangeMode(state.entries, arrangeMode),
|
|
3294
3551
|
[arrangeMode, state.entries]
|
|
3295
3552
|
);
|
|
3296
|
-
const
|
|
3553
|
+
const {
|
|
3554
|
+
iconUrlByCacheKey,
|
|
3555
|
+
reportEntryIconViewportEnter,
|
|
3556
|
+
reportEntryIconViewportLeave
|
|
3557
|
+
} = useWorkspaceFileEntryIconUrls({
|
|
3297
3558
|
entries: arrangedEntries,
|
|
3559
|
+
includeImageThumbnails: true,
|
|
3298
3560
|
resolveEntryIconUrl
|
|
3299
3561
|
});
|
|
3300
3562
|
return /* @__PURE__ */ jsx6(
|
|
@@ -3313,6 +3575,8 @@ function WorkspaceFileManagerPanelsContainer({
|
|
|
3313
3575
|
layoutMode,
|
|
3314
3576
|
pendingDirectoryPath: view.pendingDirectoryPath,
|
|
3315
3577
|
previewState: view.previewState,
|
|
3578
|
+
onEntryIconViewportEnter: reportEntryIconViewportEnter,
|
|
3579
|
+
onEntryIconViewportLeave: reportEntryIconViewportLeave,
|
|
3316
3580
|
selectedEntry: view.selectedEntry,
|
|
3317
3581
|
selectedPath: view.selectedPath,
|
|
3318
3582
|
showDropOverlay: view.showDropOverlay,
|
|
@@ -3426,11 +3690,11 @@ function WorkspaceFileManagerContextMenuContainer({
|
|
|
3426
3690
|
resolveOpenWithApplicationIcon,
|
|
3427
3691
|
session
|
|
3428
3692
|
}) {
|
|
3429
|
-
const contextMenuRef =
|
|
3693
|
+
const contextMenuRef = useRef5(null);
|
|
3430
3694
|
const { view } = useWorkspaceFileManagerContextMenuView(session);
|
|
3431
3695
|
const [openWithApplications, setOpenWithApplications] = useState7([]);
|
|
3432
3696
|
const [openWithLoading, setOpenWithLoading] = useState7(false);
|
|
3433
|
-
|
|
3697
|
+
useEffect5(() => {
|
|
3434
3698
|
const entry = view.contextMenu?.entry;
|
|
3435
3699
|
if (!entry || !view.showOpenWithAction) {
|
|
3436
3700
|
return;
|
|
@@ -3592,7 +3856,7 @@ function WorkspaceFileManagerContextMenuContainer({
|
|
|
3592
3856
|
}
|
|
3593
3857
|
function useCloseContextMenuOnOutsideInteraction(input) {
|
|
3594
3858
|
const { contextMenuRef, isOpen, session } = input;
|
|
3595
|
-
|
|
3859
|
+
useEffect5(() => {
|
|
3596
3860
|
if (!isOpen) {
|
|
3597
3861
|
return;
|
|
3598
3862
|
}
|