@yuneta/gobj-ui 7.25.16 → 7.25.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -989,6 +989,35 @@ hold the list again; the cell and a delete's question count either shape. The
989
989
  search box no longer finds a parent row by the id of one of its children: to
990
990
  find what hangs from a row, click its hook.
991
991
 
992
+ ### What a write says to the host: `EV_RECORD_WRITTEN`
993
+
994
+ `C_YUI_TREEDB_TOPICS` and `C_YUI_TREEDB_GRAPH` publish `EV_RECORD_WRITTEN`
995
+ when a write of THEIRS is answered with success: a host whose work is not
996
+ finished with the record (a schema editor, a pending Apply) needs that, and the
997
+ `EV_TREEDB_NODE_*` events cannot tell its own writes from anybody else's. The
998
+ graph does not report `__graphs__`, its own bookkeeping.
999
+
1000
+ ```js
1001
+ // kw of EV_RECORD_WRITTEN
1002
+ {
1003
+ treedb_name: "treedb_test", // the view's own treedb
1004
+ topic_name: "yunos", // the topic written (the CHILD's, for a link)
1005
+ record: {id: "1", x: 1, y: 2}, // the node the store answered, as written
1006
+ created: false, // true for create-node
1007
+ command: "update-node", // create/update/delete-node, link/unlink-nodes
1008
+ parent_ref: "realms^r1^yunos", // graph, link/unlink only
1009
+ child_ref: "yunos^1" // graph, link/unlink only
1010
+ }
1011
+ ```
1012
+
1013
+ Since `7.25.18` every field carries what it says. They were read from the
1014
+ answer's command frame, which carries back only the request's
1015
+ `__md_command__` -- `C_IEVENT_CLI` and gui_agent's `C_AGENT_TREEDB_LINK` both
1016
+ do -- so `treedb_name` and `record` arrived empty, and a link named no topic.
1017
+ `treedb_name` is now the view's, `record` is the node the answer carries (a
1018
+ delete answers the node deleted, a link the child), and a link echoes its topic
1019
+ and refs in `__md_command__`.
1020
+
992
1021
  ### Read-only treedbs: `readonly`
993
1022
 
994
1023
  `C_YUI_TREEDB_TOPICS` and `C_YUI_TREEDB_GRAPH` take a **`readonly`** attr; the
@@ -1824,6 +1853,20 @@ gobj_send_event(engine, "EV_GRAPHS_WRITE_REFUSED", {topic: "users"}, host); //
1824
1853
  gobj_send_event(engine, "EV_SAVE_GRAPH", {}, engine); // writes users; Save dark
1825
1854
  ```
1826
1855
 
1856
+ **The refusal reaches the engine through the real transports** (since
1857
+ `7.25.17`). The answer of a command carries back only the request's
1858
+ `__md_command__`, as its `command_stack` frame -- `C_IEVENT_CLI` and gui_agent's
1859
+ `C_AGENT_TREEDB_LINK` both do, and neither echoes the request's `record`. The
1860
+ host read the topic from that `record`, found none, logged *"a refused
1861
+ __graphs__ write names no topic"* and never told the engine: the 7.25.15 fix
1862
+ worked only in a test whose fake transport echoed the whole request. The host
1863
+ now puts the topic in the echo it asks for:
1864
+
1865
+ ```js
1866
+ kw.__md_command__ = {topic_name: "__graphs__", graph_topic: "users"};
1867
+ // the answer: __md_iev__.command_stack[0].kw === that echo, and nothing else
1868
+ ```
1869
+
1827
1870
  **Every saved look has a way back, in the context menu** (since `7.23.134`,
1828
1871
  replacing `reset sizes` / `reset topic sizes`). In edition, the node, the port
1829
1872
  and the edge menus each offer three resets — this one, its kind (`reset topic
@@ -2259,6 +2302,39 @@ else.
2259
2302
  gobj_send_event(editor, "EV_REFRESH", {}, host); // logged, waits
2260
2303
  // ... both deletes answered -> ST_LOADING -> the model is read again
