@yuneta/gobj-ui 7.23.80 → 7.23.81

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.
@@ -137,6 +137,7 @@ import {
137
137
  HistoryEvent,
138
138
  EdgeEvent,
139
139
  Circle,
140
+ HTML,
140
141
  Toolbar,
141
142
  register,
142
143
  } from '@antv/g6';
@@ -446,6 +447,8 @@ let PRIVATE_DATA = {
446
447
  _edge_icon_el: null, // floating properties icon element
447
448
  _edge_delete_el: null, // floating delete icon element for edge
448
449
  _edge_popover_el: null, // edge properties popover element
450
+ _port_icon_el: null, // floating port properties icon element
451
+ _port_popover_el: null, // port properties popover element
449
452
  _node_icon_el: null, // floating node properties icon element
450
453
  _node_delete_el: null, // floating delete icon element for node
451
454
  _node_popover_el: null, // node properties popover element
@@ -839,6 +842,7 @@ function register_layouts(gobj)
839
842
  register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
840
843
  register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
841
844
  register(ExtensionCategory.NODE, 'light', LightNode);
845
+ register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
842
846
  }
843
847
  }
844
848
 
@@ -940,6 +944,7 @@ function configure_events(gobj)
940
944
  update_edge_icon_position(gobj);
941
945
  update_node_icon_position(gobj);
942
946
  update_link_icon_position(gobj);
947
+ update_port_icon_position(gobj);
943
948
  });
944
949
 
945
950
  graph.on(NodeEvent.DRAG_END, (evt) => {
@@ -974,6 +979,7 @@ function configure_events(gobj)
974
979
  update_edge_icon_position(gobj);
975
980
  update_node_icon_position(gobj);
976
981
  update_link_icon_position(gobj);
982
+ update_port_icon_position(gobj);
977
983
  update_zoom_readout(gobj);
978
984
 
979
985
  /*
@@ -2132,12 +2138,32 @@ function card_shape_of(gobj, desc, record, geometry, pills_html)
2132
2138
  }
2133
2139
  }
2134
2140
  }
2141
+
2142
+ /* And the SHAPE of each port, the same way: the node's own,
2143
+ * then the topic's per-port default, then the topic's one
2144
+ * shape for every port (`all ports` in the port popover). */
2145
+ let port_shapes = geometry.port_shapes;
2146
+ let default_port_shapes = topic_defaults ? topic_defaults.port_shapes : null;
2147
+ let default_shape = topic_defaults ? topic_defaults.port_shape : null;
2148
+ for(let i = 0; i < style.ports.length; i++) {
2149
+ let key = style.ports[i].key;
2150
+ let shape = is_object(port_shapes) ? port_shapes[key] : null;
2151
+ if(!shape && is_object(default_port_shapes)) {
2152
+ shape = default_port_shapes[key];
2153
+ }
2154
+ if(!shape) {
2155
+ shape = default_shape;
2156
+ }
2157
+ if(shape && str_in_list(PORT_SHAPES, shape)) {
2158
+ style.ports[i].shape = shape;
2159
+ }
2160
+ }
2135
2161
  } else {
2136
2162
  style.port = false;
2137
2163
  style.ports = [];
2138
2164
  }
2139
2165
 
2140
- return {type: 'html', style: style};
2166
+ return {type: 'treedb-card', style: style};
2141
2167
  }
2142
2168
 
