@yuneta/gobj-ui 7.23.80 → 7.23.82

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.
@@ -6,7 +6,7 @@ import { createJSONEditor } from "vanilla-jsoneditor";
6
6
  import { TabulatorFull } from "tabulator-tables";
7
7
  import * as maplibregl from "maplibre-gl";
8
8
  import uPlot from "uplot";
9
- import { BaseLayout, BrushSelect, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HistoryEvent, NodeEvent, Toolbar, ZoomCanvas, register } from "@antv/g6";
9
+ import { BaseLayout, BrushSelect, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HTML, HistoryEvent, NodeEvent, Toolbar, ZoomCanvas, register } from "@antv/g6";
10
10
  //#region \0rolldown/runtime.js
11
11
  var __create = Object.create;
12
12
  var __defProp = Object.defineProperty;
@@ -43154,7 +43154,7 @@ var _excluded$5 = ["style"];
43154
43154
  * HTML container
43155
43155
  * @see https://github.com/pmndrs/drei#html
43156
43156
  */
43157
- var HTML = /*#__PURE__*/ function(_DisplayObject) {
43157
+ var HTML$1 = /*#__PURE__*/ function(_DisplayObject) {
43158
43158
  function HTML() {
43159
43159
  var _this;
43160
43160
  var _ref = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, style = _ref.style, rest = _objectWithoutProperties(_ref, _excluded$5);
@@ -43202,7 +43202,7 @@ var HTML = /*#__PURE__*/ function(_DisplayObject) {
43202
43202
  }
43203
43203
  ]);
43204
43204
  }(DisplayObject);