2261
2304
  ```
2305
+ - **A write in flight takes the body out of the keyboard's reach too**
2306
+ (7.25.17). `ST_SAVING` keeps the screen under it and marks the body busy.
2307
+ The busy class only stopped the pointer (`pointer-events: none`); the
2308
+ shell's focus trap put the focus back on the row control that had it, and
2309
+ Enter or Delete there sent `EV_DELETE_COLUMN` or `EV_SELECT_TOPIC`, which
2310
+ `ST_SAVING` does not declare (*"Event NOT DEFINED"*). The busy body is now
2311
+ also `inert`. `ST_LOADING` needs nothing: its body is emptied.
2312
+
2313
+ ```js
2314
+ // in ST_SAVING
2315
+ $container.querySelector(".SCHEMA_BODY").hasAttribute("inert"); // true
2316
+ ```
2317
+ - **A write marks the topic of its RECORD, not the topic on screen**
2318
+ (7.25.17). What "its version still has to move" is asked about is the set of
2319
+ topics this session wrote (the draft chip, the export warning,
2320
+ `validate_schema()`'s `written_topics`). A column write marked the topic on
2321
+ screen, so an import from the topics screen (no topic on it) marked nothing,
2322
+ and an import that added `roles` from the screen of `users` marked `users`.
2323
+ The mark is read from the record the store answered (a new topic has its id
2324
+ only there), then from the record queued (a column names its topic in its
2325
+ fkey), and only then from the screen.
2326
+
2327
+ ```js
2328
+ // import adding topic `roles`, run from db/users, every write answered
2329
+ editor.priv.written; // {"db.roles": true} (was {"db.users": true})
2330
+ ```
2331
+ - **The reason a load failed is a key** (7.25.17). The notice's detail was
2332
+ the text of `t()` taken when the load failed (`cannot reach the treedb`,
2333
+ `cannot load the schemas`) or the backend's words, shown as plain text: it
2334
+ did not change language, and with no comment it repeated the notice's own
2335
+ title. It is now kept as a key, written with `data-i18n` (the backend's
2336
+ words translate to themselves when no locale has them), and left out when
2337
+ it is the title.
2262
2338
  - **The loading screen is drawn** (7.25.8): the body is cleared, the drawing
2263
2339
  destroyed and every toolbar button disabled, as in `ST_SAVING`, because
2264
2340
  `ST_LOADING` declares none of a screen's actions. A **dialog** left open is
@@ -24924,6 +24924,17 @@ function treedb_nodes$1(gobj, treedb_name, topic_name, options, page, purpose) {
24924
24924
  return 0;
24925
24925
  }
24926
24926
  /************************************************************
24927
+ * The record a write answered: the node the store wrote (an
24928
+ * update or a create), deleted, or the child of a link. The
24929
+ * answer's frame carries back only `__md_command__`, never the
24930
+ * request's own record.
24931
+ ************************************************************/
24932
+ function answered_record$1(data) {
24933
+ if (data && typeof data === "object" && !Array.isArray(data)) return data;
24934
+ if (Array.isArray(data) && data[0] && typeof data[0] === "object") return data[0];
24935
+ return {};
24936
+ }
24937
+ /************************************************************
24927
24938
  * Command to remote service
24928
24939
  ************************************************************/
24929
24940
  function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, form_write) {
@@ -25245,9 +25256,9 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25245
25256
  case "create-node":
25246
25257
  case "update-node":
25247
25258
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", {
25248
- treedb_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "treedb_name", "", 0),
25259
+ treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name"),
25249
25260
  topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
25250
- record: (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0),
25261
+ record: answered_record$1(data),
25251
25262
  created: command === "create-node",
25252
25263
  command
25253
25264
  });
@@ -68378,6 +68389,7 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68378
68389
  options: options || {}
68379
68390
  };
68380
68391
  kw.__md_command__ = { topic_name };
68392
+ if (topic_name === "__graphs__") kw.__md_command__.graph_topic = graph_topic_of(record);
68381
68393
  let ret = (0, _yuneta_gobj_js.gobj_command)(priv.gobj_remote_yuno, command, kw, gobj);
68382
68394
  if (ret) {
68383
68395
  (0, _yuneta_gobj_js.log_error)(ret);
@@ -68407,6 +68419,29 @@ function treedb_delete_node(gobj, treedb_name, topic_name, record, options) {
68407
68419
  if (ret) (0, _yuneta_gobj_js.log_error)(ret);
68408
68420
  }
68409
68421
  /************************************************************
68422
+ * What a link or an unlink asks back with its answer: the
68423
+ * topic of the CHILD (the node a link saves) and both refs.
68424
+ * `child_ref` is "topic^id", `parent_ref` "topic^id^hook".
68425
+ ************************************************************/
68426
+ function link_echo(parent_ref, child_ref) {
68427
+ return {
68428
+ topic_name: typeof child_ref === "string" ? child_ref.split("^")[0] : "",
68429
+ parent_ref,
68430
+ child_ref
68431
+ };
68432
+ }
68433
+ /************************************************************
68434
+ * The record a write answered: the node the store wrote (an
68435
+ * update or a create), deleted, or the child of a link. The
68436
+ * answer's frame carries back only `__md_command__`, never the
68437
+ * request's own record.
68438
+ ************************************************************/
68439
+ function answered_record(data) {
68440
+ if (data && typeof data === "object" && !Array.isArray(data)) return data;
68441
+ if (Array.isArray(data) && data[0] && typeof data[0] === "object") return data[0];
68442
+ return {};
68443
+ }
68444
+ /************************************************************
68410
68445
  * Command to remote service
68411
68446
  ************************************************************/
68412
68447
  function treedb_link_nodes(gobj, treedb_name, parent_ref, child_ref, options) {
@@ -68423,6 +68458,7 @@ function treedb_link_nodes(gobj, treedb_name, parent_ref, child_ref, options) {
68423
68458
  child_ref,
68424
68459
  options: options || {}
68425
68460
  };
68461
+ kw.__md_command__ = link_echo(parent_ref, child_ref);
68426
68462
  let ret = (0, _yuneta_gobj_js.gobj_command)(priv.gobj_remote_yuno, command, kw, gobj);
68427
68463
  if (ret) (0, _yuneta_gobj_js.log_error)(ret);
68428
68464
  }
@@ -68443,6 +68479,7 @@ function treedb_unlink_nodes(gobj, treedb_name, parent_ref, child_ref, options)
68443
68479
  child_ref,
68444
68480
  options: options || {}
68445
68481
  };
68482
+ kw.__md_command__ = link_echo(parent_ref, child_ref);
68446
68483
  let ret = (0, _yuneta_gobj_js.gobj_command)(priv.gobj_remote_yuno, command, kw, gobj);
68447
68484
  if (ret) (0, _yuneta_gobj_js.log_error)(ret);
68448
68485
  }
@@ -68656,14 +68693,21 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68656
68693
  case "delete-node":
68657
68694
  case "link-nodes":
68658
68695
  case "unlink-nodes":
68659
- if (result < 0 && command === "update-node") graphs_write_refused(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0), (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0));
68660
- if (result >= 0 && (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", {
68661
- treedb_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "treedb_name", "", 0),
68662
- topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
68663
- record: (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0),
68664
- created: command === "create-node",
68665
- command
68666
- });
68696
+ if (result < 0 && command === "update-node") graphs_write_refused(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0), (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "graph_topic", "", 0));
68697
+ if (result >= 0 && (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
68698
+ let written = {
68699
+ treedb_name: priv.treedb_name,
68700
+ topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
68701
+ record: answered_record(data),
68702
+ created: command === "create-node",
68703
+ command
68704
+ };
68705
+ if (command === "link-nodes" || command === "unlink-nodes") {
68706
+ written.parent_ref = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "parent_ref", "", 0);
68707
+ written.child_ref = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "child_ref", "", 0);
68708
+ }
68709
+ (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", written);
68710
+ }
68667
68711
  break;
68668
68712
  default: (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)} Command unknown: ${command}`);
