rita-workspace 0.5.49 → 0.5.53

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/README.md CHANGED
@@ -11,6 +11,42 @@ Multi-drawing workspace feature for Excalidraw/Rita (Excalidraw fork based on B3
11
11
 
12
12
  <img width="504" height="574" alt="image" src="https://github.com/user-attachments/assets/ada58a93-a664-4ab2-80e2-55354ae3a544" />
13
13
 
14
+ ## Quick Start
15
+
16
+ Add to your main app file (e.g. `App.tsx`). For full integration (debounced save, conflict handling, toggle state), see [INTEGRATION.md](./docs/INTEGRATION.md).
17
+
18
+ ```tsx
19
+ import { WorkspaceProvider, useWorkspace, DrawingsDialog } from "rita-workspace";
20
+
21
+ function App() {
22
+ const [dialogOpen, setDialogOpen] = useState(false);
23
+
24
+ return (
25
+ <WorkspaceProvider lang="en">
26
+ <button onClick={() => setDialogOpen(true)}>My Drawings</button>
27
+ <DrawingsDialog
28
+ open={dialogOpen}
29
+ onClose={() => setDialogOpen(false)}
30
+ />
31
+ <ExcalidrawWithWorkspace />
32
+ </WorkspaceProvider>
33
+ );
34
+ }
35
+
36
+ function ExcalidrawWithWorkspace() {
37
+ const { activeDrawing, saveCurrentDrawing } = useWorkspace();
38
+
39
+ return (
40
+ <Excalidraw
41
+ initialData={activeDrawing}
42
+ onChange={(elements, appState, files) => {
43
+ saveCurrentDrawing(elements, appState, files);
44
+ }}
45
+ />
46
+ );
47
+ }
48
+ ```
49
+
14
50
  ## Features
15
51
 
16
52
  - **Multiple drawings** - Create and manage multiple drawings in one workspace
package/dist/index.d.mts CHANGED
@@ -17,6 +17,8 @@ interface Drawing {
17
17
  interface Folder {
18
18
  id: string;
19
19
  name: string;
20
+ /** Manual sort position. Lower = earlier in list. Falls back to createdAt when unset. */
21
+ position?: number;
20
22
  createdAt: number;
21
23
  updatedAt: number;
22
24
  }
@@ -77,6 +79,11 @@ declare function createFolder(name: string): Promise<Folder>;
77
79
  declare function getFolder(id: string): Promise<Folder | undefined>;
78
80
  declare function getAllFolders(): Promise<Folder[]>;
79
81
  declare function renameFolder(id: string, name: string): Promise<Folder | undefined>;
82
+ /**
83
+ * Reassigns 'position' field on the given ordered folder ids (0..n-1).
84
+ * Folders not in `orderedIds` are left untouched.
85
+ */
86
+ declare function reorderFolders(orderedIds: string[]): Promise<void>;
80
87
  declare function deleteFolder(id: string): Promise<void>;
81
88
 
82
89
  declare function getOrCreateDefaultWorkspace(): Promise<Workspace>;
@@ -160,6 +167,8 @@ interface WorkspaceContextValue {
160
167
  /** Re-numbers `position` for the given drawing IDs in order. Use the full ordered slice
161
168
  * the user reordered (e.g. all root drawings, or all drawings in a folder). */
162
169
  reorderDrawings: (orderedIds: string[]) => Promise<void>;
170
+ /** Assigns position to the listed folder ids in given order. Pass full folder list. */
171
+ reorderFolders: (orderedIds: string[]) => Promise<void>;
163
172
  saveCurrentDrawing: (expectedDrawingId: string, elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
164
173
  saveDrawingById: (id: string, elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
165
174
  refreshDrawings: () => Promise<void>;
@@ -401,4 +410,4 @@ interface WorkspacePluginProps {
401
410
  */
402
411
  declare function WorkspacePlugin(props: WorkspacePluginProps): react_jsx_runtime.JSX.Element;
403
412
 
404
- export { type Drawing, DrawingList, DrawingListItem, type DrawingMeta, DrawingsDialog, type DrawingsDialogProps, type ExcalidrawImperativeAPI, type Folder, Sidebar, type SupportedLanguage, type Translations, type Workspace, WorkspaceBridge, type WorkspaceBridgeProps, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, createFolder, deleteDrawing, deleteFolder, duplicateDrawing, getAllDrawings, getAllDrawingsMeta, getAllFolders, getDB, getDrawing, getFolder, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isDrawingOpenedEarlierInOtherTab, isLanguageSupported, moveDrawingToFolder, removeDrawingFromWorkspace, renameFolder, reorderDrawings, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang, warmDB };
413
+ export { type Drawing, DrawingList, DrawingListItem, type DrawingMeta, DrawingsDialog, type DrawingsDialogProps, type ExcalidrawImperativeAPI, type Folder, Sidebar, type SupportedLanguage, type Translations, type Workspace, WorkspaceBridge, type WorkspaceBridgeProps, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, createFolder, deleteDrawing, deleteFolder, duplicateDrawing, getAllDrawings, getAllDrawingsMeta, getAllFolders, getDB, getDrawing, getFolder, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isDrawingOpenedEarlierInOtherTab, isLanguageSupported, moveDrawingToFolder, removeDrawingFromWorkspace, renameFolder, reorderDrawings, reorderFolders, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang, warmDB };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ interface Drawing {
17
17
  interface Folder {
18
18
  id: string;
19
19
  name: string;
20
+ /** Manual sort position. Lower = earlier in list. Falls back to createdAt when unset. */
21
+ position?: number;
20
22
  createdAt: number;
21
23
  updatedAt: number;
22
24
  }
@@ -77,6 +79,11 @@ declare function createFolder(name: string): Promise<Folder>;
77
79
  declare function getFolder(id: string): Promise<Folder | undefined>;
78
80
  declare function getAllFolders(): Promise<Folder[]>;
79
81
  declare function renameFolder(id: string, name: string): Promise<Folder | undefined>;
82
+ /**
83
+ * Reassigns 'position' field on the given ordered folder ids (0..n-1).
84
+ * Folders not in `orderedIds` are left untouched.
85
+ */
86
+ declare function reorderFolders(orderedIds: string[]): Promise<void>;
80
87
  declare function deleteFolder(id: string): Promise<void>;
81
88
 
82
89
  declare function getOrCreateDefaultWorkspace(): Promise<Workspace>;
@@ -160,6 +167,8 @@ interface WorkspaceContextValue {
160
167
  /** Re-numbers `position` for the given drawing IDs in order. Use the full ordered slice
161
168
  * the user reordered (e.g. all root drawings, or all drawings in a folder). */
162
169
  reorderDrawings: (orderedIds: string[]) => Promise<void>;
170
+ /** Assigns position to the listed folder ids in given order. Pass full folder list. */
171
+ reorderFolders: (orderedIds: string[]) => Promise<void>;
163
172
  saveCurrentDrawing: (expectedDrawingId: string, elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
164
173
  saveDrawingById: (id: string, elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
165
174
  refreshDrawings: () => Promise<void>;
@@ -401,4 +410,4 @@ interface WorkspacePluginProps {
401
410
  */
402
411
  declare function WorkspacePlugin(props: WorkspacePluginProps): react_jsx_runtime.JSX.Element;
403
412
 
404
- export { type Drawing, DrawingList, DrawingListItem, type DrawingMeta, DrawingsDialog, type DrawingsDialogProps, type ExcalidrawImperativeAPI, type Folder, Sidebar, type SupportedLanguage, type Translations, type Workspace, WorkspaceBridge, type WorkspaceBridgeProps, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, createFolder, deleteDrawing, deleteFolder, duplicateDrawing, getAllDrawings, getAllDrawingsMeta, getAllFolders, getDB, getDrawing, getFolder, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isDrawingOpenedEarlierInOtherTab, isLanguageSupported, moveDrawingToFolder, removeDrawingFromWorkspace, renameFolder, reorderDrawings, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang, warmDB };
413
+ export { type Drawing, DrawingList, DrawingListItem, type DrawingMeta, DrawingsDialog, type DrawingsDialogProps, type ExcalidrawImperativeAPI, type Folder, Sidebar, type SupportedLanguage, type Translations, type Workspace, WorkspaceBridge, type WorkspaceBridgeProps, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, createFolder, deleteDrawing, deleteFolder, duplicateDrawing, getAllDrawings, getAllDrawingsMeta, getAllFolders, getDB, getDrawing, getFolder, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isDrawingOpenedEarlierInOtherTab, isLanguageSupported, moveDrawingToFolder, removeDrawingFromWorkspace, renameFolder, reorderDrawings, reorderFolders, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang, warmDB };
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ __export(index_exports, {
60
60
  removeDrawingFromWorkspace: () => removeDrawingFromWorkspace,
61
61
  renameFolder: () => renameFolder,
62
62
  reorderDrawings: () => reorderDrawings,
63
+ reorderFolders: () => reorderFolders,
63
64
  setActiveDrawing: () => setActiveDrawing,
64
65
  updateDrawing: () => updateDrawing,
65
66
  updateWorkspace: () => updateWorkspace,
@@ -224,6 +225,17 @@ async function renameFolder(id, name) {
224
225
  await db.put("folders", updated);
225
226
  return updated;
226
227
  }
228
+ async function reorderFolders(orderedIds) {
229
+ const db = await getDB();
230
+ const tx = db.transaction("folders", "readwrite");
231
+ const store = tx.objectStore("folders");
232
+ for (let i = 0; i < orderedIds.length; i++) {
233
+ const existing = await store.get(orderedIds[i]);
234
+ if (!existing) continue;
235
+ await store.put({ ...existing, position: i });
236
+ }
237
+ await tx.done;
238
+ }
227
239
  async function deleteFolder(id) {
228
240
  const db = await getDB();
229
241
  const allDrawings = await db.getAll("drawings");
@@ -1309,6 +1321,19 @@ function WorkspaceProvider({ children, lang = "en" }) {
1309
1321
  refreshDrawingsRef.current();
1310
1322
  }
1311
1323
  }, []);
1324
+ const reorderFolders2 = (0, import_react.useCallback)(async (orderedIds) => {
1325
+ const positionMap = new Map(orderedIds.map((id, idx) => [id, idx]));
1326
+ setFolders((prev) => prev.map(
1327
+ (f) => positionMap.has(f.id) ? { ...f, position: positionMap.get(f.id) } : f
1328
+ ));
1329
+ try {
1330
+ await reorderFolders(orderedIds);
1331
+ broadcastWorkspaceChange();
1332
+ } catch (err) {
1333
+ setError(err instanceof Error ? err.message : "Failed to reorder folders");
1334
+ refreshDrawingsRef.current();
1335
+ }
1336
+ }, []);
1312
1337
  const value = {
1313
1338
  workspace,
1314
1339
  drawings,
@@ -1329,6 +1354,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
1329
1354
  deleteFolder: deleteFolder2,
1330
1355
  moveDrawingToFolder: moveDrawingToFolder2,
1331
1356
  reorderDrawings: reorderDrawings2,
1357
+ reorderFolders: reorderFolders2,
1332
1358
  saveCurrentDrawing,
1333
1359
  saveDrawingById,
1334
1360
  refreshDrawings,
@@ -1698,6 +1724,7 @@ var DrawingsDialog = ({
1698
1724
  deleteFolder: deleteFolder2,
1699
1725
  moveDrawingToFolder: moveDrawingToFolder2,
1700
1726
  reorderDrawings: reorderDrawings2,
1727
+ reorderFolders: reorderFolders2,
1701
1728
  exportWorkspace,
1702
1729
  importWorkspace,
1703
1730
  exportDrawingAsExcalidraw,
@@ -1715,7 +1742,17 @@ var DrawingsDialog = ({
1715
1742
  const [switchingId, setSwitchingId] = (0, import_react5.useState)(null);
1716
1743
  const [selectedId, setSelectedId] = (0, import_react5.useState)(null);
1717
1744
  const [hoveredId, setHoveredId] = (0, import_react5.useState)(null);
1718
- const [expandedFolders, setExpandedFolders] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
1745
+ const [expandedFolders, setExpandedFolders] = (0, import_react5.useState)(() => {
1746
+ try {
1747
+ if (localStorage.getItem("rita-workspace-remember-folder-state") === "false") {
1748
+ return /* @__PURE__ */ new Set();
1749
+ }
1750
+ const raw = localStorage.getItem("rita-workspace-expanded-folders");
1751
+ if (raw) return new Set(JSON.parse(raw));
1752
+ } catch {
1753
+ }
1754
+ return /* @__PURE__ */ new Set();
1755
+ });
1719
1756
  const [creatingFolder, setCreatingFolder] = (0, import_react5.useState)(false);
1720
1757
  const [newFolderName, setNewFolderName] = (0, import_react5.useState)("");
1721
1758
  const [editingFolderId, setEditingFolderId] = (0, import_react5.useState)(null);
@@ -1745,10 +1782,35 @@ var DrawingsDialog = ({
1745
1782
  return next;
1746
1783
  });
1747
1784
  }, []);
1785
+ const [rememberFolderState, setRememberFolderState] = (0, import_react5.useState)(() => {
1786
+ try {
1787
+ return localStorage.getItem("rita-workspace-remember-folder-state") !== "false";
1788
+ } catch {
1789
+ return true;
1790
+ }
1791
+ });
1792
+ const handleToggleRememberFolderState = (0, import_react5.useCallback)(() => {
1793
+ setRememberFolderState((prev) => {
1794
+ const next = !prev;
1795
+ try {
1796
+ if (next) {
1797
+ localStorage.removeItem("rita-workspace-remember-folder-state");
1798
+ } else {
1799
+ localStorage.setItem("rita-workspace-remember-folder-state", "false");
1800
+ localStorage.removeItem("rita-workspace-expanded-folders");
1801
+ }
1802
+ } catch {
1803
+ }
1804
+ return next;
1805
+ });
1806
+ }, []);
1748
1807
  const [draggingDrawingId, setDraggingDrawingId] = (0, import_react5.useState)(null);
1749
1808
  const [dropTargetFolderId, setDropTargetFolderId] = (0, import_react5.useState)(null);
1750
1809
  const [dropTargetDrawingId, setDropTargetDrawingId] = (0, import_react5.useState)(null);
1751
1810
  const [dropTargetPosition, setDropTargetPosition] = (0, import_react5.useState)(null);
1811
+ const [draggingFolderId, setDraggingFolderId] = (0, import_react5.useState)(null);
1812
+ const [folderDropTargetId, setFolderDropTargetId] = (0, import_react5.useState)(null);
1813
+ const [folderDropPosition, setFolderDropPosition] = (0, import_react5.useState)(null);
1752
1814
  const [position, setPosition] = (0, import_react5.useState)(null);
1753
1815
  const dragRef = (0, import_react5.useRef)(null);
1754
1816
  const dialogRef = (0, import_react5.useRef)(null);
@@ -1769,6 +1831,18 @@ var DrawingsDialog = ({
1769
1831
  }
1770
1832
  prevOpenRef.current = open;
1771
1833
  }, [open, refreshDrawings]);
1834
+ (0, import_react5.useEffect)(() => {
1835
+ if (!open) return;
1836
+ try {
1837
+ if (localStorage.getItem("rita-workspace-remember-folder-state") === "false") {
1838
+ setExpandedFolders(/* @__PURE__ */ new Set());
1839
+ return;
1840
+ }
1841
+ const raw = localStorage.getItem("rita-workspace-expanded-folders");
1842
+ setExpandedFolders(raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set());
1843
+ } catch {
1844
+ }
1845
+ }, [open]);
1772
1846
  (0, import_react5.useEffect)(() => {
1773
1847
  if (creatingFolder && newFolderInputRef.current) {
1774
1848
  newFolderInputRef.current.focus();
@@ -1891,6 +1965,24 @@ var DrawingsDialog = ({
1891
1965
  ].map((d) => d.id);
1892
1966
  reorderDrawings2(ordered);
1893
1967
  }, [drawings, reorderDrawings2, moveDrawingToFolder2]);
1968
+ const handleFolderReorderDrop = (0, import_react5.useCallback)((draggedFolderId, targetFolderId, pos) => {
1969
+ if (draggedFolderId === targetFolderId) return;
1970
+ const sorted = [...folders].sort(
1971
+ (a, b) => (a.position ?? a.createdAt) - (b.position ?? b.createdAt)
1972
+ );
1973
+ const withoutDragged = sorted.filter((f) => f.id !== draggedFolderId);
1974
+ const targetIdx = withoutDragged.findIndex((f) => f.id === targetFolderId);
1975
+ if (targetIdx === -1) return;
1976
+ const insertIdx = pos === "before" ? targetIdx : targetIdx + 1;
1977
+ const dragged = folders.find((f) => f.id === draggedFolderId);
1978
+ if (!dragged) return;
1979
+ const ordered = [
1980
+ ...withoutDragged.slice(0, insertIdx),
1981
+ dragged,
1982
+ ...withoutDragged.slice(insertIdx)
1983
+ ].map((f) => f.id);
1984
+ reorderFolders2(ordered);
1985
+ }, [folders, reorderFolders2]);
1894
1986
  const toggleFolder = (0, import_react5.useCallback)((folderId) => {
1895
1987
  setExpandedFolders((prev) => {
1896
1988
  const next = new Set(prev);
@@ -1902,6 +1994,16 @@ var DrawingsDialog = ({
1902
1994
  return next;
1903
1995
  });
1904
1996
  }, []);
1997
+ (0, import_react5.useEffect)(() => {
1998
+ if (!rememberFolderState) return;
1999
+ try {
2000
+ localStorage.setItem(
2001
+ "rita-workspace-expanded-folders",
2002
+ JSON.stringify(Array.from(expandedFolders))
2003
+ );
2004
+ } catch {
2005
+ }
2006
+ }, [expandedFolders, rememberFolderState]);
1905
2007
  const getLocale = () => {
1906
2008
  if (!effectiveLang) return "en-US";
1907
2009
  const baseLang = effectiveLang.split("-")[0].toLowerCase();
@@ -1940,7 +2042,10 @@ var DrawingsDialog = ({
1940
2042
  for (const folder of folders) {
1941
2043
  byFolder[folder.id] = sorted.filter((d) => d.folderId === folder.id);
1942
2044
  }
1943
- const foldersFiltered = query ? folders.filter((f) => f.name.toLowerCase().includes(query) || (byFolder[f.id] || []).length > 0) : folders;
2045
+ const sortedFolders = [...folders].sort(
2046
+ (a, b) => (a.position ?? a.createdAt) - (b.position ?? b.createdAt)
2047
+ );
2048
+ const foldersFiltered = query ? sortedFolders.filter((f) => f.name.toLowerCase().includes(query) || (byFolder[f.id] || []).length > 0) : sortedFolders;
1944
2049
  return { rootDrawings: root, drawingsByFolder: byFolder, filteredFolders: foldersFiltered };
1945
2050
  }, [drawings, folders, searchQuery]);
1946
2051
  if (!open) return null;
@@ -2257,10 +2362,11 @@ var DrawingsDialog = ({
2257
2362
  const renderFolderGroup = (folder) => {
2258
2363
  const folderDrawings = drawingsByFolder[folder.id] || [];
2259
2364
  const isExpanded = expandedFolders.has(folder.id);
2365
+ const isFolderDropTarget = folderDropTargetId === folder.id;
2260
2366
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2261
2367
  "div",
2262
2368
  {
2263
- style: { marginBottom: "4px" },
2369
+ style: { marginBottom: "4px", position: "relative" },
2264
2370
  onDragOver: (e) => {
2265
2371
  if (!draggingDrawingId) return;
2266
2372
  e.preventDefault();
@@ -2285,6 +2391,37 @@ var DrawingsDialog = ({
2285
2391
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2286
2392
  "div",
2287
2393
  {
2394
+ draggable: !editingFolderId && !confirmDeleteFolderId,
2395
+ onDragStart: (e) => {
2396
+ if (draggingDrawingId) return;
2397
+ setDraggingFolderId(folder.id);
2398
+ e.dataTransfer.effectAllowed = "move";
2399
+ },
2400
+ onDragEnd: () => {
2401
+ setDraggingFolderId(null);
2402
+ setFolderDropTargetId(null);
2403
+ setFolderDropPosition(null);
2404
+ },
2405
+ onDragOver: (e) => {
2406
+ if (!draggingFolderId || draggingFolderId === folder.id) return;
2407
+ e.preventDefault();
2408
+ e.stopPropagation();
2409
+ e.dataTransfer.dropEffect = "move";
2410
+ const rect = e.currentTarget.getBoundingClientRect();
2411
+ const isAbove = e.clientY < rect.top + rect.height / 2;
2412
+ setFolderDropTargetId(folder.id);
2413
+ setFolderDropPosition(isAbove ? "before" : "after");
2414
+ },
2415
+ onDrop: (e) => {
2416
+ if (draggingFolderId && draggingFolderId !== folder.id && folderDropPosition) {
2417
+ e.preventDefault();
2418
+ e.stopPropagation();
2419
+ handleFolderReorderDrop(draggingFolderId, folder.id, folderDropPosition);
2420
+ setDraggingFolderId(null);
2421
+ setFolderDropTargetId(null);
2422
+ setFolderDropPosition(null);
2423
+ }
2424
+ },
2288
2425
  onClick: () => {
2289
2426
  toggleFolder(folder.id);
2290
2427
  setSelectedId(null);
@@ -2295,9 +2432,11 @@ var DrawingsDialog = ({
2295
2432
  alignItems: "center",
2296
2433
  gap: "8px",
2297
2434
  borderRadius: "8px",
2298
- cursor: "pointer",
2435
+ cursor: draggingFolderId === folder.id ? "grabbing" : "pointer",
2436
+ opacity: draggingFolderId === folder.id ? 0.5 : 1,
2299
2437
  backgroundColor: dropTargetFolderId === folder.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.2))" : "var(--color-surface-mid, rgba(0, 0, 0, 0.03))",
2300
2438
  border: dropTargetFolderId === folder.id ? "2px dashed var(--color-primary, #6c63ff)" : "2px solid transparent",
2439
+ boxShadow: isFolderDropTarget && folderDropPosition === "before" ? "inset 0 3px 0 0 var(--color-primary, #6c63ff)" : isFolderDropTarget && folderDropPosition === "after" ? "inset 0 -3px 0 0 var(--color-primary, #6c63ff)" : "none",
2301
2440
  transition: "background-color 0.15s, border-color 0.15s"
2302
2441
  },
2303
2442
  children: [
@@ -2674,36 +2813,68 @@ var DrawingsDialog = ({
2674
2813
  }
2675
2814
  )
2676
2815
  ] }),
2677
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "0 20px 16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2678
- "label",
2679
- {
2680
- style: {
2681
- display: "flex",
2682
- alignItems: "center",
2683
- gap: "10px",
2684
- padding: "10px 12px",
2685
- border: "1px solid var(--default-border-color, #e0e0e0)",
2686
- borderRadius: "8px",
2687
- cursor: "pointer",
2688
- userSelect: "none"
2689
- },
2690
- children: [
2691
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2692
- "input",
2693
- {
2694
- type: "checkbox",
2695
- checked: autoStart,
2696
- onChange: handleToggleAutoStart,
2697
- style: { width: "16px", height: "16px", cursor: "pointer", flexShrink: 0 }
2698
- }
2699
- ),
2700
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
2701
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "14px", fontWeight: 500 }, children: t.autoStartLabel }),
2702
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "12px", color: "var(--text-secondary-color, #888)", marginTop: "2px" }, children: t.autoStartDesc })
2703
- ] })
2704
- ]
2705
- }
2706
- ) })
2816
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { padding: "0 20px 16px", display: "flex", flexDirection: "column", gap: "8px" }, children: [
2817
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2818
+ "label",
2819
+ {
2820
+ style: {
2821
+ display: "flex",
2822
+ alignItems: "center",
2823
+ gap: "10px",
2824
+ padding: "10px 12px",
2825
+ border: "1px solid var(--default-border-color, #e0e0e0)",
2826
+ borderRadius: "8px",
2827
+ cursor: "pointer",
2828
+ userSelect: "none"
2829
+ },
2830
+ children: [
2831
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2832
+ "input",
2833
+ {
2834
+ type: "checkbox",
2835
+ checked: autoStart,
2836
+ onChange: handleToggleAutoStart,
2837
+ style: { width: "16px", height: "16px", cursor: "pointer", flexShrink: 0 }
2838
+ }
2839
+ ),
2840
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
2841
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "14px", fontWeight: 500 }, children: t.autoStartLabel }),
2842
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "12px", color: "var(--text-secondary-color, #888)", marginTop: "2px" }, children: t.autoStartDesc })
2843
+ ] })
2844
+ ]
2845
+ }
2846
+ ),
2847
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2848
+ "label",
2849
+ {
2850
+ style: {
2851
+ display: "flex",
2852
+ alignItems: "center",
2853
+ gap: "10px",
2854
+ padding: "10px 12px",
2855
+ border: "1px solid var(--default-border-color, #e0e0e0)",
2856
+ borderRadius: "8px",
2857
+ cursor: "pointer",
2858
+ userSelect: "none"
2859
+ },
2860
+ children: [
2861
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2862
+ "input",
2863
+ {
2864
+ type: "checkbox",
2865
+ checked: rememberFolderState,
2866
+ onChange: handleToggleRememberFolderState,
2867
+ style: { width: "16px", height: "16px", cursor: "pointer", flexShrink: 0 }
2868
+ }
2869
+ ),
2870
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
2871
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "14px", fontWeight: 500 }, children: "Kom ih\xE5g utf\xE4llda mappar" }),
2872
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: "12px", color: "var(--text-secondary-color, #888)", marginTop: "2px" }, children: "Beh\xE5ller vilka mappar som \xE4r utf\xE4llda mellan sessioner. Om av: alla mappar startar minimerade." })
2873
+ ] })
2874
+ ]
2875
+ }
2876
+ )
2877
+ ] })
2707
2878
  ]
