rita-workspace 0.3.2 → 0.4.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/README.md CHANGED
@@ -7,6 +7,7 @@ Multi-drawing workspace feature for Rita (Excalidraw fork based on B310-digital/
7
7
  - **Multiple drawings** - Create and manage multiple drawings in one workspace
8
8
  - **Menu integration** - Seamlessly integrates with Excalidraw's hamburger menu
9
9
  - **Auto-save** - All drawings saved locally in IndexedDB
10
+ - **Auto-sync** - Automatic sync between workspace and Excalidraw canvas
10
11
  - **Rename & delete** - Full drawing management via dialog
11
12
  - **i18n support** - Swedish and English with automatic Excalidraw language sync
12
13
 
@@ -20,62 +21,67 @@ yarn add rita-workspace
20
21
 
21
22
  ## Integration Guide
22
23
 
23
- Two files need to be modified in the B310/Excalidraw fork:
24
+ Three files need to be modified in the B310/Excalidraw fork:
24
25
 
25
- ### 1. `excalidraw-app/App.tsx`
26
+ ### 1. `excalidraw-app/App.tsx` - Add Provider and Bridge
26
27
 
27
- **Add import** (at the top with other imports):
28
+ **Add imports:**
28
29
 
29
30
  ```tsx
30
- import { WorkspaceProvider } from "rita-workspace";
31
+ import { WorkspaceProvider, WorkspaceBridge } from "rita-workspace";
31
32
  ```
32
33
 
33
- **Wrap with WorkspaceProvider** (in the `ExcalidrawWrapper` component to access `langCode`):
34
+ **Wrap ExcalidrawApp with WorkspaceProvider:**
34
35
 
35
36
  ```tsx
36
- const ExcalidrawWrapper = () => {
37
- const [langCode] = useAppLangCode(); // Excalidraw's language hook
38
-
39
- // ... existing code ...
40
-
37
+ const ExcalidrawApp = () => {
41
38
  return (
42
- <WorkspaceProvider lang={langCode}> {/* <-- Pass langCode here */}
43
- <div style={{ height: "100%" }}>
44
- <Excalidraw ... />
45
- </div>
46
- </WorkspaceProvider>
39
+ <TopErrorBoundary>
40
+ <Provider store={appJotaiStore}>
41
+ <WorkspaceProvider lang="sv"> {/* <-- Add this */}
42
+ <ExcalidrawWrapper />
43
+ </WorkspaceProvider> {/* <-- And this */}
44
+ </Provider>
45
+ </TopErrorBoundary>
47
46
  );
48
47
  };
49
48
  ```
50
49
 
51
- Or if you prefer wrapping at the app level:
50
+ **Add WorkspaceBridge inside ExcalidrawWrapper** (this syncs the canvas automatically):
52
51
 
53
52
  ```tsx
54
- const ExcalidrawApp = () => {
53
+ const ExcalidrawWrapper = () => {
54
+ const [excalidrawAPI, excalidrawRefCallback] =
55
+ useCallbackRefState<ExcalidrawImperativeAPI>();
56
+
57
+ // ... existing code ...
58
+
55
59
  return (
56
- <TopErrorBoundary>
57
- <Provider store={appJotaiStore}>
58
- <WorkspaceProvider lang="sv"> {/* <-- Or hardcode language */}
59
- <ExcalidrawWrapper />
60
- </WorkspaceProvider>
61
- </Provider>
62
- </TopErrorBoundary>
60
+ <div style={{ height: "100%" }}>
61
+ {/* === ADD THIS - Auto-syncs workspace with Excalidraw === */}
62
+ <WorkspaceBridge excalidrawAPI={excalidrawAPI} />
63
+
64
+ <Excalidraw
65
+ excalidrawAPI={excalidrawRefCallback}
66
+ // ... rest of props ...
67
+ />
68
+ </div>
63
69
  );
64
70
  };
65
71
  ```
66
72
 
67
- ### 2. `excalidraw-app/components/AppMainMenu.tsx`
73
+ ### 2. `excalidraw-app/components/AppMainMenu.tsx` - Add Menu Items
68
74
 
69
- **Add imports** (at the top):
75
+ **Add imports:**
70
76
 
71
77
  ```tsx
72
- import React, { useState } from "react"; // Add useState
78
+ import React, { useState } from "react";
73
79
 
74
- // Add after other imports:
75
80
  import { WorkspaceMenuItems, DrawingsDialog } from "rita-workspace";
81
+ import { LoadIcon } from "../components/icons"; // Excalidraw's folder icon
76
82
  ```
77
83
 
78
- **Add state and menu items** (inside the component):
84
+ **Add state and menu items:**
79
85
 
80
86
  ```tsx
81
87
  export const AppMainMenu: React.FC<{...}> = React.memo((props) => {
@@ -89,7 +95,7 @@ export const AppMainMenu: React.FC<{...}> = React.memo((props) => {
89
95
 
90
96
  {/* === RITA WORKSPACE === */}
91
97
  <MainMenu.Sub>
92
- <MainMenu.Sub.Trigger>📄 Ritningar</MainMenu.Sub.Trigger>
98
+ <MainMenu.Sub.Trigger>{LoadIcon} Arbetsyta</MainMenu.Sub.Trigger>
93
99
  <MainMenu.Sub.Content>
94
100
  <WorkspaceMenuItems
95
101
  onManageDrawings={() => setShowDrawingsDialog(true)}
@@ -110,77 +116,50 @@ export const AppMainMenu: React.FC<{...}> = React.memo((props) => {
110
116
  });
111
117
  ```
112
118
 
113
- ## Language Support (i18n)
119
+ ## How It Works
114
120
 
115
- Rita Workspace supports **Swedish** and **English**, with automatic sync to Excalidraw's language setting.
121
+ 1. **WorkspaceProvider** - Manages workspace state (drawings list, active drawing)
122
+ 2. **WorkspaceBridge** - Automatically syncs between workspace and Excalidraw:
123
+ - Loads drawing into canvas when you switch drawings
124
+ - Auto-saves canvas changes back to workspace
125
+ - Saves current drawing before switching to another
126
+ 3. **WorkspaceMenuItems** - Provides the menu UI for switching drawings
127
+ 4. **DrawingsDialog** - Full management UI (rename, delete, create)
116
128
 
117
- ### Automatic Language Sync
129
+ ## Language Support (i18n)
118
130
 
119
- Pass Excalidraw's `langCode` to `WorkspaceProvider` - all child components inherit the language automatically:
131
+ Pass Excalidraw's `langCode` to `WorkspaceProvider`:
120
132
 
121
133
  ```tsx
122
- const [langCode] = useAppLangCode(); // From Excalidraw
134
+ const [langCode] = useAppLangCode();
123
135
 
124
136
  <WorkspaceProvider lang={langCode}>
125
137
  {/* All components automatically use the same language */}
126
- <WorkspaceMenuItems ... />
127
- <DrawingsDialog ... />
128
- </WorkspaceProvider>
129
- ```
130
-
131
- ### Manual Override
132
-
133
- You can override the language on individual components if needed:
134
-
135
- ```tsx
136
- <WorkspaceProvider lang="en">
137
- {/* This dialog will be in Swedish despite provider being English */}
138
- <DrawingsDialog lang="sv" ... />
139
138
  </WorkspaceProvider>
140
139
  ```
141
140
 
142
- ### Supported Languages
143
-
144
141
  | Code | Language |
145
142
  |------|----------|
146
143
  | `sv`, `sv-SE` | 🇸🇪 Swedish |
147
144
  | `en`, `en-US` | 🇬🇧 English (default) |
148
145
 
149
- ## Result
150
-
151
- After integration, a "📄 Ritningar" (or "📄 Drawings") submenu appears in the hamburger menu:
152
-
153
- ```
154
- ┌─────────────────────────┐
155
- │ 📂 Open │
156
- │ 💾 Save │
157
- │ 📄 Ritningar ▶ │──┐
158
- │ 📤 Export │ │ ┌─────────────────────┐
159
- │ ... │ └──│ ✓ Current drawing │
160
- └─────────────────────────┘ │ Sketch 2 │
161
- │ Project X │
162
- │ ─────────────────── │
163
- │ + Ny ritning │
164
- │ 📄 Hantera... │
165
- └─────────────────────┘
166
- ```
167
-
168
146
  ## API Reference
169
147
 
170
148
  ### Components
171
149
 
172
150
  | Component | Description |
173
151
  |-----------|-------------|
174
- | `WorkspaceProvider` | React context provider - wrap your app with this. Accepts `lang` prop. |
175
- | `WorkspaceMenuItems` | Menu items for Excalidraw's MainMenu. Optional `lang` prop for override. |
176
- | `DrawingsDialog` | Modal dialog for managing all drawings. Optional `lang` prop for override. |
152
+ | `WorkspaceProvider` | React context provider. Props: `lang` |
153
+ | `WorkspaceBridge` | Auto-syncs workspace Excalidraw. Props: `excalidrawAPI`, `autoSaveInterval` |
154
+ | `WorkspaceMenuItems` | Menu items for MainMenu. Props: `onManageDrawings`, `lang` |
155
+ | `DrawingsDialog` | Management dialog. Props: `open`, `onClose`, `lang` |
177
156
 
178
157
  ### Hooks
179
158
 
180
159
  | Hook | Description |
181
160
  |------|-------------|
182
- | `useWorkspace()` | Access workspace state, actions, and current language |
183
- | `useWorkspaceLang()` | Get just the current language and translations |
161
+ | `useWorkspace()` | Access workspace state and actions |
162
+ | `useWorkspaceLang()` | Get current language and translations |
184
163
 
185
164
  ### useWorkspace() returns
186
165
 
@@ -191,8 +170,6 @@ const {
191
170
  activeDrawing, // Drawing | null - currently active
192
171
  isLoading, // boolean
193
172
  error, // string | null
194
-
195
- // Language
196
173
  lang, // string - current language code
197
174
  t, // Translations object
198
175
 
@@ -201,13 +178,24 @@ const {
201
178
  switchDrawing, // (id: string) => Promise<void>
202
179
  renameDrawing, // (id: string, name: string) => Promise<void>
203
180
  removeDrawing, // (id: string) => Promise<void>
204
- saveCurrentDrawing, // (elements, appState) => Promise<void>
181
+ saveCurrentDrawing, // (elements, appState, files?) => Promise<void>
205
182
  } = useWorkspace();
206
183
  ```
207
184
 
185
+ ### WorkspaceBridge Props
186
+
187
+ ```tsx
188
+ <WorkspaceBridge
189
+ excalidrawAPI={excalidrawAPI} // Required - from Excalidraw
190
+ autoSaveInterval={2000} // Optional - ms between saves (default: 2000)
191
+ onDrawingLoad={(id) => {}} // Optional - called when drawing loads
192
+ onDrawingSave={(id) => {}} // Optional - called when drawing saves
193
+ />
194
+ ```
195
+
208
196
  ## Data Storage
209
197
 
210
- Drawings are stored in IndexedDB with the following structure:
198
+ Drawings are stored in IndexedDB:
211
199
 
212
200
  ```typescript
213
201
  interface Drawing {
package/dist/index.d.mts CHANGED
@@ -74,6 +74,8 @@ interface Translations {
74
74
  clickNewToStart: string;
75
75
  modified: string;
76
76
  confirmDelete: string;
77
+ exportWorkspace: string;
78
+ importWorkspace: string;
77
79
  shortcutNewDrawing: string;
78
80
  }
79
81
  /**
@@ -99,7 +101,9 @@ interface WorkspaceContextValue {
99
101
  renameDrawing: (id: string, name: string) => Promise<void>;
100
102
  removeDrawing: (id: string) => Promise<void>;
101
103
  duplicateCurrentDrawing: () => Promise<Drawing | null>;
102
- saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>) => Promise<void>;
104
+ saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
105
+ exportWorkspace: () => Promise<void>;
106
+ importWorkspace: () => Promise<void>;
103
107
  }
104
108
  declare function useWorkspace(): WorkspaceContextValue;
105
109
  /**
@@ -262,6 +266,64 @@ declare function useExcalidrawBridge({ excalidrawAPI, autoSaveInterval, }: UseEx
262
266
  scheduleSave: () => void;
263
267
  };
264
268
 
269
+ /**
270
+ * WorkspaceBridge - Automatic sync between Workspace and Excalidraw
271
+ *
272
+ * This component handles:
273
+ * 1. Loading drawings into Excalidraw when activeDrawing changes
274
+ * 2. Auto-saving Excalidraw changes back to the workspace
275
+ * 3. Saving current drawing before switching to another
276
+ */
277
+ interface ExcalidrawImperativeAPI {
278
+ getSceneElements: () => unknown[];
279
+ getAppState: () => Record<string, unknown>;
280
+ getFiles: () => Record<string, unknown>;
281
+ updateScene: (scene: {
282
+ elements?: unknown[];
283
+ appState?: Record<string, unknown>;
284
+ commitToStore?: boolean;
285
+ }) => void;
286
+ resetScene: () => void;
287
+ scrollToContent: () => void;
288
+ }
289
+ interface WorkspaceBridgeProps {
290
+ /**
291
+ * The Excalidraw imperative API
292
+ */
293
+ excalidrawAPI: ExcalidrawImperativeAPI | null;
294
+ /**
295
+ * Auto-save interval in milliseconds (default: 2000)
296
+ * Set to 0 to disable auto-save
297
+ */
298
+ autoSaveInterval?: number;
299
+ /**
300
+ * Called when a drawing is loaded into Excalidraw
301
+ */
302
+ onDrawingLoad?: (drawingId: string) => void;
303
+ /**
304
+ * Called when a drawing is saved
305
+ */
306
+ onDrawingSave?: (drawingId: string) => void;
307
+ }
308
+ /**
309
+ * WorkspaceBridge component - place this inside your Excalidraw wrapper
310
+ *
311
+ * @example
312
+ * ```tsx
313
+ * const ExcalidrawWrapper = () => {
314
+ * const [excalidrawAPI, setExcalidrawAPI] = useState(null);
315
+ *
316
+ * return (
317
+ * <>
318
+ * <WorkspaceBridge excalidrawAPI={excalidrawAPI} />
319
+ * <Excalidraw excalidrawAPI={setExcalidrawAPI} />
320
+ * </>
321
+ * );
322
+ * };
323
+ * ```
324
+ */
325
+ declare function WorkspaceBridge({ excalidrawAPI, autoSaveInterval, onDrawingLoad, onDrawingSave, }: WorkspaceBridgeProps): null;
326
+
265
327
  interface WorkspacePluginProps {
266
328
  children: ReactNode;
267
329
  defaultSidebarOpen?: boolean;
@@ -285,4 +347,4 @@ interface WorkspacePluginProps {
285
347
  */
286
348
  declare function WorkspacePlugin(props: WorkspacePluginProps): react_jsx_runtime.JSX.Element;
287
349
 
288
- export { type Drawing, DrawingList, DrawingListItem, DrawingsDialog, type DrawingsDialogProps, Sidebar, type SupportedLanguage, type Translations, type Workspace, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, deleteDrawing, duplicateDrawing, getAllDrawings, getDB, getDrawing, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isLanguageSupported, removeDrawingFromWorkspace, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang };
350
+ export { type Drawing, DrawingList, DrawingListItem, DrawingsDialog, type DrawingsDialogProps, type ExcalidrawImperativeAPI, Sidebar, type SupportedLanguage, type Translations, type Workspace, WorkspaceBridge, type WorkspaceBridgeProps, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, deleteDrawing, duplicateDrawing, getAllDrawings, getDB, getDrawing, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isLanguageSupported, removeDrawingFromWorkspace, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang };
package/dist/index.d.ts CHANGED
@@ -74,6 +74,8 @@ interface Translations {
74
74
  clickNewToStart: string;
75
75
  modified: string;
76
76
  confirmDelete: string;
77
+ exportWorkspace: string;
78
+ importWorkspace: string;
77
79
  shortcutNewDrawing: string;
78
80
  }
79
81
  /**
@@ -99,7 +101,9 @@ interface WorkspaceContextValue {
99
101
  renameDrawing: (id: string, name: string) => Promise<void>;
100
102
  removeDrawing: (id: string) => Promise<void>;
101
103
  duplicateCurrentDrawing: () => Promise<Drawing | null>;
102
- saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>) => Promise<void>;
104
+ saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
105
+ exportWorkspace: () => Promise<void>;
106
+ importWorkspace: () => Promise<void>;
103
107
  }
104
108
  declare function useWorkspace(): WorkspaceContextValue;
105
109
  /**
@@ -262,6 +266,64 @@ declare function useExcalidrawBridge({ excalidrawAPI, autoSaveInterval, }: UseEx
262
266
  scheduleSave: () => void;
263
267
  };
264
268
 
269
+ /**
270
+ * WorkspaceBridge - Automatic sync between Workspace and Excalidraw
271
+ *
272
+ * This component handles:
273
+ * 1. Loading drawings into Excalidraw when activeDrawing changes
274
+ * 2. Auto-saving Excalidraw changes back to the workspace
275
+ * 3. Saving current drawing before switching to another
276
+ */
277
+ interface ExcalidrawImperativeAPI {
278
+ getSceneElements: () => unknown[];
279
+ getAppState: () => Record<string, unknown>;
280
+ getFiles: () => Record<string, unknown>;
281
+ updateScene: (scene: {
282
+ elements?: unknown[];
283
+ appState?: Record<string, unknown>;
284
+ commitToStore?: boolean;
285
+ }) => void;
286
+ resetScene: () => void;
287
+ scrollToContent: () => void;
288
+ }
289
+ interface WorkspaceBridgeProps {
290
+ /**
291
+ * The Excalidraw imperative API
292
+ */
293
+ excalidrawAPI: ExcalidrawImperativeAPI | null;
294
+ /**
295
+ * Auto-save interval in milliseconds (default: 2000)
296
+ * Set to 0 to disable auto-save
297
+ */
298
+ autoSaveInterval?: number;
299
+ /**
300
+ * Called when a drawing is loaded into Excalidraw
301
+ */
302
+ onDrawingLoad?: (drawingId: string) => void;
303
+ /**
304
+ * Called when a drawing is saved
305
+ */
306
+ onDrawingSave?: (drawingId: string) => void;
307
+ }
308
+ /**
309
+ * WorkspaceBridge component - place this inside your Excalidraw wrapper
310
+ *
311
+ * @example
312
+ * ```tsx
313
+ * const ExcalidrawWrapper = () => {
314
+ * const [excalidrawAPI, setExcalidrawAPI] = useState(null);
315
+ *
316
+ * return (
317
+ * <>
318
+ * <WorkspaceBridge excalidrawAPI={excalidrawAPI} />
319
+ * <Excalidraw excalidrawAPI={setExcalidrawAPI} />
320
+ * </>
321
+ * );
322
+ * };
323
+ * ```
324
+ */
325
+ declare function WorkspaceBridge({ excalidrawAPI, autoSaveInterval, onDrawingLoad, onDrawingSave, }: WorkspaceBridgeProps): null;
326
+
265
327
  interface WorkspacePluginProps {
266
328
  children: ReactNode;
267
329
  defaultSidebarOpen?: boolean;
@@ -285,4 +347,4 @@ interface WorkspacePluginProps {
285
347
  */
286
348
  declare function WorkspacePlugin(props: WorkspacePluginProps): react_jsx_runtime.JSX.Element;
287
349
 
288
- export { type Drawing, DrawingList, DrawingListItem, DrawingsDialog, type DrawingsDialogProps, Sidebar, type SupportedLanguage, type Translations, type Workspace, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, deleteDrawing, duplicateDrawing, getAllDrawings, getDB, getDrawing, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isLanguageSupported, removeDrawingFromWorkspace, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang };
350
+ export { type Drawing, DrawingList, DrawingListItem, DrawingsDialog, type DrawingsDialogProps, type ExcalidrawImperativeAPI, Sidebar, type SupportedLanguage, type Translations, type Workspace, WorkspaceBridge, type WorkspaceBridgeProps, type WorkspaceContextValue, WorkspaceMenuItems, type WorkspaceMenuItemsProps, WorkspacePlugin, WorkspaceProvider, addDrawingToWorkspace, closeDB, createDrawing, deleteDrawing, duplicateDrawing, getAllDrawings, getDB, getDrawing, getOrCreateDefaultWorkspace, getTranslations, getWorkspace, isLanguageSupported, removeDrawingFromWorkspace, setActiveDrawing, updateDrawing, updateWorkspace, useExcalidrawBridge, useWorkspace, useWorkspaceLang };
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  DrawingListItem: () => DrawingListItem,
35
35
  DrawingsDialog: () => DrawingsDialog,
36
36
  Sidebar: () => Sidebar,
37
+ WorkspaceBridge: () => WorkspaceBridge,
37
38
  WorkspaceMenuItems: () => WorkspaceMenuItems,
38
39
  WorkspacePlugin: () => WorkspacePlugin,
39
40
  WorkspaceProvider: () => WorkspaceProvider,
@@ -211,11 +212,11 @@ var import_react = require("react");
211
212
  // src/i18n/translations.ts
212
213
  var sv = {
213
214
  // Menu
214
- drawings: "Ritningar",
215
+ drawings: "Arbetsyta",
215
216
  newDrawing: "Ny ritning",
216
- manageDrawings: "Hantera ritningar...",
217
+ manageDrawings: "Hantera arbetsyta...",
217
218
  // Dialog
218
- dialogTitle: "Ritningar",
219
+ dialogTitle: "Min Arbetsyta",
219
220
  close: "St\xE4ng",
220
221
  open: "\xD6ppna",
221
222
  rename: "Byt namn",
@@ -228,16 +229,19 @@ var sv = {
228
229
  clickNewToStart: 'Klicka "Ny ritning" f\xF6r att b\xF6rja.',
229
230
  modified: "\xC4ndrad",
230
231
  confirmDelete: "Vill du ta bort denna ritning?",
232
+ // Export/Import
233
+ exportWorkspace: "Exportera",
234
+ importWorkspace: "Importera",
231
235
  // Shortcuts
232
236
  shortcutNewDrawing: "Ctrl+Alt+N"
233
237
  };
234
238
  var en = {
235
239
  // Menu
236
- drawings: "Drawings",
240
+ drawings: "Workspace",
237
241
  newDrawing: "New drawing",
238
- manageDrawings: "Manage drawings...",
242
+ manageDrawings: "Manage workspace...",
239
243
  // Dialog
240
- dialogTitle: "Drawings",
244
+ dialogTitle: "Workspace",
241
245
  close: "Close",
242
246
  open: "Open",
243
247
  rename: "Rename",
@@ -250,6 +254,9 @@ var en = {
250
254
  clickNewToStart: 'Click "New drawing" to start.',
251
255
  modified: "Modified",
252
256
  confirmDelete: "Do you want to delete this drawing?",
257
+ // Export/Import
258
+ exportWorkspace: "Export",
259
+ importWorkspace: "Import",
253
260
  // Shortcuts
254
261
  shortcutNewDrawing: "Ctrl+Alt+N"
255
262
  };
@@ -398,18 +405,83 @@ function WorkspaceProvider({ children, lang = "en" }) {
398
405
  return null;
399
406
  }
400
407
  }, [activeDrawing, workspace]);
401
- const saveCurrentDrawing = (0, import_react.useCallback)(async (elements, appState) => {
408
+ const saveCurrentDrawing = (0, import_react.useCallback)(async (elements, appState, files) => {
402
409
  if (!activeDrawing) return;
403
410
  try {
404
- const updated = await updateDrawing(activeDrawing.id, { elements, appState });
405
- if (updated) {
406
- setActiveDrawing2(updated);
407
- setDrawings((prev) => prev.map((d) => d.id === updated.id ? updated : d));
411
+ const updateData = {
412
+ elements,
413
+ appState
414
+ };
415
+ if (files) {
416
+ updateData.files = files;
408
417
  }
418
+ await updateDrawing(activeDrawing.id, updateData);
409
419
  } catch (err) {
410
420
  setError(err instanceof Error ? err.message : "Failed to save drawing");
411
421
  }
412
422
  }, [activeDrawing]);
423
+ const exportWorkspace = (0, import_react.useCallback)(async () => {
424
+ try {
425
+ const exportData = {
426
+ version: 1,
427
+ name: workspace?.name || "Min Arbetsyta",
428
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
429
+ drawings: drawings.map((d) => ({
430
+ name: d.name,
431
+ elements: d.elements,
432
+ appState: d.appState,
433
+ files: d.files,
434
+ createdAt: d.createdAt,
435
+ updatedAt: d.updatedAt
436
+ }))
437
+ };
438
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" });
439
+ const url = URL.createObjectURL(blob);
440
+ const a = document.createElement("a");
441
+ a.href = url;
442
+ a.download = `arbetsyta-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`;
443
+ document.body.appendChild(a);
444
+ a.click();
445
+ document.body.removeChild(a);
446
+ URL.revokeObjectURL(url);
447
+ } catch (err) {
448
+ setError(err instanceof Error ? err.message : "Failed to export workspace");
449
+ }
450
+ }, [workspace, drawings]);
451
+ const importWorkspace = (0, import_react.useCallback)(async () => {
452
+ if (!workspace) return;
453
+ try {
454
+ const input = document.createElement("input");
455
+ input.type = "file";
456
+ input.accept = ".json";
457
+ const file = await new Promise((resolve) => {
458
+ input.onchange = () => resolve(input.files?.[0] || null);
459
+ input.click();
460
+ });
461
+ if (!file) return;
462
+ const text = await file.text();
463
+ const data = JSON.parse(text);
464
+ if (!data.version || !Array.isArray(data.drawings)) {
465
+ throw new Error("Invalid workspace file");
466
+ }
467
+ for (const d of data.drawings) {
468
+ const drawing = await createDrawing(d.name || t.newDrawing);
469
+ await updateDrawing(drawing.id, {
470
+ elements: d.elements || [],
471
+ appState: d.appState || {},
472
+ files: d.files || {}
473
+ });
474
+ await addDrawingToWorkspace(workspace.id, drawing.id);
475
+ }
476
+ const allDrawings = await getAllDrawings();
477
+ const ws = await getOrCreateDefaultWorkspace();
478
+ const wsDrawings = allDrawings.filter((dr) => ws.drawingIds.includes(dr.id));
479
+ setWorkspace(ws);
480
+ setDrawings(wsDrawings);
481
+ } catch (err) {
482
+ setError(err instanceof Error ? err.message : "Failed to import workspace");
483
+ }
484
+ }, [workspace, t]);
413
485
  const value = {
414
486
  workspace,
415
487
  drawings,
@@ -423,7 +495,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
423
495
  renameDrawing,
424
496
  removeDrawing,
425
497
  duplicateCurrentDrawing,
426
- saveCurrentDrawing
498
+ saveCurrentDrawing,
499
+ exportWorkspace,
500
+ importWorkspace
427
501
  };
428
502
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceContext.Provider, { value, children });
429
503
  }
@@ -747,6 +821,8 @@ var DrawingsDialog = ({
747
821
  createNewDrawing,
748
822
  renameDrawing,
749
823
  removeDrawing,
824
+ exportWorkspace,
825
+ importWorkspace,
750
826
  t: contextT,
751
827
  lang: contextLang
752
828
  } = useWorkspace();
@@ -758,15 +834,13 @@ var DrawingsDialog = ({
758
834
  const handleSelect = (0, import_react5.useCallback)(async (drawing) => {
759
835
  await switchDrawing(drawing.id);
760
836
  onDrawingSelect?.(drawing);
761
- onClose();
762
- }, [switchDrawing, onDrawingSelect, onClose]);
837
+ }, [switchDrawing, onDrawingSelect]);
763
838
  const handleCreate = (0, import_react5.useCallback)(async () => {
764
839
  const newDrawing = await createNewDrawing();
765
840
  if (newDrawing) {
766
841
  onDrawingSelect?.(newDrawing);
767
- onClose();
768
842
  }
769
- }, [createNewDrawing, onDrawingSelect, onClose]);
843
+ }, [createNewDrawing, onDrawingSelect]);
770
844
  const handleStartEdit = (0, import_react5.useCallback)((drawing) => {
771
845
  setEditingId(drawing.id);
772
846
  setEditName(drawing.name);
@@ -1098,26 +1172,67 @@ var DrawingsDialog = ({
1098
1172
  alignItems: "center"
1099
1173
  },
1100
1174
  children: [
1101
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1102
- "button",
1103
- {
1104
- onClick: handleCreate,
1105
- style: {
1106
- padding: "10px 20px",
1107
- fontSize: "14px",
1108
- backgroundColor: "var(--color-primary, #6c63ff)",
1109
- color: "#fff",
1110
- border: "none",
1111
- borderRadius: "6px",
1112
- cursor: "pointer",
1113
- fontWeight: 500
1114
- },
1115
- children: [
1116
- "+ ",
1117
- t.newDrawing
1118
- ]
1119
- }
1120
- ),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1176
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1177
+ "button",
1178
+ {
1179
+ onClick: handleCreate,
1180
+ style: {
1181
+ padding: "10px 20px",
1182
+ fontSize: "14px",
1183
+ backgroundColor: "var(--color-primary, #6c63ff)",
1184
+ color: "#fff",
1185
+ border: "none",
1186
+ borderRadius: "6px",
1187
+ cursor: "pointer",
1188
+ fontWeight: 500
1189
+ },
1190
+ children: [
1191
+ "+ ",
1192
+ t.newDrawing
1193
+ ]
1194
+ }
1195
+ ),
1196
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1197
+ "button",
1198
+ {
1199
+ onClick: importWorkspace,
1200
+ style: {
1201
+ padding: "10px 20px",
1202
+ fontSize: "14px",
1203
+ backgroundColor: "transparent",
1204
+ border: "1px solid var(--default-border-color, #ccc)",
1205
+ borderRadius: "6px",
1206
+ cursor: "pointer",
1207
+ color: "inherit"
1208
+ },
1209
+ children: [
1210
+ "\u{1F4E5} ",
1211
+ t.importWorkspace
1212
+ ]
1213
+ }
1214
+ ),
1215
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1216
+ "button",
1217
+ {
1218
+ onClick: exportWorkspace,
1219
+ style: {
1220
+ padding: "10px 20px",
1221
+ fontSize: "14px",
1222
+ backgroundColor: "transparent",
1223
+ border: "1px solid var(--default-border-color, #ccc)",
1224
+ borderRadius: "6px",
1225
+ cursor: "pointer",
1226
+ color: "inherit"
1227
+ },
1228
+ disabled: drawings.length === 0,
1229
+ children: [
1230
+ "\u{1F4E4} ",
1231
+ t.exportWorkspace
1232
+ ]
1233
+ }
1234
+ )
1235
+ ] }),
1121
1236
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1122
1237
  "button",
1123
1238
  {
@@ -1185,17 +1300,119 @@ function useExcalidrawBridge({
1185
1300
  };
1186
1301
  }
1187
1302
 
1188
- // src/WorkspacePlugin.tsx
1303
+ // src/integration/WorkspaceBridge.tsx
1189
1304
  var import_react7 = require("react");
1305
+ function WorkspaceBridge({
1306
+ excalidrawAPI,
1307
+ autoSaveInterval = 2e3,
1308
+ onDrawingLoad,
1309
+ onDrawingSave
1310
+ }) {
1311
+ const { activeDrawing, saveCurrentDrawing } = useWorkspace();
1312
+ const lastDrawingIdRef = (0, import_react7.useRef)(null);
1313
+ const saveTimeoutRef = (0, import_react7.useRef)(null);
1314
+ const isLoadingRef = (0, import_react7.useRef)(false);
1315
+ const saveDrawing = (0, import_react7.useCallback)(async () => {
1316
+ if (!excalidrawAPI || !activeDrawing || isLoadingRef.current) return;
1317
+ try {
1318
+ const elements = excalidrawAPI.getSceneElements();
1319
+ const appState = excalidrawAPI.getAppState();
1320
+ const files = excalidrawAPI.getFiles();
1321
+ const persistentAppState = {
1322
+ viewBackgroundColor: appState.viewBackgroundColor,
1323
+ zoom: appState.zoom,
1324
+ scrollX: appState.scrollX,
1325
+ scrollY: appState.scrollY
1326
+ // Add other persistent properties as needed
1327
+ };
1328
+ await saveCurrentDrawing(elements, persistentAppState, files);
1329
+ onDrawingSave?.(activeDrawing.id);
1330
+ } catch (error) {
1331
+ console.error("[WorkspaceBridge] Failed to save drawing:", error);
1332
+ }
1333
+ }, [excalidrawAPI, activeDrawing, saveCurrentDrawing, onDrawingSave]);
1334
+ const scheduleSave = (0, import_react7.useCallback)(() => {
1335
+ if (autoSaveInterval <= 0) return;
1336
+ if (saveTimeoutRef.current) {
1337
+ clearTimeout(saveTimeoutRef.current);
1338
+ }
1339
+ saveTimeoutRef.current = setTimeout(saveDrawing, autoSaveInterval);
1340
+ }, [saveDrawing, autoSaveInterval]);
1341
+ (0, import_react7.useEffect)(() => {
1342
+ if (!excalidrawAPI || !activeDrawing) return;
1343
+ if (lastDrawingIdRef.current === activeDrawing.id) return;
1344
+ if (lastDrawingIdRef.current !== null) {
1345
+ if (saveTimeoutRef.current) {
1346
+ clearTimeout(saveTimeoutRef.current);
1347
+ saveTimeoutRef.current = null;
1348
+ }
1349
+ saveDrawing();
1350
+ }
1351
+ isLoadingRef.current = true;
1352
+ lastDrawingIdRef.current = activeDrawing.id;
1353
+ try {
1354
+ excalidrawAPI.updateScene({
1355
+ elements: activeDrawing.elements || [],
1356
+ appState: activeDrawing.appState || {},
1357
+ commitToStore: true
1358
+ });
1359
+ onDrawingLoad?.(activeDrawing.id);
1360
+ } catch (error) {
1361
+ console.error("[WorkspaceBridge] Failed to load drawing:", error);
1362
+ } finally {
1363
+ setTimeout(() => {
1364
+ isLoadingRef.current = false;
1365
+ }, 100);
1366
+ }
1367
+ }, [excalidrawAPI, activeDrawing, saveDrawing, onDrawingLoad]);
1368
+ (0, import_react7.useEffect)(() => {
1369
+ if (!excalidrawAPI || autoSaveInterval <= 0) return;
1370
+ let lastElementCount = 0;
1371
+ let lastChecksum = "";
1372
+ const checkForChanges = () => {
1373
+ if (isLoadingRef.current || !excalidrawAPI) return;
1374
+ try {
1375
+ const elements = excalidrawAPI.getSceneElements();
1376
+ const currentCount = elements.length;
1377
+ const currentChecksum = JSON.stringify(elements.slice(-1));
1378
+ if (currentCount !== lastElementCount || currentChecksum !== lastChecksum) {
1379
+ lastElementCount = currentCount;
1380
+ lastChecksum = currentChecksum;
1381
+ scheduleSave();
1382
+ }
1383
+ } catch {
1384
+ }
1385
+ };
1386
+ const intervalId = setInterval(checkForChanges, 1e3);
1387
+ return () => {
1388
+ clearInterval(intervalId);
1389
+ if (saveTimeoutRef.current) {
1390
+ clearTimeout(saveTimeoutRef.current);
1391
+ }
1392
+ };
1393
+ }, [excalidrawAPI, autoSaveInterval, scheduleSave]);
1394
+ (0, import_react7.useEffect)(() => {
1395
+ return () => {
1396
+ if (saveTimeoutRef.current) {
1397
+ clearTimeout(saveTimeoutRef.current);
1398
+ }
1399
+ saveDrawing();
1400
+ };
1401
+ }, [saveDrawing]);
1402
+ return null;
1403
+ }
1404
+
1405
+ // src/WorkspacePlugin.tsx
1406
+ var import_react8 = require("react");
1190
1407
  var import_jsx_runtime7 = require("react/jsx-runtime");
1191
1408
  function WorkspacePluginInner({
1192
1409
  children,
1193
1410
  defaultSidebarOpen = true,
1194
1411
  sidebarWidth = 250
1195
1412
  }) {
1196
- const [sidebarOpen, setSidebarOpen] = (0, import_react7.useState)(defaultSidebarOpen);
1413
+ const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(defaultSidebarOpen);
1197
1414
  const { activeDrawing } = useWorkspace();
1198
- (0, import_react7.useEffect)(() => {
1415
+ (0, import_react8.useEffect)(() => {
1199
1416
  const handleKeyDown = (e) => {
1200
1417
  if (e.ctrlKey && e.key === "b") {
1201
1418
  e.preventDefault();
@@ -1208,7 +1425,7 @@ function WorkspacePluginInner({
1208
1425
  window.addEventListener("keydown", handleKeyDown);
1209
1426
  return () => window.removeEventListener("keydown", handleKeyDown);
1210
1427
  }, []);
1211
- const handleToggleSidebar = (0, import_react7.useCallback)(() => {
1428
+ const handleToggleSidebar = (0, import_react8.useCallback)(() => {
1212
1429
  setSidebarOpen((prev) => !prev);
1213
1430
  }, []);
1214
1431
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
@@ -1252,6 +1469,7 @@ function WorkspacePlugin(props) {
1252
1469
  DrawingListItem,
1253
1470
  DrawingsDialog,
1254
1471
  Sidebar,
1472
+ WorkspaceBridge,
1255
1473
  WorkspaceMenuItems,
1256
1474
  WorkspacePlugin,
1257
1475
  WorkspaceProvider,
package/dist/index.mjs CHANGED
@@ -150,11 +150,11 @@ import { createContext, useContext, useEffect, useState, useCallback } from "rea
150
150
  // src/i18n/translations.ts
151
151
  var sv = {
152
152
  // Menu
153
- drawings: "Ritningar",
153
+ drawings: "Arbetsyta",
154
154
  newDrawing: "Ny ritning",
155
- manageDrawings: "Hantera ritningar...",
155
+ manageDrawings: "Hantera arbetsyta...",
156
156
  // Dialog
157
- dialogTitle: "Ritningar",
157
+ dialogTitle: "Min Arbetsyta",
158
158
  close: "St\xE4ng",
159
159
  open: "\xD6ppna",
160
160
  rename: "Byt namn",
@@ -167,16 +167,19 @@ var sv = {
167
167
  clickNewToStart: 'Klicka "Ny ritning" f\xF6r att b\xF6rja.',
168
168
  modified: "\xC4ndrad",
169
169
  confirmDelete: "Vill du ta bort denna ritning?",
170
+ // Export/Import
171
+ exportWorkspace: "Exportera",
172
+ importWorkspace: "Importera",
170
173
  // Shortcuts
171
174
  shortcutNewDrawing: "Ctrl+Alt+N"
172
175
  };
173
176
  var en = {
174
177
  // Menu
175
- drawings: "Drawings",
178
+ drawings: "Workspace",
176
179
  newDrawing: "New drawing",
177
- manageDrawings: "Manage drawings...",
180
+ manageDrawings: "Manage workspace...",
178
181
  // Dialog
179
- dialogTitle: "Drawings",
182
+ dialogTitle: "Workspace",
180
183
  close: "Close",
181
184
  open: "Open",
182
185
  rename: "Rename",
@@ -189,6 +192,9 @@ var en = {
189
192
  clickNewToStart: 'Click "New drawing" to start.',
190
193
  modified: "Modified",
191
194
  confirmDelete: "Do you want to delete this drawing?",
195
+ // Export/Import
196
+ exportWorkspace: "Export",
197
+ importWorkspace: "Import",
192
198
  // Shortcuts
193
199
  shortcutNewDrawing: "Ctrl+Alt+N"
194
200
  };
@@ -337,18 +343,83 @@ function WorkspaceProvider({ children, lang = "en" }) {
337
343
  return null;
338
344
  }
339
345
  }, [activeDrawing, workspace]);
340
- const saveCurrentDrawing = useCallback(async (elements, appState) => {
346
+ const saveCurrentDrawing = useCallback(async (elements, appState, files) => {
341
347
  if (!activeDrawing) return;
342
348
  try {
343
- const updated = await updateDrawing(activeDrawing.id, { elements, appState });
344
- if (updated) {
345
- setActiveDrawing2(updated);
346
- setDrawings((prev) => prev.map((d) => d.id === updated.id ? updated : d));
349
+ const updateData = {
350
+ elements,
351
+ appState
352
+ };
353
+ if (files) {
354
+ updateData.files = files;
347
355
  }
356
+ await updateDrawing(activeDrawing.id, updateData);
348
357
  } catch (err) {
349
358
  setError(err instanceof Error ? err.message : "Failed to save drawing");
350
359
  }
351
360
  }, [activeDrawing]);
361
+ const exportWorkspace = useCallback(async () => {
362
+ try {
363
+ const exportData = {
364
+ version: 1,
365
+ name: workspace?.name || "Min Arbetsyta",
366
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
367
+ drawings: drawings.map((d) => ({
368
+ name: d.name,
369
+ elements: d.elements,
370
+ appState: d.appState,
371
+ files: d.files,
372
+ createdAt: d.createdAt,
373
+ updatedAt: d.updatedAt
374
+ }))
375
+ };
376
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: "application/json" });
377
+ const url = URL.createObjectURL(blob);
378
+ const a = document.createElement("a");
379
+ a.href = url;
380
+ a.download = `arbetsyta-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`;
381
+ document.body.appendChild(a);
382
+ a.click();
383
+ document.body.removeChild(a);
384
+ URL.revokeObjectURL(url);
385
+ } catch (err) {
386
+ setError(err instanceof Error ? err.message : "Failed to export workspace");
387
+ }
388
+ }, [workspace, drawings]);
389
+ const importWorkspace = useCallback(async () => {
390
+ if (!workspace) return;
391
+ try {
392
+ const input = document.createElement("input");
393
+ input.type = "file";
394
+ input.accept = ".json";
395
+ const file = await new Promise((resolve) => {
396
+ input.onchange = () => resolve(input.files?.[0] || null);
397
+ input.click();
398
+ });
399
+ if (!file) return;
400
+ const text = await file.text();
401
+ const data = JSON.parse(text);
402
+ if (!data.version || !Array.isArray(data.drawings)) {
403
+ throw new Error("Invalid workspace file");
404
+ }
405
+ for (const d of data.drawings) {
406
+ const drawing = await createDrawing(d.name || t.newDrawing);
407
+ await updateDrawing(drawing.id, {
408
+ elements: d.elements || [],
409
+ appState: d.appState || {},
410
+ files: d.files || {}
411
+ });
412
+ await addDrawingToWorkspace(workspace.id, drawing.id);
413
+ }
414
+ const allDrawings = await getAllDrawings();
415
+ const ws = await getOrCreateDefaultWorkspace();
416
+ const wsDrawings = allDrawings.filter((dr) => ws.drawingIds.includes(dr.id));
417
+ setWorkspace(ws);
418
+ setDrawings(wsDrawings);
419
+ } catch (err) {
420
+ setError(err instanceof Error ? err.message : "Failed to import workspace");
421
+ }
422
+ }, [workspace, t]);
352
423
  const value = {
353
424
  workspace,
354
425
  drawings,
@@ -362,7 +433,9 @@ function WorkspaceProvider({ children, lang = "en" }) {
362
433
  renameDrawing,
363
434
  removeDrawing,
364
435
  duplicateCurrentDrawing,
365
- saveCurrentDrawing
436
+ saveCurrentDrawing,
437
+ exportWorkspace,
438
+ importWorkspace
366
439
  };
367
440
  return /* @__PURE__ */ jsx(WorkspaceContext.Provider, { value, children });
368
441
  }
@@ -686,6 +759,8 @@ var DrawingsDialog = ({
686
759
  createNewDrawing,
687
760
  renameDrawing,
688
761
  removeDrawing,
762
+ exportWorkspace,
763
+ importWorkspace,
689
764
  t: contextT,
690
765
  lang: contextLang
691
766
  } = useWorkspace();
@@ -697,15 +772,13 @@ var DrawingsDialog = ({
697
772
  const handleSelect = useCallback2(async (drawing) => {
698
773
  await switchDrawing(drawing.id);
699
774
  onDrawingSelect?.(drawing);
700
- onClose();
701
- }, [switchDrawing, onDrawingSelect, onClose]);
775
+ }, [switchDrawing, onDrawingSelect]);
702
776
  const handleCreate = useCallback2(async () => {
703
777
  const newDrawing = await createNewDrawing();
704
778
  if (newDrawing) {
705
779
  onDrawingSelect?.(newDrawing);
706
- onClose();
707
780
  }
708
- }, [createNewDrawing, onDrawingSelect, onClose]);
781
+ }, [createNewDrawing, onDrawingSelect]);
709
782
  const handleStartEdit = useCallback2((drawing) => {
710
783
  setEditingId(drawing.id);
711
784
  setEditName(drawing.name);
@@ -1037,26 +1110,67 @@ var DrawingsDialog = ({
1037
1110
  alignItems: "center"
1038
1111
  },
1039
1112
  children: [
1040
- /* @__PURE__ */ jsxs4(
1041
- "button",
1042
- {
1043
- onClick: handleCreate,
1044
- style: {
1045
- padding: "10px 20px",
1046
- fontSize: "14px",
1047
- backgroundColor: "var(--color-primary, #6c63ff)",
1048
- color: "#fff",
1049
- border: "none",
1050
- borderRadius: "6px",
1051
- cursor: "pointer",
1052
- fontWeight: 500
1053
- },
1054
- children: [
1055
- "+ ",
1056
- t.newDrawing
1057
- ]
1058
- }
1059
- ),
1113
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "8px" }, children: [
1114
+ /* @__PURE__ */ jsxs4(
1115
+ "button",
1116
+ {
1117
+ onClick: handleCreate,
1118
+ style: {
1119
+ padding: "10px 20px",
1120
+ fontSize: "14px",
1121
+ backgroundColor: "var(--color-primary, #6c63ff)",
1122
+ color: "#fff",
1123
+ border: "none",
1124
+ borderRadius: "6px",
1125
+ cursor: "pointer",
1126
+ fontWeight: 500
1127
+ },
1128
+ children: [
1129
+ "+ ",
1130
+ t.newDrawing
1131
+ ]
1132
+ }
1133
+ ),
1134
+ /* @__PURE__ */ jsxs4(
1135
+ "button",
1136
+ {
1137
+ onClick: importWorkspace,
1138
+ style: {
1139
+ padding: "10px 20px",
1140
+ fontSize: "14px",
1141
+ backgroundColor: "transparent",
1142
+ border: "1px solid var(--default-border-color, #ccc)",
1143
+ borderRadius: "6px",
1144
+ cursor: "pointer",
1145
+ color: "inherit"
1146
+ },
1147
+ children: [
1148
+ "\u{1F4E5} ",
1149
+ t.importWorkspace
1150
+ ]
1151
+ }
1152
+ ),
1153
+ /* @__PURE__ */ jsxs4(
1154
+ "button",
1155
+ {
1156
+ onClick: exportWorkspace,
1157
+ style: {
1158
+ padding: "10px 20px",
1159
+ fontSize: "14px",
1160
+ backgroundColor: "transparent",
1161
+ border: "1px solid var(--default-border-color, #ccc)",
1162
+ borderRadius: "6px",
1163
+ cursor: "pointer",
1164
+ color: "inherit"
1165
+ },
1166
+ disabled: drawings.length === 0,
1167
+ children: [
1168
+ "\u{1F4E4} ",
1169
+ t.exportWorkspace
1170
+ ]
1171
+ }
1172
+ )
1173
+ ] }),
1060
1174
  /* @__PURE__ */ jsx6(
1061
1175
  "button",
1062
1176
  {
@@ -1124,8 +1238,110 @@ function useExcalidrawBridge({
1124
1238
  };
1125
1239
  }
1126
1240
 
1241
+ // src/integration/WorkspaceBridge.tsx
1242
+ import { useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4 } from "react";
1243
+ function WorkspaceBridge({
1244
+ excalidrawAPI,
1245
+ autoSaveInterval = 2e3,
1246
+ onDrawingLoad,
1247
+ onDrawingSave
1248
+ }) {
1249
+ const { activeDrawing, saveCurrentDrawing } = useWorkspace();
1250
+ const lastDrawingIdRef = useRef3(null);
1251
+ const saveTimeoutRef = useRef3(null);
1252
+ const isLoadingRef = useRef3(false);
1253
+ const saveDrawing = useCallback4(async () => {
1254
+ if (!excalidrawAPI || !activeDrawing || isLoadingRef.current) return;
1255
+ try {
1256
+ const elements = excalidrawAPI.getSceneElements();
1257
+ const appState = excalidrawAPI.getAppState();
1258
+ const files = excalidrawAPI.getFiles();
1259
+ const persistentAppState = {
1260
+ viewBackgroundColor: appState.viewBackgroundColor,
1261
+ zoom: appState.zoom,
1262
+ scrollX: appState.scrollX,
1263
+ scrollY: appState.scrollY
1264
+ // Add other persistent properties as needed
1265
+ };
1266
+ await saveCurrentDrawing(elements, persistentAppState, files);
1267
+ onDrawingSave?.(activeDrawing.id);
1268
+ } catch (error) {
1269
+ console.error("[WorkspaceBridge] Failed to save drawing:", error);
1270
+ }
1271
+ }, [excalidrawAPI, activeDrawing, saveCurrentDrawing, onDrawingSave]);
1272
+ const scheduleSave = useCallback4(() => {
1273
+ if (autoSaveInterval <= 0) return;
1274
+ if (saveTimeoutRef.current) {
1275
+ clearTimeout(saveTimeoutRef.current);
1276
+ }
1277
+ saveTimeoutRef.current = setTimeout(saveDrawing, autoSaveInterval);
1278
+ }, [saveDrawing, autoSaveInterval]);
1279
+ useEffect4(() => {
1280
+ if (!excalidrawAPI || !activeDrawing) return;
1281
+ if (lastDrawingIdRef.current === activeDrawing.id) return;
1282
+ if (lastDrawingIdRef.current !== null) {
1283
+ if (saveTimeoutRef.current) {
1284
+ clearTimeout(saveTimeoutRef.current);
1285
+ saveTimeoutRef.current = null;
1286
+ }
1287
+ saveDrawing();
1288
+ }
1289
+ isLoadingRef.current = true;
1290
+ lastDrawingIdRef.current = activeDrawing.id;
1291
+ try {
1292
+ excalidrawAPI.updateScene({
1293
+ elements: activeDrawing.elements || [],
1294
+ appState: activeDrawing.appState || {},
1295
+ commitToStore: true
1296
+ });
1297
+ onDrawingLoad?.(activeDrawing.id);
1298
+ } catch (error) {
1299
+ console.error("[WorkspaceBridge] Failed to load drawing:", error);
1300
+ } finally {
1301
+ setTimeout(() => {
1302
+ isLoadingRef.current = false;
1303
+ }, 100);
1304
+ }
1305
+ }, [excalidrawAPI, activeDrawing, saveDrawing, onDrawingLoad]);
1306
+ useEffect4(() => {
1307
+ if (!excalidrawAPI || autoSaveInterval <= 0) return;
1308
+ let lastElementCount = 0;
1309
+ let lastChecksum = "";
1310
+ const checkForChanges = () => {
1311
+ if (isLoadingRef.current || !excalidrawAPI) return;
1312
+ try {
1313
+ const elements = excalidrawAPI.getSceneElements();
1314
+ const currentCount = elements.length;
1315
+ const currentChecksum = JSON.stringify(elements.slice(-1));
1316
+ if (currentCount !== lastElementCount || currentChecksum !== lastChecksum) {
1317
+ lastElementCount = currentCount;
1318
+ lastChecksum = currentChecksum;
1319
+ scheduleSave();
1320
+ }
1321
+ } catch {
1322
+ }
1323
+ };
1324
+ const intervalId = setInterval(checkForChanges, 1e3);
1325
+ return () => {
1326
+ clearInterval(intervalId);
1327
+ if (saveTimeoutRef.current) {
1328
+ clearTimeout(saveTimeoutRef.current);
1329
+ }
1330
+ };
1331
+ }, [excalidrawAPI, autoSaveInterval, scheduleSave]);
1332
+ useEffect4(() => {
1333
+ return () => {
1334
+ if (saveTimeoutRef.current) {
1335
+ clearTimeout(saveTimeoutRef.current);
1336
+ }
1337
+ saveDrawing();
1338
+ };
1339
+ }, [saveDrawing]);
1340
+ return null;
1341
+ }
1342
+
1127
1343
  // src/WorkspacePlugin.tsx
1128
- import { useState as useState5, useEffect as useEffect4, useCallback as useCallback4 } from "react";
1344
+ import { useState as useState5, useEffect as useEffect5, useCallback as useCallback5 } from "react";
1129
1345
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1130
1346
  function WorkspacePluginInner({
1131
1347
  children,
@@ -1134,7 +1350,7 @@ function WorkspacePluginInner({
1134
1350
  }) {
1135
1351
  const [sidebarOpen, setSidebarOpen] = useState5(defaultSidebarOpen);
1136
1352
  const { activeDrawing } = useWorkspace();
1137
- useEffect4(() => {
1353
+ useEffect5(() => {
1138
1354
  const handleKeyDown = (e) => {
1139
1355
  if (e.ctrlKey && e.key === "b") {
1140
1356
  e.preventDefault();
@@ -1147,7 +1363,7 @@ function WorkspacePluginInner({
1147
1363
  window.addEventListener("keydown", handleKeyDown);
1148
1364
  return () => window.removeEventListener("keydown", handleKeyDown);
1149
1365
  }, []);
1150
- const handleToggleSidebar = useCallback4(() => {
1366
+ const handleToggleSidebar = useCallback5(() => {
1151
1367
  setSidebarOpen((prev) => !prev);
1152
1368
  }, []);
1153
1369
  return /* @__PURE__ */ jsxs5(
@@ -1190,6 +1406,7 @@ export {
1190
1406
  DrawingListItem,
1191
1407
  DrawingsDialog,
1192
1408
  Sidebar,
1409
+ WorkspaceBridge,
1193
1410
  WorkspaceMenuItems,
1194
1411
  WorkspacePlugin,
1195
1412
  WorkspaceProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rita-workspace",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "Multi-drawing workspace feature for Rita (Excalidraw fork)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",