68669
68713
  }
@@ -68905,29 +68949,35 @@ function ac_update_node(gobj, event, kw, src) {
68905
68949
  let record = kw.record;
68906
68950
  let options = kw.options || {};
68907
68951
  if (refuse_if_readonly$1(gobj, event)) {
68908
- graphs_write_refused(gobj, topic_name, record);
68952
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record));
68909
68953
  return -1;
68910
68954
  }
68911
68955
  if (topic_name === "__graphs__" && !is_connected(gobj)) {
68912
68956
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no session, the arrangement was not sent`);
68913
- graphs_write_refused(gobj, topic_name, record);
68957
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record));
68914
68958
  return -1;
68915
68959
  }
68916
68960
  let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options);
68917
- if (ret < 0) graphs_write_refused(gobj, topic_name, record);
68961
+ if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record));
68918
68962
  return ret;
68919
68963
  }
68920
68964
  /************************************************************
68965
+ * The topic a `__graphs__` record arranges.
68966
+ ************************************************************/
68967
+ function graph_topic_of(record) {
68968
+ if (!record || typeof record !== "object") return "";
68969
+ return record.topic || record.id || "";
68970
+ }
68971
+ /************************************************************
68921
68972
  * A write of `__graphs__` did not land. The engine
68922
68973
  * (C_G6_NODES_TREE) took it for granted when it left -- nothing
68923
68974
  * answers it when it lands -- so it is told, and its next Save
68924
68975
  * writes the topic again. Other topics are the operator's data:
68925
68976
  * the answer's error says what happened to them.
68926
68977
  ************************************************************/
68927
- function graphs_write_refused(gobj, topic_name, record) {
68978
+ function graphs_write_refused(gobj, topic_name, topic) {
68928
68979
  let priv = gobj.priv;
68929
68980
  if (topic_name !== "__graphs__") return;
68930
- let topic = record && typeof record === "object" ? record.topic || record.id : "";
68931
68981
  if (!topic) {
68932
68982
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: a refused __graphs__ write names no topic`);
68933
68983
  return;
