@yuneta/gobj-ui 7.25.18 → 7.25.20

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.
@@ -10896,7 +10896,7 @@ function register_c_yui_map() {
10896
10896
  *
10897
10897
  * Graphics, Charts with uPlot
10898
10898
  *
10899
- * Copyright (c) 2025, ArtGins.
10899
+ * Copyright (c) 2025-2026, ArtGins.
10900
10900
  * All Rights Reserved.
10901
10901
  ***********************************************************************/
10902
10902
  /***************************************************************
@@ -11146,8 +11146,8 @@ function ac_add_serie(gobj, event, kw, src) {
11146
11146
  let colors = gobj_read_attr(gobj, "colors");
11147
11147
  if (!Array.isArray(colors) || !colors.length) colors = CHART_PALETTE;
11148
11148
  let color = colors[Math.max(0, idx - 1) % colors.length];
11149
- kw_get_str(gobj, kw, "stroke", color.stroke, kw_flag_t.KW_CREATE);
11150
- kw_get_str(gobj, kw, "fill", color.fill, kw_flag_t.KW_CREATE);
11149
+ if (kw.stroke === void 0) kw.stroke = color.stroke;
11150
+ if (kw.fill === void 0) kw.fill = color.fill;
11151
11151
  uplot.addSeries(kw);
11152
11152
  return 0;
11153
11153
  }
@@ -14738,10 +14738,18 @@ function push_collapsed_row(ctx, value, segments, depth, key, rows) {
14738
14738
  },
14739
14739
  is_pending ? "loading" : "click to load"
14740
14740
  ]);
14741
- if (err) children.push([
14741
+ if (err && err.is_key) children.push([
14742
+ "span",
14743
+ {
14744
+ class: "JSON_STUB_ERR has-text-danger is-size-7 ml-2",
14745
+ "data-i18n": err.error
14746
+ },
14747
+ t(err.error)
14748
+ ]);
14749
+ else if (err) children.push([
14742
14750
  "span",
14743
14751
  { class: "JSON_STUB_ERR has-text-danger is-size-7 ml-2" },
14744
- String(err)
14752
+ String(err.error)
14745
14753
  ]);
14746
14754
  let attrs = {
14747
14755
  class: "JSON_ROW JSON_COLLAPSED" + (is_pending ? " is-pending" : ""),
@@ -14958,14 +14966,32 @@ function ac_subtree_loaded(gobj, event, kw, src) {
14958
14966
  return 0;
14959
14967
  }
14960
14968
  /************************************************************
14961
- * EV_SUBTREE_ERROR { path, error } — mark the failed branch
14969
+ * EV_SUBTREE_ERROR { path, error | i18n, by_design }
14970
+ * — mark the branch that was not loaded.
14971
+ *
14972
+ * `error` is text, drawn as it came (a backend's comment).
14973
+ * `i18n` is a KEY, drawn as t(key) with data-i18n so the stub
14974
+ * changes language with the app (a text translated by the host
14975
+ * stays in the language it was sent in). The name is the one a
14976
+ * locale check scans for (`i18n: "<key>"`), so the key of every
14977
+ * host stays visible to it.
14978
+ * `by_design`: the host answers that it CANNOT read a subtree
14979
+ * (a form holds records, a pasted pad has no backend) -- that is
14980
+ * not a failure, and it is logged as a warning. Anything else is
14981
+ * a load that failed, logged as an error.
14962
14982
  ************************************************************/
14963
14983
  function ac_subtree_error(gobj, event, kw, src) {
14964
14984
  let priv = gobj.priv;
14965
14985
  let path = kw.path || "";
14986
+ let is_key = !!kw.i18n;
14987
+ let error = kw.i18n || kw.error || "error";
14966
14988
  priv.pending.delete(path);
14967
- priv.errors.set(path, kw.error || "error");
14968
- log_error(`${GCLASS_NAME$13}: subtree load failed at '${path}': ${kw.error || ""}`);
14989
+ priv.errors.set(path, {
14990
+ error,
14991
+ is_key
14992
+ });
14993
+ if (kw.by_design) log_warning(`${GCLASS_NAME$13}: subtree not loaded at '${path}': ${error}`);
14994
+ else log_error(`${GCLASS_NAME$13}: subtree load failed at '${path}': ${error}`);
14969
14995
  render_view$1(gobj);
14970
14996
  return 0;
14971
14997
  }
@@ -16015,10 +16041,11 @@ function ac_hide_diff(gobj, event, kw, src) {
16015
16041
  * document has no backend to ask for it: the SOURCE truncated
16016
16042
  * it, and that is the answer, to the viewer that asked.
16017
16043
  ***************************************************************/
16018
- function ac_expand_path(gobj, event, kw, src) {
16044
+ function ac_expand_path$1(gobj, event, kw, src) {
16019
16045
  gobj_send_event(src, "EV_SUBTREE_ERROR", {
16020
16046
  path: kw && kw.path || "",
16021
- error: t("collapsed in the source")
16047
+ i18n: "collapsed in the source",
16048
+ by_design: true
16022
16049
  }, gobj);
16023
16050
  return 0;
16024
16051
  }
@@ -16076,7 +16103,7 @@ function create_gclass$12(gclass_name) {
16076
16103
  ],
16077
16104
  [
16078
16105
  "EV_EXPAND_PATH",
16079
- ac_expand_path,
16106
+ ac_expand_path$1,
16080
16107
  null
16081
16108
  ]
16082
16109
  ]],
@@ -16113,7 +16140,7 @@ function create_gclass$12(gclass_name) {
16113
16140
  ],
16114
16141
  [
16115
16142
  "EV_EXPAND_PATH",
16116
- ac_expand_path,
16143
+ ac_expand_path$1,
16117
16144
  null
16118
16145
  ]
16119
16146
  ]],
