@tldiagram/core-ui 1.90.1 → 1.92.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/dist/index.d.ts +0 -1
- package/dist/index.js +6768 -7231
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +1 -1
- package/package.json +1 -1
- package/src/App.tsx +0 -4
- package/src/index.ts +0 -1
- package/src/pages/ViewEditor/components/EditorMenus.tsx +6 -4
- package/src/pages/ViewEditor/components/EditorOverlays.tsx +3 -2
- package/src/pages/ViewEditor/components/EmptyCanvasState.tsx +3 -2
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +17 -26
- package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +1 -1
- package/src/pages/ViewEditor/index.tsx +103 -40
- package/dist/demo/DemoPage.d.ts +0 -9
- package/dist/demo/seed.d.ts +0 -9
- package/dist/demo/store.d.ts +0 -137
- package/src/demo/DemoPage.tsx +0 -184
- package/src/demo/seed.ts +0 -67
- package/src/demo/store.ts +0 -536
package/src/demo/store.ts
DELETED
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* localStorage-backed store for the demo mode.
|
|
3
|
-
* Implements the subset of the `api` interface used by ViewEditor and its hooks.
|
|
4
|
-
* Data is scoped under the `diag:demo:*` key namespace to avoid colliding
|
|
5
|
-
* with a real logged-in session.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
LibraryElement,
|
|
10
|
-
PlacedElement,
|
|
11
|
-
Connector,
|
|
12
|
-
ViewTreeNode,
|
|
13
|
-
ViewLayer,
|
|
14
|
-
Tag,
|
|
15
|
-
ElementPlacement,
|
|
16
|
-
ExploreData,
|
|
17
|
-
} from '../types'
|
|
18
|
-
import {
|
|
19
|
-
DEMO_ELEMENTS,
|
|
20
|
-
DEMO_VIEWS,
|
|
21
|
-
DEMO_PLACEMENTS,
|
|
22
|
-
DEMO_CONNECTORS,
|
|
23
|
-
DEMO_LAYERS,
|
|
24
|
-
} from './seed'
|
|
25
|
-
|
|
26
|
-
// ── Keys ──────────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
const K = {
|
|
29
|
-
elements: 'diag:demo:elements',
|
|
30
|
-
views: 'diag:demo:views',
|
|
31
|
-
placements: (viewId: number) => `diag:demo:placements:${viewId}`,
|
|
32
|
-
connectors: (viewId: number) => `diag:demo:connectors:${viewId}`,
|
|
33
|
-
layers: (viewId: number) => `diag:demo:layers:${viewId}`,
|
|
34
|
-
tagColors: 'diag:demo:tagColors',
|
|
35
|
-
nextId: 'diag:demo:nextId',
|
|
36
|
-
} as const
|
|
37
|
-
|
|
38
|
-
// ── ID generation ──────────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
function nextId(): number {
|
|
41
|
-
const current = Number(localStorage.getItem(K.nextId) ?? '9000')
|
|
42
|
-
const next = current + 1
|
|
43
|
-
localStorage.setItem(K.nextId, String(next))
|
|
44
|
-
return next
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── Generic persistence helpers ────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
function load<T>(key: string, fallback: T): T {
|
|
50
|
-
try {
|
|
51
|
-
const raw = localStorage.getItem(key)
|
|
52
|
-
if (raw === null) return fallback
|
|
53
|
-
return JSON.parse(raw) as T
|
|
54
|
-
} catch {
|
|
55
|
-
return fallback
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function save(key: string, value: unknown): void {
|
|
60
|
-
localStorage.setItem(key, JSON.stringify(value))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ── Initialisation ─────────────────────────────────────────────────────────────
|
|
64
|
-
// Seed data is written once on first visit (when the store key is absent).
|
|
65
|
-
|
|
66
|
-
export function initDemoStore(): void {
|
|
67
|
-
if (localStorage.getItem(K.elements) === null) {
|
|
68
|
-
save(K.elements, DEMO_ELEMENTS)
|
|
69
|
-
}
|
|
70
|
-
if (localStorage.getItem(K.views) === null) {
|
|
71
|
-
save(K.views, DEMO_VIEWS)
|
|
72
|
-
}
|
|
73
|
-
for (const [rawId, placements] of Object.entries(DEMO_PLACEMENTS)) {
|
|
74
|
-
const id = Number(rawId)
|
|
75
|
-
if (localStorage.getItem(K.placements(id)) === null) {
|
|
76
|
-
save(K.placements(id), placements)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
for (const [rawId, connectors] of Object.entries(DEMO_CONNECTORS)) {
|
|
80
|
-
const id = Number(rawId)
|
|
81
|
-
if (localStorage.getItem(K.connectors(id)) === null) {
|
|
82
|
-
save(K.connectors(id), connectors)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
for (const [rawId, layers] of Object.entries(DEMO_LAYERS)) {
|
|
86
|
-
const id = Number(rawId)
|
|
87
|
-
if (localStorage.getItem(K.layers(id)) === null) {
|
|
88
|
-
save(K.layers(id), layers)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function resetDemoStore(): void {
|
|
94
|
-
localStorage.removeItem(K.elements)
|
|
95
|
-
localStorage.removeItem(K.views)
|
|
96
|
-
localStorage.removeItem(K.tagColors)
|
|
97
|
-
localStorage.removeItem(K.nextId)
|
|
98
|
-
localStorage.removeItem('diag:demo:accent-color')
|
|
99
|
-
localStorage.removeItem('diag:demo:background-color')
|
|
100
|
-
localStorage.removeItem('diag:demo:element-color')
|
|
101
|
-
for (const viewId of getAllViewIds()) {
|
|
102
|
-
localStorage.removeItem(K.placements(viewId))
|
|
103
|
-
localStorage.removeItem(K.connectors(viewId))
|
|
104
|
-
localStorage.removeItem(K.layers(viewId))
|
|
105
|
-
}
|
|
106
|
-
initDemoStore()
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ── Tree helpers ──────────────────────────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
function getAllViews(): ViewTreeNode[] {
|
|
112
|
-
return load<ViewTreeNode[]>(K.views, DEMO_VIEWS)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function flattenTree(nodes: ViewTreeNode[]): ViewTreeNode[] {
|
|
116
|
-
return nodes.flatMap((n) => [n, ...flattenTree(n.children ?? [])])
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function getAllViewIds(): number[] {
|
|
120
|
-
return flattenTree(getAllViews()).map((v) => v.id)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function findViewById(id: number): ViewTreeNode | null {
|
|
124
|
-
return flattenTree(getAllViews()).find((v) => v.id === id) ?? null
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function saveViews(roots: ViewTreeNode[]): void {
|
|
128
|
-
save(K.views, roots)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function insertViewIntoTree(roots: ViewTreeNode[], newView: ViewTreeNode): ViewTreeNode[] {
|
|
132
|
-
if (newView.parent_view_id === null) return [...roots, newView]
|
|
133
|
-
return roots.map((n) => {
|
|
134
|
-
if (n.id === newView.parent_view_id) return { ...n, children: [...(n.children ?? []), newView] }
|
|
135
|
-
return { ...n, children: insertViewIntoTree(n.children ?? [], newView) }
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function deleteViewFromTree(roots: ViewTreeNode[], id: number): ViewTreeNode[] {
|
|
140
|
-
return roots
|
|
141
|
-
.filter((n) => n.id !== id)
|
|
142
|
-
.map((n) => ({ ...n, children: deleteViewFromTree(n.children ?? [], id) }))
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ── api surface ───────────────────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
const NOW = () => new Date().toISOString()
|
|
148
|
-
|
|
149
|
-
export const demoApi = {
|
|
150
|
-
explore: {
|
|
151
|
-
load: async (): Promise<ExploreData> => {
|
|
152
|
-
const tree = getAllViews()
|
|
153
|
-
const flat = flattenTree(tree)
|
|
154
|
-
const views: ExploreData['views'] = {}
|
|
155
|
-
for (const v of flat) {
|
|
156
|
-
views[v.id] = {
|
|
157
|
-
placements: load<PlacedElement[]>(K.placements(v.id), []),
|
|
158
|
-
connectors: load<Connector[]>(K.connectors(v.id), []),
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return { tree, views, navigations: [] }
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
elements: {
|
|
166
|
-
list: async (_params?: unknown): Promise<LibraryElement[]> => {
|
|
167
|
-
return load<LibraryElement[]>(K.elements, DEMO_ELEMENTS)
|
|
168
|
-
},
|
|
169
|
-
|
|
170
|
-
get: async (id: number): Promise<LibraryElement> => {
|
|
171
|
-
const elements = load<LibraryElement[]>(K.elements, DEMO_ELEMENTS)
|
|
172
|
-
const el = elements.find((e) => e.id === id)
|
|
173
|
-
if (!el) throw new Error(`Element ${id} not found`)
|
|
174
|
-
return el
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
create: async (data: Partial<LibraryElement>): Promise<LibraryElement> => {
|
|
178
|
-
const elements = load<LibraryElement[]>(K.elements, DEMO_ELEMENTS)
|
|
179
|
-
const newEl: LibraryElement = {
|
|
180
|
-
id: nextId(),
|
|
181
|
-
name: data.name ?? 'New Element',
|
|
182
|
-
kind: data.kind ?? null,
|
|
183
|
-
description: data.description ?? null,
|
|
184
|
-
technology: data.technology ?? null,
|
|
185
|
-
url: data.url ?? null,
|
|
186
|
-
logo_url: data.logo_url ?? null,
|
|
187
|
-
technology_connectors: data.technology_connectors ?? [],
|
|
188
|
-
tags: data.tags ?? [],
|
|
189
|
-
repo: data.repo ?? null,
|
|
190
|
-
branch: data.branch ?? null,
|
|
191
|
-
file_path: data.file_path ?? null,
|
|
192
|
-
language: data.language ?? null,
|
|
193
|
-
created_at: NOW(),
|
|
194
|
-
updated_at: NOW(),
|
|
195
|
-
has_view: false,
|
|
196
|
-
view_label: null,
|
|
197
|
-
}
|
|
198
|
-
save(K.elements, [...elements, newEl])
|
|
199
|
-
return newEl
|
|
200
|
-
},
|
|
201
|
-
|
|
202
|
-
update: async (id: number, data: Partial<LibraryElement>): Promise<LibraryElement> => {
|
|
203
|
-
const elements = load<LibraryElement[]>(K.elements, DEMO_ELEMENTS)
|
|
204
|
-
const idx = elements.findIndex((e) => e.id === id)
|
|
205
|
-
if (idx === -1) throw new Error(`Element ${id} not found`)
|
|
206
|
-
const updated = { ...elements[idx], ...data, id, updated_at: NOW() }
|
|
207
|
-
const next = [...elements]
|
|
208
|
-
next[idx] = updated
|
|
209
|
-
save(K.elements, next)
|
|
210
|
-
// Patch all view placements that reference this element
|
|
211
|
-
for (const viewId of getAllViewIds()) {
|
|
212
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
213
|
-
const changed = placements.map((p) =>
|
|
214
|
-
p.element_id === id
|
|
215
|
-
? { ...p, name: updated.name, kind: updated.kind, description: updated.description, technology: updated.technology, tags: updated.tags }
|
|
216
|
-
: p,
|
|
217
|
-
)
|
|
218
|
-
save(K.placements(viewId), changed)
|
|
219
|
-
}
|
|
220
|
-
return updated
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
delete: async (_orgId: string, id: number): Promise<void> => {
|
|
224
|
-
const elements = load<LibraryElement[]>(K.elements, DEMO_ELEMENTS)
|
|
225
|
-
save(K.elements, elements.filter((e) => e.id !== id))
|
|
226
|
-
for (const viewId of getAllViewIds()) {
|
|
227
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
228
|
-
save(K.placements(viewId), placements.filter((p) => p.element_id !== id))
|
|
229
|
-
const connectors = load<Connector[]>(K.connectors(viewId), [])
|
|
230
|
-
save(K.connectors(viewId), connectors.filter((c) => c.source_element_id !== id && c.target_element_id !== id))
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
placements: async (id: number): Promise<ElementPlacement[]> => {
|
|
235
|
-
const result: ElementPlacement[] = []
|
|
236
|
-
for (const viewId of getAllViewIds()) {
|
|
237
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
238
|
-
const match = placements.find((p) => p.element_id === id)
|
|
239
|
-
if (match) result.push({ id: match.id, view_id: viewId, element_id: id, position_x: match.position_x, position_y: match.position_y })
|
|
240
|
-
}
|
|
241
|
-
return result
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
workspace: {
|
|
246
|
-
orgs: {
|
|
247
|
-
tagColors: {
|
|
248
|
-
list: async (): Promise<Record<string, Tag>> => {
|
|
249
|
-
return load<Record<string, Tag>>(K.tagColors, {})
|
|
250
|
-
},
|
|
251
|
-
set: async (tag: string, color: string, description?: string): Promise<void> => {
|
|
252
|
-
const tags = load<Record<string, Tag>>(K.tagColors, {})
|
|
253
|
-
save(K.tagColors, { ...tags, [tag]: { name: tag, color, description: description ?? null } })
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
|
|
258
|
-
views: {
|
|
259
|
-
list: async () => {
|
|
260
|
-
return flattenTree(getAllViews()).map((v) => ({
|
|
261
|
-
id: v.id,
|
|
262
|
-
owner_element_id: v.owner_element_id ?? null,
|
|
263
|
-
name: v.name,
|
|
264
|
-
label: v.level_label,
|
|
265
|
-
is_root: v.parent_view_id === null,
|
|
266
|
-
created_at: v.created_at,
|
|
267
|
-
updated_at: v.updated_at,
|
|
268
|
-
}))
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
get: async (id: number): Promise<ViewTreeNode> => {
|
|
272
|
-
const v = findViewById(id)
|
|
273
|
-
if (!v) throw new Error(`View ${id} not found`)
|
|
274
|
-
return v
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
content: async (id: number): Promise<{ placements: PlacedElement[]; connectors: Connector[] }> => {
|
|
278
|
-
return {
|
|
279
|
-
placements: load<PlacedElement[]>(K.placements(id), []),
|
|
280
|
-
connectors: load<Connector[]>(K.connectors(id), []),
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
tree: async (): Promise<ViewTreeNode[]> => {
|
|
285
|
-
return getAllViews()
|
|
286
|
-
},
|
|
287
|
-
|
|
288
|
-
create: async (data: { name: string; label?: string; parent_view_id?: number | null }) => {
|
|
289
|
-
const id = nextId()
|
|
290
|
-
const now = NOW()
|
|
291
|
-
const newView: ViewTreeNode = {
|
|
292
|
-
id,
|
|
293
|
-
name: data.name,
|
|
294
|
-
description: null,
|
|
295
|
-
level_label: data.label ?? null,
|
|
296
|
-
level: 0,
|
|
297
|
-
depth: 0,
|
|
298
|
-
owner_element_id: data.parent_view_id ?? null,
|
|
299
|
-
parent_view_id: data.parent_view_id ?? null,
|
|
300
|
-
created_at: now,
|
|
301
|
-
updated_at: now,
|
|
302
|
-
children: [],
|
|
303
|
-
}
|
|
304
|
-
const roots = getAllViews()
|
|
305
|
-
saveViews(insertViewIntoTree(roots, newView))
|
|
306
|
-
save(K.placements(id), [])
|
|
307
|
-
save(K.connectors(id), [])
|
|
308
|
-
save(K.layers(id), [])
|
|
309
|
-
|
|
310
|
-
// Mark the owning element as having a view
|
|
311
|
-
if (data.parent_view_id != null) {
|
|
312
|
-
const elements = load<LibraryElement[]>(K.elements, DEMO_ELEMENTS)
|
|
313
|
-
const idx = elements.findIndex((e) => e.id === data.parent_view_id)
|
|
314
|
-
if (idx !== -1) {
|
|
315
|
-
const next = [...elements]
|
|
316
|
-
next[idx] = { ...next[idx], has_view: true }
|
|
317
|
-
save(K.elements, next)
|
|
318
|
-
}
|
|
319
|
-
// Patch all placements of that element to reflect has_view
|
|
320
|
-
for (const viewId of getAllViewIds()) {
|
|
321
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
322
|
-
const changed = placements.map((p) =>
|
|
323
|
-
p.element_id === data.parent_view_id ? { ...p, has_view: true } : p,
|
|
324
|
-
)
|
|
325
|
-
save(K.placements(viewId), changed)
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
id,
|
|
331
|
-
owner_element_id: data.parent_view_id ?? null,
|
|
332
|
-
name: data.name,
|
|
333
|
-
label: data.label ?? null,
|
|
334
|
-
is_root: data.parent_view_id === null,
|
|
335
|
-
created_at: now,
|
|
336
|
-
updated_at: now,
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
update: async (id: number, data: { name: string; label?: string }) => {
|
|
341
|
-
const roots = getAllViews()
|
|
342
|
-
const patchInTree = (nodes: ViewTreeNode[]): ViewTreeNode[] =>
|
|
343
|
-
nodes.map((n) =>
|
|
344
|
-
n.id === id
|
|
345
|
-
? { ...n, name: data.name, level_label: data.label ?? n.level_label, updated_at: NOW() }
|
|
346
|
-
: { ...n, children: patchInTree(n.children ?? []) },
|
|
347
|
-
)
|
|
348
|
-
saveViews(patchInTree(roots))
|
|
349
|
-
const v = findViewById(id)
|
|
350
|
-
return {
|
|
351
|
-
id,
|
|
352
|
-
owner_element_id: v?.owner_element_id ?? null,
|
|
353
|
-
name: data.name,
|
|
354
|
-
label: data.label ?? null,
|
|
355
|
-
is_root: v?.parent_view_id === null,
|
|
356
|
-
created_at: v?.created_at ?? NOW(),
|
|
357
|
-
updated_at: NOW(),
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
delete: async (_orgId: string, id: number): Promise<void> => {
|
|
362
|
-
const roots = getAllViews()
|
|
363
|
-
saveViews(deleteViewFromTree(roots, id))
|
|
364
|
-
localStorage.removeItem(K.placements(id))
|
|
365
|
-
localStorage.removeItem(K.connectors(id))
|
|
366
|
-
localStorage.removeItem(K.layers(id))
|
|
367
|
-
},
|
|
368
|
-
|
|
369
|
-
placements: {
|
|
370
|
-
list: async (viewId: number): Promise<ElementPlacement[]> => {
|
|
371
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
372
|
-
return placements.map((p) => ({ id: p.id, view_id: viewId, element_id: p.element_id, position_x: p.position_x, position_y: p.position_y }))
|
|
373
|
-
},
|
|
374
|
-
|
|
375
|
-
add: async (viewId: number, elementId: number, x = 100, y = 100): Promise<ElementPlacement> => {
|
|
376
|
-
const elements = load<LibraryElement[]>(K.elements, DEMO_ELEMENTS)
|
|
377
|
-
const el = elements.find((e) => e.id === elementId)
|
|
378
|
-
if (!el) throw new Error(`Element ${elementId} not found`)
|
|
379
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
380
|
-
const id = nextId()
|
|
381
|
-
const newPlacement: PlacedElement = {
|
|
382
|
-
id,
|
|
383
|
-
view_id: viewId,
|
|
384
|
-
element_id: elementId,
|
|
385
|
-
position_x: x,
|
|
386
|
-
position_y: y,
|
|
387
|
-
name: el.name,
|
|
388
|
-
kind: el.kind,
|
|
389
|
-
description: el.description,
|
|
390
|
-
technology: el.technology,
|
|
391
|
-
url: el.url,
|
|
392
|
-
logo_url: el.logo_url,
|
|
393
|
-
technology_connectors: el.technology_connectors,
|
|
394
|
-
tags: el.tags,
|
|
395
|
-
repo: el.repo,
|
|
396
|
-
branch: el.branch,
|
|
397
|
-
file_path: el.file_path,
|
|
398
|
-
language: el.language,
|
|
399
|
-
has_view: el.has_view,
|
|
400
|
-
view_label: el.view_label,
|
|
401
|
-
}
|
|
402
|
-
save(K.placements(viewId), [...placements, newPlacement])
|
|
403
|
-
return { id, view_id: viewId, element_id: elementId, position_x: x, position_y: y }
|
|
404
|
-
},
|
|
405
|
-
|
|
406
|
-
updatePosition: async (viewId: number, elementId: number, x: number, y: number): Promise<void> => {
|
|
407
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
408
|
-
save(K.placements(viewId), placements.map((p) => p.element_id === elementId ? { ...p, position_x: x, position_y: y } : p))
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
remove: async (viewId: number, elementId: number): Promise<void> => {
|
|
412
|
-
const placements = load<PlacedElement[]>(K.placements(viewId), [])
|
|
413
|
-
save(K.placements(viewId), placements.filter((p) => p.element_id !== elementId))
|
|
414
|
-
},
|
|
415
|
-
},
|
|
416
|
-
|
|
417
|
-
layers: {
|
|
418
|
-
list: async (viewId: number): Promise<ViewLayer[]> => {
|
|
419
|
-
return load<ViewLayer[]>(K.layers(viewId), [])
|
|
420
|
-
},
|
|
421
|
-
create: async (viewId: number, data: { name: string; tags: string[]; color?: string }): Promise<ViewLayer> => {
|
|
422
|
-
const layers = load<ViewLayer[]>(K.layers(viewId), [])
|
|
423
|
-
const id = nextId()
|
|
424
|
-
const newLayer: ViewLayer = { id, diagram_id: viewId, name: data.name, tags: data.tags, color: data.color ?? '#888888', created_at: NOW(), updated_at: NOW() }
|
|
425
|
-
save(K.layers(viewId), [...layers, newLayer])
|
|
426
|
-
return newLayer
|
|
427
|
-
},
|
|
428
|
-
update: async (viewId: number, layerId: number, data: Partial<ViewLayer>): Promise<ViewLayer> => {
|
|
429
|
-
const layers = load<ViewLayer[]>(K.layers(viewId), [])
|
|
430
|
-
const idx = layers.findIndex((l) => l.id === layerId)
|
|
431
|
-
if (idx === -1) throw new Error(`Layer ${layerId} not found`)
|
|
432
|
-
const updated = { ...layers[idx], ...data, id: layerId, updated_at: NOW() }
|
|
433
|
-
const next = [...layers]
|
|
434
|
-
next[idx] = updated
|
|
435
|
-
save(K.layers(viewId), next)
|
|
436
|
-
return updated
|
|
437
|
-
},
|
|
438
|
-
delete: async (viewId: number, layerId: number): Promise<void> => {
|
|
439
|
-
const layers = load<ViewLayer[]>(K.layers(viewId), [])
|
|
440
|
-
save(K.layers(viewId), layers.filter((l) => l.id !== layerId))
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
|
-
|
|
444
|
-
reactions: {
|
|
445
|
-
list: async (_viewId: number) => [],
|
|
446
|
-
},
|
|
447
|
-
|
|
448
|
-
threads: {
|
|
449
|
-
listForElement: async () => [],
|
|
450
|
-
listForConnector: async () => [],
|
|
451
|
-
createForElement: async () => { throw new Error('Demo: threads not supported') },
|
|
452
|
-
createForConnector: async () => { throw new Error('Demo: threads not supported') },
|
|
453
|
-
addComment: async () => { throw new Error('Demo: comments not supported') },
|
|
454
|
-
resolve: async () => { /* no-op */ },
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
thumbnail: (_id: number) => Promise.resolve(null),
|
|
458
|
-
rename: async (id: number, name: string) => demoApi.workspace.views.update(id, { name }),
|
|
459
|
-
setLevel: async () => { /* no-op */ },
|
|
460
|
-
reparent: async () => { throw new Error('Demo: reparent not supported') },
|
|
461
|
-
},
|
|
462
|
-
|
|
463
|
-
connectors: {
|
|
464
|
-
list: async (viewId: number): Promise<Connector[]> => {
|
|
465
|
-
return load<Connector[]>(K.connectors(viewId), [])
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
create: async (
|
|
469
|
-
viewId: number,
|
|
470
|
-
data: {
|
|
471
|
-
source_element_id: number; target_element_id: number
|
|
472
|
-
label?: string; description?: string; relationship?: string
|
|
473
|
-
direction?: string; style?: string; url?: string
|
|
474
|
-
source_handle?: string | null; target_handle?: string | null
|
|
475
|
-
},
|
|
476
|
-
): Promise<Connector> => {
|
|
477
|
-
const connectors = load<Connector[]>(K.connectors(viewId), [])
|
|
478
|
-
const id = nextId()
|
|
479
|
-
const now = NOW()
|
|
480
|
-
const newConnector: Connector = {
|
|
481
|
-
id,
|
|
482
|
-
view_id: viewId,
|
|
483
|
-
source_element_id: data.source_element_id,
|
|
484
|
-
target_element_id: data.target_element_id,
|
|
485
|
-
label: data.label ?? null,
|
|
486
|
-
description: data.description ?? null,
|
|
487
|
-
relationship: data.relationship ?? null,
|
|
488
|
-
direction: data.direction ?? 'forward',
|
|
489
|
-
style: data.style ?? 'bezier',
|
|
490
|
-
url: data.url ?? null,
|
|
491
|
-
source_handle: data.source_handle ?? null,
|
|
492
|
-
target_handle: data.target_handle ?? null,
|
|
493
|
-
created_at: now,
|
|
494
|
-
updated_at: now,
|
|
495
|
-
}
|
|
496
|
-
save(K.connectors(viewId), [...connectors, newConnector])
|
|
497
|
-
return newConnector
|
|
498
|
-
},
|
|
499
|
-
|
|
500
|
-
update: async (
|
|
501
|
-
viewId: number,
|
|
502
|
-
connectorId: number,
|
|
503
|
-
data: Partial<Connector>,
|
|
504
|
-
): Promise<Connector> => {
|
|
505
|
-
const connectors = load<Connector[]>(K.connectors(viewId), [])
|
|
506
|
-
const idx = connectors.findIndex((c) => c.id === connectorId)
|
|
507
|
-
if (idx === -1) throw new Error(`Connector ${connectorId} not found`)
|
|
508
|
-
const updated = { ...connectors[idx], ...data, id: connectorId, updated_at: NOW() }
|
|
509
|
-
const next = [...connectors]
|
|
510
|
-
next[idx] = updated
|
|
511
|
-
save(K.connectors(viewId), next)
|
|
512
|
-
return updated
|
|
513
|
-
},
|
|
514
|
-
|
|
515
|
-
delete: async (_orgId: string, connectorId: number): Promise<void> => {
|
|
516
|
-
for (const viewId of getAllViewIds()) {
|
|
517
|
-
const connectors = load<Connector[]>(K.connectors(viewId), [])
|
|
518
|
-
const filtered = connectors.filter((c) => c.id !== connectorId)
|
|
519
|
-
if (filtered.length !== connectors.length) {
|
|
520
|
-
save(K.connectors(viewId), filtered)
|
|
521
|
-
break
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
|
|
527
|
-
elements: {
|
|
528
|
-
list: (params?: unknown) => demoApi.elements.list(params),
|
|
529
|
-
get: (id: number) => demoApi.elements.get(id),
|
|
530
|
-
create: (data: Partial<LibraryElement>) => demoApi.elements.create(data),
|
|
531
|
-
update: (id: number, data: Partial<LibraryElement>) => demoApi.elements.update(id, data),
|
|
532
|
-
delete: (orgId: string, id: number) => demoApi.elements.delete(orgId, id),
|
|
533
|
-
placements: (id: number) => demoApi.elements.placements(id),
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
}
|