@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.
- package/README.md +62 -27
- package/dist/gobj-ui.cjs.js +590 -110
- package/dist/gobj-ui.es.js +591 -111
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +632 -74
- package/src/c_yui_treedb_graph.js +58 -30
- package/src/lib_graph.css +2 -0
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -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';
|
|
@@ -262,16 +263,34 @@ const PORT_R_CHILD = 8;
|
|
|
262
263
|
const PORT_LINE_WIDTH = 2;
|
|
263
264
|
|
|
264
265
|
/*
|
|
265
|
-
* The
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
266
|
+
* The three VIEWS of a record (`node_mode`), the same three the
|
|
267
|
+
* gobj tree view has: `expanded` is the card with its pills and
|
|
268
|
+
* its ports; `compact` is a one-line pill with the name inside
|
|
269
|
+
* and small ports, the half-way house; `shape` is a figure of the
|
|
270
|
+
* topic's colour -- a square, a circle, a diamond... chosen in the
|
|
271
|
+
* node's properties -- no ports, no text, topology and nothing
|
|
272
|
+
* else. The three tiers keep their order of size in every view,
|
|
273
|
+
* so a root is still bigger than a leaf when nothing says which
|
|
274
|
+
* is which.
|
|
269
275
|
*/
|
|
270
|
-
const
|
|
276
|
+
const NODE_MODES = ['expanded', 'compact', 'shape'];
|
|
277
|
+
|
|
278
|
+
const PILL_SIZE = {
|
|
279
|
+
hierarchical: [200, 30],
|
|
280
|
+
extended: [180, 28],
|
|
281
|
+
child: [160, 26],
|
|
282
|
+
};
|
|
283
|
+
const PORT_R_PILL = 6;
|
|
284
|
+
|
|
285
|
+
const SHAPE_SIZE = {
|
|
271
286
|
hierarchical: 32,
|
|
272
287
|
extended: 28,
|
|
273
288
|
child: 22,
|
|
274
289
|
};
|
|
290
|
+
/* The figures a `shape` node can be, by G6's own node types
|
|
291
|
+
* (`square` is G6's `rect` with rounded corners). */
|
|
292
|
+
const NODE_SHAPES = ['square', 'circle', 'diamond', 'triangle', 'hexagon', 'star'];
|
|
293
|
+
const DEFAULT_NODE_SHAPE = 'square';
|
|
275
294
|
|
|
276
295
|
/***************************************************************
|
|
277
296
|
* Internal layout and operation mode definitions
|
|
@@ -397,8 +416,8 @@ SDATA(data_type_t.DTP_STRING, "main_topic", 0, "", "The topic t
|
|
|
397
416
|
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"),
|
|
398
417
|
|
|
399
418
|
/*---------------- Node shape ----------------*/
|
|
400
|
-
SDATA(data_type_t.DTP_STRING, "node_mode", 0, "expanded", "How a record is drawn: `expanded` = the card with its ports
|
|
401
|
-
SDATA(data_type_t.DTP_BOOLEAN, "node_labels", 0, true, "A
|
|
419
|
+
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)"),
|
|
420
|
+
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"),
|
|
402
421
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
|
|
403
422
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
|
|
404
423
|
|
|
@@ -446,6 +465,8 @@ let PRIVATE_DATA = {
|
|
|
446
465
|
_edge_icon_el: null, // floating properties icon element
|
|
447
466
|
_edge_delete_el: null, // floating delete icon element for edge
|
|
448
467
|
_edge_popover_el: null, // edge properties popover element
|
|
468
|
+
_port_icon_el: null, // floating port properties icon element
|
|
469
|
+
_port_popover_el: null, // port properties popover element
|
|
449
470
|
_node_icon_el: null, // floating node properties icon element
|
|
450
471
|
_node_delete_el: null, // floating delete icon element for node
|
|
451
472
|
_node_popover_el: null, // node properties popover element
|
|
@@ -839,6 +860,7 @@ function register_layouts(gobj)
|
|
|
839
860
|
register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
|
|
840
861
|
register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
|
|
841
862
|
register(ExtensionCategory.NODE, 'light', LightNode);
|
|
863
|
+
register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
|
|
842
864
|
}
|
|
843
865
|
}
|
|
844
866
|
|
|
@@ -940,6 +962,7 @@ function configure_events(gobj)
|
|
|
940
962
|
update_edge_icon_position(gobj);
|
|
941
963
|
update_node_icon_position(gobj);
|
|
942
964
|
update_link_icon_position(gobj);
|
|
965
|
+
update_port_icon_position(gobj);
|
|
943
966
|
});
|
|
944
967
|
|
|
945
968
|
graph.on(NodeEvent.DRAG_END, (evt) => {
|
|
@@ -974,6 +997,7 @@ function configure_events(gobj)
|
|
|
974
997
|
update_edge_icon_position(gobj);
|
|
975
998
|
update_node_icon_position(gobj);
|
|
976
999
|
update_link_icon_position(gobj);
|
|
1000
|
+
update_port_icon_position(gobj);
|
|
977
1001
|
update_zoom_readout(gobj);
|
|
978
1002
|
|
|
979
1003
|
/*
|
|
@@ -2014,20 +2038,47 @@ function create_topic_node(gobj, desc, record)
|
|
|
2014
2038
|
************************************************************/
|
|
2015
2039
|
function node_shape_of(gobj, node_id, desc, record, geometry, pills_html)
|
|
2016
2040
|
{
|
|
2017
|
-
|
|
2018
|
-
|
|
2041
|
+
switch(node_mode_of(gobj, node_id)) {
|
|
2042
|
+
case 'shape':
|
|
2043
|
+
return figure_shape_of(gobj, desc, record, geometry, {});
|
|
2044
|
+
case 'compact':
|
|
2045
|
+
return pill_shape_of(gobj, desc, record, geometry);
|
|
2046
|
+
default:
|
|
2047
|
+
return card_shape_of(gobj, desc, record, geometry, pills_html);
|
|
2019
2048
|
}
|
|
2020
|
-
return card_shape_of(gobj, desc, record, geometry, pills_html);
|
|
2021
2049
|
}
|
|
2022
2050
|
|
|
2023
|
-
|
|
2051
|
+
/* The view of ONE node: the graph's `node_mode`, unless the node
|
|
2052
|
+
* was toggled against it -- an expanded node in a compact or shape
|
|
2053
|
+
* graph, a compact one in an expanded graph. */
|
|
2054
|
+
function node_mode_of(gobj, node_id)
|
|
2024
2055
|
{
|
|
2025
2056
|
let priv = gobj.priv;
|
|
2026
2057
|
if(!priv._open_nodes) {
|
|
2027
2058
|
priv._open_nodes = new Set();
|
|
2028
2059
|
}
|
|
2029
|
-
let
|
|
2030
|
-
|
|
2060
|
+
let mode = gobj_read_str_attr(gobj, "node_mode");
|
|
2061
|
+
if(!mode || !str_in_list(NODE_MODES, mode)) {
|
|
2062
|
+
mode = 'expanded';
|
|
2063
|
+
}
|
|
2064
|
+
if(priv._open_nodes.has(node_id)) {
|
|
2065
|
+
return (mode === 'expanded')? 'compact' : 'expanded';
|
|
2066
|
+
}
|
|
2067
|
+
return mode;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/* Not the full card: a pill or a figure. What the size bookkeeping
|
|
2071
|
+
* asks -- neither has a size anybody chose, or pills to grow for. */
|
|
2072
|
+
function node_is_compact(gobj, node_id)
|
|
2073
|
+
{
|
|
2074
|
+
return node_mode_of(gobj, node_id) !== 'expanded';
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
function topic_defaults_of(gobj, topic_name)
|
|
2078
|
+
{
|
|
2079
|
+
let topic_props = gobj.priv._graph_properties[topic_name];
|
|
2080
|
+
return (is_object(topic_props) && is_object(topic_props.defaults))?
|
|
2081
|
+
topic_props.defaults : null;
|
|
2031
2082
|
}
|
|
2032
2083
|
|
|
2033
2084
|
/************************************************************
|
|
@@ -2109,54 +2160,139 @@ function card_shape_of(gobj, desc, record, geometry, pills_html)
|
|
|
2109
2160
|
style.portR = geometry.portR;
|
|
2110
2161
|
}
|
|
2111
2162
|
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
port: true,
|
|
2115
|
-
ports: ports,
|
|
2116
|
-
portR: node_treedb_type === 'child' ? PORT_R_CHILD : PORT_R_ENTITY,
|
|
2117
|
-
portLineWidth: PORT_LINE_WIDTH,
|
|
2118
|
-
});
|
|
2163
|
+
dress_ports(gobj, style, ports, geometry, topic_defaults,
|
|
2164
|
+
node_treedb_type === 'child' ? PORT_R_CHILD : PORT_R_ENTITY, false);
|
|
2119
2165
|
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
} else {
|
|
2166
|
+
return {type: 'treedb-card', style: style};
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
/************************************************************
|
|
2170
|
+
* The ports of a card or a pill, dressed: the node-level radius
|
|
2171
|
+
* (saved, topic default, or the tier's), each port's own radius
|
|
2172
|
+
* and shape (saved first, then the topic's per-port defaults, then
|
|
2173
|
+
* the topic's one shape for every port). `fixed_r` -- the pill --
|
|
2174
|
+
* ignores every saved radius: a pill has one size of port.
|
|
2175
|
+
************************************************************/
|
|
2176
|
+
function dress_ports(gobj, style, ports, geometry, topic_defaults, r_default, fixed_r)
|
|
2177
|
+
{
|
|
2178
|
+
if(!json_size(ports)) {
|
|
2136
2179
|
style.port = false;
|
|
2137
2180
|
style.ports = [];
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
json_object_update_missing(style, {
|
|
2184
|
+
port: true,
|
|
2185
|
+
ports: ports,
|
|
2186
|
+
portR: r_default,
|
|
2187
|
+
portLineWidth: PORT_LINE_WIDTH,
|
|
2188
|
+
});
|
|
2189
|
+
if(fixed_r) {
|
|
2190
|
+
style.portR = r_default;
|
|
2138
2191
|
}
|
|
2139
2192
|
|
|
2140
|
-
|
|
2193
|
+
let port_sizes = fixed_r? null : geometry.port_sizes;
|
|
2194
|
+
let default_port_sizes = (!fixed_r && topic_defaults)? topic_defaults.port_sizes : null;
|
|
2195
|
+
let port_shapes = geometry.port_shapes;
|
|
2196
|
+
let default_port_shapes = topic_defaults? topic_defaults.port_shapes : null;
|
|
2197
|
+
let default_shape = topic_defaults? topic_defaults.port_shape : null;
|
|
2198
|
+
for(let i = 0; i < style.ports.length; i++) {
|
|
2199
|
+
let key = style.ports[i].key;
|
|
2200
|
+
let r = is_object(port_sizes)? port_sizes[key] : null;
|
|
2201
|
+
if(r == null && is_object(default_port_sizes)) {
|
|
2202
|
+
r = default_port_sizes[key];
|
|
2203
|
+
}
|
|
2204
|
+
if(r != null) {
|
|
2205
|
+
style.ports[i].r = r;
|
|
2206
|
+
}
|
|
2207
|
+
let shape = is_object(port_shapes)? port_shapes[key] : null;
|
|
2208
|
+
if(!shape && is_object(default_port_shapes)) {
|
|
2209
|
+
shape = default_port_shapes[key];
|
|
2210
|
+
}
|
|
2211
|
+
if(!shape) {
|
|
2212
|
+
shape = default_shape;
|
|
2213
|
+
}
|
|
2214
|
+
if(shape && str_in_list(PORT_SHAPES, shape)) {
|
|
2215
|
+
style.ports[i].shape = shape;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
/************************************************************
|
|
2221
|
+
* The PILL: one line with the name inside, the tint and the
|
|
2222
|
+
* border of the topic, small ports on its edges -- the half-way
|
|
2223
|
+
* house between the card and the figure, and the same one-line
|
|
2224
|
+
* chip a leaf record already is, at the tier's width. No pills:
|
|
2225
|
+
* a hook is opened from the card (expand the node).
|
|
2226
|
+
************************************************************/
|
|
2227
|
+
function pill_shape_of(gobj, desc, record, geometry)
|
|
2228
|
+
{
|
|
2229
|
+
let priv = gobj.priv;
|
|
2230
|
+
let tier = desc.node_treedb_type;
|
|
2231
|
+
let size = PILL_SIZE[tier] || PILL_SIZE.hierarchical;
|
|
2232
|
+
let ports = build_ports(gobj, desc);
|
|
2233
|
+
|
|
2234
|
+
let style = {
|
|
2235
|
+
fill: desc.color,
|
|
2236
|
+
stroke: getStrokeColor(desc.color),
|
|
2237
|
+
lineWidth: 1,
|
|
2238
|
+
lineDash: [],
|
|
2239
|
+
halo: false,
|
|
2240
|
+
labelText: "",
|
|
2241
|
+
size: [...size],
|
|
2242
|
+
dx: -size[0] / 2,
|
|
2243
|
+
dy: -size[1] / 2,
|
|
2244
|
+
innerHTML: build_chip_innerHTML(
|
|
2245
|
+
desc.color, priv.theme, record.icon, node_label(desc, record), record.id
|
|
2246
|
+
),
|
|
2247
|
+
};
|
|
2248
|
+
dress_ports(gobj, style, ports, geometry, topic_defaults_of(gobj, desc.topic_name),
|
|
2249
|
+
PORT_R_PILL, true);
|
|
2250
|
+
return {type: 'treedb-card', style: style};
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
/* The pill's html with the flags it carries right now. */
|
|
2254
|
+
function pill_innerHTML_of(gobj, nd, theme, highlight, selected, anchored)
|
|
2255
|
+
{
|
|
2256
|
+
let desc = nd.data.desc;
|
|
2257
|
+
let record = nd.data.record || {};
|
|
2258
|
+
return build_chip_innerHTML(
|
|
2259
|
+
desc.color, theme, record.icon, node_label(desc, record), record.id,
|
|
2260
|
+
highlight, selected, anchored
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
/* The figure a `shape` node wears: the node's own, then the
|
|
2265
|
+
* topic's default (the node properties popover writes both), then
|
|
2266
|
+
* a square. */
|
|
2267
|
+
function node_figure_of(gobj, desc, geometry)
|
|
2268
|
+
{
|
|
2269
|
+
let shape = geometry? geometry.node_shape : null;
|
|
2270
|
+
if(!shape) {
|
|
2271
|
+
let defaults = topic_defaults_of(gobj, desc.topic_name);
|
|
2272
|
+
shape = defaults? defaults.node_shape : null;
|
|
2273
|
+
}
|
|
2274
|
+
return (shape && str_in_list(NODE_SHAPES, shape))? shape : DEFAULT_NODE_SHAPE;
|
|
2141
2275
|
}
|
|
2142
2276
|
|
|
2143
2277
|
/************************************************************
|
|
2144
|
-
* The
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
2148
|
-
*
|
|
2278
|
+
* The FIGURE: a native G6 node of the topic's colour -- a square,
|
|
2279
|
+
* a circle, a diamond, a triangle, a hexagon, a star -- no ports,
|
|
2280
|
+
* the name as a label under it when `node_labels` says so. Native
|
|
2281
|
+
* and not html on purpose: G6's own stroke and halo paint on it,
|
|
2282
|
+
* so the focus, the selection and the anchor are three keys of
|
|
2283
|
+
* the style instead of a rebuilt innerHTML.
|
|
2149
2284
|
*
|
|
2150
2285
|
* flags {highlight, selected, anchored}
|
|
2151
2286
|
************************************************************/
|
|
2152
|
-
function
|
|
2287
|
+
function figure_shape_of(gobj, desc, record, geometry, flags)
|
|
2153
2288
|
{
|
|
2154
2289
|
let priv = gobj.priv;
|
|
2155
2290
|
let dark = (priv.theme === "dark");
|
|
2156
2291
|
let f = flags || {};
|
|
2157
2292
|
let tier = desc.node_treedb_type;
|
|
2158
|
-
let side =
|
|
2293
|
+
let side = SHAPE_SIZE[tier] || SHAPE_SIZE.hierarchical;
|
|
2159
2294
|
let labels = gobj_read_bool_attr(gobj, "node_labels");
|
|
2295
|
+
let figure = node_figure_of(gobj, desc, geometry || {});
|
|
2160
2296
|
|
|
2161
2297
|
let stroke = getStrokeColor(desc.color);
|
|
2162
2298
|
let line_width = 1.5;
|
|
@@ -2181,7 +2317,7 @@ function compact_shape_of(gobj, desc, record, flags)
|
|
|
2181
2317
|
|
|
2182
2318
|
let style = {
|
|
2183
2319
|
size: [side, side],
|
|
2184
|
-
radius: Math.round(side / 5),
|
|
2320
|
+
radius: (figure === 'square')? Math.round(side / 5) : 0,
|
|
2185
2321
|
fill: desc.color,
|
|
2186
2322
|
stroke: stroke,
|
|
2187
2323
|
lineWidth: line_width,
|
|
@@ -2205,7 +2341,7 @@ function compact_shape_of(gobj, desc, record, flags)
|
|
|
2205
2341
|
labelBackgroundRadius: 3,
|
|
2206
2342
|
labelPadding: [1, 3],
|
|
2207
2343
|
};
|
|
2208
|
-
return {type: 'rect', style: style};
|
|
2344
|
+
return {type: (figure === 'square')? 'rect' : figure, style: style};
|
|
2209
2345
|
}
|
|
2210
2346
|
|
|
2211
2347
|
/************************************************************
|
|
@@ -2776,6 +2912,18 @@ function update_geometry(gobj, node_id)
|
|
|
2776
2912
|
} else {
|
|
2777
2913
|
delete node_props.port_sizes;
|
|
2778
2914
|
}
|
|
2915
|
+
// And the shape of each port that is not a circle
|
|
2916
|
+
let port_shapes = {};
|
|
2917
|
+
for(let i = 0; i < ports.length; i++) {
|
|
2918
|
+
if(ports[i].shape && ports[i].shape !== 'circle') {
|
|
2919
|
+
port_shapes[ports[i].key] = ports[i].shape;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
if(Object.keys(port_shapes).length > 0) {
|
|
2923
|
+
node_props.port_shapes = port_shapes;
|
|
2924
|
+
} else {
|
|
2925
|
+
delete node_props.port_shapes;
|
|
2926
|
+
}
|
|
2779
2927
|
}
|
|
2780
2928
|
|
|
2781
2929
|
topic_props.nodes[record.id] = node_props;
|
|
@@ -3485,6 +3633,7 @@ function graph_resize(gobj, width, height)
|
|
|
3485
3633
|
update_port_resize_handles_position(gobj);
|
|
3486
3634
|
update_node_icon_position(gobj);
|
|
3487
3635
|
update_link_icon_position(gobj);
|
|
3636
|
+
update_port_icon_position(gobj);
|
|
3488
3637
|
}
|
|
3489
3638
|
|
|
3490
3639
|
function graph_write_behaviors(gobj, behaviors)
|
|
@@ -3520,6 +3669,64 @@ function graph_set_behavior(gobj, behavior, set)
|
|
|
3520
3669
|
});
|
|
3521
3670
|
}
|
|
3522
3671
|
|
|
3672
|
+
/************************************************************
|
|
3673
|
+
* Custom G6 node: TreedbCard -- G6's html node with ports that
|
|
3674
|
+
* have a SHAPE.
|
|
3675
|
+
*
|
|
3676
|
+
* G6 draws every port as a circle (`drawPortShapes` upserts a
|
|
3677
|
+
* `Circle`, nothing in the port style says otherwise). A port's
|
|
3678
|
+
* `shape` -- `circle`, `square`, `diamond`, `triangle` -- picks
|
|
3679
|
+
* the G shape here, by the names G6 registers itself, and `r`
|
|
3680
|
+
* stays the one size: the half-side of the square, the half-
|
|
3681
|
+
* diagonal of the diamond, the half-height of the triangle. The
|
|
3682
|
+
* hit test, the resize handles and the edge's landing point all
|
|
3683
|
+
* read `r` and none of them care about the outline.
|
|
3684
|
+
************************************************************/
|
|
3685
|
+
const PORT_SHAPES = ['circle', 'square', 'diamond', 'triangle'];
|
|
3686
|
+
|
|
3687
|
+
class TreedbCard extends HTML
|
|
3688
|
+
{
|
|
3689
|
+
drawPortShapes(attributes, container) {
|
|
3690
|
+
const portsStyle = this.getPortsStyle(attributes);
|
|
3691
|
+
Object.keys(portsStyle).forEach((key) => {
|
|
3692
|
+
const style = portsStyle[key];
|
|
3693
|
+
const shapeKey = `port-${key}`;
|
|
3694
|
+
if(style === false) {
|
|
3695
|
+
this.upsert(shapeKey, 'circle', false, container);
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
let r = Number(style.r) || 0;
|
|
3699
|
+
switch(style.shape) {
|
|
3700
|
+
case 'square':
|
|
3701
|
+
this.upsert(shapeKey, 'rect', Object.assign({}, style, {
|
|
3702
|
+
x: -r, y: -r, width: 2 * r, height: 2 * r,
|
|
3703
|
+
}), container);
|
|
3704
|
+
break;
|
|
3705
|
+
case 'diamond':
|
|
3706
|
+
this.upsert(shapeKey, 'polygon', Object.assign({}, style, {
|
|
3707
|
+
points: [[0, -r], [r, 0], [0, r], [-r, 0]],
|
|
3708
|
+
}), container);
|
|
3709
|
+
break;
|
|
3710
|
+
case 'triangle':
|
|
3711
|
+
this.upsert(shapeKey, 'polygon', Object.assign({}, style, {
|
|
3712
|
+
points: [[0, -r], [r, r], [-r, r]],
|
|
3713
|
+
}), container);
|
|
3714
|
+
break;
|
|
3715
|
+
default:
|
|
3716
|
+
this.upsert(shapeKey, 'circle', style, container);
|
|
3717
|
+
break;
|
|
3718
|
+
}
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
/* A record's card is `treedb-card`; `html` is what it was until
|
|
3724
|
+
* 7.23.81 and what a `+N` chip still is. */
|
|
3725
|
+
function is_card_type(type)
|
|
3726
|
+
{
|
|
3727
|
+
return type === 'treedb-card' || type === 'html';
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3523
3730
|
/************************************************************
|
|
3524
3731
|
* Custom G6 node: LightNode (circle with status indicator)
|
|
3525
3732
|
************************************************************/
|
|
@@ -4661,7 +4868,7 @@ function start_node_resize(gobj, e, mx, my)
|
|
|
4661
4868
|
}
|
|
4662
4869
|
|
|
4663
4870
|
// HTML nodes: update dx, dy to keep content centered
|
|
4664
|
-
if(nodeType
|
|
4871
|
+
if(is_card_type(nodeType)) {
|
|
4665
4872
|
updateStyle.dx = -newW / 2;
|
|
4666
4873
|
updateStyle.dy = -newH / 2;
|
|
4667
4874
|
}
|
|
@@ -4842,6 +5049,7 @@ function select_port(gobj, node_id, port_key)
|
|
|
4842
5049
|
|
|
4843
5050
|
show_port_resize_handles(gobj);
|
|
4844
5051
|
show_link_icon_if_fkey(gobj);
|
|
5052
|
+
show_port_icon(gobj);
|
|
4845
5053
|
}
|
|
4846
5054
|
|
|
4847
5055
|
/************************************************************
|
|
@@ -4853,6 +5061,8 @@ function deselect_port(gobj)
|
|
|
4853
5061
|
|
|
4854
5062
|
exit_linking_mode(gobj);
|
|
4855
5063
|
hide_link_icon(gobj);
|
|
5064
|
+
hide_port_icon(gobj);
|
|
5065
|
+
hide_port_popover(gobj);
|
|
4856
5066
|
hide_port_resize_handles(gobj);
|
|
4857
5067
|
priv._selected_port_key = null;
|
|
4858
5068
|
}
|
|
@@ -4940,6 +5150,272 @@ function update_link_icon_position(gobj)
|
|
|
4940
5150
|
}
|
|
4941
5151
|
}
|
|
4942
5152
|
|
|
5153
|
+
/************************************************************
|
|
5154
|
+
* Port properties icon and popover: the gear beside a selected
|
|
5155
|
+
* port, as the node has its own. The popover has the two things
|
|
5156
|
+
* a port IS on screen -- its shape and its radius -- and the
|
|
5157
|
+
* scope: this port, the same port of every card of the topic
|
|
5158
|
+
* (the hook or fkey column), or every port there is.
|
|
5159
|
+
************************************************************/
|
|
5160
|
+
function port_icon_xy(gobj)
|
|
5161
|
+
{
|
|
5162
|
+
let priv = gobj.priv;
|
|
5163
|
+
let graph = priv.graph;
|
|
5164
|
+
let canvasPos = get_port_canvas_position(
|
|
5165
|
+
gobj, priv._selected_node_id, priv._selected_port_key
|
|
5166
|
+
);
|
|
5167
|
+
if(!canvasPos) {
|
|
5168
|
+
return null;
|
|
5169
|
+
}
|
|
5170
|
+
let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
|
|
5171
|
+
/* Under the link icon when there is one (an fkey), in its
|
|
5172
|
+
* place otherwise. */
|
|
5173
|
+
let below = priv._link_icon_el? floating_icon_step() : 0;
|
|
5174
|
+
return {
|
|
5175
|
+
x: vp[0] + floating_icon_size() / 2 + 4,
|
|
5176
|
+
y: vp[1] + floating_icon_dy() + below,
|
|
5177
|
+
};
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5180
|
+
function show_port_icon(gobj)
|
|
5181
|
+
{
|
|
5182
|
+
hide_port_icon(gobj);
|
|
5183
|
+
|
|
5184
|
+
let priv = gobj.priv;
|
|
5185
|
+
if(!priv._selected_node_id || !priv._selected_port_key) {
|
|
5186
|
+
return;
|
|
5187
|
+
}
|
|
5188
|
+
let xy = port_icon_xy(gobj);
|
|
5189
|
+
if(!xy) {
|
|
5190
|
+
return;
|
|
5191
|
+
}
|
|
5192
|
+
let icon = create_floating_icon(
|
|
5193
|
+
'gear', '#1890ff', xy.x, xy.y,
|
|
5194
|
+
t('port properties'), () => toggle_port_popover(gobj)
|
|
5195
|
+
);
|
|
5196
|
+
priv.$container.appendChild(icon);
|
|
5197
|
+
priv._port_icon_el = icon;
|
|
5198
|
+
}
|
|
5199
|
+
|
|
5200
|
+
function hide_port_icon(gobj)
|
|
5201
|
+
{
|
|
5202
|
+
hide_overlay(gobj, '_port_icon_el');
|
|
5203
|
+
}
|
|
5204
|
+
|
|
5205
|
+
function update_port_icon_position(gobj)
|
|
5206
|
+
{
|
|
5207
|
+
let priv = gobj.priv;
|
|
5208
|
+
if(!priv._port_icon_el || !priv._selected_node_id || !priv._selected_port_key) {
|
|
5209
|
+
return;
|
|
5210
|
+
}
|
|
5211
|
+
try {
|
|
5212
|
+
let xy = port_icon_xy(gobj);
|
|
5213
|
+
if(!xy) {
|
|
5214
|
+
hide_port_icon(gobj);
|
|
5215
|
+
hide_port_popover(gobj);
|
|
5216
|
+
return;
|
|
5217
|
+
}
|
|
5218
|
+
priv._port_icon_el.style.left = xy.x + 'px';
|
|
5219
|
+
priv._port_icon_el.style.top = xy.y + 'px';
|
|
5220
|
+
if(priv._port_popover_el) {
|
|
5221
|
+
priv._port_popover_el.style.left = (xy.x + floating_popover_dx()) + 'px';
|
|
5222
|
+
priv._port_popover_el.style.top = xy.y + 'px';
|
|
5223
|
+
clamp_popover_position(gobj, priv._port_popover_el);
|
|
5224
|
+
}
|
|
5225
|
+
} catch(e) {
|
|
5226
|
+
hide_port_icon(gobj);
|
|
5227
|
+
hide_port_popover(gobj);
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
|
|
5231
|
+
function toggle_port_popover(gobj)
|
|
5232
|
+
{
|
|
5233
|
+
let priv = gobj.priv;
|
|
5234
|
+
if(priv._port_popover_el) {
|
|
5235
|
+
hide_port_popover(gobj);
|
|
5236
|
+
} else {
|
|
5237
|
+
show_port_popover(gobj);
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
|
|
5241
|
+
function hide_port_popover(gobj)
|
|
5242
|
+
{
|
|
5243
|
+
hide_overlay(gobj, '_port_popover_el');
|
|
5244
|
+
}
|
|
5245
|
+
|
|
5246
|
+
/* The ports of a node with ONE of them rewritten. */
|
|
5247
|
+
function ports_with(ports, port_key, changes)
|
|
5248
|
+
{
|
|
5249
|
+
return (ports || []).map((p) => (p.key === port_key)? Object.assign({}, p, changes) : p);
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
function show_port_popover(gobj)
|
|
5253
|
+
{
|
|
5254
|
+
hide_port_popover(gobj);
|
|
5255
|
+
|
|
5256
|
+
let priv = gobj.priv;
|
|
5257
|
+
let graph = priv.graph;
|
|
5258
|
+
let node_id = priv._selected_node_id;
|
|
5259
|
+
let port_key = priv._selected_port_key;
|
|
5260
|
+
if(!node_id || !port_key) {
|
|
5261
|
+
return;
|
|
5262
|
+
}
|
|
5263
|
+
let nodeData = graph.getNodeData(node_id);
|
|
5264
|
+
if(!nodeData || !nodeData.style) {
|
|
5265
|
+
return;
|
|
5266
|
+
}
|
|
5267
|
+
let ports = nodeData.style.ports || [];
|
|
5268
|
+
let port = ports.find((p) => p.key === port_key);
|
|
5269
|
+
if(!port) {
|
|
5270
|
+
return;
|
|
5271
|
+
}
|
|
5272
|
+
let orig_shape = port.shape || 'circle';
|
|
5273
|
+
let orig_r = get_port_radius(gobj, node_id, port_key);
|
|
5274
|
+
|
|
5275
|
+
let xy = port_icon_xy(gobj);
|
|
5276
|
+
if(!xy) {
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5279
|
+
const popover = create_popover_base(
|
|
5280
|
+
xy.x + floating_popover_dx(), xy.y, 'g6-port-popover', '#d9d9d9', 180
|
|
5281
|
+
);
|
|
5282
|
+
|
|
5283
|
+
/* Live preview on THIS port; the others wait for apply. */
|
|
5284
|
+
function preview_port() {
|
|
5285
|
+
let r = parseInt(rInput.value) || orig_r;
|
|
5286
|
+
graph.updateNodeData([{id: node_id, style: {
|
|
5287
|
+
ports: ports_with(nodeData.style.ports, port_key, {shape: shapeSelect.value, r: r}),
|
|
5288
|
+
}}]);
|
|
5289
|
+
graph.draw().then(() => {
|
|
5290
|
+
update_port_resize_handles_position(gobj);
|
|
5291
|
+
});
|
|
5292
|
+
}
|
|
5293
|
+
|
|
5294
|
+
create_form_label(popover, 'shape');
|
|
5295
|
+
let shapeSelect = create_form_select(popover, PORT_SHAPES.map((shape) => {
|
|
5296
|
+
return {value: shape, label: t(shape)};
|
|
5297
|
+
}));
|
|
5298
|
+
shapeSelect.value = orig_shape;
|
|
5299
|
+
shapeSelect.addEventListener('change', preview_port);
|
|
5300
|
+
|
|
5301
|
+
create_form_label(popover, 'radius');
|
|
5302
|
+
let rInput = create_form_number_input(popover, orig_r, 2, 40, preview_port);
|
|
5303
|
+
|
|
5304
|
+
create_form_label(popover, 'apply to');
|
|
5305
|
+
let scopeSelect = create_form_select(popover, [
|
|
5306
|
+
{value: 'this', label: t('this port')},
|
|
5307
|
+
{value: 'same_topic', label: t('same port in topic')},
|
|
5308
|
+
{value: 'all', label: t('all ports')},
|
|
5309
|
+
]);
|
|
5310
|
+
|
|
5311
|
+
create_form_button_row(popover, [
|
|
5312
|
+
{
|
|
5313
|
+
text: 'cancel',
|
|
5314
|
+
style: BTN_STYLE_CANCEL,
|
|
5315
|
+
onClick: () => {
|
|
5316
|
+
graph.updateNodeData([{id: node_id, style: {
|
|
5317
|
+
ports: ports_with(nodeData.style.ports, port_key, {shape: orig_shape, r: orig_r}),
|
|
5318
|
+
}}]);
|
|
5319
|
+
graph.draw().then(() => {
|
|
5320
|
+
update_port_resize_handles_position(gobj);
|
|
5321
|
+
});
|
|
5322
|
+
hide_port_popover(gobj);
|
|
5323
|
+
},
|
|
5324
|
+
},
|
|
5325
|
+
{
|
|
5326
|
+
text: 'apply',
|
|
5327
|
+
style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
|
|
5328
|
+
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
5329
|
+
onClick: () => {
|
|
5330
|
+
apply_port_properties(gobj, node_id, port_key,
|
|
5331
|
+
shapeSelect.value, parseInt(rInput.value) || orig_r, scopeSelect.value
|
|
5332
|
+
);
|
|
5333
|
+
},
|
|
5334
|
+
},
|
|
5335
|
+
]);
|
|
5336
|
+
|
|
5337
|
+
priv.$container.appendChild(popover);
|
|
5338
|
+
priv._port_popover_el = popover;
|
|
5339
|
+
clamp_popover_position(gobj, popover);
|
|
5340
|
+
}
|
|
5341
|
+
|
|
5342
|
+
/************************************************************
|
|
5343
|
+
* Apply a port's shape and radius to one port, to the same port
|
|
5344
|
+
* of every card of the topic, or to every port -- and remember
|
|
5345
|
+
* the choice as the topic's default, the way `resize all ports`
|
|
5346
|
+
* does, so a card that arrives later is born with it.
|
|
5347
|
+
* scope: 'this', 'same_topic', 'all'
|
|
5348
|
+
************************************************************/
|
|
5349
|
+
function apply_port_properties(gobj, node_id, port_key, shape, r, scope)
|
|
5350
|
+
{
|
|
5351
|
+
let priv = gobj.priv;
|
|
5352
|
+
let graph = priv.graph;
|
|
5353
|
+
|
|
5354
|
+
let nodeData = graph.getNodeData(node_id);
|
|
5355
|
+
if(!nodeData || !nodeData.data) {
|
|
5356
|
+
return;
|
|
5357
|
+
}
|
|
5358
|
+
let source_topic = nodeData.data.topic_name;
|
|
5359
|
+
|
|
5360
|
+
if(scope === 'same_topic') {
|
|
5361
|
+
if(!is_object(priv._graph_properties[source_topic])) {
|
|
5362
|
+
priv._graph_properties[source_topic] = {};
|
|
5363
|
+
}
|
|
5364
|
+
let defaults = priv._graph_properties[source_topic].defaults || {};
|
|
5365
|
+
defaults.port_sizes = Object.assign({}, defaults.port_sizes, {[port_key]: r});
|
|
5366
|
+
defaults.port_shapes = Object.assign({}, defaults.port_shapes, {[port_key]: shape});
|
|
5367
|
+
priv._graph_properties[source_topic].defaults = defaults;
|
|
5368
|
+
} else if(scope === 'all') {
|
|
5369
|
+
for(const topic_name of Object.keys(priv.descs || {})) {
|
|
5370
|
+
if(!is_object(priv._graph_properties[topic_name])) {
|
|
5371
|
+
priv._graph_properties[topic_name] = {};
|
|
5372
|
+
}
|
|
5373
|
+
let defaults = priv._graph_properties[topic_name].defaults || {};
|
|
5374
|
+
defaults.portR = r;
|
|
5375
|
+
defaults.port_shape = shape;
|
|
5376
|
+
delete defaults.port_sizes;
|
|
5377
|
+
delete defaults.port_shapes;
|
|
5378
|
+
priv._graph_properties[topic_name].defaults = defaults;
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5382
|
+
let updates = [];
|
|
5383
|
+
for(let nd of (graph.getNodeData() || [])) {
|
|
5384
|
+
if(!nd || !nd.data || !nd.data.desc || !nd.style || !is_array(nd.style.ports)) {
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
let new_ports;
|
|
5388
|
+
if(scope === 'this') {
|
|
5389
|
+
if(nd.id !== node_id) {
|
|
5390
|
+
continue;
|
|
5391
|
+
}
|
|
5392
|
+
new_ports = ports_with(nd.style.ports, port_key, {shape: shape, r: r});
|
|
5393
|
+
} else if(scope === 'same_topic') {
|
|
5394
|
+
if(nd.data.topic_name !== source_topic) {
|
|
5395
|
+
continue;
|
|
5396
|
+
}
|
|
5397
|
+
new_ports = ports_with(nd.style.ports, port_key, {shape: shape, r: r});
|
|
5398
|
+
} else {
|
|
5399
|
+
new_ports = nd.style.ports.map((p) => Object.assign({}, p, {shape: shape, r: r}));
|
|
5400
|
+
}
|
|
5401
|
+
let upd = {ports: new_ports};
|
|
5402
|
+
if(scope === 'all') {
|
|
5403
|
+
upd.portR = r;
|
|
5404
|
+
}
|
|
5405
|
+
updates.push({id: nd.id, style: upd});
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5408
|
+
if(updates.length > 0) {
|
|
5409
|
+
graph.updateNodeData(updates);
|
|
5410
|
+
graph.draw().then(() => {
|
|
5411
|
+
update_port_resize_handles_position(gobj);
|
|
5412
|
+
update_port_icon_position(gobj);
|
|
5413
|
+
mark_graph_dirty(gobj);
|
|
5414
|
+
});
|
|
5415
|
+
}
|
|
5416
|
+
hide_port_popover(gobj);
|
|
5417
|
+
}
|
|
5418
|
+
|
|
4943
5419
|
/************************************************************
|
|
4944
5420
|
* Get a column descriptor by field id from a topic desc.
|
|
4945
5421
|
************************************************************/
|
|
@@ -5960,6 +6436,17 @@ function show_node_popover(gobj)
|
|
|
5960
6436
|
create_form_label(popover, 'line width');
|
|
5961
6437
|
let lwInput = create_form_number_input(popover, currentLW, 1, 20, preview_node);
|
|
5962
6438
|
|
|
6439
|
+
/* The figure the node wears in the `shape` view. No preview:
|
|
6440
|
+
* it shows only in that view, and applying it is the same
|
|
6441
|
+
* reshape either way. */
|
|
6442
|
+
create_form_label(popover, 'shape');
|
|
6443
|
+
let shapeSelect = create_form_select(popover, NODE_SHAPES.map((shape) => {
|
|
6444
|
+
return {value: shape, label: t(shape)};
|
|
6445
|
+
}));
|
|
6446
|
+
shapeSelect.value = nodeData.data && nodeData.data.desc
|
|
6447
|
+
? node_figure_of(gobj, nodeData.data.desc, nodeData.data.graph_props || {})
|
|
6448
|
+
: DEFAULT_NODE_SHAPE;
|
|
6449
|
+
|
|
5963
6450
|
// Apply-to scope
|
|
5964
6451
|
create_form_label(popover, 'apply to');
|
|
5965
6452
|
let scopeSelect = create_form_select(popover, [
|
|
@@ -5997,7 +6484,8 @@ function show_node_popover(gobj)
|
|
|
5997
6484
|
fillInput.value,
|
|
5998
6485
|
strokeInput.value,
|
|
5999
6486
|
parseInt(lwInput.value) || 1,
|
|
6000
|
-
scopeSelect.value
|
|
6487
|
+
scopeSelect.value,
|
|
6488
|
+
shapeSelect.value
|
|
6001
6489
|
);
|
|
6002
6490
|
},
|
|
6003
6491
|
},
|
|
@@ -6397,18 +6885,22 @@ function repaint_cards(gobj, ids)
|
|
|
6397
6885
|
continue; /* gone since it was listed */
|
|
6398
6886
|
}
|
|
6399
6887
|
let anchored = (priv.anchor_state === "on" && id === priv.anchor_id);
|
|
6400
|
-
/* A
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6888
|
+
/* A figure paints its flags on its own stroke; a pill in
|
|
6889
|
+
* its one-line html; a card in its own. */
|
|
6890
|
+
let mode = (nd && nd.data && nd.data.desc)? node_mode_of(gobj, id) : 'expanded';
|
|
6891
|
+
if(mode === 'shape') {
|
|
6892
|
+
updates.push({id: id, style: figure_shape_of(
|
|
6893
|
+
gobj, nd.data.desc, nd.data.record || {}, nd.data.graph_props || {},
|
|
6404
6894
|
{highlight: focus.has(id), selected: selected.has(id), anchored: anchored}
|
|
6405
6895
|
).style});
|
|
6406
6896
|
continue;
|
|
6407
6897
|
}
|
|
6408
|
-
let html =
|
|
6409
|
-
nd, priv.theme, focus.has(id), selected.has(id), anchored
|
|
6410
|
-
|
|
6411
|
-
|
|
6898
|
+
let html = (mode === 'compact')
|
|
6899
|
+
? pill_innerHTML_of(gobj, nd, priv.theme, focus.has(id), selected.has(id), anchored)
|
|
6900
|
+
: node_innerHTML_of(
|
|
6901
|
+
nd, priv.theme, focus.has(id), selected.has(id), anchored,
|
|
6902
|
+
pills_html_of(gobj, nd)
|
|
6903
|
+
);
|
|
6412
6904
|
if(html !== null) {
|
|
6413
6905
|
updates.push({id: id, style: {innerHTML: html}});
|
|
6414
6906
|
}
|
|
@@ -6465,17 +6957,20 @@ function refresh_html_nodes_theme(gobj, theme)
|
|
|
6465
6957
|
let id = nodes[i].id;
|
|
6466
6958
|
let nd = graph.getNodeData(id);
|
|
6467
6959
|
let anchored = (priv.anchor_state === "on" && id === priv.anchor_id);
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6960
|
+
let mode = (nd && nd.data && nd.data.desc)? node_mode_of(gobj, id) : 'expanded';
|
|
6961
|
+
if(mode === 'shape') {
|
|
6962
|
+
updates.push({id: id, style: figure_shape_of(
|
|
6963
|
+
gobj, nd.data.desc, nd.data.record || {}, nd.data.graph_props || {},
|
|
6471
6964
|
{highlight: highlighted.has(id), selected: selected.has(id), anchored: anchored}
|
|
6472
6965
|
).style});
|
|
6473
6966
|
continue;
|
|
6474
6967
|
}
|
|
6475
|
-
let html =
|
|
6476
|
-
nd, theme, highlighted.has(id), selected.has(id), anchored
|
|
6477
|
-
|
|
6478
|
-
|
|
6968
|
+
let html = (mode === 'compact')
|
|
6969
|
+
? pill_innerHTML_of(gobj, nd, theme, highlighted.has(id), selected.has(id), anchored)
|
|
6970
|
+
: node_innerHTML_of(
|
|
6971
|
+
nd, theme, highlighted.has(id), selected.has(id), anchored,
|
|
6972
|
+
pills_html_of(gobj, nd)
|
|
6973
|
+
);
|
|
6479
6974
|
if(html === null) {
|
|
6480
6975
|
html = more_innerHTML_of(gobj, nd, theme); /* a `+N` chip, or null */
|
|
6481
6976
|
}
|
|
@@ -7185,7 +7680,7 @@ function hide_unlink_confirm(gobj)
|
|
|
7185
7680
|
* Apply node properties to one or more nodes.
|
|
7186
7681
|
* scope: 'this', 'same_topic', 'all'
|
|
7187
7682
|
************************************************************/
|
|
7188
|
-
function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope)
|
|
7683
|
+
function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope, shape)
|
|
7189
7684
|
{
|
|
7190
7685
|
let priv = gobj.priv;
|
|
7191
7686
|
let graph = priv.graph;
|
|
@@ -7199,6 +7694,23 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope)
|
|
|
7199
7694
|
let updates = [];
|
|
7200
7695
|
const nodes = graph.getData().nodes;
|
|
7201
7696
|
|
|
7697
|
+
/* The figure: written where Save reads it (the node's entry in
|
|
7698
|
+
* `__graphs__`), and as the topic's default for the wider
|
|
7699
|
+
* scopes, so a record that arrives later is born with it. */
|
|
7700
|
+
let figure = (shape && str_in_list(NODE_SHAPES, shape))? shape : "";
|
|
7701
|
+
let reshaped = [];
|
|
7702
|
+
if(figure && scope !== 'this') {
|
|
7703
|
+
let topics = (scope === 'same_topic')? [source_topic] : Object.keys(priv.descs || {});
|
|
7704
|
+
for(let topic_name of topics) {
|
|
7705
|
+
if(!is_object(priv._graph_properties[topic_name])) {
|
|
7706
|
+
priv._graph_properties[topic_name] = {};
|
|
7707
|
+
}
|
|
7708
|
+
let defaults = priv._graph_properties[topic_name].defaults || {};
|
|
7709
|
+
defaults.node_shape = figure;
|
|
7710
|
+
priv._graph_properties[topic_name].defaults = defaults;
|
|
7711
|
+
}
|
|
7712
|
+
}
|
|
7713
|
+
|
|
7202
7714
|
for(let i = 0; i < nodes.length; i++) {
|
|
7203
7715
|
let nd = graph.getNodeData(nodes[i].id);
|
|
7204
7716
|
if(!nd || !nd.data || !nd.data.desc) {
|
|
@@ -7212,6 +7724,13 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope)
|
|
|
7212
7724
|
continue;
|
|
7213
7725
|
}
|
|
7214
7726
|
|
|
7727
|
+
if(figure) {
|
|
7728
|
+
node_props_entry(gobj, nd).node_shape = figure;
|
|
7729
|
+
if(node_mode_of(gobj, nodes[i].id) === 'shape') {
|
|
7730
|
+
reshaped.push(nodes[i].id);
|
|
7731
|
+
}
|
|
7732
|
+
}
|
|
7733
|
+
|
|
7215
7734
|
let updateStyle = { fill: fill, stroke: stroke, lineWidth: lineWidth };
|
|
7216
7735
|
if(nd.data.desc.node_treedb_type === 'hierarchical') {
|
|
7217
7736
|
let record = nd.data.record || {};
|
|
@@ -7227,6 +7746,9 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope)
|
|
|
7227
7746
|
|
|
7228
7747
|
if(updates.length > 0) {
|
|
7229
7748
|
graph.updateNodeData(updates);
|
|
7749
|
+
if(reshaped.length) {
|
|
7750
|
+
reshape_nodes(gobj, reshaped);
|
|
7751
|
+
}
|
|
7230
7752
|
graph.draw().then(() => {
|
|
7231
7753
|
mark_graph_dirty(gobj);
|
|
7232
7754
|
});
|
|
@@ -7235,6 +7757,32 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope)
|
|
|
7235
7757
|
hide_node_popover(gobj);
|
|
7236
7758
|
}
|
|
7237
7759
|
|
|
7760
|
+
/************************************************************
|
|
7761
|
+
* The node's entry in `_graph_properties` -- the object Save
|
|
7762
|
+
* reads and writes -- created if it has none yet, and the node's
|
|
7763
|
+
* `data.graph_props` pointed at it, so a key written here is
|
|
7764
|
+
* read by the next reshape and survives the next Save.
|
|
7765
|
+
************************************************************/
|
|
7766
|
+
function node_props_entry(gobj, nd)
|
|
7767
|
+
{
|
|
7768
|
+
let priv = gobj.priv;
|
|
7769
|
+
let topic_name = nd.data.topic_name;
|
|
7770
|
+
let record = nd.data.record || {};
|
|
7771
|
+
|
|
7772
|
+
if(!is_object(priv._graph_properties[topic_name])) {
|
|
7773
|
+
priv._graph_properties[topic_name] = {};
|
|
7774
|
+
}
|
|
7775
|
+
let topic_props = priv._graph_properties[topic_name];
|
|
7776
|
+
if(!is_object(topic_props.nodes)) {
|
|
7777
|
+
topic_props.nodes = {};
|
|
7778
|
+
}
|
|
7779
|
+
if(!is_object(topic_props.nodes[record.id])) {
|
|
7780
|
+
topic_props.nodes[record.id] = is_object(nd.data.graph_props)? nd.data.graph_props : {};
|
|
7781
|
+
}
|
|
7782
|
+
nd.data.graph_props = topic_props.nodes[record.id];
|
|
7783
|
+
return nd.data.graph_props;
|
|
7784
|
+
}
|
|
7785
|
+
|
|
7238
7786
|
/************************************************************
|
|
7239
7787
|
* Apply edge properties to one or more edges.
|
|
7240
7788
|
* scope: 'this', 'same_type', 'all'
|
|
@@ -7426,9 +7974,9 @@ function build_node_context_menu(gobj, node_id)
|
|
|
7426
7974
|
/* Open or close THIS node against the mode: the door a finger
|
|
7427
7975
|
* has to it (a double click is the mouse's). */
|
|
7428
7976
|
if(node_is_compact(gobj, node_id)) {
|
|
7429
|
-
items.push(ctx_item('g6-icon-fullscreen', t('
|
|
7977
|
+
items.push(ctx_item('g6-icon-fullscreen', t('expand node'), 'toggle_node_mode'));
|
|
7430
7978
|
} else {
|
|
7431
|
-
items.push(ctx_item('g6-icon-fullscreen-exit', t('
|
|
7979
|
+
items.push(ctx_item('g6-icon-fullscreen-exit', t('collapse node'), 'toggle_node_mode'));
|
|
7432
7980
|
}
|
|
7433
7981
|
|
|
7434
7982
|
if(gobj.priv.edit_mode && !node_is_compact(gobj, node_id)) {
|
|
@@ -7453,6 +8001,9 @@ function build_port_context_menu(gobj, node_id, port_key)
|
|
|
7453
8001
|
|
|
7454
8002
|
if(gobj.priv.edit_mode) {
|
|
7455
8003
|
inject_svg_icons();
|
|
8004
|
+
/* The popover, for a finger: the gear beside the port needs
|
|
8005
|
+
* the port selected first, and a long press is the menu. */
|
|
8006
|
+
items.push(ctx_item('g6-icon-edit', t('port properties'), 'port_properties'));
|
|
7456
8007
|
items.push(ctx_item('g6-icon-resize', t('resize all ports'), 'resize_all_ports'));
|
|
7457
8008
|
items.push(ctx_item('g6-icon-resize', t('resize topic ports'), 'resize_topic_ports'));
|
|
7458
8009
|
}
|
|
@@ -7495,6 +8046,12 @@ function handle_context_menu_click(gobj, value)
|
|
|
7495
8046
|
case 'reset_topic_sizes':
|
|
7496
8047
|
reset_sizes(gobj, true);
|
|
7497
8048
|
break;
|
|
8049
|
+
case 'port_properties':
|
|
8050
|
+
if(priv._context_node_id && priv._context_port_key) {
|
|
8051
|
+
select_port(gobj, priv._context_node_id, priv._context_port_key);
|
|
8052
|
+
show_port_popover(gobj);
|
|
8053
|
+
}
|
|
8054
|
+
break;
|
|
7498
8055
|
case 'resize_all_ports':
|
|
7499
8056
|
copy_size_to_ports(gobj, false);
|
|
7500
8057
|
break;
|
|
@@ -7581,7 +8138,7 @@ function copy_size_to_nodes(gobj, same_topic_only)
|
|
|
7581
8138
|
}
|
|
7582
8139
|
|
|
7583
8140
|
// Recalculate dx/dy for HTML nodes
|
|
7584
|
-
if(nd.type
|
|
8141
|
+
if(is_card_type(nd.type)) {
|
|
7585
8142
|
updateStyle.dx = -source_size[0] / 2;
|
|
7586
8143
|
let h = source_size.length > 1 ? source_size[1] : source_size[0];
|
|
7587
8144
|
updateStyle.dy = -h / 2;
|
|
@@ -7773,7 +8330,7 @@ function copy_size_to_ports(gobj, same_topic_only)
|
|
|
7773
8330
|
/* A card with pills is this much taller; the row sits under the subtitle. */
|
|
7774
8331
|
const PILL_ROW_HEIGHT = 24;
|
|
7775
8332
|
const MORE_CHIP_SIZE = [76, 26];
|
|
7776
|
-
/* The chip beside
|
|
8333
|
+
/* The chip beside pills and figures: a square of 22 next to a chip of 76
|
|
7777
8334
|
* read as the biggest thing in the row, so it takes the closed
|
|
7778
8335
|
* scale -- the count and nothing else. */
|
|
7779
8336
|
const MORE_CHIP_SIZE_COMPACT = [40, 22];
|
|
@@ -7787,7 +8344,7 @@ function more_chip_is_compact(gobj, nd)
|
|
|
7787
8344
|
if(parent_id) {
|
|
7788
8345
|
return node_is_compact(gobj, parent_id);
|
|
7789
8346
|
}
|
|
7790
|
-
return gobj_read_str_attr(gobj, "node_mode")
|
|
8347
|
+
return gobj_read_str_attr(gobj, "node_mode") !== "expanded";
|
|
7791
8348
|
}
|
|
7792
8349
|
|
|
7793
8350
|
function more_chip_style(gobj, nd, color, rest)
|
|
@@ -8418,6 +8975,7 @@ async function reconcile_fold_apply(gobj, opts, visible)
|
|
|
8418
8975
|
update_edge_icon_position(gobj);
|
|
8419
8976
|
update_node_icon_position(gobj);
|
|
8420
8977
|
update_link_icon_position(gobj);
|
|
8978
|
+
update_port_icon_position(gobj);
|
|
8421
8979
|
|
|
8422
8980
|
return changed;
|
|
8423
8981
|
}
|
|
@@ -9302,7 +9860,7 @@ function ac_set_loose_topics(gobj, event, kw, src)
|
|
|
9302
9860
|
function ac_set_node_mode(gobj, event, kw, src)
|
|
9303
9861
|
{
|
|
9304
9862
|
let priv = gobj.priv;
|
|
9305
|
-
let mode = (kw && kw.node_mode
|
|
9863
|
+
let mode = (kw && kw.node_mode && str_in_list(NODE_MODES, kw.node_mode))? kw.node_mode : "expanded";
|
|
9306
9864
|
|
|
9307
9865
|
gobj_write_str_attr(gobj, "node_mode", mode);
|
|
9308
9866
|
priv._open_nodes = new Set();
|
|
@@ -9316,9 +9874,9 @@ function ac_set_node_mode(gobj, event, kw, src)
|
|
|
9316
9874
|
}
|
|
9317
9875
|
|
|
9318
9876
|
/************************************************************
|
|
9319
|
-
* The name under a
|
|
9320
|
-
*
|
|
9321
|
-
*
|
|
9877
|
+
* The name under a figure, on or off. Only the `shape` nodes
|
|
9878
|
+
* change, and nothing moves: the label hangs outside the figure
|
|
9879
|
+
* and the layout measures the figure.
|
|
9322
9880
|
************************************************************/
|
|
9323
9881
|
function ac_set_node_labels(gobj, event, kw, src)
|
|
9324
9882
|
{
|
|
@@ -9328,7 +9886,7 @@ function ac_set_node_labels(gobj, event, kw, src)
|
|
|
9328
9886
|
if(!priv.graph) {
|
|
9329
9887
|
return 0;
|
|
9330
9888
|
}
|
|
9331
|
-
let ids = all_record_node_ids(gobj).filter((id) =>
|
|
9889
|
+
let ids = all_record_node_ids(gobj).filter((id) => node_mode_of(gobj, id) === 'shape');
|
|
9332
9890
|
reshape_nodes(gobj, ids);
|
|
9333
9891
|
return 0;
|
|
9334
9892
|
}
|