@yuneta/gobj-ui 7.25.19 → 7.25.21

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.
@@ -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
  }
@@ -16018,7 +16044,8 @@ function ac_hide_diff(gobj, event, kw, src) {
16018
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
  }
@@ -25104,6 +25131,27 @@ function request_schema_file(gobj) {
25104
25131
  if (ret) log_error(ret);
25105
25132
  }
25106
25133
  /************************************************************
25134
+ * A drill (or the whole document) that will get no answer from the
25135
+ * backend is answered HERE, or its stub shows "loading" for the life
25136
+ * of the viewer: C_YUI_JSON ignores every click on a pending path.
25137
+ * `answer` is {i18n: <key>} or {error: <text>}.
25138
+ ************************************************************/
25139
+ function answer_print_tranger_refused$1(gobj, path, answer) {
25140
+ let jv = gobj.priv.json_gobj;
25141
+ if (!jv || !is_gobj(jv) || gobj_is_destroying(jv)) return;
25142
+ if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", Object.assign({ path }, answer), gobj);
25143
+ else yui_shell_show_error(yui_shell_of(gobj), answer.i18n || answer.error, { t });
25144
+ }
25145
+ /************************************************************
25146
+ * The text of a transport's refusal: gobj_command() answers a string,
25147
+ * a command response or a number.
25148
+ ************************************************************/
25149
+ function refusal_text$1(ret) {
25150
+ if (typeof ret === "string" && ret) return ret;
25151
+ if (ret && typeof ret === "object" && typeof ret.comment === "string" && ret.comment) return ret.comment;
25152
+ return "print-tranger failed";
25153
+ }
25154
+ /************************************************************
25107
25155
  * The whole tranger, for the raw-json viewer.
25108
25156
  ************************************************************/
