rita-workspace 0.5.2 → 0.5.4

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
@@ -453,6 +453,54 @@ function isDrawingOpenedEarlierInOtherTab(drawingId) {
453
453
  ([tabId, entry]) => tabId !== TAB_ID && entry.drawingId === drawingId && entry.openedAt <= myOpenedAt
454
454
  );
455
455
  }
456
+ function cleanupStaleTabs() {
457
+ const tabs = getTabsMap();
458
+ const otherTabIds = Object.keys(tabs).filter((id) => id !== TAB_ID);
459
+ if (otherTabIds.length === 0) return;
460
+ const alive = /* @__PURE__ */ new Set();
461
+ try {
462
+ const channel = new BroadcastChannel(TAB_CHANNEL);
463
+ channel.onmessage = (event) => {
464
+ if (event.data?.type === "pong" && event.data?.tabId) {
465
+ alive.add(event.data.tabId);
466
+ }
467
+ };
468
+ channel.postMessage({ type: "ping", tabId: TAB_ID });
469
+ setTimeout(() => {
470
+ const currentTabs = getTabsMap();
471
+ let changed = false;
472
+ for (const tabId of otherTabIds) {
473
+ if (!alive.has(tabId) && tabId in currentTabs) {
474
+ delete currentTabs[tabId];
475
+ changed = true;
476
+ }
477
+ }
478
+ if (changed) {
479
+ const json = JSON.stringify(currentTabs);
480
+ localStorage.setItem(TABS_KEY, json);
481
+ tabsMapCache = currentTabs;
482
+ tabsMapRaw = json;
483
+ }
484
+ channel.close();
485
+ }, 500);
486
+ } catch {
487
+ const now = Date.now();
488
+ const maxAge = 24 * 60 * 60 * 1e3;
489
+ let changed = false;
490
+ for (const [tabId, entry] of Object.entries(tabs)) {
491
+ if (tabId !== TAB_ID && entry.openedAt < now - maxAge) {
492
+ delete tabs[tabId];
493
+ changed = true;
494
+ }
495
+ }
496
+ if (changed) {
497
+ const json = JSON.stringify(tabs);
498
+ localStorage.setItem(TABS_KEY, json);
499
+ tabsMapCache = tabs;
500
+ tabsMapRaw = json;
501
+ }
502
+ }
503
+ }
456
504
  function WorkspaceProvider({ children, lang = "en" }) {
457
505
  const [workspace, setWorkspace] = (0, import_react.useState)(null);
458
506
  const [drawings, setDrawings] = (0, import_react.useState)([]);
@@ -472,6 +520,16 @@ function WorkspaceProvider({ children, lang = "en" }) {
472
520
  }
473
521
  prevConflictRef.current = isDrawingConflict;
474
522
  }, [isDrawingConflict, activeDrawing?.id]);
