@yuneta/gobj-ui 7.23.20 → 7.23.21

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.
@@ -9474,6 +9474,23 @@ function getStrokeColor(fillColor, theme = "light", factor = .2) {
9474
9474
  const adjust = (v) => lighten ? Math.min(255, Math.floor(v + (255 - v) * factor)) : Math.max(0, Math.floor(v * (1 - factor)));
9475
9475
  return `rgba(${adjust(r)}, ${adjust(g)}, ${adjust(b)}, ${a.toFixed(3)})`;
9476
9476
  }
9477
+ /************************************************************
9478
+ * Returns the proportional position (between 0 and 1) of a specific point,
9479
+ * centered and spaced with margins.
9480
+ *
9481
+ * index - Index of the point (0 to count-1)
9482
+ * count - Total number of points
9483
+ * margin - Total margin space (default 0.2 means 10% on each end)
9484
+ ************************************************************/
9485
+ function getPointPosition(count, index, margin = .2) {
9486
+ if (count <= 0 || index < 0 || index >= count) {
9487
+ (0, _yuneta_gobj_js.log_error)("Invalid count or index");
9488
+ return .5;
9489
+ }
9490
+ const start = margin / 2;
9491
+ const step = (1 - margin / 2 - start) / count;
9492
+ return start + index * step + step / 2;
9493
+ }
9477
9494
  //#endregion
9478
9495
  //#region src/yui_graph_camera.js
9479
9496
  /***********************************************************************
@@ -10942,6 +10959,16 @@ function build_graph$3(gobj) {
10942
10959
  }
10943
10960
  }
10944
10961
  /************************************************************
10962
+ * The port a container key opens on its card.
10963
+ *
10964
+ * Prefixed so it can never collide with anything G6 puts in
10965
+ * the same namespace, and stringified because an array index
10966
+ * is a number and a port key is a string.
10967
+ ************************************************************/
10968
+ function port_key(key) {
10969
+ return "p_" + String(key);
10970
+ }
10971
+ /************************************************************
10945
10972
  * Build cell value HTML (matching old mx_json_viewer colors)
10946
10973
  ************************************************************/
