react-os-shell 0.2.64 → 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 (48) hide show
  1. package/dist/{Browser-OL4SV34L.js → Browser-QPQX2OXO.js} +3 -3
  2. package/dist/{Browser-OL4SV34L.js.map → Browser-QPQX2OXO.js.map} +1 -1
  3. package/dist/{Calculator-VALSEOQD.js → Calculator-ZFY3ZB5X.js} +4 -4
  4. package/dist/{Calculator-VALSEOQD.js.map → Calculator-ZFY3ZB5X.js.map} +1 -1
  5. package/dist/{Calendar-I4OLRGNW.js → Calendar-LL5DUDDN.js} +3 -3
  6. package/dist/{Calendar-I4OLRGNW.js.map → Calendar-LL5DUDDN.js.map} +1 -1
  7. package/dist/{CurrencyConverter-3DLJRL24.js → CurrencyConverter-JGAC32DB.js} +4 -4
  8. package/dist/{CurrencyConverter-3DLJRL24.js.map → CurrencyConverter-JGAC32DB.js.map} +1 -1
  9. package/dist/{Documents-JLIRD3ML.js → Documents-XW7O22QF.js} +3 -3
  10. package/dist/{Documents-JLIRD3ML.js.map → Documents-XW7O22QF.js.map} +1 -1
  11. package/dist/{Email-PN2CNRLE.js → Email-PCBQUXE4.js} +3 -3
  12. package/dist/{Email-PN2CNRLE.js.map → Email-PCBQUXE4.js.map} +1 -1
  13. package/dist/Files-UATWOZM3.js +12 -0
  14. package/dist/{Files-VQCZUKNU.js.map → Files-UATWOZM3.js.map} +1 -1
  15. package/dist/{Minesweeper-IGIPALMP.js → Minesweeper-KBZXSRJF.js} +3 -3
  16. package/dist/{Minesweeper-IGIPALMP.js.map → Minesweeper-KBZXSRJF.js.map} +1 -1
  17. package/dist/{Notepad-Q5SSDZNW.js → Notepad-XPSVB6GJ.js} +3 -3
  18. package/dist/{Notepad-Q5SSDZNW.js.map → Notepad-XPSVB6GJ.js.map} +1 -1
  19. package/dist/{PomodoroTimer-PD2HIZMT.js → PomodoroTimer-7R2GTFUR.js} +4 -4
  20. package/dist/{PomodoroTimer-PD2HIZMT.js.map → PomodoroTimer-7R2GTFUR.js.map} +1 -1
  21. package/dist/Preview-OAGLCTU6.js +8 -0
  22. package/dist/{Preview-WFIQOE3T.js.map → Preview-OAGLCTU6.js.map} +1 -1
  23. package/dist/{Spreadsheet-Y5LACQRY.js → Spreadsheet-OTOBE6YI.js} +4 -4
  24. package/dist/{Spreadsheet-Y5LACQRY.js.map → Spreadsheet-OTOBE6YI.js.map} +1 -1
  25. package/dist/{Weather-GEA3GBQQ.js → Weather-DLZUHBXU.js} +4 -4
  26. package/dist/{Weather-GEA3GBQQ.js.map → Weather-DLZUHBXU.js.map} +1 -1
  27. package/dist/{WorldClock-W2ENBGFS.js → WorldClock-WVHQD5K7.js} +4 -4
  28. package/dist/{WorldClock-W2ENBGFS.js.map → WorldClock-WVHQD5K7.js.map} +1 -1
  29. package/dist/apps/index.js +19 -19
  30. package/dist/{chunk-JDRZX27J.js → chunk-4EMPRKHL.js} +4 -4
  31. package/dist/{chunk-JDRZX27J.js.map → chunk-4EMPRKHL.js.map} +1 -1
  32. package/dist/{chunk-4CFEJ4CC.js → chunk-DVS3TOYB.js} +15 -15
  33. package/dist/chunk-DVS3TOYB.js.map +1 -0
  34. package/dist/{chunk-MY5PL6MK.js → chunk-JRUZT46J.js} +3 -3
  35. package/dist/{chunk-MY5PL6MK.js.map → chunk-JRUZT46J.js.map} +1 -1
  36. package/dist/{chunk-VDNTGVGH.js → chunk-XE4FDK4O.js} +3 -3
  37. package/dist/{chunk-VDNTGVGH.js.map → chunk-XE4FDK4O.js.map} +1 -1
  38. package/dist/{chunk-2RIRISNW.js → chunk-XWSXCBP2.js} +4 -4
  39. package/dist/{chunk-2RIRISNW.js.map → chunk-XWSXCBP2.js.map} +1 -1
  40. package/dist/{chunk-BUJKR34D.js → chunk-ZAOJ5JY5.js} +3 -3
  41. package/dist/{chunk-BUJKR34D.js.map → chunk-ZAOJ5JY5.js.map} +1 -1
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.js +207 -119
  44. package/dist/index.js.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/Files-VQCZUKNU.js +0 -12
  47. package/dist/Preview-WFIQOE3T.js +0 -8
  48. package/dist/chunk-4CFEJ4CC.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -615,6 +615,8 @@ interface DesktopItem {
615
615
  x?: number;
616
616
  y?: number;
617
617
  folderId?: string;
618
+ folderX?: number;
619
+ folderY?: number;
618
620
  filePath?: string;
619
621
  fileKind?: PreviewFileKind;
620
622
  }