@@ -72486,11 +72536,18 @@ function el_attrs(attrs) {
72486
72536
  return attrs;
72487
72537
  }
72488
72538
  /***************************************************************
72489
- * The body stops answering while a write is in flight.
72539
+ * The body stops answering while a write is in flight. For the
72540
+ * keyboard too: `pointer-events` stops the mouse alone, and the
72541
+ * shell's focus trap puts the focus back on the row control that
72542
+ * had it, where Enter or Delete sent an action ST_SAVING does
72543
+ * not declare. `inert` takes the whole body out of reach.
72490
72544
  ***************************************************************/
72491
72545
  function set_busy(gobj, busy) {
72492
72546
  let $body = $of(gobj, ".SCHEMA_BODY");
72493
- if ($body) $body.classList.toggle("SCHEMA_BUSY", !!busy);
72547
+ if ($body) {
72548
+ $body.classList.toggle("SCHEMA_BUSY", !!busy);
72549
+ $body.toggleAttribute("inert", !!busy);
72550
+ }
72494
72551
  }
72495
72552
  /***************************************************************
72496
72553
  * Shorthands for the three panes of the frame.
@@ -72706,7 +72763,7 @@ function request_model(gobj, keep_written) {
72706
72763
  priv.records_before = null;
72707
72764
  priv.load_round++;
72708
72765
  priv.reload_on_open = true;
72709
- priv.load_error = (0, i18next.t)("cannot reach the treedb");
72766
+ priv.load_error = "cannot reach the treedb";
72710
72767
  if (priv.model) yui_shell_show_error(yui_shell_of(gobj), "cannot reach the treedb", { t: i18next.t });
72711
72768
  end_load(gobj);
72712
72769
  return -1;
@@ -72917,10 +72974,13 @@ function show_notice(gobj, key, detail) {
72917
72974
  },
72918
72975
  (0, i18next.t)(key)
72919
72976
  ]));
72920
- if (detail) $notice.appendChild((0, _yuneta_gobj_js.createElement2)([
72977
+ if (detail && detail !== key) $notice.appendChild((0, _yuneta_gobj_js.createElement2)([
72921
72978
  "span",
72922
- { class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2" },
72923
- `${detail}`
72979
+ {
72980
+ class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2",
72981
+ i18n: `${detail}`
72982
+ },
72983
+ (0, i18next.t)(`${detail}`)
72924
72984
  ]));
72925
72985
  }
72926
72986
  /***************************************************************
@@ -75475,7 +75535,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75475
75535
  return 0;
75476
75536
  }
75477
75537
  priv.pending--;
75478
- if (result < 0) priv.load_error = comment || (0, i18next.t)("cannot load the schemas");
75538
+ if (result < 0) priv.load_error = comment || "cannot load the schemas";
75479
75539
  else if (priv.records && Array.isArray(priv.records[topic_name])) priv.records[topic_name] = Array.isArray(data) ? data : [];
75480
75540
  if (priv.pending > 0) return 0;
75481
75541
  if (priv.load_error) {
@@ -75511,9 +75571,10 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75511
75571
  }
75512
75572
  if (result < 0) return end_writes(gobj, comment || (0, i18next.t)("the treedb refused the write"));
75513
75573
  priv.save_wrote = true;
75574
+ let record = null;
75514
75575
  if (command === "delete-node") forget_record(gobj, topic_name, (0, _yuneta_gobj_js.kw_get_str)(gobj, md, "record_id", "", 0));
75515
75576
  else {
75516
- let record = (0, _yuneta_gobj_js.is_object)(data) ? data : Array.isArray(data) && (0, _yuneta_gobj_js.is_object)(data[0]) ? data[0] : null;
75577
+ record = (0, _yuneta_gobj_js.is_object)(data) ? data : Array.isArray(data) && (0, _yuneta_gobj_js.is_object)(data[0]) ? data[0] : null;
75517
75578
  if (record) patch_record(gobj, topic_name, record);
75518
75579
  else {
75519
75580
  let not_sent = priv.save_queue.length - 1;
@@ -75521,7 +75582,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75521
75582
  if (not_sent > 0) yui_shell_show_error(yui_shell_of(gobj), "the treedb did not describe a write back: the writes after it were not sent", { t: i18next.t });
75522
75583
  if (topic_name === T_TOPICS || topic_name === T_COLS) {
75523
75584
  let write = priv.save_queue[0];
75524
- mark_written(gobj, topic_name, write ? write.record : null);
75585
+ mark_written(gobj, topic_name, null, write ? write.record : null);
75525
75586
  }
75526
75587
  priv.reload_after_write = false;
75527
75588
  priv.save_queue = [];
@@ -75536,7 +75597,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75536
75597
  }
75537
75598
  if (topic_name === T_TOPICS || topic_name === T_COLS) {
75538
75599
  let write = priv.save_queue[0];
75539
- mark_written(gobj, topic_name, write ? write.record : null);
75600
+ mark_written(gobj, topic_name, record, write ? write.record : null);
75540
75601
  }
75541
75602
  priv.save_queue.shift();
75542
75603
  return run_next_write(gobj);
@@ -75593,25 +75654,34 @@ function late_write_done(gobj, command, topic_name, data) {
75593
75654
  ***************************************************************/