2143
2169
  /************************************************************
@@ -2776,6 +2802,18 @@ function update_geometry(gobj, node_id)
2776
2802
  } else {
2777
2803
  delete node_props.port_sizes;
2778
2804
  }
2805
+ // And the shape of each port that is not a circle
2806
+ let port_shapes = {};
2807
+ for(let i = 0; i < ports.length; i++) {
2808
+ if(ports[i].shape && ports[i].shape !== 'circle') {
2809
+ port_shapes[ports[i].key] = ports[i].shape;
2810
+ }
2811
+ }
2812
+ if(Object.keys(port_shapes).length > 0) {
2813
+ node_props.port_shapes = port_shapes;
2814
+ } else {
2815
+ delete node_props.port_shapes;
2816
+ }
2779
2817
  }
2780
2818
 
2781
2819
  topic_props.nodes[record.id] = node_props;
@@ -3485,6 +3523,7 @@ function graph_resize(gobj, width, height)
3485
3523
  update_port_resize_handles_position(gobj);
3486
3524
  update_node_icon_position(gobj);
3487
3525
  update_link_icon_position(gobj);
3526
+ update_port_icon_position(gobj);
3488
3527
  }
3489
3528
 
3490
3529
  function graph_write_behaviors(gobj, behaviors)
@@ -3520,6 +3559,64 @@ function graph_set_behavior(gobj, behavior, set)
3520
3559
  });
3521
3560
  }
3522
3561
 
3562
+ /************************************************************
3563
+ * Custom G6 node: TreedbCard -- G6's html node with ports that
3564
+ * have a SHAPE.
3565
+ *
3566
+ * G6 draws every port as a circle (`drawPortShapes` upserts a
3567
+ * `Circle`, nothing in the port style says otherwise). A port's
3568
+ * `shape` -- `circle`, `square`, `diamond`, `triangle` -- picks
3569
+ * the G shape here, by the names G6 registers itself, and `r`
3570
+ * stays the one size: the half-side of the square, the half-
3571
+ * diagonal of the diamond, the half-height of the triangle. The
3572
+ * hit test, the resize handles and the edge's landing point all
3573
+ * read `r` and none of them care about the outline.
3574
+ ************************************************************/
3575
+ const PORT_SHAPES = ['circle', 'square', 'diamond', 'triangle'];
3576
+
3577
+ class TreedbCard extends HTML
3578
+ {
3579
+ drawPortShapes(attributes, container) {
3580
+ const portsStyle = this.getPortsStyle(attributes);
3581
+ Object.keys(portsStyle).forEach((key) => {
3582
+ const style = portsStyle[key];
3583
+ const shapeKey = `port-${key}`;
3584
+ if(style === false) {
3585
+ this.upsert(shapeKey, 'circle', false, container);
3586
+ return;
3587
+ }
3588
+ let r = Number(style.r) || 0;
3589
+ switch(style.shape) {
3590
+ case 'square':
3591
+ this.upsert(shapeKey, 'rect', Object.assign({}, style, {
3592
+ x: -r, y: -r, width: 2 * r, height: 2 * r,
3593
+ }), container);
3594
+ break;
3595
+ case 'diamond':
3596
+ this.upsert(shapeKey, 'polygon', Object.assign({}, style, {
3597
+ points: [[0, -r], [r, 0], [0, r], [-r, 0]],
3598
+ }), container);
3599
+ break;
3600
+ case 'triangle':
3601
+ this.upsert(shapeKey, 'polygon', Object.assign({}, style, {
3602
+ points: [[0, -r], [r, r], [-r, r]],
3603
+ }), container);
3604
+ break;
3605
+ default:
3606
+ this.upsert(shapeKey, 'circle', style, container);
3607
+ break;
3608
+ }
3609
+ });
3610
+ }
3611
+ }
3612
+
3613
+ /* A record's card is `treedb-card`; `html` is what it was until
3614
+ * 7.23.81 and what a `+N` chip still is. */
3615
+ function is_card_type(type)
3616
+ {
3617
+ return type === 'treedb-card' || type === 'html';
3618
+ }
3619
+
3523
3620
  /************************************************************
3524
3621
  * Custom G6 node: LightNode (circle with status indicator)
3525
3622
  ************************************************************/
@@ -4661,7 +4758,7 @@ function start_node_resize(gobj, e, mx, my)
4661
4758
  }
4662
4759
 
4663
4760
  // HTML nodes: update dx, dy to keep content centered
