rita-workspace 0.1.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 +104 -0
- package/dist/index.css +168 -0
- package/dist/index.d.mts +145 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +657 -0
- package/dist/index.mjs +610 -0
- package/package.json +40 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
// src/storage/db.ts
|
|
2
|
+
import { openDB } from "idb";
|
|
3
|
+
var DB_NAME = "rita-workspace";
|
|
4
|
+
var DB_VERSION = 1;
|
|
5
|
+
var dbInstance = null;
|
|
6
|
+
async function getDB() {
|
|
7
|
+
if (dbInstance) return dbInstance;
|
|
8
|
+
dbInstance = await openDB(DB_NAME, DB_VERSION, {
|
|
9
|
+
upgrade(db) {
|
|
10
|
+
if (!db.objectStoreNames.contains("workspaces")) {
|
|
11
|
+
const workspaceStore = db.createObjectStore("workspaces", { keyPath: "id" });
|
|
12
|
+
workspaceStore.createIndex("by-updated", "updatedAt");
|
|
13
|
+
}
|
|
14
|
+
if (!db.objectStoreNames.contains("drawings")) {
|
|
15
|
+
const drawingStore = db.createObjectStore("drawings", { keyPath: "id" });
|
|
16
|
+
drawingStore.createIndex("by-updated", "updatedAt");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return dbInstance;
|
|
21
|
+
}
|
|
22
|
+
async function closeDB() {
|
|
23
|
+
if (dbInstance) {
|
|
24
|
+
dbInstance.close();
|
|
25
|
+
dbInstance = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/storage/drawingStore.ts
|
|
30
|
+
import { nanoid } from "nanoid";
|
|
31
|
+
async function createDrawing(name = "Untitled", elements = [], appState = {}) {
|
|
32
|
+
const db = await getDB();
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const drawing = {
|
|
35
|
+
id: nanoid(),
|
|
36
|
+
name,
|
|
37
|
+
elements,
|
|
38
|
+
appState,
|
|
39
|
+
files: {},
|
|
40
|
+
createdAt: now,
|
|
41
|
+
updatedAt: now
|
|
42
|
+
};
|
|
43
|
+
await db.put("drawings", drawing);
|
|
44
|
+
return drawing;
|
|
45
|
+
}
|
|
46
|
+
async function getDrawing(id) {
|
|
47
|
+
const db = await getDB();
|
|
48
|
+
return db.get("drawings", id);
|
|
49
|
+
}
|
|
50
|
+
async function getAllDrawings() {
|
|
51
|
+
const db = await getDB();
|
|
52
|
+
return db.getAllFromIndex("drawings", "by-updated");
|
|
53
|
+
}
|
|
54
|
+
async function updateDrawing(id, updates) {
|
|
55
|
+
const db = await getDB();
|
|
56
|
+
const existing = await db.get("drawings", id);
|
|
57
|
+
if (!existing) return void 0;
|
|
58
|
+
const updated = {
|
|
59
|
+
...existing,
|
|
60
|
+
...updates,
|
|
61
|
+
updatedAt: Date.now()
|
|
62
|
+
};
|
|
63
|
+
await db.put("drawings", updated);
|
|
64
|
+
return updated;
|
|
65
|
+
}
|
|
66
|
+
async function deleteDrawing(id) {
|
|
67
|
+
const db = await getDB();
|
|
68
|
+
const existing = await db.get("drawings", id);
|
|
69
|
+
if (!existing) return false;
|
|
70
|
+
await db.delete("drawings", id);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
async function duplicateDrawing(id, newName) {
|
|
74
|
+
const existing = await getDrawing(id);
|
|
75
|
+
if (!existing) return void 0;
|
|
76
|
+
return createDrawing(
|
|
77
|
+
newName || `${existing.name} (copy)`,
|
|
78
|
+
existing.elements,
|
|
79
|
+
existing.appState
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/storage/workspaceStore.ts
|
|
84
|
+
var DEFAULT_WORKSPACE_ID = "default";
|
|
85
|
+
async function getOrCreateDefaultWorkspace() {
|
|
86
|
+
const db = await getDB();
|
|
87
|
+
let workspace = await db.get("workspaces", DEFAULT_WORKSPACE_ID);
|
|
88
|
+
if (!workspace) {
|
|
89
|
+
const firstDrawing = await createDrawing("Ritning 1");
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
workspace = {
|
|
92
|
+
id: DEFAULT_WORKSPACE_ID,
|
|
93
|
+
name: "My Workspace",
|
|
94
|
+
drawingIds: [firstDrawing.id],
|
|
95
|
+
activeDrawingId: firstDrawing.id,
|
|
96
|
+
createdAt: now,
|
|
97
|
+
updatedAt: now
|
|
98
|
+
};
|
|
99
|
+
await db.put("workspaces", workspace);
|
|
100
|
+
}
|
|
101
|
+
return workspace;
|
|
102
|
+
}
|
|
103
|
+
async function getWorkspace(id) {
|
|
104
|
+
const db = await getDB();
|
|
105
|
+
return db.get("workspaces", id);
|
|
106
|
+
}
|
|
107
|
+
async function updateWorkspace(id, updates) {
|
|
108
|
+
const db = await getDB();
|
|
109
|
+
const existing = await db.get("workspaces", id);
|
|
110
|
+
if (!existing) return void 0;
|
|
111
|
+
const updated = {
|
|
112
|
+
...existing,
|
|
113
|
+
...updates,
|
|
114
|
+
updatedAt: Date.now()
|
|
115
|
+
};
|
|
116
|
+
await db.put("workspaces", updated);
|
|
117
|
+
return updated;
|
|
118
|
+
}
|
|
119
|
+
async function addDrawingToWorkspace(workspaceId, drawingId) {
|
|
120
|
+
const workspace = await getWorkspace(workspaceId);
|
|
121
|
+
if (!workspace) return void 0;
|
|
122
|
+
if (!workspace.drawingIds.includes(drawingId)) {
|
|
123
|
+
workspace.drawingIds.push(drawingId);
|
|
124
|
+
return updateWorkspace(workspaceId, {
|
|
125
|
+
drawingIds: workspace.drawingIds
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return workspace;
|
|
129
|
+
}
|
|
130
|
+
async function removeDrawingFromWorkspace(workspaceId, drawingId) {
|
|
131
|
+
const workspace = await getWorkspace(workspaceId);
|
|
132
|
+
if (!workspace) return void 0;
|
|
133
|
+
const newDrawingIds = workspace.drawingIds.filter((id) => id !== drawingId);
|
|
134
|
+
if (newDrawingIds.length === 0) {
|
|
135
|
+
return workspace;
|
|
136
|
+
}
|
|
137
|
+
const newActiveId = workspace.activeDrawingId === drawingId ? newDrawingIds[0] : workspace.activeDrawingId;
|
|
138
|
+
return updateWorkspace(workspaceId, {
|
|
139
|
+
drawingIds: newDrawingIds,
|
|
140
|
+
activeDrawingId: newActiveId
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async function setActiveDrawing(workspaceId, drawingId) {
|
|
144
|
+
return updateWorkspace(workspaceId, { activeDrawingId: drawingId });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/state/WorkspaceContext.tsx
|
|
148
|
+
import { createContext, useContext, useEffect, useState, useCallback } from "react";
|
|
149
|
+
import { jsx } from "react/jsx-runtime";
|
|
150
|
+
var WorkspaceContext = createContext(null);
|
|
151
|
+
function useWorkspace() {
|
|
152
|
+
const context = useContext(WorkspaceContext);
|
|
153
|
+
if (!context) {
|
|
154
|
+
throw new Error("useWorkspace must be used within a WorkspaceProvider");
|
|
155
|
+
}
|
|
156
|
+
return context;
|
|
157
|
+
}
|
|
158
|
+
function WorkspaceProvider({ children }) {
|
|
159
|
+
const [workspace, setWorkspace] = useState(null);
|
|
160
|
+
const [drawings, setDrawings] = useState([]);
|
|
161
|
+
const [activeDrawing, setActiveDrawing2] = useState(null);
|
|
162
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
163
|
+
const [error, setError] = useState(null);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
async function init() {
|
|
166
|
+
try {
|
|
167
|
+
setIsLoading(true);
|
|
168
|
+
const ws = await getOrCreateDefaultWorkspace();
|
|
169
|
+
setWorkspace(ws);
|
|
170
|
+
const allDrawings = await getAllDrawings();
|
|
171
|
+
const wsDrawings = allDrawings.filter((d) => ws.drawingIds.includes(d.id));
|
|
172
|
+
setDrawings(wsDrawings);
|
|
173
|
+
if (ws.activeDrawingId) {
|
|
174
|
+
const active = await getDrawing(ws.activeDrawingId);
|
|
175
|
+
setActiveDrawing2(active || null);
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
setError(err instanceof Error ? err.message : "Failed to load workspace");
|
|
179
|
+
} finally {
|
|
180
|
+
setIsLoading(false);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
init();
|
|
184
|
+
}, []);
|
|
185
|
+
const createNewDrawing = useCallback(async (name) => {
|
|
186
|
+
if (!workspace) return null;
|
|
187
|
+
try {
|
|
188
|
+
const drawing = await createDrawing(name || `Ritning ${drawings.length + 1}`);
|
|
189
|
+
await addDrawingToWorkspace(workspace.id, drawing.id);
|
|
190
|
+
await setActiveDrawing(workspace.id, drawing.id);
|
|
191
|
+
setDrawings((prev) => [...prev, drawing]);
|
|
192
|
+
setActiveDrawing2(drawing);
|
|
193
|
+
setWorkspace((prev) => prev ? {
|
|
194
|
+
...prev,
|
|
195
|
+
drawingIds: [...prev.drawingIds, drawing.id],
|
|
196
|
+
activeDrawingId: drawing.id
|
|
197
|
+
} : null);
|
|
198
|
+
return drawing;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
setError(err instanceof Error ? err.message : "Failed to create drawing");
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}, [workspace, drawings.length]);
|
|
204
|
+
const switchDrawing = useCallback(async (id) => {
|
|
205
|
+
if (!workspace) return;
|
|
206
|
+
try {
|
|
207
|
+
const drawing = await getDrawing(id);
|
|
208
|
+
if (drawing) {
|
|
209
|
+
await setActiveDrawing(workspace.id, id);
|
|
210
|
+
setActiveDrawing2(drawing);
|
|
211
|
+
setWorkspace((prev) => prev ? { ...prev, activeDrawingId: id } : null);
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
setError(err instanceof Error ? err.message : "Failed to switch drawing");
|
|
215
|
+
}
|
|
216
|
+
}, [workspace]);
|
|
217
|
+
const renameDrawing = useCallback(async (id, name) => {
|
|
218
|
+
try {
|
|
219
|
+
const updated = await updateDrawing(id, { name });
|
|
220
|
+
if (updated) {
|
|
221
|
+
setDrawings((prev) => prev.map((d) => d.id === id ? updated : d));
|
|
222
|
+
if (activeDrawing?.id === id) {
|
|
223
|
+
setActiveDrawing2(updated);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (err) {
|
|
227
|
+
setError(err instanceof Error ? err.message : "Failed to rename drawing");
|
|
228
|
+
}
|
|
229
|
+
}, [activeDrawing]);
|
|
230
|
+
const removeDrawing = useCallback(async (id) => {
|
|
231
|
+
if (!workspace || drawings.length <= 1) return;
|
|
232
|
+
try {
|
|
233
|
+
await deleteDrawing(id);
|
|
234
|
+
const updatedWorkspace = await removeDrawingFromWorkspace(workspace.id, id);
|
|
235
|
+
setDrawings((prev) => prev.filter((d) => d.id !== id));
|
|
236
|
+
if (updatedWorkspace) {
|
|
237
|
+
setWorkspace(updatedWorkspace);
|
|
238
|
+
if (activeDrawing?.id === id && updatedWorkspace.activeDrawingId) {
|
|
239
|
+
const newActive = await getDrawing(updatedWorkspace.activeDrawingId);
|
|
240
|
+
setActiveDrawing2(newActive || null);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch (err) {
|
|
244
|
+
setError(err instanceof Error ? err.message : "Failed to delete drawing");
|
|
245
|
+
}
|
|
246
|
+
}, [workspace, drawings.length, activeDrawing]);
|
|
247
|
+
const duplicateCurrentDrawing = useCallback(async () => {
|
|
248
|
+
if (!activeDrawing || !workspace) return null;
|
|
249
|
+
try {
|
|
250
|
+
const duplicate = await duplicateDrawing(activeDrawing.id);
|
|
251
|
+
if (duplicate) {
|
|
252
|
+
await addDrawingToWorkspace(workspace.id, duplicate.id);
|
|
253
|
+
setDrawings((prev) => [...prev, duplicate]);
|
|
254
|
+
setWorkspace((prev) => prev ? {
|
|
255
|
+
...prev,
|
|
256
|
+
drawingIds: [...prev.drawingIds, duplicate.id]
|
|
257
|
+
} : null);
|
|
258
|
+
return duplicate;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
setError(err instanceof Error ? err.message : "Failed to duplicate drawing");
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}, [activeDrawing, workspace]);
|
|
266
|
+
const saveCurrentDrawing = useCallback(async (elements, appState) => {
|
|
267
|
+
if (!activeDrawing) return;
|
|
268
|
+
try {
|
|
269
|
+
const updated = await updateDrawing(activeDrawing.id, { elements, appState });
|
|
270
|
+
if (updated) {
|
|
271
|
+
setActiveDrawing2(updated);
|
|
272
|
+
setDrawings((prev) => prev.map((d) => d.id === updated.id ? updated : d));
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
setError(err instanceof Error ? err.message : "Failed to save drawing");
|
|
276
|
+
}
|
|
277
|
+
}, [activeDrawing]);
|
|
278
|
+
const value = {
|
|
279
|
+
workspace,
|
|
280
|
+
drawings,
|
|
281
|
+
activeDrawing,
|
|
282
|
+
isLoading,
|
|
283
|
+
error,
|
|
284
|
+
createNewDrawing,
|
|
285
|
+
switchDrawing,
|
|
286
|
+
renameDrawing,
|
|
287
|
+
removeDrawing,
|
|
288
|
+
duplicateCurrentDrawing,
|
|
289
|
+
saveCurrentDrawing
|
|
290
|
+
};
|
|
291
|
+
return /* @__PURE__ */ jsx(WorkspaceContext.Provider, { value, children });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/ui/Sidebar/Sidebar.tsx
|
|
295
|
+
import { useState as useState3 } from "react";
|
|
296
|
+
|
|
297
|
+
// src/ui/DrawingList/DrawingListItem.tsx
|
|
298
|
+
import { useState as useState2, useRef, useEffect as useEffect2 } from "react";
|
|
299
|
+
|
|
300
|
+
// src/ui/DrawingList/DrawingList.module.css
|
|
301
|
+
var DrawingList_default = {};
|
|
302
|
+
|
|
303
|
+
// src/ui/DrawingList/DrawingListItem.tsx
|
|
304
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
305
|
+
function DrawingListItem({
|
|
306
|
+
drawing,
|
|
307
|
+
isActive,
|
|
308
|
+
onSelect,
|
|
309
|
+
onRename,
|
|
310
|
+
onDelete,
|
|
311
|
+
canDelete
|
|
312
|
+
}) {
|
|
313
|
+
const [isEditing, setIsEditing] = useState2(false);
|
|
314
|
+
const [editName, setEditName] = useState2(drawing.name);
|
|
315
|
+
const inputRef = useRef(null);
|
|
316
|
+
useEffect2(() => {
|
|
317
|
+
if (isEditing && inputRef.current) {
|
|
318
|
+
inputRef.current.focus();
|
|
319
|
+
inputRef.current.select();
|
|
320
|
+
}
|
|
321
|
+
}, [isEditing]);
|
|
322
|
+
const handleSubmit = () => {
|
|
323
|
+
const trimmed = editName.trim();
|
|
324
|
+
if (trimmed && trimmed !== drawing.name) {
|
|
325
|
+
onRename(trimmed);
|
|
326
|
+
} else {
|
|
327
|
+
setEditName(drawing.name);
|
|
328
|
+
}
|
|
329
|
+
setIsEditing(false);
|
|
330
|
+
};
|
|
331
|
+
const handleKeyDown = (e) => {
|
|
332
|
+
if (e.key === "Enter") {
|
|
333
|
+
handleSubmit();
|
|
334
|
+
} else if (e.key === "Escape") {
|
|
335
|
+
setEditName(drawing.name);
|
|
336
|
+
setIsEditing(false);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const handleDoubleClick = (e) => {
|
|
340
|
+
e.stopPropagation();
|
|
341
|
+
setIsEditing(true);
|
|
342
|
+
};
|
|
343
|
+
const handleDeleteClick = (e) => {
|
|
344
|
+
e.stopPropagation();
|
|
345
|
+
if (window.confirm(`Ta bort "${drawing.name}"?`)) {
|
|
346
|
+
onDelete();
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
const formatDate = (timestamp) => {
|
|
350
|
+
const date = new Date(timestamp);
|
|
351
|
+
return date.toLocaleDateString("sv-SE", {
|
|
352
|
+
month: "short",
|
|
353
|
+
day: "numeric",
|
|
354
|
+
hour: "2-digit",
|
|
355
|
+
minute: "2-digit"
|
|
356
|
+
});
|
|
357
|
+
};
|
|
358
|
+
return /* @__PURE__ */ jsxs(
|
|
359
|
+
"li",
|
|
360
|
+
{
|
|
361
|
+
className: `${DrawingList_default.item} ${isActive ? DrawingList_default.active : ""}`,
|
|
362
|
+
onClick: onSelect,
|
|
363
|
+
children: [
|
|
364
|
+
/* @__PURE__ */ jsx2("div", { className: DrawingList_default.itemContent, children: isEditing ? /* @__PURE__ */ jsx2(
|
|
365
|
+
"input",
|
|
366
|
+
{
|
|
367
|
+
ref: inputRef,
|
|
368
|
+
type: "text",
|
|
369
|
+
value: editName,
|
|
370
|
+
onChange: (e) => setEditName(e.target.value),
|
|
371
|
+
onBlur: handleSubmit,
|
|
372
|
+
onKeyDown: handleKeyDown,
|
|
373
|
+
className: DrawingList_default.editInput,
|
|
374
|
+
onClick: (e) => e.stopPropagation()
|
|
375
|
+
}
|
|
376
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
377
|
+
/* @__PURE__ */ jsx2(
|
|
378
|
+
"span",
|
|
379
|
+
{
|
|
380
|
+
className: DrawingList_default.name,
|
|
381
|
+
onDoubleClick: handleDoubleClick,
|
|
382
|
+
title: "Dubbelklicka f\xF6r att byta namn",
|
|
383
|
+
children: drawing.name
|
|
384
|
+
}
|
|
385
|
+
),
|
|
386
|
+
/* @__PURE__ */ jsx2("span", { className: DrawingList_default.date, children: formatDate(drawing.updatedAt) })
|
|
387
|
+
] }) }),
|
|
388
|
+
/* @__PURE__ */ jsxs("div", { className: DrawingList_default.actions, children: [
|
|
389
|
+
/* @__PURE__ */ jsx2(
|
|
390
|
+
"button",
|
|
391
|
+
{
|
|
392
|
+
className: DrawingList_default.actionButton,
|
|
393
|
+
onClick: handleDoubleClick,
|
|
394
|
+
title: "Byt namn (F2)",
|
|
395
|
+
children: "\u270F\uFE0F"
|
|
396
|
+
}
|
|
397
|
+
),
|
|
398
|
+
canDelete && /* @__PURE__ */ jsx2(
|
|
399
|
+
"button",
|
|
400
|
+
{
|
|
401
|
+
className: DrawingList_default.actionButton,
|
|
402
|
+
onClick: handleDeleteClick,
|
|
403
|
+
title: "Ta bort",
|
|
404
|
+
children: "\u{1F5D1}\uFE0F"
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
] })
|
|
408
|
+
]
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/ui/DrawingList/DrawingList.tsx
|
|
414
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
415
|
+
function DrawingList() {
|
|
416
|
+
const { drawings, activeDrawing, switchDrawing, renameDrawing, removeDrawing } = useWorkspace();
|
|
417
|
+
if (drawings.length === 0) {
|
|
418
|
+
return /* @__PURE__ */ jsx3("div", { className: DrawingList_default.empty, children: "Inga ritningar" });
|
|
419
|
+
}
|
|
420
|
+
return /* @__PURE__ */ jsx3("ul", { className: DrawingList_default.list, children: drawings.map((drawing) => /* @__PURE__ */ jsx3(
|
|
421
|
+
DrawingListItem,
|
|
422
|
+
{
|
|
423
|
+
drawing,
|
|
424
|
+
isActive: drawing.id === activeDrawing?.id,
|
|
425
|
+
onSelect: () => switchDrawing(drawing.id),
|
|
426
|
+
onRename: (name) => renameDrawing(drawing.id, name),
|
|
427
|
+
onDelete: () => removeDrawing(drawing.id),
|
|
428
|
+
canDelete: drawings.length > 1
|
|
429
|
+
},
|
|
430
|
+
drawing.id
|
|
431
|
+
)) });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/ui/Sidebar/Sidebar.module.css
|
|
435
|
+
var Sidebar_default = {};
|
|
436
|
+
|
|
437
|
+
// src/ui/Sidebar/Sidebar.tsx
|
|
438
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
439
|
+
function Sidebar({ isOpen = true, onToggle, width = 250 }) {
|
|
440
|
+
const { createNewDrawing, isLoading } = useWorkspace();
|
|
441
|
+
const [isCreating, setIsCreating] = useState3(false);
|
|
442
|
+
const handleCreateNew = async () => {
|
|
443
|
+
setIsCreating(true);
|
|
444
|
+
await createNewDrawing();
|
|
445
|
+
setIsCreating(false);
|
|
446
|
+
};
|
|
447
|
+
if (!isOpen) {
|
|
448
|
+
return /* @__PURE__ */ jsx4(
|
|
449
|
+
"button",
|
|
450
|
+
{
|
|
451
|
+
className: Sidebar_default.toggleButton,
|
|
452
|
+
onClick: onToggle,
|
|
453
|
+
title: "Open sidebar (Ctrl+B)",
|
|
454
|
+
"aria-label": "Open sidebar",
|
|
455
|
+
children: "\u2630"
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
return /* @__PURE__ */ jsxs2("aside", { className: Sidebar_default.sidebar, style: { width }, children: [
|
|
460
|
+
/* @__PURE__ */ jsxs2("header", { className: Sidebar_default.header, children: [
|
|
461
|
+
/* @__PURE__ */ jsx4("h2", { className: Sidebar_default.title, children: "Ritningar" }),
|
|
462
|
+
/* @__PURE__ */ jsx4(
|
|
463
|
+
"button",
|
|
464
|
+
{
|
|
465
|
+
className: Sidebar_default.closeButton,
|
|
466
|
+
onClick: onToggle,
|
|
467
|
+
title: "Close sidebar (Ctrl+B)",
|
|
468
|
+
"aria-label": "Close sidebar",
|
|
469
|
+
children: "\u2715"
|
|
470
|
+
}
|
|
471
|
+
)
|
|
472
|
+
] }),
|
|
473
|
+
/* @__PURE__ */ jsx4("div", { className: Sidebar_default.content, children: isLoading ? /* @__PURE__ */ jsx4("div", { className: Sidebar_default.loading, children: "Laddar..." }) : /* @__PURE__ */ jsx4(DrawingList, {}) }),
|
|
474
|
+
/* @__PURE__ */ jsx4("footer", { className: Sidebar_default.footer, children: /* @__PURE__ */ jsx4(
|
|
475
|
+
"button",
|
|
476
|
+
{
|
|
477
|
+
className: Sidebar_default.newButton,
|
|
478
|
+
onClick: handleCreateNew,
|
|
479
|
+
disabled: isCreating,
|
|
480
|
+
children: isCreating ? "Skapar..." : "+ Ny ritning"
|
|
481
|
+
}
|
|
482
|
+
) })
|
|
483
|
+
] });
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/integration/useExcalidrawBridge.ts
|
|
487
|
+
import { useEffect as useEffect3, useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
488
|
+
function useExcalidrawBridge({
|
|
489
|
+
excalidrawAPI,
|
|
490
|
+
autoSaveInterval = 2e3
|
|
491
|
+
}) {
|
|
492
|
+
const { activeDrawing, saveCurrentDrawing } = useWorkspace();
|
|
493
|
+
const saveTimeoutRef = useRef2(null);
|
|
494
|
+
const lastDrawingIdRef = useRef2(null);
|
|
495
|
+
useEffect3(() => {
|
|
496
|
+
if (!excalidrawAPI || !activeDrawing) return;
|
|
497
|
+
if (lastDrawingIdRef.current === activeDrawing.id) return;
|
|
498
|
+
lastDrawingIdRef.current = activeDrawing.id;
|
|
499
|
+
excalidrawAPI.updateScene({
|
|
500
|
+
elements: activeDrawing.elements,
|
|
501
|
+
appState: activeDrawing.appState
|
|
502
|
+
});
|
|
503
|
+
}, [excalidrawAPI, activeDrawing]);
|
|
504
|
+
const scheduleSave = useCallback2(() => {
|
|
505
|
+
if (!excalidrawAPI) return;
|
|
506
|
+
if (saveTimeoutRef.current) {
|
|
507
|
+
clearTimeout(saveTimeoutRef.current);
|
|
508
|
+
}
|
|
509
|
+
saveTimeoutRef.current = setTimeout(async () => {
|
|
510
|
+
const elements = excalidrawAPI.getSceneElements();
|
|
511
|
+
const appState = excalidrawAPI.getAppState();
|
|
512
|
+
await saveCurrentDrawing(elements, appState);
|
|
513
|
+
}, autoSaveInterval);
|
|
514
|
+
}, [excalidrawAPI, saveCurrentDrawing, autoSaveInterval]);
|
|
515
|
+
useEffect3(() => {
|
|
516
|
+
return () => {
|
|
517
|
+
if (saveTimeoutRef.current) {
|
|
518
|
+
clearTimeout(saveTimeoutRef.current);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}, []);
|
|
522
|
+
return {
|
|
523
|
+
scheduleSave
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/WorkspacePlugin.tsx
|
|
528
|
+
import { useState as useState4, useEffect as useEffect4, useCallback as useCallback3 } from "react";
|
|
529
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
530
|
+
function WorkspacePluginInner({
|
|
531
|
+
children,
|
|
532
|
+
defaultSidebarOpen = true,
|
|
533
|
+
sidebarWidth = 250
|
|
534
|
+
}) {
|
|
535
|
+
const [sidebarOpen, setSidebarOpen] = useState4(defaultSidebarOpen);
|
|
536
|
+
const { activeDrawing } = useWorkspace();
|
|
537
|
+
useEffect4(() => {
|
|
538
|
+
const handleKeyDown = (e) => {
|
|
539
|
+
if (e.ctrlKey && e.key === "b") {
|
|
540
|
+
e.preventDefault();
|
|
541
|
+
setSidebarOpen((prev) => !prev);
|
|
542
|
+
}
|
|
543
|
+
if (e.ctrlKey && e.shiftKey && e.key === "N") {
|
|
544
|
+
e.preventDefault();
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
548
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
549
|
+
}, []);
|
|
550
|
+
const handleToggleSidebar = useCallback3(() => {
|
|
551
|
+
setSidebarOpen((prev) => !prev);
|
|
552
|
+
}, []);
|
|
553
|
+
return /* @__PURE__ */ jsxs3(
|
|
554
|
+
"div",
|
|
555
|
+
{
|
|
556
|
+
style: {
|
|
557
|
+
display: "flex",
|
|
558
|
+
height: "100%",
|
|
559
|
+
width: "100%"
|
|
560
|
+
},
|
|
561
|
+
children: [
|
|
562
|
+
/* @__PURE__ */ jsx5(
|
|
563
|
+
Sidebar,
|
|
564
|
+
{
|
|
565
|
+
isOpen: sidebarOpen,
|
|
566
|
+
onToggle: handleToggleSidebar,
|
|
567
|
+
width: sidebarWidth
|
|
568
|
+
}
|
|
569
|
+
),
|
|
570
|
+
/* @__PURE__ */ jsx5(
|
|
571
|
+
"main",
|
|
572
|
+
{
|
|
573
|
+
style: {
|
|
574
|
+
flex: 1,
|
|
575
|
+
height: "100%",
|
|
576
|
+
overflow: "hidden"
|
|
577
|
+
},
|
|
578
|
+
children
|
|
579
|
+
}
|
|
580
|
+
)
|
|
581
|
+
]
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
function WorkspacePlugin(props) {
|
|
586
|
+
return /* @__PURE__ */ jsx5(WorkspaceProvider, { children: /* @__PURE__ */ jsx5(WorkspacePluginInner, { ...props }) });
|
|
587
|
+
}
|
|
588
|
+
export {
|
|
589
|
+
DrawingList,
|
|
590
|
+
DrawingListItem,
|
|
591
|
+
Sidebar,
|
|
592
|
+
WorkspacePlugin,
|
|
593
|
+
WorkspaceProvider,
|
|
594
|
+
addDrawingToWorkspace,
|
|
595
|
+
closeDB,
|
|
596
|
+
createDrawing,
|
|
597
|
+
deleteDrawing,
|
|
598
|
+
duplicateDrawing,
|
|
599
|
+
getAllDrawings,
|
|
600
|
+
getDB,
|
|
601
|
+
getDrawing,
|
|
602
|
+
getOrCreateDefaultWorkspace,
|
|
603
|
+
getWorkspace,
|
|
604
|
+
removeDrawingFromWorkspace,
|
|
605
|
+
setActiveDrawing,
|
|
606
|
+
updateDrawing,
|
|
607
|
+
updateWorkspace,
|
|
608
|
+
useExcalidrawBridge,
|
|
609
|
+
useWorkspace
|
|
610
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rita-workspace",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Multi-drawing workspace feature for Rita (Excalidraw fork)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
13
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"test:coverage": "vitest --coverage",
|
|
16
|
+
"lint": "eslint src/",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": "^18.0.0",
|
|
21
|
+
"react-dom": "^18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/react": "^18.2.0",
|
|
25
|
+
"@types/react-dom": "^18.2.0",
|
|
26
|
+
"eslint": "^8.0.0",
|
|
27
|
+
"tsup": "^8.0.0",
|
|
28
|
+
"typescript": "^5.0.0",
|
|
29
|
+
"vitest": "^1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"idb": "^8.0.0",
|
|
33
|
+
"nanoid": "^5.0.0"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/farapholch/rita-workspace.git"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT"
|
|
40
|
+
}
|