react-os-shell 0.2.63 → 0.2.66

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.
Files changed (50) hide show
  1. package/dist/{Browser-F7TJRHMK.js → Browser-QPQX2OXO.js} +3 -3
  2. package/dist/{Browser-F7TJRHMK.js.map → Browser-QPQX2OXO.js.map} +1 -1
  3. package/dist/{Calculator-FO3NUVCK.js → Calculator-ZFY3ZB5X.js} +4 -4
  4. package/dist/{Calculator-FO3NUVCK.js.map → Calculator-ZFY3ZB5X.js.map} +1 -1
  5. package/dist/{Calendar-ZGKMZ5TU.js → Calendar-LL5DUDDN.js} +3 -3
  6. package/dist/{Calendar-ZGKMZ5TU.js.map → Calendar-LL5DUDDN.js.map} +1 -1
  7. package/dist/{CurrencyConverter-IMHQD6XV.js → CurrencyConverter-JGAC32DB.js} +4 -4
  8. package/dist/{CurrencyConverter-IMHQD6XV.js.map → CurrencyConverter-JGAC32DB.js.map} +1 -1
  9. package/dist/{Documents-PHVXWN4S.js → Documents-XW7O22QF.js} +3 -3
  10. package/dist/{Documents-PHVXWN4S.js.map → Documents-XW7O22QF.js.map} +1 -1
  11. package/dist/{Email-VXTFMSS5.js → Email-PCBQUXE4.js} +3 -3
  12. package/dist/{Email-VXTFMSS5.js.map → Email-PCBQUXE4.js.map} +1 -1
  13. package/dist/Files-UATWOZM3.js +12 -0
  14. package/dist/{Files-YO5YOKD7.js.map → Files-UATWOZM3.js.map} +1 -1
  15. package/dist/{Minesweeper-PX4DUPOE.js → Minesweeper-KBZXSRJF.js} +3 -3
  16. package/dist/{Minesweeper-PX4DUPOE.js.map → Minesweeper-KBZXSRJF.js.map} +1 -1
  17. package/dist/{Notepad-LFCZD2Y3.js → Notepad-XPSVB6GJ.js} +3 -3
  18. package/dist/{Notepad-LFCZD2Y3.js.map → Notepad-XPSVB6GJ.js.map} +1 -1
  19. package/dist/{PomodoroTimer-7Y36AEWF.js → PomodoroTimer-7R2GTFUR.js} +4 -4
  20. package/dist/{PomodoroTimer-7Y36AEWF.js.map → PomodoroTimer-7R2GTFUR.js.map} +1 -1
  21. package/dist/Preview-OAGLCTU6.js +8 -0
  22. package/dist/{Preview-UMGXEYCV.js.map → Preview-OAGLCTU6.js.map} +1 -1
  23. package/dist/{Spreadsheet-L2DOK2JK.js → Spreadsheet-OTOBE6YI.js} +4 -4
  24. package/dist/{Spreadsheet-L2DOK2JK.js.map → Spreadsheet-OTOBE6YI.js.map} +1 -1
  25. package/dist/{Weather-FDL7CFDL.js → Weather-DLZUHBXU.js} +4 -4
  26. package/dist/{Weather-FDL7CFDL.js.map → Weather-DLZUHBXU.js.map} +1 -1
  27. package/dist/{WorldClock-4PCYEMLF.js → WorldClock-WVHQD5K7.js} +4 -4
  28. package/dist/{WorldClock-4PCYEMLF.js.map → WorldClock-WVHQD5K7.js.map} +1 -1
  29. package/dist/apps/index.js +19 -18
  30. package/dist/apps/index.js.map +1 -1
  31. package/dist/{chunk-6JAAM5TA.js → chunk-4EMPRKHL.js} +11 -19
  32. package/dist/chunk-4EMPRKHL.js.map +1 -0
  33. package/dist/{chunk-ONQJGZCL.js → chunk-DVS3TOYB.js} +15 -15
  34. package/dist/chunk-DVS3TOYB.js.map +1 -0
  35. package/dist/{chunk-FJ6NSPYF.js → chunk-JRUZT46J.js} +3 -3
  36. package/dist/{chunk-FJ6NSPYF.js.map → chunk-JRUZT46J.js.map} +1 -1
  37. package/dist/{chunk-E7ZO44KU.js → chunk-XE4FDK4O.js} +3 -3
  38. package/dist/{chunk-E7ZO44KU.js.map → chunk-XE4FDK4O.js.map} +1 -1
  39. package/dist/chunk-XWSXCBP2.js +46 -0
  40. package/dist/chunk-XWSXCBP2.js.map +1 -0
  41. package/dist/{chunk-TRYZ75AV.js → chunk-ZAOJ5JY5.js} +3 -3
  42. package/dist/{chunk-TRYZ75AV.js.map → chunk-ZAOJ5JY5.js.map} +1 -1
  43. package/dist/index.d.ts +6 -0
  44. package/dist/index.js +300 -138
  45. package/dist/index.js.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/Files-YO5YOKD7.js +0 -9
  48. package/dist/Preview-UMGXEYCV.js +0 -8
  49. package/dist/chunk-6JAAM5TA.js.map +0 -1
  50. package/dist/chunk-ONQJGZCL.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -606,6 +606,8 @@ interface ChangelogEntry {
606
606
  changes: string[];
607
607
  }
608
608
 
