rita-workspace 0.5.0 → 0.5.2

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
@@ -41,16 +41,22 @@ __export(index_exports, {
41
41
  addDrawingToWorkspace: () => addDrawingToWorkspace,
42
42
  closeDB: () => closeDB,
43
43
  createDrawing: () => createDrawing,
44
+ createFolder: () => createFolder,
44
45
  deleteDrawing: () => deleteDrawing,
46
+ deleteFolder: () => deleteFolder,
45
47
  duplicateDrawing: () => duplicateDrawing,
46
48
  getAllDrawings: () => getAllDrawings,
49
+ getAllFolders: () => getAllFolders,
47
50
  getDB: () => getDB,
48
51
  getDrawing: () => getDrawing,
52
+ getFolder: () => getFolder,
49
53
  getOrCreateDefaultWorkspace: () => getOrCreateDefaultWorkspace,
50
54
  getTranslations: () => getTranslations,
51
55
  getWorkspace: () => getWorkspace,
52
56
  isLanguageSupported: () => isLanguageSupported,
57
+ moveDrawingToFolder: () => moveDrawingToFolder,
53
58
  removeDrawingFromWorkspace: () => removeDrawingFromWorkspace,
59
+ renameFolder: () => renameFolder,
54
60
  setActiveDrawing: () => setActiveDrawing,
55
61
  updateDrawing: () => updateDrawing,
56
62
  updateWorkspace: () => updateWorkspace,
@@ -63,12 +69,12 @@ module.exports = __toCommonJS(index_exports);
63
69
  // src/storage/db.ts
64
70
  var import_idb = require("idb");
65
71
  var DB_NAME = "rita-workspace";
66
- var DB_VERSION = 1;
72
+ var DB_VERSION = 2;
67
73
  var dbInstance = null;
68
74
  async function getDB() {
69
75
  if (dbInstance) return dbInstance;
70
76
  dbInstance = await (0, import_idb.openDB)(DB_NAME, DB_VERSION, {
71
- upgrade(db) {
77
+ upgrade(db, oldVersion) {
72
78
  if (!db.objectStoreNames.contains("workspaces")) {
73
79
  const workspaceStore = db.createObjectStore("workspaces", { keyPath: "id" });
74
80
  workspaceStore.createIndex("by-updated", "updatedAt");
@@ -77,6 +83,10 @@ async function getDB() {
77
83
  const drawingStore = db.createObjectStore("drawings", { keyPath: "id" });
78
84
  drawingStore.createIndex("by-updated", "updatedAt");
79
85
  }
86
+ if (oldVersion < 2) {
87
+ const folderStore = db.createObjectStore("folders", { keyPath: "id" });
88
+ folderStore.createIndex("by-name", "name");
89
+ }
80
90
  }
81
91
  });
82
92
  return dbInstance;
@@ -90,12 +100,13 @@ async function closeDB() {
90
100
 
91
101
  // src/storage/drawingStore.ts
92
102
  var import_nanoid = require("nanoid");
93
- async function createDrawing(name = "Untitled", elements = [], appState = {}) {
103
+ async function createDrawing(name = "Untitled", elements = [], appState = {}, folderId) {
94
104
  const db = await getDB();
95
105
  const now = Date.now();
96
106
  const drawing = {
97
107
  id: (0, import_nanoid.nanoid)(),
98
108
  name,
109
+ folderId: folderId || null,
99
110
  elements,
100
111
  appState,
101
112
  files: {},
@@ -138,9 +149,62 @@ async function duplicateDrawing(id, newName) {
138
149
  return createDrawing(
139
150
  newName || `${existing.name} (copy)`,
140
151
  existing.elements,
141
- existing.appState
152
+ existing.appState,
153
+ existing.folderId
142
154
  );
143
155
  }
156
+ async function moveDrawingToFolder(drawingId, folderId) {
157
+ return updateDrawing(drawingId, { folderId });
158
+ }
159
+
160
+ // src/storage/folderStore.ts
161
+ var import_nanoid2 = require("nanoid");
162
+ async function createFolder(name) {
163
+ const db = await getDB();
164
+ const now = Date.now();
165
+ const folder = {
166
+ id: (0, import_nanoid2.nanoid)(),
167
+ name,
168
+ createdAt: now,
169
+ updatedAt: now
170
+ };
171
+ await db.put("folders", folder);
172
+ return folder;
173
+ }
174
+ async function getFolder(id) {
175
+ const db = await getDB();
176
+ return db.get("folders", id);
177
+ }
178
+ async function getAllFolders() {
179
+ const db = await getDB();
180
+ return db.getAllFromIndex("folders", "by-name");
181
+ }
182
+ async function renameFolder(id, name) {
183
+ const db = await getDB();
184
+ const existing = await db.get("folders", id);
185
+ if (!existing) return void 0;
186
+ const updated = {
187
+ ...existing,
188
+ name,
189
+ updatedAt: Date.now()
190
+ };
191
+ await db.put("folders", updated);
192
+ return updated;
193
+ }
194
+ async function deleteFolder(id) {
195
+ const db = await getDB();
196
+ const allDrawings = await db.getAll("drawings");
197
+ const tx = db.transaction(["drawings", "folders"], "readwrite");
198
+ for (const drawing of allDrawings) {
199
+ if (drawing.folderId === id) {
200
+ drawing.folderId = null;
201
+ drawing.updatedAt = Date.now();
202
+ await tx.objectStore("drawings").put(drawing);
203
+ }
204
+ }
205
+ await tx.objectStore("folders").delete(id);
206
+ await tx.done;
207
+ }
144
208
 
145
209
  // src/storage/workspaceStore.ts
146
210
  var DEFAULT_WORKSPACE_ID = "default";
@@ -246,6 +310,14 @@ var sv = {
246
310
  importWorkspace: "Importera arbetsyta",
247
311
  exportDrawing: "Spara som .excalidraw",
248
312
  importDrawing: "\xD6ppna .excalidraw",
313
+ // Folders
314
+ createFolder: "Skapa mapp",
315
+ renameFolder: "Byt namn p\xE5 mapp",
316
+ deleteFolder: "Ta bort mapp",
317
+ deleteFolderConfirm: "Vill du ta bort denna mapp? Ritningarna flyttas till rotniv\xE5n.",
318
+ moveToFolder: "Flytta till mapp",
319
+ moveToRoot: "Ingen mapp",
320
+ newFolderName: "Ny mapp",
249
321
  // Shortcuts
250
322
  shortcutNewDrawing: "Ctrl+Alt+N"
251
323
  };
@@ -285,6 +357,14 @@ var en = {
285
357
  importWorkspace: "Import workspace",
286
358
  exportDrawing: "Save as .excalidraw",
287
359
  importDrawing: "Open .excalidraw",
360
+ // Folders
361
+ createFolder: "Create folder",
362
+ renameFolder: "Rename folder",
363
+ deleteFolder: "Delete folder",
364
+ deleteFolderConfirm: "Delete this folder? Drawings will be moved to root.",
365
+ moveToFolder: "Move to folder",
366
+ moveToRoot: "No folder",
367
+ newFolderName: "New folder",
288
368
  // Shortcuts
289
369
  shortcutNewDrawing: "Ctrl+Alt+N"
290
370
  };
@@ -326,17 +406,23 @@ function useWorkspaceLang() {
326
406
  var TAB_ID = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
327
407
  var TABS_KEY = "rita-workspace-tabs";
328
408
  var TAB_CHANNEL = "rita-workspace-tabs";
409
+ var tabsMapCache = null;
410
+ var tabsMapRaw = null;
329
411
  function getTabsMap() {
330
412
  try {
331
- const raw = JSON.parse(localStorage.getItem(TABS_KEY) || "{}");
413
+ const raw = localStorage.getItem(TABS_KEY) || "{}";
414
+ if (raw === tabsMapRaw && tabsMapCache) return tabsMapCache;
415
+ tabsMapRaw = raw;
416
+ const parsed = JSON.parse(raw);
332
417
  const result = {};
333
- for (const [tabId, value] of Object.entries(raw)) {
418
+ for (const [tabId, value] of Object.entries(parsed)) {
334
419
  if (typeof value === "string") {
335
420
  result[tabId] = { drawingId: value, openedAt: 0 };
336
421
  } else if (value && typeof value === "object" && "drawingId" in value) {
337
422
  result[tabId] = value;
338
423
  }
339
424
  }
425
+ tabsMapCache = result;
340
426
  return result;
341
427
  } catch {
342
428
  return {};
@@ -353,7 +439,10 @@ function setTabDrawing(drawingId) {
353
439
  } else {
354
440
  delete tabs[TAB_ID];
355
441
  }
356
- localStorage.setItem(TABS_KEY, JSON.stringify(tabs));
442
+ const json = JSON.stringify(tabs);
443
+ localStorage.setItem(TABS_KEY, json);
444
+ tabsMapCache = tabs;
445
+ tabsMapRaw = json;
357
446
  }
358
447
  function isDrawingOpenedEarlierInOtherTab(drawingId) {
359
448
  const tabs = getTabsMap();
@@ -367,6 +456,7 @@ function isDrawingOpenedEarlierInOtherTab(drawingId) {
367
456
  function WorkspaceProvider({ children, lang = "en" }) {
368
457
  const [workspace, setWorkspace] = (0, import_react.useState)(null);
369
458
  const [drawings, setDrawings] = (0, import_react.useState)([]);
459
+ const [folders, setFolders] = (0, import_react.useState)([]);
370
460
  const [activeDrawing, setActiveDrawing2] = (0, import_react.useState)(null);
371
461
  const [isLoading, setIsLoading] = (0, import_react.useState)(true);
372
462
  const [error, setError] = (0, import_react.useState)(null);
@@ -442,6 +532,8 @@ function WorkspaceProvider({ children, lang = "en" }) {
442
532
  const allDrawings = await getAllDrawings();
443
533
  const wsDrawings = allDrawings.filter((d) => ws.drawingIds.includes(d.id));
444
534
  setDrawings(wsDrawings);
535
+ const allFolders = await getAllFolders();
536
+ setFolders(allFolders);
445
537
  const lastDrawingId = sessionStorage.getItem("rita-workspace-tab-drawing");
446
538
  let active = null;
447
539
  if (lastDrawingId) {
@@ -474,14 +566,20 @@ function WorkspaceProvider({ children, lang = "en" }) {
474
566
  const allDrawings = await getAllDrawings();
475
567
  const wsDrawings = allDrawings.filter((d) => workspace.drawingIds.includes(d.id));
476
568
  setDrawings(wsDrawings);
569
+ const allFolders = await getAllFolders();
570
+ setFolders(allFolders);
477
571
  } catch (err) {
478
572
  }
479
573
  }, [workspace]);
480
- const createNewDrawing = (0, import_react.useCallback)(async (name) => {
574
+ const drawingsRef = (0, import_react.useRef)(drawings);
575
+ drawingsRef.current = drawings;
576
+ const activeDrawingIdRef = (0, import_react.useRef)(activeDrawing?.id ?? null);
577
+ activeDrawingIdRef.current = activeDrawing?.id ?? null;
578
+ const createNewDrawing = (0, import_react.useCallback)(async (name, folderId) => {
481
579
  if (!workspace) return null;
482
580
  try {
483
- const defaultName = `${t.newDrawing} ${drawings.length + 1}`;
484
- const drawing = await createDrawing(name || defaultName);
581
+ const defaultName = `${t.newDrawing} ${drawingsRef.current.length + 1}`;
582
+ const drawing = await createDrawing(name || defaultName, [], {}, folderId);
485
583
  await addDrawingToWorkspace(workspace.id, drawing.id);
486
584
  setDrawings((prev) => [...prev, drawing]);
487
585
  setActiveDrawing2(drawing);
@@ -496,7 +594,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
496
594
  setError(err instanceof Error ? err.message : "Failed to create drawing");
497
595
  return null;
498
596
  }
499
- }, [workspace, drawings.length, t]);
597
+ }, [workspace, t]);
500
598
  const switchDrawing = (0, import_react.useCallback)(async (id) => {
501
599
  if (!workspace) return;
502
600
  try {
@@ -515,23 +613,23 @@ function WorkspaceProvider({ children, lang = "en" }) {
515
613
  const updated = await updateDrawing(id, { name });
516
614
  if (updated) {
517
615
  setDrawings((prev) => prev.map((d) => d.id === id ? updated : d));
518
- if (activeDrawing?.id === id) {
616
+ if (activeDrawingIdRef.current === id) {
519
617
  setActiveDrawing2(updated);
520
618
  }
521
619
  }
522
620
  } catch (err) {
523
621
  setError(err instanceof Error ? err.message : "Failed to rename drawing");
524
622
  }
525
- }, [activeDrawing]);
623
+ }, []);
526
624
  const removeDrawing = (0, import_react.useCallback)(async (id) => {
527
- if (!workspace || drawings.length <= 1) return;
625
+ if (!workspace || drawingsRef.current.length <= 1) return;
528
626
  try {
529
627
  await deleteDrawing(id);
530
628
  const updatedWorkspace = await removeDrawingFromWorkspace(workspace.id, id);
531
629
  setDrawings((prev) => prev.filter((d) => d.id !== id));
532
630
  if (updatedWorkspace) {
533
631
  setWorkspace(updatedWorkspace);
534
- if (activeDrawing?.id === id && updatedWorkspace.activeDrawingId) {
632
+ if (activeDrawingIdRef.current === id && updatedWorkspace.activeDrawingId) {
535
633
  const newActive = await getDrawing(updatedWorkspace.activeDrawingId);
536
634
  setActiveDrawing2(newActive || null);
537
635
  }
@@ -539,11 +637,11 @@ function WorkspaceProvider({ children, lang = "en" }) {
539
637
  } catch (err) {
540
638
  setError(err instanceof Error ? err.message : "Failed to delete drawing");
541
639
  }
542
- }, [workspace, drawings.length, activeDrawing]);
640
+ }, [workspace]);
543
641
  const duplicateCurrentDrawing = (0, import_react.useCallback)(async () => {
544
- if (!activeDrawing || !workspace) return null;
642
+ if (!activeDrawingIdRef.current || !workspace) return null;
545
643
  try {
546
- const duplicate = await duplicateDrawing(activeDrawing.id);
644
+ const duplicate = await duplicateDrawing(activeDrawingIdRef.current);
547
645
  if (duplicate) {
548
646
  await addDrawingToWorkspace(workspace.id, duplicate.id);
549
647
  setDrawings((prev) => [...prev, duplicate]);
@@ -558,7 +656,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
558
656
  setError(err instanceof Error ? err.message : "Failed to duplicate drawing");
559
657
  return null;
560
658
  }
561
- }, [activeDrawing, workspace]);
659
+ }, [workspace]);
562
660
  const saveCurrentDrawing = (0, import_react.useCallback)(async (elements, appState, files) => {
563
661
  if (!activeDrawing) return;
564
662
  try {
@@ -714,9 +812,52 @@ function WorkspaceProvider({ children, lang = "en" }) {
714
812
  setError(err instanceof Error ? err.message : "Failed to import drawing");
715
813
  }
716
814
  }, [workspace, t]);
815
+ const createFolder2 = (0, import_react.useCallback)(async (name) => {
816
+ try {
817
+ const folder = await createFolder(name);
818
+ setFolders((prev) => [...prev, folder]);
819
+ return folder;
820
+ } catch (err) {
821
+ setError(err instanceof Error ? err.message : "Failed to create folder");
822
+ return null;
823
+ }
824
+ }, []);
825
+ const renameFolder2 = (0, import_react.useCallback)(async (id, name) => {
826
+ try {
827
+ const updated = await renameFolder(id, name);
828
+ if (updated) {
829
+ setFolders((prev) => prev.map((f) => f.id === id ? updated : f));
830
+ }
831
+ } catch (err) {
832
+ setError(err instanceof Error ? err.message : "Failed to rename folder");
833
+ }
834
+ }, []);
835
+ const deleteFolder2 = (0, import_react.useCallback)(async (id) => {
836
+ try {
837
+ await deleteFolder(id);
838
+ setFolders((prev) => prev.filter((f) => f.id !== id));
839
+ setDrawings((prev) => prev.map((d) => d.folderId === id ? { ...d, folderId: null } : d));
840
+ } catch (err) {
841
+ setError(err instanceof Error ? err.message : "Failed to delete folder");
842
+ }
843
+ }, []);
844
+ const moveDrawingToFolder2 = (0, import_react.useCallback)(async (drawingId, folderId) => {
845
+ try {
846
+ const updated = await moveDrawingToFolder(drawingId, folderId);
847
+ if (updated) {
848
+ setDrawings((prev) => prev.map((d) => d.id === drawingId ? updated : d));
849
+ if (activeDrawingIdRef.current === drawingId) {
850
+ setActiveDrawing2(updated);
851
+ }
852
+ }
853
+ } catch (err) {
854
+ setError(err instanceof Error ? err.message : "Failed to move drawing");
855
+ }
856
+ }, []);
717
857
  const value = {
718
858
  workspace,
719
859
  drawings,
860
+ folders,
720
861
  activeDrawing,
721
862
  isLoading,
722
863
  error,
@@ -728,6 +869,10 @@ function WorkspaceProvider({ children, lang = "en" }) {
728
869
  renameDrawing,
729
870
  removeDrawing,
730
871
  duplicateCurrentDrawing,
872
+ createFolder: createFolder2,
873
+ renameFolder: renameFolder2,
874
+ deleteFolder: deleteFolder2,
875
+ moveDrawingToFolder: moveDrawingToFolder2,
731
876
  saveCurrentDrawing,
732
877
  saveDrawingById,
733
878
  refreshDrawings,
@@ -1081,11 +1226,16 @@ var DrawingsDialog = ({
1081
1226
  }) => {
1082
1227
  const {
1083
1228
  drawings,
1229
+ folders,
1084
1230
  activeDrawing,
1085
1231
  switchDrawing,
1086
1232
  createNewDrawing,
1087
1233
  renameDrawing,
1088
1234
  removeDrawing,
1235
+ createFolder: createFolder2,
1236
+ renameFolder: renameFolder2,
1237
+ deleteFolder: deleteFolder2,
1238
+ moveDrawingToFolder: moveDrawingToFolder2,
1089
1239
  exportWorkspace,
1090
1240
  importWorkspace,
1091
1241
  exportDrawingAsExcalidraw,
@@ -1099,21 +1249,44 @@ var DrawingsDialog = ({
1099
1249
  const [editingId, setEditingId] = (0, import_react5.useState)(null);
1100
1250
  const [editName, setEditName] = (0, import_react5.useState)("");
1101
1251
  const [confirmDeleteId, setConfirmDeleteId] = (0, import_react5.useState)(null);
1252
+ const [switchingId, setSwitchingId] = (0, import_react5.useState)(null);
1253
+ const [expandedFolders, setExpandedFolders] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
1254
+ const [creatingFolder, setCreatingFolder] = (0, import_react5.useState)(false);
1255
+ const [newFolderName, setNewFolderName] = (0, import_react5.useState)("");
1256
+ const [editingFolderId, setEditingFolderId] = (0, import_react5.useState)(null);
1257
+ const [editFolderName, setEditFolderName] = (0, import_react5.useState)("");
1258
+ const [confirmDeleteFolderId, setConfirmDeleteFolderId] = (0, import_react5.useState)(null);
1259
+ const [movingDrawingId, setMovingDrawingId] = (0, import_react5.useState)(null);
1260
+ const [searchQuery, setSearchQuery] = (0, import_react5.useState)("");
1261
+ const [busyFolderId, setBusyFolderId] = (0, import_react5.useState)(null);
1262
+ const [isRefreshing, setIsRefreshing] = (0, import_react5.useState)(false);
1263
+ const [draggingDrawingId, setDraggingDrawingId] = (0, import_react5.useState)(null);
1264
+ const [dropTargetFolderId, setDropTargetFolderId] = (0, import_react5.useState)(null);
1102
1265
  const [position, setPosition] = (0, import_react5.useState)(null);
1103
1266
  const dragRef = (0, import_react5.useRef)(null);
1104
1267
  const dialogRef = (0, import_react5.useRef)(null);
1268
+ const newFolderInputRef = (0, import_react5.useRef)(null);
1105
1269
  const prevOpenRef = (0, import_react5.useRef)(false);
1106
1270
  (0, import_react5.useEffect)(() => {
1107
1271
  if (open) {
1108
- refreshDrawings();
1272
+ setIsRefreshing(true);
1273
+ refreshDrawings().finally(() => setIsRefreshing(false));
1109
1274
  if (!prevOpenRef.current) {
1110
1275
  setPosition(null);
1276
+ setSearchQuery("");
1277
+ setExpandedFolders(new Set(folders.map((f) => f.id)));
1111
1278
  }
1112
1279
  }
1113
1280
  prevOpenRef.current = open;
1114
- }, [open, refreshDrawings]);
1281
+ }, [open, refreshDrawings, folders]);
1282
+ (0, import_react5.useEffect)(() => {
1283
+ if (creatingFolder && newFolderInputRef.current) {
1284
+ newFolderInputRef.current.focus();
1285
+ }
1286
+ }, [creatingFolder]);
1115
1287
  const handleMouseDown = (0, import_react5.useCallback)((e) => {
1116
1288
  if (e.target.closest("button")) return;
1289
+ if (e.target.closest("input")) return;
1117
1290
  if (!dialogRef.current) return;
1118
1291
  const rect = dialogRef.current.getBoundingClientRect();
1119
1292
  const currentX = position?.x ?? rect.left;
@@ -1134,11 +1307,13 @@ var DrawingsDialog = ({
1134
1307
  document.addEventListener("mouseup", handleMouseUp);
1135
1308
  }, [position]);
1136
1309
  const handleSelect = (0, import_react5.useCallback)(async (drawing) => {
1310
+ setSwitchingId(drawing.id);
1137
1311
  await switchDrawing(drawing.id);
1138
1312
  onDrawingSelect?.(drawing);
1313
+ setSwitchingId(null);
1139
1314
  }, [switchDrawing, onDrawingSelect]);
1140
- const handleCreate = (0, import_react5.useCallback)(async () => {
1141
- const newDrawing = await createNewDrawing();
1315
+ const handleCreate = (0, import_react5.useCallback)(async (folderId) => {
1316
+ const newDrawing = await createNewDrawing(void 0, folderId);
1142
1317
  if (newDrawing) onDrawingSelect?.(newDrawing);
1143
1318
  }, [createNewDrawing, onDrawingSelect]);
1144
1319
  const handleStartEdit = (0, import_react5.useCallback)((drawing) => {
@@ -1160,6 +1335,48 @@ var DrawingsDialog = ({
1160
1335
  await removeDrawing(id);
1161
1336
  setConfirmDeleteId(null);
1162
1337
  }, [removeDrawing]);
1338
+ const handleCreateFolder = (0, import_react5.useCallback)(async () => {
1339
+ if (newFolderName.trim()) {
1340
+ setBusyFolderId("__creating__");
1341
+ const folder = await createFolder2(newFolderName.trim());
1342
+ if (folder) {
1343
+ setExpandedFolders((prev) => /* @__PURE__ */ new Set([...prev, folder.id]));
1344
+ }
1345
+ setBusyFolderId(null);
1346
+ }
1347
+ setCreatingFolder(false);
1348
+ setNewFolderName("");
1349
+ }, [newFolderName, createFolder2]);
1350
+ const handleSaveFolderEdit = (0, import_react5.useCallback)(async () => {
1351
+ if (editingFolderId && editFolderName.trim()) {
1352
+ setBusyFolderId(editingFolderId);
1353
+ await renameFolder2(editingFolderId, editFolderName.trim());
1354
+ setBusyFolderId(null);
1355
+ }
1356
+ setEditingFolderId(null);
1357
+ setEditFolderName("");
1358
+ }, [editingFolderId, editFolderName, renameFolder2]);
1359
+ const handleDeleteFolder = (0, import_react5.useCallback)(async (id) => {
1360
+ setBusyFolderId(id);
1361
+ await deleteFolder2(id);
1362
+ setBusyFolderId(null);
1363
+ setConfirmDeleteFolderId(null);
1364
+ }, [deleteFolder2]);
1365
+ const handleMoveToFolder = (0, import_react5.useCallback)(async (drawingId, folderId) => {
1366
+ await moveDrawingToFolder2(drawingId, folderId);
1367
+ setMovingDrawingId(null);
1368
+ }, [moveDrawingToFolder2]);
1369
+ const toggleFolder = (0, import_react5.useCallback)((folderId) => {
1370
+ setExpandedFolders((prev) => {
1371
+ const next = new Set(prev);
1372
+ if (next.has(folderId)) {
1373
+ next.delete(folderId);
1374
+ } else {
1375
+ next.add(folderId);
1376
+ }
1377
+ return next;
1378
+ });
1379
+ }, []);
1163
1380
  const getLocale = () => {
1164
1381
  if (!effectiveLang) return "en-US";
1165
1382
  const baseLang = effectiveLang.split("-")[0].toLowerCase();
@@ -1174,6 +1391,17 @@ var DrawingsDialog = ({
1174
1391
  });
1175
1392
  };
1176
1393
  if (!open) return null;
1394
+ const { rootDrawings, drawingsByFolder, filteredFolders } = (0, import_react5.useMemo)(() => {
1395
+ const query = searchQuery.toLowerCase().trim();
1396
+ const filtered = query ? drawings.filter((d) => d.name.toLowerCase().includes(query)) : drawings;
1397
+ const root = filtered.filter((d) => !d.folderId);
1398
+ const byFolder = {};
1399
+ for (const folder of folders) {
1400
+ byFolder[folder.id] = filtered.filter((d) => d.folderId === folder.id);
1401
+ }
1402
+ const foldersFiltered = query ? folders.filter((f) => f.name.toLowerCase().includes(query) || (byFolder[f.id] || []).length > 0) : folders;
1403
+ return { rootDrawings: root, drawingsByFolder: byFolder, filteredFolders: foldersFiltered };
1404
+ }, [drawings, folders, searchQuery]);
1177
1405
  const dialogStyle = position ? {
1178
1406
  position: "fixed",
1179
1407
  left: position.x,
@@ -1207,6 +1435,355 @@ var DrawingsDialog = ({
1207
1435
  color: "var(--text-secondary-color, #888)",
1208
1436
  padding: "16px 20px 8px"
1209
1437
  };
1438
+ const renderDrawingRow = (drawing) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1439
+ "div",
1440
+ {
1441
+ draggable: !editingId && !confirmDeleteId,
1442
+ onDragStart: (e) => {
1443
+ setDraggingDrawingId(drawing.id);
1444
+ e.dataTransfer.effectAllowed = "move";
1445
+ e.dataTransfer.setData("text/plain", drawing.id);
1446
+ },
1447
+ onDragEnd: () => {
1448
+ setDraggingDrawingId(null);
1449
+ setDropTargetFolderId("__none__");
1450
+ setTimeout(() => setDropTargetFolderId(null), 0);
1451
+ },
1452
+ onClick: () => {
1453
+ if (editingId || confirmDeleteId || switchingId || movingDrawingId) return;
1454
+ if (activeDrawing?.id !== drawing.id) handleSelect(drawing);
1455
+ },
1456
+ style: {
1457
+ padding: "10px 12px",
1458
+ display: "flex",
1459
+ alignItems: "center",
1460
+ gap: "12px",
1461
+ borderRadius: "8px",
1462
+ marginBottom: "4px",
1463
+ cursor: draggingDrawingId ? "grabbing" : editingId || confirmDeleteId || switchingId ? "default" : "pointer",
1464
+ backgroundColor: activeDrawing?.id === drawing.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent",
1465
+ transition: "background-color 0.15s",
1466
+ opacity: draggingDrawingId === drawing.id ? 0.4 : switchingId && switchingId !== drawing.id ? 0.5 : 1
1467
+ },
1468
+ children: [
1469
+ renderThumbnail && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
1470
+ width: "64px",
1471
+ height: "48px",
1472
+ flexShrink: 0,
1473
+ borderRadius: "4px",
1474
+ overflow: "hidden",
1475
+ border: "1px solid var(--default-border-color, #e0e0e0)",
1476
+ backgroundColor: "#fff",
1477
+ display: "flex",
1478
+ alignItems: "center",
1479
+ justifyContent: "center"
1480
+ }, children: renderThumbnail(drawing) }),
1481
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, minWidth: 0 }, children: editingId === drawing.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "6px", alignItems: "center" }, children: [
1482
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1483
+ "input",
1484
+ {
1485
+ type: "text",
1486
+ value: editName,
1487
+ onChange: (e) => setEditName(e.target.value),
1488
+ onKeyDown: (e) => {
1489
+ if (e.key === "Enter") handleSaveEdit();
1490
+ if (e.key === "Escape") handleCancelEdit();
1491
+ },
1492
+ onClick: (e) => e.stopPropagation(),
1493
+ autoFocus: true,
1494
+ style: {
1495
+ flex: 1,
1496
+ padding: "4px 8px",
1497
+ fontSize: "14px",
1498
+ border: "1px solid var(--color-primary, #6c63ff)",
1499
+ borderRadius: "4px",
1500
+ outline: "none"
1501
+ }
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1505
+ "button",
1506
+ {
1507
+ onClick: (e) => {
1508
+ e.stopPropagation();
1509
+ handleSaveEdit();
1510
+ },
1511
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1512
+ children: t.save
1513
+ }
1514
+ ),
1515
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1516
+ "button",
1517
+ {
1518
+ onClick: (e) => {
1519
+ e.stopPropagation();
1520
+ handleCancelEdit();
1521
+ },
1522
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1523
+ children: t.cancel
1524
+ }
1525
+ )
1526
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1527
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
1528
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1529
+ "span",
1530
+ {
1531
+ onClick: (e) => {
1532
+ e.stopPropagation();
1533
+ handleStartEdit(drawing);
1534
+ },
1535
+ style: {
1536
+ fontWeight: activeDrawing?.id === drawing.id ? 600 : 400,
1537
+ fontSize: "14px",
1538
+ overflow: "hidden",
1539
+ textOverflow: "ellipsis",
1540
+ whiteSpace: "nowrap",
1541
+ cursor: "text"
1542
+ },
1543
+ title: t.rename,
1544
+ children: [
1545
+ switchingId === drawing.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { display: "inline-block", animation: "spin 1s linear infinite", marginRight: "4px" }, children: "\u23F3" }) : activeDrawing?.id === drawing.id ? "\u2713 " : "",
1546
+ drawing.name
1547
+ ]
1548
+ }
1549
+ ),
1550
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1551
+ "button",
1552
+ {
1553
+ onClick: (e) => {
1554
+ e.stopPropagation();
1555
+ handleStartEdit(drawing);
1556
+ },
1557
+ style: { background: "none", border: "none", cursor: "pointer", padding: "0 2px", fontSize: "12px", opacity: 0.4, flexShrink: 0 },
1558
+ title: t.rename,
1559
+ children: "\u270F\uFE0F"
1560
+ }
1561
+ )
1562
+ ] }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { fontSize: "11px", color: "var(--text-secondary-color, #888)", marginTop: "1px" }, children: [
1564
+ t.modified,
1565
+ ": ",
1566
+ formatDate(drawing.updatedAt)
1567
+ ] })
1568
+ ] }) }),
1569
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", gap: "2px", alignItems: "center" }, children: confirmDeleteId === drawing.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1570
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1571
+ "button",
1572
+ {
1573
+ onClick: (e) => {
1574
+ e.stopPropagation();
1575
+ handleDelete(drawing.id);
1576
+ },
1577
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "#dc3545", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1578
+ children: t.delete
1579
+ }
1580
+ ),
1581
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1582
+ "button",
1583
+ {
1584
+ onClick: (e) => {
1585
+ e.stopPropagation();
1586
+ setConfirmDeleteId(null);
1587
+ },
1588
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1589
+ children: t.cancel
1590
+ }
1591
+ )
1592
+ ] }) : movingDrawingId === drawing.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { onClick: (e) => e.stopPropagation(), style: { display: "flex", flexDirection: "column", gap: "2px", fontSize: "12px" }, children: [
1593
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1594
+ "button",
1595
+ {
1596
+ onClick: () => handleMoveToFolder(drawing.id, null),
1597
+ style: { padding: "3px 8px", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", backgroundColor: !drawing.folderId ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent", color: "inherit", textAlign: "left" },
1598
+ children: t.moveToRoot
1599
+ }
1600
+ ),
1601
+ folders.map((folder) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1602
+ "button",
1603
+ {
1604
+ onClick: () => handleMoveToFolder(drawing.id, folder.id),
1605
+ style: { padding: "3px 8px", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", backgroundColor: drawing.folderId === folder.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent", color: "inherit", textAlign: "left" },
1606
+ children: [
1607
+ "\u{1F4C1} ",
1608
+ folder.name
1609
+ ]
1610
+ },
1611
+ folder.id
1612
+ )),
1613
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1614
+ "button",
1615
+ {
1616
+ onClick: () => setMovingDrawingId(null),
1617
+ style: { padding: "3px 8px", border: "none", cursor: "pointer", backgroundColor: "transparent", color: "var(--text-secondary-color, #888)", textAlign: "left" },
1618
+ children: t.cancel
1619
+ }
1620
+ )
1621
+ ] }) : editingId !== drawing.id && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1622
+ folders.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1623
+ "button",
1624
+ {
1625
+ onClick: (e) => {
1626
+ e.stopPropagation();
1627
+ setMovingDrawingId(drawing.id);
1628
+ },
1629
+ style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1630
+ title: t.moveToFolder,
1631
+ children: "\u{1F4C1}"
1632
+ }
1633
+ ),
1634
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1635
+ "button",
1636
+ {
1637
+ onClick: (e) => {
1638
+ e.stopPropagation();
1639
+ exportDrawingAsExcalidraw(drawing.id);
1640
+ },
1641
+ style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1642
+ title: t.exportDrawing,
1643
+ children: "\u{1F4BE}"
1644
+ }
1645
+ ),
1646
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1647
+ "button",
1648
+ {
1649
+ onClick: (e) => {
1650
+ e.stopPropagation();
1651
+ setConfirmDeleteId(drawing.id);
1652
+ },
1653
+ style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1654
+ title: t.delete,
1655
+ disabled: drawings.length <= 1,
1656
+ children: "\u{1F5D1}\uFE0F"
1657
+ }
1658
+ )
1659
+ ] }) })
1660
+ ]
1661
+ },
1662
+ drawing.id
1663
+ );
1664
+ const renderFolderGroup = (folder) => {
1665
+ const folderDrawings = drawingsByFolder[folder.id] || [];
1666
+ const isExpanded = expandedFolders.has(folder.id);
1667
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginBottom: "4px" }, children: [
1668
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1669
+ "div",
1670
+ {
1671
+ onClick: () => toggleFolder(folder.id),
1672
+ onDragOver: (e) => {
1673
+ if (!draggingDrawingId) return;
1674
+ e.preventDefault();
1675
+ e.dataTransfer.dropEffect = "move";
1676
+ setDropTargetFolderId(folder.id);
1677
+ },
1678
+ onDragLeave: () => {
1679
+ if (dropTargetFolderId === folder.id) setDropTargetFolderId(null);
1680
+ },
1681
+ onDrop: (e) => {
1682
+ e.preventDefault();
1683
+ if (draggingDrawingId) {
1684
+ handleMoveToFolder(draggingDrawingId, folder.id);
1685
+ setDraggingDrawingId(null);
1686
+ setDropTargetFolderId(null);
1687
+ setExpandedFolders((prev) => /* @__PURE__ */ new Set([...prev, folder.id]));
1688
+ }
1689
+ },
1690
+ style: {
1691
+ padding: "8px 12px",
1692
+ display: "flex",
1693
+ alignItems: "center",
1694
+ gap: "8px",
1695
+ borderRadius: "8px",
1696
+ cursor: "pointer",
1697
+ backgroundColor: dropTargetFolderId === folder.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.2))" : "var(--color-surface-mid, rgba(0, 0, 0, 0.03))",
1698
+ border: dropTargetFolderId === folder.id ? "2px dashed var(--color-primary, #6c63ff)" : "2px solid transparent",
1699
+ transition: "background-color 0.15s, border-color 0.15s"
1700
+ },
1701
+ children: [
1702
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "12px", width: "16px", textAlign: "center", flexShrink: 0 }, children: isExpanded ? "\u25BC" : "\u25B6" }),
1703
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "16px", flexShrink: 0 }, children: busyFolderId === folder.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { display: "inline-block", animation: "spin 1s linear infinite" }, children: "\u23F3" }) : "\u{1F4C1}" }),
1704
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, minWidth: 0 }, children: editingFolderId === folder.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "6px", alignItems: "center" }, onClick: (e) => e.stopPropagation(), children: [
1705
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1706
+ "input",
1707
+ {
1708
+ type: "text",
1709
+ value: editFolderName,
1710
+ onChange: (e) => setEditFolderName(e.target.value),
1711
+ onKeyDown: (e) => {
1712
+ if (e.key === "Enter") handleSaveFolderEdit();
1713
+ if (e.key === "Escape") {
1714
+ setEditingFolderId(null);
1715
+ setEditFolderName("");
1716
+ }
1717
+ },
1718
+ autoFocus: true,
1719
+ style: { flex: 1, padding: "2px 6px", fontSize: "14px", border: "1px solid var(--color-primary, #6c63ff)", borderRadius: "4px", outline: "none" }
1720
+ }
1721
+ ),
1722
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1723
+ "button",
1724
+ {
1725
+ onClick: handleSaveFolderEdit,
1726
+ style: { padding: "2px 8px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1727
+ children: t.save
1728
+ }
1729
+ )
1730
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontWeight: 600, fontSize: "14px" }, children: [
1731
+ folder.name,
1732
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontWeight: 400, fontSize: "12px", color: "var(--text-secondary-color, #888)", marginLeft: "6px" }, children: [
1733
+ "(",
1734
+ folderDrawings.length,
1735
+ ")"
1736
+ ] })
1737
+ ] }) }),
1738
+ confirmDeleteFolderId === folder.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { onClick: (e) => e.stopPropagation(), style: { display: "flex", gap: "4px" }, children: [
1739
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1740
+ "button",
1741
+ {
1742
+ onClick: () => handleDeleteFolder(folder.id),
1743
+ style: { padding: "2px 8px", fontSize: "12px", backgroundColor: "#dc3545", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1744
+ children: t.delete
1745
+ }
1746
+ ),
1747
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1748
+ "button",
1749
+ {
1750
+ onClick: () => setConfirmDeleteFolderId(null),
1751
+ style: { padding: "2px 8px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1752
+ children: t.cancel
1753
+ }
1754
+ )
1755
+ ] }) : editingFolderId !== folder.id && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { onClick: (e) => e.stopPropagation(), style: { display: "flex", gap: "2px" }, children: [
1756
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1757
+ "button",
1758
+ {
1759
+ onClick: () => {
1760
+ setEditingFolderId(folder.id);
1761
+ setEditFolderName(folder.name);
1762
+ },
1763
+ style: { background: "none", border: "none", cursor: "pointer", padding: "2px", fontSize: "12px", opacity: 0.5 },
1764
+ title: t.renameFolder,
1765
+ children: "\u270F\uFE0F"
1766
+ }
1767
+ ),
1768
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1769
+ "button",
1770
+ {
1771
+ onClick: () => setConfirmDeleteFolderId(folder.id),
1772
+ style: { background: "none", border: "none", cursor: "pointer", padding: "2px", fontSize: "12px", opacity: 0.5 },
1773
+ title: t.deleteFolder,
1774
+ children: "\u{1F5D1}\uFE0F"
1775
+ }
1776
+ )
1777
+ ] })
1778
+ ]
1779
+ }
1780
+ ),
1781
+ isExpanded && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { paddingLeft: "24px", marginTop: "2px" }, children: [
1782
+ folderDrawings.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "8px 12px", fontSize: "12px", color: "var(--text-secondary-color, #888)", fontStyle: "italic" }, children: t.noDrawingsYet }),
1783
+ folderDrawings.map(renderDrawingRow)
1784
+ ] })
1785
+ ] }, folder.id);
1786
+ };
1210
1787
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1211
1788
  "div",