75594
75655
  function mark_written_record(gobj, topic_name, record) {
75595
75656
  let priv = gobj.priv;
75596
- if (!record) return;
75657
+ let marked = 0;
75658
+ if (!record) return 0;
75597
75659
  if (topic_name === T_TOPICS && typeof record.id === "string" && record.id) {
75598
75660
  priv.written[record.id] = true;
75599
- return;
75661
+ return 1;
75600
75662
  }
75601
- if (topic_name !== T_COLS) return;
75602
- for (let ref of parse_fkey_ref(record.topics)) priv.written[ref.id] = true;
75663
+ if (topic_name !== T_COLS) return 0;
75664
+ for (let ref of parse_fkey_ref(record.topics)) {
75665
+ priv.written[ref.id] = true;
75666
+ marked++;
75667
+ }
75668
+ return marked;
75603
75669
  }
75604
75670
  /***************************************************************
75605
75671
  * Remember which topic this write belongs to, which is what
75606
75672
  * "its version still has to move" is asked about.
75673
+ *
75674
+ * Asked of the RECORD first: the one the store answered (a new
75675
+ * topic has its id only there), then the one queued (a column
75676
+ * names its topic in its fkey). The screen is the last resort,
75677
+ * and a poor one: an import writes topics the operator is not
75678
+ * on, and from the topics screen there is no topic on it at all.
75607
75679
  ***************************************************************/
75608
- function mark_written(gobj, topic_name, record) {
75680
+ function mark_written(gobj, topic_name, answered, queued) {
75609
75681
  let priv = gobj.priv;
75682
+ if (mark_written_record(gobj, topic_name, answered) > 0) return;
75683
+ if (mark_written_record(gobj, topic_name, queued) > 0) return;
75610
75684
  let topic = current_topic(gobj);
75611
- if (topic_name === T_TOPICS && record && record.id) {
75612
- priv.written[record.id] = true;
75613
- return;
75614
- }
75615
75685
  if (topic) priv.written[topic.id] = true;
75616
75686
  }
75617
75687
  /***************************************************************
@@ -24919,6 +24919,17 @@ function treedb_nodes$1(gobj, treedb_name, topic_name, options, page, purpose) {
24919
24919
  return 0;
24920
24920
  }
24921
24921
  /************************************************************
24922
+ * The record a write answered: the node the store wrote (an
24923
+ * update or a create), deleted, or the child of a link. The
24924
+ * answer's frame carries back only `__md_command__`, never the
24925
+ * request's own record.
24926
+ ************************************************************/
24927
+ function answered_record$1(data) {
24928
+ if (data && typeof data === "object" && !Array.isArray(data)) return data;
24929
+ if (Array.isArray(data) && data[0] && typeof data[0] === "object") return data[0];
24930
+ return {};
24931
+ }
24932
+ /************************************************************
24922
24933
  * Command to remote service
24923
24934
  ************************************************************/
24924
24935
  function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, form_write) {
@@ -25240,9 +25251,9 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25240
25251
  case "create-node":
25241
25252
  case "update-node":
25242
25253
  gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
25243
- treedb_name: kw_get_str(gobj, kw_command, "treedb_name", "", 0),
25254
+ treedb_name: gobj_read_str_attr(gobj, "treedb_name"),
25244
25255
  topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
25245
- record: kw_get_dict(gobj, kw_command, "record", {}, 0),
25256
+ record: answered_record$1(data),
25246
25257
  created: command === "create-node",
25247
25258
  command
25248
25259
  });
