@yuneta/gobj-ui 7.23.102 → 7.23.104
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 +32 -0
- package/dist/gobj-ui.cjs.js +148 -21
- package/dist/gobj-ui.es.js +149 -22
- package/package.json +2 -1
- package/src/c_g6_nodes_tree.js +174 -11
- package/src/c_yui_gclass.js +2 -2
- package/src/c_yui_json.css +8 -8
- package/src/c_yui_json.js +6 -7
- package/src/c_yui_period.js +15 -15
- package/src/c_yui_treedb_graph.js +26 -1
- package/src/yui_toolbar.css +33 -0
- package/src/yui_toolbar.js +22 -1
package/README.md
CHANGED
|
@@ -947,6 +947,38 @@ Two things a host does not have to think about but a maintainer does:
|
|
|
947
947
|
**New keys for consumers: `anchor view`, `click the element to centre on`,
|
|
948
948
|
`centred: click to release`.**
|
|
949
949
|
|
|
950
|
+
### The camera belongs to the reader
|
|
951
|
+
|
|
952
|
+
Once somebody has chosen a zoom, nothing changes it but a camera command. Not a
|
|
953
|
+
**refresh**, not a change of **node mode**, not a new **main topic**: those
|
|
954
|
+
rebuild the CONTENT, and content moving is no reason to move the reader. The
|
|
955
|
+
opening frame is the only one the graph picks — a fit, and only for a graph
|
|
956
|
+
nobody has arranged.
|
|
957
|
+
|
|
958
|
+
Two mechanisms, because the question is not the same in the two cases:
|
|
959
|
+
|
|
960
|
+
- **Across a rebuild in the same view**, a NODE is held at the pixel it was on:
|
|
961
|
+
`yui_graph_viewport_of()` before, `yui_graph_place_at()` after — the pair the
|
|
962
|
+
folds have always used. It keeps the zoom because it only translates, and it
|
|
963
|
+
survives a relayout that moves everything, which restoring raw coordinates
|
|
964
|
+
would not.
|
|
965
|
+
- **Across a RELOAD** there is no node to hold on to yet, so the viewport itself
|
|
966
|
+
is restored: `{zoom, x, y}` as G6 reports them (`getZoom()` + `getPosition()`,
|
|
967
|
+
the documented pair of `zoomTo()` + `translateTo()`).
|
|
968
|
+
|
|
969
|
+
Wiring: `C_G6_NODES_TREE` takes a `camera` attr — the viewport to restore on its
|
|
970
|
+
FIRST draw, empty meaning *open fitted* — and publishes **`EV_CAMERA_CHANGED
|
|
971
|
+
{zoom, x, y}`** once a move has settled. The settle is a real time (700 ms of
|
|
972
|
+
the browser's `setTimeout`, not a `C_TIMER`): a wheel notch, a pinch and a drag
|
|
973
|
+
each fire `aftertransform` many times, and what is worth saving is where the
|
|
974
|
+
gesture ENDED. It fires for a PAN as well as a zoom — a pan is the reader's just
|
|
975
|
+
as much. `C_YUI_TREEDB_GRAPH` holds the other half: `camera` is one more
|
|
976
|
+
`SDF_PERSIST` attr of the view, saved under its name like `main_topic`, so the
|
|
977
|
+
graph opens where that treedb was left.
|
|
978
|
+
|
|
979
|
+
A host that does not care persists nothing and loses nothing: the event carries
|
|
980
|
+
`EVF_NO_WARN_SUBS`, and the attr defaults to empty.
|
|
981
|
+
|
|
950
982
|
### Moving the cards, and folding without losing your place
|
|
951
983
|
|
|
952
984
|
The JSON graph and the gobj tree take `drag-element`: a card can be dragged.
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -13420,12 +13420,11 @@ function icon_button$1(gobj, icon, event_name, label_key) {
|
|
|
13420
13420
|
* aimed — reaching the graph from the tree would mean passing
|
|
13421
13421
|
* through the text and rebuilding it on the way — so each view gets
|
|
13422
13422
|
* its own button and says where it goes. apply_view_mode() moves
|
|
13423
|
-
* the `
|
|
13424
|
-
*
|
|
13425
|
-
*
|
|
13426
|
-
*
|
|
13427
|
-
*
|
|
13428
|
-
* thing the library already had a name for.
|
|
13423
|
+
* the `selected_state` mark -- the library's one way of saying
|
|
13424
|
+
* WHICH segment of a selector is chosen, filled with the link
|
|
13425
|
+
* colour. It used to be `is-active` with a fill written here: the
|
|
13426
|
+
* right idea, spelled a third time. A toggle, which has no "which
|
|
13427
|
+
* one" to answer, wears the neutral `pressed_state` pill instead.
|
|
13429
13428
|
************************************************************/
|
|
13430
13429
|
function view_mode_switch$1(gobj) {
|
|
13431
13430
|
return [
|
|
@@ -13522,7 +13521,7 @@ function apply_view_mode$1(gobj) {
|
|
|
13522
13521
|
if ($el) $el.classList.toggle("is-hidden", mode !== "tree");
|
|
13523
13522
|
});
|
|
13524
13523
|
if (priv.$mode_btns) for (let [m, $btn] of priv.$mode_btns) {
|
|
13525
|
-
$btn.classList.toggle("
|
|
13524
|
+
$btn.classList.toggle("selected_state", m === mode);
|
|
13526
13525
|
$btn.setAttribute("aria-pressed", m === mode ? "true" : "false");
|
|
13527
13526
|
}
|
|
13528
13527
|
}
|
|
@@ -15829,7 +15828,7 @@ function apply_view_mode(gobj) {
|
|
|
15829
15828
|
};
|
|
15830
15829
|
for (let [m, $el] of Object.entries(bodies)) if ($el) $el.classList.toggle("is-hidden", m !== mode);
|
|
15831
15830
|
if (priv.$mode_btns) for (let [m, $btn] of priv.$mode_btns) {
|
|
15832
|
-
$btn.classList.toggle("
|
|
15831
|
+
$btn.classList.toggle("selected_state", m === mode);
|
|
15833
15832
|
$btn.setAttribute("aria-pressed", m === mode ? "true" : "false");
|
|
15834
15833
|
}
|
|
15835
15834
|
}
|
|
@@ -16582,7 +16581,7 @@ function machine_view_switch(gobj) {
|
|
|
16582
16581
|
return [
|
|
16583
16582
|
"button",
|
|
16584
16583
|
{
|
|
16585
|
-
class: `button is-small GCLASS_MACHINE_VIEW GCLASS_MACHINE_VIEW_${v.mode.toUpperCase()}` + (on ? "
|
|
16584
|
+
class: `button is-small GCLASS_MACHINE_VIEW GCLASS_MACHINE_VIEW_${v.mode.toUpperCase()}` + (on ? " selected_state" : ""),
|
|
16586
16585
|
type: "button",
|
|
16587
16586
|
"aria-pressed": on ? "true" : "false",
|
|
16588
16587
|
title: (0, i18next.t)(v.key),
|
|
@@ -54826,6 +54825,7 @@ var attrs_table$4 = [
|
|
|
54826
54825
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
|
|
54827
54826
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
|
|
54828
54827
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
|
|
54828
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "camera", 0, "{}", "Where the reader left the viewport: {zoom, x, y}. Empty = the graph opens fitted. Restored on the FIRST draw and published back as EV_CAMERA_CHANGED once a move settles, so a host can persist it"),
|
|
54829
54829
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
|
|
54830
54830
|
(0, _yuneta_gobj_js.SDATA_END)()
|
|
54831
54831
|
];
|
|
@@ -54833,6 +54833,9 @@ var PRIVATE_DATA$4 = {
|
|
|
54833
54833
|
anchor_id: "",
|
|
54834
54834
|
anchor_state: "off",
|
|
54835
54835
|
last_zoom: 1,
|
|
54836
|
+
_camera_placed: false,
|
|
54837
|
+
_camera_restoring: false,
|
|
54838
|
+
_camera_timer: null,
|
|
54836
54839
|
_xy: 100,
|
|
54837
54840
|
_edge_seq: 0,
|
|
54838
54841
|
_history_paused: false,
|
|
@@ -54964,8 +54967,13 @@ function mt_start$4(gobj) {
|
|
|
54964
54967
|
* Framework Method: Stop
|
|
54965
54968
|
***************************************************************/
|
|
54966
54969
|
function mt_stop$4(gobj) {
|
|
54970
|
+
let priv = gobj.priv;
|
|
54967
54971
|
let shell = yui_shell_of(gobj);
|
|
54968
54972
|
if (shell) (0, _yuneta_gobj_js.gobj_unsubscribe_event)(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
54973
|
+
if (priv._camera_timer) {
|
|
54974
|
+
clearTimeout(priv._camera_timer);
|
|
54975
|
+
priv._camera_timer = null;
|
|
54976
|
+
}
|
|
54969
54977
|
return 0;
|
|
54970
54978
|
}
|
|
54971
54979
|
/***************************************************************
|
|
@@ -55157,6 +55165,7 @@ function configure_events(gobj) {
|
|
|
55157
55165
|
update_link_icon_position(gobj);
|
|
55158
55166
|
update_port_icon_position(gobj);
|
|
55159
55167
|
update_zoom_readout(gobj);
|
|
55168
|
+
publish_camera_later(gobj);
|
|
55160
55169
|
let z = priv.last_zoom;
|
|
55161
55170
|
try {
|
|
55162
55171
|
z = priv.graph.getZoom();
|
|
@@ -56682,6 +56691,80 @@ async function graph_fitview(gobj) {
|
|
|
56682
56691
|
}
|
|
56683
56692
|
var MIN_READABLE_ZOOM = .5;
|
|
56684
56693
|
/************************************************************
|
|
56694
|
+
* THE CAMERA IS THE READER'S.
|
|
56695
|
+
*
|
|
56696
|
+
* Once somebody has chosen a zoom, nothing but a camera
|
|
56697
|
+
* command may change it -- not a refresh, not a change of node
|
|
56698
|
+
* mode, not a new main topic. Those rebuild the CONTENT, and
|
|
56699
|
+
* the content moving is no reason to move the reader.
|
|
56700
|
+
*
|
|
56701
|
+
* Two mechanisms, because they answer two different questions:
|
|
56702
|
+
*
|
|
56703
|
+
* - across a rebuild in the same view, the anchor pair
|
|
56704
|
+
* (`yui_graph_viewport_of` / `yui_graph_place_at`) keeps a
|
|
56705
|
+
* NODE at the pixel it was on, which survives a relayout
|
|
56706
|
+
* that moves everything;
|
|
56707
|
+
* - across a RELOAD there is no node to hold on to yet, so
|
|
56708
|
+
* the viewport itself is restored -- zoom and position, as
|
|
56709
|
+
* G6 reports them.
|
|
56710
|
+
************************************************************/
|
|
56711
|
+
var CAMERA_SETTLE_MS = 700;
|
|
56712
|
+
function read_camera(gobj) {
|
|
56713
|
+
let graph = gobj.priv.graph;
|
|
56714
|
+
if (!graph || typeof graph.getZoom !== "function" || typeof graph.getPosition !== "function") return null;
|
|
56715
|
+
try {
|
|
56716
|
+
let z = graph.getZoom();
|
|
56717
|
+
let p = graph.getPosition();
|
|
56718
|
+
if (!(0, _yuneta_gobj_js.is_number)(z) || !p || !(0, _yuneta_gobj_js.is_number)(p[0]) || !(0, _yuneta_gobj_js.is_number)(p[1])) return null;
|
|
56719
|
+
return {
|
|
56720
|
+
zoom: z,
|
|
56721
|
+
x: p[0],
|
|
56722
|
+
y: p[1]
|
|
56723
|
+
};
|
|
56724
|
+
} catch (e) {
|
|
56725
|
+
return null;
|
|
56726
|
+
}
|
|
56727
|
+
}
|
|
56728
|
+
async function restore_camera(gobj) {
|
|
56729
|
+
let priv = gobj.priv;
|
|
56730
|
+
let graph = priv.graph;
|
|
56731
|
+
let cam = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "camera");
|
|
56732
|
+
if (!graph || (0, _yuneta_gobj_js.empty_json)(cam) || !(0, _yuneta_gobj_js.is_number)(cam.zoom)) return false;
|
|
56733
|
+
priv._camera_restoring = true;
|
|
56734
|
+
try {
|
|
56735
|
+
await graph.zoomTo(cam.zoom);
|
|
56736
|
+
if ((0, _yuneta_gobj_js.is_number)(cam.x) && (0, _yuneta_gobj_js.is_number)(cam.y)) await graph.translateTo([cam.x, cam.y]);
|
|
56737
|
+
return true;
|
|
56738
|
+
} catch (e) {
|
|
56739
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: cannot restore the camera: ${e}`);
|
|
56740
|
+
return false;
|
|
56741
|
+
} finally {
|
|
56742
|
+
priv._camera_restoring = false;
|
|
56743
|
+
}
|
|
56744
|
+
}
|
|
56745
|
+
/************************************************************
|
|
56746
|
+
* A move settled: tell whoever persists it.
|
|
56747
|
+
*
|
|
56748
|
+
* Debounced on a REAL time and with the browser's timer: a
|
|
56749
|
+
* wheel notch, a pinch and a drag each fire `aftertransform`
|
|
56750
|
+
* many times, and what is worth saving is where the gesture
|
|
56751
|
+
* ENDED. (`set_timeout` from gobj-js is a C_TIMER and shadows
|
|
56752
|
+
* the global -- this module imports neither.)
|
|
56753
|
+
************************************************************/
|
|
56754
|
+
function publish_camera_later(gobj) {
|
|
56755
|
+
let priv = gobj.priv;
|
|
56756
|
+
if (priv._camera_restoring || !priv._camera_placed) return;
|
|
56757
|
+
if (priv._camera_timer) clearTimeout(priv._camera_timer);
|
|
56758
|
+
priv._camera_timer = setTimeout(() => {
|
|
56759
|
+
priv._camera_timer = null;
|
|
56760
|
+
if ((0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
|
|
56761
|
+
let cam = read_camera(gobj);
|
|
56762
|
+
if (!cam) return;
|
|
56763
|
+
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "camera", cam);
|
|
56764
|
+
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_CAMERA_CHANGED", cam);
|
|
56765
|
+
}, CAMERA_SETTLE_MS);
|
|
56766
|
+
}
|
|
56767
|
+
/************************************************************
|
|
56685
56768
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
56686
56769
|
* where nothing on a card can be read.
|
|
56687
56770
|
*
|
|
@@ -60625,8 +60708,11 @@ async function reconcile_fold_apply(gobj, opts, visible) {
|
|
|
60625
60708
|
}
|
|
60626
60709
|
await graph_draw(gobj);
|
|
60627
60710
|
if (changed || opts.relayout) await graph_layout(gobj);
|
|
60628
|
-
if (opts.
|
|
60629
|
-
|
|
60711
|
+
if (!(opts.restore && await restore_camera(gobj))) {
|
|
60712
|
+
if (opts.fit) await graph_fit_readable(gobj);
|
|
60713
|
+
else if (opts.keep && opts.keep_vp) await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
|
|
60714
|
+
}
|
|
60715
|
+
priv._camera_placed = true;
|
|
60630
60716
|
update_resize_handles_position(gobj);
|
|
60631
60717
|
update_port_resize_handles_position(gobj);
|
|
60632
60718
|
update_edge_icon_position(gobj);
|
|
@@ -60778,10 +60864,19 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
60778
60864
|
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: auto_layout failed: ${e}`);
|
|
60779
60865
|
}
|
|
60780
60866
|
apply_layout_direction(gobj);
|
|
60781
|
-
|
|
60782
|
-
|
|
60867
|
+
let vp_opts;
|
|
60868
|
+
if (!priv._camera_placed) vp_opts = {
|
|
60869
|
+
restore: true,
|
|
60783
60870
|
fit: auto_laid
|
|
60784
|
-
}
|
|
60871
|
+
};
|
|
60872
|
+
else {
|
|
60873
|
+
let keep = fold_keep_node(gobj);
|
|
60874
|
+
vp_opts = {
|
|
60875
|
+
keep,
|
|
60876
|
+
keep_vp: yui_graph_viewport_of(priv.graph, keep)
|
|
60877
|
+
};
|
|
60878
|
+
}
|
|
60879
|
+
reconcile_fold(gobj, Object.assign({ relayout: true }, vp_opts)).then(() => {
|
|
60785
60880
|
sync_history_plugin(gobj);
|
|
60786
60881
|
if (priv._pending_focus_topic !== null) {
|
|
60787
60882
|
let ft = priv._pending_focus_topic;
|
|
@@ -61241,11 +61336,14 @@ function ac_set_main_topic(gobj, event, kw, src) {
|
|
|
61241
61336
|
let priv = gobj.priv;
|
|
61242
61337
|
(0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "main_topic", kw && kw.topic || "");
|
|
61243
61338
|
if (!priv._fold_model) return 0;
|
|
61339
|
+
let keep = fold_keep_node(gobj);
|
|
61340
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61244
61341
|
rebuild_fold_model(gobj);
|
|
61245
61342
|
fold_expand_to_depth(priv._fold_model, priv._fold_state, (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"));
|
|
61246
61343
|
reconcile_fold(gobj, {
|
|
61247
61344
|
relayout: true,
|
|
61248
|
-
|
|
61345
|
+
keep,
|
|
61346
|
+
keep_vp: vp
|
|
61249
61347
|
});
|
|
61250
61348
|
return 0;
|
|
61251
61349
|
}
|
|
@@ -61271,11 +61369,14 @@ function ac_set_node_mode$1(gobj, event, kw, src) {
|
|
|
61271
61369
|
(0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "node_mode", mode);
|
|
61272
61370
|
priv._open_nodes = /* @__PURE__ */ new Set();
|
|
61273
61371
|
if (!priv.graph || !priv._fold_model) return 0;
|
|
61372
|
+
let keep = fold_keep_node(gobj);
|
|
61373
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61274
61374
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
61275
61375
|
reshape_more_chips(gobj);
|
|
61276
61376
|
reconcile_fold(gobj, {
|
|
61277
61377
|
relayout: true,
|
|
61278
|
-
|
|
61378
|
+
keep,
|
|
61379
|
+
keep_vp: vp
|
|
61279
61380
|
});
|
|
61280
61381
|
return 0;
|
|
61281
61382
|
}
|
|
@@ -61945,6 +62046,7 @@ function create_gclass$4(gclass_name) {
|
|
|
61945
62046
|
["EV_FIND_NODES", 0],
|
|
61946
62047
|
["EV_FIND_RESULT", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
61947
62048
|
["EV_LAYOUT_AUTOSET", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
62049
|
+
["EV_CAMERA_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
61948
62050
|
["EV_TOGGLE_FOLD", 0],
|
|
61949
62051
|
["EV_SHOW_MORE", 0],
|
|
61950
62052
|
["EV_EXPAND_ALL", 0],
|
|
@@ -62031,6 +62133,7 @@ var attrs_table$3 = [
|
|
|
62031
62133
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_LIST, "loose_topics", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
|
|
62032
62134
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "node_mode", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their pills and ports, `compact` = one-line pills with the name, `shape` = coloured figures, the topology alone. User preference"),
|
|
62033
62135
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "node_labels", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
|
|
62136
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "camera", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "{}", "Where the reader left the viewport: {zoom, x, y}. Handed to the engine on creation and written back when a move settles, so the graph opens where it was left. User preference, per treedb"),
|
|
62034
62137
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
62035
62138
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
62036
62139
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
|
|
@@ -62118,7 +62221,8 @@ function mt_create$3(gobj) {
|
|
|
62118
62221
|
main_topic: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "main_topic") || "",
|
|
62119
62222
|
loose_topics: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "loose_topics") || [],
|
|
62120
62223
|
node_mode: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "node_mode") || "expanded",
|
|
62121
|
-
node_labels: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels")
|
|
62224
|
+
node_labels: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels"),
|
|
62225
|
+
camera: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "camera") || {}
|
|
62122
62226
|
}, gobj);
|
|
62123
62227
|
populate_nodes_tree_options(gobj);
|
|
62124
62228
|
if (priv.with_treedb_tables) {}
|
|
@@ -62402,7 +62506,7 @@ function refresh_node_shape_buttons(gobj, $root) {
|
|
|
62402
62506
|
let labels = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels");
|
|
62403
62507
|
for (let $btn of $root.querySelectorAll(".GRAPH_NODE_MODE_BTN")) {
|
|
62404
62508
|
let on = $btn.getAttribute("data-mode") === mode;
|
|
62405
|
-
$btn.classList.toggle("
|
|
62509
|
+
$btn.classList.toggle("selected_state", on);
|
|
62406
62510
|
$btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
62407
62511
|
}
|
|
62408
62512
|
set_pressed_state($root, ".GRAPH_NODE_LABELS", labels);
|
|
@@ -63802,6 +63906,23 @@ function ac_layout_autoset(gobj, event, kw, src) {
|
|
|
63802
63906
|
return 0;
|
|
63803
63907
|
}
|
|
63804
63908
|
/************************************************************
|
|
63909
|
+
* The reader moved the camera, and it has settled.
|
|
63910
|
+
*
|
|
63911
|
+
* Persisted like every other choice of this toolbar, and for the
|
|
63912
|
+
* same reason: a zoom is a decision, and a decision that dies with
|
|
63913
|
+
* the tab has to be taken again every morning. The engine debounces,
|
|
63914
|
+
* so this runs once per gesture and not once per wheel notch.
|
|
63915
|
+
************************************************************/
|
|
63916
|
+
function ac_camera_changed(gobj, event, kw, src) {
|
|
63917
|
+
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "camera", {
|
|
63918
|
+
zoom: kw.zoom,
|
|
63919
|
+
x: kw.x,
|
|
63920
|
+
y: kw.y
|
|
63921
|
+
});
|
|
63922
|
+
(0, _yuneta_gobj_js.gobj_save_persistent_attrs)(gobj, "camera");
|
|
63923
|
+
return 0;
|
|
63924
|
+
}
|
|
63925
|
+
/************************************************************
|
|
63805
63926
|
* Forward the find down to the graph child.
|
|
63806
63927
|
************************************************************/
|
|
63807
63928
|
function ac_find_nodes(gobj, event, kw, src) {
|
|
@@ -64081,6 +64202,11 @@ function create_gclass$3(gclass_name) {
|
|
|
64081
64202
|
ac_layout_autoset,
|
|
64082
64203
|
null
|
|
64083
64204
|
],
|
|
64205
|
+
[
|
|
64206
|
+
"EV_CAMERA_CHANGED",
|
|
64207
|
+
ac_camera_changed,
|
|
64208
|
+
null
|
|
64209
|
+
],
|
|
64084
64210
|
[
|
|
64085
64211
|
"EV_LEGEND_TOPIC",
|
|
64086
64212
|
ac_legend_topic,
|
|
@@ -64149,6 +64275,7 @@ function create_gclass$3(gclass_name) {
|
|
|
64149
64275
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
64150
64276
|
["EV_FIND_RESULT", 0],
|
|
64151
64277
|
["EV_LAYOUT_AUTOSET", 0],
|
|
64278
|
+
["EV_CAMERA_CHANGED", 0],
|
|
64152
64279
|
["EV_LEGEND_TOPIC", 0],
|
|
64153
64280
|
["EV_LEGEND_STATE", 0],
|
|
64154
64281
|
["EV_TOPIC_SELECTED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
@@ -71061,12 +71188,12 @@ function repaint(gobj) {
|
|
|
71061
71188
|
if (on) m.$btn.setAttribute("aria-current", "true");
|
|
71062
71189
|
else m.$btn.removeAttribute("aria-current");
|
|
71063
71190
|
} else {
|
|
71064
|
-
m.$btn.classList.toggle("
|
|
71191
|
+
m.$btn.classList.toggle("selected_state", on);
|
|
71065
71192
|
m.$btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
71066
71193
|
}
|
|
71067
71194
|
}
|
|
71068
71195
|
if (mode && mode.$btn) scroll_mode_into_view(gobj, mode.$btn);
|
|
71069
|
-
if (priv.$more) priv.$more.classList.toggle("
|
|
71196
|
+
if (priv.$more) priv.$more.classList.toggle("selected_state", !!(mode && mode.overflow));
|
|
71070
71197
|
let is_bucket = !!(mode && mode.kind === "bucket");
|
|
71071
71198
|
priv.$nav.classList.toggle("yui-period-nav-off", !is_bucket);
|
|
71072
71199
|
priv.$label.disabled = !is_bucket;
|
|
@@ -71257,7 +71384,7 @@ function cell_button(gobj, label, anchor_ms, selected, logical, aria) {
|
|
|
71257
71384
|
let $btn = (0, _yuneta_gobj_js.createElement2)([
|
|
71258
71385
|
"button",
|
|
71259
71386
|
{
|
|
71260
|
-
class: `button is-small ${selected ? "
|
|
71387
|
+
class: `button is-small ${selected ? "selected_state" : "is-ghost"} ${logical}`,
|
|
71261
71388
|
type: "button",
|
|
71262
71389
|
style: "width:100%;",
|
|
71263
71390
|
...selected ? { "aria-current": "date" } : {},
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as gobj_js from "@yuneta/gobj-js";
|
|
2
|
-
import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, delete_from_list, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_bottom_gobj, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_name, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
|
|
2
|
+
import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, delete_from_list, duplicate_objects, empty_json, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_bottom_gobj, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_name, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
|
|
3
3
|
import i18next, { t } from "i18next";
|
|
4
4
|
import TomSelect from "tom-select";
|
|
5
5
|
import { createJSONEditor } from "vanilla-jsoneditor";
|
|
@@ -13415,12 +13415,11 @@ function icon_button$1(gobj, icon, event_name, label_key) {
|
|
|
13415
13415
|
* aimed — reaching the graph from the tree would mean passing
|
|
13416
13416
|
* through the text and rebuilding it on the way — so each view gets
|
|
13417
13417
|
* its own button and says where it goes. apply_view_mode() moves
|
|
13418
|
-
* the `
|
|
13419
|
-
*
|
|
13420
|
-
*
|
|
13421
|
-
*
|
|
13422
|
-
*
|
|
13423
|
-
* thing the library already had a name for.
|
|
13418
|
+
* the `selected_state` mark -- the library's one way of saying
|
|
13419
|
+
* WHICH segment of a selector is chosen, filled with the link
|
|
13420
|
+
* colour. It used to be `is-active` with a fill written here: the
|
|
13421
|
+
* right idea, spelled a third time. A toggle, which has no "which
|
|
13422
|
+
* one" to answer, wears the neutral `pressed_state` pill instead.
|
|
13424
13423
|
************************************************************/
|
|
13425
13424
|
function view_mode_switch$1(gobj) {
|
|
13426
13425
|
return [
|
|
@@ -13517,7 +13516,7 @@ function apply_view_mode$1(gobj) {
|
|
|
13517
13516
|
if ($el) $el.classList.toggle("is-hidden", mode !== "tree");
|
|
13518
13517
|
});
|
|
13519
13518
|
if (priv.$mode_btns) for (let [m, $btn] of priv.$mode_btns) {
|
|
13520
|
-
$btn.classList.toggle("
|
|
13519
|
+
$btn.classList.toggle("selected_state", m === mode);
|
|
13521
13520
|
$btn.setAttribute("aria-pressed", m === mode ? "true" : "false");
|
|
13522
13521
|
}
|
|
13523
13522
|
}
|
|
@@ -15824,7 +15823,7 @@ function apply_view_mode(gobj) {
|
|
|
15824
15823
|
};
|
|
15825
15824
|
for (let [m, $el] of Object.entries(bodies)) if ($el) $el.classList.toggle("is-hidden", m !== mode);
|
|
15826
15825
|
if (priv.$mode_btns) for (let [m, $btn] of priv.$mode_btns) {
|
|
15827
|
-
$btn.classList.toggle("
|
|
15826
|
+
$btn.classList.toggle("selected_state", m === mode);
|
|
15828
15827
|
$btn.setAttribute("aria-pressed", m === mode ? "true" : "false");
|
|
15829
15828
|
}
|
|
15830
15829
|
}
|
|
@@ -16577,7 +16576,7 @@ function machine_view_switch(gobj) {
|
|
|
16577
16576
|
return [
|
|
16578
16577
|
"button",
|
|
16579
16578
|
{
|
|
16580
|
-
class: `button is-small GCLASS_MACHINE_VIEW GCLASS_MACHINE_VIEW_${v.mode.toUpperCase()}` + (on ? "
|
|
16579
|
+
class: `button is-small GCLASS_MACHINE_VIEW GCLASS_MACHINE_VIEW_${v.mode.toUpperCase()}` + (on ? " selected_state" : ""),
|
|
16581
16580
|
type: "button",
|
|
16582
16581
|
"aria-pressed": on ? "true" : "false",
|
|
16583
16582
|
title: t(v.key),
|
|
@@ -54821,6 +54820,7 @@ var attrs_table$4 = [
|
|
|
54821
54820
|
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"),
|
|
54822
54821
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
|
|
54823
54822
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
|
|
54823
|
+
SDATA(data_type_t.DTP_DICT, "camera", 0, "{}", "Where the reader left the viewport: {zoom, x, y}. Empty = the graph opens fitted. Restored on the FIRST draw and published back as EV_CAMERA_CHANGED once a move settles, so a host can persist it"),
|
|
54824
54824
|
SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
|
|
54825
54825
|
SDATA_END()
|
|
54826
54826
|
];
|
|
@@ -54828,6 +54828,9 @@ var PRIVATE_DATA$4 = {
|
|
|
54828
54828
|
anchor_id: "",
|
|
54829
54829
|
anchor_state: "off",
|
|
54830
54830
|
last_zoom: 1,
|
|
54831
|
+
_camera_placed: false,
|
|
54832
|
+
_camera_restoring: false,
|
|
54833
|
+
_camera_timer: null,
|
|
54831
54834
|
_xy: 100,
|
|
54832
54835
|
_edge_seq: 0,
|
|
54833
54836
|
_history_paused: false,
|
|
@@ -54959,8 +54962,13 @@ function mt_start$4(gobj) {
|
|
|
54959
54962
|
* Framework Method: Stop
|
|
54960
54963
|
***************************************************************/
|
|
54961
54964
|
function mt_stop$4(gobj) {
|
|
54965
|
+
let priv = gobj.priv;
|
|
54962
54966
|
let shell = yui_shell_of(gobj);
|
|
54963
54967
|
if (shell) gobj_unsubscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
54968
|
+
if (priv._camera_timer) {
|
|
54969
|
+
clearTimeout(priv._camera_timer);
|
|
54970
|
+
priv._camera_timer = null;
|
|
54971
|
+
}
|
|
54964
54972
|
return 0;
|
|
54965
54973
|
}
|
|
54966
54974
|
/***************************************************************
|
|
@@ -55152,6 +55160,7 @@ function configure_events(gobj) {
|
|
|
55152
55160
|
update_link_icon_position(gobj);
|
|
55153
55161
|
update_port_icon_position(gobj);
|
|
55154
55162
|
update_zoom_readout(gobj);
|
|
55163
|
+
publish_camera_later(gobj);
|
|
55155
55164
|
let z = priv.last_zoom;
|
|
55156
55165
|
try {
|
|
55157
55166
|
z = priv.graph.getZoom();
|
|
@@ -56677,6 +56686,80 @@ async function graph_fitview(gobj) {
|
|
|
56677
56686
|
}
|
|
56678
56687
|
var MIN_READABLE_ZOOM = .5;
|
|
56679
56688
|
/************************************************************
|
|
56689
|
+
* THE CAMERA IS THE READER'S.
|
|
56690
|
+
*
|
|
56691
|
+
* Once somebody has chosen a zoom, nothing but a camera
|
|
56692
|
+
* command may change it -- not a refresh, not a change of node
|
|
56693
|
+
* mode, not a new main topic. Those rebuild the CONTENT, and
|
|
56694
|
+
* the content moving is no reason to move the reader.
|
|
56695
|
+
*
|
|
56696
|
+
* Two mechanisms, because they answer two different questions:
|
|
56697
|
+
*
|
|
56698
|
+
* - across a rebuild in the same view, the anchor pair
|
|
56699
|
+
* (`yui_graph_viewport_of` / `yui_graph_place_at`) keeps a
|
|
56700
|
+
* NODE at the pixel it was on, which survives a relayout
|
|
56701
|
+
* that moves everything;
|
|
56702
|
+
* - across a RELOAD there is no node to hold on to yet, so
|
|
56703
|
+
* the viewport itself is restored -- zoom and position, as
|
|
56704
|
+
* G6 reports them.
|
|
56705
|
+
************************************************************/
|
|
56706
|
+
var CAMERA_SETTLE_MS = 700;
|
|
56707
|
+
function read_camera(gobj) {
|
|
56708
|
+
let graph = gobj.priv.graph;
|
|
56709
|
+
if (!graph || typeof graph.getZoom !== "function" || typeof graph.getPosition !== "function") return null;
|
|
56710
|
+
try {
|
|
56711
|
+
let z = graph.getZoom();
|
|
56712
|
+
let p = graph.getPosition();
|
|
56713
|
+
if (!is_number(z) || !p || !is_number(p[0]) || !is_number(p[1])) return null;
|
|
56714
|
+
return {
|
|
56715
|
+
zoom: z,
|
|
56716
|
+
x: p[0],
|
|
56717
|
+
y: p[1]
|
|
56718
|
+
};
|
|
56719
|
+
} catch (e) {
|
|
56720
|
+
return null;
|
|
56721
|
+
}
|
|
56722
|
+
}
|
|
56723
|
+
async function restore_camera(gobj) {
|
|
56724
|
+
let priv = gobj.priv;
|
|
56725
|
+
let graph = priv.graph;
|
|
56726
|
+
let cam = gobj_read_attr(gobj, "camera");
|
|
56727
|
+
if (!graph || empty_json(cam) || !is_number(cam.zoom)) return false;
|
|
56728
|
+
priv._camera_restoring = true;
|
|
56729
|
+
try {
|
|
56730
|
+
await graph.zoomTo(cam.zoom);
|
|
56731
|
+
if (is_number(cam.x) && is_number(cam.y)) await graph.translateTo([cam.x, cam.y]);
|
|
56732
|
+
return true;
|
|
56733
|
+
} catch (e) {
|
|
56734
|
+
log_error(`${gobj_short_name(gobj)}: cannot restore the camera: ${e}`);
|
|
56735
|
+
return false;
|
|
56736
|
+
} finally {
|
|
56737
|
+
priv._camera_restoring = false;
|
|
56738
|
+
}
|
|
56739
|
+
}
|
|
56740
|
+
/************************************************************
|
|
56741
|
+
* A move settled: tell whoever persists it.
|
|
56742
|
+
*
|
|
56743
|
+
* Debounced on a REAL time and with the browser's timer: a
|
|
56744
|
+
* wheel notch, a pinch and a drag each fire `aftertransform`
|
|
56745
|
+
* many times, and what is worth saving is where the gesture
|
|
56746
|
+
* ENDED. (`set_timeout` from gobj-js is a C_TIMER and shadows
|
|
56747
|
+
* the global -- this module imports neither.)
|
|
56748
|
+
************************************************************/
|
|
56749
|
+
function publish_camera_later(gobj) {
|
|
56750
|
+
let priv = gobj.priv;
|
|
56751
|
+
if (priv._camera_restoring || !priv._camera_placed) return;
|
|
56752
|
+
if (priv._camera_timer) clearTimeout(priv._camera_timer);
|
|
56753
|
+
priv._camera_timer = setTimeout(() => {
|
|
56754
|
+
priv._camera_timer = null;
|
|
56755
|
+
if (gobj_is_destroying(gobj)) return;
|
|
56756
|
+
let cam = read_camera(gobj);
|
|
56757
|
+
if (!cam) return;
|
|
56758
|
+
gobj_write_attr(gobj, "camera", cam);
|
|
56759
|
+
gobj_publish_event(gobj, "EV_CAMERA_CHANGED", cam);
|
|
56760
|
+
}, CAMERA_SETTLE_MS);
|
|
56761
|
+
}
|
|
56762
|
+
/************************************************************
|
|
56680
56763
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
56681
56764
|
* where nothing on a card can be read.
|
|
56682
56765
|
*
|
|
@@ -60620,8 +60703,11 @@ async function reconcile_fold_apply(gobj, opts, visible) {
|
|
|
60620
60703
|
}
|
|
60621
60704
|
await graph_draw(gobj);
|
|
60622
60705
|
if (changed || opts.relayout) await graph_layout(gobj);
|
|
60623
|
-
if (opts.
|
|
60624
|
-
|
|
60706
|
+
if (!(opts.restore && await restore_camera(gobj))) {
|
|
60707
|
+
if (opts.fit) await graph_fit_readable(gobj);
|
|
60708
|
+
else if (opts.keep && opts.keep_vp) await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
|
|
60709
|
+
}
|
|
60710
|
+
priv._camera_placed = true;
|
|
60625
60711
|
update_resize_handles_position(gobj);
|
|
60626
60712
|
update_port_resize_handles_position(gobj);
|
|
60627
60713
|
update_edge_icon_position(gobj);
|
|
@@ -60773,10 +60859,19 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
60773
60859
|
log_error(`${gobj_short_name(gobj)}: auto_layout failed: ${e}`);
|
|
60774
60860
|
}
|
|
60775
60861
|
apply_layout_direction(gobj);
|
|
60776
|
-
|
|
60777
|
-
|
|
60862
|
+
let vp_opts;
|
|
60863
|
+
if (!priv._camera_placed) vp_opts = {
|
|
60864
|
+
restore: true,
|
|
60778
60865
|
fit: auto_laid
|
|
60779
|
-
}
|
|
60866
|
+
};
|
|
60867
|
+
else {
|
|
60868
|
+
let keep = fold_keep_node(gobj);
|
|
60869
|
+
vp_opts = {
|
|
60870
|
+
keep,
|
|
60871
|
+
keep_vp: yui_graph_viewport_of(priv.graph, keep)
|
|
60872
|
+
};
|
|
60873
|
+
}
|
|
60874
|
+
reconcile_fold(gobj, Object.assign({ relayout: true }, vp_opts)).then(() => {
|
|
60780
60875
|
sync_history_plugin(gobj);
|
|
60781
60876
|
if (priv._pending_focus_topic !== null) {
|
|
60782
60877
|
let ft = priv._pending_focus_topic;
|
|
@@ -61236,11 +61331,14 @@ function ac_set_main_topic(gobj, event, kw, src) {
|
|
|
61236
61331
|
let priv = gobj.priv;
|
|
61237
61332
|
gobj_write_str_attr(gobj, "main_topic", kw && kw.topic || "");
|
|
61238
61333
|
if (!priv._fold_model) return 0;
|
|
61334
|
+
let keep = fold_keep_node(gobj);
|
|
61335
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61239
61336
|
rebuild_fold_model(gobj);
|
|
61240
61337
|
fold_expand_to_depth(priv._fold_model, priv._fold_state, gobj_read_integer_attr(gobj, "expand_depth"));
|
|
61241
61338
|
reconcile_fold(gobj, {
|
|
61242
61339
|
relayout: true,
|
|
61243
|
-
|
|
61340
|
+
keep,
|
|
61341
|
+
keep_vp: vp
|
|
61244
61342
|
});
|
|
61245
61343
|
return 0;
|
|
61246
61344
|
}
|
|
@@ -61266,11 +61364,14 @@ function ac_set_node_mode$1(gobj, event, kw, src) {
|
|
|
61266
61364
|
gobj_write_str_attr(gobj, "node_mode", mode);
|
|
61267
61365
|
priv._open_nodes = /* @__PURE__ */ new Set();
|
|
61268
61366
|
if (!priv.graph || !priv._fold_model) return 0;
|
|
61367
|
+
let keep = fold_keep_node(gobj);
|
|
61368
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61269
61369
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
61270
61370
|
reshape_more_chips(gobj);
|
|
61271
61371
|
reconcile_fold(gobj, {
|
|
61272
61372
|
relayout: true,
|
|
61273
|
-
|
|
61373
|
+
keep,
|
|
61374
|
+
keep_vp: vp
|
|
61274
61375
|
});
|
|
61275
61376
|
return 0;
|
|
61276
61377
|
}
|
|
@@ -61940,6 +62041,7 @@ function create_gclass$4(gclass_name) {
|
|
|
61940
62041
|
["EV_FIND_NODES", 0],
|
|
61941
62042
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
|
61942
62043
|
["EV_LAYOUT_AUTOSET", event_flag_t.EVF_OUTPUT_EVENT],
|
|
62044
|
+
["EV_CAMERA_CHANGED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
61943
62045
|
["EV_TOGGLE_FOLD", 0],
|
|
61944
62046
|
["EV_SHOW_MORE", 0],
|
|
61945
62047
|
["EV_EXPAND_ALL", 0],
|
|
@@ -62026,6 +62128,7 @@ var attrs_table$3 = [
|
|
|
62026
62128
|
SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
|
|
62027
62129
|
SDATA(data_type_t.DTP_STRING, "node_mode", sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their pills and ports, `compact` = one-line pills with the name, `shape` = coloured figures, the topology alone. User preference"),
|
|
62028
62130
|
SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
|
|
62131
|
+
SDATA(data_type_t.DTP_DICT, "camera", sdata_flag_t.SDF_PERSIST, "{}", "Where the reader left the viewport: {zoom, x, y}. Handed to the engine on creation and written back when a move settles, so the graph opens where it was left. User preference, per treedb"),
|
|
62029
62132
|
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
62030
62133
|
SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
62031
62134
|
SDATA(data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
|
|
@@ -62113,7 +62216,8 @@ function mt_create$3(gobj) {
|
|
|
62113
62216
|
main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
|
|
62114
62217
|
loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
|
|
62115
62218
|
node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
|
|
62116
|
-
node_labels: gobj_read_bool_attr(gobj, "node_labels")
|
|
62219
|
+
node_labels: gobj_read_bool_attr(gobj, "node_labels"),
|
|
62220
|
+
camera: gobj_read_attr(gobj, "camera") || {}
|
|
62117
62221
|
}, gobj);
|
|
62118
62222
|
populate_nodes_tree_options(gobj);
|
|
62119
62223
|
if (priv.with_treedb_tables) {}
|
|
@@ -62397,7 +62501,7 @@ function refresh_node_shape_buttons(gobj, $root) {
|
|
|
62397
62501
|
let labels = gobj_read_bool_attr(gobj, "node_labels");
|
|
62398
62502
|
for (let $btn of $root.querySelectorAll(".GRAPH_NODE_MODE_BTN")) {
|
|
62399
62503
|
let on = $btn.getAttribute("data-mode") === mode;
|
|
62400
|
-
$btn.classList.toggle("
|
|
62504
|
+
$btn.classList.toggle("selected_state", on);
|
|
62401
62505
|
$btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
62402
62506
|
}
|
|
62403
62507
|
set_pressed_state($root, ".GRAPH_NODE_LABELS", labels);
|
|
@@ -63797,6 +63901,23 @@ function ac_layout_autoset(gobj, event, kw, src) {
|
|
|
63797
63901
|
return 0;
|
|
63798
63902
|
}
|
|
63799
63903
|
/************************************************************
|
|
63904
|
+
* The reader moved the camera, and it has settled.
|
|
63905
|
+
*
|
|
63906
|
+
* Persisted like every other choice of this toolbar, and for the
|
|
63907
|
+
* same reason: a zoom is a decision, and a decision that dies with
|
|
63908
|
+
* the tab has to be taken again every morning. The engine debounces,
|
|
63909
|
+
* so this runs once per gesture and not once per wheel notch.
|
|
63910
|
+
************************************************************/
|
|
63911
|
+
function ac_camera_changed(gobj, event, kw, src) {
|
|
63912
|
+
gobj_write_attr(gobj, "camera", {
|
|
63913
|
+
zoom: kw.zoom,
|
|
63914
|
+
x: kw.x,
|
|
63915
|
+
y: kw.y
|
|
63916
|
+
});
|
|
63917
|
+
gobj_save_persistent_attrs(gobj, "camera");
|
|
63918
|
+
return 0;
|
|
63919
|
+
}
|
|
63920
|
+
/************************************************************
|
|
63800
63921
|
* Forward the find down to the graph child.
|
|
63801
63922
|
************************************************************/
|
|
63802
63923
|
function ac_find_nodes(gobj, event, kw, src) {
|
|
@@ -64076,6 +64197,11 @@ function create_gclass$3(gclass_name) {
|
|
|
64076
64197
|
ac_layout_autoset,
|
|
64077
64198
|
null
|
|
64078
64199
|
],
|
|
64200
|
+
[
|
|
64201
|
+
"EV_CAMERA_CHANGED",
|
|
64202
|
+
ac_camera_changed,
|
|
64203
|
+
null
|
|
64204
|
+
],
|
|
64079
64205
|
[
|
|
64080
64206
|
"EV_LEGEND_TOPIC",
|
|
64081
64207
|
ac_legend_topic,
|
|
@@ -64144,6 +64270,7 @@ function create_gclass$3(gclass_name) {
|
|
|
64144
64270
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
64145
64271
|
["EV_FIND_RESULT", 0],
|
|
64146
64272
|
["EV_LAYOUT_AUTOSET", 0],
|
|
64273
|
+
["EV_CAMERA_CHANGED", 0],
|
|
64147
64274
|
["EV_LEGEND_TOPIC", 0],
|
|
64148
64275
|
["EV_LEGEND_STATE", 0],
|
|
64149
64276
|
["EV_TOPIC_SELECTED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
@@ -71056,12 +71183,12 @@ function repaint(gobj) {
|
|
|
71056
71183
|
if (on) m.$btn.setAttribute("aria-current", "true");
|
|
71057
71184
|
else m.$btn.removeAttribute("aria-current");
|
|
71058
71185
|
} else {
|
|
71059
|
-
m.$btn.classList.toggle("
|
|
71186
|
+
m.$btn.classList.toggle("selected_state", on);
|
|
71060
71187
|
m.$btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
71061
71188
|
}
|
|
71062
71189
|
}
|
|
71063
71190
|
if (mode && mode.$btn) scroll_mode_into_view(gobj, mode.$btn);
|
|
71064
|
-
if (priv.$more) priv.$more.classList.toggle("
|
|
71191
|
+
if (priv.$more) priv.$more.classList.toggle("selected_state", !!(mode && mode.overflow));
|
|
71065
71192
|
let is_bucket = !!(mode && mode.kind === "bucket");
|
|
71066
71193
|
priv.$nav.classList.toggle("yui-period-nav-off", !is_bucket);
|
|
71067
71194
|
priv.$label.disabled = !is_bucket;
|
|
@@ -71252,7 +71379,7 @@ function cell_button(gobj, label, anchor_ms, selected, logical, aria) {
|
|
|
71252
71379
|
let $btn = createElement2([
|
|
71253
71380
|
"button",
|
|
71254
71381
|
{
|
|
71255
|
-
class: `button is-small ${selected ? "
|
|
71382
|
+
class: `button is-small ${selected ? "selected_state" : "is-ghost"} ${logical}`,
|
|
71256
71383
|
type: "button",
|
|
71257
71384
|
style: "width:100%;",
|
|
71258
71385
|
...selected ? { "aria-current": "date" } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuneta/gobj-ui",
|
|
3
|
-
"version": "7.23.
|
|
3
|
+
"version": "7.23.104",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/gobj-ui.cjs.js",
|
|
6
6
|
"module": "dist/gobj-ui.es.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "vite build",
|
|
26
26
|
"test": "vitest run",
|
|
27
|
+
"deploy-round": "node scripts/deploy-round.mjs",
|
|
27
28
|
"prepublishOnly": "npm run build"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
empty_string,
|
|
71
71
|
is_object,
|
|
72
72
|
is_number,
|
|
73
|
+
empty_json,
|
|
73
74
|
kw_get_int,
|
|
74
75
|
kw_get_str,
|
|
75
76
|
kw_get_dict_value,
|
|
@@ -442,6 +443,9 @@ SDATA(data_type_t.DTP_BOOLEAN, "node_labels", 0, true, "A `shape` n
|
|
|
442
443
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
|
|
443
444
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
|
|
444
445
|
|
|
446
|
+
/*---------------- Camera ----------------*/
|
|
447
|
+
SDATA(data_type_t.DTP_DICT, "camera", 0, "{}", "Where the reader left the viewport: {zoom, x, y}. Empty = the graph opens fitted. Restored on the FIRST draw and published back as EV_CAMERA_CHANGED once a move settles, so a host can persist it"),
|
|
448
|
+
|
|
445
449
|
SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
|
|
446
450
|
|
|
447
451
|
SDATA_END()
|
|
@@ -452,6 +456,9 @@ let PRIVATE_DATA = {
|
|
|
452
456
|
anchor_id: "", // the node the camera keeps in the middle
|
|
453
457
|
anchor_state: "off", // "off" | "arming" | "on"
|
|
454
458
|
last_zoom: 1, // tells a wheel zoom from a drag in aftertransform
|
|
459
|
+
_camera_placed: false, // the first draw has decided the viewport
|
|
460
|
+
_camera_restoring: false, // a move WE made: not the reader's, do not publish it
|
|
461
|
+
_camera_timer: null, // browser timer: the gesture has to END before it is saved
|
|
455
462
|
|
|
456
463
|
_xy: 100,
|
|
457
464
|
_edge_seq: 0,
|
|
@@ -657,11 +664,19 @@ function mt_start(gobj)
|
|
|
657
664
|
***************************************************************/
|
|
658
665
|
function mt_stop(gobj)
|
|
659
666
|
{
|
|
667
|
+
let priv = gobj.priv;
|
|
660
668
|
let shell = yui_shell_of(gobj);
|
|
661
669
|
if(shell) {
|
|
662
670
|
gobj_unsubscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
|
|
663
671
|
}
|
|
664
672
|
|
|
673
|
+
/* A camera move that settles after the view is gone has nobody
|
|
674
|
+
* to tell and a graph that is no longer there to read. */
|
|
675
|
+
if(priv._camera_timer) {
|
|
676
|
+
clearTimeout(priv._camera_timer);
|
|
677
|
+
priv._camera_timer = null;
|
|
678
|
+
}
|
|
679
|
+
|
|
665
680
|
return 0;
|
|
666
681
|
}
|
|
667
682
|
|
|
@@ -1036,6 +1051,11 @@ function configure_events(gobj)
|
|
|
1036
1051
|
update_port_icon_position(gobj);
|
|
1037
1052
|
update_zoom_readout(gobj);
|
|
1038
1053
|
|
|
1054
|
+
/* Before the zoom/pan test below and not inside it: a PAN is
|
|
1055
|
+
* a camera move too, and it is the reader's just as much as
|
|
1056
|
+
* a zoom is. */
|
|
1057
|
+
publish_camera_later(gobj);
|
|
1058
|
+
|
|
1039
1059
|
/*
|
|
1040
1060
|
* A ZOOM re-centres on the anchor; a PAN does not.
|
|
1041
1061
|
* `aftertransform` fires for both, and an anchor that also
|
|
@@ -3329,6 +3349,106 @@ async function graph_fitview(gobj)
|
|
|
3329
3349
|
/* Below this the cards carry no readable text at all. */
|
|
3330
3350
|
const MIN_READABLE_ZOOM = 0.5;
|
|
3331
3351
|
|
|
3352
|
+
|
|
3353
|
+
/************************************************************
|
|
3354
|
+
* THE CAMERA IS THE READER'S.
|
|
3355
|
+
*
|
|
3356
|
+
* Once somebody has chosen a zoom, nothing but a camera
|
|
3357
|
+
* command may change it -- not a refresh, not a change of node
|
|
3358
|
+
* mode, not a new main topic. Those rebuild the CONTENT, and
|
|
3359
|
+
* the content moving is no reason to move the reader.
|
|
3360
|
+
*
|
|
3361
|
+
* Two mechanisms, because they answer two different questions:
|
|
3362
|
+
*
|
|
3363
|
+
* - across a rebuild in the same view, the anchor pair
|
|
3364
|
+
* (`yui_graph_viewport_of` / `yui_graph_place_at`) keeps a
|
|
3365
|
+
* NODE at the pixel it was on, which survives a relayout
|
|
3366
|
+
* that moves everything;
|
|
3367
|
+
* - across a RELOAD there is no node to hold on to yet, so
|
|
3368
|
+
* the viewport itself is restored -- zoom and position, as
|
|
3369
|
+
* G6 reports them.
|
|
3370
|
+
************************************************************/
|
|
3371
|
+
const CAMERA_SETTLE_MS = 700;
|
|
3372
|
+
|
|
3373
|
+
function read_camera(gobj)
|
|
3374
|
+
{
|
|
3375
|
+
let priv = gobj.priv;
|
|
3376
|
+
let graph = priv.graph;
|
|
3377
|
+
|
|
3378
|
+
if(!graph || typeof graph.getZoom !== "function" || typeof graph.getPosition !== "function") {
|
|
3379
|
+
return null;
|
|
3380
|
+
}
|
|
3381
|
+
try {
|
|
3382
|
+
let z = graph.getZoom();
|
|
3383
|
+
let p = graph.getPosition();
|
|
3384
|
+
if(!is_number(z) || !p || !is_number(p[0]) || !is_number(p[1])) {
|
|
3385
|
+
return null;
|
|
3386
|
+
}
|
|
3387
|
+
return {zoom: z, x: p[0], y: p[1]};
|
|
3388
|
+
} catch(e) {
|
|
3389
|
+
return null; /* between renders: there is no camera to read */
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
/* Returns true when it placed the viewport, so the caller knows
|
|
3394
|
+
* not to fit. */
|
|
3395
|
+
async function restore_camera(gobj)
|
|
3396
|
+
{
|
|
3397
|
+
let priv = gobj.priv;
|
|
3398
|
+
let graph = priv.graph;
|
|
3399
|
+
let cam = gobj_read_attr(gobj, "camera");
|
|
3400
|
+
|
|
3401
|
+
if(!graph || empty_json(cam) || !is_number(cam.zoom)) {
|
|
3402
|
+
return false;
|
|
3403
|
+
}
|
|
3404
|
+
priv._camera_restoring = true;
|
|
3405
|
+
try {
|
|
3406
|
+
await graph.zoomTo(cam.zoom);
|
|
3407
|
+
if(is_number(cam.x) && is_number(cam.y)) {
|
|
3408
|
+
await graph.translateTo([cam.x, cam.y]);
|
|
3409
|
+
}
|
|
3410
|
+
return true;
|
|
3411
|
+
} catch(e) {
|
|
3412
|
+
log_error(`${gobj_short_name(gobj)}: cannot restore the camera: ${e}`);
|
|
3413
|
+
return false;
|
|
3414
|
+
} finally {
|
|
3415
|
+
priv._camera_restoring = false;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
/************************************************************
|
|
3420
|
+
* A move settled: tell whoever persists it.
|
|
3421
|
+
*
|
|
3422
|
+
* Debounced on a REAL time and with the browser's timer: a
|
|
3423
|
+
* wheel notch, a pinch and a drag each fire `aftertransform`
|
|
3424
|
+
* many times, and what is worth saving is where the gesture
|
|
3425
|
+
* ENDED. (`set_timeout` from gobj-js is a C_TIMER and shadows
|
|
3426
|
+
* the global -- this module imports neither.)
|
|
3427
|
+
************************************************************/
|
|
3428
|
+
function publish_camera_later(gobj)
|
|
3429
|
+
{
|
|
3430
|
+
let priv = gobj.priv;
|
|
3431
|
+
|
|
3432
|
+
if(priv._camera_restoring || !priv._camera_placed) {
|
|
3433
|
+
return; /* our own move, or the opening one */
|
|
3434
|
+
}
|
|
3435
|
+
if(priv._camera_timer) {
|
|
3436
|
+
clearTimeout(priv._camera_timer);
|
|
3437
|
+
}
|
|
3438
|
+
priv._camera_timer = setTimeout(() => {
|
|
3439
|
+
priv._camera_timer = null;
|
|
3440
|
+
if(gobj_is_destroying(gobj)) {
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
let cam = read_camera(gobj);
|
|
3444
|
+
if(!cam) {
|
|
3445
|
+
return; /* Error already logged, or nothing to read */
|
|
3446
|
+
}
|
|
3447
|
+
gobj_write_attr(gobj, "camera", cam);
|
|
3448
|
+
gobj_publish_event(gobj, "EV_CAMERA_CHANGED", cam);
|
|
3449
|
+
}, CAMERA_SETTLE_MS);
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3332
3452
|
/************************************************************
|
|
3333
3453
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
3334
3454
|
* where nothing on a card can be read.
|
|
@@ -9241,11 +9361,26 @@ async function reconcile_fold_apply(gobj, opts, visible)
|
|
|
9241
9361
|
await graph_layout(gobj);
|
|
9242
9362
|
}
|
|
9243
9363
|
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9364
|
+
/* THE VIEWPORT, and only here.
|
|
9365
|
+
*
|
|
9366
|
+
* `restore` is the reader's saved camera coming back on the
|
|
9367
|
+
* first draw after a reload; `fit` is the opening frame of a
|
|
9368
|
+
* graph nobody has looked at yet; `keep` holds a node at the
|
|
9369
|
+
* pixel it was on across a rebuild. Anything else leaves the
|
|
9370
|
+
* camera alone, which is the default on purpose: a rebuild of
|
|
9371
|
+
* the CONTENT is not a reason to move the reader. */
|
|
9372
|
+
if(!(opts.restore && await restore_camera(gobj))) {
|
|
9373
|
+
if(opts.fit) {
|
|
9374
|
+
await graph_fit_readable(gobj);
|
|
9375
|
+
} else if(opts.keep && opts.keep_vp) {
|
|
9376
|
+
await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
|
|
9377
|
+
}
|
|
9378
|
+
}
|
|
9379
|
+
/* Whichever branch ran -- including none of them, which is a
|
|
9380
|
+
* graph somebody had arranged and left where it was -- the
|
|
9381
|
+
* opening viewport is decided from here on, and every move after
|
|
9382
|
+
* it is the reader's and worth remembering. */
|
|
9383
|
+
priv._camera_placed = true;
|
|
9249
9384
|
|
|
9250
9385
|
update_resize_handles_position(gobj);
|
|
9251
9386
|
update_port_resize_handles_position(gobj);
|
|
@@ -9484,10 +9619,23 @@ function ac_load_data(gobj, event, kw, src)
|
|
|
9484
9619
|
* nodes are about to be built: settle it first. */
|
|
9485
9620
|
apply_layout_direction(gobj);
|
|
9486
9621
|
|
|
9487
|
-
/*
|
|
9488
|
-
*
|
|
9489
|
-
*
|
|
9490
|
-
|
|
9622
|
+
/* THE FIRST load decides the viewport: the reader's saved
|
|
9623
|
+
* camera if there is one, else the opening fit -- and only
|
|
9624
|
+
* when WE laid the graph out, since a saved arrangement
|
|
9625
|
+
* includes where its owner was looking.
|
|
9626
|
+
*
|
|
9627
|
+
* Every load AFTER that is a REFRESH, and a refresh must not
|
|
9628
|
+
* move the camera: the reader asked for fresh data, not for a
|
|
9629
|
+
* new viewport. It holds a node instead, so the graph does not
|
|
9630
|
+
* jump even if the layout changed under it. */
|
|
9631
|
+
let vp_opts;
|
|
9632
|
+
if(!priv._camera_placed) {
|
|
9633
|
+
vp_opts = {restore: true, fit: auto_laid};
|
|
9634
|
+
} else {
|
|
9635
|
+
let keep = fold_keep_node(gobj);
|
|
9636
|
+
vp_opts = {keep: keep, keep_vp: yui_graph_viewport_of(priv.graph, keep)};
|
|
9637
|
+
}
|
|
9638
|
+
reconcile_fold(gobj, Object.assign({relayout: true}, vp_opts)).then(() => {
|
|
9491
9639
|
sync_history_plugin(gobj);
|
|
9492
9640
|
/* Nodes exist and are positioned now: apply a focus
|
|
9493
9641
|
* requested before the data was ready (deep link). */
|
|
@@ -10160,12 +10308,18 @@ function ac_set_main_topic(gobj, event, kw, src)
|
|
|
10160
10308
|
if(!priv._fold_model) {
|
|
10161
10309
|
return 0;
|
|
10162
10310
|
}
|
|
10311
|
+
/* Read BEFORE the model is rebuilt: the node is looked up in the
|
|
10312
|
+
* tree that is on screen. If the new trunk leaves it out,
|
|
10313
|
+
* `yui_graph_place_at` finds nothing and the camera simply stays
|
|
10314
|
+
* where it was -- which is still not a refit. */
|
|
10315
|
+
let keep = fold_keep_node(gobj);
|
|
10316
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
10163
10317
|
rebuild_fold_model(gobj);
|
|
10164
10318
|
fold_expand_to_depth(
|
|
10165
10319
|
priv._fold_model, priv._fold_state,
|
|
10166
10320
|
gobj_read_integer_attr(gobj, "expand_depth")
|
|
10167
10321
|
);
|
|
10168
|
-
reconcile_fold(gobj, {relayout: true,
|
|
10322
|
+
reconcile_fold(gobj, {relayout: true, keep: keep, keep_vp: vp});
|
|
10169
10323
|
return 0;
|
|
10170
10324
|
}
|
|
10171
10325
|
|
|
@@ -10200,9 +10354,15 @@ function ac_set_node_mode(gobj, event, kw, src)
|
|
|
10200
10354
|
if(!priv.graph || !priv._fold_model) {
|
|
10201
10355
|
return 0; /* applied when the records arrive */
|
|
10202
10356
|
}
|
|
10357
|
+
/* The camera is the reader's: a square and a card do not occupy
|
|
10358
|
+
* the same room, so the graph is laid out again -- and the node
|
|
10359
|
+
* being read stays at its pixel, at the zoom it was chosen at,
|
|
10360
|
+
* instead of the whole graph being fitted from scratch. */
|
|
10361
|
+
let keep = fold_keep_node(gobj);
|
|
10362
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
10203
10363
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
10204
10364
|
reshape_more_chips(gobj);
|
|
10205
|
-
reconcile_fold(gobj, {relayout: true,
|
|
10365
|
+
reconcile_fold(gobj, {relayout: true, keep: keep, keep_vp: vp});
|
|
10206
10366
|
return 0;
|
|
10207
10367
|
}
|
|
10208
10368
|
|
|
@@ -10898,6 +11058,9 @@ function create_gclass(gclass_name)
|
|
|
10898
11058
|
["EV_FIND_NODES", 0],
|
|
10899
11059
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
|
10900
11060
|
["EV_LAYOUT_AUTOSET", event_flag_t.EVF_OUTPUT_EVENT],
|
|
11061
|
+
/* Optional: a host that does not persist the viewport is not
|
|
11062
|
+
* doing anything wrong, so no "publish without subscribers". */
|
|
11063
|
+
["EV_CAMERA_CHANGED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
10901
11064
|
["EV_TOGGLE_FOLD", 0],
|
|
10902
11065
|
["EV_SHOW_MORE", 0],
|
|
10903
11066
|
["EV_EXPAND_ALL", 0],
|
package/src/c_yui_gclass.js
CHANGED
|
@@ -404,7 +404,7 @@ function apply_view_mode(gobj)
|
|
|
404
404
|
|
|
405
405
|
if(priv.$mode_btns) {
|
|
406
406
|
for(let [m, $btn] of priv.$mode_btns) {
|
|
407
|
-
$btn.classList.toggle('
|
|
407
|
+
$btn.classList.toggle('selected_state', m === mode);
|
|
408
408
|
$btn.setAttribute("aria-pressed", (m === mode)? "true": "false");
|
|
409
409
|
}
|
|
410
410
|
}
|
|
@@ -983,7 +983,7 @@ function machine_view_switch(gobj)
|
|
|
983
983
|
let on = (priv.machine_view === v.mode);
|
|
984
984
|
return ['button', {class: 'button is-small GCLASS_MACHINE_VIEW ' +
|
|
985
985
|
`GCLASS_MACHINE_VIEW_${v.mode.toUpperCase()}` +
|
|
986
|
-
(on? '
|
|
986
|
+
(on? ' selected_state': ''),
|
|
987
987
|
type: 'button',
|
|
988
988
|
'aria-pressed': on? 'true': 'false',
|
|
989
989
|
title: t(v.key), 'data-i18n-title': v.key,
|
package/src/c_yui_json.css
CHANGED
|
@@ -283,14 +283,14 @@
|
|
|
283
283
|
margin-bottom: 0;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
/* The current view is marked with `
|
|
287
|
-
*
|
|
288
|
-
* used to fill itself
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
.C_YUI_JSON .JSON_VIEW_MODE.
|
|
286
|
+
/* The current view is marked with `selected_state` (`yui_toolbar.css`):
|
|
287
|
+
* the library's one way of saying WHICH segment of a selector is
|
|
288
|
+
* chosen. This switch used to write that fill here itself -- the right
|
|
289
|
+
* idea in the wrong place, since the same question is answered by the
|
|
290
|
+
* graph's node modes, the map's modes and the period's granularities.
|
|
291
|
+
* `z-index` is all that is left, so the marked button's border wins
|
|
292
|
+
* over its neighbours' in the `has-addons` group. */
|
|
293
|
+
.C_YUI_JSON .JSON_VIEW_MODE.selected_state {
|
|
294
294
|
z-index: 1;
|
|
295
295
|
}
|
|
296
296
|
|
package/src/c_yui_json.js
CHANGED
|
@@ -442,12 +442,11 @@ function icon_button(gobj, icon, event_name, label_key)
|
|
|
442
442
|
* aimed — reaching the graph from the tree would mean passing
|
|
443
443
|
* through the text and rebuilding it on the way — so each view gets
|
|
444
444
|
* its own button and says where it goes. apply_view_mode() moves
|
|
445
|
-
* the `
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
* thing the library already had a name for.
|
|
445
|
+
* the `selected_state` mark -- the library's one way of saying
|
|
446
|
+
* WHICH segment of a selector is chosen, filled with the link
|
|
447
|
+
* colour. It used to be `is-active` with a fill written here: the
|
|
448
|
+
* right idea, spelled a third time. A toggle, which has no "which
|
|
449
|
+
* one" to answer, wears the neutral `pressed_state` pill instead.
|
|
451
450
|
************************************************************/
|
|
452
451
|
function view_mode_switch(gobj)
|
|
453
452
|
{
|
|
@@ -558,7 +557,7 @@ function apply_view_mode(gobj)
|
|
|
558
557
|
|
|
559
558
|
if(priv.$mode_btns) {
|
|
560
559
|
for(let [m, $btn] of priv.$mode_btns) {
|
|
561
|
-
$btn.classList.toggle('
|
|
560
|
+
$btn.classList.toggle('selected_state', m === mode);
|
|
562
561
|
$btn.setAttribute("aria-pressed", (m === mode)? "true": "false");
|
|
563
562
|
}
|
|
564
563
|
}
|
package/src/c_yui_period.js
CHANGED
|
@@ -96,8 +96,9 @@ import {
|
|
|
96
96
|
|
|
97
97
|
import i18next, {t} from "i18next";
|
|
98
98
|
|
|
99
|
-
/* `yui_toolbar.css` for `
|
|
100
|
-
*
|
|
99
|
+
/* `yui_toolbar.css` for `selected_state`: the mark of the chosen
|
|
100
|
+
* segment of a selector lives with the toolbar, and a stylesheet
|
|
101
|
+
* rides its import. */
|
|
101
102
|
import "./yui_toolbar.css";
|
|
102
103
|
import "./c_yui_period.css";
|
|
103
104
|
|
|
@@ -702,13 +703,12 @@ function repaint(gobj)
|
|
|
702
703
|
let priv = gobj.priv;
|
|
703
704
|
let mode = cur_mode(gobj);
|
|
704
705
|
|
|
705
|
-
/*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
* exactly what it is. */
|
|
706
|
+
/* This is a SELECTOR -- one granularity of several -- so the
|
|
707
|
+
* chosen segment is FILLED (`selected_state`, `yui_toolbar.css`),
|
|
708
|
+
* the same mark the graph's node modes and the map's modes wear.
|
|
709
|
+
* An overflow granularity is a `dropdown-item` and not a button,
|
|
710
|
+
* and there `is-active` is Bulma's own mark for the current item
|
|
711
|
+
* of a menu -- which is exactly what it is. */
|
|
712
712
|
for(let m of priv.modes) {
|
|
713
713
|
if(!m.$btn) {
|
|
714
714
|
continue;
|
|
@@ -724,7 +724,7 @@ function repaint(gobj)
|
|
|
724
724
|
m.$btn.removeAttribute("aria-current");
|
|
725
725
|
}
|
|
726
726
|
} else {
|
|
727
|
-
m.$btn.classList.toggle("
|
|
727
|
+
m.$btn.classList.toggle("selected_state", on);
|
|
728
728
|
m.$btn.setAttribute("aria-pressed", on? "true" : "false");
|
|
729
729
|
}
|
|
730
730
|
}
|
|
@@ -735,7 +735,7 @@ function repaint(gobj)
|
|
|
735
735
|
/* An overflow granularity in use must SAY so on the trigger: the
|
|
736
736
|
* control shows no active segment otherwise, and the user cannot
|
|
737
737
|
* tell a quarter from a month by the arrows alone. */
|
|
738
|
-
priv.$more.classList.toggle("
|
|
738
|
+
priv.$more.classList.toggle("selected_state", !!(mode && mode.overflow));
|
|
739
739
|
}
|
|
740
740
|
|
|
741
741
|
/* The navigator STAYS, disabled, in the modes with nothing to walk: it
|
|
@@ -1013,10 +1013,10 @@ function cell_button(gobj, label, anchor_ms, selected, logical, aria)
|
|
|
1013
1013
|
* year around it. */
|
|
1014
1014
|
let $btn = createElement2(
|
|
1015
1015
|
/* The ONE place `is-small` is earned: 42 cells in a popover. */
|
|
1016
|
-
/* The picked instant is
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
["button", {class: `button is-small ${selected? "
|
|
1016
|
+
/* The picked instant is the chosen one of a grid of 42, which
|
|
1017
|
+
* is the selector's question and not a toggle's, so it takes
|
|
1018
|
+
* the same fill. `aria-current="date"` says it to a reader. */
|
|
1019
|
+
["button", {class: `button is-small ${selected? "selected_state" : "is-ghost"} ${logical}`,
|
|
1020
1020
|
type: "button", style: "width:100%;",
|
|
1021
1021
|
...(selected? {"aria-current": "date"} : {}),
|
|
1022
1022
|
title: aria || "", "aria-label": aria || ""}, String(label)]);
|
|
@@ -136,6 +136,7 @@ SDATA(data_type_t.DTP_STRING, "main_topic", sdata_flag_t.SDF_PERSIST, ""
|
|
|
136
136
|
SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
|
|
137
137
|
SDATA(data_type_t.DTP_STRING, "node_mode", sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their pills and ports, `compact` = one-line pills with the name, `shape` = coloured figures, the topology alone. User preference"),
|
|
138
138
|
SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
|
|
139
|
+
SDATA(data_type_t.DTP_DICT, "camera", sdata_flag_t.SDF_PERSIST, "{}", "Where the reader left the viewport: {zoom, x, y}. Handed to the engine on creation and written back when a move settles, so the graph opens where it was left. User preference, per treedb"),
|
|
139
140
|
|
|
140
141
|
/*---------------- Remote Connection ----------------*/
|
|
141
142
|
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
@@ -281,6 +282,7 @@ function mt_create(gobj)
|
|
|
281
282
|
loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
|
|
282
283
|
node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
|
|
283
284
|
node_labels: gobj_read_bool_attr(gobj, "node_labels"),
|
|
285
|
+
camera: gobj_read_attr(gobj, "camera") || {},
|
|
284
286
|
},
|
|
285
287
|
gobj
|
|
286
288
|
);
|
|
@@ -652,9 +654,13 @@ function refresh_node_shape_buttons(gobj, $root)
|
|
|
652
654
|
mode = 'expanded';
|
|
653
655
|
}
|
|
654
656
|
let labels = gobj_read_bool_attr(gobj, "node_labels");
|
|
657
|
+
/* A SELECTOR, not a toggle: it picks one of three views, so the
|
|
658
|
+
* chosen segment is FILLED (`selected_state`) and not drawn as a
|
|
659
|
+
* pressed pill. The labels button below IS a toggle -- on or off,
|
|
660
|
+
* no "which one" -- and keeps the pill. */
|
|
655
661
|
for(let $btn of $root.querySelectorAll('.GRAPH_NODE_MODE_BTN')) {
|
|
656
662
|
let on = ($btn.getAttribute('data-mode') === mode);
|
|
657
|
-
$btn.classList.toggle('
|
|
663
|
+
$btn.classList.toggle('selected_state', on);
|
|
658
664
|
$btn.setAttribute('aria-pressed', on? 'true' : 'false');
|
|
659
665
|
}
|
|
660
666
|
set_pressed_state($root, '.GRAPH_NODE_LABELS', labels);
|
|
@@ -2884,6 +2890,23 @@ function ac_layout_autoset(gobj, event, kw, src)
|
|
|
2884
2890
|
return 0;
|
|
2885
2891
|
}
|
|
2886
2892
|
|
|
2893
|
+
/************************************************************
|
|
2894
|
+
* The reader moved the camera, and it has settled.
|
|
2895
|
+
*
|
|
2896
|
+
* Persisted like every other choice of this toolbar, and for the
|
|
2897
|
+
* same reason: a zoom is a decision, and a decision that dies with
|
|
2898
|
+
* the tab has to be taken again every morning. The engine debounces,
|
|
2899
|
+
* so this runs once per gesture and not once per wheel notch.
|
|
2900
|
+
************************************************************/
|
|
2901
|
+
function ac_camera_changed(gobj, event, kw, src)
|
|
2902
|
+
{
|
|
2903
|
+
gobj_write_attr(gobj, "camera", {
|
|
2904
|
+
zoom: kw.zoom, x: kw.x, y: kw.y
|
|
2905
|
+
});
|
|
2906
|
+
gobj_save_persistent_attrs(gobj, "camera");
|
|
2907
|
+
return 0;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2887
2910
|
/************************************************************
|
|
2888
2911
|
* Forward the find down to the graph child.
|
|
2889
2912
|
************************************************************/
|
|
@@ -3124,6 +3147,7 @@ function create_gclass(gclass_name)
|
|
|
3124
3147
|
["EV_SET_NODE_MODE", ac_set_node_mode, null],
|
|
3125
3148
|
["EV_TOGGLE_NODE_LABELS", ac_toggle_node_labels, null],
|
|
3126
3149
|
["EV_LAYOUT_AUTOSET", ac_layout_autoset, null],
|
|
3150
|
+
["EV_CAMERA_CHANGED", ac_camera_changed, null],
|
|
3127
3151
|
["EV_LEGEND_TOPIC", ac_legend_topic, null],
|
|
3128
3152
|
["EV_LEGEND_STATE", ac_legend_state, null],
|
|
3129
3153
|
["EV_FIND_RESULT", ac_find_result, null],
|
|
@@ -3169,6 +3193,7 @@ function create_gclass(gclass_name)
|
|
|
3169
3193
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
3170
3194
|
["EV_FIND_RESULT", 0],
|
|
3171
3195
|
["EV_LAYOUT_AUTOSET", 0],
|
|
3196
|
+
["EV_CAMERA_CHANGED", 0],
|
|
3172
3197
|
["EV_LEGEND_TOPIC", 0],
|
|
3173
3198
|
["EV_LEGEND_STATE", 0],
|
|
3174
3199
|
["EV_TOPIC_SELECTED",
|
package/src/yui_toolbar.css
CHANGED
|
@@ -110,3 +110,36 @@ a.flex-horizontal-section { /* To make flex any <a> */
|
|
|
110
110
|
color: var(--bulma-background, #ffffff);
|
|
111
111
|
border-color: var(--bulma-text, #2e333d);
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
/*********************************************************
|
|
115
|
+
* The CHOSEN segment of a selector is FILLED
|
|
116
|
+
*
|
|
117
|
+
* Two different controls, two different marks, and telling them apart
|
|
118
|
+
* is the whole point:
|
|
119
|
+
*
|
|
120
|
+
* - a TOGGLE is on or off, and looks PRESSED (above) -- a neutral
|
|
121
|
+
* pill, no hue, because there is no "which one" to answer;
|
|
122
|
+
* - a SELECTOR picks ONE of N, and the chosen segment is FILLED with
|
|
123
|
+
* the link colour. A hue is what the eye finds in a row of
|
|
124
|
+
* identical grey buttons: measured against the button's own ground
|
|
125
|
+
* the fill is 5.14:1 in light and 3.53:1 in dark -- less luminance
|
|
126
|
+
* than the pressed pill (9.44 / 8.46) and a different HUE, which
|
|
127
|
+
* is what says "this one, of these".
|
|
128
|
+
*
|
|
129
|
+
* The raw `--bulma-link` and not `-on-scheme`: this is a FILL. The
|
|
130
|
+
* `-on-scheme` variants exist for a brand colour used as INK -- they
|
|
131
|
+
* darken it on a light scheme so text can sit on the page -- and a
|
|
132
|
+
* darkened fill under `link-invert` text is a button nobody asked for.
|
|
133
|
+
* Ink takes `-on-scheme`; a background takes the raw token and pairs
|
|
134
|
+
* it with `-invert`.
|
|
135
|
+
*
|
|
136
|
+
* `:hover` for the same reason as the pressed rule: without it, Bulma's
|
|
137
|
+
* `.button:hover` takes the fill back and the chosen segment appears to
|
|
138
|
+
* come unchosen under the pointer.
|
|
139
|
+
*********************************************************/
|
|
140
|
+
.button.selected_state,
|
|
141
|
+
.button.selected_state:hover {
|
|
142
|
+
background-color: var(--bulma-link, #3273dc);
|
|
143
|
+
border-color: var(--bulma-link, #3273dc);
|
|
144
|
+
color: var(--bulma-link-invert, #fff);
|
|
145
|
+
}
|
package/src/yui_toolbar.js
CHANGED
|
@@ -186,4 +186,25 @@ function set_pressed_state($container, selector, set)
|
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
/************************************************************
|
|
190
|
+
* Set or reset the CHOSEN state of one segment of a selector.
|
|
191
|
+
*
|
|
192
|
+
* Not the same control as a toggle, and not the same mark: a
|
|
193
|
+
* toggle is on or off and looks pressed (a neutral pill, no
|
|
194
|
+
* hue, because there is no "which one" to answer); a selector
|
|
195
|
+
* picks ONE of N and fills the chosen segment with the link
|
|
196
|
+
* colour, which is what the eye finds in a row of identical
|
|
197
|
+
* grey buttons. See `yui_toolbar.css` for the measurements.
|
|
198
|
+
************************************************************/
|
|
199
|
+
function set_selected_state($container, selector, set)
|
|
200
|
+
{
|
|
201
|
+
if(!$container) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
$container.querySelectorAll(selector).forEach(($el) => {
|
|
205
|
+
$el.classList.toggle("selected_state", !!set);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export {yui_toolbar, yui_toolbar_icon, YUI_TOOLBAR_ICON_SIZE,
|
|
210
|
+
set_pressed_state, set_selected_state};
|