@yuneta/gobj-ui 7.23.17 → 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 +567 -170
- package/dist/gobj-ui.es.js +559 -162
- 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.css +11 -2
- package/src/c_yui_json_graph.js +237 -30
- 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,21 +10260,48 @@ 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;
|
|
10077
10271
|
/***************************************************************
|
|
10078
|
-
* Type
|
|
10079
|
-
|
|
10080
|
-
|
|
10272
|
+
* Type colours, ONE EXPLICIT PALETTE PER THEME and not a
|
|
10273
|
+
* derivation, for the same reason the tree keeps two sets of
|
|
10274
|
+
* CSS tokens: a colour that reads on white is not the same
|
|
10275
|
+
* colour lightened, and deriving one from the other made the
|
|
10276
|
+
* two views of one document disagree about what a string is.
|
|
10277
|
+
* These ARE the tree's tokens (`c_yui_json.css`), so the tree
|
|
10278
|
+
* and the graph now say green with the same green.
|
|
10279
|
+
*
|
|
10280
|
+
* The light values are darker than the `mx_json_viewer` ones
|
|
10281
|
+
* they came from, because those could not be read: `#FF8C00`
|
|
10282
|
+
* is mid-luminance, so it reaches at most **2.33:1 against any
|
|
10283
|
+
* background that exists** -- no card colour and no highlight
|
|
10284
|
+
* could have fixed it, only the colour itself. Orange and blue
|
|
10285
|
+
* moved until every type clears 4.5:1 on all five surfaces this
|
|
10286
|
+
* viewer paints (two light cards, the match chip, two dark
|
|
10287
|
+
* cards); red and green were already there.
|
|
10288
|
+
***************************************************************/
|
|
10289
|
+
var TYPE_COLORS_LIGHT = {
|
|
10081
10290
|
string: "#006000",
|
|
10082
|
-
number: "#
|
|
10083
|
-
boolean: "#
|
|
10084
|
-
null: "#
|
|
10291
|
+
number: "#b5330b",
|
|
10292
|
+
boolean: "#8C4D00",
|
|
10293
|
+
null: "#4359C6",
|
|
10085
10294
|
list: "#4C0099",
|
|
10086
10295
|
dict: "#4C0099"
|
|
10087
10296
|
};
|
|
10297
|
+
var TYPE_COLORS_DARK = {
|
|
10298
|
+
string: "#7ec87e",
|
|
10299
|
+
number: "#ff8a65",
|
|
10300
|
+
boolean: "#ffb74d",
|
|
10301
|
+
null: "#90a4f4",
|
|
10302
|
+
list: "#B799D6",
|
|
10303
|
+
dict: "#B799D6"
|
|
10304
|
+
};
|
|
10088
10305
|
/***************************************************************
|
|
10089
10306
|
* Group (card) colours. `fill` is the light-theme card; `tint`
|
|
10090
10307
|
* is the hue that fill hints at, used to derive the dark card —
|
|
@@ -10138,21 +10355,15 @@ function json_card_style(group, dark) {
|
|
|
10138
10355
|
};
|
|
10139
10356
|
}
|
|
10140
10357
|
/***************************************************************
|
|
10141
|
-
* A scalar's colour by type
|
|
10142
|
-
*
|
|
10143
|
-
*
|
|
10144
|
-
*
|
|
10145
|
-
*
|
|
10146
|
-
*
|
|
10147
|
-
* card (the worst type goes from 2.88:1 to 4.74:1) and washes the
|
|
10148
|
-
* six types toward one pastel, and past this point red, orange
|
|
10149
|
-
* and purple stop being tellable apart, which is the only reason
|
|
10150
|
-
* they are coloured at all.
|
|
10358
|
+
* A scalar's colour by type, straight out of the palette for
|
|
10359
|
+
* the theme. It used to be the light colour mixed with white,
|
|
10360
|
+
* which is how a document ended up green in one view and a
|
|
10361
|
+
* paler green in the other, and how the amount of white became
|
|
10362
|
+
* a knob nobody could set: enough of it to read on a dark card
|
|
10363
|
+
* washed the six types into one pastel.
|
|
10151
10364
|
***************************************************************/
|
|
10152
10365
|
function type_color(type, dark) {
|
|
10153
|
-
|
|
10154
|
-
if (!dark) return color;
|
|
10155
|
-
return `color-mix(in srgb, ${color} 40%, #ffffff)`;
|
|
10366
|
+
return (dark ? TYPE_COLORS_DARK : TYPE_COLORS_LIGHT)[type] || (dark ? "#e8eaed" : "black");
|
|
10156
10367
|
}
|
|
10157
10368
|
/******************************
|
|
10158
10369
|
* Framework Methods
|
|
@@ -10346,6 +10557,7 @@ function make_toolbar$3(gobj) {
|
|
|
10346
10557
|
]]);
|
|
10347
10558
|
let center_items = [];
|
|
10348
10559
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
10560
|
+
center_items.push(yui_graph_anchor_item(gobj, toolbar_wide));
|
|
10349
10561
|
center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
|
|
10350
10562
|
let current_layout = gobj_read_str_attr(gobj, "layout") || DEFAULT_LAYOUT;
|
|
10351
10563
|
let options = Object.keys(LAYOUTS$1).map(function(key) {
|
|
@@ -10428,6 +10640,15 @@ function build_graph$3(gobj) {
|
|
|
10428
10640
|
});
|
|
10429
10641
|
graph.on("aftertransform", () => {
|
|
10430
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);
|
|
10431
10652
|
});
|
|
10432
10653
|
graph.on(CanvasEvent.CLICK, (evt) => {});
|
|
10433
10654
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -10606,14 +10827,17 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
|
|
|
10606
10827
|
card_matched = true;
|
|
10607
10828
|
priv.match_count++;
|
|
10608
10829
|
}
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
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));
|
|
10615
10838
|
}
|
|
10616
10839
|
let label = get_group_label(path) || (is_dict ? "{}" : "[]");
|
|
10840
|
+
if (pending_complex.length > 0) label += is_dict ? ` {${pending_complex.length}}` : ` [${pending_complex.length}]`;
|
|
10617
10841
|
let foldable = pending_complex.length > 0;
|
|
10618
10842
|
let folded = foldable && priv.collapsed.has(path);
|
|
10619
10843
|
let colors = json_card_style(is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark);
|
|
@@ -10689,13 +10913,22 @@ function load_json(gobj) {
|
|
|
10689
10913
|
let preserve_view = !!priv.pending_preserve_view;
|
|
10690
10914
|
priv.pending_preserve_view = false;
|
|
10691
10915
|
if (nodes.length > 0) {
|
|
10916
|
+
let root_id = nodes[0].id;
|
|
10692
10917
|
graph.setData({
|
|
10693
10918
|
nodes,
|
|
10694
10919
|
edges
|
|
10695
10920
|
});
|
|
10696
10921
|
graph.render().then(() => {
|
|
10697
|
-
|
|
10698
|
-
|
|
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
|
+
});
|
|
10699
10932
|
});
|
|
10700
10933
|
}
|
|
10701
10934
|
}
|
|
@@ -10958,13 +11191,79 @@ function ac_theme$3(gobj, event, kw, src) {
|
|
|
10958
11191
|
return 0;
|
|
10959
11192
|
}
|
|
10960
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
|
+
/************************************************************
|
|
10961
11260
|
*
|
|
10962
11261
|
************************************************************/
|
|
10963
11262
|
function ac_zoom_in$2(gobj, event, kw, src) {
|
|
10964
11263
|
let graph = gobj.priv.graph;
|
|
10965
11264
|
if (graph) {
|
|
10966
11265
|
let z = graph.getZoom();
|
|
10967
|
-
graph.zoomTo(z * 1.2);
|
|
11266
|
+
after_camera$2(gobj, graph.zoomTo(z * 1.2));
|
|
10968
11267
|
}
|
|
10969
11268
|
return 0;
|
|
10970
11269
|
}
|
|
@@ -10975,7 +11274,7 @@ function ac_zoom_out$2(gobj, event, kw, src) {
|
|
|
10975
11274
|
let graph = gobj.priv.graph;
|
|
10976
11275
|
if (graph) {
|
|
10977
11276
|
let z = graph.getZoom();
|
|
10978
|
-
graph.zoomTo(z * .8);
|
|
11277
|
+
after_camera$2(gobj, graph.zoomTo(z * .8));
|
|
10979
11278
|
}
|
|
10980
11279
|
return 0;
|
|
10981
11280
|
}
|
|
@@ -10983,10 +11282,14 @@ function ac_zoom_out$2(gobj, event, kw, src) {
|
|
|
10983
11282
|
*
|
|
10984
11283
|
************************************************************/
|
|
10985
11284
|
function ac_zoom_reset$2(gobj, event, kw, src) {
|
|
10986
|
-
let
|
|
11285
|
+
let priv = gobj.priv;
|
|
11286
|
+
let graph = priv.graph;
|
|
10987
11287
|
if (graph && graph.rendered) {
|
|
10988
|
-
graph.zoomTo(1);
|
|
10989
|
-
|
|
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
|
+
}
|
|
10990
11293
|
}
|
|
10991
11294
|
return 0;
|
|
10992
11295
|
}
|
|
@@ -11005,12 +11308,21 @@ function ac_center$2(gobj, event, kw, src) {
|
|
|
11005
11308
|
*
|
|
11006
11309
|
************************************************************/
|
|
11007
11310
|
function ac_node_click$4(gobj, event, kw, src) {
|
|
11008
|
-
let
|
|
11311
|
+
let priv = gobj.priv;
|
|
11312
|
+
let graph = priv.graph;
|
|
11009
11313
|
let node_id = kw.evt.target.id;
|
|
11010
11314
|
let nodedata;
|
|
11011
11315
|
try {
|
|
11012
11316
|
nodedata = graph.getNodeData(node_id);
|
|
11013
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
|
+
}
|
|
11014
11326
|
if (nodedata && nodedata.data) gobj_publish_event(gobj, "EV_JSON_ITEM_CLICKED", {
|
|
11015
11327
|
path: nodedata.data.path,
|
|
11016
11328
|
id: node_id
|
|
@@ -11144,6 +11456,11 @@ function create_gclass$11(gclass_name) {
|
|
|
11144
11456
|
ac_node_click$4,
|
|
11145
11457
|
null
|
|
11146
11458
|
],
|
|
11459
|
+
[
|
|
11460
|
+
"EV_TOGGLE_ANCHOR",
|
|
11461
|
+
ac_toggle_anchor$2,
|
|
11462
|
+
null
|
|
11463
|
+
],
|
|
11147
11464
|
[
|
|
11148
11465
|
"EV_RESIZE",
|
|
11149
11466
|
ac_resize$2,
|
|
@@ -11174,6 +11491,7 @@ function create_gclass$11(gclass_name) {
|
|
|
11174
11491
|
["EV_ZOOM_RESET", 0],
|
|
11175
11492
|
["EV_CENTER", 0],
|
|
11176
11493
|
["EV_NODE_CLICK", 0],
|
|
11494
|
+
["EV_TOGGLE_ANCHOR", 0],
|
|
11177
11495
|
["EV_RESIZE", 0],
|
|
11178
11496
|
["EV_SHOW", 0],
|
|
11179
11497
|
["EV_HIDE", 0],
|
|
@@ -12695,7 +13013,11 @@ var PRIVATE_DATA$9 = {
|
|
|
12695
13013
|
node_by_id: null,
|
|
12696
13014
|
collapse_state: null,
|
|
12697
13015
|
resize_observer: null,
|
|
12698
|
-
_resize_raf: 0
|
|
13016
|
+
_resize_raf: 0,
|
|
13017
|
+
anchor_id: "",
|
|
13018
|
+
anchor_name: "",
|
|
13019
|
+
anchor_state: "off",
|
|
13020
|
+
last_zoom: 1
|
|
12699
13021
|
};
|
|
12700
13022
|
var __gclass__$9 = null;
|
|
12701
13023
|
var __layout_registered__ = false;
|
|
@@ -13088,6 +13410,7 @@ function make_toolbar$1(gobj) {
|
|
|
13088
13410
|
let center_items = [];
|
|
13089
13411
|
let right_items = [];
|
|
13090
13412
|
center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
|
|
13413
|
+
center_items.push(yui_graph_anchor_item(gobj, toolbar_wide));
|
|
13091
13414
|
center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
|
|
13092
13415
|
let options = [];
|
|
13093
13416
|
for (let key of Object.keys(LAYOUTS)) {
|
|
@@ -13163,6 +13486,15 @@ function build_graph$2(gobj) {
|
|
|
13163
13486
|
if (priv.theme) graph.setTheme(priv.theme);
|
|
13164
13487
|
graph.on("aftertransform", () => {
|
|
13165
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);
|
|
13166
13498
|
});
|
|
13167
13499
|
graph.on(NodeEvent.CLICK, (evt) => {
|
|
13168
13500
|
gobj_send_event(gobj, "EV_NODE_CLICK", { evt }, gobj);
|
|
@@ -13450,13 +13782,23 @@ function load_tree(gobj) {
|
|
|
13450
13782
|
let edges = [];
|
|
13451
13783
|
build_gobj_nodes(gobj, yuno, nodes, edges, null, true, layout_cfg.compact);
|
|
13452
13784
|
if (nodes.length > 0) {
|
|
13785
|
+
let root_id = nodes[0].id;
|
|
13453
13786
|
graph.setData({
|
|
13454
13787
|
nodes,
|
|
13455
13788
|
edges
|
|
13456
13789
|
});
|
|
13457
13790
|
graph.render().then(() => {
|
|
13458
|
-
|
|
13459
|
-
|
|
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
|
+
});
|
|
13460
13802
|
});
|
|
13461
13803
|
}
|
|
13462
13804
|
}
|
|
@@ -13958,13 +14300,77 @@ function ac_collapse_all(gobj, event, kw, src) {
|
|
|
13958
14300
|
return 0;
|
|
13959
14301
|
}
|
|
13960
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
|
+
/************************************************************
|
|
13961
14367
|
*
|
|
13962
14368
|
************************************************************/
|
|
13963
14369
|
function ac_zoom_in$1(gobj, event, kw, src) {
|
|
13964
14370
|
let graph = gobj.priv.graph;
|
|
13965
14371
|
if (graph) {
|
|
13966
14372
|
let z = graph.getZoom();
|
|
13967
|
-
graph.zoomTo(z * 1.2);
|
|
14373
|
+
after_camera$1(gobj, graph.zoomTo(z * 1.2));
|
|
13968
14374
|
}
|
|
13969
14375
|
return 0;
|
|
13970
14376
|
}
|
|
@@ -13975,7 +14381,7 @@ function ac_zoom_out$1(gobj, event, kw, src) {
|
|
|
13975
14381
|
let graph = gobj.priv.graph;
|
|
13976
14382
|
if (graph) {
|
|
13977
14383
|
let z = graph.getZoom();
|
|
13978
|
-
graph.zoomTo(z * .8);
|
|
14384
|
+
after_camera$1(gobj, graph.zoomTo(z * .8));
|
|
13979
14385
|
}
|
|
13980
14386
|
return 0;
|
|
13981
14387
|
}
|
|
@@ -13983,10 +14389,14 @@ function ac_zoom_out$1(gobj, event, kw, src) {
|
|
|
13983
14389
|
*
|
|
13984
14390
|
************************************************************/
|
|
13985
14391
|
function ac_zoom_reset$1(gobj, event, kw, src) {
|
|
13986
|
-
let
|
|
14392
|
+
let priv = gobj.priv;
|
|
14393
|
+
let graph = priv.graph;
|
|
13987
14394
|
if (graph && graph.rendered) {
|
|
13988
|
-
graph.zoomTo(1);
|
|
13989
|
-
|
|
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
|
+
}
|
|
13990
14400
|
}
|
|
13991
14401
|
return 0;
|
|
13992
14402
|
}
|
|
@@ -14014,6 +14424,14 @@ function ac_node_click$3(gobj, event, kw, src) {
|
|
|
14014
14424
|
if (nd && nd.data) node_data = nd.data;
|
|
14015
14425
|
} catch (e) {}
|
|
14016
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
|
+
}
|
|
14017
14435
|
let client_x = kw.evt && kw.evt.client ? kw.evt.client.x : 0;
|
|
14018
14436
|
let client_y = kw.evt && kw.evt.client ? kw.evt.client.y : 0;
|
|
14019
14437
|
show_popover(gobj, node_data, client_x, client_y);
|
|
@@ -14094,6 +14512,7 @@ function create_gclass$9(gclass_name) {
|
|
|
14094
14512
|
["EV_ZOOM_RESET", 0],
|
|
14095
14513
|
["EV_CENTER", 0],
|
|
14096
14514
|
["EV_NODE_CLICK", 0],
|
|
14515
|
+
["EV_TOGGLE_ANCHOR", 0],
|
|
14097
14516
|
["EV_RESIZE", 0],
|
|
14098
14517
|
["EV_SHOW", 0],
|
|
14099
14518
|
["EV_HIDE", 0]
|
|
@@ -14153,6 +14572,11 @@ function create_gclass$9(gclass_name) {
|
|
|
14153
14572
|
ac_node_click$3,
|
|
14154
14573
|
null
|
|
14155
14574
|
],
|
|
14575
|
+
[
|
|
14576
|
+
"EV_TOGGLE_ANCHOR",
|
|
14577
|
+
ac_toggle_anchor$1,
|
|
14578
|
+
null
|
|
14579
|
+
],
|
|
14156
14580
|
[
|
|
14157
14581
|
"EV_RESIZE",
|
|
14158
14582
|
ac_resize$1,
|
|
@@ -20715,123 +21139,6 @@ function register_c_yui_treedb_topic_with_form() {
|
|
|
20715
21139
|
return create_gclass$5(GCLASS_NAME$5);
|
|
20716
21140
|
}
|
|
20717
21141
|
//#endregion
|
|
20718
|
-
//#region src/lib_graph.js
|
|
20719
|
-
/************************************************************
|
|
20720
|
-
* Add class to elements selected
|
|
20721
|
-
************************************************************/
|
|
20722
|
-
function addClasses($container, selector, ...classNames) {
|
|
20723
|
-
$container.querySelectorAll(selector).forEach((el) => el.classList.add(...classNames));
|
|
20724
|
-
}
|
|
20725
|
-
/************************************************************
|
|
20726
|
-
* Remove class from elements selected
|
|
20727
|
-
************************************************************/
|
|
20728
|
-
function removeClasses($container, selector, ...classNames) {
|
|
20729
|
-
$container.querySelectorAll(selector).forEach((el) => el.classList.remove(...classNames));
|
|
20730
|
-
}
|
|
20731
|
-
/************************************************************
|
|
20732
|
-
* Toggle class to elements selected
|
|
20733
|
-
************************************************************/
|
|
20734
|
-
function toggleClasses($container, selector, ...classNames) {
|
|
20735
|
-
$container.querySelectorAll(selector).forEach((el) => classNames.forEach((cls) => el.classList.toggle(cls)));
|
|
20736
|
-
}
|
|
20737
|
-
/************************************************************
|
|
20738
|
-
* Remove child elements
|
|
20739
|
-
************************************************************/
|
|
20740
|
-
function removeChildElements($element) {
|
|
20741
|
-
$element.innerHTML = "";
|
|
20742
|
-
}
|
|
20743
|
-
/************************************************************
|
|
20744
|
-
* Disable/Enable elements
|
|
20745
|
-
************************************************************/
|
|
20746
|
-
function disableElements($container, selector) {
|
|
20747
|
-
$container.querySelectorAll(selector).forEach((el) => {
|
|
20748
|
-
el.setAttribute("disabled", "");
|
|
20749
|
-
});
|
|
20750
|
-
}
|
|
20751
|
-
function enableElements($container, selector) {
|
|
20752
|
-
$container.querySelectorAll(selector).forEach((el) => {
|
|
20753
|
-
if (el.hasAttribute("disabled")) el.removeAttribute("disabled");
|
|
20754
|
-
});
|
|
20755
|
-
}
|
|
20756
|
-
/************************************************************
|
|
20757
|
-
* Set or reset the color of 'enable submit action' state
|
|
20758
|
-
* Color green
|
|
20759
|
-
************************************************************/
|
|
20760
|
-
function set_submit_state($container, selector, set) {
|
|
20761
|
-
if (set) addClasses($container, selector, "color_submit_state");
|
|
20762
|
-
else removeClasses($container, selector, "color_submit_state");
|
|
20763
|
-
}
|
|
20764
|
-
/************************************************************
|
|
20765
|
-
* Set or reset the color of 'enable cancel action' state
|
|
20766
|
-
* Color red
|
|
20767
|
-
************************************************************/
|
|
20768
|
-
function set_cancel_state($container, selector, set) {
|
|
20769
|
-
if (set) addClasses($container, selector, "color_cancel_state");
|
|
20770
|
-
else removeClasses($container, selector, "color_cancel_state");
|
|
20771
|
-
}
|
|
20772
|
-
/************************************************************
|
|
20773
|
-
* Set or reset the color of 'enable actived' state
|
|
20774
|
-
* Color orange
|
|
20775
|
-
************************************************************/
|
|
20776
|
-
function set_active_state($container, selector, set) {
|
|
20777
|
-
if (set) addClasses($container, selector, "color_active_state");
|
|
20778
|
-
else removeClasses($container, selector, "color_active_state");
|
|
20779
|
-
}
|
|
20780
|
-
/************************************************************
|
|
20781
|
-
* Set or reset the 'pressed' state of a TOGGLE.
|
|
20782
|
-
*
|
|
20783
|
-
* Not one of the colours above: each of those names a KIND of
|
|
20784
|
-
* action (create, pending, history, destructive), and a toggle
|
|
20785
|
-
* is not an action -- it is a state the control is IN. So it
|
|
20786
|
-
* looks pressed instead of changing category, which also keeps
|
|
20787
|
-
* it out of the way of a neighbour wearing the same hue.
|
|
20788
|
-
************************************************************/
|
|
20789
|
-
function set_pressed_state($container, selector, set) {
|
|
20790
|
-
if (set) addClasses($container, selector, "pressed_state");
|
|
20791
|
-
else removeClasses($container, selector, "pressed_state");
|
|
20792
|
-
}
|
|
20793
|
-
/**
|
|
20794
|
-
* Returns a smart stroke color in `rgba()` format based on:
|
|
20795
|
-
* - The given fill color (hex or rgb[a])
|
|
20796
|
-
* - The current theme ('light' or 'dark')
|
|
20797
|
-
* - The adjustment factor (default 0.2)
|
|
20798
|
-
*
|
|
20799
|
-
* @param {string} fillColor - Fill color string
|
|
20800
|
-
* @param {string} theme - 'light' or 'dark'
|
|
20801
|
-
* @param {number} factor - Lighten/darken factor (default 0.2)
|
|
20802
|
-
* @returns {string} rgba(r, g, b, a)
|
|
20803
|
-
*/
|
|
20804
|
-
function getStrokeColor(fillColor, theme = "light", factor = .2) {
|
|
20805
|
-
let r, g, b, a = 1;
|
|
20806
|
-
if (fillColor.startsWith("#")) {
|
|
20807
|
-
let hex = fillColor.slice(1);
|
|
20808
|
-
if (hex.length === 3 || hex.length === 4) hex = hex.split("").map((c) => c + c).join("");
|
|
20809
|
-
if (hex.length === 6) {
|
|
20810
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
20811
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
20812
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
20813
|
-
} else if (hex.length === 8) {
|
|
20814
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
20815
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
20816
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
20817
|
-
a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
20818
|
-
} else throw new Error("Unsupported hex format");
|
|
20819
|
-
} else if (fillColor.startsWith("rgb")) {
|
|
20820
|
-
const match = fillColor.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\s*\)/);
|
|
20821
|
-
if (!match) throw new Error("Invalid rgb()/rgba() format.");
|
|
20822
|
-
r = parseInt(match[1], 10);
|
|
20823
|
-
g = parseInt(match[2], 10);
|
|
20824
|
-
b = parseInt(match[3], 10);
|
|
20825
|
-
if (match[4] !== void 0) a = parseFloat(match[4]);
|
|
20826
|
-
} else throw new Error("Unsupported color format.");
|
|
20827
|
-
const luminance = (.299 * r + .587 * g + .114 * b) / 255;
|
|
20828
|
-
let lighten;
|
|
20829
|
-
if (theme === "dark") lighten = luminance < .7;
|
|
20830
|
-
else lighten = luminance < .3;
|
|
20831
|
-
const adjust = (v) => lighten ? Math.min(255, Math.floor(v + (255 - v) * factor)) : Math.max(0, Math.floor(v * (1 - factor)));
|
|
20832
|
-
return `rgba(${adjust(r)}, ${adjust(g)}, ${adjust(b)}, ${a.toFixed(3)})`;
|
|
20833
|
-
}
|
|
20834
|
-
//#endregion
|
|
20835
21142
|
//#region src/treedb_node_label.js
|
|
20836
21143
|
/***********************************************************************
|
|
20837
21144
|
* treedb_node_label.js
|
|
@@ -47699,6 +48006,13 @@ var ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
|
|
|
47699
48006
|
stroke="currentColor" stroke-width="40"
|
|
47700
48007
|
stroke-dasharray="59 36"/>
|
|
47701
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>
|
|
47702
48016
|
<!-- floppy-disk (save) -->
|
|
47703
48017
|
<symbol id="g6-icon-save" viewBox="0 0 448 512">
|
|
47704
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"/>
|
|
@@ -47892,6 +48206,9 @@ var attrs_table$4 = [
|
|
|
47892
48206
|
SDATA_END()
|
|
47893
48207
|
];
|
|
47894
48208
|
var PRIVATE_DATA$4 = {
|
|
48209
|
+
anchor_id: "",
|
|
48210
|
+
anchor_state: "off",
|
|
48211
|
+
last_zoom: 1,
|
|
47895
48212
|
_xy: 100,
|
|
47896
48213
|
_edge_seq: 0,
|
|
47897
48214
|
_history_paused: false,
|
|
@@ -48188,6 +48505,15 @@ function configure_events(gobj) {
|
|
|
48188
48505
|
update_node_icon_position(gobj);
|
|
48189
48506
|
update_link_icon_position(gobj);
|
|
48190
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);
|
|
48191
48517
|
});
|
|
48192
48518
|
priv._on_language_changed = () => {
|
|
48193
48519
|
update_toolbar(gobj);
|
|
@@ -48425,6 +48751,12 @@ function configure_toolbar(gobj) {
|
|
|
48425
48751
|
value: "reset",
|
|
48426
48752
|
className: "EV_ZOOM_RESET",
|
|
48427
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)
|
|
48428
48760
|
}
|
|
48429
48761
|
];
|
|
48430
48762
|
if (priv.edit_mode) items.push({
|
|
@@ -48466,6 +48798,9 @@ function configure_toolbar(gobj) {
|
|
|
48466
48798
|
case "reset":
|
|
48467
48799
|
gobj_send_event(gobj, "EV_ZOOM_RESET", {}, gobj);
|
|
48468
48800
|
break;
|
|
48801
|
+
case "anchor":
|
|
48802
|
+
gobj_send_event(gobj, "EV_TOGGLE_ANCHOR", {}, gobj);
|
|
48803
|
+
break;
|
|
48469
48804
|
case "auto-fit":
|
|
48470
48805
|
gobj_send_event(gobj, "EV_AUTO_FIT", {}, gobj);
|
|
48471
48806
|
break;
|
|
@@ -49476,12 +49811,12 @@ function graph_find_nodes(gobj, term) {
|
|
|
49476
49811
|
async function graph_zoom_in(gobj) {
|
|
49477
49812
|
let graph = gobj.priv.graph;
|
|
49478
49813
|
const z = graph.getZoom();
|
|
49479
|
-
|
|
49814
|
+
after_camera(gobj, graph.zoomTo(z * 1.1));
|
|
49480
49815
|
}
|
|
49481
49816
|
async function graph_zoom_out(gobj) {
|
|
49482
49817
|
let graph = gobj.priv.graph;
|
|
49483
49818
|
const z = graph.getZoom();
|
|
49484
|
-
|
|
49819
|
+
after_camera(gobj, graph.zoomTo(z * .9));
|
|
49485
49820
|
}
|
|
49486
49821
|
async function graph_render(gobj) {
|
|
49487
49822
|
await gobj.priv.graph.render();
|
|
@@ -52482,7 +52817,56 @@ function ac_zoom_out(gobj, event, kw, src) {
|
|
|
52482
52817
|
return 0;
|
|
52483
52818
|
}
|
|
52484
52819
|
function ac_zoom_reset(gobj, event, kw, src) {
|
|
52485
|
-
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);
|
|
52486
52870
|
return 0;
|
|
52487
52871
|
}
|
|
52488
52872
|
/************************************************************
|
|
@@ -52656,6 +53040,13 @@ function ac_node_click$2(gobj, event, kw, src) {
|
|
|
52656
53040
|
let priv = gobj.priv;
|
|
52657
53041
|
let graph = priv.graph;
|
|
52658
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
|
+
}
|
|
52659
53050
|
try {
|
|
52660
53051
|
let nodedata = graph.getNodeData(node_id);
|
|
52661
53052
|
if (nodedata && nodedata.data && nodedata.data.desc) {
|
|
@@ -52917,6 +53308,11 @@ function create_gclass$4(gclass_name) {
|
|
|
52917
53308
|
ac_zoom_reset,
|
|
52918
53309
|
null
|
|
52919
53310
|
],
|
|
53311
|
+
[
|
|
53312
|
+
"EV_TOGGLE_ANCHOR",
|
|
53313
|
+
ac_toggle_anchor,
|
|
53314
|
+
null
|
|
53315
|
+
],
|
|
52920
53316
|
[
|
|
52921
53317
|
"EV_TOGGLE_TOOLBARS",
|
|
52922
53318
|
ac_toggle_toolbars,
|
|
@@ -53030,6 +53426,7 @@ function create_gclass$4(gclass_name) {
|
|
|
53030
53426
|
["EV_ZOOM_IN", 0],
|
|
53031
53427
|
["EV_ZOOM_OUT", 0],
|
|
53032
53428
|
["EV_ZOOM_RESET", 0],
|
|
53429
|
+
["EV_TOGGLE_ANCHOR", 0],
|
|
53033
53430
|
["EV_TOGGLE_TOOLBARS", 0],
|
|
53034
53431
|
["EV_AUTO_FIT", 0],
|
|
53035
53432
|
["EV_ZOOM_SELECTION", 0],
|