@visuallyjs/browser-ui-svelte 1.2.0 → 1.2.2

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.
@@ -55,7 +55,7 @@
55
55
 
56
56
  function colorPicked() {
57
57
  const v = colorInput.value
58
- inspector.setValue(props.propertyName, v)
58
+ inspector.setValue(props.propertyName, v, null)
59
59
  addColor(v)
60
60
  }
61
61
 
@@ -78,7 +78,7 @@
78
78
  })
79
79
 
80
80
  function selectSwatch(c:string) {
81
- inspector.setValue(props.propertyName, c)
81
+ inspector.setValue(props.propertyName, c, null)
82
82
  colorInput.value = c
83
83
  }
84
84
 
@@ -30,7 +30,7 @@
30
30
  inspector = i
31
31
  const edgeMappings = resolveMappings()
32
32
  edgeTypePicker = new EdgeTypePicker(inspector.$ui, container, edgeMappings, inspector.getValue(propertyName), (v) => {
33
- inspector.setValue(propertyName, v)
33
+ inspector.setValue(propertyName, v, null)
34
34
  })
35
35
 
36
36
  edgeTypePicker.render(propertyName, inspector._currentCommonData)
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ import {BackgroundPlugin, GeneratedGridBackground} from "@visuallyjs/browser-ui"
3
+ import {useSurface} from "./use-surface.svelte";
4
+ import {usePaper} from "./use-paper.svelte";
5
+ import {type GridBackgroundComponentProps} from "./definitions";
6
+
7
+ let initialized = false
8
+ const surface = useSurface()
9
+ const paper = usePaper()
10
+
11
+ const props:GridBackgroundComponentProps = $props()
12
+
13
+ const o = props || {}
14
+
15
+ $effect(() => {
16
+ if (!initialized) {
17
+ if (surface.current) {
18
+ initialized = true
19
+ surface.current.addPlugin({
20
+ type: BackgroundPlugin.type,
21
+ options: {
22
+ type: GeneratedGridBackground.type,
23
+ ...o
24
+ }
25
+ })
26
+ }
27
+ if (paper.current) {
28
+ initialized = true
29
+ paper.current.addPlugin({
30
+ type: BackgroundPlugin.type,
31
+ options: {
32
+ type: GeneratedGridBackground.type,
33
+ ...o
34
+ }
35
+ })
36
+ }
37
+ }
38
+ })
39
+ </script>
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import {useSurface} from "./use-surface.svelte";
3
+ import { BackgroundPlugin, SimpleBackground } from "@visuallyjs/browser-ui"
4
+ import {usePaper} from "./use-paper.svelte";
5
+ import {ImageBackgroundComponentProps} from "./definitions";
6
+
7
+ let initialized = false
8
+ const surface = useSurface()
9
+ const paper = usePaper()
10
+
11
+ const { url }:ImageBackgroundComponentProps = $props()
12
+
13
+ $effect(() => {
14
+ if (!initialized) {
15
+ if (surface.current) {
16
+ initialized = true
17
+ surface.current.addPlugin({
18
+ type: BackgroundPlugin.type,
19
+ options: {
20
+ type: SimpleBackground.type,
21
+ url
22
+ }
23
+ })
24
+ }
25
+ if (paper.current) {
26
+ initialized = true
27
+ paper.current.addPlugin({
28
+ type: BackgroundPlugin.type,
29
+ options: {
30
+ type: SimpleBackground.type,
31
+ url
32
+ }
33
+ })
34
+ }
35
+ }
36
+ })
37
+ </script>
@@ -0,0 +1,35 @@
1
+ <script lang="ts">
2
+
3
+ // This is a placeholder component that the surface uses to render each Svelte overlay,
4
+ // via a portal
5
+ import {type BrowserElement, BrowserUI} from "@visuallyjs/browser-ui";
6
+
7
+ let {component, edge, ui, data, className, el} = $props()
8
+
9
+ // svelte wants uppercase here. So we just assign to an uppercase var, which we use in the
10
+ // template
11
+ const ComponentToRender = component
12
+
13
+ // Define the portal action
14
+ function portal(node: BrowserElement, target: BrowserElement) {
15
+ if (!target) return; // Ensure target exists
16
+
17
+ target.appendChild(node);
18
+
19
+ return {
20
+ destroy() {
21
+ if (target.contains(node)) {
22
+ target.removeChild(node);
23
+ }
24
+ }
25
+ };
26
+ }
27
+
28
+ const _ui = $state.raw(ui) as BrowserUI
29
+
30
+ </script>
31
+
32
+ <!-- Use the portal action on the root div -->
33
+ <div use:portal={el} class={className}>
34
+ <ComponentToRender this={component} data={data} ui={_ui} edge={edge} model={_ui.model}/>
35
+ </div>
@@ -0,0 +1,367 @@
1
+ <script lang="ts">
2
+ import { onMount, type Snippet } from "svelte";
3
+ import {
4
+ BrowserUISvelteModel,
5
+ newInstance,
6
+ type PaperComponentProps
7
+ } from "./index";
8
+ import {
9
+ isNode,
10
+ isGroup,
11
+ renderPaper,
12
+ uuid,
13
+ EVENT_RENDER_END,
14
+ CLASS_DEFAULT_GROUP,
15
+ CLASS_DEFAULT_NODE, Paper,
16
+ type BrowserElement,
17
+ type ObjectData,
18
+ Vertex,
19
+ partition,
20
+ type VertexUpdatedParams,
21
+ extend, EVENT_GRAPH_CLEARED, EVENT_NODE_UPDATED, EVENT_GROUP_UPDATED,
22
+ type NodeRemovedParams, EVENT_NODE_REMOVED,
23
+ type GroupRemovedParams, EVENT_GROUP_REMOVED,
24
+ type PaperOptions,
25
+ OVERLAY_TYPE_CUSTOM,
26
+ type Connection,
27
+ type EdgeUpdatedParams,
28
+ type EdgeRemovedParams,
29
+ EVENT_EDGE_UPDATED,
30
+ EVENT_EDGE_REMOVED,
31
+ ELEMENT_DIV, Edge
32
+ } from "@visuallyjs/browser-ui";
33
+ import { internal_getPaperContext } from "./surface-store";
34
+
35
+ import DefaultNodeComponent from './DefaultNodeComponent.svelte'
36
+ import DefaultGroupComponent from './DefaultGroupComponent.svelte'
37
+ import WrapperComponent from "./WrapperComponent.svelte";
38
+ import OverlayWrapperComponent from "./OverlayWrapperComponent.svelte";
39
+
40
+ let container:HTMLElement;
41
+
42
+ let {
43
+ className,
44
+ renderOptions,
45
+ viewOptions,
46
+ modelOptions,
47
+ model,
48
+ data,
49
+ url,
50
+ shapeLibrary,
51
+ injector,
52
+ children
53
+ }:PaperComponentProps = $props();
54
+
55
+ const paperContext = internal_getPaperContext(false);
56
+
57
+ let paper:Paper;
58
+
59
+ export function getPaper() {
60
+ return paper
61
+ }
62
+
63
+ export function getModel() {
64
+ return paper?.model
65
+ }
66
+
67
+ type ComponentHolder = {
68
+ component:any,
69
+ props:any,
70
+ vertex:Vertex,
71
+ def:any,
72
+ id:string
73
+ }
74
+
75
+ const componentMap:Map<string, ComponentHolder> = new Map();
76
+ const id = uuid();
77
+
78
+ // list of vertices to render
79
+ let _vertices:Array<ComponentHolder> = []
80
+
81
+ type OverlayHolder = {
82
+ component: any,
83
+ props: any,
84
+ edge: any,
85
+ id: string,
86
+ el:BrowserElement
87
+ }
88
+
89
+ let _overlays: Array<OverlayHolder> = []
90
+
91
+ // state wrapper around the vertex list
92
+ let vertexState = $state({
93
+ vertices:_vertices
94
+ })
95
+
96
+ let overlayState = $state({
97
+ overlays: _overlays
98
+ })
99
+
100
+ // manages updating the vertex state cleanly.
101
+ function _updateVertexState() {
102
+ vertexState.vertices.length = 0
103
+ vertexState.vertices.push(..._vertices.slice())
104
+ }
105
+
106
+ function _updateOverlayState() {
107
+ overlayState.overlays.length = 0
108
+ overlayState.overlays.push(..._overlays.slice())
109
+ }
110
+
111
+ function _initialiseSvelteOverlays() {
112
+ const edgeTypes = viewOptions?.edges || {}
113
+ for (let type in edgeTypes) {
114
+ const edgeDefinition = edgeTypes[type];
115
+ if (edgeDefinition.overlays) {
116
+ edgeDefinition.overlays = edgeDefinition.overlays.map((ov: any) => {
117
+ if (ov.component != null) {
118
+ return {
119
+ type: OVERLAY_TYPE_CUSTOM,
120
+ options: {
121
+ create: (c: Connection<BrowserElement>) => {
122
+ const el = document.createElement(ELEMENT_DIV);
123
+ const overlayProps = {
124
+ data: c.edge.data,
125
+ model: paper.model,
126
+ ui: paper,
127
+ edge: c.edge,
128
+ vertex: c.edge, // Some components might expect vertex
129
+ ...ov.props
130
+ };
131
+
132
+ const holder: OverlayHolder = {
133
+ component: ov.component,
134
+ props: overlayProps,
135
+ edge: c.edge,
136
+ id: uuid(),
137
+ el
138
+ };
139
+
140
+ _overlays.push(holder);
141
+ _updateOverlayState();
142
+ return el;
143
+ }
144
+ }
145
+ };
146
+ }
147
+ return ov;
148
+ });
149
+ }
150
+ }
151
+ }
152
+
153
+ function render ( id:string, data:ObjectData, model:BrowserUISvelteModel, type:string,
154
+ paper:Paper, def:any, modelObject:Vertex, eventInfo:any) {
155
+
156
+ const hasComponent = def.component != null
157
+ let component;
158
+ if (!hasComponent) {
159
+ if (isNode(modelObject)) {
160
+ component = DefaultNodeComponent;
161
+ } else if (isGroup(modelObject)) {
162
+ component = DefaultGroupComponent;
163
+ }
164
+ } else {
165
+ component = def.component;
166
+ }
167
+
168
+ if (component) {
169
+ let propsToSet:Record<string, any> = {
170
+ data: modelObject.data,
171
+ model,
172
+ ui:paper,
173
+ componentId: modelObject.getFullId(),
174
+ vertex: modelObject,
175
+ eventInfo,
176
+ className:hasComponent ? null : (isGroup(modelObject) ? CLASS_DEFAULT_GROUP : CLASS_DEFAULT_NODE)
177
+ }
178
+
179
+ if (injector != null) {
180
+ const injectedProps = injector(modelObject);
181
+ if (injectedProps != null) {
182
+ for (let key in injectedProps) {
183
+ propsToSet[key] = injectedProps[key];
184
+ }
185
+ }
186
+ }
187
+
188
+ const props = $state(propsToSet)
189
+ const componentHolder:ComponentHolder = {
190
+ component, props, vertex:modelObject, def, id:modelObject.id
191
+ }
192
+
193
+ _vertices.push(componentHolder)
194
+ _updateVertexState()
195
+ }
196
+
197
+ }
198
+
199
+ function cleanupVertex(objId:string, el:BrowserElement) {
200
+ el.parentNode && el.parentNode.removeChild(el);
201
+ }
202
+
203
+ onMount(async () => {
204
+ model = model || newInstance(modelOptions || {});
205
+
206
+ const templateRenderer = {
207
+ asynchronous: true,
208
+ reactive: true,
209
+ usesWrapperElement:true,
210
+ update: (el:BrowserElement, data:Object, vertex:Vertex) => {
211
+ const component = componentMap.get(vertex.id);
212
+ if (component) {
213
+ component.props.data = vertex.data
214
+ }
215
+ },
216
+ render,
217
+ cleanupVertex,
218
+ cleanupPort: (objId:string, el:BrowserElement) => {},
219
+ rerender: (
220
+ id:string,
221
+ data:ObjectData,
222
+ model:BrowserUISvelteModel,
223
+ type:string,
224
+ paper:Paper,
225
+ def:any,
226
+ modelObject:Vertex,
227
+ eventInfo:any,
228
+ originalElement:BrowserElement
229
+ ) => {
230
+
231
+ // split into vertices we want to keep and the one we want to discard
232
+ const p = partition(_vertices, (v) => v.vertex !== modelObject)
233
+ // assign the ones we want to keep, update state
234
+ _vertices = p.left
235
+ _updateVertexState()
236
+
237
+ // remove the old DOM element
238
+ if (p.right.length > 0) {
239
+ cleanupVertex(modelObject.id, originalElement)
240
+ }
241
+
242
+ // on next tick, render again. it's important to wait for the next tick or the
243
+ // vertices state array gets messed up
244
+ setTimeout(() => {
245
+ render(id, data, model, type, paper, def, modelObject, eventInfo)
246
+ })
247
+ }
248
+ };
249
+
250
+ const params = (renderOptions || {}) as PaperOptions;
251
+ params.view = (viewOptions || {}) as any;
252
+ params.id = id;
253
+
254
+ if (shapeLibrary != null) {
255
+ params.shapes = {
256
+ library: shapeLibrary,
257
+ };
258
+ }
259
+
260
+ _initialiseSvelteOverlays();
261
+
262
+ paper = renderPaper(model, container, templateRenderer, params);
263
+
264
+ // with continuous anchors it's necessary to do this, for
265
+ // asynchronous-y reasons. $redrawEveryConnection only redraws
266
+ // edges and does not update element positions so it's
267
+ // reasonably quick.
268
+ paper.bind(EVENT_RENDER_END, () => setTimeout(() => paper.$redrawEveryConnection()))
269
+
270
+ if (paperContext != null) {
271
+ paperContext.set(paper);
272
+ }
273
+
274
+ const updateVertex = (vertexId: string, eventPayload: VertexUpdatedParams) => {
275
+
276
+ let mergeData = () => {
277
+ let data = {}
278
+ if (eventPayload.originalData || eventPayload.updates) {
279
+ data = extend(eventPayload.originalData, eventPayload.updates)
280
+ }
281
+ return data
282
+ };
283
+
284
+ const entry = _vertices.find((v) => v.vertex.id === vertexId);
285
+ if (entry != null) {
286
+ entry.props.data = mergeData()
287
+ _updateVertexState()
288
+
289
+ entry.vertex.getAllEdges().forEach((edge:Edge) => updateOverlays(edge.id, {edge}))
290
+ }
291
+ }
292
+
293
+ const removeVertex = (id: string) => {
294
+ _vertices = _vertices.filter(n => n.vertex.id !== id)
295
+ _updateVertexState()
296
+ }
297
+
298
+ model.bind<VertexUpdatedParams>(EVENT_NODE_UPDATED, (p: VertexUpdatedParams) => {
299
+ updateVertex(p.vertex.getFullId(), p)
300
+ });
301
+
302
+ model.bind<VertexUpdatedParams>(EVENT_GROUP_UPDATED, (p: VertexUpdatedParams) => {
303
+ updateVertex(p.vertex.getFullId(), p)
304
+ });
305
+ //
306
+ model.bind<NodeRemovedParams>(EVENT_NODE_REMOVED, (p: NodeRemovedParams) => {
307
+ removeVertex(p.node.id)
308
+ });
309
+ //
310
+ model.bind<GroupRemovedParams>(EVENT_GROUP_REMOVED, (p: GroupRemovedParams) => {
311
+ removeVertex(p.group.id)
312
+ });
313
+
314
+ const updateOverlays = (edgeId: string, eventPayload: { edge:Edge} ) => {
315
+ _overlays.forEach(o => {
316
+ if (o.edge.id === edgeId) {
317
+ o.props.data = eventPayload.edge.data;
318
+ }
319
+ });
320
+ _updateOverlayState();
321
+ };
322
+
323
+ const removeOverlays = (edgeId: string) => {
324
+ _overlays = _overlays.filter(o => o.edge.id !== edgeId);
325
+ _updateOverlayState();
326
+ };
327
+
328
+ model.bind<EdgeUpdatedParams>(EVENT_EDGE_UPDATED, (p) => updateOverlays(p.edge.id, p));
329
+ model.bind<EdgeRemovedParams>(EVENT_EDGE_REMOVED, (p) => removeOverlays(p.edge.id));
330
+
331
+
332
+ model.bind(EVENT_GRAPH_CLEARED, () => {
333
+ _vertices = []
334
+ _updateVertexState()
335
+ _overlays = []
336
+ _updateOverlayState()
337
+ })
338
+
339
+ if (url != null) {
340
+ model.load({ url });
341
+ } else if (data != null) {
342
+ model.load({ data });
343
+ }
344
+ });
345
+
346
+ </script>
347
+
348
+ <div class={className} bind:this={container}>
349
+ {#each vertexState.vertices as vertex (vertex.id)}
350
+ <WrapperComponent component={vertex.component}
351
+ data={vertex.props.data}
352
+ ui={paper}
353
+ def={vertex.def}
354
+ vertex={vertex.vertex}
355
+ className={vertex.props.className}/>
356
+ {/each}
357
+
358
+ {#each overlayState.overlays as overlay (overlay.id)}
359
+ <OverlayWrapperComponent component={overlay.component}
360
+ data={overlay.props.data}
361
+ ui={paper}
362
+ edge={overlay.edge}
363
+ className="vjs-svelte-overlay"
364
+ el={overlay.el}/>
365
+ {/each}
366
+ {@render children?.()}
367
+ </div>
@@ -0,0 +1,11 @@
1
+ <script>
2
+
3
+ import { internal_getPaperContext } from "./surface-store"
4
+
5
+ const { children } = $props()
6
+
7
+ internal_getPaperContext(false)
8
+
9
+ </script>
10
+
11
+ {@render children?.()}