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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,16 +16,16 @@ import {
16
16
  splitWorkspaceFileName,
17
17
  workspaceFilePreviewMaxBytes,
18
18
  workspaceFileTextMaxBytes
19
- } from "./chunk-IMOFXYBB.js";
19
+ } from "./chunk-HYOTD6NY.js";
20
20
  import {
21
21
  createWorkspaceFileManagerI18nRuntime,
22
22
  resolveRevealInFolderLabel,
23
23
  workspaceFileManagerI18nNamespace,
24
24
  workspaceFileManagerI18nResources
25
- } from "./chunk-P2UOYMTS.js";
25
+ } from "./chunk-AUUZNQ37.js";
26
26
 
27
27
  // src/ui/WorkspaceFileManager.tsx
28
- import { useEffect as useEffect4, useRef as useRef4, useState as useState6 } from "react";
28
+ import { useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState7 } from "react";
29
29
  import { cn as cn6 } from "@tutti-os/ui-system";
30
30
 
31
31
  // src/ui/WorkspaceFileManagerMenus.tsx
@@ -932,8 +932,18 @@ function shouldResolveWorkspaceFileEntryIcon(entry) {
932
932
  if (isWorkspaceApplicationBundle(entry)) {
933
933
  return true;
934
934
  }
935
+ if (shouldUseWorkspaceFileExtensionDocumentIcon(entry)) {
936
+ return false;
937
+ }
935
938
  return entry.kind === "file";
936
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
+ }
937
947
  function isWorkspaceApplicationBundle(entry) {
938
948
  return entry.name.trim().toLowerCase().endsWith(".app");
939
949
  }
@@ -942,7 +952,7 @@ function resolveWorkspaceFileEntryIconCacheKey(entry) {
942
952
  }
943
953
 
944
954
  // src/ui/WorkspaceFileEntryIcon.tsx