@@ -24932,7 +24959,7 @@ function answered_record$1(data) {
24932
24959
  /************************************************************
24933
24960
  * Command to remote service
24934
24961
  ************************************************************/
24935
- function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, form_write) {
24962
+ function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, form_write, created) {
24936
24963
  let command = "update-node";
24937
24964
  let gobj_remote_yuno = gobj_read_pointer_attr(gobj, "gobj_remote_yuno");
24938
24965
  let kw = {
@@ -24944,6 +24971,7 @@ function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, fo
24944
24971
  };
24945
24972
  kw.__md_command__ = { topic_name };
24946
24973
  if (form_write) kw.__md_command__.form_write = form_write;
24974
+ if (created) kw.__md_command__.created = true;
24947
24975
  let ret = gobj_command(gobj_remote_yuno, command, kw, gobj);
24948
24976
  if (ret) {
24949
24977
  log_error(ret);
@@ -24967,7 +24995,11 @@ function treedb_delete_node$1(gobj, treedb_name, topic_name, record, options) {
24967
24995
  };
24968
24996
  kw.__md_command__ = { topic_name };
24969
24997
  let ret = gobj_command(gobj_remote_yuno, command, kw, gobj);
24970
- if (ret) log_error(ret);
24998
+ if (ret) {
24999
+ log_error(ret);
25000
+ return -1;
25001
+ }
25002
+ return 0;
24971
25003
  }
24972
25004
  /************************************************************
24973
25005
  * True on a phone-width viewport (Bulma's mobile breakpoint).
@@ -25109,7 +25141,7 @@ function request_print_tranger$1(gobj, path) {
25109
25141
  let jv = priv.json_gobj;
25110
25142
  if (path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
25111
25143
  path,
25112
- error: t("no session")
25144
+ i18n: "no session"
25113
25145
  }, gobj);
25114
25146
  return;
25115
25147
  }
@@ -25250,17 +25282,18 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
25250
25282
  break;
25251
25283
  case "create-node":
25252
25284
  case "update-node":
25285
+ case "delete-node":
25253
25286
  gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
25254
25287
  treedb_name: gobj_read_str_attr(gobj, "treedb_name"),
25255
25288
  topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
25256
25289
  record: answered_record$1(data),
25257
- created: command === "create-node",
25290
+ created: command === "create-node" || kw_command.created === true,
25258
25291
  command
25259
25292
  });
25293
+ if (command === "delete-node") break;
25260
25294
  settle_form_write(gobj.priv.form_writes, kw_get_int(gobj, kw_command, "form_write", 0, 0), kw_get_str(gobj, kw_command, "topic_name", "", 0));
25261
25295
  answer_form_write(gobj, get_gobj_formtable(gobj, kw_get_str(gobj, kw_command, "topic_name", "", 0)), kw_get_int(gobj, kw_command, "form_write", 0, 0), true);
25262
25296
  break;
25263
- case "delete-node": break;
25264
25297
  default: log_error(`${gobj_short_name(gobj)} Command unknown: ${command}`);
25265
25298
  }
25266
25299
  return 0;
@@ -25528,7 +25561,7 @@ function ac_create_record(gobj, event, kw, src) {
25528
25561
  create: true,
25529
25562
  create_only: true,
25530
25563
  autolink: true
25531
- }, form_write) < 0) {
25564
+ }, form_write, true) < 0) {
25532
25565
  answer_form_write(gobj, src, form_write, false);
25533
25566
  return -1;
25534
25567
  }
@@ -29446,6 +29479,25 @@ function refuse_if_readonly$2(gobj, event) {
29446
29479
  * From external, at the beginning, load all topic data
29447
29480
  ************************************************************/
29448
29481
  /***************************************************************
29482
+ * One of MY json viewers (the schema, a cell, the table's records)
29483
+ * met a `__collapsed__` sentinel and the reader opened it. The
29484
+ * viewer never fetches: it asks its host, and waits on "loading"
29485
+ * until it is answered. This view holds records, not a store it can
29486
+ * read a subtree of -- the lazy drill is `print-tranger`, which the
29487
+ * TOPICS view runs for its own viewer, over a whole tranger -- so the
29488
+ * answer is that it cannot, and the viewer shows that on the stub
29489
+ * instead of loading for ever.
29490
+ ***************************************************************/
29491
+ function ac_expand_path(gobj, event, kw, src) {
29492
+ let path = kw && kw.path || "";
29493
+ gobj_send_event(src, "EV_SUBTREE_ERROR", {
29494
+ path,
29495
+ i18n: "this part cannot be loaded here",
29496
+ by_design: true
29497
+ }, gobj);
29498
+ return 0;
29499
+ }
29500
+ /***************************************************************
29449
29501
  * The language changed (the shell publishes it after the app switched).
29450
29502
  *
29451
29503
  * The column headers carry their key (col_label's title formatter emits a
@@ -30739,6 +30791,11 @@ function create_gclass$5(gclass_name) {
30739
30791
  ac_show_schema,
30740
30792
  null
30741
30793
  ],
30794
+ [
30795
+ "EV_EXPAND_PATH",
30796
+ ac_expand_path,
30797
+ null
30798
+ ],
30742
30799
  [
30743
30800
  "EV_CELL_EDITED",
30744
30801
  ac_cell_edited,
@@ -30854,6 +30911,7 @@ function create_gclass$5(gclass_name) {
30854
30911
  ["EV_CHANGE_LOCALE", 0],
30855
30912
  ["EV_REFRESH", 0],
30856
30913
  ["EV_SHOW_SCHEMA", 0],
30914
+ ["EV_EXPAND_PATH", 0],
30857
30915
  ["EV_CELL_EDITED", 0],
30858
30916
  ["EV_REPULL_PAGE", 0],
30859
30917
  ["EV_PAGE_LOADED", 0],
@@ -59259,6 +59317,7 @@ var PRIVATE_DATA$4 = {
59259
59317
  _graph_properties: {},
59260
59318
  _saved_graph_properties: {},
59261
59319
  _graphs_writes_owed: {},
59320
+ _graphs_load: 0,
59262
59321
  yet_showed: false,
59263
59322
  edit_mode: false,
59264
59323
  operation_mode: null,
@@ -60968,7 +61027,8 @@ function save_topic_graph_properties(gobj, topic_name) {
60968
61027
  list_dict: true,
60969
61028
  autolink: false,
60970
61029
  create: true
60971
- }
61030
+ },
61031
+ graphs_load: priv._graphs_load
60972
61032
  };
60973
61033
  gobj_publish_event(gobj, "EV_UPDATE_NODE", kw_update);
60974
61034
  }
@@ -65749,6 +65809,7 @@ function ac_clear_data(gobj, event, kw, src) {
65749
65809
  hide_create_popover(gobj);
65750
65810
  gobj_write_attr(gobj, "records", {});
65751
65811
  priv._graphs_writes_owed = {};
65812
+ priv._graphs_load++;
65752
65813
  priv._nodes_total = 0;
65753
65814
  priv._nodes_placed = 0;
65754
65815
  priv._fold_model = null;
@@ -65850,12 +65911,19 @@ function ac_save_graph(gobj, event, kw, src) {
65850
65911
  /************************************************************
65851
65912
  * The host says a write of `__graphs__` did not land: the
65852
65913
  * backend refused it, the transport refused it on the way out,
65853
- * or there was no session to send it on. kw: {topic}
65914
+ * or there was no session to send it on.
65915
+ * kw: {topic, graphs_load}
65854
65916
  *
65855
65917
  * save_topic_graph_properties() recorded it as saved before it
65856
65918
  * left. Forgotten now, and the Save is lit again: the next Save
65857
65919
  * writes the topic again. Before, the view believed the backend
65858
65920
  * held the arrangement, and the next Save had nothing to write.
65921
+ *
65922
+ * `graphs_load` is the load the write was sent from, echoed by the
65923
+ * host. A refusal of an EARLIER load is about an arrangement the
65924
+ * reload replaced with what the backend holds: applied, it lit the
65925
+ * Save over nothing changed (before gobj-ui 7.25.20). A host that
65926
+ * echoes no load is taken at its word, as before.
65859
65927
  ************************************************************/
65860
65928
  function ac_graphs_write_refused(gobj, event, kw, src) {
65861
65929
  let priv = gobj.priv;
@@ -65864,6 +65932,10 @@ function ac_graphs_write_refused(gobj, event, kw, src) {
65864
65932
  log_error(`${gobj_short_name(gobj)}: ${event} names no topic`);
65865
65933
  return -1;
65866
65934
  }
65935
+ if (kw.graphs_load !== void 0 && kw.graphs_load !== priv._graphs_load) {
65936
+ log_warning(`${gobj_short_name(gobj)}: a refused write of the arrangement of '${topic_name}' belongs to an earlier load: ignored`);
65937
+ return 0;
65938
+ }
65867
65939
  log_warning(`${gobj_short_name(gobj)}: the arrangement of '${topic_name}' was not saved: the next Save writes it again`);
65868
65940
  forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
65869
65941
  priv._graphs_writes_owed[topic_name] = true;
@@ -65871,6 +65943,22 @@ function ac_graphs_write_refused(gobj, event, kw, src) {
65871
65943
  return 0;
65872
65944
  }
65873
65945
  /************************************************************
65946
+ * An echo of a `__graphs__` record is what the backend holds for
65947
+ * its topic now, and the view has just taken it as shown and as
65948
+ * saved: a write of that topic owed from an earlier refusal is
65949
+ * owed no more. Left owed, the Save stayed lit with nothing to
65950
+ * write -- the plan compares the two, and they are equal -- until
65951
+ * a reload: a refused Save #1 answered after Save #2 landed, or
65952
+ * another browser saving the topic, did exactly that.
65953
+ ************************************************************/
65954
+ function settle_owed_graphs_write(gobj, rec) {
65955
+ let priv = gobj.priv;
65956
+ let topic_name = rec && rec.topic;
65957
+ if (!topic_name || !priv._graphs_writes_owed[topic_name]) return;
65958
+ delete priv._graphs_writes_owed[topic_name];
65959
+ update_history_buttons(gobj);
65960
+ }
65961
+ /************************************************************
65874
65962
  * Node created, from subscription
65875
65963
  ************************************************************/
65876
65964
  function ac_node_created(gobj, event, kw, src) {
@@ -65881,7 +65969,8 @@ function ac_node_created(gobj, event, kw, src) {
65881
65969
  if (!priv.descs || !priv.graph) return 0;
65882
65970
  if (topic_name === "__graphs__") {
65883
65971
  priv.__graphs__.push(node);
65884
- build_graph_properties(gobj);
65972
+ apply_graphs_echo(priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node);
65973
+ settle_owed_graphs_write(gobj, node);
65885
65974
  return 0;
65886
65975
  }
65887
65976
  if (!priv.descs[topic_name]) {
@@ -65917,6 +66006,7 @@ function ac_node_updated(gobj, event, kw, src) {
65917
66006
  }
65918
66007
  if (!found) priv.__graphs__.push(node);
65919
66008
  apply_graphs_echo(priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node);
66009
+ settle_owed_graphs_write(gobj, node);
65920
66010
  return 0;
65921
66011
  }
65922
66012
  let desc = priv.descs[topic_name];
@@ -68260,7 +68350,7 @@ function request_print_tranger(gobj, path) {
68260
68350
  let jv = priv.json_gobj;
68261
68351
  if (path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
68262
68352
  path,
68263
- error: t("no session")
68353
+ i18n: "no session"
68264
68354
  }, gobj);
68265
68355
  return;
68266
68356
  }
@@ -68369,7 +68459,7 @@ function treedb_create_node(gobj, treedb_name, topic_name, record, options) {
68369
68459
  /************************************************************
68370
68460
  * Command to remote service
68371
68461
  ************************************************************/
68372
- function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68462
+ function treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load) {
68373
68463
  let priv = gobj.priv;
68374
68464
  if (!priv.gobj_remote_yuno) {
68375
68465
  log_error(`${gobj_short_name(gobj)}: No gobj_remote_yuno defined`);
@@ -68384,7 +68474,10 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68384
68474
  options: options || {}
68385
68475
  };
68386
68476
  kw.__md_command__ = { topic_name };
68387
- if (topic_name === "__graphs__") kw.__md_command__.graph_topic = graph_topic_of(record);
68477
+ if (topic_name === "__graphs__") {
68478
+ kw.__md_command__.graph_topic = graph_topic_of(record);
68479
+ if (graphs_load !== void 0) kw.__md_command__.graphs_load = graphs_load;
68480
+ }
68388
68481
  let ret = gobj_command(priv.gobj_remote_yuno, command, kw, gobj);
68389
68482
  if (ret) {
68390
68483
  log_error(ret);
@@ -68688,7 +68781,7 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68688
68781
  case "delete-node":
68689
68782
  case "link-nodes":
68690
68783
  case "unlink-nodes":
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));
68784
+ 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), kw_command ? kw_command.graphs_load : void 0);
68692
68785
  if (result >= 0 && kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
68693
68786
  let written = {
68694
68787
  treedb_name: priv.treedb_name,
@@ -68943,17 +69036,18 @@ function ac_update_node(gobj, event, kw, src) {
68943
69036
  let topic_name = kw.topic_name;
68944
69037
  let record = kw.record;
68945
69038
  let options = kw.options || {};
69039
+ let graphs_load = kw.graphs_load;
68946
69040
  if (refuse_if_readonly$1(gobj, event)) {
68947
- graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69041
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
68948
69042
  return -1;
68949
69043
  }
68950
69044
  if (topic_name === "__graphs__" && !is_connected(gobj)) {
68951
69045
  log_warning(`${gobj_short_name(gobj)}: no session, the arrangement was not sent`);
68952
- graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69046
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
68953
69047
  return -1;
68954
69048
  }
68955
- let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options);
68956
- if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69049
+ let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load);
69050
+ if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
68957
69051
  return ret;
68958
69052
  }
68959
69053
  /************************************************************
@@ -68968,9 +69062,11 @@ function graph_topic_of(record) {
68968
69062
  * (C_G6_NODES_TREE) took it for granted when it left -- nothing
68969
69063
  * answers it when it lands -- so it is told, and its next Save
68970
69064
  * writes the topic again. Other topics are the operator's data:
68971
- * the answer's error says what happened to them.
69065
+ * the answer's error says what happened to them. `graphs_load` is
69066
+ * the engine's load the write was sent from, handed back so it can
69067
+ * tell a refusal of an earlier load from one of the current.
68972
69068
  ************************************************************/
68973
- function graphs_write_refused(gobj, topic_name, topic) {
69069
+ function graphs_write_refused(gobj, topic_name, topic, graphs_load) {
68974
69070
  let priv = gobj.priv;
68975
69071
  if (topic_name !== "__graphs__") return;
68976
69072
  if (!topic) {
@@ -68978,7 +69074,9 @@ function graphs_write_refused(gobj, topic_name, topic) {
68978
69074
  return;
68979
69075
  }
68980
69076
  if (!priv.gobj_nodes_tree) return;
68981
- gobj_send_event(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", { topic }, gobj);
69077
+ let kw = { topic };
69078
+ if (graphs_load !== void 0) kw.graphs_load = graphs_load;
69079
+ gobj_send_event(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", kw, gobj);
68982
69080
  }
68983
69081
  /********************************************
68984
69082
  * Message from G6_nodes_tree
@@ -72951,8 +73049,12 @@ function current_topic(gobj) {
72951
73049
  * One line for what the whole view has to say: loading, empty,
72952
73050
  * or the reason nothing loaded. Carries its i18n key, so it
72953
73051
  * follows a language change like any other text.
73052
+ *
73053
+ * `detail` is a NAME (a treedb, a topic) shown as it is, unless
73054
+ * `detail_is_key` says it is a key: a name is not text to
73055
+ * translate, and a topic called `nodes` is not "Nodos".
72954
73056
  ***************************************************************/
72955
- function show_notice(gobj, key, detail) {
73057
+ function show_notice(gobj, key, detail, detail_is_key) {
72956
73058
  let $notice = $of(gobj, ".SCHEMA_NOTICE");
72957
73059
  if (!$notice) return;
72958
73060
  clear($notice);
@@ -72969,14 +73071,15 @@ function show_notice(gobj, key, detail) {
72969
73071
  },
72970
73072
  t(key)
72971
73073
  ]));
72972
- if (detail && detail !== key) $notice.appendChild(createElement2([
72973
- "span",
72974
- {
72975
- class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2",
72976
- i18n: `${detail}`
72977
- },
72978
- t(`${detail}`)
72979
- ]));
73074
+ if (detail && detail !== key) {
73075
+ let attrs = { class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2" };
73076
+ if (detail_is_key) attrs.i18n = `${detail}`;
73077
+ $notice.appendChild(createElement2([
73078
+ "span",
73079
+ attrs,
73080
+ detail_is_key ? t(`${detail}`) : `${detail}`
73081
+ ]));
73082
+ }
72980
73083
  }
72981
73084
  /***************************************************************
72982
73085
  * A button of the toolbar. Every one carries an icon and a
@@ -73163,7 +73266,7 @@ function render(gobj) {
73163
73266
  return;
73164
73267
  }
73165
73268
  if (state === "ST_IDLE") {
73166
- show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected", priv.load_error);
73269
+ show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected", priv.load_error, true);
73167
73270
  destroy_diagram(gobj);
73168
73271
  clear($body);
73169
73272
  return;
@@ -75164,13 +75267,18 @@ function run_next_write(gobj) {
75164
75267
  let write = priv.save_queue[0];
75165
75268
  let command = write_command(write.op);
75166
75269
  let options = write_options(write.op);
75270
+ if (!transport_in_session(gobj)) {
75271
+ log_warning(`${gobj_short_name(gobj)}: no session, '${command}' of '${write.topic_name}' was not sent`);
75272
+ priv.reload_on_open = true;
75273
+ return end_writes(gobj, "cannot reach the treedb");
75274
+ }
75167
75275
  priv.write_tag++;
75168
75276
  if (remote_command(gobj, command, {
75169
75277
  topic_name: write.topic_name,
75170
75278
  record: write.record,
75171
75279
  options,
75172
75280
  record_id: write.record.id || ""
75173
- }, { write: priv.write_tag }) < 0) return end_writes(gobj, t("cannot reach the treedb"));
75281
+ }, { write: priv.write_tag }) < 0) return end_writes(gobj, "cannot reach the treedb");
75174
75282
  return 0;
75175
75283
  }
75176
75284
  /***************************************************************
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.25.18",
3
+ "version": "7.25.20",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@rollup/plugin-terser": "^1.0.0",
32
32
  "@vitest/coverage-v8": "^4.1.10",
33
- "@yuneta/gobj-js": "^7.25.3",
33
+ "@yuneta/gobj-js": "^7.25.6",
34
34
  "vite": "^8.2.1",
35
35
  "vitest": "^4.1.10"
36
36
  },
@@ -494,6 +494,7 @@ let PRIVATE_DATA = {
494
494
  _graph_properties: {}, // topic_name → {nodes: {node_id: {x,y,size,...}}}
495
495
  _saved_graph_properties: {}, // the same, as the BACKEND has it: what a Save compares against
496
496
  _graphs_writes_owed: {}, // topic_name → true: a __graphs__ write the host said did not land
497
+ _graphs_load: 0, // the LOAD a __graphs__ write belongs to: EV_CLEAR_DATA starts another
497
498
  yet_showed: false,
498
499
  edit_mode: false,
499
500
  operation_mode: null,
@@ -3301,7 +3302,10 @@ function save_topic_graph_properties(gobj, topic_name)
3301
3302
  list_dict: true,
3302
3303
  autolink: false,
3303
3304
  create: true
3304
- }
3305
+ },
3306
+ /* Echoed back with a refusal (EV_GRAPHS_WRITE_REFUSED), so
3307
+ * one answered after a reload is told from one of THIS load. */
3308
+ graphs_load: priv._graphs_load
3305
3309
  };
3306
3310
  gobj_publish_event(gobj, "EV_UPDATE_NODE", kw_update);
3307
3311
  }
@@ -10372,6 +10376,9 @@ function ac_clear_data(gobj, event, kw, src)
10372
10376
  * reload brings what the backend holds, and that is what the view
10373
10377
  * will hold. */
10374
10378
  priv._graphs_writes_owed = {};
10379
+ /* And a write still on its way belongs to it: its refusal, if
10380
+ * one comes, is about an arrangement this view no longer holds. */
10381
+ priv._graphs_load++;
10375
10382
 
10376
10383
  /* The node counters belong to the data that is going away: a refresh
10377
10384
  * that kept them would decide the layout on the previous treedb. */
@@ -10537,12 +10544,19 @@ function ac_save_graph(gobj, event, kw, src)
10537
10544
  /************************************************************
10538
10545
  * The host says a write of `__graphs__` did not land: the
10539
10546
  * backend refused it, the transport refused it on the way out,
10540
- * or there was no session to send it on. kw: {topic}
10547
+ * or there was no session to send it on.
10548
+ * kw: {topic, graphs_load}
10541
10549
  *
10542
10550
  * save_topic_graph_properties() recorded it as saved before it
10543
10551
  * left. Forgotten now, and the Save is lit again: the next Save
10544
10552
  * writes the topic again. Before, the view believed the backend
10545
10553
  * held the arrangement, and the next Save had nothing to write.
10554
+ *
10555
+ * `graphs_load` is the load the write was sent from, echoed by the
10556
+ * host. A refusal of an EARLIER load is about an arrangement the
10557
+ * reload replaced with what the backend holds: applied, it lit the
10558
+ * Save over nothing changed (before gobj-ui 7.25.20). A host that
10559
+ * echoes no load is taken at its word, as before.
10546
10560
  ************************************************************/
10547
10561
  function ac_graphs_write_refused(gobj, event, kw, src)
10548
10562
  {
@@ -10553,6 +10567,11 @@ function ac_graphs_write_refused(gobj, event, kw, src)
10553
10567
  log_error(`${gobj_short_name(gobj)}: ${event} names no topic`);
10554
10568
  return -1;
10555
10569
  }
10570
+ if(kw.graphs_load !== undefined && kw.graphs_load !== priv._graphs_load) {
10571
+ log_warning(`${gobj_short_name(gobj)}: a refused write of the arrangement ` +
10572
+ `of '${topic_name}' belongs to an earlier load: ignored`);
10573
+ return 0;
10574
+ }
10556
10575
  log_warning(`${gobj_short_name(gobj)}: the arrangement of '${topic_name}' ` +
10557
10576
  `was not saved: the next Save writes it again`);
10558
10577
  forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
@@ -10565,6 +10584,27 @@ function ac_graphs_write_refused(gobj, event, kw, src)
10565
10584
  return 0;
10566
10585
  }
10567
10586
 
10587
+ /************************************************************
10588
+ * An echo of a `__graphs__` record is what the backend holds for
10589
+ * its topic now, and the view has just taken it as shown and as
10590
+ * saved: a write of that topic owed from an earlier refusal is
10591
+ * owed no more. Left owed, the Save stayed lit with nothing to
10592
+ * write -- the plan compares the two, and they are equal -- until
10593
+ * a reload: a refused Save #1 answered after Save #2 landed, or
10594
+ * another browser saving the topic, did exactly that.
10595
+ ************************************************************/
10596
+ function settle_owed_graphs_write(gobj, rec)
10597
+ {
10598
+ let priv = gobj.priv;
10599
+ let topic_name = rec && rec.topic;
10600
+
10601
+ if(!topic_name || !priv._graphs_writes_owed[topic_name]) {
10602
+ return;
10603
+ }
10604
+ delete priv._graphs_writes_owed[topic_name];
10605
+ update_history_buttons(gobj);
10606
+ }
10607
+
10568
10608
  /************************************************************
10569
10609
  * Node created, from subscription
10570
10610
  ************************************************************/
@@ -10579,10 +10619,17 @@ function ac_node_created(gobj, event, kw, src)
10579
10619
  return 0;
10580
10620
  }
