@stocksharp/diagram 0.4.1 → 0.5.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 +42 -0
- package/dist/esm/canvas-renderer.js +91 -16
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/diagram/context-menu.js +272 -0
- package/dist/esm/diagram/context-menu.js.map +1 -0
- package/dist/esm/diagram/stocksharp-diagram.js +83 -5
- 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/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 +879 -164
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/canvas-renderer.d.ts +12 -0
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +36 -2
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/context-menu.d.ts +31 -0
- package/dist/types/diagram/context-menu.d.ts.map +1 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts +11 -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/i18n.d.ts +4 -0
- package/dist/types/i18n.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -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 +103 -16
- package/src/diagram/api.ts +46 -2
- package/src/diagram/context-menu.ts +298 -0
- package/src/diagram/stocksharp-diagram.ts +93 -6
- package/src/draw-surface.ts +61 -0
- package/src/i18n.ts +7 -0
- package/src/index.ts +5 -0
- package/src/svg-surface.ts +430 -0
package/README.md
CHANGED
|
@@ -188,6 +188,48 @@ Socket input is reported separately through `portClicked`, with
|
|
|
188
188
|
never starts a wire. `contextMenuRequested` also includes the exact port when
|
|
189
189
|
the menu was opened over a socket.
|
|
190
190
|
|
|
191
|
+
### The context menu
|
|
192
|
+
|
|
193
|
+
The control has no menu widget: right-click suppresses the browser's own menu
|
|
194
|
+
and emits `contextMenuRequested` with page coordinates, whatever was hit, and
|
|
195
|
+
the menu contents already resolved. Draw it however the host draws menus, then
|
|
196
|
+
send the choice back through `executeContextCommand()`.
|
|
197
|
+
|
|
198
|
+
`commands` is a tree in display order. An entry with a `group` is a submenu;
|
|
199
|
+
anything else is a command. `enabled` is computed for both — a submenu is off
|
|
200
|
+
when every item inside it is:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
diagram.on('contextMenuRequested', ({ x, y, commands }) => {
|
|
204
|
+
showMenu(x, y, commands.map((item) => 'group' in item
|
|
205
|
+
? { label: t(item.group), enabled: item.enabled, items: item.commands }
|
|
206
|
+
: { label: t(item.command), enabled: item.enabled }));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
menu.on('pick', (command) => diagram.executeContextCommand(command));
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`undo`, `redo`, `cut`, `copy`, `paste` and `delete` are carried out by the
|
|
213
|
+
control itself. The rest are requests it cannot answer alone: `open`,
|
|
214
|
+
`properties` and `help` raise `nodeOpen` / `nodeProperties` / `nodeHelp`, and
|
|
215
|
+
the `export` submenu raises `exportRequested` with the chosen format. Nothing
|
|
216
|
+
is produced until the host acts on it — only the host knows where the result
|
|
217
|
+
should go, and with which options:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
diagram.on('exportRequested', ({ format }) => {
|
|
221
|
+
if (format === 'document') return download('strategy.json', diagram.saveDocument());
|
|
222
|
+
const options = { scope: 'content', padding: 40 };
|
|
223
|
+
if (format === 'svg') return download('strategy.svg', diagram.takeSvg(options));
|
|
224
|
+
diagram.takeScreenshot(options).toBlob((blob) => download('strategy.png', blob));
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Export is offered whenever the diagram has a node, including in read-only mode,
|
|
229
|
+
because it only reads. Menu labels come from the host's i18n bundle
|
|
230
|
+
(`ctxExportAs`, `ctxExportDocument`, `ctxExportPng`, `ctxExportSvg`, and the
|
|
231
|
+
keys named after the other commands).
|
|
232
|
+
|
|
191
233
|
Runtime failures flash the node border before leaving it red. Errors found
|
|
192
234
|
while loading a scheme use a red background. Hovering either state shows the
|
|
193
235
|
full error text in a tooltip:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SvgSurface } from './svg-surface.js';
|
|
1
2
|
// Internal dependency-free canvas renderer used by StockSharpDiagram.
|
|
2
3
|
//
|
|
3
4
|
// Dependency-free, pure 2D canvas. Demonstrates the hard parts: typed
|
|
@@ -1100,6 +1101,65 @@ export class Diagram {
|
|
|
1100
1101
|
copyContext.drawImage(this.canvas, 0, 0);
|
|
1101
1102
|
return copy;
|
|
1102
1103
|
}
|
|
1104
|
+
const layout = this.exportLayout(scope, options);
|
|
1105
|
+
const { pixelRatio, exportScale, padding, bounds, width, height, pixelWidth, pixelHeight } = layout;
|
|
1106
|
+
const output = document.createElement('canvas');
|
|
1107
|
+
output.width = pixelWidth;
|
|
1108
|
+
output.height = pixelHeight;
|
|
1109
|
+
output.style.width = `${width}px`;
|
|
1110
|
+
output.style.height = `${height}px`;
|
|
1111
|
+
const outputContext = output.getContext('2d');
|
|
1112
|
+
if (outputContext === null)
|
|
1113
|
+
throw new Error('ssdiagram: screenshot 2d context unavailable');
|
|
1114
|
+
const previous = {
|
|
1115
|
+
ctx: this.ctx,
|
|
1116
|
+
width: this.width,
|
|
1117
|
+
height: this.height,
|
|
1118
|
+
dpr: this.dpr,
|
|
1119
|
+
scale: this.scale,
|
|
1120
|
+
offX: this.offX,
|
|
1121
|
+
offY: this.offY,
|
|
1122
|
+
overviewVisible: this.overviewVisible,
|
|
1123
|
+
background: this.opts.background,
|
|
1124
|
+
};
|
|
1125
|
+
try {
|
|
1126
|
+
this.ctx = outputContext;
|
|
1127
|
+
this.width = width;
|
|
1128
|
+
this.height = height;
|
|
1129
|
+
this.dpr = pixelRatio;
|
|
1130
|
+
this.scale = exportScale;
|
|
1131
|
+
this.offX = scope === 'content' && bounds !== null ? padding - bounds.minX * exportScale : previous.offX;
|
|
1132
|
+
this.offY = scope === 'content' && bounds !== null ? padding - bounds.minY * exportScale : previous.offY;
|
|
1133
|
+
this.overviewVisible = options.includeOverview ?? (scope === 'viewport' && previous.overviewVisible);
|
|
1134
|
+
if (options.background !== undefined)
|
|
1135
|
+
this.opts.background = options.background;
|
|
1136
|
+
outputContext.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
1137
|
+
this.draw({
|
|
1138
|
+
grid: options.includeGrid ?? true,
|
|
1139
|
+
overview: this.overviewVisible,
|
|
1140
|
+
selection: options.includeSelection ?? scope === 'viewport',
|
|
1141
|
+
runtime: options.includeRuntimeState ?? true,
|
|
1142
|
+
transient: false,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
finally {
|
|
1146
|
+
this.ctx = previous.ctx;
|
|
1147
|
+
this.width = previous.width;
|
|
1148
|
+
this.height = previous.height;
|
|
1149
|
+
this.dpr = previous.dpr;
|
|
1150
|
+
this.scale = previous.scale;
|
|
1151
|
+
this.offX = previous.offX;
|
|
1152
|
+
this.offY = previous.offY;
|
|
1153
|
+
this.overviewVisible = previous.overviewVisible;
|
|
1154
|
+
this.opts.background = previous.background;
|
|
1155
|
+
}
|
|
1156
|
+
return output;
|
|
1157
|
+
}
|
|
1158
|
+
// Everything an export needs to know about size and placement, for either output. Shared on
|
|
1159
|
+
// purpose: a raster and a vector export of the same scope must frame the picture identically,
|
|
1160
|
+
// and two copies of this arithmetic would eventually disagree about padding or where the origin
|
|
1161
|
+
// sits. A block comment here would be lifted onto the next public member in the .d.ts.
|
|
1162
|
+
exportLayout(scope, options) {
|
|
1103
1163
|
const pixelRatio = this.positiveScreenshotNumber(options.pixelRatio ?? this.dpr, 'pixelRatio');
|
|
1104
1164
|
const exportScale = scope === 'content'
|
|
1105
1165
|
? this.positiveScreenshotNumber(options.scale ?? 1, 'scale')
|
|
@@ -1119,14 +1179,33 @@ export class Diagram {
|
|
|
1119
1179
|
if (pixelWidth > 16384 || pixelHeight > 16384 || pixelWidth * pixelHeight > 268435456) {
|
|
1120
1180
|
throw new RangeError(`ssdiagram: screenshot is too large (${pixelWidth}x${pixelHeight})`);
|
|
1121
1181
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1182
|
+
return { pixelRatio, exportScale, padding, bounds, width, height, pixelWidth, pixelHeight };
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* The same picture takeScreenshot produces, as SVG.
|
|
1186
|
+
*
|
|
1187
|
+
* It is the renderer that draws it -- the surface underneath simply records instead of paints --
|
|
1188
|
+
* so the vector output cannot drift from what the screen and the raster export show. Text keeps
|
|
1189
|
+
* the on-screen metrics by measuring through a real 2D context.
|
|
1190
|
+
*
|
|
1191
|
+
* Scale and pixelRatio still frame the picture (they decide the viewBox), but nothing is
|
|
1192
|
+
* resampled: the result is resolution-independent, which is the point of asking for it.
|
|
1193
|
+
*/
|
|
1194
|
+
takeSvg(options = {}) {
|
|
1195
|
+
if (this.destroyed)
|
|
1196
|
+
throw new Error('ssdiagram: cannot export a destroyed diagram');
|
|
1197
|
+
const scope = options.scope ?? 'viewport';
|
|
1198
|
+
if (scope !== 'viewport' && scope !== 'content') {
|
|
1199
|
+
throw new RangeError(`ssdiagram: unsupported screenshot scope "${String(scope)}"`);
|
|
1200
|
+
}
|
|
1201
|
+
const { exportScale, padding, bounds, width, height } = this.exportLayout(scope, options);
|
|
1202
|
+
const metrics = this.canvas.getContext('2d');
|
|
1203
|
+
const surface = new SvgSurface({
|
|
1204
|
+
width,
|
|
1205
|
+
height,
|
|
1206
|
+
background: options.background ?? this.opts.background ?? '#1b1b1f',
|
|
1207
|
+
metrics: metrics ?? undefined,
|
|
1208
|
+
});
|
|
1130
1209
|
const previous = {
|
|
1131
1210
|
ctx: this.ctx,
|
|
1132
1211
|
width: this.width,
|
|
@@ -1136,20 +1215,17 @@ export class Diagram {
|
|
|
1136
1215
|
offX: this.offX,
|
|
1137
1216
|
offY: this.offY,
|
|
1138
1217
|
overviewVisible: this.overviewVisible,
|
|
1139
|
-
background: this.opts.background,
|
|
1140
1218
|
};
|
|
1141
1219
|
try {
|
|
1142
|
-
this.ctx =
|
|
1220
|
+
this.ctx = surface;
|
|
1143
1221
|
this.width = width;
|
|
1144
1222
|
this.height = height;
|
|
1145
|
-
|
|
1223
|
+
// 1: an SVG has no device pixels to multiply by, and the viewBox carries the size.
|
|
1224
|
+
this.dpr = 1;
|
|
1146
1225
|
this.scale = exportScale;
|
|
1147
1226
|
this.offX = scope === 'content' && bounds !== null ? padding - bounds.minX * exportScale : previous.offX;
|
|
1148
1227
|
this.offY = scope === 'content' && bounds !== null ? padding - bounds.minY * exportScale : previous.offY;
|
|
1149
1228
|
this.overviewVisible = options.includeOverview ?? (scope === 'viewport' && previous.overviewVisible);
|
|
1150
|
-
if (options.background !== undefined)
|
|
1151
|
-
this.opts.background = options.background;
|
|
1152
|
-
outputContext.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
1153
1229
|
this.draw({
|
|
1154
1230
|
grid: options.includeGrid ?? true,
|
|
1155
1231
|
overview: this.overviewVisible,
|
|
@@ -1167,9 +1243,8 @@ export class Diagram {
|
|
|
1167
1243
|
this.offX = previous.offX;
|
|
1168
1244
|
this.offY = previous.offY;
|
|
1169
1245
|
this.overviewVisible = previous.overviewVisible;
|
|
1170
|
-
this.opts.background = previous.background;
|
|
1171
1246
|
}
|
|
1172
|
-
return
|
|
1247
|
+
return surface.toSvg();
|
|
1173
1248
|
}
|
|
1174
1249
|
positiveScreenshotNumber(value, name) {
|
|
1175
1250
|
if (!Number.isFinite(value) || value <= 0) {
|