@yuneta/gobj-ui 7.25.7 → 7.25.8

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
@@ -2177,9 +2177,24 @@ else.
2177
2177
  gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host); // waits
2178
2178
  // ... the three `nodes` answers land -> ST_TOPICS of `db`
2179
2179
  ```
2180
+ - **The loading screen is drawn** (7.25.8): the body is cleared, the drawing
2181
+ destroyed and every toolbar button disabled, as in `ST_SAVING`, because
2182
+ `ST_LOADING` declares none of a screen's actions. A **dialog** left open is
2183
+ the one thing still reachable: its Save (`EV_SAVE_COLUMN`, `EV_SAVE_TOPIC`),
2184
+ a confirmation (`EV_CONFIRMED`), an orphan delete or an import is refused
2185
+ with `the schemas are loading: try again when they are in` (an i18n key the
2186
+ consumer's locales carry) and the dialog stays open with what was typed, so
2187
+ the same Save works once the load is in. A write that ends with an error --
2188
+ a drop included -- still applies a position that waited, as a load does.
2189
+ Out of session a load is not asked at all (no *"not in session"* errors);
2190
+ the reconnect loads.
2180
2191
  - A write this view gave up on (a drop, a deadline) whose answer still says it
2181
2192
  was DONE makes the editor read the model again (at once, after the write in
2182
- flight, or on the reconnect). Through a routing adapter that settled the
2193
+ flight, or on the reconnect), and it is a write for the host too: it
2194
+ publishes `EV_RECORD_WRITTEN` and marks the topic of the record answered.
2195
+ Only the host's `EV_REFRESH` (a Save sends it) forgets what this session
2196
+ wrote; the reloads the editor asks itself (the reconnect's, a late write's)
2197
+ keep the draft chips. Through a routing adapter that settled the
2183
2198
  write on its OWN deadline the late answer does not reach the view -- the
2184
2199
  adapter echoes a node event instead, which this view does not hear -- and
2185
2200
  Refresh is what reads the store.
@@ -2614,6 +2629,22 @@ live region it is both read and announced when it changes.
2614
2629
  > long time and nothing ever rendered it; the claim is gone. Implement it there
2615
2630
  > the day a menu entry needs one.
2616
2631
 
2632
+ ### Toasts — `yui_shell_show_info` / `_warning` / `_error`
2633
+
2634
+ `yui_shell_show_error(shell, "key", {t, timeout})` shows a non-blocking toast
2635
+ (5000 ms by default, `timeout: 0` = until closed) and returns `{close()}`. A
2636
+ string message of the same kind as a toast still on screen is not stacked
2637
+ under it -- there is ONE toast per message -- but each call is a caller of its
2638
+ own: it gets its own handle and its own time, and the toast goes when every
2639
+ caller has closed its handle or timed out (7.25.8). The ✕ closes it for all.
2640
+
2641
+ ```js
2642
+ const a = yui_shell_show_error(shell, "the connection dropped", {t}); // 5 s
2643
+ const b = yui_shell_show_error(shell, "the connection dropped", {t, timeout: 0}); // same toast
2644
+ a.close(); // still shown: b holds it
2645
+ b.close(); // gone
2646
+ ```
2647
+
2617
2648
  ### Modals — `yui_shell_show_modal` and the `before_close` veto
2618
2649
 
2619
2650
  `yui_shell_show_modal(shell, $box, opts)` is the standard popup: pass
@@ -3027,12 +3027,20 @@ function maybe_apply_translator($node, opts) {
3027
3027
  * Returns `{ close() }` so callers can dismiss programmatically.
3028
3028
  *
3029
3029
  * ONE toast per message on screen: a string message of the same
3030
- * kind as a toast still showing is not stacked under it -- that
3031
- * toast is kept, its time starts again, and its handle is
3032
- * returned. One close of a transport settles every request it
3033
- * cut, each view shows its failure, and the operator got a
3034
- * column of identical "the connection dropped". A message built
3035
- * of nodes is never merged: its text is not a key.
3030
+ * kind as a toast still showing is not stacked under it. One
3031
+ * close of a transport settles every request it cut, each view
3032
+ * shows its failure, and the operator got a column of identical
3033
+ * "the connection dropped". A message built of nodes is never
3034
+ * merged: its text is not a key.
3035
+ *
3036
+ * A repeat is still a CALLER of its own: it gets its own handle
3037
+ * and its own time, and the toast stays while any caller holds
3038
+ * it -- until every handle is closed or has timed out. Handed the
3039
+ * first caller's handle and timer, a repeat asking `timeout: 0`
3040
+ * was dismissed on the first one's timer, and closing one
3041
+ * caller's handle closed a toast another was still showing
3042
+ * (third independent review). The operator's ✕ closes it for
3043
+ * everybody.
3036
3044
  ***************************************************************/
3037
3045
  function show_notification(shell, kind, message, opts) {
3038
3046
  let $layer = notification_layer(shell);
@@ -3040,12 +3048,10 @@ function show_notification(shell, kind, message, opts) {
3040
3048
  (0, _yuneta_gobj_js.log_warning)("yui_shell_show_*: shell has no notification layer");
3041
3049
  return { close: () => {} };
3042
3050
  }
3051
+ let timeout = opts && opts.timeout != null ? opts.timeout : 5e3;
3043
3052
  let key = typeof message === "string" ? `${kind}\u0000${message}` : null;
3044
3053
  if (key !== null) {
3045
- for (let $shown of Array.from($layer.children)) if ($shown.__toast_key__ === key && $shown.__toast__) {
3046
- $shown.__toast__.restart();
3047
- return $shown.__toast__.handle;
3048
- }
3054
+ for (let $shown of Array.from($layer.children)) if ($shown.__toast_key__ === key && $shown.__toast__) return $shown.__toast__.hold(timeout);
3049
3055
  }
3050
3056
  let p_attrs = typeof message === "string" ? { i18n: message } : {};
3051
3057
  let $note = (0, _yuneta_gobj_js.createElement2)([
@@ -3069,35 +3075,38 @@ function show_notification(shell, kind, message, opts) {
3069
3075
  ]);
3070
3076
  $layer.appendChild($note);
3071
3077
  maybe_apply_translator($note, opts);
3072
- let timeout_id = null;
3078
+ let holders = /* @__PURE__ */ new Set();
3073
3079
  let closed = false;
3074
- let close = function() {
3080
+ let close_toast = function() {
3075
3081
  if (closed) return;
3076
3082
  closed = true;
3077
- if (timeout_id) {
3078
- clearTimeout(timeout_id);
3079
- timeout_id = null;
3083
+ for (let holder of holders) if (holder.timeout_id) {
3084
+ clearTimeout(holder.timeout_id);
3085
+ holder.timeout_id = null;
3080
3086
  }
3087
+ holders.clear();
3081
3088
  if ($note.parentNode) $note.parentNode.removeChild($note);
3082
3089
  };
3083
- let $del = $note.querySelector(".delete");
3084
- if ($del) $del.addEventListener("click", close);
3085
- let timeout = opts && opts.timeout != null ? opts.timeout : 5e3;
3086
- let restart = function() {
3087
- if (timeout_id) {
3088
- clearTimeout(timeout_id);
3089
- timeout_id = null;
3090
- }
3091
- if (timeout > 0) timeout_id = setTimeout(close, timeout);
3090
+ let hold = function(ms) {
3091
+ let holder = { timeout_id: null };
3092
+ let release = function() {
3093
+ if (!holders.has(holder)) return;
3094
+ if (holder.timeout_id) {
3095
+ clearTimeout(holder.timeout_id);
3096
+ holder.timeout_id = null;
3097
+ }
3098
+ holders.delete(holder);
3099
+ if (holders.size === 0) close_toast();
3100
+ };
3101
+ holders.add(holder);
3102
+ if (ms > 0) holder.timeout_id = setTimeout(release, ms);
3103
+ return { close: release };
3092
3104
  };
3093
- restart();
3094
- let handle = { close };
3105
+ let $del = $note.querySelector(".delete");
3106
+ if ($del) $del.addEventListener("click", close_toast);
3095
3107
  $note.__toast_key__ = key;
3096
- $note.__toast__ = {
3097
- handle,
3098
- restart
3099
- };
3100
- return handle;
3108
+ $note.__toast__ = { hold };
3109
+ return hold(timeout);
3101
3110
  }
3102
3111
  function yui_shell_show_info(shell, message, opts) {
3103
3112
  return show_notification(shell, "info", message, opts);
@@ -72168,6 +72177,7 @@ var PRIVATE_DATA$1 = {
72168
72177
  pending: 0,
72169
72178
  load_error: "",
72170
72179
  load_round: 0,
72180
+ load_keeps_written: false,
72171
72181
  treedb_id: "",
72172
72182
  topic_name: "",
72173
72183
  diagram: false,
@@ -72346,10 +72356,28 @@ function refuse_if_readonly(gobj, event) {
72346
72356
  * schema set of the yuno is here: everything below — the
72347
72357
  * drawing, the validation, the export — is computed from these
72348
72358
  * and asks the backend nothing more.
72359
+ *
72360
+ * THE LOADING SCREEN IS DRAWN, not assumed. ST_LOADING declares
72361
+ * none of the actions of a screen, so the screen it replaces goes
72362
+ * and the toolbar stops answering: left up, a click on a card, on
72363
+ * Back or on the drawing answered "Event NOT DEFINED in state
72364
+ * ST_LOADING" and was lost (third independent review). A dialog
72365
+ * left open is answered in ST_LOADING instead (ac_wait_for_the_load).
72366
+ *
72367
+ * `keep_written`: a load the host did not ask for -- the reconnect,
72368
+ * a write that turned out done -- keeps what this session wrote.
72369
+ * Only the host's EV_REFRESH (a Save sends it) forgets it.
72370
+ *
72371
+ * A load that cannot leave WHOLE did not happen (out of session it
72372
+ * is not even asked): the records the model was built on are put
72373
+ * back (emptied, the next write patched them into a model with no
72374
+ * treedb), the round is spent so a request that did leave is
72375
+ * stale, and the session back asks again.
72349
72376
  ***************************************************************/
72350
- function request_model(gobj) {
72377
+ function request_model(gobj, keep_written) {
72351
72378
  let priv = gobj.priv;
72352
72379
  if (!(0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "gobj_remote_yuno")) return -1;
72380
+ let previous = priv.records;
72353
72381
  priv.records = {
72354
72382
  treedbs: [],
72355
72383
  topics: [],
@@ -72358,22 +72386,31 @@ function request_model(gobj) {
72358
72386
  priv.load_error = "";
72359
72387
  priv.pending = 0;
72360
72388
  priv.load_round++;
72389
+ priv.load_keeps_written = !!keep_written;
72361
72390
  (0, _yuneta_gobj_js.gobj_change_state)(gobj, "ST_LOADING");
72362
- show_notice(gobj, "loading the schemas");
72363
- for (let topic_name of [
72364
- T_TREEDBS,
72365
- T_TOPICS,
72366
- T_COLS
72367
- ]) if (remote_command(gobj, "nodes", {
72368
- topic_name,
72369
- options: { list_dict: true }
72370
- }, { round: priv.load_round }) === 0) priv.pending++;
72371
- if (priv.pending === 0) {
72391
+ set_busy(gobj, false);
72392
+ render(gobj);
72393
+ let left = 0;
72394
+ if (transport_in_session(gobj)) {
72395
+ for (let topic_name of [
72396
+ T_TREEDBS,
72397
+ T_TOPICS,
72398
+ T_COLS
72399
+ ]) if (remote_command(gobj, "nodes", {
72400
+ topic_name,
72401
+ options: { list_dict: true }
72402
+ }, { round: priv.load_round }) === 0) left++;
72403
+ }
72404
+ if (left < 3) {
72405
+ priv.records = previous;
72406
+ priv.load_round++;
72407
+ priv.reload_on_open = true;
72372
72408
  priv.load_error = (0, i18next.t)("cannot reach the treedb");
72373
- (0, _yuneta_gobj_js.gobj_change_state)(gobj, "ST_IDLE");
72374
- show_notice(gobj, "cannot reach the treedb");
72409
+ if (priv.model) yui_shell_show_error(yui_shell_of(gobj), "cannot reach the treedb", { t: i18next.t });
72410
+ end_load(gobj);
72375
72411
  return -1;
72376
72412
  }
72413
+ priv.pending = left;
72377
72414
  return 0;
72378
72415
  }
72379
72416
  /***************************************************************
@@ -72436,15 +72473,19 @@ function build_model(gobj) {
72436
72473
  * From here on, "did this session change it?" has an answer.
72437
72474
  *
72438
72475
  * Called ONLY when records arrive from the store: that is what a
72439
- * measurement of "changed since" can be measured against. Both
72440
- * halves reset together -- a baseline without its `written` is
72441
- * the same false positive from the other side, since every
72442
- * version equals a baseline just taken.
72476
+ * measurement of "changed since" can be measured against.
72477
+ *
72478
+ * `written` is forgotten only by a load the HOST asked for (its
72479
+ * EV_REFRESH, which a Save sends). Since M36 a write moves no
72480
+ * version, so `written` is the draft chip of what this session
72481
+ * wrote and no longer half of a version measurement: a reload of
72482
+ * the reconnect, or of a write that turned out done, forgot the
72483
+ * chips of writes the host had not been told about.
72443
72484
  ***************************************************************/
72444
72485
  function start_measuring(gobj) {
72445
72486
  let priv = gobj.priv;
72446
72487
  priv.baseline = {};
72447
- priv.written = {};
72488
+ if (!priv.load_keeps_written) priv.written = {};
72448
72489
  priv.host_ids = {};
72449
72490
  if (!priv.model) return;
72450
72491
  for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
@@ -72626,8 +72667,10 @@ function render_toolbar(gobj) {
72626
72667
  let state = (0, _yuneta_gobj_js.gobj_current_state)(gobj);
72627
72668
  let readonly = is_readonly(gobj);
72628
72669
  let has_model = !!priv.model;
72670
+ let on_a_treedb = state !== "ST_IDLE" && state !== "ST_EMPTY";
72671
+ let in_flight = state === "ST_SAVING" || state === "ST_LOADING";
72629
72672
  let $left = [];
72630
- if (priv.treedb_id) $left.push([
72673
+ if (priv.treedb_id && on_a_treedb) $left.push([
72631
72674
  "button",
72632
72675
  {
72633
72676
  class: "SCHEMA_BACK button",
@@ -72700,17 +72743,17 @@ function render_toolbar(gobj) {
72700
72743
  $trail
72701
72744
  ]);
72702
72745
  let $right = [];
72703
- if (priv.treedb_id && has_model) {
72746
+ if (priv.treedb_id && has_model && on_a_treedb) {
72704
72747
  $right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-compass-drafting", priv.diagram ? "topics" : "diagram", "EV_TOGGLE_DIAGRAM", false));
72705
72748
  $right.push(toolbar_button("SCHEMA_VALIDATE_BTN", "yi-square-check", "check", "EV_VALIDATE", false));
72706
72749
  $right.push(toolbar_button("SCHEMA_EXPORT_BTN", "yi-download", "export", "EV_EXPORT", false));
72707
72750
  if (!readonly) $right.push(toolbar_button("SCHEMA_IMPORT_BTN", "yi-upload", "import", "EV_IMPORT", false));
72708
72751
  }
72709
- if (priv.topic_name && !readonly && !priv.diagram) {
72752
+ if (priv.topic_name && !readonly && !priv.diagram && on_a_treedb) {
72710
72753
  if (undo_order_writes(gobj, current_topic(gobj)).length > 0) $right.push(toolbar_button("SCHEMA_UNDO_ORDER_BTN", "yi-arrow-rotate-left", "undo the order", "EV_UNDO_ORDER", false));
72711
72754
  $right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus", "new column", "EV_ADD_COLUMN", false));
72712
72755
  }
72713
- if (priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram) $right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus", "new topic", "EV_ADD_TOPIC", false));
72756
+ if (priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram && on_a_treedb) $right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus", "new topic", "EV_ADD_TOPIC", false));
72714
72757
  $right.push(toolbar_button("SCHEMA_REFRESH_BTN", "yi-arrows-rotate", "refresh", "EV_REFRESH", state === "ST_LOADING"));
72715
72758
  let $bar = (0, _yuneta_gobj_js.createElement2)([
72716
72759
  "div",
@@ -72736,7 +72779,7 @@ function render_toolbar(gobj) {
72736
72779
  ]);
72737
72780
  for (let $button of $bar.querySelectorAll("button")) {
72738
72781
  $button.__gobj__ = gobj;
72739
- if (state === "ST_SAVING") $button.disabled = true;
72782
+ if (in_flight) $button.disabled = true;
72740
72783
  }
72741
72784
  $toolbar.appendChild($bar);
72742
72785
  (0, _yuneta_gobj_js.refresh_language)($toolbar, i18next.t);
@@ -72754,16 +72797,19 @@ function render(gobj) {
72754
72797
  render_toolbar(gobj);
72755
72798
  if (state === "ST_LOADING") {
72756
72799
  show_notice(gobj, "loading the schemas");
72800
+ destroy_diagram(gobj);
72757
72801
  clear($body);
72758
72802
  return;
72759
72803
  }
72760
72804
  if (state === "ST_IDLE") {
72761
72805
  show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected", priv.load_error);
72806
+ destroy_diagram(gobj);
72762
72807
  clear($body);
72763
72808
  return;
72764
72809
  }
72765
72810
  if (state === "ST_EMPTY") {
72766
72811
  show_notice(gobj, "this yuno stores no schema");
72812
+ destroy_diagram(gobj);
72767
72813
  clear($body);
72768
72814
  return;
72769
72815
  }
@@ -73452,12 +73498,16 @@ function render_diagram(gobj) {
73452
73498
  let priv = gobj.priv;
73453
73499
  let $body = $of(gobj, ".SCHEMA_BODY");
73454
73500
  let treedb = current_treedb(gobj);
73455
- if (!$body || !treedb) {
73501
+ if (!$body) return;
73502
+ if (!treedb) {
73503
+ destroy_diagram(gobj);
73504
+ clear($body);
73456
73505
  show_notice(gobj, "that treedb is not here any more", priv.treedb_id);
73457
73506
  return;
73458
73507
  }
73459
73508
  if (!(0, _yuneta_gobj_js.gclass_find_by_name)("C_YUI_TREEDB_SCHEMA")) {
73460
73509
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: C_YUI_TREEDB_SCHEMA not registered by the app`);
73510
+ clear($body);
73461
73511
  show_notice(gobj, "the schema drawing is not available");
