rita-workspace 0.5.0 → 0.5.1

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.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  // src/storage/db.ts
2
2
  import { openDB } from "idb";
3
3
  var DB_NAME = "rita-workspace";
4
- var DB_VERSION = 1;
4
+ var DB_VERSION = 2;
5
5
  var dbInstance = null;
6
6
  async function getDB() {
7
7
  if (dbInstance) return dbInstance;
8
8
  dbInstance = await openDB(DB_NAME, DB_VERSION, {
9
- upgrade(db) {
9
+ upgrade(db, oldVersion) {
10
10
  if (!db.objectStoreNames.contains("workspaces")) {
11
11
  const workspaceStore = db.createObjectStore("workspaces", { keyPath: "id" });
12
12
  workspaceStore.createIndex("by-updated", "updatedAt");
@@ -15,6 +15,10 @@ async function getDB() {
15
15
  const drawingStore = db.createObjectStore("drawings", { keyPath: "id" });
16
16
  drawingStore.createIndex("by-updated", "updatedAt");
17
17
  }
18
+ if (oldVersion < 2) {
19
+ const folderStore = db.createObjectStore("folders", { keyPath: "id" });
20
+ folderStore.createIndex("by-name", "name");
21
+ }
18
22
  }
19
23
  });
20
24
  return dbInstance;
@@ -28,12 +32,13 @@ async function closeDB() {
28
32
 
29
33
  // src/storage/drawingStore.ts
30
34
  import { nanoid } from "nanoid";
31
- async function createDrawing(name = "Untitled", elements = [], appState = {}) {
35
+ async function createDrawing(name = "Untitled", elements = [], appState = {}, folderId) {
32
36
  const db = await getDB();
33
37
  const now = Date.now();
34
38
  const drawing = {
35
39
  id: nanoid(),
36
40
  name,
41
+ folderId: folderId || null,
37
42
  elements,
38
43
  appState,
39
44
  files: {},
@@ -76,9 +81,62 @@ async function duplicateDrawing(id, newName) {
76
81
  return createDrawing(
77
82
  newName || `${existing.name} (copy)`,
78
83
  existing.elements,
79
- existing.appState
84
+ existing.appState,
85
+ existing.folderId
80
86
  );
81
87
  }
88
+ async function moveDrawingToFolder(drawingId, folderId) {
89
+ return updateDrawing(drawingId, { folderId });
90
+ }
91
+
92
+ // src/storage/folderStore.ts
93
+ import { nanoid as nanoid2 } from "nanoid";
94
+ async function createFolder(name) {
95
+ const db = await getDB();
96
+ const now = Date.now();
97
+ const folder = {
98
+ id: nanoid2(),
99
+ name,
100
+ createdAt: now,
101
+ updatedAt: now
102
+ };
103
+ await db.put("folders", folder);
104
+ return folder;
105
+ }
106
+ async function getFolder(id) {
107
+ const db = await getDB();
108
+ return db.get("folders", id);
109
+ }
110
+ async function getAllFolders() {
111
+ const db = await getDB();
112
+ return db.getAllFromIndex("folders", "by-name");
113
+ }
114
+ async function renameFolder(id, name) {
115
+ const db = await getDB();
116
+ const existing = await db.get("folders", id);
117
+ if (!existing) return void 0;
118
+ const updated = {
119
+ ...existing,
120
+ name,
121
+ updatedAt: Date.now()
122
+ };
123
+ await db.put("folders", updated);
124
+ return updated;
125
+ }
126
+ async function deleteFolder(id) {
127
+ const db = await getDB();
128
+ const allDrawings = await db.getAll("drawings");
129
+ const tx = db.transaction(["drawings", "folders"], "readwrite");
130
+ for (const drawing of allDrawings) {
131
+ if (drawing.folderId === id) {
132
+ drawing.folderId = null;
133
+ drawing.updatedAt = Date.now();
134
+ await tx.objectStore("drawings").put(drawing);
135
+ }
136
+ }
137
+ await tx.objectStore("folders").delete(id);
138
+ await tx.done;
139
+ }
82
140
 
83
141
  // src/storage/workspaceStore.ts
84
142
  var DEFAULT_WORKSPACE_ID = "default";
@@ -184,6 +242,14 @@ var sv = {
184
242
  importWorkspace: "Importera arbetsyta",
185
243
  exportDrawing: "Spara som .excalidraw",
186
244
  importDrawing: "\xD6ppna .excalidraw",
245
+ // Folders
246
+ createFolder: "Skapa mapp",
247
+ renameFolder: "Byt namn p\xE5 mapp",
248
+ deleteFolder: "Ta bort mapp",
249
+ deleteFolderConfirm: "Vill du ta bort denna mapp? Ritningarna flyttas till rotniv\xE5n.",
250
+ moveToFolder: "Flytta till mapp",
251
+ moveToRoot: "Ingen mapp",
252
+ newFolderName: "Ny mapp",
187
253
  // Shortcuts
188
254
  shortcutNewDrawing: "Ctrl+Alt+N"
189
255
  };
@@ -223,6 +289,14 @@ var en = {
223
289
  importWorkspace: "Import workspace",
224
290
  exportDrawing: "Save as .excalidraw",
225
291
  importDrawing: "Open .excalidraw",
292
+ // Folders
293
+ createFolder: "Create folder",
294
+ renameFolder: "Rename folder",
295
+ deleteFolder: "Delete folder",
296
+ deleteFolderConfirm: "Delete this folder? Drawings will be moved to root.",
297
+ moveToFolder: "Move to folder",
298
+ moveToRoot: "No folder",
299
+ newFolderName: "New folder",
226
300
  // Shortcuts
227
301
  shortcutNewDrawing: "Ctrl+Alt+N"
228
302
  };
@@ -305,6 +379,7 @@ function isDrawingOpenedEarlierInOtherTab(drawingId) {
305
379
  function WorkspaceProvider({ children, lang = "en" }) {
306
380
  const [workspace, setWorkspace] = useState(null);
307
381
  const [drawings, setDrawings] = useState([]);
382
+ const [folders, setFolders] = useState([]);
308
383
  const [activeDrawing, setActiveDrawing2] = useState(null);
309
384
  const [isLoading, setIsLoading] = useState(true);
310
385
  const [error, setError] = useState(null);
@@ -380,6 +455,8 @@ function WorkspaceProvider({ children, lang = "en" }) {
380
455
  const allDrawings = await getAllDrawings();
381
456
  const wsDrawings = allDrawings.filter((d) => ws.drawingIds.includes(d.id));
382
457
  setDrawings(wsDrawings);
458
+ const allFolders = await getAllFolders();
459
+ setFolders(allFolders);
383
460
  const lastDrawingId = sessionStorage.getItem("rita-workspace-tab-drawing");
384
461
  let active = null;
385
462
  if (lastDrawingId) {
@@ -412,14 +489,16 @@ function WorkspaceProvider({ children, lang = "en" }) {
412
489
  const allDrawings = await getAllDrawings();
413
490
  const wsDrawings = allDrawings.filter((d) => workspace.drawingIds.includes(d.id));
414
491
  setDrawings(wsDrawings);
492
+ const allFolders = await getAllFolders();
493
+ setFolders(allFolders);
415
494
  } catch (err) {
416
495
  }
417
496
  }, [workspace]);
418
- const createNewDrawing = useCallback(async (name) => {
497
+ const createNewDrawing = useCallback(async (name, folderId) => {
419
498
  if (!workspace) return null;
420
499
  try {
421
500
  const defaultName = `${t.newDrawing} ${drawings.length + 1}`;
422
- const drawing = await createDrawing(name || defaultName);
501
+ const drawing = await createDrawing(name || defaultName, [], {}, folderId);
423
502
  await addDrawingToWorkspace(workspace.id, drawing.id);
424
503
  setDrawings((prev) => [...prev, drawing]);
425
504
  setActiveDrawing2(drawing);
@@ -652,9 +731,52 @@ function WorkspaceProvider({ children, lang = "en" }) {
652
731
  setError(err instanceof Error ? err.message : "Failed to import drawing");
653
732
  }
654
733
  }, [workspace, t]);
734
+ const createFolder2 = useCallback(async (name) => {
735
+ try {
736
+ const folder = await createFolder(name);
737
+ setFolders((prev) => [...prev, folder]);
738
+ return folder;
739
+ } catch (err) {
740
+ setError(err instanceof Error ? err.message : "Failed to create folder");
741
+ return null;
742
+ }
743
+ }, []);
744
+ const renameFolder2 = useCallback(async (id, name) => {
745
+ try {
746
+ const updated = await renameFolder(id, name);
747
+ if (updated) {
748
+ setFolders((prev) => prev.map((f) => f.id === id ? updated : f));
749
+ }
750
+ } catch (err) {
751
+ setError(err instanceof Error ? err.message : "Failed to rename folder");
752
+ }
753
+ }, []);
754
+ const deleteFolder2 = useCallback(async (id) => {
755
+ try {
756
+ await deleteFolder(id);
757
+ setFolders((prev) => prev.filter((f) => f.id !== id));
758
+ setDrawings((prev) => prev.map((d) => d.folderId === id ? { ...d, folderId: null } : d));
759
+ } catch (err) {
760
+ setError(err instanceof Error ? err.message : "Failed to delete folder");
761
+ }
762
+ }, []);
763
+ const moveDrawingToFolder2 = useCallback(async (drawingId, folderId) => {
764
+ try {
765
+ const updated = await moveDrawingToFolder(drawingId, folderId);
766
+ if (updated) {
767
+ setDrawings((prev) => prev.map((d) => d.id === drawingId ? updated : d));
768
+ if (activeDrawing?.id === drawingId) {
769
+ setActiveDrawing2(updated);
770
+ }
771
+ }
772
+ } catch (err) {
773
+ setError(err instanceof Error ? err.message : "Failed to move drawing");
774
+ }
775
+ }, [activeDrawing]);
655
776
  const value = {
656
777
  workspace,
657
778
  drawings,
779
+ folders,
658
780
  activeDrawing,
659
781
  isLoading,
660
782
  error,
@@ -666,6 +788,10 @@ function WorkspaceProvider({ children, lang = "en" }) {
666
788
  renameDrawing,
667
789
  removeDrawing,
668
790
  duplicateCurrentDrawing,
791
+ createFolder: createFolder2,
792
+ renameFolder: renameFolder2,
793
+ deleteFolder: deleteFolder2,
794
+ moveDrawingToFolder: moveDrawingToFolder2,
669
795
  saveCurrentDrawing,
670
796
  saveDrawingById,
671
797
  refreshDrawings,
@@ -1019,11 +1145,16 @@ var DrawingsDialog = ({
1019
1145
  }) => {
1020
1146
  const {
1021
1147
  drawings,
1148
+ folders,
1022
1149
  activeDrawing,
1023
1150
  switchDrawing,
1024
1151
  createNewDrawing,
1025
1152
  renameDrawing,
1026
1153
  removeDrawing,
1154
+ createFolder: createFolder2,
1155
+ renameFolder: renameFolder2,
1156
+ deleteFolder: deleteFolder2,
1157
+ moveDrawingToFolder: moveDrawingToFolder2,
1027
1158
  exportWorkspace,
1028
1159
  importWorkspace,
1029
1160
  exportDrawingAsExcalidraw,
@@ -1037,21 +1168,39 @@ var DrawingsDialog = ({
1037
1168
  const [editingId, setEditingId] = useState4(null);
1038
1169
  const [editName, setEditName] = useState4("");
1039
1170
  const [confirmDeleteId, setConfirmDeleteId] = useState4(null);
1171
+ const [switchingId, setSwitchingId] = useState4(null);
1172
+ const [expandedFolders, setExpandedFolders] = useState4(/* @__PURE__ */ new Set());
1173
+ const [creatingFolder, setCreatingFolder] = useState4(false);
1174
+ const [newFolderName, setNewFolderName] = useState4("");
1175
+ const [editingFolderId, setEditingFolderId] = useState4(null);
1176
+ const [editFolderName, setEditFolderName] = useState4("");
1177
+ const [confirmDeleteFolderId, setConfirmDeleteFolderId] = useState4(null);
1178
+ const [movingDrawingId, setMovingDrawingId] = useState4(null);
1179
+ const [searchQuery, setSearchQuery] = useState4("");
1040
1180
  const [position, setPosition] = useState4(null);
1041
1181
  const dragRef = useRef3(null);
1042
1182
  const dialogRef = useRef3(null);
1183
+ const newFolderInputRef = useRef3(null);
1043
1184
  const prevOpenRef = useRef3(false);
1044
1185
  useEffect3(() => {
1045
1186
  if (open) {
1046
1187
  refreshDrawings();
1047
1188
  if (!prevOpenRef.current) {
1048
1189
  setPosition(null);
1190
+ setSearchQuery("");
1191
+ setExpandedFolders(new Set(folders.map((f) => f.id)));
1049
1192
  }
1050
1193
  }
1051
1194
  prevOpenRef.current = open;
1052
- }, [open, refreshDrawings]);
1195
+ }, [open, refreshDrawings, folders]);
1196
+ useEffect3(() => {
1197
+ if (creatingFolder && newFolderInputRef.current) {
1198
+ newFolderInputRef.current.focus();
1199
+ }
1200
+ }, [creatingFolder]);
1053
1201
  const handleMouseDown = useCallback2((e) => {
1054
1202
  if (e.target.closest("button")) return;
1203
+ if (e.target.closest("input")) return;
1055
1204
  if (!dialogRef.current) return;
1056
1205
  const rect = dialogRef.current.getBoundingClientRect();
1057
1206
  const currentX = position?.x ?? rect.left;
@@ -1072,11 +1221,13 @@ var DrawingsDialog = ({
1072
1221
  document.addEventListener("mouseup", handleMouseUp);
1073
1222
  }, [position]);
1074
1223
  const handleSelect = useCallback2(async (drawing) => {
1224
+ setSwitchingId(drawing.id);
1075
1225
  await switchDrawing(drawing.id);
1076
1226
  onDrawingSelect?.(drawing);
1227
+ setSwitchingId(null);
1077
1228
  }, [switchDrawing, onDrawingSelect]);
1078
- const handleCreate = useCallback2(async () => {
1079
- const newDrawing = await createNewDrawing();
1229
+ const handleCreate = useCallback2(async (folderId) => {
1230
+ const newDrawing = await createNewDrawing(void 0, folderId);
1080
1231
  if (newDrawing) onDrawingSelect?.(newDrawing);
1081
1232
  }, [createNewDrawing, onDrawingSelect]);
1082
1233
  const handleStartEdit = useCallback2((drawing) => {
@@ -1098,6 +1249,42 @@ var DrawingsDialog = ({
1098
1249
  await removeDrawing(id);
1099
1250
  setConfirmDeleteId(null);
1100
1251
  }, [removeDrawing]);
1252
+ const handleCreateFolder = useCallback2(async () => {
1253
+ if (newFolderName.trim()) {
1254
+ const folder = await createFolder2(newFolderName.trim());
1255
+ if (folder) {
1256
+ setExpandedFolders((prev) => /* @__PURE__ */ new Set([...prev, folder.id]));
1257
+ }
1258
+ }
1259
+ setCreatingFolder(false);
1260
+ setNewFolderName("");
1261
+ }, [newFolderName, createFolder2]);
1262
+ const handleSaveFolderEdit = useCallback2(async () => {
1263
+ if (editingFolderId && editFolderName.trim()) {
1264
+ await renameFolder2(editingFolderId, editFolderName.trim());
1265
+ }
1266
+ setEditingFolderId(null);
1267
+ setEditFolderName("");
1268
+ }, [editingFolderId, editFolderName, renameFolder2]);
1269
+ const handleDeleteFolder = useCallback2(async (id) => {
1270
+ await deleteFolder2(id);
1271
+ setConfirmDeleteFolderId(null);
1272
+ }, [deleteFolder2]);
1273
+ const handleMoveToFolder = useCallback2(async (drawingId, folderId) => {
1274
+ await moveDrawingToFolder2(drawingId, folderId);
1275
+ setMovingDrawingId(null);
1276
+ }, [moveDrawingToFolder2]);
1277
+ const toggleFolder = useCallback2((folderId) => {
1278
+ setExpandedFolders((prev) => {
1279
+ const next = new Set(prev);
1280
+ if (next.has(folderId)) {
1281
+ next.delete(folderId);
1282
+ } else {
1283
+ next.add(folderId);
1284
+ }
1285
+ return next;
1286
+ });
1287
+ }, []);
1101
1288
  const getLocale = () => {
1102
1289
  if (!effectiveLang) return "en-US";
1103
1290
  const baseLang = effectiveLang.split("-")[0].toLowerCase();
@@ -1112,6 +1299,14 @@ var DrawingsDialog = ({
1112
1299
  });
1113
1300
  };
1114
1301
  if (!open) return null;
1302
+ const query = searchQuery.toLowerCase().trim();
1303
+ const filteredDrawings = query ? drawings.filter((d) => d.name.toLowerCase().includes(query)) : drawings;
1304
+ const rootDrawings = filteredDrawings.filter((d) => !d.folderId);
1305
+ const drawingsByFolder = {};
1306
+ for (const folder of folders) {
1307
+ drawingsByFolder[folder.id] = filteredDrawings.filter((d) => d.folderId === folder.id);
1308
+ }
1309
+ const filteredFolders = query ? folders.filter((f) => f.name.toLowerCase().includes(query) || (drawingsByFolder[f.id] || []).length > 0) : folders;
1115
1310
  const dialogStyle = position ? {
1116
1311
  position: "fixed",
1117
1312
  left: position.x,
@@ -1145,6 +1340,324 @@ var DrawingsDialog = ({
1145
1340
  color: "var(--text-secondary-color, #888)",
1146
1341
  padding: "16px 20px 8px"
1147
1342
  };
1343
+ const renderDrawingRow = (drawing) => /* @__PURE__ */ jsxs4(
1344
+ "div",
1345
+ {
1346
+ onClick: () => {
1347
+ if (editingId || confirmDeleteId || switchingId || movingDrawingId) return;
1348
+ if (activeDrawing?.id !== drawing.id) handleSelect(drawing);
1349
+ },
1350
+ style: {
1351
+ padding: "10px 12px",
1352
+ display: "flex",
1353
+ alignItems: "center",
1354
+ gap: "12px",
1355
+ borderRadius: "8px",
1356
+ marginBottom: "4px",
1357
+ cursor: editingId || confirmDeleteId || switchingId ? "default" : "pointer",
1358
+ backgroundColor: activeDrawing?.id === drawing.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent",
1359
+ transition: "background-color 0.15s",
1360
+ opacity: switchingId && switchingId !== drawing.id ? 0.5 : 1
1361
+ },
1362
+ children: [
1363
+ renderThumbnail && /* @__PURE__ */ jsx6("div", { style: {
1364
+ width: "64px",
1365
+ height: "48px",
1366
+ flexShrink: 0,
1367
+ borderRadius: "4px",
1368
+ overflow: "hidden",
1369
+ border: "1px solid var(--default-border-color, #e0e0e0)",
1370
+ backgroundColor: "#fff",
1371
+ display: "flex",
1372
+ alignItems: "center",
1373
+ justifyContent: "center"
1374
+ }, children: renderThumbnail(drawing) }),
1375
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1, minWidth: 0 }, children: editingId === drawing.id ? /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "6px", alignItems: "center" }, children: [
1376
+ /* @__PURE__ */ jsx6(
1377
+ "input",
1378
+ {
1379
+ type: "text",
1380
+ value: editName,
1381
+ onChange: (e) => setEditName(e.target.value),
1382
+ onKeyDown: (e) => {
1383
+ if (e.key === "Enter") handleSaveEdit();
1384
+ if (e.key === "Escape") handleCancelEdit();
1385
+ },
1386
+ onClick: (e) => e.stopPropagation(),
1387
+ autoFocus: true,
1388
+ style: {
1389
+ flex: 1,
1390
+ padding: "4px 8px",
1391
+ fontSize: "14px",
1392
+ border: "1px solid var(--color-primary, #6c63ff)",
1393
+ borderRadius: "4px",
1394
+ outline: "none"
1395
+ }
1396
+ }
1397
+ ),
1398
+ /* @__PURE__ */ jsx6(
1399
+ "button",
1400
+ {
1401
+ onClick: (e) => {
1402
+ e.stopPropagation();
1403
+ handleSaveEdit();
1404
+ },
1405
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1406
+ children: t.save
1407
+ }
1408
+ ),
1409
+ /* @__PURE__ */ jsx6(
1410
+ "button",
1411
+ {
1412
+ onClick: (e) => {
1413
+ e.stopPropagation();
1414
+ handleCancelEdit();
1415
+ },
1416
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1417
+ children: t.cancel
1418
+ }
1419
+ )
1420
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
1421
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
1422
+ /* @__PURE__ */ jsxs4(
1423
+ "span",
1424
+ {
1425
+ onClick: (e) => {
1426
+ e.stopPropagation();
1427
+ handleStartEdit(drawing);
1428
+ },
1429
+ style: {
1430
+ fontWeight: activeDrawing?.id === drawing.id ? 600 : 400,
1431
+ fontSize: "14px",
1432
+ overflow: "hidden",
1433
+ textOverflow: "ellipsis",
1434
+ whiteSpace: "nowrap",
1435
+ cursor: "text"
1436
+ },
1437
+ title: t.rename,
1438
+ children: [
1439
+ switchingId === drawing.id ? /* @__PURE__ */ jsx6("span", { style: { display: "inline-block", animation: "spin 1s linear infinite", marginRight: "4px" }, children: "\u23F3" }) : activeDrawing?.id === drawing.id ? "\u2713 " : "",
1440
+ drawing.name
1441
+ ]
1442
+ }
1443
+ ),
1444
+ /* @__PURE__ */ jsx6(
1445
+ "button",
1446
+ {
1447
+ onClick: (e) => {
1448
+ e.stopPropagation();
1449
+ handleStartEdit(drawing);
1450
+ },
1451
+ style: { background: "none", border: "none", cursor: "pointer", padding: "0 2px", fontSize: "12px", opacity: 0.4, flexShrink: 0 },
1452
+ title: t.rename,
1453
+ children: "\u270F\uFE0F"
1454
+ }
1455
+ )
1456
+ ] }),
1457
+ /* @__PURE__ */ jsxs4("div", { style: { fontSize: "11px", color: "var(--text-secondary-color, #888)", marginTop: "1px" }, children: [
1458
+ t.modified,
1459
+ ": ",
1460
+ formatDate(drawing.updatedAt)
1461
+ ] })
1462
+ ] }) }),
1463
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: "2px", alignItems: "center" }, children: confirmDeleteId === drawing.id ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1464
+ /* @__PURE__ */ jsx6(
1465
+ "button",
1466
+ {
1467
+ onClick: (e) => {
1468
+ e.stopPropagation();
1469
+ handleDelete(drawing.id);
1470
+ },
1471
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "#dc3545", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1472
+ children: t.delete
1473
+ }
1474
+ ),
1475
+ /* @__PURE__ */ jsx6(
1476
+ "button",
1477
+ {
1478
+ onClick: (e) => {
1479
+ e.stopPropagation();
1480
+ setConfirmDeleteId(null);
1481
+ },
1482
+ style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1483
+ children: t.cancel
1484
+ }
1485
+ )
1486
+ ] }) : movingDrawingId === drawing.id ? /* @__PURE__ */ jsxs4("div", { onClick: (e) => e.stopPropagation(), style: { display: "flex", flexDirection: "column", gap: "2px", fontSize: "12px" }, children: [
1487
+ /* @__PURE__ */ jsx6(
1488
+ "button",
1489
+ {
1490
+ onClick: () => handleMoveToFolder(drawing.id, null),
1491
+ 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" },
1492
+ children: t.moveToRoot
1493
+ }
1494
+ ),
1495
+ folders.map((folder) => /* @__PURE__ */ jsxs4(
1496
+ "button",
1497
+ {
1498
+ onClick: () => handleMoveToFolder(drawing.id, folder.id),
1499
+ 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" },
1500
+ children: [
1501
+ "\u{1F4C1} ",
1502
+ folder.name
1503
+ ]
1504
+ },
1505
+ folder.id
1506
+ )),
1507
+ /* @__PURE__ */ jsx6(
1508
+ "button",
1509
+ {
1510
+ onClick: () => setMovingDrawingId(null),
1511
+ style: { padding: "3px 8px", border: "none", cursor: "pointer", backgroundColor: "transparent", color: "var(--text-secondary-color, #888)", textAlign: "left" },
1512
+ children: t.cancel
1513
+ }
1514
+ )
1515
+ ] }) : editingId !== drawing.id && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1516
+ folders.length > 0 && /* @__PURE__ */ jsx6(
1517
+ "button",
1518
+ {
1519
+ onClick: (e) => {
1520
+ e.stopPropagation();
1521
+ setMovingDrawingId(drawing.id);
1522
+ },
1523
+ style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1524
+ title: t.moveToFolder,
1525
+ children: "\u{1F4C1}"
1526
+ }
1527
+ ),
1528
+ /* @__PURE__ */ jsx6(
1529
+ "button",
1530
+ {
1531
+ onClick: (e) => {
1532
+ e.stopPropagation();
1533
+ exportDrawingAsExcalidraw(drawing.id);
1534
+ },
1535
+ style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1536
+ title: t.exportDrawing,
1537
+ children: "\u{1F4BE}"
1538
+ }
1539
+ ),
1540
+ /* @__PURE__ */ jsx6(
1541
+ "button",
1542
+ {
1543
+ onClick: (e) => {
1544
+ e.stopPropagation();
1545
+ setConfirmDeleteId(drawing.id);
1546
+ },
1547
+ style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1548
+ title: t.delete,
1549
+ disabled: drawings.length <= 1,
1550
+ children: "\u{1F5D1}\uFE0F"
1551
+ }
1552
+ )
1553
+ ] }) })
1554
+ ]
1555
+ },
1556
+ drawing.id
1557
+ );
1558
+ const renderFolderGroup = (folder) => {
1559
+ const folderDrawings = drawingsByFolder[folder.id] || [];
1560
+ const isExpanded = expandedFolders.has(folder.id);
1561
+ return /* @__PURE__ */ jsxs4("div", { style: { marginBottom: "4px" }, children: [
1562
+ /* @__PURE__ */ jsxs4(
1563
+ "div",
1564
+ {
1565
+ onClick: () => toggleFolder(folder.id),
1566
+ style: {
1567
+ padding: "8px 12px",
1568
+ display: "flex",
1569
+ alignItems: "center",
1570
+ gap: "8px",
1571
+ borderRadius: "8px",
1572
+ cursor: "pointer",
1573
+ backgroundColor: "var(--color-surface-mid, rgba(0, 0, 0, 0.03))"
1574
+ },
1575
+ children: [
1576
+ /* @__PURE__ */ jsx6("span", { style: { fontSize: "12px", width: "16px", textAlign: "center", flexShrink: 0 }, children: isExpanded ? "\u25BC" : "\u25B6" }),
1577
+ /* @__PURE__ */ jsx6("span", { style: { fontSize: "16px", flexShrink: 0 }, children: "\u{1F4C1}" }),
1578
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1, minWidth: 0 }, children: editingFolderId === folder.id ? /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "6px", alignItems: "center" }, onClick: (e) => e.stopPropagation(), children: [
1579
+ /* @__PURE__ */ jsx6(
1580
+ "input",
1581
+ {
1582
+ type: "text",
1583
+ value: editFolderName,
1584
+ onChange: (e) => setEditFolderName(e.target.value),
1585
+ onKeyDown: (e) => {
1586
+ if (e.key === "Enter") handleSaveFolderEdit();
1587
+ if (e.key === "Escape") {
1588
+ setEditingFolderId(null);
1589
+ setEditFolderName("");
1590
+ }
1591
+ },
1592
+ autoFocus: true,
1593
+ style: { flex: 1, padding: "2px 6px", fontSize: "14px", border: "1px solid var(--color-primary, #6c63ff)", borderRadius: "4px", outline: "none" }
1594
+ }
1595
+ ),
1596
+ /* @__PURE__ */ jsx6(
1597
+ "button",
1598
+ {
1599
+ onClick: handleSaveFolderEdit,
1600
+ style: { padding: "2px 8px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1601
+ children: t.save
1602
+ }
1603
+ )
1604
+ ] }) : /* @__PURE__ */ jsxs4("span", { style: { fontWeight: 600, fontSize: "14px" }, children: [
1605
+ folder.name,
1606
+ /* @__PURE__ */ jsxs4("span", { style: { fontWeight: 400, fontSize: "12px", color: "var(--text-secondary-color, #888)", marginLeft: "6px" }, children: [
1607
+ "(",
1608
+ folderDrawings.length,
1609
+ ")"
1610
+ ] })
1611
+ ] }) }),
1612
+ confirmDeleteFolderId === folder.id ? /* @__PURE__ */ jsxs4("div", { onClick: (e) => e.stopPropagation(), style: { display: "flex", gap: "4px" }, children: [
1613
+ /* @__PURE__ */ jsx6(
1614
+ "button",
1615
+ {
1616
+ onClick: () => handleDeleteFolder(folder.id),
1617
+ style: { padding: "2px 8px", fontSize: "12px", backgroundColor: "#dc3545", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1618
+ children: t.delete
1619
+ }
1620
+ ),
1621
+ /* @__PURE__ */ jsx6(
1622
+ "button",
1623
+ {
1624
+ onClick: () => setConfirmDeleteFolderId(null),
1625
+ style: { padding: "2px 8px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1626
+ children: t.cancel
1627
+ }
1628
+ )
1629
+ ] }) : editingFolderId !== folder.id && /* @__PURE__ */ jsxs4("div", { onClick: (e) => e.stopPropagation(), style: { display: "flex", gap: "2px" }, children: [
1630
+ /* @__PURE__ */ jsx6(
1631
+ "button",
1632
+ {
1633
+ onClick: () => {
1634
+ setEditingFolderId(folder.id);
1635
+ setEditFolderName(folder.name);
1636
+ },
1637
+ style: { background: "none", border: "none", cursor: "pointer", padding: "2px", fontSize: "12px", opacity: 0.5 },
1638
+ title: t.renameFolder,
1639
+ children: "\u270F\uFE0F"
1640
+ }
1641
+ ),
1642
+ /* @__PURE__ */ jsx6(
1643
+ "button",
1644
+ {
1645
+ onClick: () => setConfirmDeleteFolderId(folder.id),
1646
+ style: { background: "none", border: "none", cursor: "pointer", padding: "2px", fontSize: "12px", opacity: 0.5 },
1647
+ title: t.deleteFolder,
1648
+ children: "\u{1F5D1}\uFE0F"
1649
+ }
1650
+ )
1651
+ ] })
1652
+ ]
1653
+ }
1654
+ ),
1655
+ isExpanded && /* @__PURE__ */ jsxs4("div", { style: { paddingLeft: "24px", marginTop: "2px" }, children: [
1656
+ folderDrawings.length === 0 && /* @__PURE__ */ jsx6("div", { style: { padding: "8px 12px", fontSize: "12px", color: "var(--text-secondary-color, #888)", fontStyle: "italic" }, children: t.noDrawingsYet }),
1657
+ folderDrawings.map(renderDrawingRow)
1658
+ ] })
1659
+ ] }, folder.id);
1660
+ };
1148
1661
  return /* @__PURE__ */ jsx6(
1149
1662
  "div",
1150
1663
  {
@@ -1200,181 +1713,32 @@ var DrawingsDialog = ({
1200
1713
  ]
1201
1714
  }
1202
1715
  ),
1716
+ drawings.length > 3 && /* @__PURE__ */ jsx6("div", { style: { padding: "8px 20px", borderBottom: "1px solid var(--default-border-color, #e0e0e0)" }, children: /* @__PURE__ */ jsx6(
1717
+ "input",
1718
+ {
1719
+ type: "text",
1720
+ value: searchQuery,
1721
+ onChange: (e) => setSearchQuery(e.target.value),
1722
+ placeholder: "\u{1F50D}",
1723
+ style: {
1724
+ width: "100%",
1725
+ padding: "6px 12px",
1726
+ fontSize: "14px",
1727
+ border: "1px solid var(--default-border-color, #e0e0e0)",
1728
+ borderRadius: "6px",
1729
+ outline: "none",
1730
+ backgroundColor: "transparent",
1731
+ color: "inherit",
1732
+ boxSizing: "border-box"
1733
+ }
1734
+ }
1735
+ ) }),
1203
1736
  /* @__PURE__ */ jsxs4("div", { style: { flex: 1, overflow: "auto" }, children: [
1204
- drawings.length > 0 && /* @__PURE__ */ jsx6("div", { style: { padding: "8px 20px 0" }, children: drawings.map((drawing) => /* @__PURE__ */ jsxs4(
1205
- "div",
1206
- {
1207
- onClick: () => {
1208
- if (editingId || confirmDeleteId) return;
1209
- if (activeDrawing?.id !== drawing.id) handleSelect(drawing);
1210
- },
1211
- style: {
1212
- padding: "10px 12px",
1213
- display: "flex",
1214
- alignItems: "center",
1215
- gap: "12px",
1216
- borderRadius: "8px",
1217
- marginBottom: "4px",
1218
- cursor: editingId || confirmDeleteId ? "default" : "pointer",
1219
- backgroundColor: activeDrawing?.id === drawing.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent",
1220
- transition: "background-color 0.15s"
1221
- },
1222
- children: [
1223
- renderThumbnail && /* @__PURE__ */ jsx6("div", { style: {
1224
- width: "64px",
1225
- height: "48px",
1226
- flexShrink: 0,
1227
- borderRadius: "4px",
1228
- overflow: "hidden",
1229
- border: "1px solid var(--default-border-color, #e0e0e0)",
1230
- backgroundColor: "#fff",
1231
- display: "flex",
1232
- alignItems: "center",
1233
- justifyContent: "center"
1234
- }, children: renderThumbnail(drawing) }),
1235
- /* @__PURE__ */ jsx6("div", { style: { flex: 1, minWidth: 0 }, children: editingId === drawing.id ? /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "6px", alignItems: "center" }, children: [
1236
- /* @__PURE__ */ jsx6(
1237
- "input",
1238
- {
1239
- type: "text",
1240
- value: editName,
1241
- onChange: (e) => setEditName(e.target.value),
1242
- onKeyDown: (e) => {
1243
- if (e.key === "Enter") handleSaveEdit();
1244
- if (e.key === "Escape") handleCancelEdit();
1245
- },
1246
- onClick: (e) => e.stopPropagation(),
1247
- autoFocus: true,
1248
- style: {
1249
- flex: 1,
1250
- padding: "4px 8px",
1251
- fontSize: "14px",
1252
- border: "1px solid var(--color-primary, #6c63ff)",
1253
- borderRadius: "4px",
1254
- outline: "none"
1255
- }
1256
- }
1257
- ),
1258
- /* @__PURE__ */ jsx6(
1259
- "button",
1260
- {
1261
- onClick: (e) => {
1262
- e.stopPropagation();
1263
- handleSaveEdit();
1264
- },
1265
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1266
- children: t.save
1267
- }
1268
- ),
1269
- /* @__PURE__ */ jsx6(
1270
- "button",
1271
- {
1272
- onClick: (e) => {
1273
- e.stopPropagation();
1274
- handleCancelEdit();
1275
- },
1276
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1277
- children: t.cancel
1278
- }
1279
- )
1280
- ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
1281
- /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
1282
- /* @__PURE__ */ jsxs4(
1283
- "span",
1284
- {
1285
- onClick: (e) => {
1286
- e.stopPropagation();
1287
- handleStartEdit(drawing);
1288
- },
1289
- style: {
1290
- fontWeight: activeDrawing?.id === drawing.id ? 600 : 400,
1291
- fontSize: "14px",
1292
- overflow: "hidden",
1293
- textOverflow: "ellipsis",
1294
- whiteSpace: "nowrap",
1295
- cursor: "text"
1296
- },
1297
- title: t.rename,
1298
- children: [
1299
- activeDrawing?.id === drawing.id && "\u2713 ",
1300
- drawing.name
1301
- ]
1302
- }
1303
- ),
1304
- /* @__PURE__ */ jsx6(
1305
- "button",
1306
- {
1307
- onClick: (e) => {
1308
- e.stopPropagation();
1309
- handleStartEdit(drawing);
1310
- },
1311
- style: { background: "none", border: "none", cursor: "pointer", padding: "0 2px", fontSize: "12px", opacity: 0.4, flexShrink: 0 },
1312
- title: t.rename,
1313
- children: "\u270F\uFE0F"
1314
- }
1315
- )
1316
- ] }),
1317
- /* @__PURE__ */ jsxs4("div", { style: { fontSize: "11px", color: "var(--text-secondary-color, #888)", marginTop: "1px" }, children: [
1318
- t.modified,
1319
- ": ",
1320
- formatDate(drawing.updatedAt)
1321
- ] })
1322
- ] }) }),
1323
- /* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: "2px" }, children: confirmDeleteId === drawing.id ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1324
- /* @__PURE__ */ jsx6(
1325
- "button",
1326
- {
1327
- onClick: (e) => {
1328
- e.stopPropagation();
1329
- handleDelete(drawing.id);
1330
- },
1331
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "#dc3545", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer" },
1332
- children: t.delete
1333
- }
1334
- ),
1335
- /* @__PURE__ */ jsx6(
1336
- "button",
1337
- {
1338
- onClick: (e) => {
1339
- e.stopPropagation();
1340
- setConfirmDeleteId(null);
1341
- },
1342
- style: { padding: "4px 10px", fontSize: "12px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "4px", cursor: "pointer", color: "inherit" },
1343
- children: t.cancel
1344
- }
1345
- )
1346
- ] }) : editingId !== drawing.id && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1347
- /* @__PURE__ */ jsx6(
1348
- "button",
1349
- {
1350
- onClick: (e) => {
1351
- e.stopPropagation();
1352
- exportDrawingAsExcalidraw(drawing.id);
1353
- },
1354
- style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1355
- title: t.exportDrawing,
1356
- children: "\u{1F4BE}"
1357
- }
1358
- ),
1359
- /* @__PURE__ */ jsx6(
1360
- "button",
1361
- {
1362
- onClick: (e) => {
1363
- e.stopPropagation();
1364
- setConfirmDeleteId(drawing.id);
1365
- },
1366
- style: { background: "none", border: "none", cursor: "pointer", padding: "4px", fontSize: "14px", opacity: 0.5 },
1367
- title: t.delete,
1368
- disabled: drawings.length <= 1,
1369
- children: "\u{1F5D1}\uFE0F"
1370
- }
1371
- )
1372
- ] }) })
1373
- ]
1374
- },
1375
- drawing.id
1376
- )) }),
1377
- drawings.length === 0 && /* @__PURE__ */ jsxs4("div", { style: { padding: "24px 20px", textAlign: "center", color: "var(--text-secondary-color, #666)" }, children: [
1737
+ (drawings.length > 0 || folders.length > 0) && /* @__PURE__ */ jsxs4("div", { style: { padding: "8px 20px 0" }, children: [
1738
+ filteredFolders.map(renderFolderGroup),
1739
+ rootDrawings.map(renderDrawingRow)
1740
+ ] }),
1741
+ drawings.length === 0 && folders.length === 0 && /* @__PURE__ */ jsxs4("div", { style: { padding: "24px 20px", textAlign: "center", color: "var(--text-secondary-color, #666)" }, children: [
1378
1742
  /* @__PURE__ */ jsx6("p", { children: t.noDrawingsYet }),
1379
1743
  /* @__PURE__ */ jsx6("p", { children: t.clickNewToStart })
1380
1744
  ] }),
@@ -1386,7 +1750,7 @@ var DrawingsDialog = ({
1386
1750
  icon: "\u{1F4C4}",
1387
1751
  label: t.createNewDrawing,
1388
1752
  description: t.createNewDrawingDesc,
1389
- onClick: handleCreate,
1753
+ onClick: () => handleCreate(),
1390
1754
  primary: true
1391
1755
  }
1392
1756
  ),
@@ -1398,6 +1762,60 @@ var DrawingsDialog = ({
1398
1762
  description: t.openFromFileDesc,
1399
1763
  onClick: importExcalidrawFile
1400
1764
  }
1765
+ ),
1766
+ creatingFolder ? /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "8px", alignItems: "center", padding: "4px 0" }, children: [
1767
+ /* @__PURE__ */ jsx6(
1768
+ "input",
1769
+ {
1770
+ ref: newFolderInputRef,
1771
+ type: "text",
1772
+ value: newFolderName,
1773
+ onChange: (e) => setNewFolderName(e.target.value),
1774
+ onKeyDown: (e) => {
1775
+ if (e.key === "Enter") handleCreateFolder();
1776
+ if (e.key === "Escape") {
1777
+ setCreatingFolder(false);
1778
+ setNewFolderName("");
1779
+ }
1780
+ },
1781
+ placeholder: t.newFolderName,
1782
+ style: {
1783
+ flex: 1,
1784
+ padding: "8px 12px",
1785
+ fontSize: "14px",
1786
+ border: "1px solid var(--color-primary, #6c63ff)",
1787
+ borderRadius: "8px",
1788
+ outline: "none"
1789
+ }
1790
+ }
1791
+ ),
1792
+ /* @__PURE__ */ jsx6(
1793
+ "button",
1794
+ {
1795
+ onClick: handleCreateFolder,
1796
+ style: { padding: "8px 16px", fontSize: "14px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "8px", cursor: "pointer" },
1797
+ children: t.save
1798
+ }
1799
+ ),
1800
+ /* @__PURE__ */ jsx6(
1801
+ "button",
1802
+ {
1803
+ onClick: () => {
1804
+ setCreatingFolder(false);
1805
+ setNewFolderName("");
1806
+ },
1807
+ style: { padding: "8px 16px", fontSize: "14px", backgroundColor: "transparent", border: "1px solid var(--default-border-color, #ccc)", borderRadius: "8px", cursor: "pointer", color: "inherit" },
1808
+ children: t.cancel
1809
+ }
1810
+ )
1811
+ ] }) : /* @__PURE__ */ jsx6(
1812
+ ActionButton,
1813
+ {
1814
+ icon: "\u{1F4C1}",
1815
+ label: t.createFolder,
1816
+ description: "",
1817
+ onClick: () => setCreatingFolder(true)
1818
+ }
1401
1819
  )
1402
1820
  ] }),
1403
1821
  /* @__PURE__ */ jsx6("div", { style: sectionHeaderStyle, children: t.sectionWorkspace }),
@@ -1643,16 +2061,22 @@ export {
1643
2061
  addDrawingToWorkspace,
1644
2062
  closeDB,
1645
2063
  createDrawing,
2064
+ createFolder,
1646
2065
  deleteDrawing,
2066
+ deleteFolder,
1647
2067
  duplicateDrawing,
1648
2068
  getAllDrawings,
2069
+ getAllFolders,
1649
2070
  getDB,
1650
2071
  getDrawing,
2072
+ getFolder,
1651
2073
  getOrCreateDefaultWorkspace,
1652
2074
  getTranslations,
1653
2075
  getWorkspace,
1654
2076
  isLanguageSupported,
2077
+ moveDrawingToFolder,
1655
2078
  removeDrawingFromWorkspace,
2079
+ renameFolder,
1656
2080
  setActiveDrawing,
1657
2081
  updateDrawing,
1658
2082
  updateWorkspace,