10947
10974
  function build_cell_html(key, value, type, dark, matched, fold) {
@@ -11084,7 +11111,7 @@ function get_group_label(path) {
11084
11111
  /************************************************************
11085
11112
  * Recursively build nodes and edges from JSON
11086
11113
  ************************************************************/
11087
- function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11114
+ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port) {
11088
11115
  let group_id = gen_node_id$1(gobj);
11089
11116
  let is_dict = (0, _yuneta_gobj_js.is_object)(kw);
11090
11117
  let is_list = (0, _yuneta_gobj_js.is_array)(kw);
@@ -11108,7 +11135,8 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11108
11135
  if (has_branch(value)) {
11109
11136
  pending_complex.push({
11110
11137
  key,
11111
- value
11138
+ value,
11139
+ port: port_key(key)
11112
11140
  });
11113
11141
  if (is_pure_collection(value)) {
11114
11142
  let child_path = add_segment(path, key);
@@ -11124,7 +11152,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11124
11152
  }
11125
11153
  if (parent_id && is_pure_collection(kw)) {
11126
11154
  if (priv.collapsed.has(path)) return parent_id;
11127
- for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, parent_id);
11155
+ for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, parent_id, parent_port);
11128
11156
  return parent_id;
11129
11157
  }
11130
11158
  let label = get_group_label(path) || (is_dict ? "{}" : "[]");
@@ -11155,6 +11183,17 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11155
11183
  ">${FOLD_SPACER}<span style="flex:1 1 auto; text-align:center;">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span>${fold_toggle_html(path, foldable, folded, pending_complex.length, colors)}</div>
11156
11184
  ${content_html}
11157
11185
  </div>`;
11186
+ let ports = [];
11187
+ for (let i = 0; i < pending_complex.length; i++) {
11188
+ let pending = pending_complex[i];
11189
+ let child_colors = json_card_style((0, _yuneta_gobj_js.is_object)(pending.value) ? GROUP_COLORS.dict : GROUP_COLORS.list, dark);
11190
+ ports.push({
11191
+ key: pending.port,
11192
+ placement: [getPointPosition(pending_complex.length, i), 1],
11193
+ fill: child_colors.border,
11194
+ stroke: colors.border
11195
+ });
11196
+ }
11158
11197
  let node = {
11159
11198
  id: group_id,
11160
11199
  type: "html",
@@ -11166,20 +11205,28 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11166
11205
  innerHTML: node_html,
11167
11206
  size: [min_width, 24 + lines.length * 22],
11168
11207
  dx: -(min_width / 2),
11169
- dy: -((24 + lines.length * 22) / 2)
11208
+ dy: -((24 + lines.length * 22) / 2),
11209
+ port: ports.length > 0,
11210
+ ports,
11211
+ portR: 4,
11212
+ portLineWidth: 1
11170
11213
  }
11171
11214
  };
11172
11215
  nodes.push(node);
11173
- if (parent_id) edges.push({
11174
- source: parent_id,
11175
- target: group_id,
11176
- style: {
11177
- stroke: colors.border,
11178
- lineWidth: 1
11179
- }
11180
- });
11216
+ if (parent_id) {
11217
+ let edge = {
11218
+ source: parent_id,
11219
+ target: group_id,
11220
+ style: {
11221
+ stroke: colors.border,
11222
+ lineWidth: 1
11223
+ }
11224
+ };
11225
+ if (parent_port) edge.style.sourcePort = parent_port;
11226
+ edges.push(edge);
11227
+ }
11181
11228
  if (folded) return group_id;
11182
- for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, group_id);
11229
+ for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, group_id, pending.port);
11183
11230
  return group_id;
11184
11231
  }
11185
11232
  /************************************************************
@@ -49032,43 +49079,6 @@ function configure_behaviour(gobj) {
49032
49079
  graph_write_behaviors(gobj, behaviors);
49033
49080
  }
49034
49081
  /************************************************************
49035
- * Returns the proportional position (between 0 and 1) of a specific point,
49036
- * centered and spaced with margins.
49037
- *
49038
- * index - Index of the point (0 to count-1)
49039
- * count - Total number of points
49040
- * margin - Total margin space (default 0.2 means 10% on each end)
49041
- ************************************************************/
49042
- function getPointPosition(count, index, margin = .2) {
49043
- if (count <= 0 || index < 0 || index >= count) {
49044
- (0, _yuneta_gobj_js.log_error)("Invalid count or index");
49045
- return .5;
49046
- }
49047
- const start = margin / 2;
49048
- const step = (1 - margin / 2 - start) / count;
49049
- return start + index * step + step / 2;
49050
- }
49051
- /************************************************************
49052
- * Count hooks and fkeys in topic desc, classify node type
49053
- ************************************************************/
49054
- function calculate_hooks_fkeys_counter(desc) {
49055
- let cols = desc.cols;
49056
- desc.hooks_counter = 0;
49057
- desc.fkeys_counter = 0;
49058
- for (let i = 0; i < cols.length; i++) {
49059
- let col = cols[i];
49060
- switch ((0, _yuneta_gobj_js.treedb_get_field_desc)(col).type) {
49061
- case "hook":
49062
- desc.hooks_counter++;
49063
- break;
49064
- case "fkey": desc.fkeys_counter++;
49065
- }
49066
- }
49067
- if (desc.hooks_counter === 0) desc.node_treedb_type = "child";
49068
- else if (desc.fkeys_counter === 0) desc.node_treedb_type = "extended";
49069
- else desc.node_treedb_type = "hierarchical";
49070
- }
49071
- /************************************************************
49072
49082
  * Build ports for a topic desc
49073
49083
  ************************************************************/
49074
49084
  function build_ports(gobj, desc) {
@@ -9346,6 +9346,14 @@ function yui_toolbar(attrs = {}, items = []) {
9346
9346
  }
9347
9347
  //#endregion
9348
9348
  //#region src/lib_graph.js
9349
+ /***********************************************************************
9350
+ * lib_graph.js
9351
+ *
9352
+ * Utilities for graph, using Bulma
9353
+ *
9354
+ * Copyright (c) 2025, ArtGins.
9355
+ * All Rights Reserved.
9356
+ ***********************************************************************/
9349
9357
  /************************************************************
9350
9358
  * Add class to elements selected
9351
9359
  ************************************************************/
@@ -9461,6 +9469,23 @@ function getStrokeColor(fillColor, theme = "light", factor = .2) {
9461
9469
  const adjust = (v) => lighten ? Math.min(255, Math.floor(v + (255 - v) * factor)) : Math.max(0, Math.floor(v * (1 - factor)));
9462
9470
  return `rgba(${adjust(r)}, ${adjust(g)}, ${adjust(b)}, ${a.toFixed(3)})`;
9463
9471
  }
9472
+ /************************************************************
9473
+ * Returns the proportional position (between 0 and 1) of a specific point,
9474
+ * centered and spaced with margins.
9475
+ *
9476
+ * index - Index of the point (0 to count-1)
9477
+ * count - Total number of points
9478
+ * margin - Total margin space (default 0.2 means 10% on each end)
9479
+ ************************************************************/
9480
+ function getPointPosition(count, index, margin = .2) {
9481
+ if (count <= 0 || index < 0 || index >= count) {
9482
+ log_error("Invalid count or index");
9483
+ return .5;
9484
+ }
9485
+ const start = margin / 2;
9486
+ const step = (1 - margin / 2 - start) / count;
9487
+ return start + index * step + step / 2;
9488
+ }
9464
9489
  //#endregion
9465
9490
  //#region src/yui_graph_camera.js
9466
9491
  /***********************************************************************
@@ -10929,6 +10954,16 @@ function build_graph$3(gobj) {
10929
10954
  }
10930
10955
  }
10931
10956
  /************************************************************
10957
+ * The port a container key opens on its card.
10958
+ *
10959
+ * Prefixed so it can never collide with anything G6 puts in
10960
+ * the same namespace, and stringified because an array index
10961
+ * is a number and a port key is a string.
10962
+ ************************************************************/
10963
+ function port_key(key) {
10964
+ return "p_" + String(key);
10965
+ }
10966
+ /************************************************************
10932
10967
  * Build cell value HTML (matching old mx_json_viewer colors)
10933
10968
  ************************************************************/
10934
10969
  function build_cell_html(key, value, type, dark, matched, fold) {
@@ -11071,7 +11106,7 @@ function get_group_label(path) {
11071
11106
  /************************************************************
11072
11107
  * Recursively build nodes and edges from JSON
11073
11108
  ************************************************************/
11074
- function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11109
+ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port) {
11075
11110
  let group_id = gen_node_id$1(gobj);
11076
11111
  let is_dict = is_object(kw);
11077
11112
  let is_list = is_array(kw);
@@ -11095,7 +11130,8 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11095
11130
  if (has_branch(value)) {
11096
11131
  pending_complex.push({
11097
11132
  key,
11098
- value
11133
+ value,
11134
+ port: port_key(key)
11099
11135
  });
11100
11136
  if (is_pure_collection(value)) {
11101
11137
  let child_path = add_segment(path, key);
@@ -11111,7 +11147,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11111
11147
  }
11112
11148
  if (parent_id && is_pure_collection(kw)) {
11113
11149
  if (priv.collapsed.has(path)) return parent_id;
11114
- for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, parent_id);
11150
+ for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, parent_id, parent_port);
11115
11151
  return parent_id;
11116
11152
  }
11117
11153
  let label = get_group_label(path) || (is_dict ? "{}" : "[]");
@@ -11142,6 +11178,17 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11142
11178
  ">${FOLD_SPACER}<span style="flex:1 1 auto; text-align:center;">${escapeHtml(label)}</span>${fold_toggle_html(path, foldable, folded, pending_complex.length, colors)}</div>
11143
11179
  ${content_html}
11144
11180
  </div>`;
11181
+ let ports = [];
11182
+ for (let i = 0; i < pending_complex.length; i++) {
11183
+ let pending = pending_complex[i];
11184
+ let child_colors = json_card_style(is_object(pending.value) ? GROUP_COLORS.dict : GROUP_COLORS.list, dark);
11185
+ ports.push({
11186
+ key: pending.port,
11187
+ placement: [getPointPosition(pending_complex.length, i), 1],
11188
+ fill: child_colors.border,
11189
+ stroke: colors.border
11190
+ });
11191
+ }
11145
11192
  let node = {
11146
11193
  id: group_id,
11147
11194
  type: "html",
@@ -11153,20 +11200,28 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
11153
11200
  innerHTML: node_html,
11154
11201
  size: [min_width, 24 + lines.length * 22],
11155
11202
  dx: -(min_width / 2),
11156
- dy: -((24 + lines.length * 22) / 2)
11203
+ dy: -((24 + lines.length * 22) / 2),
11204
+ port: ports.length > 0,
11205
+ ports,
11206
+ portR: 4,
11207
+ portLineWidth: 1
11157
11208
  }
11158
11209
  };
