rita-workspace 0.3.1 → 0.4.0

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,7 +7,9 @@ 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
12
+ - **i18n support** - Swedish and English with automatic Excalidraw language sync
11
13
 
12
14
  ## Installation
13
15
 
@@ -19,99 +21,127 @@ yarn add rita-workspace
19
21
 
20
22
  ## Integration Guide
21
23
 
22
- Two files need to be modified in the B310/Excalidraw fork:
24
+ Three files need to be modified in the B310/Excalidraw fork:
23
25
 
24
- ### 1. `excalidraw-app/App.tsx`
26
+ ### 1. `excalidraw-app/App.tsx` - Add Provider and Bridge
25
27
 
26
- **Add import** (at the top with other imports):
28
+ **Add imports:**
27
29
 
28
30
  ```tsx
29
- import { WorkspaceProvider } from "rita-workspace";
31
+ import { WorkspaceProvider, WorkspaceBridge } from "rita-workspace";
30
32
  ```
31
33
 
32
- **Wrap with WorkspaceProvider** (in the `ExcalidrawApp` component):
34
+ **Wrap ExcalidrawApp with WorkspaceProvider:**
33
35
 
34
36
  ```tsx
35
37
  const ExcalidrawApp = () => {
36
- // ...
37
38
  return (
38
39
  <TopErrorBoundary>
39
40
  <Provider store={appJotaiStore}>
40
- <WorkspaceProvider> {/* <-- Add this */}
41
+ <WorkspaceProvider lang="sv"> {/* <-- Add this */}
41
42
  <ExcalidrawWrapper />
42
- </WorkspaceProvider> {/* <-- And this */}
43
+ </WorkspaceProvider> {/* <-- And this */}
43
44
  </Provider>
44
45
  </TopErrorBoundary>
45
46
  );
46
47
  };
47
48
  ```
48
49
 
49
- ### 2. `excalidraw-app/components/AppMainMenu.tsx`
50
+ **Add WorkspaceBridge inside ExcalidrawWrapper** (this syncs the canvas automatically):
50
51
 
51
- **Add imports** (at the top):
52
+ ```tsx
53
+ const ExcalidrawWrapper = () => {
54
+ const [excalidrawAPI, excalidrawRefCallback] =
55
+ useCallbackRefState<ExcalidrawImperativeAPI>();
56
+
57
+ // ... existing code ...
58
+
59
+ return (
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>
69
+ );
70
+ };
71
+ ```
72
+
73
+ ### 2. `excalidraw-app/components/AppMainMenu.tsx` - Add Menu Items
74
+
75
+ **Add imports:**
52
76
 
53
77
  ```tsx
54
- import React, { useState } from "react"; // Add useState
78
+ import React, { useState } from "react";
55
79
 
56
- // Add after other imports:
57
80
  import { WorkspaceMenuItems, DrawingsDialog } from "rita-workspace";
81
+ import { LoadIcon } from "../components/icons"; // Excalidraw's folder icon
58
82
  ```
59
83
 
60
- **Add state and menu items** (inside the component):
84
+ **Add state and menu items:**
61
85
 
62
86
  ```tsx
63
87
  export const AppMainMenu: React.FC<{...}> = React.memo((props) => {
64
- const [showDrawingsDialog, setShowDrawingsDialog] = useState(false); // Add this
88
+ const [showDrawingsDialog, setShowDrawingsDialog] = useState(false);
65
89
 
66
90
  return (
67
- <> {/* Add fragment */}
91
+ <>
68
92
  <MainMenu>
69
93
  <MainMenu.DefaultItems.LoadScene />
70
94
  <MainMenu.DefaultItems.SaveToActiveFile />
71
95
 
72
- {/* === RITA WORKSPACE: Add this block === */}
96
+ {/* === RITA WORKSPACE === */}
73
97
  <MainMenu.Sub>
74
- <MainMenu.Sub.Trigger>📄 Ritningar</MainMenu.Sub.Trigger>
98
+ <MainMenu.Sub.Trigger>{LoadIcon} Arbetsyta</MainMenu.Sub.Trigger>
75
99
  <MainMenu.Sub.Content>
76
100
  <WorkspaceMenuItems
77
101
  onManageDrawings={() => setShowDrawingsDialog(true)}
78
102
  />
79
103
  </MainMenu.Sub.Content>
80
104
  </MainMenu.Sub>
81
- {/* === END RITA WORKSPACE === */}
82
105
 
83
106
  <MainMenu.DefaultItems.Export />
84
107
  {/* ... rest of menu items ... */}
85
108
  </MainMenu>
86
109
 
87
- {/* === RITA WORKSPACE: Add dialog === */}
88
110
  <DrawingsDialog
89
111
  open={showDrawingsDialog}
90
112
  onClose={() => setShowDrawingsDialog(false)}
91
113
  />
92
- </> {/* Close fragment */}
114
+ </>
93
115
  );
94
116
  });
95
117
  ```
