rita-workspace 0.5.27 → 0.5.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +127 -19
  2. package/dist/index.mjs +127 -19
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -432,19 +432,34 @@ function useWorkspaceLang() {
432
432
  return { lang: context.lang, t: context.t };
433
433
  }
434
434
  var TAB_ID_KEY = "rita-workspace-tab-id";
435
+ var TAB_ENTRY_KEY = "rita-workspace-tab-entry";
436
+ function generateFreshTabId() {
437
+ return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
438
+ }
435
439
  var TAB_ID = (() => {
436
440
  try {
437
441
  const existing = sessionStorage.getItem(TAB_ID_KEY);
438
442
  if (existing) return existing;
439
443
  } catch {
440
444
  }
441
- const fresh = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
445
+ const fresh = generateFreshTabId();
442
446
  try {
443
447
  sessionStorage.setItem(TAB_ID_KEY, fresh);
444
448
  } catch {
445
449
  }
446
450
  return fresh;
447
451
  })();
452
+ function regenerateTabId() {
453
+ TAB_ID = generateFreshTabId();
454
+ try {
455
+ sessionStorage.setItem(TAB_ID_KEY, TAB_ID);
456
+ } catch {
457
+ }
458
+ try {
459
+ sessionStorage.removeItem(TAB_ENTRY_KEY);
460
+ } catch {
461
+ }
462
+ }
448
463
  var TABS_KEY = "rita-workspace-tabs";
449
464
  var TAB_CHANNEL = "rita-workspace-tabs";
450
465
  function broadcastWorkspaceChange() {
@@ -477,7 +492,6 @@ function getTabsMap() {
477
492
  return {};
478
493
  }
479
494
  }