11159
11210
  nodes.push(node);
11160
- if (parent_id) edges.push({
11161
- source: parent_id,
11162
- target: group_id,
11163
- style: {
11164
- stroke: colors.border,
11165
- lineWidth: 1
11166
- }
11167
- });
11211
+ if (parent_id) {
11212
+ let edge = {
11213
+ source: parent_id,
11214
+ target: group_id,
11215
+ style: {
11216
+ stroke: colors.border,
11217
+ lineWidth: 1
11218
+ }
11219
+ };
11220
+ if (parent_port) edge.style.sourcePort = parent_port;
11221
+ edges.push(edge);
11222
+ }
11168
11223
  if (folded) return group_id;
11169
- for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, group_id);
11224
+ for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, group_id, pending.port);
11170
11225
  return group_id;
11171
11226
  }
11172
11227
  /************************************************************
@@ -49019,43 +49074,6 @@ function configure_behaviour(gobj) {
49019
49074
  graph_write_behaviors(gobj, behaviors);
49020
49075
  }
49021
49076
  /************************************************************
49022
- * Returns the proportional position (between 0 and 1) of a specific point,
49023
- * centered and spaced with margins.
49024
- *
49025
- * index - Index of the point (0 to count-1)
49026
- * count - Total number of points
49027
- * margin - Total margin space (default 0.2 means 10% on each end)
49028
- ************************************************************/
49029
- function getPointPosition(count, index, margin = .2) {
49030
- if (count <= 0 || index < 0 || index >= count) {
49031
- log_error("Invalid count or index");
49032
- return .5;
49033
- }
49034
- const start = margin / 2;
49035
- const step = (1 - margin / 2 - start) / count;
49036
- return start + index * step + step / 2;
49037
- }
49038
- /************************************************************
49039
- * Count hooks and fkeys in topic desc, classify node type
49040
- ************************************************************/
49041
- function calculate_hooks_fkeys_counter(desc) {
49042
- let cols = desc.cols;
49043
- desc.hooks_counter = 0;
49044
- desc.fkeys_counter = 0;
49045
- for (let i = 0; i < cols.length; i++) {
49046
- let col = cols[i];
49047
- switch (treedb_get_field_desc(col).type) {
49048
- case "hook":
49049
- desc.hooks_counter++;
49050
- break;
49051
- case "fkey": desc.fkeys_counter++;
49052
- }
49053
- }
49054
- if (desc.hooks_counter === 0) desc.node_treedb_type = "child";
49055
- else if (desc.fkeys_counter === 0) desc.node_treedb_type = "extended";
49056
- else desc.node_treedb_type = "hierarchical";
49057
- }
49058
- /************************************************************
49059
49077
  * Build ports for a topic desc
49060
49078
  ************************************************************/