25109
25157
  function request_print_tranger$1(gobj, path) {
@@ -25111,11 +25159,12 @@ function request_print_tranger$1(gobj, path) {
25111
25159
  let remote = gobj_read_pointer_attr(gobj, "gobj_remote_yuno");
25112
25160
  if (!remote) {
25113
25161
  log_error(`${gobj_short_name(gobj)}: No gobj_remote_yuno defined`);
25114
- let jv = priv.json_gobj;
25115
- if (path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
25116
- path,
25117
- error: t("no session")
25118
- }, gobj);
25162
+ answer_print_tranger_refused$1(gobj, path, { i18n: "no session" });
25163
+ return;
25164
+ }
25165
+ if (!is_connected$1(gobj)) {
25166
+ log_warning(`${gobj_short_name(gobj)}: print-tranger '${path || ""}' not asked: no session`);
25167
+ answer_print_tranger_refused$1(gobj, path, { i18n: "no session" });
25119
25168
  return;
25120
25169
  }
25121
25170
  let ret = gobj_command(remote, "print-tranger", {
@@ -25126,7 +25175,10 @@ function request_print_tranger$1(gobj, path) {
25126
25175
  path: path || "",
25127
25176
  __md_command__: { path: path || "" }
25128
25177
  }, gobj);
25129
- if (ret) log_error(ret);
25178
+ if (ret) {
25179
+ log_error(ret);
25180
+ answer_print_tranger_refused$1(gobj, path, { error: refusal_text$1(ret) });
25181
+ }
25130
25182
  }
25131
25183
  /***************************************************************
25132
25184
  * The last gate before a write leaves for the yuno. The per-topic
@@ -29465,7 +29517,8 @@ function ac_expand_path(gobj, event, kw, src) {
29465
29517
  let path = kw && kw.path || "";
29466
29518
  gobj_send_event(src, "EV_SUBTREE_ERROR", {
29467
29519
  path,
29468
- error: t("this part cannot be loaded here")
29520
+ i18n: "this part cannot be loaded here",
29521
+ by_design: true
29469
29522
  }, gobj);
29470
29523
  return 0;
29471
29524
  }
@@ -59289,6 +59342,7 @@ var PRIVATE_DATA$4 = {
59289
59342
  _graph_properties: {},
59290
59343
  _saved_graph_properties: {},
59291
59344
  _graphs_writes_owed: {},
59345
+ _graphs_load: 0,
59292
59346
  yet_showed: false,
59293
59347
  edit_mode: false,
59294
59348
  operation_mode: null,
@@ -60998,7 +61052,8 @@ function save_topic_graph_properties(gobj, topic_name) {
60998
61052
  list_dict: true,
60999
61053
  autolink: false,
61000
61054
  create: true
61001
- }
61055
+ },
61056
+ graphs_load: priv._graphs_load
61002
61057
  };
61003
61058
  gobj_publish_event(gobj, "EV_UPDATE_NODE", kw_update);
61004
61059
  }
@@ -65779,6 +65834,7 @@ function ac_clear_data(gobj, event, kw, src) {
65779
65834
  hide_create_popover(gobj);
65780
65835
  gobj_write_attr(gobj, "records", {});
65781
65836
  priv._graphs_writes_owed = {};
65837
+ priv._graphs_load++;
65782
65838
  priv._nodes_total = 0;
65783
65839
  priv._nodes_placed = 0;
65784
65840
  priv._fold_model = null;
@@ -65880,12 +65936,19 @@ function ac_save_graph(gobj, event, kw, src) {
65880
65936
  /************************************************************
65881
65937
  * The host says a write of `__graphs__` did not land: the
65882
65938
  * backend refused it, the transport refused it on the way out,
65883
- * or there was no session to send it on. kw: {topic}
65939
+ * or there was no session to send it on.
65940
+ * kw: {topic, graphs_load}
65884
65941
  *
65885
65942
  * save_topic_graph_properties() recorded it as saved before it
65886
65943
  * left. Forgotten now, and the Save is lit again: the next Save
65887
65944
  * writes the topic again. Before, the view believed the backend
65888
65945
  * held the arrangement, and the next Save had nothing to write.
65946
+ *
65947
+ * `graphs_load` is the load the write was sent from, echoed by the
65948
+ * host. A refusal of an EARLIER load is about an arrangement the
65949
+ * reload replaced with what the backend holds: applied, it lit the
65950
+ * Save over nothing changed (before gobj-ui 7.25.20). A host that
65951
+ * echoes no load is taken at its word, as before.
65889
65952
  ************************************************************/
65890
65953
  function ac_graphs_write_refused(gobj, event, kw, src) {
65891
65954
  let priv = gobj.priv;
@@ -65894,6 +65957,10 @@ function ac_graphs_write_refused(gobj, event, kw, src) {
65894
65957
  log_error(`${gobj_short_name(gobj)}: ${event} names no topic`);
65895
65958
  return -1;
65896
65959
  }
65960
+ if (kw.graphs_load !== void 0 && kw.graphs_load !== priv._graphs_load) {
65961
+ log_warning(`${gobj_short_name(gobj)}: a refused write of the arrangement of '${topic_name}' belongs to an earlier load: ignored`);
65962
+ return 0;
65963
+ }
65897
65964
  log_warning(`${gobj_short_name(gobj)}: the arrangement of '${topic_name}' was not saved: the next Save writes it again`);
65898
65965
  forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
65899
65966
  priv._graphs_writes_owed[topic_name] = true;
@@ -65927,7 +65994,7 @@ function ac_node_created(gobj, event, kw, src) {
65927
65994
  if (!priv.descs || !priv.graph) return 0;
65928
65995
  if (topic_name === "__graphs__") {
65929
65996
  priv.__graphs__.push(node);
65930
- build_graph_properties(gobj);
65997
+ apply_graphs_echo(priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node);
65931
65998
  settle_owed_graphs_write(gobj, node);
65932
65999
  return 0;
65933
66000
  }
@@ -68297,6 +68364,27 @@ function close_json_viewer(gobj) {
68297
68364
  }
68298
68365
  }
68299
68366
  /************************************************************
68367
+ * A drill (or the whole document) that will get no answer from the
68368
+ * backend is answered HERE, or its stub shows "loading" for the life
68369
+ * of the viewer: C_YUI_JSON ignores every click on a pending path.
68370
+ * `answer` is {i18n: <key>} or {error: <text>}.
68371
+ ************************************************************/
68372
+ function answer_print_tranger_refused(gobj, path, answer) {
68373
+ let jv = gobj.priv.json_gobj;
68374
+ if (!jv || !is_gobj(jv) || gobj_is_destroying(jv)) return;
68375
+ if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", Object.assign({ path }, answer), gobj);
68376
+ else yui_shell_show_error(yui_shell_of(gobj), answer.i18n || answer.error, { t });
68377
+ }
68378
+ /************************************************************
68379
+ * The text of a transport's refusal: gobj_command() answers a string,
68380
+ * a command response or a number.
68381
+ ************************************************************/
68382
+ function refusal_text(ret) {
68383
+ if (typeof ret === "string" && ret) return ret;
68384
+ if (ret && typeof ret === "object" && typeof ret.comment === "string" && ret.comment) return ret.comment;
68385
+ return "print-tranger failed";
68386
+ }
68387
+ /************************************************************
68300
68388
  * Fetch the treedb's raw tranger (or one subtree when `path` is set) as
68301
68389
  * bounded, drillable JSON. Collapsed at 100 so a huge tranger stays a
68302
68390
  * small payload of `__collapsed__` stubs the viewer expands on demand.
@@ -68305,11 +68393,12 @@ function request_print_tranger(gobj, path) {
68305
68393
  let priv = gobj.priv;
68306
68394
  if (!priv.gobj_remote_yuno) {
68307
68395
  log_error(`${gobj_short_name(gobj)}: No gobj_remote_yuno defined`);
68308
- let jv = priv.json_gobj;
68309
- if (path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
68310
- path,
68311
- error: t("no session")
68312
- }, gobj);
68396
+ answer_print_tranger_refused(gobj, path, { i18n: "no session" });
68397
+ return;
68398
+ }
68399
+ if (!is_connected(gobj)) {
68400
+ log_warning(`${gobj_short_name(gobj)}: print-tranger '${path || ""}' not asked: no session`);
68401
+ answer_print_tranger_refused(gobj, path, { i18n: "no session" });
68313
68402
  return;
68314
68403
  }
68315
68404
  let ret = gobj_command(priv.gobj_remote_yuno, "print-tranger", {
@@ -68320,7 +68409,10 @@ function request_print_tranger(gobj, path) {
68320
68409
  path: path || "",
68321
68410
  __md_command__: { path: path || "" }
68322
68411
  }, gobj);
68323
- if (ret) log_error(ret);
68412
+ if (ret) {
68413
+ log_error(ret);
68414
+ answer_print_tranger_refused(gobj, path, { error: refusal_text(ret) });
68415
+ }
68324
68416
  }
68325
68417
  /************************************************************
68326
68418
  * Populate layout dropdown from child's available layouts
@@ -68417,7 +68509,7 @@ function treedb_create_node(gobj, treedb_name, topic_name, record, options) {
68417
68509
  /************************************************************
68418
68510
  * Command to remote service
68419
68511
  ************************************************************/
68420
- function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68512
+ function treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load) {
68421
68513
  let priv = gobj.priv;
68422
68514
  if (!priv.gobj_remote_yuno) {
68423
68515
  log_error(`${gobj_short_name(gobj)}: No gobj_remote_yuno defined`);
@@ -68432,7 +68524,10 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options) {
68432
68524
  options: options || {}
68433
68525
  };
68434
68526
  kw.__md_command__ = { topic_name };
68435
- if (topic_name === "__graphs__") kw.__md_command__.graph_topic = graph_topic_of(record);
68527
+ if (topic_name === "__graphs__") {
68528
+ kw.__md_command__.graph_topic = graph_topic_of(record);
68529
+ if (graphs_load !== void 0) kw.__md_command__.graphs_load = graphs_load;
68530
+ }
68436
68531
  let ret = gobj_command(priv.gobj_remote_yuno, command, kw, gobj);
68437
68532
  if (ret) {
68438
68533
  log_error(ret);
@@ -68736,7 +68831,7 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
68736
68831
  case "delete-node":
68737
68832
  case "link-nodes":
68738
68833
  case "unlink-nodes":
68739
- 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));
68834
+ 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);
68740
68835
  if (result >= 0 && kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
68741
68836
  let written = {
68742
68837
  treedb_name: priv.treedb_name,
@@ -68991,17 +69086,18 @@ function ac_update_node(gobj, event, kw, src) {
68991
69086
  let topic_name = kw.topic_name;
68992
69087
  let record = kw.record;
68993
69088
  let options = kw.options || {};
69089
+ let graphs_load = kw.graphs_load;
68994
69090
  if (refuse_if_readonly$1(gobj, event)) {
68995
- graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69091
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
68996
69092
  return -1;
68997
69093
  }
68998
69094
  if (topic_name === "__graphs__" && !is_connected(gobj)) {
68999
69095
  log_warning(`${gobj_short_name(gobj)}: no session, the arrangement was not sent`);
69000
- graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69096
+ graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
69001
69097
  return -1;
69002
69098
  }
69003
- let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options);
69004
- if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record));
69099
+ let ret = treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load);
69100
+ if (ret < 0) graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
69005
69101
  return ret;
