react-os-shell 0.1.12 → 0.1.14

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
@@ -1,10 +1,10 @@
1
1
  import { useEmailUnreadCount } from './chunk-PDFQNHW7.js';
2
- import { useGoogleAuth } from './chunk-5O2KEISQ.js';
3
2
  import { formatDate } from './chunk-NSU7OHPC.js';
4
3
  export { formatDate } from './chunk-NSU7OHPC.js';
4
+ import { useGoogleAuth } from './chunk-5O2KEISQ.js';
5
+ import { playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
5
6
  import { toast_default } from './chunk-WIJ45SYD.js';
6
7
  export { toast_default as toast } from './chunk-WIJ45SYD.js';
7
- import { playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
8
8
  import { useShellPrefs } from './chunk-36VM54SC.js';
9
9
  export { ShellPrefsProvider, useLocalStoragePrefs, useShellPrefs } from './chunk-36VM54SC.js';
10
10
  import { useWindowManager, glassStyle, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, Modal, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, sectionIcons, ModalActions, useModalActive } from './chunk-24K73LGZ.js';
@@ -643,7 +643,7 @@ function StatusBadge({ status }) {
643
643
  }
644
644
 
645
645
  // src/version.ts
646
- var VERSION = "0.1.12" ;
646
+ var VERSION = "0.1.14" ;
647
647
  var APP_VERSION = VERSION;
648
648
 
649
649
  // src/changelog.ts
@@ -748,6 +748,183 @@ function DesktopHostProvider({ value, children }) {
748
748
  function useDesktopHost() {
749
749
  return useContext(DesktopHostContext);
750
750
  }
751
+ function FolderWindow({ folder, items, onClose, onOpen, onMoveOut, onReorder }) {
752
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
753
+ const [rubber, setRubber] = useState(null);
754
+ const didDragRubber = useRef(false);
755
+ const bodyRef = useRef(null);
756
+ const [dragIdx, setDragIdx] = useState(null);
757
+ const [dropIdx, setDropIdx] = useState(null);
758
+ const toggleSelect = (i, e) => {
759
+ e.stopPropagation();
760
+ if (e.shiftKey || e.metaKey || e.ctrlKey) {
761
+ setSelected((prev) => {
762
+ const next = new Set(prev);
763
+ next.has(i) ? next.delete(i) : next.add(i);
764
+ return next;
765
+ });
766
+ } else {
767
+ setSelected(/* @__PURE__ */ new Set([i]));
768
+ }
769
+ };
770
+ const startRubber = (e) => {
771
+ if (e.button !== 0 || e.target !== bodyRef.current) return;
772
+ const r = bodyRef.current.getBoundingClientRect();
773
+ const x = e.clientX - r.left;
774
+ const y = e.clientY - r.top;
775
+ setRubber({ x1: x, y1: y, x2: x, y2: y });
776
+ didDragRubber.current = false;
777
+ setSelected(/* @__PURE__ */ new Set());
778
+ };
779
+ useEffect(() => {
780
+ if (!rubber) return;
781
+ const move = (e) => {
782
+ const r = bodyRef.current?.getBoundingClientRect();
783
+ if (!r) return;
784
+ const x = e.clientX - r.left;
785
+ const y = e.clientY - r.top;
786
+ const dx = x - rubber.x1, dy = y - rubber.y1;
787
+ if (dx * dx + dy * dy > 16) didDragRubber.current = true;
788
+ setRubber((prev) => prev ? { ...prev, x2: x, y2: y } : null);
789
+ };
790
+ const up = () => {
791
+ const next = /* @__PURE__ */ new Set();
792
+ const r = bodyRef.current;
793
+ if (r && rubber) {
794
+ const minX = Math.min(rubber.x1, rubber.x2);
795
+ const maxX = Math.max(rubber.x1, rubber.x2);
796
+ const minY = Math.min(rubber.y1, rubber.y2);
797
+ const maxY = Math.max(rubber.y1, rubber.y2);
798
+ const tiles = r.querySelectorAll("[data-folder-item]");
799
+ const containerRect = r.getBoundingClientRect();
800
+ tiles.forEach((t) => {
801
+ const tr = t.getBoundingClientRect();
802
+ const tx = tr.left - containerRect.left;
803
+ const ty = tr.top - containerRect.top;
804
+ if (tx + tr.width > minX && tx < maxX && ty + tr.height > minY && ty < maxY) {
805
+ const i = parseInt(t.getAttribute("data-folder-item") || "-1", 10);
806
+ if (i >= 0) next.add(i);
807
+ }
808
+ });
809
+ }
810
+ if (didDragRubber.current) setSelected(next);
811
+ setRubber(null);
812
+ };
813
+ window.addEventListener("pointermove", move);
814
+ window.addEventListener("pointerup", up);
815
+ return () => {
816
+ window.removeEventListener("pointermove", move);
817
+ window.removeEventListener("pointerup", up);
818
+ };
819
+ }, [rubber]);
820
+ const onItemDragStart = (i) => (e) => {
821
+ e.dataTransfer.effectAllowed = "move";
822
+ e.dataTransfer.setData("text/plain", String(i));
823
+ setDragIdx(i);
824
+ };
825
+ const onItemDragOver = (i) => (e) => {
826
+ e.preventDefault();
827
+ if (dragIdx !== null && dragIdx !== i) setDropIdx(i);
828
+ };
829
+ const onItemDrop = (i) => (e) => {
830
+ e.preventDefault();
831
+ if (dragIdx === null || dragIdx === i) {
832
+ setDragIdx(null);
833
+ setDropIdx(null);
834
+ return;
835
+ }
836
+ const next = [...items];
837
+ const [moved] = next.splice(dragIdx, 1);
838
+ next.splice(i, 0, moved);
839
+ onReorder(next);
840
+ setDragIdx(null);
841
+ setDropIdx(null);
842
+ };
843
+ const moveSelectedOut = () => {
844
+ if (selected.size === 0) return;
845
+ onMoveOut(Array.from(selected).map((i) => items[i]).filter(Boolean));
846
+ setSelected(/* @__PURE__ */ new Set());
847
+ };
848
+ 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" }) });
849
+ return /* @__PURE__ */ jsx(Modal, { open: true, onClose, title: folder.name, icon: folderIcon, size: "lg", children: /* @__PURE__ */ jsxs(
850
+ "div",
851
+ {
852
+ ref: bodyRef,
853
+ onPointerDown: startRubber,
854
+ onClick: () => {
855
+ if (didDragRubber.current) {
856
+ didDragRubber.current = false;
857
+ return;
858
+ }
859
+ setSelected(/* @__PURE__ */ new Set());
860
+ },
861
+ className: "relative h-full min-h-[300px] p-3 overflow-auto",
862
+ style: {
863
+ 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%)"
864
+ },
865
+ children: [
866
+ 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: [
867
+ /* @__PURE__ */ jsxs("span", { children: [
868
+ selected.size,
869
+ " selected"
870
+ ] }),
871
+ /* @__PURE__ */ jsx("button", { onClick: moveSelectedOut, className: "px-2 py-0.5 rounded text-blue-600 hover:bg-blue-50", children: "Move to desktop" }),
872
+ /* @__PURE__ */ jsx("button", { onClick: () => setSelected(/* @__PURE__ */ new Set()), className: "px-2 py-0.5 rounded text-gray-500 hover:bg-gray-100", children: "Clear" })
873
+ ] }),
874
+ 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) => {
875
+ const isSelected = selected.has(i);
876
+ const isDropTarget = dropIdx === i && dragIdx !== i;
877
+ return /* @__PURE__ */ jsxs(
878
+ "div",
879
+ {
880
+ "data-folder-item": i,
881
+ draggable: true,
882
+ onDragStart: onItemDragStart(i),
883
+ onDragOver: onItemDragOver(i),
884
+ onDrop: onItemDrop(i),
885
+ onDragEnd: () => {
886
+ setDragIdx(null);
887
+ setDropIdx(null);
888
+ },
889
+ onClick: (e) => toggleSelect(i, e),
890
+ onDoubleClick: () => onOpen(item),
891
+ 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" : ""}`,
892
+ children: [
893
+ /* @__PURE__ */ jsx(
894
+ "button",
895
+ {
896
+ onClick: (e) => {
897
+ e.stopPropagation();
898
+ onMoveOut([item]);
899
+ },
900
+ title: "Move to desktop",
901
+ 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",
902
+ 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" }) })
903
+ }
904
+ ),
905
+ /* @__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() }),
906
+ /* @__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 })
907
+ ]
908
+ },
909
+ `${item.entityType}-${item.entityId}-${i}`
910
+ );
911
+ }) }),
912
+ rubber && /* @__PURE__ */ jsx(
913
+ "div",
914
+ {
915
+ className: "absolute border border-blue-500 bg-blue-500/10 pointer-events-none",
916
+ style: {
917
+ left: Math.min(rubber.x1, rubber.x2),
918
+ top: Math.min(rubber.y1, rubber.y2),
919
+ width: Math.abs(rubber.x2 - rubber.x1),
920
+ height: Math.abs(rubber.y2 - rubber.y1)
921
+ }
922
+ }
923
+ )
924
+ ]
925
+ }
926
+ ) });
927
+ }
751
928
  function Desktop({ profile }) {
752
929
  useQueryClient();
753
930
  const { openEntity, openPage } = useWindowManager();
@@ -1016,8 +1193,8 @@ function Desktop({ profile }) {
1016
1193
  window.removeEventListener("pointerup", up);
1017
1194
  };
1018
1195
  }, [dragging, snapEnabled, favDocs, folders, desktopItems]);
1019
- const favDocsKey = JSON.stringify(favDocs.map((d) => d.entityId));
1020
- const foldersKey = JSON.stringify(folders.map((f) => f.id));
1196
+ const favDocsKey = JSON.stringify(favDocs.map((d) => `${d.entityId}:${d.x ?? ""}:${d.y ?? ""}:${d.folderId ?? ""}`));
1197
+ const foldersKey = JSON.stringify(folders.map((f) => `${f.id}:${f.x ?? ""}:${f.y ?? ""}`));
1021
1198
  useEffect(() => {
1022
1199
  setLocalPositions({});
1023
1200
  }, [favDocsKey, foldersKey]);
@@ -1599,35 +1776,29 @@ function Desktop({ profile }) {
1599
1776
  openFolder && (() => {
1600
1777
  const folder = folders.find((f) => f.id === openFolder);
1601
1778
  if (!folder) return null;
1602
- const items = folderItems(openFolder);
1603
- return /* @__PURE__ */ jsx(Modal, { open: true, onClose: () => setOpenFolder(null), title: folder.name, size: "lg", children: items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 text-center py-8", children: "Folder is empty. Drag documents here." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3 p-2", children: items.map((item, i) => /* @__PURE__ */ jsxs(
1604
- "div",
1779
+ return /* @__PURE__ */ jsx(
1780
+ FolderWindow,
1605
1781
  {
1606
- className: "group relative flex flex-col items-center gap-1 w-20 p-2 rounded-lg hover:bg-gray-100 cursor-default",
1607
- onDoubleClick: () => openEntity(item.entityType, item.entityId, null, item.label),
1608
- children: [
1609
- /* @__PURE__ */ jsx(
1610
- "button",
1611
- {
1612
- onClick: (e) => {
1613
- e.stopPropagation();
1614
- const updated = favDocs.map((d) => d.entityType === item.entityType && d.entityId === item.entityId && d.folderId === openFolder ? { ...d, folderId: void 0 } : d);
1615
- saveDocs(updated);
1616
- },
1617
- title: "Move to Desktop",
1618
- 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",
1619
- 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" }) })
1620
- }
1621
- ),
1622
- /* @__PURE__ */ jsx("div", { className: `w-12 h-12 rounded-lg bg-gray-50 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() }),
1623
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-700 font-medium text-center leading-tight truncate w-full", children: item.label })
1624
- ]
1625
- },
1626
- i
1627
- )) }) });
1782
+ folder,
1783
+ items: folderItems(openFolder),
1784
+ onClose: () => setOpenFolder(null),
1785
+ onOpen: (item) => openEntity(item.entityType, item.entityId, null, item.label),
1786
+ onMoveOut: (toMove) => {
1787
+ const ids = new Set(toMove.map((t) => `${t.entityType}|${t.entityId}`));
1788
+ const updated = favDocs.map(
1789
+ (d) => d.folderId === openFolder && ids.has(`${d.entityType}|${d.entityId}`) ? { ...d, folderId: void 0 } : d
1790
+ );
1791
+ saveDocs(updated);
1792
+ },
1793
+ onReorder: (nextItems) => {
1794
+ const others = favDocs.filter((d) => d.folderId !== openFolder);
1795
+ saveDocs([...others, ...nextItems]);
1796
+ }
1797
+ }
1798
+ );
1628
1799
  })(),
1629
1800
  aboutOpen && (() => {
1630
- const version = APP_VERSION;
1801
+ const version = host.productVersion ?? APP_VERSION;
1631
1802
  return /* @__PURE__ */ jsx(Modal, { open: true, onClose: () => setAboutOpen(false), title: `About ${host.productName ?? "this app"}`, size: "sm", bodyScroll: false, compact: true, dimensions: [340, 420], children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center", children: [
1632
1803
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 pt-4 pb-3 w-full", children: [
1633
1804
  /* @__PURE__ */ jsx("img", { src: host.productIcon ?? "/favicon.svg", alt: "", className: "h-16 w-16" }),
@@ -1676,7 +1847,7 @@ function Desktop({ profile }) {
1676
1847
  ] })
1677
1848
  ] }) });
1678
1849
  })(),
1679
- (prefs.show_desktop_version ?? true) && /* @__PURE__ */ jsx(
1850
+ (prefs.show_desktop_version ?? true) && (host.productVersion ?? APP_VERSION) && /* @__PURE__ */ jsx(
1680
1851
  "button",
1681
1852
  {
1682
1853
  onClick: (e) => {
@@ -1684,7 +1855,7 @@ function Desktop({ profile }) {
1684
1855
  setWhatsNewOpen(true);
1685
1856
  },
1686
1857
  className: `absolute bottom-3 text-[10px] text-white/50 font-mono select-none drop-shadow-[0_1px_1px_rgba(0,0,0,0.5)] hover:text-white/80 transition-colors cursor-pointer ${prefs.taskbar_position === "top" ? "right-3" : prefs.taskbar_position === "left" ? "right-3" : prefs.taskbar_position === "right" ? "left-3" : "right-3 !bottom-16"}`,
1687
- children: APP_VERSION
1858
+ children: host.productVersion ?? APP_VERSION
1688
1859
  }
1689
1860
  ),
1690
1861
  whatsNewOpen && /* @__PURE__ */ jsx(Modal, { open: true, onClose: () => setWhatsNewOpen(false), title: "What's New", size: "md", bodyScroll: false, children: /* @__PURE__ */ jsx("div", { className: "space-y-5 max-h-[60vh] overflow-y-auto px-1", children: changelog_default.map((entry, i) => /* @__PURE__ */ jsxs("div", { children: [