@yuneta/gobj-ui 7.23.18 → 7.23.19
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/dist/gobj-ui.cjs.js +531 -151
- package/dist/gobj-ui.es.js +523 -143
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +135 -3
- package/src/c_yui_gobj_tree_js.js +185 -7
- package/src/c_yui_json_graph.js +200 -10
- package/src/lib_icons.js +7 -0
- package/src/yui_graph_camera.js +95 -0
package/dist/gobj-ui.es.js
CHANGED
|
@@ -9345,6 +9345,123 @@ function yui_toolbar(attrs = {}, items = []) {
|
|
|
9345
9345
|
return $toolbar;
|
|
9346
9346
|
}
|
|
9347
9347
|
//#endregion
|
|
9348
|
+
//#region src/lib_graph.js
|
|
9349
|
+
/************************************************************
|
|
9350
|
+
* Add class to elements selected
|
|
9351
|
+
************************************************************/
|
|
9352
|
+
function addClasses($container, selector, ...classNames) {
|
|
9353
|
+
$container.querySelectorAll(selector).forEach((el) => el.classList.add(...classNames));
|
|
9354
|
+
}
|
|
9355
|
+
/************************************************************
|
|
9356
|
+
* Remove class from elements selected
|
|
9357
|
+
************************************************************/
|
|
9358
|
+
function removeClasses($container, selector, ...classNames) {
|
|
9359
|
+
$container.querySelectorAll(selector).forEach((el) => el.classList.remove(...classNames));
|
|
9360
|
+
}
|
|
9361
|
+
/************************************************************
|
|
9362
|
+
* Toggle class to elements selected
|
|
9363
|
+
************************************************************/
|
|
9364
|
+
function toggleClasses($container, selector, ...classNames) {
|
|
9365
|
+
$container.querySelectorAll(selector).forEach((el) => classNames.forEach((cls) => el.classList.toggle(cls)));
|
|
9366
|
+
}
|
|
9367
|
+
/************************************************************
|
|
9368
|
+
* Remove child elements
|
|
9369
|
+
************************************************************/
|
|
9370
|
+
function removeChildElements($element) {
|
|
9371
|
+
$element.innerHTML = "";
|
|
9372
|
+
}
|
|
9373
|
+
/************************************************************
|
|
9374
|
+
* Disable/Enable elements
|
|
9375
|
+
************************************************************/
|
|
9376
|
+
function disableElements($container, selector) {
|
|
9377
|
+
$container.querySelectorAll(selector).forEach((el) => {
|
|
9378
|
+
el.setAttribute("disabled", "");
|
|
9379
|
+
});
|
|
9380
|
+
}
|
|
9381
|
+
function enableElements($container, selector) {
|
|
9382
|
+
$container.querySelectorAll(selector).forEach((el) => {
|
|
9383
|
+
if (el.hasAttribute("disabled")) el.removeAttribute("disabled");
|
|
9384
|
+
});
|
|
9385
|
+
}
|
|
9386
|
+
/************************************************************
|
|
9387
|
+
* Set or reset the color of 'enable submit action' state
|
|
9388
|
+
* Color green
|
|
9389
|
+
************************************************************/
|
|
9390
|
+
function set_submit_state($container, selector, set) {
|
|
9391
|
+
if (set) addClasses($container, selector, "color_submit_state");
|
|
9392
|
+
else removeClasses($container, selector, "color_submit_state");
|
|
9393
|
+
}
|
|
9394
|
+
/************************************************************
|
|
9395
|
+
* Set or reset the color of 'enable cancel action' state
|
|
9396
|
+
* Color red
|
|
9397
|
+
************************************************************/
|
|
9398
|
+
function set_cancel_state($container, selector, set) {
|
|
9399
|
+
if (set) addClasses($container, selector, "color_cancel_state");
|
|
9400
|
+
else removeClasses($container, selector, "color_cancel_state");
|
|
9401
|
+
}
|
|
9402
|
+
/************************************************************
|
|
9403
|
+
* Set or reset the color of 'enable actived' state
|
|
9404
|
+
* Color orange
|
|
9405
|
+
************************************************************/
|
|
9406
|
+
function set_active_state($container, selector, set) {
|
|
9407
|
+
if (set) addClasses($container, selector, "color_active_state");
|
|
9408
|
+
else removeClasses($container, selector, "color_active_state");
|
|
9409
|
+
}
|
|
9410
|
+
/************************************************************
|
|
9411
|
+
* Set or reset the 'pressed' state of a TOGGLE.
|
|
9412
|
+
*
|
|
9413
|
+
* Not one of the colours above: each of those names a KIND of
|
|
9414
|
+
* action (create, pending, history, destructive), and a toggle
|
|
9415
|
+
* is not an action -- it is a state the control is IN. So it
|
|
9416
|
+
* looks pressed instead of changing category, which also keeps
|
|
9417
|
+
* it out of the way of a neighbour wearing the same hue.
|
|
9418
|
+
************************************************************/
|
|
9419
|
+
function set_pressed_state($container, selector, set) {
|
|
9420
|
+
if (set) addClasses($container, selector, "pressed_state");
|
|
9421
|
+
else removeClasses($container, selector, "pressed_state");
|
|
9422
|
+
}
|
|
9423
|
+
/**
|
|
9424
|
+
* Returns a smart stroke color in `rgba()` format based on:
|
|
9425
|
+
* - The given fill color (hex or rgb[a])
|
|
9426
|
+
* - The current theme ('light' or 'dark')
|
|
9427
|
+
* - The adjustment factor (default 0.2)
|
|
9428
|
+
*
|
|
9429
|
+
* @param {string} fillColor - Fill color string
|
|
9430
|
+
* @param {string} theme - 'light' or 'dark'
|
|
9431
|
+
* @param {number} factor - Lighten/darken factor (default 0.2)
|
|
9432
|
+
* @returns {string} rgba(r, g, b, a)
|
|
9433
|
+
*/
|
|
9434
|
+
function getStrokeColor(fillColor, theme = "light", factor = .2) {
|
|
9435
|
+
let r, g, b, a = 1;
|
|
9436
|
+
if (fillColor.startsWith("#")) {
|
|
9437
|
+
let hex = fillColor.slice(1);
|
|
9438
|
+
if (hex.length === 3 || hex.length === 4) hex = hex.split("").map((c) => c + c).join("");
|
|
9439
|
+
if (hex.length === 6) {
|
|
9440
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
9441
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
9442
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
9443
|
+
} else if (hex.length === 8) {
|
|
9444
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
9445
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
9446
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
9447
|
+
a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
9448
|
+
} else throw new Error("Unsupported hex format");
|
|
9449
|
+
} else if (fillColor.startsWith("rgb")) {
|
|
9450
|
+
const match = fillColor.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\s*\)/);
|
|
9451
|
+
if (!match) throw new Error("Invalid rgb()/rgba() format.");
|
|
9452
|
+
r = parseInt(match[1], 10);
|
|
9453
|
+
g = parseInt(match[2], 10);
|
|
9454
|
+
b = parseInt(match[3], 10);
|
|
9455
|
+
if (match[4] !== void 0) a = parseFloat(match[4]);
|
|
9456
|
+
} else throw new Error("Unsupported color format.");
|
|
9457
|
+
const luminance = (.299 * r + .587 * g + .114 * b) / 255;
|
|
9458
|
+
let lighten;
|
|
9459
|
+
if (theme === "dark") lighten = luminance < .7;
|
|
9460
|
+
else lighten = luminance < .3;
|
|
9461
|
+
const adjust = (v) => lighten ? Math.min(255, Math.floor(v + (255 - v) * factor)) : Math.max(0, Math.floor(v * (1 - factor)));
|
|
9462
|
+
return `rgba(${adjust(r)}, ${adjust(g)}, ${adjust(b)}, ${a.toFixed(3)})`;
|
|
9463
|
+
}
|
|
9464
|
+
//#endregion
|
|
9348
9465
|
//#region src/yui_graph_camera.js
|
|
9349
9466
|
/***********************************************************************
|
|
9350
9467
|
* yui_graph_camera.js
|
|
@@ -9493,6 +9610,79 @@ function yui_graph_fold_items(gobj, wide) {
|
|
|
9493
9610
|
return [camera_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand all", wide, " transform: rotate(90deg);"), camera_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all", wide)];
|
|
9494
9611
|
}
|
|
9495
9612
|
/************************************************************
|
|
9613
|
+
* The ANCHOR toggle: pick one element and every camera move
|
|
9614
|
+
* from then on leaves it in the MIDDLE.
|
|
9615
|
+
*
|
|
9616
|
+
* Why it exists: a graph that fits on screen is unreadable at
|
|
9617
|
+
* the zoom that makes it fit, and the zoom that makes it
|
|
9618
|
+
* readable does not fit -- so the useful view is always a
|
|
9619
|
+
* fraction of the document, and which fraction is the whole
|
|
9620
|
+
* question. Without an anchor, `1:1` lands wherever the
|
|
9621
|
+
* layout's origin happens to be and the reader hunts for the
|
|
9622
|
+
* node they were looking at.
|
|
9623
|
+
*
|
|
9624
|
+
* Crosshairs and not a thumbtack. A tack says "do not move
|
|
9625
|
+
* this", which is what a pinned NODE would mean in a graph you
|
|
9626
|
+
* can drag nodes around in; this pins the CAMERA to a target,
|
|
9627
|
+
* which is what a crosshair says and what every map that
|
|
9628
|
+
* offers it draws. It is also in the icon set already, and a
|
|
9629
|
+
* `yi-*` class that is not is a black square.
|
|
9630
|
+
*
|
|
9631
|
+
* Three states, because two would not say what a press does:
|
|
9632
|
+
*
|
|
9633
|
+
* off no target; the camera behaves as it always did
|
|
9634
|
+
* arming waiting for you to click the element you mean
|
|
9635
|
+
* on locked; every zoom re-centres on it
|
|
9636
|
+
************************************************************/
|
|
9637
|
+
function yui_graph_anchor_item(gobj, wide) {
|
|
9638
|
+
return camera_button(gobj, "yi-location-crosshairs", "EV_TOGGLE_ANCHOR", "anchor view", wide);
|
|
9639
|
+
}
|
|
9640
|
+
/************************************************************
|
|
9641
|
+
* Paint the anchor button. `state` is "off" | "arming" | "on".
|
|
9642
|
+
*
|
|
9643
|
+
* Both live states look PRESSED, because both are states the
|
|
9644
|
+
* control is IN rather than actions; what separates them is the
|
|
9645
|
+
* colour, and only the waiting one takes a palette colour --
|
|
9646
|
+
* it is the one that needs the reader to do something next.
|
|
9647
|
+
************************************************************/
|
|
9648
|
+
function yui_graph_update_anchor($container, state) {
|
|
9649
|
+
if (!$container) return;
|
|
9650
|
+
let $btn = $container.querySelector(".EV_TOGGLE_ANCHOR");
|
|
9651
|
+
if (!$btn) return;
|
|
9652
|
+
set_pressed_state($container, ".EV_TOGGLE_ANCHOR", state !== "off");
|
|
9653
|
+
$btn.classList.toggle("color_pending_state", state === "arming");
|
|
9654
|
+
$btn.setAttribute("aria-pressed", state === "on" ? "true" : "false");
|
|
9655
|
+
let key = state === "arming" ? "click the element to centre on" : state === "on" ? "centred: click to release" : "anchor view";
|
|
9656
|
+
$btn.setAttribute("title", t(key));
|
|
9657
|
+
$btn.setAttribute("data-i18n-title", key);
|
|
9658
|
+
$btn.setAttribute("aria-label", t(key));
|
|
9659
|
+
$btn.setAttribute("data-i18n-aria-label", key);
|
|
9660
|
+
}
|
|
9661
|
+
/************************************************************
|
|
9662
|
+
* Put `node_id` back in the middle of the viewport.
|
|
9663
|
+
*
|
|
9664
|
+
* Called AFTER the camera moved, never instead of moving it:
|
|
9665
|
+
* the zoom is what the reader asked for, the centring is what
|
|
9666
|
+
* keeps the thing they are reading under their eyes.
|
|
9667
|
+
*
|
|
9668
|
+
* Guarded on every side because it runs on every wheel notch:
|
|
9669
|
+
* a graph mid-rebuild, an anchor whose node went away with a
|
|
9670
|
+
* fold, a G6 version without `focusElement` -- none of those is
|
|
9671
|
+
* an error worth a log line per notch, they are all "there is
|
|
9672
|
+
* nothing to centre right now".
|
|
9673
|
+
************************************************************/
|
|
9674
|
+
function yui_graph_center_on(graph, node_id) {
|
|
9675
|
+
if (!graph || !node_id) return false;
|
|
9676
|
+
try {
|
|
9677
|
+
if (typeof graph.getElementData === "function" && !graph.getElementData(node_id)) return false;
|
|
9678
|
+
if (typeof graph.focusElement !== "function") return false;
|
|
9679
|
+
graph.focusElement(node_id);
|
|
9680
|
+
return true;
|
|
9681
|
+
} catch (e) {
|
|
9682
|
+
return false;
|
|
9683
|
+
}
|
|
9684
|
+
}
|
|
9685
|
+
/************************************************************
|
|
9496
9686
|
* A refresh button, for the graphs that offer one.
|
|
9497
9687
|
************************************************************/
|
|
9498
9688
|
function yui_graph_refresh_item(gobj, wide) {
|
|
@@ -10070,7 +10260,11 @@ var PRIVATE_DATA$11 = {
|
|
|
10070
10260
|
$layout_select: null,
|
|
10071
10261
|
$find_input: null,
|
|
10072
10262
|
$find_result: null,
|
|
10073
|
-
$find_count: null
|
|
10263
|
+
$find_count: null,
|
|
10264
|
+
anchor_id: "",
|
|
10265
|
+
anchor_path: "",
|
|
10266
|
+
anchor_state: "off",
|
|
10267
|
+
last_zoom: 1
|
|
10074
10268
|
};
|
|
10075
10269
|
var __gclass__$11 = null;
|
|
10076
10270
|
var __layout_registered__$1 = false;
|
|
@@ -10363,6 +10557,7 @@ function make_toolbar$3(gobj) {
|
|
|
10363
10557
|
]]);
|
|
10364
10558
|
let center_items = [];
|
|
10365
10559
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
10560
|
+
center_items.push(yui_graph_anchor_item(gobj, toolbar_wide));
|
|
10366
10561
|
center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
|
|
10367
10562
|
let current_layout = gobj_read_str_attr(gobj, "layout") || DEFAULT_LAYOUT;
|
|
10368
10563
|
let options = Object.keys(LAYOUTS$1).map(function(key) {
|
|
@@ -10445,6 +10640,15 @@ function build_graph$3(gobj) {
|
|
|
10445
10640
|
});
|
|
10446
10641
|
graph.on("aftertransform", () => {
|
|
10447
10642
|
yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
|
|
10643
|
+
let z = priv.last_zoom;
|
|
10644
|
+
try {
|
|
10645
|
+
z = priv.graph.getZoom();
|
|
10646
|
+
} catch (e) {
|
|
10647
|
+
return;
|
|
10648
|
+
}
|
|
10649
|
+
if (z === priv.last_zoom) return;
|
|
10650
|
+
priv.last_zoom = z;
|
|
10651
|
+
if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
|
|
10448
10652
|
});
|
|
10449
10653
|
graph.on(CanvasEvent.CLICK, (evt) => {});
|
|
10450
10654
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -10623,14 +10827,17 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
|
|
|
10623
10827
|
card_matched = true;
|
|
10624
10828
|
priv.match_count++;
|
|
10625
10829
|
}
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10830
|
+
if (is_object(value) && json_object_size(value) > 0 || is_array(value) && value.length > 0) {
|
|
10831
|
+
pending_complex.push({
|
|
10832
|
+
key,
|
|
10833
|
+
value
|
|
10834
|
+
});
|
|
10835
|
+
continue;
|
|
10836
|
+
}
|
|
10837
|
+
lines.push(build_cell_html(key, value, type, dark, matched));
|
|
10632
10838
|
}
|
|
10633
10839
|
let label = get_group_label(path) || (is_dict ? "{}" : "[]");
|
|
10840
|
+
if (pending_complex.length > 0) label += is_dict ? ` {${pending_complex.length}}` : ` [${pending_complex.length}]`;
|
|
10634
10841
|
let foldable = pending_complex.length > 0;
|
|
10635
10842
|
let folded = foldable && priv.collapsed.has(path);
|
|
10636
10843
|
let colors = json_card_style(is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark);
|
|
@@ -10706,13 +10913,22 @@ function load_json(gobj) {
|
|
|
10706
10913
|
let preserve_view = !!priv.pending_preserve_view;
|
|
10707
10914
|
priv.pending_preserve_view = false;
|
|
10708
10915
|
if (nodes.length > 0) {
|
|
10916
|
+
let root_id = nodes[0].id;
|
|
10709
10917
|
graph.setData({
|
|
10710
10918
|
nodes,
|
|
10711
10919
|
edges
|
|
10712
10920
|
});
|
|
10713
10921
|
graph.render().then(() => {
|
|
10714
|
-
|
|
10715
|
-
|
|
10922
|
+
reanchor$1(gobj);
|
|
10923
|
+
if (preserve_view) {
|
|
10924
|
+
yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
|
|
10925
|
+
return;
|
|
10926
|
+
}
|
|
10927
|
+
Promise.resolve(graph.zoomTo(1)).then(() => {
|
|
10928
|
+
if (!yui_graph_center_on(graph, priv.anchor_id)) yui_graph_center_on(graph, root_id);
|
|
10929
|
+
priv.last_zoom = 1;
|
|
10930
|
+
yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
|
|
10931
|
+
});
|
|
10716
10932
|
});
|
|
10717
10933
|
}
|
|
10718
10934
|
}
|
|
@@ -10975,13 +11191,79 @@ function ac_theme$3(gobj, event, kw, src) {
|
|
|
10975
11191
|
return 0;
|
|
10976
11192
|
}
|
|
10977
11193
|
/************************************************************
|
|
11194
|
+
* Every camera action ends the same way once an anchor is
|
|
11195
|
+
* set: the thing you were reading goes back to the middle.
|
|
11196
|
+
*
|
|
11197
|
+
* Chained on the promise G6 returns rather than run beside it
|
|
11198
|
+
* -- centring before the zoom has finished centres the OLD
|
|
11199
|
+
* view, and the reader watches the node slide away and come
|
|
11200
|
+
* back.
|
|
11201
|
+
************************************************************/
|
|
11202
|
+
function after_camera$2(gobj, moved) {
|
|
11203
|
+
let priv = gobj.priv;
|
|
11204
|
+
if (priv.anchor_state !== "on" || !priv.anchor_id) return;
|
|
11205
|
+
Promise.resolve(moved).then(function() {
|
|
11206
|
+
yui_graph_center_on(priv.graph, priv.anchor_id);
|
|
11207
|
+
}).catch(function() {});
|
|
11208
|
+
}
|
|
11209
|
+
/************************************************************
|
|
11210
|
+
* Point the anchor back at its node after a rebuild.
|
|
11211
|
+
*
|
|
11212
|
+
* Node ids are generated per build, so the id the reader
|
|
11213
|
+
* chose does not survive a refresh, a fold or a layout
|
|
11214
|
+
* change -- the PATH does, and it is what the anchor really
|
|
11215
|
+
* means: "this place in the document", not "this card".
|
|
11216
|
+
*
|
|
11217
|
+
* A path that is no longer drawn (its parent got folded)
|
|
11218
|
+
* releases the anchor rather than leaving a button that says
|
|
11219
|
+
* it is locked onto something invisible.
|
|
11220
|
+
************************************************************/
|
|
11221
|
+
function reanchor$1(gobj) {
|
|
11222
|
+
let priv = gobj.priv;
|
|
11223
|
+
if (priv.anchor_state !== "on" || !priv.anchor_path) return;
|
|
11224
|
+
let found = "";
|
|
11225
|
+
try {
|
|
11226
|
+
for (let node of priv.graph.getNodeData()) if (node && node.data && node.data.path === priv.anchor_path) {
|
|
11227
|
+
found = node.id;
|
|
11228
|
+
break;
|
|
11229
|
+
}
|
|
11230
|
+
} catch (e) {
|
|
11231
|
+
return;
|
|
11232
|
+
}
|
|
11233
|
+
priv.anchor_id = found;
|
|
11234
|
+
if (!found) {
|
|
11235
|
+
priv.anchor_state = "off";
|
|
11236
|
+
priv.anchor_path = "";
|
|
11237
|
+
}
|
|
11238
|
+
yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
|
|
11239
|
+
}
|
|
11240
|
+
/************************************************************
|
|
11241
|
+
* Arm the anchor, or let it go.
|
|
11242
|
+
*
|
|
11243
|
+
* Three states and one button, so a press ADVANCES: with no
|
|
11244
|
+
* target it starts waiting for one, and with a target it
|
|
11245
|
+
* releases. Pressing it while it waits is a cancel, which is
|
|
11246
|
+
* what somebody who pressed it by mistake will try.
|
|
11247
|
+
************************************************************/
|
|
11248
|
+
function ac_toggle_anchor$2(gobj, event, kw, src) {
|
|
11249
|
+
let priv = gobj.priv;
|
|
11250
|
+
if (priv.anchor_state === "off") priv.anchor_state = "arming";
|
|
11251
|
+
else {
|
|
11252
|
+
priv.anchor_state = "off";
|
|
11253
|
+
priv.anchor_id = "";
|
|
11254
|
+
priv.anchor_path = "";
|
|
11255
|
+
}
|
|
11256
|
+
yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
|
|
11257
|
+
return 0;
|
|
11258
|
+
}
|
|
11259
|
+
/************************************************************
|
|
10978
11260
|
*
|
|
10979
11261
|
************************************************************/
|
|
10980
11262
|
function ac_zoom_in$2(gobj, event, kw, src) {
|
|
10981
11263
|
let graph = gobj.priv.graph;
|
|
10982
11264
|
if (graph) {
|
|
10983
11265
|
let z = graph.getZoom();
|
|
10984
|
-
graph.zoomTo(z * 1.2);
|
|
11266
|
+
after_camera$2(gobj, graph.zoomTo(z * 1.2));
|
|
10985
11267
|
}
|
|
10986
11268
|
return 0;
|
|
10987
11269
|
}
|
|
@@ -10992,7 +11274,7 @@ function ac_zoom_out$2(gobj, event, kw, src) {
|
|
|
10992
11274
|
let graph = gobj.priv.graph;
|
|
10993
11275
|
if (graph) {
|
|
10994
11276
|
let z = graph.getZoom();
|
|
10995
|
-
graph.zoomTo(z * .8);
|
|
11277
|
+
after_camera$2(gobj, graph.zoomTo(z * .8));
|
|
10996
11278
|
}
|
|
10997
11279
|
return 0;
|
|
10998
11280
|
}
|
|
@@ -11000,10 +11282,14 @@ function ac_zoom_out$2(gobj, event, kw, src) {
|
|
|
11000
11282
|
*
|
|
11001
11283
|
************************************************************/
|
|
11002
11284
|
function ac_zoom_reset$2(gobj, event, kw, src) {
|
|
11003
|
-
let
|
|
11285
|
+
let priv = gobj.priv;
|
|
11286
|
+
let graph = priv.graph;
|
|
11004
11287
|
if (graph && graph.rendered) {
|
|
11005
|
-
graph.zoomTo(1);
|
|
11006
|
-
|
|
11288
|
+
if (priv.anchor_state === "on" && priv.anchor_id) after_camera$2(gobj, graph.zoomTo(1));
|
|
11289
|
+
else {
|
|
11290
|
+
graph.zoomTo(1);
|
|
11291
|
+
graph.translateTo([0, 0]);
|
|
11292
|
+
}
|
|
11007
11293
|
}
|
|
11008
11294
|
return 0;
|
|
11009
11295
|
}
|
|
@@ -11022,12 +11308,21 @@ function ac_center$2(gobj, event, kw, src) {
|
|
|
11022
11308
|
*
|
|
11023
11309
|
************************************************************/
|
|
11024
11310
|
function ac_node_click$4(gobj, event, kw, src) {
|
|
11025
|
-
let
|
|
11311
|
+
let priv = gobj.priv;
|
|
11312
|
+
let graph = priv.graph;
|
|
11026
11313
|
let node_id = kw.evt.target.id;
|
|
11027
11314
|
let nodedata;
|
|
11028
11315
|
try {
|
|
11029
11316
|
nodedata = graph.getNodeData(node_id);
|
|
11030
11317
|
} catch (e) {}
|
|
11318
|
+
if (priv.anchor_state === "arming") {
|
|
11319
|
+
priv.anchor_id = node_id;
|
|
11320
|
+
priv.anchor_path = nodedata && nodedata.data ? nodedata.data.path : "";
|
|
11321
|
+
priv.anchor_state = "on";
|
|
11322
|
+
yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
|
|
11323
|
+
yui_graph_center_on(graph, node_id);
|
|
11324
|
+
return 0;
|
|
11325
|
+
}
|
|
11031
11326
|
if (nodedata && nodedata.data) gobj_publish_event(gobj, "EV_JSON_ITEM_CLICKED", {
|
|
11032
11327
|
path: nodedata.data.path,
|
|
11033
11328
|
id: node_id
|
|
@@ -11161,6 +11456,11 @@ function create_gclass$11(gclass_name) {
|
|
|
11161
11456
|
ac_node_click$4,
|
|
11162
11457
|
null
|
|
11163
11458
|
],
|
|
11459
|
+
[
|
|
11460
|
+
"EV_TOGGLE_ANCHOR",
|
|
11461
|
+
ac_toggle_anchor$2,
|
|
11462
|
+
null
|
|
11463
|
+
],
|
|
11164
11464
|
[
|
|
11165
11465
|
"EV_RESIZE",
|
|
11166
11466
|
ac_resize$2,
|
|
@@ -11191,6 +11491,7 @@ function create_gclass$11(gclass_name) {
|
|
|
11191
11491
|
["EV_ZOOM_RESET", 0],
|
|
11192
11492
|
["EV_CENTER", 0],
|
|
11193
11493
|
["EV_NODE_CLICK", 0],
|
|
11494
|
+
["EV_TOGGLE_ANCHOR", 0],
|
|
11194
11495
|
["EV_RESIZE", 0],
|
|
11195
11496
|
["EV_SHOW", 0],
|
|
11196
11497
|
["EV_HIDE", 0],
|
|
@@ -12712,7 +13013,11 @@ var PRIVATE_DATA$9 = {
|
|
|
12712
13013
|
node_by_id: null,
|
|
12713
13014
|
collapse_state: null,
|
|
12714
13015
|
resize_observer: null,
|
|
12715
|
-
_resize_raf: 0
|
|
13016
|
+
_resize_raf: 0,
|
|
13017
|
+
anchor_id: "",
|
|
13018
|
+
anchor_name: "",
|
|
13019
|
+
anchor_state: "off",
|
|
13020
|
+
last_zoom: 1
|
|
12716
13021
|
};
|
|
12717
13022
|
var __gclass__$9 = null;
|
|
12718
13023
|
var __layout_registered__ = false;
|
|
@@ -13105,6 +13410,7 @@ function make_toolbar$1(gobj) {
|
|
|
13105
13410
|
let center_items = [];
|
|
13106
13411
|
let right_items = [];
|
|
13107
13412
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
13413
|
+
center_items.push(yui_graph_anchor_item(gobj, toolbar_wide));
|
|
13108
13414
|
center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
|
|
13109
13415
|
let options = [];
|
|
13110
13416
|
for (let key of Object.keys(LAYOUTS)) {
|
|
@@ -13180,6 +13486,15 @@ function build_graph$2(gobj) {
|
|
|
13180
13486
|
if (priv.theme) graph.setTheme(priv.theme);
|
|
13181
13487
|
graph.on("aftertransform", () => {
|
|
13182
13488
|
yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
|
|
13489
|
+
let z = priv.last_zoom;
|
|
13490
|
+
try {
|
|
13491
|
+
z = priv.graph.getZoom();
|
|
13492
|
+
} catch (e) {
|
|
13493
|
+
return;
|
|
13494
|
+
}
|
|
13495
|
+
if (z === priv.last_zoom) return;
|
|
13496
|
+
priv.last_zoom = z;
|
|
13497
|
+
if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
|
|
13183
13498
|
});
|
|
13184
13499
|
graph.on(NodeEvent.CLICK, (evt) => {
|
|
13185
13500
|
gobj_send_event(gobj, "EV_NODE_CLICK", { evt }, gobj);
|
|
@@ -13467,13 +13782,23 @@ function load_tree(gobj) {
|
|
|
13467
13782
|
let edges = [];
|
|
13468
13783
|
build_gobj_nodes(gobj, yuno, nodes, edges, null, true, layout_cfg.compact);
|
|
13469
13784
|
if (nodes.length > 0) {
|
|
13785
|
+
let root_id = nodes[0].id;
|
|
13470
13786
|
graph.setData({
|
|
13471
13787
|
nodes,
|
|
13472
13788
|
edges
|
|
13473
13789
|
});
|
|
13474
13790
|
graph.render().then(() => {
|
|
13475
|
-
|
|
13476
|
-
|
|
13791
|
+
reanchor(gobj);
|
|
13792
|
+
if (anchor) {
|
|
13793
|
+
restore_view_anchored(gobj, anchor);
|
|
13794
|
+
return;
|
|
13795
|
+
}
|
|
13796
|
+
if (preserve_view) return;
|
|
13797
|
+
Promise.resolve(graph.zoomTo(1)).then(() => {
|
|
13798
|
+
if (!yui_graph_center_on(graph, priv.anchor_id)) yui_graph_center_on(graph, root_id);
|
|
13799
|
+
priv.last_zoom = 1;
|
|
13800
|
+
yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
|
|
13801
|
+
});
|
|
13477
13802
|
});
|
|
13478
13803
|
}
|
|
13479
13804
|
}
|
|
@@ -13975,13 +14300,77 @@ function ac_collapse_all(gobj, event, kw, src) {
|
|
|
13975
14300
|
return 0;
|
|
13976
14301
|
}
|
|
13977
14302
|
/************************************************************
|
|
14303
|
+
* Every camera action ends the same way once an anchor is
|
|
14304
|
+
* set: the thing you were reading goes back to the middle.
|
|
14305
|
+
*
|
|
14306
|
+
* Chained on the promise G6 returns rather than run beside it
|
|
14307
|
+
* -- centring before the zoom has finished centres the OLD
|
|
14308
|
+
* view, and the reader watches the node slide away and come
|
|
14309
|
+
* back.
|
|
14310
|
+
************************************************************/
|
|
14311
|
+
function after_camera$1(gobj, moved) {
|
|
14312
|
+
let priv = gobj.priv;
|
|
14313
|
+
if (priv.anchor_state !== "on" || !priv.anchor_id) return;
|
|
14314
|
+
Promise.resolve(moved).then(function() {
|
|
14315
|
+
yui_graph_center_on(priv.graph, priv.anchor_id);
|
|
14316
|
+
}).catch(function() {});
|
|
14317
|
+
}
|
|
14318
|
+
/************************************************************
|
|
14319
|
+
* Point the anchor back at its node after a rebuild.
|
|
14320
|
+
*
|
|
14321
|
+
* Node ids are generated per build, so the id the reader chose
|
|
14322
|
+
* does not survive an expand, a collapse or a refresh -- the
|
|
14323
|
+
* `full_name` does, and it is what the anchor really means:
|
|
14324
|
+
* "this gobj", not "this card".
|
|
14325
|
+
*
|
|
14326
|
+
* A gobj no longer drawn (its branch got collapsed, or it went
|
|
14327
|
+
* away) releases the anchor rather than leaving a button that
|
|
14328
|
+
* claims to be locked onto something invisible.
|
|
14329
|
+
************************************************************/
|
|
14330
|
+
function reanchor(gobj) {
|
|
14331
|
+
let priv = gobj.priv;
|
|
14332
|
+
if (priv.anchor_state !== "on" || !priv.anchor_name) return;
|
|
14333
|
+
let found = "";
|
|
14334
|
+
if (priv.node_by_id) {
|
|
14335
|
+
for (let id of Object.keys(priv.node_by_id)) if (priv.node_by_id[id].full_name === priv.anchor_name) {
|
|
14336
|
+
found = id;
|
|
14337
|
+
break;
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14340
|
+
priv.anchor_id = found;
|
|
14341
|
+
if (!found) {
|
|
14342
|
+
priv.anchor_state = "off";
|
|
14343
|
+
priv.anchor_name = "";
|
|
14344
|
+
}
|
|
14345
|
+
yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
|
|
14346
|
+
}
|
|
14347
|
+
/************************************************************
|
|
14348
|
+
* Arm the anchor, or let it go.
|
|
14349
|
+
*
|
|
14350
|
+
* Three states and one button, so a press ADVANCES: with no
|
|
14351
|
+
* target it starts waiting for one, and with a target it
|
|
14352
|
+
* releases. Pressing it while it waits is a cancel, which is
|
|
14353
|
+
* what somebody who pressed it by mistake will try.
|
|
14354
|
+
************************************************************/
|
|
14355
|
+
function ac_toggle_anchor$1(gobj, event, kw, src) {
|
|
14356
|
+
let priv = gobj.priv;
|
|
14357
|
+
if (priv.anchor_state === "off") priv.anchor_state = "arming";
|
|
14358
|
+
else {
|
|
14359
|
+
priv.anchor_state = "off";
|
|
14360
|
+
priv.anchor_id = "";
|
|
14361
|
+
priv.anchor_name = "";
|
|
14362
|
+
}
|
|
14363
|
+
yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
|
|
14364
|
+
return 0;
|
|
14365
|
+
}
|
|
14366
|
+
/************************************************************
|
|
13978
14367
|
*
|
|
13979
14368
|
************************************************************/
|
|
13980
14369
|
function ac_zoom_in$1(gobj, event, kw, src) {
|
|
13981
14370
|
let graph = gobj.priv.graph;
|
|
13982
14371
|
if (graph) {
|
|
13983
14372
|
let z = graph.getZoom();
|
|
13984
|
-
graph.zoomTo(z * 1.2);
|
|
14373
|
+
after_camera$1(gobj, graph.zoomTo(z * 1.2));
|
|
13985
14374
|
}
|
|
13986
14375
|
return 0;
|
|
13987
14376
|
}
|
|
@@ -13992,7 +14381,7 @@ function ac_zoom_out$1(gobj, event, kw, src) {
|
|
|
13992
14381
|
let graph = gobj.priv.graph;
|
|
13993
14382
|
if (graph) {
|
|
13994
14383
|
let z = graph.getZoom();
|
|
13995
|
-
graph.zoomTo(z * .8);
|
|
14384
|
+
after_camera$1(gobj, graph.zoomTo(z * .8));
|
|
13996
14385
|
}
|
|
13997
14386
|
return 0;
|
|
13998
14387
|
}
|
|
@@ -14000,10 +14389,14 @@ function ac_zoom_out$1(gobj, event, kw, src) {
|
|
|
14000
14389
|
*
|
|
14001
14390
|
************************************************************/
|
|
14002
14391
|
function ac_zoom_reset$1(gobj, event, kw, src) {
|
|
14003
|
-
let
|
|
14392
|
+
let priv = gobj.priv;
|
|
14393
|
+
let graph = priv.graph;
|
|
14004
14394
|
if (graph && graph.rendered) {
|
|
14005
|
-
graph.zoomTo(1);
|
|
14006
|
-
|
|
14395
|
+
if (priv.anchor_state === "on" && priv.anchor_id) after_camera$1(gobj, graph.zoomTo(1));
|
|
14396
|
+
else {
|
|
14397
|
+
graph.zoomTo(1);
|
|
14398
|
+
graph.translateTo([0, 0]);
|
|
14399
|
+
}
|
|
14007
14400
|
}
|
|
14008
14401
|
return 0;
|
|
14009
14402
|
}
|
|
@@ -14031,6 +14424,14 @@ function ac_node_click$3(gobj, event, kw, src) {
|
|
|
14031
14424
|
if (nd && nd.data) node_data = nd.data;
|
|
14032
14425
|
} catch (e) {}
|
|
14033
14426
|
if (!node_data) return 0;
|
|
14427
|
+
if (priv.anchor_state === "arming") {
|
|
14428
|
+
priv.anchor_id = node_id;
|
|
14429
|
+
priv.anchor_name = node_data.full_name || "";
|
|
14430
|
+
priv.anchor_state = "on";
|
|
14431
|
+
yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
|
|
14432
|
+
yui_graph_center_on(graph, node_id);
|
|
14433
|
+
return 0;
|
|
14434
|
+
}
|
|
14034
14435
|
let client_x = kw.evt && kw.evt.client ? kw.evt.client.x : 0;
|
|
14035
14436
|
let client_y = kw.evt && kw.evt.client ? kw.evt.client.y : 0;
|
|
14036
14437
|
show_popover(gobj, node_data, client_x, client_y);
|
|
@@ -14111,6 +14512,7 @@ function create_gclass$9(gclass_name) {
|
|
|
14111
14512
|
["EV_ZOOM_RESET", 0],
|
|
14112
14513
|
["EV_CENTER", 0],
|
|
14113
14514
|
["EV_NODE_CLICK", 0],
|
|
14515
|
+
["EV_TOGGLE_ANCHOR", 0],
|
|
14114
14516
|
["EV_RESIZE", 0],
|
|
14115
14517
|
["EV_SHOW", 0],
|
|
14116
14518
|
["EV_HIDE", 0]
|
|
@@ -14170,6 +14572,11 @@ function create_gclass$9(gclass_name) {
|
|
|
14170
14572
|
ac_node_click$3,
|
|
14171
14573
|
null
|
|
14172
14574
|
],
|
|
14575
|
+
[
|
|
14576
|
+
"EV_TOGGLE_ANCHOR",
|
|
14577
|
+
ac_toggle_anchor$1,
|
|
14578
|
+
null
|
|
14579
|
+
],
|
|
14173
14580
|
[
|
|
14174
14581
|
"EV_RESIZE",
|
|
14175
14582
|
ac_resize$1,
|
|
@@ -20732,123 +21139,6 @@ function register_c_yui_treedb_topic_with_form() {
|
|
|
20732
21139
|
return create_gclass$5(GCLASS_NAME$5);
|
|
20733
21140
|
}
|
|
20734
21141
|
//#endregion
|
|
20735
|
-
//#region src/lib_graph.js
|
|
20736
|
-
/************************************************************
|
|
20737
|
-
* Add class to elements selected
|
|
20738
|
-
************************************************************/
|
|
20739
|
-
function addClasses($container, selector, ...classNames) {
|
|
20740
|
-
$container.querySelectorAll(selector).forEach((el) => el.classList.add(...classNames));
|
|
20741
|
-
}
|
|
20742
|
-
/************************************************************
|
|
20743
|
-
* Remove class from elements selected
|
|
20744
|
-
************************************************************/
|
|
20745
|
-
function removeClasses($container, selector, ...classNames) {
|
|
20746
|
-
$container.querySelectorAll(selector).forEach((el) => el.classList.remove(...classNames));
|
|
20747
|
-
}
|
|
20748
|
-
/************************************************************
|
|
20749
|
-
* Toggle class to elements selected
|
|
20750
|
-
************************************************************/
|
|
20751
|
-
function toggleClasses($container, selector, ...classNames) {
|
|
20752
|
-
$container.querySelectorAll(selector).forEach((el) => classNames.forEach((cls) => el.classList.toggle(cls)));
|
|
20753
|
-
}
|
|
20754
|
-
/************************************************************
|
|
20755
|
-
* Remove child elements
|
|
20756
|
-
************************************************************/
|
|
20757
|
-
function removeChildElements($element) {
|
|
20758
|
-
$element.innerHTML = "";
|
|
20759
|
-
}
|
|
20760
|
-
/************************************************************
|
|
20761
|
-
* Disable/Enable elements
|
|
20762
|
-
************************************************************/
|
|
20763
|
-
function disableElements($container, selector) {
|
|
20764
|
-
$container.querySelectorAll(selector).forEach((el) => {
|
|
20765
|
-
el.setAttribute("disabled", "");
|
|
20766
|
-
});
|
|
20767
|
-
}
|
|
20768
|
-
function enableElements($container, selector) {
|
|
20769
|
-
$container.querySelectorAll(selector).forEach((el) => {
|
|
20770
|
-
if (el.hasAttribute("disabled")) el.removeAttribute("disabled");
|
|
20771
|
-
});
|
|
20772
|
-
}
|
|
20773
|
-
/************************************************************
|
|
20774
|
-
* Set or reset the color of 'enable submit action' state
|
|
20775
|
-
* Color green
|
|
20776
|
-
************************************************************/
|
|
20777
|
-
function set_submit_state($container, selector, set) {
|
|
20778
|
-
if (set) addClasses($container, selector, "color_submit_state");
|
|
20779
|
-
else removeClasses($container, selector, "color_submit_state");
|
|
20780
|
-
}
|
|
20781
|
-
/************************************************************
|
|
20782
|
-
* Set or reset the color of 'enable cancel action' state
|
|
20783
|
-
* Color red
|
|
20784
|
-
************************************************************/
|
|
20785
|
-
function set_cancel_state($container, selector, set) {
|
|
20786
|
-
if (set) addClasses($container, selector, "color_cancel_state");
|
|
20787
|
-
else removeClasses($container, selector, "color_cancel_state");
|
|
20788
|
-
}
|
|
20789
|
-
/************************************************************
|
|
20790
|
-
* Set or reset the color of 'enable actived' state
|
|
20791
|
-
* Color orange
|
|
20792
|
-
************************************************************/
|
|
20793
|
-
function set_active_state($container, selector, set) {
|
|
20794
|
-
if (set) addClasses($container, selector, "color_active_state");
|
|
20795
|
-
else removeClasses($container, selector, "color_active_state");
|
|
20796
|
-
}
|
|
20797
|
-
/************************************************************
|
|
20798
|
-
* Set or reset the 'pressed' state of a TOGGLE.
|
|
20799
|
-
*
|
|
20800
|
-
* Not one of the colours above: each of those names a KIND of
|
|
20801
|
-
* action (create, pending, history, destructive), and a toggle
|
|
20802
|
-
* is not an action -- it is a state the control is IN. So it
|
|
20803
|
-
* looks pressed instead of changing category, which also keeps
|
|
20804
|
-
* it out of the way of a neighbour wearing the same hue.
|
|
20805
|
-
************************************************************/
|
|
20806
|
-
function set_pressed_state($container, selector, set) {
|
|
20807
|
-
if (set) addClasses($container, selector, "pressed_state");
|
|
20808
|
-
else removeClasses($container, selector, "pressed_state");
|
|
20809
|
-
}
|
|
20810
|
-
/**
|
|
20811
|
-
* Returns a smart stroke color in `rgba()` format based on:
|
|
20812
|
-
* - The given fill color (hex or rgb[a])
|
|
20813
|
-
* - The current theme ('light' or 'dark')
|
|
20814
|
-
* - The adjustment factor (default 0.2)
|
|
20815
|
-
*
|
|
20816
|
-
* @param {string} fillColor - Fill color string
|
|
20817
|
-
* @param {string} theme - 'light' or 'dark'
|
|
20818
|
-
* @param {number} factor - Lighten/darken factor (default 0.2)
|
|
20819
|
-
* @returns {string} rgba(r, g, b, a)
|
|
20820
|
-
*/
|
|
20821
|
-
function getStrokeColor(fillColor, theme = "light", factor = .2) {
|
|
20822
|
-
let r, g, b, a = 1;
|
|
20823
|
-
if (fillColor.startsWith("#")) {
|
|
20824
|
-
let hex = fillColor.slice(1);
|
|
20825
|
-
if (hex.length === 3 || hex.length === 4) hex = hex.split("").map((c) => c + c).join("");
|
|
20826
|
-
if (hex.length === 6) {
|
|
20827
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
20828
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
20829
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
20830
|
-
} else if (hex.length === 8) {
|
|
20831
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
20832
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
20833
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
20834
|
-
a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
20835
|
-
} else throw new Error("Unsupported hex format");
|
|
20836
|
-
} else if (fillColor.startsWith("rgb")) {
|
|
20837
|
-
const match = fillColor.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\s*\)/);
|
|
20838
|
-
if (!match) throw new Error("Invalid rgb()/rgba() format.");
|
|
20839
|
-
r = parseInt(match[1], 10);
|
|
20840
|
-
g = parseInt(match[2], 10);
|
|
20841
|
-
b = parseInt(match[3], 10);
|
|
20842
|
-
if (match[4] !== void 0) a = parseFloat(match[4]);
|
|
20843
|
-
} else throw new Error("Unsupported color format.");
|
|
20844
|
-
const luminance = (.299 * r + .587 * g + .114 * b) / 255;
|
|
20845
|
-
let lighten;
|
|
20846
|
-
if (theme === "dark") lighten = luminance < .7;
|
|
20847
|
-
else lighten = luminance < .3;
|
|
20848
|
-
const adjust = (v) => lighten ? Math.min(255, Math.floor(v + (255 - v) * factor)) : Math.max(0, Math.floor(v * (1 - factor)));
|
|
20849
|
-
return `rgba(${adjust(r)}, ${adjust(g)}, ${adjust(b)}, ${a.toFixed(3)})`;
|
|
20850
|
-
}
|
|
20851
|
-
//#endregion
|
|
20852
21142
|
//#region src/treedb_node_label.js
|
|
20853
21143
|
/***********************************************************************
|
|
20854
21144
|
* treedb_node_label.js
|
|
@@ -47716,6 +48006,13 @@ var ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
|
|
|
47716
48006
|
stroke="currentColor" stroke-width="40"
|
|
47717
48007
|
stroke-dasharray="59 36"/>
|
|
47718
48008
|
</symbol>
|
|
48009
|
+
<!-- location-crosshairs (anchor the camera on one element).
|
|
48010
|
+
The SAME path as the DOM toolbars and their yi-location-crosshairs
|
|
48011
|
+
mask: the two toolbars sit in one console, and a control that
|
|
48012
|
+
does one thing has to be drawn one way. -->
|
|
48013
|
+
<symbol id="g6-icon-anchor" viewBox="0 0 576 512">
|
|
48014
|
+
<path fill="currentColor" d="M288-16c17.7 0 32 14.3 32 32l0 18.3c98.1 14 175.7 91.6 189.7 189.7l18.3 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-18.3 0c-14 98.1-91.6 175.7-189.7 189.7l0 18.3c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-18.3C157.9 463.7 80.3 386.1 66.3 288L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l18.3 0C80.3 125.9 157.9 48.3 256 34.3L256 16c0-17.7 14.3-32 32-32zM128 256a160 160 0 1 0 320 0 160 160 0 1 0 -320 0zm160-96a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"/>
|
|
48015
|
+
</symbol>
|
|
47719
48016
|
<!-- floppy-disk (save) -->
|
|
47720
48017
|
<symbol id="g6-icon-save" viewBox="0 0 448 512">
|
|
47721
48018
|
<path fill="currentColor" d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-242.7c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32L64 32zm32 96c0-17.7 14.3-32 32-32l160 0c17.7 0 32 14.3 32 32l0 64c0 17.7-14.3 32-32 32l-160 0c-17.7 0-32-14.3-32-32l0-64zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/>
|
|
@@ -47909,6 +48206,9 @@ var attrs_table$4 = [
|
|
|
47909
48206
|
SDATA_END()
|
|
47910
48207
|
];
|
|
47911
48208
|
var PRIVATE_DATA$4 = {
|
|
48209
|
+
anchor_id: "",
|
|
48210
|
+
anchor_state: "off",
|
|
48211
|
+
last_zoom: 1,
|
|
47912
48212
|
_xy: 100,
|
|
47913
48213
|
_edge_seq: 0,
|
|
47914
48214
|
_history_paused: false,
|
|
@@ -48205,6 +48505,15 @@ function configure_events(gobj) {
|
|
|
48205
48505
|
update_node_icon_position(gobj);
|
|
48206
48506
|
update_link_icon_position(gobj);
|
|
48207
48507
|
update_zoom_readout(gobj);
|
|
48508
|
+
let z = priv.last_zoom;
|
|
48509
|
+
try {
|
|
48510
|
+
z = priv.graph.getZoom();
|
|
48511
|
+
} catch (e) {
|
|
48512
|
+
return;
|
|
48513
|
+
}
|
|
48514
|
+
if (z === priv.last_zoom) return;
|
|
48515
|
+
priv.last_zoom = z;
|
|
48516
|
+
if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
|
|
48208
48517
|
});
|
|
48209
48518
|
priv._on_language_changed = () => {
|
|
48210
48519
|
update_toolbar(gobj);
|
|
@@ -48442,6 +48751,12 @@ function configure_toolbar(gobj) {
|
|
|
48442
48751
|
value: "reset",
|
|
48443
48752
|
className: "EV_ZOOM_RESET",
|
|
48444
48753
|
title: t("actual size")
|
|
48754
|
+
},
|
|
48755
|
+
{
|
|
48756
|
+
id: "g6-icon-anchor",
|
|
48757
|
+
value: "anchor",
|
|
48758
|
+
className: "EV_TOGGLE_ANCHOR" + (priv.anchor_state !== "off" ? " pressed_state" : "") + (priv.anchor_state === "arming" ? " color_pending_state" : ""),
|
|
48759
|
+
title: anchor_title(priv.anchor_state)
|
|
48445
48760
|
}
|
|
48446
48761
|
];
|
|
48447
48762
|
if (priv.edit_mode) items.push({
|
|
@@ -48483,6 +48798,9 @@ function configure_toolbar(gobj) {
|
|
|
48483
48798
|
case "reset":
|
|
48484
48799
|
gobj_send_event(gobj, "EV_ZOOM_RESET", {}, gobj);
|
|
48485
48800
|
break;
|
|
48801
|
+
case "anchor":
|
|
48802
|
+
gobj_send_event(gobj, "EV_TOGGLE_ANCHOR", {}, gobj);
|
|
48803
|
+
break;
|
|
48486
48804
|
case "auto-fit":
|
|
48487
48805
|
gobj_send_event(gobj, "EV_AUTO_FIT", {}, gobj);
|
|
48488
48806
|
break;
|
|
@@ -49493,12 +49811,12 @@ function graph_find_nodes(gobj, term) {
|
|
|
49493
49811
|
async function graph_zoom_in(gobj) {
|
|
49494
49812
|
let graph = gobj.priv.graph;
|
|
49495
49813
|
const z = graph.getZoom();
|
|
49496
|
-
|
|
49814
|
+
after_camera(gobj, graph.zoomTo(z * 1.1));
|
|
49497
49815
|
}
|
|
49498
49816
|
async function graph_zoom_out(gobj) {
|
|
49499
49817
|
let graph = gobj.priv.graph;
|
|
49500
49818
|
const z = graph.getZoom();
|
|
49501
|
-
|
|
49819
|
+
after_camera(gobj, graph.zoomTo(z * .9));
|
|
49502
49820
|
}
|
|
49503
49821
|
async function graph_render(gobj) {
|
|
49504
49822
|
await gobj.priv.graph.render();
|
|
@@ -52499,7 +52817,56 @@ function ac_zoom_out(gobj, event, kw, src) {
|
|
|
52499
52817
|
return 0;
|
|
52500
52818
|
}
|
|
52501
52819
|
function ac_zoom_reset(gobj, event, kw, src) {
|
|
52502
|
-
gobj.priv
|
|
52820
|
+
let priv = gobj.priv;
|
|
52821
|
+
let graph = priv.graph;
|
|
52822
|
+
if (priv.anchor_state === "on" && priv.anchor_id) after_camera(gobj, graph.zoomTo(1));
|
|
52823
|
+
else graph.zoomTo(1);
|
|
52824
|
+
return 0;
|
|
52825
|
+
}
|
|
52826
|
+
/************************************************************
|
|
52827
|
+
* What the anchor button says it will do next.
|
|
52828
|
+
************************************************************/
|
|
52829
|
+
function anchor_title(state) {
|
|
52830
|
+
if (state === "arming") return t("click the element to centre on");
|
|
52831
|
+
if (state === "on") return t("centred: click to release");
|
|
52832
|
+
return t("anchor view");
|
|
52833
|
+
}
|
|
52834
|
+
/************************************************************
|
|
52835
|
+
* Every camera action ends the same way once an anchor is
|
|
52836
|
+
* set: the thing you were reading goes back to the middle.
|
|
52837
|
+
*
|
|
52838
|
+
* Chained on the promise G6 returns rather than run beside it
|
|
52839
|
+
* -- centring before the zoom has finished centres the OLD
|
|
52840
|
+
* view, and the reader watches the node slide away and come
|
|
52841
|
+
* back.
|
|
52842
|
+
************************************************************/
|
|
52843
|
+
function after_camera(gobj, moved) {
|
|
52844
|
+
let priv = gobj.priv;
|
|
52845
|
+
if (priv.anchor_state !== "on" || !priv.anchor_id) return;
|
|
52846
|
+
Promise.resolve(moved).then(function() {
|
|
52847
|
+
yui_graph_center_on(priv.graph, priv.anchor_id);
|
|
52848
|
+
}).catch(function() {});
|
|
52849
|
+
}
|
|
52850
|
+
/************************************************************
|
|
52851
|
+
* Arm the anchor, or let it go.
|
|
52852
|
+
*
|
|
52853
|
+
* Three states and one button, so a press ADVANCES: with no
|
|
52854
|
+
* target it starts waiting for one, and with a target it
|
|
52855
|
+
* releases. Pressing it while it waits is a cancel, which is
|
|
52856
|
+
* what somebody who pressed it by mistake will try.
|
|
52857
|
+
*
|
|
52858
|
+
* Unlike the DOM toolbars, the button is not repainted in
|
|
52859
|
+
* place: this toolbar is a G6 plugin that rebuilds its whole
|
|
52860
|
+
* item list, and the pressed classes are baked into that list.
|
|
52861
|
+
************************************************************/
|
|
52862
|
+
function ac_toggle_anchor(gobj, event, kw, src) {
|
|
52863
|
+
let priv = gobj.priv;
|
|
52864
|
+
if (priv.anchor_state === "off") priv.anchor_state = "arming";
|
|
52865
|
+
else {
|
|
52866
|
+
priv.anchor_state = "off";
|
|
52867
|
+
priv.anchor_id = "";
|
|
52868
|
+
}
|
|
52869
|
+
update_toolbar(gobj);
|
|
52503
52870
|
return 0;
|
|
52504
52871
|
}
|
|
52505
52872
|
/************************************************************
|
|
@@ -52673,6 +53040,13 @@ function ac_node_click$2(gobj, event, kw, src) {
|
|
|
52673
53040
|
let priv = gobj.priv;
|
|
52674
53041
|
let graph = priv.graph;
|
|
52675
53042
|
let node_id = kw.evt.target.id;
|
|
53043
|
+
if (priv.anchor_state === "arming") {
|
|
53044
|
+
priv.anchor_id = node_id;
|
|
53045
|
+
priv.anchor_state = "on";
|
|
53046
|
+
update_toolbar(gobj);
|
|
53047
|
+
yui_graph_center_on(graph, node_id);
|
|
53048
|
+
return 0;
|
|
53049
|
+
}
|
|
52676
53050
|
try {
|
|
52677
53051
|
let nodedata = graph.getNodeData(node_id);
|
|
52678
53052
|
if (nodedata && nodedata.data && nodedata.data.desc) {
|
|
@@ -52934,6 +53308,11 @@ function create_gclass$4(gclass_name) {
|
|
|
52934
53308
|
ac_zoom_reset,
|
|
52935
53309
|
null
|
|
52936
53310
|
],
|
|
53311
|
+
[
|
|
53312
|
+
"EV_TOGGLE_ANCHOR",
|
|
53313
|
+
ac_toggle_anchor,
|
|
53314
|
+
null
|
|
53315
|
+
],
|
|
52937
53316
|
[
|
|
52938
53317
|
"EV_TOGGLE_TOOLBARS",
|
|
52939
53318
|
ac_toggle_toolbars,
|
|
@@ -53047,6 +53426,7 @@ function create_gclass$4(gclass_name) {
|
|
|
53047
53426
|
["EV_ZOOM_IN", 0],
|
|
53048
53427
|
["EV_ZOOM_OUT", 0],
|
|
53049
53428
|
["EV_ZOOM_RESET", 0],
|
|
53429
|
+
["EV_TOGGLE_ANCHOR", 0],
|
|
53050
53430
|
["EV_TOGGLE_TOOLBARS", 0],
|
|
53051
53431
|
["EV_AUTO_FIT", 0],
|
|
53052
53432
|
["EV_ZOOM_SELECTION", 0],
|