69006
69102
  }
69007
69103
  /************************************************************
@@ -69016,9 +69112,11 @@ function graph_topic_of(record) {
69016
69112
  * (C_G6_NODES_TREE) took it for granted when it left -- nothing
69017
69113
  * answers it when it lands -- so it is told, and its next Save
69018
69114
  * writes the topic again. Other topics are the operator's data:
69019
- * the answer's error says what happened to them.
69115
+ * the answer's error says what happened to them. `graphs_load` is
69116
+ * the engine's load the write was sent from, handed back so it can
69117
+ * tell a refusal of an earlier load from one of the current.
69020
69118
  ************************************************************/
69021
- function graphs_write_refused(gobj, topic_name, topic) {
69119
+ function graphs_write_refused(gobj, topic_name, topic, graphs_load) {
69022
69120
  let priv = gobj.priv;
69023
69121
  if (topic_name !== "__graphs__") return;
69024
69122
  if (!topic) {
@@ -69026,7 +69124,9 @@ function graphs_write_refused(gobj, topic_name, topic) {
69026
69124
  return;
69027
69125
  }
69028
69126
  if (!priv.gobj_nodes_tree) return;
69029
- gobj_send_event(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", { topic }, gobj);
69127
+ let kw = { topic };
69128
+ if (graphs_load !== void 0) kw.graphs_load = graphs_load;
69129
+ gobj_send_event(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", kw, gobj);
69030
69130
  }
69031
69131
  /********************************************
69032
69132
  * Message from G6_nodes_tree
@@ -75217,13 +75317,18 @@ function run_next_write(gobj) {
75217
75317
  let write = priv.save_queue[0];
75218
75318
  let command = write_command(write.op);
75219
75319
  let options = write_options(write.op);
75320
+ if (!transport_in_session(gobj)) {
75321
+ log_warning(`${gobj_short_name(gobj)}: no session, '${command}' of '${write.topic_name}' was not sent`);
75322
+ priv.reload_on_open = true;
75323
+ return end_writes(gobj, "cannot reach the treedb");
75324
+ }
75220
75325
  priv.write_tag++;
75221
75326
  if (remote_command(gobj, command, {
75222
75327
  topic_name: write.topic_name,
75223
75328
  record: write.record,
75224
75329
  options,
75225
75330
  record_id: write.record.id || ""
75226
- }, { write: priv.write_tag }) < 0) return end_writes(gobj, t("cannot reach the treedb"));
75331
+ }, { write: priv.write_tag }) < 0) return end_writes(gobj, "cannot reach the treedb");
75227
75332
  return 0;
75228
75333
  }
75229
75334
  /***************************************************************
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.25.19",
3
+ "version": "7.25.21",
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.5",
33
+ "@yuneta/gobj-js": "^7.25.7",
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);
@@ -10600,10 +10619,16 @@ function ac_node_created(gobj, event, kw, src)
10600
10619
  return 0;
10601
10620
  }
10602
10621
 
10603
- // 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). */
10604
10627
  if(topic_name === '__graphs__') {
10605
10628
  priv.__graphs__.push(node);
10606
- build_graph_properties(gobj);
10629
+ apply_graphs_echo(
10630
+ priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node
10631
+ );
10607
10632
  settle_owed_graphs_write(gobj, node);
10608
10633
  return 0;
10609
10634
  }
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
  }
@@ -2820,6 +2820,19 @@ function run_next_write(gobj)
2820
2820
  let command = write_command(write.op);
2821
2821
  let options = write_options(write.op);
2822
2822
 
2823
+ /* Out of session a write is not sent, and ends here. A direct
2824
+ * C_IEVENT_CLI answers a command out of session with null -- the
2825
+ * answer of a request that LEFT -- so without this the editor
2826
+ * waited in ST_SAVING, inert, for an answer that never comes
2827
+ * (before gobj-ui 7.25.20). What the queue wrote before is not
2828
+ * known to the next session: it reads the model again. */
2829
+ if(!transport_in_session(gobj)) {
2830
+ log_warning(`${gobj_short_name(gobj)}: no session, '${command}' ` +
2831
+ `of '${write.topic_name}' was not sent`);
2832
+ priv.reload_on_open = true;
2833
+ return end_writes(gobj, "cannot reach the treedb");
2834
+ }
2835
+
2823
2836
  priv.write_tag++;
2824
2837
  if(remote_command(gobj, command, {
2825
2838
  topic_name: write.topic_name,
@@ -2827,7 +2840,7 @@ function run_next_write(gobj)
2827
2840
  options: options,
2828
2841
  record_id: write.record.id || ""
2829
2842
  }, {write: priv.write_tag}) < 0) {
2830
- return end_writes(gobj, t("cannot reach the treedb"));
2843
+ return end_writes(gobj, "cannot reach the treedb"); /* a key: the toast translates it */
2831
2844
  }
2832
2845
  return 0;
2833
2846
  }