@stocksharp/diagram 0.1.0 → 0.2.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- # @stocksharp/diagram
1
+ # StockSharp JS Strategy Diagram
2
2
 
3
3
  [![Build and test](https://github.com/StockSharp/Diagram/actions/workflows/ci.yml/badge.svg)](https://github.com/StockSharp/Diagram/actions/workflows/ci.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/%40stocksharp%2Fdiagram.svg)](https://www.npmjs.com/package/@stocksharp/diagram)
@@ -11,6 +11,7 @@ renderer, read-only web embed, and versioned diagram document model.
11
11
  ![StockSharp JS Strategy Diagram — visual strategy editor with typed connections and element palette](sample.png)
12
12
 
13
13
  [Live demo](https://stocksharp.github.io/Diagram/demo/) ·
14
+ [StockSharp website](https://stocksharp.com/) ·
14
15
  [GitHub repository](https://github.com/StockSharp/Diagram) ·
15
16
  [Issue tracker](https://github.com/StockSharp/Diagram/issues)
16
17
 
@@ -311,7 +312,9 @@ npm test
311
312
  npm run build
312
313
  npm run serve
313
314
  npm run pack:check
314
- npm run release:check -- v0.1.0
315
+ npm run release:patch
316
+ npm run release:minor
317
+ npm run release:major
315
318
  npm run api:check
316
319
  npm run api:update # only after reviewing an intentional public API change
317
320
  npm run test:browser
@@ -323,23 +326,30 @@ The browser suite covers Chromium smoke, interaction and lifecycle scenarios
323
326
  at DPR 1 and 2.
324
327
 
325
328
  CI verifies type checking, the reviewed declaration snapshot, unit/integration
326
- tests, Chromium smoke/interaction/lifecycle checks, all bundles and tarball
327
- contents. GitHub Pages publishes the demo from `main`.
329
+ tests, all bundles and tarball contents. The Playwright browser suite runs
330
+ locally via `npm run test:browser` (not in CI — the headless unit tests already
331
+ cover the same behaviour). GitHub Pages publishes the demo from `main`.
328
332
 
329
- Publishing is release-driven. Set `package.json` to the intended version and
330
- publish a GitHub Release tagged `v<version>`. The `release.yml` workflow rejects
331
- a mismatched tag, rebuilds and tests the repository, attaches the exact `.tgz`
332
- artifact to the release, and publishes that tarball to npm with provenance.
333
-
334
- The first publication needs a short-lived npm granular access token in the
335
- repository Actions secret `NPM_TOKEN`. After `@stocksharp/diagram` exists on npm,
336
- configure tokenless trusted publishing, then remove the secret and revoke the
337
- bootstrap token:
333
+ Publishing is driven by the version in `package.json`. `release.yml` runs on
334
+ every push to `main` and publishes only when that version is not yet on npm, so
335
+ an ordinary push is a no-op. To cut a release, bump the version and push:
338
336
 
339
337
  ```sh
340
- npm trust github @stocksharp/diagram --file release.yml --repo StockSharp/Diagram --allow-publish
338
+ npm run release:patch # or release:minor / release:major
339
+ git push
341
340
  ```
342
341
 
342
+ The new version triggers `release.yml`, which rebuilds and tests the repository,
343
+ publishes to npm with provenance, then creates the `v<version>` tag and GitHub
344
+ Release. A failed publication can be retried from the workflow's `Run workflow`
345
+ action; already-published versions are detected and skipped.
346
+
347
+ Publishing authenticates through npm **trusted publishing (OIDC)** — there is no
348
+ `NPM_TOKEN` secret. The workflow grants `id-token: write`, and npm exchanges the
349
+ GitHub OIDC token for a short-lived publish token. The npm Trusted Publisher is
350
+ bound to the workflow filename `release.yml`, so do not rename that workflow
351
+ without updating the trusted publisher configuration on npmjs first.
352
+
343
353
  ## License
344
354
 
345
355
  Copyright © 2010-present StockSharp Platform LLC and/or its affiliates. All
@@ -170,6 +170,7 @@ const RELINK_DRAG_THRESHOLD_PX = 4;
170
170
  const PORT_SQ = 9; // socket square size — sits outside the node
171
171
  const LINK_STUB = 22; // min horizontal lead-in/out before the elbow
172
172
  const LINK_HOP = 5; // jump-over arc radius at crossings
173
+ const LINK_CORNER = 8; // corner rounding radius at elbows
173
174
  const SNAP_PX = 32; // plug↔socket magnet radius (screen px)
174
175
  const DEFAULT_GRID_SIZE = 28;
175
176
  const ZOOM_MIN = 0.15;
@@ -1345,6 +1346,8 @@ export class Diagram {
1345
1346
  this.opts.gridColor = t.gridColor;
1346
1347
  if (t.linkMaxLightness !== undefined)
1347
1348
  this.opts.linkMaxLightness = t.linkMaxLightness;
1349
+ if (t.typedLinkColors !== undefined)
1350
+ this.opts.typedLinkColors = t.typedLinkColors;
1348
1351
  if (t.overviewBackground !== undefined)
1349
1352
  this.opts.overviewBackground = t.overviewBackground;
1350
1353
  if (t.overviewBorderColor !== undefined)
@@ -1455,6 +1458,16 @@ export class Diagram {
1455
1458
  const base = map[type] ?? `hsl(${hashHue(type)}, 62%, 58%)`;
1456
1459
  return maxL !== undefined ? clampLightness(base, maxL) : base;
1457
1460
  }
1461
+ // Selection/hover accent. The dark-canvas blue washes out on a light canvas, so a light theme
1462
+ // (linkMaxLightness < 0.5) inverts to a darker, more saturated blue that stays clearly visible.
1463
+ selectionColor() {
1464
+ const maxL = this.opts.linkMaxLightness;
1465
+ return maxL !== undefined && maxL < 0.5 ? '#0d6efd' : '#4aa3ff';
1466
+ }
1467
+ linkHoverColor() {
1468
+ const maxL = this.opts.linkMaxLightness;
1469
+ return maxL !== undefined && maxL < 0.5 ? '#6aa9f0' : '#cfe3ff';
1470
+ }
1458
1471
  // ---- hit testing (world coords) ---------------------------------
1459
1472
  portAt(wx, wy) {
1460
1473
  for (let i = this.nodes.length - 1; i >= 0; i -= 1) {
@@ -2379,14 +2392,16 @@ export class Diagram {
2379
2392
  if (a === null || b === null)
2380
2393
  continue;
2381
2394
  const fp = this.nodes.find((n) => n.id === l.from)?.outPorts.find((p) => p.id === l.fromPort);
2382
- const baseColor = fp ? this.portColor(fp.type) : '#7d828a';
2395
+ const baseColor = (this.opts.typedLinkColors ?? false) && fp !== undefined
2396
+ ? this.portColor(fp.type)
2397
+ : this.portColor('');
2383
2398
  const state = options.selection && l === this.selectedLink
2384
2399
  ? 'sel'
2385
2400
  : options.transient && (l === this.hoveredLink
2386
2401
  || (hoveredNode !== null && (l.from === hoveredNode.id || l.to === hoveredNode.id)))
2387
2402
  ? 'hov'
2388
2403
  : 'norm';
2389
- const color = state === 'sel' ? '#4aa3ff' : state === 'hov' ? '#cfe3ff' : baseColor;
2404
+ const color = state === 'sel' ? this.selectionColor() : state === 'hov' ? this.linkHoverColor() : baseColor;
2390
2405
  const width = state === 'sel' ? 3 : state === 'hov' ? 2.6 : 2;
2391
2406
  const pts = this.routeLink(a, b, new Set([l.from, l.to]));
2392
2407
  this.strokeRoute(pts, color, width, prior);
@@ -2599,7 +2614,7 @@ export class Diagram {
2599
2614
  ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? '#ffd1dc' : n.color;
2600
2615
  ctx.fill();
2601
2616
  ctx.lineWidth = selected ? 2 : 1.5;
2602
- ctx.strokeStyle = selected ? '#4aa3ff' : n.border;
2617
+ ctx.strokeStyle = selected ? this.selectionColor() : n.border;
2603
2618
  ctx.stroke();
2604
2619
  if (hasLoadError || hasRuntimeError) {
2605
2620
  let alpha = 1;
@@ -2706,6 +2721,20 @@ export class Diagram {
2706
2721
  ctx.beginPath();
2707
2722
  ctx.moveTo(pts[0][0], pts[0][1]);
2708
2723
  const H = LINK_HOP;
2724
+ // End a segment at its elbow with a rounded corner toward the next point (arcTo), clamped so it never
2725
+ // eats more than half of either adjacent segment. The final segment ends straight into the port.
2726
+ const finish = (i, x1, y1, x2, y2) => {
2727
+ if (i < pts.length - 1) {
2728
+ const nx = pts[i + 1][0];
2729
+ const ny = pts[i + 1][1];
2730
+ const seg1 = Math.abs(x2 - x1) + Math.abs(y2 - y1);
2731
+ const seg2 = Math.abs(nx - x2) + Math.abs(ny - y2);
2732
+ ctx.arcTo(x2, y2, nx, ny, Math.min(LINK_CORNER, seg1 / 2, seg2 / 2));
2733
+ }
2734
+ else {
2735
+ ctx.lineTo(x2, y2);
2736
+ }
2737
+ };
2709
2738
  for (let i = 1; i < pts.length; i += 1) {
2710
2739
  const [x1, y1] = pts[i - 1];
2711
2740
  const [x2, y2] = pts[i];
@@ -2723,7 +2752,7 @@ export class Diagram {
2723
2752
  else
2724
2753
  ctx.arc(cx, y1, H, 0, Math.PI, true);
2725
2754
  }
2726
- ctx.lineTo(x2, y2);
2755
+ finish(i, x1, y1, x2, y2);
2727
2756
  }
2728
2757
  else if (x1 === x2 && y1 !== y2) {
2729
2758
  const dir = y2 > y1 ? 1 : -1;
@@ -2739,7 +2768,7 @@ export class Diagram {
2739
2768
  else
2740
2769
  ctx.arc(x1, cy, H, Math.PI / 2, -Math.PI / 2, true);
2741
2770
  }
2742
- ctx.lineTo(x2, y2);
2771
+ finish(i, x1, y1, x2, y2);
2743
2772
  }
2744
2773
  else {
2745
2774
  ctx.lineTo(x2, y2);