@yuneta/gobj-ui 7.23.103 → 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 +136 -8
- package/dist/gobj-ui.es.js +137 -9
- package/package.json +2 -1
- package/src/c_g6_nodes_tree.js +174 -11
- package/src/c_yui_treedb_graph.js +21 -0
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
|
@@ -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, 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"),
|
|
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,80 @@ 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" || 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
|
+
/************************************************************
|
|
56684
56768
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
56685
56769
|
* where nothing on a card can be read.
|
|
56686
56770
|
*
|
|
@@ -60624,8 +60708,11 @@ async function reconcile_fold_apply(gobj, opts, visible) {
|
|
|
60624
60708
|
}
|
|
60625
60709
|
await graph_draw(gobj);
|
|
60626
60710
|
if (changed || opts.relayout) await graph_layout(gobj);
|
|
60627
|
-
if (opts.
|
|
60628
|
-
|
|
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;
|
|
60629
60716
|
update_resize_handles_position(gobj);
|
|
60630
60717
|
update_port_resize_handles_position(gobj);
|
|
60631
60718
|
update_edge_icon_position(gobj);
|
|
@@ -60777,10 +60864,19 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
60777
60864
|
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: auto_layout failed: ${e}`);
|
|
60778
60865
|
}
|
|
60779
60866
|
apply_layout_direction(gobj);
|
|
60780
|
-
|
|
60781
|
-
|
|
60867
|
+
let vp_opts;
|
|
60868
|
+
if (!priv._camera_placed) vp_opts = {
|
|
60869
|
+
restore: true,
|
|
60782
60870
|
fit: auto_laid
|
|
60783
|
-
}
|
|
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(() => {
|
|
60784
60880
|
sync_history_plugin(gobj);
|
|
60785
60881
|
if (priv._pending_focus_topic !== null) {
|
|
60786
60882
|
let ft = priv._pending_focus_topic;
|
|
@@ -61240,11 +61336,14 @@ function ac_set_main_topic(gobj, event, kw, src) {
|
|
|
61240
61336
|
let priv = gobj.priv;
|
|
61241
61337
|
(0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "main_topic", kw && kw.topic || "");
|
|
61242
61338
|
if (!priv._fold_model) return 0;
|
|
61339
|
+
let keep = fold_keep_node(gobj);
|
|
61340
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61243
61341
|
rebuild_fold_model(gobj);
|
|
61244
61342
|
fold_expand_to_depth(priv._fold_model, priv._fold_state, (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "expand_depth"));
|
|
61245
61343
|
reconcile_fold(gobj, {
|
|
61246
61344
|
relayout: true,
|
|
61247
|
-
|
|
61345
|
+
keep,
|
|
61346
|
+
keep_vp: vp
|
|
61248
61347
|
});
|
|
61249
61348
|
return 0;
|
|
61250
61349
|
}
|
|
@@ -61270,11 +61369,14 @@ function ac_set_node_mode$1(gobj, event, kw, src) {
|
|
|
61270
61369
|
(0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "node_mode", mode);
|
|
61271
61370
|
priv._open_nodes = /* @__PURE__ */ new Set();
|
|
61272
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);
|
|
61273
61374
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
61274
61375
|
reshape_more_chips(gobj);
|
|
61275
61376
|
reconcile_fold(gobj, {
|
|
61276
61377
|
relayout: true,
|
|
61277
|
-
|
|
61378
|
+
keep,
|
|
61379
|
+
keep_vp: vp
|
|
61278
61380
|
});
|
|
61279
61381
|
return 0;
|
|
61280
61382
|
}
|
|
@@ -61944,6 +62046,7 @@ function create_gclass$4(gclass_name) {
|
|
|
61944
62046
|
["EV_FIND_NODES", 0],
|
|
61945
62047
|
["EV_FIND_RESULT", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
61946
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],
|
|
61947
62050
|
["EV_TOGGLE_FOLD", 0],
|
|
61948
62051
|
["EV_SHOW_MORE", 0],
|
|
61949
62052
|
["EV_EXPAND_ALL", 0],
|
|
@@ -62030,6 +62133,7 @@ var attrs_table$3 = [
|
|
|
62030
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"),
|
|
62031
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"),
|
|
62032
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"),
|
|
62033
62137
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
62034
62138
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
62035
62139
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
|
|
@@ -62117,7 +62221,8 @@ function mt_create$3(gobj) {
|
|
|
62117
62221
|
main_topic: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "main_topic") || "",
|
|
62118
62222
|
loose_topics: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "loose_topics") || [],
|
|
62119
62223
|
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")
|
|
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") || {}
|
|
62121
62226
|
}, gobj);
|
|
62122
62227
|
populate_nodes_tree_options(gobj);
|
|
62123
62228
|
if (priv.with_treedb_tables) {}
|
|
@@ -63801,6 +63906,23 @@ function ac_layout_autoset(gobj, event, kw, src) {
|
|
|
63801
63906
|
return 0;
|
|
63802
63907
|
}
|
|
63803
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
|
+
/************************************************************
|
|
63804
63926
|
* Forward the find down to the graph child.
|
|
63805
63927
|
************************************************************/
|
|
63806
63928
|
function ac_find_nodes(gobj, event, kw, src) {
|
|
@@ -64080,6 +64202,11 @@ function create_gclass$3(gclass_name) {
|
|
|
64080
64202
|
ac_layout_autoset,
|
|
64081
64203
|
null
|
|
64082
64204
|
],
|
|
64205
|
+
[
|
|
64206
|
+
"EV_CAMERA_CHANGED",
|
|
64207
|
+
ac_camera_changed,
|
|
64208
|
+
null
|
|
64209
|
+
],
|
|
64083
64210
|
[
|
|
64084
64211
|
"EV_LEGEND_TOPIC",
|
|
64085
64212
|
ac_legend_topic,
|
|
@@ -64148,6 +64275,7 @@ function create_gclass$3(gclass_name) {
|
|
|
64148
64275
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
64149
64276
|
["EV_FIND_RESULT", 0],
|
|
64150
64277
|
["EV_LAYOUT_AUTOSET", 0],
|
|
64278
|
+
["EV_CAMERA_CHANGED", 0],
|
|
64151
64279
|
["EV_LEGEND_TOPIC", 0],
|
|
64152
64280
|
["EV_LEGEND_STATE", 0],
|
|
64153
64281
|
["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, 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"),
|
|
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,80 @@ 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" || 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
|
+
/************************************************************
|
|
56679
56763
|
* Fit the graph in the viewport, but never zoom out past the point
|
|
56680
56764
|
* where nothing on a card can be read.
|
|
56681
56765
|
*
|
|
@@ -60619,8 +60703,11 @@ async function reconcile_fold_apply(gobj, opts, visible) {
|
|
|
60619
60703
|
}
|
|
60620
60704
|
await graph_draw(gobj);
|
|
60621
60705
|
if (changed || opts.relayout) await graph_layout(gobj);
|
|
60622
|
-
if (opts.
|
|
60623
|
-
|
|
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;
|
|
60624
60711
|
update_resize_handles_position(gobj);
|
|
60625
60712
|
update_port_resize_handles_position(gobj);
|
|
60626
60713
|
update_edge_icon_position(gobj);
|
|
@@ -60772,10 +60859,19 @@ function ac_load_data(gobj, event, kw, src) {
|
|
|
60772
60859
|
log_error(`${gobj_short_name(gobj)}: auto_layout failed: ${e}`);
|
|
60773
60860
|
}
|
|
60774
60861
|
apply_layout_direction(gobj);
|
|
60775
|
-
|
|
60776
|
-
|
|
60862
|
+
let vp_opts;
|
|
60863
|
+
if (!priv._camera_placed) vp_opts = {
|
|
60864
|
+
restore: true,
|
|
60777
60865
|
fit: auto_laid
|
|
60778
|
-
}
|
|
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(() => {
|
|
60779
60875
|
sync_history_plugin(gobj);
|
|
60780
60876
|
if (priv._pending_focus_topic !== null) {
|
|
60781
60877
|
let ft = priv._pending_focus_topic;
|
|
@@ -61235,11 +61331,14 @@ function ac_set_main_topic(gobj, event, kw, src) {
|
|
|
61235
61331
|
let priv = gobj.priv;
|
|
61236
61332
|
gobj_write_str_attr(gobj, "main_topic", kw && kw.topic || "");
|
|
61237
61333
|
if (!priv._fold_model) return 0;
|
|
61334
|
+
let keep = fold_keep_node(gobj);
|
|
61335
|
+
let vp = yui_graph_viewport_of(priv.graph, keep);
|
|
61238
61336
|
rebuild_fold_model(gobj);
|
|
61239
61337
|
fold_expand_to_depth(priv._fold_model, priv._fold_state, gobj_read_integer_attr(gobj, "expand_depth"));
|
|
61240
61338
|
reconcile_fold(gobj, {
|
|
61241
61339
|
relayout: true,
|
|
61242
|
-
|
|
61340
|
+
keep,
|
|
61341
|
+
keep_vp: vp
|
|
61243
61342
|
});
|
|
61244
61343
|
return 0;
|
|
61245
61344
|
}
|
|
@@ -61265,11 +61364,14 @@ function ac_set_node_mode$1(gobj, event, kw, src) {
|
|
|
61265
61364
|
gobj_write_str_attr(gobj, "node_mode", mode);
|
|
61266
61365
|
priv._open_nodes = /* @__PURE__ */ new Set();
|
|
61267
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);
|
|
61268
61369
|
reshape_nodes(gobj, all_record_node_ids(gobj));
|
|
61269
61370
|
reshape_more_chips(gobj);
|
|
61270
61371
|
reconcile_fold(gobj, {
|
|
61271
61372
|
relayout: true,
|
|
61272
|
-
|
|
61373
|
+
keep,
|
|
61374
|
+
keep_vp: vp
|
|
61273
61375
|
});
|
|
61274
61376
|
return 0;
|
|
61275
61377
|
}
|
|
@@ -61939,6 +62041,7 @@ function create_gclass$4(gclass_name) {
|
|
|
61939
62041
|
["EV_FIND_NODES", 0],
|
|
61940
62042
|
["EV_FIND_RESULT", event_flag_t.EVF_OUTPUT_EVENT],
|
|
61941
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],
|
|
61942
62045
|
["EV_TOGGLE_FOLD", 0],
|
|
61943
62046
|
["EV_SHOW_MORE", 0],
|
|
61944
62047
|
["EV_EXPAND_ALL", 0],
|
|
@@ -62025,6 +62128,7 @@ var attrs_table$3 = [
|
|
|
62025
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"),
|
|
62026
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"),
|
|
62027
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"),
|
|
62028
62132
|
SDATA(data_type_t.DTP_POINTER, "gobj_remote_yuno", 0, null, "Remote Yuno to request data"),
|
|
62029
62133
|
SDATA(data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
62030
62134
|
SDATA(data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
|
|
@@ -62112,7 +62216,8 @@ function mt_create$3(gobj) {
|
|
|
62112
62216
|
main_topic: gobj_read_str_attr(gobj, "main_topic") || "",
|
|
62113
62217
|
loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
|
|
62114
62218
|
node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
|
|
62115
|
-
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") || {}
|
|
62116
62221
|
}, gobj);
|
|
62117
62222
|
populate_nodes_tree_options(gobj);
|
|
62118
62223
|
if (priv.with_treedb_tables) {}
|
|
@@ -63796,6 +63901,23 @@ function ac_layout_autoset(gobj, event, kw, src) {
|
|
|
63796
63901
|
return 0;
|
|
63797
63902
|
}
|
|
63798
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
|
+
/************************************************************
|
|
63799
63921
|
* Forward the find down to the graph child.
|
|
63800
63922
|
************************************************************/
|
|
63801
63923
|
function ac_find_nodes(gobj, event, kw, src) {
|
|
@@ -64075,6 +64197,11 @@ function create_gclass$3(gclass_name) {
|
|
|
64075
64197
|
ac_layout_autoset,
|
|
64076
64198
|
null
|
|
64077
64199
|
],
|
|
64200
|
+
[
|
|
64201
|
+
"EV_CAMERA_CHANGED",
|
|
64202
|
+
ac_camera_changed,
|
|
64203
|
+
null
|
|
64204
|
+
],
|
|
64078
64205
|
[
|
|
64079
64206
|
"EV_LEGEND_TOPIC",
|
|
64080
64207
|
ac_legend_topic,
|
|
@@ -64143,6 +64270,7 @@ function create_gclass$3(gclass_name) {
|
|
|
64143
64270
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
64144
64271
|
["EV_FIND_RESULT", 0],
|
|
64145
64272
|
["EV_LAYOUT_AUTOSET", 0],
|
|
64273
|
+
["EV_CAMERA_CHANGED", 0],
|
|
64146
64274
|
["EV_LEGEND_TOPIC", 0],
|
|
64147
64275
|
["EV_LEGEND_STATE", 0],
|
|
64148
64276
|
["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.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],
|
|
@@ -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",
|