@stocksharp/diagram 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/esm/canvas-renderer.js +28 -22
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/core/action-registry.js +24 -0
- package/dist/esm/core/action-registry.js.map +1 -1
- package/dist/esm/core/document.js +85 -28
- package/dist/esm/core/document.js.map +1 -1
- package/dist/esm/core/json.js +17 -0
- package/dist/esm/core/json.js.map +1 -0
- package/dist/esm/core/model.js +4 -0
- package/dist/esm/core/model.js.map +1 -1
- package/dist/esm/core/state.js +16 -2
- package/dist/esm/core/state.js.map +1 -1
- package/dist/esm/diagram/catalog.js +17 -5
- package/dist/esm/diagram/catalog.js.map +1 -1
- package/dist/esm/diagram/event-emitter.js +10 -0
- package/dist/esm/diagram/event-emitter.js.map +1 -1
- package/dist/esm/diagram/stocksharp-diagram.js +69 -57
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/diagram/types.js +2 -1
- package/dist/esm/diagram/types.js.map +1 -1
- package/dist/esm/embed.js +129 -39
- package/dist/esm/embed.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/ssdiagram.js +314 -147
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/canvas-renderer.d.ts +3 -5
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/core/action-registry.d.ts +7 -0
- package/dist/types/core/action-registry.d.ts.map +1 -1
- package/dist/types/core/document.d.ts.map +1 -1
- package/dist/types/core/json.d.ts +11 -0
- package/dist/types/core/json.d.ts.map +1 -0
- package/dist/types/core/model.d.ts +10 -1
- package/dist/types/core/model.d.ts.map +1 -1
- package/dist/types/core/state.d.ts +24 -1
- package/dist/types/core/state.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +13 -6
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/catalog.d.ts +2 -1
- package/dist/types/diagram/catalog.d.ts.map +1 -1
- package/dist/types/diagram/event-emitter.d.ts +6 -1
- package/dist/types/diagram/event-emitter.d.ts.map +1 -1
- package/dist/types/diagram/palette.d.ts +1 -1
- package/dist/types/diagram/palette.d.ts.map +1 -1
- package/dist/types/diagram/stocksharp-diagram.d.ts +32 -21
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/diagram/types.d.ts +4 -4
- package/dist/types/diagram/types.d.ts.map +1 -1
- package/dist/types/embed.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/canvas-renderer.ts +26 -24
- package/src/core/action-registry.ts +25 -0
- package/src/core/document.ts +91 -31
- package/src/core/json.ts +16 -0
- package/src/core/model.ts +14 -1
- package/src/core/state.ts +41 -3
- package/src/diagram/api.ts +14 -5
- package/src/diagram/catalog.ts +18 -6
- package/src/diagram/event-emitter.ts +12 -1
- package/src/diagram/palette.ts +1 -1
- package/src/diagram/stocksharp-diagram.ts +91 -75
- package/src/diagram/types.ts +6 -5
- package/src/embed.ts +149 -40
- package/src/index.ts +4 -0
package/src/diagram/api.ts
CHANGED
|
@@ -2,15 +2,24 @@ import type { DiagramDocument } from '../core/model.js';
|
|
|
2
2
|
import type {
|
|
3
3
|
DiagramRuntimeState,
|
|
4
4
|
DiagramSelection,
|
|
5
|
+
DiagramSnapshot,
|
|
5
6
|
DiagramViewState,
|
|
6
7
|
} from '../core/state.js';
|
|
7
8
|
import type { DiagramNode, Link, Port, PortDirection } from './types.js';
|
|
8
9
|
|
|
10
|
+
/** What the fullscreen button calls itself, in the host page's language. */
|
|
11
|
+
export interface DiagramFullscreenLabels {
|
|
12
|
+
enter: string;
|
|
13
|
+
exit: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export interface DiagramOptions {
|
|
10
17
|
div: HTMLElement;
|
|
11
18
|
catalog: import('./catalog.js').StockSharpCatalog;
|
|
12
19
|
/** Show the built-in top-right fullscreen request button. Defaults to true. */
|
|
13
20
|
showFullscreenButton?: boolean;
|
|
21
|
+
/** Tooltip/aria text for that button. Defaults to English. */
|
|
22
|
+
fullscreenLabels?: DiagramFullscreenLabels;
|
|
14
23
|
overviewContainer?: HTMLElement | null;
|
|
15
24
|
zoomLabel?: HTMLElement | null;
|
|
16
25
|
/** Optional system clipboard adapter. Pass null to force memory-only clipboard. */
|
|
@@ -224,7 +233,7 @@ export interface LinkValidatorArgs {
|
|
|
224
233
|
|
|
225
234
|
export type LinkValidator = (args: LinkValidatorArgs) => boolean;
|
|
226
235
|
|
|
227
|
-
export interface DiagramEvents
|
|
236
|
+
export interface DiagramEvents {
|
|
228
237
|
nodeAdded: NodeChangePayload;
|
|
229
238
|
nodeRemoved: NodeChangePayload;
|
|
230
239
|
linkAdded: LinkChangePayload;
|
|
@@ -248,10 +257,10 @@ export interface DiagramEvents extends Record<string, unknown> {
|
|
|
248
257
|
nodeProperties: NodeChangePayload;
|
|
249
258
|
nodeOpen: NodeChangePayload;
|
|
250
259
|
nodeHelp: NodeChangePayload;
|
|
251
|
-
zoomChanged: DiagramViewState
|
|
252
|
-
viewChanged: DiagramViewState
|
|
253
|
-
selectionChanged: DiagramSelection
|
|
254
|
-
runtimeStateChanged: { state: DiagramRuntimeState };
|
|
260
|
+
zoomChanged: DiagramSnapshot<DiagramViewState>;
|
|
261
|
+
viewChanged: DiagramSnapshot<DiagramViewState>;
|
|
262
|
+
selectionChanged: DiagramSnapshot<DiagramSelection>;
|
|
263
|
+
runtimeStateChanged: { state: DiagramSnapshot<DiagramRuntimeState> };
|
|
255
264
|
undoStackChanged: { canUndo: boolean; canRedo: boolean };
|
|
256
265
|
documentLoaded: { document: DiagramDocument };
|
|
257
266
|
documentLoadFailed: DocumentLoadFailedPayload;
|
package/src/diagram/catalog.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from './event-emitter.js';
|
|
2
2
|
import { Node, NodeInit, PortType, PortTypeInit } from './types.js';
|
|
3
3
|
|
|
4
|
-
export interface CatalogEvents
|
|
4
|
+
export interface CatalogEvents {
|
|
5
5
|
portTypesChanged: PortType[];
|
|
6
6
|
nodeTypesChanged: Node[];
|
|
7
7
|
}
|
|
@@ -35,21 +35,33 @@ export class StockSharpCatalog extends EventEmitter<CatalogEvents> {
|
|
|
35
35
|
// imported node ("Element type is missing from the palette"). Node.id
|
|
36
36
|
// keeps its original casing so save/load round-trips unchanged.
|
|
37
37
|
addNodeType(node: Node | NodeInit): void {
|
|
38
|
-
|
|
38
|
+
// Copied on the way in as well as on the way out: otherwise the caller
|
|
39
|
+
// keeps a handle on the stored definition and can edit it afterwards.
|
|
40
|
+
// The clone covers the init branch too -- Node's constructor keeps the
|
|
41
|
+
// caller's Port instances and parameters array as they are.
|
|
42
|
+
const n = (node instanceof Node ? node : new Node(node)).clone();
|
|
39
43
|
this.nodeTypes.set(n.id.toLowerCase(), n);
|
|
40
|
-
this.
|
|
44
|
+
this.emitNodeTypesChanged();
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
removeNodeType(id: string): void {
|
|
44
48
|
this.nodeTypes.delete(id.toLowerCase());
|
|
45
|
-
this.
|
|
49
|
+
this.emitNodeTypesChanged();
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
getNodeType(id: string): Node | null {
|
|
49
|
-
return this.nodeTypes.get(id.toLowerCase()) ?? null;
|
|
53
|
+
return this.nodeTypes.get(id.toLowerCase())?.clone() ?? null;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
getNodeTypes(): Node[] {
|
|
53
|
-
return Array.from(this.nodeTypes.values());
|
|
57
|
+
return Array.from(this.nodeTypes.values(), (node) => node.clone());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Node definitions are mutable, so the payload has to be a copy of each one.
|
|
61
|
+
// Building a catalog is a loop of addNodeType calls, which would make that
|
|
62
|
+
// quadratic; skipping it while nobody listens keeps catalog construction --
|
|
63
|
+
// where no subscriber exists yet -- as cheap as it was.
|
|
64
|
+
private emitNodeTypesChanged(): void {
|
|
65
|
+
if (this.hasHandlers('nodeTypesChanged')) this.emit('nodeTypesChanged', this.getNodeTypes());
|
|
54
66
|
}
|
|
55
67
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export type EventHandler<T> = (payload: T) => void;
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// TEvents is constrained to object rather than Record<string, unknown>: the
|
|
4
|
+
// latter forces every event map to carry a string index signature, which
|
|
5
|
+
// collapses keyof TEvents to string and lets any misspelled name compile.
|
|
6
|
+
export class EventEmitter<TEvents extends object> {
|
|
4
7
|
private readonly handlers = new Map<keyof TEvents, Set<EventHandler<unknown>>>();
|
|
5
8
|
|
|
6
9
|
on<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): () => void {
|
|
@@ -38,6 +41,14 @@ export class EventEmitter<TEvents extends Record<string, unknown>> {
|
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Whether anyone is listening. Lets a subject skip building a payload that
|
|
46
|
+
* is expensive to produce and that nothing would read.
|
|
47
|
+
*/
|
|
48
|
+
protected hasHandlers<K extends keyof TEvents>(event: K): boolean {
|
|
49
|
+
return (this.handlers.get(event)?.size ?? 0) > 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
protected clearEventHandlers(): void {
|
|
42
53
|
this.handlers.clear();
|
|
43
54
|
}
|
package/src/diagram/palette.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface PaletteContextMenuPayload extends PaletteNodePayload {
|
|
|
22
22
|
y: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export interface PaletteEvents
|
|
25
|
+
export interface PaletteEvents {
|
|
26
26
|
selectionChanged: PaletteSelectionChangedPayload;
|
|
27
27
|
nodeActivated: PaletteNodePayload;
|
|
28
28
|
contextMenuRequested: PaletteContextMenuPayload;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
parseDiagramViewState,
|
|
7
7
|
serializeDiagramViewState,
|
|
8
8
|
} from '../core/view-state.js';
|
|
9
|
-
import type { DiagramDocument } from '../core/model.js';
|
|
9
|
+
import type { DiagramDocument, PortDynamicMode } from '../core/model.js';
|
|
10
10
|
import { DiagramActionRegistry } from '../core/action-registry.js';
|
|
11
11
|
import type {
|
|
12
12
|
DiagramGlobalErrorKind,
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
DiagramPortRuntimeState,
|
|
15
15
|
DiagramRuntimeState,
|
|
16
16
|
DiagramSelection,
|
|
17
|
+
DiagramSnapshot,
|
|
17
18
|
DiagramViewState,
|
|
18
19
|
} from '../core/state.js';
|
|
19
20
|
import {
|
|
@@ -32,6 +33,7 @@ import type {
|
|
|
32
33
|
DiagramClipboard,
|
|
33
34
|
ContextCommand,
|
|
34
35
|
DiagramLoadOptions,
|
|
36
|
+
DiagramFullscreenLabels,
|
|
35
37
|
DiagramGridSettings,
|
|
36
38
|
DiagramNodeBounds,
|
|
37
39
|
DiagramPoint,
|
|
@@ -75,7 +77,7 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
75
77
|
private destroyed = false;
|
|
76
78
|
private fullscreen = false;
|
|
77
79
|
private fullscreenButtonVisible = true;
|
|
78
|
-
private
|
|
80
|
+
private fullscreenLabels: DiagramFullscreenLabels = { enter: 'Enter fullscreen', exit: 'Exit fullscreen' };
|
|
79
81
|
private readonly contextActions = new DiagramActionRegistry<ContextCommand, ContextActionContext>();
|
|
80
82
|
|
|
81
83
|
constructor(options: DiagramOptions) {
|
|
@@ -92,6 +94,7 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
92
94
|
gridSize: options.gridSize,
|
|
93
95
|
});
|
|
94
96
|
this.fullscreenButtonVisible = options.showFullscreenButton ?? true;
|
|
97
|
+
this.fullscreenLabels = options.fullscreenLabels ?? this.fullscreenLabels;
|
|
95
98
|
this.prepareFullscreenButtonHost();
|
|
96
99
|
this.fullscreenButton = this.createFullscreenButton();
|
|
97
100
|
this.updateFullscreenButton();
|
|
@@ -102,7 +105,6 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
setLinkValidator(validator: LinkValidator | null): void {
|
|
105
|
-
this.linkValidator = validator;
|
|
106
108
|
this.canvas.setLinkValidator(validator === null ? null : ({ fromNode, fromPort, toNode, toPort }) => validator({
|
|
107
109
|
fromNode: this.fromCanvasNode(fromNode),
|
|
108
110
|
fromPort: this.fromCanvasPort(fromPort),
|
|
@@ -175,7 +177,7 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
175
177
|
this.canvas.setGridSnap(enabled, size);
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
getGridSnap(): DiagramGridSettings {
|
|
180
|
+
getGridSnap(): DiagramSnapshot<DiagramGridSettings> {
|
|
179
181
|
return this.canvas.getGridSnap();
|
|
180
182
|
}
|
|
181
183
|
|
|
@@ -199,29 +201,33 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
199
201
|
this.canvas.removeLink(this.toCanvasLink(link));
|
|
200
202
|
}
|
|
201
203
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
205
|
+
addPort(nodeId: string, direction: PortDirection, port: Port): boolean {
|
|
206
|
+
return this.canvas.addPort(nodeId, direction, this.toCanvasPort(port));
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
210
|
+
removePort(nodeId: string, direction: PortDirection, portId: string): boolean {
|
|
211
|
+
return this.canvas.removePort(nodeId, direction, portId);
|
|
208
212
|
}
|
|
209
213
|
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
215
|
+
updatePortType(nodeId: string, direction: PortDirection, portId: string, type: string): boolean {
|
|
216
|
+
return this.canvas.updatePortType(nodeId, direction, portId, type);
|
|
212
217
|
}
|
|
213
218
|
|
|
214
219
|
updatePort(nodeId: string, direction: PortDirection, portId: string, patch: PortUpdate): boolean {
|
|
215
220
|
return this.canvas.updatePort(nodeId, direction, portId, patch);
|
|
216
221
|
}
|
|
217
222
|
|
|
223
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
218
224
|
setNodePorts(
|
|
219
225
|
nodeId: string,
|
|
220
|
-
inPorts: ReadonlyArray<{ key: string; name: string; description: string; type: string; maxLinks: number; availableTypes?: string[]; isDynamic?: boolean; dynamicMode?:
|
|
221
|
-
outPorts: ReadonlyArray<{ key: string; name: string; description: string; type: string; maxLinks: number; availableTypes?: string[]; isDynamic?: boolean; dynamicMode?:
|
|
222
|
-
):
|
|
226
|
+
inPorts: ReadonlyArray<{ key: string; name: string; description: string; type: string; maxLinks: number; availableTypes?: string[]; isDynamic?: boolean; dynamicMode?: PortDynamicMode }>,
|
|
227
|
+
outPorts: ReadonlyArray<{ key: string; name: string; description: string; type: string; maxLinks: number; availableTypes?: string[]; isDynamic?: boolean; dynamicMode?: PortDynamicMode }>,
|
|
228
|
+
): boolean {
|
|
223
229
|
const current = this.canvas.findNode(nodeId);
|
|
224
|
-
if (current === undefined) return;
|
|
230
|
+
if (current === undefined) return false;
|
|
225
231
|
const convert = (port: typeof inPorts[number]): CanvasPortInit => ({
|
|
226
232
|
id: port.key,
|
|
227
233
|
name: port.name,
|
|
@@ -240,15 +246,17 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
240
246
|
for (const sibling of current.outPorts.filter((port) => port.isSibling)) {
|
|
241
247
|
if (!nextOut.some((port) => port.id === sibling.id)) nextOut.push(sibling.toInit());
|
|
242
248
|
}
|
|
243
|
-
this.canvas.setNodePorts(nodeId, nextIn, nextOut);
|
|
249
|
+
return this.canvas.setNodePorts(nodeId, nextIn, nextOut);
|
|
244
250
|
}
|
|
245
251
|
|
|
246
|
-
|
|
247
|
-
|
|
252
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
253
|
+
updateNode(nodeId: string, patch: { name?: string; description?: string; color?: string; border?: string }): boolean {
|
|
254
|
+
return this.canvas.updateNode(nodeId, patch);
|
|
248
255
|
}
|
|
249
256
|
|
|
250
|
-
|
|
251
|
-
|
|
257
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
258
|
+
setNodeMessage(nodeId: string, message: string): boolean {
|
|
259
|
+
return this.canvas.updateNode(nodeId, { message });
|
|
252
260
|
}
|
|
253
261
|
|
|
254
262
|
setNodeError(nodeId: string, message: string, options: NodeErrorOptions = {}): boolean {
|
|
@@ -259,11 +267,11 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
259
267
|
return this.canvas.clearNodeError(nodeId, kind);
|
|
260
268
|
}
|
|
261
269
|
|
|
262
|
-
getRuntimeState(): DiagramRuntimeState {
|
|
270
|
+
getRuntimeState(): DiagramSnapshot<DiagramRuntimeState> {
|
|
263
271
|
return this.canvas.getRuntimeState();
|
|
264
272
|
}
|
|
265
273
|
|
|
266
|
-
setRuntimeState(state: DiagramRuntimeState): void {
|
|
274
|
+
setRuntimeState(state: DiagramSnapshot<DiagramRuntimeState>): void {
|
|
267
275
|
this.canvas.setRuntimeState(state);
|
|
268
276
|
}
|
|
269
277
|
|
|
@@ -288,12 +296,14 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
288
296
|
this.canvas.setGlobalError(message, kind);
|
|
289
297
|
}
|
|
290
298
|
|
|
291
|
-
|
|
292
|
-
|
|
299
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
300
|
+
setNodeParamValue(nodeId: string, paramName: string, value: string | undefined): boolean {
|
|
301
|
+
return this.canvas.setNodeParamValue(nodeId, paramName, value);
|
|
293
302
|
}
|
|
294
303
|
|
|
295
|
-
|
|
296
|
-
|
|
304
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
305
|
+
setNodeName(nodeId: string, value: string): boolean {
|
|
306
|
+
return this.canvas.updateNode(nodeId, { name: value });
|
|
297
307
|
}
|
|
298
308
|
|
|
299
309
|
/** Groups host-driven document edits into one undo/redo operation. */
|
|
@@ -309,7 +319,7 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
309
319
|
this.canvas.setReadOnly(readonly);
|
|
310
320
|
}
|
|
311
321
|
|
|
312
|
-
getInteractionPermissions(): DiagramInteractionPermissions {
|
|
322
|
+
getInteractionPermissions(): DiagramSnapshot<DiagramInteractionPermissions> {
|
|
313
323
|
return this.canvas.getInteractionPermissions();
|
|
314
324
|
}
|
|
315
325
|
|
|
@@ -338,11 +348,11 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
338
348
|
this.canvas.setZoom(scale);
|
|
339
349
|
}
|
|
340
350
|
|
|
341
|
-
getViewState(): DiagramViewState {
|
|
351
|
+
getViewState(): DiagramSnapshot<DiagramViewState> {
|
|
342
352
|
return this.canvas.getViewState();
|
|
343
353
|
}
|
|
344
354
|
|
|
345
|
-
setViewState(state: DiagramViewState): void {
|
|
355
|
+
setViewState(state: DiagramSnapshot<DiagramViewState>): void {
|
|
346
356
|
this.canvas.setViewState(state);
|
|
347
357
|
this.overviewContainer?.classList.toggle('hidden', !state.overviewVisible);
|
|
348
358
|
}
|
|
@@ -362,7 +372,7 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
362
372
|
return this.canvas.takeScreenshot(options);
|
|
363
373
|
}
|
|
364
374
|
|
|
365
|
-
getSelection(): DiagramSelection {
|
|
375
|
+
getSelection(): DiagramSnapshot<DiagramSelection> {
|
|
366
376
|
return this.canvas.getSelection();
|
|
367
377
|
}
|
|
368
378
|
|
|
@@ -396,6 +406,16 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
396
406
|
return this.fullscreenButtonVisible;
|
|
397
407
|
}
|
|
398
408
|
|
|
409
|
+
/** Text of the fullscreen button, for a host that renders in another language. */
|
|
410
|
+
setFullscreenLabels(labels: DiagramSnapshot<DiagramFullscreenLabels>): void {
|
|
411
|
+
this.fullscreenLabels = labels;
|
|
412
|
+
this.updateFullscreenButton();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
getFullscreenLabels(): DiagramSnapshot<DiagramFullscreenLabels> {
|
|
416
|
+
return { ...this.fullscreenLabels };
|
|
417
|
+
}
|
|
418
|
+
|
|
399
419
|
/** Updates only the control's state after the host changed its own layout. */
|
|
400
420
|
setFullscreenState(fullscreen: boolean): void {
|
|
401
421
|
if (this.fullscreen === fullscreen) return;
|
|
@@ -625,7 +645,7 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
625
645
|
|
|
626
646
|
private updateFullscreenButton(): void {
|
|
627
647
|
const fullscreen = this.fullscreen;
|
|
628
|
-
const label = fullscreen ?
|
|
648
|
+
const label = fullscreen ? this.fullscreenLabels.exit : this.fullscreenLabels.enter;
|
|
629
649
|
const path = fullscreen
|
|
630
650
|
? 'M4 9h5V4M20 9h-5V4M4 15h5v5M20 15h-5v5'
|
|
631
651
|
: 'M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5';
|
|
@@ -747,51 +767,47 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
747
767
|
|
|
748
768
|
private registerContextActions(): void {
|
|
749
769
|
const permissions = () => this.canvas.getInteractionPermissions();
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
this.contextActions.register({
|
|
792
|
-
id: 'help',
|
|
793
|
-
canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
|
|
794
|
-
execute: ({ nodes }) => this.emit('nodeHelp', { nodes }),
|
|
770
|
+
// Keyed by ContextCommand, so a command added to the union without an
|
|
771
|
+
// entry here fails to compile rather than quietly missing from the menu.
|
|
772
|
+
// Key order is menu order.
|
|
773
|
+
this.contextActions.registerAll({
|
|
774
|
+
undo: {
|
|
775
|
+
canExecute: () => this.canUndo(),
|
|
776
|
+
execute: () => this.undo(),
|
|
777
|
+
},
|
|
778
|
+
redo: {
|
|
779
|
+
canExecute: () => this.canRedo(),
|
|
780
|
+
execute: () => this.redo(),
|
|
781
|
+
},
|
|
782
|
+
cut: {
|
|
783
|
+
canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy && permissions().deleteSelection,
|
|
784
|
+
execute: () => this.cutSelection(),
|
|
785
|
+
},
|
|
786
|
+
copy: {
|
|
787
|
+
canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy,
|
|
788
|
+
execute: () => this.copySelection(),
|
|
789
|
+
},
|
|
790
|
+
paste: {
|
|
791
|
+
canExecute: () => this.canvas.hasClipboard() && permissions().paste,
|
|
792
|
+
execute: () => this.pasteSelection(),
|
|
793
|
+
},
|
|
794
|
+
open: {
|
|
795
|
+
canExecute: ({ nodes }) => nodes.length === 1 && nodes[0].openAction.length > 0,
|
|
796
|
+
execute: ({ nodes }) => this.emit('nodeOpen', { nodes }),
|
|
797
|
+
},
|
|
798
|
+
delete: {
|
|
799
|
+
canExecute: ({ selection }) => permissions().deleteSelection
|
|
800
|
+
&& (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
|
|
801
|
+
execute: () => this.canvas.deleteSelection(),
|
|
802
|
+
},
|
|
803
|
+
properties: {
|
|
804
|
+
canExecute: ({ nodes }) => nodes.length > 0,
|
|
805
|
+
execute: ({ nodes }) => this.emit('nodeProperties', { nodes }),
|
|
806
|
+
},
|
|
807
|
+
help: {
|
|
808
|
+
canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
|
|
809
|
+
execute: ({ nodes }) => this.emit('nodeHelp', { nodes }),
|
|
810
|
+
},
|
|
795
811
|
});
|
|
796
812
|
}
|
|
797
813
|
|
package/src/diagram/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { toPortDynamicMode } from '../core/model.js';
|
|
2
|
+
import type { JsonObject, PortDynamicMode } from '../core/model.js';
|
|
2
3
|
|
|
3
4
|
// Core data model — node/port catalog (palette) and the live diagram (DiagramNode + Link).
|
|
4
5
|
|
|
@@ -29,7 +30,7 @@ export interface PortInit {
|
|
|
29
30
|
/// True when this port can be added/removed at runtime on a node.
|
|
30
31
|
isDynamic?: boolean;
|
|
31
32
|
/// "" / "manual" / "onConnect" — see PalettePortDto.dynamicMode docs.
|
|
32
|
-
dynamicMode?:
|
|
33
|
+
dynamicMode?: PortDynamicMode;
|
|
33
34
|
/// True when this port was spawned by the grow-on-connect pipeline.
|
|
34
35
|
/// Distinguishes anchor + runtime siblings on save (only siblings are
|
|
35
36
|
/// persisted in the diagram blob; anchors come from the palette schema).
|
|
@@ -47,7 +48,7 @@ export class Port {
|
|
|
47
48
|
maxLinks: number;
|
|
48
49
|
availableTypes: string[];
|
|
49
50
|
isDynamic: boolean;
|
|
50
|
-
dynamicMode:
|
|
51
|
+
dynamicMode: PortDynamicMode;
|
|
51
52
|
isSibling: boolean;
|
|
52
53
|
|
|
53
54
|
constructor(init: PortInit) {
|
|
@@ -58,7 +59,7 @@ export class Port {
|
|
|
58
59
|
this.maxLinks = typeof init.maxLinks === 'number' ? init.maxLinks : 0;
|
|
59
60
|
this.availableTypes = init.availableTypes ?? [];
|
|
60
61
|
this.isDynamic = init.isDynamic ?? false;
|
|
61
|
-
this.dynamicMode = init.dynamicMode
|
|
62
|
+
this.dynamicMode = toPortDynamicMode(init.dynamicMode);
|
|
62
63
|
this.isSibling = init.isSibling ?? false;
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -268,7 +269,7 @@ export interface PortData {
|
|
|
268
269
|
/// the anchor port spawns a sibling
|
|
269
270
|
/// on each link drop and the link is rerouted to the sibling.
|
|
270
271
|
isDynamic?: boolean;
|
|
271
|
-
dynamicMode?:
|
|
272
|
+
dynamicMode?: PortDynamicMode;
|
|
272
273
|
/// True when this port was spawned by the dynamic-growth pipeline rather
|
|
273
274
|
/// than declared on the palette element. Persists in the diagram blob so
|
|
274
275
|
/// reload restores the same set of sibling sockets.
|