@yuneta/gobj-ui 7.23.138 → 7.23.140
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 +189 -22
- package/dist/gobj-ui.es.js +189 -22
- package/package.json +1 -1
- package/src/c_yui_node.js +6 -0
- package/src/c_yui_shell.js +54 -0
- package/src/c_yui_treedb_topics.css +25 -0
- package/src/c_yui_treedb_topics.js +144 -26
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -911,6 +911,7 @@ var PRIVATE_DATA$21 = {
|
|
|
911
911
|
navs: [],
|
|
912
912
|
item_index: {},
|
|
913
913
|
section_pos: {},
|
|
914
|
+
route_mru: [],
|
|
914
915
|
sub_routes: {},
|
|
915
916
|
event_handlers: {},
|
|
916
917
|
hash_handler: null,
|
|
@@ -1619,6 +1620,7 @@ function navigate_to(gobj, route, depth, no_drain) {
|
|
|
1619
1620
|
stage.active_route = matched_route;
|
|
1620
1621
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "current_route", route);
|
|
1621
1622
|
remember_position(gobj, route);
|
|
1623
|
+
remember_route(gobj, route);
|
|
1622
1624
|
update_secondary_nav_visibility(gobj, entry);
|
|
1623
1625
|
close_all_drawers(gobj);
|
|
1624
1626
|
close_toolbar_dropdown(gobj);
|
|
@@ -2645,6 +2647,40 @@ function remembered_position(gobj, route) {
|
|
|
2645
2647
|
return last;
|
|
2646
2648
|
}
|
|
2647
2649
|
/************************************************************
|
|
2650
|
+
* Every route the shell lands on, newest last, a route once.
|
|
2651
|
+
* Bounded: it answers "where was the reader last under X", and
|
|
2652
|
+
* a page that has been open all day has no business growing it.
|
|
2653
|
+
************************************************************/
|
|
2654
|
+
var ROUTE_MRU_MAX = 64;
|
|
2655
|
+
function remember_route(gobj, route) {
|
|
2656
|
+
let mru = gobj.priv.route_mru;
|
|
2657
|
+
let i = mru.indexOf(route);
|
|
2658
|
+
if (i >= 0) mru.splice(i, 1);
|
|
2659
|
+
mru.push(route);
|
|
2660
|
+
if (mru.length > ROUTE_MRU_MAX) mru.splice(0, mru.length - ROUTE_MRU_MAX);
|
|
2661
|
+
}
|
|
2662
|
+
/************************************************************
|
|
2663
|
+
* Where the reader last was UNDER `route`: the most recent
|
|
2664
|
+
* visited route that is `route` itself or lies below it, or
|
|
2665
|
+
* `route` when none does. A mirror of the url for the page, like
|
|
2666
|
+
* the section memory -- never stored, never applied on its own:
|
|
2667
|
+
* a control that wants to go back to a view AS IT WAS LEFT asks,
|
|
2668
|
+
* and navigates there itself.
|
|
2669
|
+
*
|
|
2670
|
+
* What it is for: a treedb graph's focus lives in the url
|
|
2671
|
+
* (`<graph>/<topic>`), so a button that opened the bare route
|
|
2672
|
+
* took the focus away every time it was used.
|
|
2673
|
+
************************************************************/
|
|
2674
|
+
function yui_shell_last_route_under(shell_gobj, route) {
|
|
2675
|
+
let priv = shell_gobj && shell_gobj.priv;
|
|
2676
|
+
if (!priv || !(0, _yuneta_gobj_js.is_array)(priv.route_mru) || !route) return route;
|
|
2677
|
+
for (let i = priv.route_mru.length - 1; i >= 0; i--) {
|
|
2678
|
+
let r = priv.route_mru[i];
|
|
2679
|
+
if (r === route || r.indexOf(route + "/") === 0) return r;
|
|
2680
|
+
}
|
|
2681
|
+
return route;
|
|
2682
|
+
}
|
|
2683
|
+
/************************************************************
|
|
2648
2684
|
* Resolve the shell that governs `gobj`: the nearest
|
|
2649
2685
|
* C_YUI_SHELL ancestor, else the last shell created on the
|
|
2650
2686
|
* page (apps have exactly one). Null when no shell exists —
|
|
@@ -21798,7 +21834,7 @@ var attrs_table$6 = [
|
|
|
21798
21834
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
|
|
21799
21835
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_cards_landing", 0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
|
|
21800
21836
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
|
|
21801
|
-
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "card_action_routes", 0, null, "
|
|
21837
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "card_action_routes", 0, null, "Hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show the info and table icons, and `graph` without its `/{topic}` is the toolbar's graph button (the card has no graph icon: its topic segment is a focus); absent ⇒ a single card that opens the table."),
|
|
21802
21838
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
|
|
21803
21839
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
|
|
21804
21840
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "source_url", 0, "", "The backend this view browses (host-supplied, typically the wss url of the connection), shown in the toolbar. A tab label names the TREEDB, and the same treedb name lives on more than one node, so the node has to be readable somewhere -- and a tab is too narrow to carry a url. Empty ⇒ nothing is shown."),
|
|
@@ -21908,15 +21944,12 @@ function cmd_get_topic_data$1(gobj, cmd, kw, src) {
|
|
|
21908
21944
|
************************************************************/
|
|
21909
21945
|
function build_ui$6(gobj) {
|
|
21910
21946
|
let source_url = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "source_url") || "";
|
|
21911
|
-
let
|
|
21912
|
-
let graph_tpl = (0, _yuneta_gobj_js.is_object)(routes) && typeof routes.graph === "string" ? routes.graph : "";
|
|
21913
|
-
let graph_href = /\/\{topic\}$/.test(graph_tpl) ? graph_tpl.replace(/\/\{topic\}$/, "") : "";
|
|
21947
|
+
let graph_href = graph_route_of(gobj);
|
|
21914
21948
|
let graph_items = [];
|
|
21915
21949
|
if (graph_href) graph_items.push([
|
|
21916
|
-
"
|
|
21950
|
+
"button",
|
|
21917
21951
|
{
|
|
21918
21952
|
class: "button TREEDB_GRAPH_BTN",
|
|
21919
|
-
href: graph_href,
|
|
21920
21953
|
style: "margin-left:auto;",
|
|
21921
21954
|
title: (0, i18next.t)("graph"),
|
|
21922
21955
|
"aria-label": (0, i18next.t)("graph"),
|
|
@@ -21931,7 +21964,11 @@ function build_ui$6(gobj) {
|
|
|
21931
21964
|
"span",
|
|
21932
21965
|
{ i18n: "graph" },
|
|
21933
21966
|
"graph"
|
|
21934
|
-
]]
|
|
21967
|
+
]],
|
|
21968
|
+
{ click: (evt) => {
|
|
21969
|
+
evt.stopPropagation();
|
|
21970
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_OPEN_GRAPH", {}, gobj);
|
|
21971
|
+
} }
|
|
21935
21972
|
]);
|
|
21936
21973
|
let $container = (0, _yuneta_gobj_js.createElement2)([
|
|
21937
21974
|
"div",
|
|
@@ -22092,6 +22129,17 @@ function build_ui$6(gobj) {
|
|
|
22092
22129
|
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_cards_landing")) show_topics_landing(gobj);
|
|
22093
22130
|
}
|
|
22094
22131
|
/************************************************************
|
|
22132
|
+
* The graph's route with no topic: the host's card template
|
|
22133
|
+
* `card_action_routes.graph` without its trailing `/{topic}`,
|
|
22134
|
+
* as a ROUTE (no `#`). "" when the host gives no such template.
|
|
22135
|
+
************************************************************/
|
|
22136
|
+
function graph_route_of(gobj) {
|
|
22137
|
+
let routes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "card_action_routes");
|
|
22138
|
+
let tpl = (0, _yuneta_gobj_js.is_object)(routes) && typeof routes.graph === "string" ? routes.graph : "";
|
|
22139
|
+
if (!/\/\{topic\}$/.test(tpl)) return "";
|
|
22140
|
+
return tpl.replace(/\/\{topic\}$/, "").replace(/^#/, "");
|
|
22141
|
+
}
|
|
22142
|
+
/************************************************************
|
|
22095
22143
|
* Is the backend session live? The Raw JSON viewer issues a remote
|
|
22096
22144
|
* print-tranger, so it only makes sense with a session — the remote
|
|
22097
22145
|
* (C_IEVENT_CLI) is in ST_SESSION exactly while connected.
|
|
@@ -22221,13 +22269,103 @@ function card_action_anchor(logical_class, icon, i18n_key, href) {
|
|
|
22221
22269
|
];
|
|
22222
22270
|
}
|
|
22223
22271
|
/************************************************************
|
|
22272
|
+
* What a topic card says about its topic, from its desc alone
|
|
22273
|
+
* (no request): its version and how many columns it has, the
|
|
22274
|
+
* topics it hangs FROM (its fkeys) and the ones that hang from
|
|
22275
|
+
* it (its hooks). The names are DATA and are not translated;
|
|
22276
|
+
* each label carries its key. Lists are cut with an ellipsis,
|
|
22277
|
+
* whole in the title.
|
|
22278
|
+
************************************************************/
|
|
22279
|
+
function topic_card_meta(desc) {
|
|
22280
|
+
if (!(0, _yuneta_gobj_js.is_object)(desc)) return [];
|
|
22281
|
+
let cols = (0, _yuneta_gobj_js.is_array)(desc.cols) ? desc.cols : [];
|
|
22282
|
+
let n_cols = 0;
|
|
22283
|
+
let parents = [];
|
|
22284
|
+
let children = [];
|
|
22285
|
+
for (let col of cols) {
|
|
22286
|
+
if (!col || !col.id || col.id.charAt(0) === "_") continue;
|
|
22287
|
+
n_cols++;
|
|
22288
|
+
if ((0, _yuneta_gobj_js.is_object)(col.fkey)) {
|
|
22289
|
+
for (let t_name of Object.keys(col.fkey)) if (!parents.includes(t_name)) parents.push(t_name);
|
|
22290
|
+
}
|
|
22291
|
+
if ((0, _yuneta_gobj_js.is_object)(col.hook)) {
|
|
22292
|
+
for (let t_name of Object.keys(col.hook)) if (!children.includes(t_name)) children.push(t_name);
|
|
22293
|
+
}
|
|
22294
|
+
}
|
|
22295
|
+
let meta = [];
|
|
22296
|
+
let head = [];
|
|
22297
|
+
if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") head.push([
|
|
22298
|
+
"span",
|
|
22299
|
+
{
|
|
22300
|
+
class: "TREEDB_TOPIC_CARD_VERSION",
|
|
22301
|
+
title: (0, i18next.t)("version"),
|
|
22302
|
+
"data-i18n-title": "version"
|
|
22303
|
+
},
|
|
22304
|
+
`v${desc.topic_version}`
|
|
22305
|
+
]);
|
|
22306
|
+
head.push([
|
|
22307
|
+
"span",
|
|
22308
|
+
{ class: "TREEDB_TOPIC_CARD_COLS" },
|
|
22309
|
+
[[
|
|
22310
|
+
"span",
|
|
22311
|
+
{},
|
|
22312
|
+
`${n_cols}`
|
|
22313
|
+
], [
|
|
22314
|
+
"span",
|
|
22315
|
+
{ i18n: "columns" },
|
|
22316
|
+
"columns"
|
|
22317
|
+
]]
|
|
22318
|
+
]);
|
|
22319
|
+
meta.push([
|
|
22320
|
+
"div",
|
|
22321
|
+
{ class: "TREEDB_TOPIC_CARD_META" },
|
|
22322
|
+
head
|
|
22323
|
+
]);
|
|
22324
|
+
let relation = (cls, key, arrow, names) => {
|
|
22325
|
+
let list = names.join(", ");
|
|
22326
|
+
return [
|
|
22327
|
+
"div",
|
|
22328
|
+
{
|
|
22329
|
+
class: `TREEDB_TOPIC_CARD_REL ${cls}`,
|
|
22330
|
+
title: list
|
|
22331
|
+
},
|
|
22332
|
+
[
|
|
22333
|
+
[
|
|
22334
|
+
"span",
|
|
22335
|
+
{
|
|
22336
|
+
class: "TREEDB_TOPIC_CARD_REL_ARROW",
|
|
22337
|
+
"aria-hidden": "true"
|
|
22338
|
+
},
|
|
22339
|
+
arrow
|
|
22340
|
+
],
|
|
22341
|
+
[
|
|
22342
|
+
"span",
|
|
22343
|
+
{
|
|
22344
|
+
class: "TREEDB_TOPIC_CARD_REL_LABEL",
|
|
22345
|
+
i18n: key
|
|
22346
|
+
},
|
|
22347
|
+
key
|
|
22348
|
+
],
|
|
22349
|
+
[
|
|
22350
|
+
"span",
|
|
22351
|
+
{ class: "TREEDB_TOPIC_CARD_REL_LIST" },
|
|
22352
|
+
list
|
|
22353
|
+
]
|
|
22354
|
+
]
|
|
22355
|
+
];
|
|
22356
|
+
};
|
|
22357
|
+
if (parents.length) meta.push(relation("TREEDB_TOPIC_CARD_PARENTS", "parents", "↑", parents));
|
|
22358
|
+
if (children.length) meta.push(relation("TREEDB_TOPIC_CARD_CHILDREN", "children", "↓", children));
|
|
22359
|
+
return meta;
|
|
22360
|
+
}
|
|
22361
|
+
/************************************************************
|
|
22224
22362
|
* Add one topic card to the cards-landing grid (list->detail).
|
|
22225
22363
|
* With `card_action_routes` (host-supplied templates) the card
|
|
22226
22364
|
* shows three hash-routed icons — info / table / graph. Without,
|
|
22227
22365
|
* it falls back to a single card that opens the table (same id
|
|
22228
22366
|
* contract as the tab, `<gobj>?<topic>`).
|
|
22229
22367
|
************************************************************/
|
|
22230
|
-
function add_topic_card(gobj, id, text, icon) {
|
|
22368
|
+
function add_topic_card(gobj, id, text, icon, desc) {
|
|
22231
22369
|
let $grid = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container").querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
|
|
22232
22370
|
if (!$grid) return;
|
|
22233
22371
|
let routes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "card_action_routes");
|
|
@@ -22270,25 +22408,28 @@ function add_topic_card(gobj, id, text, icon) {
|
|
|
22270
22408
|
let actions = [];
|
|
22271
22409
|
if (routes.info) actions.push(card_action_anchor("TREEDB_CARD_INFO", "yi-circle-info", "info", fill(routes.info)));
|
|
22272
22410
|
if (routes.table) actions.push(card_action_anchor("TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
|
|
22273
|
-
if (routes.graph) actions.push(card_action_anchor("TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
|
|
22274
22411
|
let $card = (0, _yuneta_gobj_js.createElement2)([
|
|
22275
22412
|
"div",
|
|
22276
22413
|
{
|
|
22277
22414
|
class: "TREEDB_TOPIC_CARD",
|
|
22278
22415
|
"data-topic": topic
|
|
22279
22416
|
},
|
|
22280
|
-
[
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22417
|
+
[
|
|
22418
|
+
[
|
|
22419
|
+
"span",
|
|
22420
|
+
{
|
|
22421
|
+
class: "TREEDB_TOPIC_CARD_NAME",
|
|
22422
|
+
i18n: text
|
|
22423
|
+
},
|
|
22424
|
+
text
|
|
22425
|
+
],
|
|
22426
|
+
...topic_card_meta(desc),
|
|
22427
|
+
[
|
|
22428
|
+
"div",
|
|
22429
|
+
{ class: "TREEDB_TOPIC_CARD_ACTIONS" },
|
|
22430
|
+
actions
|
|
22431
|
+
]
|
|
22432
|
+
]
|
|
22292
22433
|
]);
|
|
22293
22434
|
if (gobj.priv._selected_card_topic === topic) $card.classList.add("is-selected");
|
|
22294
22435
|
$card.addEventListener("click", function() {
|
|
@@ -22641,7 +22782,7 @@ function process_treedb_descs$1(gobj) {
|
|
|
22641
22782
|
let id = `${(0, _yuneta_gobj_js.gobj_name)(gobj)}?${desc.topic_name}`;
|
|
22642
22783
|
let gobj_topic_form = (0, _yuneta_gobj_js.gobj_create_service)(id, "C_YUI_TREEDB_TOPIC_WITH_FORM", kw_topic_form, gobj);
|
|
22643
22784
|
add_tab(gobj, gobj_topic_form, id, key, "yi-table");
|
|
22644
|
-
add_topic_card(gobj, id, key, "yi-table");
|
|
22785
|
+
add_topic_card(gobj, id, key, "yi-table", desc);
|
|
22645
22786
|
(0, _yuneta_gobj_js.gobj_start)(gobj_topic_form);
|
|
22646
22787
|
}
|
|
22647
22788
|
let href = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "last_selection");
|
|
@@ -23367,6 +23508,26 @@ function ac_open_json$1(gobj, event, kw, src) {
|
|
|
23367
23508
|
return 0;
|
|
23368
23509
|
}
|
|
23369
23510
|
/********************************************
|
|
23511
|
+
* Open the graph where the reader left it: its last visited
|
|
23512
|
+
* route under the graph's base -- `<graph>/<topic>` when a topic
|
|
23513
|
+
* was focused there -- or the bare base when the graph was never
|
|
23514
|
+
* opened in this page. A user move: pushed, so Back returns.
|
|
23515
|
+
********************************************/
|
|
23516
|
+
function ac_open_graph(gobj, event, kw, src) {
|
|
23517
|
+
let base = graph_route_of(gobj);
|
|
23518
|
+
if (!base) {
|
|
23519
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: open graph with no graph route`);
|
|
23520
|
+
return -1;
|
|
23521
|
+
}
|
|
23522
|
+
let shell = yui_shell_of(gobj);
|
|
23523
|
+
if (!shell) {
|
|
23524
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: open graph with no shell`);
|
|
23525
|
+
return -1;
|
|
23526
|
+
}
|
|
23527
|
+
yui_shell_navigate(shell, yui_shell_last_route_under(shell, base), { push: true });
|
|
23528
|
+
return 0;
|
|
23529
|
+
}
|
|
23530
|
+
/********************************************
|
|
23370
23531
|
* The viewer asked to load a collapsed subtree (tranger mode only):
|
|
23371
23532
|
* re-issue print-tranger for that path.
|
|
23372
23533
|
********************************************/
|
|
@@ -23465,6 +23626,11 @@ function create_gclass$6(gclass_name) {
|
|
|
23465
23626
|
ac_open_json$1,
|
|
23466
23627
|
null
|
|
23467
23628
|
],
|
|
23629
|
+
[
|
|
23630
|
+
"EV_OPEN_GRAPH",
|
|
23631
|
+
ac_open_graph,
|
|
23632
|
+
null
|
|
23633
|
+
],
|
|
23468
23634
|
[
|
|
23469
23635
|
"EV_EXPAND_PATH",
|
|
23470
23636
|
ac_json_expand_path$1,
|
|
@@ -23529,6 +23695,7 @@ function create_gclass$6(gclass_name) {
|
|
|
23529
23695
|
["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
23530
23696
|
["EV_REFRESH_TOPIC", 0],
|
|
23531
23697
|
["EV_OPEN_JSON", 0],
|
|
23698
|
+
["EV_OPEN_GRAPH", 0],
|
|
23532
23699
|
["EV_EXPAND_PATH", 0],
|
|
23533
23700
|
["EV_JSON_CLOSED", 0],
|
|
23534
23701
|
["EV_SHOW", 0],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -906,6 +906,7 @@ var PRIVATE_DATA$21 = {
|
|
|
906
906
|
navs: [],
|
|
907
907
|
item_index: {},
|
|
908
908
|
section_pos: {},
|
|
909
|
+
route_mru: [],
|
|
909
910
|
sub_routes: {},
|
|
910
911
|
event_handlers: {},
|
|
911
912
|
hash_handler: null,
|
|
@@ -1614,6 +1615,7 @@ function navigate_to(gobj, route, depth, no_drain) {
|
|
|
1614
1615
|
stage.active_route = matched_route;
|
|
1615
1616
|
gobj_write_attr(gobj, "current_route", route);
|
|
1616
1617
|
remember_position(gobj, route);
|
|
1618
|
+
remember_route(gobj, route);
|
|
1617
1619
|
update_secondary_nav_visibility(gobj, entry);
|
|
1618
1620
|
close_all_drawers(gobj);
|
|
1619
1621
|
close_toolbar_dropdown(gobj);
|
|
@@ -2640,6 +2642,40 @@ function remembered_position(gobj, route) {
|
|
|
2640
2642
|
return last;
|
|
2641
2643
|
}
|
|
2642
2644
|
/************************************************************
|
|
2645
|
+
* Every route the shell lands on, newest last, a route once.
|
|
2646
|
+
* Bounded: it answers "where was the reader last under X", and
|
|
2647
|
+
* a page that has been open all day has no business growing it.
|
|
2648
|
+
************************************************************/
|
|
2649
|
+
var ROUTE_MRU_MAX = 64;
|
|
2650
|
+
function remember_route(gobj, route) {
|
|
2651
|
+
let mru = gobj.priv.route_mru;
|
|
2652
|
+
let i = mru.indexOf(route);
|
|
2653
|
+
if (i >= 0) mru.splice(i, 1);
|
|
2654
|
+
mru.push(route);
|
|
2655
|
+
if (mru.length > ROUTE_MRU_MAX) mru.splice(0, mru.length - ROUTE_MRU_MAX);
|
|
2656
|
+
}
|
|
2657
|
+
/************************************************************
|
|
2658
|
+
* Where the reader last was UNDER `route`: the most recent
|
|
2659
|
+
* visited route that is `route` itself or lies below it, or
|
|
2660
|
+
* `route` when none does. A mirror of the url for the page, like
|
|
2661
|
+
* the section memory -- never stored, never applied on its own:
|
|
2662
|
+
* a control that wants to go back to a view AS IT WAS LEFT asks,
|
|
2663
|
+
* and navigates there itself.
|
|
2664
|
+
*
|
|
2665
|
+
* What it is for: a treedb graph's focus lives in the url
|
|
2666
|
+
* (`<graph>/<topic>`), so a button that opened the bare route
|
|
2667
|
+
* took the focus away every time it was used.
|
|
2668
|
+
************************************************************/
|
|
2669
|
+
function yui_shell_last_route_under(shell_gobj, route) {
|
|
2670
|
+
let priv = shell_gobj && shell_gobj.priv;
|
|
2671
|
+
if (!priv || !is_array(priv.route_mru) || !route) return route;
|
|
2672
|
+
for (let i = priv.route_mru.length - 1; i >= 0; i--) {
|
|
2673
|
+
let r = priv.route_mru[i];
|
|
2674
|
+
if (r === route || r.indexOf(route + "/") === 0) return r;
|
|
2675
|
+
}
|
|
2676
|
+
return route;
|
|
2677
|
+
}
|
|
2678
|
+
/************************************************************
|
|
2643
2679
|
* Resolve the shell that governs `gobj`: the nearest
|
|
2644
2680
|
* C_YUI_SHELL ancestor, else the last shell created on the
|
|
2645
2681
|
* page (apps have exactly one). Null when no shell exists —
|
|
@@ -21793,7 +21829,7 @@ var attrs_table$6 = [
|
|
|
21793
21829
|
SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
|
|
21794
21830
|
SDATA(data_type_t.DTP_BOOLEAN, "with_cards_landing", 0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
|
|
21795
21831
|
SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
|
|
21796
|
-
SDATA(data_type_t.DTP_JSON, "card_action_routes", 0, null, "
|
|
21832
|
+
SDATA(data_type_t.DTP_JSON, "card_action_routes", 0, null, "Hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show the info and table icons, and `graph` without its `/{topic}` is the toolbar's graph button (the card has no graph icon: its topic segment is a focus); absent ⇒ a single card that opens the table."),
|
|
21797
21833
|
SDATA(data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
|
|
21798
21834
|
SDATA(data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
|
|
21799
21835
|
SDATA(data_type_t.DTP_STRING, "source_url", 0, "", "The backend this view browses (host-supplied, typically the wss url of the connection), shown in the toolbar. A tab label names the TREEDB, and the same treedb name lives on more than one node, so the node has to be readable somewhere -- and a tab is too narrow to carry a url. Empty ⇒ nothing is shown."),
|
|
@@ -21903,15 +21939,12 @@ function cmd_get_topic_data$1(gobj, cmd, kw, src) {
|
|
|
21903
21939
|
************************************************************/
|
|
21904
21940
|
function build_ui$6(gobj) {
|
|
21905
21941
|
let source_url = gobj_read_str_attr(gobj, "source_url") || "";
|
|
21906
|
-
let
|
|
21907
|
-
let graph_tpl = is_object(routes) && typeof routes.graph === "string" ? routes.graph : "";
|
|
21908
|
-
let graph_href = /\/\{topic\}$/.test(graph_tpl) ? graph_tpl.replace(/\/\{topic\}$/, "") : "";
|
|
21942
|
+
let graph_href = graph_route_of(gobj);
|
|
21909
21943
|
let graph_items = [];
|
|
21910
21944
|
if (graph_href) graph_items.push([
|
|
21911
|
-
"
|
|
21945
|
+
"button",
|
|
21912
21946
|
{
|
|
21913
21947
|
class: "button TREEDB_GRAPH_BTN",
|
|
21914
|
-
href: graph_href,
|
|
21915
21948
|
style: "margin-left:auto;",
|
|
21916
21949
|
title: t("graph"),
|
|
21917
21950
|
"aria-label": t("graph"),
|
|
@@ -21926,7 +21959,11 @@ function build_ui$6(gobj) {
|
|
|
21926
21959
|
"span",
|
|
21927
21960
|
{ i18n: "graph" },
|
|
21928
21961
|
"graph"
|
|
21929
|
-
]]
|
|
21962
|
+
]],
|
|
21963
|
+
{ click: (evt) => {
|
|
21964
|
+
evt.stopPropagation();
|
|
21965
|
+
gobj_send_event(gobj, "EV_OPEN_GRAPH", {}, gobj);
|
|
21966
|
+
} }
|
|
21930
21967
|
]);
|
|
21931
21968
|
let $container = createElement2([
|
|
21932
21969
|
"div",
|
|
@@ -22087,6 +22124,17 @@ function build_ui$6(gobj) {
|
|
|
22087
22124
|
if (gobj_read_bool_attr(gobj, "with_cards_landing")) show_topics_landing(gobj);
|
|
22088
22125
|
}
|
|
22089
22126
|
/************************************************************
|
|
22127
|
+
* The graph's route with no topic: the host's card template
|
|
22128
|
+
* `card_action_routes.graph` without its trailing `/{topic}`,
|
|
22129
|
+
* as a ROUTE (no `#`). "" when the host gives no such template.
|
|
22130
|
+
************************************************************/
|
|
22131
|
+
function graph_route_of(gobj) {
|
|
22132
|
+
let routes = gobj_read_attr(gobj, "card_action_routes");
|
|
22133
|
+
let tpl = is_object(routes) && typeof routes.graph === "string" ? routes.graph : "";
|
|
22134
|
+
if (!/\/\{topic\}$/.test(tpl)) return "";
|
|
22135
|
+
return tpl.replace(/\/\{topic\}$/, "").replace(/^#/, "");
|
|
22136
|
+
}
|
|
22137
|
+
/************************************************************
|
|
22090
22138
|
* Is the backend session live? The Raw JSON viewer issues a remote
|
|
22091
22139
|
* print-tranger, so it only makes sense with a session — the remote
|
|
22092
22140
|
* (C_IEVENT_CLI) is in ST_SESSION exactly while connected.
|
|
@@ -22216,13 +22264,103 @@ function card_action_anchor(logical_class, icon, i18n_key, href) {
|
|
|
22216
22264
|
];
|
|
22217
22265
|
}
|
|
22218
22266
|
/************************************************************
|
|
22267
|
+
* What a topic card says about its topic, from its desc alone
|
|
22268
|
+
* (no request): its version and how many columns it has, the
|
|
22269
|
+
* topics it hangs FROM (its fkeys) and the ones that hang from
|
|
22270
|
+
* it (its hooks). The names are DATA and are not translated;
|
|
22271
|
+
* each label carries its key. Lists are cut with an ellipsis,
|
|
22272
|
+
* whole in the title.
|
|
22273
|
+
************************************************************/
|
|
22274
|
+
function topic_card_meta(desc) {
|
|
22275
|
+
if (!is_object(desc)) return [];
|
|
22276
|
+
let cols = is_array(desc.cols) ? desc.cols : [];
|
|
22277
|
+
let n_cols = 0;
|
|
22278
|
+
let parents = [];
|
|
22279
|
+
let children = [];
|
|
22280
|
+
for (let col of cols) {
|
|
22281
|
+
if (!col || !col.id || col.id.charAt(0) === "_") continue;
|
|
22282
|
+
n_cols++;
|
|
22283
|
+
if (is_object(col.fkey)) {
|
|
22284
|
+
for (let t_name of Object.keys(col.fkey)) if (!parents.includes(t_name)) parents.push(t_name);
|
|
22285
|
+
}
|
|
22286
|
+
if (is_object(col.hook)) {
|
|
22287
|
+
for (let t_name of Object.keys(col.hook)) if (!children.includes(t_name)) children.push(t_name);
|
|
22288
|
+
}
|
|
22289
|
+
}
|
|
22290
|
+
let meta = [];
|
|
22291
|
+
let head = [];
|
|
22292
|
+
if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") head.push([
|
|
22293
|
+
"span",
|
|
22294
|
+
{
|
|
22295
|
+
class: "TREEDB_TOPIC_CARD_VERSION",
|
|
22296
|
+
title: t("version"),
|
|
22297
|
+
"data-i18n-title": "version"
|
|
22298
|
+
},
|
|
22299
|
+
`v${desc.topic_version}`
|
|
22300
|
+
]);
|
|
22301
|
+
head.push([
|
|
22302
|
+
"span",
|
|
22303
|
+
{ class: "TREEDB_TOPIC_CARD_COLS" },
|
|
22304
|
+
[[
|
|
22305
|
+
"span",
|
|
22306
|
+
{},
|
|
22307
|
+
`${n_cols}`
|
|
22308
|
+
], [
|
|
22309
|
+
"span",
|
|
22310
|
+
{ i18n: "columns" },
|
|
22311
|
+
"columns"
|
|
22312
|
+
]]
|
|
22313
|
+
]);
|
|
22314
|
+
meta.push([
|
|
22315
|
+
"div",
|
|
22316
|
+
{ class: "TREEDB_TOPIC_CARD_META" },
|
|
22317
|
+
head
|
|
22318
|
+
]);
|
|
22319
|
+
let relation = (cls, key, arrow, names) => {
|
|
22320
|
+
let list = names.join(", ");
|
|
22321
|
+
return [
|
|
22322
|
+
"div",
|
|
22323
|
+
{
|
|
22324
|
+
class: `TREEDB_TOPIC_CARD_REL ${cls}`,
|
|
22325
|
+
title: list
|
|
22326
|
+
},
|
|
22327
|
+
[
|
|
22328
|
+
[
|
|
22329
|
+
"span",
|
|
22330
|
+
{
|
|
22331
|
+
class: "TREEDB_TOPIC_CARD_REL_ARROW",
|
|
22332
|
+
"aria-hidden": "true"
|
|
22333
|
+
},
|
|
22334
|
+
arrow
|
|
22335
|
+
],
|
|
22336
|
+
[
|
|
22337
|
+
"span",
|
|
22338
|
+
{
|
|
22339
|
+
class: "TREEDB_TOPIC_CARD_REL_LABEL",
|
|
22340
|
+
i18n: key
|
|
22341
|
+
},
|
|
22342
|
+
key
|
|
22343
|
+
],
|
|
22344
|
+
[
|
|
22345
|
+
"span",
|
|
22346
|
+
{ class: "TREEDB_TOPIC_CARD_REL_LIST" },
|
|
22347
|
+
list
|
|
22348
|
+
]
|
|
22349
|
+
]
|
|
22350
|
+
];
|
|
22351
|
+
};
|
|
22352
|
+
if (parents.length) meta.push(relation("TREEDB_TOPIC_CARD_PARENTS", "parents", "↑", parents));
|
|
22353
|
+
if (children.length) meta.push(relation("TREEDB_TOPIC_CARD_CHILDREN", "children", "↓", children));
|
|
22354
|
+
return meta;
|
|
22355
|
+
}
|
|
22356
|
+
/************************************************************
|
|
22219
22357
|
* Add one topic card to the cards-landing grid (list->detail).
|
|
22220
22358
|
* With `card_action_routes` (host-supplied templates) the card
|
|
22221
22359
|
* shows three hash-routed icons — info / table / graph. Without,
|
|
22222
22360
|
* it falls back to a single card that opens the table (same id
|
|
22223
22361
|
* contract as the tab, `<gobj>?<topic>`).
|
|
22224
22362
|
************************************************************/
|
|
22225
|
-
function add_topic_card(gobj, id, text, icon) {
|
|
22363
|
+
function add_topic_card(gobj, id, text, icon, desc) {
|
|
22226
22364
|
let $grid = gobj_read_attr(gobj, "$container").querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
|
|
22227
22365
|
if (!$grid) return;
|
|
22228
22366
|
let routes = gobj_read_attr(gobj, "card_action_routes");
|
|
@@ -22265,25 +22403,28 @@ function add_topic_card(gobj, id, text, icon) {
|
|
|
22265
22403
|
let actions = [];
|
|
22266
22404
|
if (routes.info) actions.push(card_action_anchor("TREEDB_CARD_INFO", "yi-circle-info", "info", fill(routes.info)));
|
|
22267
22405
|
if (routes.table) actions.push(card_action_anchor("TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
|
|
22268
|
-
if (routes.graph) actions.push(card_action_anchor("TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
|
|
22269
22406
|
let $card = createElement2([
|
|
22270
22407
|
"div",
|
|
22271
22408
|
{
|
|
22272
22409
|
class: "TREEDB_TOPIC_CARD",
|
|
22273
22410
|
"data-topic": topic
|
|
22274
22411
|
},
|
|
22275
|
-
[
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22412
|
+
[
|
|
22413
|
+
[
|
|
22414
|
+
"span",
|
|
22415
|
+
{
|
|
22416
|
+
class: "TREEDB_TOPIC_CARD_NAME",
|
|
22417
|
+
i18n: text
|
|
22418
|
+
},
|
|
22419
|
+
text
|
|
22420
|
+
],
|
|
22421
|
+
...topic_card_meta(desc),
|
|
22422
|
+
[
|
|
22423
|
+
"div",
|
|
22424
|
+
{ class: "TREEDB_TOPIC_CARD_ACTIONS" },
|
|
22425
|
+
actions
|
|
22426
|
+
]
|
|
22427
|
+
]
|
|
22287
22428
|
]);
|
|
22288
22429
|
if (gobj.priv._selected_card_topic === topic) $card.classList.add("is-selected");
|
|
22289
22430
|
$card.addEventListener("click", function() {
|
|
@@ -22636,7 +22777,7 @@ function process_treedb_descs$1(gobj) {
|
|
|
22636
22777
|
let id = `${gobj_name(gobj)}?${desc.topic_name}`;
|
|
22637
22778
|
let gobj_topic_form = gobj_create_service(id, "C_YUI_TREEDB_TOPIC_WITH_FORM", kw_topic_form, gobj);
|
|
22638
22779
|
add_tab(gobj, gobj_topic_form, id, key, "yi-table");
|
|
22639
|
-
add_topic_card(gobj, id, key, "yi-table");
|
|
22780
|
+
add_topic_card(gobj, id, key, "yi-table", desc);
|
|
22640
22781
|
gobj_start(gobj_topic_form);
|
|
22641
22782
|
}
|
|
22642
22783
|
let href = gobj_read_str_attr(gobj, "last_selection");
|
|
@@ -23362,6 +23503,26 @@ function ac_open_json$1(gobj, event, kw, src) {
|
|
|
23362
23503
|
return 0;
|
|
23363
23504
|
}
|
|
23364
23505
|
/********************************************
|
|
23506
|
+
* Open the graph where the reader left it: its last visited
|
|
23507
|
+
* route under the graph's base -- `<graph>/<topic>` when a topic
|
|
23508
|
+
* was focused there -- or the bare base when the graph was never
|
|
23509
|
+
* opened in this page. A user move: pushed, so Back returns.
|
|
23510
|
+
********************************************/
|
|
23511
|
+
function ac_open_graph(gobj, event, kw, src) {
|
|
23512
|
+
let base = graph_route_of(gobj);
|
|
23513
|
+
if (!base) {
|
|
23514
|
+
log_error(`${gobj_short_name(gobj)}: open graph with no graph route`);
|
|
23515
|
+
return -1;
|
|
23516
|
+
}
|
|
23517
|
+
let shell = yui_shell_of(gobj);
|
|
23518
|
+
if (!shell) {
|
|
23519
|
+
log_error(`${gobj_short_name(gobj)}: open graph with no shell`);
|
|
23520
|
+
return -1;
|
|
23521
|
+
}
|
|
23522
|
+
yui_shell_navigate(shell, yui_shell_last_route_under(shell, base), { push: true });
|
|
23523
|
+
return 0;
|
|
23524
|
+
}
|
|
23525
|
+
/********************************************
|
|
23365
23526
|
* The viewer asked to load a collapsed subtree (tranger mode only):
|
|
23366
23527
|
* re-issue print-tranger for that path.
|
|
23367
23528
|
********************************************/
|
|
@@ -23460,6 +23621,11 @@ function create_gclass$6(gclass_name) {
|
|
|
23460
23621
|
ac_open_json$1,
|
|
23461
23622
|
null
|
|
23462
23623
|
],
|
|
23624
|
+
[
|
|
23625
|
+
"EV_OPEN_GRAPH",
|
|
23626
|
+
ac_open_graph,
|
|
23627
|
+
null
|
|
23628
|
+
],
|
|
23463
23629
|
[
|
|
23464
23630
|
"EV_EXPAND_PATH",
|
|
23465
23631
|
ac_json_expand_path$1,
|
|
@@ -23524,6 +23690,7 @@ function create_gclass$6(gclass_name) {
|
|
|
23524
23690
|
["EV_RECORD_WRITTEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
|
|
23525
23691
|
["EV_REFRESH_TOPIC", 0],
|
|
23526
23692
|
["EV_OPEN_JSON", 0],
|
|
23693
|
+
["EV_OPEN_GRAPH", 0],
|
|
23527
23694
|
["EV_EXPAND_PATH", 0],
|
|
23528
23695
|
["EV_JSON_CLOSED", 0],
|
|
23529
23696
|
["EV_SHOW", 0],
|
package/package.json
CHANGED
package/src/c_yui_node.js
CHANGED
|
@@ -1324,6 +1324,12 @@ function ac_add_node(gobj, event, kw, src)
|
|
|
1324
1324
|
if(spec.link) {
|
|
1325
1325
|
kw_child.link = spec.link;
|
|
1326
1326
|
}
|
|
1327
|
+
/* A declared attr of this gclass that the spec could not reach: a
|
|
1328
|
+
* node whose children are workspaces (a treedb: its topics, its
|
|
1329
|
+
* graph) is declared in the config like every other one. */
|
|
1330
|
+
if(spec.remember_position) {
|
|
1331
|
+
kw_child.remember_position = true;
|
|
1332
|
+
}
|
|
1327
1333
|
let child = gobj_create_pure_child(spec.id, GCLASS_NAME, kw_child, gobj);
|
|
1328
1334
|
|
|
1329
1335
|
let index = (typeof kw.index === "number") ? kw.index : priv.children.length;
|
package/src/c_yui_shell.js
CHANGED
|
@@ -117,6 +117,10 @@ let PRIVATE_DATA = {
|
|
|
117
117
|
/* Section route -> last position visited inside it. Mirrors the
|
|
118
118
|
* url for the duration of the page; see remember_position(). */
|
|
119
119
|
section_pos: {},
|
|
120
|
+
/* The routes visited, newest last, bounded -- for "where did the
|
|
121
|
+
* reader leave this subtree" (yui_shell_last_route_under). Same
|
|
122
|
+
* nature as section_pos: a mirror of the url, for the page. */
|
|
123
|
+
route_mru: [],
|
|
120
124
|
/* Sub-route contributor registry (ROUTING.md): a mounted view
|
|
121
125
|
* declares the deep, view-owned children of its base route
|
|
122
126
|
* (topics, /info, /schema, focus topics — subpaths that are NOT
|
|
@@ -1465,6 +1469,7 @@ function navigate_to(gobj, route, depth, no_drain)
|
|
|
1465
1469
|
stage.active_route = matched_route;
|
|
1466
1470
|
gobj_write_attr(gobj, "current_route", route);
|
|
1467
1471
|
remember_position(gobj, route);
|
|
1472
|
+
remember_route(gobj, route);
|
|
1468
1473
|
|
|
1469
1474
|
/* Show/hide secondary navs according to parent item */
|
|
1470
1475
|
update_secondary_nav_visibility(gobj, entry);
|
|
@@ -3187,6 +3192,54 @@ function remembered_position(gobj, route)
|
|
|
3187
3192
|
return last;
|
|
3188
3193
|
}
|
|
3189
3194
|
|
|
3195
|
+
/************************************************************
|
|
3196
|
+
* Every route the shell lands on, newest last, a route once.
|
|
3197
|
+
* Bounded: it answers "where was the reader last under X", and
|
|
3198
|
+
* a page that has been open all day has no business growing it.
|
|
3199
|
+
************************************************************/
|
|
3200
|
+
const ROUTE_MRU_MAX = 64;
|
|
3201
|
+
|
|
3202
|
+
function remember_route(gobj, route)
|
|
3203
|
+
{
|
|
3204
|
+
let priv = gobj.priv;
|
|
3205
|
+
let mru = priv.route_mru;
|
|
3206
|
+
let i = mru.indexOf(route);
|
|
3207
|
+
if(i >= 0) {
|
|
3208
|
+
mru.splice(i, 1);
|
|
3209
|
+
}
|
|
3210
|
+
mru.push(route);
|
|
3211
|
+
if(mru.length > ROUTE_MRU_MAX) {
|
|
3212
|
+
mru.splice(0, mru.length - ROUTE_MRU_MAX);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
/************************************************************
|
|
3217
|
+
* Where the reader last was UNDER `route`: the most recent
|
|
3218
|
+
* visited route that is `route` itself or lies below it, or
|
|
3219
|
+
* `route` when none does. A mirror of the url for the page, like
|
|
3220
|
+
* the section memory -- never stored, never applied on its own:
|
|
3221
|
+
* a control that wants to go back to a view AS IT WAS LEFT asks,
|
|
3222
|
+
* and navigates there itself.
|
|
3223
|
+
*
|
|
3224
|
+
* What it is for: a treedb graph's focus lives in the url
|
|
3225
|
+
* (`<graph>/<topic>`), so a button that opened the bare route
|
|
3226
|
+
* took the focus away every time it was used.
|
|
3227
|
+
************************************************************/
|
|
3228
|
+
function yui_shell_last_route_under(shell_gobj, route)
|
|
3229
|
+
{
|
|
3230
|
+
let priv = shell_gobj && shell_gobj.priv;
|
|
3231
|
+
if(!priv || !is_array(priv.route_mru) || !route) {
|
|
3232
|
+
return route;
|
|
3233
|
+
}
|
|
3234
|
+
for(let i = priv.route_mru.length - 1; i >= 0; i--) {
|
|
3235
|
+
let r = priv.route_mru[i];
|
|
3236
|
+
if(r === route || r.indexOf(route + "/") === 0) {
|
|
3237
|
+
return r;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
return route;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3190
3243
|
/************************************************************
|
|
3191
3244
|
* Resolve the shell that governs `gobj`: the nearest
|
|
3192
3245
|
* C_YUI_SHELL ancestor, else the last shell created on the
|
|
@@ -3609,6 +3662,7 @@ export {
|
|
|
3609
3662
|
yui_shell_of,
|
|
3610
3663
|
yui_shell_zone,
|
|
3611
3664
|
yui_shell_navigate,
|
|
3665
|
+
yui_shell_last_route_under,
|
|
3612
3666
|
yui_shell_nav_map,
|
|
3613
3667
|
yui_shell_set_sub_routes,
|
|
3614
3668
|
yui_shell_register_event_handler,
|
|
@@ -36,6 +36,31 @@
|
|
|
36
36
|
line-height: 1.2;
|
|
37
37
|
word-break: break-word;
|
|
38
38
|
}
|
|
39
|
+
/* What the card says about its topic: version + columns, then the
|
|
40
|
+
* topics it hangs from and the ones hanging from it. Muted and small,
|
|
41
|
+
* under the name; a long list is cut, whole in the title. The spans
|
|
42
|
+
* are spaced by `gap` because createElement2 trims text nodes. */
|
|
43
|
+
.TREEDB_TOPIC_CARD_META,
|
|
44
|
+
.TREEDB_TOPIC_CARD_REL {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: baseline;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
gap: 0.3rem;
|
|
49
|
+
max-width: 100%;
|
|
50
|
+
font-size: 0.75rem;
|
|
51
|
+
color: var(--bulma-text-weak, #7a7a7a);
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
}
|
|
54
|
+
.TREEDB_TOPIC_CARD_COLS {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
gap: 0.25rem;
|
|
57
|
+
}
|
|
58
|
+
.TREEDB_TOPIC_CARD_REL_LIST {
|
|
59
|
+
min-width: 0;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
text-overflow: ellipsis;
|
|
62
|
+
color: var(--bulma-text, inherit);
|
|
63
|
+
}
|
|
39
64
|
.TREEDB_TOPIC_CARD_ACTIONS {
|
|
40
65
|
display: flex;
|
|
41
66
|
justify-content: center;
|
|
@@ -58,7 +58,12 @@ import {
|
|
|
58
58
|
import "./c_yui_treedb_topics.css";
|
|
59
59
|
|
|
60
60
|
import {yui_shell_show_error, yui_shell_show_modal, yui_shell_popup_layer} from "./shell_modals.js";
|
|
61
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
yui_shell_of,
|
|
63
|
+
yui_shell_set_sub_routes,
|
|
64
|
+
yui_shell_navigate,
|
|
65
|
+
yui_shell_last_route_under,
|
|
66
|
+
} from "./c_yui_shell.js";
|
|
62
67
|
import {nodes_answer} from "./nodes_answer.js";
|
|
63
68
|
import {yui_toolbar_icon} from "./yui_toolbar.js";
|
|
64
69
|
|
|
@@ -84,7 +89,7 @@ SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system t
|
|
|
84
89
|
SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
|
|
85
90
|
SDATA(data_type_t.DTP_BOOLEAN, "with_cards_landing",0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
|
|
86
91
|
SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
|
|
87
|
-
SDATA(data_type_t.DTP_JSON, "card_action_routes",0, null, "
|
|
92
|
+
SDATA(data_type_t.DTP_JSON, "card_action_routes",0, null, "Hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show the info and table icons, and `graph` without its `/{topic}` is the toolbar's graph button (the card has no graph icon: its topic segment is a focus); absent ⇒ a single card that opens the table."),
|
|
88
93
|
SDATA(data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
|
|
89
94
|
SDATA(data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
|
|
90
95
|
SDATA(data_type_t.DTP_STRING, "source_url", 0, "", "The backend this view browses (host-supplied, typically the wss url of the connection), shown in the toolbar. A tab label names the TREEDB, and the same treedb name lives on more than one node, so the node has to be readable somewhere -- and a tab is too narrow to carry a url. Empty ⇒ nothing is shown."),
|
|
@@ -254,26 +259,28 @@ function build_ui(gobj)
|
|
|
254
259
|
{
|
|
255
260
|
let source_url = gobj_read_str_attr(gobj, "source_url") || "";
|
|
256
261
|
|
|
257
|
-
/* The
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
let
|
|
264
|
-
let graph_tpl = is_object(routes) && typeof routes.graph === "string"? routes.graph : "";
|
|
265
|
-
let graph_href = /\/\{topic\}$/.test(graph_tpl)? graph_tpl.replace(/\/\{topic\}$/, "") : "";
|
|
262
|
+
/* The treedb as a graph, AS THE READER LEFT IT. The route is the
|
|
263
|
+
* card template without its `/{topic}`; a host whose template does
|
|
264
|
+
* not end in it, or gives none, gets no button. Not a fixed link:
|
|
265
|
+
* the graph's focus lives in its url (`<graph>/<topic>`), so the
|
|
266
|
+
* bare route took it away every time -- the click asks the shell
|
|
267
|
+
* where the reader last was under that route (ac_open_graph). */
|
|
268
|
+
let graph_href = graph_route_of(gobj);
|
|
266
269
|
let graph_items = [];
|
|
267
270
|
if(graph_href) {
|
|
268
271
|
graph_items.push(
|
|
269
|
-
['
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
'data-i18n-title': 'graph', 'data-i18n-aria-label': 'graph'}, [
|
|
272
|
+
['button', {class: 'button TREEDB_GRAPH_BTN',
|
|
273
|
+
style: 'margin-left:auto;',
|
|
274
|
+
title: t('graph'), 'aria-label': t('graph'),
|
|
275
|
+
'data-i18n-title': 'graph', 'data-i18n-aria-label': 'graph'}, [
|
|
274
276
|
['span', {class: 'icon'}, [yui_toolbar_icon('yi-hexagon-nodes')]],
|
|
275
277
|
['span', {i18n: 'graph'}, 'graph']
|
|
276
|
-
]
|
|
278
|
+
], {
|
|
279
|
+
click: (evt) => {
|
|
280
|
+
evt.stopPropagation();
|
|
281
|
+
gobj_send_event(gobj, "EV_OPEN_GRAPH", {}, gobj);
|
|
282
|
+
}
|
|
283
|
+
}]
|
|
277
284
|
);
|
|
278
285
|
}
|
|
279
286
|
|
|
@@ -389,6 +396,21 @@ function build_ui(gobj)
|
|
|
389
396
|
}
|
|
390
397
|
}
|
|
391
398
|
|
|
399
|
+
/************************************************************
|
|
400
|
+
* The graph's route with no topic: the host's card template
|
|
401
|
+
* `card_action_routes.graph` without its trailing `/{topic}`,
|
|
402
|
+
* as a ROUTE (no `#`). "" when the host gives no such template.
|
|
403
|
+
************************************************************/
|
|
404
|
+
function graph_route_of(gobj)
|
|
405
|
+
{
|
|
406
|
+
let routes = gobj_read_attr(gobj, "card_action_routes");
|
|
407
|
+
let tpl = (is_object(routes) && typeof routes.graph === "string")? routes.graph : "";
|
|
408
|
+
if(!/\/\{topic\}$/.test(tpl)) {
|
|
409
|
+
return "";
|
|
410
|
+
}
|
|
411
|
+
return tpl.replace(/\/\{topic\}$/, "").replace(/^#/, "");
|
|
412
|
+
}
|
|
413
|
+
|
|
392
414
|
/************************************************************
|
|
393
415
|
* Is the backend session live? The Raw JSON viewer issues a remote
|
|
394
416
|
* print-tranger, so it only makes sense with a session — the remote
|
|
@@ -599,6 +621,74 @@ function card_action_anchor(logical_class, icon, i18n_key, href)
|
|
|
599
621
|
]];
|
|
600
622
|
}
|
|
601
623
|
|
|
624
|
+
/************************************************************
|
|
625
|
+
* What a topic card says about its topic, from its desc alone
|
|
626
|
+
* (no request): its version and how many columns it has, the
|
|
627
|
+
* topics it hangs FROM (its fkeys) and the ones that hang from
|
|
628
|
+
* it (its hooks). The names are DATA and are not translated;
|
|
629
|
+
* each label carries its key. Lists are cut with an ellipsis,
|
|
630
|
+
* whole in the title.
|
|
631
|
+
************************************************************/
|
|
632
|
+
function topic_card_meta(desc)
|
|
633
|
+
{
|
|
634
|
+
if(!is_object(desc)) {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
let cols = is_array(desc.cols)? desc.cols : [];
|
|
638
|
+
let n_cols = 0;
|
|
639
|
+
let parents = [];
|
|
640
|
+
let children = [];
|
|
641
|
+
for(let col of cols) {
|
|
642
|
+
if(!col || !col.id || col.id.charAt(0) === "_") {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
n_cols++;
|
|
646
|
+
if(is_object(col.fkey)) {
|
|
647
|
+
for(let t_name of Object.keys(col.fkey)) {
|
|
648
|
+
if(!parents.includes(t_name)) {
|
|
649
|
+
parents.push(t_name);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if(is_object(col.hook)) {
|
|
654
|
+
for(let t_name of Object.keys(col.hook)) {
|
|
655
|
+
if(!children.includes(t_name)) {
|
|
656
|
+
children.push(t_name);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
let meta = [];
|
|
663
|
+
let head = [];
|
|
664
|
+
if(desc.topic_version !== undefined && desc.topic_version !== null && desc.topic_version !== "") {
|
|
665
|
+
head.push(['span', {class: 'TREEDB_TOPIC_CARD_VERSION',
|
|
666
|
+
title: t('version'), 'data-i18n-title': 'version'},
|
|
667
|
+
`v${desc.topic_version}`]);
|
|
668
|
+
}
|
|
669
|
+
head.push(['span', {class: 'TREEDB_TOPIC_CARD_COLS'}, [
|
|
670
|
+
['span', {}, `${n_cols}`],
|
|
671
|
+
['span', {i18n: 'columns'}, 'columns']
|
|
672
|
+
]]);
|
|
673
|
+
meta.push(['div', {class: 'TREEDB_TOPIC_CARD_META'}, head]);
|
|
674
|
+
|
|
675
|
+
let relation = (cls, key, arrow, names) => {
|
|
676
|
+
let list = names.join(", ");
|
|
677
|
+
return ['div', {class: `TREEDB_TOPIC_CARD_REL ${cls}`, title: list}, [
|
|
678
|
+
['span', {class: 'TREEDB_TOPIC_CARD_REL_ARROW', 'aria-hidden': 'true'}, arrow],
|
|
679
|
+
['span', {class: 'TREEDB_TOPIC_CARD_REL_LABEL', i18n: key}, key],
|
|
680
|
+
['span', {class: 'TREEDB_TOPIC_CARD_REL_LIST'}, list]
|
|
681
|
+
]];
|
|
682
|
+
};
|
|
683
|
+
if(parents.length) {
|
|
684
|
+
meta.push(relation('TREEDB_TOPIC_CARD_PARENTS', 'parents', '↑', parents));
|
|
685
|
+
}
|
|
686
|
+
if(children.length) {
|
|
687
|
+
meta.push(relation('TREEDB_TOPIC_CARD_CHILDREN', 'children', '↓', children));
|
|
688
|
+
}
|
|
689
|
+
return meta;
|
|
690
|
+
}
|
|
691
|
+
|
|
602
692
|
/************************************************************
|
|
603
693
|
* Add one topic card to the cards-landing grid (list->detail).
|
|
604
694
|
* With `card_action_routes` (host-supplied templates) the card
|
|
@@ -606,7 +696,7 @@ function card_action_anchor(logical_class, icon, i18n_key, href)
|
|
|
606
696
|
* it falls back to a single card that opens the table (same id
|
|
607
697
|
* contract as the tab, `<gobj>?<topic>`).
|
|
608
698
|
************************************************************/
|
|
609
|
-
function add_topic_card(gobj, id, text, icon)
|
|
699
|
+
function add_topic_card(gobj, id, text, icon, desc)
|
|
610
700
|
{
|
|
611
701
|
let $container = gobj_read_attr(gobj, "$container");
|
|
612
702
|
let $grid = $container.querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
|
|
@@ -635,9 +725,16 @@ function add_topic_card(gobj, id, text, icon)
|
|
|
635
725
|
return;
|
|
636
726
|
}
|
|
637
727
|
|
|
638
|
-
/*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
728
|
+
/* Two-icon card: info / table, real hash anchors. {topic} in each
|
|
729
|
+
* template is the raw topic name (identifier-safe, matching the
|
|
730
|
+
* tab/host `<base_route>/<topic>` convention).
|
|
731
|
+
*
|
|
732
|
+
* NO graph icon any more. Its route was `<graph>/<topic>`, and that
|
|
733
|
+
* segment is a FOCUS: every way into the graph from a card arrived
|
|
734
|
+
* with that topic highlighted, over whatever the reader had left
|
|
735
|
+
* there. The graph is one button in the toolbar now, with no topic
|
|
736
|
+
* (routes.graph still feeds it), and the room goes to what the card
|
|
737
|
+
* can say about its topic. */
|
|
641
738
|
let topic = id.indexOf("?") >= 0 ? id.split("?")[1] : id;
|
|
642
739
|
let fill = (tpl) => String(tpl || "").replace("{topic}", topic);
|
|
643
740
|
|
|
@@ -650,14 +747,11 @@ function add_topic_card(gobj, id, text, icon)
|
|
|
650
747
|
actions.push(card_action_anchor(
|
|
651
748
|
"TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
|
|
652
749
|
}
|
|
653
|
-
if(routes.graph) {
|
|
654
|
-
actions.push(card_action_anchor(
|
|
655
|
-
"TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
|
|
656
|
-
}
|
|
657
750
|
|
|
658
751
|
let $card = createElement2(
|
|
659
752
|
['div', {class: 'TREEDB_TOPIC_CARD', 'data-topic': topic}, [
|
|
660
753
|
['span', {class: 'TREEDB_TOPIC_CARD_NAME', i18n: text}, text],
|
|
754
|
+
...topic_card_meta(desc),
|
|
661
755
|
['div', {class: 'TREEDB_TOPIC_CARD_ACTIONS'}, actions]
|
|
662
756
|
]]
|
|
663
757
|
);
|
|
@@ -1087,7 +1181,7 @@ function process_treedb_descs(gobj)
|
|
|
1087
1181
|
|
|
1088
1182
|
// TODO get icon from remote config
|
|
1089
1183
|
add_tab(gobj, gobj_topic_form, id, key, "yi-table");
|
|
1090
|
-
add_topic_card(gobj, id, key, "yi-table");
|
|
1184
|
+
add_topic_card(gobj, id, key, "yi-table", desc);
|
|
1091
1185
|
|
|
1092
1186
|
gobj_start(gobj_topic_form);
|
|
1093
1187
|
}
|
|
@@ -2404,6 +2498,28 @@ function ac_open_json(gobj, event, kw, src)
|
|
|
2404
2498
|
return 0;
|
|
2405
2499
|
}
|
|
2406
2500
|
|
|
2501
|
+
/********************************************
|
|
2502
|
+
* Open the graph where the reader left it: its last visited
|
|
2503
|
+
* route under the graph's base -- `<graph>/<topic>` when a topic
|
|
2504
|
+
* was focused there -- or the bare base when the graph was never
|
|
2505
|
+
* opened in this page. A user move: pushed, so Back returns.
|
|
2506
|
+
********************************************/
|
|
2507
|
+
function ac_open_graph(gobj, event, kw, src)
|
|
2508
|
+
{
|
|
2509
|
+
let base = graph_route_of(gobj);
|
|
2510
|
+
if(!base) {
|
|
2511
|
+
log_error(`${gobj_short_name(gobj)}: open graph with no graph route`);
|
|
2512
|
+
return -1;
|
|
2513
|
+
}
|
|
2514
|
+
let shell = yui_shell_of(gobj);
|
|
2515
|
+
if(!shell) {
|
|
2516
|
+
log_error(`${gobj_short_name(gobj)}: open graph with no shell`);
|
|
2517
|
+
return -1;
|
|
2518
|
+
}
|
|
2519
|
+
yui_shell_navigate(shell, yui_shell_last_route_under(shell, base), {push: true});
|
|
2520
|
+
return 0;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2407
2523
|
/********************************************
|
|
2408
2524
|
* The viewer asked to load a collapsed subtree (tranger mode only):
|
|
2409
2525
|
* re-issue print-tranger for that path.
|
|
@@ -2484,6 +2600,7 @@ function create_gclass(gclass_name)
|
|
|
2484
2600
|
["EV_DELETE_RECORD", ac_delete_record, null],
|
|
2485
2601
|
["EV_REFRESH_TOPIC", ac_refresh_topic, null],
|
|
2486
2602
|
["EV_OPEN_JSON", ac_open_json, null],
|
|
2603
|
+
["EV_OPEN_GRAPH", ac_open_graph, null],
|
|
2487
2604
|
["EV_EXPAND_PATH", ac_json_expand_path, null],
|
|
2488
2605
|
["EV_JSON_CLOSED", ac_json_closed, null],
|
|
2489
2606
|
["EV_SHOW", ac_show, null],
|
|
@@ -2514,6 +2631,7 @@ function create_gclass(gclass_name)
|
|
|
2514
2631
|
event_flag_t.EVF_NO_WARN_SUBS],
|
|
2515
2632
|
["EV_REFRESH_TOPIC", 0],
|
|
2516
2633
|
["EV_OPEN_JSON", 0],
|
|
2634
|
+
["EV_OPEN_GRAPH", 0],
|
|
2517
2635
|
["EV_EXPAND_PATH", 0],
|
|
2518
2636
|
["EV_JSON_CLOSED", 0],
|
|
2519
2637
|
["EV_SHOW", 0],
|