945
- import { jsx as jsx2 } from "react/jsx-runtime";
955
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
946
956
  function WorkspaceFileEntryIcon({
947
957
  entry,
948
958
  frameClassName,
@@ -988,6 +998,9 @@ function DefaultEntryIcon({
988
998
  if (isWorkspaceApplicationBundle(entry)) {
989
999
  return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
990
1000
  }
1001
+ if (shouldUseWorkspaceFileExtensionDocumentIcon(entry)) {
1002
+ return /* @__PURE__ */ jsx2(ExtensionDocumentIcon, { entry, iconClassName });
1003
+ }
991
1004
  switch (visualKind) {
992
1005
  case "directory":
993
1006
  return /* @__PURE__ */ jsx2(FolderFilledIcon, { className: iconClassName });
@@ -1006,6 +1019,25 @@ function DefaultEntryIcon({
1006
1019
  return /* @__PURE__ */ jsx2(FileTextIcon, { className: iconClassName });
1007
1020
  }
1008
1021
  }
1022
+ function ExtensionDocumentIcon({
1023
+ entry,
1024
+ iconClassName
1025
+ }) {
1026
+ const extension = resolveWorkspaceFileExtension(entry.name).slice(0, 5).toUpperCase();
1027
+ const showExtension = extension.length > 0 && iconClassName.includes("52px");
1028
+ return /* @__PURE__ */ jsxs2(
1029
+ "span",
1030
+ {
1031
+ "aria-hidden": "true",
1032
+ className: cn2("relative inline-block overflow-visible", iconClassName),
1033
+ children: [
1034
+ /* @__PURE__ */ jsx2("span", { className: "absolute inset-[5%] rounded-[6px] border border-black/10 bg-linear-to-br from-white via-[#f8f8f8] to-[#ececec] shadow-[0_8px_16px_rgba(0,0,0,0.18),inset_0_1px_0_rgba(255,255,255,0.85)]" }),
1035
+ /* @__PURE__ */ jsx2("span", { className: "absolute top-[5%] right-[5%] h-[28%] w-[28%] overflow-hidden rounded-tr-[6px]", children: /* @__PURE__ */ jsx2("span", { className: "absolute top-0 right-0 h-full w-full origin-top-right -skew-x-3 rounded-bl-[4px] border-b border-l border-black/10 bg-linear-to-br from-white to-[#d9d9d9] shadow-[-2px_3px_5px_rgba(0,0,0,0.18)]" }) }),
1036
+ showExtension ? /* @__PURE__ */ jsx2("span", { className: "absolute right-[12%] bottom-[14%] left-[12%] truncate text-center text-[10px] leading-none font-semibold tracking-wide text-[#7a7a7a]", children: extension }) : null
1037
+ ]
1038
+ }
1039
+ );
1040
+ }
1009
1041
  function entryIconColorClassName(visualKind, isAppBundle) {
1010
1042
  if (isAppBundle) {
1011
1043
  return "text-[var(--text-tertiary)]";
@@ -1030,7 +1062,7 @@ function workspaceFileManagerIconGridFrameClassName() {
1030
1062
  }
1031
1063
 
1032
1064
  // src/ui/WorkspaceFileManagerIconGrid.tsx
1033
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1065
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1034
1066
  function WorkspaceFileManagerIconGrid({
1035
1067
  contextMenuEntryPath,
1036
1068
  copy,
@@ -1141,7 +1173,7 @@ function WorkspaceFileManagerIconTile({
1141
1173
  }
1142
1174
  );
1143
1175
  if (isInlineRenaming) {
1144
- return /* @__PURE__ */ jsxs2(
1176
+ return /* @__PURE__ */ jsxs3(
1145
1177
  "div",
1146
1178
  {
1147
1179
  "aria-label": entry.name,
@@ -1165,7 +1197,7 @@ function WorkspaceFileManagerIconTile({
1165
1197
  }
1166
1198
  );
1167
1199
  }
1168
- return /* @__PURE__ */ jsxs2(
1200
+ return /* @__PURE__ */ jsxs3(
1169
1201
  "button",
1170
1202
  {
1171
1203
  "aria-label": entry.name,
@@ -1192,7 +1224,7 @@ function WorkspaceFileManagerIconTile({
1192
1224
  },
1193
1225
  children: [
1194
1226
  iconGraphic,
1195
- /* @__PURE__ */ jsxs2("span", { className: "line-clamp-2 w-full break-all text-[13px] leading-[18px] text-[var(--text-primary)]", children: [
1227
+ /* @__PURE__ */ jsxs3("span", { className: "line-clamp-2 w-full break-all text-[13px] leading-[18px] text-[var(--text-primary)]", children: [
1196
1228
  /* @__PURE__ */ jsx3("span", { children: nameParts.start }),
1197
1229
  nameParts.end ? /* @__PURE__ */ jsx3("span", { children: nameParts.end }) : null
1198
1230
  ] })
@@ -1225,7 +1257,7 @@ function IconTileRenameInput({
1225
1257
  }
1226
1258
  }, [hasFileExtension, nameParts.start.length]);
1227
1259
  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: [
1260
+ return /* @__PURE__ */ jsxs3("span", { className: "flex w-full min-w-0 flex-col gap-0.5", children: [
1229
1261
  /* @__PURE__ */ jsx3(
1230
1262
  "input",
1231
1263
  {
@@ -1261,8 +1293,126 @@ function IconTileRenameInput({
1261
1293
  ] });
1262
1294
  }
1263
1295
 
1296
+ // src/ui/workspaceFileManagerArrangeMode.ts
1297
+ var workspaceFileManagerArrangeModeStorageKey = "nextop.workspace-file-manager.arrange-mode";
1298
+ var workspaceFileManagerArrangeModes = /* @__PURE__ */ new Set([
1299
+ "none",
1300
+ "name",
1301
+ "kind",
1302
+ "application",
1303
+ "lastOpened",
1304
+ "dateAdded",
1305
+ "modified",
1306
+ "created",
1307
+ "size",
1308
+ "tags"
1309
+ ]);
1310
+ function readWorkspaceFileManagerArrangeMode() {
1311
+ if (typeof window === "undefined") {
1312
+ return "none";
1313
+ }
1314
+ const stored = window.localStorage.getItem(
1315
+ workspaceFileManagerArrangeModeStorageKey
1316
+ );
1317
+ return isWorkspaceFileManagerArrangeMode(stored) ? stored : "none";
1318
+ }
1319
+ function writeWorkspaceFileManagerArrangeMode(arrangeMode) {
1320
+ if (typeof window === "undefined") {
1321
+ return;
1322
+ }
1323
+ window.localStorage.setItem(
1324
+ workspaceFileManagerArrangeModeStorageKey,
1325
+ arrangeMode
1326
+ );
1327
+ }
1328
+ function sortWorkspaceFileEntriesForArrangeMode(entries, arrangeMode) {
1329
+ if (arrangeMode === "none") {
1330
+ return entries;
1331
+ }
1332
+ return [...entries].sort((left, right) => {
1333
+ switch (arrangeMode) {
1334
+ case "name":
1335
+ return compareDirectoryFirst(left, right) || compareEntryName(left, right);
1336
+ case "kind":
1337
+ return compareDirectoryFirst(left, right) || compareText(
1338
+ resolveEntryKindGroup(left),
1339
+ resolveEntryKindGroup(right)
1340
+ ) || compareEntryName(left, right);
1341
+ case "application":
1342
+ return compareDirectoryFirst(left, right) || compareText(
1343
+ resolveEntryApplicationGroup(left),
1344
+ resolveEntryApplicationGroup(right)
1345
+ ) || compareEntryName(left, right);
1346
+ case "lastOpened":
1347
+ return compareDateDescending(left, right, "lastOpened") || compareEntryName(left, right);
1348
+ case "dateAdded":
1349
+ return compareDateDescending(left, right, "dateAdded") || compareEntryName(left, right);
1350
+ case "modified":
1351
+ return compareDateDescending(left, right, "modified") || compareEntryName(left, right);
1352
+ case "created":
1353
+ return compareDateDescending(left, right, "created") || compareEntryName(left, right);
1354
+ case "size":
1355
+ return compareSizeDescending(left, right) || compareEntryName(left, right);
1356
+ case "tags":
1357
+ return compareDirectoryFirst(left, right) || compareEntryName(left, right);
1358
+ }
1359
+ });
1360
+ }
1361
+ function isWorkspaceFileManagerArrangeMode(value) {
1362
+ return workspaceFileManagerArrangeModes.has(
1363
+ value
1364
+ );
1365
+ }
1366
+ function compareDirectoryFirst(left, right) {
1367
+ const leftRank = left.kind === "directory" ? 0 : 1;
1368
+ const rightRank = right.kind === "directory" ? 0 : 1;
1369
+ return leftRank - rightRank;
1370
+ }
1371
+ function compareEntryName(left, right) {
1372
+ return compareText(left.name, right.name);
1373
+ }
1374
+ function compareText(left, right) {
1375
+ return left.localeCompare(right, void 0, {
1376
+ numeric: true,
1377
+ sensitivity: "base"
1378
+ });
1379
+ }
1380
+ function resolveWorkspaceFileEntryArrangeDateMs(entry, arrangeMode) {
1381
+ switch (arrangeMode) {
1382
+ case "lastOpened":
1383
+ return entry.lastOpenedMs ?? entry.mtimeMs;
1384
+ case "dateAdded":
1385
+ case "created":
1386
+ return entry.createdTimeMs ?? entry.mtimeMs;
1387
+ case "modified":
1388
+ default:
1389
+ return entry.mtimeMs;
1390
+ }
1391
+ }
1392
+ function compareDateDescending(left, right, arrangeMode) {
1393
+ return (resolveWorkspaceFileEntryArrangeDateMs(right, arrangeMode) ?? 0) - (resolveWorkspaceFileEntryArrangeDateMs(left, arrangeMode) ?? 0);
1394
+ }
1395
+ function compareSizeDescending(left, right) {
1396
+ return (right.sizeBytes ?? 0) - (left.sizeBytes ?? 0);
1397
+ }
1398
+ function resolveEntryKindGroup(entry) {
1399
+ if (entry.kind === "directory") {
1400
+ return "directory";
1401
+ }
1402
+ return resolveWorkspaceFileVisualKind(entry);
1403
+ }
1404
+ function resolveEntryApplicationGroup(entry) {
1405
+ if (entry.kind === "directory") {
1406
+ return "folder";
1407
+ }
1408
+ if (entry.name.trim().toLowerCase().endsWith(".app")) {
1409
+ return "application";
1410
+ }
1411
+ return resolveWorkspaceFileExtension(entry.name) || resolveEntryKindGroup(entry);
1412
+ }
1413
+
1264
1414
  // src/ui/WorkspaceFileManagerPanels.tsx
1265
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1415
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1266
1416
  var workspaceFileManagerTableGridClassName = "grid-cols-[minmax(0,_1fr)_148px_96px]";
1267
1417
  var workspaceFileManagerTableGridStyle = {
1268
1418
  gridTemplateColumns: "minmax(0, 1fr) 148px 96px"
@@ -1284,6 +1434,7 @@ var workspaceFileManagerMoveDragAutoScrollEdgePx = 48;
1284
1434
  var workspaceFileManagerMoveDragAutoScrollStepPx = 12;
1285
1435
  var workspaceFileManagerEntryOpenClickIntervalMs = 500;
1286
1436
  function WorkspaceFileManagerPanels({
1437
+ arrangeMode,
1287
1438
  canMove,
1288
1439
  contextMenuEntryPath,
1289
1440
  dateLocale,
@@ -1328,6 +1479,10 @@ function WorkspaceFileManagerPanels({
1328
1479
  const entriesRef = useRef3(state.entries);
1329
1480
  const nativeEntryDragEnabled = onEntryDragStart !== void 0 && (entryDragMode === "external" || entryDragMode === void 0 && !canMove);
1330
1481
  const internalMoveEnabled = canMove && !nativeEntryDragEnabled;
1482
+ const dateColumnLabel = resolveWorkspaceFileManagerDateColumnLabel(
1483
+ copy,
1484
+ arrangeMode
1485
+ );
1331
1486
  const stopMoveDragAutoScroll = useCallback2(() => {
1332
1487
  const autoScroll = moveDragAutoScrollRef.current;
1333
1488
  if (autoScroll?.frameId !== null && autoScroll?.frameId !== void 0) {
@@ -1515,7 +1670,7 @@ function WorkspaceFileManagerPanels({
1515
1670
  stopMoveDragAutoScroll,
1516
1671
  updateMoveDragAutoScroll
1517
1672
  ]);
1518
- const showPreviewPanel = false;
1673
+ const showPreviewPanel = layoutMode === "list";
1519
1674
  const useStackedPreview = false;
1520
1675
  const previewPaneClassName = useStackedPreview ? "min-w-0 border-t border-[var(--border-1)]" : "min-w-[220px] border-l border-[var(--border-1)]";
1521
1676
  const tableGridClassName = useStackedPreview ? workspaceFileManagerCompactTableGridClassName : workspaceFileManagerTableGridClassName;
@@ -1637,8 +1792,8 @@ function WorkspaceFileManagerPanels({
1637
1792
  onEntryClick: handleEntryClick,
1638
1793
  onEntryPointerDown: handleEntryPointerDown
1639
1794
  }
1640
- ) : /* @__PURE__ */ jsxs3("div", { className: "flex flex-col", children: [
1641
- /* @__PURE__ */ jsxs3(
1795
+ ) : /* @__PURE__ */ jsxs4("div", { className: "flex flex-col", children: [
1796
+ /* @__PURE__ */ jsxs4(
1642
1797
  "div",
1643
1798
  {
1644
1799
  className: cn4(
@@ -1652,7 +1807,7 @@ function WorkspaceFileManagerPanels({
1652
1807
  "span",
1653
1808
  {
1654
1809
  className: cn4("whitespace-nowrap", tableCellPaddingClassName),
1655
- children: copy.t("modifiedLabel")
1810
+ children: dateColumnLabel
1656
1811
  }
1657
1812
  ),
1658
1813
  /* @__PURE__ */ jsx4(
@@ -1670,6 +1825,7 @@ function WorkspaceFileManagerPanels({
1670
1825
  {
1671
1826
  contextMenuActive: contextMenuEntryPath === entry.path,
1672
1827
  copy,
1828
+ arrangeMode,
1673
1829
  dateLocale,
1674
1830
  entry,
1675
1831
  iconUrlByCacheKey,
@@ -1719,13 +1875,13 @@ function WorkspaceFileManagerPanels({
1719
1875
  )
1720
1876
  }
1721
1877
  ) : null;
1722
- return /* @__PURE__ */ jsxs3(
1878
+ return /* @__PURE__ */ jsxs4(
1723
1879
  "div",
1724
1880
  {
1725
1881
  className: "relative min-h-0 min-w-0 flex-1 overflow-hidden",
1726
1882
  ref: rootRef,
1727
1883
  children: [
1728
- /* @__PURE__ */ jsxs3(
1884
+ /* @__PURE__ */ jsxs4(
1729
1885
  "div",
1730
1886
  {
1731
1887
  className: cn4(
@@ -1804,7 +1960,21 @@ function useWorkspaceFileManagerStackedLayout(breakpoint) {
1804
1960
  }, [breakpoint]);
1805
1961
  return { isStacked, rootRef };
1806
1962
  }
1963
+ function resolveWorkspaceFileManagerDateColumnLabel(copy, arrangeMode) {
1964
+ switch (arrangeMode) {
1965
+ case "lastOpened":
1966
+ return copy.t("arrangeLastOpenedLabel");
1967
+ case "dateAdded":
1968
+ return copy.t("arrangeDateAddedLabel");
1969
+ case "created":
1970
+ return copy.t("arrangeCreatedLabel");
1971
+ case "modified":
1972
+ default:
1973
+ return copy.t("modifiedLabel");
1974
+ }
1975
+ }
1807
1976
  function EntryRow({
1977
+ arrangeMode,
1808
1978
  canMove,
1809
1979
  contextMenuActive,
1810
1980
  copy,
@@ -1884,7 +2054,10 @@ function EntryRow({
1884
2054
  "truncate text-xs text-[var(--text-secondary)]",
1885
2055
  tableCellPaddingClassName
1886
2056
  ),
1887
- children: formatWorkspaceFileModifiedTime(entry.mtimeMs, dateLocale)
2057
+ children: formatWorkspaceFileModifiedTime(
2058
+ resolveWorkspaceFileEntryArrangeDateMs(entry, arrangeMode),
2059
+ dateLocale
2060
+ )
1888
2061
  }
1889
2062
  );
1890
2063
  const sizeCell = /* @__PURE__ */ jsx4(
@@ -1898,13 +2071,13 @@ function EntryRow({
1898
2071
  }
1899
2072
  );
1900
2073
  if (isInlineRenaming) {
1901
- return /* @__PURE__ */ jsxs3("div", { ...rowProps, ref: divRowRef, children: [
2074
+ return /* @__PURE__ */ jsxs4("div", { ...rowProps, ref: divRowRef, children: [
1902
2075
  nameCell,
1903
2076
  modifiedCell,
1904
2077
  sizeCell
1905
2078
  ] });
1906
2079
  }
1907
- return /* @__PURE__ */ jsxs3(
2080
+ return /* @__PURE__ */ jsxs4(
1908
2081
  "button",
1909
2082
  {
1910
2083
  ...rowProps,
@@ -1935,7 +2108,7 @@ function MoveDragPreview({
1935
2108
  iconUrlByCacheKey,
1936
2109
  preview
1937
2110
  }) {
1938
- return /* @__PURE__ */ jsxs3(
2111
+ return /* @__PURE__ */ jsxs4(
1939
2112
  "div",
1940
2113
  {
1941
2114
  "aria-hidden": "true",
@@ -2042,7 +2215,7 @@ function EntryNameCell({
2042
2215
  }
2043
2216
  }, [name, onConfirmInlineRename]);
2044
2217
  if (isInlineRenaming) {
2045
- return /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 items-center gap-2", children: [
2218
+ return /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2", children: [
2046
2219
  /* @__PURE__ */ jsx4(
2047
2220
  WorkspaceFileEntryIcon,
2048
2221
  {
@@ -2052,7 +2225,7 @@ function EntryNameCell({
2052
2225
  iconUrlByCacheKey
2053
2226
  }
2054
2227
  ),
2055
- /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2228
+ /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2056
2229
  /* @__PURE__ */ jsx4(
2057
2230
  "input",
2058
2231
  {
@@ -2104,7 +2277,7 @@ function EntryNameCell({
2104
2277
  ] })
2105
2278
  ] });
2106
2279
  }
2107
- return /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 items-center gap-2", children: [
2280
+ return /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2", children: [
2108
2281
  /* @__PURE__ */ jsx4(
2109
2282
  WorkspaceFileEntryIcon,
2110
2283
  {
@@ -2115,7 +2288,7 @@ function EntryNameCell({
2115
2288
  isEnteringDirectory
2116
2289
  }
2117
2290
  ),
2118
- /* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
2291
+ /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 max-w-full overflow-hidden whitespace-nowrap text-sm", children: [
2119
2292
  /* @__PURE__ */ jsx4("span", { className: "min-w-0 overflow-hidden text-ellipsis", children: nameParts.start }),
2120
2293
  nameParts.end ? /* @__PURE__ */ jsx4("span", { className: "flex-none overflow-hidden text-ellipsis", children: nameParts.end }) : null
2121
2294
  ] })
@@ -2152,14 +2325,14 @@ function PreviewPane({
2152
2325
  if (!entry || previewState.status === "empty") {
2153
2326
  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") }) });
2154
2327
  }
2155
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
2328
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
2156
2329
  /* @__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: [
2330
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-col gap-[14px]", children: [
2331
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-col gap-[3px]", children: [
2159
2332
  /* @__PURE__ */ jsx4("strong", { className: "min-w-0 truncate text-[15px] font-semibold text-[var(--text-primary)]", children: entry.name }),
2160
2333
  /* @__PURE__ */ jsx4("p", { className: "min-w-0 truncate text-xs text-[var(--text-secondary)]", children: entry.path })
2161
2334
  ] }),
2162
- /* @__PURE__ */ jsxs3("dl", { className: "border-t border-[var(--border-1)]", children: [
2335
+ /* @__PURE__ */ jsxs4("dl", { className: "border-t border-[var(--border-1)]", children: [
2163
2336
  /* @__PURE__ */ jsx4(
2164
2337
  PreviewDetail,
2165
2338
  {
@@ -2215,7 +2388,7 @@ function PreviewDetail({
2215
2388
  label,
2216
2389
  value
2217
2390
  }) {
2218
- return /* @__PURE__ */ jsxs3(
2391
+ return /* @__PURE__ */ jsxs4(
2219
2392
  "div",
2220
2393
  {
2221
2394
  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",
@@ -2239,6 +2412,13 @@ import {
2239
2412
  ArrowLeftIcon,
2240
2413
  ArrowRightIcon as ArrowRightIcon2,
2241
2414
  Button as Button2,
2415
+ ChevronDownIcon,
2416
+ DropdownMenu,
2417
+ DropdownMenuContent,
2418
+ DropdownMenuRadioGroup,
2419
+ DropdownMenuRadioItem,
2420
+ DropdownMenuSeparator,
2421
+ DropdownMenuTrigger,
2242
2422
  LoadingIcon as LoadingIcon3,
2243
2423
  RefreshIcon,
2244
2424
  ViewGridLinedIcon,
@@ -2246,7 +2426,7 @@ import {
2246
2426
  cn as cn5
2247
2427
  } from "@tutti-os/ui-system";
2248
2428
  import { useState as useState3 } from "react";
2249
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2429
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2250
2430
  function WorkspaceFileManagerToolbar({
2251
2431
  breadcrumbs,
2252
2432
  canGoBack,
@@ -2256,9 +2436,11 @@ function WorkspaceFileManagerToolbar({
2256
2436
  isBusy,
2257
2437
  isLoading,
2258
2438
  isMutating,
2439
+ arrangeMode,
2259
2440
  layoutMode,
2260
2441
  onGoBack,
2261
2442
  onGoForward,
2443
+ onArrangeModeChange,
2262
2444
  onLayoutModeChange,
2263
2445
  onLoadDirectory,
2264
2446
  onRefresh
@@ -2268,7 +2450,7 @@ function WorkspaceFileManagerToolbar({
2268
2450
  setRefreshAnimationKey((currentKey) => currentKey + 1);
2269
2451
  onRefresh();
2270
2452
  };
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: [
2453
+ return /* @__PURE__ */ jsxs5("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
2454
  /* @__PURE__ */ jsx5(
2273
2455
  ToolbarIconButton,
2274
2456
  {
@@ -2309,16 +2491,18 @@ function WorkspaceFileManagerToolbar({
2309
2491
  )) })
2310
2492
  }
2311
2493
  ),
2312
- /* @__PURE__ */ jsxs4("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: [
2494
+ /* @__PURE__ */ jsxs5("div", { className: "@max-[600px]/workspace-file-manager:justify-end flex flex-none items-center gap-1.5", children: [
2313
2495
  /* @__PURE__ */ jsx5(
2314
2496
  LayoutModeToggle,
2315
2497
  {
2498
+ arrangeMode,
2316
2499
  copy,
2317
2500
  layoutMode,
2501
+ onArrangeModeChange,
2318
2502
  onLayoutModeChange
2319
2503
  }
2320
2504
  ),
2321
- /* @__PURE__ */ jsxs4(
2505
+ /* @__PURE__ */ jsxs5(
2322
2506
  ToolbarActionButton,
2323
2507
  {
2324
2508
  disabled: isLoading || isMutating || isBusy,
@@ -2342,11 +2526,25 @@ function WorkspaceFileManagerToolbar({
2342
2526
  ] });
2343
2527
  }
2344
2528
  function LayoutModeToggle({
2529
+ arrangeMode,
2345
2530
  copy,
2346
2531
  layoutMode,
2532
+ onArrangeModeChange,
2347
2533
  onLayoutModeChange
2348
2534
  }) {
2349
- return /* @__PURE__ */ jsxs4(
2535
+ const arrangeOptions = [
2536
+ { label: copy.t("arrangeNoneLabel"), mode: "none" },
2537
+ { label: copy.t("nameLabel"), mode: "name" },
2538
+ { label: copy.t("arrangeKindLabel"), mode: "kind" },
2539
+ { label: copy.t("arrangeApplicationLabel"), mode: "application" },
2540
+ { label: copy.t("arrangeLastOpenedLabel"), mode: "lastOpened" },
2541
+ { label: copy.t("arrangeDateAddedLabel"), mode: "dateAdded" },
2542
+ { label: copy.t("modifiedLabel"), mode: "modified" },
2543
+ { label: copy.t("arrangeCreatedLabel"), mode: "created" },
2544
+ { label: copy.t("sizeLabel"), mode: "size" },
2545
+ { label: copy.t("arrangeTagsLabel"), mode: "tags" }
2546
+ ];
2547
+ return /* @__PURE__ */ jsxs5(
2350
2548
  "div",
2351
2549
  {
2352
2550
  className: "flex items-center rounded-md border border-[var(--border-1)] bg-[var(--transparency-block)] p-0.5",
@@ -2373,7 +2571,50 @@ function LayoutModeToggle({
2373
2571
  },
2374
2572
  children: /* @__PURE__ */ jsx5(ViewListLinedIcon, { className: "size-4" })
2375
2573
  }
2376
- )
2574
+ ),
2575
+ /* @__PURE__ */ jsx5("span", { className: "mx-0.5 h-4 w-px bg-[var(--border-1)]", "aria-hidden": true }),
2576
+ /* @__PURE__ */ jsxs5(DropdownMenu, { children: [
2577
+ /* @__PURE__ */ jsx5(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(
2578
+ Button2,
2579
+ {
2580
+ "aria-label": copy.t("arrangeMenuLabel"),
2581
+ className: "size-6 min-w-6 rounded-[4px] p-0 text-[var(--text-secondary)] data-[state=open]:bg-[var(--background-fronted)] data-[state=open]:text-[var(--text-primary)] data-[state=open]:shadow-sm",
2582
+ size: "icon-sm",
2583
+ title: copy.t("arrangeMenuLabel"),
2584
+ type: "button",
2585
+ variant: "ghost",
2586
+ children: /* @__PURE__ */ jsx5(ChevronDownIcon, { className: "size-3.5" })
2587
+ }
2588
+ ) }),
2589
+ /* @__PURE__ */ jsx5(
2590
+ DropdownMenuContent,
2591
+ {
2592
+ align: "end",
2593
+ className: "min-w-[236px] px-1 py-1",
2594
+ sideOffset: 7,
2595
+ children: /* @__PURE__ */ jsx5(
2596
+ DropdownMenuRadioGroup,
2597
+ {
2598
+ value: arrangeMode,
2599
+ onValueChange: (nextMode) => {
2600
+ onArrangeModeChange(nextMode);
2601
+ },
2602
+ children: arrangeOptions.map((option, index) => /* @__PURE__ */ jsxs5("div", { children: [
2603
+ index === 1 ? /* @__PURE__ */ jsx5(DropdownMenuSeparator, {}) : null,
2604
+ /* @__PURE__ */ jsx5(
2605
+ DropdownMenuRadioItem,
2606
+ {
2607
+ className: "h-8 text-sm font-normal",
2608
+ value: option.mode,
2609
+ children: option.label
2610
+ }
2611
+ )
2612
+ ] }, option.mode))
2613
+ }
2614
+ )
2615
+ }
2616
+ )
2617
+ ] })
2377
2618
  ]
2378
2619
  }
2379
2620
  );
@@ -2448,7 +2689,7 @@ function BreadcrumbButton({
2448
2689
  onClick,
2449
2690
  showSeparator
2450
2691
  }) {
2451
- return /* @__PURE__ */ jsxs4("li", { className: "flex min-w-0 items-center gap-2", children: [
2692
+ return /* @__PURE__ */ jsxs5("li", { className: "flex min-w-0 items-center gap-2", children: [
2452
2693
  showSeparator ? /* @__PURE__ */ jsx5("span", { className: "flex-none text-[var(--text-tertiary)]", children: "/" }) : null,
2453
2694
  /* @__PURE__ */ jsx5(
2454
2695
  "button",
@@ -2465,8 +2706,24 @@ function BreadcrumbButton({
2465
2706
  ] });
2466
2707
  }
2467
2708
 
2468
- // src/ui/useWorkspaceFileManagerLayoutMode.ts
2709
+ // src/ui/useWorkspaceFileManagerArrangeMode.ts
2469
2710
  import { useCallback as useCallback3, useState as useState4 } from "react";
2711
+ function useWorkspaceFileManagerArrangeMode() {
2712
+ const [arrangeMode, setArrangeModeState] = useState4(
2713
+ readWorkspaceFileManagerArrangeMode
2714
+ );
2715
+ const setArrangeMode = useCallback3(
2716
+ (nextArrangeMode) => {
2717
+ setArrangeModeState(nextArrangeMode);
2718
+ writeWorkspaceFileManagerArrangeMode(nextArrangeMode);
2719
+ },
2720
+ []
2721
+ );
2722
+ return { arrangeMode, setArrangeMode };
2723
+ }
2724
+
2725
+ // src/ui/useWorkspaceFileManagerLayoutMode.ts
2726
+ import { useCallback as useCallback4, useState as useState5 } from "react";
2470
2727
 
2471
2728
  // src/ui/workspaceFileManagerLayoutMode.ts
2472
2729
  var workspaceFileManagerLayoutModeStorageKey = "nextop.workspace-file-manager.layout-mode";
@@ -2491,10 +2748,10 @@ function writeWorkspaceFileManagerLayoutMode(layoutMode) {
2491
2748
 
2492
2749
  // src/ui/useWorkspaceFileManagerLayoutMode.ts
2493
2750
  function useWorkspaceFileManagerLayoutMode() {
2494
- const [layoutMode, setLayoutModeState] = useState4(
2751
+ const [layoutMode, setLayoutModeState] = useState5(
2495
2752
  readWorkspaceFileManagerLayoutMode
2496
2753
  );
2497
- const setLayoutMode = useCallback3(
2754
+ const setLayoutMode = useCallback4(
2498
2755
  (nextLayoutMode) => {
2499
2756
  setLayoutModeState(nextLayoutMode);
2500
2757
  writeWorkspaceFileManagerLayoutMode(nextLayoutMode);
@@ -2505,13 +2762,13 @@ function useWorkspaceFileManagerLayoutMode() {
2505
2762
  }
2506
2763
 
2507
2764
  // src/ui/useWorkspaceFileEntryIconUrls.ts
2508
- import { useEffect as useEffect3, useMemo, useState as useState5 } from "react";
2765
+ import { useEffect as useEffect3, useMemo, useState as useState6 } from "react";
2509
2766
  function buildIconTargetSignature(entries) {
2510
2767
  return entries.filter(shouldResolveWorkspaceFileEntryIcon).map((entry) => resolveWorkspaceFileEntryIconCacheKey(entry)).join("\0");
2511
2768
  }
2512
2769
  function useWorkspaceFileEntryIconUrls(input) {
2513
2770
  const { entries, resolveEntryIconUrl } = input;
2514
- const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState5(() => /* @__PURE__ */ new Map());
2771
+ const [iconUrlByCacheKey, setIconUrlByCacheKey] = useState6(() => /* @__PURE__ */ new Map());
2515
2772
  const iconTargetSignature = useMemo(
2516
2773
  () => buildIconTargetSignature(entries),
2517
2774
  [entries]
@@ -2742,7 +2999,7 @@ function useWorkspaceFileManagerContextMenuView(session) {
2742
2999
  }
2743
3000
 
2744
3001
  // src/ui/WorkspaceFileManager.tsx
2745
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3002
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2746
3003
  function WorkspaceFileManager({
2747
3004
  className,
2748
3005
  dateLocale,
@@ -2760,6 +3017,7 @@ function WorkspaceFileManager({
2760
3017
  surface = "card"
2761
3018
  }) {
2762
3019
  const rootRef = useRef4(null);
3020
+ const { arrangeMode, setArrangeMode } = useWorkspaceFileManagerArrangeMode();
2763
3021
  const { layoutMode, setLayoutMode } = useWorkspaceFileManagerLayoutMode();
2764
3022
  const rootView = useWorkspaceFileManagerRootView(session);
2765
3023
  const { state: panelsState, view: panelsView } = useWorkspaceFileManagerPanelsView(session);
@@ -2901,7 +3159,7 @@ function WorkspaceFileManager({
2901
3159
  rootView.currentDirectoryPath
2902
3160
  );
2903
3161
  }
2904
- return /* @__PURE__ */ jsxs5(
3162
+ return /* @__PURE__ */ jsxs6(
2905
3163
  "section",
2906
3164
  {
2907
3165
  className: cn6(
@@ -2917,12 +3175,14 @@ function WorkspaceFileManager({
2917
3175
  onDrop: handleDrop,
2918
3176
  ref: rootRef,
2919
3177
  children: [
2920
- /* @__PURE__ */ jsxs5("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
3178
+ /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", children: [
2921
3179
  /* @__PURE__ */ jsx6(
2922
3180
  WorkspaceFileManagerToolbarContainer,
2923
3181
  {
2924
3182
  i18n,
3183
+ arrangeMode,
2925
3184
  layoutMode,
3185
+ onArrangeModeChange: setArrangeMode,
2926
3186
  onDirectoryExpanded,
2927
3187
  onLayoutModeChange: setLayoutMode,
2928
3188
  session
@@ -2940,6 +3200,7 @@ function WorkspaceFileManager({
2940
3200
  {
2941
3201
  dateLocale,
2942
3202
  entryDragMode,
3203
+ arrangeMode,
2943
3204
  i18n,
2944
3205
  layoutMode,
2945
3206
  onDirectoryExpanded,
@@ -2970,8 +3231,10 @@ function WorkspaceFileManager({
2970
3231
  );
2971
3232
  }
2972
3233
  function WorkspaceFileManagerToolbarContainer({
3234
+ arrangeMode,
2973
3235
  i18n,
2974
3236
  layoutMode,
3237
+ onArrangeModeChange,
2975
3238
  onDirectoryExpanded,
2976
3239
  onLayoutModeChange,
2977
3240
  session
@@ -2988,7 +3251,9 @@ function WorkspaceFileManagerToolbarContainer({
2988
3251
  isBusy: view.isBusy,
2989
3252
  isLoading: view.isLoading,
2990
3253
  isMutating: view.isMutating,
3254
+ arrangeMode,
2991
3255
  layoutMode,
3256
+ onArrangeModeChange,
2992
3257
  onGoBack: () => {
2993
3258
  void session.goBack();
2994
3259
  },
@@ -3012,6 +3277,7 @@ function WorkspaceFileManagerToolbarContainer({
3012
3277
  );
3013
3278
  }
3014
3279
  function WorkspaceFileManagerPanelsContainer({
3280
+ arrangeMode,
3015
3281
  dateLocale,
3016
3282
  entryDragMode,
3017
3283
  i18n,
@@ -3023,13 +3289,18 @@ function WorkspaceFileManagerPanelsContainer({
3023
3289
  session
3024
3290
  }) {
3025
3291
  const { state, view } = useWorkspaceFileManagerPanelsView(session);
3292
+ const arrangedEntries = useMemo2(
3293
+ () => sortWorkspaceFileEntriesForArrangeMode(state.entries, arrangeMode),
3294
+ [arrangeMode, state.entries]
3295
+ );
3026
3296
  const iconUrlByCacheKey = useWorkspaceFileEntryIconUrls({
3027
- entries: state.entries,
3297
+ entries: arrangedEntries,
3028
3298
  resolveEntryIconUrl
3029
3299
  });
3030
3300
  return /* @__PURE__ */ jsx6(
3031
3301
  WorkspaceFileManagerPanels,
3032
3302
  {
3303
+ arrangeMode,
3033
3304
  canMove: view.canMove,
3034
3305
  contextMenuEntryPath: view.contextMenuEntryPath,
3035
3306
  copy: i18n,
@@ -3046,7 +3317,7 @@ function WorkspaceFileManagerPanelsContainer({
3046
3317
  selectedPath: view.selectedPath,
3047
3318
  showDropOverlay: view.showDropOverlay,
3048
3319
  state: {
3049
- entries: state.entries,
3320
+ entries: arrangedEntries,
3050
3321
  error: state.error,
3051
3322
  isLoading: state.isLoading
3052
3323
  },
@@ -3084,7 +3355,7 @@ function WorkspaceFileManagerDialogsContainer({
3084
3355
  session
3085
3356
  }) {
3086
3357
  const { state, view } = useWorkspaceFileManagerDialogsView(session);
3087
- return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3358
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
3088
3359
  /* @__PURE__ */ jsx6(
3089
3360
  WorkspaceFileManagerCreateDialog,
3090
3361
  {
@@ -3157,8 +3428,8 @@ function WorkspaceFileManagerContextMenuContainer({
3157
3428
  }) {
3158
3429
  const contextMenuRef = useRef4(null);
3159
3430
  const { view } = useWorkspaceFileManagerContextMenuView(session);
3160
- const [openWithApplications, setOpenWithApplications] = useState6([]);
3161
- const [openWithLoading, setOpenWithLoading] = useState6(false);
3431
+ const [openWithApplications, setOpenWithApplications] = useState7([]);
3432
+ const [openWithLoading, setOpenWithLoading] = useState7(false);
3162
3433
  useEffect4(() => {
3163
3434
  const entry = view.contextMenu?.entry;
3164
3435
  if (!entry || !view.showOpenWithAction) {