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 +66 -78
- package/dist/index.d.mts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +258 -40
- package/dist/index.mjs +256 -39
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
34
|
+
**Wrap ExcalidrawApp with WorkspaceProvider:**
|
|
34
35
|
|
|
35
36
|
```tsx
|
|
36
|
-
const
|
|
37
|
-
const [langCode] = useAppLangCode(); // Excalidraw's language hook
|
|
38
|
-
|
|
39
|
-
// ... existing code ...
|
|
40
|
-
|
|
37
|
+
const ExcalidrawApp = () => {
|
|
41
38
|
return (
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
+
**Add WorkspaceBridge inside ExcalidrawWrapper** (this syncs the canvas automatically):
|
|
52
51
|
|
|
53
52
|
```tsx
|
|
54
|
-
const
|
|
53
|
+
const ExcalidrawWrapper = () => {
|
|
54
|
+
const [excalidrawAPI, excalidrawRefCallback] =
|
|
55
|
+
useCallbackRefState<ExcalidrawImperativeAPI>();
|
|
56
|
+
|
|
57
|
+
// ... existing code ...
|
|
58
|
+
|
|
55
59
|
return (
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
75
|
+
**Add imports:**
|
|
70
76
|
|
|
71
77
|
```tsx
|
|
72
|
-
import React, { useState } from "react";
|
|
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
|
|
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
|
|
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
|
-
##
|
|
119
|
+
## How It Works
|
|
114
120
|
|
|
115
|
-
|
|
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
|
-
|
|
129
|
+
## Language Support (i18n)
|
|
118
130
|
|
|
119
|
-
Pass Excalidraw's `langCode` to `WorkspaceProvider
|
|
131
|
+
Pass Excalidraw's `langCode` to `WorkspaceProvider`:
|
|
120
132
|
|
|
121
133
|
```tsx
|
|
122
|
-
const [langCode] = useAppLangCode();
|
|
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
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
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
|
|
183
|
-
| `useWorkspaceLang()` | Get
|
|
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
|
|
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: "
|
|
215
|
+
drawings: "Arbetsyta",
|
|
215
216
|
newDrawing: "Ny ritning",
|
|
216
|
-
manageDrawings: "Hantera
|
|
217
|
+
manageDrawings: "Hantera arbetsyta...",
|
|
217
218
|
// Dialog
|
|
218
|
-
dialogTitle: "
|
|
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: "
|
|
240
|
+
drawings: "Workspace",
|
|
237
241
|
newDrawing: "New drawing",
|
|
238
|
-
manageDrawings: "Manage
|
|
242
|
+
manageDrawings: "Manage workspace...",
|
|
239
243
|
// Dialog
|
|
240
|
-
dialogTitle: "
|
|
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
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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/
|
|
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,
|
|
1413
|
+
const [sidebarOpen, setSidebarOpen] = (0, import_react8.useState)(defaultSidebarOpen);
|
|
1197
1414
|
const { activeDrawing } = useWorkspace();
|
|
1198
|
-
(0,
|
|
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,
|
|
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: "
|
|
153
|
+
drawings: "Arbetsyta",
|
|
154
154
|
newDrawing: "Ny ritning",
|
|
155
|
-
manageDrawings: "Hantera
|
|
155
|
+
manageDrawings: "Hantera arbetsyta...",
|
|
156
156
|
// Dialog
|
|
157
|
-
dialogTitle: "
|
|
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: "
|
|
178
|
+
drawings: "Workspace",
|
|
176
179
|
newDrawing: "New drawing",
|
|
177
|
-
manageDrawings: "Manage
|
|
180
|
+
manageDrawings: "Manage workspace...",
|
|
178
181
|
// Dialog
|
|
179
|
-
dialogTitle: "
|
|
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
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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,
|