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.
- package/dist/index.js +127 -19
- package/dist/index.mjs +127 -19
- 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 =
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
|
|
656
|
+
let cancelled = false;
|
|
657
|
+
let timer = null;
|
|
658
|
+
(async () => {
|
|
659
|
+
if (await detectTabIdCollision()) {
|
|
660
|
+
regenerateTabId();
|
|
618
661
|
}
|
|
619
|
-
|
|
620
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
980
|
-
|
|
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 =
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
|
|
585
|
+
let cancelled = false;
|
|
586
|
+
let timer = null;
|
|
587
|
+
(async () => {
|
|
588
|
+
if (await detectTabIdCollision()) {
|
|
589
|
+
regenerateTabId();
|
|
547
590
|
}
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
909
|
-
|
|
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
|
}
|