package/dist/index.js CHANGED
@@ -5,16 +5,16 @@ 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-2RIRISNW.js';
8
+ import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-XWSXCBP2.js';
9
9
  import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
10
- import { setPdfPreview } from './chunk-BUJKR34D.js';
10
+ import { setPdfPreview } from './chunk-ZAOJ5JY5.js';
11
11
  import './chunk-KUIPWCTJ.js';
12
12
  import { toast_default } from './chunk-WIJ45SYD.js';
13
13
  export { toast_default as toast } from './chunk-WIJ45SYD.js';
14
- import './chunk-MY5PL6MK.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-4CFEJ4CC.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-4CFEJ4CC.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
20
  import { useAuth } from './chunk-ADJ3CERD.js';
@@ -915,7 +915,7 @@ function StatusBadge({ status }) {
915
915
  }
916
916
 
917
917
  // src/version.ts
918
- var VERSION = "0.2.64" ;
918
+ var VERSION = "0.2.66" ;
919
919
  var APP_VERSION = VERSION;
920
920
 
921
921
  // src/changelog.ts
@@ -1028,6 +1028,29 @@ var PREVIEW_FILE_COLORS = {
1028
1028
  };
1029
1029
  var DOCUMENTS_FOLDER_ID = "documents";
1030
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
+ }
1031
1054
  var GRID = 90;
1032
1055
  function snapToGrid(x, y) {
1033
1056
  return { x: Math.round(x / GRID) * GRID, y: Math.round(y / GRID) * GRID };
@@ -1039,13 +1062,35 @@ function DesktopHostProvider({ value, children }) {
1039
1062
  function useDesktopHost() {
1040
1063
  return useContext(DesktopHostContext);
1041
1064
  }
1042
- 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 }) {
1043
1069
  const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
1044
1070
  const [rubber, setRubber] = useState(null);
1045
1071
  const didDragRubber = useRef(false);
1046
1072
  const bodyRef = useRef(null);
1047
- const [dragIdx, setDragIdx] = useState(null);
1048
- 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
+ };
1049
1094
  const toggleSelect = (i, e) => {
1050
1095
  e.stopPropagation();
1051
1096
  if (e.shiftKey || e.metaKey || e.ctrlKey) {
@@ -1054,53 +1099,57 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1054
1099
  next.has(i) ? next.delete(i) : next.add(i);
1055
1100
  return next;
1056
1101
  });
1057
- } else {
1102
+ } else if (!selected.has(i)) {
1058
1103
  setSelected(/* @__PURE__ */ new Set([i]));
1059
1104
  }
1060
1105
  };
1061
1106
  const startRubber = (e) => {
1062
1107
  if (e.button !== 0 || e.target !== bodyRef.current) return;
1063
- const r = bodyRef.current.getBoundingClientRect();
1064
- const x = e.clientX - r.left;
1065
- 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;
1066
1112
  setRubber({ x1: x, y1: y, x2: x, y2: y });
1067
1113
  didDragRubber.current = false;
1068
1114
  setSelected(/* @__PURE__ */ new Set());
1069
1115
  };
