@yuneta/gobj-ui 7.23.83 → 7.23.84
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 +33 -7
- package/dist/gobj-ui.cjs.js +203 -67
- package/dist/gobj-ui.es.js +203 -67
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +162 -38
- package/src/c_yui_treedb_graph.js +59 -3
- package/src/treedb_layout.js +55 -16
- package/src/treedb_layout.test.js +15 -0
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
gobj_short_name,
|
|
59
59
|
gobj_is_destroying,
|
|
60
60
|
gobj_subscribe_event,
|
|
61
|
+
gobj_unsubscribe_event,
|
|
61
62
|
gobj_publish_event,
|
|
62
63
|
gobj_send_event,
|
|
63
64
|
gobj_post_event,
|
|
@@ -143,7 +144,7 @@ import {
|
|
|
143
144
|
} from '@antv/g6';
|
|
144
145
|
|
|
145
146
|
import {Circle as CircleGeometry, Rect as RectGeometry} from '@antv/g';
|
|
146
|
-
import
|
|
147
|
+
import {t} from "i18next";
|
|
147
148
|
|
|
148
149
|
import {inject_svg_icons} from "./lib_icons.js";
|
|
149
150
|
import {ensure_drag_canvas_patch} from "./g6_drag_canvas_touch.js";
|
|
@@ -157,6 +158,7 @@ import {
|
|
|
157
158
|
consume_long_press_click,
|
|
158
159
|
} from "./g6_touch_gestures.js";
|
|
159
160
|
import {yui_theme_now, yui_watch_theme} from "./yui_theme.js";
|
|
161
|
+
import {yui_shell_of} from "./c_yui_shell.js";
|
|
160
162
|
|
|
161
163
|
/***************************************************************
|
|
162
164
|
* YuiToolbar — G6 Toolbar subclass that adds per-item className
|
|
@@ -465,11 +467,14 @@ let PRIVATE_DATA = {
|
|
|
465
467
|
_edge_icon_el: null, // floating properties icon element
|
|
466
468
|
_edge_delete_el: null, // floating delete icon element for edge
|
|
467
469
|
_edge_popover_el: null, // edge properties popover element
|
|
470
|
+
_edge_preview_undo: null, // ...and how to undo what it previewed
|
|
468
471
|
_port_icon_el: null, // floating port properties icon element
|
|
469
472
|
_port_popover_el: null, // port properties popover element
|
|
473
|
+
_port_preview_undo: null, // ...and how to undo what it previewed
|
|
470
474
|
_node_icon_el: null, // floating node properties icon element
|
|
471
475
|
_node_delete_el: null, // floating delete icon element for node
|
|
472
476
|
_node_popover_el: null, // node properties popover element
|
|
477
|
+
_node_preview_undo: null, // ...and how to undo what it previewed
|
|
473
478
|
_node_detail_el: null, // read-only detail popover (on click)
|
|
474
479
|
_detail_node_id: null, // node id the detail popover is showing
|
|
475
480
|
_delete_confirm_el: null, // delete confirmation popover
|
|
@@ -613,6 +618,18 @@ function mt_writing(gobj, path)
|
|
|
613
618
|
***************************************************************/
|
|
614
619
|
function mt_start(gobj)
|
|
615
620
|
{
|
|
621
|
+
/* A language change reaches the toolbars and the `+N` chips as
|
|
622
|
+
* the shell's EVENT, not as a raw i18next listener: what the
|
|
623
|
+
* toolbar draws and what a chip says are built here, out of
|
|
624
|
+
* reach of refresh_language(), so somebody has to repaint them
|
|
625
|
+
* -- and that somebody is an action of this machine, where the
|
|
626
|
+
* `machine` trace can see it. Same wiring as C_YUI_PERIOD and
|
|
627
|
+
* C_YUI_GOBJ_TREE_JS. */
|
|
628
|
+
let shell = yui_shell_of(gobj);
|
|
629
|
+
if(shell) {
|
|
630
|
+
gobj_subscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
631
|
+
}
|
|
632
|
+
|
|
616
633
|
return 0;
|
|
617
634
|
}
|
|
618
635
|
|
|
@@ -621,6 +638,11 @@ function mt_start(gobj)
|
|
|
621
638
|
***************************************************************/
|
|
622
639
|
function mt_stop(gobj)
|
|
623
640
|
{
|
|
641
|
+
let shell = yui_shell_of(gobj);
|
|
642
|
+
if(shell) {
|
|
643
|
+
gobj_unsubscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
644
|
+
}
|
|
645
|
+
|
|
624
646
|
return 0;
|
|
625
647
|
}
|
|
626
648
|
|
|
@@ -631,11 +653,6 @@ function mt_destroy(gobj)
|
|
|
631
653
|
{
|
|
632
654
|
let priv = gobj.priv;
|
|
633
655
|
|
|
634
|
-
if(priv._on_language_changed) {
|
|
635
|
-
i18next.off('languageChanged', priv._on_language_changed);
|
|
636
|
-
priv._on_language_changed = null;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
656
|
uninstall_fold_listener(gobj);
|
|
640
657
|
|
|
641
658
|
if(priv._on_pointerdown_focus) {
|
|
@@ -1022,12 +1039,6 @@ function configure_events(gobj)
|
|
|
1022
1039
|
}
|
|
1023
1040
|
});
|
|
1024
1041
|
|
|
1025
|
-
// Re-render G6 toolbars (and the `+N` chips' titles) when language changes
|
|
1026
|
-
priv._on_language_changed = () => {
|
|
1027
|
-
update_toolbar(gobj);
|
|
1028
|
-
repaint_more_chips(gobj);
|
|
1029
|
-
};
|
|
1030
|
-
i18next.on('languageChanged', priv._on_language_changed);
|
|
1031
1042
|
|
|
1032
1043
|
/* Clicking a CARD is clicking a DOM element inside the container,
|
|
1033
1044
|
* and that takes the keyboard away from G6's canvas -- the only
|
|
@@ -1865,6 +1876,17 @@ const LR_LAYOUTS = ["dagre"];
|
|
|
1865
1876
|
* toolbar's +/-, and the two-finger pinch of g6_touch_gestures.
|
|
1866
1877
|
* `enable` on the scroll keeps the two apart: a Ctrl wheel is
|
|
1867
1878
|
* the zoom's, so the scroll stands aside for it.
|
|
1879
|
+
*
|
|
1880
|
+
* And the two have to name the SAME key, or the gesture falls
|
|
1881
|
+
* between them: the scroll stood aside for Meta as well, while
|
|
1882
|
+
* the zoom listened for Control alone, so Cmd + wheel neither
|
|
1883
|
+
* scrolled nor zoomed. It is ONE key and not two because a G6
|
|
1884
|
+
* `trigger` is a CHORD, not a list of alternatives -- its
|
|
1885
|
+
* `Shortcut.match()` compares the held keys to the bound ones
|
|
1886
|
+
* as a SET, so ["Control", "Meta"] means both keys down at
|
|
1887
|
+
* once. Control it is, on every platform: it is what a
|
|
1888
|
+
* trackpad pinch arrives as, so a Mac keeps its pinch and
|
|
1889
|
+
* gains nothing to press by mistake.
|
|
1868
1890
|
************************************************************/
|
|
1869
1891
|
function camera_behaviors()
|
|
1870
1892
|
{
|
|
@@ -1872,7 +1894,7 @@ function camera_behaviors()
|
|
|
1872
1894
|
{
|
|
1873
1895
|
type: "scroll-canvas",
|
|
1874
1896
|
key: "scroll-canvas",
|
|
1875
|
-
enable: (event) => !(event &&
|
|
1897
|
+
enable: (event) => !(event && event.ctrlKey),
|
|
1876
1898
|
},
|
|
1877
1899
|
{
|
|
1878
1900
|
type: "zoom-canvas",
|
|
@@ -4046,6 +4068,42 @@ function hide_overlay(gobj, ...keys)
|
|
|
4046
4068
|
/************************************************************
|
|
4047
4069
|
* Enable the save button (mark graph as dirty).
|
|
4048
4070
|
************************************************************/
|
|
4071
|
+
/************************************************************
|
|
4072
|
+
* A popover that PREVIEWS its changes owes an undo.
|
|
4073
|
+
*
|
|
4074
|
+
* What the preview writes is not the reader's answer until
|
|
4075
|
+
* `apply` is pressed, and `cancel` used to be the only way
|
|
4076
|
+
* back: every other way out of the popover -- a click on the
|
|
4077
|
+
* canvas, the icon pressed again, the element going away --
|
|
4078
|
+
* left the preview standing on the element, and the next Save
|
|
4079
|
+
* wrote it to `__graphs__`. So the undo is kept beside the
|
|
4080
|
+
* popover and run by whoever hides it; `apply` drops it first,
|
|
4081
|
+
* because then the preview IS the answer.
|
|
4082
|
+
************************************************************/
|
|
4083
|
+
function set_preview_undo(gobj, key, undo)
|
|
4084
|
+
{
|
|
4085
|
+
gobj.priv[key] = undo;
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
function drop_preview_undo(gobj, key)
|
|
4089
|
+
{
|
|
4090
|
+
gobj.priv[key] = null;
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
function run_preview_undo(gobj, key)
|
|
4094
|
+
{
|
|
4095
|
+
let undo = gobj.priv[key];
|
|
4096
|
+
gobj.priv[key] = null;
|
|
4097
|
+
if(!undo || gobj_is_destroying(gobj)) {
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
4100
|
+
try {
|
|
4101
|
+
undo();
|
|
4102
|
+
} catch(e) {
|
|
4103
|
+
log_error(`${gobj_short_name(gobj)}: cannot undo the preview: ${e}`);
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4049
4107
|
function mark_graph_dirty(gobj)
|
|
4050
4108
|
{
|
|
4051
4109
|
let $container = gobj_read_attr(gobj, "$container");
|
|
@@ -4990,6 +5048,34 @@ function port_hit_slop_world(graph)
|
|
|
4990
5048
|
return slop / zoom;
|
|
4991
5049
|
}
|
|
4992
5050
|
|
|
5051
|
+
/************************************************************
|
|
5052
|
+
* Whether a point landed on a port, by the port's SHAPE.
|
|
5053
|
+
*
|
|
5054
|
+
* A port was a circle and the test was a radius; since 7.23.81
|
|
5055
|
+
* it can be a square, a diamond or a triangle too, and a circle
|
|
5056
|
+
* around a diamond answers for the air off its corners while
|
|
5057
|
+
* missing the middle of its edges. `r` is the drawn radius with
|
|
5058
|
+
* the hit slop already added, and the shapes are the ones
|
|
5059
|
+
* TreedbCard.drawPortShapes() draws from it.
|
|
5060
|
+
*
|
|
5061
|
+
* dx, dy the point, relative to the port's centre
|
|
5062
|
+
************************************************************/
|
|
5063
|
+
function port_shape_hit(shape, dx, dy, r)
|
|
5064
|
+
{
|
|
5065
|
+
switch(shape) {
|
|
5066
|
+
case 'square':
|
|
5067
|
+
return Math.abs(dx) <= r && Math.abs(dy) <= r;
|
|
5068
|
+
case 'diamond':
|
|
5069
|
+
return Math.abs(dx) + Math.abs(dy) <= r;
|
|
5070
|
+
case 'triangle':
|
|
5071
|
+
/* (0,-r), (r,r), (-r,r): under the base and inside the
|
|
5072
|
+
* two slanted sides, which meet at 2|x| - y = r. */
|
|
5073
|
+
return dy <= r && (2 * Math.abs(dx) - dy) <= r;
|
|
5074
|
+
default:
|
|
5075
|
+
return Math.hypot(dx, dy) <= r;
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
|
|
4993
5079
|
/************************************************************
|
|
4994
5080
|
* Detect if a click in canvas coordinates hits a port.
|
|
4995
5081
|
* Returns the port key string, or null if no port hit.
|
|
@@ -5021,8 +5107,9 @@ function detect_port_click(gobj, node_id, canvasX, canvasY)
|
|
|
5021
5107
|
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
5022
5108
|
|
|
5023
5109
|
/* Nearest wins, so the enlarged areas of two ports
|
|
5024
|
-
* overlapping is not a tie -- it is the closer one.
|
|
5025
|
-
|
|
5110
|
+
* overlapping is not a tie -- it is the closer one. The
|
|
5111
|
+
* distance ranks them; the SHAPE says who is in the running. */
|
|
5112
|
+
if(port_shape_hit(ports[i].shape, dx, dy, r + slop) && dist < best_dist) {
|
|
5026
5113
|
best_dist = dist;
|
|
5027
5114
|
best_key = ports[i].key;
|
|
5028
5115
|
}
|
|
@@ -5240,6 +5327,7 @@ function toggle_port_popover(gobj)
|
|
|
5240
5327
|
|
|
5241
5328
|
function hide_port_popover(gobj)
|
|
5242
5329
|
{
|
|
5330
|
+
run_preview_undo(gobj, '_port_preview_undo');
|
|
5243
5331
|
hide_overlay(gobj, '_port_popover_el');
|
|
5244
5332
|
}
|
|
5245
5333
|
|
|
@@ -5280,6 +5368,17 @@ function show_port_popover(gobj)
|
|
|
5280
5368
|
xy.x + floating_popover_dx(), xy.y, 'g6-port-popover', '#d9d9d9', 180
|
|
5281
5369
|
);
|
|
5282
5370
|
|
|
5371
|
+
/* What the preview writes on this port, undone: whoever hides
|
|
5372
|
+
* the popover without applying runs it (see set_preview_undo). */
|
|
5373
|
+
let undo_port = () => {
|
|
5374
|
+
graph.updateNodeData([{id: node_id, style: {
|
|
5375
|
+
ports: ports_with(nodeData.style.ports, port_key, {shape: orig_shape, r: orig_r}),
|
|
5376
|
+
}}]);
|
|
5377
|
+
graph.draw().then(() => {
|
|
5378
|
+
update_port_resize_handles_position(gobj);
|
|
5379
|
+
});
|
|
5380
|
+
};
|
|
5381
|
+
|
|
5283
5382
|
/* Live preview on THIS port; the others wait for apply. */
|
|
5284
5383
|
function preview_port() {
|
|
5285
5384
|
let r = parseInt(rInput.value) || orig_r;
|
|
@@ -5313,13 +5412,7 @@ function show_port_popover(gobj)
|
|
|
5313
5412
|
text: 'cancel',
|
|
5314
5413
|
style: BTN_STYLE_CANCEL,
|
|
5315
5414
|
onClick: () => {
|
|
5316
|
-
|
|
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);
|
|
5415
|
+
hide_port_popover(gobj); /* which undoes the preview */
|
|
5323
5416
|
},
|
|
5324
5417
|
},
|
|
5325
5418
|
{
|
|
@@ -5327,6 +5420,7 @@ function show_port_popover(gobj)
|
|
|
5327
5420
|
style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
|
|
5328
5421
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
5329
5422
|
onClick: () => {
|
|
5423
|
+
drop_preview_undo(gobj, '_port_preview_undo');
|
|
5330
5424
|
apply_port_properties(gobj, node_id, port_key,
|
|
5331
5425
|
shapeSelect.value, parseInt(rInput.value) || orig_r, scopeSelect.value
|
|
5332
5426
|
);
|
|
@@ -5336,6 +5430,7 @@ function show_port_popover(gobj)
|
|
|
5336
5430
|
|
|
5337
5431
|
priv.$container.appendChild(popover);
|
|
5338
5432
|
priv._port_popover_el = popover;
|
|
5433
|
+
set_preview_undo(gobj, '_port_preview_undo', undo_port);
|
|
5339
5434
|
clamp_popover_position(gobj, popover);
|
|
5340
5435
|
}
|
|
5341
5436
|
|
|
@@ -6184,6 +6279,12 @@ function show_edge_popover(gobj)
|
|
|
6184
6279
|
'g6-edge-popover', '#d9d9d9', 180
|
|
6185
6280
|
);
|
|
6186
6281
|
|
|
6282
|
+
/* What the preview writes, undone (see set_preview_undo). */
|
|
6283
|
+
let undo_edge = () => {
|
|
6284
|
+
graph.updateEdgeData([{ id: edge_id, style: { lineWidth: origLW, stroke: origStroke } }]);
|
|
6285
|
+
graph.draw();
|
|
6286
|
+
};
|
|
6287
|
+
|
|
6187
6288
|
// Live preview: update the selected edge in real time
|
|
6188
6289
|
function preview_edge() {
|
|
6189
6290
|
let lw = parseInt(lwInput.value) || 2;
|
|
@@ -6213,9 +6314,7 @@ function show_edge_popover(gobj)
|
|
|
6213
6314
|
text: 'cancel',
|
|
6214
6315
|
style: BTN_STYLE_CANCEL,
|
|
6215
6316
|
onClick: () => {
|
|
6216
|
-
|
|
6217
|
-
graph.draw();
|
|
6218
|
-
deselect_edge(gobj);
|
|
6317
|
+
deselect_edge(gobj); /* which undoes the preview */
|
|
6219
6318
|
},
|
|
6220
6319
|
},
|
|
6221
6320
|
{
|
|
@@ -6223,6 +6322,7 @@ function show_edge_popover(gobj)
|
|
|
6223
6322
|
style: 'flex:1;padding:6px;background:#52c41a;color:#fff;border:none;' +
|
|
6224
6323
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
6225
6324
|
onClick: () => {
|
|
6325
|
+
drop_preview_undo(gobj, '_edge_preview_undo');
|
|
6226
6326
|
apply_edge_properties(gobj, edge_id,
|
|
6227
6327
|
parseInt(lwInput.value) || 2,
|
|
6228
6328
|
colorInput.value,
|
|
@@ -6234,6 +6334,7 @@ function show_edge_popover(gobj)
|
|
|
6234
6334
|
|
|
6235
6335
|
priv.$container.appendChild(popover);
|
|
6236
6336
|
priv._edge_popover_el = popover;
|
|
6337
|
+
set_preview_undo(gobj, '_edge_preview_undo', undo_edge);
|
|
6237
6338
|
|
|
6238
6339
|
// Clamp popover inside the container
|
|
6239
6340
|
clamp_popover_position(gobj, popover);
|
|
@@ -6278,6 +6379,7 @@ function clamp_popover_position(gobj, popover)
|
|
|
6278
6379
|
|
|
6279
6380
|
function hide_edge_popover(gobj)
|
|
6280
6381
|
{
|
|
6382
|
+
run_preview_undo(gobj, '_edge_preview_undo');
|
|
6281
6383
|
hide_overlay(gobj, '_edge_popover_el');
|
|
6282
6384
|
}
|
|
6283
6385
|
|
|
@@ -6404,6 +6506,22 @@ function show_node_popover(gobj)
|
|
|
6404
6506
|
let node_graph_type = nodeData.data && nodeData.data.desc ?
|
|
6405
6507
|
nodeData.data.desc.node_treedb_type : null;
|
|
6406
6508
|
|
|
6509
|
+
/* What the preview writes, undone (see set_preview_undo). */
|
|
6510
|
+
let undo_node = () => {
|
|
6511
|
+
let restoreStyle = { fill: origFill, stroke: origStroke, lineWidth: origLW };
|
|
6512
|
+
if(node_graph_type === 'hierarchical') {
|
|
6513
|
+
let record = nodeData.data.record || {};
|
|
6514
|
+
restoreStyle.innerHTML = build_node_innerHTML(
|
|
6515
|
+
origFill, priv.theme, record.icon,
|
|
6516
|
+
node_label(nodeData.data.desc, record),
|
|
6517
|
+
nodeData.data.desc.topic_name, false, record.id,
|
|
6518
|
+
false, false, false, pills_html_of(gobj, nodeData)
|
|
6519
|
+
);
|
|
6520
|
+
}
|
|
6521
|
+
graph.updateNodeData([{ id: node_id, style: restoreStyle }]);
|
|
6522
|
+
graph.draw();
|
|
6523
|
+
};
|
|
6524
|
+
|
|
6407
6525
|
// Live preview
|
|
6408
6526
|
function preview_node() {
|
|
6409
6527
|
let fill = fillInput.value;
|
|
@@ -6460,19 +6578,7 @@ function show_node_popover(gobj)
|
|
|
6460
6578
|
text: 'cancel',
|
|
6461
6579
|
style: BTN_STYLE_CANCEL,
|
|
6462
6580
|
onClick: () => {
|
|
6463
|
-
|
|
6464
|
-
if(node_graph_type === 'hierarchical') {
|
|
6465
|
-
let record = nodeData.data.record || {};
|
|
6466
|
-
restoreStyle.innerHTML = build_node_innerHTML(
|
|
6467
|
-
origFill, priv.theme, record.icon,
|
|
6468
|
-
node_label(nodeData.data.desc, record),
|
|
6469
|
-
nodeData.data.desc.topic_name, false, record.id,
|
|
6470
|
-
false, false, false, pills_html_of(gobj, nodeData)
|
|
6471
|
-
);
|
|
6472
|
-
}
|
|
6473
|
-
graph.updateNodeData([{ id: node_id, style: restoreStyle }]);
|
|
6474
|
-
graph.draw();
|
|
6475
|
-
hide_node_popover(gobj);
|
|
6581
|
+
hide_node_popover(gobj); /* which undoes the preview */
|
|
6476
6582
|
},
|
|
6477
6583
|
},
|
|
6478
6584
|
{
|
|
@@ -6480,6 +6586,7 @@ function show_node_popover(gobj)
|
|
|
6480
6586
|
style: 'flex:1;padding:6px;background:#1890ff;color:#fff;border:none;' +
|
|
6481
6587
|
'border-radius:4px;cursor:pointer;font-size:13px;font-weight:500;',
|
|
6482
6588
|
onClick: () => {
|
|
6589
|
+
drop_preview_undo(gobj, '_node_preview_undo');
|
|
6483
6590
|
apply_node_properties(gobj, node_id,
|
|
6484
6591
|
fillInput.value,
|
|
6485
6592
|
strokeInput.value,
|
|
@@ -6493,6 +6600,7 @@ function show_node_popover(gobj)
|
|
|
6493
6600
|
|
|
6494
6601
|
priv.$container.appendChild(popover);
|
|
6495
6602
|
priv._node_popover_el = popover;
|
|
6603
|
+
set_preview_undo(gobj, '_node_preview_undo', undo_node);
|
|
6496
6604
|
|
|
6497
6605
|
// Clamp popover inside the container
|
|
6498
6606
|
clamp_popover_position(gobj, popover);
|
|
@@ -6500,6 +6608,7 @@ function show_node_popover(gobj)
|
|
|
6500
6608
|
|
|
6501
6609
|
function hide_node_popover(gobj)
|
|
6502
6610
|
{
|
|
6611
|
+
run_preview_undo(gobj, '_node_preview_undo');
|
|
6503
6612
|
hide_overlay(gobj, '_node_popover_el');
|
|
6504
6613
|
}
|
|
6505
6614
|
|
|
@@ -8980,6 +9089,19 @@ async function reconcile_fold_apply(gobj, opts, visible)
|
|
|
8980
9089
|
return changed;
|
|
8981
9090
|
}
|
|
8982
9091
|
|
|
9092
|
+
/************************************************************
|
|
9093
|
+
* The application changed language: what this gclass DRAWS has
|
|
9094
|
+
* to be drawn again. `refresh_language()` reaches what carries
|
|
9095
|
+
* an i18n key in the DOM; a G6 toolbar and the html of a chip
|
|
9096
|
+
* are built here and carry none.
|
|
9097
|
+
************************************************************/
|
|
9098
|
+
function ac_language_changed(gobj, event, kw, src)
|
|
9099
|
+
{
|
|
9100
|
+
update_toolbar(gobj);
|
|
9101
|
+
repaint_more_chips(gobj);
|
|
9102
|
+
return 0;
|
|
9103
|
+
}
|
|
9104
|
+
|
|
8983
9105
|
/************************************************************
|
|
8984
9106
|
* Repaint the `+N` chips (theme or language changed).
|
|
8985
9107
|
************************************************************/
|
|
@@ -10527,6 +10649,7 @@ function create_gclass(gclass_name)
|
|
|
10527
10649
|
["EV_HIDE", ac_hide, null],
|
|
10528
10650
|
["EV_RESIZE", ac_resize, null],
|
|
10529
10651
|
["EV_THEME", ac_theme, null],
|
|
10652
|
+
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
10530
10653
|
]]
|
|
10531
10654
|
];
|
|
10532
10655
|
|
|
@@ -10621,6 +10744,7 @@ function create_gclass(gclass_name)
|
|
|
10621
10744
|
["EV_HIDE", 0],
|
|
10622
10745
|
["EV_RESIZE", 0],
|
|
10623
10746
|
["EV_THEME", 0],
|
|
10747
|
+
["EV_LANGUAGE_CHANGED", 0],
|
|
10624
10748
|
];
|
|
10625
10749
|
|
|
10626
10750
|
/*----------------------------------------*
|
|
@@ -173,6 +173,7 @@ let PRIVATE_DATA = {
|
|
|
173
173
|
layout: null,
|
|
174
174
|
_topics_subscribed: {},
|
|
175
175
|
_legend_state: null, /* the engine's last EV_LEGEND_STATE */
|
|
176
|
+
_find_result: null, /* ...and its last EV_FIND_RESULT */
|
|
176
177
|
_links_subscribed: false, /* EV_TREEDB_NODE_LINKED/UNLINKED (treedb-wide) */
|
|
177
178
|
|
|
178
179
|
is_pinhold_window: false, // inherited of v6, todo review
|
|
@@ -303,6 +304,18 @@ function mt_start(gobj)
|
|
|
303
304
|
{
|
|
304
305
|
let priv = gobj.priv;
|
|
305
306
|
|
|
307
|
+
/* The legend is built from the engine's EV_LEGEND_STATE, which
|
|
308
|
+
* arrives after a reconcile and not when the language changes:
|
|
309
|
+
* a chip's title says an ACTION (show / hide this topic), and
|
|
310
|
+
* the strip would keep the old language until somebody folded
|
|
311
|
+
* something. The keys travel in `data-i18n-title` too, so this
|
|
312
|
+
* is the belt to that pair of braces -- and it is what repaints
|
|
313
|
+
* the counted line of the find box, which has no key at all. */
|
|
314
|
+
let shell = yui_shell_of(gobj);
|
|
315
|
+
if(shell) {
|
|
316
|
+
gobj_subscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
317
|
+
}
|
|
318
|
+
|
|
306
319
|
if(priv.gobj_nodes_tree) {
|
|
307
320
|
gobj_start(priv.gobj_nodes_tree);
|
|
308
321
|
}
|
|
@@ -323,6 +336,7 @@ function mt_stop(gobj)
|
|
|
323
336
|
let shell = yui_shell_of(gobj);
|
|
324
337
|
if(shell) {
|
|
325
338
|
yui_shell_set_sub_routes(shell, gobj_read_str_attr(gobj, "base_route"), null);
|
|
339
|
+
gobj_unsubscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
326
340
|
}
|
|
327
341
|
close_json_viewer(gobj);
|
|
328
342
|
gobj_stop_children(gobj);
|
|
@@ -2523,6 +2537,8 @@ function refresh_legend(gobj)
|
|
|
2523
2537
|
hidden? `${entry.total}` : `${entry.visible}/${entry.total}`]
|
|
2524
2538
|
);
|
|
2525
2539
|
|
|
2540
|
+
let body_key = is_main? 'main topic' : (hidden? 'show topic' : 'hide topic');
|
|
2541
|
+
|
|
2526
2542
|
/* Built as an object first: an attribute that is `undefined`
|
|
2527
2543
|
* is still SET by createElement2 (to the string "undefined"),
|
|
2528
2544
|
* and a `disabled="undefined"` button is a disabled button --
|
|
@@ -2533,7 +2549,14 @@ function refresh_legend(gobj)
|
|
|
2533
2549
|
(is_main? ' GRAPH_LEGEND_MAIN' : ''),
|
|
2534
2550
|
type: 'button',
|
|
2535
2551
|
style: 'gap:0; flex:0 0 auto;' + (focused? ' ' + LEGEND_FOCUSED_STYLE : ''),
|
|
2536
|
-
|
|
2552
|
+
/* The KEY as well as the text: the strip is redrawn only
|
|
2553
|
+
* when the engine speaks again (EV_LEGEND_STATE), and a
|
|
2554
|
+
* language change is not that -- refresh_language() reaches
|
|
2555
|
+
* what carries `data-i18n-title` and nothing else. The key
|
|
2556
|
+
* is picked here because the chip is rebuilt whenever the
|
|
2557
|
+
* state behind it changes. */
|
|
2558
|
+
title: t(body_key),
|
|
2559
|
+
'data-i18n-title': body_key,
|
|
2537
2560
|
'aria-label': topic_name,
|
|
2538
2561
|
'aria-pressed': hidden? 'false' : 'true',
|
|
2539
2562
|
};
|
|
@@ -2560,7 +2583,9 @@ function refresh_legend(gobj)
|
|
|
2560
2583
|
extras.push(['button', {class: 'GRAPH_LEGEND_STAR button is-small pressed_state',
|
|
2561
2584
|
type: 'button', style: 'padding:0 .4rem;',
|
|
2562
2585
|
title: t('main topic'), 'data-i18n-title': 'main topic',
|
|
2563
|
-
'aria-label': t('main topic'),
|
|
2586
|
+
'aria-label': t('main topic'),
|
|
2587
|
+
'data-i18n-aria-label': 'main topic',
|
|
2588
|
+
'aria-pressed': 'true'},
|
|
2564
2589
|
[['span', {style: LEGEND_GLYPH_STYLE}, '★']], {
|
|
2565
2590
|
click: (evt) => {
|
|
2566
2591
|
evt.stopPropagation();
|
|
@@ -2577,7 +2602,8 @@ function refresh_legend(gobj)
|
|
|
2577
2602
|
extras.push(['button', {class: 'GRAPH_LEGEND_STAR button is-small',
|
|
2578
2603
|
type: 'button', style: 'padding:0 .4rem;',
|
|
2579
2604
|
title: t('main topic'), 'data-i18n-title': 'main topic',
|
|
2580
|
-
'aria-label': t('main topic')
|
|
2605
|
+
'aria-label': t('main topic'),
|
|
2606
|
+
'data-i18n-aria-label': 'main topic'},
|
|
2581
2607
|
[['span', {style: LEGEND_GLYPH_STYLE}, '☆']], {
|
|
2582
2608
|
click: (evt) => {
|
|
2583
2609
|
evt.stopPropagation();
|
|
@@ -2597,6 +2623,7 @@ function refresh_legend(gobj)
|
|
|
2597
2623
|
type: 'button', style: 'padding:0 .45rem;',
|
|
2598
2624
|
title: t('loose records'), 'data-i18n-title': 'loose records',
|
|
2599
2625
|
'aria-label': t('loose records'),
|
|
2626
|
+
'data-i18n-aria-label': 'loose records',
|
|
2600
2627
|
'aria-pressed': entry.loose_shown? 'true' : 'false'
|
|
2601
2628
|
}, `+${entry.loose}`, {
|
|
2602
2629
|
click: (evt) => {
|
|
@@ -2614,6 +2641,7 @@ function refresh_legend(gobj)
|
|
|
2614
2641
|
type: 'button', style: 'padding:0 .4rem;',
|
|
2615
2642
|
title: t('highlight topic'), 'data-i18n-title': 'highlight topic',
|
|
2616
2643
|
'aria-label': t('highlight topic'),
|
|
2644
|
+
'data-i18n-aria-label': 'highlight topic',
|
|
2617
2645
|
'aria-pressed': focused? 'true' : 'false'
|
|
2618
2646
|
}, [['i', {class: 'yi-location-crosshairs', style: LEGEND_GLYPH_STYLE}]], {
|
|
2619
2647
|
click: (evt) => {
|
|
@@ -2633,6 +2661,20 @@ function refresh_legend(gobj)
|
|
|
2633
2661
|
}
|
|
2634
2662
|
}
|
|
2635
2663
|
|
|
2664
|
+
/************************************************************
|
|
2665
|
+
* The application changed language. `refresh_language()` has
|
|
2666
|
+
* already been over the document by now (the shell runs it
|
|
2667
|
+
* before publishing), so what is left is what carries no key:
|
|
2668
|
+
* the legend, whose chips say an ACTION that depends on the
|
|
2669
|
+
* state, and the find box's counted line.
|
|
2670
|
+
************************************************************/
|
|
2671
|
+
function ac_language_changed(gobj, event, kw, src)
|
|
2672
|
+
{
|
|
2673
|
+
refresh_legend(gobj);
|
|
2674
|
+
refresh_find_result(gobj);
|
|
2675
|
+
return 0;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2636
2678
|
/************************************************************
|
|
2637
2679
|
* The engine said what is drawn: repaint the strip.
|
|
2638
2680
|
************************************************************/
|
|
@@ -2848,6 +2890,18 @@ function ac_toggle_node_labels(gobj, event, kw, src)
|
|
|
2848
2890
|
************************************************************/
|
|
2849
2891
|
function ac_find_result(gobj, event, kw, src)
|
|
2850
2892
|
{
|
|
2893
|
+
/* Kept, because the line is COMPOSED -- a counted word and a
|
|
2894
|
+
* count of hidden topics -- so no i18n key in the DOM can
|
|
2895
|
+
* re-translate it: its owner repaints it from the last answer
|
|
2896
|
+
* when the language changes. */
|
|
2897
|
+
gobj.priv._find_result = kw || null;
|
|
2898
|
+
refresh_find_result(gobj);
|
|
2899
|
+
return 0;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
function refresh_find_result(gobj)
|
|
2903
|
+
{
|
|
2904
|
+
let kw = gobj.priv._find_result;
|
|
2851
2905
|
let $container = gobj_read_attr(gobj, "$container");
|
|
2852
2906
|
if(!$container) {
|
|
2853
2907
|
return 0;
|
|
@@ -3004,6 +3058,7 @@ function create_gclass(gclass_name)
|
|
|
3004
3058
|
["EV_SHOW", ac_show, null],
|
|
3005
3059
|
["EV_HIDE", ac_hide, null],
|
|
3006
3060
|
["EV_TRANSPORT_STATE", ac_transport_state, null],
|
|
3061
|
+
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
3007
3062
|
]]
|
|
3008
3063
|
];
|
|
3009
3064
|
|
|
@@ -3052,6 +3107,7 @@ function create_gclass(gclass_name)
|
|
|
3052
3107
|
event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
3053
3108
|
["EV_SHOW", 0],
|
|
3054
3109
|
["EV_HIDE", 0],
|
|
3110
|
+
["EV_LANGUAGE_CHANGED", 0],
|
|
3055
3111
|
["EV_TRANSPORT_STATE", 0],
|
|
3056
3112
|
];
|
|
3057
3113
|
|
package/src/treedb_layout.js
CHANGED
|
@@ -51,8 +51,10 @@
|
|
|
51
51
|
*
|
|
52
52
|
* `radial`: the root in the middle and a ring per depth, each
|
|
53
53
|
* subtree owning a sector proportional to its leaves; the radius
|
|
54
|
-
* of a ring grows until its neighbours sit side by side
|
|
55
|
-
* nothing overlaps by
|
|
54
|
+
* of a ring grows until its neighbours sit side by side AND
|
|
55
|
+
* until it clears the ring inside it, so nothing overlaps by
|
|
56
|
+
* construction, across the ring or along the radius (see
|
|
57
|
+
* `layout_radial`).
|
|
56
58
|
*
|
|
57
59
|
* Pure: takes plain nodes and edges, returns positions. The G6
|
|
58
60
|
* classes in c_g6_nodes_tree.js are two thin adapters over it.
|
|
@@ -272,6 +274,18 @@ function tidy_tree_lr(nodes, edges, o)
|
|
|
272
274
|
return pos;
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
/* How much of a card lies along the direction `a`: the projection
|
|
278
|
+
* of its rectangle on that direction. Full width when the card is
|
|
279
|
+
* read along its width, full height across it, and the honest
|
|
280
|
+
* mixture in between. */
|
|
281
|
+
function extent_at(n, a)
|
|
282
|
+
{
|
|
283
|
+
if(!n) {
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
return Math.abs(n.w * Math.cos(a)) + Math.abs(n.h * Math.sin(a));
|
|
287
|
+
}
|
|
288
|
+
|
|
275
289
|
/************************************************************
|
|
276
290
|
* The radial tree: the root in the middle, a ring per depth.
|
|
277
291
|
*
|
|
@@ -333,10 +347,10 @@ export function layout_radial(nodes, edges, opts)
|
|
|
333
347
|
/* An explicit stack, not recursion: the sectors of a ring are
|
|
334
348
|
* sorted by angle in pass 2, so the order they are pushed in does
|
|
335
349
|
* not matter -- only that every node gets its own. */
|
|
336
|
-
let sector = (root_id, root_from, root_span) => {
|
|
337
|
-
let stack = [{id: root_id, from: root_from, span: root_span}];
|
|
350
|
+
let sector = (root_id, root_from, root_span, root_parent) => {
|
|
351
|
+
let stack = [{id: root_id, from: root_from, span: root_span, parent: root_parent}];
|
|
338
352
|
while(stack.length) {
|
|
339
|
-
let {id, from, span} = stack.pop();
|
|
353
|
+
let {id, from, span, parent} = stack.pop();
|
|
340
354
|
let a = from + span / 2;
|
|
341
355
|
angle.set(id, a);
|
|
342
356
|
let ring = ring_of(id);
|
|
@@ -344,11 +358,21 @@ export function layout_radial(nodes, edges, opts)
|
|
|
344
358
|
rings[ring] = [];
|
|
345
359
|
}
|
|
346
360
|
let n = t.by_id.get(id);
|
|
347
|
-
rings[ring].push({
|
|
361
|
+
rings[ring].push({
|
|
362
|
+
id: id,
|
|
363
|
+
angle: a,
|
|
364
|
+
diag: Math.hypot(n.w, n.h),
|
|
365
|
+
/* How much of the card lies along the RADIUS at this
|
|
366
|
+
* angle -- the projection of the rectangle on that
|
|
367
|
+
* direction -- for itself and for the node it hangs
|
|
368
|
+
* from, which is the one it has to clear. */
|
|
369
|
+
ext: extent_at(n, a),
|
|
370
|
+
parent_ext: parent? extent_at(t.by_id.get(parent), a) : 0,
|
|
371
|
+
});
|
|
348
372
|
let cursor = from;
|
|
349
373
|
for(let kid of (t.children.get(id) || [])) {
|
|
350
374
|
let part = span * leaves.get(kid) / leaves.get(id);
|
|
351
|
-
stack.push({id: kid, from: cursor, span: part});
|
|
375
|
+
stack.push({id: kid, from: cursor, span: part, parent: id});
|
|
352
376
|
cursor += part;
|
|
353
377
|
}
|
|
354
378
|
}
|
|
@@ -359,28 +383,43 @@ export function layout_radial(nodes, edges, opts)
|
|
|
359
383
|
let cursor = -Math.PI / 2; /* the first child at the top */
|
|
360
384
|
for(let kid of (t.children.get(root) || [])) {
|
|
361
385
|
let part = 2 * Math.PI * leaves.get(kid) / leaves.get(root);
|
|
362
|
-
sector(kid, cursor, part);
|
|
386
|
+
sector(kid, cursor, part, root);
|
|
363
387
|
cursor += part;
|
|
364
388
|
}
|
|
365
389
|
} else {
|
|
366
390
|
let cursor = -Math.PI / 2;
|
|
367
391
|
for(let root of t.roots) {
|
|
368
392
|
let part = 2 * Math.PI * leaves.get(root) / total;
|
|
369
|
-
sector(root, cursor, part);
|
|
393
|
+
sector(root, cursor, part, null);
|
|
370
394
|
cursor += part;
|
|
371
395
|
}
|
|
372
396
|
}
|
|
373
397
|
|
|
374
|
-
/* Pass 2, the RADII
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
398
|
+
/* Pass 2, the RADII. Two constraints, and the larger wins.
|
|
399
|
+
*
|
|
400
|
+
* ACROSS the ring: its two closest neighbours have to sit side
|
|
401
|
+
* by side -- the arc between two adjacent centres is r·Δangle,
|
|
402
|
+
* and it has to hold half of each card plus the gap. Measured on
|
|
403
|
+
* NEIGHBOURS, not on the ring's total length: a leafless root
|
|
404
|
+
* squeezed between two big subtrees gets a sliver of angle, and
|
|
405
|
+
* it is the sliver that sets the radius.
|
|
406
|
+
*
|
|
407
|
+
* ALONG the radius: `ranksep` is a step between CENTRES, so a
|
|
408
|
+
* ring one step out was one step out whatever the cards
|
|
409
|
+
* measured -- 180 between the centres of two cards that reach
|
|
410
|
+
* 95 each way is two cards touching. The tidy tree never had
|
|
411
|
+
* this: its `ranksep` is a gap between COLUMNS, and a column
|
|
412
|
+
* carries its own width. Here every card is measured against
|
|
413
|
+
* the one it HANGS FROM, in the direction it hangs in -- the
|
|
414
|
+
* two are radially aligned, or nearly, since a child lives
|
|
415
|
+
* inside its parent's sector -- and the ring goes out far
|
|
416
|
+
* enough for the worst of them. */
|
|
381
417
|
let radius = [0];
|
|
382
418
|
for(let ring = 1; ring < rings.length; ring++) {
|
|
383
419
|
let r = radius[ring - 1] + o.ranksep;
|
|
420
|
+
for(let n of (rings[ring] || [])) {
|
|
421
|
+
r = Math.max(r, radius[ring - 1] + (n.parent_ext + n.ext) / 2 + o.nodesep);
|
|
422
|
+
}
|
|
384
423
|
let list = (rings[ring] || []).slice().sort((a, b) => a.angle - b.angle);
|
|
385
424
|
for(let i = 0; i < list.length && list.length > 1; i++) {
|
|
386
425
|
let a = list[i];
|