73462
73512
  return;
73463
73513
  }
@@ -74727,26 +74777,21 @@ function end_load(gobj) {
74727
74777
  ***************************************************************/
74728
74778
  function end_writes(gobj, error) {
74729
74779
  let priv = gobj.priv;
74780
+ let seg = priv.pending_seg;
74730
74781
  priv.save_queue = [];
74782
+ priv.pending_seg = null;
74731
74783
  (0, _yuneta_gobj_js.gobj_change_state)(gobj, priv.save_return || "ST_TREEDBS");
74732
74784
  priv.save_return = "";
74733
74785
  show_notice(gobj, "");
74734
74786
  set_busy(gobj, false);
74735
- if (priv.pending_seg !== null) {
74736
- let seg = priv.pending_seg;
74737
- priv.pending_seg = null;
74738
- if (!error) {
74739
- apply_seg(gobj, seg);
74740
- if (priv.save_wrote) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
74741
- priv.save_wrote = false;
74742
- return reload_if_owed(gobj);
74743
- }
74744
- }
74745
74787
  if (error) yui_shell_show_error(yui_shell_of(gobj), error, { t: i18next.t });
74746
74788
  if (priv.save_wrote) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
74747
74789
  priv.save_wrote = false;
74748
- render(gobj);
74749
- publish_check(gobj);
74790
+ if (seg !== null) apply_seg(gobj, seg);
74791
+ else {
74792
+ render(gobj);
74793
+ publish_check(gobj);
74794
+ }
74750
74795
  reload_if_owed(gobj);
74751
74796
  return error ? -1 : 0;
74752
74797
  }
@@ -74763,7 +74808,7 @@ function reload_if_owed(gobj) {
74763
74808
  priv.reload_on_open = true;
74764
74809
  return 0;
74765
74810
  }
74766
- return request_model(gobj);
74811
+ return request_model(gobj, true);
74767
74812
  }