4664
- if(nodeType === 'html') {
4761
+ if(is_card_type(nodeType)) {
4665
4762
  updateStyle.dx = -newW / 2;
4666
4763
  updateStyle.dy = -newH / 2;
4667
4764
  }
@@ -4842,6 +4939,7 @@ function select_port(gobj, node_id, port_key)
4842
4939
 
4843
4940
  show_port_resize_handles(gobj);
4844
4941
  show_link_icon_if_fkey(gobj);
4942
+ show_port_icon(gobj);
4845
4943
  }
4846
4944
 
4847
4945
  /************************************************************
@@ -4853,6 +4951,8 @@ function deselect_port(gobj)
4853
4951
 
4854
4952
  exit_linking_mode(gobj);
4855
4953
  hide_link_icon(gobj);
4954
+ hide_port_icon(gobj);
4955
+ hide_port_popover(gobj);
4856
4956
  hide_port_resize_handles(gobj);
4857
4957
  priv._selected_port_key = null;
4858
4958
  }
@@ -4940,6 +5040,272 @@ function update_link_icon_position(gobj)
4940
5040
  }
4941
5041
  }
4942
5042
 
5043
+ /************************************************************
5044
+ * Port properties icon and popover: the gear beside a selected
5045
+ * port, as the node has its own. The popover has the two things
5046
+ * a port IS on screen -- its shape and its radius -- and the
5047
+ * scope: this port, the same port of every card of the topic
5048
+ * (the hook or fkey column), or every port there is.
5049
+ ************************************************************/
5050
+ function port_icon_xy(gobj)
5051
+ {
5052
+ let priv = gobj.priv;
5053
+ let graph = priv.graph;
5054
+ let canvasPos = get_port_canvas_position(
5055
+ gobj, priv._selected_node_id, priv._selected_port_key
5056
+ );
5057
+ if(!canvasPos) {
5058
+ return null;
5059
+ }
5060
+ let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
5061
+ /* Under the link icon when there is one (an fkey), in its
5062
+ * place otherwise. */
5063
+ let below = priv._link_icon_el? floating_icon_step() : 0;
5064
+ return {
5065
+ x: vp[0] + floating_icon_size() / 2 + 4,
5066
+ y: vp[1] + floating_icon_dy() + below,
5067
+ };
5068
+ }
5069
+
5070
+ function show_port_icon(gobj)
5071
+ {
5072
+ hide_port_icon(gobj);
5073
+
5074
+ let priv = gobj.priv;
5075
+ if(!priv._selected_node_id || !priv._selected_port_key) {
5076
+ return;
5077
+ }
5078
+ let xy = port_icon_xy(gobj);
5079
+ if(!xy) {
5080
+ return;
5081
+ }
5082
+ let icon = create_floating_icon(
5083
+ 'gear', '#1890ff', xy.x, xy.y,
5084
+ t('port properties'), () => toggle_port_popover(gobj)
5085
+ );
5086
+ priv.$container.appendChild(icon);
5087
+ priv._port_icon_el = icon;
5088
+ }
5089
+
5090
+ function hide_port_icon(gobj)
5091
+ {
5092
+ hide_overlay(gobj, '_port_icon_el');
5093
+ }
5094
+
5095
+ function update_port_icon_position(gobj)
5096
+ {
5097
+ let priv = gobj.priv;
5098
+ if(!priv._port_icon_el || !priv._selected_node_id || !priv._selected_port_key) {
5099
+ return;
5100
+ }
5101
+ try {
5102
+ let xy = port_icon_xy(gobj);
5103
+ if(!xy) {
5104
+ hide_port_icon(gobj);
5105
+ hide_port_popover(gobj);
5106
+ return;
5107
+ }
5108
+ priv._port_icon_el.style.left = xy.x + 'px';
5109
+ priv._port_icon_el.style.top = xy.y + 'px';
5110
+ if(priv._port_popover_el) {
5111
+ priv._port_popover_el.style.left = (xy.x + floating_popover_dx()) + 'px';
5112
+ priv._port_popover_el.style.top = xy.y + 'px';
5113
+ clamp_popover_position(gobj, priv._port_popover_el);
5114
+ }
5115
+ } catch(e) {
5116
+ hide_port_icon(gobj);
5117
+ hide_port_popover(gobj);
5118
+ }
5119
+ }
5120
+
5121
+ function toggle_port_popover(gobj)
5122
+ {
5123
+ let priv = gobj.priv;
5124
+ if(priv._port_popover_el) {
5125
+ hide_port_popover(gobj);
5126
+ } else {
5127
+ show_port_popover(gobj);
5128
+ }
5129
+ }
5130
+
5131
+ function hide_port_popover(gobj)
5132
+ {
5133
+ hide_overlay(gobj, '_port_popover_el');
5134
+ }
5135
+
5136
+ /* The ports of a node with ONE of them rewritten. */
5137
+ function ports_with(ports, port_key, changes)
5138
+ {
5139
+ return (ports || []).map((p) => (p.key === port_key)? Object.assign({}, p, changes) : p);
5140
+ }
5141
+
5142
+ function show_port_popover(gobj)
5143
+ {
5144
+ hide_port_popover(gobj);
5145
+
5146
+ let priv = gobj.priv;
5147
+ let graph = priv.graph;
5148
+ let node_id = priv._selected_node_id;
5149
+ let port_key = priv._selected_port_key;
5150
+ if(!node_id || !port_key) {
5151
+ return;
5152
+ }
5153
+ let nodeData = graph.getNodeData(node_id);
5154
+ if(!nodeData || !nodeData.style) {
5155
+ return;
5156
+ }
5157
+ let ports = nodeData.style.ports || [];
5158
+ let port = ports.find((p) => p.key === port_key);
5159
+ if(!port) {
5160
+ return;
5161
+ }
5162
+ let orig_shape = port.shape || 'circle';
5163
+ let orig_r = get_port_radius(gobj, node_id, port_key);
5164
+
5165
+ let xy = port_icon_xy(gobj);
5166
+ if(!xy) {
5167
+ return;
5168
+ }
5169
+ const popover = create_popover_base(
5170
+ xy.x + floating_popover_dx(), xy.y, 'g6-port-popover', '#d9d9d9', 180
5171
+ );
5172
+
5173
+ /* Live preview on THIS port; the others wait for apply. */
5174
+ function preview_port() {
5175
+ let r = parseInt(rInput.value) || orig_r;
5176
+ graph.updateNodeData([{id: node_id, style: {
5177
+ ports: ports_with(nodeData.style.ports, port_key, {shape: shapeSelect.value, r: r}),
5178
+ }}]);
5179
+ graph.draw().then(() => {
5180
+ update_port_resize_handles_position(gobj);
5181
+ });
5182
+ }
5183
+
5184
+ create_form_label(popover, 'shape');
5185
+ let shapeSelect = create_form_select(popover, PORT_SHAPES.map((shape) => {
5186
+ return {value: shape, label: t(shape)};
5187
+ }));
5188
+ shapeSelect.value = orig_shape;
5189
+ shapeSelect.addEventListener('change', preview_port);
5190
+
5191
+ create_form_label(popover, 'radius');
5192
+ let rInput = create_form_number_input(popover, orig_r, 2, 40, preview_port);
5193
+
5194
+ create_form_label(popover, 'apply to');
5195
+ let scopeSelect = create_form_select(popover, [
5196
+ {value: 'this', label: t('this port')},
5197
+ {value: 'same_topic', label: t('same port in topic')},
5198
+ {value: 'all', label: t('all ports')},
5199
+ ]);
5200
+
5201
+ create_form_button_row(popover, [
5202
+ {
5203
+ text: 'cancel',
5204
+ style: BTN_STYLE_CANCEL,
5205
+ onClick: () => {
5206
+ graph.updateNodeData([{id: node_id, style: {
5207
+ ports: ports_with(nodeData.style.ports, port_key, {shape: orig_shape, r: orig_r}),
5208
+ }}]);
5209
+ graph.draw().then(() => {
5210
+ update_port_resize_handles_position(gobj);
5211
+ });
5212
+ hide_port_popover(gobj);
5213
+ },
5214
+ },
5215
+ {
5216
+ text: 'apply',
5217
+ style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
5218
+ 'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
5219
+ onClick: () => {
5220
+ apply_port_properties(gobj, node_id, port_key,
5221
+ shapeSelect.value, parseInt(rInput.value) || orig_r, scopeSelect.value
5222
+ );
5223
+ },
5224
+ },
5225
+ ]);
5226
+
5227
+ priv.$container.appendChild(popover);
5228
+ priv._port_popover_el = popover;
5229
+ clamp_popover_position(gobj, popover);
5230
+ }
5231
+
5232
+ /************************************************************
5233
+ * Apply a port's shape and radius to one port, to the same port
5234
+ * of every card of the topic, or to every port -- and remember
5235
+ * the choice as the topic's default, the way `resize all ports`
5236
+ * does, so a card that arrives later is born with it.
5237
+ * scope: 'this', 'same_topic', 'all'
5238
+ ************************************************************/
5239
+ function apply_port_properties(gobj, node_id, port_key, shape, r, scope)
5240
+ {
5241
+ let priv = gobj.priv;
5242
+ let graph = priv.graph;
5243
+
5244
+ let nodeData = graph.getNodeData(node_id);
5245
+ if(!nodeData || !nodeData.data) {
5246
+ return;
5247
+ }
5248
+ let source_topic = nodeData.data.topic_name;
5249
+
5250
+ if(scope === 'same_topic') {
5251
+ if(!is_object(priv._graph_properties[source_topic])) {
5252
+ priv._graph_properties[source_topic] = {};
5253
+ }
5254
+ let defaults = priv._graph_properties[source_topic].defaults || {};
5255
+ defaults.port_sizes = Object.assign({}, defaults.port_sizes, {[port_key]: r});
5256
+ defaults.port_shapes = Object.assign({}, defaults.port_shapes, {[port_key]: shape});
5257
+ priv._graph_properties[source_topic].defaults = defaults;
5258
+ } else if(scope === 'all') {
5259
+ for(const topic_name of Object.keys(priv.descs || {})) {
5260
+ if(!is_object(priv._graph_properties[topic_name])) {
5261
+ priv._graph_properties[topic_name] = {};
5262
+ }
5263
+ let defaults = priv._graph_properties[topic_name].defaults || {};
5264
+ defaults.portR = r;
5265
+ defaults.port_shape = shape;
5266
+ delete defaults.port_sizes;
5267
+ delete defaults.port_shapes;
5268
+ priv._graph_properties[topic_name].defaults = defaults;
5269
+ }
5270
+ }
5271
+
5272
+ let updates = [];
5273
+ for(let nd of (graph.getNodeData() || [])) {
5274
+ if(!nd || !nd.data || !nd.data.desc || !nd.style || !is_array(nd.style.ports)) {
5275
+ continue;
5276
+ }
5277
+ let new_ports;
5278
+ if(scope === 'this') {
5279
+ if(nd.id !== node_id) {
5280
+ continue;
5281
+ }
5282
+ new_ports = ports_with(nd.style.ports, port_key, {shape: shape, r: r});
5283
+ } else if(scope === 'same_topic') {
5284
+ if(nd.data.topic_name !== source_topic) {
5285
+ continue;
5286
+ }
5287
+ new_ports = ports_with(nd.style.ports, port_key, {shape: shape, r: r});
5288
+ } else {
5289
+ new_ports = nd.style.ports.map((p) => Object.assign({}, p, {shape: shape, r: r}));
5290
+ }
5291
+ let upd = {ports: new_ports};
5292
+ if(scope === 'all') {
5293
+ upd.portR = r;
5294
+ }
5295
+ updates.push({id: nd.id, style: upd});
5296
+ }
5297
+
5298
+ if(updates.length > 0) {
5299
+ graph.updateNodeData(updates);
5300
+ graph.draw().then(() => {
5301
+ update_port_resize_handles_position(gobj);
5302
+ update_port_icon_position(gobj);
5303
+ mark_graph_dirty(gobj);
5304
+ });
5305
+ }
5306
+ hide_port_popover(gobj);
5307
+ }
5308
+
4943
5309
  /************************************************************
4944
5310
  * Get a column descriptor by field id from a topic desc.
4945
5311
  ************************************************************/
