@yuneta/gobj-ui 7.25.6 → 7.25.8
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 +53 -1
- package/dist/gobj-ui.cjs.js +330 -63
- package/dist/gobj-ui.es.js +330 -63
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +339 -66
- package/src/schema_editor_reload.wiring.test.js +473 -0
- package/src/schema_editor_transport.wiring.test.js +174 -1
- package/src/shell_modals.js +61 -12
- package/src/shell_toast_once.test.js +125 -0
|
@@ -206,6 +206,7 @@ let PRIVATE_DATA = {
|
|
|
206
206
|
pending: 0, /* `nodes` requests still in flight */
|
|
207
207
|
load_error: "",
|
|
208
208
|
load_round: 0, /* which load an answer belongs to: echoed as `round` */
|
|
209
|
+
load_keeps_written: false, /* the load in flight is not the host's: `written` survives it */
|
|
209
210
|
|
|
210
211
|
/* Where the operator is. The url carries it; these mirror it. */
|
|
211
212
|
treedb_id: "",
|
|
@@ -231,8 +232,11 @@ let PRIVATE_DATA = {
|
|
|
231
232
|
write_tag: 0, /* which write an answer belongs to: echoed as `write` */
|
|
232
233
|
connected: false,
|
|
233
234
|
reload_on_open: false, /* a drop cut a load or a write: ask the model again */
|
|
235
|
+
dropped_round: 0, /* the load a drop ended: its failures are the drop, said once */
|
|
236
|
+
dropped_write: 0, /* the same for the write a drop ended */
|
|
237
|
+
reload_after_write: false, /* a write given up turned out DONE while another was in flight */
|
|
234
238
|
|
|
235
|
-
/* A position that arrived while a write was in flight */
|
|
239
|
+
/* A position that arrived while a load or a write was in flight */
|
|
236
240
|
pending_seg: null,
|
|
237
241
|
|
|
238
242
|
/* The import dialog's plan and the two nodes it draws into */
|
|
@@ -455,8 +459,25 @@ function refuse_if_readonly(gobj, event)
|
|
|
455
459
|
* schema set of the yuno is here: everything below — the
|
|
456
460
|
* drawing, the validation, the export — is computed from these
|
|
457
461
|
* and asks the backend nothing more.
|
|
462
|
+
*
|
|
463
|
+
* THE LOADING SCREEN IS DRAWN, not assumed. ST_LOADING declares
|
|
464
|
+
* none of the actions of a screen, so the screen it replaces goes
|
|
465
|
+
* and the toolbar stops answering: left up, a click on a card, on
|
|
466
|
+
* Back or on the drawing answered "Event NOT DEFINED in state
|
|
467
|
+
* ST_LOADING" and was lost (third independent review). A dialog
|
|
468
|
+
* left open is answered in ST_LOADING instead (ac_wait_for_the_load).
|
|
469
|
+
*
|
|
470
|
+
* `keep_written`: a load the host did not ask for -- the reconnect,
|
|
471
|
+
* a write that turned out done -- keeps what this session wrote.
|
|
472
|
+
* Only the host's EV_REFRESH (a Save sends it) forgets it.
|
|
473
|
+
*
|
|
474
|
+
* A load that cannot leave WHOLE did not happen (out of session it
|
|
475
|
+
* is not even asked): the records the model was built on are put
|
|
476
|
+
* back (emptied, the next write patched them into a model with no
|
|
477
|
+
* treedb), the round is spent so a request that did leave is
|
|
478
|
+
* stale, and the session back asks again.
|
|
458
479
|
***************************************************************/
|
|
459
|
-
function request_model(gobj)
|
|
480
|
+
function request_model(gobj, keep_written)
|
|
460
481
|
{
|
|
461
482
|
let priv = gobj.priv;
|
|
462
483
|
let remote = gobj_read_pointer_attr(gobj, "gobj_remote_yuno");
|
|
@@ -465,27 +486,41 @@ function request_model(gobj)
|
|
|
465
486
|
return -1; /* not mounted on a transport yet: mt_start retries via EV_TRANSPORT_STATE */
|
|
466
487
|
}
|
|
467
488
|
|
|
489
|
+
let previous = priv.records;
|
|
468
490
|
priv.records = {treedbs: [], topics: [], cols: []};
|
|
469
491
|
priv.load_error = "";
|
|
470
492
|
priv.pending = 0;
|
|
471
493
|
priv.load_round++;
|
|
494
|
+
priv.load_keeps_written = !!keep_written;
|
|
472
495
|
gobj_change_state(gobj, "ST_LOADING");
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
496
|
+
set_busy(gobj, false);
|
|
497
|
+
render(gobj);
|
|
498
|
+
|
|
499
|
+
/* Out of session nothing is asked: each request would come back
|
|
500
|
+
* refused and logged as an ERROR, for a drop that is not one. */
|
|
501
|
+
let left = 0;
|
|
502
|
+
if(transport_in_session(gobj)) {
|
|
503
|
+
for(let topic_name of [T_TREEDBS, T_TOPICS, T_COLS]) {
|
|
504
|
+
if(remote_command(gobj, "nodes", {
|
|
505
|
+
topic_name: topic_name,
|
|
506
|
+
options: {list_dict: true}
|
|
507
|
+
}, {round: priv.load_round}) === 0) {
|
|
508
|
+
left++;
|
|
509
|
+
}
|
|
481
510
|
}
|
|
482
511
|
}
|
|
483
|
-
if(
|
|
512
|
+
if(left < 3) {
|
|
513
|
+
priv.records = previous;
|
|
514
|
+
priv.load_round++;
|
|
515
|
+
priv.reload_on_open = true;
|
|
484
516
|
priv.load_error = t("cannot reach the treedb");
|
|
485
|
-
|
|
486
|
-
|
|
517
|
+
if(priv.model) {
|
|
518
|
+
yui_shell_show_error(yui_shell_of(gobj), "cannot reach the treedb", {t: t});
|
|
519
|
+
}
|
|
520
|
+
end_load(gobj);
|
|
487
521
|
return -1;
|
|
488
522
|
}
|
|
523
|
+
priv.pending = left;
|
|
489
524
|
return 0;
|
|
490
525
|
}
|
|
491
526
|
|
|
@@ -568,17 +603,23 @@ function build_model(gobj)
|
|
|
568
603
|
* From here on, "did this session change it?" has an answer.
|
|
569
604
|
*
|
|
570
605
|
* Called ONLY when records arrive from the store: that is what a
|
|
571
|
-
* measurement of "changed since" can be measured against.
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
606
|
+
* measurement of "changed since" can be measured against.
|
|
607
|
+
*
|
|
608
|
+
* `written` is forgotten only by a load the HOST asked for (its
|
|
609
|
+
* EV_REFRESH, which a Save sends). Since M36 a write moves no
|
|
610
|
+
* version, so `written` is the draft chip of what this session
|
|
611
|
+
* wrote and no longer half of a version measurement: a reload of
|
|
612
|
+
* the reconnect, or of a write that turned out done, forgot the
|
|
613
|
+
* chips of writes the host had not been told about.
|
|
575
614
|
***************************************************************/
|
|
576
615
|
function start_measuring(gobj)
|
|
577
616
|
{
|
|
578
617
|
let priv = gobj.priv;
|
|
579
618
|
|
|
580
619
|
priv.baseline = {};
|
|
581
|
-
priv.
|
|
620
|
+
if(!priv.load_keeps_written) {
|
|
621
|
+
priv.written = {};
|
|
622
|
+
}
|
|
582
623
|
priv.host_ids = {};
|
|
583
624
|
|
|
584
625
|
if(!priv.model) {
|
|
@@ -802,12 +843,19 @@ function render_toolbar(gobj)
|
|
|
802
843
|
let state = gobj_current_state(gobj);
|
|
803
844
|
let readonly = is_readonly(gobj);
|
|
804
845
|
let has_model = !!priv.model;
|
|
846
|
+
/* The position can outlive its screen (a load that failed keeps the
|
|
847
|
+
* url's; ST_EMPTY has a treedb in the url and none in the model),
|
|
848
|
+
* and a button there sends what only a treedb screen declares. */
|
|
849
|
+
let on_a_treedb = state !== "ST_IDLE" && state !== "ST_EMPTY";
|
|
850
|
+
/* Nothing is legal while the model is in the air: every action is
|
|
851
|
+
* computed against it. */
|
|
852
|
+
let in_flight = state === "ST_SAVING" || state === "ST_LOADING";
|
|
805
853
|
|
|
806
854
|
/*----------------------------------------------*
|
|
807
855
|
* Left: back, then the position as a trail.
|
|
808
856
|
*----------------------------------------------*/
|
|
809
857
|
let $left = [];
|
|
810
|
-
if(priv.treedb_id) {
|
|
858
|
+
if(priv.treedb_id && on_a_treedb) {
|
|
811
859
|
$left.push(["button", {class: "SCHEMA_BACK button",
|
|
812
860
|
type: "button",
|
|
813
861
|
title: t("back"), "aria-label": t("back"),
|
|
@@ -844,7 +892,7 @@ function render_toolbar(gobj)
|
|
|
844
892
|
* refused says why when it is pressed.
|
|
845
893
|
*----------------------------------------------*/
|
|
846
894
|
let $right = [];
|
|
847
|
-
if(priv.treedb_id && has_model) {
|
|
895
|
+
if(priv.treedb_id && has_model && on_a_treedb) {
|
|
848
896
|
/* The compass, like every other door to a SCHEMA: the
|
|
849
897
|
* `hexagon-nodes` it wore is the graph of the DATA. */
|
|
850
898
|
$right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-compass-drafting",
|
|
@@ -858,7 +906,7 @@ function render_toolbar(gobj)
|
|
|
858
906
|
"import", "EV_IMPORT", false));
|
|
859
907
|
}
|
|
860
908
|
}
|
|
861
|
-
if(priv.topic_name && !readonly && !priv.diagram) {
|
|
909
|
+
if(priv.topic_name && !readonly && !priv.diagram && on_a_treedb) {
|
|
862
910
|
/* Only while there IS somewhere to go back to: a drag is a write,
|
|
863
911
|
* and this is the way back from one. */
|
|
864
912
|
if(undo_order_writes(gobj, current_topic(gobj)).length > 0) {
|
|
@@ -868,7 +916,7 @@ function render_toolbar(gobj)
|
|
|
868
916
|
$right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus",
|
|
869
917
|
"new column", "EV_ADD_COLUMN", false));
|
|
870
918
|
}
|
|
871
|
-
if(priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram) {
|
|
919
|
+
if(priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram && on_a_treedb) {
|
|
872
920
|
$right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus",
|
|
873
921
|
"new topic", "EV_ADD_TOPIC", false));
|
|
874
922
|
}
|
|
@@ -891,7 +939,7 @@ function render_toolbar(gobj)
|
|
|
891
939
|
* destroyed. */
|
|
892
940
|
for(let $button of $bar.querySelectorAll("button")) {
|
|
893
941
|
$button.__gobj__ = gobj;
|
|
894
|
-
if(
|
|
942
|
+
if(in_flight) {
|
|
895
943
|
$button.disabled = true;
|
|
896
944
|
}
|
|
897
945
|
}
|
|
@@ -915,19 +963,25 @@ function render(gobj)
|
|
|
915
963
|
}
|
|
916
964
|
render_toolbar(gobj);
|
|
917
965
|
|
|
966
|
+
/* A screen with nothing to draw takes the drawing down too: left
|
|
967
|
+
* alive, it was only re-shown on the way back -- into a body that
|
|
968
|
+
* no longer held it. */
|
|
918
969
|
if(state === "ST_LOADING") {
|
|
919
970
|
show_notice(gobj, "loading the schemas");
|
|
971
|
+
destroy_diagram(gobj);
|
|
920
972
|
clear($body);
|
|
921
973
|
return;
|
|
922
974
|
}
|
|
923
975
|
if(state === "ST_IDLE") {
|
|
924
976
|
show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected",
|
|
925
977
|
priv.load_error);
|
|
978
|
+
destroy_diagram(gobj);
|
|
926
979
|
clear($body);
|
|
927
980
|
return;
|
|
928
981
|
}
|
|
929
982
|
if(state === "ST_EMPTY") {
|
|
930
983
|
show_notice(gobj, "this yuno stores no schema");
|
|
984
|
+
destroy_diagram(gobj);
|
|
931
985
|
clear($body);
|
|
932
986
|
return;
|
|
933
987
|
}
|
|
@@ -1468,12 +1522,21 @@ function render_diagram(gobj)
|
|
|
1468
1522
|
let $body = $of(gobj, ".SCHEMA_BODY");
|
|
1469
1523
|
let treedb = current_treedb(gobj);
|
|
1470
1524
|
|
|
1471
|
-
if(!$body
|
|
1525
|
+
if(!$body) {
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
/* Said on an EMPTY body, as the topics and the columns say it:
|
|
1529
|
+
* render() clears the body for every screen but this one, and the
|
|
1530
|
+
* screen before stayed up under the notice, clickable. */
|
|
1531
|
+
if(!treedb) {
|
|
1532
|
+
destroy_diagram(gobj);
|
|
1533
|
+
clear($body);
|
|
1472
1534
|
show_notice(gobj, "that treedb is not here any more", priv.treedb_id);
|
|
1473
1535
|
return;
|
|
1474
1536
|
}
|
|
1475
1537
|
if(!gclass_find_by_name("C_YUI_TREEDB_SCHEMA")) {
|
|
1476
1538
|
log_error(`${gobj_short_name(gobj)}: C_YUI_TREEDB_SCHEMA not registered by the app`);
|
|
1539
|
+
clear($body);
|
|
1477
1540
|
show_notice(gobj, "the schema drawing is not available");
|
|
1478
1541
|
return;
|
|
1479
1542
|
}
|
|
@@ -2403,6 +2466,15 @@ function transport_in_session(gobj)
|
|
|
2403
2466
|
* one the drop abandoned (or a deadline settled) can still come
|
|
2404
2467
|
* back: counted, it ended the next load with the old data or
|
|
2405
2468
|
* moved the next write's queue.
|
|
2469
|
+
*
|
|
2470
|
+
* The state is part of the question, and that is only true
|
|
2471
|
+
* because NOTHING moves the editor out of ST_LOADING or ST_SAVING
|
|
2472
|
+
* but the end of what is in flight: a position the host sends
|
|
2473
|
+
* meanwhile waits in `pending_seg` (ac_show()). 7.25.6 let EV_SHOW
|
|
2474
|
+
* move a RELOAD out of ST_LOADING, and every answer of it was then
|
|
2475
|
+
* "for a load that is over": the records it had emptied stayed
|
|
2476
|
+
* empty, and the next write patched them into a model with no
|
|
2477
|
+
* treedb at all.
|
|
2406
2478
|
***************************************************************/
|
|
2407
2479
|
function is_current_load(gobj, md)
|
|
2408
2480
|
{
|
|
@@ -2431,19 +2503,51 @@ function transport_dropped(gobj)
|
|
|
2431
2503
|
|
|
2432
2504
|
priv.reload_on_open = true;
|
|
2433
2505
|
if(state === "ST_SAVING") {
|
|
2506
|
+
log_warning(`${gobj_short_name(gobj)}: the transport dropped during a write: ` +
|
|
2507
|
+
`the model is read again on the reconnect`);
|
|
2508
|
+
priv.dropped_write = priv.write_tag;
|
|
2434
2509
|
priv.write_tag++;
|
|
2435
2510
|
return end_writes(gobj, "the connection dropped during the write");
|
|
2436
2511
|
}
|
|
2437
2512
|
if(state === "ST_LOADING") {
|
|
2513
|
+
/* Said HERE, once: the transport settles every request the drop
|
|
2514
|
+
* cut, and those failures are this one event (see
|
|
2515
|
+
* ac_mt_command_answer()). */
|
|
2516
|
+
log_warning(`${gobj_short_name(gobj)}: the transport dropped during the load: ` +
|
|
2517
|
+
`the model is read again on the reconnect`);
|
|
2518
|
+
priv.dropped_round = priv.load_round;
|
|
2438
2519
|
priv.load_round++;
|
|
2439
2520
|
priv.pending = 0;
|
|
2440
2521
|
priv.load_error = "";
|
|
2441
|
-
|
|
2442
|
-
|
|
2522
|
+
return end_load(gobj);
|
|
2523
|
+
}
|
|
2524
|
+
return 0;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
/***************************************************************
|
|
2528
|
+
* A load is over -- landed, failed or cut -- and the editor goes
|
|
2529
|
+
* where the operator is: the position the host sent while it was
|
|
2530
|
+
* in flight (ac_show() kept it), or the one it had. Without a
|
|
2531
|
+
* model there is no screen to go to: the position is kept for the
|
|
2532
|
+
* next load, and the editor says why it is empty.
|
|
2533
|
+
***************************************************************/
|
|
2534
|
+
function end_load(gobj)
|
|
2535
|
+
{
|
|
2536
|
+
let priv = gobj.priv;
|
|
2537
|
+
let seg = priv.pending_seg;
|
|
2538
|
+
|
|
2539
|
+
priv.pending_seg = null;
|
|
2540
|
+
if(priv.model) {
|
|
2541
|
+
if(seg !== null) {
|
|
2542
|
+
return apply_seg(gobj, seg);
|
|
2443
2543
|
}
|
|
2444
|
-
|
|
2445
|
-
|
|
2544
|
+
return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
|
|
2545
|
+
}
|
|
2546
|
+
if(seg !== null) {
|
|
2547
|
+
adopt_seg(gobj, seg);
|
|
2446
2548
|
}
|
|
2549
|
+
gobj_change_state(gobj, "ST_IDLE");
|
|
2550
|
+
render(gobj);
|
|
2447
2551
|
return 0;
|
|
2448
2552
|
}
|
|
2449
2553
|
|
|
@@ -2457,29 +2561,15 @@ function end_writes(gobj, error)
|
|
|
2457
2561
|
{
|
|
2458
2562
|
let priv = gobj.priv;
|
|
2459
2563
|
|
|
2564
|
+
let seg = priv.pending_seg;
|
|
2565
|
+
|
|
2460
2566
|
priv.save_queue = [];
|
|
2567
|
+
priv.pending_seg = null;
|
|
2461
2568
|
gobj_change_state(gobj, priv.save_return || "ST_TREEDBS");
|
|
2462
2569
|
priv.save_return = "";
|
|
2463
2570
|
show_notice(gobj, "");
|
|
2464
2571
|
set_busy(gobj, false);
|
|
2465
2572
|
|
|
2466
|
-
/* A position that arrived while the write was in flight: applied
|
|
2467
|
-
* now, so a Back pressed mid-save is not simply lost. */
|
|
2468
|
-
if(priv.pending_seg !== null) {
|
|
2469
|
-
let seg = priv.pending_seg;
|
|
2470
|
-
priv.pending_seg = null;
|
|
2471
|
-
if(!error) {
|
|
2472
|
-
apply_seg(gobj, seg);
|
|
2473
|
-
if(priv.save_wrote) {
|
|
2474
|
-
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
2475
|
-
treedb_name: gobj_read_str_attr(gobj, "treedb_name")
|
|
2476
|
-
});
|
|
2477
|
-
}
|
|
2478
|
-
priv.save_wrote = false;
|
|
2479
|
-
return 0;
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
2573
|
if(error) {
|
|
2484
2574
|
yui_shell_show_error(yui_shell_of(gobj), error, {t: t});
|
|
2485
2575
|
}
|
|
@@ -2489,11 +2579,42 @@ function end_writes(gobj, error)
|
|
|
2489
2579
|
});
|
|
2490
2580
|
}
|
|
2491
2581
|
priv.save_wrote = false;
|
|
2492
|
-
|
|
2493
|
-
|
|
2582
|
+
|
|
2583
|
+
/* A position that arrived while the write was in flight: applied
|
|
2584
|
+
* now, so a Back pressed mid-save is not simply lost -- with an
|
|
2585
|
+
* error too. The host's url already says it, and a load cut by a
|
|
2586
|
+
* drop applies it (end_load()): the write kept the old screen, and
|
|
2587
|
+
* the url and the view disagreed. */
|
|
2588
|
+
if(seg !== null) {
|
|
2589
|
+
apply_seg(gobj, seg);
|
|
2590
|
+
} else {
|
|
2591
|
+
render(gobj);
|
|
2592
|
+
publish_check(gobj);
|
|
2593
|
+
}
|
|
2594
|
+
reload_if_owed(gobj);
|
|
2494
2595
|
return error ? -1 : 0;
|
|
2495
2596
|
}
|
|
2496
2597
|
|
|
2598
|
+
/***************************************************************
|
|
2599
|
+
* A write given up earlier turned out DONE while another one was
|
|
2600
|
+
* in flight (see ac_mt_command_answer()): the model is read again
|
|
2601
|
+
* now that nothing is.
|
|
2602
|
+
***************************************************************/
|
|
2603
|
+
function reload_if_owed(gobj)
|
|
2604
|
+
{
|
|
2605
|
+
let priv = gobj.priv;
|
|
2606
|
+
|
|
2607
|
+
if(!priv.reload_after_write) {
|
|
2608
|
+
return 0;
|
|
2609
|
+
}
|
|
2610
|
+
priv.reload_after_write = false;
|
|
2611
|
+
if(!transport_in_session(gobj)) {
|
|
2612
|
+
priv.reload_on_open = true;
|
|
2613
|
+
return 0;
|
|
2614
|
+
}
|
|
2615
|
+
return request_model(gobj, true);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2497
2618
|
/***************************************************************
|
|
2498
2619
|
* The record a column form produces.
|
|
2499
2620
|
*
|
|
@@ -2595,13 +2716,20 @@ function go(gobj, treedb_id, topic_name, diagram, mirror)
|
|
|
2595
2716
|
{
|
|
2596
2717
|
let priv = gobj.priv;
|
|
2597
2718
|
|
|
2719
|
+
/* Moving IS the answer to a position that waited: one left behind
|
|
2720
|
+
* sent a later load back to it. */
|
|
2721
|
+
priv.pending_seg = null;
|
|
2598
2722
|
priv.treedb_id = treedb_id || "";
|
|
2599
2723
|
priv.topic_name = topic_name || "";
|
|
2600
2724
|
priv.diagram = !!diagram;
|
|
2601
2725
|
|
|
2726
|
+
/* No model and no load (a load in flight never gets here: ac_show()
|
|
2727
|
+
* keeps the position for it): nothing to draw, and ST_LOADING would
|
|
2728
|
+
* claim a load that nobody asked for -- the reconnect skipped the one
|
|
2729
|
+
* it owed BECAUSE the state said a load was running. */
|
|
2602
2730
|
let state = "ST_TREEDBS";
|
|
2603
2731
|
if(!priv.model) {
|
|
2604
|
-
state = "
|
|
2732
|
+
state = "ST_IDLE";
|
|
2605
2733
|
} else if(priv.model.treedbs.length === 0) {
|
|
2606
2734
|
state = "ST_EMPTY";
|
|
2607
2735
|
} else if(priv.treedb_id && priv.diagram) {
|
|
@@ -2647,18 +2775,39 @@ function position_seg(gobj)
|
|
|
2647
2775
|
* empty screen: a shared link outlives the schema it points at.
|
|
2648
2776
|
***************************************************************/
|
|
2649
2777
|
function apply_seg(gobj, seg)
|
|
2778
|
+
{
|
|
2779
|
+
let pos = seg_position(seg);
|
|
2780
|
+
|
|
2781
|
+
return go(gobj, pos.treedb_id, pos.topic_name, pos.diagram, false);
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
function seg_position(seg)
|
|
2650
2785
|
{
|
|
2651
2786
|
let parts = String(seg || "").split("/").filter(x => x.length > 0);
|
|
2652
2787
|
let treedb_id = parts[0] || "";
|
|
2653
2788
|
let tail = parts[1] || "";
|
|
2654
2789
|
|
|
2655
2790
|
if(!treedb_id) {
|
|
2656
|
-
return
|
|
2791
|
+
return {treedb_id: "", topic_name: "", diagram: false};
|
|
2657
2792
|
}
|
|
2658
2793
|
if(tail === DIAGRAM_SEG) {
|
|
2659
|
-
return
|
|
2794
|
+
return {treedb_id: treedb_id, topic_name: "", diagram: true};
|
|
2660
2795
|
}
|
|
2661
|
-
return
|
|
2796
|
+
return {treedb_id: treedb_id, topic_name: tail, diagram: false};
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
/***************************************************************
|
|
2800
|
+
* Where the operator is, taken WITHOUT a screen: there is no model
|
|
2801
|
+
* to draw it on yet, and the load that brings one goes there.
|
|
2802
|
+
***************************************************************/
|
|
2803
|
+
function adopt_seg(gobj, seg)
|
|
2804
|
+
{
|
|
2805
|
+
let priv = gobj.priv;
|
|
2806
|
+
let pos = seg_position(seg);
|
|
2807
|
+
|
|
2808
|
+
priv.treedb_id = pos.treedb_id;
|
|
2809
|
+
priv.topic_name = pos.topic_name;
|
|
2810
|
+
priv.diagram = pos.diagram;
|
|
2662
2811
|
}
|
|
2663
2812
|
|
|
2664
2813
|
|
|
@@ -2719,6 +2868,9 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2719
2868
|
if(command === "nodes") {
|
|
2720
2869
|
let topic_name = kw_get_str(gobj, md, "topic_name", "", 0);
|
|
2721
2870
|
if(!is_current_load(gobj, md)) {
|
|
2871
|
+
if(result < 0 && kw_get_int(gobj, md, "round", 0, 0) === priv.dropped_round) {
|
|
2872
|
+
return 0; /* the drop that ended this load failed it: said once, in transport_dropped() */
|
|
2873
|
+
}
|
|
2722
2874
|
log_warning(`${gobj_short_name(gobj)}: 'nodes' of '${topic_name}' answered ` +
|
|
2723
2875
|
`for a load that is over: ignored`);
|
|
2724
2876
|
return 0;
|
|
@@ -2734,9 +2886,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2734
2886
|
}
|
|
2735
2887
|
if(priv.load_error) {
|
|
2736
2888
|
priv.model = null;
|
|
2737
|
-
|
|
2738
|
-
render(gobj);
|
|
2739
|
-
return 0;
|
|
2889
|
+
return end_load(gobj);
|
|
2740
2890
|
}
|
|
2741
2891
|
/* Records straight from the store: what an undo would name is the
|
|
2742
2892
|
* order that was ON SCREEN, and this is the stored one arriving.
|
|
@@ -2749,17 +2899,24 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2749
2899
|
* against. Taken on the write patches instead, it measured every
|
|
2750
2900
|
* version against itself. */
|
|
2751
2901
|
start_measuring(gobj);
|
|
2752
|
-
/* Back to where the operator was
|
|
2753
|
-
* the answer to a deep link that
|
|
2754
|
-
|
|
2902
|
+
/* Back to where the operator was, or went while it loaded: the
|
|
2903
|
+
* load may be a refresh, or the answer to a deep link that
|
|
2904
|
+
* arrived before the model. */
|
|
2905
|
+
return end_load(gobj);
|
|
2755
2906
|
}
|
|
2756
2907
|
|
|
2757
2908
|
if(command === "update-node" || command === "delete-node") {
|
|
2758
2909
|
let topic_name = kw_get_str(gobj, md, "topic_name", "", 0);
|
|
2759
2910
|
if(!is_current_write(gobj, md)) {
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2911
|
+
if(result < 0 && kw_get_int(gobj, md, "write", 0, 0) === priv.dropped_write) {
|
|
2912
|
+
return 0; /* the drop that ended this write failed it: said once, in transport_dropped() */
|
|
2913
|
+
}
|
|
2914
|
+
if(result < 0) {
|
|
2915
|
+
log_warning(`${gobj_short_name(gobj)}: '${command}' of '${topic_name}' answered ` +
|
|
2916
|
+
`for a write that is over (result ${result}): ignored`);
|
|
2917
|
+
return 0;
|
|
2918
|
+
}
|
|
2919
|
+
return late_write_done(gobj, command, topic_name, data);
|
|
2763
2920
|
}
|
|
2764
2921
|
if(result < 0) {
|
|
2765
2922
|
return end_writes(gobj, comment || t("the treedb refused the write"));
|
|
@@ -2776,8 +2933,17 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2776
2933
|
/* The treedb wrote it and did not describe it back: ask
|
|
2777
2934
|
* again rather than draw a model that has drifted. */
|
|
2778
2935
|
log_warning(`${gobj_short_name(gobj)}: '${command}' answered no record`);
|
|
2936
|
+
if(topic_name === T_TOPICS || topic_name === T_COLS) {
|
|
2937
|
+
let write = priv.save_queue[0];
|
|
2938
|
+
mark_written(gobj, topic_name, write ? write.record : null);
|
|
2939
|
+
}
|
|
2779
2940
|
priv.save_queue = [];
|
|
2780
|
-
|
|
2941
|
+
priv.save_return = "";
|
|
2942
|
+
priv.save_wrote = false;
|
|
2943
|
+
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
2944
|
+
treedb_name: gobj_read_str_attr(gobj, "treedb_name")
|
|
2945
|
+
});
|
|
2946
|
+
request_model(gobj, true);
|
|
2781
2947
|
return 0;
|
|
2782
2948
|
}
|
|
2783
2949
|
}
|
|
@@ -2796,6 +2962,81 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2796
2962
|
return 0;
|
|
2797
2963
|
}
|
|
2798
2964
|
|
|
2965
|
+
/***************************************************************
|
|
2966
|
+
* A write this view gave up on (a drop, a deadline) answers that
|
|
2967
|
+
* it was DONE: the store holds a change the model does not show.
|
|
2968
|
+
* The model is read again -- now, or when what is in flight ends,
|
|
2969
|
+
* or on the reconnect. A load in flight needs nothing: it was
|
|
2970
|
+
* asked after the write, on the same transport, and reads it.
|
|
2971
|
+
*
|
|
2972
|
+
* Only when the answer reaches this view. A routing adapter that
|
|
2973
|
+
* settled the write on its own deadline (gui_agent's
|
|
2974
|
+
* C_AGENT_TREEDB_LINK) does not deliver its late answer again --
|
|
2975
|
+
* the view was answered once, as failed -- and echoes the node
|
|
2976
|
+
* event instead, which this view does not hear: there, Refresh is
|
|
2977
|
+
* what reads the store.
|
|
2978
|
+
*
|
|
2979
|
+
* It is a WRITE like any other for the host (EV_RECORD_WRITTEN):
|
|
2980
|
+
* the host was told nothing when it was given up, and it owns the
|
|
2981
|
+
* Save that publishes it. The reload keeps what this session wrote
|
|
2982
|
+
* (request_model()), and the record it answered marks its topic.
|
|
2983
|
+
***************************************************************/
|
|
2984
|
+
function late_write_done(gobj, command, topic_name, data)
|
|
2985
|
+
{
|
|
2986
|
+
let priv = gobj.priv;
|
|
2987
|
+
let state = gobj_current_state(gobj);
|
|
2988
|
+
|
|
2989
|
+
log_warning(`${gobj_short_name(gobj)}: '${command}' of '${topic_name}' was given up, ` +
|
|
2990
|
+
`and it was DONE: the model is read again`);
|
|
2991
|
+
mark_written_record(gobj, topic_name, is_object(data) ? data
|
|
2992
|
+
: (Array.isArray(data) && is_object(data[0]) ? data[0] : null));
|
|
2993
|
+
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
2994
|
+
treedb_name: gobj_read_str_attr(gobj, "treedb_name")
|
|
2995
|
+
});
|
|
2996
|
+
if(state === "ST_LOADING") {
|
|
2997
|
+
return 0;
|
|
2998
|
+
}
|
|
2999
|
+
if(state === "ST_SAVING") {
|
|
3000
|
+
priv.reload_after_write = true;
|
|
3001
|
+
return 0;
|
|
3002
|
+
}
|
|
3003
|
+
if(!transport_in_session(gobj)) {
|
|
3004
|
+
priv.reload_on_open = true;
|
|
3005
|
+
return 0;
|
|
3006
|
+
}
|
|
3007
|
+
return request_model(gobj, true);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
/***************************************************************
|
|
3011
|
+
* The topic a record written belongs to, read off the RECORD and
|
|
3012
|
+
* not off the screen: a write answered late may be about a topic
|
|
3013
|
+
* the operator has left. A column names its topic in its fkey
|
|
3014
|
+
* (`topics^<topic id>^cols`); a topic is its own id.
|
|
3015
|
+
***************************************************************/
|
|
3016
|
+
function mark_written_record(gobj, topic_name, record)
|
|
3017
|
+
{
|
|
3018
|
+
let priv = gobj.priv;
|
|
3019
|
+
|
|
3020
|
+
if(!record) {
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
if(topic_name === T_TOPICS && typeof record.id === "string" && record.id) {
|
|
3024
|
+
priv.written[record.id] = true;
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
if(topic_name !== T_COLS) {
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
let refs = Array.isArray(record.topics) ? record.topics
|
|
3031
|
+
: (typeof record.topics === "string" ? [record.topics] : []);
|
|
3032
|
+
for(let ref of refs) {
|
|
3033
|
+
let parts = String(ref).split("^");
|
|
3034
|
+
if(parts.length >= 2 && parts[1]) {
|
|
3035
|
+
priv.written[parts[1]] = true;
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
|
|
2799
3040
|
/***************************************************************
|
|
2800
3041
|
* Remember which topic this write belongs to, which is what
|
|
2801
3042
|
* "its version still has to move" is asked about.
|
|
@@ -2822,10 +3063,14 @@ function ac_show(gobj, event, kw, src)
|
|
|
2822
3063
|
{
|
|
2823
3064
|
let priv = gobj.priv;
|
|
2824
3065
|
let seg = (kw && kw.subpath) || "";
|
|
3066
|
+
let state = gobj_current_state(gobj);
|
|
2825
3067
|
|
|
2826
|
-
|
|
3068
|
+
/* Nothing but the end of what is in flight moves the editor out of
|
|
3069
|
+
* these two (see is_current_load()): the position waits, and the
|
|
3070
|
+
* last one sent wins. */
|
|
3071
|
+
if(state === "ST_SAVING" || state === "ST_LOADING") {
|
|
2827
3072
|
priv.pending_seg = seg;
|
|
2828
|
-
return 0; /* applied when the write lands (end_writes) */
|
|
3073
|
+
return 0; /* applied when the write lands (end_writes) or the load (end_load) */
|
|
2829
3074
|
}
|
|
2830
3075
|
return apply_seg(gobj, seg);
|
|
2831
3076
|
}
|
|
@@ -2867,7 +3112,7 @@ function ac_transport_state(gobj, event, kw, src)
|
|
|
2867
3112
|
if((!priv.model || priv.reload_on_open) &&
|
|
2868
3113
|
state !== "ST_LOADING" && state !== "ST_SAVING") {
|
|
2869
3114
|
priv.reload_on_open = false;
|
|
2870
|
-
return request_model(gobj);
|
|
3115
|
+
return request_model(gobj, true);
|
|
2871
3116
|
}
|
|
2872
3117
|
render_toolbar(gobj);
|
|
2873
3118
|
return 0;
|
|
@@ -2886,7 +3131,28 @@ function ac_language_changed(gobj, event, kw, src)
|
|
|
2886
3131
|
|
|
2887
3132
|
function ac_refresh(gobj, event, kw, src)
|
|
2888
3133
|
{
|
|
2889
|
-
return request_model(gobj);
|
|
3134
|
+
return request_model(gobj, false);
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
/***************************************************************
|
|
3138
|
+
* What a DIALOG sends while the model is being read again: the
|
|
3139
|
+
* Save of a form left open when the reload started, a
|
|
3140
|
+
* confirmation, an import. The loading screen took the rest of
|
|
3141
|
+
* the view away (request_model()); a dialog is the one thing the
|
|
3142
|
+
* operator can still reach, and what it asks is computed against
|
|
3143
|
+
* a model the answers in the air are about to replace.
|
|
3144
|
+
*
|
|
3145
|
+
* Refused, and SAID -- and the dialog stays open with what was
|
|
3146
|
+
* typed, so the same Save works once the load is in. Undeclared,
|
|
3147
|
+
* it answered "Event NOT DEFINED in state ST_LOADING" and the
|
|
3148
|
+
* edit was gone with no word (third independent review).
|
|
3149
|
+
***************************************************************/
|
|
3150
|
+
function ac_wait_for_the_load(gobj, event, kw, src)
|
|
3151
|
+
{
|
|
3152
|
+
log_warning(`${gobj_short_name(gobj)}: ${event} refused, the schemas are loading`);
|
|
3153
|
+
yui_shell_show_error(yui_shell_of(gobj),
|
|
3154
|
+
"the schemas are loading: try again when they are in", {t: t});
|
|
3155
|
+
return -1;
|
|
2890
3156
|
}
|
|
2891
3157
|
|
|
2892
3158
|
/***************************************************************
|
|
@@ -3518,7 +3784,14 @@ function create_gclass(gclass_name)
|
|
|
3518
3784
|
const states = [
|
|
3519
3785
|
["ST_IDLE", COMMON.slice()],
|
|
3520
3786
|
|
|
3521
|
-
["ST_LOADING", COMMON.
|
|
3787
|
+
["ST_LOADING", COMMON.concat([
|
|
3788
|
+
["EV_SAVE_COLUMN", ac_wait_for_the_load, null],
|
|
3789
|
+
["EV_SAVE_TOPIC", ac_wait_for_the_load, null],
|
|
3790
|
+
["EV_DELETE_ORPHAN", ac_wait_for_the_load, null],
|
|
3791
|
+
["EV_PREVIEW_IMPORT", ac_wait_for_the_load, null],
|
|
3792
|
+
["EV_APPLY_IMPORT", ac_wait_for_the_load, null],
|
|
3793
|
+
["EV_CONFIRMED", ac_wait_for_the_load, null]
|
|
3794
|
+
])],
|
|
3522
3795
|
|
|
3523
3796
|
["ST_EMPTY", COMMON.slice()],
|
|
3524
3797
|
|