74768
74813
  /***************************************************************
74769
74814
  * The record a column form produces.
@@ -74829,6 +74874,7 @@ function topic_record(gobj, treedb, topic, values) {
74829
74874
  ***************************************************************/
74830
74875
  function go(gobj, treedb_id, topic_name, diagram, mirror) {
74831
74876
  let priv = gobj.priv;
74877
+ priv.pending_seg = null;
74832
74878
  priv.treedb_id = treedb_id || "";
74833
74879
  priv.topic_name = topic_name || "";
74834
74880
  priv.diagram = !!diagram;
@@ -74945,7 +74991,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74945
74991
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' of '${topic_name}' answered for a write that is over (result ${result}): ignored`);
74946
74992
  return 0;
74947
74993
  }
74948
- return late_write_done(gobj, command, topic_name);
74994
+ return late_write_done(gobj, command, topic_name, data);
74949
74995
  }
74950
74996
  if (result < 0) return end_writes(gobj, comment || (0, i18next.t)("the treedb refused the write"));
74951
74997
  priv.save_wrote = true;
@@ -74955,8 +75001,15 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74955
75001
  if (record) patch_record(gobj, topic_name, record);
74956
75002
  else {
74957
75003
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' answered no record`);
75004
+ if (topic_name === T_TOPICS || topic_name === T_COLS) {
75005
+ let write = priv.save_queue[0];
75006
+ mark_written(gobj, topic_name, write ? write.record : null);
75007
+ }
74958
75008
  priv.save_queue = [];
74959
- request_model(gobj);
75009
+ priv.save_return = "";
75010
+ priv.save_wrote = false;
75011
+ (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
75012
+ request_model(gobj, true);
74960
75013
  return 0;
74961
75014
  }
74962
75015
  }