1070
1116
  useEffect(() => {
1071
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
+ };
1072
1135
  const move = (e) => {
1073
- const r = bodyRef.current?.getBoundingClientRect();
1074
- if (!r) return;
1075
- const x = e.clientX - r.left;
1076
- 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;
1077
1141
  const dx = x - rubber.x1, dy = y - rubber.y1;
1078
1142
  if (dx * dx + dy * dy > 16) didDragRubber.current = true;
1079
1143
  setRubber((prev) => prev ? { ...prev, x2: x, y2: y } : null);
1080
- };
1081
- const up = () => {
1082
- const next = /* @__PURE__ */ new Set();
1083
- const r = bodyRef.current;
1084
- if (r && rubber) {
1085
- const minX = Math.min(rubber.x1, rubber.x2);
1086
- const maxX = Math.max(rubber.x1, rubber.x2);
1087
- const minY = Math.min(rubber.y1, rubber.y2);
1088
- const maxY = Math.max(rubber.y1, rubber.y2);
1089
- const tiles = r.querySelectorAll("[data-folder-item]");
1090
- const containerRect = r.getBoundingClientRect();
1091
- tiles.forEach((t) => {
1092
- const tr = t.getBoundingClientRect();
1093
- const tx = tr.left - containerRect.left;
1094
- const ty = tr.top - containerRect.top;
1095
- if (tx + tr.width > minX && tx < maxX && ty + tr.height > minY && ty < maxY) {
1096
- const i = parseInt(t.getAttribute("data-folder-item") || "-1", 10);
1097
- if (i >= 0) next.add(i);
1098
- }
1099
- });
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));
1100
1150
  }
1101
- if (didDragRubber.current) setSelected(next);
1102
- setRubber(null);
1103
1151
  };
1152
+ const up = () => setRubber(null);
1104
1153
  window.addEventListener("pointermove", move);
1105
1154
  window.addEventListener("pointerup", up);
1106
1155
  return () => {
@@ -1108,28 +1157,75 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1108
1157
  window.removeEventListener("pointerup", up);
1109
1158
  };
1110
1159
  }, [rubber]);
1111
- const onItemDragStart = (i) => (e) => {
1112
- e.dataTransfer.effectAllowed = "move";
1113
- e.dataTransfer.setData("text/plain", String(i));
1114
- setDragIdx(i);
1115
- };
1116
- const onItemDragOver = (i) => (e) => {
1117
- e.preventDefault();
1118
- if (dragIdx !== null && dragIdx !== i) setDropIdx(i);
1119
- };
1120
- 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();
1121
1165
  e.preventDefault();
1122
- if (dragIdx === null || dragIdx === i) {
1123
- setDragIdx(null);
1124
- setDropIdx(null);
1125
- return;
1126
- }
1127
- const next = [...items];
1128
- const [moved] = next.splice(dragIdx, 1);
1129
- next.splice(i, 0, moved);
1130
- onReorder(next);
1131
- setDragIdx(null);
1132
- 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);
1133
1229
  };
1134
1230
  const moveSelectedOut = () => {
1135
1231
  if (selected.size === 0) return;
@@ -1137,6 +1233,10 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1137
1233
  setSelected(/* @__PURE__ */ new Set());
1138
1234
  };