@@ -7453,6 +7819,9 @@ function build_port_context_menu(gobj, node_id, port_key)
7453
7819
 
7454
7820
  if(gobj.priv.edit_mode) {
7455
7821
  inject_svg_icons();
7822
+ /* The popover, for a finger: the gear beside the port needs
7823
+ * the port selected first, and a long press is the menu. */
7824
+ items.push(ctx_item('g6-icon-edit', t('port properties'), 'port_properties'));
7456
7825
  items.push(ctx_item('g6-icon-resize', t('resize all ports'), 'resize_all_ports'));
7457
7826
  items.push(ctx_item('g6-icon-resize', t('resize topic ports'), 'resize_topic_ports'));
7458
7827
  }
@@ -7495,6 +7864,12 @@ function handle_context_menu_click(gobj, value)
7495
7864
  case 'reset_topic_sizes':
7496
7865
  reset_sizes(gobj, true);
7497
7866
  break;
7867
+ case 'port_properties':
7868
+ if(priv._context_node_id && priv._context_port_key) {
7869
+ select_port(gobj, priv._context_node_id, priv._context_port_key);
7870
+ show_port_popover(gobj);
7871
+ }
7872
+ break;
7498
7873
  case 'resize_all_ports':
7499
7874
  copy_size_to_ports(gobj, false);