480
- var TAB_ENTRY_KEY = "rita-workspace-tab-entry";
481
495
  function setTabDrawing(drawingId) {
482
496
  const tabs = getTabsMap();
483
497
  if (drawingId) {
@@ -516,6 +530,32 @@ function isDrawingOpenedEarlierInOtherTab(drawingId) {
516
530
  ([tabId, entry]) => tabId !== TAB_ID && entry.drawingId === drawingId && entry.openedAt <= myOpenedAt
517
531
  );
518
532
  }
533
+ var ownProbeIds = /* @__PURE__ */ new Set();
534
+ function detectTabIdCollision() {
535
+ return new Promise((resolve) => {
536
+ let channel;
537
+ try {
538
+ channel = new BroadcastChannel(TAB_CHANNEL);
539
+ } catch {
540
+ resolve(false);
541
+ return;
542
+ }
543
+ const probeId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
544
+ ownProbeIds.add(probeId);
545
+ let collided = false;
546
+ channel.onmessage = (event) => {
547
+ if (event.data?.type === "id-collision" && event.data?.tabId === TAB_ID) {
548
+ collided = true;
549
+ }
550
+ };
551
+ channel.postMessage({ type: "id-claim", tabId: TAB_ID, probeId });
552
+ setTimeout(() => {
553
+ channel.close();
554
+ ownProbeIds.delete(probeId);
555
+ resolve(collided);
556
+ }, 300);
557
+ });
558
+ }
519
559
  function cleanupStaleTabs() {
520
560
  const tabs = getTabsMap();
521
561
  const otherTabIds = Object.keys(tabs).filter((id) => id !== TAB_ID);
@@ -597,6 +637,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
597
637
  channel.onmessage = (event) => {
598
638
  if (event.data?.type === "ping") {
599
639
  channel?.postMessage({ type: "pong", tabId: TAB_ID });
640
+ } else if (event.data?.type === "id-claim" && event.data?.tabId === TAB_ID) {
641
+ if (event.data?.probeId && ownProbeIds.has(event.data.probeId)) return;
642
+ channel?.postMessage({ type: "id-collision", tabId: TAB_ID });
600
643
  } else if (event.data?.type === "workspace-changed" && event.data?.tabId !== TAB_ID) {
601
644
  refreshDrawingsRef.current();
602
645
  }
@@ -608,18 +651,32 @@ function WorkspaceProvider({ children, lang = "en" }) {
608
651
  };
609
652
  }, []);
610
653
  const hasCleanedUpRef = (0, import_react.useRef)(false);
654
+ const [tabIdReady, setTabIdReady] = (0, import_react.useState)(false);
611
655
  (0, import_react.useEffect)(() => {
612
- cleanupStaleTabs();
613
- const timer = setTimeout(() => {
614
- hasCleanedUpRef.current = true;
615
- const drawingId = activeDrawingIdRef.current;
616
- if (drawingId) {
617
- setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
656
+ let cancelled = false;
657
+ let timer = null;
658
+ (async () => {
659
+ if (await detectTabIdCollision()) {
660
+ regenerateTabId();
618
661
  }
619
- }, 600);
620
- return () => clearTimeout(timer);
662
+ if (cancelled) return;
663
+ setTabIdReady(true);
664
+ cleanupStaleTabs();
665
+ timer = setTimeout(() => {
666
+ hasCleanedUpRef.current = true;
667
+ const drawingId = activeDrawingIdRef.current;
668
+ if (drawingId) {
669
+ setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
670
+ }
671
+ }, 600);
672
+ })();
673
+ return () => {
674
+ cancelled = true;
675
+ if (timer) clearTimeout(timer);
676
+ };
621
677
  }, []);
622
678
  (0, import_react.useEffect)(() => {
679
+ if (!tabIdReady) return;
623
680
  const drawingId = activeDrawing?.id || null;
624
681
  setTabDrawing(drawingId);
625
682
  if (hasCleanedUpRef.current) {
@@ -679,7 +736,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
679
736
  document.removeEventListener("visibilitychange", onVisibilityChange);
680
737
  stopPolling();
681
738
  };
682
- }, [activeDrawing?.id]);
739
+ }, [activeDrawing?.id, tabIdReady]);
683
740
  (0, import_react.useEffect)(() => {
684
741
  const onUnload = () => {
685
742
  const tabs = getTabsMap();
@@ -771,7 +828,17 @@ function WorkspaceProvider({ children, lang = "en" }) {
771
828
  const now = Date.now();
772
829
  const tempId = `temp-${now}`;
773
830
  const allDrawings = await getAllDrawings();
774
- const defaultName = `${t.newDrawing} ${allDrawings.length + 1}`;
831
+ const prefix = t.newDrawing;
832
+ const suffixRegex = new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} (\\d+)$`);
833
+ let maxSuffix = 0;
834
+ for (const d of allDrawings) {
835
+ const m = d.name.match(suffixRegex);
836
+ if (m) {
837
+ const n = parseInt(m[1], 10);
838
+ if (!isNaN(n) && n > maxSuffix) maxSuffix = n;
839
+ }
840
+ }
841
+ const defaultName = `${prefix} ${maxSuffix + 1}`;
775
842
  const tempDrawing = {
776
843
  id: tempId,
777
844
  name: name || defaultName,
@@ -835,7 +902,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
835
902
  }
836
903
  }, [refreshDrawings]);
837
904
  const removeDrawing = (0, import_react.useCallback)(async (id) => {
838
- if (!workspace || drawingsRef.current.length <= 1) return;
905
+ if (!workspace) return;
839
906
  const removedDrawing = drawingsRef.current.find((d) => d.id === id);
840
907
  const wasActive = activeDrawingIdRef.current === id;
841
908
  const remaining = drawingsRef.current.filter((d) => d.id !== id);
@@ -850,6 +917,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
850
917
  if (updatedWorkspace) {
851
918
  setWorkspace(updatedWorkspace);
852
919
  }
920
+ if (remaining.length === 0) {
921
+ await createNewDrawing();
922
+ }
853
923
  broadcastWorkspaceChange();
854
924
  } catch (err) {
855
925
  if (removedDrawing) {
@@ -857,7 +927,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
857
927
  }
858
928
  setError(err instanceof Error ? err.message : "Failed to delete drawing");
859
929
  }
860
- }, [workspace]);
930
+ }, [workspace, createNewDrawing]);
861
931
  const duplicateCurrentDrawing = (0, import_react.useCallback)(async () => {
862
932
  if (!activeDrawingIdRef.current || !workspace) return null;
863
933
  const source = drawingsRef.current.find((d) => d.id === activeDrawingIdRef.current);
@@ -904,6 +974,15 @@ function WorkspaceProvider({ children, lang = "en" }) {
904
974
  updateData.files = files;
905
975
  }
906
976
  await updateDrawing(activeDrawing.id, updateData);
977
+ const now = Date.now();
978
+ const patch = {
979
+ elements,
980
+ appState,
981
+ ...files ? { files } : {},
982
+ updatedAt: now
983
+ };
984
+ setDrawings((prev) => prev.map((d) => d.id === activeDrawing.id ? { ...d, ...patch } : d));
985
+ setActiveDrawing2((prev) => prev && prev.id === activeDrawing.id ? { ...prev, ...patch } : prev);
907
986
  } catch (err) {
908
987
  setError(err instanceof Error ? err.message : "Failed to save drawing");
909
988
  }
@@ -919,6 +998,15 @@ function WorkspaceProvider({ children, lang = "en" }) {
919
998
  updateData.files = files;
920
999
  }
921
1000
  await updateDrawing(id, updateData);
1001
+ const now = Date.now();
1002
+ const patch = {
1003
+ elements,
1004
+ appState,
1005
+ ...files ? { files } : {},
1006
+ updatedAt: now
1007
+ };
1008
+ setDrawings((prev) => prev.map((d) => d.id === id ? { ...d, ...patch } : d));
1009
+ setActiveDrawing2((prev) => prev && prev.id === id ? { ...prev, ...patch } : prev);
922
1010
  } catch (err) {
923
1011
  setError(err instanceof Error ? err.message : "Failed to save drawing");
924
1012
  }
@@ -926,11 +1014,18 @@ function WorkspaceProvider({ children, lang = "en" }) {
926
1014
  const exportWorkspace = (0, import_react.useCallback)(async () => {
927
1015
  try {
928
1016
  const exportData = {
929
- version: 1,
1017
+ version: 2,
930
1018
  name: workspace?.name || "Min Arbetsyta",
931
1019
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1020
+ folders: folders.map((f) => ({
1021
+ id: f.id,
1022
+ name: f.name,
1023
+ createdAt: f.createdAt,
1024
+ updatedAt: f.updatedAt
1025
+ })),
932
1026
  drawings: drawings.map((d) => ({
933
1027
  name: d.name,
1028
+ folderId: d.folderId ?? null,
934
1029
  elements: d.elements,
935
1030
  appState: d.appState,
936
1031
  files: d.files,
@@ -950,7 +1045,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
950
1045
  } catch (err) {
951
1046
  setError(err instanceof Error ? err.message : "Failed to export workspace");
952
1047
  }
953
- }, [workspace, drawings]);
1048
+ }, [workspace, drawings, folders]);
954
1049
  const importWorkspace = (0, import_react.useCallback)(async () => {
955
1050
  if (!workspace) return;
956
1051
  try {
@@ -967,8 +1062,17 @@ function WorkspaceProvider({ children, lang = "en" }) {
967
1062
  if (!data.version || !Array.isArray(data.drawings)) {
968
1063
  throw new Error("Invalid workspace file");
969
1064
  }
1065
+ const folderIdMap = /* @__PURE__ */ new Map();
1066
+ if (Array.isArray(data.folders)) {
1067
+ for (const f of data.folders) {
1068
+ if (!f?.name || !f?.id) continue;
1069
+ const created = await createFolder(f.name);
1070
+ folderIdMap.set(f.id, created.id);
1071
+ }
1072
+ }
970
1073
  for (const d of data.drawings) {
971
- const drawing = await createDrawing(d.name || t.newDrawing);
1074
+ const mappedFolderId = d.folderId ? folderIdMap.get(d.folderId) ?? null : null;
1075
+ const drawing = await createDrawing(d.name || t.newDrawing, [], {}, mappedFolderId);
972
1076
  await updateDrawing(drawing.id, {
973
1077
  elements: d.elements || [],
974
1078
  appState: d.appState || {},
@@ -976,11 +1080,15 @@ function WorkspaceProvider({ children, lang = "en" }) {
976
1080
  });
977
1081
  await addDrawingToWorkspace(workspace.id, drawing.id);
978
1082
  }
979
- const allDrawings = await getAllDrawings();
980
- const ws = await getOrCreateDefaultWorkspace();
1083
+ const [allDrawings, allFolders, ws] = await Promise.all([
1084
+ getAllDrawings(),
1085
+ getAllFolders(),
1086
+ getOrCreateDefaultWorkspace()
1087
+ ]);
981
1088
  const wsDrawings = allDrawings.filter((dr) => ws.drawingIds.includes(dr.id));
982
1089
  setWorkspace(ws);
983
1090
  setDrawings(wsDrawings);
1091
+ setFolders(allFolders);
984
1092
  } catch (err) {
985
1093
  setError(err instanceof Error ? err.message : "Failed to import workspace");
986
1094
  }
package/dist/index.mjs CHANGED
@@ -361,19 +361,34 @@ function useWorkspaceLang() {
361
361
  return { lang: context.lang, t: context.t };
362
362
  }
363
363
  var TAB_ID_KEY = "rita-workspace-tab-id";
364
+ var TAB_ENTRY_KEY = "rita-workspace-tab-entry";
365
+ function generateFreshTabId() {
366
+ return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
367
+ }
364
368
  var TAB_ID = (() => {
365
369
  try {
366
370
  const existing = sessionStorage.getItem(TAB_ID_KEY);
367
371
  if (existing) return existing;
368
372
  } catch {
369
373
  }
370
- const fresh = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
374
+ const fresh = generateFreshTabId();
371
375
  try {
372
376
  sessionStorage.setItem(TAB_ID_KEY, fresh);
373
377
  } catch {
374
378
  }
375
379
  return fresh;
376
380
  })();
381
+ function regenerateTabId() {
382
+ TAB_ID = generateFreshTabId();
383
+ try {
384
+ sessionStorage.setItem(TAB_ID_KEY, TAB_ID);
385
+ } catch {
386
+ }
387
+ try {
388
+ sessionStorage.removeItem(TAB_ENTRY_KEY);
389
+ } catch {
390
+ }
391
+ }
377
392
  var TABS_KEY = "rita-workspace-tabs";
378
393
  var TAB_CHANNEL = "rita-workspace-tabs";
379
394
  function broadcastWorkspaceChange() {
@@ -406,7 +421,6 @@ function getTabsMap() {
406
421
  return {};
407
422
  }
408
423
  }
409
- var TAB_ENTRY_KEY = "rita-workspace-tab-entry";
410
424
  function setTabDrawing(drawingId) {
411
425
  const tabs = getTabsMap();
412
426
  if (drawingId) {
@@ -445,6 +459,32 @@ function isDrawingOpenedEarlierInOtherTab(drawingId) {
445
459
  ([tabId, entry]) => tabId !== TAB_ID && entry.drawingId === drawingId && entry.openedAt <= myOpenedAt
446
460
  );
447
461
  }
462
+ var ownProbeIds = /* @__PURE__ */ new Set();
463
+ function detectTabIdCollision() {
464
+ return new Promise((resolve) => {
465
+ let channel;
466
+ try {
467
+ channel = new BroadcastChannel(TAB_CHANNEL);
468
+ } catch {
469
+ resolve(false);
470
+ return;
471
+ }
472
+ const probeId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
473
+ ownProbeIds.add(probeId);
474
+ let collided = false;
475
+ channel.onmessage = (event) => {
476
+ if (event.data?.type === "id-collision" && event.data?.tabId === TAB_ID) {
477
+ collided = true;
478
+ }
479
+ };
480
+ channel.postMessage({ type: "id-claim", tabId: TAB_ID, probeId });
481
+ setTimeout(() => {
482
+ channel.close();
483
+ ownProbeIds.delete(probeId);
484
+ resolve(collided);
485
+ }, 300);
486
+ });
487
+ }
448
488
  function cleanupStaleTabs() {
449
489
  const tabs = getTabsMap();
450
490
  const otherTabIds = Object.keys(tabs).filter((id) => id !== TAB_ID);
@@ -526,6 +566,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
526
566
  channel.onmessage = (event) => {
527
567
  if (event.data?.type === "ping") {
528
568
  channel?.postMessage({ type: "pong", tabId: TAB_ID });
569
+ } else if (event.data?.type === "id-claim" && event.data?.tabId === TAB_ID) {
570
+ if (event.data?.probeId && ownProbeIds.has(event.data.probeId)) return;
571
+ channel?.postMessage({ type: "id-collision", tabId: TAB_ID });
529
572
  } else if (event.data?.type === "workspace-changed" && event.data?.tabId !== TAB_ID) {
530
573
  refreshDrawingsRef.current();
531
574
  }
@@ -537,18 +580,32 @@ function WorkspaceProvider({ children, lang = "en" }) {
537
580
  };
538
581
  }, []);
539
582
  const hasCleanedUpRef = useRef(false);
583
+ const [tabIdReady, setTabIdReady] = useState(false);
540
584
  useEffect(() => {
541
- cleanupStaleTabs();
542
- const timer = setTimeout(() => {
543
- hasCleanedUpRef.current = true;
544
- const drawingId = activeDrawingIdRef.current;
545
- if (drawingId) {
546
- setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
585
+ let cancelled = false;
586
+ let timer = null;
587
+ (async () => {
588
+ if (await detectTabIdCollision()) {
589
+ regenerateTabId();
547
590
  }
548
- }, 600);
549
- return () => clearTimeout(timer);
591
+ if (cancelled) return;
592
+ setTabIdReady(true);
593
+ cleanupStaleTabs();
594
+ timer = setTimeout(() => {
595
+ hasCleanedUpRef.current = true;
596
+ const drawingId = activeDrawingIdRef.current;
597
+ if (drawingId) {
598
+ setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
599
+ }
600
+ }, 600);
601
+ })();
602
+ return () => {
603
+ cancelled = true;
604
+ if (timer) clearTimeout(timer);
605
+ };
550
606
  }, []);
551
607
  useEffect(() => {
608
+ if (!tabIdReady) return;
552
609
  const drawingId = activeDrawing?.id || null;
553
610
  setTabDrawing(drawingId);
554
611
  if (hasCleanedUpRef.current) {
@@ -608,7 +665,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
608
665
  document.removeEventListener("visibilitychange", onVisibilityChange);
609
666
  stopPolling();
610
667
  };
611
- }, [activeDrawing?.id]);
668
+ }, [activeDrawing?.id, tabIdReady]);
612
669
  useEffect(() => {
613
670
  const onUnload = () => {
614
671
  const tabs = getTabsMap();
@@ -700,7 +757,17 @@ function WorkspaceProvider({ children, lang = "en" }) {
700
757
  const now = Date.now();
701
758
  const tempId = `temp-${now}`;
702
759
  const allDrawings = await getAllDrawings();
703
- const defaultName = `${t.newDrawing} ${allDrawings.length + 1}`;
760
+ const prefix = t.newDrawing;
761
+ const suffixRegex = new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} (\\d+)$`);
762
+ let maxSuffix = 0;
763
+ for (const d of allDrawings) {
764
+ const m = d.name.match(suffixRegex);
765
+ if (m) {
766
+ const n = parseInt(m[1], 10);
767
+ if (!isNaN(n) && n > maxSuffix) maxSuffix = n;
768
+ }
769
+ }
770
+ const defaultName = `${prefix} ${maxSuffix + 1}`;
704
771
  const tempDrawing = {
705
772
  id: tempId,
706
773
  name: name || defaultName,
@@ -764,7 +831,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
764
831
  }
765
832
  }, [refreshDrawings]);
