@yuneta/gobj-ui 7.23.36 → 7.23.37

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
@@ -1467,7 +1467,10 @@ for the other side:
1467
1467
 
1468
1468
  The last one is not a rename: `states2json()` cannot name an action, so a cell
1469
1469
  from a **backend** gclass says THAT there is one and never which. The viewer
1470
- draws a mark rather than printing a name nobody wrote.
1470
+ draws a mark rather than printing a name nobody wrote — and an em dash for the
1471
+ third case, an event declared with NO action (`{EV_TX_READY, 0, 0}`, which
1472
+ C_WEBSOCKET has in all four states): legal in that state, and deliberately
1473
+ doing nothing.
1471
1474
 
1472
1475
  **The machine is a matrix**, rows = events and columns = states — the shape the
1473
1476
  FSM is declared in, and the one that survives 12 events against 3 states as
@@ -1507,7 +1510,11 @@ What it refuses to draw is the interesting half:
1507
1510
  - **One edge per PAIR of states**, not per transition, labelled with the events
1508
1511
  that make the jump (three, then a count). G6 draws overlapping edges on top
1509
1512
  of each other, so a second edge between the same two states is invisible and
1510
- nothing tells the reader it is there.
1513
+ nothing tells the reader it is there. A RECIPROCAL pair (`A -> B` and
1514
+ `B -> A`) is bowed apart into two arcs — with the SAME `curveOffset` on both,
1515
+ because G6 measures it from each edge's own direction, and with the type
1516
+ varied through a FUNCTION on `edge.type`, because a `type` written on the
1517
+ datum is overridden by the graph-level default.
1511
1518
  - **An event a state handles without leaving is not an edge.** Ten self-loops
1512
1519
  on one card say nothing a number cannot say better, so the card carries
1513
1520
  `n events · m leave` instead.