43205
- HTML.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(DisplayObject.PARSED_STYLE_LIST), [
43205
+ HTML$1.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(DisplayObject.PARSED_STYLE_LIST), [
43206
43206
  "x",
43207
43207
  "y",
43208
43208
  "$el",
@@ -54525,11 +54525,31 @@ var SELECT_RING = "rgba(59,130,246,0.95)";
54525
54525
  var PORT_R_ENTITY = 14;
54526
54526
  var PORT_R_CHILD = 8;
54527
54527
  var PORT_LINE_WIDTH = 2;
54528
- var COMPACT_SIZE = {
54528
+ var NODE_MODES$1 = [
54529
+ "expanded",
54530
+ "compact",
54531
+ "shape"
54532
+ ];
54533
+ var PILL_SIZE = {
54534
+ hierarchical: [200, 30],
54535
+ extended: [180, 28],
54536
+ child: [160, 26]
54537
+ };
54538
+ var PORT_R_PILL = 6;
54539
+ var SHAPE_SIZE = {
54529
54540
  hierarchical: 32,
54530
54541
  extended: 28,
54531
54542
  child: 22
54532
54543
  };
54544
+ var NODE_SHAPES = [
54545
+ "square",
54546
+ "circle",
54547
+ "diamond",
54548
+ "triangle",
54549
+ "hexagon",
54550
+ "star"
54551
+ ];
54552
+ var DEFAULT_NODE_SHAPE = "square";
54533
54553
  /***************************************************************
54534
54554
  * Internal layout and operation mode definitions
54535
54555
  ***************************************************************/
@@ -54610,8 +54630,8 @@ var attrs_table$4 = [
54610
54630
  SDATA(data_type_t.DTP_LIST, "hidden_topics", 0, "[]", "Topics left out of the tree: no card, no pill counts them, no edge reaches them"),
54611
54631
  SDATA(data_type_t.DTP_STRING, "main_topic", 0, "", "The topic the tree hangs from; empty = deduced (the one whose hooks reach the most others). Its parentless records are the roots; a parentless record of a topic linked to it is LOOSE and shown only on request"),
54612
54632
  SDATA(data_type_t.DTP_LIST, "loose_topics", 0, "[]", "Topics whose loose records (no parent, in a topic the schema hangs from the main one) are shown as roots"),
54613
- SDATA(data_type_t.DTP_STRING, "node_mode", 0, "expanded", "How a record is drawn: `expanded` = the card with its ports (the only shape a link can be edited on); `compact` = a rounded square of the topic's colour, no ports, no text -- the topology alone. One node can be toggled against it (EV_TOGGLE_NODE_MODE)"),
54614
- SDATA(data_type_t.DTP_BOOLEAN, "node_labels", 0, true, "A compact node says its name under the square. Off, it is a coloured square and nothing else"),
54633
+ SDATA(data_type_t.DTP_STRING, "node_mode", 0, "expanded", "How a record is drawn: `expanded` = the card with its pills and ports; `compact` = a one-line pill with the name and small ports; `shape` = a figure of the topic's colour (square, circle, diamond... per node or topic, from the node properties), no ports, no text -- the topology alone. One node can be toggled against it (EV_TOGGLE_NODE_MODE)"),
54634
+ SDATA(data_type_t.DTP_BOOLEAN, "node_labels", 0, true, "A `shape` node says its name under the figure. Off, it is a coloured figure and nothing else"),
54615
54635
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
54616
54636
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
54617
54637
  SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
@@ -54653,6 +54673,8 @@ var PRIVATE_DATA$4 = {
54653
54673
  _edge_icon_el: null,
54654
54674
  _edge_delete_el: null,
54655
54675
  _edge_popover_el: null,
54676
+ _port_icon_el: null,
54677
+ _port_popover_el: null,
54656
54678
  _node_icon_el: null,
54657
54679
  _node_delete_el: null,
54658
54680
  _node_popover_el: null,
@@ -54843,6 +54865,7 @@ function register_layouts(gobj) {
54843
54865
  register(ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
54844
54866
  register(ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
54845
54867
  register(ExtensionCategory.NODE, "light", LightNode);
54868
+ register(ExtensionCategory.NODE, "treedb-card", TreedbCard);
54846
54869
  }
54847
54870
  }
54848
54871
  /************************************************************
@@ -54912,6 +54935,7 @@ function configure_events(gobj) {
54912
54935
  update_edge_icon_position(gobj);
54913
54936
  update_node_icon_position(gobj);
54914
54937
  update_link_icon_position(gobj);
54938
+ update_port_icon_position(gobj);
54915
54939
  });
54916
54940
  graph.on(NodeEvent.DRAG_END, (evt) => {
54917
54941
  gobj_send_event(gobj, "EV_NODE_DRAG_END", { evt }, gobj);
@@ -54936,6 +54960,7 @@ function configure_events(gobj) {
54936
54960
  update_edge_icon_position(gobj);
54937
54961
  update_node_icon_position(gobj);
54938
54962
  update_link_icon_position(gobj);
54963
+ update_port_icon_position(gobj);
54939
54964
  update_zoom_readout(gobj);
54940
54965
  let z = priv.last_zoom;
54941
54966
  try {
@@ -55584,14 +55609,26 @@ function create_topic_node(gobj, desc, record) {
55584
55609
  * the other one would leave behind (`labelText`, `ports`).
55585
55610
  ************************************************************/
55586
55611
  function node_shape_of(gobj, node_id, desc, record, geometry, pills_html) {
55587
- if (node_is_compact(gobj, node_id)) return compact_shape_of(gobj, desc, record, {});
55588
- return card_shape_of(gobj, desc, record, geometry, pills_html);
55612
+ switch (node_mode_of(gobj, node_id)) {
55613
+ case "shape": return figure_shape_of(gobj, desc, record, geometry, {});
55614
+ case "compact": return pill_shape_of(gobj, desc, record, geometry);
55615
+ default: return card_shape_of(gobj, desc, record, geometry, pills_html);
55616
+ }
55589
55617
  }
55590
- function node_is_compact(gobj, node_id) {
55618
+ function node_mode_of(gobj, node_id) {
55591
55619
  let priv = gobj.priv;
55592
55620
  if (!priv._open_nodes) priv._open_nodes = /* @__PURE__ */ new Set();
55593
- let compact = gobj_read_str_attr(gobj, "node_mode") === "compact";
55594
- return priv._open_nodes.has(node_id) ? !compact : compact;
55621
+ let mode = gobj_read_str_attr(gobj, "node_mode");
55622
+ if (!mode || !str_in_list(NODE_MODES$1, mode)) mode = "expanded";
55623
+ if (priv._open_nodes.has(node_id)) return mode === "expanded" ? "compact" : "expanded";
55624
+ return mode;
55625
+ }
55626
+ function node_is_compact(gobj, node_id) {
55627
+ return node_mode_of(gobj, node_id) !== "expanded";
55628
+ }
55629
+ function topic_defaults_of(gobj, topic_name) {
55630
+ let topic_props = gobj.priv._graph_properties[topic_name];
55631
+ return is_object(topic_props) && is_object(topic_props.defaults) ? topic_props.defaults : null;
55595
55632
  }
55596
55633
  /************************************************************
55597
55634
  * The CARD: an html node, the tier's size, the ports, the
@@ -55637,45 +55674,107 @@ function card_shape_of(gobj, desc, record, geometry, pills_html) {
55637
55674
  style.dy = -(saved_size.length > 1 ? saved_size[1] : saved_size[0]) / 2;
55638
55675
  }
55639
55676
  if (geometry.portR > 0) style.portR = geometry.portR;
55640
- if (json_size(ports)) {
55641
- json_object_update_missing(style, {
55642
- port: true,
55643
- ports,
55644
- portR: node_treedb_type === "child" ? PORT_R_CHILD : PORT_R_ENTITY,
55645
- portLineWidth: PORT_LINE_WIDTH
55646
- });
55647
- let port_sizes = geometry.port_sizes;
55648
- let default_port_sizes = topic_defaults ? topic_defaults.port_sizes : null;
55649
- if (is_object(port_sizes) || is_object(default_port_sizes)) for (let i = 0; i < style.ports.length; i++) {
55650
- let key = style.ports[i].key;
55651
- let r = is_object(port_sizes) ? port_sizes[key] : null;
55652
- if (r == null && is_object(default_port_sizes)) r = default_port_sizes[key];
55653
- if (r != null) style.ports[i].r = r;
55654
- }
55655
- } else {
55677
+ dress_ports(gobj, style, ports, geometry, topic_defaults, node_treedb_type === "child" ? PORT_R_CHILD : PORT_R_ENTITY, false);
55678
+ return {
55679
+ type: "treedb-card",
55680
+ style
55681
+ };
55682
+ }
55683
+ /************************************************************
55684
+ * The ports of a card or a pill, dressed: the node-level radius
55685
+ * (saved, topic default, or the tier's), each port's own radius
55686
+ * and shape (saved first, then the topic's per-port defaults, then
55687
+ * the topic's one shape for every port). `fixed_r` -- the pill --
55688
+ * ignores every saved radius: a pill has one size of port.
55689
+ ************************************************************/
55690
+ function dress_ports(gobj, style, ports, geometry, topic_defaults, r_default, fixed_r) {
55691
+ if (!json_size(ports)) {
55656
55692
  style.port = false;
55657
55693
  style.ports = [];
55694
+ return;
55658
55695
  }
55696
+ json_object_update_missing(style, {
55697
+ port: true,
55698
+ ports,
55699
+ portR: r_default,
55700
+ portLineWidth: PORT_LINE_WIDTH
55701
+ });
55702
+ if (fixed_r) style.portR = r_default;
55703
+ let port_sizes = fixed_r ? null : geometry.port_sizes;
55704
+ let default_port_sizes = !fixed_r && topic_defaults ? topic_defaults.port_sizes : null;
55705
+ let port_shapes = geometry.port_shapes;
55706
+ let default_port_shapes = topic_defaults ? topic_defaults.port_shapes : null;
55707
+ let default_shape = topic_defaults ? topic_defaults.port_shape : null;
55708
+ for (let i = 0; i < style.ports.length; i++) {
55709
+ let key = style.ports[i].key;
55710
+ let r = is_object(port_sizes) ? port_sizes[key] : null;
55711
+ if (r == null && is_object(default_port_sizes)) r = default_port_sizes[key];
55712
+ if (r != null) style.ports[i].r = r;
55713
+ let shape = is_object(port_shapes) ? port_shapes[key] : null;
55714
+ if (!shape && is_object(default_port_shapes)) shape = default_port_shapes[key];
55715
+ if (!shape) shape = default_shape;
55716
+ if (shape && str_in_list(PORT_SHAPES, shape)) style.ports[i].shape = shape;
55717
+ }
55718
+ }
55719
+ /************************************************************
55720
+ * The PILL: one line with the name inside, the tint and the
55721
+ * border of the topic, small ports on its edges -- the half-way
55722
+ * house between the card and the figure, and the same one-line
55723
+ * chip a leaf record already is, at the tier's width. No pills:
55724
+ * a hook is opened from the card (expand the node).
55725
+ ************************************************************/
55726
+ function pill_shape_of(gobj, desc, record, geometry) {
55727
+ let priv = gobj.priv;
55728
+ let size = PILL_SIZE[desc.node_treedb_type] || PILL_SIZE.hierarchical;
55729
+ let ports = build_ports(gobj, desc);
55730
+ let style = {
55731
+ fill: desc.color,
55732
+ stroke: getStrokeColor(desc.color),
55733
+ lineWidth: 1,
55734
+ lineDash: [],
55735
+ halo: false,
55736
+ labelText: "",
55737
+ size: [...size],
55738
+ dx: -size[0] / 2,
55739
+ dy: -size[1] / 2,
55740
+ innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id)
55741
+ };
55742
+ dress_ports(gobj, style, ports, geometry, topic_defaults_of(gobj, desc.topic_name), PORT_R_PILL, true);
55659
55743
  return {
55660
- type: "html",
55744
+ type: "treedb-card",
55661
55745
  style
55662
55746
  };
55663
55747
  }
55748
+ function pill_innerHTML_of(gobj, nd, theme, highlight, selected, anchored) {
55749
+ let desc = nd.data.desc;
55750
+ let record = nd.data.record || {};
55751
+ return build_chip_innerHTML(desc.color, theme, record.icon, node_label(desc, record), record.id, highlight, selected, anchored);
55752
+ }
55753
+ function node_figure_of(gobj, desc, geometry) {
55754
+ let shape = geometry ? geometry.node_shape : null;
55755
+ if (!shape) {
55756
+ let defaults = topic_defaults_of(gobj, desc.topic_name);
55757
+ shape = defaults ? defaults.node_shape : null;
55758
+ }
55759
+ return shape && str_in_list(NODE_SHAPES, shape) ? shape : DEFAULT_NODE_SHAPE;
55760
+ }
55664
55761
  /************************************************************
55665
- * The SQUARE: a G6 `rect` of the topic's colour, no ports, the
55666
- * name as a label under it when `node_labels` says so. A native
55667
- * shape and not html on purpose: G6's own stroke and halo paint
55668
- * on it, so the focus, the selection and the anchor are three
55669
- * keys of the style instead of a rebuilt innerHTML.
55762
+ * The FIGURE: a native G6 node of the topic's colour -- a square,
55763
+ * a circle, a diamond, a triangle, a hexagon, a star -- no ports,
55764
+ * the name as a label under it when `node_labels` says so. Native
55765
+ * and not html on purpose: G6's own stroke and halo paint on it,
55766
+ * so the focus, the selection and the anchor are three keys of
55767
+ * the style instead of a rebuilt innerHTML.
55670
55768
  *
55671
55769
  * flags {highlight, selected, anchored}
55672
55770
  ************************************************************/
55673
- function compact_shape_of(gobj, desc, record, flags) {
55771
+ function figure_shape_of(gobj, desc, record, geometry, flags) {
55674
55772
  let dark = gobj.priv.theme === "dark";
55675
55773
  let f = flags || {};
55676
55774
  let tier = desc.node_treedb_type;
55677
- let side = COMPACT_SIZE[tier] || COMPACT_SIZE.hierarchical;
55775
+ let side = SHAPE_SIZE[tier] || SHAPE_SIZE.hierarchical;
55678
55776
  let labels = gobj_read_bool_attr(gobj, "node_labels");
55777
+ let figure = node_figure_of(gobj, desc, geometry || {});
55679
55778
  let stroke = getStrokeColor(desc.color);
55680
55779
  let line_width = 1.5;
55681
55780
  if (f.selected) {
@@ -55692,34 +55791,35 @@ function compact_shape_of(gobj, desc, record, flags) {
55692
55791
  dash = [2, 2];
55693
55792
  line_width = Math.max(line_width, 2.5);
55694
55793
  }
55794
+ let style = {
55795
+ size: [side, side],
55796
+ radius: figure === "square" ? Math.round(side / 5) : 0,
55797
+ fill: desc.color,
55798
+ stroke,
55799
+ lineWidth: line_width,
55800
+ lineDash: dash,
55801
+ halo: !!f.highlight,
55802
+ haloStroke: HIGHLIGHT_HALO,
55803
+ haloLineWidth: 8,
55804
+ port: false,
55805
+ ports: [],
55806
+ labelText: labels ? node_label(desc, record) : "",
55807
+ labelPlacement: "bottom",
55808
+ labelFontSize: 11,
55809
+ labelFill: dark ? "#e6e9ef" : "#2e333d",
55810
+ labelMaxWidth: 150,
55811
+ labelWordWrap: true,
55812
+ labelMaxLines: 1,
55813
+ labelTextOverflow: "ellipsis",
55814
+ labelBackground: true,
55815
+ labelBackgroundFill: dark ? "#1b2230" : "#ffffff",
55816
+ labelBackgroundOpacity: .85,
55817
+ labelBackgroundRadius: 3,
55818
+ labelPadding: [1, 3]
55819
+ };
55695
55820
  return {
55696
- type: "rect",
55697
- style: {
55698
- size: [side, side],
55699
- radius: Math.round(side / 5),
55700
- fill: desc.color,
55701
- stroke,
55702
- lineWidth: line_width,
55703
- lineDash: dash,
55704
- halo: !!f.highlight,
55705
- haloStroke: HIGHLIGHT_HALO,
55706
- haloLineWidth: 8,
55707
- port: false,
55708
- ports: [],
55709
- labelText: labels ? node_label(desc, record) : "",
55710
- labelPlacement: "bottom",
55711
- labelFontSize: 11,
55712
- labelFill: dark ? "#e6e9ef" : "#2e333d",
55713
- labelMaxWidth: 150,
55714
- labelWordWrap: true,
55715
- labelMaxLines: 1,
55716
- labelTextOverflow: "ellipsis",
55717
- labelBackground: true,
55718
- labelBackgroundFill: dark ? "#1b2230" : "#ffffff",
55719
- labelBackgroundOpacity: .85,
55720
- labelBackgroundRadius: 3,
55721
- labelPadding: [1, 3]
55722
- }
55821
+ type: figure === "square" ? "rect" : figure,
55822
+ style
55723
55823
  };
55724
55824
  }
55725
55825
  /************************************************************
@@ -56081,6 +56181,10 @@ function update_geometry(gobj, node_id) {
56081
56181
  for (let i = 0; i < ports.length; i++) if (ports[i].r != null) port_sizes[ports[i].key] = ports[i].r;
56082
56182
  if (Object.keys(port_sizes).length > 0) node_props.port_sizes = port_sizes;
56083
56183
  else delete node_props.port_sizes;
56184
+ let port_shapes = {};
56185
+ for (let i = 0; i < ports.length; i++) if (ports[i].shape && ports[i].shape !== "circle") port_shapes[ports[i].key] = ports[i].shape;
56186
+ if (Object.keys(port_shapes).length > 0) node_props.port_shapes = port_shapes;
56187
+ else delete node_props.port_shapes;
56084
56188
  }
56085
56189
  topic_props.nodes[record.id] = node_props;
56086
56190
  nodedata.data.graph_props = node_props;
@@ -56563,11 +56667,74 @@ function graph_resize(gobj, width, height) {
56563
56667
  update_port_resize_handles_position(gobj);
56564
56668
  update_node_icon_position(gobj);
56565
56669
  update_link_icon_position(gobj);
56670
+ update_port_icon_position(gobj);
56566
56671
  }
56567
56672
  function graph_write_behaviors(gobj, behaviors) {
56568
56673
  gobj.priv.graph.setOptions({ behaviors });
56569
56674
  }
56570
56675
  /************************************************************
56676
+ * Custom G6 node: TreedbCard -- G6's html node with ports that
56677
+ * have a SHAPE.
56678
+ *
56679
+ * G6 draws every port as a circle (`drawPortShapes` upserts a
56680
+ * `Circle`, nothing in the port style says otherwise). A port's
56681
+ * `shape` -- `circle`, `square`, `diamond`, `triangle` -- picks
56682
+ * the G shape here, by the names G6 registers itself, and `r`
56683
+ * stays the one size: the half-side of the square, the half-
56684
+ * diagonal of the diamond, the half-height of the triangle. The
56685
+ * hit test, the resize handles and the edge's landing point all
56686
+ * read `r` and none of them care about the outline.
56687
+ ************************************************************/
56688
+ var PORT_SHAPES = [
56689
+ "circle",
56690
+ "square",
56691
+ "diamond",
56692
+ "triangle"
56693
+ ];
56694
+ var TreedbCard = class extends HTML {
56695
+ drawPortShapes(attributes, container) {
56696
+ const portsStyle = this.getPortsStyle(attributes);
56697
+ Object.keys(portsStyle).forEach((key) => {
56698
+ const style = portsStyle[key];
56699
+ const shapeKey = `port-${key}`;
56700
+ if (style === false) {
56701
+ this.upsert(shapeKey, "circle", false, container);
56702
+ return;
56703
+ }
56704
+ let r = Number(style.r) || 0;
56705
+ switch (style.shape) {
56706
+ case "square":
56707
+ this.upsert(shapeKey, "rect", Object.assign({}, style, {
56708
+ x: -r,
56709
+ y: -r,
56710
+ width: 2 * r,
56711
+ height: 2 * r
56712
+ }), container);
56713
+ break;
56714
+ case "diamond":
56715
+ this.upsert(shapeKey, "polygon", Object.assign({}, style, { points: [
56716
+ [0, -r],
56717
+ [r, 0],
56718
+ [0, r],
56719
+ [-r, 0]
56720
+ ] }), container);
56721
+ break;
56722
+ case "triangle":
56723
+ this.upsert(shapeKey, "polygon", Object.assign({}, style, { points: [
56724
+ [0, -r],
56725
+ [r, r],
56726
+ [-r, r]
56727
+ ] }), container);
56728
+ break;
56729
+ default: this.upsert(shapeKey, "circle", style, container);
56730
+ }
56731
+ });
56732
+ }
56733
+ };
56734
+ function is_card_type(type) {
56735
+ return type === "treedb-card" || type === "html";
56736
+ }
56737
+ /************************************************************
56571
56738
  * Custom G6 node: LightNode (circle with status indicator)
56572
56739
  ************************************************************/
56573
56740
  var LightNode = class extends Circle {
@@ -57313,7 +57480,7 @@ function start_node_resize(gobj, e, mx, my) {
57313
57480
  };
57314
57481
  if (nodeType === "circle") updateStyle.size = [Math.max(newW, newH)];
57315
57482
  else updateStyle.size = [newW, newH];
57316
- if (nodeType === "html") {
57483
+ if (is_card_type(nodeType)) {
57317
57484
  updateStyle.dx = -newW / 2;
57318
57485
  updateStyle.dy = -newH / 2;
57319
57486
  }
@@ -57438,6 +57605,7 @@ function select_port(gobj, node_id, port_key) {
57438
57605
  priv._selected_port_key = port_key;
57439
57606
  show_port_resize_handles(gobj);
57440
57607
  show_link_icon_if_fkey(gobj);
57608
+ show_port_icon(gobj);
57441
57609
  }
57442
57610
  /************************************************************
57443
57611
  * Deselect port: clear port state, return to node selection.
@@ -57446,6 +57614,8 @@ function deselect_port(gobj) {
57446
57614
  let priv = gobj.priv;
57447
57615
  exit_linking_mode(gobj);
57448
57616
  hide_link_icon(gobj);
57617
+ hide_port_icon(gobj);
57618
+ hide_port_popover(gobj);
57449
57619
  hide_port_resize_handles(gobj);
57450
57620
  priv._selected_port_key = null;
57451
57621
  }
@@ -57498,6 +57668,217 @@ function update_link_icon_position(gobj) {
57498
57668
  }
57499
57669
  }
57500
57670
  /************************************************************
57671
+ * Port properties icon and popover: the gear beside a selected
57672
+ * port, as the node has its own. The popover has the two things
57673
+ * a port IS on screen -- its shape and its radius -- and the
57674
+ * scope: this port, the same port of every card of the topic
57675
+ * (the hook or fkey column), or every port there is.
57676
+ ************************************************************/
57677
+ function port_icon_xy(gobj) {
57678
+ let priv = gobj.priv;
57679
+ let graph = priv.graph;
57680
+ let canvasPos = get_port_canvas_position(gobj, priv._selected_node_id, priv._selected_port_key);
57681
+ if (!canvasPos) return null;
57682
+ let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
57683
+ let below = priv._link_icon_el ? floating_icon_step() : 0;
57684
+ return {
57685
+ x: vp[0] + floating_icon_size() / 2 + 4,
57686
+ y: vp[1] + floating_icon_dy() + below
57687
+ };
57688
+ }
57689
+ function show_port_icon(gobj) {
57690
+ hide_port_icon(gobj);
57691
+ let priv = gobj.priv;
57692
+ if (!priv._selected_node_id || !priv._selected_port_key) return;
57693
+ let xy = port_icon_xy(gobj);
57694
+ if (!xy) return;
57695
+ let icon = create_floating_icon("gear", "#1890ff", xy.x, xy.y, t("port properties"), () => toggle_port_popover(gobj));
57696
+ priv.$container.appendChild(icon);
57697
+ priv._port_icon_el = icon;
57698
+ }
57699
+ function hide_port_icon(gobj) {
57700
+ hide_overlay(gobj, "_port_icon_el");
57701
+ }
57702
+ function update_port_icon_position(gobj) {
57703
+ let priv = gobj.priv;
57704
+ if (!priv._port_icon_el || !priv._selected_node_id || !priv._selected_port_key) return;
57705
+ try {
57706
+ let xy = port_icon_xy(gobj);
57707
+ if (!xy) {
57708
+ hide_port_icon(gobj);
57709
+ hide_port_popover(gobj);
57710
+ return;
57711
+ }
57712
+ priv._port_icon_el.style.left = xy.x + "px";
57713
+ priv._port_icon_el.style.top = xy.y + "px";
57714
+ if (priv._port_popover_el) {
57715
+ priv._port_popover_el.style.left = xy.x + floating_popover_dx() + "px";
57716
+ priv._port_popover_el.style.top = xy.y + "px";
57717
+ clamp_popover_position(gobj, priv._port_popover_el);
57718
+ }
57719
+ } catch (e) {
57720
+ hide_port_icon(gobj);
57721
+ hide_port_popover(gobj);
57722
+ }
57723
+ }
57724
+ function toggle_port_popover(gobj) {
57725
+ if (gobj.priv._port_popover_el) hide_port_popover(gobj);
57726
+ else show_port_popover(gobj);
57727
+ }
57728
+ function hide_port_popover(gobj) {
57729
+ hide_overlay(gobj, "_port_popover_el");
57730
+ }
57731
+ function ports_with(ports, port_key, changes) {
57732
+ return (ports || []).map((p) => p.key === port_key ? Object.assign({}, p, changes) : p);
57733
+ }
57734
+ function show_port_popover(gobj) {
57735
+ hide_port_popover(gobj);
57736
+ let priv = gobj.priv;
57737
+ let graph = priv.graph;
57738
+ let node_id = priv._selected_node_id;
57739
+ let port_key = priv._selected_port_key;
57740
+ if (!node_id || !port_key) return;
57741
+ let nodeData = graph.getNodeData(node_id);
57742
+ if (!nodeData || !nodeData.style) return;
57743
+ let port = (nodeData.style.ports || []).find((p) => p.key === port_key);
57744
+ if (!port) return;
57745
+ let orig_shape = port.shape || "circle";
57746
+ let orig_r = get_port_radius(gobj, node_id, port_key);
57747
+ let xy = port_icon_xy(gobj);
57748
+ if (!xy) return;
57749
+ const popover = create_popover_base(xy.x + floating_popover_dx(), xy.y, "g6-port-popover", "#d9d9d9", 180);
57750
+ function preview_port() {
57751
+ let r = parseInt(rInput.value) || orig_r;
57752
+ graph.updateNodeData([{
57753
+ id: node_id,
57754
+ style: { ports: ports_with(nodeData.style.ports, port_key, {
57755
+ shape: shapeSelect.value,
57756
+ r
57757
+ }) }
57758
+ }]);
57759
+ graph.draw().then(() => {
57760
+ update_port_resize_handles_position(gobj);
57761
+ });
57762
+ }
57763
+ create_form_label(popover, "shape");
57764
+ let shapeSelect = create_form_select(popover, PORT_SHAPES.map((shape) => {
57765
+ return {
57766
+ value: shape,
57767
+ label: t(shape)
57768
+ };
57769
+ }));
57770
+ shapeSelect.value = orig_shape;
57771
+ shapeSelect.addEventListener("change", preview_port);
57772
+ create_form_label(popover, "radius");
57773
+ let rInput = create_form_number_input(popover, orig_r, 2, 40, preview_port);
57774
+ create_form_label(popover, "apply to");
57775
+ let scopeSelect = create_form_select(popover, [
57776
+ {
57777
+ value: "this",
57778
+ label: t("this port")
57779
+ },
57780
+ {
57781
+ value: "same_topic",
57782
+ label: t("same port in topic")
57783
+ },
57784
+ {
57785
+ value: "all",
57786
+ label: t("all ports")
57787
+ }
57788
+ ]);
57789
+ create_form_button_row(popover, [{
57790
+ text: "cancel",
57791
+ style: BTN_STYLE_CANCEL,
57792
+ onClick: () => {
57793
+ graph.updateNodeData([{
57794
+ id: node_id,
57795
+ style: { ports: ports_with(nodeData.style.ports, port_key, {
57796
+ shape: orig_shape,
57797
+ r: orig_r
57798
+ }) }
57799
+ }]);
57800
+ graph.draw().then(() => {
57801
+ update_port_resize_handles_position(gobj);
57802
+ });
57803
+ hide_port_popover(gobj);
57804
+ }
57805
+ }, {
57806
+ text: "apply",
57807
+ style: "flex:1;padding:6px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;",
57808
+ onClick: () => {
57809
+ apply_port_properties(gobj, node_id, port_key, shapeSelect.value, parseInt(rInput.value) || orig_r, scopeSelect.value);
57810
+ }
57811
+ }]);
57812
+ priv.$container.appendChild(popover);
57813
+ priv._port_popover_el = popover;
57814
+ clamp_popover_position(gobj, popover);
57815
+ }
57816
+ /************************************************************
57817
+ * Apply a port's shape and radius to one port, to the same port
57818
+ * of every card of the topic, or to every port -- and remember
57819
+ * the choice as the topic's default, the way `resize all ports`
57820
+ * does, so a card that arrives later is born with it.
57821
+ * scope: 'this', 'same_topic', 'all'
57822
+ ************************************************************/
57823
+ function apply_port_properties(gobj, node_id, port_key, shape, r, scope) {
57824
+ let priv = gobj.priv;
57825
+ let graph = priv.graph;
57826
+ let nodeData = graph.getNodeData(node_id);
57827
+ if (!nodeData || !nodeData.data) return;
57828
+ let source_topic = nodeData.data.topic_name;
57829
+ if (scope === "same_topic") {
57830
+ if (!is_object(priv._graph_properties[source_topic])) priv._graph_properties[source_topic] = {};
57831
+ let defaults = priv._graph_properties[source_topic].defaults || {};
57832
+ defaults.port_sizes = Object.assign({}, defaults.port_sizes, { [port_key]: r });
57833
+ defaults.port_shapes = Object.assign({}, defaults.port_shapes, { [port_key]: shape });
57834
+ priv._graph_properties[source_topic].defaults = defaults;
57835
+ } else if (scope === "all") for (const topic_name of Object.keys(priv.descs || {})) {
57836
+ if (!is_object(priv._graph_properties[topic_name])) priv._graph_properties[topic_name] = {};
57837
+ let defaults = priv._graph_properties[topic_name].defaults || {};
57838
+ defaults.portR = r;
57839
+ defaults.port_shape = shape;
57840
+ delete defaults.port_sizes;
57841
+ delete defaults.port_shapes;
57842
+ priv._graph_properties[topic_name].defaults = defaults;
57843
+ }
57844
+ let updates = [];
57845
+ for (let nd of graph.getNodeData() || []) {
57846
+ if (!nd || !nd.data || !nd.data.desc || !nd.style || !is_array(nd.style.ports)) continue;
57847
+ let new_ports;
57848
+ if (scope === "this") {
57849
+ if (nd.id !== node_id) continue;
57850
+ new_ports = ports_with(nd.style.ports, port_key, {
57851
+ shape,
57852
+ r
57853
+ });
57854
+ } else if (scope === "same_topic") {
57855
+ if (nd.data.topic_name !== source_topic) continue;
57856
+ new_ports = ports_with(nd.style.ports, port_key, {
57857
+ shape,
57858
+ r
57859
+ });
57860
+ } else new_ports = nd.style.ports.map((p) => Object.assign({}, p, {
57861
+ shape,
57862
+ r
57863
+ }));
57864
+ let upd = { ports: new_ports };
57865
+ if (scope === "all") upd.portR = r;
57866
+ updates.push({
57867
+ id: nd.id,
57868
+ style: upd
57869
+ });
57870
+ }
57871
+ if (updates.length > 0) {
57872
+ graph.updateNodeData(updates);
57873
+ graph.draw().then(() => {
57874
+ update_port_resize_handles_position(gobj);
57875
+ update_port_icon_position(gobj);
57876
+ mark_graph_dirty(gobj);
57877
+ });
57878
+ }
57879
+ hide_port_popover(gobj);
57880
+ }
57881
+ /************************************************************
57501
57882
  * Get a column descriptor by field id from a topic desc.
57502
57883
  ************************************************************/
57503
57884
  function get_col_by_id(desc, col_id) {
@@ -58204,6 +58585,14 @@ function show_node_popover(gobj) {
58204
58585
  let strokeInput = create_form_color_input(popover, currentStroke, preview_node);
58205
58586
  create_form_label(popover, "line width");
58206
58587
  let lwInput = create_form_number_input(popover, currentLW, 1, 20, preview_node);
58588
+ create_form_label(popover, "shape");
58589
+ let shapeSelect = create_form_select(popover, NODE_SHAPES.map((shape) => {
58590
+ return {
58591
+ value: shape,
58592
+ label: t(shape)
58593
+ };
58594
+ }));
58595
+ shapeSelect.value = nodeData.data && nodeData.data.desc ? node_figure_of(gobj, nodeData.data.desc, nodeData.data.graph_props || {}) : DEFAULT_NODE_SHAPE;
58207
58596
  create_form_label(popover, "apply to");
58208
58597
  let scopeSelect = create_form_select(popover, [
58209
58598
  {
@@ -58243,7 +58632,7 @@ function show_node_popover(gobj) {
58243
58632
  text: "apply",
58244
58633
  style: "flex:1;padding:6px;background:#1890ff;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;",
58245
58634
  onClick: () => {
58246
- apply_node_properties(gobj, node_id, fillInput.value, strokeInput.value, parseInt(lwInput.value) || 1, scopeSelect.value);
58635
+ apply_node_properties(gobj, node_id, fillInput.value, strokeInput.value, parseInt(lwInput.value) || 1, scopeSelect.value, shapeSelect.value);
58247
58636
  }
58248
58637
  }]);
58249
58638
  priv.$container.appendChild(popover);
@@ -58474,10 +58863,11 @@ function repaint_cards(gobj, ids) {
58474
58863
  continue;
58475
58864
  }
58476
58865
  let anchored = priv.anchor_state === "on" && id === priv.anchor_id;
58477
- if (nd && nd.data && nd.data.desc && node_is_compact(gobj, id)) {
58866
+ let mode = nd && nd.data && nd.data.desc ? node_mode_of(gobj, id) : "expanded";
58867
+ if (mode === "shape") {
58478
58868
  updates.push({
58479
58869
  id,
58480
- style: compact_shape_of(gobj, nd.data.desc, nd.data.record || {}, {
58870
+ style: figure_shape_of(gobj, nd.data.desc, nd.data.record || {}, nd.data.graph_props || {}, {
58481
58871
  highlight: focus.has(id),
58482
58872
  selected: selected.has(id),
58483
58873
  anchored
@@ -58485,7 +58875,7 @@ function repaint_cards(gobj, ids) {
58485
58875
  });
58486
58876
  continue;
58487
58877
  }
58488
- let html = node_innerHTML_of(nd, priv.theme, focus.has(id), selected.has(id), anchored, pills_html_of(gobj, nd));
58878
+ let html = mode === "compact" ? pill_innerHTML_of(gobj, nd, priv.theme, focus.has(id), selected.has(id), anchored) : node_innerHTML_of(nd, priv.theme, focus.has(id), selected.has(id), anchored, pills_html_of(gobj, nd));
58489
58879
  if (html !== null) updates.push({
58490
58880
  id,
58491
58881
  style: { innerHTML: html }
@@ -58523,10 +58913,11 @@ function refresh_html_nodes_theme(gobj, theme) {
58523
58913
  let id = nodes[i].id;
58524
58914
  let nd = graph.getNodeData(id);
58525
58915
  let anchored = priv.anchor_state === "on" && id === priv.anchor_id;
58526
- if (nd && nd.data && nd.data.desc && node_is_compact(gobj, id)) {
58916
+ let mode = nd && nd.data && nd.data.desc ? node_mode_of(gobj, id) : "expanded";
58917
+ if (mode === "shape") {
58527
58918
  updates.push({
58528
58919
  id,
58529
- style: compact_shape_of(gobj, nd.data.desc, nd.data.record || {}, {
58920
+ style: figure_shape_of(gobj, nd.data.desc, nd.data.record || {}, nd.data.graph_props || {}, {
58530
58921
  highlight: highlighted.has(id),
58531
58922
  selected: selected.has(id),
58532
58923
  anchored
@@ -58534,7 +58925,7 @@ function refresh_html_nodes_theme(gobj, theme) {
58534
58925
  });
58535
58926
  continue;
58536
58927
  }
58537
- let html = node_innerHTML_of(nd, theme, highlighted.has(id), selected.has(id), anchored, pills_html_of(gobj, nd));
58928
+ let html = mode === "compact" ? pill_innerHTML_of(gobj, nd, theme, highlighted.has(id), selected.has(id), anchored) : node_innerHTML_of(nd, theme, highlighted.has(id), selected.has(id), anchored, pills_html_of(gobj, nd));
58538
58929
  if (html === null) html = more_innerHTML_of(gobj, nd, theme);
58539
58930
  if (html !== null) updates.push({
58540
58931
  id,
@@ -58992,7 +59383,7 @@ function hide_unlink_confirm(gobj) {
58992
59383
  * Apply node properties to one or more nodes.
58993
59384
  * scope: 'this', 'same_topic', 'all'
58994
59385
  ************************************************************/
58995
- function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
59386
+ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope, shape) {
58996
59387
  let priv = gobj.priv;
58997
59388
  let graph = priv.graph;
58998
59389
  let nodeData = graph.getNodeData(node_id);
@@ -59000,11 +59391,26 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
59000
59391
  let source_topic = nodeData.data.desc ? nodeData.data.desc.topic_name : null;
59001
59392
  let updates = [];
59002
59393
  const nodes = graph.getData().nodes;
59394
+ let figure = shape && str_in_list(NODE_SHAPES, shape) ? shape : "";
59395
+ let reshaped = [];
59396
+ if (figure && scope !== "this") {
59397
+ let topics = scope === "same_topic" ? [source_topic] : Object.keys(priv.descs || {});
59398
+ for (let topic_name of topics) {
59399
+ if (!is_object(priv._graph_properties[topic_name])) priv._graph_properties[topic_name] = {};
59400
+ let defaults = priv._graph_properties[topic_name].defaults || {};
59401
+ defaults.node_shape = figure;
59402
+ priv._graph_properties[topic_name].defaults = defaults;
59403
+ }
59404
+ }
59003
59405
  for (let i = 0; i < nodes.length; i++) {
59004
59406
  let nd = graph.getNodeData(nodes[i].id);
59005
59407
  if (!nd || !nd.data || !nd.data.desc) continue;
59006
59408
  if (scope === "this" && nodes[i].id !== node_id) continue;
59007
59409
  if (scope === "same_topic" && nd.data.desc.topic_name !== source_topic) continue;
59410
+ if (figure) {
59411
+ node_props_entry(gobj, nd).node_shape = figure;
59412
+ if (node_mode_of(gobj, nodes[i].id) === "shape") reshaped.push(nodes[i].id);
59413
+ }
59008
59414
  let updateStyle = {
59009
59415
  fill,
59010
59416
  stroke,
@@ -59021,6 +59427,7 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
59021
59427
  }
59022
59428
  if (updates.length > 0) {
59023
59429
  graph.updateNodeData(updates);
59430
+ if (reshaped.length) reshape_nodes(gobj, reshaped);
59024
59431
  graph.draw().then(() => {
59025
59432
  mark_graph_dirty(gobj);
59026
59433
  });
@@ -59028,6 +59435,23 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
59028
59435
  hide_node_popover(gobj);
59029
59436
  }
59030
59437
  /************************************************************
59438
+ * The node's entry in `_graph_properties` -- the object Save
59439
+ * reads and writes -- created if it has none yet, and the node's
59440
+ * `data.graph_props` pointed at it, so a key written here is
59441
+ * read by the next reshape and survives the next Save.
59442
+ ************************************************************/
59443
+ function node_props_entry(gobj, nd) {
59444
+ let priv = gobj.priv;
59445
+ let topic_name = nd.data.topic_name;
59446
+ let record = nd.data.record || {};
59447
+ if (!is_object(priv._graph_properties[topic_name])) priv._graph_properties[topic_name] = {};
59448
+ let topic_props = priv._graph_properties[topic_name];
59449
+ if (!is_object(topic_props.nodes)) topic_props.nodes = {};
59450
+ if (!is_object(topic_props.nodes[record.id])) topic_props.nodes[record.id] = is_object(nd.data.graph_props) ? nd.data.graph_props : {};
59451
+ nd.data.graph_props = topic_props.nodes[record.id];
59452
+ return nd.data.graph_props;
59453
+ }
59454
+ /************************************************************
59031
59455
  * Apply edge properties to one or more edges.
59032
59456
  * scope: 'this', 'same_type', 'all'
59033
59457
  ************************************************************/
@@ -59155,8 +59579,8 @@ function ctx_item(icon_id, label, value) {
59155
59579
  function build_node_context_menu(gobj, node_id) {
59156
59580
  let items = [];
59157
59581
  inject_svg_icons();
59158
- if (node_is_compact(gobj, node_id)) items.push(ctx_item("g6-icon-fullscreen", t("open node"), "toggle_node_mode"));
59159
- else items.push(ctx_item("g6-icon-fullscreen-exit", t("close node"), "toggle_node_mode"));
59582
+ if (node_is_compact(gobj, node_id)) items.push(ctx_item("g6-icon-fullscreen", t("expand node"), "toggle_node_mode"));
59583
+ else items.push(ctx_item("g6-icon-fullscreen-exit", t("collapse node"), "toggle_node_mode"));
59160
59584
  if (gobj.priv.edit_mode && !node_is_compact(gobj, node_id)) {
59161
59585
  items.push(ctx_item("g6-icon-resize", t("resize all"), "resize_all_nodes"));
59162
59586
  items.push(ctx_item("g6-icon-resize", t("resize topic nodes"), "resize_topic_nodes"));
@@ -59172,6 +59596,7 @@ function build_port_context_menu(gobj, node_id, port_key) {
59172
59596
  let items = [];
59173
59597
  if (gobj.priv.edit_mode) {
59174
59598
  inject_svg_icons();
59599
+ items.push(ctx_item("g6-icon-edit", t("port properties"), "port_properties"));
59175
59600
  items.push(ctx_item("g6-icon-resize", t("resize all ports"), "resize_all_ports"));
59176
59601
  items.push(ctx_item("g6-icon-resize", t("resize topic ports"), "resize_topic_ports"));
59177
59602
  }
@@ -59204,6 +59629,12 @@ function handle_context_menu_click(gobj, value) {
59204
59629
  case "reset_topic_sizes":
59205
59630
  reset_sizes(gobj, true);
59206
59631
  break;
59632
+ case "port_properties":
59633
+ if (priv._context_node_id && priv._context_port_key) {
59634
+ select_port(gobj, priv._context_node_id, priv._context_port_key);
59635
+ show_port_popover(gobj);
59636
+ }
59637
+ break;
59207
59638
  case "resize_all_ports":
59208
59639
  copy_size_to_ports(gobj, false);
59209
59640
  break;
@@ -59247,7 +59678,7 @@ function copy_size_to_nodes(gobj, same_topic_only) {
59247
59678
  if (nodes[i].id === node_id) continue;
59248
59679
  let updateStyle = { size: [...source_size] };
59249
59680
  if (source_portR != null) updateStyle.portR = source_portR;
59250
- if (nd.type === "html") {
59681
+ if (is_card_type(nd.type)) {
59251
59682
  updateStyle.dx = -source_size[0] / 2;
59252
59683
  updateStyle.dy = -(source_size.length > 1 ? source_size[1] : source_size[0]) / 2;
59253
59684
  }
@@ -59388,7 +59819,7 @@ var MORE_CHIP_SIZE_COMPACT = [40, 22];
59388
59819
  function more_chip_is_compact(gobj, nd) {
59389
59820
  let parent_id = nd && nd.data ? nd.data.parent_node : "";
59390
59821
  if (parent_id) return node_is_compact(gobj, parent_id);
59391
- return gobj_read_str_attr(gobj, "node_mode") === "compact";
59822
+ return gobj_read_str_attr(gobj, "node_mode") !== "expanded";
59392
59823
  }
59393
59824
  function more_chip_style(gobj, nd, color, rest) {
59394
59825
  let compact = more_chip_is_compact(gobj, nd);
@@ -59847,6 +60278,7 @@ async function reconcile_fold_apply(gobj, opts, visible) {
59847
60278
  update_edge_icon_position(gobj);
59848
60279
  update_node_icon_position(gobj);
59849
60280
  update_link_icon_position(gobj);
60281
+ update_port_icon_position(gobj);
59850
60282
  return changed;
59851
60283
  }
59852
60284
  /************************************************************
@@ -60438,9 +60870,9 @@ function ac_set_loose_topics(gobj, event, kw, src) {
60438
60870
  * and lays the graph out again, because a square and a card
60439
60871
  * do not occupy the same room.
60440
60872
  ************************************************************/
60441
- function ac_set_node_mode(gobj, event, kw, src) {
60873
+ function ac_set_node_mode$1(gobj, event, kw, src) {
60442
60874
  let priv = gobj.priv;
60443
- let mode = kw && kw.node_mode === "compact" ? "compact" : "expanded";
60875
+ let mode = kw && kw.node_mode && str_in_list(NODE_MODES$1, kw.node_mode) ? kw.node_mode : "expanded";
60444
60876
  gobj_write_str_attr(gobj, "node_mode", mode);
60445
60877
  priv._open_nodes = /* @__PURE__ */ new Set();
60446
60878
  if (!priv.graph || !priv._fold_model) return 0;
@@ -60453,15 +60885,15 @@ function ac_set_node_mode(gobj, event, kw, src) {
60453
60885
  return 0;
60454
60886
  }
60455
60887
  /************************************************************
60456
- * The name under a closed square, on or off. Only the closed
60457
- * nodes change, and nothing moves: the label hangs outside the
60458
- * square and the layout measures the square.
60888
+ * The name under a figure, on or off. Only the `shape` nodes
60889
+ * change, and nothing moves: the label hangs outside the figure
60890
+ * and the layout measures the figure.
60459
60891
  ************************************************************/
60460
60892
  function ac_set_node_labels(gobj, event, kw, src) {
60461
60893
  let priv = gobj.priv;
60462
60894
  gobj_write_bool_attr(gobj, "node_labels", !!(kw && kw.node_labels));
60463
60895
  if (!priv.graph) return 0;
60464
- reshape_nodes(gobj, all_record_node_ids(gobj).filter((id) => node_is_compact(gobj, id)));
60896
+ reshape_nodes(gobj, all_record_node_ids(gobj).filter((id) => node_mode_of(gobj, id) === "shape"));
60465
60897
  return 0;
60466
60898
  }
60467
60899
  /************************************************************
@@ -60469,7 +60901,7 @@ function ac_set_node_labels(gobj, event, kw, src) {
60469
60901
  * or the context menu). It holds still on screen while the
60470
60902
  * rest of the graph makes room, as a fold does.
60471
60903
  ************************************************************/
60472
- function ac_toggle_node_mode$1(gobj, event, kw, src) {
60904
+ function ac_toggle_node_mode(gobj, event, kw, src) {
60473
60905
  let priv = gobj.priv;
60474
60906
  let node_id = kw && kw.id || "";
60475
60907
  if (!priv.graph || !node_id) {
@@ -60998,7 +61430,7 @@ function create_gclass$4(gclass_name) {
60998
61430
  ],
60999
61431
  [
61000
61432
  "EV_SET_NODE_MODE",
61001
- ac_set_node_mode,
61433
+ ac_set_node_mode$1,
61002
61434
  null
61003
61435
  ],
61004
61436
  [
@@ -61008,7 +61440,7 @@ function create_gclass$4(gclass_name) {
61008
61440
  ],
61009
61441
  [
61010
61442
  "EV_TOGGLE_NODE_MODE",
61011
- ac_toggle_node_mode$1,
61443
+ ac_toggle_node_mode,
61012
61444
  null
61013
61445
  ],
61014
61446
  [
@@ -61189,8 +61621,8 @@ var attrs_table$3 = [
61189
61621
  SDATA(data_type_t.DTP_LIST, "hidden_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics hidden from the graph (legend). User preference, per treedb"),
61190
61622
  SDATA(data_type_t.DTP_STRING, "main_topic", sdata_flag_t.SDF_PERSIST, "", "The topic the tree hangs from; empty = deduced by the graph. User preference, per treedb"),
61191
61623
  SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
61192
- SDATA(data_type_t.DTP_STRING, "node_mode", sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their ports, `compact` = coloured squares, the topology alone. User preference"),
61193
- SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A compact node says its name under its square. User preference"),
61624
+ SDATA(data_type_t.DTP_STRING, "node_mode", sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their pills and ports, `compact` = one-line pills with the name, `shape` = coloured figures, the topology alone. User preference"),
61625
+ SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
61194
61626
  SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
61195
61627
  SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
61196
61628
  SDATA(data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
@@ -61474,44 +61906,92 @@ function option_label(name) {
61474
61906
  * its context menu (the child's business).
61475
61907
  ************************************************************/
61476
61908
  function node_shape_items(gobj, wide) {
61477
- let toggle = (cls, icon, event_name, label_key) => {
61478
- return [
61909
+ let options = NODE_MODES.map((mode) => [
61910
+ "option",
61911
+ {
61912
+ value: mode,
61913
+ "data-i18n": NODE_MODE_LABELS[mode]
61914
+ },
61915
+ t(NODE_MODE_LABELS[mode])
61916
+ ]);
61917
+ return [
61918
+ [
61919
+ "span",
61920
+ {
61921
+ class: "GRAPH_NODE_MODE_LABEL is-hidden-mobile",
61922
+ style: "padding-left:10px; padding-right:5px;",
61923
+ i18n: "nodes"
61924
+ },
61925
+ "nodes"
61926
+ ],
61927
+ [
61928
+ "div",
61929
+ { class: "select" },
61930
+ [[
61931
+ "select",
61932
+ {
61933
+ class: "GRAPH_NODE_MODE_SELECT",
61934
+ title: t("nodes"),
61935
+ "data-i18n-title": "nodes",
61936
+ "aria-label": t("nodes"),
61937
+ "data-i18n-aria-label": "nodes"
61938
+ },
61939
+ options
61940
+ ]],
61941
+ { change: (evt) => {
61942
+ evt.stopPropagation();
61943
+ gobj_send_event(gobj, "EV_SET_NODE_MODE", { node_mode: evt.target.value }, gobj);
61944
+ } }
61945
+ ],
61946
+ [
61479
61947
  "button",
61480
61948
  {
61481
- class: `${cls} button`,
61949
+ class: "GRAPH_NODE_LABELS button",
61482
61950
  type: "button",
61483
61951
  style: {
61484
61952
  height: wide,
61485
61953
  width: "2.5em"
61486
61954
  },
61487
- title: t(label_key),
61488
- "data-i18n-title": label_key,
61489
- "aria-label": t(label_key),
61490
- "data-i18n-aria-label": label_key,
61955
+ title: t("node labels"),
61956
+ "data-i18n-title": "node labels",
61957
+ "aria-label": t("node labels"),
61958
+ "data-i18n-aria-label": "node labels",
61491
61959
  "aria-pressed": "false"
61492
61960
  },
61493
61961
  ["i", {
61494
61962
  style: "font-size:1.5em; color:inherit;",
61495
- class: icon
61963
+ class: "yi-font"
61496
61964
  }],
61497
61965
  { click: (evt) => {
61498
61966
  evt.stopPropagation();
61499
- gobj_send_event(gobj, event_name, {}, gobj);
61967
+ gobj_send_event(gobj, "EV_TOGGLE_NODE_LABELS", {}, gobj);
61500
61968
  } }
61501
- ];
61502
- };
61503
- return [toggle("GRAPH_NODE_MODE", "yi-hexagon-nodes", "EV_TOGGLE_NODE_MODE", "closed nodes"), toggle("GRAPH_NODE_LABELS", "yi-font", "EV_TOGGLE_NODE_LABELS", "node labels")];
61969
+ ]
61970
+ ];
61504
61971
  }
61972
+ var NODE_MODES = [
61973
+ "expanded",
61974
+ "compact",
61975
+ "shape"
61976
+ ];
61977
+ var NODE_MODE_LABELS = {
61978
+ expanded: "full",
61979
+ compact: "compact",
61980
+ shape: "shape"
61981
+ };
61505
61982
  function refresh_node_shape_buttons(gobj, $root) {
61506
61983
  if (!$root) return;
61507
- let compact = gobj_read_str_attr(gobj, "node_mode") === "compact";
61984
+ let mode = gobj_read_str_attr(gobj, "node_mode");
61985
+ if (!NODE_MODES.includes(mode)) mode = "expanded";
61508
61986
  let labels = gobj_read_bool_attr(gobj, "node_labels");
61509
- set_pressed_state($root, ".GRAPH_NODE_MODE", compact);
61987
+ let $mode = $root.querySelector(".GRAPH_NODE_MODE_SELECT");
61988
+ if ($mode) $mode.value = mode;
61510
61989
  set_pressed_state($root, ".GRAPH_NODE_LABELS", labels);
61511
- let $mode = $root.querySelector(".GRAPH_NODE_MODE");
61512
- if ($mode) $mode.setAttribute("aria-pressed", compact ? "true" : "false");
61513
61990
  let $labels = $root.querySelector(".GRAPH_NODE_LABELS");
61514
- if ($labels) $labels.setAttribute("aria-pressed", labels ? "true" : "false");
61991
+ if ($labels) {
61992
+ $labels.setAttribute("aria-pressed", labels ? "true" : "false");
61993
+ $labels.disabled = mode !== "shape";
61994
+ }
61515
61995
  }
61516
61996
  function make_toolbar(gobj) {
61517
61997
  let priv = gobj.priv;
@@ -62898,13 +63378,13 @@ function ac_collapse_all(gobj, event, kw, src) {
62898
63378
  return 0;
62899
63379
  }
62900
63380
  /************************************************************
62901
- * The shape pair of the toolbar: the attr flips, persists,
62902
- * shows on the button, and reaches the G6 child, which owns
62903
- * the nodes.
63381
+ * The view of the nodes and the labels toggle of the toolbar:
63382
+ * the attr changes, persists, shows on the control, and reaches
63383
+ * the G6 child, which owns the nodes.
62904
63384
  ************************************************************/
62905
- function ac_toggle_node_mode(gobj, event, kw, src) {
63385
+ function ac_set_node_mode(gobj, event, kw, src) {
62906
63386
  let priv = gobj.priv;
62907
- let mode = gobj_read_str_attr(gobj, "node_mode") === "compact" ? "expanded" : "compact";
63387
+ let mode = kw && NODE_MODES.includes(kw.node_mode) ? kw.node_mode : "expanded";
62908
63388
  gobj_write_str_attr(gobj, "node_mode", mode);
62909
63389
  gobj_save_persistent_attrs(gobj, "node_mode");
62910
63390
  refresh_node_shape_buttons(gobj, priv.$container);
@@ -63135,8 +63615,8 @@ function create_gclass$3(gclass_name) {
63135
63615
  null
63136
63616
  ],
63137
63617
  [
63138
- "EV_TOGGLE_NODE_MODE",
63139
- ac_toggle_node_mode,
63618
+ "EV_SET_NODE_MODE",
63619
+ ac_set_node_mode,
63140
63620
  null
63141
63621
  ],
63142
63622
  [
@@ -63208,7 +63688,7 @@ function create_gclass$3(gclass_name) {
63208
63688
  ["EV_FIND_NODES", 0],
63209
63689
  ["EV_EXPAND_ALL", 0],
63210
63690
  ["EV_COLLAPSE_ALL", 0],
63211
- ["EV_TOGGLE_NODE_MODE", 0],
63691
+ ["EV_SET_NODE_MODE", 0],
63212
63692
  ["EV_TOGGLE_NODE_LABELS", 0],
63213
63693
  ["EV_FIND_RESULT", 0],
63214
63694
  ["EV_LAYOUT_AUTOSET", 0],