766
833
  const removeDrawing = useCallback(async (id) => {
767
- if (!workspace || drawingsRef.current.length <= 1) return;
834
+ if (!workspace) return;
768
835
  const removedDrawing = drawingsRef.current.find((d) => d.id === id);
769
836
  const wasActive = activeDrawingIdRef.current === id;
770
837
  const remaining = drawingsRef.current.filter((d) => d.id !== id);
@@ -779,6 +846,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
779
846
  if (updatedWorkspace) {
780
847
  setWorkspace(updatedWorkspace);
781
848
  }
849
+ if (remaining.length === 0) {
850
+ await createNewDrawing();
851
+ }
782
852
  broadcastWorkspaceChange();
783
853
  } catch (err) {
784
854
  if (removedDrawing) {
@@ -786,7 +856,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
786
856
  }
787
857
  setError(err instanceof Error ? err.message : "Failed to delete drawing");
788
858
  }
789
- }, [workspace]);
859
+ }, [workspace, createNewDrawing]);
790
860
  const duplicateCurrentDrawing = useCallback(async () => {
791
861
  if (!activeDrawingIdRef.current || !workspace) return null;
792
862
  const source = drawingsRef.current.find((d) => d.id === activeDrawingIdRef.current);
@@ -833,6 +903,15 @@ function WorkspaceProvider({ children, lang = "en" }) {
833
903
  updateData.files = files;
834
904
  }
835
905
  await updateDrawing(activeDrawing.id, updateData);
906
+ const now = Date.now();
907
+ const patch = {
908
+ elements,
909
+ appState,
910
+ ...files ? { files } : {},
911
+ updatedAt: now
912
+ };
913
+ setDrawings((prev) => prev.map((d) => d.id === activeDrawing.id ? { ...d, ...patch } : d));
914
+ setActiveDrawing2((prev) => prev && prev.id === activeDrawing.id ? { ...prev, ...patch } : prev);
836
915
  } catch (err) {
837
916
  setError(err instanceof Error ? err.message : "Failed to save drawing");
838
917
  }
@@ -848,6 +927,15 @@ function WorkspaceProvider({ children, lang = "en" }) {
848
927
  updateData.files = files;
849
928
  }
850
929
  await updateDrawing(id, updateData);
930
+ const now = Date.now();
931
+ const patch = {
932
+ elements,
933
+ appState,
934
+ ...files ? { files } : {},
935
+ updatedAt: now
936
+ };
937
+ setDrawings((prev) => prev.map((d) => d.id === id ? { ...d, ...patch } : d));
938
+ setActiveDrawing2((prev) => prev && prev.id === id ? { ...prev, ...patch } : prev);
851
939
  } catch (err) {
852
940
  setError(err instanceof Error ? err.message : "Failed to save drawing");
853
941
  }
