@yuneta/gobj-ui 7.25.15 → 7.25.16

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.
@@ -3432,8 +3432,28 @@ function build_dialog(shell, message, buttons, opts) {
3432
3432
  if ($primary) $primary.focus();
3433
3433
  });
3434
3434
  }
3435
+ /***************************************************************
3436
+ * The labels a confirmation shows when the caller names none.
3437
+ *
3438
+ * They are i18n KEYS, lower-case like every key: a button
3439
+ * translates its label with the app's t() -- its text, its title
3440
+ * and its aria-label. Until 7.25.16 they were "OK", "Yes", "No",
3441
+ * "Delete" and "Cancel", keys that no locale carries, so a dialog
3442
+ * that took the defaults read in English in every language.
3443
+ *
3444
+ * Written as `i18n:` entries so the validate-locales script of a
3445
+ * consumer finds them when it scans this module: an app that
3446
+ * mounts these dialogs must define the five keys.
3447
+ ***************************************************************/
3448
+ var DEFAULT_LABEL = Object.freeze({
3449
+ ok: Object.freeze({ i18n: "ok" }),
3450
+ yes: Object.freeze({ i18n: "yes" }),
3451
+ no: Object.freeze({ i18n: "no" }),
3452
+ delete: Object.freeze({ i18n: "delete" }),
3453
+ cancel: Object.freeze({ i18n: "cancel" })
3454
+ });
3435
3455
  function yui_shell_confirm_ok(shell, message, opts) {
3436
- let label = opts && opts.ok_label || "OK";
3456
+ let label = opts && opts.ok_label || DEFAULT_LABEL.ok.i18n;
3437
3457
  if (!opts || !opts.type) opts = Object.assign({}, opts, { type: "success" });
3438
3458
  return build_dialog(shell, message, [{
3439
3459
  label,
@@ -3442,8 +3462,8 @@ function yui_shell_confirm_ok(shell, message, opts) {
3442
3462
  }], opts).then(() => void 0);
3443
3463
  }
3444
3464
  function yui_shell_confirm_yesno(shell, message, opts) {
3445
- let yes_label = opts && opts.yes_label || "Yes";
3446
- let no_label = opts && opts.no_label || "No";
3465
+ let yes_label = opts && opts.yes_label || DEFAULT_LABEL.yes.i18n;
3466
+ let no_label = opts && opts.no_label || DEFAULT_LABEL.no.i18n;
3447
3467
  return build_dialog(shell, message, [{
3448
3468
  label: yes_label,
3449
3469
  value: "yes",
@@ -3468,8 +3488,8 @@ function yui_shell_confirm_yesno(shell, message, opts) {
3468
3488
  * pressed.
3469
3489
  ***************************************************************/
3470
3490
  function yui_shell_confirm_danger(shell, message, opts) {
3471
- let confirm_label = opts && opts.confirm_label || "Delete";
3472
- let cancel_label = opts && opts.cancel_label || "Cancel";
3491
+ let confirm_label = opts && opts.confirm_label || DEFAULT_LABEL.delete.i18n;
3492
+ let cancel_label = opts && opts.cancel_label || DEFAULT_LABEL.cancel.i18n;
3473
3493
  if (!opts || !opts.type) opts = Object.assign({}, opts, { type: "danger" });
3474
3494
  return build_dialog(shell, message, [{
3475
3495
  label: confirm_label,
@@ -3481,9 +3501,9 @@ function yui_shell_confirm_danger(shell, message, opts) {
3481
3501
  }], opts).then((v) => v === "confirm");
3482
3502
  }
3483
3503
  function yui_shell_confirm_yesnocancel(shell, message, opts) {
3484
- let yes_label = opts && opts.yes_label || "Yes";
3485
- let no_label = opts && opts.no_label || "No";
3486
- let cancel_label = opts && opts.cancel_label || "Cancel";
3504
+ let yes_label = opts && opts.yes_label || DEFAULT_LABEL.yes.i18n;
3505
+ let no_label = opts && opts.no_label || DEFAULT_LABEL.no.i18n;
3506
+ let cancel_label = opts && opts.cancel_label || DEFAULT_LABEL.cancel.i18n;
3487
3507
  return build_dialog(shell, message, [
3488
3508
  {
3489
3509
  label: yes_label,
@@ -59227,6 +59247,7 @@ var PRIVATE_DATA$4 = {
59227
59247
  __graphs__: [],
59228
59248
  _graph_properties: {},
59229
59249
  _saved_graph_properties: {},
59250
+ _graphs_writes_owed: {},
59230
59251
  yet_showed: false,
59231
59252
  edit_mode: false,
59232
59253
  operation_mode: null,
@@ -60922,6 +60943,7 @@ function save_topic_graph_properties(gobj, topic_name) {
60922
60943
  if (!topic_arrangement_changed(properties, priv._saved_graph_properties[topic_name])) return;
60923
60944
  properties.__origin__ = gobj_read_str_attr(gobj_yuno(), "node_uuid");
60924
60945
  priv._saved_graph_properties[topic_name] = json_deep_copy(properties);
60946
+ delete priv._graphs_writes_owed[topic_name];
60925
60947
  let kw_update = {
60926
60948
  treedb_name: priv.treedb_name,
60927
60949
  topic_name: "__graphs__",
@@ -61003,8 +61025,13 @@ function update_history_buttons(gobj) {
61003
61025
  } else {
61004
61026
  disableElements($container, ".EV_HISTORY_UNDO");
61005
61027
  set_active_state($container, ".EV_HISTORY_UNDO", false);
61006
- disableElements($container, ".EV_SAVE_GRAPH");
61007
- set_submit_state($container, ".EV_SAVE_GRAPH", false);
61028
+ if (graphs_write_is_owed(gobj)) {
61029
+ enableElements($container, ".EV_SAVE_GRAPH");
61030
+ set_submit_state($container, ".EV_SAVE_GRAPH", true);
61031
+ } else {
61032
+ disableElements($container, ".EV_SAVE_GRAPH");
61033
+ set_submit_state($container, ".EV_SAVE_GRAPH", false);
61034
+ }
61008
61035
  }
61009
61036
  } else {
61010
61037
  disableElements($container, ".EV_HISTORY_REDO");
@@ -61961,6 +61988,15 @@ function run_preview_undo(gobj, key) {
61961
61988
  log_error(`${gobj_short_name(gobj)}: cannot undo the preview: ${e}`);
61962
61989
  }
61963
61990
  }
61991
+ /************************************************************
61992
+ * Is a `__graphs__` write owed? The host said one did not land
61993
+ * (EV_GRAPHS_WRITE_REFUSED), and no Save has written that topic
61994
+ * since. The Save button stays lit while it is: the history
61995
+ * knows nothing of it, so `canUndo()` alone would put it out.
61996
+ ************************************************************/
61997
+ function graphs_write_is_owed(gobj) {
61998
+ return Object.keys(gobj.priv._graphs_writes_owed).length > 0;
61999
+ }
61964
62000
  function mark_graph_dirty(gobj) {
61965
62001
  let $container = gobj_read_attr(gobj, "$container");
61966
62002
  enableElements($container, ".EV_SAVE_GRAPH");
@@ -65701,6 +65737,7 @@ function ac_clear_data(gobj, event, kw, src) {
65701
65737
  deselect_node(gobj);
65702
65738
  hide_create_popover(gobj);
65703
65739
  gobj_write_attr(gobj, "records", {});
65740
+ priv._graphs_writes_owed = {};
65704
65741
  priv._nodes_total = 0;
65705
65742
  priv._nodes_placed = 0;
65706
65743
  priv._fold_model = null;
@@ -65793,11 +65830,9 @@ function ac_save_graph(gobj, event, kw, src) {
65793
65830
  disableElements($container, ".EV_SAVE_GRAPH");
65794
65831
  set_submit_state($container, ".EV_SAVE_GRAPH", false);
65795
65832
  let history = graph_get_plugin(gobj, "history");
65796
- if (history) {
65797
- history.clear();
65798
- update_history_buttons(gobj);
65799
- }
65833
+ if (history) history.clear();
65800
65834
  save_geometry(gobj);
65835
+ update_history_buttons(gobj);
65801
65836
  }
65802
65837
  return 0;
65803
65838
  }
@@ -65820,6 +65855,7 @@ function ac_graphs_write_refused(gobj, event, kw, src) {
65820
65855
  }
65821
65856
  log_warning(`${gobj_short_name(gobj)}: the arrangement of '${topic_name}' was not saved: the next Save writes it again`);
65822
65857
  forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
65858
+ priv._graphs_writes_owed[topic_name] = true;
65823
65859
  if (priv.edit_mode) mark_graph_dirty(gobj);
65824
65860
  return 0;
65825
65861
  }
@@ -72329,6 +72365,8 @@ var PRIVATE_DATA$1 = {
72329
72365
  dropped_round: 0,
72330
72366
  dropped_write: 0,
72331
72367
  reload_after_write: false,
72368
+ refresh_after_write: false,
72369
+ reopen_form: null,
72332
72370
  pending_seg: null,
72333
72371
  import_plan: null,
72334
72372
  import_pane: null,
@@ -72534,14 +72572,61 @@ function owes_reload(gobj) {
72534
72572
  let state = gobj_current_state(gobj);
72535
72573
  return gobj.priv.reload_on_open && transport_in_session(gobj) && state !== "ST_LOADING" && state !== "ST_SAVING";
72536
72574
  }
72537
- function reload_first(gobj, event) {
72575
+ function reload_first(gobj, event, reopen) {
72576
+ let priv = gobj.priv;
72538
72577
  if (!owes_reload(gobj)) return false;
72539
- gobj.priv.reload_on_open = false;
72578
+ priv.reload_on_open = false;
72579
+ if (reopen && priv.dialog) {
72580
+ priv.reopen_form = Object.assign(json_deep_copy(reopen), {
72581
+ dialog: priv.dialog,
72582
+ treedb_id: priv.treedb_id
72583
+ });
72584
+ log_warning(`${gobj_short_name(gobj)}: ${event} refused, the schemas shown may be out of date: they are read again first, and the form opens again on them with its changes`);
72585
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas shown may be out of date: they are read again, and the form opens again on them with your changes", { t });
72586
+ request_model(gobj, true);
72587
+ return true;
72588
+ }
72540
72589
  log_warning(`${gobj_short_name(gobj)}: ${event} refused, the schemas shown may be out of date: they are read again first`);
72541
72590
  yui_shell_show_error(yui_shell_of(gobj), "the schemas shown may be out of date: they are read again, try again when they are in", { t });
72542
72591
  request_model(gobj, true);
72543
72592
  return true;
72544
72593
  }
72594
+ /***************************************************************
72595
+ * The form reload_first() took down, opened again on the model
72596
+ * the load just made, with the operator's changes on top.
72597
+ *
72598
+ * Only where it can mean the same thing: on the treedb it was
72599
+ * opened on, on the screen whose Save it has, and on a record
72600
+ * that is still there. Otherwise it is not opened, and that is
72601
+ * said -- a form opened on another treedb, or on nothing, would
72602
+ * save somewhere the operator never meant.
72603
+ ***************************************************************/
72604
+ function reopen_form(gobj, r) {
72605
+ let priv = gobj.priv;
72606
+ let state = gobj_current_state(gobj);
72607
+ let treedb = current_treedb(gobj);
72608
+ let topic = null;
72609
+ let col = null;
72610
+ let fits = false;
72611
+ if (treedb && r.treedb_id === priv.treedb_id) {
72612
+ if (r.form === "column" && state === "ST_COLUMNS") {
72613
+ topic = find_topic(priv.model, priv.treedb_id, r.topic);
72614
+ col = topic && !r.creating ? find_col(priv.model, priv.treedb_id, r.topic, r.col) : null;
72615
+ fits = !!topic && (r.creating || !!col);
72616
+ } else if (r.form === "topic" && state === "ST_TOPICS") {
72617
+ topic = r.creating ? null : find_topic(priv.model, priv.treedb_id, r.topic);
72618
+ fits = r.creating || !!topic;
72619
+ }
72620
+ }
72621
+ if (!fits) {
72622
+ log_warning(`${gobj_short_name(gobj)}: the ${r.form} form was not opened again: what it edited is not on the schemas the reload read (${r.treedb_id} ${r.topic}${r.col ? "." + r.col : ""}, now ${state})`);
72623
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again and what the form was editing is not there any more", { t });
72624
+ return -1;
72625
+ }
72626
+ if (r.form === "column") open_column_form(gobj, topic, col, null, r.changed);
72627
+ else open_topic_form(gobj, treedb, topic, r.changed);
72628
+ return 0;
72629
+ }
72545
72630
  function reload_after_move(gobj) {
72546
72631
  if (!owes_reload(gobj)) return 0;
72547
72632
  gobj.priv.reload_on_open = false;
@@ -72687,7 +72772,7 @@ function build_model(gobj) {
72687
72772
  * measurement of "changed since" can be measured against.
72688
72773
  *
72689
72774
  * `written` is forgotten only by an EV_REFRESH (the view's own
72690
- * Refresh button, or a host that sends one). Since M36 a write moves no
72775
+ * Refresh button, or a host that sends one). Since 7.23.196 a write moves no
72691
72776
  * version, so `written` is the draft chip of what this session
72692
72777
  * wrote and no longer half of a version measurement: a reload of
72693
72778
  * the reconnect, or of a write that turned out done, forgot the
@@ -73965,6 +74050,55 @@ function read_form($root) {
73965
74050
  return out;
73966
74051
  }
73967
74052
  /***************************************************************
74053
+ * A column form as its Save sends it: the fields and the flags.
74054
+ ***************************************************************/
74055
+ function column_form_state($form) {
74056
+ let values = read_form($form);
74057
+ values.flag = read_flags($form);
74058
+ return values;
74059
+ }
74060
+ /***************************************************************
74061
+ * The fields the operator CHANGED: those whose value is not the
74062
+ * one the form was opened with.
74063
+ ***************************************************************/
74064
+ function form_changes(initial, values) {
74065
+ let changed = {};
74066
+ for (let name of Object.keys(values)) if (JSON.stringify(values[name]) !== JSON.stringify(initial[name])) changed[name] = values[name];
74067
+ return changed;
74068
+ }
74069
+ /***************************************************************
74070
+ * Put changes back into a form just built (reopen_form()). The
74071
+ * flags are drawn again for the type the changes leave, holding
74072
+ * the flags they name, so none of them is hidden by the type.
74073
+ ***************************************************************/
74074
+ function apply_form_changes(gobj, $form, changes) {
74075
+ for (let name of Object.keys(changes)) {
74076
+ if (name === "flag") continue;
74077
+ let $control = $form.querySelector(`[data-name="${name}"]`);
74078
+ if (!$control) {
74079
+ log_warning(`${gobj_short_name(gobj)}: a change of '${name}' has no field in the form opened again: not put back`);
74080
+ continue;
74081
+ }
74082
+ if ($control.type === "checkbox") $control.checked = !!changes[name];
74083
+ else $control.value = changes[name];
74084
+ }
74085
+ let $type = $form.querySelector("[data-name=\"type\"]");
74086
+ if ($type && $form.querySelector(".SCHEMA_FLAGS")) {
74087
+ let flags = Array.isArray(changes.flag) ? changes.flag : read_flags($form);
74088
+ redraw_flags(gobj, $form, $type.value, flags);
74089
+ }
74090
+ }
74091
+ /***************************************************************
74092
+ * Draw the flags of a column form again, for `type`, with
74093
+ * `current` checked.
74094
+ ***************************************************************/
74095
+ function redraw_flags(gobj, $form, type, current) {
74096
+ let $holder = $form.querySelector(".SCHEMA_FLAGS");
74097
+ let $new = createElement2(flags_field(gobj, type, current));
74098
+ $holder.parentNode.replaceChild($new, $holder);
74099
+ refresh_language($new, t);
74100
+ }
74101
+ /***************************************************************
73968
74102
  * The flags of a column, as checkboxes that say what they do.
73969
74103
  *
73970
74104
  * Grouped the way they act, greyed when they are meaningless on
@@ -74038,7 +74172,7 @@ function read_flags($root) {
74038
74172
  * Offering a field that silently does something else is worse
74039
74173
  * than not offering it.
74040
74174
  ***************************************************************/
74041
- function open_column_form(gobj, topic, col, prefill) {
74175
+ function open_column_form(gobj, topic, col, prefill, changes) {
74042
74176
  let priv = gobj.priv;
74043
74177
  let record = prefill || col && col.record || {};
74044
74178
  let creating = !col;
@@ -74142,11 +74276,7 @@ function open_column_form(gobj, topic, col, prefill) {
74142
74276
  ]);
74143
74277
  let $type = $form.querySelector("[data-name=\"type\"]");
74144
74278
  $type.addEventListener("change", () => {
74145
- let $holder = $form.querySelector(".SCHEMA_FLAGS");
74146
- let current = read_flags($form);
74147
- let $new = createElement2(flags_field(gobj, $type.value, current));
74148
- $holder.parentNode.replaceChild($new, $holder);
74149
- refresh_language($new, t);
74279
+ redraw_flags(gobj, $form, $type.value, read_flags($form));
74150
74280
  });
74151
74281
  $form.addEventListener("change", (evt) => {
74152
74282
  let $box = evt.target.closest(".SCHEMA_FLAG_BOX");
@@ -74158,15 +74288,17 @@ function open_column_form(gobj, topic, col, prefill) {
74158
74288
  evt.stopPropagation();
74159
74289
  close_dialog(gobj);
74160
74290
  });
74291
+ let initial = column_form_state($form);
74292
+ if (changes) apply_form_changes(gobj, $form, changes);
74161
74293
  $form.querySelector(".SCHEMA_COL_FORM_SAVE").addEventListener("click", (evt) => {
74162
74294
  evt.stopPropagation();
74163
- let values = read_form($form);
74164
- values.flag = read_flags($form);
74295
+ let values = column_form_state($form);
74165
74296
  gobj_send_event(gobj, "EV_SAVE_COLUMN", {
74166
74297
  topic: topic.name,
74167
74298
  col: creating ? "" : col.name,
74168
74299
  creating,
74169
74300
  values,
74301
+ changed: creating ? values : form_changes(initial, values),
74170
74302
  model_gen
74171
74303
  }, gobj);
74172
74304
  });
@@ -74186,7 +74318,7 @@ function stringify_field(value) {
74186
74318
  * actually has: a pkey naming no column is a topic that does
74187
74319
  * not open, and it is the first thing the validator looks for.
74188
74320
  ***************************************************************/
74189
- function open_topic_form(gobj, treedb, topic) {
74321
+ function open_topic_form(gobj, treedb, topic, changes) {
74190
74322
  let record = topic && topic.record || {};
74191
74323
  let creating = !topic;
74192
74324
  let col_names = topic ? topic.cols.map((c) => c.name) : [];
@@ -74281,12 +74413,16 @@ function open_topic_form(gobj, treedb, topic) {
74281
74413
  evt.stopPropagation();
74282
74414
  close_dialog(gobj);
74283
74415
  });
74416
+ let initial = read_form($form);
74417
+ if (changes) apply_form_changes(gobj, $form, changes);
74284
74418
  $form.querySelector(".SCHEMA_TOPIC_FORM_SAVE").addEventListener("click", (evt) => {
74285
74419
  evt.stopPropagation();
74420
+ let values = read_form($form);
74286
74421
  gobj_send_event(gobj, "EV_SAVE_TOPIC", {
74287
74422
  topic: creating ? "" : topic.name,
74288
74423
  creating,
74289
- values: read_form($form),
74424
+ values,
74425
+ changed: creating ? values : form_changes(initial, values),
74290
74426
  model_gen
74291
74427
  }, gobj);
74292
74428
  });
@@ -75056,20 +75192,28 @@ function transport_dropped(gobj) {
75056
75192
  function end_load(gobj) {
75057
75193
  let priv = gobj.priv;
75058
75194
  let seg = priv.pending_seg;
75195
+ let reopen = priv.reopen_form;
75059
75196
  priv.pending_seg = null;
75197
+ priv.reopen_form = null;
75060
75198
  if (priv.dialog && priv.dialog_gen !== priv.model_gen) {
75061
- log_warning(`${gobj_short_name(gobj)}: a dialog was open on the schemas the load replaced: closed`);
75199
+ if (!reopen || reopen.dialog !== priv.dialog) {
75200
+ reopen = null;
75201
+ log_warning(`${gobj_short_name(gobj)}: a dialog was open on the schemas the load replaced: closed`);
75202
+ yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again: open the dialog again", { t });
75203
+ }
75062
75204
  close_dialog(gobj);
75063
- yui_shell_show_error(yui_shell_of(gobj), "the schemas were read again: open the dialog again", { t });
75064
- }
75205
+ } else reopen = null;
75206
+ let ret = 0;
75065
75207
  if (priv.model) {
75066
- if (seg !== null) return apply_seg(gobj, seg);
75067
- return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
75208
+ if (seg !== null) ret = apply_seg(gobj, seg);
75209
+ else ret = go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
75210
+ } else {
75211
+ if (seg !== null) adopt_seg(gobj, seg);
75212
+ gobj_change_state(gobj, "ST_IDLE");
75213
+ render(gobj);
75068
75214
  }
75069
- if (seg !== null) adopt_seg(gobj, seg);
75070
- gobj_change_state(gobj, "ST_IDLE");
75071
- render(gobj);
75072
- return 0;
75215
+ if (reopen) reopen_form(gobj, reopen);
75216
+ return ret;
75073
75217
  }
75074
75218
  /***************************************************************
75075
75219
  * The queue is done, or gave up. Either way the model is what
@@ -75094,7 +75238,11 @@ function end_writes(gobj, error) {
75094
75238
  render(gobj);
75095
75239
  publish_check(gobj);
75096
75240
  }
75097
- reload_if_owed(gobj);
75241
+ if (priv.refresh_after_write) {
75242
+ priv.refresh_after_write = false;
75243
+ priv.reload_after_write = false;
75244
+ request_model(gobj, false);
75245
+ } else reload_if_owed(gobj);
75098
75246
  return error ? -1 : 0;
75099
75247
  }
75100
75248
  /***************************************************************
@@ -75375,7 +75523,9 @@ function ac_mt_command_answer(gobj, event, kw, src) {
75375
75523
  priv.save_return = "";
75376
75524
  priv.save_wrote = false;
75377
75525
  gobj_publish_event(gobj, "EV_RECORD_WRITTEN", { treedb_name: gobj_read_str_attr(gobj, "treedb_name") });
75378
- request_model(gobj, true);
75526
+ let refresh = priv.refresh_after_write;
75527
+ priv.refresh_after_write = false;
75528
+ request_model(gobj, !refresh);
75379
75529
  return 0;
75380
75530
  }
75381
75531
  }
@@ -75522,6 +75672,21 @@ function ac_refresh$1(gobj, event, kw, src) {
75522
75672
  return request_model(gobj, false);
75523
75673
  }
75524
75674
  /***************************************************************
75675
+ * EV_REFRESH while writes are in flight: the view's own Refresh
75676
+ * is busy then, so it is a host's (after a Save, say).
75677
+ *
75678
+ * Run when the writes END (end_writes()), not now. Run now, the
75679
+ * load replaced ST_SAVING and what was still queued was never
75680
+ * sent, with no word: nothing but the end of what is in flight
75681
+ * moves this view out of ST_LOADING or ST_SAVING.
75682
+ ***************************************************************/
75683
+ function ac_refresh_after_the_writes(gobj, event, kw, src) {
75684
+ let priv = gobj.priv;
75685
+ log_warning(`${gobj_short_name(gobj)}: ${event} while writes are in flight: the schemas are read again when the writes end`);
75686
+ priv.refresh_after_write = true;
75687
+ return 0;
75688
+ }
75689
+ /***************************************************************
75525
75690
  * What a DIALOG sends while the model is being read again: the
75526
75691
  * Save of a form left open when the reload started, a
75527
75692
  * confirmation, an import. The loading screen took the rest of
@@ -75696,7 +75861,13 @@ function ac_save_column(gobj, event, kw, src) {
75696
75861
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
75697
75862
  let col = kw.creating ? null : find_col(priv.model, priv.treedb_id, kw.topic, kw.col);
75698
75863
  if (refuse_if_readonly(gobj, event)) return -1;
75699
- if (reload_first(gobj, event)) return -1;
75864
+ if (reload_first(gobj, event, {
75865
+ form: "column",
75866
+ topic: kw.topic,
75867
+ col: kw.col,
75868
+ creating: !!kw.creating,
75869
+ changed: kw.changed || {}
75870
+ })) return -1;
75700
75871
  if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75701
75872
  if (!topic || !kw.creating && !col) {
75702
75873
  log_error(`${gobj_short_name(gobj)}: ${event} names no column: ${kw.topic}.${kw.col}`);
@@ -75824,7 +75995,13 @@ function ac_save_topic(gobj, event, kw, src) {
75824
75995
  let treedb = current_treedb(gobj);
75825
75996
  let topic = kw.creating ? null : find_topic(priv.model, priv.treedb_id, kw.topic);
75826
75997
  if (refuse_if_readonly(gobj, event)) return -1;
75827
- if (reload_first(gobj, event)) return -1;
75998
+ if (reload_first(gobj, event, {
75999
+ form: "topic",
76000
+ topic: kw.topic,
76001
+ col: "",
76002
+ creating: !!kw.creating,
76003
+ changed: kw.changed || {}
76004
+ })) return -1;
75828
76005
  if (built_on_a_replaced_model(gobj, event, kw)) return -1;
75829
76006
  if (!treedb || !kw.creating && !topic) {
75830
76007
  log_error(`${gobj_short_name(gobj)}: ${event} names no topic: ${kw.topic}`);
@@ -76287,7 +76464,11 @@ function create_gclass$1(gclass_name) {
76287
76464
  null
76288
76465
  ]
76289
76466
  ])],
76290
- ["ST_SAVING", COMMON.slice()]
76467
+ ["ST_SAVING", COMMON.filter((a) => a[0] !== "EV_REFRESH").concat([[
76468
+ "EV_REFRESH",
76469
+ ac_refresh_after_the_writes,
76470
+ null
76471
+ ]])]
76291
76472
  ];
76292
76473
  const event_types = [
76293
76474
  ["EV_MT_COMMAND_ANSWER", event_flag_t.EVF_PUBLIC_EVENT],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.25.15",
3
+ "version": "7.25.16",
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.22.2",
33
+ "@yuneta/gobj-js": "^7.25.2",
34
34
  "vite": "^8.2.1",
35
35
  "vitest": "^4.1.10"
36
36
  },
@@ -493,6 +493,7 @@ let PRIVATE_DATA = {
493
493
  __graphs__: [], // Rows of __graphs__
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
+ _graphs_writes_owed: {}, // topic_name → true: a __graphs__ write the host said did not land
496
497
  yet_showed: false,
497
498
  edit_mode: false,
498
499
  operation_mode: null,
@@ -3283,6 +3284,9 @@ function save_topic_graph_properties(gobj, topic_name)
3283
3284
  * The host says when one does NOT land (EV_GRAPHS_WRITE_REFUSED),
3284
3285
  * and the next Save writes the topic again. */
3285
3286
  priv._saved_graph_properties[topic_name] = json_deep_copy(properties);
3287
+ /* Paid: this is the write that was owed, if one was. Refused
3288
+ * again, EV_GRAPHS_WRITE_REFUSED owes it again. */
3289
+ delete priv._graphs_writes_owed[topic_name];
3286
3290
 
3287
3291
  let kw_update = {
3288
3292
  treedb_name: priv.treedb_name,
@@ -3388,9 +3392,16 @@ function update_history_buttons(gobj)
3388
3392
  disableElements($container, ".EV_HISTORY_UNDO");
3389
3393
  set_active_state($container, ".EV_HISTORY_UNDO", false);
3390
3394
 
3391
- // No more undos: graph is back to original state, disable save
3392
- disableElements($container, ".EV_SAVE_GRAPH");
3393
- set_submit_state($container, ".EV_SAVE_GRAPH", false);
3395
+ /* No more undos: the graph is back to what was loaded
3396
+ * or saved -- unless a write of it did not land. That
3397
+ * one is still owed, and only a Save pays it. */
3398
+ if(graphs_write_is_owed(gobj)) {
3399
+ enableElements($container, ".EV_SAVE_GRAPH");
3400
+ set_submit_state($container, ".EV_SAVE_GRAPH", true);
3401
+ } else {
3402
+ disableElements($container, ".EV_SAVE_GRAPH");
3403
+ set_submit_state($container, ".EV_SAVE_GRAPH", false);
3404
+ }
3394
3405
  }
3395
3406
  } else {
3396
3407
  disableElements($container, ".EV_HISTORY_REDO");
@@ -4763,6 +4774,17 @@ function run_preview_undo(gobj, key)
4763
4774
  }
4764
4775
  }
4765
4776
 
4777
+ /************************************************************
4778
+ * Is a `__graphs__` write owed? The host said one did not land
4779
+ * (EV_GRAPHS_WRITE_REFUSED), and no Save has written that topic
4780
+ * since. The Save button stays lit while it is: the history
4781
+ * knows nothing of it, so `canUndo()` alone would put it out.
4782
+ ************************************************************/
4783
+ function graphs_write_is_owed(gobj)
4784
+ {
4785
+ return Object.keys(gobj.priv._graphs_writes_owed).length > 0;
4786
+ }
4787
+
4766
4788
  function mark_graph_dirty(gobj)
4767
4789
  {
4768
4790
  let $container = gobj_read_attr(gobj, "$container");
@@ -10346,6 +10368,11 @@ function ac_clear_data(gobj, event, kw, src)
10346
10368
 
10347
10369
  gobj_write_attr(gobj, "records", {});
10348
10370
 
10371
+ /* A write owed is owed on the arrangement being thrown away: the
10372
+ * reload brings what the backend holds, and that is what the view
10373
+ * will hold. */
10374
+ priv._graphs_writes_owed = {};
10375
+
10349
10376
  /* The node counters belong to the data that is going away: a refresh
10350
10377
  * that kept them would decide the layout on the previous treedb. */
10351
10378
  priv._nodes_total = 0;
@@ -10495,10 +10522,13 @@ function ac_save_graph(gobj, event, kw, src)
10495
10522
  let history = graph_get_plugin(gobj, "history");
10496
10523
  if(history) {
10497
10524
  history.clear();
10498
- update_history_buttons(gobj);
10499
10525
  }
10500
10526
 
10501
10527
  save_geometry(gobj); // Publish an EV_UPDATE_NODE of each node
10528
+
10529
+ /* After the writes: they pay a write owed, and the buttons
10530
+ * are repainted from what is still owed. */
10531
+ update_history_buttons(gobj);
10502
10532
  }
10503
10533
 
10504
10534
  return 0;
@@ -10526,6 +10556,9 @@ function ac_graphs_write_refused(gobj, event, kw, src)
10526
10556
  log_warning(`${gobj_short_name(gobj)}: the arrangement of '${topic_name}' ` +
10527
10557
  `was not saved: the next Save writes it again`);
10528
10558
  forget_refused_graphs_write(priv._saved_graph_properties, topic_name);
10559
+ /* Owed until a Save writes it: update_history_buttons() keeps
10560
+ * the Save lit while it is, in edition and on entering it. */
10561
+ priv._graphs_writes_owed[topic_name] = true;
10529
10562
  if(priv.edit_mode) {
10530
10563
  mark_graph_dirty(gobj);
10531
10564
  }