96
118
 
97
- ## Result
119
+ ## How It Works
98
120
 
99
- After integration, a "📄 Ritningar" submenu appears in the hamburger menu:
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)
100
128
 
129
+ ## Language Support (i18n)
130
+
131
+ Pass Excalidraw's `langCode` to `WorkspaceProvider`:
132
+
133
+ ```tsx
134
+ const [langCode] = useAppLangCode();
135
+
136
+ <WorkspaceProvider lang={langCode}>
137
+ {/* All components automatically use the same language */}
138
+ </WorkspaceProvider>
101
139
  ```
102
- ┌─────────────────────────┐
103
- 📂 Open │
104
- │ 💾 Save │
105
- 📄 Ritningar ▶ │──┐
106
- 📤 Export │ │ ┌─────────────────────┐
107
- │ ... │ └──│ ✓ Current drawing │
108
- └─────────────────────────┘ │ Sketch 2 │
109
- │ Project X │
110
- │ ─────────────────── │
111
- │ + Ny ritning │
112
- │ 📄 Hantera... │
113
- └─────────────────────┘
114
- ```
140
+
141
+ | Code | Language |
142
+ |------|----------|
143
+ | `sv`, `sv-SE` | 🇸🇪 Swedish |
144
+ | `en`, `en-US` | 🇬🇧 English (default) |
115
145
 
116
146
  ## API Reference
117
147
 
@@ -119,24 +149,32 @@ After integration, a "📄 Ritningar" submenu appears in the hamburger menu:
119
149
 
120
150
  | Component | Description |
121
151
  |-----------|-------------|
122
- | `WorkspaceProvider` | React context provider - wrap your app with this |
123
- | `WorkspaceMenuItems` | Menu items for Excalidraw's MainMenu |
124
- | `DrawingsDialog` | Modal dialog for managing all drawings |
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` |
125
156
 
126
157
  ### Hooks
127
158
 
128
159
  | Hook | Description |
129
160
  |------|-------------|
130
161
  | `useWorkspace()` | Access workspace state and actions |
162
+ | `useWorkspaceLang()` | Get current language and translations |
131
163
 
132
164
  ### useWorkspace() returns
133
165
 
134
166
  ```tsx
135
167
  const {
168
+ // State
136
169
  drawings, // Drawing[] - all drawings
137
170
  activeDrawing, // Drawing | null - currently active
138
171
  isLoading, // boolean
139
- createNewDrawing, // () => Promise<Drawing>
172
+ error, // string | null
173
+ lang, // string - current language code
174
+ t, // Translations object
175
+
176
+ // Actions
177
+ createNewDrawing, // (name?: string) => Promise<Drawing>
140
178
  switchDrawing, // (id: string) => Promise<void>
141
179
  renameDrawing, // (id: string, name: string) => Promise<void>
142
180
  removeDrawing, // (id: string) => Promise<void>
@@ -144,9 +182,20 @@ const {
144
182
  } = useWorkspace();
145
183
  ```
146
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
+
147
196
  ## Data Storage
148
197
 
149
- Drawings are stored in IndexedDB with the following structure:
198
+ Drawings are stored in IndexedDB:
150
199
 
