@stocksharp/diagram 0.4.1 → 0.6.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 +105 -2
- package/dist/esm/canvas-renderer.js +116 -36
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/diagram/context-menu.js +319 -0
- package/dist/esm/diagram/context-menu.js.map +1 -0
- package/dist/esm/diagram/stocksharp-diagram.js +121 -9
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/draw-surface.js +2 -0
- package/dist/esm/draw-surface.js.map +1 -0
- package/dist/esm/embed.js +7 -4
- package/dist/esm/embed.js.map +1 -1
- package/dist/esm/i18n.js +15 -4
- package/dist/esm/i18n.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/svg-surface.js +341 -0
- package/dist/esm/svg-surface.js.map +1 -0
- package/dist/ssdiagram.js +978 -188
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/canvas-renderer.d.ts +13 -0
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +42 -2
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/context-menu.d.ts +33 -0
- package/dist/types/diagram/context-menu.d.ts.map +1 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts +18 -5
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/draw-surface.d.ts +52 -0
- package/dist/types/draw-surface.d.ts.map +1 -0
- package/dist/types/embed.d.ts.map +1 -1
- package/dist/types/i18n.d.ts +12 -0
- package/dist/types/i18n.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/svg-surface.d.ts +74 -0
- package/dist/types/svg-surface.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/canvas-renderer.ts +120 -27
- package/src/diagram/api.ts +53 -2
- package/src/diagram/context-menu.ts +363 -0
- package/src/diagram/stocksharp-diagram.ts +134 -10
- package/src/draw-surface.ts +61 -0
- package/src/embed.ts +7 -4
- package/src/i18n.ts +41 -6
- package/src/index.ts +10 -0
- package/src/svg-surface.ts +430 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { t } from '../i18n.js';
|
|
2
|
+
/**
|
|
3
|
+
* The control's own context menu.
|
|
4
|
+
*
|
|
5
|
+
* It exists because right-click already suppresses the browser's menu whether or not anyone
|
|
6
|
+
* is listening. A control that takes the native menu away owes one back, so this is on by
|
|
7
|
+
* default; a host with its own menu turns it off (`showContextMenu: false`) and keeps using
|
|
8
|
+
* `contextMenuRequested`, which is emitted either way.
|
|
9
|
+
*
|
|
10
|
+
* Styling follows the fullscreen button: inline styles driven by `--ssdiagram-*` custom
|
|
11
|
+
* properties with sensible fallbacks, so the menu is usable with no stylesheet at all and
|
|
12
|
+
* retintable without one. Class names are stable for hosts that would rather use CSS.
|
|
13
|
+
*/
|
|
14
|
+
const ROOT_CLASS = 'ssdiagram-context-menu';
|
|
15
|
+
const ITEM_CLASS = 'ssdiagram-context-menu-item';
|
|
16
|
+
const SUBMENU_CLASS = 'ssdiagram-context-menu-submenu';
|
|
17
|
+
const SEPARATOR_CLASS = 'ssdiagram-context-menu-separator';
|
|
18
|
+
const LABEL_CLASS = 'ssdiagram-context-menu-label';
|
|
19
|
+
const ARROW_CLASS = 'ssdiagram-context-menu-arrow';
|
|
20
|
+
const CHECK_CLASS = 'ssdiagram-context-menu-check';
|
|
21
|
+
/** A rule below these entries, so related commands read as blocks rather than one long list. */
|
|
22
|
+
const SEPARATE_AFTER = new Set(['redo', 'paste', 'delete', 'overview']);
|
|
23
|
+
function label(id) {
|
|
24
|
+
switch (id) {
|
|
25
|
+
case 'undo': return t('undo', 'Undo');
|
|
26
|
+
case 'redo': return t('redo', 'Redo');
|
|
27
|
+
case 'cut': return t('cut', 'Cut');
|
|
28
|
+
case 'copy': return t('copy', 'Copy');
|
|
29
|
+
case 'paste': return t('paste', 'Paste');
|
|
30
|
+
case 'open': return t('ctxOpen', 'Open');
|
|
31
|
+
case 'delete': return t('ctxDelete', 'Delete');
|
|
32
|
+
case 'export': return t('ctxExportAs', 'Export as');
|
|
33
|
+
case 'exportDocument': return t('ctxExportDocument', 'Scheme');
|
|
34
|
+
case 'exportPng': return t('ctxExportPng', 'PNG image');
|
|
35
|
+
case 'exportSvg': return t('ctxExportSvg', 'SVG image');
|
|
36
|
+
case 'overview': return t('ctxOverview', 'Overview');
|
|
37
|
+
case 'properties': return t('properties', 'Properties');
|
|
38
|
+
case 'help': return t('ctxHelp', 'Help');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const PANEL_STYLE = {
|
|
42
|
+
position: 'fixed',
|
|
43
|
+
zIndex: '2147483000',
|
|
44
|
+
minWidth: '176px',
|
|
45
|
+
// Translations run longer than English -- German and Russian routinely half again. Without
|
|
46
|
+
// a cap the panel just grows, and on a narrow viewport it leaves the screen entirely, which
|
|
47
|
+
// no amount of repositioning can recover.
|
|
48
|
+
maxWidth: 'min(320px, calc(100vw - 16px))',
|
|
49
|
+
padding: '4px',
|
|
50
|
+
border: '1px solid var(--ssdiagram-menu-border, var(--ssdiagram-control-border, var(--t-border, #3a4250)))',
|
|
51
|
+
borderRadius: '7px',
|
|
52
|
+
background: 'var(--ssdiagram-menu-background, var(--ssdiagram-control-background, var(--t-panel, #181a20)))',
|
|
53
|
+
color: 'var(--ssdiagram-menu-color, var(--ssdiagram-control-color, var(--t-text, #eaecef)))',
|
|
54
|
+
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.45)',
|
|
55
|
+
font: 'var(--ssdiagram-menu-font, 12.5px/1.35 system-ui, -apple-system, "Segoe UI", sans-serif)',
|
|
56
|
+
boxSizing: 'border-box',
|
|
57
|
+
userSelect: 'none',
|
|
58
|
+
};
|
|
59
|
+
const ITEM_STYLE = {
|
|
60
|
+
display: 'flex',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
gap: '10px',
|
|
63
|
+
width: '100%',
|
|
64
|
+
padding: '6px 10px',
|
|
65
|
+
border: '0',
|
|
66
|
+
borderRadius: '4px',
|
|
67
|
+
background: 'transparent',
|
|
68
|
+
color: 'inherit',
|
|
69
|
+
font: 'inherit',
|
|
70
|
+
// Logical, not physical: on an RTL page these follow the writing direction instead of
|
|
71
|
+
// pinning the text left and truncating from the wrong end.
|
|
72
|
+
textAlign: 'start',
|
|
73
|
+
overflowWrap: 'anywhere',
|
|
74
|
+
cursor: 'pointer',
|
|
75
|
+
boxSizing: 'border-box',
|
|
76
|
+
};
|
|
77
|
+
const HOVER_BACKGROUND = 'var(--ssdiagram-menu-hover, var(--t-hover, rgba(255, 255, 255, 0.08)))';
|
|
78
|
+
const DISABLED_COLOR = 'var(--ssdiagram-menu-disabled-color, var(--t-text-dim, #848e9c))';
|
|
79
|
+
export class ContextMenuView {
|
|
80
|
+
constructor(options) {
|
|
81
|
+
this.root = null;
|
|
82
|
+
this.submenu = null;
|
|
83
|
+
this.submenuTriggers = new Map();
|
|
84
|
+
this.detach = [];
|
|
85
|
+
this.container = options.container;
|
|
86
|
+
this.execute = options.execute;
|
|
87
|
+
}
|
|
88
|
+
get isOpen() {
|
|
89
|
+
return this.root !== null;
|
|
90
|
+
}
|
|
91
|
+
show(x, y, items) {
|
|
92
|
+
this.hide();
|
|
93
|
+
const owner = this.container.ownerDocument ?? document;
|
|
94
|
+
const root = this.panel(owner);
|
|
95
|
+
root.className = ROOT_CLASS;
|
|
96
|
+
root.setAttribute('role', 'menu');
|
|
97
|
+
const gutter = items.some((item) => !('group' in item) && item.checked !== undefined);
|
|
98
|
+
items.forEach((item, index) => {
|
|
99
|
+
if ('group' in item)
|
|
100
|
+
root.appendChild(this.groupEntry(owner, item, gutter));
|
|
101
|
+
else
|
|
102
|
+
root.appendChild(this.commandEntry(owner, item.command, item.enabled, true, item.checked, gutter));
|
|
103
|
+
const id = 'group' in item ? item.group : item.command;
|
|
104
|
+
if (SEPARATE_AFTER.has(id) && index < items.length - 1)
|
|
105
|
+
root.appendChild(this.separator(owner));
|
|
106
|
+
});
|
|
107
|
+
this.container.appendChild(root);
|
|
108
|
+
this.root = root;
|
|
109
|
+
this.place(root, x, y);
|
|
110
|
+
this.watchForDismissal(owner);
|
|
111
|
+
}
|
|
112
|
+
hide() {
|
|
113
|
+
for (const dispose of this.detach.splice(0))
|
|
114
|
+
dispose();
|
|
115
|
+
this.submenu = null;
|
|
116
|
+
this.submenuTriggers.clear();
|
|
117
|
+
this.root?.remove();
|
|
118
|
+
this.root = null;
|
|
119
|
+
}
|
|
120
|
+
destroy() {
|
|
121
|
+
this.hide();
|
|
122
|
+
}
|
|
123
|
+
panel(owner) {
|
|
124
|
+
const panel = owner.createElement('div');
|
|
125
|
+
Object.assign(panel.style, PANEL_STYLE);
|
|
126
|
+
return panel;
|
|
127
|
+
}
|
|
128
|
+
separator(owner) {
|
|
129
|
+
const rule = owner.createElement('div');
|
|
130
|
+
rule.className = SEPARATOR_CLASS;
|
|
131
|
+
Object.assign(rule.style, {
|
|
132
|
+
height: '1px',
|
|
133
|
+
margin: '4px 6px',
|
|
134
|
+
background: 'var(--ssdiagram-menu-border, var(--t-border, #3a4250))',
|
|
135
|
+
});
|
|
136
|
+
return rule;
|
|
137
|
+
}
|
|
138
|
+
item(owner, text, enabled, arrow, checked, gutter) {
|
|
139
|
+
const button = owner.createElement('button');
|
|
140
|
+
button.type = 'button';
|
|
141
|
+
button.className = ITEM_CLASS;
|
|
142
|
+
button.setAttribute('role', checked === undefined ? 'menuitem' : 'menuitemcheckbox');
|
|
143
|
+
if (checked !== undefined)
|
|
144
|
+
button.setAttribute('aria-checked', String(checked));
|
|
145
|
+
Object.assign(button.style, ITEM_STYLE);
|
|
146
|
+
if (!enabled) {
|
|
147
|
+
button.disabled = true;
|
|
148
|
+
button.style.color = DISABLED_COLOR;
|
|
149
|
+
button.style.cursor = 'default';
|
|
150
|
+
}
|
|
151
|
+
// Every item in a panel that has a checkable entry gets the same gutter, so the
|
|
152
|
+
// captions stay on one line rather than one row sitting further in than the rest.
|
|
153
|
+
if (gutter) {
|
|
154
|
+
const tick = owner.createElement('span');
|
|
155
|
+
tick.className = CHECK_CLASS;
|
|
156
|
+
tick.textContent = checked === true ? '✓' : '';
|
|
157
|
+
tick.setAttribute('aria-hidden', 'true');
|
|
158
|
+
Object.assign(tick.style, { flex: 'none', width: '12px', textAlign: 'center' });
|
|
159
|
+
button.appendChild(tick);
|
|
160
|
+
}
|
|
161
|
+
const caption = owner.createElement('span');
|
|
162
|
+
caption.className = LABEL_CLASS;
|
|
163
|
+
caption.textContent = text;
|
|
164
|
+
caption.style.flex = '1';
|
|
165
|
+
button.appendChild(caption);
|
|
166
|
+
if (arrow) {
|
|
167
|
+
const chevron = owner.createElement('span');
|
|
168
|
+
chevron.className = ARROW_CLASS;
|
|
169
|
+
// Points the way the submenu actually opens, which on an RTL page is the other way.
|
|
170
|
+
chevron.textContent = this.rtl() ? '‹' : '›';
|
|
171
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
172
|
+
Object.assign(chevron.style, { flex: 'none', opacity: '0.7', fontSize: '14px' });
|
|
173
|
+
button.appendChild(chevron);
|
|
174
|
+
}
|
|
175
|
+
return button;
|
|
176
|
+
}
|
|
177
|
+
// `closesSubmenu` separates the two places an item can live. A top-level item shuts any
|
|
178
|
+
// open submenu when the pointer arrives, the way menus everywhere behave. An item inside
|
|
179
|
+
// the submenu must not: reaching it means the pointer has entered that very panel, and
|
|
180
|
+
// closing it there would pull it out from under the pointer and drop the click on the
|
|
181
|
+
// canvas behind.
|
|
182
|
+
commandEntry(owner, command, enabled, closesSubmenu, checked, gutter) {
|
|
183
|
+
const button = this.item(owner, label(command), enabled, false, checked, gutter);
|
|
184
|
+
this.on(button, 'pointerenter', () => {
|
|
185
|
+
if (closesSubmenu)
|
|
186
|
+
this.closeSubmenu();
|
|
187
|
+
if (enabled)
|
|
188
|
+
button.style.background = HOVER_BACKGROUND;
|
|
189
|
+
});
|
|
190
|
+
this.on(button, 'pointerleave', () => { button.style.background = 'transparent'; });
|
|
191
|
+
this.on(button, 'click', () => {
|
|
192
|
+
if (!enabled)
|
|
193
|
+
return;
|
|
194
|
+
// Hide first: a command may open a dialog, and a menu left on top of it looks stuck.
|
|
195
|
+
this.hide();
|
|
196
|
+
this.execute(command);
|
|
197
|
+
});
|
|
198
|
+
return button;
|
|
199
|
+
}
|
|
200
|
+
groupEntry(owner, group, gutter) {
|
|
201
|
+
const host = owner.createElement('div');
|
|
202
|
+
host.style.position = 'relative';
|
|
203
|
+
const button = this.item(owner, label(group.group), group.enabled, true, undefined, gutter);
|
|
204
|
+
button.setAttribute('aria-haspopup', 'menu');
|
|
205
|
+
button.setAttribute('aria-expanded', 'false');
|
|
206
|
+
host.appendChild(button);
|
|
207
|
+
const panel = this.panel(owner);
|
|
208
|
+
panel.className = SUBMENU_CLASS;
|
|
209
|
+
panel.setAttribute('role', 'menu');
|
|
210
|
+
panel.setAttribute('aria-label', label(group.group));
|
|
211
|
+
// insetInlineStart, not left: the submenu opens towards the end of the line, which
|
|
212
|
+
// is the right on an LTR page and the left on an RTL one.
|
|
213
|
+
Object.assign(panel.style, { position: 'absolute', insetInlineStart: '100%', top: '-5px', display: 'none' });
|
|
214
|
+
const childGutter = group.commands.some((child) => child.checked !== undefined);
|
|
215
|
+
for (const child of group.commands) {
|
|
216
|
+
panel.appendChild(this.commandEntry(owner, child.command, child.enabled, false, child.checked, childGutter));
|
|
217
|
+
}
|
|
218
|
+
host.appendChild(panel);
|
|
219
|
+
const open = () => {
|
|
220
|
+
if (!group.enabled)
|
|
221
|
+
return;
|
|
222
|
+
button.style.background = HOVER_BACKGROUND;
|
|
223
|
+
button.setAttribute('aria-expanded', 'true');
|
|
224
|
+
panel.style.display = 'block';
|
|
225
|
+
this.submenu = panel;
|
|
226
|
+
this.keepOnScreen(panel);
|
|
227
|
+
};
|
|
228
|
+
this.on(button, 'pointerenter', open);
|
|
229
|
+
// Tap opens it too: a touch device never sends pointerenter on its own.
|
|
230
|
+
this.on(button, 'click', open);
|
|
231
|
+
this.on(host, 'pointerleave', () => {
|
|
232
|
+
if (this.submenu !== panel)
|
|
233
|
+
return;
|
|
234
|
+
button.style.background = 'transparent';
|
|
235
|
+
button.setAttribute('aria-expanded', 'false');
|
|
236
|
+
panel.style.display = 'none';
|
|
237
|
+
this.submenu = null;
|
|
238
|
+
});
|
|
239
|
+
this.submenuTriggers.set(panel, button);
|
|
240
|
+
return host;
|
|
241
|
+
}
|
|
242
|
+
closeSubmenu() {
|
|
243
|
+
if (this.submenu === null)
|
|
244
|
+
return;
|
|
245
|
+
this.submenu.style.display = 'none';
|
|
246
|
+
this.submenuTriggers.get(this.submenu)?.setAttribute('aria-expanded', 'false');
|
|
247
|
+
this.submenu = null;
|
|
248
|
+
}
|
|
249
|
+
// Reads the resolved writing direction so the submenu's side and its arrow agree with it.
|
|
250
|
+
rtl() {
|
|
251
|
+
if (typeof getComputedStyle !== 'function')
|
|
252
|
+
return false;
|
|
253
|
+
try {
|
|
254
|
+
return getComputedStyle(this.container).direction === 'rtl';
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// A detached or fake container has no computed style; left-to-right is the safe read.
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Keeps the panel inside the viewport, flipping it left when it would run off the edge. */
|
|
262
|
+
keepOnScreen(panel) {
|
|
263
|
+
if (typeof panel.getBoundingClientRect !== 'function' || typeof window === 'undefined')
|
|
264
|
+
return;
|
|
265
|
+
const box = panel.getBoundingClientRect();
|
|
266
|
+
if (box.right > window.innerWidth - 4 || box.left < 4) {
|
|
267
|
+
panel.style.insetInlineStart = 'auto';
|
|
268
|
+
panel.style.insetInlineEnd = '100%';
|
|
269
|
+
}
|
|
270
|
+
if (box.bottom > window.innerHeight - 4) {
|
|
271
|
+
panel.style.top = 'auto';
|
|
272
|
+
panel.style.bottom = '-5px';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
place(root, x, y) {
|
|
276
|
+
root.style.left = `${x}px`;
|
|
277
|
+
root.style.top = `${y}px`;
|
|
278
|
+
if (typeof root.getBoundingClientRect !== 'function' || typeof window === 'undefined')
|
|
279
|
+
return;
|
|
280
|
+
const box = root.getBoundingClientRect();
|
|
281
|
+
// Flip rather than clamp: a menu shoved back onto the screen would cover the very
|
|
282
|
+
// thing that was right-clicked.
|
|
283
|
+
if (box.right > window.innerWidth - 4)
|
|
284
|
+
root.style.left = `${Math.max(4, x - box.width)}px`;
|
|
285
|
+
if (box.bottom > window.innerHeight - 4)
|
|
286
|
+
root.style.top = `${Math.max(4, y - box.height)}px`;
|
|
287
|
+
}
|
|
288
|
+
watchForDismissal(owner) {
|
|
289
|
+
const dismiss = () => this.hide();
|
|
290
|
+
const outside = (event) => {
|
|
291
|
+
// Duck-typed rather than `target instanceof Node`: this runs in whatever document
|
|
292
|
+
// the host mounted us in, and a cross-document Node fails that check.
|
|
293
|
+
const root = this.root;
|
|
294
|
+
const target = event.target;
|
|
295
|
+
if (root !== null && target !== null && typeof root.contains === 'function' && root.contains(target))
|
|
296
|
+
return;
|
|
297
|
+
this.hide();
|
|
298
|
+
};
|
|
299
|
+
this.on(owner, 'pointerdown', outside, true);
|
|
300
|
+
this.on(owner, 'keydown', (event) => {
|
|
301
|
+
if (event.key === 'Escape')
|
|
302
|
+
this.hide();
|
|
303
|
+
});
|
|
304
|
+
if (typeof window !== 'undefined') {
|
|
305
|
+
// Scroll and resize move the diagram out from under the menu, which would otherwise
|
|
306
|
+
// hang in place pointing at nothing.
|
|
307
|
+
this.on(window, 'blur', dismiss);
|
|
308
|
+
this.on(window, 'resize', dismiss);
|
|
309
|
+
this.on(window, 'scroll', dismiss, true);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
on(target, type, listener, capture = false) {
|
|
313
|
+
if (typeof target.addEventListener !== 'function')
|
|
314
|
+
return;
|
|
315
|
+
target.addEventListener(type, listener, capture);
|
|
316
|
+
this.detach.push(() => target.removeEventListener?.(type, listener, capture));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=context-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-menu.js","sourceRoot":"","sources":["../../../src/diagram/context-menu.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,30 @@ import { parseDiagramDocument, serializeDiagramDocument, } from '../core/documen
|
|
|
2
2
|
import { parseDiagramViewState, serializeDiagramViewState, } from '../core/view-state.js';
|
|
3
3
|
import { DiagramActionRegistry } from '../core/action-registry.js';
|
|
4
4
|
import { Diagram as CanvasDiagram, } from '../canvas-renderer.js';
|
|
5
|
+
import { ContextMenuView } from './context-menu.js';
|
|
6
|
+
import { t } from '../i18n.js';
|
|
5
7
|
import { EventEmitter } from './event-emitter.js';
|
|
6
8
|
import { DiagramNode, Link, Node, Port, } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Menu order, and the only place the nesting is declared. The registry is flat because
|
|
11
|
+
* every command must be runnable by id, so the shape of the menu cannot live there.
|
|
12
|
+
* A command missing from this list would be executable and never offered; the test suite
|
|
13
|
+
* walks both and refuses to let the two drift.
|
|
14
|
+
*/
|
|
15
|
+
const CONTEXT_MENU = [
|
|
16
|
+
'undo', 'redo',
|
|
17
|
+
'cut', 'copy', 'paste',
|
|
18
|
+
'open',
|
|
19
|
+
'delete',
|
|
20
|
+
{ group: 'export', commands: ['exportDocument', 'exportPng', 'exportSvg'] },
|
|
21
|
+
'overview',
|
|
22
|
+
'properties',
|
|
23
|
+
'help',
|
|
24
|
+
];
|
|
25
|
+
/** Commands that toggle rather than do, so the menu reports their state instead of just enabling them. */
|
|
26
|
+
const CONTEXT_CHECKED = {
|
|
27
|
+
overview: (diagram) => diagram.getViewState().overviewVisible,
|
|
28
|
+
};
|
|
7
29
|
export class StockSharpDiagram extends EventEmitter {
|
|
8
30
|
constructor(options) {
|
|
9
31
|
super();
|
|
@@ -16,8 +38,12 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
16
38
|
this.destroyed = false;
|
|
17
39
|
this.fullscreen = false;
|
|
18
40
|
this.fullscreenButtonVisible = true;
|
|
19
|
-
|
|
41
|
+
// null means the host never named the button, so its wording comes from the shared bundle.
|
|
42
|
+
// Keeping the two apart is what lets an explicit label win while an untouched control still
|
|
43
|
+
// follows the page's language instead of being stuck in English.
|
|
44
|
+
this.fullscreenLabels = null;
|
|
20
45
|
this.contextActions = new DiagramActionRegistry();
|
|
46
|
+
this.contextMenu = null;
|
|
21
47
|
this.div = options.div;
|
|
22
48
|
this.catalog = options.catalog;
|
|
23
49
|
this.overviewContainer = options.overviewContainer ?? null;
|
|
@@ -30,10 +56,11 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
30
56
|
gridSize: options.gridSize,
|
|
31
57
|
});
|
|
32
58
|
this.fullscreenButtonVisible = options.showFullscreenButton ?? true;
|
|
33
|
-
this.fullscreenLabels = options.fullscreenLabels ??
|
|
59
|
+
this.fullscreenLabels = options.fullscreenLabels ?? null;
|
|
34
60
|
this.prepareFullscreenButtonHost();
|
|
35
61
|
this.fullscreenButton = this.createFullscreenButton();
|
|
36
62
|
this.updateFullscreenButton();
|
|
63
|
+
this.setContextMenuEnabled(options.showContextMenu ?? true);
|
|
37
64
|
this.registerContextActions();
|
|
38
65
|
this.bindCanvasEvents();
|
|
39
66
|
this.disposables.push(this.catalog.on('portTypesChanged', () => this.applySocketTheme()));
|
|
@@ -250,6 +277,10 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
250
277
|
takeScreenshot(options = {}) {
|
|
251
278
|
return this.canvas.takeScreenshot(options);
|
|
252
279
|
}
|
|
280
|
+
/** The same picture as takeScreenshot, as an SVG document. */
|
|
281
|
+
takeSvg(options = {}) {
|
|
282
|
+
return this.canvas.takeSvg(options);
|
|
283
|
+
}
|
|
253
284
|
getSelection() {
|
|
254
285
|
return this.canvas.getSelection();
|
|
255
286
|
}
|
|
@@ -281,8 +312,22 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
281
312
|
this.fullscreenLabels = labels;
|
|
282
313
|
this.updateFullscreenButton();
|
|
283
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Re-reads the shared bundle for text the control renders itself. Call it after changing
|
|
317
|
+
* the page language: the context menu is rebuilt on every open and needs nothing, but the
|
|
318
|
+
* fullscreen button is drawn once and would otherwise keep the old wording.
|
|
319
|
+
*/
|
|
320
|
+
refreshLabels() {
|
|
321
|
+
this.updateFullscreenButton();
|
|
322
|
+
}
|
|
284
323
|
getFullscreenLabels() {
|
|
285
|
-
return { ...this.
|
|
324
|
+
return { ...this.resolveFullscreenLabels() };
|
|
325
|
+
}
|
|
326
|
+
resolveFullscreenLabels() {
|
|
327
|
+
return this.fullscreenLabels ?? {
|
|
328
|
+
enter: t('fullscreenEnter', 'Enter fullscreen'),
|
|
329
|
+
exit: t('fullscreenExit', 'Exit fullscreen'),
|
|
330
|
+
};
|
|
286
331
|
}
|
|
287
332
|
/** Updates only the control's state after the host changed its own layout. */
|
|
288
333
|
setFullscreenState(fullscreen) {
|
|
@@ -366,11 +411,46 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
366
411
|
const fallback = this.canvas.getClipboardDocument();
|
|
367
412
|
return fallback !== null && this.canvas.pasteDocument(fallback).length > 0;
|
|
368
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Turns the built-in menu on or off. Off leaves `contextMenuRequested` untouched, so a
|
|
416
|
+
* host that draws its own is only trading one menu for another, never losing the event.
|
|
417
|
+
*/
|
|
418
|
+
setContextMenuEnabled(enabled) {
|
|
419
|
+
if (enabled === (this.contextMenu !== null))
|
|
420
|
+
return;
|
|
421
|
+
if (!enabled) {
|
|
422
|
+
this.contextMenu?.destroy();
|
|
423
|
+
this.contextMenu = null;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
this.contextMenu = new ContextMenuView({
|
|
427
|
+
container: this.div,
|
|
428
|
+
// Through the public entry point, so the built-in menu is exactly as privileged
|
|
429
|
+
// as a host's own -- same guards, same contextCommand event.
|
|
430
|
+
execute: (command) => { this.executeContextCommand(command); },
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
isContextMenuEnabled() {
|
|
434
|
+
return this.contextMenu !== null;
|
|
435
|
+
}
|
|
369
436
|
getContextCommands() {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
437
|
+
const context = this.contextActionContext();
|
|
438
|
+
const state = (command) => {
|
|
439
|
+
const checked = CONTEXT_CHECKED[command];
|
|
440
|
+
return {
|
|
441
|
+
command,
|
|
442
|
+
enabled: this.contextActions.canExecute(command, context),
|
|
443
|
+
...(checked === undefined ? {} : { checked: checked(this) }),
|
|
444
|
+
};
|
|
445
|
+
};
|
|
446
|
+
return CONTEXT_MENU.map((entry) => {
|
|
447
|
+
if (typeof entry === 'string')
|
|
448
|
+
return state(entry);
|
|
449
|
+
const commands = entry.commands.map(state);
|
|
450
|
+
// A submenu that opens onto nothing but greyed items is itself dead, and
|
|
451
|
+
// saying so here spares every host from working the same thing out again.
|
|
452
|
+
return { group: entry.group, enabled: commands.some(({ enabled }) => enabled), commands };
|
|
453
|
+
});
|
|
374
454
|
}
|
|
375
455
|
executeContextCommand(command) {
|
|
376
456
|
const context = this.contextActionContext();
|
|
@@ -444,6 +524,8 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
444
524
|
if (this.destroyed)
|
|
445
525
|
return;
|
|
446
526
|
this.destroyed = true;
|
|
527
|
+
this.contextMenu?.destroy();
|
|
528
|
+
this.contextMenu = null;
|
|
447
529
|
for (const dispose of this.disposables.splice(0).reverse())
|
|
448
530
|
dispose();
|
|
449
531
|
this.canvas.destroy();
|
|
@@ -501,7 +583,8 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
501
583
|
}
|
|
502
584
|
updateFullscreenButton() {
|
|
503
585
|
const fullscreen = this.fullscreen;
|
|
504
|
-
const
|
|
586
|
+
const labels = this.resolveFullscreenLabels();
|
|
587
|
+
const label = fullscreen ? labels.exit : labels.enter;
|
|
505
588
|
const path = fullscreen
|
|
506
589
|
? 'M4 9h5V4M20 9h-5V4M4 15h5v5M20 15h-5v5'
|
|
507
590
|
: 'M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5';
|
|
@@ -595,6 +678,7 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
595
678
|
this.overviewContainer?.classList.toggle('hidden', !state.overviewVisible);
|
|
596
679
|
this.emit('viewChanged', state);
|
|
597
680
|
}), this.canvas.on('undoStackChanged', (state) => this.emit('undoStackChanged', state)), this.canvas.on('contextMenu', ({ x, y, node, link, port }) => {
|
|
681
|
+
const commands = this.getContextCommands();
|
|
598
682
|
this.emit('contextMenuRequested', {
|
|
599
683
|
x,
|
|
600
684
|
y,
|
|
@@ -602,8 +686,11 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
602
686
|
link: link === null ? null : this.fromCanvasLink(link),
|
|
603
687
|
port: port === null ? null : this.fromCanvasPort(port.port),
|
|
604
688
|
portDirection: port?.port.direction ?? null,
|
|
605
|
-
commands
|
|
689
|
+
commands,
|
|
606
690
|
});
|
|
691
|
+
// After the event, so a host that draws its own menu has already been told
|
|
692
|
+
// and can have switched this one off in the handler.
|
|
693
|
+
this.contextMenu?.show(x, y, commands);
|
|
607
694
|
}));
|
|
608
695
|
}
|
|
609
696
|
registerContextActions() {
|
|
@@ -641,6 +728,30 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
641
728
|
&& (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
|
|
642
729
|
execute: () => this.canvas.deleteSelection(),
|
|
643
730
|
},
|
|
731
|
+
// Export asks rather than does. Where the result goes -- a download, an upload,
|
|
732
|
+
// a clipboard, a print preview -- is the host's decision, and so is which options
|
|
733
|
+
// to render with, so the control reports the request and stops there.
|
|
734
|
+
// Availability follows the diagram, not the selection: all three export the whole
|
|
735
|
+
// picture, and an empty diagram has none to give. They stay on in read-only mode,
|
|
736
|
+
// which forbids changing the diagram, not looking at it.
|
|
737
|
+
exportDocument: {
|
|
738
|
+
canExecute: ({ document }) => document.nodes.length > 0,
|
|
739
|
+
execute: () => this.emit('exportRequested', { format: 'document' }),
|
|
740
|
+
},
|
|
741
|
+
exportPng: {
|
|
742
|
+
canExecute: ({ document }) => document.nodes.length > 0,
|
|
743
|
+
execute: () => this.emit('exportRequested', { format: 'png' }),
|
|
744
|
+
},
|
|
745
|
+
exportSvg: {
|
|
746
|
+
canExecute: ({ document }) => document.nodes.length > 0,
|
|
747
|
+
execute: () => this.emit('exportRequested', { format: 'svg' }),
|
|
748
|
+
},
|
|
749
|
+
// Always available: it shows the overview as readily as it hides it, and an empty
|
|
750
|
+
// diagram is exactly when someone may want the panel out of the way.
|
|
751
|
+
overview: {
|
|
752
|
+
canExecute: () => true,
|
|
753
|
+
execute: () => this.setOverviewVisible(!this.getViewState().overviewVisible),
|
|
754
|
+
},
|
|
644
755
|
properties: {
|
|
645
756
|
canExecute: ({ nodes }) => nodes.length > 0,
|
|
646
757
|
execute: ({ nodes }) => this.emit('nodeProperties', { nodes }),
|
|
@@ -658,6 +769,7 @@ export class StockSharpDiagram extends EventEmitter {
|
|
|
658
769
|
const linkIds = new Set(selection.linkIds);
|
|
659
770
|
return {
|
|
660
771
|
selection,
|
|
772
|
+
document,
|
|
661
773
|
nodes: document.nodes
|
|
662
774
|
.filter((node) => nodeIds.has(node.id))
|
|
663
775
|
.map((node) => this.fromCanvasInit(node)),
|