@yuneta/gobj-ui 7.25.7 → 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 +60 -1
- package/dist/gobj-ui.cjs.js +355 -101
- package/dist/gobj-ui.es.js +355 -101
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +384 -77
- package/src/schema_editor_reload.wiring.test.js +787 -0
- package/src/shell_modals.js +58 -33
- package/src/shell_toast_once.test.js +106 -3
|
@@ -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,10 +203,13 @@ 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` */
|
|
212
|
+
load_keeps_written: false, /* the load in flight is not the host's: `written` survives it */
|
|
209
213
|
|
|
210
214
|
/* Where the operator is. The url carries it; these mirror it. */
|
|
211
215
|
treedb_id: "",
|
|
@@ -223,6 +227,7 @@ let PRIVATE_DATA = {
|
|
|
223
227
|
/* Children and overlays */
|
|
224
228
|
diagram_gobj: null,
|
|
225
229
|
dialog: null,
|
|
230
|
+
dialog_gen: 0, /* the model_gen the open dialog was built on */
|
|
226
231
|
|
|
227
232
|
/* A write, or a plan of them, in flight */
|
|
228
233
|
save_queue: null,
|
|
@@ -443,6 +448,36 @@ function refuse_if_readonly(gobj, event)
|
|
|
443
448
|
return true;
|
|
444
449
|
}
|
|
445
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
|
+
|
|
446
481
|
|
|
447
482
|
|
|
448
483
|
|
|
@@ -458,8 +493,31 @@ function refuse_if_readonly(gobj, event)
|
|
|
458
493
|
* schema set of the yuno is here: everything below — the
|
|
459
494
|
* drawing, the validation, the export — is computed from these
|
|
460
495
|
* and asks the backend nothing more.
|
|
496
|
+
*
|
|
497
|
+
* THE LOADING SCREEN IS DRAWN, not assumed. ST_LOADING declares
|
|
498
|
+
* none of the actions of a screen, so the screen it replaces goes
|
|
499
|
+
* and the toolbar stops answering: left up, a click on a card, on
|
|
500
|
+
* Back or on the drawing answered "Event NOT DEFINED in state
|
|
501
|
+
* ST_LOADING" and was lost (third independent review). A dialog
|
|
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.
|
|
507
|
+
*
|
|
508
|
+
* `keep_written`: a load the host did not ask for -- the reconnect,
|
|
509
|
+
* a write that turned out done -- keeps what this session wrote.
|
|
510
|
+
* Only the host's EV_REFRESH (a Save sends it) forgets it.
|
|
511
|
+
*
|
|
512
|
+
* A load that cannot leave WHOLE did not happen (out of session it
|
|
513
|
+
* is not even asked): the records the model was built on are put
|
|
514
|
+
* back (emptied, the next write patched them into a model with no
|
|
515
|
+
* treedb), the round is spent so a request that did leave is
|
|
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()).
|
|
461
519
|
***************************************************************/
|
|
462
|
-
function request_model(gobj)
|
|
520
|
+
function request_model(gobj, keep_written)
|
|
463
521
|
{
|
|
464
522
|
let priv = gobj.priv;
|
|
465
523
|
let remote = gobj_read_pointer_attr(gobj, "gobj_remote_yuno");
|
|
@@ -468,27 +526,45 @@ function request_model(gobj)
|
|
|
468
526
|
return -1; /* not mounted on a transport yet: mt_start retries via EV_TRANSPORT_STATE */
|
|
469
527
|
}
|
|
470
528
|
|
|
529
|
+
forget_import_plan(gobj);
|
|
530
|
+
|
|
531
|
+
let previous = priv.records;
|
|
532
|
+
priv.records_before = previous;
|
|
471
533
|
priv.records = {treedbs: [], topics: [], cols: []};
|
|
472
534
|
priv.load_error = "";
|
|
473
535
|
priv.pending = 0;
|
|
474
536
|
priv.load_round++;
|
|
537
|
+
priv.load_keeps_written = !!keep_written;
|
|
475
538
|
gobj_change_state(gobj, "ST_LOADING");
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
539
|
+
set_busy(gobj, false);
|
|
540
|
+
render(gobj);
|
|
541
|
+
|
|
542
|
+
/* Out of session nothing is asked: each request would come back
|
|
543
|
+
* refused and logged as an ERROR, for a drop that is not one. */
|
|
544
|
+
let left = 0;
|
|
545
|
+
if(transport_in_session(gobj)) {
|
|
546
|
+
for(let topic_name of [T_TREEDBS, T_TOPICS, T_COLS]) {
|
|
547
|
+
if(remote_command(gobj, "nodes", {
|
|
548
|
+
topic_name: topic_name,
|
|
549
|
+
options: {list_dict: true}
|
|
550
|
+
}, {round: priv.load_round}) === 0) {
|
|
551
|
+
left++;
|
|
552
|
+
}
|
|
484
553
|
}
|
|
485
554
|
}
|
|
486
|
-
if(
|
|
555
|
+
if(left < 3) {
|
|
556
|
+
priv.records = previous;
|
|
557
|
+
priv.records_before = null;
|
|
558
|
+
priv.load_round++;
|
|
559
|
+
priv.reload_on_open = true;
|
|
487
560
|
priv.load_error = t("cannot reach the treedb");
|
|
488
|
-
|
|
489
|
-
|
|
561
|
+
if(priv.model) {
|
|
562
|
+
yui_shell_show_error(yui_shell_of(gobj), "cannot reach the treedb", {t: t});
|
|
563
|
+
}
|
|
564
|
+
end_load(gobj);
|
|
490
565
|
return -1;
|
|
491
566
|
}
|
|
567
|
+
priv.pending = left;
|
|
492
568
|
return 0;
|
|
493
569
|
}
|
|
494
570
|
|
|
@@ -571,17 +647,23 @@ function build_model(gobj)
|
|
|
571
647
|
* From here on, "did this session change it?" has an answer.
|
|
572
648
|
*
|
|
573
649
|
* Called ONLY when records arrive from the store: that is what a
|
|
574
|
-
* measurement of "changed since" can be measured against.
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
650
|
+
* measurement of "changed since" can be measured against.
|
|
651
|
+
*
|
|
652
|
+
* `written` is forgotten only by a load the HOST asked for (its
|
|
653
|
+
* EV_REFRESH, which a Save sends). Since M36 a write moves no
|
|
654
|
+
* version, so `written` is the draft chip of what this session
|
|
655
|
+
* wrote and no longer half of a version measurement: a reload of
|
|
656
|
+
* the reconnect, or of a write that turned out done, forgot the
|
|
657
|
+
* chips of writes the host had not been told about.
|
|
578
658
|
***************************************************************/
|
|
579
659
|
function start_measuring(gobj)
|
|
580
660
|
{
|
|
581
661
|
let priv = gobj.priv;
|
|
582
662
|
|
|
583
663
|
priv.baseline = {};
|
|
584
|
-
priv.
|
|
664
|
+
if(!priv.load_keeps_written) {
|
|
665
|
+
priv.written = {};
|
|
666
|
+
}
|
|
585
667
|
priv.host_ids = {};
|
|
586
668
|
|
|
587
669
|
if(!priv.model) {
|
|
@@ -605,9 +687,10 @@ function start_measuring(gobj)
|
|
|
605
687
|
* until the host saves it (C_TREEDB's save-schema, which raises the
|
|
606
688
|
* versions of what changed). Two sources say a topic is one, and
|
|
607
689
|
* they are kept APART because they are forgotten differently:
|
|
608
|
-
* - `written`: what THIS session wrote
|
|
609
|
-
*
|
|
610
|
-
*
|
|
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());
|
|
611
694
|
* - `host_ids`: what the host said last (EV_DRAFTS), replaced WHOLE
|
|
612
695
|
* by the next EV_DRAFTS. Folded into `written`, a topic the host
|
|
613
696
|
* named once stayed a draft after the Save that published it.
|
|
@@ -805,12 +888,27 @@ function render_toolbar(gobj)
|
|
|
805
888
|
let state = gobj_current_state(gobj);
|
|
806
889
|
let readonly = is_readonly(gobj);
|
|
807
890
|
let has_model = !!priv.model;
|
|
891
|
+
/* The position can outlive its screen (a load that failed keeps the
|
|
892
|
+
* url's; ST_EMPTY has a treedb in the url and none in the model),
|
|
893
|
+
* and a button there sends what only a treedb screen declares. */
|
|
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);
|
|
903
|
+
/* Nothing is legal while the model is in the air: every action is
|
|
904
|
+
* computed against it. */
|
|
905
|
+
let in_flight = state === "ST_SAVING" || state === "ST_LOADING";
|
|
808
906
|
|
|
809
907
|
/*----------------------------------------------*
|
|
810
908
|
* Left: back, then the position as a trail.
|
|
811
909
|
*----------------------------------------------*/
|
|
812
910
|
let $left = [];
|
|
813
|
-
if(priv.treedb_id) {
|
|
911
|
+
if(priv.treedb_id && on_a_treedb) {
|
|
814
912
|
$left.push(["button", {class: "SCHEMA_BACK button",
|
|
815
913
|
type: "button",
|
|
816
914
|
title: t("back"), "aria-label": t("back"),
|
|
@@ -847,7 +945,7 @@ function render_toolbar(gobj)
|
|
|
847
945
|
* refused says why when it is pressed.
|
|
848
946
|
*----------------------------------------------*/
|
|
849
947
|
let $right = [];
|
|
850
|
-
if(priv.treedb_id && has_model) {
|
|
948
|
+
if(priv.treedb_id && has_model && treedb_here) {
|
|
851
949
|
/* The compass, like every other door to a SCHEMA: the
|
|
852
950
|
* `hexagon-nodes` it wore is the graph of the DATA. */
|
|
853
951
|
$right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-compass-drafting",
|
|
@@ -861,7 +959,7 @@ function render_toolbar(gobj)
|
|
|
861
959
|
"import", "EV_IMPORT", false));
|
|
862
960
|
}
|
|
863
961
|
}
|
|
864
|
-
if(priv.topic_name && !readonly && !priv.diagram) {
|
|
962
|
+
if(priv.topic_name && !readonly && !priv.diagram && topic_here) {
|
|
865
963
|
/* Only while there IS somewhere to go back to: a drag is a write,
|
|
866
964
|
* and this is the way back from one. */
|
|
867
965
|
if(undo_order_writes(gobj, current_topic(gobj)).length > 0) {
|
|
@@ -871,7 +969,7 @@ function render_toolbar(gobj)
|
|
|
871
969
|
$right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus",
|
|
872
970
|
"new column", "EV_ADD_COLUMN", false));
|
|
873
971
|
}
|
|
874
|
-
if(priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram) {
|
|
972
|
+
if(priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram && treedb_here) {
|
|
875
973
|
$right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus",
|
|
876
974
|
"new topic", "EV_ADD_TOPIC", false));
|
|
877
975
|
}
|
|
@@ -894,7 +992,7 @@ function render_toolbar(gobj)
|
|
|
894
992
|
* destroyed. */
|
|
895
993
|
for(let $button of $bar.querySelectorAll("button")) {
|
|
896
994
|
$button.__gobj__ = gobj;
|
|
897
|
-
if(
|
|
995
|
+
if(in_flight) {
|
|
898
996
|
$button.disabled = true;
|
|
899
997
|
}
|
|
900
998
|
}
|
|
@@ -918,19 +1016,25 @@ function render(gobj)
|
|
|
918
1016
|
}
|
|
919
1017
|
render_toolbar(gobj);
|
|
920
1018
|
|
|
1019
|
+
/* A screen with nothing to draw takes the drawing down too: left
|
|
1020
|
+
* alive, it was only re-shown on the way back -- into a body that
|
|
1021
|
+
* no longer held it. */
|
|
921
1022
|
if(state === "ST_LOADING") {
|
|
922
1023
|
show_notice(gobj, "loading the schemas");
|
|
1024
|
+
destroy_diagram(gobj);
|
|
923
1025
|
clear($body);
|
|
924
1026
|
return;
|
|
925
1027
|
}
|
|
926
1028
|
if(state === "ST_IDLE") {
|
|
927
1029
|
show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected",
|
|
928
1030
|
priv.load_error);
|
|
1031
|
+
destroy_diagram(gobj);
|
|
929
1032
|
clear($body);
|
|
930
1033
|
return;
|
|
931
1034
|
}
|
|
932
1035
|
if(state === "ST_EMPTY") {
|
|
933
1036
|
show_notice(gobj, "this yuno stores no schema");
|
|
1037
|
+
destroy_diagram(gobj);
|
|
934
1038
|
clear($body);
|
|
935
1039
|
return;
|
|
936
1040
|
}
|
|
@@ -1471,12 +1575,21 @@ function render_diagram(gobj)
|
|
|
1471
1575
|
let $body = $of(gobj, ".SCHEMA_BODY");
|
|
1472
1576
|
let treedb = current_treedb(gobj);
|
|
1473
1577
|
|
|
1474
|
-
if(!$body
|
|
1578
|
+
if(!$body) {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
/* Said on an EMPTY body, as the topics and the columns say it:
|
|
1582
|
+
* render() clears the body for every screen but this one, and the
|
|
1583
|
+
* screen before stayed up under the notice, clickable. */
|
|
1584
|
+
if(!treedb) {
|
|
1585
|
+
destroy_diagram(gobj);
|
|
1586
|
+
clear($body);
|
|
1475
1587
|
show_notice(gobj, "that treedb is not here any more", priv.treedb_id);
|
|
1476
1588
|
return;
|
|
1477
1589
|
}
|
|
1478
1590
|
if(!gclass_find_by_name("C_YUI_TREEDB_SCHEMA")) {
|
|
1479
1591
|
log_error(`${gobj_short_name(gobj)}: C_YUI_TREEDB_SCHEMA not registered by the app`);
|
|
1592
|
+
clear($body);
|
|
1480
1593
|
show_notice(gobj, "the schema drawing is not available");
|
|
1481
1594
|
return;
|
|
1482
1595
|
}
|
|
@@ -1571,6 +1684,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix)
|
|
|
1571
1684
|
let priv = gobj.priv;
|
|
1572
1685
|
|
|
1573
1686
|
close_dialog(gobj);
|
|
1687
|
+
priv.dialog_gen = priv.model_gen;
|
|
1574
1688
|
priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), $content, {
|
|
1575
1689
|
dialog: true,
|
|
1576
1690
|
title: title,
|
|
@@ -1619,6 +1733,10 @@ function name_control($spec, label)
|
|
|
1619
1733
|
$spec[1]["aria-label"] = t(label);
|
|
1620
1734
|
$spec[1]["data-i18n-aria-label"] = label;
|
|
1621
1735
|
}
|
|
1736
|
+
if(!$spec[1].title) {
|
|
1737
|
+
$spec[1].title = t(label);
|
|
1738
|
+
$spec[1]["data-i18n-title"] = label;
|
|
1739
|
+
}
|
|
1622
1740
|
return true;
|
|
1623
1741
|
}
|
|
1624
1742
|
for(let child of ($spec[2] || [])) {
|
|
@@ -1629,6 +1747,19 @@ function name_control($spec, label)
|
|
|
1629
1747
|
return false;
|
|
1630
1748
|
}
|
|
1631
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
|
+
|
|
1632
1763
|
function field(logical, name, label, $control, help)
|
|
1633
1764
|
{
|
|
1634
1765
|
/* The label is a SIBLING of the control -- Bulma's `field` shape --
|
|
@@ -1782,6 +1913,7 @@ function open_column_form(gobj, topic, col, prefill)
|
|
|
1782
1913
|
let hook_col = hook_topic ? hook[hook_topic] : "";
|
|
1783
1914
|
let treedb = current_treedb(gobj);
|
|
1784
1915
|
let sibling_topics = treedb ? treedb.topics.map(x => x.name) : [];
|
|
1916
|
+
let model_gen = priv.model_gen;
|
|
1785
1917
|
|
|
1786
1918
|
let $form = createElement2(
|
|
1787
1919
|
["div", {class: "SCHEMA_COL_FORM box"}, [
|
|
@@ -1823,9 +1955,10 @@ function open_column_form(gobj, topic, col, prefill)
|
|
|
1823
1955
|
text_input("description", record.description, "")),
|
|
1824
1956
|
["div", {class: "SCHEMA_COL_FORM_ACTIONS is-flex mt-4",
|
|
1825
1957
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
1826
|
-
["button", {class: "SCHEMA_COL_FORM_CANCEL button", type: "button"},
|
|
1958
|
+
["button", name_attrs("cancel", {class: "SCHEMA_COL_FORM_CANCEL button", type: "button"}),
|
|
1827
1959
|
[["span", {i18n: "cancel"}, t("cancel")]]],
|
|
1828
|
-
["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"}),
|
|
1829
1962
|
[["span", {class: "icon"}, [["i", {class: "yi-floppy-disk"}]]],
|
|
1830
1963
|
["span", {i18n: "save"}, t("save")]]]
|
|
1831
1964
|
]]
|
|
@@ -1866,10 +1999,11 @@ function open_column_form(gobj, topic, col, prefill)
|
|
|
1866
1999
|
let values = read_form($form);
|
|
1867
2000
|
values.flag = read_flags($form);
|
|
1868
2001
|
gobj_send_event(gobj, "EV_SAVE_COLUMN", {
|
|
1869
|
-
topic:
|
|
1870
|
-
col:
|
|
1871
|
-
creating:
|
|
1872
|
-
values:
|
|
2002
|
+
topic: topic.name,
|
|
2003
|
+
col: creating ? "" : col.name,
|
|
2004
|
+
creating: creating,
|
|
2005
|
+
values: values,
|
|
2006
|
+
model_gen: model_gen
|
|
1873
2007
|
}, gobj);
|
|
1874
2008
|
});
|
|
1875
2009
|
|
|
@@ -1902,6 +2036,7 @@ function open_topic_form(gobj, treedb, topic)
|
|
|
1902
2036
|
let record = (topic && topic.record) || {};
|
|
1903
2037
|
let creating = !topic;
|
|
1904
2038
|
let col_names = topic ? topic.cols.map(c => c.name) : [];
|
|
2039
|
+
let model_gen = gobj.priv.model_gen;
|
|
1905
2040
|
|
|
1906
2041
|
let $form = createElement2(
|
|
1907
2042
|
["div", {class: "SCHEMA_TOPIC_FORM box"}, [
|
|
@@ -1941,10 +2076,10 @@ function open_topic_form(gobj, treedb, topic)
|
|
|
1941
2076
|
]],
|
|
1942
2077
|
["div", {class: "SCHEMA_TOPIC_FORM_ACTIONS is-flex mt-4",
|
|
1943
2078
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
1944
|
-
["button", {class: "SCHEMA_TOPIC_FORM_CANCEL button", type: "button"},
|
|
2079
|
+
["button", name_attrs("cancel", {class: "SCHEMA_TOPIC_FORM_CANCEL button", type: "button"}),
|
|
1945
2080
|
[["span", {i18n: "cancel"}, t("cancel")]]],
|
|
1946
|
-
["button", {class: "SCHEMA_TOPIC_FORM_SAVE button is-primary",
|
|
1947
|
-
|
|
2081
|
+
["button", name_attrs("save", {class: "SCHEMA_TOPIC_FORM_SAVE button is-primary",
|
|
2082
|
+
type: "button"}),
|
|
1948
2083
|
[["span", {class: "icon"}, [["i", {class: "yi-floppy-disk"}]]],
|
|
1949
2084
|
["span", {i18n: "save"}, t("save")]]]
|
|
1950
2085
|
]]
|
|
@@ -1958,9 +2093,10 @@ function open_topic_form(gobj, treedb, topic)
|
|
|
1958
2093
|
$form.querySelector(".SCHEMA_TOPIC_FORM_SAVE").addEventListener("click", (evt) => {
|
|
1959
2094
|
evt.stopPropagation();
|
|
1960
2095
|
gobj_send_event(gobj, "EV_SAVE_TOPIC", {
|
|
1961
|
-
topic:
|
|
1962
|
-
creating:
|
|
1963
|
-
values:
|
|
2096
|
+
topic: creating ? "" : topic.name,
|
|
2097
|
+
creating: creating,
|
|
2098
|
+
values: read_form($form),
|
|
2099
|
+
model_gen: model_gen
|
|
1964
2100
|
}, gobj);
|
|
1965
2101
|
});
|
|
1966
2102
|
|
|
@@ -2113,14 +2249,15 @@ function copy_text(gobj, $text)
|
|
|
2113
2249
|
***************************************************************/
|
|
2114
2250
|
function open_import(gobj, treedb)
|
|
2115
2251
|
{
|
|
2252
|
+
let model_gen = gobj.priv.model_gen;
|
|
2116
2253
|
let $content = createElement2(
|
|
2117
2254
|
["div", {class: "SCHEMA_IMPORT box"}, [
|
|
2118
2255
|
["p", {class: "SCHEMA_IMPORT_HELP help mb-2",
|
|
2119
2256
|
i18n: "paste a schema as json: the plan is shown before anything is written"},
|
|
2120
2257
|
t("paste a schema as json: the plan is shown before anything is written")],
|
|
2121
|
-
["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",
|
|
2122
2259
|
rows: "12", spellcheck: "false",
|
|
2123
|
-
placeholder: '{"id": "...", "schema_version": "1", "topics": []}'}, ""],
|
|
2260
|
+
placeholder: '{"id": "...", "schema_version": "1", "topics": []}'}), ""],
|
|
2124
2261
|
["label", {class: "SCHEMA_IMPORT_PRUNE checkbox mt-2"}, [
|
|
2125
2262
|
["input", {type: "checkbox", class: "SCHEMA_IMPORT_PRUNE_BOX", checked: "checked"}],
|
|
2126
2263
|
["span", {class: "ml-1", i18n: "delete what the pasted schema does not declare"},
|
|
@@ -2129,13 +2266,13 @@ function open_import(gobj, treedb)
|
|
|
2129
2266
|
["div", {class: "SCHEMA_IMPORT_PLAN mt-3"}, []],
|
|
2130
2267
|
["div", {class: "SCHEMA_IMPORT_ACTIONS is-flex mt-3",
|
|
2131
2268
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
2132
|
-
["button", {class: "SCHEMA_IMPORT_CANCEL button", type: "button"},
|
|
2269
|
+
["button", name_attrs("cancel", {class: "SCHEMA_IMPORT_CANCEL button", type: "button"}),
|
|
2133
2270
|
[["span", {i18n: "cancel"}, t("cancel")]]],
|
|
2134
|
-
["button", {class: "SCHEMA_IMPORT_PREVIEW button", type: "button"},
|
|
2271
|
+
["button", name_attrs("preview", {class: "SCHEMA_IMPORT_PREVIEW button", type: "button"}),
|
|
2135
2272
|
[["span", {class: "icon"}, [["i", {class: "yi-eye"}]]],
|
|
2136
2273
|
["span", {i18n: "preview"}, t("preview")]]],
|
|
2137
|
-
["button", {class: "SCHEMA_IMPORT_RUN button is-warning",
|
|
2138
|
-
|
|
2274
|
+
["button", name_attrs("import", {class: "SCHEMA_IMPORT_RUN button is-warning",
|
|
2275
|
+
type: "button", disabled: "disabled"}),
|
|
2139
2276
|
[["span", {class: "icon"}, [["i", {class: "yi-upload"}]]],
|
|
2140
2277
|
["span", {i18n: "import"}, t("import")]]]
|
|
2141
2278
|
]]
|
|
@@ -2154,13 +2291,14 @@ function open_import(gobj, treedb)
|
|
|
2154
2291
|
$content.querySelector(".SCHEMA_IMPORT_PREVIEW").addEventListener("click", (evt) => {
|
|
2155
2292
|
evt.stopPropagation();
|
|
2156
2293
|
gobj_send_event(gobj, "EV_PREVIEW_IMPORT", {
|
|
2157
|
-
text:
|
|
2158
|
-
prune:
|
|
2294
|
+
text: $text.value,
|
|
2295
|
+
prune: $prune.checked,
|
|
2296
|
+
model_gen: model_gen
|
|
2159
2297
|
}, gobj);
|
|
2160
2298
|
});
|
|
2161
2299
|
$run.addEventListener("click", (evt) => {
|
|
2162
2300
|
evt.stopPropagation();
|
|
2163
|
-
gobj_send_event(gobj, "EV_APPLY_IMPORT", {}, gobj);
|
|
2301
|
+
gobj_send_event(gobj, "EV_APPLY_IMPORT", {model_gen: model_gen}, gobj);
|
|
2164
2302
|
});
|
|
2165
2303
|
|
|
2166
2304
|
gobj.priv.import_pane = $plan_pane;
|
|
@@ -2168,6 +2306,23 @@ function open_import(gobj, treedb)
|
|
|
2168
2306
|
open_dialog(gobj, $content, "import", "SCHEMA_IMPORT_DIALOG", treedb.id);
|
|
2169
2307
|
}
|
|
2170
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
|
+
|
|
2171
2326
|
/***************************************************************
|
|
2172
2327
|
* The plan, drawn where the paste is: one line per write, the
|
|
2173
2328
|
* deletions apart and counted, because they are what the
|
|
@@ -2244,7 +2399,7 @@ function open_orphans(gobj)
|
|
|
2244
2399
|
["td", {class: "SCHEMA_ORPHAN_ID"}, [["code", {}, `${entry.id}`]]],
|
|
2245
2400
|
["td", {class: "SCHEMA_ORPHAN_ACTIONS has-text-right"},
|
|
2246
2401
|
readonly ? [] : [row_icon(gobj, "SCHEMA_ORPHAN_DELETE", "yi-trash", "delete",
|
|
2247
|
-
"EV_DELETE_ORPHAN", {kind: kind, id: entry.id})]]
|
|
2402
|
+
"EV_DELETE_ORPHAN", {kind: kind, id: entry.id, model_gen: priv.model_gen})]]
|
|
2248
2403
|
]];
|
|
2249
2404
|
};
|
|
2250
2405
|
|
|
@@ -2459,6 +2614,11 @@ function transport_dropped(gobj)
|
|
|
2459
2614
|
priv.load_round++;
|
|
2460
2615
|
priv.pending = 0;
|
|
2461
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
|
+
}
|
|
2462
2622
|
return end_load(gobj);
|
|
2463
2623
|
}
|
|
2464
2624
|
return 0;
|
|
@@ -2470,6 +2630,12 @@ function transport_dropped(gobj)
|
|
|
2470
2630
|
* in flight (ac_show() kept it), or the one it had. Without a
|
|
2471
2631
|
* model there is no screen to go to: the position is kept for the
|
|
2472
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.
|
|
2473
2639
|
***************************************************************/
|
|
2474
2640
|
function end_load(gobj)
|
|
2475
2641
|
{
|
|
@@ -2477,6 +2643,13 @@ function end_load(gobj)
|
|
|
2477
2643
|
let seg = priv.pending_seg;
|
|
2478
2644
|
|
|
2479
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
|
+
}
|
|
2480
2653
|
if(priv.model) {
|
|
2481
2654
|
if(seg !== null) {
|
|
2482
2655
|
return apply_seg(gobj, seg);
|
|
@@ -2501,29 +2674,15 @@ function end_writes(gobj, error)
|
|
|
2501
2674
|
{
|
|
2502
2675
|
let priv = gobj.priv;
|
|
2503
2676
|
|
|
2677
|
+
let seg = priv.pending_seg;
|
|
2678
|
+
|
|
2504
2679
|
priv.save_queue = [];
|
|
2680
|
+
priv.pending_seg = null;
|
|
2505
2681
|
gobj_change_state(gobj, priv.save_return || "ST_TREEDBS");
|
|
2506
2682
|
priv.save_return = "";
|
|
2507
2683
|
show_notice(gobj, "");
|
|
2508
2684
|
set_busy(gobj, false);
|
|
2509
2685
|
|
|
2510
|
-
/* A position that arrived while the write was in flight: applied
|
|
2511
|
-
* now, so a Back pressed mid-save is not simply lost. */
|
|
2512
|
-
if(priv.pending_seg !== null) {
|
|
2513
|
-
let seg = priv.pending_seg;
|
|
2514
|
-
priv.pending_seg = null;
|
|
2515
|
-
if(!error) {
|
|
2516
|
-
apply_seg(gobj, seg);
|
|
2517
|
-
if(priv.save_wrote) {
|
|
2518
|
-
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
2519
|
-
treedb_name: gobj_read_str_attr(gobj, "treedb_name")
|
|
2520
|
-
});
|
|
2521
|
-
}
|
|
2522
|
-
priv.save_wrote = false;
|
|
2523
|
-
return reload_if_owed(gobj);
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
2686
|
if(error) {
|
|
2528
2687
|
yui_shell_show_error(yui_shell_of(gobj), error, {t: t});
|
|
2529
2688
|
}
|
|
@@ -2533,8 +2692,18 @@ function end_writes(gobj, error)
|
|
|
2533
2692
|
});
|
|
2534
2693
|
}
|
|
2535
2694
|
priv.save_wrote = false;
|
|
2536
|
-
|
|
2537
|
-
|
|
2695
|
+
|
|
2696
|
+
/* A position that arrived while the write was in flight: applied
|
|
2697
|
+
* now, so a Back pressed mid-save is not simply lost -- with an
|
|
2698
|
+
* error too. The host's url already says it, and a load cut by a
|
|
2699
|
+
* drop applies it (end_load()): the write kept the old screen, and
|
|
2700
|
+
* the url and the view disagreed. */
|
|
2701
|
+
if(seg !== null) {
|
|
2702
|
+
apply_seg(gobj, seg);
|
|
2703
|
+
} else {
|
|
2704
|
+
render(gobj);
|
|
2705
|
+
publish_check(gobj);
|
|
2706
|
+
}
|
|
2538
2707
|
reload_if_owed(gobj);
|
|
2539
2708
|
return error ? -1 : 0;
|
|
2540
2709
|
}
|
|
@@ -2556,7 +2725,7 @@ function reload_if_owed(gobj)
|
|
|
2556
2725
|
priv.reload_on_open = true;
|
|
2557
2726
|
return 0;
|
|
2558
2727
|
}
|
|
2559
|
-
return request_model(gobj);
|
|
2728
|
+
return request_model(gobj, true);
|
|
2560
2729
|
}
|
|
2561
2730
|
|
|
2562
2731
|
/***************************************************************
|
|
@@ -2660,6 +2829,9 @@ function go(gobj, treedb_id, topic_name, diagram, mirror)
|
|
|
2660
2829
|
{
|
|
2661
2830
|
let priv = gobj.priv;
|
|
2662
2831
|
|
|
2832
|
+
/* Moving IS the answer to a position that waited: one left behind
|
|
2833
|
+
* sent a later load back to it. */
|
|
2834
|
+
priv.pending_seg = null;
|
|
2663
2835
|
priv.treedb_id = treedb_id || "";
|
|
2664
2836
|
priv.topic_name = topic_name || "";
|
|
2665
2837
|
priv.diagram = !!diagram;
|
|
@@ -2826,6 +2998,23 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2826
2998
|
return 0;
|
|
2827
2999
|
}
|
|
2828
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
|
+
}
|
|
2829
3018
|
priv.model = null;
|
|
2830
3019
|
return end_load(gobj);
|
|
2831
3020
|
}
|
|
@@ -2834,6 +3023,8 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2834
3023
|
* (NOT in build_model(): that also runs on the patch after every
|
|
2835
3024
|
* write, which is exactly when the undo has to survive.) */
|
|
2836
3025
|
priv.order_undo = {};
|
|
3026
|
+
priv.records_before = null;
|
|
3027
|
+
priv.model_gen++;
|
|
2837
3028
|
build_model(gobj);
|
|
2838
3029
|
/* Same reason as order_undo above: this is the LOAD, and the
|
|
2839
3030
|
* baseline is what a later "the version did not move" is measured
|
|
@@ -2857,7 +3048,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2857
3048
|
`for a write that is over (result ${result}): ignored`);
|
|
2858
3049
|
return 0;
|
|
2859
3050
|
}
|
|
2860
|
-
return late_write_done(gobj, command, topic_name);
|
|
3051
|
+
return late_write_done(gobj, command, topic_name, data);
|
|
2861
3052
|
}
|
|
2862
3053
|
if(result < 0) {
|
|
2863
3054
|
return end_writes(gobj, comment || t("the treedb refused the write"));
|
|
@@ -2872,10 +3063,33 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2872
3063
|
patch_record(gobj, topic_name, record);
|
|
2873
3064
|
} else {
|
|
2874
3065
|
/* The treedb wrote it and did not describe it back: ask
|
|
2875
|
-
* again rather than draw a model that has drifted.
|
|
2876
|
-
|
|
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
|
+
}
|
|
3081
|
+
if(topic_name === T_TOPICS || topic_name === T_COLS) {
|
|
3082
|
+
let write = priv.save_queue[0];
|
|
3083
|
+
mark_written(gobj, topic_name, write ? write.record : null);
|
|
3084
|
+
}
|
|
3085
|
+
priv.reload_after_write = false;
|
|
2877
3086
|
priv.save_queue = [];
|
|
2878
|
-
|
|
3087
|
+
priv.save_return = "";
|
|
3088
|
+
priv.save_wrote = false;
|
|
3089
|
+
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
3090
|
+
treedb_name: gobj_read_str_attr(gobj, "treedb_name")
|
|
3091
|
+
});
|
|
3092
|
+
request_model(gobj, true);
|
|
2879
3093
|
return 0;
|
|
2880
3094
|
}
|
|
2881
3095
|
}
|
|
@@ -2907,14 +3121,24 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2907
3121
|
* the view was answered once, as failed -- and echoes the node
|
|
2908
3122
|
* event instead, which this view does not hear: there, Refresh is
|
|
2909
3123
|
* what reads the store.
|
|
3124
|
+
*
|
|
3125
|
+
* It is a WRITE like any other for the host (EV_RECORD_WRITTEN):
|
|
3126
|
+
* the host was told nothing when it was given up, and it owns the
|
|
3127
|
+
* Save that publishes it. The reload keeps what this session wrote
|
|
3128
|
+
* (request_model()), and the record it answered marks its topic.
|
|
2910
3129
|
***************************************************************/
|
|
2911
|
-
function late_write_done(gobj, command, topic_name)
|
|
3130
|
+
function late_write_done(gobj, command, topic_name, data)
|
|
2912
3131
|
{
|
|
2913
3132
|
let priv = gobj.priv;
|
|
2914
3133
|
let state = gobj_current_state(gobj);
|
|
2915
3134
|
|
|
2916
3135
|
log_warning(`${gobj_short_name(gobj)}: '${command}' of '${topic_name}' was given up, ` +
|
|
2917
3136
|
`and it was DONE: the model is read again`);
|
|
3137
|
+
mark_written_record(gobj, topic_name, is_object(data) ? data
|
|
3138
|
+
: (Array.isArray(data) && is_object(data[0]) ? data[0] : null));
|
|
3139
|
+
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
3140
|
+
treedb_name: gobj_read_str_attr(gobj, "treedb_name")
|
|
3141
|
+
});
|
|
2918
3142
|
if(state === "ST_LOADING") {
|
|
2919
3143
|
return 0;
|
|
2920
3144
|
}
|
|
@@ -2926,7 +3150,38 @@ function late_write_done(gobj, command, topic_name)
|
|
|
2926
3150
|
priv.reload_on_open = true;
|
|
2927
3151
|
return 0;
|
|
2928
3152
|
}
|
|
2929
|
-
return request_model(gobj);
|
|
3153
|
+
return request_model(gobj, true);
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
/***************************************************************
|
|
3157
|
+
* The topic a record written belongs to, read off the RECORD and
|
|
3158
|
+
* not off the screen: a write answered late may be about a topic
|
|
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).
|
|
3167
|
+
***************************************************************/
|
|
3168
|
+
function mark_written_record(gobj, topic_name, record)
|
|
3169
|
+
{
|
|
3170
|
+
let priv = gobj.priv;
|
|
3171
|
+
|
|
3172
|
+
if(!record) {
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
if(topic_name === T_TOPICS && typeof record.id === "string" && record.id) {
|
|
3176
|
+
priv.written[record.id] = true;
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
if(topic_name !== T_COLS) {
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
for(let ref of parse_fkey_ref(record.topics)) {
|
|
3183
|
+
priv.written[ref.id] = true;
|
|
3184
|
+
}
|
|
2930
3185
|
}
|
|
2931
3186
|
|
|
2932
3187
|
/***************************************************************
|
|
@@ -3004,7 +3259,7 @@ function ac_transport_state(gobj, event, kw, src)
|
|
|
3004
3259
|
if((!priv.model || priv.reload_on_open) &&
|
|
3005
3260
|
state !== "ST_LOADING" && state !== "ST_SAVING") {
|
|
3006
3261
|
priv.reload_on_open = false;
|
|
3007
|
-
return request_model(gobj);
|
|
3262
|
+
return request_model(gobj, true);
|
|
3008
3263
|
}
|
|
3009
3264
|
render_toolbar(gobj);
|
|
3010
3265
|
return 0;
|
|
@@ -3023,7 +3278,33 @@ function ac_language_changed(gobj, event, kw, src)
|
|
|
3023
3278
|
|
|
3024
3279
|
function ac_refresh(gobj, event, kw, src)
|
|
3025
3280
|
{
|
|
3026
|
-
return request_model(gobj);
|
|
3281
|
+
return request_model(gobj, false);
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
/***************************************************************
|
|
3285
|
+
* What a DIALOG sends while the model is being read again: the
|
|
3286
|
+
* Save of a form left open when the reload started, a
|
|
3287
|
+
* confirmation, an import. The loading screen took the rest of
|
|
3288
|
+
* the view away (request_model()); a dialog is the one thing the
|
|
3289
|
+
* operator can still reach, and what it asks is computed against
|
|
3290
|
+
* a model the answers in the air are about to replace.
|
|
3291
|
+
*
|
|
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.
|
|
3301
|
+
***************************************************************/
|
|
3302
|
+
function ac_wait_for_the_load(gobj, event, kw, src)
|
|
3303
|
+
{
|
|
3304
|
+
log_warning(`${gobj_short_name(gobj)}: ${event} refused, the schemas are loading`);
|
|
3305
|
+
yui_shell_show_error(yui_shell_of(gobj),
|
|
3306
|
+
"the schemas are loading: wait for them", {t: t});
|
|
3307
|
+
return -1;
|
|
3027
3308
|
}
|
|
3028
3309
|
|
|
3029
3310
|
/***************************************************************
|
|
@@ -3116,6 +3397,7 @@ function ac_show_orphans(gobj, event, kw, src)
|
|
|
3116
3397
|
***************************************************************/
|
|
3117
3398
|
function confirm_then(gobj, message, detail, kw)
|
|
3118
3399
|
{
|
|
3400
|
+
kw = Object.assign({model_gen: gobj.priv.model_gen}, kw);
|
|
3119
3401
|
yui_shell_confirm_danger(yui_shell_of(gobj), message, {
|
|
3120
3402
|
t: t,
|
|
3121
3403
|
detail: detail
|
|
@@ -3201,6 +3483,9 @@ function ac_save_column(gobj, event, kw, src)
|
|
|
3201
3483
|
if(refuse_if_readonly(gobj, event)) {
|
|
3202
3484
|
return -1;
|
|
3203
3485
|
}
|
|
3486
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3487
|
+
return -1;
|
|
3488
|
+
}
|
|
3204
3489
|
if(!topic || (!kw.creating && !col)) {
|
|
3205
3490
|
log_error(`${gobj_short_name(gobj)}: ${event} names no column: ${kw.topic}.${kw.col}`);
|
|
3206
3491
|
return -1;
|
|
@@ -3355,6 +3640,9 @@ function ac_save_topic(gobj, event, kw, src)
|
|
|
3355
3640
|
if(refuse_if_readonly(gobj, event)) {
|
|
3356
3641
|
return -1;
|
|
3357
3642
|
}
|
|
3643
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3644
|
+
return -1;
|
|
3645
|
+
}
|
|
3358
3646
|
if(!treedb || (!kw.creating && !topic)) {
|
|
3359
3647
|
log_error(`${gobj_short_name(gobj)}: ${event} names no topic: ${kw.topic}`);
|
|
3360
3648
|
return -1;
|
|
@@ -3409,6 +3697,9 @@ function ac_delete_orphan(gobj, event, kw, src)
|
|
|
3409
3697
|
if(refuse_if_readonly(gobj, event)) {
|
|
3410
3698
|
return -1;
|
|
3411
3699
|
}
|
|
3700
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3701
|
+
return -1;
|
|
3702
|
+
}
|
|
3412
3703
|
if(empty_string(kw.id)) {
|
|
3413
3704
|
log_error(`${gobj_short_name(gobj)}: ${event} names nothing`);
|
|
3414
3705
|
return -1;
|
|
@@ -3481,6 +3772,9 @@ function ac_preview_import(gobj, event, kw, src)
|
|
|
3481
3772
|
let treedb = current_treedb(gobj);
|
|
3482
3773
|
let incoming = null;
|
|
3483
3774
|
|
|
3775
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3776
|
+
return -1;
|
|
3777
|
+
}
|
|
3484
3778
|
if(!treedb) {
|
|
3485
3779
|
log_error(`${gobj_short_name(gobj)}: ${event} with no treedb open`);
|
|
3486
3780
|
return -1;
|
|
@@ -3508,6 +3802,9 @@ function ac_apply_import(gobj, event, kw, src)
|
|
|
3508
3802
|
if(refuse_if_readonly(gobj, event)) {
|
|
3509
3803
|
return -1;
|
|
3510
3804
|
}
|
|
3805
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3806
|
+
return -1;
|
|
3807
|
+
}
|
|
3511
3808
|
if(!plan || plan.writes.length === 0) {
|
|
3512
3809
|
log_error(`${gobj_short_name(gobj)}: ${event} with no plan to run`);
|
|
3513
3810
|
return -1;
|
|
@@ -3558,6 +3855,9 @@ function ac_confirmed(gobj, event, kw, src)
|
|
|
3558
3855
|
if(refuse_if_readonly(gobj, event)) {
|
|
3559
3856
|
return -1;
|
|
3560
3857
|
}
|
|
3858
|
+
if(built_on_a_replaced_model(gobj, event, kw)) {
|
|
3859
|
+
return -1;
|
|
3860
|
+
}
|
|
3561
3861
|
if(kw.what === "import") {
|
|
3562
3862
|
return run_import(gobj);
|
|
3563
3863
|
}
|
|
@@ -3655,7 +3955,14 @@ function create_gclass(gclass_name)
|
|
|
3655
3955
|
const states = [
|
|
3656
3956
|
["ST_IDLE", COMMON.slice()],
|
|
3657
3957
|
|
|
3658
|
-
["ST_LOADING", COMMON.
|
|
3958
|
+
["ST_LOADING", COMMON.concat([
|
|
3959
|
+
["EV_SAVE_COLUMN", ac_wait_for_the_load, null],
|
|
3960
|
+
["EV_SAVE_TOPIC", ac_wait_for_the_load, null],
|
|
3961
|
+
["EV_DELETE_ORPHAN", ac_wait_for_the_load, null],
|
|
3962
|
+
["EV_PREVIEW_IMPORT", ac_wait_for_the_load, null],
|
|
3963
|
+
["EV_APPLY_IMPORT", ac_wait_for_the_load, null],
|
|
3964
|
+
["EV_CONFIRMED", ac_wait_for_the_load, null]
|
|
3965
|
+
])],
|
|
3659
3966
|
|
|
3660
3967
|
["ST_EMPTY", COMMON.slice()],
|
|
3661
3968
|
|