@yuneta/gobj-ui 7.25.11 → 7.25.13
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 +38 -4
- package/dist/gobj-ui.cjs.js +106 -21
- package/dist/gobj-ui.es.js +106 -21
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +83 -19
- package/src/c_yui_treedb_topics.js +61 -10
- package/src/form_writes_in_flight.js +11 -3
- package/src/schema_editor_reload.wiring.test.js +187 -1
- package/src/treedb_topics_transport.wiring.test.js +116 -3
package/README.md
CHANGED
|
@@ -819,6 +819,26 @@ A write is known by its topic AND its serial (`<topic>^<form_write>`): each
|
|
|
819
819
|
topic's form counts its serials from 1, and keyed by the serial alone two
|
|
820
820
|
forms saving at once shared one entry.
|
|
821
821
|
|
|
822
|
+
**A write cut by the drop is not a refusal** (7.25.13). A refused write
|
|
823
|
+
reads its topic again at once, so a cell edited in place goes back to what the
|
|
824
|
+
store has. A write that fails while the transport is NOT in session is the
|
|
825
|
+
drop: a routing adapter (gui_agent's `C_AGENT_TREEDB_LINK`) settles what it
|
|
826
|
+
had in flight when its session closes. The topic cannot be read then — the
|
|
827
|
+
transport refuses the read (*"cannot route 'nodes' -- not in session"*) — so
|
|
828
|
+
the view logs a warning and owes the read. Writes in flight on the
|
|
829
|
+
disconnect edge are owed the same way. The first edge that finds the
|
|
830
|
+
transport in session again pays it: each owed topic whose table is open is
|
|
831
|
+
read once. An `EV_CONNECTION_STATE` "up" that comes before the view's own
|
|
832
|
+
transport is in session leaves the read owed. A form is answered once, also
|
|
833
|
+
when the edge answered it before the failure arrived.
|
|
834
|
+
|
|
835
|
+
```js
|
|
836
|
+
// Session up: a cell edit goes out as update-node.
|
|
837
|
+
// Session drops; the adapter answers it {result: -1} -> no read, a warning
|
|
838
|
+
// EV_TRANSPORT_STATE {connected: true}, transport in session
|
|
839
|
+
// -> `nodes` of that topic, once
|
|
840
|
+
```
|
|
841
|
+
|
|
822
842
|
**The pkey2 of an update goes back as the record had it** (7.25.5). It names
|
|
823
843
|
the instance the write is for, and a `time` pkey2 that crossed the form's
|
|
824
844
|
`datetime-local` came back without its seconds — another instance, or none.
|
|
@@ -2134,6 +2154,11 @@ And what the toolbar offers, each answering a question the storage could not:
|
|
|
2134
2154
|
answer. Escaping crosses two layers and the second is not JSON's:
|
|
2135
2155
|
`helper_quote2doublequote()` rewrites *every* single quote before the parse,
|
|
2136
2156
|
so a quote inside a value can only survive as `\u0027`.
|
|
2157
|
+
Its C / JSON switch is two named buttons, and a click on one is an event,
|
|
2158
|
+
`EV_EXPORT_VIEW` (`{pane: "c" | "json"}`), answered in every state: the
|
|
2159
|
+
texts were composed when the dialog opened, and the dialog outlives the
|
|
2160
|
+
screen and the loads under it (7.25.12; before, the switch ran in the DOM
|
|
2161
|
+
handler and the `machine` trace did not show it).
|
|
2137
2162
|
- **import** — the writes that make the stored schema equal a pasted one, shown
|
|
2138
2163
|
as a **plan** before it runs. Import is the one operation here that can delete
|
|
2139
2164
|
a column, so what is confirmed is what runs.
|
|
@@ -2201,8 +2226,10 @@ else.
|
|
|
2201
2226
|
column form sends every field it shows, so its Save after a reload wrote
|
|
2202
2227
|
the OLD record over the newer one -- 7.25.8 told the operator to "try
|
|
2203
2228
|
again", which did exactly that. The import plan is forgotten when a load
|
|
2204
|
-
|
|
2205
|
-
it
|
|
2229
|
+
LANDS, with the model it was computed on (7.25.12; from 7.25.9 to 7.25.11
|
|
2230
|
+
it went when the load LEFT, so a load that then failed left the dialog up
|
|
2231
|
+
with no plan and Import disabled). A load that fails or never leaves keeps
|
|
2232
|
+
it, and Import stays enabled. A Yes that finds it gone is said (`the import plan is gone: preview it again`). A load that
|
|
2206
2233
|
failed or never left replaced nothing, and the dialog stays with what was
|
|
2207
2234
|
typed. A confirmation is a shell modal and outlives a load: its Yes in
|
|
2208
2235
|
`ST_EMPTY` or `ST_IDLE` is refused with the same words, not *"NOT
|
|
@@ -2225,7 +2252,12 @@ else.
|
|
|
2225
2252
|
orphans -- is refused with
|
|
2226
2253
|
`the schemas shown may be out of date: they are read again, try again when they are in`
|
|
2227
2254
|
and the model is read again (keeping the draft chips); a move goes where it
|
|
2228
|
-
was going and reads there
|
|
2255
|
+
was going and reads there -- the editor's own moves, and since 7.25.12 the
|
|
2256
|
+
host's too (`EV_SHOW` from a url or the browser's Back). **Any load that
|
|
2257
|
+
lands pays it** (7.25.12): the operator's Refresh, a late write's reload,
|
|
2258
|
+
the reconnect. Before, only the reload the editor asked for itself cleared
|
|
2259
|
+
it, so after a Refresh that landed the next edit was refused and read the
|
|
2260
|
+
store again. Not at once and not on a timer: a load refused
|
|
2229
2261
|
on a deadline asked again at once is refused the same way, and one asked
|
|
2230
2262
|
on a clock is polling. A Refresh asked while a load is in flight keeps the
|
|
2231
2263
|
records of the model shown (7.25.10), not the half the first load got.
|
|
@@ -2235,7 +2267,9 @@ else.
|
|
|
2235
2267
|
// the load failed in session: model kept, reload owed
|
|
2236
2268
|
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host); // refused, said; ST_LOADING
|
|
2237
2269
|
// ... the load lands: the same click now opens the form
|
|
2238
|
-
```
|
|
2270
|
+
```
|
|
2271
|
+
|
|
2272
|
+
A write answered with no record reloads the model and
|
|
2239
2273
|
says what it did not send after it
|
|
2240
2274
|
(`the treedb did not describe a write back: the writes after it were not sent`).
|
|
2241
2275
|
- The toolbar offers Diagram, Check, Export, Import, New topic and New column
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -23160,11 +23160,17 @@ function track_form_write(in_flight, form_write, topic_name) {
|
|
|
23160
23160
|
};
|
|
23161
23161
|
}
|
|
23162
23162
|
/************************************************************
|
|
23163
|
-
* A write was answered, one way or the other
|
|
23163
|
+
* A write was answered, one way or the other. True when it was
|
|
23164
|
+
* still in flight: false when something else answered it already
|
|
23165
|
+
* (the transport closing, abandon_form_writes), so its form must
|
|
23166
|
+
* not be answered twice.
|
|
23164
23167
|
************************************************************/
|
|
23165
23168
|
function settle_form_write(in_flight, form_write, topic_name) {
|
|
23166
|
-
if (!in_flight || !form_write) return;
|
|
23167
|
-
|
|
23169
|
+
if (!in_flight || !form_write) return false;
|
|
23170
|
+
const key = form_write_key(topic_name, form_write);
|
|
23171
|
+
if (!Object.prototype.hasOwnProperty.call(in_flight, key)) return false;
|
|
23172
|
+
delete in_flight[key];
|
|
23173
|
+
return true;
|
|
23168
23174
|
}
|
|
23169
23175
|
/************************************************************
|
|
23170
23176
|
* Nothing in flight can be answered any more: take them all,
|
|
@@ -23751,6 +23757,7 @@ var __gclass__$6 = null;
|
|
|
23751
23757
|
***************************************************************/
|
|
23752
23758
|
function mt_create$6(gobj) {
|
|
23753
23759
|
gobj.priv.form_writes = {};
|
|
23760
|
+
gobj.priv.reload_on_session = {};
|
|
23754
23761
|
gobj.priv.conn_shell = null;
|
|
23755
23762
|
build_ui$6(gobj);
|
|
23756
23763
|
let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
|
|
@@ -25181,9 +25188,15 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
25181
25188
|
yui_shell_show_error(yui_shell_of(gobj), comment, { t: i18next.t });
|
|
25182
25189
|
if (command === "update-node" || command === "create-node") {
|
|
25183
25190
|
let failed_topic = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0);
|
|
25184
|
-
|
|
25185
|
-
|
|
25186
|
-
|
|
25191
|
+
let form_write = (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0);
|
|
25192
|
+
if (failed_topic) {
|
|
25193
|
+
if (is_connected$1(gobj)) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TOPIC", { topic_name: failed_topic }, gobj);
|
|
25194
|
+
else {
|
|
25195
|
+
(0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the write of '${failed_topic}' was cut by the drop: the topic is read again when the session is back`);
|
|
25196
|
+
gobj.priv.reload_on_session[failed_topic] = true;
|
|
25197
|
+
}
|
|
25198
|
+
}
|
|
25199
|
+
if (settle_form_write(gobj.priv.form_writes, form_write, failed_topic)) answer_form_write(gobj, get_gobj_formtable(gobj, failed_topic), form_write, false);
|
|
25187
25200
|
}
|
|
25188
25201
|
}
|
|
25189
25202
|
return 0;
|
|
@@ -25383,11 +25396,31 @@ function ac_transport_state$2(gobj, event, kw, src) {
|
|
|
25383
25396
|
refresh_toolbar_buttons(gobj, connected);
|
|
25384
25397
|
if (!connected) abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
|
|
25385
25398
|
(0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
|
|
25399
|
+
if (w.topic_name) gobj.priv.reload_on_session[w.topic_name] = true;
|
|
25386
25400
|
answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
|
|
25387
25401
|
});
|
|
25402
|
+
else reload_owed_topics(gobj);
|
|
25388
25403
|
return 0;
|
|
25389
25404
|
}
|
|
25390
25405
|
/************************************************************
|
|
25406
|
+
* The session is back: read again the topics a drop left
|
|
25407
|
+
* unknown. Only when the TRANSPORT says so too -- the app's
|
|
25408
|
+
* connection (EV_CONNECTION_STATE) can report "up" before the
|
|
25409
|
+
* transport of this view is in session, and a read asked then is
|
|
25410
|
+
* refused. What is owed stays owed until an edge finds the
|
|
25411
|
+
* transport in session; each topic is read once.
|
|
25412
|
+
************************************************************/
|
|
25413
|
+
function reload_owed_topics(gobj) {
|
|
25414
|
+
let priv = gobj.priv;
|
|
25415
|
+
if (!is_connected$1(gobj)) return;
|
|
25416
|
+
let topics = Object.keys(priv.reload_on_session);
|
|
25417
|
+
priv.reload_on_session = {};
|
|
25418
|
+
topics.forEach((topic_name) => {
|
|
25419
|
+
if (!get_gobj_formtable(gobj, topic_name)) return;
|
|
25420
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TOPIC", { topic_name }, gobj);
|
|
25421
|
+
});
|
|
25422
|
+
}
|
|
25423
|
+
/************************************************************
|
|
25391
25424
|
* Parent (routing) inform us that we go hidden
|
|
25392
25425
|
************************************************************/
|
|
25393
25426
|
function ac_hide$4(gobj, event, kw, src) {
|
|
@@ -72220,7 +72253,8 @@ var PRIVATE_DATA$1 = {
|
|
|
72220
72253
|
pending_seg: null,
|
|
72221
72254
|
import_plan: null,
|
|
72222
72255
|
import_pane: null,
|
|
72223
|
-
import_run: null
|
|
72256
|
+
import_run: null,
|
|
72257
|
+
export_view: null
|
|
72224
72258
|
};
|
|
72225
72259
|
var __gclass__$1 = null;
|
|
72226
72260
|
/******************************
|
|
@@ -72405,7 +72439,12 @@ function built_on_a_replaced_model(gobj, event, kw) {
|
|
|
72405
72439
|
* export) -- is refused and said: it would be computed on what
|
|
72406
72440
|
* the store may no longer hold, and the load that lands closes a
|
|
72407
72441
|
* dialog built on the old model anyway (end_load()). A move is
|
|
72408
|
-
* not refused (reload_after_move()): it goes, and reads there
|
|
72442
|
+
* not refused (reload_after_move()): it goes, and reads there --
|
|
72443
|
+
* the editor's own moves and the host's alike (ac_show(): a url,
|
|
72444
|
+
* the browser's Back).
|
|
72445
|
+
*
|
|
72446
|
+
* Paid by ANY load that lands, whoever asked it
|
|
72447
|
+
* (ac_mt_command_answer()): the store has just been read whole.
|
|
72409
72448
|
*
|
|
72410
72449
|
* Not on a timer, and not at once: a load refused on a deadline
|
|
72411
72450
|
* asked again at once is refused the same way, and one asked on a
|
|
@@ -72447,9 +72486,10 @@ function reload_after_move(gobj) {
|
|
|
72447
72486
|
* left open is answered in ST_LOADING instead (ac_wait_for_the_load),
|
|
72448
72487
|
* and a load that LANDS closes it (end_load()): it was built on the
|
|
72449
72488
|
* model the load replaced, and its Save sends every field it shows.
|
|
72450
|
-
* An import plan is forgotten
|
|
72451
|
-
*
|
|
72452
|
-
*
|
|
72489
|
+
* An import plan is forgotten for the same reason, and at the same
|
|
72490
|
+
* moment: when the load LANDS and the model it was computed on goes
|
|
72491
|
+
* (ac_mt_command_answer()). A load that cannot leave, or that fails,
|
|
72492
|
+
* replaces nothing, and the plan stays with the dialog showing it.
|
|
72453
72493
|
*
|
|
72454
72494
|
* `keep_written`: a load nobody ASKED for -- the reconnect, a write
|
|
72455
72495
|
* that turned out done, the reload a refused load owed -- keeps what
|
|
@@ -72503,7 +72543,6 @@ function request_model(gobj, keep_written) {
|
|
|
72503
72543
|
end_load(gobj);
|
|
72504
72544
|
return -1;
|
|
72505
72545
|
}
|
|
72506
|
-
forget_import_plan(gobj);
|
|
72507
72546
|
priv.pending = left;
|
|
72508
72547
|
return 0;
|
|
72509
72548
|
}
|
|
@@ -73663,6 +73702,7 @@ function close_dialog(gobj) {
|
|
|
73663
73702
|
if (!priv.dialog) return;
|
|
73664
73703
|
let dialog = priv.dialog;
|
|
73665
73704
|
priv.dialog = null;
|
|
73705
|
+
priv.export_view = null;
|
|
73666
73706
|
try {
|
|
73667
73707
|
dialog.close();
|
|
73668
73708
|
} catch (e) {
|
|
@@ -73681,6 +73721,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
|
|
|
73681
73721
|
t: i18next.t,
|
|
73682
73722
|
on_close: () => {
|
|
73683
73723
|
priv.dialog = null;
|
|
73724
|
+
priv.export_view = null;
|
|
73684
73725
|
}
|
|
73685
73726
|
});
|
|
73686
73727
|
(0, _yuneta_gobj_js.refresh_language)($content, i18next.t);
|
|
@@ -74409,17 +74450,43 @@ function open_export(gobj, treedb) {
|
|
|
74409
74450
|
let $text = $content.querySelector(".SCHEMA_EXPORT_TEXT");
|
|
74410
74451
|
for (let $tab of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) $tab.addEventListener("click", (evt) => {
|
|
74411
74452
|
evt.stopPropagation();
|
|
74412
|
-
|
|
74413
|
-
$other.classList.toggle("selected_state", $other === $tab);
|
|
74414
|
-
$other.setAttribute("aria-pressed", $other === $tab ? "true" : "false");
|
|
74415
|
-
}
|
|
74416
|
-
$text.value = $tab.dataset.pane === "c" ? c_text : json_text;
|
|
74453
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EXPORT_VIEW", { pane: $tab.dataset.pane }, gobj);
|
|
74417
74454
|
});
|
|
74418
74455
|
$content.querySelector(".SCHEMA_EXPORT_COPY").addEventListener("click", (evt) => {
|
|
74419
74456
|
evt.stopPropagation();
|
|
74420
74457
|
copy_text(gobj, $text);
|
|
74421
74458
|
});
|
|
74422
74459
|
open_dialog(gobj, $content, "export", "SCHEMA_EXPORT_DIALOG", treedb.id);
|
|
74460
|
+
priv.export_view = {
|
|
74461
|
+
$content,
|
|
74462
|
+
texts: {
|
|
74463
|
+
c: c_text,
|
|
74464
|
+
json: json_text
|
|
74465
|
+
}
|
|
74466
|
+
};
|
|
74467
|
+
}
|
|
74468
|
+
/***************************************************************
|
|
74469
|
+
* Show one of the export's two texts, and say which one is shown
|
|
74470
|
+
* (`selected_state` + aria-pressed). Reached only through
|
|
74471
|
+
* EV_EXPORT_VIEW: a click is an action like any other.
|
|
74472
|
+
***************************************************************/
|
|
74473
|
+
function show_export_view(gobj, pane) {
|
|
74474
|
+
let view = gobj.priv.export_view;
|
|
74475
|
+
if (!view) {
|
|
74476
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: export view '${pane}' asked with no export open`);
|
|
74477
|
+
return -1;
|
|
74478
|
+
}
|
|
74479
|
+
if (!Object.prototype.hasOwnProperty.call(view.texts, pane)) {
|
|
74480
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: the export has no view '${pane}'`);
|
|
74481
|
+
return -1;
|
|
74482
|
+
}
|
|
74483
|
+
for (let $tab of view.$content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
|
|
74484
|
+
let shown = $tab.dataset.pane === pane;
|
|
74485
|
+
$tab.classList.toggle("selected_state", shown);
|
|
74486
|
+
$tab.setAttribute("aria-pressed", shown ? "true" : "false");
|
|
74487
|
+
}
|
|
74488
|
+
view.$content.querySelector(".SCHEMA_EXPORT_TEXT").value = view.texts[pane];
|
|
74489
|
+
return 0;
|
|
74423
74490
|
}
|
|
74424
74491
|
/***************************************************************
|
|
74425
74492
|
* Copy through the clipboard when the page is allowed one, and
|
|
@@ -74574,9 +74641,9 @@ function open_import(gobj, treedb) {
|
|
|
74574
74641
|
}
|
|
74575
74642
|
/***************************************************************
|
|
74576
74643
|
* The plan is a list of writes computed against the model shown:
|
|
74577
|
-
* a load replaces the model, so the plan goes with it
|
|
74578
|
-
*
|
|
74579
|
-
*
|
|
74644
|
+
* a load that LANDS replaces the model, so the plan goes with it
|
|
74645
|
+
* (and end_load() closes the dialog built on that model). A load
|
|
74646
|
+
* that fails or never leaves keeps the model, and the plan.
|
|
74580
74647
|
***************************************************************/
|
|
74581
74648
|
function forget_import_plan(gobj) {
|
|
74582
74649
|
let priv = gobj.priv;
|
|
@@ -75143,6 +75210,8 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
75143
75210
|
priv.order_undo = {};
|
|
75144
75211
|
priv.records_before = null;
|
|
75145
75212
|
priv.model_gen++;
|
|
75213
|
+
priv.reload_on_open = false;
|
|
75214
|
+
forget_import_plan(gobj);
|
|
75146
75215
|
build_model(gobj);
|
|
75147
75216
|
start_measuring(gobj);
|
|
75148
75217
|
return end_load(gobj);
|
|
@@ -75273,7 +75342,8 @@ function ac_show(gobj, event, kw, src) {
|
|
|
75273
75342
|
priv.pending_seg = seg;
|
|
75274
75343
|
return 0;
|
|
75275
75344
|
}
|
|
75276
|
-
|
|
75345
|
+
apply_seg(gobj, seg);
|
|
75346
|
+
return reload_after_move(gobj);
|
|
75277
75347
|
}
|
|
75278
75348
|
/***************************************************************
|
|
75279
75349
|
* Hidden by the host: drop the overlays. A dialog outlives the
|
|
@@ -75712,6 +75782,15 @@ function ac_export(gobj, event, kw, src) {
|
|
|
75712
75782
|
open_export(gobj, treedb);
|
|
75713
75783
|
return 0;
|
|
75714
75784
|
}
|
|
75785
|
+
/***************************************************************
|
|
75786
|
+
* The export dialog's C / JSON switch. Its texts were composed
|
|
75787
|
+
* when it opened, so it reads nothing of the model and is
|
|
75788
|
+
* answered in every state: the dialog stays up through a load
|
|
75789
|
+
* that fails and through a move sent by the host.
|
|
75790
|
+
***************************************************************/
|
|
75791
|
+
function ac_export_view(gobj, event, kw, src) {
|
|
75792
|
+
return show_export_view(gobj, kw && kw.pane);
|
|
75793
|
+
}
|
|
75715
75794
|
function ac_import(gobj, event, kw, src) {
|
|
75716
75795
|
let treedb = current_treedb(gobj);
|
|
75717
75796
|
if (refuse_if_readonly(gobj, event)) return -1;
|
|
@@ -75895,6 +75974,11 @@ function create_gclass$1(gclass_name) {
|
|
|
75895
75974
|
"EV_DRAFTS",
|
|
75896
75975
|
ac_drafts,
|
|
75897
75976
|
null
|
|
75977
|
+
],
|
|
75978
|
+
[
|
|
75979
|
+
"EV_EXPORT_VIEW",
|
|
75980
|
+
ac_export_view,
|
|
75981
|
+
null
|
|
75898
75982
|
]
|
|
75899
75983
|
];
|
|
75900
75984
|
const TREEDB_WIDE = [
|
|
@@ -76102,6 +76186,7 @@ function create_gclass$1(gclass_name) {
|
|
|
76102
76186
|
["EV_UNDO_ORDER", 0],
|
|
76103
76187
|
["EV_VALIDATE", 0],
|
|
76104
76188
|
["EV_EXPORT", 0],
|
|
76189
|
+
["EV_EXPORT_VIEW", 0],
|
|
76105
76190
|
["EV_IMPORT", 0],
|
|
76106
76191
|
["EV_PREVIEW_IMPORT", 0],
|
|
76107
76192
|
["EV_APPLY_IMPORT", 0],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -23155,11 +23155,17 @@ function track_form_write(in_flight, form_write, topic_name) {
|
|
|
23155
23155
|
};
|
|
23156
23156
|
}
|
|
23157
23157
|
/************************************************************
|
|
23158
|
-
* A write was answered, one way or the other
|
|
23158
|
+
* A write was answered, one way or the other. True when it was
|
|
23159
|
+
* still in flight: false when something else answered it already
|
|
23160
|
+
* (the transport closing, abandon_form_writes), so its form must
|
|
23161
|
+
* not be answered twice.
|
|
23159
23162
|
************************************************************/
|
|
23160
23163
|
function settle_form_write(in_flight, form_write, topic_name) {
|
|
23161
|
-
if (!in_flight || !form_write) return;
|
|
23162
|
-
|
|
23164
|
+
if (!in_flight || !form_write) return false;
|
|
23165
|
+
const key = form_write_key(topic_name, form_write);
|
|
23166
|
+
if (!Object.prototype.hasOwnProperty.call(in_flight, key)) return false;
|
|
23167
|
+
delete in_flight[key];
|
|
23168
|
+
return true;
|
|
23163
23169
|
}
|
|
23164
23170
|
/************************************************************
|
|
23165
23171
|
* Nothing in flight can be answered any more: take them all,
|
|
@@ -23746,6 +23752,7 @@ var __gclass__$6 = null;
|
|
|
23746
23752
|
***************************************************************/
|
|
23747
23753
|
function mt_create$6(gobj) {
|
|
23748
23754
|
gobj.priv.form_writes = {};
|
|
23755
|
+
gobj.priv.reload_on_session = {};
|
|
23749
23756
|
gobj.priv.conn_shell = null;
|
|
23750
23757
|
build_ui$6(gobj);
|
|
23751
23758
|
let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
@@ -25176,9 +25183,15 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
25176
25183
|
yui_shell_show_error(yui_shell_of(gobj), comment, { t });
|
|
25177
25184
|
if (command === "update-node" || command === "create-node") {
|
|
25178
25185
|
let failed_topic = kw_get_str(gobj, kw_command, "topic_name", "", 0);
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
25186
|
+
let form_write = kw_get_int(gobj, kw_command, "form_write", 0, 0);
|
|
25187
|
+
if (failed_topic) {
|
|
25188
|
+
if (is_connected$1(gobj)) gobj_send_event(gobj, "EV_REFRESH_TOPIC", { topic_name: failed_topic }, gobj);
|
|
25189
|
+
else {
|
|
25190
|
+
log_warning(`${gobj_short_name(gobj)}: the write of '${failed_topic}' was cut by the drop: the topic is read again when the session is back`);
|
|
25191
|
+
gobj.priv.reload_on_session[failed_topic] = true;
|
|
25192
|
+
}
|
|
25193
|
+
}
|
|
25194
|
+
if (settle_form_write(gobj.priv.form_writes, form_write, failed_topic)) answer_form_write(gobj, get_gobj_formtable(gobj, failed_topic), form_write, false);
|
|
25182
25195
|
}
|
|
25183
25196
|
}
|
|
25184
25197
|
return 0;
|
|
@@ -25378,11 +25391,31 @@ function ac_transport_state$2(gobj, event, kw, src) {
|
|
|
25378
25391
|
refresh_toolbar_buttons(gobj, connected);
|
|
25379
25392
|
if (!connected) abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
|
|
25380
25393
|
log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
|
|
25394
|
+
if (w.topic_name) gobj.priv.reload_on_session[w.topic_name] = true;
|
|
25381
25395
|
answer_form_write(gobj, get_gobj_formtable(gobj, w.topic_name), w.form_write, false);
|
|
25382
25396
|
});
|
|
25397
|
+
else reload_owed_topics(gobj);
|
|
25383
25398
|
return 0;
|
|
25384
25399
|
}
|
|
25385
25400
|
/************************************************************
|
|
25401
|
+
* The session is back: read again the topics a drop left
|
|
25402
|
+
* unknown. Only when the TRANSPORT says so too -- the app's
|
|
25403
|
+
* connection (EV_CONNECTION_STATE) can report "up" before the
|
|
25404
|
+
* transport of this view is in session, and a read asked then is
|
|
25405
|
+
* refused. What is owed stays owed until an edge finds the
|
|
25406
|
+
* transport in session; each topic is read once.
|
|
25407
|
+
************************************************************/
|
|
25408
|
+
function reload_owed_topics(gobj) {
|
|
25409
|
+
let priv = gobj.priv;
|
|
25410
|
+
if (!is_connected$1(gobj)) return;
|
|
25411
|
+
let topics = Object.keys(priv.reload_on_session);
|
|
25412
|
+
priv.reload_on_session = {};
|
|
25413
|
+
topics.forEach((topic_name) => {
|
|
25414
|
+
if (!get_gobj_formtable(gobj, topic_name)) return;
|
|
25415
|
+
gobj_send_event(gobj, "EV_REFRESH_TOPIC", { topic_name }, gobj);
|
|
25416
|
+
});
|
|
25417
|
+
}
|
|
25418
|
+
/************************************************************
|
|
25386
25419
|
* Parent (routing) inform us that we go hidden
|
|
25387
25420
|
************************************************************/
|
|
25388
25421
|
function ac_hide$4(gobj, event, kw, src) {
|
|
@@ -72215,7 +72248,8 @@ var PRIVATE_DATA$1 = {
|
|
|
72215
72248
|
pending_seg: null,
|
|
72216
72249
|
import_plan: null,
|
|
72217
72250
|
import_pane: null,
|
|
72218
|
-
import_run: null
|
|
72251
|
+
import_run: null,
|
|
72252
|
+
export_view: null
|
|
72219
72253
|
};
|
|
72220
72254
|
var __gclass__$1 = null;
|
|
72221
72255
|
/******************************
|
|
@@ -72400,7 +72434,12 @@ function built_on_a_replaced_model(gobj, event, kw) {
|
|
|
72400
72434
|
* export) -- is refused and said: it would be computed on what
|
|
72401
72435
|
* the store may no longer hold, and the load that lands closes a
|
|
72402
72436
|
* dialog built on the old model anyway (end_load()). A move is
|
|
72403
|
-
* not refused (reload_after_move()): it goes, and reads there
|
|
72437
|
+
* not refused (reload_after_move()): it goes, and reads there --
|
|
72438
|
+
* the editor's own moves and the host's alike (ac_show(): a url,
|
|
72439
|
+
* the browser's Back).
|
|
72440
|
+
*
|
|
72441
|
+
* Paid by ANY load that lands, whoever asked it
|
|
72442
|
+
* (ac_mt_command_answer()): the store has just been read whole.
|
|
72404
72443
|
*
|
|
72405
72444
|
* Not on a timer, and not at once: a load refused on a deadline
|
|
72406
72445
|
* asked again at once is refused the same way, and one asked on a
|
|
@@ -72442,9 +72481,10 @@ function reload_after_move(gobj) {
|
|
|
72442
72481
|
* left open is answered in ST_LOADING instead (ac_wait_for_the_load),
|
|
72443
72482
|
* and a load that LANDS closes it (end_load()): it was built on the
|
|
72444
72483
|
* model the load replaced, and its Save sends every field it shows.
|
|
72445
|
-
* An import plan is forgotten
|
|
72446
|
-
*
|
|
72447
|
-
*
|
|
72484
|
+
* An import plan is forgotten for the same reason, and at the same
|
|
72485
|
+
* moment: when the load LANDS and the model it was computed on goes
|
|
72486
|
+
* (ac_mt_command_answer()). A load that cannot leave, or that fails,
|
|
72487
|
+
* replaces nothing, and the plan stays with the dialog showing it.
|
|
72448
72488
|
*
|
|
72449
72489
|
* `keep_written`: a load nobody ASKED for -- the reconnect, a write
|
|
72450
72490
|
* that turned out done, the reload a refused load owed -- keeps what
|
|
@@ -72498,7 +72538,6 @@ function request_model(gobj, keep_written) {
|
|
|
72498
72538
|
end_load(gobj);
|
|
72499
72539
|
return -1;
|
|
72500
72540
|
}
|
|
72501
|
-
forget_import_plan(gobj);
|
|
72502
72541
|
priv.pending = left;
|
|
72503
72542
|
return 0;
|
|
72504
72543
|
}
|
|
@@ -73658,6 +73697,7 @@ function close_dialog(gobj) {
|
|
|
73658
73697
|
if (!priv.dialog) return;
|
|
73659
73698
|
let dialog = priv.dialog;
|
|
73660
73699
|
priv.dialog = null;
|
|
73700
|
+
priv.export_view = null;
|
|
73661
73701
|
try {
|
|
73662
73702
|
dialog.close();
|
|
73663
73703
|
} catch (e) {
|
|
@@ -73676,6 +73716,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix) {
|
|
|
73676
73716
|
t,
|
|
73677
73717
|
on_close: () => {
|
|
73678
73718
|
priv.dialog = null;
|
|
73719
|
+
priv.export_view = null;
|
|
73679
73720
|
}
|
|
73680
73721
|
});
|
|
73681
73722
|
refresh_language($content, t);
|
|
@@ -74404,17 +74445,43 @@ function open_export(gobj, treedb) {
|
|
|
74404
74445
|
let $text = $content.querySelector(".SCHEMA_EXPORT_TEXT");
|
|
74405
74446
|
for (let $tab of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) $tab.addEventListener("click", (evt) => {
|
|
74406
74447
|
evt.stopPropagation();
|
|
74407
|
-
|
|
74408
|
-
$other.classList.toggle("selected_state", $other === $tab);
|
|
74409
|
-
$other.setAttribute("aria-pressed", $other === $tab ? "true" : "false");
|
|
74410
|
-
}
|
|
74411
|
-
$text.value = $tab.dataset.pane === "c" ? c_text : json_text;
|
|
74448
|
+
gobj_send_event(gobj, "EV_EXPORT_VIEW", { pane: $tab.dataset.pane }, gobj);
|
|
74412
74449
|
});
|
|
74413
74450
|
$content.querySelector(".SCHEMA_EXPORT_COPY").addEventListener("click", (evt) => {
|
|
74414
74451
|
evt.stopPropagation();
|
|
74415
74452
|
copy_text(gobj, $text);
|
|
74416
74453
|
});
|
|
74417
74454
|
open_dialog(gobj, $content, "export", "SCHEMA_EXPORT_DIALOG", treedb.id);
|
|
74455
|
+
priv.export_view = {
|
|
74456
|
+
$content,
|
|
74457
|
+
texts: {
|
|
74458
|
+
c: c_text,
|
|
74459
|
+
json: json_text
|
|
74460
|
+
}
|
|
74461
|
+
};
|
|
74462
|
+
}
|
|
74463
|
+
/***************************************************************
|
|
74464
|
+
* Show one of the export's two texts, and say which one is shown
|
|
74465
|
+
* (`selected_state` + aria-pressed). Reached only through
|
|
74466
|
+
* EV_EXPORT_VIEW: a click is an action like any other.
|
|
74467
|
+
***************************************************************/
|
|
74468
|
+
function show_export_view(gobj, pane) {
|
|
74469
|
+
let view = gobj.priv.export_view;
|
|
74470
|
+
if (!view) {
|
|
74471
|
+
log_error(`${gobj_short_name(gobj)}: export view '${pane}' asked with no export open`);
|
|
74472
|
+
return -1;
|
|
74473
|
+
}
|
|
74474
|
+
if (!Object.prototype.hasOwnProperty.call(view.texts, pane)) {
|
|
74475
|
+
log_error(`${gobj_short_name(gobj)}: the export has no view '${pane}'`);
|
|
74476
|
+
return -1;
|
|
74477
|
+
}
|
|
74478
|
+
for (let $tab of view.$content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
|
|
74479
|
+
let shown = $tab.dataset.pane === pane;
|
|
74480
|
+
$tab.classList.toggle("selected_state", shown);
|
|
74481
|
+
$tab.setAttribute("aria-pressed", shown ? "true" : "false");
|
|
74482
|
+
}
|
|
74483
|
+
view.$content.querySelector(".SCHEMA_EXPORT_TEXT").value = view.texts[pane];
|
|
74484
|
+
return 0;
|
|
74418
74485
|
}
|
|
74419
74486
|
/***************************************************************
|
|
74420
74487
|
* Copy through the clipboard when the page is allowed one, and
|
|
@@ -74569,9 +74636,9 @@ function open_import(gobj, treedb) {
|
|
|
74569
74636
|
}
|
|
74570
74637
|
/***************************************************************
|
|
74571
74638
|
* The plan is a list of writes computed against the model shown:
|
|
74572
|
-
* a load replaces the model, so the plan goes with it
|
|
74573
|
-
*
|
|
74574
|
-
*
|
|
74639
|
+
* a load that LANDS replaces the model, so the plan goes with it
|
|
74640
|
+
* (and end_load() closes the dialog built on that model). A load
|
|
74641
|
+
* that fails or never leaves keeps the model, and the plan.
|
|
74575
74642
|
***************************************************************/
|
|
74576
74643
|
function forget_import_plan(gobj) {
|
|
74577
74644
|
let priv = gobj.priv;
|
|
@@ -75138,6 +75205,8 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
75138
75205
|
priv.order_undo = {};
|
|
75139
75206
|
priv.records_before = null;
|
|
75140
75207
|
priv.model_gen++;
|
|
75208
|
+
priv.reload_on_open = false;
|
|
75209
|
+
forget_import_plan(gobj);
|
|
75141
75210
|
build_model(gobj);
|
|
75142
75211
|
start_measuring(gobj);
|
|
75143
75212
|
return end_load(gobj);
|
|
@@ -75268,7 +75337,8 @@ function ac_show(gobj, event, kw, src) {
|
|
|
75268
75337
|
priv.pending_seg = seg;
|
|
75269
75338
|
return 0;
|
|
75270
75339
|
}
|
|
75271
|
-
|
|
75340
|
+
apply_seg(gobj, seg);
|
|
75341
|
+
return reload_after_move(gobj);
|
|
75272
75342
|
}
|
|
75273
75343
|
/***************************************************************
|
|
75274
75344
|
* Hidden by the host: drop the overlays. A dialog outlives the
|
|
@@ -75707,6 +75777,15 @@ function ac_export(gobj, event, kw, src) {
|
|
|
75707
75777
|
open_export(gobj, treedb);
|
|
75708
75778
|
return 0;
|
|
75709
75779
|
}
|
|
75780
|
+
/***************************************************************
|
|
75781
|
+
* The export dialog's C / JSON switch. Its texts were composed
|
|
75782
|
+
* when it opened, so it reads nothing of the model and is
|
|
75783
|
+
* answered in every state: the dialog stays up through a load
|
|
75784
|
+
* that fails and through a move sent by the host.
|
|
75785
|
+
***************************************************************/
|
|
75786
|
+
function ac_export_view(gobj, event, kw, src) {
|
|
75787
|
+
return show_export_view(gobj, kw && kw.pane);
|
|
75788
|
+
}
|
|
75710
75789
|
function ac_import(gobj, event, kw, src) {
|
|
75711
75790
|
let treedb = current_treedb(gobj);
|
|
75712
75791
|
if (refuse_if_readonly(gobj, event)) return -1;
|
|
@@ -75890,6 +75969,11 @@ function create_gclass$1(gclass_name) {
|
|
|
75890
75969
|
"EV_DRAFTS",
|
|
75891
75970
|
ac_drafts,
|
|
75892
75971
|
null
|
|
75972
|
+
],
|
|
75973
|
+
[
|
|
75974
|
+
"EV_EXPORT_VIEW",
|
|
75975
|
+
ac_export_view,
|
|
75976
|
+
null
|
|
75893
75977
|
]
|
|
75894
75978
|
];
|
|
75895
75979
|
const TREEDB_WIDE = [
|
|
@@ -76097,6 +76181,7 @@ function create_gclass$1(gclass_name) {
|
|
|
76097
76181
|
["EV_UNDO_ORDER", 0],
|
|
76098
76182
|
["EV_VALIDATE", 0],
|
|
76099
76183
|
["EV_EXPORT", 0],
|
|
76184
|
+
["EV_EXPORT_VIEW", 0],
|
|
76100
76185
|
["EV_IMPORT", 0],
|
|
76101
76186
|
["EV_PREVIEW_IMPORT", 0],
|
|
76102
76187
|
["EV_APPLY_IMPORT", 0],
|
package/package.json
CHANGED
|
@@ -249,6 +249,9 @@ let PRIVATE_DATA = {
|
|
|
249
249
|
import_plan: null,
|
|
250
250
|
import_pane: null,
|
|
251
251
|
import_run: null,
|
|
252
|
+
|
|
253
|
+
/* The export dialog while it is up: its content and its two texts */
|
|
254
|
+
export_view: null,
|
|
252
255
|
};
|
|
253
256
|
|
|
254
257
|
let __gclass__ = null;
|
|
@@ -494,7 +497,12 @@ function built_on_a_replaced_model(gobj, event, kw)
|
|
|
494
497
|
* export) -- is refused and said: it would be computed on what
|
|
495
498
|
* the store may no longer hold, and the load that lands closes a
|
|
496
499
|
* dialog built on the old model anyway (end_load()). A move is
|
|
497
|
-
* not refused (reload_after_move()): it goes, and reads there
|
|
500
|
+
* not refused (reload_after_move()): it goes, and reads there --
|
|
501
|
+
* the editor's own moves and the host's alike (ac_show(): a url,
|
|
502
|
+
* the browser's Back).
|
|
503
|
+
*
|
|
504
|
+
* Paid by ANY load that lands, whoever asked it
|
|
505
|
+
* (ac_mt_command_answer()): the store has just been read whole.
|
|
498
506
|
*
|
|
499
507
|
* Not on a timer, and not at once: a load refused on a deadline
|
|
500
508
|
* asked again at once is refused the same way, and one asked on a
|
|
@@ -558,9 +566,10 @@ function reload_after_move(gobj)
|
|
|
558
566
|
* left open is answered in ST_LOADING instead (ac_wait_for_the_load),
|
|
559
567
|
* and a load that LANDS closes it (end_load()): it was built on the
|
|
560
568
|
* model the load replaced, and its Save sends every field it shows.
|
|
561
|
-
* An import plan is forgotten
|
|
562
|
-
*
|
|
563
|
-
*
|
|
569
|
+
* An import plan is forgotten for the same reason, and at the same
|
|
570
|
+
* moment: when the load LANDS and the model it was computed on goes
|
|
571
|
+
* (ac_mt_command_answer()). A load that cannot leave, or that fails,
|
|
572
|
+
* replaces nothing, and the plan stays with the dialog showing it.
|
|
564
573
|
*
|
|
565
574
|
* `keep_written`: a load nobody ASKED for -- the reconnect, a write
|
|
566
575
|
* that turned out done, the reload a refused load owed -- keeps what
|
|
@@ -626,9 +635,6 @@ function request_model(gobj, keep_written)
|
|
|
626
635
|
end_load(gobj);
|
|
627
636
|
return -1;
|
|
628
637
|
}
|
|
629
|
-
/* Only a load that LEFT replaces the model the plan was computed
|
|
630
|
-
* on; one that could not be sent keeps it, and the plan with it. */
|
|
631
|
-
forget_import_plan(gobj);
|
|
632
638
|
priv.pending = left;
|
|
633
639
|
return 0;
|
|
634
640
|
}
|
|
@@ -1739,6 +1745,7 @@ function close_dialog(gobj)
|
|
|
1739
1745
|
}
|
|
1740
1746
|
let dialog = priv.dialog;
|
|
1741
1747
|
priv.dialog = null;
|
|
1748
|
+
priv.export_view = null;
|
|
1742
1749
|
try {
|
|
1743
1750
|
dialog.close();
|
|
1744
1751
|
} catch(e) {
|
|
@@ -1760,6 +1767,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix)
|
|
|
1760
1767
|
t: t,
|
|
1761
1768
|
on_close: () => {
|
|
1762
1769
|
priv.dialog = null;
|
|
1770
|
+
priv.export_view = null;
|
|
1763
1771
|
}
|
|
1764
1772
|
});
|
|
1765
1773
|
refresh_language($content, t);
|
|
@@ -2279,11 +2287,7 @@ function open_export(gobj, treedb)
|
|
|
2279
2287
|
for(let $tab of $content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
|
|
2280
2288
|
$tab.addEventListener("click", (evt) => {
|
|
2281
2289
|
evt.stopPropagation();
|
|
2282
|
-
|
|
2283
|
-
$other.classList.toggle("selected_state", $other === $tab);
|
|
2284
|
-
$other.setAttribute("aria-pressed", ($other === $tab)? "true": "false");
|
|
2285
|
-
}
|
|
2286
|
-
$text.value = ($tab.dataset.pane === "c") ? c_text : json_text;
|
|
2290
|
+
gobj_send_event(gobj, "EV_EXPORT_VIEW", {pane: $tab.dataset.pane}, gobj);
|
|
2287
2291
|
});
|
|
2288
2292
|
}
|
|
2289
2293
|
$content.querySelector(".SCHEMA_EXPORT_COPY").addEventListener("click", (evt) => {
|
|
@@ -2292,6 +2296,35 @@ function open_export(gobj, treedb)
|
|
|
2292
2296
|
});
|
|
2293
2297
|
|
|
2294
2298
|
open_dialog(gobj, $content, "export", "SCHEMA_EXPORT_DIALOG", treedb.id);
|
|
2299
|
+
/* After open_dialog(): it closes the dialog before, and that forgets
|
|
2300
|
+
* the view of the one it closes. */
|
|
2301
|
+
priv.export_view = {$content: $content, texts: {c: c_text, json: json_text}};
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
/***************************************************************
|
|
2305
|
+
* Show one of the export's two texts, and say which one is shown
|
|
2306
|
+
* (`selected_state` + aria-pressed). Reached only through
|
|
2307
|
+
* EV_EXPORT_VIEW: a click is an action like any other.
|
|
2308
|
+
***************************************************************/
|
|
2309
|
+
function show_export_view(gobj, pane)
|
|
2310
|
+
{
|
|
2311
|
+
let view = gobj.priv.export_view;
|
|
2312
|
+
|
|
2313
|
+
if(!view) {
|
|
2314
|
+
log_error(`${gobj_short_name(gobj)}: export view '${pane}' asked with no export open`);
|
|
2315
|
+
return -1;
|
|
2316
|
+
}
|
|
2317
|
+
if(!Object.prototype.hasOwnProperty.call(view.texts, pane)) {
|
|
2318
|
+
log_error(`${gobj_short_name(gobj)}: the export has no view '${pane}'`);
|
|
2319
|
+
return -1;
|
|
2320
|
+
}
|
|
2321
|
+
for(let $tab of view.$content.querySelectorAll(".SCHEMA_EXPORT_TAB")) {
|
|
2322
|
+
let shown = ($tab.dataset.pane === pane);
|
|
2323
|
+
$tab.classList.toggle("selected_state", shown);
|
|
2324
|
+
$tab.setAttribute("aria-pressed", shown ? "true" : "false");
|
|
2325
|
+
}
|
|
2326
|
+
view.$content.querySelector(".SCHEMA_EXPORT_TEXT").value = view.texts[pane];
|
|
2327
|
+
return 0;
|
|
2295
2328
|
}
|
|
2296
2329
|
|
|
2297
2330
|
/***************************************************************
|
|
@@ -2384,9 +2417,9 @@ function open_import(gobj, treedb)
|
|
|
2384
2417
|
|
|
2385
2418
|
/***************************************************************
|
|
2386
2419
|
* The plan is a list of writes computed against the model shown:
|
|
2387
|
-
* a load replaces the model, so the plan goes with it
|
|
2388
|
-
*
|
|
2389
|
-
*
|
|
2420
|
+
* a load that LANDS replaces the model, so the plan goes with it
|
|
2421
|
+
* (and end_load() closes the dialog built on that model). A load
|
|
2422
|
+
* that fails or never leaves keeps the model, and the plan.
|
|
2390
2423
|
***************************************************************/
|
|
2391
2424
|
function forget_import_plan(gobj)
|
|
2392
2425
|
{
|
|
@@ -3103,6 +3136,17 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
3103
3136
|
priv.order_undo = {};
|
|
3104
3137
|
priv.records_before = null;
|
|
3105
3138
|
priv.model_gen++;
|
|
3139
|
+
/* The store was read whole: whatever reload was owed is paid,
|
|
3140
|
+
* whoever asked for this one -- the operator's Refresh, a late
|
|
3141
|
+
* write, the reconnect. Left owed, the next edit was refused
|
|
3142
|
+
* and read the store AGAIN (sixth independent review). */
|
|
3143
|
+
priv.reload_on_open = false;
|
|
3144
|
+
/* The plan was a list of writes computed on the model this load
|
|
3145
|
+
* replaces. Forgotten HERE, where the model goes, and not when
|
|
3146
|
+
* the requests leave: a load that fails replaces nothing, and
|
|
3147
|
+
* forgetting early left its dialog up with no plan and Import
|
|
3148
|
+
* disabled (sixth independent review). */
|
|
3149
|
+
forget_import_plan(gobj);
|
|
3106
3150
|
build_model(gobj);
|
|
3107
3151
|
/* Same reason as order_undo above: this is the LOAD, and the
|
|
3108
3152
|
* baseline is what a later "the version did not move" is measured
|
|
@@ -3297,7 +3341,12 @@ function ac_show(gobj, event, kw, src)
|
|
|
3297
3341
|
priv.pending_seg = seg;
|
|
3298
3342
|
return 0; /* applied when the write lands (end_writes) or the load (end_load) */
|
|
3299
3343
|
}
|
|
3300
|
-
|
|
3344
|
+
/* A move like the editor's own (ac_back() and the rest): it goes,
|
|
3345
|
+
* and pays the reload a refused load owes there. Without it a url
|
|
3346
|
+
* or the browser's Back left it owed on a screen that asked for
|
|
3347
|
+
* nothing (sixth independent review). */
|
|
3348
|
+
apply_seg(gobj, seg);
|
|
3349
|
+
return reload_after_move(gobj);
|
|
3301
3350
|
}
|
|
3302
3351
|
|
|
3303
3352
|
/***************************************************************
|
|
@@ -3903,6 +3952,17 @@ function ac_export(gobj, event, kw, src)
|
|
|
3903
3952
|
return 0;
|
|
3904
3953
|
}
|
|
3905
3954
|
|
|
3955
|
+
/***************************************************************
|
|
3956
|
+
* The export dialog's C / JSON switch. Its texts were composed
|
|
3957
|
+
* when it opened, so it reads nothing of the model and is
|
|
3958
|
+
* answered in every state: the dialog stays up through a load
|
|
3959
|
+
* that fails and through a move sent by the host.
|
|
3960
|
+
***************************************************************/
|
|
3961
|
+
function ac_export_view(gobj, event, kw, src)
|
|
3962
|
+
{
|
|
3963
|
+
return show_export_view(gobj, kw && kw.pane);
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3906
3966
|
function ac_import(gobj, event, kw, src)
|
|
3907
3967
|
{
|
|
3908
3968
|
let treedb = current_treedb(gobj);
|
|
@@ -3994,8 +4054,9 @@ function run_import(gobj)
|
|
|
3994
4054
|
let priv = gobj.priv;
|
|
3995
4055
|
let plan = priv.import_plan;
|
|
3996
4056
|
|
|
3997
|
-
/* Not an ERROR: a load that
|
|
3998
|
-
* and the confirmation of it may still be
|
|
4057
|
+
/* Not an ERROR: a load that lands forgets the plan
|
|
4058
|
+
* (ac_mt_command_answer()), and the confirmation of it may still be
|
|
4059
|
+
* up. The operator's Yes
|
|
3999
4060
|
* is answered, not lost in the log. */
|
|
4000
4061
|
if(!plan) {
|
|
4001
4062
|
log_warning(`${gobj_short_name(gobj)}: the import plan is gone`);
|
|
@@ -4099,7 +4160,9 @@ function create_gclass(gclass_name)
|
|
|
4099
4160
|
["EV_TRANSPORT_STATE", ac_transport_state, null],
|
|
4100
4161
|
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
4101
4162
|
["EV_REFRESH", ac_refresh, null],
|
|
4102
|
-
["EV_DRAFTS", ac_drafts, null]
|
|
4163
|
+
["EV_DRAFTS", ac_drafts, null],
|
|
4164
|
+
/* The export dialog outlives the screen it was opened from. */
|
|
4165
|
+
["EV_EXPORT_VIEW", ac_export_view, null]
|
|
4103
4166
|
];
|
|
4104
4167
|
|
|
4105
4168
|
/* What a treedb is open for: reading it whole, and writing it
|
|
@@ -4215,6 +4278,7 @@ function create_gclass(gclass_name)
|
|
|
4215
4278
|
["EV_UNDO_ORDER", 0],
|
|
4216
4279
|
["EV_VALIDATE", 0],
|
|
4217
4280
|
["EV_EXPORT", 0],
|
|
4281
|
+
["EV_EXPORT_VIEW", 0],
|
|
4218
4282
|
["EV_IMPORT", 0],
|
|
4219
4283
|
["EV_PREVIEW_IMPORT", 0],
|
|
4220
4284
|
["EV_APPLY_IMPORT", 0],
|
|
@@ -160,6 +160,10 @@ function mt_create(gobj)
|
|
|
160
160
|
* and serial (form_writes_in_flight): answered refused when the
|
|
161
161
|
* transport closes under them. */
|
|
162
162
|
gobj.priv.form_writes = {};
|
|
163
|
+
/* The topics a write cut by a DROP left unknown: the table may show
|
|
164
|
+
* what was typed and the store what it had. Read again when the
|
|
165
|
+
* session is back (reload_owed_topics), never out of session. */
|
|
166
|
+
gobj.priv.reload_on_session = {};
|
|
163
167
|
gobj.priv.conn_shell = null; /* the shell whose EV_CONNECTION_STATE we hear */
|
|
164
168
|
|
|
165
169
|
build_ui(gobj);
|
|
@@ -2092,18 +2096,34 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2092
2096
|
* topic back to what the treedb actually has. */
|
|
2093
2097
|
if(command === "update-node" || command === "create-node") {
|
|
2094
2098
|
let failed_topic = kw_get_str(gobj, kw_command, "topic_name", "", 0);
|
|
2099
|
+
let form_write = kw_get_int(gobj, kw_command, "form_write", 0, 0);
|
|
2095
2100
|
if(failed_topic) {
|
|
2096
|
-
|
|
2097
|
-
|
|
2101
|
+
if(is_connected(gobj)) {
|
|
2102
|
+
gobj_send_event(gobj, "EV_REFRESH_TOPIC",
|
|
2103
|
+
{topic_name: failed_topic}, gobj);
|
|
2104
|
+
} else {
|
|
2105
|
+
/* A failure out of session is the DROP, not a
|
|
2106
|
+
* refusal: a routing adapter settles what it had
|
|
2107
|
+
* in flight when its session closes. The topic
|
|
2108
|
+
* cannot be read now -- asked, the transport
|
|
2109
|
+
* refuses it ("cannot route 'nodes' -- not in
|
|
2110
|
+
* session"). The reconnect reads it. */
|
|
2111
|
+
log_warning(`${gobj_short_name(gobj)}: the write of ` +
|
|
2112
|
+
`'${failed_topic}' was cut by the drop: the topic is ` +
|
|
2113
|
+
`read again when the session is back`);
|
|
2114
|
+
gobj.priv.reload_on_session[failed_topic] = true;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
/* Answered once: the transport closing may have answered
|
|
2118
|
+
* this form already (ac_transport_state). */
|
|
2119
|
+
if(settle_form_write(gobj.priv.form_writes, form_write, failed_topic)) {
|
|
2120
|
+
answer_form_write(
|
|
2121
|
+
gobj,
|
|
2122
|
+
get_gobj_formtable(gobj, failed_topic),
|
|
2123
|
+
form_write,
|
|
2124
|
+
false
|
|
2125
|
+
);
|
|
2098
2126
|
}
|
|
2099
|
-
settle_form_write(gobj.priv.form_writes,
|
|
2100
|
-
kw_get_int(gobj, kw_command, "form_write", 0, 0), failed_topic);
|
|
2101
|
-
answer_form_write(
|
|
2102
|
-
gobj,
|
|
2103
|
-
get_gobj_formtable(gobj, failed_topic),
|
|
2104
|
-
kw_get_int(gobj, kw_command, "form_write", 0, 0),
|
|
2105
|
-
false
|
|
2106
|
-
);
|
|
2107
2127
|
}
|
|
2108
2128
|
}
|
|
2109
2129
|
return 0;
|
|
@@ -2462,6 +2482,11 @@ function ac_transport_state(gobj, event, kw, src)
|
|
|
2462
2482
|
if(!connected) {
|
|
2463
2483
|
abandon_form_writes(gobj.priv.form_writes).forEach((w) => {
|
|
2464
2484
|
log_warning(`${gobj_short_name(gobj)}: transport closed, the write ${w.form_write} of '${w.topic_name}' is lost`);
|
|
2485
|
+
/* Whether it reached the store is unknown: read it again
|
|
2486
|
+
* when the session is back. */
|
|
2487
|
+
if(w.topic_name) {
|
|
2488
|
+
gobj.priv.reload_on_session[w.topic_name] = true;
|
|
2489
|
+
}
|
|
2465
2490
|
answer_form_write(
|
|
2466
2491
|
gobj,
|
|
2467
2492
|
get_gobj_formtable(gobj, w.topic_name),
|
|
@@ -2469,10 +2494,36 @@ function ac_transport_state(gobj, event, kw, src)
|
|
|
2469
2494
|
false
|
|
2470
2495
|
);
|
|
2471
2496
|
});
|
|
2497
|
+
} else {
|
|
2498
|
+
reload_owed_topics(gobj);
|
|
2472
2499
|
}
|
|
2473
2500
|
return 0;
|
|
2474
2501
|
}
|
|
2475
2502
|
|
|
2503
|
+
/************************************************************
|
|
2504
|
+
* The session is back: read again the topics a drop left
|
|
2505
|
+
* unknown. Only when the TRANSPORT says so too -- the app's
|
|
2506
|
+
* connection (EV_CONNECTION_STATE) can report "up" before the
|
|
2507
|
+
* transport of this view is in session, and a read asked then is
|
|
2508
|
+
* refused. What is owed stays owed until an edge finds the
|
|
2509
|
+
* transport in session; each topic is read once.
|
|
2510
|
+
************************************************************/
|
|
2511
|
+
function reload_owed_topics(gobj)
|
|
2512
|
+
{
|
|
2513
|
+
let priv = gobj.priv;
|
|
2514
|
+
if(!is_connected(gobj)) {
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
let topics = Object.keys(priv.reload_on_session);
|
|
2518
|
+
priv.reload_on_session = {};
|
|
2519
|
+
topics.forEach((topic_name) => {
|
|
2520
|
+
if(!get_gobj_formtable(gobj, topic_name)) {
|
|
2521
|
+
return; /* its table is closed: opening it reads it */
|
|
2522
|
+
}
|
|
2523
|
+
gobj_send_event(gobj, "EV_REFRESH_TOPIC", {topic_name: topic_name}, gobj);
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2476
2527
|
/************************************************************
|
|
2477
2528
|
* Parent (routing) inform us that we go hidden
|
|
2478
2529
|
************************************************************/
|
|
@@ -44,14 +44,22 @@ function track_form_write(in_flight, form_write, topic_name)
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/************************************************************
|
|
47
|
-
* A write was answered, one way or the other
|
|
47
|
+
* A write was answered, one way or the other. True when it was
|
|
48
|
+
* still in flight: false when something else answered it already
|
|
49
|
+
* (the transport closing, abandon_form_writes), so its form must
|
|
50
|
+
* not be answered twice.
|
|
48
51
|
************************************************************/
|
|
49
52
|
function settle_form_write(in_flight, form_write, topic_name)
|
|
50
53
|
{
|
|
51
54
|
if(!in_flight || !form_write) {
|
|
52
|
-
return;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const key = form_write_key(topic_name, form_write);
|
|
58
|
+
if(!Object.prototype.hasOwnProperty.call(in_flight, key)) {
|
|
59
|
+
return false;
|
|
53
60
|
}
|
|
54
|
-
delete in_flight[
|
|
61
|
+
delete in_flight[key];
|
|
62
|
+
return true;
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
/************************************************************
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
* silence, and a toolbar gated on the state instead of on the
|
|
26
26
|
* treedb being there.
|
|
27
27
|
*
|
|
28
|
+
* The sixth independent review (same day) found where the owed
|
|
29
|
+
* reload still lingered: a load that LANDED by another road (the
|
|
30
|
+
* operator's Refresh, a late write) left it owed, so the next
|
|
31
|
+
* edit was refused and read the store again; a move sent by the
|
|
32
|
+
* host did not run it; a load that failed forgot the import plan
|
|
33
|
+
* of a dialog it left up; and the export's two views switched in
|
|
34
|
+
* a DOM handler, out of the machine.
|
|
35
|
+
*
|
|
28
36
|
* Driven through the FSM on a document double, with a fake
|
|
29
37
|
* transport whose state is the one the library reads.
|
|
30
38
|
*
|
|
@@ -68,6 +76,7 @@ const {
|
|
|
68
76
|
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
69
77
|
gobj_start, gobj_send_event, gobj_read_attr,
|
|
70
78
|
gobj_change_state, gobj_current_state,
|
|
79
|
+
gobj_set_gobj_trace,
|
|
71
80
|
set_log_callback,
|
|
72
81
|
} = await import("@yuneta/gobj-js");
|
|
73
82
|
const {register_c_yui_schema_editor} = await import("./c_yui_schema_editor.js");
|
|
@@ -575,11 +584,12 @@ describe("a dialog built on the model a reload replaced (fourth review)", () =>
|
|
|
575
584
|
expect(editor.priv.import_plan.writes.length).toBeGreaterThan(0);
|
|
576
585
|
|
|
577
586
|
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
578
|
-
expect(editor.priv.import_plan).toBe(null);
|
|
579
587
|
answer_the_load_with(editor, remote, records_with((r) => {
|
|
580
588
|
r.cols.push({id: "db.users.email", value: "email", order: 2, type: "integer",
|
|
581
589
|
topics: ["topics^db.users^cols"]});
|
|
582
590
|
}));
|
|
591
|
+
/* Forgotten when the load LANDS, not when it leaves (sixth review). */
|
|
592
|
+
expect(editor.priv.import_plan).toBe(null);
|
|
583
593
|
expect(dialog.closed).toBe(true);
|
|
584
594
|
|
|
585
595
|
dialog.$content.querySelector(".SCHEMA_IMPORT_RUN").disabled = false;
|
|
@@ -1035,3 +1045,179 @@ describe("the answers of the editor's confirmations are i18n keys (fifth review,
|
|
|
1035
1045
|
.toEqual(["delete", "cancel"]);
|
|
1036
1046
|
});
|
|
1037
1047
|
});
|
|
1048
|
+
|
|
1049
|
+
describe("the owed reload, settled wherever a load lands (sixth review)", () => {
|
|
1050
|
+
|
|
1051
|
+
function refused_load(name, subpath)
|
|
1052
|
+
{
|
|
1053
|
+
const built = build(name, subpath);
|
|
1054
|
+
gobj_send_event(built.editor, "EV_REFRESH", {}, built.host);
|
|
1055
|
+
const asked = take("nodes");
|
|
1056
|
+
answer(built.editor, built.remote, asked[0], -1, null, "deadline");
|
|
1057
|
+
answer(built.editor, built.remote, asked[1], 0, RECORDS[asked[1].kw.topic_name]);
|
|
1058
|
+
answer(built.editor, built.remote, asked[2], 0, RECORDS[asked[2].kw.topic_name]);
|
|
1059
|
+
expect(built.editor.priv.reload_on_open).toBe(true);
|
|
1060
|
+
expect(shown).toEqual([KEPT]);
|
|
1061
|
+
return built;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
test("the operator's Refresh that lands pays it: the next edit opens its form", () => {
|
|
1065
|
+
const {editor, remote, host} = refused_load("q1", "db/users");
|
|
1066
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1067
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
1068
|
+
expect(gobj_current_state(editor)).toBe("ST_COLUMNS");
|
|
1069
|
+
expect(editor.priv.reload_on_open).toBe(false);
|
|
1070
|
+
|
|
1071
|
+
const before = modals.length;
|
|
1072
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1073
|
+
expect(modals.length).toBe(before + 1);
|
|
1074
|
+
expect(take("nodes")).toEqual([]);
|
|
1075
|
+
expect(shown).toEqual([KEPT]);
|
|
1076
|
+
expect(errors()).toEqual([]);
|
|
1077
|
+
expect(not_defined()).toEqual([]);
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
test("the reload of a late write that lands pays it too", () => {
|
|
1081
|
+
const {editor, remote, host} = build("q2", "db/users");
|
|
1082
|
+
const w = start_a_write(editor, host, "name");
|
|
1083
|
+
drop(editor, remote, host);
|
|
1084
|
+
reconnect(editor, remote, host);
|
|
1085
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
1086
|
+
|
|
1087
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1088
|
+
const asked = take("nodes");
|
|
1089
|
+
answer(editor, remote, asked[0], -1, null, "deadline");
|
|
1090
|
+
answer(editor, remote, asked[1], 0, RECORDS[asked[1].kw.topic_name]);
|
|
1091
|
+
answer(editor, remote, asked[2], 0, RECORDS[asked[2].kw.topic_name]);
|
|
1092
|
+
expect(editor.priv.reload_on_open).toBe(true);
|
|
1093
|
+
|
|
1094
|
+
/* The write given up answers DONE: the model is read again. */
|
|
1095
|
+
answer(editor, remote, w, 0, {id: "db.users.name", value: "name", type: "string",
|
|
1096
|
+
order: 2, topics: [{id: "db.users", topic_name: "topics", hook_name: "cols"}]});
|
|
1097
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
1098
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
1099
|
+
expect(editor.priv.reload_on_open).toBe(false);
|
|
1100
|
+
|
|
1101
|
+
const before = modals.length;
|
|
1102
|
+
gobj_send_event(editor, "EV_EDIT_COLUMN", {col: "id"}, host);
|
|
1103
|
+
expect(modals.length).toBe(before + 1);
|
|
1104
|
+
expect(take("nodes")).toEqual([]);
|
|
1105
|
+
expect(errors()).toEqual([]);
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
test("a move sent by the HOST (a url, the browser's Back) runs it, as the editor's own moves do", () => {
|
|
1109
|
+
const {editor, remote, host} = refused_load("q3", "db/users");
|
|
1110
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
1111
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
1112
|
+
expect(editor.priv.reload_on_open).toBe(false);
|
|
1113
|
+
expect(shown).toEqual([KEPT]); /* a move is not refused */
|
|
1114
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
1115
|
+
expect(gobj_current_state(editor)).toBe("ST_TOPICS");
|
|
1116
|
+
expect(errors()).toEqual([]);
|
|
1117
|
+
expect(not_defined()).toEqual([]);
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
test("a host EV_SHOW with nothing owed asks for nothing", () => {
|
|
1121
|
+
const {editor, remote, host} = build("q4", "db/users");
|
|
1122
|
+
gobj_send_event(editor, "EV_SHOW", {subpath: "db"}, host);
|
|
1123
|
+
expect(gobj_current_state(editor)).toBe("ST_TOPICS");
|
|
1124
|
+
expect(take("nodes")).toEqual([]);
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
describe("the import plan outlives a load that fails (sixth review)", () => {
|
|
1129
|
+
|
|
1130
|
+
const IMPORT = JSON.stringify({id: "db", topics: [{id: "users", pkey: "id", cols: [
|
|
1131
|
+
{id: "id", type: "string", flag: ["persistent", "required"]},
|
|
1132
|
+
{id: "email", type: "string"}
|
|
1133
|
+
]}]});
|
|
1134
|
+
|
|
1135
|
+
test("a load that LEFT and failed replaced nothing: the plan and its Import stay", () => {
|
|
1136
|
+
const {editor, remote, host} = build("m1", "db");
|
|
1137
|
+
gobj_send_event(editor, "EV_IMPORT", {}, host);
|
|
1138
|
+
const dialog = modals[modals.length - 1];
|
|
1139
|
+
gobj_send_event(editor, "EV_PREVIEW_IMPORT", {prune: false, text: IMPORT,
|
|
1140
|
+
model_gen: editor.priv.model_gen}, editor);
|
|
1141
|
+
const plan = editor.priv.import_plan;
|
|
1142
|
+
expect(plan.writes.length).toBeGreaterThan(0);
|
|
1143
|
+
|
|
1144
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1145
|
+
const asked = take("nodes");
|
|
1146
|
+
answer(editor, remote, asked[0], -1, null, "deadline");
|
|
1147
|
+
answer(editor, remote, asked[1], 0, RECORDS[asked[1].kw.topic_name]);
|
|
1148
|
+
answer(editor, remote, asked[2], 0, RECORDS[asked[2].kw.topic_name]);
|
|
1149
|
+
|
|
1150
|
+
expect(dialog.closed).toBe(false);
|
|
1151
|
+
expect(editor.priv.import_plan).toBe(plan);
|
|
1152
|
+
expect(dialog.$content.querySelector(".SCHEMA_IMPORT_RUN").disabled).toBe(false);
|
|
1153
|
+
expect(dialog.$content.querySelector(".SCHEMA_IMPORT_PLAN").childNodes.length)
|
|
1154
|
+
.toBeGreaterThan(0);
|
|
1155
|
+
expect(errors()).toEqual([]);
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
test("a load that LANDS forgets it, and closes the dialog it was shown in", () => {
|
|
1159
|
+
const {editor, remote, host} = build("m2", "db");
|
|
1160
|
+
gobj_send_event(editor, "EV_IMPORT", {}, host);
|
|
1161
|
+
const dialog = modals[modals.length - 1];
|
|
1162
|
+
gobj_send_event(editor, "EV_PREVIEW_IMPORT", {prune: false, text: IMPORT,
|
|
1163
|
+
model_gen: editor.priv.model_gen}, editor);
|
|
1164
|
+
expect(editor.priv.import_plan).toBeTruthy();
|
|
1165
|
+
|
|
1166
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1167
|
+
expect(editor.priv.import_plan).toBeTruthy(); /* in the air: nothing replaced yet */
|
|
1168
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
1169
|
+
expect(editor.priv.import_plan).toBe(null);
|
|
1170
|
+
expect(dialog.closed).toBe(true);
|
|
1171
|
+
expect(shown).toEqual([STALE]);
|
|
1172
|
+
expect(errors()).toEqual([]);
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
describe("the export's two views go through the machine (sixth review)", () => {
|
|
1177
|
+
|
|
1178
|
+
test("a click on a view is an event, and the action switches the text", () => {
|
|
1179
|
+
const {editor, remote, host} = build("y1", "db");
|
|
1180
|
+
gobj_send_event(editor, "EV_EXPORT", {}, host);
|
|
1181
|
+
const $content = modals[modals.length - 1].$content;
|
|
1182
|
+
const $tabs = [...$content.querySelectorAll(".SCHEMA_EXPORT_TAB")];
|
|
1183
|
+
const $text = $content.querySelector(".SCHEMA_EXPORT_TEXT");
|
|
1184
|
+
|
|
1185
|
+
gobj_set_gobj_trace(editor, "machine", true);
|
|
1186
|
+
try {
|
|
1187
|
+
$tabs[1].click();
|
|
1188
|
+
} finally {
|
|
1189
|
+
gobj_set_gobj_trace(editor, "machine", false);
|
|
1190
|
+
}
|
|
1191
|
+
expect(logged.some((l) => /EV_EXPORT_VIEW/.test(l.msg))).toBe(true);
|
|
1192
|
+
expect($text.value.trim().startsWith("{")).toBe(true);
|
|
1193
|
+
expect($tabs.map(($b) => $b.getAttribute("aria-pressed"))).toEqual(["false", "true"]);
|
|
1194
|
+
expect($tabs.map(($b) => $b.classList.contains("selected_state"))).toEqual([false, true]);
|
|
1195
|
+
|
|
1196
|
+
gobj_send_event(editor, "EV_EXPORT_VIEW", {pane: "c"}, editor);
|
|
1197
|
+
expect($text.value.trim().startsWith("{")).toBe(false);
|
|
1198
|
+
expect($tabs.map(($b) => $b.getAttribute("aria-pressed"))).toEqual(["true", "false"]);
|
|
1199
|
+
expect(errors()).toEqual([]);
|
|
1200
|
+
expect(not_defined()).toEqual([]);
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
test("answered while a load is in the air, where the dialog is still up", () => {
|
|
1204
|
+
const {editor, remote, host} = build("y2", "db");
|
|
1205
|
+
gobj_send_event(editor, "EV_EXPORT", {}, host);
|
|
1206
|
+
const $content = modals[modals.length - 1].$content;
|
|
1207
|
+
gobj_send_event(editor, "EV_REFRESH", {}, host);
|
|
1208
|
+
expect(gobj_current_state(editor)).toBe("ST_LOADING");
|
|
1209
|
+
$content.querySelectorAll(".SCHEMA_EXPORT_TAB")[1].click();
|
|
1210
|
+
expect($content.querySelector(".SCHEMA_EXPORT_TEXT").value.trim().startsWith("{")).toBe(true);
|
|
1211
|
+
expect(not_defined()).toEqual([]);
|
|
1212
|
+
expect(answer_the_load(editor, remote)).toBe(3);
|
|
1213
|
+
expect(errors()).toEqual([]);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
test("a view that is not one, or no export open, is an ERROR, not a guess", () => {
|
|
1217
|
+
const {editor, remote, host} = build("y3", "db");
|
|
1218
|
+
gobj_send_event(editor, "EV_EXPORT_VIEW", {pane: "c"}, editor);
|
|
1219
|
+
gobj_send_event(editor, "EV_EXPORT", {}, host);
|
|
1220
|
+
gobj_send_event(editor, "EV_EXPORT_VIEW", {pane: "yaml"}, editor);
|
|
1221
|
+
expect(errors().length).toBe(2);
|
|
1222
|
+
});
|
|
1223
|
+
});
|
|
@@ -22,7 +22,7 @@ const {
|
|
|
22
22
|
gclass_create,
|
|
23
23
|
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
24
24
|
gobj_start, gobj_send_event,
|
|
25
|
-
gobj_change_state,
|
|
25
|
+
gobj_change_state, gobj_current_state,
|
|
26
26
|
set_log_callback,
|
|
27
27
|
} = gobj_js;
|
|
28
28
|
const {register_c_yui_shell, yui_shell_set_connection_state} = await import("./c_yui_shell.js");
|
|
@@ -34,11 +34,16 @@ const commands = []; /* what the fake transport was asked */
|
|
|
34
34
|
|
|
35
35
|
/*
|
|
36
36
|
* The transport: answers nothing, like a C_IEVENT_CLI whose socket is
|
|
37
|
-
* about to drop. Its state is what `is_connected()` reads.
|
|
37
|
+
* about to drop. Its state is what `is_connected()` reads. Out of
|
|
38
|
+
* session it refuses what it is asked, as gui_agent's routing adapter
|
|
39
|
+
* (C_AGENT_TREEDB_LINK) does.
|
|
38
40
|
*/
|
|
39
41
|
function remote_command_parser(gobj, command, kw, src)
|
|
40
42
|
{
|
|
41
43
|
commands.push({command, kw});
|
|
44
|
+
if(gobj_current_state(gobj) !== "ST_SESSION") {
|
|
45
|
+
return `cannot route '${command}' -- not in session`;
|
|
46
|
+
}
|
|
42
47
|
return null;
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -78,7 +83,11 @@ beforeAll(() => {
|
|
|
78
83
|
]]],
|
|
79
84
|
{},
|
|
80
85
|
0,
|
|
81
|
-
[
|
|
86
|
+
[
|
|
87
|
+
SDATA(data_type_t.DTP_STRING, "topic_name", 0, "", "topic"),
|
|
88
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_remote_paging", 0, false, "pulls its pages"),
|
|
89
|
+
SDATA_END()
|
|
90
|
+
],
|
|
82
91
|
{}, 0, 0, 0, 0
|
|
83
92
|
);
|
|
84
93
|
/* The host of the view: it hears what a CHILD view publishes. */
|
|
@@ -226,3 +235,107 @@ describe("a Save in flight when the backend drops (M8)", () => {
|
|
|
226
235
|
]);
|
|
227
236
|
});
|
|
228
237
|
});
|
|
238
|
+
|
|
239
|
+
/*
|
|
240
|
+
* The answer the routing adapter gives a write it had in flight when
|
|
241
|
+
* its session closed: a failure, settled by the adapter itself.
|
|
242
|
+
*/
|
|
243
|
+
function failed_write(topic_name, form_write)
|
|
244
|
+
{
|
|
245
|
+
const md = {topic_name: topic_name, treedb_name: "treedb_test", record: {id: "x"}};
|
|
246
|
+
if(form_write) {
|
|
247
|
+
md.form_write = form_write;
|
|
248
|
+
}
|
|
249
|
+
return {result: -1, comment: "the session closed", data: null, __md_iev__: {
|
|
250
|
+
command_stack: [{command: "update-node", kw: md}]
|
|
251
|
+
}};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function nodes_asked()
|
|
255
|
+
{
|
|
256
|
+
return commands.filter((c) => c.command === "nodes").map((c) => c.kw.topic_name);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
describe("a write cut by the drop does not reload out of session", () => {
|
|
260
|
+
|
|
261
|
+
test("the failure lands before the edge: no reload now, one on the reconnect", () => {
|
|
262
|
+
const {shell, topics} = build("t6");
|
|
263
|
+
yui_shell_set_connection_state(shell, true);
|
|
264
|
+
|
|
265
|
+
/* A cell edited in place: it looks saved until the topic is read again. */
|
|
266
|
+
gobj_send_event(topics, "EV_UPDATE_FIELD",
|
|
267
|
+
{topic_name: "users", id: "x", field: "name", value: "typed"}, topics);
|
|
268
|
+
expect(commands.filter((c) => c.command === "update-node").length).toBe(1);
|
|
269
|
+
|
|
270
|
+
/* The adapter leaves ST_SESSION and settles what it had in flight. */
|
|
271
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
272
|
+
gobj_send_event(topics, "EV_MT_COMMAND_ANSWER", failed_write("users", 0), remote);
|
|
273
|
+
|
|
274
|
+
expect(nodes_asked()).toEqual([]);
|
|
275
|
+
expect(errors().filter((m) => m.includes("not in session"))).toEqual([]);
|
|
276
|
+
|
|
277
|
+
gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: false}, topics);
|
|
278
|
+
expect(nodes_asked()).toEqual([]);
|
|
279
|
+
|
|
280
|
+
/* The reconnect reloads what the drop left unknown -- once, though
|
|
281
|
+
* two edges report it. */
|
|
282
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
283
|
+
gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
|
|
284
|
+
yui_shell_set_connection_state(shell, false);
|
|
285
|
+
yui_shell_set_connection_state(shell, true);
|
|
286
|
+
expect(nodes_asked()).toEqual(["users"]);
|
|
287
|
+
expect(errors()).toEqual([]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("the edge lands first: the form is answered once, the reload waits", () => {
|
|
291
|
+
const {shell, topics, forms} = build("t7");
|
|
292
|
+
yui_shell_set_connection_state(shell, true);
|
|
293
|
+
save(topics, forms.users, 1);
|
|
294
|
+
|
|
295
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
296
|
+
yui_shell_set_connection_state(shell, false);
|
|
297
|
+
gobj_send_event(topics, "EV_MT_COMMAND_ANSWER", failed_write("users", 1), remote);
|
|
298
|
+
|
|
299
|
+
expect(answers).toEqual([
|
|
300
|
+
{topic: "users", event: "EV_WRITE_REFUSED", form_write: 1},
|
|
301
|
+
]);
|
|
302
|
+
expect(nodes_asked()).toEqual([]);
|
|
303
|
+
|
|
304
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
305
|
+
yui_shell_set_connection_state(shell, true);
|
|
306
|
+
expect(nodes_asked()).toEqual(["users"]);
|
|
307
|
+
expect(errors()).toEqual([]);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("an edge that says 'up' before the transport is: the reload still waits", () => {
|
|
311
|
+
const {shell, topics} = build("t8");
|
|
312
|
+
yui_shell_set_connection_state(shell, true);
|
|
313
|
+
gobj_send_event(topics, "EV_UPDATE_FIELD",
|
|
314
|
+
{topic_name: "roles", id: "x", field: "name", value: "typed"}, topics);
|
|
315
|
+
gobj_change_state(remote, "ST_DISCONNECTED");
|
|
316
|
+
gobj_send_event(topics, "EV_MT_COMMAND_ANSWER", failed_write("roles", 0), remote);
|
|
317
|
+
yui_shell_set_connection_state(shell, false);
|
|
318
|
+
|
|
319
|
+
/* The app's connection is back, the view's transport is not yet. */
|
|
320
|
+
yui_shell_set_connection_state(shell, true);
|
|
321
|
+
expect(nodes_asked()).toEqual([]);
|
|
322
|
+
|
|
323
|
+
gobj_change_state(remote, "ST_SESSION");
|
|
324
|
+
gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
|
|
325
|
+
expect(nodes_asked()).toEqual(["roles"]);
|
|
326
|
+
expect(errors().filter((m) => m.includes("not in session"))).toEqual([]);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("a write refused IN session reloads its topic at once, and only then", () => {
|
|
330
|
+
const {shell, topics} = build("t9");
|
|
331
|
+
yui_shell_set_connection_state(shell, true);
|
|
332
|
+
gobj_send_event(topics, "EV_UPDATE_FIELD",
|
|
333
|
+
{topic_name: "users", id: "x", field: "name", value: "typed"}, topics);
|
|
334
|
+
gobj_send_event(topics, "EV_MT_COMMAND_ANSWER", failed_write("users", 0), remote);
|
|
335
|
+
expect(nodes_asked()).toEqual(["users"]);
|
|
336
|
+
|
|
337
|
+
gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: false}, topics);
|
|
338
|
+
gobj_send_event(topics, "EV_TRANSPORT_STATE", {connected: true}, topics);
|
|
339
|
+
expect(nodes_asked()).toEqual(["users"]);
|
|
340
|
+
});
|
|
341
|
+
});
|