523
+ (0, import_react.useEffect)(() => {
524
+ cleanupStaleTabs();
525
+ const timer = setTimeout(() => {
526
+ const drawingId = activeDrawing?.id;
527
+ if (drawingId) {
528
+ setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
529
+ }
530
+ }, 600);
531
+ return () => clearTimeout(timer);
532
+ }, []);
475
533
  (0, import_react.useEffect)(() => {
476
534
  const drawingId = activeDrawing?.id || null;
477
535
  setTabDrawing(drawingId);
@@ -491,6 +549,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
491
549
  channel.postMessage({ type: "drawing-changed", tabId: TAB_ID, drawingId });
492
550
  channel.onmessage = (event) => {
493
551
  if (event.data?.tabId !== TAB_ID) {
552
+ if (event.data?.type === "ping") {
553
+ channel?.postMessage({ type: "pong", tabId: TAB_ID });
554
+ }
494
555
  recheckConflict();
495
556
  }
496
557
  };
@@ -1390,7 +1451,6 @@ var DrawingsDialog = ({
1390
1451
  minute: "2-digit"
1391
1452
  });
1392
1453
  };
1393
- if (!open) return null;
1394
1454
  const { rootDrawings, drawingsByFolder, filteredFolders } = (0, import_react5.useMemo)(() => {
1395
1455
  const query = searchQuery.toLowerCase().trim();
1396
1456
  const filtered = query ? drawings.filter((d) => d.name.toLowerCase().includes(query)) : drawings;
@@ -1402,6 +1462,7 @@ var DrawingsDialog = ({
1402
1462
  const foldersFiltered = query ? folders.filter((f) => f.name.toLowerCase().includes(query) || (byFolder[f.id] || []).length > 0) : folders;
1403
1463
  return { rootDrawings: root, drawingsByFolder: byFolder, filteredFolders: foldersFiltered };
1404
1464
  }, [drawings, folders, searchQuery]);
1465
+ if (!open) return null;
1405
1466
  const dialogStyle = position ? {
1406
1467
  position: "fixed",
1407
1468
  left: position.x,
@@ -1449,10 +1510,6 @@ var DrawingsDialog = ({
1449
1510
  setDropTargetFolderId("__none__");
1450
1511
  setTimeout(() => setDropTargetFolderId(null), 0);
1451
1512
  },
1452
- onClick: () => {
1453
- if (editingId || confirmDeleteId || switchingId || movingDrawingId) return;
1454
- if (activeDrawing?.id !== drawing.id) handleSelect(drawing);
1455
- },
1456
1513
  style: {
1457
1514
  padding: "10px 12px",
1458
1515
  display: "flex",
@@ -1460,7 +1517,7 @@ var DrawingsDialog = ({
1460
1517
  gap: "12px",
1461
1518
  borderRadius: "8px",
1462
1519
  marginBottom: "4px",
1463
- cursor: draggingDrawingId ? "grabbing" : editingId || confirmDeleteId || switchingId ? "default" : "pointer",
1520
+ cursor: draggingDrawingId ? "grabbing" : "default",
1464
1521
  backgroundColor: activeDrawing?.id === drawing.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent",
1465
1522
  transition: "background-color 0.15s",
1466
1523
  opacity: draggingDrawingId === drawing.id ? 0.4 : switchingId && switchingId !== drawing.id ? 0.5 : 1
@@ -1619,6 +1676,19 @@ var DrawingsDialog = ({
1619
1676
  }
1620
1677
  )
1621
1678
  ] }) : editingId !== drawing.id && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1679
+ activeDrawing?.id !== drawing.id && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1680
+ "button",
1681
+ {
1682
+ onClick: (e) => {
1683
+ e.stopPropagation();
1684
+ handleSelect(drawing);
1685
+ },
1686
+ disabled: !!switchingId,
1687
+ style: { padding: "3px 8px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer", opacity: switchingId ? 0.6 : 1 },
1688
+ title: t.open,
1689
+ children: switchingId === drawing.id ? "\u23F3" : t.open
1690
+ }
1691
+ ),
1622
1692
  folders.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1623
1693
  "button",
1624
1694
  {
package/dist/index.mjs CHANGED
@@ -385,6 +385,54 @@ function isDrawingOpenedEarlierInOtherTab(drawingId) {
385
385
  ([tabId, entry]) => tabId !== TAB_ID && entry.drawingId === drawingId && entry.openedAt <= myOpenedAt
386
386
  );
387
387
  }
388
+ function cleanupStaleTabs() {
389
+ const tabs = getTabsMap();
390
+ const otherTabIds = Object.keys(tabs).filter((id) => id !== TAB_ID);
391
+ if (otherTabIds.length === 0) return;
392
+ const alive = /* @__PURE__ */ new Set();
393
+ try {
394
+ const channel = new BroadcastChannel(TAB_CHANNEL);
395
+ channel.onmessage = (event) => {
396
+ if (event.data?.type === "pong" && event.data?.tabId) {
397
+ alive.add(event.data.tabId);
398
+ }
399
+ };
400
+ channel.postMessage({ type: "ping", tabId: TAB_ID });
401
+ setTimeout(() => {
402
+ const currentTabs = getTabsMap();
403
+ let changed = false;
404
+ for (const tabId of otherTabIds) {
405
+ if (!alive.has(tabId) && tabId in currentTabs) {
406
+ delete currentTabs[tabId];
407
+ changed = true;
408
+ }
409
+ }
410
+ if (changed) {
411
+ const json = JSON.stringify(currentTabs);
412
+ localStorage.setItem(TABS_KEY, json);
413
+ tabsMapCache = currentTabs;
414
+ tabsMapRaw = json;
415
+ }
416
+ channel.close();
417
+ }, 500);
418
+ } catch {
419
+ const now = Date.now();
420
+ const maxAge = 24 * 60 * 60 * 1e3;
421
+ let changed = false;
422
+ for (const [tabId, entry] of Object.entries(tabs)) {
423
+ if (tabId !== TAB_ID && entry.openedAt < now - maxAge) {
424
+ delete tabs[tabId];
425
+ changed = true;
426
+ }
427
+ }
428
+ if (changed) {
429
+ const json = JSON.stringify(tabs);
430
+ localStorage.setItem(TABS_KEY, json);
431
+ tabsMapCache = tabs;
432
+ tabsMapRaw = json;
433
+ }
434
+ }
435
+ }
388
436
  function WorkspaceProvider({ children, lang = "en" }) {
389
437
  const [workspace, setWorkspace] = useState(null);
390
438
  const [drawings, setDrawings] = useState([]);
@@ -404,6 +452,16 @@ function WorkspaceProvider({ children, lang = "en" }) {
404
452
  }
405
453
  prevConflictRef.current = isDrawingConflict;
406
454
  }, [isDrawingConflict, activeDrawing?.id]);
455
+ useEffect(() => {
456
+ cleanupStaleTabs();
457
+ const timer = setTimeout(() => {
458
+ const drawingId = activeDrawing?.id;
459
+ if (drawingId) {
460
+ setIsDrawingConflict(isDrawingOpenedEarlierInOtherTab(drawingId));
461
+ }
462
+ }, 600);
463
+ return () => clearTimeout(timer);
464
+ }, []);
407
465
  useEffect(() => {
408
466
  const drawingId = activeDrawing?.id || null;
409
467
  setTabDrawing(drawingId);
@@ -423,6 +481,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
423
481
  channel.postMessage({ type: "drawing-changed", tabId: TAB_ID, drawingId });
424
482
  channel.onmessage = (event) => {
425
483
  if (event.data?.tabId !== TAB_ID) {
484
+ if (event.data?.type === "ping") {
485
+ channel?.postMessage({ type: "pong", tabId: TAB_ID });
486
+ }
426
487
  recheckConflict();
427
488
  }
428
489
  };
@@ -1322,7 +1383,6 @@ var DrawingsDialog = ({
1322
1383
  minute: "2-digit"
1323
1384
  });
1324
1385
  };
1325
- if (!open) return null;
1326
1386
  const { rootDrawings, drawingsByFolder, filteredFolders } = useMemo(() => {
1327
1387
  const query = searchQuery.toLowerCase().trim();
1328
1388
  const filtered = query ? drawings.filter((d) => d.name.toLowerCase().includes(query)) : drawings;
@@ -1334,6 +1394,7 @@ var DrawingsDialog = ({
1334
1394
  const foldersFiltered = query ? folders.filter((f) => f.name.toLowerCase().includes(query) || (byFolder[f.id] || []).length > 0) : folders;
1335
1395
  return { rootDrawings: root, drawingsByFolder: byFolder, filteredFolders: foldersFiltered };
1336
1396
  }, [drawings, folders, searchQuery]);
1397
+ if (!open) return null;
1337
1398
  const dialogStyle = position ? {
1338
1399
  position: "fixed",
1339
1400
  left: position.x,
@@ -1381,10 +1442,6 @@ var DrawingsDialog = ({
1381
1442
  setDropTargetFolderId("__none__");
1382
1443
  setTimeout(() => setDropTargetFolderId(null), 0);
1383
1444
  },
1384
- onClick: () => {
1385
- if (editingId || confirmDeleteId || switchingId || movingDrawingId) return;
1386
- if (activeDrawing?.id !== drawing.id) handleSelect(drawing);
1387
- },
1388
1445
  style: {
1389
1446
  padding: "10px 12px",
1390
1447
  display: "flex",
@@ -1392,7 +1449,7 @@ var DrawingsDialog = ({
1392
1449
  gap: "12px",
1393
1450
  borderRadius: "8px",
1394
1451
  marginBottom: "4px",
1395
- cursor: draggingDrawingId ? "grabbing" : editingId || confirmDeleteId || switchingId ? "default" : "pointer",
1452
+ cursor: draggingDrawingId ? "grabbing" : "default",
1396
1453
  backgroundColor: activeDrawing?.id === drawing.id ? "var(--color-primary-light, rgba(108, 99, 255, 0.1))" : "transparent",
1397
1454
  transition: "background-color 0.15s",
1398
1455
  opacity: draggingDrawingId === drawing.id ? 0.4 : switchingId && switchingId !== drawing.id ? 0.5 : 1
@@ -1551,6 +1608,19 @@ var DrawingsDialog = ({
1551
1608
  }
1552
1609
  )
1553
1610
  ] }) : editingId !== drawing.id && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1611
+ activeDrawing?.id !== drawing.id && /* @__PURE__ */ jsx6(
1612
+ "button",
1613
+ {
1614
+ onClick: (e) => {
1615
+ e.stopPropagation();
1616
+ handleSelect(drawing);
1617
+ },
1618
+ disabled: !!switchingId,
1619
+ style: { padding: "3px 8px", fontSize: "12px", backgroundColor: "var(--color-primary, #6c63ff)", color: "#fff", border: "none", borderRadius: "4px", cursor: "pointer", opacity: switchingId ? 0.6 : 1 },
1620
+ title: t.open,
1621
+ children: switchingId === drawing.id ? "\u23F3" : t.open
1622
+ }
1623
+ ),
1554
1624
  folders.length > 0 && /* @__PURE__ */ jsx6(
1555
1625
  "button",
1556
1626
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rita-workspace",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Multi-drawing workspace feature for Rita (Excalidraw fork)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",