1139
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);
1140
1240
  return /* @__PURE__ */ jsx(Modal, { open: true, onClose, title: folder.name, icon: folderIcon, size: "lg", children: /* @__PURE__ */ jsxs(
1141
1241
  "div",
1142
1242
  {
@@ -1154,7 +1254,7 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1154
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%)"
1155
1255
  },
1156
1256
  children: [
1157
- 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: [
1158
1258
  /* @__PURE__ */ jsxs("span", { children: [
1159
1259
  selected.size,
1160
1260
  " selected"
@@ -1162,40 +1262,22 @@ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder })
1162
1262
  /* @__PURE__ */ jsx("button", { onClick: moveSelectedOut, className: "px-2 py-0.5 rounded text-blue-600 hover:bg-blue-50", children: "Move to desktop" }),
1163
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" })
1164
1264
  ] }),
1165
- 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) => {
1166
1266
  const isSelected = selected.has(i);
1167
- const isDropTarget = dropIdx === i && dragIdx !== i;
1168
- return /* @__PURE__ */ jsxs(
1267
+ const pos = getItemFolderPos(item, i);
1268
+ return /* @__PURE__ */ jsx(
1169
1269
  "div",
1170
1270
  {
1171
1271
  "data-folder-item": i,
1172
- draggable: true,
1173
- onDragStart: onItemDragStart(i),
1174
- onDragOver: onItemDragOver(i),
1175
- onDrop: onItemDrop(i),
1176
- onDragEnd: () => {
1177
- setDragIdx(null);
1178
- setDropIdx(null);
1179
- },
1272
+ onPointerDown: (e) => startItemDrag(i, e),
1180
1273
  onClick: (e) => toggleSelect(i, e),
1181
1274
  onDoubleClick: () => onOpen(item),
1182
- 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" : ""}`,
1183
- children: [
1184
- /* @__PURE__ */ jsx(
1185
- "button",
1186
- {
1187
- onClick: (e) => {
1188
- e.stopPropagation();
1189
- onMoveOut([item]);
1190
- },
1191
- title: "Move to desktop",
1192
- 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",
1193
- 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" }) })
1194
- }
1195
- ),
1196
- /* @__PURE__ */ jsx("div", { className: `w-12 h-12 rounded-lg bg-white shadow flex items-center justify-center text-xs font-bold ${item.entityType === "preview-file" && item.fileKind ? PREVIEW_FILE_COLORS[item.fileKind] : ENTITY_ICON_COLORS[item.entityType] || "text-gray-600"}`, children: item.entityType === "preview-file" && item.fileKind ? PREVIEW_FILE_CODES[item.fileKind] : ENTITY_ICONS[item.entityType] || item.entityType.slice(0, 3).toUpperCase() }),
1197
- /* @__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 })
1198
- ]
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
+ ] })
1199
1281
  },
1200
1282
  `${item.entityType}-${item.entityId}-${i}`
1201
1283
  );
@@ -1495,7 +1577,7 @@ function Desktop({ profile }) {
1495
1577
  const desktopIdx = favDocs.indexOf(desktopItems[move2.entry.idx]);
1496
1578
  if (desktopIdx === -1) continue;
1497
1579
  if (droppedOnFolder) {
1498
- 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 };
1499
1581
  } else {
1500
1582
  updated[desktopIdx] = { ...updated[desktopIdx], x: move2.finalRight, y: move2.finalTop, folderId: void 0 };
1501
1583
  positionsPatch[`item-${desktopIdx}`] = { right: move2.finalRight, top: move2.finalTop };
@@ -1771,31 +1853,10 @@ function Desktop({ profile }) {
1771
1853
  purple: "bg-purple-100 border-purple-300",
1772
1854
  orange: "bg-orange-100 border-orange-300"
1773
1855
  };
1774
- const renderIcon = (entityType, label, isSelected, entityId, fileKind) => {
1775
- const isPreviewFile = entityType === "preview-file" && fileKind;
1776
- const previewColor2 = isPreviewFile ? PREVIEW_FILE_COLORS[fileKind] : null;
1777
- const previewCode = isPreviewFile ? PREVIEW_FILE_CODES[fileKind] : null;
1778
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 w-20 p-2", children: [
1779
- 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: [
1780
- /* @__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" }),
1781
- /* @__PURE__ */ jsx("path", { d: "M6 18h36", stroke: "#eab308", strokeWidth: "1.5" })
1782
- ] }) }) : 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: (() => {
1783
- const icon = navIcons[label] || (entityId ? navIcons[entityId] : void 0);
1784
- if (icon && isValidElement(icon)) {
1785
- return cloneElement(icon, { className: "h-10 w-10 text-white drop-shadow-[0_2px_3px_rgba(0,0,0,0.4)]" });
1786
- }
1787
- 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" }) });
1788
- })() }) : /* @__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: [
1789
- /* @__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: [
1790
- /* @__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" }),
1791
- /* @__PURE__ */ jsx("path", { d: "M26 0l10 10H30a4 4 0 01-4-4V0z", fill: "currentColor", fillOpacity: "0.2" }),
1792
- /* @__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" })
1793
- ] }),
1794
- /* @__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() })
1795
- ] }),
1796
- /* @__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 })
1797
- ] });
1798
- };
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 }),
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 })
1859
+ ] });
1799
1860
  const menuStyle = (x, y) => ({
1800
1861
  ...x + 180 > window.innerWidth ? { right: window.innerWidth - x } : { left: x },
1801
1862
  ...y + 250 > window.innerHeight ? { bottom: window.innerHeight - y } : { top: y }
@@ -2239,13 +2300,40 @@ function Desktop({ profile }) {
2239
2300
  onMoveOut: (toMove) => {
2240
2301
  const ids = new Set(toMove.map((t) => `${t.entityType}|${t.entityId}`));
2241
2302
  const updated = favDocs.map(
2242
- (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
2243
2304
  );
2244
2305
  saveDocs(updated);
2245
2306
  },
2246
- onReorder: (nextItems) => {
2247
- const others = favDocs.filter((d) => d.folderId !== openFolder);
2248
- 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);
2249
2337
  }
2250
2338
  }
2251
2339
  );