@yuneta/gobj-ui 7.23.103 → 7.23.105
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 +37 -0
- package/dist/gobj-ui.cjs.js +139 -8
- package/dist/gobj-ui.es.js +140 -9
- package/package.json +2 -1
- package/src/c_g6_nodes_tree.js +194 -11
- package/src/c_yui_treedb_graph.js +21 -0
package/README.md
CHANGED
|
@@ -947,6 +947,43 @@ 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** the same thing is saved and replayed: the zoom, a NODE,
|
|
966
|
+
and the viewport pixel that node was on. **Not G6's own position** — an
|
|
967
|
+
absolute `translateTo()` leaves the camera at `canvasCentre - T/zoom`, so
|
|
968
|
+
`getPosition()` and `translateTo()` are each other's inverse only at zoom 1,
|
|
969
|
+
and restoring one with the other brings the zoom back and puts the graph
|
|
970
|
+
somewhere else. That is the whole reason `yui_graph_place_at()` exists.
|
|
971
|
+
|
|
972
|
+
Wiring: `C_G6_NODES_TREE` takes a `camera` attr — the viewport to restore on its
|
|
973
|
+
FIRST draw, empty meaning *open fitted* — and publishes **`EV_CAMERA_CHANGED
|
|
974
|
+
{zoom, node, x, y}`** once a move has settled. A saved node this load does not
|
|
975
|
+
have leaves the graph to its opening fit, checked before the zoom is touched:
|
|
976
|
+
a zoom with no framing to go with it is worse than a fit. The settle is a real time (700 ms of
|
|
977
|
+
the browser's `setTimeout`, not a `C_TIMER`): a wheel notch, a pinch and a drag
|
|
978
|
+
each fire `aftertransform` many times, and what is worth saving is where the
|
|
979
|
+
gesture ENDED. It fires for a PAN as well as a zoom — a pan is the reader's just
|
|
980
|
+
as much. `C_YUI_TREEDB_GRAPH` holds the other half: `camera` is one more
|
|
981
|
+
`SDF_PERSIST` attr of the view, saved under its name like `main_topic`, so the
|
|
982
|
+
graph opens where that treedb was left.
|
|
983
|
+
|
|
984
|
+
A host that does not care persists nothing and loses nothing: the event carries
|
|
985
|
+
`EVF_NO_WARN_SUBS`, and the attr defaults to empty.
|
|
986
|
+
|
|
950
987
|
### Moving the cards, and folding without losing your place
|
|
951
988
|
|
|
952
989
|
The JSON graph and the gobj tree take `drag-element`: a card can be dragged.
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -54825,6 +54825,7 @@ var attrs_table$4 = [
|
|
|
54825
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"),
|
|
54826
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"),
|
|
54827
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, node, x, y} -- a NODE at a viewport pixel, not G6's own position, which does not restore. 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"),
|
|
54828
54829
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
|
|
54829
54830
|
(0, _yuneta_gobj_js.SDATA_END)()
|
|
54830
54831
|
];
|
|
@@ -54832,6 +54833,9 @@ var PRIVATE_DATA$4 = {
|
|
|
54832
54833
|
anchor_id: "",
|
|
54833
54834
|
anchor_state: "off",
|
|
54834
54835
|
last_zoom: 1,
|
|
54836
|
+
_camera_placed: false,
|
|
54837
|
+
_camera_restoring: false,
|
|
54838
|
+
_camera_timer: null,
|
|
54835
54839
|
_xy: 100,
|
|
54836
54840
|
_edge_seq: 0,
|
|
54837
54841
|
_history_paused: false,
|
|
@@ -54963,8 +54967,13 @@ function mt_start$4(gobj) {
|
|
|
54963
54967
|
* Framework Method: Stop
|
|
54964
54968
|
***************************************************************/
|
|
54965
54969
|
function mt_stop$4(gobj) {
|
|
54970
|
+
let priv = gobj.priv;
|
|
54966
54971
|
let shell = yui_shell_of(gobj);
|
|
54967
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
|
+
}
|
|
54968
54977
|
return 0;
|
|
54969
54978
|
}
|
|
54970
54979
|
/***************************************************************
|
|
@@ -55156,6 +55165,7 @@ function configure_events(gobj) {
|
|
|
55156
55165
|
update_link_icon_position(gobj);
|
|
55157
55166
|
update_port_icon_position(gobj);
|
|
55158
55167
|
update_zoom_readout(gobj);
|
|
55168
|
+
publish_camera_later(gobj);
|
|
55159
55169
|
let z = priv.last_zoom;
|
|
55160
55170
|
try {
|
|
55161
55171
|
z = priv.graph.getZoom();
|
|
@@ -56681,6 +56691,83 @@ async function graph_fitview(gobj) {
|
|
|
56681
56691
|
}
|
|
56682
56692
|
var MIN_READABLE_ZOOM = .5;
|
|
56683
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") return null;
|
|
56715
|
+
try {
|
|
56716
|
+
let z = graph.getZoom();
|
|
56717
|
+
let node = fold_keep_node(gobj);
|
|
56718
|
+
let vp = node ? yui_graph_viewport_of(graph, node) : null;
|
|
56719
|
+
if (!(0, _yuneta_gobj_js.is_number)(z) || !node || !vp || !(0, _yuneta_gobj_js.is_number)(vp[0]) || !(0, _yuneta_gobj_js.is_number)(vp[1])) return null;
|
|
56720
|
+
return {
|
|
56721
|
+
zoom: z,
|
|
56722
|
+
node,
|
|
56723
|
+
x: vp[0],
|
|
56724
|
+
y: vp[1]
|
|
56725
|
+
};
|
|
56726
|
+
} catch (e) {
|
|
56727
|
+
return null;
|
|
56728
|
+
}
|
|
56729
|
+
}
|
|
56730
|
+
async function restore_camera(gobj) {
|
|
56731
|
+
let priv = gobj.priv;
|
|
56732
|
+
let graph = priv.graph;
|
|
56733
|
+
let cam = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "camera");
|
|
56734
|
+
if (!graph || (0, _yuneta_gobj_js.empty_json)(cam) || !(0, _yuneta_gobj_js.is_number)(cam.zoom) || !cam.node || !(0, _yuneta_gobj_js.is_number)(cam.x) || !(0, _yuneta_gobj_js.is_number)(cam.y)) return false;
|
|
56735
|
+
if (!yui_graph_viewport_of(graph, cam.node)) return false;
|
|
56736
|
+
priv._camera_restoring = true;
|
|
56737
|
+
try {
|
|
56738
|
+
await graph.zoomTo(cam.zoom);
|
|
56739
|
+
await yui_graph_place_at(graph, cam.node, [cam.x, cam.y]);
|
|
56740
|
+
return true;
|
|
56741
|
+
} catch (e) {
|
|
56742
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: cannot restore the camera: ${e}`);
|
|
56743
|
+
return false;
|
|
56744
|
+
} finally {
|
|
56745
|
+
priv._camera_restoring = false;
|
|
56746
|
+
}
|
|
56747
|
+
}
|
|
56748
|
+
/************************************************************
|
|
56749
|
+
* A move settled: tell whoever persists it.
|
|
56750
|
+
*
|
|
56751
|
+
* Debounced on a REAL time and with the browser's timer: a
|
|
56752
|
+
* wheel notch, a pinch and a drag each fire `aftertransform`
|
|
56753
|
+
* many times, and what is worth saving is where the gesture
|
|
56754
|
+
* ENDED. (`set_timeout` from gobj-js is a C_TIMER and shadows
|
|
56755
|
+
* the global -- this module imports neither.)
|
|
56756
|
+
************************************************************/
|
|
56757
|
+
function publish_camera_later(gobj) {
|
|
56758
|
+
let priv = gobj.priv;
|
|
56759
|
+
if (priv._camera_restoring || !priv._camera_placed) return;
|
|
56760
|
+
if (priv._camera_timer) clearTimeout(priv._camera_timer);
|
|
56761
|
+
priv._camera_timer = setTimeout(() => {
|
|
56762
|
+
priv._camera_timer = null;
|
|
56763
|
+
if ((0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
|
|
56764
|
+
let cam = read_camera(gobj);
|
|
56765
|
+
if (!cam) return;
|
|
56766
|
+
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "camera", cam);
|
|
56767
|
+
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_CAMERA_CHANGED", cam);
|
|
56768
|
+
}, CAMERA_SETTLE_MS);
|
|
56769
|
+
}
|
|
56770
|
+
/************************************************************
|
|
56684
56771
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
56685
56772
|
* where nothing on a card can be read.
|
|
56686
56773
|
*
|
|
@@ -60624,8 +60711,11 @@ async function reconcile_fold_apply(gobj, opts, visible) {
|
|
|
60624
60711
|
}
|
|
60625
60712
|
await graph_draw(gobj);
|
|
60626
60713
|
if (changed || opts.relayout) await graph_layout(gobj);
|
|
60627
|
-
if (opts.
|
|
60628
|
-
|
|
60714
|
+
if (!(opts.restore && await restore_camera(gobj))) {
|
|
60715
|
+
if (opts.fit) await graph_fit_readable(gobj);
|
|
60716
|
+
else if (opts.keep && opts.keep_vp) await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
|
|
60717
|
+
}
|
|
60718
|
+
priv._camera_placed = true;
|
|
60629
60719
|
update_resize_handles_position(gobj);
|
|
60630
60720
|
update_port_resize_handles_position(gobj);
|
|
60631
60721
|
update_edge_icon_position(gobj);
|
|
@@ -60777,10 +60867,19 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
60777
60867
|
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: auto_layout failed: ${e}`);
|
|
60778
60868
|
}
|
|
60779
60869
|
apply_layout_direction(gobj);
|
|
60780
|
-
|
|
60781
|
-
|
|
60870
|
+
let vp_opts;
|
|
60871
|
+
if (!priv._camera_placed) vp_opts = {
|
|
60872
|
+
restore: true,
|
|
60782
60873
|
fit: auto_laid
|
|
60783
|
-
}
|
|
60874
|
+
};
|
|
60875
|
+
else {
|
|
60876
|
+
let keep = fold_keep_node(gobj);
|
|
60877
|
+
vp_opts = {
|
|
60878
|
+
keep,
|
|
60879
|
+
keep_vp: yui_graph_viewport_of(priv.graph, keep)
|
|
60880
|
+
};
|
|
60881
|
+
}
|
|
60882
|
+
reconcile_fold(gobj, Object.assign({ relayout: true }, vp_opts)).then(() => {
|
|
60784
60883
|
sync_history_plugin(gobj);
|
|
60785
60884
|
if (priv._pending_focus_topic !== null) {
|
|
60786
60885
|
let ft = priv._pending_focus_topic;
|
|
@@ -61240,11 +61339,14 @@ function ac_set_main_topic(gobj, event, kw, src) {
|
|
|
61240
61339
|
let priv = gobj.priv;
|
|
61241
61340
|
(0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "main_topic", kw && kw.topic || "");
|
|
61242
61341
|
if (!priv._fold_model) return 0;
|
|
61342
|
+
let keep = fold_keep_node(gobj);
|
|
61343
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61243
61344
|
rebuild_fold_model(gobj);
|
|
61244
61345
|
fold_expand_to_depth(priv._fold_model, priv._fold_state, (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"));
|
|
61245
61346
|
reconcile_fold(gobj, {
|
|
61246
61347
|
relayout: true,
|
|
61247
|
-
|
|
61348
|
+
keep,
|
|
61349
|
+
keep_vp: vp
|
|
61248
61350
|
});
|
|
61249
61351
|
return 0;
|
|
61250
61352
|
}
|
|
@@ -61270,11 +61372,14 @@ function ac_set_node_mode$1(gobj, event, kw, src) {
|
|
|
61270
61372
|
(0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "node_mode", mode);
|
|
61271
61373
|
priv._open_nodes = /* @__PURE__ */ new Set();
|
|
61272
61374
|
if (!priv.graph || !priv._fold_model) return 0;
|
|
61375
|
+
let keep = fold_keep_node(gobj);
|
|
61376
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61273
61377
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
61274
61378
|
reshape_more_chips(gobj);
|
|
61275
61379
|
reconcile_fold(gobj, {
|
|
61276
61380
|
relayout: true,
|
|
61277
|
-
|
|
61381
|
+
keep,
|
|
61382
|
+
keep_vp: vp
|
|
61278
61383
|
});
|
|
61279
61384
|
return 0;
|
|
61280
61385
|
}
|
|
@@ -61944,6 +62049,7 @@ function create_gclass$4(gclass_name) {
|
|
|
61944
62049
|
["EV_FIND_NODES", 0],
|
|
61945
62050
|
["EV_FIND_RESULT", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
61946
62051
|
["EV_LAYOUT_AUTOSET", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
62052
|
+
["EV_CAMERA_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
61947
62053
|
["EV_TOGGLE_FOLD", 0],
|
|
61948
62054
|
["EV_SHOW_MORE", 0],
|
|
61949
62055
|
["EV_EXPAND_ALL", 0],
|
|
@@ -62030,6 +62136,7 @@ var attrs_table$3 = [
|
|
|
62030
62136
|
(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"),
|
|
62031
62137
|
(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"),
|
|
62032
62138
|
(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"),
|
|
62139
|
+
(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"),
|
|
62033
62140
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
62034
62141
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
62035
62142
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
|
|
@@ -62117,7 +62224,8 @@ function mt_create$3(gobj) {
|
|
|
62117
62224
|
main_topic: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "main_topic") || "",
|
|
62118
62225
|
loose_topics: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "loose_topics") || [],
|
|
62119
62226
|
node_mode: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "node_mode") || "expanded",
|
|
62120
|
-
node_labels: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels")
|
|
62227
|
+
node_labels: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels"),
|
|
62228
|
+
camera: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "camera") || {}
|
|
62121
62229
|
}, gobj);
|
|
62122
62230
|
populate_nodes_tree_options(gobj);
|
|
62123
62231
|
if (priv.with_treedb_tables) {}
|
|
@@ -63801,6 +63909,23 @@ function ac_layout_autoset(gobj, event, kw, src) {
|
|
|
63801
63909
|
return 0;
|
|
63802
63910
|
}
|
|
63803
63911
|
/************************************************************
|
|
63912
|
+
* The reader moved the camera, and it has settled.
|
|
63913
|
+
*
|
|
63914
|
+
* Persisted like every other choice of this toolbar, and for the
|
|
63915
|
+
* same reason: a zoom is a decision, and a decision that dies with
|
|
63916
|
+
* the tab has to be taken again every morning. The engine debounces,
|
|
63917
|
+
* so this runs once per gesture and not once per wheel notch.
|
|
63918
|
+
************************************************************/
|
|
63919
|
+
function ac_camera_changed(gobj, event, kw, src) {
|
|
63920
|
+
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "camera", {
|
|
63921
|
+
zoom: kw.zoom,
|
|
63922
|
+
x: kw.x,
|
|
63923
|
+
y: kw.y
|
|
63924
|
+
});
|
|
63925
|
+
(0, _yuneta_gobj_js.gobj_save_persistent_attrs)(gobj, "camera");
|
|
63926
|
+
return 0;
|
|
63927
|
+
}
|
|
63928
|
+
/************************************************************
|
|
63804
63929
|
* Forward the find down to the graph child.
|
|
63805
63930
|
************************************************************/
|
|
63806
63931
|
function ac_find_nodes(gobj, event, kw, src) {
|
|
@@ -64080,6 +64205,11 @@ function create_gclass$3(gclass_name) {
|
|
|
64080
64205
|
ac_layout_autoset,
|
|
64081
64206
|
null
|
|
64082
64207
|
],
|
|
64208
|
+
[
|
|
64209
|
+
"EV_CAMERA_CHANGED",
|
|
64210
|
+
ac_camera_changed,
|
|
64211
|
+
null
|
|
64212
|
+
],
|
|
64083
64213
|
[
|
|
64084
64214
|
"EV_LEGEND_TOPIC",
|
|
64085
64215
|
ac_legend_topic,
|
|
@@ -64148,6 +64278,7 @@ function create_gclass$3(gclass_name) {
|
|
|
64148
64278
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
64149
64279
|
["EV_FIND_RESULT", 0],
|
|
64150
64280
|
["EV_LAYOUT_AUTOSET", 0],
|
|
64281
|
+
["EV_CAMERA_CHANGED", 0],
|
|
64151
64282
|
["EV_LEGEND_TOPIC", 0],
|
|
64152
64283
|
["EV_LEGEND_STATE", 0],
|
|
64153
64284
|
["EV_TOPIC_SELECTED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
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";
|
|
@@ -54820,6 +54820,7 @@ var attrs_table$4 = [
|
|
|
54820
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"),
|
|
54821
54821
|
SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
|
|
54822
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, node, x, y} -- a NODE at a viewport pixel, not G6's own position, which does not restore. 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"),
|
|
54823
54824
|
SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of header"),
|
|
54824
54825
|
SDATA_END()
|
|
54825
54826
|
];
|
|
@@ -54827,6 +54828,9 @@ var PRIVATE_DATA$4 = {
|
|
|
54827
54828
|
anchor_id: "",
|
|
54828
54829
|
anchor_state: "off",
|
|
54829
54830
|
last_zoom: 1,
|
|
54831
|
+
_camera_placed: false,
|
|
54832
|
+
_camera_restoring: false,
|
|
54833
|
+
_camera_timer: null,
|
|
54830
54834
|
_xy: 100,
|
|
54831
54835
|
_edge_seq: 0,
|
|
54832
54836
|
_history_paused: false,
|
|
@@ -54958,8 +54962,13 @@ function mt_start$4(gobj) {
|
|
|
54958
54962
|
* Framework Method: Stop
|
|
54959
54963
|
***************************************************************/
|
|
54960
54964
|
function mt_stop$4(gobj) {
|
|
54965
|
+
let priv = gobj.priv;
|
|
54961
54966
|
let shell = yui_shell_of(gobj);
|
|
54962
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
|
+
}
|
|
54963
54972
|
return 0;
|
|
54964
54973
|
}
|
|
54965
54974
|
/***************************************************************
|
|
@@ -55151,6 +55160,7 @@ function configure_events(gobj) {
|
|
|
55151
55160
|
update_link_icon_position(gobj);
|
|
55152
55161
|
update_port_icon_position(gobj);
|
|
55153
55162
|
update_zoom_readout(gobj);
|
|
55163
|
+
publish_camera_later(gobj);
|
|
55154
55164
|
let z = priv.last_zoom;
|
|
55155
55165
|
try {
|
|
55156
55166
|
z = priv.graph.getZoom();
|
|
@@ -56676,6 +56686,83 @@ async function graph_fitview(gobj) {
|
|
|
56676
56686
|
}
|
|
56677
56687
|
var MIN_READABLE_ZOOM = .5;
|
|
56678
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") return null;
|
|
56710
|
+
try {
|
|
56711
|
+
let z = graph.getZoom();
|
|
56712
|
+
let node = fold_keep_node(gobj);
|
|
56713
|
+
let vp = node ? yui_graph_viewport_of(graph, node) : null;
|
|
56714
|
+
if (!is_number(z) || !node || !vp || !is_number(vp[0]) || !is_number(vp[1])) return null;
|
|
56715
|
+
return {
|
|
56716
|
+
zoom: z,
|
|
56717
|
+
node,
|
|
56718
|
+
x: vp[0],
|
|
56719
|
+
y: vp[1]
|
|
56720
|
+
};
|
|
56721
|
+
} catch (e) {
|
|
56722
|
+
return null;
|
|
56723
|
+
}
|
|
56724
|
+
}
|
|
56725
|
+
async function restore_camera(gobj) {
|
|
56726
|
+
let priv = gobj.priv;
|
|
56727
|
+
let graph = priv.graph;
|
|
56728
|
+
let cam = gobj_read_attr(gobj, "camera");
|
|
56729
|
+
if (!graph || empty_json(cam) || !is_number(cam.zoom) || !cam.node || !is_number(cam.x) || !is_number(cam.y)) return false;
|
|
56730
|
+
if (!yui_graph_viewport_of(graph, cam.node)) return false;
|
|
56731
|
+
priv._camera_restoring = true;
|
|
56732
|
+
try {
|
|
56733
|
+
await graph.zoomTo(cam.zoom);
|
|
56734
|
+
await yui_graph_place_at(graph, cam.node, [cam.x, cam.y]);
|
|
56735
|
+
return true;
|
|
56736
|
+
} catch (e) {
|
|
56737
|
+
log_error(`${gobj_short_name(gobj)}: cannot restore the camera: ${e}`);
|
|
56738
|
+
return false;
|
|
56739
|
+
} finally {
|
|
56740
|
+
priv._camera_restoring = false;
|
|
56741
|
+
}
|
|
56742
|
+
}
|
|
56743
|
+
/************************************************************
|
|
56744
|
+
* A move settled: tell whoever persists it.
|
|
56745
|
+
*
|
|
56746
|
+
* Debounced on a REAL time and with the browser's timer: a
|
|
56747
|
+
* wheel notch, a pinch and a drag each fire `aftertransform`
|
|
56748
|
+
* many times, and what is worth saving is where the gesture
|
|
56749
|
+
* ENDED. (`set_timeout` from gobj-js is a C_TIMER and shadows
|
|
56750
|
+
* the global -- this module imports neither.)
|
|
56751
|
+
************************************************************/
|
|
56752
|
+
function publish_camera_later(gobj) {
|
|
56753
|
+
let priv = gobj.priv;
|
|
56754
|
+
if (priv._camera_restoring || !priv._camera_placed) return;
|
|
56755
|
+
if (priv._camera_timer) clearTimeout(priv._camera_timer);
|
|
56756
|
+
priv._camera_timer = setTimeout(() => {
|
|
56757
|
+
priv._camera_timer = null;
|
|
56758
|
+
if (gobj_is_destroying(gobj)) return;
|
|
56759
|
+
let cam = read_camera(gobj);
|
|
56760
|
+
if (!cam) return;
|
|
56761
|
+
gobj_write_attr(gobj, "camera", cam);
|
|
56762
|
+
gobj_publish_event(gobj, "EV_CAMERA_CHANGED", cam);
|
|
56763
|
+
}, CAMERA_SETTLE_MS);
|
|
56764
|
+
}
|
|
56765
|
+
/************************************************************
|
|
56679
56766
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
56680
56767
|
* where nothing on a card can be read.
|
|
56681
56768
|
*
|
|
@@ -60619,8 +60706,11 @@ async function reconcile_fold_apply(gobj, opts, visible) {
|
|
|
60619
60706
|
}
|
|
60620
60707
|
await graph_draw(gobj);
|
|
60621
60708
|
if (changed || opts.relayout) await graph_layout(gobj);
|
|
60622
|
-
if (opts.
|
|
60623
|
-
|
|
60709
|
+
if (!(opts.restore && await restore_camera(gobj))) {
|
|
60710
|
+
if (opts.fit) await graph_fit_readable(gobj);
|
|
60711
|
+
else if (opts.keep && opts.keep_vp) await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
|
|
60712
|
+
}
|
|
60713
|
+
priv._camera_placed = true;
|
|
60624
60714
|
update_resize_handles_position(gobj);
|
|
60625
60715
|
update_port_resize_handles_position(gobj);
|
|
60626
60716
|
update_edge_icon_position(gobj);
|
|
@@ -60772,10 +60862,19 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
60772
60862
|
log_error(`${gobj_short_name(gobj)}: auto_layout failed: ${e}`);
|
|
60773
60863
|
}
|
|
60774
60864
|
apply_layout_direction(gobj);
|
|
60775
|
-
|
|
60776
|
-
|
|
60865
|
+
let vp_opts;
|
|
60866
|
+
if (!priv._camera_placed) vp_opts = {
|
|
60867
|
+
restore: true,
|
|
60777
60868
|
fit: auto_laid
|
|
60778
|
-
}
|
|
60869
|
+
};
|
|
60870
|
+
else {
|
|
60871
|
+
let keep = fold_keep_node(gobj);
|
|
60872
|
+
vp_opts = {
|
|
60873
|
+
keep,
|
|
60874
|
+
keep_vp: yui_graph_viewport_of(priv.graph, keep)
|
|
60875
|
+
};
|
|
60876
|
+
}
|
|
60877
|
+
reconcile_fold(gobj, Object.assign({ relayout: true }, vp_opts)).then(() => {
|
|
60779
60878
|
sync_history_plugin(gobj);
|
|
60780
60879
|
if (priv._pending_focus_topic !== null) {
|
|
60781
60880
|
let ft = priv._pending_focus_topic;
|
|
@@ -61235,11 +61334,14 @@ function ac_set_main_topic(gobj, event, kw, src) {
|
|
|
61235
61334
|
let priv = gobj.priv;
|
|
61236
61335
|
gobj_write_str_attr(gobj, "main_topic", kw && kw.topic || "");
|
|
61237
61336
|
if (!priv._fold_model) return 0;
|
|
61337
|
+
let keep = fold_keep_node(gobj);
|
|
61338
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61238
61339
|
rebuild_fold_model(gobj);
|
|
61239
61340
|
fold_expand_to_depth(priv._fold_model, priv._fold_state, gobj_read_integer_attr(gobj, "expand_depth"));
|
|
61240
61341
|
reconcile_fold(gobj, {
|
|
61241
61342
|
relayout: true,
|
|
61242
|
-
|
|
61343
|
+
keep,
|
|
61344
|
+
keep_vp: vp
|
|
61243
61345
|
});
|
|
61244
61346
|
return 0;
|
|
61245
61347
|
}
|
|
@@ -61265,11 +61367,14 @@ function ac_set_node_mode$1(gobj, event, kw, src) {
|
|
|
61265
61367
|
gobj_write_str_attr(gobj, "node_mode", mode);
|
|
61266
61368
|
priv._open_nodes = /* @__PURE__ */ new Set();
|
|
61267
61369
|
if (!priv.graph || !priv._fold_model) return 0;
|
|
61370
|
+
let keep = fold_keep_node(gobj);
|
|
61371
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61268
61372
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
61269
61373
|
reshape_more_chips(gobj);
|
|
61270
61374
|
reconcile_fold(gobj, {
|
|
61271
61375
|
relayout: true,
|
|
61272
|
-
|
|
61376
|
+
keep,
|
|
61377
|
+
keep_vp: vp
|
|
61273
61378
|
});
|
|
61274
61379
|
return 0;
|
|
61275
61380
|
}
|
|
@@ -61939,6 +62044,7 @@ function create_gclass$4(gclass_name) {
|
|
|
61939
62044
|
["EV_FIND_NODES", 0],
|
|
61940
62045
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
|
61941
62046
|
["EV_LAYOUT_AUTOSET", event_flag_t.EVF_OUTPUT_EVENT],
|
|
62047
|
+
["EV_CAMERA_CHANGED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
61942
62048
|
["EV_TOGGLE_FOLD", 0],
|
|
61943
62049
|
["EV_SHOW_MORE", 0],
|
|
61944
62050
|
["EV_EXPAND_ALL", 0],
|
|
@@ -62025,6 +62131,7 @@ var attrs_table$3 = [
|
|
|
62025
62131
|
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"),
|
|
62026
62132
|
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"),
|
|
62027
62133
|
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"),
|
|
62134
|
+
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"),
|
|
62028
62135
|
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
62029
62136
|
SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
62030
62137
|
SDATA(data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
|
|
@@ -62112,7 +62219,8 @@ function mt_create$3(gobj) {
|
|
|
62112
62219
|
main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
|
|
62113
62220
|
loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
|
|
62114
62221
|
node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
|
|
62115
|
-
node_labels: gobj_read_bool_attr(gobj, "node_labels")
|
|
62222
|
+
node_labels: gobj_read_bool_attr(gobj, "node_labels"),
|
|
62223
|
+
camera: gobj_read_attr(gobj, "camera") || {}
|
|
62116
62224
|
}, gobj);
|
|
62117
62225
|
populate_nodes_tree_options(gobj);
|
|
62118
62226
|
if (priv.with_treedb_tables) {}
|
|
@@ -63796,6 +63904,23 @@ function ac_layout_autoset(gobj, event, kw, src) {
|
|
|
63796
63904
|
return 0;
|
|
63797
63905
|
}
|
|
63798
63906
|
/************************************************************
|
|
63907
|
+
* The reader moved the camera, and it has settled.
|
|
63908
|
+
*
|
|
63909
|
+
* Persisted like every other choice of this toolbar, and for the
|
|
63910
|
+
* same reason: a zoom is a decision, and a decision that dies with
|
|
63911
|
+
* the tab has to be taken again every morning. The engine debounces,
|
|
63912
|
+
* so this runs once per gesture and not once per wheel notch.
|
|
63913
|
+
************************************************************/
|
|
63914
|
+
function ac_camera_changed(gobj, event, kw, src) {
|
|
63915
|
+
gobj_write_attr(gobj, "camera", {
|
|
63916
|
+
zoom: kw.zoom,
|
|
63917
|
+
x: kw.x,
|
|
63918
|
+
y: kw.y
|
|
63919
|
+
});
|
|
63920
|
+
gobj_save_persistent_attrs(gobj, "camera");
|
|
63921
|
+
return 0;
|
|
63922
|
+
}
|
|
63923
|
+
/************************************************************
|
|
63799
63924
|
* Forward the find down to the graph child.
|
|
63800
63925
|
************************************************************/
|
|
63801
63926
|
function ac_find_nodes(gobj, event, kw, src) {
|
|
@@ -64075,6 +64200,11 @@ function create_gclass$3(gclass_name) {
|
|
|
64075
64200
|
ac_layout_autoset,
|
|
64076
64201
|
null
|
|
64077
64202
|
],
|
|
64203
|
+
[
|
|
64204
|
+
"EV_CAMERA_CHANGED",
|
|
64205
|
+
ac_camera_changed,
|
|
64206
|
+
null
|
|
64207
|
+
],
|
|
64078
64208
|
[
|
|
64079
64209
|
"EV_LEGEND_TOPIC",
|
|
64080
64210
|
ac_legend_topic,
|
|
@@ -64143,6 +64273,7 @@ function create_gclass$3(gclass_name) {
|
|
|
64143
64273
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
64144
64274
|
["EV_FIND_RESULT", 0],
|
|
64145
64275
|
["EV_LAYOUT_AUTOSET", 0],
|
|
64276
|
+
["EV_CAMERA_CHANGED", 0],
|
|
64146
64277
|
["EV_LEGEND_TOPIC", 0],
|
|
64147
64278
|
["EV_LEGEND_STATE", 0],
|
|
64148
64279
|
["EV_TOPIC_SELECTED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuneta/gobj-ui",
|
|
3
|
-
"version": "7.23.
|
|
3
|
+
"version": "7.23.105",
|
|
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, node, x, y} -- a NODE at a viewport pixel, not G6's own position, which does not restore. 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,126 @@ 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
|
+
/* A camera is saved as a NODE at a VIEWPORT PIXEL, plus the zoom --
|
|
3374
|
+
* never as G6's own position.
|
|
3375
|
+
*
|
|
3376
|
+
* `translateTo()` does not take viewport pixels: an absolute translate
|
|
3377
|
+
* leaves the camera at `canvasCentre - T/zoom`, so `getPosition()` and
|
|
3378
|
+
* `translateTo()` are only each other's inverse at zoom 1. Restoring
|
|
3379
|
+
* one with the other brought the zoom back and put the graph somewhere
|
|
3380
|
+
* else, which is exactly what it looks like -- and the reason to write
|
|
3381
|
+
* the pair down (`yui_graph_place_at`, gobj-ui 7.23.29) was that it
|
|
3382
|
+
* had already cost fifteen iterations once.
|
|
3383
|
+
*
|
|
3384
|
+
* A node and a pixel is also the SAME thing the folds keep across a
|
|
3385
|
+
* rebuild. One idea, used twice. */
|
|
3386
|
+
function read_camera(gobj)
|
|
3387
|
+
{
|
|
3388
|
+
let priv = gobj.priv;
|
|
3389
|
+
let graph = priv.graph;
|
|
3390
|
+
|
|
3391
|
+
if(!graph || typeof graph.getZoom !== "function") {
|
|
3392
|
+
return null;
|
|
3393
|
+
}
|
|
3394
|
+
try {
|
|
3395
|
+
let z = graph.getZoom();
|
|
3396
|
+
let node = fold_keep_node(gobj);
|
|
3397
|
+
let vp = node? yui_graph_viewport_of(graph, node) : null;
|
|
3398
|
+
if(!is_number(z) || !node || !vp || !is_number(vp[0]) || !is_number(vp[1])) {
|
|
3399
|
+
return null;
|
|
3400
|
+
}
|
|
3401
|
+
return {zoom: z, node: node, x: vp[0], y: vp[1]};
|
|
3402
|
+
} catch(e) {
|
|
3403
|
+
return null; /* between renders: there is no camera to read */
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
/* Returns true when it placed the viewport, so the caller knows
|
|
3408
|
+
* not to fit. */
|
|
3409
|
+
async function restore_camera(gobj)
|
|
3410
|
+
{
|
|
3411
|
+
let priv = gobj.priv;
|
|
3412
|
+
let graph = priv.graph;
|
|
3413
|
+
let cam = gobj_read_attr(gobj, "camera");
|
|
3414
|
+
|
|
3415
|
+
if(!graph || empty_json(cam) || !is_number(cam.zoom) ||
|
|
3416
|
+
!cam.node || !is_number(cam.x) || !is_number(cam.y)) {
|
|
3417
|
+
return false; /* nothing saved, or saved by a version that wrote
|
|
3418
|
+
G6's own position -- which never restored */
|
|
3419
|
+
}
|
|
3420
|
+
/* Checked BEFORE the zoom is touched: a saved node that this load
|
|
3421
|
+
* does not have (the treedb changed under it) must leave the graph
|
|
3422
|
+
* to its opening fit, not at a zoom with no framing to go with it. */
|
|
3423
|
+
if(!yui_graph_viewport_of(graph, cam.node)) {
|
|
3424
|
+
return false;
|
|
3425
|
+
}
|
|
3426
|
+
priv._camera_restoring = true;
|
|
3427
|
+
try {
|
|
3428
|
+
await graph.zoomTo(cam.zoom);
|
|
3429
|
+
await yui_graph_place_at(graph, cam.node, [cam.x, cam.y]);
|
|
3430
|
+
return true;
|
|
3431
|
+
} catch(e) {
|
|
3432
|
+
log_error(`${gobj_short_name(gobj)}: cannot restore the camera: ${e}`);
|
|
3433
|
+
return false;
|
|
3434
|
+
} finally {
|
|
3435
|
+
priv._camera_restoring = false;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
/************************************************************
|
|
3440
|
+
* A move settled: tell whoever persists it.
|
|
3441
|
+
*
|
|
3442
|
+
* Debounced on a REAL time and with the browser's timer: a
|
|
3443
|
+
* wheel notch, a pinch and a drag each fire `aftertransform`
|
|
3444
|
+
* many times, and what is worth saving is where the gesture
|
|
3445
|
+
* ENDED. (`set_timeout` from gobj-js is a C_TIMER and shadows
|
|
3446
|
+
* the global -- this module imports neither.)
|
|
3447
|
+
************************************************************/
|
|
3448
|
+
function publish_camera_later(gobj)
|
|
3449
|
+
{
|
|
3450
|
+
let priv = gobj.priv;
|
|
3451
|
+
|
|
3452
|
+
if(priv._camera_restoring || !priv._camera_placed) {
|
|
3453
|
+
return; /* our own move, or the opening one */
|
|
3454
|
+
}
|
|
3455
|
+
if(priv._camera_timer) {
|
|
3456
|
+
clearTimeout(priv._camera_timer);
|
|
3457
|
+
}
|
|
3458
|
+
priv._camera_timer = setTimeout(() => {
|
|
3459
|
+
priv._camera_timer = null;
|
|
3460
|
+
if(gobj_is_destroying(gobj)) {
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
let cam = read_camera(gobj);
|
|
3464
|
+
if(!cam) {
|
|
3465
|
+
return; /* Error already logged, or nothing to read */
|
|
3466
|
+
}
|
|
3467
|
+
gobj_write_attr(gobj, "camera", cam);
|
|
3468
|
+
gobj_publish_event(gobj, "EV_CAMERA_CHANGED", cam);
|
|
3469
|
+
}, CAMERA_SETTLE_MS);
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3332
3472
|
/************************************************************
|
|
3333
3473
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
3334
3474
|
* where nothing on a card can be read.
|
|
@@ -9241,11 +9381,26 @@ async function reconcile_fold_apply(gobj, opts, visible)
|
|
|
9241
9381
|
await graph_layout(gobj);
|
|
9242
9382
|
}
|
|
9243
9383
|
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9384
|
+
/* THE VIEWPORT, and only here.
|
|
9385
|
+
*
|
|
9386
|
+
* `restore` is the reader's saved camera coming back on the
|
|
9387
|
+
* first draw after a reload; `fit` is the opening frame of a
|
|
9388
|
+
* graph nobody has looked at yet; `keep` holds a node at the
|
|
9389
|
+
* pixel it was on across a rebuild. Anything else leaves the
|
|
9390
|
+
* camera alone, which is the default on purpose: a rebuild of
|
|
9391
|
+
* the CONTENT is not a reason to move the reader. */
|
|
9392
|
+
if(!(opts.restore && await restore_camera(gobj))) {
|
|
9393
|
+
if(opts.fit) {
|
|
9394
|
+
await graph_fit_readable(gobj);
|
|
9395
|
+
} else if(opts.keep && opts.keep_vp) {
|
|
9396
|
+
await yui_graph_place_at(graph, opts.keep, opts.keep_vp);
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9399
|
+
/* Whichever branch ran -- including none of them, which is a
|
|
9400
|
+
* graph somebody had arranged and left where it was -- the
|
|
9401
|
+
* opening viewport is decided from here on, and every move after
|
|
9402
|
+
* it is the reader's and worth remembering. */
|
|
9403
|
+
priv._camera_placed = true;
|
|
9249
9404
|
|
|
9250
9405
|
update_resize_handles_position(gobj);
|
|
9251
9406
|
update_port_resize_handles_position(gobj);
|
|
@@ -9484,10 +9639,23 @@ function ac_load_data(gobj, event, kw, src)
|
|
|
9484
9639
|
* nodes are about to be built: settle it first. */
|
|
9485
9640
|
apply_layout_direction(gobj);
|
|
9486
9641
|
|
|
9487
|
-
/*
|
|
9488
|
-
*
|
|
9489
|
-
*
|
|
9490
|
-
|
|
9642
|
+
/* THE FIRST load decides the viewport: the reader's saved
|
|
9643
|
+
* camera if there is one, else the opening fit -- and only
|
|
9644
|
+
* when WE laid the graph out, since a saved arrangement
|
|
9645
|
+
* includes where its owner was looking.
|
|
9646
|
+
*
|
|
9647
|
+
* Every load AFTER that is a REFRESH, and a refresh must not
|
|
9648
|
+
* move the camera: the reader asked for fresh data, not for a
|
|
9649
|
+
* new viewport. It holds a node instead, so the graph does not
|
|
9650
|
+
* jump even if the layout changed under it. */
|
|
9651
|
+
let vp_opts;
|
|
9652
|
+
if(!priv._camera_placed) {
|
|
9653
|
+
vp_opts = {restore: true, fit: auto_laid};
|
|
9654
|
+
} else {
|
|
9655
|
+
let keep = fold_keep_node(gobj);
|
|
9656
|
+
vp_opts = {keep: keep, keep_vp: yui_graph_viewport_of(priv.graph, keep)};
|
|
9657
|
+
}
|
|
9658
|
+
reconcile_fold(gobj, Object.assign({relayout: true}, vp_opts)).then(() => {
|
|
9491
9659
|
sync_history_plugin(gobj);
|
|
9492
9660
|
/* Nodes exist and are positioned now: apply a focus
|
|
9493
9661
|
* requested before the data was ready (deep link). */
|
|
@@ -10160,12 +10328,18 @@ function ac_set_main_topic(gobj, event, kw, src)
|
|
|
10160
10328
|
if(!priv._fold_model) {
|
|
10161
10329
|
return 0;
|
|
10162
10330
|
}
|
|
10331
|
+
/* Read BEFORE the model is rebuilt: the node is looked up in the
|
|
10332
|
+
* tree that is on screen. If the new trunk leaves it out,
|
|
10333
|
+
* `yui_graph_place_at` finds nothing and the camera simply stays
|
|
10334
|
+
* where it was -- which is still not a refit. */
|
|
10335
|
+
let keep = fold_keep_node(gobj);
|
|
10336
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
10163
10337
|
rebuild_fold_model(gobj);
|
|
10164
10338
|
fold_expand_to_depth(
|
|
10165
10339
|
priv._fold_model, priv._fold_state,
|
|
10166
10340
|
gobj_read_integer_attr(gobj, "expand_depth")
|
|
10167
10341
|
);
|
|
10168
|
-
reconcile_fold(gobj, {relayout: true,
|
|
10342
|
+
reconcile_fold(gobj, {relayout: true, keep: keep, keep_vp: vp});
|
|
10169
10343
|
return 0;
|
|
10170
10344
|
}
|
|
10171
10345
|
|
|
@@ -10200,9 +10374,15 @@ function ac_set_node_mode(gobj, event, kw, src)
|
|
|
10200
10374
|
if(!priv.graph || !priv._fold_model) {
|
|
10201
10375
|
return 0; /* applied when the records arrive */
|
|
10202
10376
|
}
|
|
10377
|
+
/* The camera is the reader's: a square and a card do not occupy
|
|
10378
|
+
* the same room, so the graph is laid out again -- and the node
|
|
10379
|
+
* being read stays at its pixel, at the zoom it was chosen at,
|
|
10380
|
+
* instead of the whole graph being fitted from scratch. */
|
|
10381
|
+
let keep = fold_keep_node(gobj);
|
|
10382
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
10203
10383
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
10204
10384
|
reshape_more_chips(gobj);
|
|
10205
|
-
reconcile_fold(gobj, {relayout: true,
|
|
10385
|
+
reconcile_fold(gobj, {relayout: true, keep: keep, keep_vp: vp});
|
|
10206
10386
|
return 0;
|
|
10207
10387
|
}
|
|
10208
10388
|
|
|
@@ -10898,6 +11078,9 @@ function create_gclass(gclass_name)
|
|
|
10898
11078
|
["EV_FIND_NODES", 0],
|
|
10899
11079
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
|
10900
11080
|
["EV_LAYOUT_AUTOSET", event_flag_t.EVF_OUTPUT_EVENT],
|
|
11081
|
+
/* Optional: a host that does not persist the viewport is not
|
|
11082
|
+
* doing anything wrong, so no "publish without subscribers". */
|
|
11083
|
+
["EV_CAMERA_CHANGED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
10901
11084
|
["EV_TOGGLE_FOLD", 0],
|
|
10902
11085
|
["EV_SHOW_MORE", 0],
|
|
10903
11086
|
["EV_EXPAND_ALL", 0],
|
|
@@ -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
|
);
|
|
@@ -2888,6 +2890,23 @@ function ac_layout_autoset(gobj, event, kw, src)
|
|
|
2888
2890
|
return 0;
|
|
2889
2891
|
}
|
|
2890
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
|
+
|
|
2891
2910
|
/************************************************************
|
|
2892
2911
|
* Forward the find down to the graph child.
|
|
2893
2912
|
************************************************************/
|
|
@@ -3128,6 +3147,7 @@ function create_gclass(gclass_name)
|
|
|
3128
3147
|
["EV_SET_NODE_MODE", ac_set_node_mode, null],
|
|
3129
3148
|
["EV_TOGGLE_NODE_LABELS", ac_toggle_node_labels, null],
|
|
3130
3149
|
["EV_LAYOUT_AUTOSET", ac_layout_autoset, null],
|
|
3150
|
+
["EV_CAMERA_CHANGED", ac_camera_changed, null],
|
|
3131
3151
|
["EV_LEGEND_TOPIC", ac_legend_topic, null],
|
|
3132
3152
|
["EV_LEGEND_STATE", ac_legend_state, null],
|
|
3133
3153
|
["EV_FIND_RESULT", ac_find_result, null],
|
|
@@ -3173,6 +3193,7 @@ function create_gclass(gclass_name)
|
|
|
3173
3193
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
3174
3194
|
["EV_FIND_RESULT", 0],
|
|
3175
3195
|
["EV_LAYOUT_AUTOSET", 0],
|
|
3196
|
+
["EV_CAMERA_CHANGED", 0],
|
|
3176
3197
|
["EV_LEGEND_TOPIC", 0],
|
|
3177
3198
|
["EV_LEGEND_STATE", 0],
|
|
3178
3199
|
["EV_TOPIC_SELECTED",
|