609
+ type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';
610
+
609
611
  interface DesktopItem {
610
612
  entityType: string;
611
613
  entityId: string;
@@ -613,6 +615,10 @@ interface DesktopItem {
613
615
  x?: number;
614
616
  y?: number;
615
617
  folderId?: string;
618
+ folderX?: number;
619
+ folderY?: number;
620
+ filePath?: string;
621
+ fileKind?: PreviewFileKind;
616
622
  }
617
623
  interface DesktopFolder {
618
624
  id: string;
package/dist/index.js CHANGED
@@ -5,18 +5,20 @@ import { subscribePomo, getPomoSnapshot } from './chunk-MK3HLUO4.js';
5
5
  import { useGoogleAuth } from './chunk-MVWEL34Y.js';
6
6
  import { useShellPrefs } from './chunk-36VM54SC.js';
7
7
  export { ShellPrefsProvider, useLocalStoragePrefs, useShellPrefs } from './chunk-36VM54SC.js';
8
+ import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-XWSXCBP2.js';
8
9
  import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
9
- import { setPdfPreview } from './chunk-TRYZ75AV.js';
10
+ import { setPdfPreview } from './chunk-ZAOJ5JY5.js';
10
11
  import './chunk-KUIPWCTJ.js';
11
12
  import { toast_default } from './chunk-WIJ45SYD.js';
12
13
  export { toast_default as toast } from './chunk-WIJ45SYD.js';
13
- import { useAuth } from './chunk-ADJ3CERD.js';
14
- export { ShellAuthProvider, setShellAuthBridge, useShellAuth } from './chunk-ADJ3CERD.js';
14
+ import './chunk-JRUZT46J.js';
15
15
  export { EditableGrid } from './chunk-GP4Y3VCB.js';
16
- import { useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, Modal, useIsMobile, ModalActions, useModalActive, WINDOW_REGISTRY, isPageEntry, LoadingSpinner, ThumbCard, activateModal } from './chunk-ONQJGZCL.js';
17
- export { CancelButton, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, commitExposeHighlight, exitExposeMode, getActiveWindowRoute, getExposeHighlight, isEntityEntry, isPageEntry, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, subscribeExposeHighlight, toggleExposeMode, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-ONQJGZCL.js';
16
+ import { useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, Modal, useIsMobile, ModalActions, useModalActive, WINDOW_REGISTRY, isPageEntry, LoadingSpinner, ThumbCard, activateModal } from './chunk-DVS3TOYB.js';
17
+ export { CancelButton, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, commitExposeHighlight, exitExposeMode, getActiveWindowRoute, getExposeHighlight, isEntityEntry, isPageEntry, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, subscribeExposeHighlight, toggleExposeMode, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-DVS3TOYB.js';
18
18
  import { confirm } from './chunk-PLGHQ7QW.js';
19
19
  export { ConfirmProvider, confirm, confirmDestructive, prompt } from './chunk-PLGHQ7QW.js';
20
+ import { useAuth } from './chunk-ADJ3CERD.js';
21
+ export { ShellAuthProvider, setShellAuthBridge, useShellAuth } from './chunk-ADJ3CERD.js';
20
22
  import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, sectionIcons } from './chunk-SSA762W5.js';
21
23
  export { GLASS_DIVIDER, GLASS_INPUT_BG, glassStyle, setShellNavIcons } from './chunk-SSA762W5.js';
22
24
  import { createContext, lazy, useState, useRef, useEffect, useCallback, useLayoutEffect, useContext, Suspense, isValidElement, cloneElement, useSyncExternalStore, useMemo } from 'react';
@@ -913,7 +915,7 @@ function StatusBadge({ status }) {
913
915
  }
914
916
 
915
917
  // src/version.ts
916
- var VERSION = "0.2.62" ;
918
+ var VERSION = "0.2.66" ;
917
919
  var APP_VERSION = VERSION;
918
920
 
919
921
  // src/changelog.ts
@@ -1010,6 +1012,45 @@ var ENTITY_ICONS = {
1010
1012
  proposal: "PR",
1011
1013
  folder: "FLD"
1012
1014
  };
1015
+ var PREVIEW_FILE_CODES = {
1016
+ pdf: "PDF",
1017
+ dxf: "DXF",
1018
+ "3d": "STP",
1019
+ image: "IMG",
1020
+ csv: "CSV"
1021
+ };
1022
+ var PREVIEW_FILE_COLORS = {
1023
+ pdf: "text-red-600",
1024
+ dxf: "text-blue-600",
1025
+ "3d": "text-purple-600",
1026
+ image: "text-emerald-600",
1027
+ csv: "text-green-600"
1028
+ };
1029
+ var DOCUMENTS_FOLDER_ID = "documents";
1030
+ var DOCUMENTS_FOLDER_NAME = "Documents";
1031
+ function FileIconTile({ entityType, isSelected, entityId, label, fileKind }) {
1032
+ const isPreviewFile = entityType === "preview-file" && fileKind;
1033
+ const previewColor2 = isPreviewFile ? PREVIEW_FILE_COLORS[fileKind] : null;
1034
+ const previewCode = isPreviewFile ? PREVIEW_FILE_CODES[fileKind] : null;
1035
+ if (entityType === "folder") {
1036
+ return /* @__PURE__ */ jsx("div", { className: `w-12 h-12 flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: /* @__PURE__ */ jsxs("svg", { className: "h-12 w-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]", viewBox: "0 0 48 48", children: [
1037
+ /* @__PURE__ */ jsx("path", { d: "M6 12a4 4 0 014-4h10l4 4h14a4 4 0 014 4v20a4 4 0 01-4 4H10a4 4 0 01-4-4V12z", fill: "white", stroke: "#eab308", strokeWidth: "2", strokeLinejoin: "round" }),
1038
+ /* @__PURE__ */ jsx("path", { d: "M6 18h36", stroke: "#eab308", strokeWidth: "1.5" })
1039
+ ] }) });
1040
+ }
1041
+ if (entityType === "page") {
1042
+ const icon = label && navIcons[label] || (entityId ? navIcons[entityId] : void 0);
1043
+ return /* @__PURE__ */ jsx("div", { className: `w-12 h-12 flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: icon && isValidElement(icon) ? cloneElement(icon, { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]" }) : /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6z" }) }) });
1044
+ }
1045
+ return /* @__PURE__ */ jsxs("div", { className: `w-12 h-12 relative flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: [
1046
+ /* @__PURE__ */ jsxs("svg", { className: `w-10 h-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)] ${previewColor2 ?? ENTITY_ICON_COLORS[entityType] ?? "text-gray-500"}`, viewBox: "0 0 40 48", fill: "none", children: [
1047
+ /* @__PURE__ */ jsx("path", { d: "M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z", fill: "white", fillOpacity: "0.92" }),
1048
+ /* @__PURE__ */ jsx("path", { d: "M26 0l10 10H30a4 4 0 01-4-4V0z", fill: "currentColor", fillOpacity: "0.2" }),
1049
+ /* @__PURE__ */ jsx("path", { d: "M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z", stroke: "currentColor", strokeWidth: "1.5", strokeOpacity: "0.5" })
1050
+ ] }),
1051
+ /* @__PURE__ */ jsx("span", { className: `absolute inset-0 flex items-center justify-center text-[9px] font-bold pt-2 ${previewColor2 ?? ENTITY_ICON_COLORS[entityType] ?? "text-gray-600"}`, children: previewCode ?? ENTITY_ICONS[entityType] ?? entityType.slice(0, 3).toUpperCase() })
1052
+ ] });
1053
+ }
1013
1054
  var GRID = 90;
1014
1055
  function snapToGrid(x, y) {
1015
1056
  return { x: Math.round(x / GRID) * GRID, y: Math.round(y / GRID) * GRID };
@@ -1021,13 +1062,35 @@ function DesktopHostProvider({ value, children }) {
1021
1062
  function useDesktopHost() {
1022
1063
  return useContext(DesktopHostContext);
1023
1064
  }
1024
- function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder }) {
1065
+ var FOLDER_GRID = 90;
1066
+ var FOLDER_ITEM_H = 80;
1067
+ var FOLDER_PAD = 12;
1068
+ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onSetFolderPosition, onDragOutToDesktop }) {
1025
1069
  const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
1026
1070
  const [rubber, setRubber] = useState(null);
1027
1071
  const didDragRubber = useRef(false);
1028
1072
  const bodyRef = useRef(null);
1029
- const [dragIdx, setDragIdx] = useState(null);
1030
- const [dropIdx, setDropIdx] = useState(null);
1073
+ const [bodyWidth, setBodyWidth] = useState(800);
1074
+ const setDragOutsideClass = (on) => {
1075
+ const el = bodyRef.current;
1076
+ if (!el) return;
1077
+ el.style.outline = on ? "2px dashed rgb(96 165 250 / 0.6)" : "";
1078
+ el.style.outlineOffset = on ? "-2px" : "";
1079
+ };
1080
+ useEffect(() => {
1081
+ const el = bodyRef.current;
1082
+ if (!el) return;
1083
+ const update = () => setBodyWidth(el.clientWidth);
1084
+ update();
1085
+ const ro = new ResizeObserver(update);
1086
+ ro.observe(el);
1087
+ return () => ro.disconnect();
1088
+ }, []);
1089
+ const cols = Math.max(1, Math.floor((bodyWidth - FOLDER_PAD) / FOLDER_GRID));
1090
+ const getItemFolderPos = (item, idx) => {
1091
+ if (item.folderX != null && item.folderY != null) return { x: item.folderX, y: item.folderY };
1092
+ return { x: FOLDER_PAD + idx % cols * FOLDER_GRID, y: FOLDER_PAD + Math.floor(idx / cols) * FOLDER_GRID };
1093
+ };
1031
1094
  const toggleSelect = (i, e) => {
1032
1095
  e.stopPropagation();
1033
1096
  if (e.shiftKey || e.metaKey || e.ctrlKey) {
@@ -1036,53 +1099,57 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1036
1099
  next.has(i) ? next.delete(i) : next.add(i);
1037
1100
  return next;
1038
1101
  });
1039
- } else {
1102
+ } else if (!selected.has(i)) {
1040
1103
  setSelected(/* @__PURE__ */ new Set([i]));
1041
1104
  }
1042
1105
  };
1043
1106
  const startRubber = (e) => {
1044
1107
  if (e.button !== 0 || e.target !== bodyRef.current) return;
1045
- const r = bodyRef.current.getBoundingClientRect();
1046
- const x = e.clientX - r.left;
1047
- const y = e.clientY - r.top;
1108
+ const el = bodyRef.current;
1109
+ const r = el.getBoundingClientRect();
1110
+ const x = e.clientX - r.left + el.scrollLeft;
1111
+ const y = e.clientY - r.top + el.scrollTop;
1048
1112
  setRubber({ x1: x, y1: y, x2: x, y2: y });
1049
1113
  didDragRubber.current = false;
1050
1114
  setSelected(/* @__PURE__ */ new Set());
1051
1115
  };
1052
1116
  useEffect(() => {
1053
1117
  if (!rubber) return;
1118
+ const computeSelection = (minX, maxX, minY, maxY) => {
1119
+ const next = /* @__PURE__ */ new Set();
1120
+ const r = bodyRef.current;
1121
+ if (!r) return next;
1122
+ const tiles = r.querySelectorAll("[data-folder-item]");
1123
+ const containerRect = r.getBoundingClientRect();
1124
+ tiles.forEach((t) => {
1125
+ const tr = t.getBoundingClientRect();
1126
+ const tx = tr.left - containerRect.left + r.scrollLeft;
1127
+ const ty = tr.top - containerRect.top + r.scrollTop;
1128
+ if (tx + tr.width > minX && tx < maxX && ty + tr.height > minY && ty < maxY) {
1129
+ const i = parseInt(t.getAttribute("data-folder-item") || "-1", 10);
1130
+ if (i >= 0) next.add(i);
1131
+ }
1132
+ });
1133
+ return next;
1134
+ };
1054
1135
  const move = (e) => {
1055
- const r = bodyRef.current?.getBoundingClientRect();
1056
- if (!r) return;
1057
- const x = e.clientX - r.left;
1058
- const y = e.clientY - r.top;
1136
+ const el = bodyRef.current;
1137
+ if (!el) return;
1138
+ const r = el.getBoundingClientRect();
1139
+ const x = e.clientX - r.left + el.scrollLeft;
1140
+ const y = e.clientY - r.top + el.scrollTop;
1059
1141
  const dx = x - rubber.x1, dy = y - rubber.y1;
1060
1142
  if (dx * dx + dy * dy > 16) didDragRubber.current = true;
1061
1143
  setRubber((prev) => prev ? { ...prev, x2: x, y2: y } : null);
1062
- };
1063
- const up = () => {
1064
- const next = /* @__PURE__ */ new Set();
1065
- const r = bodyRef.current;
1066
- if (r && rubber) {
1067
- const minX = Math.min(rubber.x1, rubber.x2);
1068
- const maxX = Math.max(rubber.x1, rubber.x2);
1069
- const minY = Math.min(rubber.y1, rubber.y2);
1070
- const maxY = Math.max(rubber.y1, rubber.y2);
1071
- const tiles = r.querySelectorAll("[data-folder-item]");
1072
- const containerRect = r.getBoundingClientRect();
1073
- tiles.forEach((t) => {
1074
- const tr = t.getBoundingClientRect();
1075
- const tx = tr.left - containerRect.left;
1076
- const ty = tr.top - containerRect.top;
1077
- if (tx + tr.width > minX && tx < maxX && ty + tr.height > minY && ty < maxY) {
1078
- const i = parseInt(t.getAttribute("data-folder-item") || "-1", 10);
1079
- if (i >= 0) next.add(i);
1080
- }
1081
- });
1144
+ if (didDragRubber.current) {
1145
+ const minX = Math.min(rubber.x1, x);
1146
+ const maxX = Math.max(rubber.x1, x);
1147
+ const minY = Math.min(rubber.y1, y);
1148
+ const maxY = Math.max(rubber.y1, y);
1149
+ setSelected(computeSelection(minX, maxX, minY, maxY));
1082
1150
  }
1083
- if (didDragRubber.current) setSelected(next);
1084
- setRubber(null);
1085
1151
  };
1152
+ const up = () => setRubber(null);
1086
1153
  window.addEventListener("pointermove", move);
1087
1154
  window.addEventListener("pointerup", up);
1088
1155
  return () => {
@@ -1090,28 +1157,75 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1090
1157
  window.removeEventListener("pointerup", up);
1091
1158
  };
1092
1159
  }, [rubber]);
1093
- const onItemDragStart = (i) => (e) => {
1094
- e.dataTransfer.effectAllowed = "move";
1095
- e.dataTransfer.setData("text/plain", String(i));
1096
- setDragIdx(i);
1097
- };
1098
- const onItemDragOver = (i) => (e) => {
1099
- e.preventDefault();
1100
- if (dragIdx !== null && dragIdx !== i) setDropIdx(i);
1101
- };
1102
- const onItemDrop = (i) => (e) => {
1160
+ const dragEntriesRef = useRef([]);
1161
+ const dragStartRef = useRef(null);
1162
+ const startItemDrag = (i, e) => {
1163
+ if (e.button !== 0) return;
1164
+ e.stopPropagation();
1103
1165
  e.preventDefault();
1104
- if (dragIdx === null || dragIdx === i) {
1105
- setDragIdx(null);
1106
- setDropIdx(null);
1107
- return;
1108
- }
1109
- const next = [...items];
1110
- const [moved] = next.splice(dragIdx, 1);
1111
- next.splice(i, 0, moved);
1112
- onReorder(next);
1113
- setDragIdx(null);
1114
- setDropIdx(null);
1166
+ const useMulti = selected.has(i) && selected.size > 1;
1167
+ const idxs = useMulti ? Array.from(selected) : [i];
1168
+ if (!useMulti) setSelected(/* @__PURE__ */ new Set([i]));
1169
+ const entries = idxs.map((idx) => {
1170
+ const item = items[idx];
1171
+ const pos = getItemFolderPos(item, idx);
1172
+ const el = bodyRef.current?.querySelector(`[data-folder-item="${idx}"]`) ?? null;
1173
+ return { idx, item, origX: pos.x, origY: pos.y, el };
1174
+ }).filter((e2) => e2.item);
1175
+ dragEntriesRef.current = entries;
1176
+ dragStartRef.current = { clientX: e.clientX, clientY: e.clientY };
1177
+ setDragOutsideClass(false);
1178
+ let moved = false;
1179
+ const move = (ev) => {
1180
+ const start = dragStartRef.current;
1181
+ if (!start) return;
1182
+ const dx = ev.clientX - start.clientX;
1183
+ const dy = ev.clientY - start.clientY;
1184
+ if (!moved && (Math.abs(dx) > 3 || Math.abs(dy) > 3)) moved = true;
1185
+ if (!moved) return;
1186
+ for (const entry of dragEntriesRef.current) {
1187
+ if (!entry.el) continue;
1188
+ entry.el.style.left = `${entry.origX + dx}px`;
1189
+ entry.el.style.top = `${entry.origY + dy}px`;
1190
+ entry.el.style.zIndex = "100";
1191
+ entry.el.style.opacity = "0.85";
1192
+ }
1193
+ const r = bodyRef.current?.getBoundingClientRect();
1194
+ const inside = !!r && ev.clientX >= r.left && ev.clientX <= r.right && ev.clientY >= r.top && ev.clientY <= r.bottom;
1195
+ setDragOutsideClass(!inside);
1196
+ };
1197
+ const up = (ev) => {
1198
+ window.removeEventListener("pointermove", move);
1199
+ window.removeEventListener("pointerup", up);
1200
+ const entries2 = dragEntriesRef.current;
1201
+ dragEntriesRef.current = [];
1202
+ dragStartRef.current = null;
1203
+ for (const entry of entries2) {
1204
+ if (!entry.el) continue;
1205
+ entry.el.style.zIndex = "";
1206
+ entry.el.style.opacity = "";
1207
+ }
1208
+ setDragOutsideClass(false);
1209
+ if (!moved) return;
1210
+ const r = bodyRef.current?.getBoundingClientRect();
1211
+ const inside = !!r && ev.clientX >= r.left && ev.clientX <= r.right && ev.clientY >= r.top && ev.clientY <= r.bottom;
1212
+ if (!inside) {
1213
+ const drops = entries2.map((e2) => ({ item: e2.item, clientX: ev.clientX, clientY: ev.clientY }));
1214
+ setSelected(/* @__PURE__ */ new Set());
1215
+ onDragOutToDesktop(drops);
1216
+ } else {
1217
+ const updates = entries2.map((e2) => {
1218
+ const leftStr = e2.el?.style.left || `${e2.origX}px`;
1219
+ const topStr = e2.el?.style.top || `${e2.origY}px`;
1220
+ const x = Math.max(0, parseFloat(leftStr));
1221
+ const y = Math.max(0, parseFloat(topStr));
1222
+ return { item: e2.item, x, y };
1223
+ });
1224
+ onSetFolderPosition(updates);
1225
+ }
1226
+ };
1227
+ window.addEventListener("pointermove", move);
1228
+ window.addEventListener("pointerup", up);
1115
1229
  };
1116
1230
  const moveSelectedOut = () => {
1117
1231
  if (selected.size === 0) return;
@@ -1119,6 +1233,10 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1119
1233
  setSelected(/* @__PURE__ */ new Set());
1120
1234
  };
1121
1235
  const folderIcon = /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-amber-500", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M2 6a2 2 0 012-2h5l2 2h9a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" }) });
1236
+ const contentHeight = items.reduce((max, item, i) => {
1237
+ const pos = getItemFolderPos(item, i);
1238
+ return Math.max(max, pos.y + FOLDER_ITEM_H + FOLDER_PAD);
1239
+ }, 300);
1122
1240
  return /* @__PURE__ */ jsx(Modal, { open: true, onClose, title: folder.name, icon: folderIcon, size: "lg", children: /* @__PURE__ */ jsxs(
1123
1241
  "div",
1124
1242
  {
@@ -1136,7 +1254,7 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1136
1254
  background: "linear-gradient(135deg, rgba(254, 243, 199, 0.55) 0%, rgba(253, 230, 138, 0.4) 50%, rgba(252, 211, 77, 0.3) 100%)"
1137
1255
  },
1138
1256
  children: [
1139
- selected.size > 0 && /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 mb-2 flex items-center gap-2 px-2 py-1 rounded-md bg-white/80 backdrop-blur-sm shadow border border-gray-200 text-xs text-gray-700 w-fit", children: [
1257
+ selected.size > 0 && /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-20 mb-2 flex items-center gap-2 px-2 py-1 rounded-md bg-white/80 backdrop-blur-sm shadow border border-gray-200 text-xs text-gray-700 w-fit", children: [
1140
1258
  /* @__PURE__ */ jsxs("span", { children: [
1141
1259
  selected.size,
1142
1260
  " selected"
@@ -1144,40 +1262,22 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1144
1262
  /* @__PURE__ */ jsx("button", { onClick: moveSelectedOut, className: "px-2 py-0.5 rounded text-blue-600 hover:bg-blue-50", children: "Move to desktop" }),
1145
1263
  /* @__PURE__ */ jsx("button", { onClick: () => setSelected(/* @__PURE__ */ new Set()), className: "px-2 py-0.5 rounded text-gray-500 hover:bg-gray-100", children: "Clear" })
1146
1264
  ] }),
1147
- items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 text-center py-8 italic", children: "Folder is empty. Drag documents here." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3", children: items.map((item, i) => {
1265
+ items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 text-center py-8 italic", children: "Folder is empty. Drag documents here." }) : /* @__PURE__ */ jsx("div", { className: "relative", style: { height: contentHeight }, children: items.map((item, i) => {
1148
1266
  const isSelected = selected.has(i);
1149
- const isDropTarget = dropIdx === i && dragIdx !== i;
1150
- return /* @__PURE__ */ jsxs(
1267
+ const pos = getItemFolderPos(item, i);
1268
+ return /* @__PURE__ */ jsx(
1151
1269
  "div",
1152
1270
  {
1153
1271
  "data-folder-item": i,
1154
- draggable: true,
1155
- onDragStart: onItemDragStart(i),
1156
- onDragOver: onItemDragOver(i),
1157
- onDrop: onItemDrop(i),
1158
- onDragEnd: () => {
1159
- setDragIdx(null);
1160
- setDropIdx(null);
1161
- },
1272
+ onPointerDown: (e) => startItemDrag(i, e),
1162
1273
  onClick: (e) => toggleSelect(i, e),
1163
1274
  onDoubleClick: () => onOpen(item),
1164
- className: `group relative flex flex-col items-center gap-1 w-20 p-2 rounded-lg cursor-default transition-colors ${isSelected ? "bg-blue-200/60 ring-2 ring-blue-400" : "hover:bg-white/60"} ${isDropTarget ? "ring-2 ring-blue-500 ring-dashed" : ""} ${dragIdx === i ? "opacity-40" : ""}`,
1165
- children: [
1166
- /* @__PURE__ */ jsx(
1167
- "button",
1168
- {
1169
- onClick: (e) => {
1170
- e.stopPropagation();
1171
- onMoveOut([item]);
1172
- },
1173
- title: "Move to desktop",
1174
- className: "absolute -top-1 -right-1 h-5 w-5 rounded-full bg-gray-200 text-gray-500 flex items-center justify-center text-[10px] opacity-0 group-hover:opacity-100 transition-opacity hover:bg-red-100 hover:text-red-600 shadow-sm z-10",
1175
- children: /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" }) })
1176
- }
1177
- ),
1178
- /* @__PURE__ */ jsx("div", { className: `w-12 h-12 rounded-lg bg-white shadow flex items-center justify-center text-xs font-bold ${ENTITY_ICON_COLORS[item.entityType] || "text-gray-600"}`, children: ENTITY_ICONS[item.entityType] || item.entityType.slice(0, 3).toUpperCase() }),
1179
- /* @__PURE__ */ jsx("span", { className: `text-[10px] font-medium text-center leading-tight truncate w-full ${isSelected ? "text-blue-900" : "text-gray-700"}`, children: item.label })
1180
- ]
1275
+ style: { position: "absolute", left: pos.x, top: pos.y },
1276
+ className: "cursor-default select-none",
1277
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1278
+ /* @__PURE__ */ jsx(FileIconTile, { entityType: item.entityType, isSelected, entityId: item.entityId, label: item.label, fileKind: item.fileKind }),
1279
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] font-medium text-center leading-tight truncate w-full ${isSelected ? "text-blue-900 bg-blue-200/60 rounded px-1" : "text-gray-700"}`, children: item.label })
1280
+ ] })
1181
1281
  },