2708
2879
  }
2709
2880
  )
@@ -3041,6 +3212,7 @@ function WorkspacePlugin(props) {
3041
3212
  removeDrawingFromWorkspace,
3042
3213
  renameFolder,
3043
3214
  reorderDrawings,
3215
+ reorderFolders,
3044
3216
  setActiveDrawing,
3045
3217
  updateDrawing,
3046
3218
  updateWorkspace,
package/dist/index.mjs CHANGED
@@ -152,6 +152,17 @@ async function renameFolder(id, name) {
152
152
  await db.put("folders", updated);
153
153
  return updated;
154
154
  }
155
+ async function reorderFolders(orderedIds) {
156
+ const db = await getDB();
157
+ const tx = db.transaction("folders", "readwrite");
158
+ const store = tx.objectStore("folders");
159
+ for (let i = 0; i < orderedIds.length; i++) {
160
+ const existing = await store.get(orderedIds[i]);
161
+ if (!existing) continue;
162
+ await store.put({ ...existing, position: i });
163
+ }
164
+ await tx.done;
165
+ }
155
166
  async function deleteFolder(id) {
156
167
  const db = await getDB();
157
168
  const allDrawings = await db.getAll("drawings");
@@ -1237,6 +1248,19 @@ function WorkspaceProvider({ children, lang = "en" }) {
1237
1248
  refreshDrawingsRef.current();
1238
1249
  }
1239
1250
  }, []);
