@yuneta/gobj-ui 7.23.137 → 7.23.139

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.
@@ -21798,7 +21798,7 @@ var attrs_table$6 = [
21798
21798
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
21799
21799
  (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
21800
  (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, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
21801
+ (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
21802
  (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
21803
  (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
21804
  (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,6 +21908,31 @@ function cmd_get_topic_data$1(gobj, cmd, kw, src) {
21908
21908
  ************************************************************/
21909
21909
  function build_ui$6(gobj) {
21910
21910
  let source_url = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "source_url") || "";
21911
+ let routes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "card_action_routes");
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\}$/, "") : "";
21914
+ let graph_items = [];
21915
+ if (graph_href) graph_items.push([
21916
+ "a",
21917
+ {
21918
+ class: "button TREEDB_GRAPH_BTN",
21919
+ href: graph_href,
21920
+ style: "margin-left:auto;",
21921
+ title: (0, i18next.t)("graph"),
21922
+ "aria-label": (0, i18next.t)("graph"),
21923
+ "data-i18n-title": "graph",
21924
+ "data-i18n-aria-label": "graph"
21925
+ },
21926
+ [[
21927
+ "span",
21928
+ { class: "icon" },
21929
+ [yui_toolbar_icon("yi-hexagon-nodes")]
21930
+ ], [
21931
+ "span",
21932
+ { i18n: "graph" },
21933
+ "graph"
21934
+ ]]
21935
+ ]);
21911
21936
  let $container = (0, _yuneta_gobj_js.createElement2)([
21912
21937
  "div",
21913
21938
  {
@@ -21988,11 +22013,12 @@ function build_ui$6(gobj) {
21988
22013
  source_url
21989
22014
  ]]
21990
22015
  ],
22016
+ ...graph_items,
21991
22017
  [
21992
22018
  "button",
21993
22019
  {
21994
22020
  class: "button TREEDB_JSON_BTN",
21995
- style: "margin-left:auto;",
22021
+ style: graph_href ? "" : "margin-left:auto;",
21996
22022
  title: (0, i18next.t)("raw json"),
21997
22023
  "aria-label": (0, i18next.t)("raw json"),
21998
22024
  "data-i18n-title": "raw json",
@@ -22195,13 +22221,103 @@ function card_action_anchor(logical_class, icon, i18n_key, href) {
22195
22221
  ];
22196
22222
  }
22197
22223
  /************************************************************
22224
+ * What a topic card says about its topic, from its desc alone
22225
+ * (no request): its version and how many columns it has, the
22226
+ * topics it hangs FROM (its fkeys) and the ones that hang from
22227
+ * it (its hooks). The names are DATA and are not translated;
22228
+ * each label carries its key. Lists are cut with an ellipsis,
22229
+ * whole in the title.
22230
+ ************************************************************/
22231
+ function topic_card_meta(desc) {
22232
+ if (!(0, _yuneta_gobj_js.is_object)(desc)) return [];
22233
+ let cols = (0, _yuneta_gobj_js.is_array)(desc.cols) ? desc.cols : [];
22234
+ let n_cols = 0;
22235
+ let parents = [];
22236
+ let children = [];
22237
+ for (let col of cols) {
22238
+ if (!col || !col.id || col.id.charAt(0) === "_") continue;
22239
+ n_cols++;
22240
+ if ((0, _yuneta_gobj_js.is_object)(col.fkey)) {
22241
+ for (let t_name of Object.keys(col.fkey)) if (!parents.includes(t_name)) parents.push(t_name);
22242
+ }
22243
+ if ((0, _yuneta_gobj_js.is_object)(col.hook)) {
22244
+ for (let t_name of Object.keys(col.hook)) if (!children.includes(t_name)) children.push(t_name);
22245
+ }
22246
+ }
22247
+ let meta = [];
22248
+ let head = [];
22249
+ if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") head.push([
22250
+ "span",
22251
+ {
22252
+ class: "TREEDB_TOPIC_CARD_VERSION",
22253
+ title: (0, i18next.t)("version"),
22254
+ "data-i18n-title": "version"
22255
+ },
22256
+ `v${desc.topic_version}`
22257
+ ]);
22258
+ head.push([
22259
+ "span",
22260
+ { class: "TREEDB_TOPIC_CARD_COLS" },
22261
+ [[
22262
+ "span",
22263
+ {},
22264
+ `${n_cols}`
22265
+ ], [
22266
+ "span",
22267
+ { i18n: "columns" },
22268
+ "columns"
22269
+ ]]
22270
+ ]);
22271
+ meta.push([
22272
+ "div",
22273
+ { class: "TREEDB_TOPIC_CARD_META" },
22274
+ head
22275
+ ]);
22276
+ let relation = (cls, key, arrow, names) => {
22277
+ let list = names.join(", ");
22278
+ return [
22279
+ "div",
22280
+ {
22281
+ class: `TREEDB_TOPIC_CARD_REL ${cls}`,
22282
+ title: list
22283
+ },
22284
+ [
22285
+ [
22286
+ "span",
22287
+ {
22288
+ class: "TREEDB_TOPIC_CARD_REL_ARROW",
22289
+ "aria-hidden": "true"
22290
+ },
22291
+ arrow
22292
+ ],
22293
+ [
22294
+ "span",
22295
+ {
22296
+ class: "TREEDB_TOPIC_CARD_REL_LABEL",
22297
+ i18n: key
22298
+ },
22299
+ key
22300
+ ],
22301
+ [
22302
+ "span",
22303
+ { class: "TREEDB_TOPIC_CARD_REL_LIST" },
22304
+ list
22305
+ ]
22306
+ ]
22307
+ ];
22308
+ };
22309
+ if (parents.length) meta.push(relation("TREEDB_TOPIC_CARD_PARENTS", "parents", "↑", parents));
22310
+ if (children.length) meta.push(relation("TREEDB_TOPIC_CARD_CHILDREN", "children", "↓", children));
22311
+ return meta;
22312
+ }
22313
+ /************************************************************
22198
22314
  * Add one topic card to the cards-landing grid (list->detail).
22199
22315
  * With `card_action_routes` (host-supplied templates) the card
22200
22316
  * shows three hash-routed icons — info / table / graph. Without,
22201
22317
  * it falls back to a single card that opens the table (same id
22202
22318
  * contract as the tab, `<gobj>?<topic>`).
22203
22319
  ************************************************************/
22204
- function add_topic_card(gobj, id, text, icon) {
22320
+ function add_topic_card(gobj, id, text, icon, desc) {
22205
22321
  let $grid = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container").querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
22206
22322
  if (!$grid) return;
22207
22323
  let routes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "card_action_routes");
@@ -22244,25 +22360,28 @@ function add_topic_card(gobj, id, text, icon) {
22244
22360
  let actions = [];
22245
22361
  if (routes.info) actions.push(card_action_anchor("TREEDB_CARD_INFO", "yi-circle-info", "info", fill(routes.info)));
22246
22362
  if (routes.table) actions.push(card_action_anchor("TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
22247
- if (routes.graph) actions.push(card_action_anchor("TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
22248
22363
  let $card = (0, _yuneta_gobj_js.createElement2)([
22249
22364
  "div",
22250
22365
  {
22251
22366
  class: "TREEDB_TOPIC_CARD",
22252
22367
  "data-topic": topic
22253
22368
  },
22254
- [[
22255
- "span",
22256
- {
22257
- class: "TREEDB_TOPIC_CARD_NAME",
22258
- i18n: text
22259
- },
22260
- text
22261
- ], [
22262
- "div",
22263
- { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22264
- actions
22265
- ]]
22369
+ [
22370
+ [
22371
+ "span",
22372
+ {
22373
+ class: "TREEDB_TOPIC_CARD_NAME",
22374
+ i18n: text
22375
+ },
22376
+ text
22377
+ ],
22378
+ ...topic_card_meta(desc),
22379
+ [
22380
+ "div",
22381
+ { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22382
+ actions
22383
+ ]
22384
+ ]
22266
22385
  ]);
22267
22386
  if (gobj.priv._selected_card_topic === topic) $card.classList.add("is-selected");
22268
22387
  $card.addEventListener("click", function() {
@@ -22615,7 +22734,7 @@ function process_treedb_descs$1(gobj) {
22615
22734
  let id = `${(0, _yuneta_gobj_js.gobj_name)(gobj)}?${desc.topic_name}`;
22616
22735
  let gobj_topic_form = (0, _yuneta_gobj_js.gobj_create_service)(id, "C_YUI_TREEDB_TOPIC_WITH_FORM", kw_topic_form, gobj);
22617
22736
  add_tab(gobj, gobj_topic_form, id, key, "yi-table");
22618
- add_topic_card(gobj, id, key, "yi-table");
22737
+ add_topic_card(gobj, id, key, "yi-table", desc);
22619
22738
  (0, _yuneta_gobj_js.gobj_start)(gobj_topic_form);
22620
22739
  }
22621
22740
  let href = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "last_selection");
@@ -21793,7 +21793,7 @@ var attrs_table$6 = [
21793
21793
  SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
21794
21794
  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
21795
  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, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
21796
+ 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
21797
  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
21798
  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
21799
  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,6 +21903,31 @@ function cmd_get_topic_data$1(gobj, cmd, kw, src) {
21903
21903
  ************************************************************/
21904
21904
  function build_ui$6(gobj) {
21905
21905
  let source_url = gobj_read_str_attr(gobj, "source_url") || "";
21906
+ let routes = gobj_read_attr(gobj, "card_action_routes");
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\}$/, "") : "";
21909
+ let graph_items = [];
21910
+ if (graph_href) graph_items.push([
21911
+ "a",
21912
+ {
21913
+ class: "button TREEDB_GRAPH_BTN",
21914
+ href: graph_href,
21915
+ style: "margin-left:auto;",
21916
+ title: t("graph"),
21917
+ "aria-label": t("graph"),
21918
+ "data-i18n-title": "graph",
21919
+ "data-i18n-aria-label": "graph"
21920
+ },
21921
+ [[
21922
+ "span",
21923
+ { class: "icon" },
21924
+ [yui_toolbar_icon("yi-hexagon-nodes")]
21925
+ ], [
21926
+ "span",
21927
+ { i18n: "graph" },
21928
+ "graph"
21929
+ ]]
21930
+ ]);
21906
21931
  let $container = createElement2([
21907
21932
  "div",
21908
21933
  {
@@ -21983,11 +22008,12 @@ function build_ui$6(gobj) {
21983
22008
  source_url
21984
22009
  ]]
21985
22010
  ],
22011
+ ...graph_items,
21986
22012
  [
21987
22013
  "button",
21988
22014
  {
21989
22015
  class: "button TREEDB_JSON_BTN",
21990
- style: "margin-left:auto;",
22016
+ style: graph_href ? "" : "margin-left:auto;",
21991
22017
  title: t("raw json"),
21992
22018
  "aria-label": t("raw json"),
21993
22019
  "data-i18n-title": "raw json",
@@ -22190,13 +22216,103 @@ function card_action_anchor(logical_class, icon, i18n_key, href) {
22190
22216
  ];
22191
22217
  }
22192
22218
  /************************************************************
22219
+ * What a topic card says about its topic, from its desc alone
22220
+ * (no request): its version and how many columns it has, the
22221
+ * topics it hangs FROM (its fkeys) and the ones that hang from
22222
+ * it (its hooks). The names are DATA and are not translated;
22223
+ * each label carries its key. Lists are cut with an ellipsis,
22224
+ * whole in the title.
22225
+ ************************************************************/
22226
+ function topic_card_meta(desc) {
22227
+ if (!is_object(desc)) return [];
22228
+ let cols = is_array(desc.cols) ? desc.cols : [];
22229
+ let n_cols = 0;
22230
+ let parents = [];
22231
+ let children = [];
22232
+ for (let col of cols) {
22233
+ if (!col || !col.id || col.id.charAt(0) === "_") continue;
22234
+ n_cols++;
22235
+ if (is_object(col.fkey)) {
22236
+ for (let t_name of Object.keys(col.fkey)) if (!parents.includes(t_name)) parents.push(t_name);
22237
+ }
22238
+ if (is_object(col.hook)) {
22239
+ for (let t_name of Object.keys(col.hook)) if (!children.includes(t_name)) children.push(t_name);
22240
+ }
22241
+ }
22242
+ let meta = [];
22243
+ let head = [];
22244
+ if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") head.push([
22245
+ "span",
22246
+ {
22247
+ class: "TREEDB_TOPIC_CARD_VERSION",
22248
+ title: t("version"),
22249
+ "data-i18n-title": "version"
22250
+ },
22251
+ `v${desc.topic_version}`
22252
+ ]);
22253
+ head.push([
22254
+ "span",
22255
+ { class: "TREEDB_TOPIC_CARD_COLS" },
22256
+ [[
22257
+ "span",
22258
+ {},
22259
+ `${n_cols}`
22260
+ ], [
22261
+ "span",
22262
+ { i18n: "columns" },
22263
+ "columns"
22264
+ ]]
22265
+ ]);
22266
+ meta.push([
22267
+ "div",
22268
+ { class: "TREEDB_TOPIC_CARD_META" },
22269
+ head
22270
+ ]);
22271
+ let relation = (cls, key, arrow, names) => {
22272
+ let list = names.join(", ");
22273
+ return [
22274
+ "div",
22275
+ {
22276
+ class: `TREEDB_TOPIC_CARD_REL ${cls}`,
22277
+ title: list
22278
+ },
22279
+ [
22280
+ [
22281
+ "span",
22282
+ {
22283
+ class: "TREEDB_TOPIC_CARD_REL_ARROW",
22284
+ "aria-hidden": "true"
22285
+ },
22286
+ arrow
22287
+ ],
22288
+ [
22289
+ "span",
22290
+ {
22291
+ class: "TREEDB_TOPIC_CARD_REL_LABEL",
22292
+ i18n: key
22293
+ },
22294
+ key
22295
+ ],
22296
+ [
22297
+ "span",
22298
+ { class: "TREEDB_TOPIC_CARD_REL_LIST" },
22299
+ list
22300
+ ]
22301
+ ]
22302
+ ];
22303
+ };
22304
+ if (parents.length) meta.push(relation("TREEDB_TOPIC_CARD_PARENTS", "parents", "↑", parents));
22305
+ if (children.length) meta.push(relation("TREEDB_TOPIC_CARD_CHILDREN", "children", "↓", children));
22306
+ return meta;
22307
+ }
22308
+ /************************************************************
22193
22309
  * Add one topic card to the cards-landing grid (list->detail).
22194
22310
  * With `card_action_routes` (host-supplied templates) the card
22195
22311
  * shows three hash-routed icons — info / table / graph. Without,
22196
22312
  * it falls back to a single card that opens the table (same id
22197
22313
  * contract as the tab, `<gobj>?<topic>`).
22198
22314
  ************************************************************/
22199
- function add_topic_card(gobj, id, text, icon) {
22315
+ function add_topic_card(gobj, id, text, icon, desc) {
22200
22316
  let $grid = gobj_read_attr(gobj, "$container").querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
22201
22317
  if (!$grid) return;
22202
22318
  let routes = gobj_read_attr(gobj, "card_action_routes");
@@ -22239,25 +22355,28 @@ function add_topic_card(gobj, id, text, icon) {
22239
22355
  let actions = [];
22240
22356
  if (routes.info) actions.push(card_action_anchor("TREEDB_CARD_INFO", "yi-circle-info", "info", fill(routes.info)));
22241
22357
  if (routes.table) actions.push(card_action_anchor("TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
22242
- if (routes.graph) actions.push(card_action_anchor("TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
22243
22358
  let $card = createElement2([
22244
22359
  "div",
22245
22360
  {
22246
22361
  class: "TREEDB_TOPIC_CARD",
22247
22362
  "data-topic": topic
22248
22363
  },
22249
- [[
22250
- "span",
22251
- {
22252
- class: "TREEDB_TOPIC_CARD_NAME",
22253
- i18n: text
22254
- },
22255
- text
22256
- ], [
22257
- "div",
22258
- { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22259
- actions
22260
- ]]
22364
+ [
22365
+ [
22366
+ "span",
22367
+ {
22368
+ class: "TREEDB_TOPIC_CARD_NAME",
22369
+ i18n: text
22370
+ },
22371
+ text
22372
+ ],
22373
+ ...topic_card_meta(desc),
22374
+ [
22375
+ "div",
22376
+ { class: "TREEDB_TOPIC_CARD_ACTIONS" },
22377
+ actions
22378
+ ]
22379
+ ]
22261
22380
  ]);
22262
22381
  if (gobj.priv._selected_card_topic === topic) $card.classList.add("is-selected");
22263
22382
  $card.addEventListener("click", function() {
@@ -22610,7 +22729,7 @@ function process_treedb_descs$1(gobj) {
22610
22729
  let id = `${gobj_name(gobj)}?${desc.topic_name}`;
22611
22730
  let gobj_topic_form = gobj_create_service(id, "C_YUI_TREEDB_TOPIC_WITH_FORM", kw_topic_form, gobj);
22612
22731
  add_tab(gobj, gobj_topic_form, id, key, "yi-table");
22613
- add_topic_card(gobj, id, key, "yi-table");
22732
+ add_topic_card(gobj, id, key, "yi-table", desc);
22614
22733
  gobj_start(gobj_topic_form);
22615
22734
  }
22616
22735
  let href = gobj_read_str_attr(gobj, "last_selection");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.137",
3
+ "version": "7.23.139",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -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;
@@ -84,7 +84,7 @@ SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system t
84
84
  SDATA(data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
85
85
  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
86
  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, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
87
+ 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
88
  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
89
  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
90
  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,6 +254,29 @@ function build_ui(gobj)
254
254
  {
255
255
  let source_url = gobj_read_str_attr(gobj, "source_url") || "";
256
256
 
257
+ /* The whole treedb as a graph, with NO topic focused. A card's graph
258
+ * icon lands on `<graph route>/<topic>`, and that segment is a
259
+ * FOCUS: going through a card was the only way in, and it always
260
+ * arrived with that topic highlighted. The route is the card's
261
+ * template without its `/{topic}`; a host whose template does not
262
+ * end in it, or gives none, gets no button. */
263
+ let routes = gobj_read_attr(gobj, "card_action_routes");
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\}$/, "") : "";
266
+ let graph_items = [];
267
+ if(graph_href) {
268
+ graph_items.push(
269
+ ['a', {class: 'button TREEDB_GRAPH_BTN',
270
+ href: graph_href,
271
+ style: 'margin-left:auto;',
272
+ title: t('graph'), 'aria-label': t('graph'),
273
+ 'data-i18n-title': 'graph', 'data-i18n-aria-label': 'graph'}, [
274
+ ['span', {class: 'icon'}, [yui_toolbar_icon('yi-hexagon-nodes')]],
275
+ ['span', {i18n: 'graph'}, 'graph']
276
+ ]]
277
+ );
278
+ }
279
+
257
280
  /*----------------------------------------------*
258
281
  * Layout Schema
259
282
  *----------------------------------------------*/
@@ -262,12 +285,15 @@ function build_ui(gobj)
262
285
  ['div', {class: 'is-flex-grow-0'}, [
263
286
  ['div', {class: 'is-flex is-align-items-center TREEDB_TOPICS_TOOLBAR',
264
287
  style: 'gap:.25rem; padding:.25rem .25rem;'}, [
265
- /* This toolbar never holds more than TWO buttons at once
266
- * (back|toggle on the left, json on the right), so the
267
- * labels stay on mobile — a deliberate exception to the
268
- * icon-only-on-mobile rule: two bare squares side by side
269
- * read as the same control. The graph's toolbar has many
270
- * more, so it keeps is-hidden-mobile. */
288
+ /* This toolbar never holds more than THREE buttons at once
289
+ * (back|toggle on the left, graph and json on the right),
290
+ * so the labels stay on mobile — a deliberate exception
291
+ * to the icon-only-on-mobile rule: bare squares side by
292
+ * side read as the same control, and the toggle and the
293
+ * graph wear the same icon. They fit at 360px because the
294
+ * source url between them gives way down to its icon. The
295
+ * graph's toolbar has many more, so it keeps
296
+ * is-hidden-mobile. */
271
297
 
272
298
  /* Back to the topic-cards grid (cards-landing mode only);
273
299
  * hidden until a topic is open. */
@@ -309,11 +335,14 @@ function build_ui(gobj)
309
335
  ['span', {class: 'icon'}, [yui_toolbar_icon('yi-cloudversify')]],
310
336
  ['span', {class: 'TREEDB_TOPICS_SOURCE_URL'}, source_url]
311
337
  ]],
338
+ ...graph_items,
312
339
  /* Inspect the treedb's raw tranger json (whole service,
313
- * print-tranger, lazy drill). Last + margin-left:auto:
314
- * flush right, away from the back arrow it sat next to. */
340
+ * print-tranger, lazy drill). Last and flush right, away
341
+ * from the back arrow it sat next to: the `margin-left:
342
+ * auto` goes on the first button of the right group,
343
+ * which is the graph one when there is one. */
315
344
  ['button', {class: 'button TREEDB_JSON_BTN',
316
- style: 'margin-left:auto;',
345
+ style: graph_href? '' : 'margin-left:auto;',
317
346
  title: t('raw json'), 'aria-label': t('raw json'),
318
347
  'data-i18n-title': 'raw json', 'data-i18n-aria-label': 'raw json'}, [
319
348
  ['span', {class: 'icon'}, [yui_toolbar_icon('yi-eye')]],
@@ -570,6 +599,74 @@ function card_action_anchor(logical_class, icon, i18n_key, href)
570
599
  ]];
571
600
  }
572
601
 
602
+ /************************************************************
603
+ * What a topic card says about its topic, from its desc alone
604
+ * (no request): its version and how many columns it has, the
605
+ * topics it hangs FROM (its fkeys) and the ones that hang from
606
+ * it (its hooks). The names are DATA and are not translated;
607
+ * each label carries its key. Lists are cut with an ellipsis,
608
+ * whole in the title.
609
+ ************************************************************/
610
+ function topic_card_meta(desc)
611
+ {
612
+ if(!is_object(desc)) {
613
+ return [];
614
+ }
615
+ let cols = is_array(desc.cols)? desc.cols : [];
616
+ let n_cols = 0;
617
+ let parents = [];
618
+ let children = [];
619
+ for(let col of cols) {
620
+ if(!col || !col.id || col.id.charAt(0) === "_") {
621
+ continue;
622
+ }
623
+ n_cols++;
624
+ if(is_object(col.fkey)) {
625
+ for(let t_name of Object.keys(col.fkey)) {
626
+ if(!parents.includes(t_name)) {
627
+ parents.push(t_name);
628
+ }
629
+ }
630
+ }
631
+ if(is_object(col.hook)) {
632
+ for(let t_name of Object.keys(col.hook)) {
633
+ if(!children.includes(t_name)) {
634
+ children.push(t_name);
635
+ }
636
+ }
637
+ }
638
+ }
639
+
640
+ let meta = [];
641
+ let head = [];
642
+ if(desc.topic_version !== undefined && desc.topic_version !== null && desc.topic_version !== "") {
643
+ head.push(['span', {class: 'TREEDB_TOPIC_CARD_VERSION',
644
+ title: t('version'), 'data-i18n-title': 'version'},
645
+ `v${desc.topic_version}`]);
646
+ }
647
+ head.push(['span', {class: 'TREEDB_TOPIC_CARD_COLS'}, [
648
+ ['span', {}, `${n_cols}`],
649
+ ['span', {i18n: 'columns'}, 'columns']
650
+ ]]);
651
+ meta.push(['div', {class: 'TREEDB_TOPIC_CARD_META'}, head]);
652
+
653
+ let relation = (cls, key, arrow, names) => {
654
+ let list = names.join(", ");
655
+ return ['div', {class: `TREEDB_TOPIC_CARD_REL ${cls}`, title: list}, [
656
+ ['span', {class: 'TREEDB_TOPIC_CARD_REL_ARROW', 'aria-hidden': 'true'}, arrow],
657
+ ['span', {class: 'TREEDB_TOPIC_CARD_REL_LABEL', i18n: key}, key],
658
+ ['span', {class: 'TREEDB_TOPIC_CARD_REL_LIST'}, list]
659
+ ]];
660
+ };
661
+ if(parents.length) {
662
+ meta.push(relation('TREEDB_TOPIC_CARD_PARENTS', 'parents', '↑', parents));
663
+ }
664
+ if(children.length) {
665
+ meta.push(relation('TREEDB_TOPIC_CARD_CHILDREN', 'children', '↓', children));
666
+ }
667
+ return meta;
668
+ }
669
+
573
670
  /************************************************************
574
671
  * Add one topic card to the cards-landing grid (list->detail).
575
672
  * With `card_action_routes` (host-supplied templates) the card
@@ -577,7 +674,7 @@ function card_action_anchor(logical_class, icon, i18n_key, href)
577
674
  * it falls back to a single card that opens the table (same id
578
675
  * contract as the tab, `<gobj>?<topic>`).
579
676
  ************************************************************/
580
- function add_topic_card(gobj, id, text, icon)
677
+ function add_topic_card(gobj, id, text, icon, desc)
581
678
  {
582
679
  let $container = gobj_read_attr(gobj, "$container");
583
680
  let $grid = $container.querySelector(".TREEDB_TOPICS_LANDING .yui-nav-cards");
@@ -606,9 +703,16 @@ function add_topic_card(gobj, id, text, icon)
606
703
  return;
607
704
  }
608
705
 
609
- /* Three-icon card: info / table / graph, all real hash anchors.
610
- * {topic} in each template is the raw topic name (identifier-safe,
611
- * matching the tab/host `<base_route>/<topic>` convention). */
706
+ /* Two-icon card: info / table, real hash anchors. {topic} in each
707
+ * template is the raw topic name (identifier-safe, matching the
708
+ * tab/host `<base_route>/<topic>` convention).
709
+ *
710
+ * NO graph icon any more. Its route was `<graph>/<topic>`, and that
711
+ * segment is a FOCUS: every way into the graph from a card arrived
712
+ * with that topic highlighted, over whatever the reader had left
713
+ * there. The graph is one button in the toolbar now, with no topic
714
+ * (routes.graph still feeds it), and the room goes to what the card
715
+ * can say about its topic. */
612
716
  let topic = id.indexOf("?") >= 0 ? id.split("?")[1] : id;
613
717
  let fill = (tpl) => String(tpl || "").replace("{topic}", topic);
614
718
 
@@ -621,14 +725,11 @@ function add_topic_card(gobj, id, text, icon)
621
725
  actions.push(card_action_anchor(
622
726
  "TREEDB_CARD_TABLE", "yi-table", "table", fill(routes.table)));
623
727
  }
624
- if(routes.graph) {
625
- actions.push(card_action_anchor(
626
- "TREEDB_CARD_GRAPH", "yi-hexagon-nodes", "graph", fill(routes.graph)));
627
- }
628
728
 
629
729
  let $card = createElement2(
630
730
  ['div', {class: 'TREEDB_TOPIC_CARD', 'data-topic': topic}, [
631
731
  ['span', {class: 'TREEDB_TOPIC_CARD_NAME', i18n: text}, text],
732
+ ...topic_card_meta(desc),
632
733
  ['div', {class: 'TREEDB_TOPIC_CARD_ACTIONS'}, actions]
633
734
  ]]
634
735
  );
@@ -1058,7 +1159,7 @@ function process_treedb_descs(gobj)
1058
1159
 
1059
1160
  // TODO get icon from remote config
1060
1161
  add_tab(gobj, gobj_topic_form, id, key, "yi-table");
1061
- add_topic_card(gobj, id, key, "yi-table");
1162
+ add_topic_card(gobj, id, key, "yi-table", desc);
1062
1163
 
1063
1164
  gobj_start(gobj_topic_form);
1064
1165
  }