@yuneta/gobj-ui 7.25.8 → 7.25.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -6
- package/dist/gobj-ui.cjs.js +168 -43
- package/dist/gobj-ui.es.js +168 -43
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +217 -46
- package/src/schema_editor_reload.wiring.test.js +321 -7
- package/src/shell_modals.js +8 -4
- package/src/shell_toast_once.test.js +42 -0
|
@@ -135,6 +135,7 @@ import {
|
|
|
135
135
|
col_enum,
|
|
136
136
|
is_empty_value,
|
|
137
137
|
fkey_ref,
|
|
138
|
+
parse_fkey_ref,
|
|
138
139
|
next_order,
|
|
139
140
|
moved_orders,
|
|
140
141
|
} from "./schema_model.js";
|
|
@@ -202,7 +203,9 @@ SDATA_END()
|
|
|
202
203
|
let PRIVATE_DATA = {
|
|
203
204
|
/* What the store answered, and what it means */
|
|
204
205
|
records: null, /* {treedbs, topics, cols} as they arrived */
|
|
206
|
+
records_before: null, /* the records of the model shown, while a load replaces them */
|
|
205
207
|
model: null, /* build_schema_model() of the above */
|
|
208
|
+
model_gen: 0, /* which LOAD the model is: a dialog is stamped with it */
|
|
206
209
|
pending: 0, /* `nodes` requests still in flight */
|
|
207
210
|
load_error: "",
|
|
208
211
|
load_round: 0, /* which load an answer belongs to: echoed as `round` */
|
|
@@ -224,6 +227,7 @@ let PRIVATE_DATA = {
|
|
|
224
227
|
/* Children and overlays */
|
|
225
228
|
diagram_gobj: null,
|
|
226
229
|
dialog: null,
|
|
230
|
+
dialog_gen: 0, /* the model_gen the open dialog was built on */
|
|
227
231
|
|
|
228
232
|
/* A write, or a plan of them, in flight */
|
|
229
233
|
save_queue: null,
|
|
@@ -444,6 +448,36 @@ function refuse_if_readonly(gobj, event)
|
|
|
444
448
|
return true;
|
|
445
449
|
}
|
|
446
450
|
|
|
451
|
+
/***************************************************************
|
|
452
|
+
* A decision taken on a model a load has since REPLACED: the Save
|
|
453
|
+
* of a form, a confirmation, an import. Each carries the
|
|
454
|
+
* `model_gen` it was built on (a number, not the model: a kw is
|
|
455
|
+
* dumped by the machine trace).
|
|
456
|
+
*
|
|
457
|
+
* Refused, and said. A column form sends every field it shows, so
|
|
458
|
+
* its Save after a reload wrote the OLD record over the newer one
|
|
459
|
+
* (fourth independent review, probe A3); a form whose column the
|
|
460
|
+
* reload removed answered "names no column" for ever. end_load()
|
|
461
|
+
* closes such a dialog when the load lands; this is the same rule
|
|
462
|
+
* for whatever arrives anyway -- a stale screen, a confirmation
|
|
463
|
+
* that was up, a keyboard.
|
|
464
|
+
*
|
|
465
|
+
* A kw with no stamp is not a dialog's and is not asked.
|
|
466
|
+
***************************************************************/
|
|
467
|
+
function built_on_a_replaced_model(gobj, event, kw)
|
|
468
|
+
{
|
|
469
|
+
let priv = gobj.priv;
|
|
470
|
+
|
|
471
|
+
if(!kw || kw.model_gen === undefined || kw.model_gen === priv.model_gen) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
log_warning(`${gobj_short_name(gobj)}: ${event} refused, it was decided on ` +
|
|
475
|
+
`the schemas a reload replaced`);
|
|
476
|
+
yui_shell_show_error(yui_shell_of(gobj),
|
|
477
|
+
"the schemas were read again: open the dialog again", {t: t});
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
447
481
|
|
|
448
482
|
|
|
449
483
|
|
|
@@ -465,7 +499,11 @@ function refuse_if_readonly(gobj, event)
|
|
|
465
499
|
* and the toolbar stops answering: left up, a click on a card, on
|
|
466
500
|
* Back or on the drawing answered "Event NOT DEFINED in state
|
|
467
501
|
* ST_LOADING" and was lost (third independent review). A dialog
|
|
468
|
-
* left open is answered in ST_LOADING instead (ac_wait_for_the_load)
|
|
502
|
+
* left open is answered in ST_LOADING instead (ac_wait_for_the_load),
|
|
503
|
+
* and a load that LANDS closes it (end_load()): it was built on the
|
|
504
|
+
* model the load replaced, and its Save sends every field it shows.
|
|
505
|
+
* An import plan is forgotten here for the same reason: it is a list
|
|
506
|
+
* of writes computed against the model going away.
|
|
469
507
|
*
|
|
470
508
|
* `keep_written`: a load the host did not ask for -- the reconnect,
|
|
471
509
|
* a write that turned out done -- keeps what this session wrote.
|
|
@@ -475,7 +513,9 @@ function refuse_if_readonly(gobj, event)
|
|
|
475
513
|
* is not even asked): the records the model was built on are put
|
|
476
514
|
* back (emptied, the next write patched them into a model with no
|
|
477
515
|
* treedb), the round is spent so a request that did leave is
|
|
478
|
-
* stale, and the session back asks again.
|
|
516
|
+
* stale, and the session back asks again. A load that left and
|
|
517
|
+
* FAILED keeps them the same way (`records_before`,
|
|
518
|
+
* ac_mt_command_answer()).
|
|
479
519
|
***************************************************************/
|
|
480
520
|
function request_model(gobj, keep_written)
|
|
481
521
|
{
|
|
@@ -486,7 +526,10 @@ function request_model(gobj, keep_written)
|
|
|
486
526
|
return -1; /* not mounted on a transport yet: mt_start retries via EV_TRANSPORT_STATE */
|
|
487
527
|
}
|
|
488
528
|
|
|
529
|
+
forget_import_plan(gobj);
|
|
530
|
+
|
|
489
531
|
let previous = priv.records;
|
|
532
|
+
priv.records_before = previous;
|
|
490
533
|
priv.records = {treedbs: [], topics: [], cols: []};
|
|
491
534
|
priv.load_error = "";
|
|
492
535
|
priv.pending = 0;
|
|
@@ -511,6 +554,7 @@ function request_model(gobj, keep_written)
|
|
|
511
554
|
}
|
|
512
555
|
if(left < 3) {
|
|
513
556
|
priv.records = previous;
|
|
557
|
+
priv.records_before = null;
|
|
514
558
|
priv.load_round++;
|
|
515
559
|
priv.reload_on_open = true;
|
|
516
560
|
priv.load_error = t("cannot reach the treedb");
|
|
@@ -643,9 +687,10 @@ function start_measuring(gobj)
|
|
|
643
687
|
* until the host saves it (C_TREEDB's save-schema, which raises the
|
|
644
688
|
* versions of what changed). Two sources say a topic is one, and
|
|
645
689
|
* they are kept APART because they are forgotten differently:
|
|
646
|
-
* - `written`: what THIS session wrote
|
|
647
|
-
*
|
|
648
|
-
*
|
|
690
|
+
* - `written`: what THIS session wrote. Only a load the HOST asked
|
|
691
|
+
* for forgets it (its EV_REFRESH, which a Save sends); the
|
|
692
|
+
* reconnect's reload, the one owed after a write and a late
|
|
693
|
+
* write's keep it (start_measuring(), request_model());
|
|
649
694
|
* - `host_ids`: what the host said last (EV_DRAFTS), replaced WHOLE
|
|
650
695
|
* by the next EV_DRAFTS. Folded into `written`, a topic the host
|
|
651
696
|
* named once stayed a draft after the Save that published it.
|
|
@@ -847,6 +892,14 @@ function render_toolbar(gobj)
|
|
|
847
892
|
* url's; ST_EMPTY has a treedb in the url and none in the model),
|
|
848
893
|
* and a button there sends what only a treedb screen declares. */
|
|
849
894
|
let on_a_treedb = state !== "ST_IDLE" && state !== "ST_EMPTY";
|
|
895
|
+
/* ...and the screen can outlive the treedb or the topic it is ON: a
|
|
896
|
+
* reload that no longer has them keeps the position (it says so,
|
|
897
|
+
* and Back is the way out). Asked of the MODEL, not of the state:
|
|
898
|
+
* gated on the state, Diagram, Check, Export, Import and New topic
|
|
899
|
+
* stayed up there and each logged "with no treedb open" (fourth
|
|
900
|
+
* independent review, probe D2). */
|
|
901
|
+
let treedb_here = on_a_treedb && !!current_treedb(gobj);
|
|
902
|
+
let topic_here = treedb_here && !!current_topic(gobj);
|
|
850
903
|
/* Nothing is legal while the model is in the air: every action is
|
|
851
904
|
* computed against it. */
|
|
852
905
|
let in_flight = state === "ST_SAVING" || state === "ST_LOADING";
|
|
@@ -892,7 +945,7 @@ function render_toolbar(gobj)
|
|
|
892
945
|
* refused says why when it is pressed.
|
|
893
946
|
*----------------------------------------------*/
|
|
894
947
|
let $right = [];
|
|
895
|
-
if(priv.treedb_id && has_model &&
|
|
948
|
+
if(priv.treedb_id && has_model && treedb_here) {
|
|
896
949
|
/* The compass, like every other door to a SCHEMA: the
|
|
897
950
|
* `hexagon-nodes` it wore is the graph of the DATA. */
|
|
898
951
|
$right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-compass-drafting",
|
|
@@ -906,7 +959,7 @@ function render_toolbar(gobj)
|
|
|
906
959
|
"import", "EV_IMPORT", false));
|
|
907
960
|
}
|
|
908
961
|
}
|
|
909
|
-
if(priv.topic_name && !readonly && !priv.diagram &&
|
|
962
|
+
if(priv.topic_name && !readonly && !priv.diagram && topic_here) {
|
|
910
963
|
/* Only while there IS somewhere to go back to: a drag is a write,
|
|
911
964
|
* and this is the way back from one. */
|
|
912
965
|
if(undo_order_writes(gobj, current_topic(gobj)).length > 0) {
|
|
@@ -916,7 +969,7 @@ function render_toolbar(gobj)
|
|
|
916
969
|
$right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus",
|
|
917
970
|
"new column", "EV_ADD_COLUMN", false));
|
|
918
971
|
}
|
|
919
|
-
if(priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram &&
|
|
972
|
+
if(priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram && treedb_here) {
|
|
920
973
|
$right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus",
|
|
921
974
|
"new topic", "EV_ADD_TOPIC", false));
|
|
922
975
|
}
|
|
@@ -1631,6 +1684,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix)
|
|
|
1631
1684
|
let priv = gobj.priv;
|
|
1632
1685
|
|
|
1633
1686
|
close_dialog(gobj);
|
|
1687
|
+
priv.dialog_gen = priv.model_gen;
|
|
1634
1688
|
priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), $content, {
|
|
1635
1689
|
dialog: true,
|
|
1636
1690
|
title: title,
|
|
@@ -1679,6 +1733,10 @@ function name_control($spec, label)
|
|
|
1679
1733
|
$spec[1]["aria-label"] = t(label);
|
|
1680
1734
|
$spec[1]["data-i18n-aria-label"] = label;
|
|
1681
1735
|
}
|
|
1736
|
+
if(!$spec[1].title) {
|
|
1737
|
+
$spec[1].title = t(label);
|
|
1738
|
+
$spec[1]["data-i18n-title"] = label;
|
|
1739
|
+
}
|
|
1682
1740
|
return true;
|
|
1683
1741
|
}
|
|
1684
1742
|
for(let child of ($spec[2] || [])) {
|
|
@@ -1689,6 +1747,19 @@ function name_control($spec, label)
|
|
|
1689
1747
|
return false;
|
|
1690
1748
|
}
|
|
1691
1749
|
|
|
1750
|
+
/***************************************************************
|
|
1751
|
+
* The four attributes that NAME a control -- a title and an
|
|
1752
|
+
* aria-label, each with the key a language change re-translates
|
|
1753
|
+
* it by. A visible label is not a name (CLAUDE.md).
|
|
1754
|
+
***************************************************************/
|
|
1755
|
+
function name_attrs(key, attrs)
|
|
1756
|
+
{
|
|
1757
|
+
return Object.assign({
|
|
1758
|
+
title: t(key), "data-i18n-title": key,
|
|
1759
|
+
"aria-label": t(key), "data-i18n-aria-label": key
|
|
1760
|
+
}, attrs);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1692
1763
|
function field(logical, name, label, $control, help)
|
|
1693
1764
|
{
|
|
1694
1765
|
/* The label is a SIBLING of the control -- Bulma's `field` shape --
|
|
@@ -1842,6 +1913,7 @@ function open_column_form(gobj, topic, col, prefill)
|
|
|
1842
1913
|
let hook_col = hook_topic ? hook[hook_topic] : "";
|
|
1843
1914
|
let treedb = current_treedb(gobj);
|
|
1844
1915
|
let sibling_topics = treedb ? treedb.topics.map(x => x.name) : [];
|
|
1916
|
+
let model_gen = priv.model_gen;
|
|
1845
1917
|
|
|
1846
1918
|
let $form = createElement2(
|
|
1847
1919
|
["div", {class: "SCHEMA_COL_FORM box"}, [
|
|
@@ -1883,9 +1955,10 @@ function open_column_form(gobj, topic, col, prefill)
|
|
|
1883
1955
|
text_input("description", record.description, "")),
|
|
1884
1956
|
["div", {class: "SCHEMA_COL_FORM_ACTIONS is-flex mt-4",
|
|
1885
1957
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
1886
|
-
["button", {class: "SCHEMA_COL_FORM_CANCEL button", type: "button"},
|
|
1958
|
+
["button", name_attrs("cancel", {class: "SCHEMA_COL_FORM_CANCEL button", type: "button"}),
|
|
1887
1959
|
[["span", {i18n: "cancel"}, t("cancel")]]],
|
|
1888
|
-
["button", {class: "SCHEMA_COL_FORM_SAVE button is-primary",
|
|
1960
|
+
["button", name_attrs("save", {class: "SCHEMA_COL_FORM_SAVE button is-primary",
|
|
1961
|
+
type: "button"}),
|
|
1889
1962
|
[["span", {class: "icon"}, [["i", {class: "yi-floppy-disk"}]]],
|
|
1890
1963
|
["span", {i18n: "save"}, t("save")]]]
|
|
1891
1964
|
]]
|
|
@@ -1926,10 +1999,11 @@ function open_column_form(gobj, topic, col, prefill)
|
|
|
1926
1999
|
let values = read_form($form);
|
|
1927
2000
|
values.flag = read_flags($form);
|
|
1928
2001
|
gobj_send_event(gobj, "EV_SAVE_COLUMN", {
|
|
1929
|
-
topic:
|
|
1930
|
-
col:
|
|
1931
|
-
creating:
|
|
1932
|
-
values:
|
|
2002
|
+
topic: topic.name,
|
|
2003
|
+
col: creating ? "" : col.name,
|
|
2004
|
+
creating: creating,
|
|
2005
|
+
values: values,
|
|
2006
|
+
model_gen: model_gen
|
|
1933
2007
|
}, gobj);
|
|
1934
2008
|
});
|
|
1935
2009
|
|
|
@@ -1962,6 +2036,7 @@ function open_topic_form(gobj, treedb, topic)
|
|
|
1962
2036
|
let record = (topic && topic.record) || {};
|
|
1963
2037
|
let creating = !topic;
|
|
1964
2038
|
let col_names = topic ? topic.cols.map(c => c.name) : [];
|
|
2039
|
+
let model_gen = gobj.priv.model_gen;
|
|
1965
2040
|
|
|
1966
2041
|
let $form = createElement2(
|
|
1967
2042
|
["div", {class: "SCHEMA_TOPIC_FORM box"}, [
|
|
@@ -2001,10 +2076,10 @@ function open_topic_form(gobj, treedb, topic)
|
|
|
2001
2076
|
]],
|
|
2002
2077
|
["div", {class: "SCHEMA_TOPIC_FORM_ACTIONS is-flex mt-4",
|
|
2003
2078
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
2004
|
-
["button", {class: "SCHEMA_TOPIC_FORM_CANCEL button", type: "button"},
|
|
2079
|
+
["button", name_attrs("cancel", {class: "SCHEMA_TOPIC_FORM_CANCEL button", type: "button"}),
|
|
2005
2080
|
[["span", {i18n: "cancel"}, t("cancel")]]],
|
|
2006
|
-
["button", {class: "SCHEMA_TOPIC_FORM_SAVE button is-primary",
|
|
2007
|
-
|
|
2081
|
+
["button", name_attrs("save", {class: "SCHEMA_TOPIC_FORM_SAVE button is-primary",
|
|
2082
|
+
type: "button"}),
|
|
2008
2083
|
[["span", {class: "icon"}, [["i", {class: "yi-floppy-disk"}]]],
|
|
2009
2084
|
["span", {i18n: "save"}, t("save")]]]
|
|
2010
2085
|
]]
|
|
@@ -2018,9 +2093,10 @@ function open_topic_form(gobj, treedb, topic)
|
|
|
2018
2093
|
$form.querySelector(".SCHEMA_TOPIC_FORM_SAVE").addEventListener("click", (evt) => {
|
|
2019
2094
|
evt.stopPropagation();
|
|
2020
2095
|
gobj_send_event(gobj, "EV_SAVE_TOPIC", {
|
|
2021
|
-
topic:
|
|
2022
|
-
creating:
|
|
2023
|
-
values:
|
|
2096
|
+
topic: creating ? "" : topic.name,
|
|
2097
|
+
creating: creating,
|
|
2098
|
+
values: read_form($form),
|
|
2099
|
+
model_gen: model_gen
|
|
2024
2100
|
}, gobj);
|
|
2025
2101
|
});
|
|
2026
2102
|
|
|
@@ -2173,14 +2249,15 @@ function copy_text(gobj, $text)
|
|
|
2173
2249
|
***************************************************************/
|
|
2174
2250
|
function open_import(gobj, treedb)
|
|
2175
2251
|
{
|
|
2252
|
+
let model_gen = gobj.priv.model_gen;
|
|
2176
2253
|
let $content = createElement2(
|
|
2177
2254
|
["div", {class: "SCHEMA_IMPORT box"}, [
|
|
2178
2255
|
["p", {class: "SCHEMA_IMPORT_HELP help mb-2",
|
|
2179
2256
|
i18n: "paste a schema as json: the plan is shown before anything is written"},
|
|
2180
2257
|
t("paste a schema as json: the plan is shown before anything is written")],
|
|
2181
|
-
["textarea", {class: "SCHEMA_IMPORT_TEXT textarea is-small is-family-monospace",
|
|
2258
|
+
["textarea", name_attrs("schema", {class: "SCHEMA_IMPORT_TEXT textarea is-small is-family-monospace",
|
|
2182
2259
|
rows: "12", spellcheck: "false",
|
|
2183
|
-
placeholder: '{"id": "...", "schema_version": "1", "topics": []}'}, ""],
|
|
2260
|
+
placeholder: '{"id": "...", "schema_version": "1", "topics": []}'}), ""],
|
|
2184
2261
|
["label", {class: "SCHEMA_IMPORT_PRUNE checkbox mt-2"}, [
|
|
2185
2262
|
["input", {type: "checkbox", class: "SCHEMA_IMPORT_PRUNE_BOX", checked: "checked"}],
|
|
2186
2263
|
["span", {class: "ml-1", i18n: "delete what the pasted schema does not declare"},
|
|
@@ -2189,13 +2266,13 @@ function open_import(gobj, treedb)
|
|
|
2189
2266
|
["div", {class: "SCHEMA_IMPORT_PLAN mt-3"}, []],
|
|
2190
2267
|
["div", {class: "SCHEMA_IMPORT_ACTIONS is-flex mt-3",
|
|
2191
2268
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
2192
|
-
["button", {class: "SCHEMA_IMPORT_CANCEL button", type: "button"},
|
|
2269
|
+
["button", name_attrs("cancel", {class: "SCHEMA_IMPORT_CANCEL button", type: "button"}),
|
|
2193
2270
|
[["span", {i18n: "cancel"}, t("cancel")]]],
|
|
2194
|
-
["button", {class: "SCHEMA_IMPORT_PREVIEW button", type: "button"},
|
|
2271
|
+
["button", name_attrs("preview", {class: "SCHEMA_IMPORT_PREVIEW button", type: "button"}),
|
|
2195
2272
|
[["span", {class: "icon"}, [["i", {class: "yi-eye"}]]],
|
|
2196
2273
|
["span", {i18n: "preview"}, t("preview")]]],
|
|
2197
|
-
["button", {class: "SCHEMA_IMPORT_RUN button is-warning",
|
|
2198
|
-
|
|
2274
|
+
["button", name_attrs("import", {class: "SCHEMA_IMPORT_RUN button is-warning",
|
|
2275
|
+
type: "button", disabled: "disabled"}),
|
|
2199
2276
|
[["span", {class: "icon"}, [["i", {class: "yi-upload"}]]],
|
|
2200
2277
|
["span", {i18n: "import"}, t("import")]]]
|
|
2201
2278
|
]]
|
|
@@ -2214,13 +2291,14 @@ function open_import(gobj, treedb)
|
|
|
2214
2291
|
$content.querySelector(".SCHEMA_IMPORT_PREVIEW").addEventListener("click", (evt) => {
|
|
2215
2292
|
evt.stopPropagation();
|
|
2216
2293
|
gobj_send_event(gobj, "EV_PREVIEW_IMPORT", {
|
|
2217
|
-
text:
|
|
2218
|
-
prune:
|
|
2294
|
+
text: $text.value,
|
|
2295
|
+
prune: $prune.checked,
|
|
2296
|
+
model_gen: model_gen
|
|
2219
2297
|
}, gobj);
|
|
2220
2298
|
});
|
|
2221
2299
|
$run.addEventListener("click", (evt) => {
|
|
2222
2300
|
evt.stopPropagation();
|
|
2223
|
-
gobj_send_event(gobj, "EV_APPLY_IMPORT", {}, gobj);
|
|
2301
|
+
gobj_send_event(gobj, "EV_APPLY_IMPORT", {model_gen: model_gen}, gobj);
|
|
2224
2302
|
});
|
|
2225
2303
|
|
|
2226
2304
|
gobj.priv.import_pane = $plan_pane;
|
|
@@ -2228,6 +2306,23 @@ function open_import(gobj, treedb)
|
|
|
2228
2306
|
open_dialog(gobj, $content, "import", "SCHEMA_IMPORT_DIALOG", treedb.id);
|
|
2229
2307
|
}
|
|
2230
2308
|
|
|
2309
|
+
/***************************************************************
|
|
2310
|
+
* The plan is a list of writes computed against the model shown:
|
|
2311
|
+
* a load replaces the model, so the plan goes with it and the
|
|
2312
|
+
* dialog, if it stays up (a load that failed keeps the model),
|
|
2313
|
+
* asks for a Preview again.
|
|
2314
|
+
***************************************************************/
|
|
2315
|
+
function forget_import_plan(gobj)
|
|
2316
|
+
{
|
|
2317
|
+
let priv = gobj.priv;
|
|
2318
|
+
|
|
2319
|
+
priv.import_plan = null;
|
|
2320
|
+
clear(priv.import_pane);
|
|
2321
|
+
if(priv.import_run) {
|
|
2322
|
+
priv.import_run.disabled = true;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2231
2326
|
/***************************************************************
|
|
2232
2327
|
* The plan, drawn where the paste is: one line per write, the
|
|
2233
2328
|
* deletions apart and counted, because they are what the
|
|
@@ -2304,7 +2399,7 @@ function open_orphans(gobj)
|
|
|
2304
2399
|
["td", {class: "SCHEMA_ORPHAN_ID"}, [["code", {}, `${entry.id}`]]],
|
|
2305
2400
|
["td", {class: "SCHEMA_ORPHAN_ACTIONS has-text-right"},
|
|
2306
2401
|
readonly ? [] : [row_icon(gobj, "SCHEMA_ORPHAN_DELETE", "yi-trash", "delete",
|
|
2307
|
-
"EV_DELETE_ORPHAN", {kind: kind, id: entry.id})]]
|
|
2402
|
+
"EV_DELETE_ORPHAN", {kind: kind, id: entry.id, model_gen: priv.model_gen})]]
|
|
2308
2403
|
]];
|
|
2309
2404
|
};
|
|
2310
2405
|
|
|
@@ -2519,6 +2614,11 @@ function transport_dropped(gobj)
|
|
|
2519
2614
|
priv.load_round++;
|
|
2520
2615
|
priv.pending = 0;
|
|
2521
2616
|
priv.load_error = "";
|
|
2617
|
+
if(priv.records_before) {
|
|
2618
|
+
/* The records of the model kept, not the half the load got. */
|
|
2619
|
+
priv.records = priv.records_before;
|
|
2620
|
+
priv.records_before = null;
|
|
2621
|
+
}
|
|
2522
2622
|
return end_load(gobj);
|
|
2523
2623
|
}
|
|
2524
2624
|
return 0;
|
|
@@ -2530,6 +2630,12 @@ function transport_dropped(gobj)
|
|
|
2530
2630
|
* in flight (ac_show() kept it), or the one it had. Without a
|
|
2531
2631
|
* model there is no screen to go to: the position is kept for the
|
|
2532
2632
|
* next load, and the editor says why it is empty.
|
|
2633
|
+
*
|
|
2634
|
+
* A dialog built on the model this load REPLACED is closed, and the
|
|
2635
|
+
* operator told to open it again: it shows the old record, and its
|
|
2636
|
+
* Save would write every field of it over the newer one. A load
|
|
2637
|
+
* that failed or never left replaced nothing (`model_gen` did not
|
|
2638
|
+
* move), and the dialog stays with what was typed.
|
|
2533
2639
|
***************************************************************/
|
|
2534
2640
|
function end_load(gobj)
|
|
2535
2641
|
{
|
|
@@ -2537,6 +2643,13 @@ function end_load(gobj)
|
|
|
2537
2643
|
let seg = priv.pending_seg;
|
|
2538
2644
|
|
|
2539
2645
|
priv.pending_seg = null;
|
|
2646
|
+
if(priv.dialog && priv.dialog_gen !== priv.model_gen) {
|
|
2647
|
+
log_warning(`${gobj_short_name(gobj)}: a dialog was open on the schemas ` +
|
|
2648
|
+
`the load replaced: closed`);
|
|
2649
|
+
close_dialog(gobj);
|
|
2650
|
+
yui_shell_show_error(yui_shell_of(gobj),
|
|
2651
|
+
"the schemas were read again: open the dialog again", {t: t});
|
|
2652
|
+
}
|
|
2540
2653
|
if(priv.model) {
|
|
2541
2654
|
if(seg !== null) {
|
|
2542
2655
|
return apply_seg(gobj, seg);
|
|
@@ -2885,6 +2998,23 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2885
2998
|
return 0;
|
|
2886
2999
|
}
|
|
2887
3000
|
if(priv.load_error) {
|
|
3001
|
+
/* Refused IN session (a deadline of a routing adapter on a
|
|
3002
|
+
* slow `nodes`): the model shown is what the store held a
|
|
3003
|
+
* moment ago, and blanking it took the screen and every open
|
|
3004
|
+
* form with it -- a form's Save then answered "Event NOT
|
|
3005
|
+
* DEFINED in state ST_IDLE" (fourth independent review). It
|
|
3006
|
+
* stays, as it does when the load cannot leave, and the
|
|
3007
|
+
* reload is owed. */
|
|
3008
|
+
if(priv.model) {
|
|
3009
|
+
log_warning(`${gobj_short_name(gobj)}: the schemas could not be read ` +
|
|
3010
|
+
`again (${priv.load_error}): the previous ones stay`);
|
|
3011
|
+
priv.records = priv.records_before;
|
|
3012
|
+
priv.records_before = null;
|
|
3013
|
+
priv.reload_on_open = true;
|
|
3014
|
+
yui_shell_show_error(yui_shell_of(gobj),
|
|
3015
|
+
"cannot read the schemas again: the previous ones stay", {t: t});
|
|
3016
|
+
return end_load(gobj);
|
|
3017
|
+
}
|
|
2888
3018
|
priv.model = null;
|
|
2889
3019
|
return end_load(gobj);
|
|
2890
3020
|
}
|
|
@@ -2893,6 +3023,8 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2893
3023
|
* (NOT in build_model(): that also runs on the patch after every
|
|
2894
3024
|
* write, which is exactly when the undo has to survive.) */
|
|
2895
3025
|
priv.order_undo = {};
|
|
3026
|
+
priv.records_before = null;
|
|
3027
|
+
priv.model_gen++;
|
|
2896
3028
|
build_model(gobj);
|
|
2897
3029
|
/* Same reason as order_undo above: this is the LOAD, and the
|
|
2898
3030
|
* baseline is what a later "the version did not move" is measured
|
|
@@ -2931,12 +3063,26 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2931
3063
|
patch_record(gobj, topic_name, record);
|
|
2932
3064
|
} else {
|
|
2933
3065
|
/* The treedb wrote it and did not describe it back: ask
|
|
2934
|
-
* again rather than draw a model that has drifted.
|
|
2935
|
-
|
|
3066
|
+
* again rather than draw a model that has drifted. What
|
|
3067
|
+
* was queued after it is not sent -- it was computed on
|
|
3068
|
+
* the model that drifted -- and that is SAID: dropped in
|
|
3069
|
+
* silence, a drag of five columns stopped at the first.
|
|
3070
|
+
* The reload is also the one a late write may have owed
|
|
3071
|
+
* (reload_after_write): left set, the next write loaded
|
|
3072
|
+
* the whole model once more (fourth independent review). */
|
|
3073
|
+
let not_sent = priv.save_queue.length - 1;
|
|
3074
|
+
log_warning(`${gobj_short_name(gobj)}: '${command}' answered no record` +
|
|
3075
|
+
(not_sent > 0 ? `: ${not_sent} write(s) after it were not sent` : ""));
|
|
3076
|
+
if(not_sent > 0) {
|
|
3077
|
+
yui_shell_show_error(yui_shell_of(gobj),
|
|
3078
|
+
"the treedb did not describe a write back: the writes after it were not sent",
|
|
3079
|
+
{t: t});
|
|
3080
|
+
}
|
|
2936
3081
|
if(topic_name === T_TOPICS || topic_name === T_COLS) {
|
|
2937
3082
|
let write = priv.save_queue[0];
|
|
2938
3083
|
mark_written(gobj, topic_name, write ? write.record : null);
|
|
2939
3084
|
}
|
|
3085
|
+
priv.reload_after_write = false;
|
|
2940
3086
|
priv.save_queue = [];
|
|
2941
3087
|
priv.save_return = "";
|
|
2942
3088
|
priv.save_wrote = false;
|
|
@@ -3010,8 +3156,14 @@ function late_write_done(gobj, command, topic_name, data)
|
|
|
3010
3156
|
/***************************************************************
|
|
3011
3157
|
* The topic a record written belongs to, read off the RECORD and
|
|
3012
3158
|
* not off the screen: a write answered late may be about a topic
|
|
3013
|
-
* the operator has left. A column names its topic in its fkey
|
|
3014
|
-
*
|
|
3159
|
+
* the operator has left. A column names its topic in its fkey; a
|
|
3160
|
+
* topic is its own id.
|
|
3161
|
+
*
|
|
3162
|
+
* The fkey is read with parse_fkey_ref(), because the store answers
|
|
3163
|
+
* in list_dict -- `[{topic_name, id, hook_name}]` -- and not in the
|
|
3164
|
+
* `topics^<topic id>^cols` strings this view writes. Split as a
|
|
3165
|
+
* string, every production answer marked nothing (fourth independent
|
|
3166
|
+
* review, probe C).
|
|
3015
3167
|
***************************************************************/
|
|
3016
3168
|
function mark_written_record(gobj, topic_name, record)
|
|
3017
3169
|
{
|
|
@@ -3027,13 +3179,8 @@ function mark_written_record(gobj, topic_name, record)
|
|
|
3027
3179
|
if(topic_name !== T_COLS) {
|
|
3028
3180
|
return;
|
|
3029
3181
|
}
|
|
3030
|
-
let
|
|
3031
|
-
|
|
3032
|
-
for(let ref of refs) {
|
|
3033
|
-
let parts = String(ref).split("^");
|
|
3034
|
-
if(parts.length >= 2 && parts[1]) {
|
|
3035
|
-
priv.written[parts[1]] = true;
|
|
3036
|
-
}
|
|
3182
|
+
for(let ref of parse_fkey_ref(record.topics)) {
|
|
3183
|
+
priv.written[ref.id] = true;
|
|
3037
3184
|
}
|
|
3038
3185
|
}
|
|
3039
3186
|
|
|
@@ -3142,16 +3289,21 @@ function ac_refresh(gobj, event, kw, src)
|
|
|
3142
3289
|
* operator can still reach, and what it asks is computed against
|
|
3143
3290
|
* a model the answers in the air are about to replace.
|
|
3144
3291
|
*
|
|
3145
|
-
* Refused, and SAID
|
|
3146
|
-
*
|
|
3147
|
-
*
|
|
3148
|
-
*
|
|
3292
|
+
* Refused, and SAID. Undeclared, it answered "Event NOT DEFINED
|
|
3293
|
+
* in state ST_LOADING" and the edit was gone with no word (third
|
|
3294
|
+
* independent review).
|
|
3295
|
+
*
|
|
3296
|
+
* And NOT "try again": the same Save, once the load was in, sent
|
|
3297
|
+
* the form built on the old model -- every field of the old record
|
|
3298
|
+
* over the newer one (fourth independent review). The load that
|
|
3299
|
+
* lands closes such a dialog (end_load()); one that fails leaves it
|
|
3300
|
+
* up, on the model it was built on.
|
|
3149
3301
|
***************************************************************/
|
|
3150
3302
|
function ac_wait_for_the_load(gobj, event, kw, src)
|
|
3151
3303
|
{
|
|
3152
3304
|
log_warning(`${gobj_short_name(gobj)}: ${event} refused, the schemas are loading`);
|
|
3153
3305
|
yui_shell_show_error(yui_shell_of(gobj),
|
|
3154
|
-
"the schemas are loading:
|
|
3306
|
+
"the schemas are loading: wait for them", {t: t});
|
|
3155
3307
|
return -1;
|
|
3156
3308
|
}
|
|
3157
3309
|
|
|
@@ -3245,6 +3397,7 @@ function ac_show_orphans(gobj, event, kw, src)
|
|
|
3245
3397
|
***************************************************************/
|
|
3246
3398
|
function confirm_then(gobj, message, detail, kw)
|
|
3247
3399
|
{
|
|
3400
|
+
kw = Object.assign({model_gen: gobj.priv.model_gen}, kw);
|
|
3248
3401
|
yui_shell_confirm_danger(yui_shell_of(gobj), message, {
|
|
3249
3402
|
t: t,
|
|
3250
3403
|
detail: detail
|
|
@@ -3330,6 +3483,9 @@ function ac_save_column(gobj, event, kw, src)
|
|
|
3330
3483
|
if(refuse_if_readonly(gobj, event)) {
|
|
3331
3484
|
return -1;
|
|
3332
3485
|
}
|
|
3486
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3487
|
+
return -1;
|
|
3488
|
+
}
|
|
3333
3489
|
if(!topic || (!kw.creating && !col)) {
|
|
3334
3490
|
log_error(`${gobj_short_name(gobj)}: ${event} names no column: ${kw.topic}.${kw.col}`);
|
|
3335
3491
|
return -1;
|
|
@@ -3484,6 +3640,9 @@ function ac_save_topic(gobj, event, kw, src)
|
|
|
3484
3640
|
if(refuse_if_readonly(gobj, event)) {
|
|
3485
3641
|
return -1;
|
|
3486
3642
|
}
|
|
3643
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3644
|
+
return -1;
|
|
3645
|
+
}
|
|
3487
3646
|
if(!treedb || (!kw.creating && !topic)) {
|
|
3488
3647
|
log_error(`${gobj_short_name(gobj)}: ${event} names no topic: ${kw.topic}`);
|
|
3489
3648
|
return -1;
|
|
@@ -3538,6 +3697,9 @@ function ac_delete_orphan(gobj, event, kw, src)
|
|
|
3538
3697
|
if(refuse_if_readonly(gobj, event)) {
|
|
3539
3698
|
return -1;
|
|
3540
3699
|
}
|
|
3700
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3701
|
+
return -1;
|
|
3702
|
+
}
|
|
3541
3703
|
if(empty_string(kw.id)) {
|
|
3542
3704
|
log_error(`${gobj_short_name(gobj)}: ${event} names nothing`);
|
|
3543
3705
|
return -1;
|
|
@@ -3610,6 +3772,9 @@ function ac_preview_import(gobj, event, kw, src)
|
|
|
3610
3772
|
let treedb = current_treedb(gobj);
|
|
3611
3773
|
let incoming = null;
|
|
3612
3774
|
|
|
3775
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3776
|
+
return -1;
|
|
3777
|
+
}
|
|
3613
3778
|
if(!treedb) {
|
|
3614
3779
|
log_error(`${gobj_short_name(gobj)}: ${event} with no treedb open`);
|
|
3615
3780
|
return -1;
|
|
@@ -3637,6 +3802,9 @@ function ac_apply_import(gobj, event, kw, src)
|
|
|
3637
3802
|
if(refuse_if_readonly(gobj, event)) {
|
|
3638
3803
|
return -1;
|
|
3639
3804
|
}
|
|
3805
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3806
|
+
return -1;
|
|
3807
|
+
}
|
|
3640
3808
|
if(!plan || plan.writes.length === 0) {
|
|
3641
3809
|
log_error(`${gobj_short_name(gobj)}: ${event} with no plan to run`);
|
|
3642
3810
|
return -1;
|
|
@@ -3687,6 +3855,9 @@ function ac_confirmed(gobj, event, kw, src)
|
|
|
3687
3855
|
if(refuse_if_readonly(gobj, event)) {
|
|
3688
3856
|
return -1;
|
|
3689
3857
|
}
|
|
3858
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3859
|
+
return -1;
|
|
3860
|
+
}
|
|
3690
3861
|
if(kw.what === "import") {
|
|
3691
3862
|
return run_import(gobj);
|
|
3692
3863
|
}
|