7500
7875
  break;
@@ -7581,7 +7956,7 @@ function copy_size_to_nodes(gobj, same_topic_only)
7581
7956
  }
7582
7957
 
7583
7958
  // Recalculate dx/dy for HTML nodes
7584
- if(nd.type === 'html') {
7959
+ if(is_card_type(nd.type)) {
7585
7960
  updateStyle.dx = -source_size[0] / 2;
7586
7961
  let h = source_size.length > 1 ? source_size[1] : source_size[0];
7587
7962
  updateStyle.dy = -h / 2;
@@ -8418,6 +8793,7 @@ async function reconcile_fold_apply(gobj, opts, visible)
8418
8793
  update_edge_icon_position(gobj);
8419
8794
  update_node_icon_position(gobj);
8420
8795
  update_link_icon_position(gobj);
8796
+ update_port_icon_position(gobj);
8421
8797
 
8422
8798
  return changed;
8423
8799
  }
package/src/lib_graph.css CHANGED
@@ -86,6 +86,7 @@
86
86
  .graph-container .g6-create-popover,
87
87
  .graph-container .g6-node-popover,
88
88
  .graph-container .g6-edge-popover,
89
+ .graph-container .g6-port-popover,
89
90
  .graph-container .g6-node-detail {
90
91
  -webkit-user-select: text;
91
92
  user-select: text;
@@ -99,6 +100,7 @@
99
100
  .graph-container .g6-create-popover,
100
101
  .graph-container .g6-node-popover,
101
102
  .graph-container .g6-edge-popover,
103
+ .graph-container .g6-port-popover,
102
104
  .graph-container .g6-node-detail,
103
105
  .graph-container .g6-contextmenu {
104
106
  touch-action: auto;