@@ -14010,7 +14010,7 @@ function build_graph$3(gobj) {
14010
14010
  zoomRange: [.2, 3],
14011
14011
  padding: 24,
14012
14012
  edge: {
14013
- type: "polyline",
14013
+ type: (d) => d && d.data && d.data.reciprocal ? "quadratic" : "polyline",
14014
14014
  style: {
14015
14015
  stroke: edge_color(),
14016
14016
  lineWidth: 1.2,
@@ -14151,11 +14151,18 @@ function build_data(gobj) {
14151
14151
  let shown = pair.events.slice(0, MAX_EDGE_LABELS);
14152
14152
  let label = shown.join("\n");
14153
14153
  if (pair.events.length > shown.length) label += "\n+" + (pair.events.length - shown.length);
14154
+ let style = { labelText: label };
14155
+ let reciprocal = by_pair.has(pair.target + " " + pair.source);
14156
+ if (reciprocal) {
14157
+ style.curveOffset = 34;
14158
+ style.labelPlacement = .5;
14159
+ }
14154
14160
  edges.push({
14155
14161
  id: "e_" + edges.length,
14156
14162
  source: pair.source,
14157
14163
  target: pair.target,
14158
- style: { labelText: label }
14164
+ data: { reciprocal },
14165
+ style
14159
14166
  });
14160
14167
  }
14161
14168
  return {
@@ -15542,7 +15549,7 @@ function build_machine(gobj, fsm) {
15542
15549
  { class: "GCLASS_MX_ACTION" },
15543
15550
  cell.action
15544
15551
  ]);
15545
- else items.push([
15552
+ else if (cell.has_action) items.push([
15546
15553
  "span",
15547
15554
  {
15548
15555
  class: "GCLASS_MX_ACTION is-unnamed",
@@ -15551,6 +15558,15 @@ function build_machine(gobj, fsm) {
15551
15558
  },
15552
15559
  "●"
15553
15560
  ]);
15561
+ else items.push([
15562
+ "span",
15563
+ {
15564
+ class: "GCLASS_MX_ACTION is-none-action",
15565
+ title: (0, i18next.t)("accepted with no action"),
15566
+ "data-i18n-title": "accepted with no action"
15567
+ },
15568
+ "—"
15569
+ ]);
15554
15570
  if (cell.next_state) {
15555
15571
  items.push([
15556
15572
  "span",
@@ -15679,6 +15695,19 @@ function build_legend(fsm, current) {
15679
15695
  (0, i18next.t)("not declared in this state")
15680
15696
  ]]
15681
15697
  ]);
15698
+ if (fsm.rows.some((row) => row.cells.some((cell) => cell && !cell.has_action))) items.push([
15699
+ "span",
15700
+ { class: "GCLASS_LEGEND_ITEM" },
15701
+ [[
15702
+ "i",
15703
+ { class: "GCLASS_LEGEND_SWATCH is-none-action" },
15704
+ "—"
15705
+ ], [
15706
+ "span",
15707
+ { i18n: "accepted with no action" },
15708
+ (0, i18next.t)("accepted with no action")
15709
+ ]]
15710
+ ]);
15682
15711
  if (current && fsm.states.some((s) => s.name === current)) items.push([
15683
15712
  "span",
15684
15713
  { class: "GCLASS_LEGEND_ITEM" },
@@ -14005,7 +14005,7 @@ function build_graph$3(gobj) {
14005
14005
  zoomRange: [.2, 3],
14006
14006
  padding: 24,
14007
14007
  edge: {
14008
- type: "polyline",
14008
+ type: (d) => d && d.data && d.data.reciprocal ? "quadratic" : "polyline",
14009
14009
  style: {
14010
14010
  stroke: edge_color(),
14011
14011
  lineWidth: 1.2,
@@ -14146,11 +14146,18 @@ function build_data(gobj) {
14146
14146
  let shown = pair.events.slice(0, MAX_EDGE_LABELS);
14147
14147
  let label = shown.join("\n");
14148
14148
  if (pair.events.length > shown.length) label += "\n+" + (pair.events.length - shown.length);
14149
+ let style = { labelText: label };
14150
+ let reciprocal = by_pair.has(pair.target + " " + pair.source);
14151
+ if (reciprocal) {
14152
+ style.curveOffset = 34;
14153
+ style.labelPlacement = .5;
14154
+ }
14149
14155
  edges.push({
14150
14156
  id: "e_" + edges.length,
14151
14157
  source: pair.source,
14152
14158
  target: pair.target,
14153
- style: { labelText: label }
14159
+ data: { reciprocal },
14160
+ style
14154
14161
  });
14155
14162
  }
14156
14163
  return {
@@ -15537,7 +15544,7 @@ function build_machine(gobj, fsm) {
15537
15544
  { class: "GCLASS_MX_ACTION" },
15538
15545
  cell.action
15539
15546
  ]);
15540
- else items.push([
15547
+ else if (cell.has_action) items.push([
15541
15548
  "span",
15542
15549
  {
15543
15550
  class: "GCLASS_MX_ACTION is-unnamed",
@@ -15546,6 +15553,15 @@ function build_machine(gobj, fsm) {
15546
15553
  },
15547
15554
  "●"
15548
15555
  ]);
15556
+ else items.push([
15557
+ "span",
15558
+ {
15559
+ class: "GCLASS_MX_ACTION is-none-action",
15560
+ title: t("accepted with no action"),
15561
+ "data-i18n-title": "accepted with no action"
15562
+ },
15563
+ "—"
15564
+ ]);
15549
15565
  if (cell.next_state) {
15550
15566
  items.push([
15551
15567
  "span",
@@ -15674,6 +15690,19 @@ function build_legend(fsm, current) {
15674
15690
  t("not declared in this state")
15675
15691
  ]]
15676
15692
  ]);
15693
+ if (fsm.rows.some((row) => row.cells.some((cell) => cell && !cell.has_action))) items.push([
15694
+ "span",
15695
+ { class: "GCLASS_LEGEND_ITEM" },
15696
+ [[
15697
+ "i",
15698
+ { class: "GCLASS_LEGEND_SWATCH is-none-action" },
15699
+ "—"
15700
+ ], [
15701
+ "span",
15702
+ { i18n: "accepted with no action" },
15703
+ t("accepted with no action")
15704
+ ]]
15705
+ ]);
15677
15706
  if (current && fsm.states.some((s) => s.name === current)) items.push([
15678
15707
  "span",
15679
15708
  { class: "GCLASS_LEGEND_ITEM" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.36",
3
+ "version": "7.23.37",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -395,7 +395,15 @@ function build_graph(gobj)
395
395
  * the edge of the box. */
396
396
  padding: 24,
397
397
  edge: {
398
- type: 'polyline',
398
+ /*
399
+ * A FUNCTION, not a name: a `type` written on the edge
400
+ * datum is overridden by the graph-level default, so a
401
+ * reciprocal pair went on drawing as two straight
402
+ * segments on top of each other. Asking the datum here is
403
+ * what G6 documents for varying the type per element.
404
+ */
405
+ type: (d) => (d && d.data && d.data.reciprocal)?
406
+ 'quadratic': 'polyline',
399
407
  style: {
400
408
  stroke: edge_color(),
401
409
  lineWidth: 1.2,
@@ -609,11 +617,51 @@ function build_data(gobj)
609
617
  if(pair.events.length > shown.length) {
610
618
  label += "\n+" + (pair.events.length - shown.length);
611
619
  }
620
+
621
+ let style = {labelText: label};
622
+
623
+ /*
624
+ * A pair that goes BOTH ways is drawn as two arcs, one bowing
625
+ * each way.
626
+ *
627
+ * Grouping by pair fixed the transitions that share an ordered
628
+ * pair; a RECIPROCAL pair is a different key and lands on the
629
+ * same straight segment -- so the two arrows overlap and the
630
+ * second label is painted under the first. Real case:
631
+ * C_WEBSOCKET goes to ST_WAIT_HANDSHAKE on EV_CONNECTED and
632
+ * comes back on EV_DISCONNECTED, and only the return was
633
+ * readable.
634
+ */
635
+ let reciprocal = by_pair.has(pair.target + " " + pair.source);
636
+ if(reciprocal) {
637
+ /* Bowed opposite ways so the two arrows separate -- and
638
+ * the two LABELS moved off the midpoint as well, one to
639
+ * each third of its own curve. Bowing alone was not
640
+ * enough: both labels are placed at the middle by default,
641
+ * they carry an opaque background, and the one drawn last
642
+ * simply covered the other. */
643
+ /* The SAME offset on both, not opposite ones. G6 measures
644
+ * `curveOffset` from the edge's OWN direction, and a
645
+ * reciprocal pair runs in opposite directions -- so equal
646
+ * signs bow them to opposite sides of the screen, and
647
+ * opposite signs put them on the same side, one on top of
648
+ * the other.
649
+ *
650
+ * The label stays at the MIDDLE of its arc, where the two
651
+ * are furthest apart. Moving it along the curve instead
652
+ * (0.3 / 0.7) pushed it under a state card: G6's html
653
+ * nodes are DOM elements ABOVE the canvas, so whatever the
654
+ * curve draws under one is invisible. */
655
+ style.curveOffset = 34;
656
+ style.labelPlacement = 0.5;
657
+ }
658
+
612
659
  edges.push({
613
660
  id: "e_" + edges.length,
614
661
  source: pair.source,
615
662
  target: pair.target,
616
- style: {labelText: label},
663
+ data: {reciprocal: reciprocal},
664
+ style: style,
617
665
  });
618
666
  }
619
667
 
@@ -506,3 +506,19 @@
506
506
  min-height: 16rem;
507
507
  max-height: 70vh;
508
508
  }
509
+
510
+ /*
511
+ * An event declared with no action at all: legal here, and it does
512
+ * nothing. Quieter than an action and louder than an empty cell,
513
+ * because it is neither.
514
+ */
515
+ .C_YUI_GCLASS .GCLASS_MX_ACTION.is-none-action {
516
+ color: var(--gclass-muted);
517
+ cursor: help;
518
+ }
519
+
520
+ .C_YUI_GCLASS .GCLASS_LEGEND_SWATCH.is-none-action {
521
+ border-color: transparent;
522
+ color: var(--gclass-muted);
523
+ width: auto;
524
+ }
@@ -910,13 +910,24 @@ function build_machine(gobj, fsm)
910
910
  let items = [];
911
911
  if(cell.action) {
912
912
  items.push(['span', {class: 'GCLASS_MX_ACTION'}, cell.action]);
913
- } else {
913
+ } else if(cell.has_action) {
914
914
  /* The C side cannot name an action: `states2json()`
915
915
  * writes the literal "action" for all of them. A mark
916
916
  * is the honest drawing of "there is one". */
917
917
  items.push(['span', {class: 'GCLASS_MX_ACTION is-unnamed',
918
918
  title: t("action"),
919
919
  'data-i18n-title': 'action'}, "●"]);
920
+ } else {
921
+ /* Declared with NO action and no next state --
922
+ * `{EV_TX_READY, 0, 0}`, four of them in C_WEBSOCKET.
923
+ * It is not the same as an empty cell (which throws)
924
+ * and it is not an action either: the event is legal
925
+ * here and deliberately does nothing. Drawing it as a
926
+ * mark claimed an action the gclass never declared. */
927
+ items.push(['span', {class: 'GCLASS_MX_ACTION is-none-action',
928
+ title: t("accepted with no action"),
929
+ 'data-i18n-title': 'accepted with no action'},
930
+ "—"]);
920
931
  }
921
932
  if(cell.next_state) {
922
933
  items.push(['span', {class: 'GCLASS_MX_ARROW'}, "▸"]);
@@ -1004,6 +1015,19 @@ function build_legend(fsm, current)
1004
1015
  ['span', {i18n: 'not declared in this state'}, t("not declared in this state")]
1005
1016
  ]]);
1006
1017
 
1018
+ /* Only if there is one on screen: a legend for a mark nobody drew
1019
+ * is noise, and most gclasses declare no actionless event. */
1020
+ let has_actionless = fsm.rows.some(
1021
+ (row) => row.cells.some((cell) => cell && !cell.has_action)
1022
+ );
1023
+ if(has_actionless) {
1024
+ items.push(['span', {class: 'GCLASS_LEGEND_ITEM'}, [
1025
+ ['i', {class: 'GCLASS_LEGEND_SWATCH is-none-action'}, "—"],
1026
+ ['span', {i18n: 'accepted with no action'},
1027
+ t("accepted with no action")]
1028
+ ]]);
1029
+ }
1030
+
1007
1031
  if(current && fsm.states.some((s) => s.name === current)) {
1008
1032
  items.push(['span', {class: 'GCLASS_LEGEND_ITEM'}, [
1009
1033
  ['i', {class: 'GCLASS_LEGEND_SWATCH is-current'}],
@@ -215,6 +215,20 @@ describe("build_fsm", () => {
215
215
  expect(fsm.published[0].flags).toEqual(["EVF_OUTPUT_EVENT"]);
216
216
  });
217
217
 
218
+ it("tells a declared-but-actionless cell from an action", () => {
219
+ /* `{EV_TX_READY, 0, 0}` -- four of them in C_WEBSOCKET: the
220
+ * event is legal in that state and deliberately does nothing. */
221
+ const none = build_fsm({
222
+ events: [{event_name: "EV_TX_READY", event_flag: ""}],
223
+ states: {"ST_IDLE": [
224
+ {event_name: "EV_TX_READY", action: "", next_state: ""},
225
+ ]},
226
+ });
227
+ expect(none.rows[0].cells[0]).toEqual({
228
+ action: "", has_action: false, next_state: "",
229
+ });
230
+ });
231
+
218
232
  it("says an action exists without inventing its name", () => {
219
233
  expect(fsm.rows[0].cells[1]).toEqual({
220
234
  action: "", has_action: true, next_state: "",