1251
+ const reorderFolders2 = useCallback(async (orderedIds) => {
1252
+ const positionMap = new Map(orderedIds.map((id, idx) => [id, idx]));
1253
+ setFolders((prev) => prev.map(
1254
+ (f) => positionMap.has(f.id) ? { ...f, position: positionMap.get(f.id) } : f
1255
+ ));
1256
+ try {
1257
+ await reorderFolders(orderedIds);
1258
+ broadcastWorkspaceChange();
1259
+ } catch (err) {
1260
+ setError(err instanceof Error ? err.message : "Failed to reorder folders");
1261
+ refreshDrawingsRef.current();
1262
+ }
1263
+ }, []);
1240
1264
  const value = {
1241
1265
  workspace,
1242
1266
  drawings,
@@ -1257,6 +1281,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
1257
1281
  deleteFolder: deleteFolder2,
1258
1282
  moveDrawingToFolder: moveDrawingToFolder2,
1259
1283
  reorderDrawings: reorderDrawings2,
1284
+ reorderFolders: reorderFolders2,
1260
1285
  saveCurrentDrawing,
1261
1286
  saveDrawingById,
1262
1287
  refreshDrawings,
@@ -1626,6 +1651,7 @@ var DrawingsDialog = ({
1626
1651
  deleteFolder: deleteFolder2,
1627
1652
  moveDrawingToFolder: moveDrawingToFolder2,
1628
1653
  reorderDrawings: reorderDrawings2,
1654
+ reorderFolders: reorderFolders2,
1629
1655
  exportWorkspace,
1630
1656
  importWorkspace,
1631
1657
  exportDrawingAsExcalidraw,
@@ -1643,7 +1669,17 @@ var DrawingsDialog = ({
1643
1669
  const [switchingId, setSwitchingId] = useState4(null);
1644
1670
  const [selectedId, setSelectedId] = useState4(null);
1645
1671
  const [hoveredId, setHoveredId] = useState4(null);
1646
- const [expandedFolders, setExpandedFolders] = useState4(/* @__PURE__ */ new Set());
1672
+ const [expandedFolders, setExpandedFolders] = useState4(() => {
1673
+ try {
1674
+ if (localStorage.getItem("rita-workspace-remember-folder-state") === "false") {
1675
+ return /* @__PURE__ */ new Set();
1676
+ }
1677
+ const raw = localStorage.getItem("rita-workspace-expanded-folders");
1678
+ if (raw) return new Set(JSON.parse(raw));
1679
+ } catch {
1680
+ }
1681
+ return /* @__PURE__ */ new Set();
1682
+ });
1647
1683
  const [creatingFolder, setCreatingFolder] = useState4(false);
1648
1684
  const [newFolderName, setNewFolderName] = useState4("");
1649
1685
  const [editingFolderId, setEditingFolderId] = useState4(null);
@@ -1673,10 +1709,35 @@ var DrawingsDialog = ({
1673
1709
  return next;
1674
1710
  });
1675
1711
  }, []);
1712
+ const [rememberFolderState, setRememberFolderState] = useState4(() => {
1713
+ try {
1714
+ return localStorage.getItem("rita-workspace-remember-folder-state") !== "false";
1715
+ } catch {
1716
+ return true;
1717
+ }
1718
+ });
1719
+ const handleToggleRememberFolderState = useCallback2(() => {
1720
+ setRememberFolderState((prev) => {
1721
+ const next = !prev;
1722
+ try {
1723
+ if (next) {
1724
+ localStorage.removeItem("rita-workspace-remember-folder-state");
1725
+ } else {
1726
+ localStorage.setItem("rita-workspace-remember-folder-state", "false");
1727
+ localStorage.removeItem("rita-workspace-expanded-folders");
1728
+ }
1729
+ } catch {
1730
+ }
1731
+ return next;
1732
+ });
1733
+ }, []);
1676
1734
  const [draggingDrawingId, setDraggingDrawingId] = useState4(null);
1677
1735
  const [dropTargetFolderId, setDropTargetFolderId] = useState4(null);
1678
1736
  const [dropTargetDrawingId, setDropTargetDrawingId] = useState4(null);
1679
1737
  const [dropTargetPosition, setDropTargetPosition] = useState4(null);
1738
+ const [draggingFolderId, setDraggingFolderId] = useState4(null);
1739
+ const [folderDropTargetId, setFolderDropTargetId] = useState4(null);
1740
+ const [folderDropPosition, setFolderDropPosition] = useState4(null);
1680
1741
  const [position, setPosition] = useState4(null);
1681
1742
  const dragRef = useRef3(null);
1682
1743
  const dialogRef = useRef3(null);
@@ -1697,6 +1758,18 @@ var DrawingsDialog = ({
1697
1758
  }
1698
1759
  prevOpenRef.current = open;
1699
1760
  }, [open, refreshDrawings]);
1761
+ useEffect3(() => {
1762
+ if (!open) return;
1763
+ try {
1764
+ if (localStorage.getItem("rita-workspace-remember-folder-state") === "false") {
1765
+ setExpandedFolders(/* @__PURE__ */ new Set());
1766
+ return;
1767
+ }
1768
+ const raw = localStorage.getItem("rita-workspace-expanded-folders");
1769
+ setExpandedFolders(raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set());
1770
+ } catch {
1771
+ }
1772
+ }, [open]);
1700
1773
  useEffect3(() => {
1701
1774
  if (creatingFolder && newFolderInputRef.current) {
1702
1775
  newFolderInputRef.current.focus();
@@ -1819,6 +1892,24 @@ var DrawingsDialog = ({
1819
1892
  ].map((d) => d.id);
1820
1893
  reorderDrawings2(ordered);
1821
1894
  }, [drawings, reorderDrawings2, moveDrawingToFolder2]);
1895
+ const handleFolderReorderDrop = useCallback2((draggedFolderId, targetFolderId, pos) => {
1896
+ if (draggedFolderId === targetFolderId) return;
1897
+ const sorted = [...folders].sort(
1898
+ (a, b) => (a.position ?? a.createdAt) - (b.position ?? b.createdAt)
1899
+ );
1900
+ const withoutDragged = sorted.filter((f) => f.id !== draggedFolderId);
1901
+ const targetIdx = withoutDragged.findIndex((f) => f.id === targetFolderId);
1902
+ if (targetIdx === -1) return;
1903
+ const insertIdx = pos === "before" ? targetIdx : targetIdx + 1;
1904
+ const dragged = folders.find((f) => f.id === draggedFolderId);
1905
+ if (!dragged) return;
1906
+ const ordered = [
1907
+ ...withoutDragged.slice(0, insertIdx),
1908
+ dragged,
1909
+ ...withoutDragged.slice(insertIdx)
1910
+ ].map((f) => f.id);
1911
+ reorderFolders2(ordered);
1912
+ }, [folders, reorderFolders2]);
1822
1913
  const toggleFolder = useCallback2((folderId) => {
1823
1914
  setExpandedFolders((prev) => {
1824
1915
  const next = new Set(prev);
@@ -1830,6 +1921,16 @@ var DrawingsDialog = ({
1830
1921
  return next;
1831
1922
  });
1832
1923
  }, []);
1924
+ useEffect3(() => {
1925
+ if (!rememberFolderState) return;
1926
+ try {
1927
+ localStorage.setItem(
1928
+ "rita-workspace-expanded-folders",
1929
+ JSON.stringify(Array.from(expandedFolders))
1930
+ );
1931
+ } catch {
1932
+ }
1933
+ }, [expandedFolders, rememberFolderState]);
1833
1934
  const getLocale = () => {
1834
1935
  if (!effectiveLang) return "en-US";
1835
1936
  const baseLang = effectiveLang.split("-")[0].toLowerCase();
@@ -1868,7 +1969,10 @@ var DrawingsDialog = ({
1868
1969
  for (const folder of folders) {
1869
1970
  byFolder[folder.id] = sorted.filter((d) => d.folderId === folder.id);
1870
1971
  }
1871
- const foldersFiltered = query ? folders.filter((f) => f.name.toLowerCase().includes(query) || (byFolder[f.id] || []).length > 0) : folders;
1972
+ const sortedFolders = [...folders].sort(
1973
+ (a, b) => (a.position ?? a.createdAt) - (b.position ?? b.createdAt)
1974
+ );
1975
+ const foldersFiltered = query ? sortedFolders.filter((f) => f.name.toLowerCase().includes(query) || (byFolder[f.id] || []).length > 0) : sortedFolders;
1872
1976
  return { rootDrawings: root, drawingsByFolder: byFolder, filteredFolders: foldersFiltered };
1873
1977
  }, [drawings, folders, searchQuery]);
1874
1978
  if (!open) return null;
@@ -2185,10 +2289,11 @@ var DrawingsDialog = ({
2185
2289
  const renderFolderGroup = (folder) => {
2186
2290
  const folderDrawings = drawingsByFolder[folder.id] || [];
2187
2291
  const isExpanded = expandedFolders.has(folder.id);
2292
+ const isFolderDropTarget = folderDropTargetId === folder.id;
2188
2293
  return /* @__PURE__ */ jsxs4(
2189
2294
  "div",
2190
2295
  {
2191
- style: { marginBottom: "4px" },
2296
+ style: { marginBottom: "4px", position: "relative" },
2192
2297
  onDragOver: (e) => {
2193
2298
  if (!draggingDrawingId) return;
2194
2299
  e.preventDefault();
@@ -2213,6 +2318,37 @@ var DrawingsDialog = ({
2213
2318
  /* @__PURE__ */ jsxs4(
2214
2319
  "div",
2215
2320
  {
2321
+ draggable: !editingFolderId && !confirmDeleteFolderId,
2322
+ onDragStart: (e) => {
2323
+ if (draggingDrawingId) return;
2324
+ setDraggingFolderId(folder.id);
2325
+ e.dataTransfer.effectAllowed = "move";
2326
+ },
2327
+ onDragEnd: () => {
2328
+ setDraggingFolderId(null);
2329
+ setFolderDropTargetId(null);
2330
+ setFolderDropPosition(null);
2331
+ },
2332
+ onDragOver: (e) => {
2333
+ if (!draggingFolderId || draggingFolderId === folder.id) return;
2334
+ e.preventDefault();
2335
+ e.stopPropagation();
2336
+ e.dataTransfer.dropEffect = "move";
2337
+ const rect = e.currentTarget.getBoundingClientRect();
2338
+ const isAbove = e.clientY < rect.top + rect.height / 2;
2339
+ setFolderDropTargetId(folder.id);
2340
+ setFolderDropPosition(isAbove ? "before" : "after");
2341
+ },
2342
+ onDrop: (e) => {
2343
+ if (draggingFolderId && draggingFolderId !== folder.id && folderDropPosition) {
2344
+ e.preventDefault();
2345
+ e.stopPropagation();
2346
+ handleFolderReorderDrop(draggingFolderId, folder.id, folderDropPosition);
2347
+ setDraggingFolderId(null);
2348
+ setFolderDropTargetId(null);
2349
+ setFolderDropPosition(null);
2350
+ }
2351
+ },
2216
2352
  onClick: () => {
2217
2353
  toggleFolder(folder.id);
2218
2354
  setSelectedId(null);
@@ -2223,9 +2359,11 @@ var DrawingsDialog = ({
2223
2359
  alignItems: "center",
2224
2360
  gap: "8px",
2225
2361
  borderRadius: "8px",
2226
- cursor: "pointer",
2362
+ cursor: draggingFolderId === folder.id ? "grabbing" : "pointer",
2363
+ opacity: draggingFolderId === folder.id ? 0.5 : 1,
2227
2364
  backgroundColor: dropTargetFolderId === folder.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.2))" : "var(--color-surface-mid, rgba(0, 0, 0, 0.03))",
2228
2365
  border: dropTargetFolderId === folder.id ? "2px dashed var(--color-primary, #6c63ff)" : "2px solid transparent",
2366
+ boxShadow: isFolderDropTarget && folderDropPosition === "before" ? "inset 0 3px 0 0 var(--color-primary, #6c63ff)" : isFolderDropTarget && folderDropPosition === "after" ? "inset 0 -3px 0 0 var(--color-primary, #6c63ff)" : "none",
2229
2367
  transition: "background-color 0.15s, border-color 0.15s"
2230
2368
  },
2231
2369
  children: [
@@ -2602,36 +2740,68 @@ var DrawingsDialog = ({
2602
2740
  }
2603
2741
  )
2604
2742
  ] }),
2605
- /* @__PURE__ */ jsx6("div", { style: { padding: "0 20px 16px" }, children: /* @__PURE__ */ jsxs4(
2606
- "label",
2607
- {
2608
- style: {
2609
- display: "flex",
2610
- alignItems: "center",
2611
- gap: "10px",
2612
- padding: "10px 12px",
2613
- border: "1px solid var(--default-border-color, #e0e0e0)",
2614
- borderRadius: "8px",
2615
- cursor: "pointer",
2616
- userSelect: "none"
2617
- },
2618
- children: [
2619
- /* @__PURE__ */ jsx6(
2620
- "input",
2621
- {
2622
- type: "checkbox",
2623
- checked: autoStart,
2624
- onChange: handleToggleAutoStart,
2625
- style: { width: "16px", height: "16px", cursor: "pointer", flexShrink: 0 }
2626
- }
2627
- ),
2628
- /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0 }, children: [
2629
- /* @__PURE__ */ jsx6("div", { style: { fontSize: "14px", fontWeight: 500 }, children: t.autoStartLabel }),
2630
- /* @__PURE__ */ jsx6("div", { style: { fontSize: "12px", color: "var(--text-secondary-color, #888)", marginTop: "2px" }, children: t.autoStartDesc })
2631
- ] })
2632
- ]
2633
- }
2634
- ) })
2743
+ /* @__PURE__ */ jsxs4("div", { style: { padding: "0 20px 16px", display: "flex", flexDirection: "column", gap: "8px" }, children: [
2744
+ /* @__PURE__ */ jsxs4(
2745
+ "label",
2746
+ {
2747
+ style: {
2748
+ display: "flex",
2749
+ alignItems: "center",
2750
+ gap: "10px",
2751
+ padding: "10px 12px",
2752
+ border: "1px solid var(--default-border-color, #e0e0e0)",
2753
+ borderRadius: "8px",
2754
+ cursor: "pointer",
2755
+ userSelect: "none"
2756
+ },
2757
+ children: [
2758
+ /* @__PURE__ */ jsx6(
2759
+ "input",
2760
+ {
2761
+ type: "checkbox",
2762
+ checked: autoStart,
2763
+ onChange: handleToggleAutoStart,
2764
+ style: { width: "16px", height: "16px", cursor: "pointer", flexShrink: 0 }
2765
+ }
2766
+ ),
2767
+ /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0 }, children: [
2768
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: "14px", fontWeight: 500 }, children: t.autoStartLabel }),
2769
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: "12px", color: "var(--text-secondary-color, #888)", marginTop: "2px" }, children: t.autoStartDesc })
2770
+ ] })
2771
+ ]
2772
+ }
2773
+ ),
2774
+ /* @__PURE__ */ jsxs4(
2775
+ "label",
2776
+ {
2777
+ style: {
2778
+ display: "flex",
2779
+ alignItems: "center",
2780
+ gap: "10px",
2781
+ padding: "10px 12px",
2782
+ border: "1px solid var(--default-border-color, #e0e0e0)",
2783
+ borderRadius: "8px",
2784
+ cursor: "pointer",
2785
+ userSelect: "none"
2786
+ },
2787
+ children: [
2788
+ /* @__PURE__ */ jsx6(
2789
+ "input",
2790
+ {
2791
+ type: "checkbox",
2792
+ checked: rememberFolderState,
2793
+ onChange: handleToggleRememberFolderState,
2794
+ style: { width: "16px", height: "16px", cursor: "pointer", flexShrink: 0 }
2795
+ }
2796
+ ),
2797
+ /* @__PURE__ */ jsxs4("div", { style: { flex: 1, minWidth: 0 }, children: [
2798
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: "14px", fontWeight: 500 }, children: "Kom ih\xE5g utf\xE4llda mappar" }),
2799
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: "12px", color: "var(--text-secondary-color, #888)", marginTop: "2px" }, children: "Beh\xE5ller vilka mappar som \xE4r utf\xE4llda mellan sessioner. Om av: alla mappar startar minimerade." })
2800
+ ] })
2801
+ ]
2802
+ }
2803
+ )
2804
+ ] })
2635
2805
  ]
2636
2806
  }
2637
2807
  )
@@ -2968,6 +3138,7 @@ export {
2968
3138
  removeDrawingFromWorkspace,
2969
3139
  renameFolder,
2970
3140
  reorderDrawings,
3141
+ reorderFolders,
2971
3142
  setActiveDrawing,
2972
3143
  updateDrawing,
2973
3144
  updateWorkspace,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rita-workspace",
3
- "version": "0.5.49",
3
+ "version": "0.5.53",
4
4
  "description": "Multi-drawing workspace feature for Rita (Excalidraw fork)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",