@tutti-os/workspace-file-manager 0.0.9 → 0.0.11
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-DXLDUMVG.js} +71 -2
- package/dist/chunk-DXLDUMVG.js.map +1 -0
- package/dist/index.js +343 -121
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +15 -2
- package/dist/services/index.js +11 -1
- package/package.json +4 -4
- package/dist/chunk-H3SDVOT7.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,18 +5,22 @@ 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-DXLDUMVG.js";
|
|
20
24
|
import {
|
|
21
25
|
createWorkspaceFileManagerI18nRuntime,
|
|
22
26
|
resolveRevealInFolderLabel,
|
|
@@ -25,7 +29,7 @@ import {
|
|
|
25
29
|
} from "./chunk-XOFPTWRV.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,82 @@ 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
|
+
if (records.some(
|
|
985
|
+
(record) => record.isIntersecting && record.intersectionRatio > 0
|
|
986
|
+
)) {
|
|
987
|
+
reportViewportEnter();
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
reportViewportLeave();
|
|
991
|
+
},
|
|
992
|
+
{ root: viewportRoot, rootMargin: "0px", threshold: 0 }
|
|
993
|
+
);
|
|
994
|
+
observer.observe(element);
|
|
995
|
+
if (isWorkspaceFileEntryIconVisible(element, viewportRoot)) {
|
|
996
|
+
reportViewportEnter();
|
|
997
|
+
} else {
|
|
998
|
+
reportViewportLeave();
|
|
999
|
+
}
|
|
1000
|
+
return () => {
|
|
1001
|
+
observer.disconnect();
|
|
1002
|
+
reportViewportLeave();
|
|
1003
|
+
};
|
|
1004
|
+
}, [cacheKey, entry, isEnteringDirectory, onViewportLeave, onViewportEnter]);
|
|
966
1005
|
return /* @__PURE__ */ jsx2(
|
|
967
1006
|
"span",
|
|
968
1007
|
{
|
|
1008
|
+
ref: frameRef,
|
|
969
1009
|
className: cn2(
|
|
970
1010
|
"grid flex-none place-items-center overflow-hidden",
|
|
971
1011
|
frameClassName,
|
|
@@ -990,6 +1030,19 @@ function WorkspaceFileEntryIcon({
|
|
|
990
1030
|
}
|
|
991
1031
|
);
|
|
992
1032
|
}
|
|
1033
|
+
function resolveWorkspaceFileEntryIconViewport(element) {
|
|
1034
|
+
return element.closest('[data-slot="scroll-area-viewport"]');
|
|
1035
|
+
}
|
|
1036
|
+
function isWorkspaceFileEntryIconVisible(element, viewportRoot) {
|
|
1037
|
+
const elementRect = element.getBoundingClientRect();
|
|
1038
|
+
const viewportRect = viewportRoot?.getBoundingClientRect() ?? {
|
|
1039
|
+
bottom: window.innerHeight,
|
|
1040
|
+
left: 0,
|
|
1041
|
+
right: window.innerWidth,
|
|
1042
|
+
top: 0
|
|
1043
|
+
};
|
|
1044
|
+
return elementRect.bottom > viewportRect.top && elementRect.top < viewportRect.bottom && elementRect.right > viewportRect.left && elementRect.left < viewportRect.right;
|
|
1045
|
+
}
|
|
993
1046
|
function DefaultEntryIcon({
|
|
994
1047
|
entry,
|
|
995
1048
|
iconClassName,
|
|
@@ -1076,6 +1129,8 @@ function WorkspaceFileManagerIconGrid({
|
|
|
1076
1129
|
moveDragPreviewEntryPath,
|
|
1077
1130
|
moveDragTargetEntryPath,
|
|
1078
1131
|
pendingDirectoryPath,
|
|
1132
|
+
onEntryIconViewportLeave,
|
|
1133
|
+
onEntryIconViewportEnter,
|
|
1079
1134
|
selectedPath,
|
|
1080
1135
|
onCancelInlineRename,
|
|
1081
1136
|
onClearInlineRenameValidation,
|
|
@@ -1107,6 +1162,8 @@ function WorkspaceFileManagerIconGrid({
|
|
|
1107
1162
|
moveDragActive,
|
|
1108
1163
|
moveDragSource: moveDragPreviewEntryPath === entry.path,
|
|
1109
1164
|
moveDragTarget: moveDragTargetEntryPath === entry.path,
|
|
1165
|
+
onEntryIconViewportLeave,
|
|
1166
|
+
onEntryIconViewportEnter,
|
|
1110
1167
|
selected: selectedPath === entry.path,
|
|
1111
1168
|
onCancelInlineRename,
|
|
1112
1169
|
onClearInlineRenameValidation,
|
|
@@ -1134,6 +1191,8 @@ function WorkspaceFileManagerIconTile({
|
|
|
1134
1191
|
moveDragActive,
|
|
1135
1192
|
moveDragSource,
|
|
1136
1193
|
moveDragTarget,
|
|
1194
|
+
onEntryIconViewportLeave,
|
|
1195
|
+
onEntryIconViewportEnter,
|
|
1137
1196
|
selected,
|
|
1138
1197
|
onCancelInlineRename,
|
|
1139
1198
|
onClearInlineRenameValidation,
|
|
@@ -1143,7 +1202,7 @@ function WorkspaceFileManagerIconTile({
|
|
|
1143
1202
|
onClick,
|
|
1144
1203
|
onPointerDown
|
|
1145
1204
|
}) {
|
|
1146
|
-
const buttonRef =
|
|
1205
|
+
const buttonRef = useRef3(null);
|
|
1147
1206
|
const nameParts = splitWorkspaceFileName(entry.name);
|
|
1148
1207
|
useLayoutEffect2(() => {
|
|
1149
1208
|
if (!selected) {
|
|
@@ -1169,7 +1228,9 @@ function WorkspaceFileManagerIconTile({
|
|
|
1169
1228
|
frameClassName: workspaceFileManagerIconGridFrameClassName(),
|
|
1170
1229
|
iconClassName: workspaceFileManagerIconGridIconClassName(),
|
|
1171
1230
|
iconUrlByCacheKey,
|
|
1172
|
-
isEnteringDirectory
|
|
1231
|
+
isEnteringDirectory,
|
|
1232
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
1233
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
1173
1234
|
}
|
|
1174
1235
|
);
|
|
1175
1236
|
if (isInlineRenaming) {
|
|
@@ -1243,7 +1304,7 @@ function IconTileRenameInput({
|
|
|
1243
1304
|
}) {
|
|
1244
1305
|
const nameParts = splitWorkspaceFileName(entry.name);
|
|
1245
1306
|
const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
|
|
1246
|
-
const inputRef =
|
|
1307
|
+
const inputRef = useRef3(null);
|
|
1247
1308
|
useLayoutEffect2(() => {
|
|
1248
1309
|
const input = inputRef.current;
|
|
1249
1310
|
if (!input) {
|
|
@@ -1448,6 +1509,8 @@ function WorkspaceFileManagerPanels({
|
|
|
1448
1509
|
layoutMode,
|
|
1449
1510
|
pendingDirectoryPath,
|
|
1450
1511
|
previewState,
|
|
1512
|
+
onEntryIconViewportLeave,
|
|
1513
|
+
onEntryIconViewportEnter,
|
|
1451
1514
|
selectedEntry,
|
|
1452
1515
|
selectedPath,
|
|
1453
1516
|
state,
|
|
@@ -1465,18 +1528,18 @@ function WorkspaceFileManagerPanels({
|
|
|
1465
1528
|
const { rootRef } = useWorkspaceFileManagerStackedLayout(
|
|
1466
1529
|
workspaceFileManagerStackedBreakpoint
|
|
1467
1530
|
);
|
|
1468
|
-
const previewResizeRef =
|
|
1531
|
+
const previewResizeRef = useRef4(null);
|
|
1469
1532
|
const [previewPaneWidth, setPreviewPaneWidth] = useState2(
|
|
1470
1533
|
workspaceFileManagerPreviewDefaultWidth
|
|
1471
1534
|
);
|
|
1472
1535
|
const [moveDragPreview, setMoveDragPreview] = useState2(null);
|
|
1473
|
-
const lastEntryClickRef =
|
|
1536
|
+
const lastEntryClickRef = useRef4(
|
|
1474
1537
|
null
|
|
1475
1538
|
);
|
|
1476
|
-
const moveDragAutoScrollRef =
|
|
1477
|
-
const moveDragRef =
|
|
1478
|
-
const suppressNextClickRef =
|
|
1479
|
-
const entriesRef =
|
|
1539
|
+
const moveDragAutoScrollRef = useRef4(null);
|
|
1540
|
+
const moveDragRef = useRef4(null);
|
|
1541
|
+
const suppressNextClickRef = useRef4(false);
|
|
1542
|
+
const entriesRef = useRef4(state.entries);
|
|
1480
1543
|
const nativeEntryDragEnabled = onEntryDragStart !== void 0 && (entryDragMode === "external" || entryDragMode === void 0 && !canMove);
|
|
1481
1544
|
const internalMoveEnabled = canMove && !nativeEntryDragEnabled;
|
|
1482
1545
|
const dateColumnLabel = resolveWorkspaceFileManagerDateColumnLabel(
|
|
@@ -1590,11 +1653,11 @@ function WorkspaceFileManagerPanels({
|
|
|
1590
1653
|
},
|
|
1591
1654
|
[entrySelectionEnabled, onOpenEntry, onSelect, shouldSuppressEntryClick]
|
|
1592
1655
|
);
|
|
1593
|
-
|
|
1656
|
+
useEffect3(() => {
|
|
1594
1657
|
lastEntryClickRef.current = null;
|
|
1595
1658
|
entriesRef.current = state.entries;
|
|
1596
1659
|
}, [state.entries]);
|
|
1597
|
-
|
|
1660
|
+
useEffect3(() => {
|
|
1598
1661
|
function handleDocumentPointerMove(event) {
|
|
1599
1662
|
const drag = moveDragRef.current;
|
|
1600
1663
|
if (!drag) {
|
|
@@ -1783,6 +1846,8 @@ function WorkspaceFileManagerPanels({
|
|
|
1783
1846
|
moveDragPreviewEntryPath: moveDragPreview?.entry.path ?? null,
|
|
1784
1847
|
moveDragTargetEntryPath: moveDragPreview?.targetDirectoryPath ?? null,
|
|
1785
1848
|
pendingDirectoryPath,
|
|
1849
|
+
onEntryIconViewportLeave,
|
|
1850
|
+
onEntryIconViewportEnter,
|
|
1786
1851
|
selectedPath,
|
|
1787
1852
|
onCancelInlineRename,
|
|
1788
1853
|
onClearInlineRenameValidation,
|
|
@@ -1842,6 +1907,8 @@ function WorkspaceFileManagerPanels({
|
|
|
1842
1907
|
moveDragTarget: moveDragPreview?.targetDirectoryPath === entry.path,
|
|
1843
1908
|
tableCellPaddingClassName,
|
|
1844
1909
|
selected: selectedPath === entry.path,
|
|
1910
|
+
onEntryIconViewportLeave,
|
|
1911
|
+
onEntryIconViewportEnter,
|
|
1845
1912
|
onCancelInlineRename,
|
|
1846
1913
|
onClearInlineRenameValidation,
|
|
1847
1914
|
onConfirmInlineRename,
|
|
@@ -1924,7 +1991,9 @@ function WorkspaceFileManagerPanels({
|
|
|
1924
1991
|
MoveDragPreview,
|
|
1925
1992
|
{
|
|
1926
1993
|
iconUrlByCacheKey,
|
|
1927
|
-
preview: moveDragPreview
|
|
1994
|
+
preview: moveDragPreview,
|
|
1995
|
+
onEntryIconViewportLeave,
|
|
1996
|
+
onEntryIconViewportEnter
|
|
1928
1997
|
}
|
|
1929
1998
|
) : null
|
|
1930
1999
|
]
|
|
@@ -1932,7 +2001,7 @@ function WorkspaceFileManagerPanels({
|
|
|
1932
2001
|
);
|
|
1933
2002
|
}
|
|
1934
2003
|
function useWorkspaceFileManagerStackedLayout(breakpoint) {
|
|
1935
|
-
const rootRef =
|
|
2004
|
+
const rootRef = useRef4(null);
|
|
1936
2005
|
const [isStacked, setIsStacked] = useState2(false);
|
|
1937
2006
|
useLayoutEffect3(() => {
|
|
1938
2007
|
const element = rootRef.current;
|
|
@@ -1991,6 +2060,8 @@ function EntryRow({
|
|
|
1991
2060
|
moveDragActive,
|
|
1992
2061
|
moveDragSource,
|
|
1993
2062
|
moveDragTarget,
|
|
2063
|
+
onEntryIconViewportLeave,
|
|
2064
|
+
onEntryIconViewportEnter,
|
|
1994
2065
|
selected,
|
|
1995
2066
|
tableCellPaddingClassName,
|
|
1996
2067
|
onCancelInlineRename,
|
|
@@ -2001,8 +2072,8 @@ function EntryRow({
|
|
|
2001
2072
|
onClick,
|
|
2002
2073
|
onPointerDown
|
|
2003
2074
|
}) {
|
|
2004
|
-
const buttonRowRef =
|
|
2005
|
-
const divRowRef =
|
|
2075
|
+
const buttonRowRef = useRef4(null);
|
|
2076
|
+
const divRowRef = useRef4(null);
|
|
2006
2077
|
useLayoutEffect3(() => {
|
|
2007
2078
|
if (!selected) {
|
|
2008
2079
|
return;
|
|
@@ -2042,6 +2113,8 @@ function EntryRow({
|
|
|
2042
2113
|
isEnteringDirectory,
|
|
2043
2114
|
isInlineRenaming,
|
|
2044
2115
|
isRenaming,
|
|
2116
|
+
onEntryIconViewportLeave,
|
|
2117
|
+
onEntryIconViewportEnter,
|
|
2045
2118
|
onCancelInlineRename,
|
|
2046
2119
|
onClearInlineRenameValidation,
|
|
2047
2120
|
onConfirmInlineRename
|
|
@@ -2106,7 +2179,9 @@ function EntryRow({
|
|
|
2106
2179
|
}
|
|
2107
2180
|
function MoveDragPreview({
|
|
2108
2181
|
iconUrlByCacheKey,
|
|
2109
|
-
preview
|
|
2182
|
+
preview,
|
|
2183
|
+
onEntryIconViewportLeave,
|
|
2184
|
+
onEntryIconViewportEnter
|
|
2110
2185
|
}) {
|
|
2111
2186
|
return /* @__PURE__ */ jsxs4(
|
|
2112
2187
|
"div",
|
|
@@ -2125,7 +2200,9 @@ function MoveDragPreview({
|
|
|
2125
2200
|
entry: preview.entry,
|
|
2126
2201
|
frameClassName: "size-[18px]",
|
|
2127
2202
|
iconClassName: "size-4",
|
|
2128
|
-
iconUrlByCacheKey
|
|
2203
|
+
iconUrlByCacheKey,
|
|
2204
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
2205
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
2129
2206
|
}
|
|
2130
2207
|
),
|
|
2131
2208
|
/* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: preview.entry.name })
|
|
@@ -2176,6 +2253,8 @@ function EntryNameCell({
|
|
|
2176
2253
|
isEnteringDirectory = false,
|
|
2177
2254
|
isInlineRenaming = false,
|
|
2178
2255
|
isRenaming = false,
|
|
2256
|
+
onEntryIconViewportLeave,
|
|
2257
|
+
onEntryIconViewportEnter,
|
|
2179
2258
|
onCancelInlineRename,
|
|
2180
2259
|
onClearInlineRenameValidation,
|
|
2181
2260
|
onConfirmInlineRename
|
|
@@ -2183,9 +2262,9 @@ function EntryNameCell({
|
|
|
2183
2262
|
const nameParts = splitWorkspaceFileName(entry.name);
|
|
2184
2263
|
const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
|
|
2185
2264
|
const [name, setName] = useState2(entry.name);
|
|
2186
|
-
const inputRef =
|
|
2187
|
-
const skipBlurRef =
|
|
2188
|
-
|
|
2265
|
+
const inputRef = useRef4(null);
|
|
2266
|
+
const skipBlurRef = useRef4(false);
|
|
2267
|
+
useEffect3(() => {
|
|
2189
2268
|
if (!isInlineRenaming) {
|
|
2190
2269
|
return;
|
|
2191
2270
|
}
|
|
@@ -2222,7 +2301,9 @@ function EntryNameCell({
|
|
|
2222
2301
|
entry,
|
|
2223
2302
|
frameClassName: "size-[18px]",
|
|
2224
2303
|
iconClassName: "size-4",
|
|
2225
|
-
iconUrlByCacheKey
|
|
2304
|
+
iconUrlByCacheKey,
|
|
2305
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
2306
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
2226
2307
|
}
|
|
2227
2308
|
),
|
|
2228
2309
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
|
|
@@ -2285,7 +2366,9 @@ function EntryNameCell({
|
|
|
2285
2366
|
frameClassName: "size-[18px]",
|
|
2286
2367
|
iconClassName: "size-4",
|
|
2287
2368
|
iconUrlByCacheKey,
|
|
2288
|
-
isEnteringDirectory
|
|
2369
|
+
isEnteringDirectory,
|
|
2370
|
+
onViewportLeave: onEntryIconViewportLeave,
|
|
2371
|
+
onViewportEnter: onEntryIconViewportEnter
|
|
2289
2372
|
}
|
|
2290
2373
|
),
|
|
2291
2374
|
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
|
|
@@ -2762,74 +2845,207 @@ function useWorkspaceFileManagerLayoutMode() {
|
|
|
2762
2845
|
}
|
|
2763
2846
|
|
|
2764
2847
|
// src/ui/useWorkspaceFileEntryIconUrls.ts
|
|
2765
|
-
import { useEffect as
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
function
|
|
2770
|
-
const
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
);
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2848
|
+
import { useCallback as useCallback5, useEffect as useEffect4, useMemo, useState as useState6 } from "react";
|
|
2849
|
+
|
|
2850
|
+
// src/ui/workspaceFileEntryIconUrlQueue.ts
|
|
2851
|
+
var defaultMaxConcurrentIconRequests = 3;
|
|
2852
|
+
function createWorkspaceFileEntryIconUrlQueue(options) {
|
|
2853
|
+
const maxConcurrent = Math.max(
|
|
2854
|
+
1,
|
|
2855
|
+
Math.floor(options.maxConcurrent ?? defaultMaxConcurrentIconRequests)
|
|
2856
|
+
);
|
|
2857
|
+
const iconUrlByCacheKey = /* @__PURE__ */ new Map();
|
|
2858
|
+
const inFlightCacheKeys = /* @__PURE__ */ new Set();
|
|
2859
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2860
|
+
const queuedEntries = /* @__PURE__ */ new Map();
|
|
2861
|
+
const visibleReferenceCountByCacheKey = /* @__PURE__ */ new Map();
|
|
2862
|
+
let activeCount = 0;
|
|
2863
|
+
let disposed = false;
|
|
2864
|
+
let retainedCacheKeys = null;
|
|
2865
|
+
function publish() {
|
|
2866
|
+
for (const listener of listeners) {
|
|
2867
|
+
listener();
|
|
2782
2868
|
}
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2869
|
+
}
|
|
2870
|
+
function isRetained(cacheKey) {
|
|
2871
|
+
return retainedCacheKeys === null || retainedCacheKeys.has(cacheKey);
|
|
2872
|
+
}
|
|
2873
|
+
function isVisible(cacheKey) {
|
|
2874
|
+
return (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) > 0;
|
|
2875
|
+
}
|
|
2876
|
+
function retainVisibleReference(cacheKey) {
|
|
2877
|
+
visibleReferenceCountByCacheKey.set(
|
|
2878
|
+
cacheKey,
|
|
2879
|
+
(visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) + 1
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
function releaseVisibleReference(cacheKey) {
|
|
2883
|
+
const nextCount = (visibleReferenceCountByCacheKey.get(cacheKey) ?? 0) - 1;
|
|
2884
|
+
if (nextCount > 0) {
|
|
2885
|
+
visibleReferenceCountByCacheKey.set(cacheKey, nextCount);
|
|
2886
|
+
return false;
|
|
2887
|
+
}
|
|
2888
|
+
visibleReferenceCountByCacheKey.delete(cacheKey);
|
|
2889
|
+
queuedEntries.delete(cacheKey);
|
|
2890
|
+
const hadCachedIcon = iconUrlByCacheKey.delete(cacheKey);
|
|
2891
|
+
if (hadCachedIcon) {
|
|
2892
|
+
publish();
|
|
2893
|
+
}
|
|
2894
|
+
return true;
|
|
2895
|
+
}
|
|
2896
|
+
function drain() {
|
|
2897
|
+
if (disposed || !options.resolveEntryIconUrl) {
|
|
2788
2898
|
return;
|
|
2789
2899
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2900
|
+
while (activeCount < maxConcurrent && queuedEntries.size > 0) {
|
|
2901
|
+
const next = queuedEntries.entries().next().value;
|
|
2902
|
+
if (!next) {
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
const [cacheKey, entry] = next;
|
|
2906
|
+
queuedEntries.delete(cacheKey);
|
|
2907
|
+
if (!isVisible(cacheKey) || iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey)) {
|
|
2908
|
+
continue;
|
|
2909
|
+
}
|
|
2910
|
+
activeCount += 1;
|
|
2911
|
+
inFlightCacheKeys.add(cacheKey);
|
|
2912
|
+
void options.resolveEntryIconUrl(entry).then((iconUrl) => {
|
|
2913
|
+
if (!disposed && isRetained(cacheKey) && isVisible(cacheKey)) {
|
|
2914
|
+
iconUrlByCacheKey.set(cacheKey, iconUrl?.trim() || null);
|
|
2915
|
+
publish();
|
|
2799
2916
|
}
|
|
2800
|
-
})
|
|
2801
|
-
|
|
2802
|
-
|
|
2917
|
+
}).catch(() => {
|
|
2918
|
+
if (!disposed && isRetained(cacheKey) && isVisible(cacheKey)) {
|
|
2919
|
+
iconUrlByCacheKey.set(cacheKey, null);
|
|
2920
|
+
publish();
|
|
2921
|
+
}
|
|
2922
|
+
}).finally(() => {
|
|
2923
|
+
activeCount -= 1;
|
|
2924
|
+
inFlightCacheKeys.delete(cacheKey);
|
|
2925
|
+
drain();
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
return {
|
|
2930
|
+
dispose() {
|
|
2931
|
+
disposed = true;
|
|
2932
|
+
queuedEntries.clear();
|
|
2933
|
+
visibleReferenceCountByCacheKey.clear();
|
|
2934
|
+
listeners.clear();
|
|
2935
|
+
},
|
|
2936
|
+
leaveViewport(entry) {
|
|
2937
|
+
if (disposed || !shouldResolveWorkspaceFileEntryIcon(entry)) {
|
|
2803
2938
|
return;
|
|
2804
2939
|
}
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2940
|
+
releaseVisibleReference(resolveWorkspaceFileEntryIconCacheKey(entry));
|
|
2941
|
+
},
|
|
2942
|
+
enterViewport(entry) {
|
|
2943
|
+
if (disposed || !options.resolveEntryIconUrl || !shouldResolveWorkspaceFileEntryIcon(entry)) {
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
|
|
2947
|
+
retainVisibleReference(cacheKey);
|
|
2948
|
+
if (iconUrlByCacheKey.has(cacheKey) || inFlightCacheKeys.has(cacheKey) || queuedEntries.has(cacheKey)) {
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
queuedEntries.set(cacheKey, entry);
|
|
2952
|
+
drain();
|
|
2953
|
+
},
|
|
2954
|
+
retainEntries(entries) {
|
|
2955
|
+
retainedCacheKeys = new Set(
|
|
2956
|
+
entries.filter(shouldResolveWorkspaceFileEntryIcon).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry))
|
|
2957
|
+
);
|
|
2958
|
+
let changed = false;
|
|
2959
|
+
for (const cacheKey of iconUrlByCacheKey.keys()) {
|
|
2960
|
+
if (!isRetained(cacheKey)) {
|
|
2961
|
+
iconUrlByCacheKey.delete(cacheKey);
|
|
2962
|
+
changed = true;
|
|
2963
|
+
continue;
|
|
2814
2964
|
}
|
|
2815
|
-
if (
|
|
2965
|
+
if (iconUrlByCacheKey.get(cacheKey) === null) {
|
|
2966
|
+
iconUrlByCacheKey.delete(cacheKey);
|
|
2816
2967
|
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
2968
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2969
|
+
}
|
|
2970
|
+
for (const cacheKey of queuedEntries.keys()) {
|
|
2971
|
+
if (!isRetained(cacheKey)) {
|
|
2972
|
+
queuedEntries.delete(cacheKey);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
for (const cacheKey of visibleReferenceCountByCacheKey.keys()) {
|
|
2976
|
+
if (!isRetained(cacheKey)) {
|
|
2977
|
+
visibleReferenceCountByCacheKey.delete(cacheKey);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
if (changed) {
|
|
2981
|
+
publish();
|
|
2982
|
+
}
|
|
2983
|
+
},
|
|
2984
|
+
snapshot() {
|
|
2985
|
+
return new Map(iconUrlByCacheKey);
|
|
2986
|
+
},
|
|
2987
|
+
subscribe(listener) {
|
|
2988
|
+
if (disposed) {
|
|
2989
|
+
return () => {
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
listeners.add(listener);
|
|
2993
|
+
return () => {
|
|
2994
|
+
listeners.delete(listener);
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
// src/ui/useWorkspaceFileEntryIconUrls.ts
|
|
3001
|
+
function buildIconTargetSignature(entries) {
|
|
3002
|
+
return entries.filter(shouldResolveWorkspaceFileEntryIcon).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
|
|
3003
|
+
}
|
|
3004
|
+
function useWorkspaceFileEntryIconUrls(input) {
|
|
3005
|
+
const { entries, resolveEntryIconUrl } = input;
|
|
3006
|
+
const queue = useMemo(
|
|
3007
|
+
() => createWorkspaceFileEntryIconUrlQueue({
|
|
3008
|
+
resolveEntryIconUrl
|
|
3009
|
+
}),
|
|
3010
|
+
[resolveEntryIconUrl]
|
|
3011
|
+
);
|
|
3012
|
+
const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState6(() => queue.snapshot());
|
|
3013
|
+
const iconTargetSignature = useMemo(
|
|
3014
|
+
() => buildIconTargetSignature(entries),
|
|
3015
|
+
[entries]
|
|
3016
|
+
);
|
|
3017
|
+
useEffect4(() => {
|
|
3018
|
+
queue.retainEntries(entries);
|
|
3019
|
+
}, [entries, iconTargetSignature, queue]);
|
|
3020
|
+
useEffect4(() => {
|
|
3021
|
+
setIconUrlByCacheKey(queue.snapshot());
|
|
3022
|
+
return queue.subscribe(() => {
|
|
3023
|
+
setIconUrlByCacheKey(queue.snapshot());
|
|
2827
3024
|
});
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
3025
|
+
}, [queue]);
|
|
3026
|
+
useEffect4(
|
|
3027
|
+
() => () => {
|
|
3028
|
+
queue.dispose();
|
|
3029
|
+
},
|
|
3030
|
+
[queue]
|
|
3031
|
+
);
|
|
3032
|
+
const reportEntryIconViewportEnter = useCallback5(
|
|
3033
|
+
(entry) => {
|
|
3034
|
+
queue.enterViewport(entry);
|
|
3035
|
+
},
|
|
3036
|
+
[queue]
|
|
3037
|
+
);
|
|
3038
|
+
const reportEntryIconViewportLeave = useCallback5(
|
|
3039
|
+
(entry) => {
|
|
3040
|
+
queue.leaveViewport(entry);
|
|
3041
|
+
},
|
|
3042
|
+
[queue]
|
|
3043
|
+
);
|
|
3044
|
+
return {
|
|
3045
|
+
iconUrlByCacheKey,
|
|
3046
|
+
reportEntryIconViewportEnter,
|
|
3047
|
+
reportEntryIconViewportLeave
|
|
3048
|
+
};
|
|
2833
3049
|
}
|
|
2834
3050
|
|
|
2835
3051
|
// src/ui/workspaceFileManagerAnalytics.ts
|
|
@@ -3016,12 +3232,12 @@ function WorkspaceFileManager({
|
|
|
3016
3232
|
session,
|
|
3017
3233
|
surface = "card"
|
|
3018
3234
|
}) {
|
|
3019
|
-
const rootRef =
|
|
3235
|
+
const rootRef = useRef5(null);
|
|
3020
3236
|
const { arrangeMode, setArrangeMode } = useWorkspaceFileManagerArrangeMode();
|
|
3021
3237
|
const { layoutMode, setLayoutMode } = useWorkspaceFileManagerLayoutMode();
|
|
3022
3238
|
const rootView = useWorkspaceFileManagerRootView(session);
|
|
3023
3239
|
const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
|
|
3024
|
-
|
|
3240
|
+
useEffect5(() => {
|
|
3025
3241
|
function handleCopyShortcut(event) {
|
|
3026
3242
|
if (!(event.metaKey || event.ctrlKey) || event.key !== "c" || event.shiftKey) {
|
|
3027
3243
|
return;
|
|
@@ -3050,7 +3266,7 @@ function WorkspaceFileManager({
|
|
|
3050
3266
|
window.removeEventListener("keydown", handleCopyShortcut);
|
|
3051
3267
|
};
|
|
3052
3268
|
}, [onCopyEntry, panelsState, panelsView.selectedEntry, session]);
|
|
3053
|
-
|
|
3269
|
+
useEffect5(() => {
|
|
3054
3270
|
function handleRenameShortcut(event) {
|
|
3055
3271
|
if (event.key !== "Enter" || event.metaKey || event.ctrlKey || event.altKey) {
|
|
3056
3272
|
return;
|
|
@@ -3076,7 +3292,7 @@ function WorkspaceFileManager({
|
|
|
3076
3292
|
window.removeEventListener("keydown", handleRenameShortcut);
|
|
3077
3293
|
};
|
|
3078
3294
|
}, [panelsState, panelsView.selectedEntry, session]);
|
|
3079
|
-
|
|
3295
|
+
useEffect5(() => {
|
|
3080
3296
|
function resetDropOverlay() {
|
|
3081
3297
|
session.resetDragDepth();
|
|
3082
3298
|
}
|
|
@@ -3293,7 +3509,11 @@ function WorkspaceFileManagerPanelsContainer({
|
|
|
3293
3509
|
() => sortWorkspaceFileEntriesForArrangeMode(state.entries, arrangeMode),
|
|
3294
3510
|
[arrangeMode, state.entries]
|
|
3295
3511
|
);
|
|
3296
|
-
const
|
|
3512
|
+
const {
|
|
3513
|
+
iconUrlByCacheKey,
|
|
3514
|
+
reportEntryIconViewportEnter,
|
|
3515
|
+
reportEntryIconViewportLeave
|
|
3516
|
+
} = useWorkspaceFileEntryIconUrls({
|
|
3297
3517
|
entries: arrangedEntries,
|
|
3298
3518
|
resolveEntryIconUrl
|
|
3299
3519
|
});
|
|
@@ -3313,6 +3533,8 @@ function WorkspaceFileManagerPanelsContainer({
|
|
|
3313
3533
|
layoutMode,
|
|
3314
3534
|
pendingDirectoryPath: view.pendingDirectoryPath,
|
|
3315
3535
|
previewState: view.previewState,
|
|
3536
|
+
onEntryIconViewportEnter: reportEntryIconViewportEnter,
|
|
3537
|
+
onEntryIconViewportLeave: reportEntryIconViewportLeave,
|
|
3316
3538
|
selectedEntry: view.selectedEntry,
|
|
3317
3539
|
selectedPath: view.selectedPath,
|
|
3318
3540
|
showDropOverlay: view.showDropOverlay,
|
|
@@ -3426,11 +3648,11 @@ function WorkspaceFileManagerContextMenuContainer({
|
|
|
3426
3648
|
resolveOpenWithApplicationIcon,
|
|
3427
3649
|
session
|
|
3428
3650
|
}) {
|
|
3429
|
-
const contextMenuRef =
|
|
3651
|
+
const contextMenuRef = useRef5(null);
|
|
3430
3652
|
const { view } = useWorkspaceFileManagerContextMenuView(session);
|
|
3431
3653
|
const [openWithApplications, setOpenWithApplications] = useState7([]);
|
|
3432
3654
|
const [openWithLoading, setOpenWithLoading] = useState7(false);
|
|
3433
|
-
|
|
3655
|
+
useEffect5(() => {
|
|
3434
3656
|
const entry = view.contextMenu?.entry;
|
|
3435
3657
|
if (!entry || !view.showOpenWithAction) {
|
|
3436
3658
|
return;
|
|
@@ -3592,7 +3814,7 @@ function WorkspaceFileManagerContextMenuContainer({
|
|
|
3592
3814
|
}
|
|
3593
3815
|
function useCloseContextMenuOnOutsideInteraction(input) {
|
|
3594
3816
|
const { contextMenuRef, isOpen, session } = input;
|
|
3595
|
-
|
|
3817
|
+
useEffect5(() => {
|
|
3596
3818
|
if (!isOpen) {
|
|
3597
3819
|
return;
|
|
3598
3820
|
}
|