1212
1789
  {
@@ -1262,181 +1839,65 @@ var DrawingsDialog = ({
1262
1839
  ]
1263
1840
  }
1264
1841
  ),
1842
+ drawings.length > 3 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "8px 20px", borderBottom: "1px solid var(--default-border-color, #e0e0e0)" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1843
+ "input",
1844
+ {
1845
+ type: "text",
1846
+ value: searchQuery,
1847
+ onChange: (e) => setSearchQuery(e.target.value),
1848
+ placeholder: "\u{1F50D}",
1849
+ style: {
1850
+ width: "100%",
1851
+ padding: "6px 12px",
1852
+ fontSize: "14px",
1853
+ border: "1px solid var(--default-border-color, #e0e0e0)",
1854
+ borderRadius: "6px",
1855
+ outline: "none",
1856
+ backgroundColor: "transparent",
1857
+ color: "inherit",
1858
+ boxSizing: "border-box"
1859
+ }
1860
+ }
1861
+ ) }),
1265
1862
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, overflow: "auto" }, children: [
1266
- drawings.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "8px 20px 0" }, children: drawings.map((drawing) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1267
- "div",
1268
- {
1269
- onClick: () => {
1270
- if (editingId || confirmDeleteId) return;
1271
- if (activeDrawing?.id !== drawing.id) handleSelect(drawing);
1272
- },
1273
- style: {
1274
- padding: "10px 12px",
1275
- display: "flex",
1276
- alignItems: "center",
1277
- gap: "12px",
1278
- borderRadius: "8px",
1279
- marginBottom: "4px",
1280
- cursor: editingId || confirmDeleteId ? "default" : "pointer",
1281
- backgroundColor: activeDrawing?.id === drawing.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent",
1282
- transition: "background-color 0.15s"
1283
- },
1284
- children: [
1285
- renderThumbnail && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
1286
- width: "64px",
1287
- height: "48px",
1288
- flexShrink: 0,
1289
- borderRadius: "4px",
1290
- overflow: "hidden",
1291
- border: "1px solid var(--default-border-color, #e0e0e0)",
1292
- backgroundColor: "#fff",
1293
- display: "flex",
1294
- alignItems: "center",
1295
- justifyContent: "center"
1296
- }, children: renderThumbnail(drawing) }),
1297
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, minWidth: 0 }, children: editingId === drawing.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "6px", alignItems: "center" }, children: [
1298
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1299
- "input",
1300
- {
1301
- type: "text",
1302
- value: editName,
1303
- onChange: (e) => setEditName(e.target.value),
1304
- onKeyDown: (e) => {
1305
- if (e.key === "Enter") handleSaveEdit();
1306
- if (e.key === "Escape") handleCancelEdit();
1307
- },
1308
- onClick: (e) => e.stopPropagation(),
1309
- autoFocus: true,
1310
- style: {
1311
- flex: 1,
1312
- padding: "4px 8px",
1313
- fontSize: "14px",
1314
- border: "1px solid var(--color-primary, #6c63ff)",
1315
- borderRadius: "4px",
1316
- outline: "none"
1317
- }
1318
- }
1319
- ),
1320
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1321
- "button",
1322
- {
1323
- onClick: (e) => {
1324
- e.stopPropagation();
1325
- handleSaveEdit();
1326
- },
1327
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1328
- children: t.save
1329
- }
1330
- ),
1331
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1332
- "button",
1333
- {
1334
- onClick: (e) => {
1335
- e.stopPropagation();
1336
- handleCancelEdit();
1337
- },
1338
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1339
- children: t.cancel
1340
- }
1341
- )
1342
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1343
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
1344
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1345
- "span",
1346
- {
1347
- onClick: (e) => {
1348
- e.stopPropagation();
1349
- handleStartEdit(drawing);
1350
- },
1351
- style: {
1352
- fontWeight: activeDrawing?.id === drawing.id ? 600 : 400,
1353
- fontSize: "14px",
1354
- overflow: "hidden",
1355
- textOverflow: "ellipsis",
1356
- whiteSpace: "nowrap",
1357
- cursor: "text"
1358
- },
1359
- title: t.rename,
1360
- children: [
1361
- activeDrawing?.id === drawing.id && "\u2713 ",
1362
- drawing.name
1363
- ]
1364
- }
1365
- ),
1366
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1367
- "button",
1368
- {
1369
- onClick: (e) => {
1370
- e.stopPropagation();
1371
- handleStartEdit(drawing);
1372
- },
1373
- style: { background: "none", border: "none", cursor: "pointer", padding: "0 2px", fontSize: "12px", opacity: 0.4, flexShrink: 0 },
1374
- title: t.rename,
1375
- children: "\u270F\uFE0F"
1376
- }
1377
- )
1378
- ] }),
1379
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { fontSize: "11px", color: "var(--text-secondary-color, #888)", marginTop: "1px" }, children: [
1380
- t.modified,
1381
- ": ",
1382
- formatDate(drawing.updatedAt)
1383
- ] })
1384
- ] }) }),
1385
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", gap: "2px" }, children: confirmDeleteId === drawing.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1386
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1387
- "button",
1388
- {
1389
- onClick: (e) => {
1390
- e.stopPropagation();
1391
- handleDelete(drawing.id);
1392
- },
1393
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "#dc3545", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1394
- children: t.delete
1395
- }
1396
- ),
1397
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1398
- "button",
1399
- {
1400
- onClick: (e) => {
1401
- e.stopPropagation();
1402
- setConfirmDeleteId(null);
1403
- },
1404
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1405
- children: t.cancel
1406
- }
1407
- )
1408
- ] }) : editingId !== drawing.id && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1409
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1410
- "button",
1411
- {
1412
- onClick: (e) => {
1413
- e.stopPropagation();
1414
- exportDrawingAsExcalidraw(drawing.id);
1415
- },
1416
- style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1417
- title: t.exportDrawing,
1418
- children: "\u{1F4BE}"
1419
- }
1420
- ),
1421
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1422
- "button",
1423
- {
1424
- onClick: (e) => {
1425
- e.stopPropagation();
1426
- setConfirmDeleteId(drawing.id);
1427
- },
1428
- style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1429
- title: t.delete,
1430
- disabled: drawings.length <= 1,
1431
- children: "\u{1F5D1}\uFE0F"
1432
- }
1433
- )
1434
- ] }) })
1435
- ]
1436
- },
1437
- drawing.id
1438
- )) }),
1439
- drawings.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { padding: "24px 20px", textAlign: "center", color: "var(--text-secondary-color, #666)" }, children: [
1863
+ isRefreshing && drawings.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "24px 20px", textAlign: "center", color: "var(--text-secondary-color, #666)" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { display: "inline-block", animation: "spin 1s linear infinite", fontSize: "24px" }, children: "\u23F3" }) }) : drawings.length > 0 || folders.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { padding: "8px 20px 0" }, children: [
1864
+ filteredFolders.map(renderFolderGroup),
1865
+ draggingDrawingId && folders.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1866
+ "div",
1867
+ {
1868
+ onDragOver: (e) => {
1869
+ e.preventDefault();
1870
+ e.dataTransfer.dropEffect = "move";
1871
+ setDropTargetFolderId("__root__");
1872
+ },
1873
+ onDragLeave: () => {
1874
+ if (dropTargetFolderId === "__root__") setDropTargetFolderId(null);
1875
+ },
1876
+ onDrop: (e) => {
1877
+ e.preventDefault();
1878
+ if (draggingDrawingId) {
1879
+ handleMoveToFolder(draggingDrawingId, null);
1880
+ setDraggingDrawingId(null);
1881
+ setDropTargetFolderId(null);
1882
+ }
1883
+ },
1884
+ style: {
1885
+ padding: "8px 12px",
1886
+ marginBottom: "4px",
1887
+ borderRadius: "8px",
1888
+ textAlign: "center",
1889
+ fontSize: "12px",
1890
+ color: "var(--text-secondary-color, #888)",
1891
+ backgroundColor: dropTargetFolderId === "__root__" ? "var(--color-primary-light, rgba(108, 99, 255, 0.2))" : "transparent",
1892
+ border: "2px dashed var(--default-border-color, #ccc)",
1893
+ borderColor: dropTargetFolderId === "__root__" ? "var(--color-primary, #6c63ff)" : "var(--default-border-color, #ccc)",
1894
+ transition: "background-color 0.15s, border-color 0.15s"
1895
+ },
1896
+ children: t.moveToRoot
1897
+ }
1898
+ ),
1899
+ rootDrawings.map(renderDrawingRow)
1900
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { padding: "24px 20px", textAlign: "center", color: "var(--text-secondary-color, #666)" }, children: [
1440
1901
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: t.noDrawingsYet }),
1441
1902
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: t.clickNewToStart })
1442
1903
  ] }),