151
200
  ```typescript
152
201
  interface Drawing {
package/dist/index.d.mts CHANGED
@@ -99,7 +99,7 @@ interface WorkspaceContextValue {
99
99
  renameDrawing: (id: string, name: string) => Promise<void>;
100
100
  removeDrawing: (id: string) => Promise<void>;
101
101
  duplicateCurrentDrawing: () => Promise<Drawing | null>;
102
- saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>) => Promise<void>;
102
+ saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
103
103
  }
104
104
  declare function useWorkspace(): WorkspaceContextValue;
105
105
  /**
@@ -262,6 +262,64 @@ declare function useExcalidrawBridge({ excalidrawAPI, autoSaveInterval, }: UseEx
262
262
  scheduleSave: () => void;
263
263
  };
264
264
 
265
+ /**
266
+ * WorkspaceBridge - Automatic sync between Workspace and Excalidraw
267
+ *
268
+ * This component handles:
269
+ * 1. Loading drawings into Excalidraw when activeDrawing changes
270
+ * 2. Auto-saving Excalidraw changes back to the workspace
271
+ * 3. Saving current drawing before switching to another
272
+ */
273
+ interface ExcalidrawImperativeAPI {
274
+ getSceneElements: () => unknown[];
275
+ getAppState: () => Record<string, unknown>;
276
+ getFiles: () => Record<string, unknown>;
277
+ updateScene: (scene: {
278
+ elements?: unknown[];
279
+ appState?: Record<string, unknown>;
280
+ commitToStore?: boolean;
281
+ }) => void;
282
+ resetScene: () => void;
283
+ scrollToContent: () => void;
284
+ }
285
+ interface WorkspaceBridgeProps {
286
+ /**
287
+ * The Excalidraw imperative API
288
+ */
289
+ excalidrawAPI: ExcalidrawImperativeAPI | null;
290
+ /**
291
+ * Auto-save interval in milliseconds (default: 2000)
292
+ * Set to 0 to disable auto-save
293
+ */
294
+ autoSaveInterval?: number;
295
+ /**
296
+ * Called when a drawing is loaded into Excalidraw
297
+ */
298
+ onDrawingLoad?: (drawingId: string) => void;
299
+ /**
300
+ * Called when a drawing is saved
301
+ */
302
+ onDrawingSave?: (drawingId: string) => void;
303
+ }
304
+ /**
305
+ * WorkspaceBridge component - place this inside your Excalidraw wrapper
306
+ *
307
+ * @example
308
+ * ```tsx
309
+ * const ExcalidrawWrapper = () => {
310
+ * const [excalidrawAPI, setExcalidrawAPI] = useState(null);
311
+ *
312
+ * return (
313
+ * <>
314
+ * <WorkspaceBridge excalidrawAPI={excalidrawAPI} />
315
+ * <Excalidraw excalidrawAPI={setExcalidrawAPI} />
316
+ * </>
317
+ * );
318
+ * };
319
+ * ```
320
+ */
321
+ declare function WorkspaceBridge({ excalidrawAPI, autoSaveInterval, onDrawingLoad, onDrawingSave, }: WorkspaceBridgeProps): null;
322
+
265
323
  interface WorkspacePluginProps {
266
324
  children: ReactNode;
267
325
  defaultSidebarOpen?: boolean;
@@ -285,4 +343,4 @@ interface WorkspacePluginProps {
285
343
  */
286
344
  declare function WorkspacePlugin(props: WorkspacePluginProps): react_jsx_runtime.JSX.Element;
287
345
 
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 };
346
+ 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
@@ -99,7 +99,7 @@ interface WorkspaceContextValue {
99
99
  renameDrawing: (id: string, name: string) => Promise<void>;
100
100
  removeDrawing: (id: string) => Promise<void>;
101
101
  duplicateCurrentDrawing: () => Promise<Drawing | null>;
102
- saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>) => Promise<void>;
102
+ saveCurrentDrawing: (elements: unknown[], appState: Record<string, unknown>, files?: Record<string, unknown>) => Promise<void>;
103
103
  }
104
104
  declare function useWorkspace(): WorkspaceContextValue;
105
105
  /**
@@ -262,6 +262,64 @@ declare function useExcalidrawBridge({ excalidrawAPI, autoSaveInterval, }: UseEx
262
262
  scheduleSave: () => void;
263
263
  };
264
264
 
265
+ /**
266
+ * WorkspaceBridge - Automatic sync between Workspace and Excalidraw
267
+ *
268
+ * This component handles:
269
+ * 1. Loading drawings into Excalidraw when activeDrawing changes
270
+ * 2. Auto-saving Excalidraw changes back to the workspace
271
+ * 3. Saving current drawing before switching to another
272
+ */
273
+ interface ExcalidrawImperativeAPI {
274
+ getSceneElements: () => unknown[];
275
+ getAppState: () => Record<string, unknown>;
276
+ getFiles: () => Record<string, unknown>;
277
+ updateScene: (scene: {
278
+ elements?: unknown[];
279
+ appState?: Record<string, unknown>;
280
+ commitToStore?: boolean;
281
+ }) => void;
282
+ resetScene: () => void;
283
+ scrollToContent: () => void;
284
+ }
285
+ interface WorkspaceBridgeProps {
286
+ /**
287
+ * The Excalidraw imperative API
288
+ */
289
+ excalidrawAPI: ExcalidrawImperativeAPI | null;
290
+ /**
291
+ * Auto-save interval in milliseconds (default: 2000)
292
+ * Set to 0 to disable auto-save
293
+ */
294
+ autoSaveInterval?: number;
295
+ /**
296
+ * Called when a drawing is loaded into Excalidraw
297
+ */
298
+ onDrawingLoad?: (drawingId: string) => void;
299
+ /**
300
+ * Called when a drawing is saved
301
+ */
302
+ onDrawingSave?: (drawingId: string) => void;
303
+ }
304
+ /**
305
+ * WorkspaceBridge component - place this inside your Excalidraw wrapper
306
+ *
307
+ * @example
308
+ * ```tsx
309
+ * const ExcalidrawWrapper = () => {
310
+ * const [excalidrawAPI, setExcalidrawAPI] = useState(null);
311
+ *
312
+ * return (
313
+ * <>
314
+ * <WorkspaceBridge excalidrawAPI={excalidrawAPI} />
315
+ * <Excalidraw excalidrawAPI={setExcalidrawAPI} />
316
+ * </>
317
+ * );
318
+ * };
319
+ * ```
320
+ */
321
+ declare function WorkspaceBridge({ excalidrawAPI, autoSaveInterval, onDrawingLoad, onDrawingSave, }: WorkspaceBridgeProps): null;
322
+
265
323
  interface WorkspacePluginProps {
266
324
  children: ReactNode;
267
325
  defaultSidebarOpen?: boolean;
@@ -285,4 +343,4 @@ interface WorkspacePluginProps {
285
343
  */
286
344
  declare function WorkspacePlugin(props: WorkspacePluginProps): react_jsx_runtime.JSX.Element;
287
345
 
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 };
346
+ 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: "Arbetsyta",
219
220
  close: "St\xE4ng",
220
221
  open: "\xD6ppna",
221
222
  rename: "Byt namn",
@@ -233,11 +234,11 @@ var sv = {
233
234
  };
234
235
  var en = {
235
236
  // Menu
236
- drawings: "Drawings",
237
+ drawings: "Workspace",
237
238
  newDrawing: "New drawing",
238
- manageDrawings: "Manage drawings...",
239
+ manageDrawings: "Manage workspace...",
239
240
  // Dialog
240
- dialogTitle: "Drawings",
241
+ dialogTitle: "Workspace",
241
242
  close: "Close",
242
243
  open: "Open",
243
244
  rename: "Rename",
@@ -398,10 +399,17 @@ function WorkspaceProvider({ children, lang = "en" }) {
398
399
  return null;
399
400
  }
400
401
  }, [activeDrawing, workspace]);
401
- const saveCurrentDrawing = (0, import_react.useCallback)(async (elements, appState) => {
402
+ const saveCurrentDrawing = (0, import_react.useCallback)(async (elements, appState, files) => {
402
403
  if (!activeDrawing) return;
403
404
  try {
404
- const updated = await updateDrawing(activeDrawing.id, { elements, appState });
405
+ const updateData = {
406
+ elements,
407
+ appState
408
+ };
409
+ if (files) {
410
+ updateData.files = files;
411
+ }
412
+ const updated = await updateDrawing(activeDrawing.id, updateData);
405
413
  if (updated) {
406
414
  setActiveDrawing2(updated);
407
415
  setDrawings((prev) => prev.map((d) => d.id === updated.id ? updated : d));
@@ -1185,17 +1193,119 @@ function useExcalidrawBridge({
1185
1193
  };
1186
1194
  }
1187
1195
 
1188
- // src/WorkspacePlugin.tsx
1196
+ // src/integration/WorkspaceBridge.tsx
1189
1197
  var import_react7 = require("react");
1198
+ function WorkspaceBridge({
1199
+ excalidrawAPI,
1200
+ autoSaveInterval = 2e3,
1201
+ onDrawingLoad,
1202
+ onDrawingSave
1203
+ }) {
1204
+ const { activeDrawing, saveCurrentDrawing } = useWorkspace();
1205
+ const lastDrawingIdRef = (0, import_react7.useRef)(null);
1206
+ const saveTimeoutRef = (0, import_react7.useRef)(null);
1207
+ const isLoadingRef = (0, import_react7.useRef)(false);
1208
+ const saveDrawing = (0, import_react7.useCallback)(async () => {
1209
+ if (!excalidrawAPI || !activeDrawing || isLoadingRef.current) return;
1210
+ try {
1211
+ const elements = excalidrawAPI.getSceneElements();
1212
+ const appState = excalidrawAPI.getAppState();
1213
+ const files = excalidrawAPI.getFiles();
1214
+ const persistentAppState = {
1215
+ viewBackgroundColor: appState.viewBackgroundColor,
1216
+ zoom: appState.zoom,
1217
+ scrollX: appState.scrollX,
1218
+ scrollY: appState.scrollY
1219
+ // Add other persistent properties as needed
1220
+ };
1221
+ await saveCurrentDrawing(elements, persistentAppState, files);
1222
+ onDrawingSave?.(activeDrawing.id);
1223
+ } catch (error) {
1224
+ console.error("[WorkspaceBridge] Failed to save drawing:", error);
1225
+ }
1226
+ }, [excalidrawAPI, activeDrawing, saveCurrentDrawing, onDrawingSave]);
1227
+ const scheduleSave = (0, import_react7.useCallback)(() => {
1228
+ if (autoSaveInterval <= 0) return;
1229
+ if (saveTimeoutRef.current) {
1230
+ clearTimeout(saveTimeoutRef.current);
1231
+ }
1232
+ saveTimeoutRef.current = setTimeout(saveDrawing, autoSaveInterval);
1233
+ }, [saveDrawing, autoSaveInterval]);
1234
+ (0, import_react7.useEffect)(() => {
1235
+ if (!excalidrawAPI || !activeDrawing) return;
1236
+ if (lastDrawingIdRef.current === activeDrawing.id) return;
1237
+ if (lastDrawingIdRef.current !== null) {
1238
+ if (saveTimeoutRef.current) {
1239
+ clearTimeout(saveTimeoutRef.current);
1240
+ saveTimeoutRef.current = null;
1241
+ }
1242
+ saveDrawing();
1243
+ }
1244
+ isLoadingRef.current = true;
1245
+ lastDrawingIdRef.current = activeDrawing.id;
1246
+ try {
1247
+ excalidrawAPI.updateScene({
1248
+ elements: activeDrawing.elements || [],
1249
+ appState: activeDrawing.appState || {},
1250
+ commitToStore: true
1251
+ });
1252
+ onDrawingLoad?.(activeDrawing.id);
1253
+ } catch (error) {
1254
+ console.error("[WorkspaceBridge] Failed to load drawing:", error);
1255
+ } finally {
1256
+ setTimeout(() => {
1257
+ isLoadingRef.current = false;
1258
+ }, 100);
1259
+ }
1260
+ }, [excalidrawAPI, activeDrawing, saveDrawing, onDrawingLoad]);
1261
+ (0, import_react7.useEffect)(() => {
1262
+ if (!excalidrawAPI || autoSaveInterval <= 0) return;
1263
+ let lastElementCount = 0;
1264
+ let lastChecksum = "";
1265
+ const checkForChanges = () => {
1266
+ if (isLoadingRef.current || !excalidrawAPI) return;
1267
+ try {
1268
+ const elements = excalidrawAPI.getSceneElements();
1269
+ const currentCount = elements.length;
1270
+ const currentChecksum = JSON.stringify(elements.slice(-1));
1271
+ if (currentCount !== lastElementCount || currentChecksum !== lastChecksum) {
1272
+ lastElementCount = currentCount;
1273
+ lastChecksum = currentChecksum;
1274
+ scheduleSave();
1275
+ }
1276
+ } catch {
1277
+ }
1278
+ };
1279
+ const intervalId = setInterval(checkForChanges, 1e3);
1280
+ return () => {
1281
+ clearInterval(intervalId);
1282
+ if (saveTimeoutRef.current) {
1283
+ clearTimeout(saveTimeoutRef.current);
1284
+ }
1285
+ };
1286
+ }, [excalidrawAPI, autoSaveInterval, scheduleSave]);
1287
+ (0, import_react7.useEffect)(() => {
1288
+ return () => {
1289
+ if (saveTimeoutRef.current) {
1290
+ clearTimeout(saveTimeoutRef.current);
1291
+ }
1292
+ saveDrawing();
1293
+ };
1294
+ }, [saveDrawing]);
1295
+ return null;
1296
+ }
1297
+
1298
+ // src/WorkspacePlugin.tsx
1299
+ var import_react8 = require("react");
1190
1300
  var import_jsx_runtime7 = require("react/jsx-runtime");
1191
1301
  function WorkspacePluginInner({
1192
1302
  children,
1193
1303
  defaultSidebarOpen = true,
1194
1304
  sidebarWidth = 250
1195
1305
  }) {
1196
- const [sidebarOpen, setSidebarOpen] = (0, import_react7.useState)(defaultSidebarOpen);
1306
+ const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(defaultSidebarOpen);
1197
1307
  const { activeDrawing } = useWorkspace();
1198
- (0, import_react7.useEffect)(() => {
1308
+ (0, import_react8.useEffect)(() => {
1199
1309
  const handleKeyDown = (e) => {
1200
1310
  if (e.ctrlKey && e.key === "b") {
1201
1311
  e.preventDefault();
@@ -1208,7 +1318,7 @@ function WorkspacePluginInner({
1208
1318
  window.addEventListener("keydown", handleKeyDown);
1209
1319
  return () => window.removeEventListener("keydown", handleKeyDown);
1210
1320
  }, []);
1211
- const handleToggleSidebar = (0, import_react7.useCallback)(() => {
1321
+ const handleToggleSidebar = (0, import_react8.useCallback)(() => {
1212
1322
  setSidebarOpen((prev) => !prev);
1213
1323
  }, []);
1214
1324
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
@@ -1252,6 +1362,7 @@ function WorkspacePlugin(props) {
1252
1362
  DrawingListItem,
1253
1363
  DrawingsDialog,
1254
1364
  Sidebar,
1365
+ WorkspaceBridge,
1255
1366
  WorkspaceMenuItems,
1256
1367
  WorkspacePlugin,
1257
1368
  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: "Arbetsyta",
158
158
  close: "St\xE4ng",
159
159
  open: "\xD6ppna",
160
160
  rename: "Byt namn",
@@ -172,11 +172,11 @@ var sv = {
172
172
  };
173
173
  var en = {
174
174
  // Menu
175
- drawings: "Drawings",
175
+ drawings: "Workspace",
176
176
  newDrawing: "New drawing",
177
- manageDrawings: "Manage drawings...",
177
+ manageDrawings: "Manage workspace...",
178
178
  // Dialog
179
- dialogTitle: "Drawings",
179
+ dialogTitle: "Workspace",
180
180
  close: "Close",
181
181
  open: "Open",
182
182
  rename: "Rename",
@@ -337,10 +337,17 @@ function WorkspaceProvider({ children, lang = "en" }) {
337
337
  return null;
338
338
  }
339
339
  }, [activeDrawing, workspace]);
340
- const saveCurrentDrawing = useCallback(async (elements, appState) => {
340
+ const saveCurrentDrawing = useCallback(async (elements, appState, files) => {
341
341
  if (!activeDrawing) return;
342
342
  try {
343
- const updated = await updateDrawing(activeDrawing.id, { elements, appState });
343
+ const updateData = {
344
+ elements,
345
+ appState
346
+ };
347
+ if (files) {
348
+ updateData.files = files;
349
+ }
350
+ const updated = await updateDrawing(activeDrawing.id, updateData);
344
351
  if (updated) {
345
352
  setActiveDrawing2(updated);
346
353
  setDrawings((prev) => prev.map((d) => d.id === updated.id ? updated : d));
@@ -1124,8 +1131,110 @@ function useExcalidrawBridge({
1124
1131
  };
1125
1132
  }
1126
1133
 
1134
+ // src/integration/WorkspaceBridge.tsx
1135
+ import { useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4 } from "react";
1136
+ function WorkspaceBridge({
1137
+ excalidrawAPI,
1138
+ autoSaveInterval = 2e3,
1139
+ onDrawingLoad,
1140
+ onDrawingSave
1141
+ }) {
1142
+ const { activeDrawing, saveCurrentDrawing } = useWorkspace();
1143
+ const lastDrawingIdRef = useRef3(null);
1144
+ const saveTimeoutRef = useRef3(null);
1145
+ const isLoadingRef = useRef3(false);
1146
+ const saveDrawing = useCallback4(async () => {
1147
+ if (!excalidrawAPI || !activeDrawing || isLoadingRef.current) return;
1148
+ try {
1149
+ const elements = excalidrawAPI.getSceneElements();
1150
+ const appState = excalidrawAPI.getAppState();
1151
+ const files = excalidrawAPI.getFiles();
1152
+ const persistentAppState = {
1153
+ viewBackgroundColor: appState.viewBackgroundColor,
1154
+ zoom: appState.zoom,
1155
+ scrollX: appState.scrollX,
1156
+ scrollY: appState.scrollY
1157
+ // Add other persistent properties as needed
1158
+ };
1159
+ await saveCurrentDrawing(elements, persistentAppState, files);
1160
+ onDrawingSave?.(activeDrawing.id);
1161
+ } catch (error) {
1162
+ console.error("[WorkspaceBridge] Failed to save drawing:", error);
1163
+ }
1164
+ }, [excalidrawAPI, activeDrawing, saveCurrentDrawing, onDrawingSave]);
1165
+ const scheduleSave = useCallback4(() => {
1166
+ if (autoSaveInterval <= 0) return;
1167
+ if (saveTimeoutRef.current) {
1168
+ clearTimeout(saveTimeoutRef.current);
1169
+ }
1170
+ saveTimeoutRef.current = setTimeout(saveDrawing, autoSaveInterval);
1171
+ }, [saveDrawing, autoSaveInterval]);
1172
+ useEffect4(() => {
1173
+ if (!excalidrawAPI || !activeDrawing) return;
1174
+ if (lastDrawingIdRef.current === activeDrawing.id) return;
1175
+ if (lastDrawingIdRef.current !== null) {
1176
+ if (saveTimeoutRef.current) {
1177
+ clearTimeout(saveTimeoutRef.current);
1178
+ saveTimeoutRef.current = null;
1179
+ }
1180
+ saveDrawing();
1181
+ }
1182
+ isLoadingRef.current = true;
1183
+ lastDrawingIdRef.current = activeDrawing.id;
1184
+ try {
1185
+ excalidrawAPI.updateScene({
1186
+ elements: activeDrawing.elements || [],
1187
+ appState: activeDrawing.appState || {},
1188
+ commitToStore: true
1189
+ });
1190
+ onDrawingLoad?.(activeDrawing.id);
1191
+ } catch (error) {
1192
+ console.error("[WorkspaceBridge] Failed to load drawing:", error);
1193
+ } finally {
1194
+ setTimeout(() => {
1195
+ isLoadingRef.current = false;
1196
+ }, 100);
1197
+ }
1198
+ }, [excalidrawAPI, activeDrawing, saveDrawing, onDrawingLoad]);
1199
+ useEffect4(() => {
1200
+ if (!excalidrawAPI || autoSaveInterval <= 0) return;
1201
+ let lastElementCount = 0;
1202
+ let lastChecksum = "";
1203
+ const checkForChanges = () => {
1204
+ if (isLoadingRef.current || !excalidrawAPI) return;
1205
+ try {
1206
+ const elements = excalidrawAPI.getSceneElements();
1207
+ const currentCount = elements.length;
1208
+ const currentChecksum = JSON.stringify(elements.slice(-1));
1209
+ if (currentCount !== lastElementCount || currentChecksum !== lastChecksum) {
1210
+ lastElementCount = currentCount;
1211
+ lastChecksum = currentChecksum;
1212
+ scheduleSave();
1213
+ }
1214
+ } catch {
1215
+ }
1216
+ };
1217
+ const intervalId = setInterval(checkForChanges, 1e3);
1218
+ return () => {
1219
+ clearInterval(intervalId);
1220
+ if (saveTimeoutRef.current) {
1221
+ clearTimeout(saveTimeoutRef.current);
1222
+ }
1223
+ };
1224
+ }, [excalidrawAPI, autoSaveInterval, scheduleSave]);
1225
+ useEffect4(() => {
1226
+ return () => {
1227
+ if (saveTimeoutRef.current) {
1228
+ clearTimeout(saveTimeoutRef.current);
1229
+ }
1230
+ saveDrawing();
1231
+ };
1232
+ }, [saveDrawing]);
1233
+ return null;
1234
+ }
1235
+
1127
1236
  // src/WorkspacePlugin.tsx
1128
- import { useState as useState5, useEffect as useEffect4, useCallback as useCallback4 } from "react";
1237
+ import { useState as useState5, useEffect as useEffect5, useCallback as useCallback5 } from "react";
1129
1238
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1130
1239
  function WorkspacePluginInner({
1131
1240
  children,
@@ -1134,7 +1243,7 @@ function WorkspacePluginInner({
1134
1243
  }) {
1135
1244
  const [sidebarOpen, setSidebarOpen] = useState5(defaultSidebarOpen);
1136
1245
  const { activeDrawing } = useWorkspace();
1137
- useEffect4(() => {
1246
+ useEffect5(() => {
1138
1247
  const handleKeyDown = (e) => {
1139
1248
  if (e.ctrlKey && e.key === "b") {
1140
1249
  e.preventDefault();
@@ -1147,7 +1256,7 @@ function WorkspacePluginInner({
1147
1256
  window.addEventListener("keydown", handleKeyDown);
1148
1257
  return () => window.removeEventListener("keydown", handleKeyDown);
1149
1258
  }, []);
1150
- const handleToggleSidebar = useCallback4(() => {
1259
+ const handleToggleSidebar = useCallback5(() => {
1151
1260
  setSidebarOpen((prev) => !prev);
1152
1261
  }, []);
1153
1262
  return /* @__PURE__ */ jsxs5(
@@ -1190,6 +1299,7 @@ export {
1190
1299
  DrawingListItem,
1191
1300
  DrawingsDialog,
1192
1301
  Sidebar,
1302
+ WorkspaceBridge,
1193
1303
  WorkspaceMenuItems,
1194
1304
  WorkspacePlugin,
1195
1305
  WorkspaceProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rita-workspace",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Multi-drawing workspace feature for Rita (Excalidraw fork)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",