49061
49079
  function build_ports(gobj, desc) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.20",
3
+ "version": "7.23.21",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -80,6 +80,7 @@ import {
80
80
  set_cancel_state,
81
81
  set_active_state,
82
82
  set_pressed_state,
83
+ getPointPosition,
83
84
  getStrokeColor,
84
85
  } from "./lib_graph.js";
85
86
 
@@ -1598,59 +1599,6 @@ function configure_behaviour(gobj)
1598
1599
  graph_write_behaviors(gobj, behaviors);
1599
1600
  }
1600
1601
 
1601
- /************************************************************
1602
- * Returns the proportional position (between 0 and 1) of a specific point,
1603
- * centered and spaced with margins.
1604
- *
1605
- * index - Index of the point (0 to count-1)
1606
- * count - Total number of points
1607
- * margin - Total margin space (default 0.2 means 10% on each end)
1608
- ************************************************************/
1609
- function getPointPosition(count, index, margin = 0.2)
1610
- {
1611
- if(count <= 0 || index < 0 || index >= count) {
1612
- log_error("Invalid count or index");
1613
- return 0.5;
1614
- }
1615
-
1616
- const start = margin / 2;
1617
- const end = 1 - margin / 2;
1618
- const step = (end - start) / count;
1619
-
1620
- return start + index * step + step / 2;
1621
- }
1622
-
1623
- /************************************************************
1624
- * Count hooks and fkeys in topic desc, classify node type
1625
- ************************************************************/
1626
- function calculate_hooks_fkeys_counter(desc)
1627
- {
1628
- let cols = desc.cols;
1629
- desc.hooks_counter = 0;
1630
- desc.fkeys_counter = 0;
1631
-
1632
- for(let i=0; i<cols.length; i++) {
1633
- let col = cols[i];
1634
- const field_desc = treedb_get_field_desc(col);
1635
- switch(field_desc.type) {
1636
- case "hook":
1637
- desc.hooks_counter++;
1638
- break;
1639
- case "fkey":
1640
- desc.fkeys_counter++;
1641
- break;
1642
- }
1643
- }
1644
-
1645
- if(desc.hooks_counter === 0) {
1646
- desc.node_treedb_type = 'child';
1647
- } else if(desc.fkeys_counter === 0) {
1648
- desc.node_treedb_type = 'extended';
1649
- } else {
1650
- desc.node_treedb_type = 'hierarchical';
1651
- }
1652
- }
1653
-
1654
1602
  /************************************************************
1655
1603
  * Build ports for a topic desc
1656
1604
  ************************************************************/