@@ -68373,6 +68384,7 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68373
68384
  options: options || {}
68374
68385
  };
68375
68386
  kw.__md_command__ = { topic_name };
68387
+ if (topic_name === "__graphs__") kw.__md_command__.graph_topic = graph_topic_of(record);
68376
68388
  let ret = gobj_command(priv.gobj_remote_yuno, command, kw, gobj);
68377
68389
  if (ret) {
68378
68390
  log_error(ret);
@@ -68402,6 +68414,29 @@ function treedb_delete_node(gobj, treedb_name, topic_name, record, options) {
68402
68414
  if (ret) log_error(ret);
68403
68415
  }
68404
68416
  /************************************************************
68417
+ * What a link or an unlink asks back with its answer: the
68418
+ * topic of the CHILD (the node a link saves) and both refs.
68419
+ * `child_ref` is "topic^id", `parent_ref` "topic^id^hook".
68420
+ ************************************************************/
68421
+ function link_echo(parent_ref, child_ref) {
68422
+ return {
68423
+ topic_name: typeof child_ref === "string" ? child_ref.split("^")[0] : "",
68424
+ parent_ref,
68425
+ child_ref
68426
+ };
68427
+ }
68428
+ /************************************************************
68429
+ * The record a write answered: the node the store wrote (an
68430
+ * update or a create), deleted, or the child of a link. The
68431
+ * answer's frame carries back only `__md_command__`, never the
68432
+ * request's own record.
68433
+ ************************************************************/
68434
+ function answered_record(data) {
68435
+ if (data && typeof data === "object" && !Array.isArray(data)) return data;
68436
+ if (Array.isArray(data) && data[0] && typeof data[0] === "object") return data[0];
68437
+ return {};
68438
+ }
68439
+ /************************************************************
68405
68440
  * Command to remote service
68406
68441
  ************************************************************/
68407
68442
  function treedb_link_nodes(gobj, treedb_name, parent_ref, child_ref, options) {
@@ -68418,6 +68453,7 @@ function treedb_link_nodes(gobj, treedb_name, parent_ref, child_ref, options) {
68418
68453
  child_ref,
68419
68454
  options: options || {}
68420
68455
  };
68456
+ kw.__md_command__ = link_echo(parent_ref, child_ref);
68421
68457
  let ret = gobj_command(priv.gobj_remote_yuno, command, kw, gobj);
68422
68458
  if (ret) log_error(ret);
68423
68459
  }
@@ -68438,6 +68474,7 @@ function treedb_unlink_nodes(gobj, treedb_name, parent_ref, child_ref, options)
68438
68474
  child_ref,
68439
68475
  options: options || {}
68440
68476
  };
68477
+ kw.__md_command__ = link_echo(parent_ref, child_ref);
68441
68478
  let ret = gobj_command(priv.gobj_remote_yuno, command, kw, gobj);
68442
68479
  if (ret) log_error(ret);
68443
68480
  }
@@ -68651,14 +68688,21 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68651
68688
  case "delete-node":
68652
68689
  case "link-nodes":
68653
68690
  case "unlink-nodes":
68654
- if (result < 0 && command === "update-node") graphs_write_refused(gobj, kw_get_str(gobj, kw_command, "topic_name", "", 0), kw_get_dict(gobj, kw_command, "record", {}, 0));
68655
- if (result >= 0 && kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
68656
- treedb_name: kw_get_str(gobj, kw_command, "treedb_name", "", 0),
68657
- topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
68658
- record: kw_get_dict(gobj, kw_command, "record", {}, 0),
68659
- created: command === "create-node",
68660
- command
68661
- });
68691
+ if (result < 0 && command === "update-node") graphs_write_refused(gobj, kw_get_str(gobj, kw_command, "topic_name", "", 0), kw_get_str(gobj, kw_command, "graph_topic", "", 0));
68692
+ if (result >= 0 && kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
68693
+ let written = {
68694
+ treedb_name: priv.treedb_name,
68695
+ topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
68696
+ record: answered_record(data),
68697
+ created: command === "create-node",
68698
+ command
68699
+ };
68700
+ if (command === "link-nodes" || command === "unlink-nodes") {
68701
+ written.parent_ref = kw_get_str(gobj, kw_command, "parent_ref", "", 0);
68702
+ written.child_ref = kw_get_str(gobj, kw_command, "child_ref", "", 0);
68703
+ }
68704
+ gobj_publish_event(gobj, "EV_RECORD_WRITTEN", written);
68705
+ }
68662
68706
  break;
