@tutti-os/workspace-file-manager 0.0.2 → 0.0.4

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
@@ -22,11 +22,11 @@ import {
22
22
  resolveRevealInFolderLabel,
23
23
  workspaceFileManagerI18nNamespace,
24
24
  workspaceFileManagerI18nResources
25
- } from "./chunk-2AG4ND4D.js";
25
+ } from "./chunk-P2UOYMTS.js";
26
26
 
27
27
  // src/ui/WorkspaceFileManager.tsx
28
- import { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
29
- import { cn as cn4 } from "@tutti-os/ui-system";
28
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState6 } from "react";
29
+ import { cn as cn6 } from "@tutti-os/ui-system";
30
30
 
31
31
  // src/ui/WorkspaceFileManagerMenus.tsx
32
32
  import {
@@ -385,7 +385,7 @@ function ContextMenuActionButton({
385
385
  "button",
386
386
  {
387
387
  className: cn(
388
- "flex h-8 w-full items-center gap-2 rounded-md border-0 bg-transparent px-2 text-left text-sm transition-colors disabled:cursor-default disabled:opacity-50",
388
+ "flex h-8 w-full items-center gap-2 rounded-md border-0 bg-transparent px-2 text-left text-[13px] transition-colors disabled:cursor-default disabled:opacity-50",
389
389
  danger ? "text-[var(--state-danger)] hover:bg-[var(--on-danger)]" : "text-[var(--text-primary)] hover:bg-transparency-block"
390
390
  ),
391
391
  disabled,
@@ -490,7 +490,7 @@ function OpenWithMenuItem({
490
490
  "aria-expanded": open,
491
491
  "aria-haspopup": "menu",
492
492
  className: cn(
493
- "flex h-8 w-full items-center gap-2 rounded-md border-0 bg-transparent px-2 text-left text-sm text-[var(--text-primary)] transition-colors hover:bg-transparency-block disabled:cursor-default disabled:opacity-50",
493
+ "flex h-8 w-full items-center gap-2 rounded-md border-0 bg-transparent px-2 text-left text-[13px] text-[var(--text-primary)] transition-colors hover:bg-transparency-block disabled:cursor-default disabled:opacity-50",
494
494
  open && "bg-transparency-block"
495
495
  ),
496
496
  disabled: busy,
@@ -555,7 +555,7 @@ function OpenWithMenuItem({
555
555
  }
556
556
  ) : null,
557
557
  showExternalSection ? /* @__PURE__ */ jsx(ContextMenuDivider, {}) : null,
558
- isLoading ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs text-[var(--text-tertiary)]", children: copy.t("openWithLoadingLabel") }) : null,
558
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-[11px] text-[var(--text-tertiary)]", children: copy.t("openWithLoadingLabel") }) : null,
559
559
  applications.map((application) => {
560
560
  const resolvedIcon = resolveOpenWithApplicationIcon?.(application);
561
561
  return /* @__PURE__ */ jsx(
@@ -651,7 +651,7 @@ function WorkspaceFileManagerCreateDialog({
651
651
  }
652
652
  }
653
653
  ),
654
- dialog.errorMessage ? /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--state-danger)]", children: dialog.errorMessage }) : null,
654
+ dialog.errorMessage ? /* @__PURE__ */ jsx("p", { className: "text-[13px] text-[var(--state-danger)]", children: dialog.errorMessage }) : null,
655
655
  /* @__PURE__ */ jsxs(DialogFooter, { children: [
656
656
  /* @__PURE__ */ jsx(
657
657
  Button,
@@ -815,10 +815,10 @@ function WorkspaceFileManagerImportConflictDialog({
815
815
  /* @__PURE__ */ jsx("div", { className: "max-h-60 overflow-auto rounded-lg border border-[var(--border-1)] bg-transparency-block", children: /* @__PURE__ */ jsx("div", { className: "divide-y divide-[var(--border-1)]", children: dialog.conflicts.map((conflict) => /* @__PURE__ */ jsxs(
816
816
  "div",
817
817
  {
818
- className: "flex flex-col gap-1 px-4 py-3 text-sm",
818
+ className: "flex flex-col gap-1 px-4 py-3 text-[13px]",
819
819
  children: [
820
820
  /* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: conflict.name }),
821
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-[var(--text-secondary)]", children: [
821
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-[var(--text-secondary)]", children: [
822
822
  copy.t("importConflictReviewLabel"),
823
823
  ":",
824
824
  " ",
@@ -872,7 +872,7 @@ function ImportConflictSummary({
872
872
  if (summaryItems.length === 0) {
873
873
  return null;
874
874
  }
875
- return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 text-xs text-[var(--text-secondary)]", children: summaryItems.map((item) => /* @__PURE__ */ jsx(
875
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 text-[11px] text-[var(--text-secondary)]", children: summaryItems.map((item) => /* @__PURE__ */ jsx(
876
876
  "span",
877
877
  {
878
878
  className: "rounded-md border border-[var(--border-1)] px-2 py-1",
@@ -894,24 +894,375 @@ function importSummaryReasonCopyKey(reason) {
894
894
 
895
895
  // src/ui/WorkspaceFileManagerPanels.tsx
896
896
  import {
897
- FileCodeIcon,
898
- FileTextIcon,
899
- FolderFilledIcon,
900
- ImageFileIcon,
901
- LoadingIcon,
897
+ FileCodeIcon as FileCodeIcon2,
898
+ FileTextIcon as FileTextIcon2,
899
+ FolderFilledIcon as FolderFilledIcon2,
900
+ ImageFileIcon as ImageFileIcon2,
901
+ LoadingIcon as LoadingIcon2,
902
902
  ScrollArea,
903
- VideoFileIcon,
904
- cn as cn2
903
+ VideoFileIcon as VideoFileIcon2,
904
+ cn as cn4
905
905
  } from "@tutti-os/ui-system";
906
906
  import { WorkspaceFilePreviewSurface as SharedWorkspaceFilePreviewSurface } from "@tutti-os/workspace-file-preview/react";
907
907
  import {
908
908
  useCallback as useCallback2,
909
909
  useEffect as useEffect2,
910
- useLayoutEffect as useLayoutEffect2,
911
- useRef as useRef2,
910
+ useLayoutEffect as useLayoutEffect3,
911
+ useRef as useRef3,
912
912
  useState as useState2
913
913
  } from "react";
914
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
914
+
915
+ // src/ui/WorkspaceFileManagerIconGrid.tsx
916
+ import { cn as cn3 } from "@tutti-os/ui-system";
917
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
918
+
919
+ // src/ui/WorkspaceFileEntryIcon.tsx
920
+ import {
921
+ FileCodeIcon,
922
+ FileTextIcon,
923
+ FolderFilledIcon,
924
+ ImageFileIcon,
925
+ LoadingIcon,
926
+ VideoFileIcon,
927
+ cn as cn2
928
+ } from "@tutti-os/ui-system";
929
+
930
+ // src/ui/workspaceFileEntryIconPolicy.ts
931
+ function shouldResolveWorkspaceFileEntryIcon(entry) {
932
+ if (isWorkspaceApplicationBundle(entry)) {
933
+ return true;
934
+ }
935
+ return entry.kind === "file";
936
+ }
937
+ function isWorkspaceApplicationBundle(entry) {
938
+ return entry.name.trim().toLowerCase().endsWith(".app");
939
+ }
940
+ function resolveWorkspaceFileEntryIconCacheKey(entry) {
941
+ return `${entry.path}:${entry.mtimeMs ?? 0}`;
942
+ }
943
+
944
+ // src/ui/WorkspaceFileEntryIcon.tsx
945
+ import { jsx as jsx2 } from "react/jsx-runtime";
946
+ function WorkspaceFileEntryIcon({
947
+ entry,
948
+ frameClassName,
949
+ iconClassName = "size-4",
950
+ iconUrlByCacheKey,
951
+ isEnteringDirectory = false
952
+ }) {
953
+ const visualKind = resolveWorkspaceFileVisualKind(entry);
954
+ const isAppBundle = isWorkspaceApplicationBundle(entry);
955
+ const iconUrl = iconUrlByCacheKey?.get(resolveWorkspaceFileEntryIconCacheKey(entry)) ?? null;
956
+ return /* @__PURE__ */ jsx2(
957
+ "span",
958
+ {
959
+ className: cn2(
960
+ "grid flex-none place-items-center overflow-hidden",
961
+ frameClassName,
962
+ isEnteringDirectory ? "text-[var(--text-tertiary)]" : entryIconColorClassName(visualKind, isAppBundle)
963
+ ),
964
+ children: isEnteringDirectory ? /* @__PURE__ */ jsx2(LoadingIcon, { className: iconClassName + " animate-spin" }) : iconUrl ? /* @__PURE__ */ jsx2(
965
+ "img",
966
+ {
967
+ alt: "",
968
+ className: cn2(iconClassName, "rounded-[4px] object-contain"),
969
+ draggable: false,
970
+ src: iconUrl
971
+ }
972
+ ) : /* @__PURE__ */ jsx2(
973
+ DefaultEntryIcon,
974
+ {
975
+ entry,
976
+ iconClassName,
977
+ visualKind
978
+ }
979
+ )
980
+ }
981
+ );
982
+ }
983
+ function DefaultEntryIcon({
984
+ entry,
985
+ iconClassName,
986
+ visualKind
987
+ }) {
988
+ if (isWorkspaceApplicationBundle(entry)) {
989
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
990
+ }
991
+ switch (visualKind) {
992
+ case "directory":
993
+ return /* @__PURE__ */ jsx2(FolderFilledIcon, { className: iconClassName });
994
+ case "image":
995
+ return /* @__PURE__ */ jsx2(ImageFileIcon, { className: iconClassName });
996
+ case "video":
997
+ return /* @__PURE__ */ jsx2(VideoFileIcon, { className: iconClassName });
998
+ case "markdown":
999
+ case "document":
1000
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1001
+ case "code":
1002
+ return /* @__PURE__ */ jsx2(FileCodeIcon, { className: iconClassName });
1003
+ case "binary":
1004
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1005
+ default:
1006
+ return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1007
+ }
1008
+ }
1009
+ function entryIconColorClassName(visualKind, isAppBundle) {
1010
+ if (isAppBundle) {
1011
+ return "text-[var(--text-tertiary)]";
1012
+ }
1013
+ return visualKind === "directory" ? "text-[var(--rich-text-mention-file)]" : "text-[var(--text-tertiary)]";
1014
+ }
1015
+
1016
+ // src/ui/workspaceFileManagerIconGridLayout.ts
1017
+ var workspaceFileManagerIconGridIconSizePx = 52;
1018
+ var workspaceFileManagerIconGridTileMinWidthPx = 108;
1019
+ var workspaceFileManagerIconGridTileMaxWidthPx = 120;
1020
+ var workspaceFileManagerIconGridLayout = {
1021
+ iconSizePx: workspaceFileManagerIconGridIconSizePx,
1022
+ tileMaxWidthPx: workspaceFileManagerIconGridTileMaxWidthPx,
1023
+ tileMinWidthPx: workspaceFileManagerIconGridTileMinWidthPx
1024
+ };
1025
+ function workspaceFileManagerIconGridIconClassName() {
1026
+ return "size-[52px]";
1027
+ }
1028
+ function workspaceFileManagerIconGridFrameClassName() {
1029
+ return "size-[60px]";
1030
+ }
1031
+
1032
+ // src/ui/WorkspaceFileManagerIconGrid.tsx
1033
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1034
+ function WorkspaceFileManagerIconGrid({
1035
+ contextMenuEntryPath,
1036
+ copy,
1037
+ draggable,
1038
+ entries,
1039
+ iconUrlByCacheKey,
1040
+ inlineRenameEntryPath,
1041
+ inlineRenameValidation,
1042
+ isRenaming,
1043
+ moveDragActive,
1044
+ moveDragPreviewEntryPath,
1045
+ moveDragTargetEntryPath,
1046
+ pendingDirectoryPath,
1047
+ selectedPath,
1048
+ onCancelInlineRename,
1049
+ onClearInlineRenameValidation,
1050
+ onConfirmInlineRename,
1051
+ onContextMenu,
1052
+ onDragStart,
1053
+ onEntryClick,
1054
+ onEntryPointerDown
1055
+ }) {
1056
+ return /* @__PURE__ */ jsx3(
1057
+ "div",
1058
+ {
1059
+ className: "grid auto-rows-min content-start items-start gap-x-2 gap-y-6 px-4 py-4",
1060
+ style: {
1061
+ gridTemplateColumns: `repeat(auto-fill, minmax(${workspaceFileManagerIconGridLayout.tileMinWidthPx}px, 1fr))`
1062
+ },
1063
+ children: entries.map((entry) => /* @__PURE__ */ jsx3(
1064
+ WorkspaceFileManagerIconTile,
1065
+ {
1066
+ contextMenuActive: contextMenuEntryPath === entry.path,
1067
+ copy,
1068
+ draggable,
1069
+ entry,
1070
+ iconUrlByCacheKey,
1071
+ inlineRenameValidation: inlineRenameEntryPath === entry.path ? inlineRenameValidation : null,
1072
+ isEnteringDirectory: pendingDirectoryPath === entry.path,
1073
+ isInlineRenaming: inlineRenameEntryPath === entry.path,
1074
+ isRenaming,
1075
+ moveDragActive,
1076
+ moveDragSource: moveDragPreviewEntryPath === entry.path,
1077
+ moveDragTarget: moveDragTargetEntryPath === entry.path,
1078
+ selected: selectedPath === entry.path,
1079
+ onCancelInlineRename,
1080
+ onClearInlineRenameValidation,
1081
+ onConfirmInlineRename,
1082
+ onContextMenu,
1083
+ onDragStart,
1084
+ onClick: onEntryClick,
1085
+ onPointerDown: onEntryPointerDown
1086
+ },
1087
+ entry.path
1088
+ ))
1089
+ }
1090
+ );
1091
+ }
1092
+ function WorkspaceFileManagerIconTile({
1093
+ contextMenuActive,
1094
+ copy,
1095
+ draggable,
1096
+ entry,
1097
+ iconUrlByCacheKey,
1098
+ inlineRenameValidation,
1099
+ isEnteringDirectory,
1100
+ isInlineRenaming,
1101
+ isRenaming,
1102
+ moveDragActive,
1103
+ moveDragSource,
1104
+ moveDragTarget,
1105
+ selected,
1106
+ onCancelInlineRename,
1107
+ onClearInlineRenameValidation,
1108
+ onConfirmInlineRename,
1109
+ onContextMenu,
1110
+ onDragStart,
1111
+ onClick,
1112
+ onPointerDown
1113
+ }) {
1114
+ const buttonRef = useRef2(null);
1115
+ const nameParts = splitWorkspaceFileName(entry.name);
1116
+ useLayoutEffect2(() => {
1117
+ if (!selected) {
1118
+ return;
1119
+ }
1120
+ buttonRef.current?.scrollIntoView({
1121
+ block: "nearest",
1122
+ inline: "nearest"
1123
+ });
1124
+ }, [selected]);
1125
+ const tileClassName = cn3(
1126
+ "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",
1127
+ isInlineRenaming ? "cursor-default" : "cursor-pointer hover:bg-transparency-block",
1128
+ moveDragActive && "cursor-grabbing",
1129
+ selected || contextMenuActive || isInlineRenaming ? "border-[var(--border-focus)] bg-transparency-block text-[var(--text-primary)]" : "text-[var(--text-secondary)]",
1130
+ moveDragSource && "opacity-55",
1131
+ moveDragTarget && "bg-[var(--accent-bg)] text-[var(--text-primary)] outline outline-1 -outline-offset-1 outline-[var(--border-focus)]"
1132
+ );
1133
+ const iconGraphic = /* @__PURE__ */ jsx3(
1134
+ WorkspaceFileEntryIcon,
1135
+ {
1136
+ entry,
1137
+ frameClassName: workspaceFileManagerIconGridFrameClassName(),
1138
+ iconClassName: workspaceFileManagerIconGridIconClassName(),
1139
+ iconUrlByCacheKey,
1140
+ isEnteringDirectory
1141
+ }
1142
+ );
1143
+ if (isInlineRenaming) {
1144
+ return /* @__PURE__ */ jsxs2(
1145
+ "div",
1146
+ {
1147
+ "aria-label": entry.name,
1148
+ className: tileClassName,
1149
+ "data-workspace-file-entry-path": entry.path,
1150
+ children: [
1151
+ iconGraphic,
1152
+ /* @__PURE__ */ jsx3(
1153
+ IconTileRenameInput,
1154
+ {
1155
+ copy,
1156
+ entry,
1157
+ inlineRenameValidation,
1158
+ isRenaming,
1159
+ onCancelInlineRename,
1160
+ onClearInlineRenameValidation,
1161
+ onConfirmInlineRename
1162
+ }
1163
+ )
1164
+ ]
1165
+ }
1166
+ );
1167
+ }
1168
+ return /* @__PURE__ */ jsxs2(
1169
+ "button",
1170
+ {
1171
+ "aria-label": entry.name,
1172
+ className: tileClassName,
1173
+ "data-workspace-file-entry-path": entry.path,
1174
+ draggable,
1175
+ ref: buttonRef,
1176
+ type: "button",
1177
+ onClick: () => {
1178
+ onClick(entry);
1179
+ },
1180
+ onContextMenu: (event) => {
1181
+ onContextMenu(event, entry);
1182
+ },
1183
+ onDragStart: (event) => {
1184
+ if (!draggable) {
1185
+ event.preventDefault();
1186
+ return;
1187
+ }
1188
+ onDragStart?.(entry, event.dataTransfer);
1189
+ },
1190
+ onPointerDown: (event) => {
1191
+ onPointerDown(entry, event);
1192
+ },
1193
+ children: [
1194
+ iconGraphic,
1195
+ /* @__PURE__ */ jsxs2("span", { className: "line-clamp-2 w-full break-all text-[13px] leading-[18px] text-[var(--text-primary)]", children: [
1196
+ /* @__PURE__ */ jsx3("span", { children: nameParts.start }),
1197
+ nameParts.end ? /* @__PURE__ */ jsx3("span", { children: nameParts.end }) : null
1198
+ ] })
1199
+ ]
1200
+ }
1201
+ );
1202
+ }
1203
+ function IconTileRenameInput({
1204
+ copy,
1205
+ entry,
1206
+ inlineRenameValidation,
1207
+ isRenaming,
1208
+ onCancelInlineRename,
1209
+ onClearInlineRenameValidation,
1210
+ onConfirmInlineRename
1211
+ }) {
1212
+ const nameParts = splitWorkspaceFileName(entry.name);
1213
+ const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
1214
+ const inputRef = useRef2(null);
1215
+ useLayoutEffect2(() => {
1216
+ const input = inputRef.current;
1217
+ if (!input) {
1218
+ return;
1219
+ }
1220
+ input.focus();
1221
+ if (hasFileExtension) {
1222
+ input.setSelectionRange(0, nameParts.start.length);
1223
+ } else {
1224
+ input.select();
1225
+ }
1226
+ }, [hasFileExtension, nameParts.start.length]);
1227
+ const validationMessage = inlineRenameValidation === "required" ? copy.t("createNameRequired") : inlineRenameValidation === "invalid" ? copy.t("createNameInvalid") : null;
1228
+ return /* @__PURE__ */ jsxs2("span", { className: "flex w-full min-w-0 flex-col gap-0.5", children: [
1229
+ /* @__PURE__ */ jsx3(
1230
+ "input",
1231
+ {
1232
+ "aria-invalid": inlineRenameValidation !== null,
1233
+ "aria-label": copy.t("renameLabel"),
1234
+ className: cn3(
1235
+ "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",
1236
+ inlineRenameValidation !== null && "border-[var(--state-danger)]"
1237
+ ),
1238
+ defaultValue: entry.name,
1239
+ disabled: isRenaming,
1240
+ ref: inputRef,
1241
+ onBlur: (event) => {
1242
+ void onConfirmInlineRename(event.currentTarget.value);
1243
+ },
1244
+ onChange: () => {
1245
+ onClearInlineRenameValidation();
1246
+ },
1247
+ onKeyDown: (event) => {
1248
+ if (event.key === "Enter") {
1249
+ event.preventDefault();
1250
+ void onConfirmInlineRename(event.currentTarget.value);
1251
+ return;
1252
+ }
1253
+ if (event.key === "Escape") {
1254
+ event.preventDefault();
1255
+ onCancelInlineRename();
1256
+ }
1257
+ }
1258
+ }
1259
+ ),
1260
+ validationMessage ? /* @__PURE__ */ jsx3("span", { className: "text-[10px] leading-3 text-[var(--state-danger)]", children: validationMessage }) : null
1261
+ ] });
1262
+ }
1263
+
1264
+ // src/ui/WorkspaceFileManagerPanels.tsx
1265
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
915
1266
  var workspaceFileManagerTableGridClassName = "grid-cols-[minmax(0,_1fr)_148px_96px]";
916
1267
  var workspaceFileManagerTableGridStyle = {
917
1268
  gridTemplateColumns: "minmax(0, 1fr) 148px 96px"
@@ -939,9 +1290,11 @@ function WorkspaceFileManagerPanels({
939
1290
  entryDragMode,
940
1291
  entrySelectionEnabled = true,
941
1292
  copy,
1293
+ iconUrlByCacheKey,
942
1294
  inlineRenameEntryPath,
943
1295
  inlineRenameValidation,
944
1296
  isRenaming,
1297
+ layoutMode,
945
1298
  pendingDirectoryPath,
946
1299
  previewState,
947
1300
  selectedEntry,
@@ -958,21 +1311,21 @@ function WorkspaceFileManagerPanels({
958
1311
  onOpenEntry,
959
1312
  onSelect
960
1313
  }) {
961
- const { isStacked, rootRef } = useWorkspaceFileManagerStackedLayout(
1314
+ const { rootRef } = useWorkspaceFileManagerStackedLayout(
962
1315
  workspaceFileManagerStackedBreakpoint
963
1316
  );
964
- const previewResizeRef = useRef2(null);
1317
+ const previewResizeRef = useRef3(null);
965
1318
  const [previewPaneWidth, setPreviewPaneWidth] = useState2(
966
1319
  workspaceFileManagerPreviewDefaultWidth
967
1320
  );
968
1321
  const [moveDragPreview, setMoveDragPreview] = useState2(null);
969
- const lastEntryClickRef = useRef2(
1322
+ const lastEntryClickRef = useRef3(
970
1323
  null
971
1324
  );
972
- const moveDragAutoScrollRef = useRef2(null);
973
- const moveDragRef = useRef2(null);
974
- const suppressNextClickRef = useRef2(false);
975
- const entriesRef = useRef2(state.entries);
1325
+ const moveDragAutoScrollRef = useRef3(null);
1326
+ const moveDragRef = useRef3(null);
1327
+ const suppressNextClickRef = useRef3(false);
1328
+ const entriesRef = useRef3(state.entries);
976
1329
  const nativeEntryDragEnabled = onEntryDragStart !== void 0 && (entryDragMode === "external" || entryDragMode === void 0 && !canMove);
977
1330
  const internalMoveEnabled = canMove && !nativeEntryDragEnabled;
978
1331
  const stopMoveDragAutoScroll = useCallback2(() => {
@@ -1162,9 +1515,11 @@ function WorkspaceFileManagerPanels({
1162
1515
  stopMoveDragAutoScroll,
1163
1516
  updateMoveDragAutoScroll
1164
1517
  ]);
1165
- const previewPaneClassName = isStacked ? "min-w-0 border-t border-[var(--border-1)]" : "min-w-[220px] border-l border-[var(--border-1)]";
1166
- const tableGridClassName = isStacked ? workspaceFileManagerCompactTableGridClassName : workspaceFileManagerTableGridClassName;
1167
- const tableGridStyle = isStacked ? workspaceFileManagerCompactTableGridStyle : workspaceFileManagerTableGridStyle;
1518
+ const showPreviewPanel = false;
1519
+ const useStackedPreview = false;
1520
+ const previewPaneClassName = useStackedPreview ? "min-w-0 border-t border-[var(--border-1)]" : "min-w-[220px] border-l border-[var(--border-1)]";
1521
+ const tableGridClassName = useStackedPreview ? workspaceFileManagerCompactTableGridClassName : workspaceFileManagerTableGridClassName;
1522
+ const tableGridStyle = useStackedPreview ? workspaceFileManagerCompactTableGridStyle : workspaceFileManagerTableGridStyle;
1168
1523
  const tableCellPaddingClassName = "px-4";
1169
1524
  const clampPreviewPaneWidth = useCallback2(
1170
1525
  (width, maxWidth) => {
@@ -1176,9 +1531,9 @@ function WorkspaceFileManagerPanels({
1176
1531
  },
1177
1532
  []
1178
1533
  );
1179
- useLayoutEffect2(() => {
1534
+ useLayoutEffect3(() => {
1180
1535
  const element = rootRef.current;
1181
- if (!element || isStacked) {
1536
+ if (!element || useStackedPreview || !showPreviewPanel) {
1182
1537
  return;
1183
1538
  }
1184
1539
  const publishLayout = () => {
@@ -1203,7 +1558,7 @@ function WorkspaceFileManagerPanels({
1203
1558
  return () => {
1204
1559
  observer.disconnect();
1205
1560
  };
1206
- }, [clampPreviewPaneWidth, isStacked, rootRef]);
1561
+ }, [clampPreviewPaneWidth, rootRef, showPreviewPanel, useStackedPreview]);
1207
1562
  const handlePreviewResizePointerDown = useCallback2(
1208
1563
  (event) => {
1209
1564
  if (event.button !== 0) {
@@ -1250,49 +1605,74 @@ function WorkspaceFileManagerPanels({
1250
1605
  },
1251
1606
  []
1252
1607
  );
1253
- const tablePanel = /* @__PURE__ */ jsx2(
1608
+ const tablePanel = /* @__PURE__ */ jsx4(
1254
1609
  "div",
1255
1610
  {
1256
- className: cn2(
1611
+ className: cn4(
1257
1612
  "flex min-h-0 min-w-0 flex-col overflow-hidden",
1258
- isStacked ? "h-[44%] max-h-[48%] flex-none" : "flex-1"
1613
+ useStackedPreview ? "h-[44%] max-h-[48%] flex-none" : "flex-1"
1259
1614
  ),
1260
1615
  onContextMenu: handleTablePanelContextMenu,
1261
- children: state.error ? /* @__PURE__ */ jsx2(FeedbackState, { message: state.error }) : state.isLoading && state.entries.length === 0 ? /* @__PURE__ */ jsx2(FeedbackState, { message: copy.t("loading") }) : state.entries.length === 0 ? /* @__PURE__ */ jsx2(FeedbackState, { message: copy.t("emptyDirectory") }) : /* @__PURE__ */ jsx2(ScrollArea, { className: "min-h-0 flex-1 [&_[data-orientation=vertical][data-slot=scroll-area-scrollbar]]:opacity-100", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col", children: [
1262
- /* @__PURE__ */ jsxs2(
1616
+ children: state.error ? /* @__PURE__ */ jsx4(FeedbackState, { message: state.error }) : state.isLoading && state.entries.length === 0 ? /* @__PURE__ */ jsx4(FeedbackState, { message: copy.t("loading") }) : state.entries.length === 0 ? /* @__PURE__ */ jsx4(FeedbackState, { message: copy.t("emptyDirectory") }) : /* @__PURE__ */ jsx4(ScrollArea, { className: "min-h-0 flex-1 [&_[data-orientation=vertical][data-slot=scroll-area-scrollbar]]:opacity-100", children: layoutMode === "icon" ? /* @__PURE__ */ jsx4(
1617
+ WorkspaceFileManagerIconGrid,
1618
+ {
1619
+ contextMenuEntryPath,
1620
+ copy,
1621
+ draggable: nativeEntryDragEnabled,
1622
+ entries: state.entries,
1623
+ iconUrlByCacheKey,
1624
+ inlineRenameEntryPath,
1625
+ inlineRenameValidation,
1626
+ isRenaming,
1627
+ moveDragActive: moveDragPreview !== null,
1628
+ moveDragPreviewEntryPath: moveDragPreview?.entry.path ?? null,
1629
+ moveDragTargetEntryPath: moveDragPreview?.targetDirectoryPath ?? null,
1630
+ pendingDirectoryPath,
1631
+ selectedPath,
1632
+ onCancelInlineRename,
1633
+ onClearInlineRenameValidation,
1634
+ onConfirmInlineRename,
1635
+ onContextMenu: onEntryContextMenu,
1636
+ onDragStart: onEntryDragStart,
1637
+ onEntryClick: handleEntryClick,
1638
+ onEntryPointerDown: handleEntryPointerDown
1639
+ }
1640
+ ) : /* @__PURE__ */ jsxs3("div", { className: "flex flex-col", children: [
1641
+ /* @__PURE__ */ jsxs3(
1263
1642
  "div",
1264
1643
  {
1265
- className: cn2(
1644
+ className: cn4(
1266
1645
  "grid h-9 min-h-9 items-center gap-x-6 border-b border-[var(--border-1)] text-xs font-normal text-[var(--text-secondary)]",
1267
1646
  tableGridClassName
1268
1647
  ),
1269
1648
  style: tableGridStyle,
1270
1649
  children: [
1271
- /* @__PURE__ */ jsx2("span", { className: tableCellPaddingClassName, children: copy.t("nameLabel") }),
1272
- /* @__PURE__ */ jsx2(
1650
+ /* @__PURE__ */ jsx4("span", { className: tableCellPaddingClassName, children: copy.t("nameLabel") }),
1651
+ /* @__PURE__ */ jsx4(
1273
1652
  "span",
1274
1653
  {
1275
- className: cn2("whitespace-nowrap", tableCellPaddingClassName),
1654
+ className: cn4("whitespace-nowrap", tableCellPaddingClassName),
1276
1655
  children: copy.t("modifiedLabel")
1277
1656
  }
1278
1657
  ),
1279
- /* @__PURE__ */ jsx2(
1658
+ /* @__PURE__ */ jsx4(
1280
1659
  "span",
1281
1660
  {
1282
- className: cn2("whitespace-nowrap", tableCellPaddingClassName),
1661
+ className: cn4("whitespace-nowrap", tableCellPaddingClassName),
1283
1662
  children: copy.t("sizeLabel")
1284
1663
  }
1285
1664
  )
1286
1665
  ]
1287
1666
  }
1288
1667
  ),
1289
- state.entries.map((entry) => /* @__PURE__ */ jsx2(
1668
+ state.entries.map((entry) => /* @__PURE__ */ jsx4(
1290
1669
  EntryRow,
1291
1670
  {
1292
1671
  contextMenuActive: contextMenuEntryPath === entry.path,
1293
1672
  copy,
1294
1673
  dateLocale,
1295
1674
  entry,
1675
+ iconUrlByCacheKey,
1296
1676
  canMove: internalMoveEnabled,
1297
1677
  draggable: nativeEntryDragEnabled,
1298
1678
  gridClassName: tableGridClassName,
@@ -1319,16 +1699,16 @@ function WorkspaceFileManagerPanels({
1319
1699
  ] }) })
1320
1700
  }
1321
1701
  );
1322
- const previewPanel = /* @__PURE__ */ jsx2(
1702
+ const previewPanel = showPreviewPanel ? /* @__PURE__ */ jsx4(
1323
1703
  "aside",
1324
1704
  {
1325
- className: cn2(
1705
+ className: cn4(
1326
1706
  "relative flex h-full min-h-0 flex-col gap-[14px] overflow-auto p-4",
1327
- isStacked ? "max-h-[44%] flex-none" : "flex-none",
1707
+ useStackedPreview ? "max-h-[44%] flex-none" : "flex-none",
1328
1708
  previewPaneClassName
1329
1709
  ),
1330
- style: isStacked ? void 0 : { width: previewPaneWidth },
1331
- children: /* @__PURE__ */ jsx2(
1710
+ style: useStackedPreview ? void 0 : { width: previewPaneWidth },
1711
+ children: /* @__PURE__ */ jsx4(
1332
1712
  PreviewPane,
1333
1713
  {
1334
1714
  copy,
@@ -1338,23 +1718,23 @@ function WorkspaceFileManagerPanels({
1338
1718
  }
1339
1719
  )
1340
1720
  }
1341
- );
1342
- return /* @__PURE__ */ jsxs2(
1721
+ ) : null;
1722
+ return /* @__PURE__ */ jsxs3(
1343
1723
  "div",
1344
1724
  {
1345
1725
  className: "relative min-h-0 min-w-0 flex-1 overflow-hidden",
1346
1726
  ref: rootRef,
1347
1727
  children: [
1348
- /* @__PURE__ */ jsxs2(
1728
+ /* @__PURE__ */ jsxs3(
1349
1729
  "div",
1350
1730
  {
1351
- className: cn2(
1731
+ className: cn4(
1352
1732
  "h-full min-h-0 min-w-0 bg-transparent",
1353
- isStacked ? "flex flex-col gap-3" : "flex"
1733
+ useStackedPreview ? "flex flex-col gap-3" : "flex"
1354
1734
  ),
1355
1735
  children: [
1356
1736
  tablePanel,
1357
- isStacked ? null : /* @__PURE__ */ jsx2(
1737
+ showPreviewPanel && !useStackedPreview ? /* @__PURE__ */ jsx4(
1358
1738
  "div",
1359
1739
  {
1360
1740
  "aria-orientation": "vertical",
@@ -1368,31 +1748,37 @@ function WorkspaceFileManagerPanels({
1368
1748
  onPointerMove: handlePreviewResizePointerMove,
1369
1749
  onPointerUp: handlePreviewResizePointerEnd
1370
1750
  }
1371
- ),
1751
+ ) : null,
1372
1752
  previewPanel
1373
1753
  ]
1374
1754
  }
1375
1755
  ),
1376
- /* @__PURE__ */ jsx2(
1756
+ /* @__PURE__ */ jsx4(
1377
1757
  "div",
1378
1758
  {
1379
1759
  "aria-hidden": "true",
1380
- className: cn2(
1760
+ className: cn4(
1381
1761
  "pointer-events-none absolute inset-0 grid place-items-center rounded-[var(--workbench-window-radius,8px)] border border-dashed border-[var(--border-focus)] bg-[var(--accent-bg)] opacity-0 transition-opacity duration-150 ease-out",
1382
1762
  showDropOverlay && "opacity-100"
1383
1763
  ),
1384
- children: /* @__PURE__ */ jsx2("div", { className: "rounded-lg border border-[var(--border-1)] bg-[var(--background-fronted)] px-5 py-3 text-sm font-normal text-[var(--text-primary)] shadow-panel", children: copy.t("dropToImportLabel") })
1764
+ children: /* @__PURE__ */ jsx4("div", { className: "rounded-lg border border-[var(--border-1)] bg-[var(--background-fronted)] px-5 py-3 text-sm font-normal text-[var(--text-primary)] shadow-panel", children: copy.t("dropToImportLabel") })
1385
1765
  }
1386
1766
  ),
1387
- moveDragPreview ? /* @__PURE__ */ jsx2(MoveDragPreview, { preview: moveDragPreview }) : null
1767
+ moveDragPreview ? /* @__PURE__ */ jsx4(
1768
+ MoveDragPreview,
1769
+ {
1770
+ iconUrlByCacheKey,
1771
+ preview: moveDragPreview
1772
+ }
1773
+ ) : null
1388
1774
  ]
1389
1775
  }
1390
1776
  );
1391
1777
  }
1392
1778
  function useWorkspaceFileManagerStackedLayout(breakpoint) {
1393
- const rootRef = useRef2(null);
1779
+ const rootRef = useRef3(null);
1394
1780
  const [isStacked, setIsStacked] = useState2(false);
1395
- useLayoutEffect2(() => {
1781
+ useLayoutEffect3(() => {
1396
1782
  const element = rootRef.current;
1397
1783
  if (!element) {
1398
1784
  return;
@@ -1427,6 +1813,7 @@ function EntryRow({
1427
1813
  entry,
1428
1814
  gridClassName,
1429
1815
  gridStyle,
1816
+ iconUrlByCacheKey,
1430
1817
  inlineRenameValidation,
1431
1818
  isEnteringDirectory,
1432
1819
  isInlineRenaming,
@@ -1444,9 +1831,9 @@ function EntryRow({
1444
1831
  onClick,
1445
1832
  onPointerDown
1446
1833
  }) {
1447
- const buttonRowRef = useRef2(null);
1448
- const divRowRef = useRef2(null);
1449
- useLayoutEffect2(() => {
1834
+ const buttonRowRef = useRef3(null);
1835
+ const divRowRef = useRef3(null);
1836
+ useLayoutEffect3(() => {
1450
1837
  if (!selected) {
1451
1838
  return;
1452
1839
  }
@@ -1455,7 +1842,7 @@ function EntryRow({
1455
1842
  inline: "nearest"
1456
1843
  });
1457
1844
  }, [selected]);
1458
- const rowClassName = cn2(
1845
+ const rowClassName = cn4(
1459
1846
  "grid min-h-10 w-full items-center gap-x-6 border-b border-[var(--border-1)] px-0 text-left transition-colors",
1460
1847
  gridClassName,
1461
1848
  isInlineRenaming ? "cursor-default" : "cursor-pointer hover:bg-transparency-block",
@@ -1475,11 +1862,12 @@ function EntryRow({
1475
1862
  onContextMenu(event, entry);
1476
1863
  }
1477
1864
  };
1478
- const nameCell = /* @__PURE__ */ jsx2("span", { className: tableCellPaddingClassName, children: /* @__PURE__ */ jsx2(
1865
+ const nameCell = /* @__PURE__ */ jsx4("span", { className: tableCellPaddingClassName, children: /* @__PURE__ */ jsx4(
1479
1866
  EntryNameCell,
1480
1867
  {
1481
1868
  copy,
1482
1869
  entry,
1870
+ iconUrlByCacheKey,
1483
1871
  inlineRenameValidation,
1484
1872
  isEnteringDirectory,
1485
1873
  isInlineRenaming,
@@ -1489,20 +1877,20 @@ function EntryRow({
1489
1877
  onConfirmInlineRename
1490
1878
  }
1491
1879
  ) });
1492
- const modifiedCell = /* @__PURE__ */ jsx2(
1880
+ const modifiedCell = /* @__PURE__ */ jsx4(
1493
1881
  "span",
1494
1882
  {
1495
- className: cn2(
1883
+ className: cn4(
1496
1884
  "truncate text-xs text-[var(--text-secondary)]",
1497
1885
  tableCellPaddingClassName
1498
1886
  ),
1499
1887
  children: formatWorkspaceFileModifiedTime(entry.mtimeMs, dateLocale)
1500
1888
  }
1501
1889
  );
1502
- const sizeCell = /* @__PURE__ */ jsx2(
1890
+ const sizeCell = /* @__PURE__ */ jsx4(
1503
1891
  "span",
1504
1892
  {
1505
- className: cn2(
1893
+ className: cn4(
1506
1894
  "truncate text-xs text-[var(--text-secondary)]",
1507
1895
  tableCellPaddingClassName
1508
1896
  ),
@@ -1510,13 +1898,13 @@ function EntryRow({
1510
1898
  }
1511
1899
  );
1512
1900
  if (isInlineRenaming) {
1513
- return /* @__PURE__ */ jsxs2("div", { ...rowProps, ref: divRowRef, children: [
1901
+ return /* @__PURE__ */ jsxs3("div", { ...rowProps, ref: divRowRef, children: [
1514
1902
  nameCell,
1515
1903
  modifiedCell,
1516
1904
  sizeCell
1517
1905
  ] });
1518
1906
  }
1519
- return /* @__PURE__ */ jsxs2(
1907
+ return /* @__PURE__ */ jsxs3(
1520
1908
  "button",
1521
1909
  {
1522
1910
  ...rowProps,
@@ -1544,10 +1932,10 @@ function EntryRow({
1544
1932
  );
1545
1933
  }
1546
1934
  function MoveDragPreview({
1935
+ iconUrlByCacheKey,
1547
1936
  preview
1548
1937
  }) {
1549
- const visualKind = resolveWorkspaceFileVisualKind(preview.entry);
1550
- return /* @__PURE__ */ jsxs2(
1938
+ return /* @__PURE__ */ jsxs3(
1551
1939
  "div",
1552
1940
  {
1553
1941
  "aria-hidden": "true",
@@ -1558,17 +1946,16 @@ function MoveDragPreview({
1558
1946
  transform: "translate(12px, 12px)"
1559
1947
  },
1560
1948
  children: [
1561
- /* @__PURE__ */ jsx2(
1562
- "span",
1949
+ /* @__PURE__ */ jsx4(
1950
+ WorkspaceFileEntryIcon,
1563
1951
  {
1564
- className: cn2(
1565
- "grid size-[18px] flex-none place-items-center",
1566
- entryIconColorClassName(visualKind)
1567
- ),
1568
- children: /* @__PURE__ */ jsx2(EntryIcon, { visualKind })
1952
+ entry: preview.entry,
1953
+ frameClassName: "size-[18px]",
1954
+ iconClassName: "size-4",
1955
+ iconUrlByCacheKey
1569
1956
  }
1570
1957
  ),
1571
- /* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate", children: preview.entry.name })
1958
+ /* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: preview.entry.name })
1572
1959
  ]
1573
1960
  }
1574
1961
  );
@@ -1611,6 +1998,7 @@ function canMoveEntryToDirectory(movedEntry, targetEntry) {
1611
1998
  function EntryNameCell({
1612
1999
  copy,
1613
2000
  entry,
2001
+ iconUrlByCacheKey,
1614
2002
  inlineRenameValidation = null,
1615
2003
  isEnteringDirectory = false,
1616
2004
  isInlineRenaming = false,
@@ -1619,12 +2007,11 @@ function EntryNameCell({
1619
2007
  onClearInlineRenameValidation,
1620
2008
  onConfirmInlineRename
1621
2009
  }) {
1622
- const visualKind = resolveWorkspaceFileVisualKind(entry);
1623
2010
  const nameParts = splitWorkspaceFileName(entry.name);
1624
2011
  const hasFileExtension = nameParts.end.length > 0 && nameParts.end.startsWith(".");
1625
2012
  const [name, setName] = useState2(entry.name);
1626
- const inputRef = useRef2(null);
1627
- const skipBlurRef = useRef2(false);
2013
+ const inputRef = useRef3(null);
2014
+ const skipBlurRef = useRef3(false);
1628
2015
  useEffect2(() => {
1629
2016
  if (!isInlineRenaming) {
1630
2017
  return;
@@ -1632,7 +2019,7 @@ function EntryNameCell({
1632
2019
  setName(entry.name);
1633
2020
  skipBlurRef.current = false;
1634
2021
  }, [entry.name, entry.path, isInlineRenaming]);
1635
- useLayoutEffect2(() => {
2022
+ useLayoutEffect3(() => {
1636
2023
  if (!isInlineRenaming) {
1637
2024
  return;
1638
2025
  }
@@ -1655,24 +2042,23 @@ function EntryNameCell({
1655
2042
  }
1656
2043
  }, [name, onConfirmInlineRename]);
1657
2044
  if (isInlineRenaming) {
1658
- return /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-2", children: [
1659
- /* @__PURE__ */ jsx2(
1660
- "span",
2045
+ return /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 items-center gap-2", children: [
2046
+ /* @__PURE__ */ jsx4(
2047
+ WorkspaceFileEntryIcon,
1661
2048
  {
1662
- className: cn2(
1663
- "grid size-[18px] flex-none place-items-center",
1664
- entryIconColorClassName(visualKind)
1665
- ),
1666
- children: /* @__PURE__ */ jsx2(EntryIcon, { visualKind })
2049
+ entry,
2050
+ frameClassName: "size-[18px]",
2051
+ iconClassName: "size-4",
2052
+ iconUrlByCacheKey
1667
2053
  }
1668
2054
  ),
1669
- /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
1670
- /* @__PURE__ */ jsx2(
2055
+ /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2056
+ /* @__PURE__ */ jsx4(
1671
2057
  "input",
1672
2058
  {
1673
2059
  "aria-invalid": inlineRenameValidation !== null,
1674
2060
  "aria-label": copy.t("renameLabel"),
1675
- className: cn2(
2061
+ className: cn4(
1676
2062
  "min-w-0 max-w-full rounded-md border border-transparent bg-[var(--transparency-block)] px-1.5 py-0.5 text-sm text-[var(--text-primary)] outline-none transition-colors duration-200 selection:bg-[var(--transparency-active)] selection:text-[var(--text-primary)] hover:bg-[var(--transparency-hover)] focus:bg-[var(--transparency-hover)] focus-visible:border-transparent focus-visible:bg-[var(--transparency-hover)] disabled:bg-[var(--transparency-block)] disabled:text-[var(--text-disabled)] disabled:opacity-100",
1677
2063
  inlineRenameValidation !== null && "border-[var(--state-danger)]"
1678
2064
  ),
@@ -1714,50 +2100,47 @@ function EntryNameCell({
1714
2100
  }
1715
2101
  }
1716
2102
  ),
1717
- validationMessage ? /* @__PURE__ */ jsx2("span", { className: "text-xs text-[var(--state-danger)]", children: validationMessage }) : null
2103
+ validationMessage ? /* @__PURE__ */ jsx4("span", { className: "text-xs text-[var(--state-danger)]", children: validationMessage }) : null
1718
2104
  ] })
1719
2105
  ] });
1720
2106
  }
1721
- return /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-2", children: [
1722
- /* @__PURE__ */ jsx2(
1723
- "span",
2107
+ return /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 items-center gap-2", children: [
2108
+ /* @__PURE__ */ jsx4(
2109
+ WorkspaceFileEntryIcon,
1724
2110
  {
1725
- className: cn2(
1726
- "grid size-[18px] flex-none place-items-center",
1727
- isEnteringDirectory ? "text-[var(--text-tertiary)]" : entryIconColorClassName(visualKind)
1728
- ),
1729
- children: isEnteringDirectory ? /* @__PURE__ */ jsx2(LoadingIcon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx2(EntryIcon, { visualKind })
2111
+ entry,
2112
+ frameClassName: "size-[18px]",
2113
+ iconClassName: "size-4",
2114
+ iconUrlByCacheKey,
2115
+ isEnteringDirectory
1730
2116
  }
1731
2117
  ),
1732
- /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
1733
- /* @__PURE__ */ jsx2("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: nameParts.start }),
1734
- nameParts.end ? /* @__PURE__ */ jsx2("span", { className: "flex-none overflow-hidden text-ellipsis", children: nameParts.end }) : null
2118
+ /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
2119
+ /* @__PURE__ */ jsx4("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: nameParts.start }),
2120
+ nameParts.end ? /* @__PURE__ */ jsx4("span", { className: "flex-none overflow-hidden text-ellipsis", children: nameParts.end }) : null
1735
2121
  ] })
1736
2122
  ] });
1737
2123
  }
1738
- function entryIconColorClassName(visualKind) {
1739
- return visualKind === "directory" ? "text-[var(--rich-text-mention-file)]" : "text-[var(--text-tertiary)]";
1740
- }
1741
2124
  function EntryIcon({
1742
2125
  className = "size-4",
1743
2126
  visualKind
1744
2127
  }) {
1745
2128
  switch (visualKind) {
1746
2129
  case "directory":
1747
- return /* @__PURE__ */ jsx2(FolderFilledIcon, { className });
2130
+ return /* @__PURE__ */ jsx4(FolderFilledIcon2, { className });
1748
2131
  case "image":
1749
- return /* @__PURE__ */ jsx2(ImageFileIcon, { className });
2132
+ return /* @__PURE__ */ jsx4(ImageFileIcon2, { className });
1750
2133
  case "video":
1751
- return /* @__PURE__ */ jsx2(VideoFileIcon, { className });
2134
+ return /* @__PURE__ */ jsx4(VideoFileIcon2, { className });
1752
2135
  case "markdown":
1753
2136
  case "document":
1754
- return /* @__PURE__ */ jsx2(FileTextIcon, { className });
2137
+ return /* @__PURE__ */ jsx4(FileTextIcon2, { className });
1755
2138
  case "code":
1756
- return /* @__PURE__ */ jsx2(FileCodeIcon, { className });
2139
+ return /* @__PURE__ */ jsx4(FileCodeIcon2, { className });
1757
2140
  case "binary":
1758
- return /* @__PURE__ */ jsx2(FileTextIcon, { className });
2141
+ return /* @__PURE__ */ jsx4(FileTextIcon2, { className });
1759
2142
  default:
1760
- return /* @__PURE__ */ jsx2(FileTextIcon, { className });
2143
+ return /* @__PURE__ */ jsx4(FileTextIcon2, { className });
1761
2144
  }
1762
2145
  }
1763
2146
  function PreviewPane({
@@ -1767,24 +2150,24 @@ function PreviewPane({
1767
2150
  previewState
1768
2151
  }) {
1769
2152
  if (!entry || previewState.status === "empty") {
1770
- return /* @__PURE__ */ jsx2("div", { className: "grid h-full min-h-[180px] place-items-center overflow-hidden p-8 text-center text-sm leading-5 text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx2("span", { className: "max-w-[24ch] [overflow-wrap:anywhere]", children: copy.t("previewEmptyLabel") }) });
2153
+ return /* @__PURE__ */ jsx4("div", { className: "grid h-full min-h-[180px] place-items-center overflow-hidden p-8 text-center text-sm leading-5 text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx4("span", { className: "max-w-[24ch] [overflow-wrap:anywhere]", children: copy.t("previewEmptyLabel") }) });
1771
2154
  }
1772
- return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1773
- /* @__PURE__ */ jsx2(PreviewSurface, { copy, previewState }),
1774
- /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-[14px]", children: [
1775
- /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col gap-[3px]", children: [
1776
- /* @__PURE__ */ jsx2("strong", { className: "min-w-0 truncate text-[15px] font-semibold text-[var(--text-primary)]", children: entry.name }),
1777
- /* @__PURE__ */ jsx2("p", { className: "min-w-0 truncate text-xs text-[var(--text-secondary)]", children: entry.path })
2155
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
2156
+ /* @__PURE__ */ jsx4(PreviewSurface, { copy, previewState }),
2157
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-col gap-[14px]", children: [
2158
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-col gap-[3px]", children: [
2159
+ /* @__PURE__ */ jsx4("strong", { className: "min-w-0 truncate text-[15px] font-semibold text-[var(--text-primary)]", children: entry.name }),
2160
+ /* @__PURE__ */ jsx4("p", { className: "min-w-0 truncate text-xs text-[var(--text-secondary)]", children: entry.path })
1778
2161
  ] }),
1779
- /* @__PURE__ */ jsxs2("dl", { className: "border-t border-[var(--border-1)]", children: [
1780
- /* @__PURE__ */ jsx2(
2162
+ /* @__PURE__ */ jsxs3("dl", { className: "border-t border-[var(--border-1)]", children: [
2163
+ /* @__PURE__ */ jsx4(
1781
2164
  PreviewDetail,
1782
2165
  {
1783
2166
  label: copy.t("modifiedLabel"),
1784
2167
  value: formatWorkspaceFileModifiedTime(entry.mtimeMs, dateLocale)
1785
2168
  }
1786
2169
  ),
1787
- /* @__PURE__ */ jsx2(
2170
+ /* @__PURE__ */ jsx4(
1788
2171
  PreviewDetail,
1789
2172
  {
1790
2173
  label: copy.t("sizeLabel"),
@@ -1799,7 +2182,7 @@ function PreviewSurface({
1799
2182
  copy,
1800
2183
  previewState
1801
2184
  }) {
1802
- return /* @__PURE__ */ jsx2(
2185
+ return /* @__PURE__ */ jsx4(
1803
2186
  SharedWorkspaceFilePreviewSurface,
1804
2187
  {
1805
2188
  directoryMessage: copy.t("previewDirectoryLabel"),
@@ -1807,10 +2190,10 @@ function PreviewSurface({
1807
2190
  frameClassName: "flex h-60 min-h-60 max-h-60 items-center justify-center overflow-hidden rounded-lg border border-[var(--border-1)] bg-[var(--transparency-block)]",
1808
2191
  imageAlt: (entry) => entry.name,
1809
2192
  imageFrameClassName: "p-4",
1810
- loadingIndicator: /* @__PURE__ */ jsx2("span", { className: "mx-auto grid size-11 place-items-center rounded-lg bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx2(LoadingIcon, { className: "size-4 animate-spin" }) }),
2193
+ loadingIndicator: /* @__PURE__ */ jsx4("span", { className: "mx-auto grid size-11 place-items-center rounded-lg bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx4(LoadingIcon2, { className: "size-4 animate-spin" }) }),
1811
2194
  loadingMessage: copy.t("previewLoadingLabel"),
1812
2195
  messageClassName: "max-w-[24ch] [overflow-wrap:anywhere]",
1813
- renderIcon: (entry) => /* @__PURE__ */ jsx2(
2196
+ renderIcon: (entry) => /* @__PURE__ */ jsx4(
1814
2197
  EntryIcon,
1815
2198
  {
1816
2199
  className: "mx-auto size-7",
@@ -1832,20 +2215,20 @@ function PreviewDetail({
1832
2215
  label,
1833
2216
  value
1834
2217
  }) {
1835
- return /* @__PURE__ */ jsxs2(
2218
+ return /* @__PURE__ */ jsxs3(
1836
2219
  "div",
1837
2220
  {
1838
2221
  className: "@max-[600px]/workspace-file-manager:grid-cols-1 @max-[600px]/workspace-file-manager:gap-0.5 grid gap-2.5 border-b border-[var(--border-1)] py-2.5 text-xs",
1839
2222
  style: workspaceFileManagerPreviewDetailGridStyle,
1840
2223
  children: [
1841
- /* @__PURE__ */ jsx2("dt", { className: "truncate text-[var(--text-secondary)]", children: label }),
1842
- /* @__PURE__ */ jsx2("dd", { className: "@max-[600px]/workspace-file-manager:text-left truncate text-right text-[var(--text-primary)]", children: value })
2224
+ /* @__PURE__ */ jsx4("dt", { className: "truncate text-[var(--text-secondary)]", children: label }),
2225
+ /* @__PURE__ */ jsx4("dd", { className: "@max-[600px]/workspace-file-manager:text-left truncate text-right text-[var(--text-primary)]", children: value })
1843
2226
  ]
1844
2227
  }
1845
2228
  );
1846
2229
  }
1847
2230
  function FeedbackState({ message }) {
1848
- return /* @__PURE__ */ jsx2("div", { className: "grid min-h-0 flex-1 place-items-center p-6 text-center text-sm text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx2("span", { className: "max-w-[34ch] [overflow-wrap:anywhere]", children: message }) });
2231
+ return /* @__PURE__ */ jsx4("div", { className: "grid min-h-0 flex-1 place-items-center p-6 text-center text-sm text-[var(--text-tertiary)]", children: /* @__PURE__ */ jsx4("span", { className: "max-w-[34ch] [overflow-wrap:anywhere]", children: message }) });
1849
2232
  }
1850
2233
  function hasFileDragPayload(dataTransfer) {
1851
2234
  return Array.from(dataTransfer.types).includes("Files");
@@ -1856,12 +2239,14 @@ import {
1856
2239
  ArrowLeftIcon,
1857
2240
  ArrowRightIcon as ArrowRightIcon2,
1858
2241
  Button as Button2,
1859
- LoadingIcon as LoadingIcon2,
2242
+ LoadingIcon as LoadingIcon3,
1860
2243
  RefreshIcon,
1861
- cn as cn3
2244
+ ViewGridLinedIcon,
2245
+ ViewListLinedIcon,
2246
+ cn as cn5
1862
2247
  } from "@tutti-os/ui-system";
1863
2248
  import { useState as useState3 } from "react";
1864
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2249
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1865
2250
  function WorkspaceFileManagerToolbar({
1866
2251
  breadcrumbs,
1867
2252
  canGoBack,
@@ -1871,8 +2256,10 @@ function WorkspaceFileManagerToolbar({
1871
2256
  isBusy,
1872
2257
  isLoading,
1873
2258
  isMutating,
2259
+ layoutMode,
1874
2260
  onGoBack,
1875
2261
  onGoForward,
2262
+ onLayoutModeChange,
1876
2263
  onLoadDirectory,
1877
2264
  onRefresh
1878
2265
  }) {
@@ -1881,34 +2268,34 @@ function WorkspaceFileManagerToolbar({
1881
2268
  setRefreshAnimationKey((currentKey) => currentKey + 1);
1882
2269
  onRefresh();
1883
2270
  };
1884
- return /* @__PURE__ */ jsxs3("header", { className: "@max-[600px]/workspace-file-manager:flex-nowrap flex h-10 min-h-10 w-full min-w-0 items-center gap-2 border-b border-[var(--border-1)] px-2 py-1", children: [
1885
- /* @__PURE__ */ jsx3(
2271
+ return /* @__PURE__ */ jsxs4("header", { className: "@max-[600px]/workspace-file-manager:flex-nowrap flex h-10 min-h-10 w-full min-w-0 items-center gap-2 border-b border-[var(--border-1)] px-2 py-1", children: [
2272
+ /* @__PURE__ */ jsx5(
1886
2273
  ToolbarIconButton,
1887
2274
  {
1888
2275
  ariaLabel: copy.t("backLabel"),
1889
2276
  disabled: !canGoBack || isLoading || isBusy,
1890
2277
  title: copy.t("backLabel"),
1891
2278
  onClick: onGoBack,
1892
- children: /* @__PURE__ */ jsx3(ArrowLeftIcon, { className: "size-4" })
2279
+ children: /* @__PURE__ */ jsx5(ArrowLeftIcon, { className: "size-4" })
1893
2280
  }
1894
2281
  ),
1895
- /* @__PURE__ */ jsx3(
2282
+ /* @__PURE__ */ jsx5(
1896
2283
  ToolbarIconButton,
1897
2284
  {
1898
2285
  ariaLabel: copy.t("forwardLabel"),
1899
2286
  disabled: !canGoForward || isLoading || isBusy,
1900
2287
  title: copy.t("forwardLabel"),
1901
2288
  onClick: onGoForward,
1902
- children: /* @__PURE__ */ jsx3(ArrowRightIcon2, { className: "size-4" })
2289
+ children: /* @__PURE__ */ jsx5(ArrowRightIcon2, { className: "size-4" })
1903
2290
  }
1904
2291
  ),
1905
- /* @__PURE__ */ jsx3(
2292
+ /* @__PURE__ */ jsx5(
1906
2293
  "nav",
1907
2294
  {
1908
2295
  "aria-label": currentDirectoryPath,
1909
2296
  className: "@max-[600px]/workspace-file-manager:flex-auto flex min-w-0 flex-1 overflow-hidden pr-2",
1910
2297
  "data-workspace-file-manager-path": "",
1911
- children: /* @__PURE__ */ jsx3("ol", { className: "flex min-w-0 max-w-full flex-1 items-center gap-2 overflow-hidden", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsx3(
2298
+ children: /* @__PURE__ */ jsx5("ol", { className: "flex min-w-0 max-w-full flex-1 items-center gap-2 overflow-hidden", children: breadcrumbs.map((crumb, index) => /* @__PURE__ */ jsx5(
1912
2299
  BreadcrumbButton,
1913
2300
  {
1914
2301
  active: index === breadcrumbs.length - 1,
@@ -1922,28 +2309,99 @@ function WorkspaceFileManagerToolbar({
1922
2309
  )) })
1923
2310
  }
1924
2311
  ),
1925
- /* @__PURE__ */ jsx3("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: /* @__PURE__ */ jsxs3(
1926
- ToolbarActionButton,
1927
- {
1928
- disabled: isLoading || isMutating || isBusy,
1929
- onClick: handleRefresh,
1930
- children: [
1931
- isLoading ? /* @__PURE__ */ jsx3(LoadingIcon2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx3(
1932
- RefreshIcon,
1933
- {
1934
- className: cn3(
1935
- "size-4",
1936
- refreshAnimationKey > 0 && "motion-safe:animate-[spin_520ms_cubic-bezier(0.4,0,0.2,1)_1_reverse]"
1937
- )
1938
- },
1939
- refreshAnimationKey
1940
- ),
1941
- /* @__PURE__ */ jsx3("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
1942
- ]
1943
- }
1944
- ) })
2312
+ /* @__PURE__ */ jsxs4("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: [
2313
+ /* @__PURE__ */ jsx5(
2314
+ LayoutModeToggle,
2315
+ {
2316
+ copy,
2317
+ layoutMode,
2318
+ onLayoutModeChange
2319
+ }
2320
+ ),
2321
+ /* @__PURE__ */ jsxs4(
2322
+ ToolbarActionButton,
2323
+ {
2324
+ disabled: isLoading || isMutating || isBusy,
2325
+ onClick: handleRefresh,
2326
+ children: [
2327
+ isLoading ? /* @__PURE__ */ jsx5(LoadingIcon3, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx5(
2328
+ RefreshIcon,
2329
+ {
2330
+ className: cn5(
2331
+ "size-4",
2332
+ refreshAnimationKey > 0 && "motion-safe:animate-[spin_520ms_cubic-bezier(0.4,0,0.2,1)_1_reverse]"
2333
+ )
2334
+ },
2335
+ refreshAnimationKey
2336
+ ),
2337
+ /* @__PURE__ */ jsx5("span", { className: "@max-[600px]/workspace-file-manager:hidden", children: copy.t("refreshLabel") })
2338
+ ]
2339
+ }
2340
+ )
2341
+ ] })
1945
2342
  ] });
1946
2343
  }
2344
+ function LayoutModeToggle({
2345
+ copy,
2346
+ layoutMode,
2347
+ onLayoutModeChange
2348
+ }) {
2349
+ return /* @__PURE__ */ jsxs4(
2350
+ "div",
2351
+ {
2352
+ className: "flex items-center rounded-md border border-[var(--border-1)] bg-[var(--transparency-block)] p-0.5",
2353
+ role: "group",
2354
+ children: [
2355
+ /* @__PURE__ */ jsx5(
2356
+ LayoutModeButton,
2357
+ {
2358
+ active: layoutMode === "icon",
2359
+ ariaLabel: copy.t("layoutIconViewLabel"),
2360
+ onClick: () => {
2361
+ onLayoutModeChange("icon");
2362
+ },
2363
+ children: /* @__PURE__ */ jsx5(ViewGridLinedIcon, { className: "size-4" })
2364
+ }
2365
+ ),
2366
+ /* @__PURE__ */ jsx5(
2367
+ LayoutModeButton,
2368
+ {
2369
+ active: layoutMode === "list",
2370
+ ariaLabel: copy.t("layoutListViewLabel"),
2371
+ onClick: () => {
2372
+ onLayoutModeChange("list");
2373
+ },
2374
+ children: /* @__PURE__ */ jsx5(ViewListLinedIcon, { className: "size-4" })
2375
+ }
2376
+ )
2377
+ ]
2378
+ }
2379
+ );
2380
+ }
2381
+ function LayoutModeButton({
2382
+ active,
2383
+ ariaLabel,
2384
+ children,
2385
+ onClick
2386
+ }) {
2387
+ return /* @__PURE__ */ jsx5(
2388
+ Button2,
2389
+ {
2390
+ "aria-label": ariaLabel,
2391
+ "aria-pressed": active,
2392
+ className: cn5(
2393
+ "size-6 min-w-6 rounded-[4px] p-0 text-text-secondary",
2394
+ active && "!bg-background-fronted text-foreground hover:!bg-background-fronted"
2395
+ ),
2396
+ size: "icon-sm",
2397
+ title: ariaLabel,
2398
+ type: "button",
2399
+ variant: "ghost",
2400
+ onClick,
2401
+ children
2402
+ }
2403
+ );
2404
+ }
1947
2405
  function ToolbarIconButton({
1948
2406
  ariaLabel,
1949
2407
  children,
@@ -1951,7 +2409,7 @@ function ToolbarIconButton({
1951
2409
  onClick,
1952
2410
  title
1953
2411
  }) {
1954
- return /* @__PURE__ */ jsx3(
2412
+ return /* @__PURE__ */ jsx5(
1955
2413
  Button2,
1956
2414
  {
1957
2415
  "aria-label": ariaLabel,
@@ -1971,7 +2429,7 @@ function ToolbarActionButton({
1971
2429
  disabled,
1972
2430
  onClick
1973
2431
  }) {
1974
- return /* @__PURE__ */ jsx3(
2432
+ return /* @__PURE__ */ jsx5(
1975
2433
  Button2,
1976
2434
  {
1977
2435
  className: "@max-[600px]/workspace-file-manager:size-7 @max-[600px]/workspace-file-manager:min-w-7 @max-[600px]/workspace-file-manager:px-0 cursor-pointer",
@@ -1990,12 +2448,12 @@ function BreadcrumbButton({
1990
2448
  onClick,
1991
2449
  showSeparator
1992
2450
  }) {
1993
- return /* @__PURE__ */ jsxs3("li", { className: "flex min-w-0 items-center gap-2", children: [
1994
- showSeparator ? /* @__PURE__ */ jsx3("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
1995
- /* @__PURE__ */ jsx3(
2451
+ return /* @__PURE__ */ jsxs4("li", { className: "flex min-w-0 items-center gap-2", children: [
2452
+ showSeparator ? /* @__PURE__ */ jsx5("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
2453
+ /* @__PURE__ */ jsx5(
1996
2454
  "button",
1997
2455
  {
1998
- className: cn3(
2456
+ className: cn5(
1999
2457
  "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap border-0 bg-transparent text-sm font-normal transition-colors",
2000
2458
  active ? "font-semibold text-[var(--text-primary)]" : "text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
2001
2459
  ),
@@ -2007,6 +2465,116 @@ function BreadcrumbButton({
2007
2465
  ] });
2008
2466
  }
2009
2467
 
2468
+ // src/ui/useWorkspaceFileManagerLayoutMode.ts
2469
+ import { useCallback as useCallback3, useState as useState4 } from "react";
2470
+
2471
+ // src/ui/workspaceFileManagerLayoutMode.ts
2472
+ var workspaceFileManagerLayoutModeStorageKey = "nextop.workspace-file-manager.layout-mode";
2473
+ function readWorkspaceFileManagerLayoutMode() {
2474
+ if (typeof window === "undefined") {
2475
+ return "list";
2476
+ }
2477
+ const stored = window.localStorage.getItem(
2478
+ workspaceFileManagerLayoutModeStorageKey
2479
+ );
2480
+ return stored === "icon" ? "icon" : "list";
2481
+ }
2482
+ function writeWorkspaceFileManagerLayoutMode(layoutMode) {
2483
+ if (typeof window === "undefined") {
2484
+ return;
2485
+ }
2486
+ window.localStorage.setItem(
2487
+ workspaceFileManagerLayoutModeStorageKey,
2488
+ layoutMode
2489
+ );
2490
+ }
2491
+
2492
+ // src/ui/useWorkspaceFileManagerLayoutMode.ts
2493
+ function useWorkspaceFileManagerLayoutMode() {
2494
+ const [layoutMode, setLayoutModeState] = useState4(
2495
+ readWorkspaceFileManagerLayoutMode
2496
+ );
2497
+ const setLayoutMode = useCallback3(
2498
+ (nextLayoutMode) => {
2499
+ setLayoutModeState(nextLayoutMode);
2500
+ writeWorkspaceFileManagerLayoutMode(nextLayoutMode);
2501
+ },
2502
+ []
2503
+ );
2504
+ return { layoutMode, setLayoutMode };
2505
+ }
2506
+
2507
+ // src/ui/useWorkspaceFileEntryIconUrls.ts
2508
+ import { useEffect as useEffect3, useMemo, useState as useState5 } from "react";
2509
+ function buildIconTargetSignature(entries) {
2510
+ return entries.filter(shouldResolveWorkspaceFileEntryIcon).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
2511
+ }
2512
+ function useWorkspaceFileEntryIconUrls(input) {
2513
+ const { entries, resolveEntryIconUrl } = input;
2514
+ const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState5(() => /* @__PURE__ */ new Map());
2515
+ const iconTargetSignature = useMemo(
2516
+ () => buildIconTargetSignature(entries),
2517
+ [entries]
2518
+ );
2519
+ useEffect3(() => {
2520
+ if (!resolveEntryIconUrl) {
2521
+ setIconUrlByCacheKey(
2522
+ (current) => current.size === 0 ? current : /* @__PURE__ */ new Map()
2523
+ );
2524
+ return;
2525
+ }
2526
+ const targets = entries.filter(shouldResolveWorkspaceFileEntryIcon);
2527
+ if (targets.length === 0) {
2528
+ setIconUrlByCacheKey(
2529
+ (current) => current.size === 0 ? current : /* @__PURE__ */ new Map()
2530
+ );
2531
+ return;
2532
+ }
2533
+ let cancelled = false;
2534
+ void Promise.all(
2535
+ targets.map(async (entry) => {
2536
+ const cacheKey = resolveWorkspaceFileEntryIconCacheKey(entry);
2537
+ try {
2538
+ const iconUrl = await resolveEntryIconUrl(entry);
2539
+ return [cacheKey, iconUrl?.trim() || null];
2540
+ } catch {
2541
+ return [cacheKey, null];
2542
+ }
2543
+ })
2544
+ ).then((results) => {
2545
+ if (cancelled) {
2546
+ return;
2547
+ }
2548
+ setIconUrlByCacheKey((current) => {
2549
+ const nextCacheKeys = new Set(results.map(([cacheKey]) => cacheKey));
2550
+ let changed = false;
2551
+ const next = /* @__PURE__ */ new Map();
2552
+ for (const [cacheKey, iconUrl] of results) {
2553
+ next.set(cacheKey, iconUrl);
2554
+ if (current.get(cacheKey) !== iconUrl) {
2555
+ changed = true;
2556
+ }
2557
+ }
2558
+ if (current.size !== next.size) {
2559
+ changed = true;
2560
+ } else if (!changed) {
2561
+ for (const cacheKey of current.keys()) {
2562
+ if (!nextCacheKeys.has(cacheKey)) {
2563
+ changed = true;
2564
+ break;
2565
+ }
2566
+ }
2567
+ }
2568
+ return changed ? next : current;
2569
+ });
2570
+ });
2571
+ return () => {
2572
+ cancelled = true;
2573
+ };
2574
+ }, [entries, iconTargetSignature, resolveEntryIconUrl]);
2575
+ return iconUrlByCacheKey;
2576
+ }
2577
+
2010
2578
  // src/ui/workspaceFileManagerAnalytics.ts
2011
2579
  function shouldTrackDirectoryExpanded(input) {
2012
2580
  return normalizeDirectoryPath(input.currentDirectoryPath) !== normalizeDirectoryPath(input.nextDirectoryPath);
@@ -2174,7 +2742,7 @@ function useWorkspaceFileManagerContextMenuView(session) {
2174
2742
  }
2175
2743
 
2176
2744
  // src/ui/WorkspaceFileManager.tsx
2177
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2745
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2178
2746
  function WorkspaceFileManager({
2179
2747
  className,
2180
2748
  dateLocale,
@@ -2186,14 +2754,16 @@ function WorkspaceFileManager({
2186
2754
  onEntryDragStart,
2187
2755
  openInAppBrowserIcon,
2188
2756
  resolveOpenWithApplicationIcon,
2757
+ resolveEntryIconUrl,
2189
2758
  hostOs = "linux",
2190
2759
  session,
2191
2760
  surface = "card"
2192
2761
  }) {
2193
- const rootRef = useRef3(null);
2762
+ const rootRef = useRef4(null);
2763
+ const { layoutMode, setLayoutMode } = useWorkspaceFileManagerLayoutMode();
2194
2764
  const rootView = useWorkspaceFileManagerRootView(session);
2195
2765
  const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
2196
- useEffect3(() => {
2766
+ useEffect4(() => {
2197
2767
  function handleCopyShortcut(event) {
2198
2768
  if (!(event.metaKey || event.ctrlKey) || event.key !== "c" || event.shiftKey) {
2199
2769
  return;
@@ -2222,7 +2792,7 @@ function WorkspaceFileManager({
2222
2792
  window.removeEventListener("keydown", handleCopyShortcut);
2223
2793
  };
2224
2794
  }, [onCopyEntry, panelsState, panelsView.selectedEntry, session]);
2225
- useEffect3(() => {
2795
+ useEffect4(() => {
2226
2796
  function handleRenameShortcut(event) {
2227
2797
  if (event.key !== "Enter" || event.metaKey || event.ctrlKey || event.altKey) {
2228
2798
  return;
@@ -2248,7 +2818,7 @@ function WorkspaceFileManager({
2248
2818
  window.removeEventListener("keydown", handleRenameShortcut);
2249
2819
  };
2250
2820
  }, [panelsState, panelsView.selectedEntry, session]);
2251
- useEffect3(() => {
2821
+ useEffect4(() => {
2252
2822
  function resetDropOverlay() {
2253
2823
  session.resetDragDepth();
2254
2824
  }
@@ -2331,10 +2901,10 @@ function WorkspaceFileManager({
2331
2901
  rootView.currentDirectoryPath
2332
2902
  );
2333
2903
  }
2334
- return /* @__PURE__ */ jsxs4(
2904
+ return /* @__PURE__ */ jsxs5(
2335
2905
  "section",
2336
2906
  {
2337
- className: cn4(
2907
+ className: cn6(
2338
2908
  "@container/workspace-file-manager relative flex h-full min-h-0 w-full overflow-hidden text-[14px] text-[var(--text-primary)]",
2339
2909
  surface === "card" ? "rounded-lg border border-[var(--border-1)] bg-[var(--background-panel)]" : "rounded-none border-0 bg-transparent",
2340
2910
  className
@@ -2347,39 +2917,43 @@ function WorkspaceFileManager({
2347
2917
  onDrop: handleDrop,
2348
2918
  ref: rootRef,
2349
2919
  children: [
2350
- /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
2351
- /* @__PURE__ */ jsx4(
2920
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
2921
+ /* @__PURE__ */ jsx6(
2352
2922
  WorkspaceFileManagerToolbarContainer,
2353
2923
  {
2354
2924
  i18n,
2925
+ layoutMode,
2355
2926
  onDirectoryExpanded,
2927
+ onLayoutModeChange: setLayoutMode,
2356
2928
  session
2357
2929
  }
2358
2930
  ),
2359
- /* @__PURE__ */ jsx4(
2931
+ /* @__PURE__ */ jsx6(
2360
2932
  "div",
2361
2933
  {
2362
2934
  className: "@max-[600px]/workspace-file-manager:flex-col @max-[600px]/workspace-file-manager:gap-3 flex min-h-0 min-w-0 flex-1 overflow-hidden",
2363
2935
  style: {
2364
2936
  "--workspace-file-manager-dialog-overlay-z-index": "20"
2365
2937
  },
2366
- children: /* @__PURE__ */ jsx4(
2938
+ children: /* @__PURE__ */ jsx6(
2367
2939
  WorkspaceFileManagerPanelsContainer,
2368
2940
  {
2369
2941
  dateLocale,
2370
2942
  entryDragMode,
2371
2943
  i18n,
2944
+ layoutMode,
2372
2945
  onDirectoryExpanded,
2373
2946
  onEntryDragStart,
2374
2947
  onOpenContextMenu: openContextMenu,
2948
+ resolveEntryIconUrl,
2375
2949
  session
2376
2950
  }
2377
2951
  )
2378
2952
  }
2379
2953
  )
2380
2954
  ] }),
2381
- /* @__PURE__ */ jsx4(WorkspaceFileManagerDialogsContainer, { i18n, session }),
2382
- /* @__PURE__ */ jsx4(
2955
+ /* @__PURE__ */ jsx6(WorkspaceFileManagerDialogsContainer, { i18n, session }),
2956
+ /* @__PURE__ */ jsx6(
2383
2957
  WorkspaceFileManagerContextMenuContainer,
2384
2958
  {
2385
2959
  hostOs,
@@ -2397,11 +2971,13 @@ function WorkspaceFileManager({
2397
2971
  }
2398
2972
  function WorkspaceFileManagerToolbarContainer({
2399
2973
  i18n,
2974
+ layoutMode,
2400
2975
  onDirectoryExpanded,
2976
+ onLayoutModeChange,
2401
2977
  session
2402
2978
  }) {
2403
2979
  const { view } = useWorkspaceFileManagerToolbarView(session, i18n);
2404
- return /* @__PURE__ */ jsx4(
2980
+ return /* @__PURE__ */ jsx6(
2405
2981
  WorkspaceFileManagerToolbar,
2406
2982
  {
2407
2983
  breadcrumbs: view.breadcrumbs,
@@ -2412,12 +2988,14 @@ function WorkspaceFileManagerToolbarContainer({
2412
2988
  isBusy: view.isBusy,
2413
2989
  isLoading: view.isLoading,
2414
2990
  isMutating: view.isMutating,
2991
+ layoutMode,
2415
2992
  onGoBack: () => {
2416
2993
  void session.goBack();
2417
2994
  },
2418
2995
  onGoForward: () => {
2419
2996
  void session.goForward();
2420
2997
  },
2998
+ onLayoutModeChange,
2421
2999
  onLoadDirectory: (path) => {
2422
3000
  if (shouldTrackDirectoryExpanded({
2423
3001
  currentDirectoryPath: view.currentDirectoryPath,
@@ -2437,13 +3015,19 @@ function WorkspaceFileManagerPanelsContainer({
2437
3015
  dateLocale,
2438
3016
  entryDragMode,
2439
3017
  i18n,
3018
+ layoutMode,
2440
3019
  onDirectoryExpanded,
2441
3020
  onEntryDragStart,
2442
3021
  onOpenContextMenu,
3022
+ resolveEntryIconUrl,
2443
3023
  session
2444
3024
  }) {
2445
3025
  const { state, view } = useWorkspaceFileManagerPanelsView(session);
2446
- return /* @__PURE__ */ jsx4(
3026
+ const iconUrlByCacheKey = useWorkspaceFileEntryIconUrls({
3027
+ entries: state.entries,
3028
+ resolveEntryIconUrl
3029
+ });
3030
+ return /* @__PURE__ */ jsx6(
2447
3031
  WorkspaceFileManagerPanels,
2448
3032
  {
2449
3033
  canMove: view.canMove,
@@ -2451,9 +3035,11 @@ function WorkspaceFileManagerPanelsContainer({
2451
3035
  copy: i18n,
2452
3036
  dateLocale,
2453
3037
  entryDragMode,
3038
+ iconUrlByCacheKey,
2454
3039
  inlineRenameEntryPath: view.inlineRenameEntryPath,
2455
3040
  inlineRenameValidation: view.inlineRenameValidation,
2456
3041
  isRenaming: view.isRenaming,
3042
+ layoutMode,
2457
3043
  pendingDirectoryPath: view.pendingDirectoryPath,
2458
3044
  previewState: view.previewState,
2459
3045
  selectedEntry: view.selectedEntry,
@@ -2498,8 +3084,8 @@ function WorkspaceFileManagerDialogsContainer({
2498
3084
  session
2499
3085
  }) {
2500
3086
  const { state, view } = useWorkspaceFileManagerDialogsView(session);
2501
- return /* @__PURE__ */ jsxs4(Fragment3, { children: [
2502
- /* @__PURE__ */ jsx4(
3087
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3088
+ /* @__PURE__ */ jsx6(
2503
3089
  WorkspaceFileManagerCreateDialog,
2504
3090
  {
2505
3091
  busy: view.isBusy && state.busyAction === "create",
@@ -2516,7 +3102,7 @@ function WorkspaceFileManagerDialogsContainer({
2516
3102
  }
2517
3103
  }
2518
3104
  ),
2519
- /* @__PURE__ */ jsx4(
3105
+ /* @__PURE__ */ jsx6(
2520
3106
  WorkspaceFileManagerDeleteDialog,
2521
3107
  {
2522
3108
  busy: view.isDeleting,
@@ -2530,7 +3116,7 @@ function WorkspaceFileManagerDialogsContainer({
2530
3116
  }
2531
3117
  }
2532
3118
  ),
2533
- /* @__PURE__ */ jsx4(
3119
+ /* @__PURE__ */ jsx6(
2534
3120
  WorkspaceFileManagerImportConflictDialog,
2535
3121
  {
2536
3122
  busy: view.isImporting,
@@ -2544,7 +3130,7 @@ function WorkspaceFileManagerDialogsContainer({
2544
3130
  }
2545
3131
  }
2546
3132
  ),
2547
- /* @__PURE__ */ jsx4(
3133
+ /* @__PURE__ */ jsx6(
2548
3134
  WorkspaceFileManagerUnsupportedDialog,
2549
3135
  {
2550
3136
  copy: i18n,
@@ -2569,11 +3155,11 @@ function WorkspaceFileManagerContextMenuContainer({
2569
3155
  resolveOpenWithApplicationIcon,
2570
3156
  session
2571
3157
  }) {
2572
- const contextMenuRef = useRef3(null);
3158
+ const contextMenuRef = useRef4(null);
2573
3159
  const { view } = useWorkspaceFileManagerContextMenuView(session);
2574
- const [openWithApplications, setOpenWithApplications] = useState4([]);
2575
- const [openWithLoading, setOpenWithLoading] = useState4(false);
2576
- useEffect3(() => {
3160
+ const [openWithApplications, setOpenWithApplications] = useState6([]);
3161
+ const [openWithLoading, setOpenWithLoading] = useState6(false);
3162
+ useEffect4(() => {
2577
3163
  const entry = view.contextMenu?.entry;
2578
3164
  if (!entry || !view.showOpenWithAction) {
2579
3165
  return;
@@ -2608,7 +3194,7 @@ function WorkspaceFileManagerContextMenuContainer({
2608
3194
  isOpen: view.contextMenu !== null,
2609
3195
  session
2610
3196
  });
2611
- return /* @__PURE__ */ jsx4(
3197
+ return /* @__PURE__ */ jsx6(
2612
3198
  WorkspaceFileManagerContextMenu,
2613
3199
  {
2614
3200
  busy: view.isBusy || view.isLoading || view.isMutating,
@@ -2735,7 +3321,7 @@ function WorkspaceFileManagerContextMenuContainer({
2735
3321
  }
2736
3322
  function useCloseContextMenuOnOutsideInteraction(input) {
2737
3323
  const { contextMenuRef, isOpen, session } = input;
2738
- useEffect3(() => {
3324
+ useEffect4(() => {
2739
3325
  if (!isOpen) {
2740
3326
  return;
2741
3327
  }