@@ -72,6 +72,7 @@ import {
72
72
  import { ensure_drag_canvas_patch } from "./g6_drag_canvas_touch.js";
73
73
  import { ensure_pinch_zoom_patch } from "./g6_touch_gestures.js";
74
74
  import { yui_is_dark, yui_theme_now, yui_watch_theme } from "./yui_theme.js";
75
+ import { getPointPosition } from "./lib_graph.js";
75
76
  import {
76
77
  is_pure_collection,
77
78
  has_branch,
@@ -772,6 +773,18 @@ function build_graph(gobj)
772
773
  }
773
774
  }
774
775
 
776
+ /************************************************************
777
+ * The port a container key opens on its card.
778
+ *
779
+ * Prefixed so it can never collide with anything G6 puts in
780
+ * the same namespace, and stringified because an array index
781
+ * is a number and a port key is a string.
782
+ ************************************************************/
783
+ function port_key(key)
784
+ {
785
+ return "p_" + String(key);
786
+ }
787
+
775
788
  /************************************************************
776
789
  * Build cell value HTML (matching old mx_json_viewer colors)
777
790
  ************************************************************/
@@ -981,7 +994,7 @@ function get_group_label(path)
981
994
  /************************************************************
982
995
  * Recursively build nodes and edges from JSON
983
996
  ************************************************************/