68663
68707
  default: log_error(`${gobj_short_name(gobj)} Command unknown: ${command}`);
68664
68708
  }
@@ -68900,29 +68944,35 @@ function ac_update_node(gobj, event, kw, src) {
68900
68944
  let record = kw.record;
68901
68945
  let options = kw.options || {};
68902
68946
  if (refuse_if_readonly$1(gobj, event)) {
68903
- graphs_write_refused(gobj, topic_name, record);
68947
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record));
68904
68948
  return -1;
68905
68949
  }
68906
68950
  if (topic_name === "__graphs__" && !is_connected(gobj)) {
68907
68951
  log_warning(`${gobj_short_name(gobj)}: no session, the arrangement was not sent`);
68908
- graphs_write_refused(gobj, topic_name, record);
68952
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record));
68909
68953
  return -1;
68910
68954
  }
68911
68955
  let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options);
68912
- if (ret < 0) graphs_write_refused(gobj, topic_name, record);
68956
+ if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record));
68913
68957
  return ret;
68914
68958
  }
68915
68959
  /************************************************************
68960
+ * The topic a `__graphs__` record arranges.
68961
+ ************************************************************/
68962
+ function graph_topic_of(record) {
68963
+ if (!record || typeof record !== "object") return "";
68964
+ return record.topic || record.id || "";
68965
+ }
68966
+ /************************************************************
68916
68967
  * A write of `__graphs__` did not land. The engine
68917
68968
  * (C_G6_NODES_TREE) took it for granted when it left -- nothing
68918
68969
  * answers it when it lands -- so it is told, and its next Save
68919
68970
  * writes the topic again. Other topics are the operator's data:
68920
68971
  * the answer's error says what happened to them.
68921
68972
  ************************************************************/