1182
1282
  `${item.entityType}-${item.entityId}-${i}`
1183
1283
  );
@@ -1321,6 +1421,32 @@ function Desktop({ profile }) {
1321
1421
  if (host.saveSnap) host.saveSnap(v);
1322
1422
  else saveShellPrefs({ desktop_snap: v });
1323
1423
  }, [host, saveShellPrefs]);
1424
+ const previewStateRef = useRef({ folders, favDocs, saveFolders, saveDocs });
1425
+ previewStateRef.current = { folders, favDocs, saveFolders, saveDocs };
1426
+ useEffect(() => {
1427
+ const handler = (e) => {
1428
+ const detail = e.detail;
1429
+ if (!detail?.filePath) return;
1430
+ const { folders: fs, favDocs: docs, saveFolders: sf, saveDocs: sd } = previewStateRef.current;
1431
+ if (!fs.some((f) => f.id === DOCUMENTS_FOLDER_ID)) {
1432
+ sf([...fs, { id: DOCUMENTS_FOLDER_ID, name: DOCUMENTS_FOLDER_NAME }]);
1433
+ }
1434
+ const deduped = docs.filter(
1435
+ (d) => !(d.entityType === "preview-file" && d.filePath === detail.filePath)
1436
+ );
1437
+ const next = {
1438
+ entityType: "preview-file",
1439
+ entityId: detail.filePath,
1440
+ label: detail.filename,
1441
+ filePath: detail.filePath,
1442
+ fileKind: detail.kind,
1443
+ folderId: DOCUMENTS_FOLDER_ID
1444
+ };
1445
+ sd([next, ...deduped]);
1446
+ };
1447
+ window.addEventListener(PREVIEW_OPENED_EVENT, handler);
1448
+ return () => window.removeEventListener(PREVIEW_OPENED_EVENT, handler);
1449
+ }, []);
1324
1450
  const getDefaultPos = (idx) => {
1325
1451
  const col = Math.floor(idx / 8);
1326
1452
  const row = idx % 8;
@@ -1451,7 +1577,7 @@ function Desktop({ profile }) {
1451
1577
  const desktopIdx = favDocs.indexOf(desktopItems[move2.entry.idx]);
1452
1578
  if (desktopIdx === -1) continue;
1453
1579
  if (droppedOnFolder) {
1454
- updated[desktopIdx] = { ...updated[desktopIdx], folderId: droppedOnFolder.id, x: void 0, y: void 0 };
1580
+ updated[desktopIdx] = { ...updated[desktopIdx], folderId: droppedOnFolder.id, x: void 0, y: void 0, folderX: void 0, folderY: void 0 };
1455
1581
  } else {
1456
1582
  updated[desktopIdx] = { ...updated[desktopIdx], x: move2.finalRight, y: move2.finalTop, folderId: void 0 };
1457
1583
  positionsPatch[`item-${desktopIdx}`] = { right: move2.finalRight, top: move2.finalTop };
@@ -1504,6 +1630,25 @@ function Desktop({ profile }) {
1504
1630
  };
1505
1631
  useEffect(() => {
1506
1632
  if (!rubberBand) return;
1633
+ const computeSelection = (minX, maxX, minY, maxY) => {
1634
+ const sel = /* @__PURE__ */ new Set();
1635
+ const cw = containerRef.current?.clientWidth || 800;
1636
+ desktopItems.forEach((item, i) => {
1637
+ const pos = getItemPos(item, i);
1638
+ const leftX = cw - pos.right - 80;
1639
+ if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
1640
+ sel.add(`item-${i}`);
1641
+ }
1642
+ });
1643
+ folders.forEach((f, i) => {
1644
+ const pos = getFolderPos(f, i);
1645
+ const leftX = cw - pos.right - 80;
1646
+ if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
1647
+ sel.add(`folder-${i}`);
1648
+ }
1649
+ });
1650
+ return sel;
1651
+ };
1507
1652
  const move = (e) => {
1508
1653
  const rect = containerRef.current?.getBoundingClientRect();
1509
1654
  if (!rect) return;
@@ -1513,31 +1658,15 @@ function Desktop({ profile }) {
1513
1658
  const dy = y - rubberBand.startY;
1514
1659
  if (dx * dx + dy * dy > 16) didRubberBandDragRef.current = true;
1515
1660
  setRubberBand((prev) => prev ? { ...prev, endX: x, endY: y } : null);
1661
+ if (didRubberBandDragRef.current) {
1662
+ const minX = Math.min(rubberBand.startX, x);
1663
+ const maxX = Math.max(rubberBand.startX, x);
1664
+ const minY = Math.min(rubberBand.startY, y);
1665
+ const maxY = Math.max(rubberBand.startY, y);
1666
+ setSelected(computeSelection(minX, maxX, minY, maxY));
1667
+ }
1516
1668
  };
1517
1669
  const up = () => {
1518
- if (rubberBand) {
1519
- const minX = Math.min(rubberBand.startX, rubberBand.endX);
1520
- const maxX = Math.max(rubberBand.startX, rubberBand.endX);
1521
- const minY = Math.min(rubberBand.startY, rubberBand.endY);
1522
- const maxY = Math.max(rubberBand.startY, rubberBand.endY);
1523
- const sel = /* @__PURE__ */ new Set();
1524
- const cw = containerRef.current?.clientWidth || 800;
1525
- desktopItems.forEach((item, i) => {
1526
- const pos = getItemPos(item, i);
1527
- const leftX = cw - pos.right - 80;
1528
- if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
1529
- sel.add(`item-${i}`);
1530
- }
1531
- });
1532
- folders.forEach((f, i) => {
1533
- const pos = getFolderPos(f, i);
1534
- const leftX = cw - pos.right - 80;
1535
- if (leftX + 40 > minX && leftX < maxX && pos.top + 40 > minY && pos.top < maxY) {
1536
- sel.add(`folder-${i}`);
1537
- }
1538
- });
1539
- setSelected(sel);
1540
- }
1541
1670
  setRubberBand(null);
1542
1671
  };
1543
1672
  window.addEventListener("pointermove", move);
@@ -1724,24 +1853,8 @@ function Desktop({ profile }) {
1724
1853
  purple: "bg-purple-100 border-purple-300",
1725
1854
  orange: "bg-orange-100 border-orange-300"
1726
1855
  };
1727
- const renderIcon = (entityType, label, isSelected, entityId) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1728
- entityType === "folder" ? /* @__PURE__ */ jsx("div", { className: `w-12 h-12 flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: /* @__PURE__ */ jsxs("svg", { className: "h-12 w-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]", viewBox: "0 0 48 48", children: [
1729
- /* @__PURE__ */ jsx("path", { d: "M6 12a4 4 0 014-4h10l4 4h14a4 4 0 014 4v20a4 4 0 01-4 4H10a4 4 0 01-4-4V12z", fill: "white", stroke: "#eab308", strokeWidth: "2", strokeLinejoin: "round" }),
1730
- /* @__PURE__ */ jsx("path", { d: "M6 18h36", stroke: "#eab308", strokeWidth: "1.5" })
1731
- ] }) }) : entityType === "page" ? /* @__PURE__ */ jsx("div", { className: `w-12 h-12 flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: (() => {
1732
- const icon = navIcons[label] || (entityId ? navIcons[entityId] : void 0);
1733
- if (icon && isValidElement(icon)) {
1734
- return cloneElement(icon, { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]" });
1735
- }
1736
- return /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6z" }) });
1737
- })() }) : /* @__PURE__ */ jsxs("div", { className: `w-12 h-12 relative flex items-center justify-center ${isSelected ? "rounded-lg bg-blue-400/30 ring-2 ring-blue-500" : ""}`, children: [
1738
- /* @__PURE__ */ jsxs("svg", { className: `w-10 h-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)] ${ENTITY_ICON_COLORS[entityType] || "text-gray-500"}`, viewBox: "0 0 40 48", fill: "none", children: [
1739
- /* @__PURE__ */ jsx("path", { d: "M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z", fill: "white", fillOpacity: "0.92" }),
1740
- /* @__PURE__ */ jsx("path", { d: "M26 0l10 10H30a4 4 0 01-4-4V0z", fill: "currentColor", fillOpacity: "0.2" }),
1741
- /* @__PURE__ */ jsx("path", { d: "M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z", stroke: "currentColor", strokeWidth: "1.5", strokeOpacity: "0.5" })
1742
- ] }),
1743
- /* @__PURE__ */ jsx("span", { className: `absolute inset-0 flex items-center justify-center text-[9px] font-bold pt-2 ${ENTITY_ICON_COLORS[entityType] || "text-gray-600"}`, children: ENTITY_ICONS[entityType] || entityType.slice(0, 3).toUpperCase() })
1744
- ] }),
1856
+ const renderIcon = (entityType, label, isSelected, entityId, fileKind) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1857
+ /* @__PURE__ */ jsx(FileIconTile, { entityType, isSelected, entityId, label, fileKind }),
1745
1858
  /* @__PURE__ */ jsx("span", { className: `text-[10px] font-medium text-center leading-tight w-full drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)] ${isSelected ? "text-blue-200 bg-blue-600/60 rounded px-1" : "text-white"}`, children: label })
1746
1859
  ] });
1747
1860
  const menuStyle = (x, y) => ({
@@ -1866,10 +1979,21 @@ function Desktop({ profile }) {
1866
1979
  onContextMenu: (e) => handleItemContextMenu(e, i),
1867
1980
  onDoubleClick: (e) => {
1868
1981
  e.stopPropagation();
1869
- doc.entityType === "page" ? openPage(doc.entityId) : openEntity(doc.entityType, doc.entityId, null, doc.label);
1982
+ if (doc.entityType === "preview-file" && doc.filePath && doc.fileKind) {
1983
+ openPreviewFile({
1984
+ filePath: doc.filePath,
1985
+ filename: doc.label,
1986
+ kind: doc.fileKind,
1987
+ onStaged: (route) => openPage(route)
1988
+ });
1989
+ } else if (doc.entityType === "page") {
1990
+ openPage(doc.entityId);
1991
+ } else {
1992
+ openEntity(doc.entityType, doc.entityId, null, doc.label);
1993
+ }
1870
1994
  },
1871
1995
  className: "cursor-default select-none",
1872
- children: renderIcon(doc.entityType, doc.label, isSelected, doc.entityId)
1996
+ children: renderIcon(doc.entityType, doc.label, isSelected, doc.entityId, doc.fileKind)
1873
1997
  },
1874
1998
  `item-${doc.entityType}-${doc.entityId}-${i}`
1875
1999
  );
@@ -2161,17 +2285,55 @@ function Desktop({ profile }) {
2161
2285
  folder,
2162
2286
  items: folderItems(openFolder),
2163
2287
  onClose: () => setOpenFolder(null),
2164
- onOpen: (item) => openEntity(item.entityType, item.entityId, null, item.label),
2288
+ onOpen: (item) => {
2289
+ if (item.entityType === "preview-file" && item.filePath && item.fileKind) {
2290
+ openPreviewFile({
2291
+ filePath: item.filePath,
2292
+ filename: item.label,
2293
+ kind: item.fileKind,
2294
+ onStaged: (route) => openPage(route)
2295
+ });
2296
+ } else {
2297
+ openEntity(item.entityType, item.entityId, null, item.label);
2298
+ }
2299
+ },
2165
2300
  onMoveOut: (toMove) => {
2166
2301
  const ids = new Set(toMove.map((t) => `${t.entityType}|${t.entityId}`));
2167
2302
  const updated = favDocs.map(
2168
- (d) => d.folderId === openFolder && ids.has(`${d.entityType}|${d.entityId}`) ? { ...d, folderId: void 0 } : d
2303
+ (d) => d.folderId === openFolder && ids.has(`${d.entityType}|${d.entityId}`) ? { ...d, folderId: void 0, folderX: void 0, folderY: void 0 } : d
2169
2304
  );
2170
2305
  saveDocs(updated);
2171
2306
  },
2172
- onReorder: (nextItems) => {
2173
- const others = favDocs.filter((d) => d.folderId !== openFolder);
2174
- saveDocs([...others, ...nextItems]);
2307
+ onSetFolderPosition: (updates) => {
2308
+ const patch = new Map(updates.map((u) => [`${u.item.entityType}|${u.item.entityId}`, u]));
2309
+ const updated = favDocs.map((d) => {
2310
+ const u = patch.get(`${d.entityType}|${d.entityId}`);
2311
+ return u ? { ...d, folderX: u.x, folderY: u.y } : d;
2312
+ });
2313
+ saveDocs(updated);
2314
+ },
2315
+ onDragOutToDesktop: (drops) => {
2316
+ const containerRect = containerRef.current?.getBoundingClientRect();
2317
+ const cw = containerRef.current?.clientWidth ?? window.innerWidth;
2318
+ const patch = /* @__PURE__ */ new Map();
2319
+ for (const d of drops) {
2320
+ const offX = containerRect ? d.clientX - containerRect.left : d.clientX;
2321
+ const offY = containerRect ? d.clientY - containerRect.top : d.clientY;
2322
+ let right = cw - offX - 40;
2323
+ let top = Math.max(0, offY - 40);
2324
+ if (snapEnabled) {
2325
+ const s = snapToGrid(right, top);
2326
+ right = s.x;
2327
+ top = s.y;
2328
+ }
2329
+ right = Math.max(0, right);
2330
+ patch.set(`${d.item.entityType}|${d.item.entityId}`, { right, top });
2331
+ }
2332
+ const updated = favDocs.map((d) => {
2333
+ const p = patch.get(`${d.entityType}|${d.entityId}`);
2334
+ return p ? { ...d, folderId: void 0, folderX: void 0, folderY: void 0, x: p.right, y: p.top } : d;
2335
+ });
2336
+ saveDocs(updated);
2175
2337
  }
2176
2338
  }
2177
2339
  );