10581
10621
 
10582
- // Handle __graphs__ creation: update _graph_properties
10622
+ /* The first record of a topic's arrangement. Only THAT topic
10623
+ * moves, as for an update echo: build_graph_properties() took
10624
+ * every topic's saved copy from the live objects the view edits
10625
+ * in place, so an unsaved change of another topic counted as
10626
+ * saved and its Save was lost (before gobj-ui 7.25.20). */
10583
10627
  if(topic_name === '__graphs__') {
10584
10628
  priv.__graphs__.push(node);
10585
- build_graph_properties(gobj);
10629
+ apply_graphs_echo(
10630
+ priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node
10631
+ );
10632
+ settle_owed_graphs_write(gobj, node);
10586
10633
  return 0;
10587
10634
  }
10588
10635
 
@@ -10649,6 +10696,7 @@ function ac_node_updated(gobj, event, kw, src)
10649
10696
  apply_graphs_echo(
10650
10697
  priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node
10651
10698
  );
10699
+ settle_owed_graphs_write(gobj, node);
10652
10700
  return 0;
10653
10701
  }
10654
10702
 
package/src/c_yui_json.js CHANGED
@@ -64,6 +64,7 @@ import {
64
64
  gclass_create,
65
65
  gclass_find_by_name,
66
66
  log_error,
67
+ log_warning,
67
68
  gobj_read_pointer_attr,
68
69
  gobj_parent,
69
70
  gobj_subscribe_event,
@@ -898,8 +899,12 @@ function push_collapsed_row(ctx, value, segments, depth, key, rows)
898
899
  children.push(['span', {class: 'JSON_STUB_HINT is-size-7 ml-2',
899
900
  'data-i18n': is_pending ? 'loading' : 'click to load'},
900
901
  is_pending ? 'loading' : 'click to load']);
901
- if(err) {
902
- children.push(['span', {class: 'JSON_STUB_ERR has-text-danger is-size-7 ml-2'}, String(err)]);
902
+ if(err && err.is_key) {
903
+ children.push(['span', {class: 'JSON_STUB_ERR has-text-danger is-size-7 ml-2',
904
+ 'data-i18n': err.error}, t(err.error)]);
905
+ } else if(err) {
906
+ children.push(['span', {class: 'JSON_STUB_ERR has-text-danger is-size-7 ml-2'},
907
+ String(err.error)]);
903
908
  }
904
909
 
905
910
  let attrs = {
@@ -1117,17 +1122,35 @@ function ac_subtree_loaded(gobj, event, kw, src)
1117
1122
  }
1118
1123
 
1119
1124
  /************************************************************
1120
- * EV_SUBTREE_ERROR { path, error } — mark the failed branch
1125
+ * EV_SUBTREE_ERROR { path, error | i18n, by_design }
1126
+ * — mark the branch that was not loaded.
1127
+ *
1128
+ * `error` is text, drawn as it came (a backend's comment).
1129
+ * `i18n` is a KEY, drawn as t(key) with data-i18n so the stub
1130
+ * changes language with the app (a text translated by the host
1131
+ * stays in the language it was sent in). The name is the one a
1132
+ * locale check scans for (`i18n: "<key>"`), so the key of every
1133
+ * host stays visible to it.
1134
+ * `by_design`: the host answers that it CANNOT read a subtree
1135
+ * (a form holds records, a pasted pad has no backend) -- that is
1136
+ * not a failure, and it is logged as a warning. Anything else is
1137
+ * a load that failed, logged as an error.
1121
1138
  ************************************************************/
1122
1139
  function ac_subtree_error(gobj, event, kw, src)
1123
1140
  {
1124
1141
  let priv = gobj.priv;
1125
1142
 
1126
1143
  let path = kw.path || "";
1144
+ let is_key = !!kw.i18n;
1145
+ let error = kw.i18n || kw.error || "error";
1127
1146
  priv.pending.delete(path);
1128
- priv.errors.set(path, kw.error || "error");
1147
+ priv.errors.set(path, {error: error, is_key: is_key});
1129
1148
 
1130
- log_error(`${GCLASS_NAME}: subtree load failed at '${path}': ${kw.error || ""}`);
1149
+ if(kw.by_design) {
1150
+ log_warning(`${GCLASS_NAME}: subtree not loaded at '${path}': ${error}`);
1151
+ } else {
1152
+ log_error(`${GCLASS_NAME}: subtree load failed at '${path}': ${error}`);
1153
+ }
1131
1154
 
1132
1155
  render_view(gobj);
1133
1156
  return 0;
@@ -798,8 +798,9 @@ function ac_hide_diff(gobj, event, kw, src)
798
798
  function ac_expand_path(gobj, event, kw, src)
799
799
  {
800
800
  gobj_send_event(src, "EV_SUBTREE_ERROR", {
801
- path: (kw && kw.path) || "",
802
- error: t("collapsed in the source")
801
+ path: (kw && kw.path) || "",
802
+ i18n: "collapsed in the source",
803
+ by_design: true
803
804
  }, gobj);
804
805
  return 0;
805
806
  }