68922
- function graphs_write_refused(gobj, topic_name, record) {
68973
+ function graphs_write_refused(gobj, topic_name, topic) {
68923
68974
  let priv = gobj.priv;
68924
68975
  if (topic_name !== "__graphs__") return;
68925
- let topic = record && typeof record === "object" ? record.topic || record.id : "";
68926
68976
  if (!topic) {
68927
68977
  log_error(`${gobj_short_name(gobj)}: a refused __graphs__ write names no topic`);
68928
68978
  return;
@@ -72481,11 +72531,18 @@ function el_attrs(attrs) {
72481
72531
  return attrs;
72482
72532
  }
72483
72533
  /***************************************************************
72484
- * The body stops answering while a write is in flight.
72534
+ * The body stops answering while a write is in flight. For the
72535
+ * keyboard too: `pointer-events` stops the mouse alone, and the
72536
+ * shell's focus trap puts the focus back on the row control that
72537
+ * had it, where Enter or Delete sent an action ST_SAVING does
72538
+ * not declare. `inert` takes the whole body out of reach.
72485
72539
  ***************************************************************/
72486
72540
  function set_busy(gobj, busy) {
72487
72541
  let $body = $of(gobj, ".SCHEMA_BODY");
72488
- if ($body) $body.classList.toggle("SCHEMA_BUSY", !!busy);
72542
+ if ($body) {
72543
+ $body.classList.toggle("SCHEMA_BUSY", !!busy);
72544
+ $body.toggleAttribute("inert", !!busy);
72545
+ }
72489
72546
  }
72490
72547
  /***************************************************************
72491
72548
  * Shorthands for the three panes of the frame.
@@ -72701,7 +72758,7 @@ function request_model(gobj, keep_written) {
72701
72758
  priv.records_before = null;
72702
72759
  priv.load_round++;
72703
72760
  priv.reload_on_open = true;
72704
- priv.load_error = t("cannot reach the treedb");
72761
+ priv.load_error = "cannot reach the treedb";
72705
72762
  if (priv.model) yui_shell_show_error(yui_shell_of(gobj), "cannot reach the treedb", { t });
72706
72763
  end_load(gobj);
72707
72764
  return -1;
@@ -72912,10 +72969,13 @@ function show_notice(gobj, key, detail) {
72912
72969
  },
72913
72970
  t(key)
72914
72971
  ]));
72915
- if (detail) $notice.appendChild(createElement2([
72972
+ if (detail && detail !== key) $notice.appendChild(createElement2([
72916
72973
  "span",
72917
- { class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2" },
72918
- `${detail}`
72974
+ {
72975
+ class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2",
72976
+ i18n: `${detail}`
72977
+ },
72978
+ t(`${detail}`)
72919
72979
  ]));
72920
72980
  }
72921
72981
  /***************************************************************
@@ -75470,7 +75530,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75470
75530
  return 0;
75471
75531
  }
75472
75532
  priv.pending--;
75473
- if (result < 0) priv.load_error = comment || t("cannot load the schemas");
75533
+ if (result < 0) priv.load_error = comment || "cannot load the schemas";
75474
75534
  else if (priv.records && Array.isArray(priv.records[topic_name])) priv.records[topic_name] = Array.isArray(data) ? data : [];
75475
75535
  if (priv.pending > 0) return 0;
75476
75536
  if (priv.load_error) {
@@ -75506,9 +75566,10 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75506
75566
  }
75507
75567
  if (result < 0) return end_writes(gobj, comment || t("the treedb refused the write"));
75508
75568
  priv.save_wrote = true;
75569
+ let record = null;
75509
75570
  if (command === "delete-node") forget_record(gobj, topic_name, kw_get_str(gobj, md, "record_id", "", 0));
75510
75571
  else {
75511
- let record = is_object(data) ? data : Array.isArray(data) && is_object(data[0]) ? data[0] : null;
75572
+ record = is_object(data) ? data : Array.isArray(data) && is_object(data[0]) ? data[0] : null;
75512
75573
  if (record) patch_record(gobj, topic_name, record);
75513
75574
  else {
75514
75575
  let not_sent = priv.save_queue.length - 1;
@@ -75516,7 +75577,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75516
75577
  if (not_sent > 0) yui_shell_show_error(yui_shell_of(gobj), "the treedb did not describe a write back: the writes after it were not sent", { t });
75517
75578
  if (topic_name === T_TOPICS || topic_name === T_COLS) {
75518
75579
  let write = priv.save_queue[0];
75519
- mark_written(gobj, topic_name, write ? write.record : null);
75580
+ mark_written(gobj, topic_name, null, write ? write.record : null);
75520
75581
  }
75521
75582
  priv.reload_after_write = false;
75522
75583
  priv.save_queue = [];
@@ -75531,7 +75592,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75531
75592
  }
75532
75593
  if (topic_name === T_TOPICS || topic_name === T_COLS) {
75533
75594
  let write = priv.save_queue[0];
75534
- mark_written(gobj, topic_name, write ? write.record : null);
75595
+ mark_written(gobj, topic_name, record, write ? write.record : null);
75535
75596
  }
75536
75597
  priv.save_queue.shift();
75537
75598
  return run_next_write(gobj);
@@ -75588,25 +75649,34 @@ function late_write_done(gobj, command, topic_name, data) {
75588
75649
  ***************************************************************/
75589
75650
  function mark_written_record(gobj, topic_name, record) {
75590
75651
  let priv = gobj.priv;
75591
- if (!record) return;
75652
+ let marked = 0;
75653
+ if (!record) return 0;
75592
75654
  if (topic_name === T_TOPICS && typeof record.id === "string" && record.id) {
75593
75655
  priv.written[record.id] = true;
75594
- return;
75656
+ return 1;
75595
75657
  }
75596
- if (topic_name !== T_COLS) return;
75597
- for (let ref of parse_fkey_ref(record.topics)) priv.written[ref.id] = true;
75658
+ if (topic_name !== T_COLS) return 0;
75659
+ for (let ref of parse_fkey_ref(record.topics)) {
75660
+ priv.written[ref.id] = true;
75661
+ marked++;
75662
+ }
75663
+ return marked;
75598
75664
  }
75599
75665
  /***************************************************************
75600
75666
  * Remember which topic this write belongs to, which is what
75601
75667
  * "its version still has to move" is asked about.
75668
+ *
75669
+ * Asked of the RECORD first: the one the store answered (a new
75670
+ * topic has its id only there), then the one queued (a column
75671
+ * names its topic in its fkey). The screen is the last resort,
75672
+ * and a poor one: an import writes topics the operator is not
75673
+ * on, and from the topics screen there is no topic on it at all.
75602
75674
  ***************************************************************/
75603
- function mark_written(gobj, topic_name, record) {
75675
+ function mark_written(gobj, topic_name, answered, queued) {
75604
75676
  let priv = gobj.priv;
75677
+ if (mark_written_record(gobj, topic_name, answered) > 0) return;
75678
+ if (mark_written_record(gobj, topic_name, queued) > 0) return;
75605
75679
  let topic = current_topic(gobj);
75606
- if (topic_name === T_TOPICS && record && record.id) {
75607
- priv.written[record.id] = true;
75608
- return;
75609
- }
75610
75680
  if (topic) priv.written[topic.id] = true;
75611
75681
  }
75612
75682
  /***************************************************************