@@ -1448,7 +1909,7 @@ var DrawingsDialog = ({
1448
1909
  icon: "\u{1F4C4}",
1449
1910
  label: t.createNewDrawing,
1450
1911
  description: t.createNewDrawingDesc,
1451
- onClick: handleCreate,
1912
+ onClick: () => handleCreate(),
1452
1913
  primary: true
1453
1914
  }
1454
1915
  ),
@@ -1460,6 +1921,61 @@ var DrawingsDialog = ({
1460
1921
  description: t.openFromFileDesc,
1461
1922
  onClick: importExcalidrawFile
1462
1923
  }
1924
+ ),
1925
+ creatingFolder ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "center", padding: "4px 0" }, children: [
1926
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1927
+ "input",
1928
+ {
1929
+ ref: newFolderInputRef,
1930
+ type: "text",
1931
+ value: newFolderName,
1932
+ onChange: (e) => setNewFolderName(e.target.value),
1933
+ onKeyDown: (e) => {
1934
+ if (e.key === "Enter") handleCreateFolder();
1935
+ if (e.key === "Escape") {
1936
+ setCreatingFolder(false);
1937
+ setNewFolderName("");
1938
+ }
1939
+ },
1940
+ placeholder: t.newFolderName,
1941
+ style: {
1942
+ flex: 1,
1943
+ padding: "8px 12px",
1944
+ fontSize: "14px",
1945
+ border: "1px solid var(--color-primary, #6c63ff)",
1946
+ borderRadius: "8px",
1947
+ outline: "none"
1948
+ }
1949
+ }
1950
+ ),
1951
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1952
+ "button",
1953
+ {
1954
+ onClick: handleCreateFolder,
1955
+ disabled: busyFolderId === "__creating__",
1956
+ style: { padding: "8px 16px", fontSize: "14px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "8px", cursor: "pointer", opacity: busyFolderId === "__creating__" ? 0.6 : 1 },
1957
+ children: busyFolderId === "__creating__" ? "\u23F3" : t.save
1958
+ }
1959
+ ),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1961
+ "button",
1962
+ {
1963
+ onClick: () => {
1964
+ setCreatingFolder(false);
1965
+ setNewFolderName("");
1966
+ },
1967
+ style: { padding: "8px 16px", fontSize: "14px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "8px", cursor: "pointer", color: "inherit" },
1968
+ children: t.cancel
1969
+ }
1970
+ )
1971
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1972
+ ActionButton,
1973
+ {
1974
+ icon: "\u{1F4C1}",
1975
+ label: t.createFolder,
1976
+ description: "",
1977
+ onClick: () => setCreatingFolder(true)
1978
+ }
1463
1979
  )
1464
1980
  ] }),
1465
1981
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: sectionHeaderStyle, children: t.sectionWorkspace }),
@@ -1706,16 +2222,22 @@ function WorkspacePlugin(props) {
1706
2222
  addDrawingToWorkspace,
1707
2223
  closeDB,
1708
2224
  createDrawing,
2225
+ createFolder,
1709
2226
  deleteDrawing,
2227
+ deleteFolder,
1710
2228
  duplicateDrawing,
1711
2229
  getAllDrawings,
2230
+ getAllFolders,
1712
2231
  getDB,
1713
2232
  getDrawing,
2233
+ getFolder,
1714
2234
  getOrCreateDefaultWorkspace,
1715
2235
  getTranslations,
1716
2236
  getWorkspace,
1717
2237
  isLanguageSupported,
2238
+ moveDrawingToFolder,
1718
2239
  removeDrawingFromWorkspace,
2240
+ renameFolder,
1719
2241
  setActiveDrawing,
1720
2242
  updateDrawing,
1721
2243
  updateWorkspace,