984
- function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
997
+ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
985
998
  {
986
999
  let group_id = gen_node_id(gobj);
987
1000
  let is_dict = is_object(kw);
@@ -1031,7 +1044,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
1031
1044
  */
1032
1045
  let fold = null;
1033
1046
  if(has_branch(value)) {
1034
- pending_complex.push({key: key, value: value});
1047
+ pending_complex.push({key: key, value: value, port: port_key(key)});
1035
1048
 
1036
1049
  /*
1037
1050
  * A child that gets no card of its own is folded from HERE
@@ -1062,13 +1075,21 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
1062
1075
  return parent_id;
1063
1076
  }
1064
1077
  for(let pending of pending_complex) {
1078
+ /*
1079
+ * `parent_port` and not one of ours: this collection has no
1080
+ * card, so the port its row opened in the PARENT is the one
1081
+ * every one of these lines leaves from -- fourteen columns
1082
+ * out of the single `cols` port, which is what the document
1083
+ * says and what the reader traces back.
1084
+ */
1065
1085
  build_json_nodes(
1066
1086
  gobj,
1067
1087
  add_segment(path, pending.key),
1068
1088
  pending.value,
1069
1089
  nodes,
1070
1090
  edges,
1071
- parent_id
1091
+ parent_id,
1092
+ parent_port
1072
1093
  );
1073
1094
  }
1074
1095
  return parent_id;
@@ -1120,6 +1141,38 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
1120
1141
  ${content_html}
1121
1142
  </div>`;
1122
1143
 
1144
+ /*
1145
+ * ONE PORT PER CONTAINER KEY, and every line leaves from its own.
1146
+ *
1147
+ * Without them each card was a single anchor: fourteen lines came
1148
+ * out of one point, and which row a line belonged to was a guess
1149
+ * the reader had to make from where it landed. A port makes the row
1150
+ * the ORIGIN, which is the same thing the treedb graph does with a
1151
+ * hook column and the reason `getPointPosition` is now shared.
1152
+ *
1153
+ * On the BOTTOM edge because the layouts put children below, and
1154
+ * exactly ON it (`y = 1`) rather than inside: an html node draws its
1155
+ * HTML in a DOM layer above the canvas, so a port fully inside the
1156
+ * box would be painted under the card and never seen. Half of it
1157
+ * sticks out.
1158
+ *
1159
+ * Coloured by what the row LEADS TO -- a dict or a list -- which is
1160
+ * the one thing the port can say that the row does not.
1161
+ */
1162
+ let ports = [];
1163
+ for(let i = 0; i < pending_complex.length; i++) {
1164
+ let pending = pending_complex[i];
1165
+ let child_colors = json_card_style(
1166
+ is_object(pending.value)? GROUP_COLORS.dict: GROUP_COLORS.list, dark
1167
+ );
1168
+ ports.push({
1169
+ key: pending.port,
1170
+ placement: [getPointPosition(pending_complex.length, i), 1],
1171
+ fill: child_colors.border,
1172
+ stroke: colors.border,
1173
+ });
1174
+ }
1175
+
1123
1176
  let node = {
1124
1177
  id: group_id,
1125
1178
  type: 'html',
@@ -1132,22 +1185,30 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
1132
1185
  size: [min_width, 24 + lines.length * 22],
1133
1186
  dx: -(min_width / 2),
1134
1187
  dy: -((24 + lines.length * 22) / 2),
1188
+ port: ports.length > 0,
1189
+ ports: ports,
1190
+ portR: 4,
1191
+ portLineWidth: 1,
1135
1192
  },
1136
1193
  };
1137
1194
  nodes.push(node);
1138
1195
 
1139
1196
  /*
1140
- * Edge from parent to this group
1197
+ * Edge from parent to this group, out of the port its key opened.
1141
1198
  */
1142
1199
  if(parent_id) {
1143
- edges.push({
1200
+ let edge = {
1144
1201
  source: parent_id,
1145
1202
  target: group_id,
1146
1203
  style: {
1147
1204
  stroke: colors.border,
1148
1205
  lineWidth: 1,
1149
1206
  }
1150
- });
1207
+ };
1208
+ if(parent_port) {
1209
+ edge.style.sourcePort = parent_port;
1210
+ }
1211
+ edges.push(edge);
1151
1212
  }
1152
1213
 
1153
1214
  /*
@@ -1163,7 +1224,8 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
1163
1224
  pending.value,
1164
1225
  nodes,
1165
1226
  edges,
1166
- group_id
1227
+ group_id,
1228
+ pending.port
1167
1229
  );
1168
1230
  }
1169
1231
 
package/src/lib_graph.js CHANGED
@@ -8,6 +8,7 @@
8
8
  ***********************************************************************/
9
9
  import {
10
10
  gobj_read_attr,
11
+ log_error,
11
12
  } from "@yuneta/gobj-js";
12
13
 
13
14
  // import {t} from "i18next";
@@ -195,6 +196,59 @@ function getStrokeColor(fillColor, theme = 'light', factor = 0.2) {
195
196
  //=======================================================================
196
197
  // Expose the class via the global object
197
198
  //=======================================================================
199
+ /************************************************************
200
+ * Returns the proportional position (between 0 and 1) of a specific point,
201
+ * centered and spaced with margins.
202
+ *
203
+ * index - Index of the point (0 to count-1)
204
+ * count - Total number of points
205
+ * margin - Total margin space (default 0.2 means 10% on each end)
206
+ ************************************************************/
207
+ function getPointPosition(count, index, margin = 0.2)
208
+ {
209
+ if(count <= 0 || index < 0 || index >= count) {
210
+ log_error("Invalid count or index");
211
+ return 0.5;
212
+ }
213
+
214
+ const start = margin / 2;
215
+ const end = 1 - margin / 2;
216
+ const step = (end - start) / count;
217
+
218
+ return start + index * step + step / 2;
219
+ }
220
+
221
+ /************************************************************
222
+ * Count hooks and fkeys in topic desc, classify node type
223
+ ************************************************************/
224
+ function calculate_hooks_fkeys_counter(desc)
225
+ {
226
+ let cols = desc.cols;
227
+ desc.hooks_counter = 0;
228
+ desc.fkeys_counter = 0;
229
+
230
+ for(let i=0; i<cols.length; i++) {
231
+ let col = cols[i];
232
+ const field_desc = treedb_get_field_desc(col);
233
+ switch(field_desc.type) {
234
+ case "hook":
235
+ desc.hooks_counter++;
236
+ break;
237
+ case "fkey":
238
+ desc.fkeys_counter++;
239
+ break;
240
+ }
241
+ }
242
+
243
+ if(desc.hooks_counter === 0) {
244
+ desc.node_treedb_type = 'child';
245
+ } else if(desc.fkeys_counter === 0) {
246
+ desc.node_treedb_type = 'extended';
247
+ } else {
248
+ desc.node_treedb_type = 'hierarchical';
249
+ }
250
+ }
251
+
198
252
  export {
199
253
  addClasses,
200
254
  removeClasses,
@@ -207,4 +261,5 @@ export {
207
261
  set_active_state,
208
262
  set_pressed_state,
209
263
  getStrokeColor,
264
+ getPointPosition,
210
265
  };