@@ -74983,11 +75036,18 @@ function ac_mt_command_answer(gobj, event, kw, src) {
74983
75036
  * the view was answered once, as failed -- and echoes the node
74984
75037
  * event instead, which this view does not hear: there, Refresh is
74985
75038
  * what reads the store.
75039
+ *
75040
+ * It is a WRITE like any other for the host (EV_RECORD_WRITTEN):
75041
+ * the host was told nothing when it was given up, and it owns the
75042
+ * Save that publishes it. The reload keeps what this session wrote
75043
+ * (request_model()), and the record it answered marks its topic.
74986
75044
  ***************************************************************/
74987
- function late_write_done(gobj, command, topic_name) {
75045
+ function late_write_done(gobj, command, topic_name, data) {
74988
75046
  let priv = gobj.priv;
74989
75047
  let state = (0, _yuneta_gobj_js.gobj_current_state)(gobj);
74990
75048
  (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: '${command}' of '${topic_name}' was given up, and it was DONE: the model is read again`);
75049
+ mark_written_record(gobj, topic_name, (0, _yuneta_gobj_js.is_object)(data) ? data : Array.isArray(data) && (0, _yuneta_gobj_js.is_object)(data[0]) ? data[0] : null);
75050
+ (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", { treedb_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name") });
74991
75051
  if (state === "ST_LOADING") return 0;
74992
75052
  if (state === "ST_SAVING") {
74993
75053
  priv.reload_after_write = true;
@@ -74997,7 +75057,27 @@ function late_write_done(gobj, command, topic_name) {
74997
75057
  priv.reload_on_open = true;
74998
75058
  return 0;
74999
75059
  }
75000
- return request_model(gobj);
75060
+ return request_model(gobj, true);
75061
+ }
75062
+ /***************************************************************
75063
+ * The topic a record written belongs to, read off the RECORD and
75064
+ * not off the screen: a write answered late may be about a topic
75065
+ * the operator has left. A column names its topic in its fkey
75066
+ * (`topics^<topic id>^cols`); a topic is its own id.
75067
+ ***************************************************************/
75068
+ function mark_written_record(gobj, topic_name, record) {
75069
+ let priv = gobj.priv;
75070
+ if (!record) return;
75071
+ if (topic_name === T_TOPICS && typeof record.id === "string" && record.id) {
75072
+ priv.written[record.id] = true;
75073
+ return;
75074
+ }
75075
+ if (topic_name !== T_COLS) return;
75076
+ let refs = Array.isArray(record.topics) ? record.topics : typeof record.topics === "string" ? [record.topics] : [];
75077
+ for (let ref of refs) {
75078
+ let parts = String(ref).split("^");
75079
+ if (parts.length >= 2 && parts[1]) priv.written[parts[1]] = true;
75080
+ }
75001
75081
  }
75002
75082
  /***************************************************************
75003
75083
  * Remember which topic this write belongs to, which is what
@@ -75056,7 +75136,7 @@ function ac_transport_state(gobj, event, kw, src) {
75056
75136
  }
75057
75137
  if ((!priv.model || priv.reload_on_open) && state !== "ST_LOADING" && state !== "ST_SAVING") {
75058
75138
  priv.reload_on_open = false;
75059
- return request_model(gobj);
75139
+ return request_model(gobj, true);
75060
75140
  }
75061
75141
  render_toolbar(gobj);
75062
75142
  return 0;
@@ -75071,7 +75151,25 @@ function ac_language_changed$1(gobj, event, kw, src) {
75071
75151
  return 0;
75072
75152
  }
75073
75153
  function ac_refresh$1(gobj, event, kw, src) {
75074
- return request_model(gobj);
75154
+ return request_model(gobj, false);
75155
+ }
75156
+ /***************************************************************
75157
+ * What a DIALOG sends while the model is being read again: the
75158
+ * Save of a form left open when the reload started, a
75159
+ * confirmation, an import. The loading screen took the rest of
75160
+ * the view away (request_model()); a dialog is the one thing the
75161
+ * operator can still reach, and what it asks is computed against
75162
+ * a model the answers in the air are about to replace.
75163
+ *
75164
+ * Refused, and SAID -- and the dialog stays open with what was
75165
+ * typed, so the same Save works once the load is in. Undeclared,
75166
+ * it answered "Event NOT DEFINED in state ST_LOADING" and the
75167
+ * edit was gone with no word (third independent review).
75168
+ ***************************************************************/
75169
+ function ac_wait_for_the_load(gobj, event, kw, src) {
75170
+ (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, the schemas are loading`);
75171
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas are loading: try again when they are in", { t: i18next.t });
75172
+ return -1;
75075
75173
  }
75076
75174
  /***************************************************************
75077
75175
  * {drafts: {treedb_name: [topic names]}} -- the host says which
@@ -75619,7 +75717,38 @@ function create_gclass$1(gclass_name) {
75619
75717
  ];
75620
75718
  const states = [
75621
75719
  ["ST_IDLE", COMMON.slice()],
75622
- ["ST_LOADING", COMMON.slice()],
75720
+ ["ST_LOADING", COMMON.concat([
75721
+ [
75722
+ "EV_SAVE_COLUMN",
75723
+ ac_wait_for_the_load,
75724
+ null
75725
+ ],
75726
+ [
75727
+ "EV_SAVE_TOPIC",
75728
+ ac_wait_for_the_load,
75729
+ null
75730
+ ],
75731
+ [
75732
+ "EV_DELETE_ORPHAN",
75733
+ ac_wait_for_the_load,
75734
+ null
75735
+ ],
75736
+ [
75737
+ "EV_PREVIEW_IMPORT",
75738
+ ac_wait_for_the_load,
75739
+ null
75740
+ ],
75741
+ [
75742
+ "EV_APPLY_IMPORT",
75743
+ ac_wait_for_the_load,
75744
+ null
75745
+ ],
75746
+ [
75747
+ "EV_CONFIRMED",
75748
+ ac_wait_for_the_load,
75749
+ null
75750
+ ]
75751
+ ])],
75623
75752
  ["ST_EMPTY", COMMON.slice()],
75624
75753
  ["ST_TREEDBS", COMMON.concat([
75625
75754
  [