@@ -855,11 +943,18 @@ function WorkspaceProvider({ children, lang = "en" }) {
855
943
  const exportWorkspace = useCallback(async () => {
856
944
  try {
857
945
  const exportData = {
858
- version: 1,
946
+ version: 2,
859
947
  name: workspace?.name || "Min Arbetsyta",
860
948
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
949
+ folders: folders.map((f) => ({
950
+ id: f.id,
951
+ name: f.name,
952
+ createdAt: f.createdAt,
953
+ updatedAt: f.updatedAt
954
+ })),
861
955
  drawings: drawings.map((d) => ({
862
956
  name: d.name,
957
+ folderId: d.folderId ?? null,
863
958
  elements: d.elements,
864
959
  appState: d.appState,
865
960
  files: d.files,
@@ -879,7 +974,7 @@ function WorkspaceProvider({ children, lang = "en" }) {
879
974
  } catch (err) {
880
975
  setError(err instanceof Error ? err.message : "Failed to export workspace");
881
976
  }
882
- }, [workspace, drawings]);
977
+ }, [workspace, drawings, folders]);
883
978
  const importWorkspace = useCallback(async () => {
884
979
  if (!workspace) return;
885
980
  try {
@@ -896,8 +991,17 @@ function WorkspaceProvider({ children, lang = "en" }) {
896
991
  if (!data.version || !Array.isArray(data.drawings)) {
897
992
  throw new Error("Invalid workspace file");
898
993
  }
994
+ const folderIdMap = /* @__PURE__ */ new Map();
995
+ if (Array.isArray(data.folders)) {
996
+ for (const f of data.folders) {
997
+ if (!f?.name || !f?.id) continue;
998
+ const created = await createFolder(f.name);
999
+ folderIdMap.set(f.id, created.id);
1000
+ }
1001
+ }
899
1002
  for (const d of data.drawings) {
900
- const drawing = await createDrawing(d.name || t.newDrawing);
1003
+ const mappedFolderId = d.folderId ? folderIdMap.get(d.folderId) ?? null : null;
1004
+ const drawing = await createDrawing(d.name || t.newDrawing, [], {}, mappedFolderId);
901
1005
  await updateDrawing(drawing.id, {
902
1006
  elements: d.elements || [],
903
1007
  appState: d.appState || {},
@@ -905,11 +1009,15 @@ function WorkspaceProvider({ children, lang = "en" }) {
905
1009
  });
906
1010
  await addDrawingToWorkspace(workspace.id, drawing.id);
907
1011
  }
908
- const allDrawings = await getAllDrawings();
909
- const ws = await getOrCreateDefaultWorkspace();
1012
+ const [allDrawings, allFolders, ws] = await Promise.all([
1013
+ getAllDrawings(),
1014
+ getAllFolders(),
1015
+ getOrCreateDefaultWorkspace()
1016
+ ]);
910
1017
  const wsDrawings = allDrawings.filter((dr) => ws.drawingIds.includes(dr.id));
911
1018
  setWorkspace(ws);
912
1019
  setDrawings(wsDrawings);
1020
+ setFolders(allFolders);
913
1021
  } catch (err) {
914
1022
  setError(err instanceof Error ? err.message : "Failed to import workspace");
915
1023
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rita-workspace",
3
- "version": "0.5.27",
3
+ "version": "0.5.29",
4
4
  "description": "Multi-drawing workspace feature for Rita (Excalidraw fork)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",