@yuneta/gobj-ui 7.23.191 → 7.23.193
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 +24 -0
- package/dist/gobj-ui.cjs.js +238 -62
- package/dist/gobj-ui.es.js +238 -62
- package/package.json +3 -3
- package/src/c_g6_nodes_tree.js +15 -2
- package/src/c_yui_treedb_topic_with_form.js +143 -49
- package/src/c_yui_treedb_topics.js +62 -15
- package/src/graph_save_plan.js +40 -0
- package/src/graph_save_plan.test.js +57 -1
- package/src/yui_row_search.js +7 -2
- package/src/yui_row_search.test.js +22 -0
- package/src/yui_table_select.js +50 -0
- package/src/yui_table_select.test.js +54 -0
package/README.md
CHANGED
|
@@ -774,6 +774,30 @@ A refused write puts the topic back to what the treedb has. Leaving the typed
|
|
|
774
774
|
value on screen is tolerable for a form, which stays open on the values it
|
|
775
775
|
failed with; a cell edited in place would just look saved.
|
|
776
776
|
|
|
777
|
+
**The form stays open until the backend answers** (7.23.193). With
|
|
778
|
+
`form_waits_for_answer` set — `C_YUI_TREEDB_TOPICS` sets it — a Save leaves the
|
|
779
|
+
form open and busy, and `EV_CREATE_RECORD` / `EV_UPDATE_RECORD` carry a
|
|
780
|
+
`form_write` serial. The host echoes it back in `EV_WRITE_DONE` (the form
|
|
781
|
+
closes) or `EV_WRITE_REFUSED` (it stays open on what was typed), and it must
|
|
782
|
+
answer EVERY way the write can end: refused before it left (read-only, no
|
|
783
|
+
session) as well as answered by the backend, or the form waits forever. A host
|
|
784
|
+
that does not answer leaves the attribute off, and the form closes when the
|
|
785
|
+
write is published, as before 7.23.193 — whatever the backend says.
|
|
786
|
+
|
|
787
|
+
```js
|
|
788
|
+
// A host of C_YUI_TREEDB_TOPIC_WITH_FORM that answers the form's writes
|
|
789
|
+
gobj_create_pure_child(name, "C_YUI_TREEDB_TOPIC_WITH_FORM",
|
|
790
|
+
{topic_name: "users", form_waits_for_answer: true /* ... */}, host);
|
|
791
|
+
// on the backend's answer to the write that carried kw.form_write:
|
|
792
|
+
gobj_send_event(topic_form, ok ? "EV_WRITE_DONE" : "EV_WRITE_REFUSED",
|
|
793
|
+
{form_write: kw.form_write}, host);
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
**A delete is resolved by IDENTITY, never by position** (7.23.193). The row's
|
|
797
|
+
`id` crosses the confirmation dialog; a Tabulator position is regenerated by
|
|
798
|
+
every row the node events add or remove while the dialog is open, and it used
|
|
799
|
+
to delete the row that sat there when the person answered.
|
|
800
|
+
|
|
777
801
|
### Reading a topic table: filters, columns, CSV
|
|
778
802
|
|
|
779
803
|
`C_YUI_TREEDB_TOPIC_WITH_FORM` had one global search box over the loaded rows.
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -24549,7 +24549,8 @@ function process_treedb_descs$1(gobj) {
|
|
|
24549
24549
|
desc,
|
|
24550
24550
|
with_remote_paging: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_remote_paging"),
|
|
24551
24551
|
page_size: (0, _yuneta_gobj_js.gobj_read_integer_attr)(gobj, "page_size"),
|
|
24552
|
-
with_selection_bar: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_selection_bar")
|
|
24552
|
+
with_selection_bar: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_selection_bar"),
|
|
24553
|
+
form_waits_for_answer: true
|
|
24553
24554
|
};
|
|
24554
24555
|
let id = `${(0, _yuneta_gobj_js.gobj_name)(gobj)}?${desc.topic_name}`;
|
|
24555
24556
|
let gobj_topic_form = (0, _yuneta_gobj_js.gobj_create_service)(id, "C_YUI_TREEDB_TOPIC_WITH_FORM", kw_topic_form, gobj);
|
|
@@ -24695,7 +24696,7 @@ function treedb_nodes$1(gobj, treedb_name, topic_name, options, page, purpose) {
|
|
|
24695
24696
|
/************************************************************
|
|
24696
24697
|
* Command to remote service
|
|
24697
24698
|
************************************************************/
|
|
24698
|
-
function treedb_update_node$1(gobj, treedb_name, topic_name, record, options) {
|
|
24699
|
+
function treedb_update_node$1(gobj, treedb_name, topic_name, record, options, form_write) {
|
|
24699
24700
|
let command = "update-node";
|
|
24700
24701
|
let gobj_remote_yuno = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "gobj_remote_yuno");
|
|
24701
24702
|
let kw = {
|
|
@@ -24706,8 +24707,13 @@ function treedb_update_node$1(gobj, treedb_name, topic_name, record, options) {
|
|
|
24706
24707
|
options: options || {}
|
|
24707
24708
|
};
|
|
24708
24709
|
kw.__md_command__ = { topic_name };
|
|
24710
|
+
if (form_write) kw.__md_command__.form_write = form_write;
|
|
24709
24711
|
let ret = (0, _yuneta_gobj_js.gobj_command)(gobj_remote_yuno, command, kw, gobj);
|
|
24710
|
-
if (ret)
|
|
24712
|
+
if (ret) {
|
|
24713
|
+
(0, _yuneta_gobj_js.log_error)(ret);
|
|
24714
|
+
return -1;
|
|
24715
|
+
}
|
|
24716
|
+
return 0;
|
|
24711
24717
|
}
|
|
24712
24718
|
/************************************************************
|
|
24713
24719
|
* Command to remote service
|
|
@@ -24970,6 +24976,7 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
24970
24976
|
if (command === "update-node" || command === "create-node") {
|
|
24971
24977
|
let failed_topic = (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0);
|
|
24972
24978
|
if (failed_topic) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REFRESH_TOPIC", { topic_name: failed_topic }, gobj);
|
|
24979
|
+
answer_form_write(gobj, get_gobj_formtable(gobj, failed_topic), (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), false);
|
|
24973
24980
|
}
|
|
24974
24981
|
}
|
|
24975
24982
|
return 0;
|
|
@@ -25006,6 +25013,7 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
|
|
|
25006
25013
|
created: command === "create-node",
|
|
25007
25014
|
command
|
|
25008
25015
|
});
|
|
25016
|
+
answer_form_write(gobj, get_gobj_formtable(gobj, (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0)), (0, _yuneta_gobj_js.kw_get_int)(gobj, kw_command, "form_write", 0, 0), true);
|
|
25009
25017
|
break;
|
|
25010
25018
|
case "delete-node": break;
|
|
25011
25019
|
default: (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)} Command unknown: ${command}`);
|
|
@@ -25221,15 +25229,23 @@ function ac_treedb_node_deleted$1(gobj, event, kw, src) {
|
|
|
25221
25229
|
* }
|
|
25222
25230
|
********************************************/
|
|
25223
25231
|
function ac_create_record(gobj, event, kw, src) {
|
|
25224
|
-
|
|
25232
|
+
let form_write = kw && kw.form_write || 0;
|
|
25233
|
+
if (refuse_if_readonly$3(gobj, event)) {
|
|
25234
|
+
answer_form_write(gobj, src, form_write, false);
|
|
25235
|
+
return -1;
|
|
25236
|
+
}
|
|
25225
25237
|
let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
|
|
25226
25238
|
let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
|
|
25227
25239
|
let record = kw.record;
|
|
25228
|
-
|
|
25240
|
+
if (treedb_update_node$1(gobj, treedb_name, topic_name, record, {
|
|
25229
25241
|
list_dict: true,
|
|
25230
25242
|
create: true,
|
|
25231
25243
|
autolink: true
|
|
25232
|
-
})
|
|
25244
|
+
}, form_write) < 0) {
|
|
25245
|
+
answer_form_write(gobj, src, form_write, false);
|
|
25246
|
+
return -1;
|
|
25247
|
+
}
|
|
25248
|
+
return 0;
|
|
25233
25249
|
}
|
|
25234
25250
|
/********************************************
|
|
25235
25251
|
* A topic table wants its records as json, WITH the metadata of
|
|
@@ -25379,14 +25395,37 @@ function ac_update_field(gobj, event, kw, src) {
|
|
|
25379
25395
|
* }
|
|
25380
25396
|
********************************************/
|
|
25381
25397
|
function ac_update_record(gobj, event, kw, src) {
|
|
25382
|
-
|
|
25398
|
+
let form_write = kw && kw.form_write || 0;
|
|
25399
|
+
if (refuse_if_readonly$3(gobj, event)) {
|
|
25400
|
+
answer_form_write(gobj, src, form_write, false);
|
|
25401
|
+
return -1;
|
|
25402
|
+
}
|
|
25383
25403
|
let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
|
|
25384
25404
|
let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
|
|
25385
25405
|
let record = kw.record;
|
|
25386
|
-
|
|
25406
|
+
if (treedb_update_node$1(gobj, treedb_name, topic_name, record, {
|
|
25387
25407
|
list_dict: true,
|
|
25388
25408
|
autolink: true
|
|
25389
|
-
})
|
|
25409
|
+
}, form_write) < 0) {
|
|
25410
|
+
answer_form_write(gobj, src, form_write, false);
|
|
25411
|
+
return -1;
|
|
25412
|
+
}
|
|
25413
|
+
return 0;
|
|
25414
|
+
}
|
|
25415
|
+
/************************************************************
|
|
25416
|
+
* Tell a topic form how its write ended: EV_WRITE_DONE closes
|
|
25417
|
+
* it, EV_WRITE_REFUSED leaves it open on what was typed. Every
|
|
25418
|
+
* way a form's write can end passes here -- refused before it
|
|
25419
|
+
* left (read-only, no session) or answered by the backend -- so
|
|
25420
|
+
* a form never waits for an answer that will not come.
|
|
25421
|
+
************************************************************/
|
|
25422
|
+
function answer_form_write(gobj, gobj_topic_form, form_write, ok) {
|
|
25423
|
+
if (!form_write) return;
|
|
25424
|
+
if (!gobj_topic_form) {
|
|
25425
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no topic form to answer write ${form_write}`);
|
|
25426
|
+
return;
|
|
25427
|
+
}
|
|
25428
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj_topic_form, ok ? "EV_WRITE_DONE" : "EV_WRITE_REFUSED", { form_write }, gobj);
|
|
25390
25429
|
}
|
|
25391
25430
|
/********************************************
|
|
25392
25431
|
* Event from formtable
|
|
@@ -25757,6 +25796,46 @@ function yui_selected_rows(tabulator) {
|
|
|
25757
25796
|
}
|
|
25758
25797
|
}
|
|
25759
25798
|
/***************************************************************
|
|
25799
|
+
* The IDENTITY of some rows: the value of the table's `index`
|
|
25800
|
+
* field (Tabulator's default is `id`). This is what crosses a
|
|
25801
|
+
* confirmation dialog -- never the rows' POSITIONS, which the
|
|
25802
|
+
* table regenerates with every addData / deleteRow while the
|
|
25803
|
+
* dialog is open, so a position names whatever row sits there
|
|
25804
|
+
* when the person answers.
|
|
25805
|
+
***************************************************************/
|
|
25806
|
+
function yui_row_ids(tabulator, rows) {
|
|
25807
|
+
let field = tabulator && tabulator.options && tabulator.options.index || "id";
|
|
25808
|
+
let ids = [];
|
|
25809
|
+
for (let row of rows || []) {
|
|
25810
|
+
let id = row ? row[field] : void 0;
|
|
25811
|
+
if (id !== void 0 && id !== null && id !== "") ids.push(id);
|
|
25812
|
+
}
|
|
25813
|
+
return ids;
|
|
25814
|
+
}
|
|
25815
|
+
/***************************************************************
|
|
25816
|
+
* The rows that carry these ids NOW -> {rows, missing}.
|
|
25817
|
+
* `rows` are the rows' data; `missing` the ids no row carries
|
|
25818
|
+
* any more. A missing id is never replaced by another row.
|
|
25819
|
+
***************************************************************/
|
|
25820
|
+
function yui_rows_by_ids(tabulator, ids) {
|
|
25821
|
+
let rows = [];
|
|
25822
|
+
let missing = [];
|
|
25823
|
+
for (let id of ids || []) {
|
|
25824
|
+
let row = null;
|
|
25825
|
+
if (tabulator && typeof tabulator.getRow === "function") try {
|
|
25826
|
+
row = tabulator.getRow(id) || null;
|
|
25827
|
+
} catch (e) {
|
|
25828
|
+
row = null;
|
|
25829
|
+
}
|
|
25830
|
+
if (row) rows.push(row.getData());
|
|
25831
|
+
else missing.push(id);
|
|
25832
|
+
}
|
|
25833
|
+
return {
|
|
25834
|
+
rows,
|
|
25835
|
+
missing
|
|
25836
|
+
};
|
|
25837
|
+
}
|
|
25838
|
+
/***************************************************************
|
|
25760
25839
|
* Drop the selection (after acting on it, or after a
|
|
25761
25840
|
* reload that leaves the ticked rows behind).
|
|
25762
25841
|
***************************************************************/
|
|
@@ -26184,10 +26263,18 @@ function yui_icon_is_defined(name) {
|
|
|
26184
26263
|
***********************************************************************/
|
|
26185
26264
|
var MAX_DEPTH = 4;
|
|
26186
26265
|
/***************************************************************
|
|
26187
|
-
*
|
|
26266
|
+
* A REFERENCE to a node: an fkey as `nodes` answers it with
|
|
26267
|
+
* `list_dict` -- `{id, topic_name, hook_name}` -- or a hook's child
|
|
26268
|
+
* as the node events deliver it, `{id, topic_name}`. It carries
|
|
26269
|
+
* nothing but those keys; an object with more is data.
|
|
26188
26270
|
***************************************************************/
|
|
26271
|
+
var REF_KEYS = /* @__PURE__ */ new Set([
|
|
26272
|
+
"id",
|
|
26273
|
+
"topic_name",
|
|
26274
|
+
"hook_name"
|
|
26275
|
+
]);
|
|
26189
26276
|
function is_fkey_ref(value) {
|
|
26190
|
-
return !!value && typeof value === "object" && !Array.isArray(value) && "id" in value && "topic_name" in value &&
|
|
26277
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && "id" in value && "topic_name" in value && Object.keys(value).every((k) => REF_KEYS.has(k));
|
|
26191
26278
|
}
|
|
26192
26279
|
/***************************************************************
|
|
26193
26280
|
* A hook read with `hook_size`: the COUNT of its children,
|
|
@@ -26447,6 +26534,7 @@ var attrs_table$5 = [
|
|
|
26447
26534
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_delete_button", 0, true, "Button toolbar DELETE"),
|
|
26448
26535
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_copy_button", 0, true, "Button toolbar COPY"),
|
|
26449
26536
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_paste_button", 0, true, "Button toolbar PASTE"),
|
|
26537
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "form_waits_for_answer", 0, false, "The form stays open (busy) after a Save until the host answers EV_WRITE_DONE (close) or EV_WRITE_REFUSED (stay open on what was typed). Off: the form closes when the write is published, whatever the backend says. The host must echo the `form_write` serial of EV_CREATE_RECORD / EV_UPDATE_RECORD; C_YUI_TREEDB_TOPICS does"),
|
|
26450
26538
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_refresh_button", 0, true, "Button toolbar REFRESH"),
|
|
26451
26539
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_search_button", 0, true, "Button toolbar SEARCH"),
|
|
26452
26540
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_schema_button", 0, true, "Button toolbar SCHEMA (show the topic's desc)"),
|
|
@@ -26622,6 +26710,8 @@ function popup_mount_layer(gobj) {
|
|
|
26622
26710
|
function mt_create$5(gobj) {
|
|
26623
26711
|
gobj.priv.reading_files = 0;
|
|
26624
26712
|
gobj.priv.read_serial = 0;
|
|
26713
|
+
gobj.priv.awaiting_write = 0;
|
|
26714
|
+
gobj.priv.write_serial = 0;
|
|
26625
26715
|
let name = (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj));
|
|
26626
26716
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "table_id", "table" + name);
|
|
26627
26717
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "toolbar_id", "toolbar" + name);
|
|
@@ -27140,14 +27230,11 @@ function render_selection_state(gobj) {
|
|
|
27140
27230
|
let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
|
|
27141
27231
|
let $delete = $container ? $container.querySelector(`.button-delete-record`) : null;
|
|
27142
27232
|
let $copy = $container ? $container.querySelector(`.button-copy-record`) : null;
|
|
27143
|
-
|
|
27144
|
-
|
|
27145
|
-
|
|
27146
|
-
|
|
27147
|
-
|
|
27148
|
-
$delete.setAttribute("disabled", true);
|
|
27149
|
-
$copy.setAttribute("disabled", true);
|
|
27150
|
-
}
|
|
27233
|
+
let enable = yui_selected_rows((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator")).length > 0 && (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "editable");
|
|
27234
|
+
for (let $button of [$delete, $copy]) {
|
|
27235
|
+
if (!$button) continue;
|
|
27236
|
+
if (enable) $button.removeAttribute("disabled");
|
|
27237
|
+
else $button.setAttribute("disabled", true);
|
|
27151
27238
|
}
|
|
27152
27239
|
render_selection_bar(gobj);
|
|
27153
27240
|
}
|
|
@@ -27455,7 +27542,7 @@ function create_tabulator(gobj) {
|
|
|
27455
27542
|
hozAlign,
|
|
27456
27543
|
formatter: colFormatter
|
|
27457
27544
|
};
|
|
27458
|
-
if (max_col_width > 0) colDef.
|
|
27545
|
+
if (max_col_width > 0) colDef.maxInitialWidth = max_col_width;
|
|
27459
27546
|
if (with_header_filters) apply_header_filter(colDef, field_desc);
|
|
27460
27547
|
if (with_inline_edit) inline_editor_for(colDef, col, field_desc);
|
|
27461
27548
|
if (vertAlign) colDef.vertAlign = vertAlign;
|
|
@@ -27493,10 +27580,7 @@ function create_tabulator(gobj) {
|
|
|
27493
27580
|
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EDIT_RECORD", { index }, gobj);
|
|
27494
27581
|
} else if (e.target.closest(".remove")) {
|
|
27495
27582
|
e.stopPropagation();
|
|
27496
|
-
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_DELETE_ROWS", {
|
|
27497
|
-
index,
|
|
27498
|
-
row
|
|
27499
|
-
}, gobj);
|
|
27583
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_DELETE_ROWS", { row }, gobj);
|
|
27500
27584
|
}
|
|
27501
27585
|
}
|
|
27502
27586
|
});
|
|
@@ -28034,6 +28118,7 @@ function form_may_close(gobj) {
|
|
|
28034
28118
|
function teardown_form_child(gobj) {
|
|
28035
28119
|
let priv = gobj.priv;
|
|
28036
28120
|
priv.reading_files = 0;
|
|
28121
|
+
priv.awaiting_write = 0;
|
|
28037
28122
|
if (priv.form) {
|
|
28038
28123
|
if ((0, _yuneta_gobj_js.gobj_is_running)(priv.form)) (0, _yuneta_gobj_js.gobj_stop)(priv.form);
|
|
28039
28124
|
(0, _yuneta_gobj_js.gobj_destroy)(priv.form);
|
|
@@ -29043,25 +29128,31 @@ function ac_edition_mode(gobj, event, kw, src) {
|
|
|
29043
29128
|
let $button_copy_record = $container.querySelector(`.button-copy-record`);
|
|
29044
29129
|
let $button_paste_record = $container.querySelector(`.button-paste-record`);
|
|
29045
29130
|
if (editable) {
|
|
29046
|
-
$button_edit_record.classList.add("is-primary");
|
|
29047
|
-
$button_new_record
|
|
29048
|
-
|
|
29131
|
+
if ($button_edit_record) $button_edit_record.classList.add("is-primary");
|
|
29132
|
+
if ($button_new_record) {
|
|
29133
|
+
$button_new_record.classList.add("is-info");
|
|
29134
|
+
$button_new_record.removeAttribute("disabled");
|
|
29135
|
+
}
|
|
29136
|
+
if ($button_delete_record) $button_delete_record.classList.add("is-danger");
|
|
29049
29137
|
tabulator.showColumn("_operation");
|
|
29050
29138
|
tabulator.showColumn("_check_box_state_");
|
|
29051
|
-
$button_new_record.removeAttribute("disabled");
|
|
29052
29139
|
if ($button_paste_record) $button_paste_record.removeAttribute("disabled");
|
|
29053
29140
|
if (yui_selected_rows(tabulator).length) {
|
|
29054
|
-
$button_delete_record.removeAttribute("disabled");
|
|
29141
|
+
if ($button_delete_record) $button_delete_record.removeAttribute("disabled");
|
|
29055
29142
|
if ($button_copy_record) $button_copy_record.removeAttribute("disabled");
|
|
29056
29143
|
}
|
|
29057
29144
|
} else {
|
|
29058
|
-
$button_edit_record.classList.remove("is-primary");
|
|
29059
|
-
$button_new_record
|
|
29060
|
-
|
|
29145
|
+
if ($button_edit_record) $button_edit_record.classList.remove("is-primary");
|
|
29146
|
+
if ($button_new_record) {
|
|
29147
|
+
$button_new_record.classList.remove("is-info");
|
|
29148
|
+
$button_new_record.setAttribute("disabled", true);
|
|
29149
|
+
}
|
|
29150
|
+
if ($button_delete_record) {
|
|
29151
|
+
$button_delete_record.classList.remove("is-danger");
|
|
29152
|
+
$button_delete_record.setAttribute("disabled", true);
|
|
29153
|
+
}
|
|
29061
29154
|
tabulator.hideColumn("_operation");
|
|
29062
29155
|
tabulator.hideColumn("_check_box_state_");
|
|
29063
|
-
$button_new_record.setAttribute("disabled", true);
|
|
29064
|
-
$button_delete_record.setAttribute("disabled", true);
|
|
29065
29156
|
if ($button_copy_record) $button_copy_record.setAttribute("disabled", true);
|
|
29066
29157
|
if ($button_paste_record) $button_paste_record.setAttribute("disabled", true);
|
|
29067
29158
|
}
|
|
@@ -29103,7 +29194,12 @@ function ac_new_row(gobj, event, kw, src) {
|
|
|
29103
29194
|
* kw {} empty
|
|
29104
29195
|
* - From the column _operation delete icon inside a row
|
|
29105
29196
|
* It will delete this one row
|
|
29106
|
-
* kw {
|
|
29197
|
+
* kw {row:}
|
|
29198
|
+
*
|
|
29199
|
+
* What crosses the confirmation is the rows' IDENTITY, never their
|
|
29200
|
+
* position: the view applies EV_TREEDB_NODE_* of every writer while the
|
|
29201
|
+
* dialog is open, and a position names whatever row sits there when the
|
|
29202
|
+
* person answers -- with force:true (A6 of the 2026-09-21 review).
|
|
29107
29203
|
************************************************************/
|
|
29108
29204
|
function ac_delete_rows(gobj, event, kw, src) {
|
|
29109
29205
|
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
@@ -29117,17 +29213,20 @@ function ac_delete_rows(gobj, event, kw, src) {
|
|
|
29117
29213
|
});
|
|
29118
29214
|
return 0;
|
|
29119
29215
|
}
|
|
29120
|
-
confirm_then$1(gobj, build_delete_question(gobj, rows), {
|
|
29216
|
+
confirm_then$1(gobj, build_delete_question(gobj, rows), {
|
|
29217
|
+
what: "delete_selection",
|
|
29218
|
+
ids: yui_row_ids(tabulator, rows)
|
|
29219
|
+
});
|
|
29121
29220
|
} else confirm_then$1(gobj, build_delete_question(gobj, kw.row), {
|
|
29122
29221
|
what: "delete_row",
|
|
29123
|
-
|
|
29222
|
+
ids: yui_row_ids(tabulator, [kw.row])
|
|
29124
29223
|
});
|
|
29125
29224
|
return 0;
|
|
29126
29225
|
}
|
|
29127
29226
|
/************************************************************
|
|
29128
29227
|
* {what, ...} -- the person said yes to what confirm_then()
|
|
29129
|
-
* asked. The rows are
|
|
29130
|
-
*
|
|
29228
|
+
* asked. The rows are found again HERE by the ids the question
|
|
29229
|
+
* named: the kw is plain json, and a Tabulator row is not.
|
|
29131
29230
|
************************************************************/
|
|
29132
29231
|
function ac_confirmed$2(gobj, event, kw, src) {
|
|
29133
29232
|
let priv = gobj.priv;
|
|
@@ -29139,31 +29238,16 @@ function ac_confirmed$2(gobj, event, kw, src) {
|
|
|
29139
29238
|
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
29140
29239
|
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
29141
29240
|
let topic_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name");
|
|
29142
|
-
if (what === "delete_selection") {
|
|
29143
|
-
let
|
|
29144
|
-
if (
|
|
29145
|
-
|
|
29146
|
-
|
|
29147
|
-
}
|
|
29148
|
-
for (let row of rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
29241
|
+
if (what === "delete_selection" || what === "delete_row") {
|
|
29242
|
+
let found = yui_rows_by_ids(tabulator, Array.isArray(kw.ids) ? kw.ids : []);
|
|
29243
|
+
if (found.missing.length) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: rows gone while confirming the delete, not deleted: ${found.missing.join(", ")}`);
|
|
29244
|
+
if (!found.rows.length) return -1;
|
|
29245
|
+
for (let row of found.rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
29149
29246
|
topic_name,
|
|
29150
29247
|
record: row
|
|
29151
29248
|
});
|
|
29152
29249
|
return 0;
|
|
29153
29250
|
}
|
|
29154
|
-
if (what === "delete_row") {
|
|
29155
|
-
let index = kw.index;
|
|
29156
|
-
let row = tabulator && typeof index === "number" ? tabulator.getRowFromPosition(index) : null;
|
|
29157
|
-
if (!row) {
|
|
29158
|
-
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no row: ${index}`);
|
|
29159
|
-
return -1;
|
|
29160
|
-
}
|
|
29161
|
-
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
29162
|
-
topic_name,
|
|
29163
|
-
record: row.getData()
|
|
29164
|
-
});
|
|
29165
|
-
return 0;
|
|
29166
|
-
}
|
|
29167
29251
|
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} of nothing this view asked about: ${what}`);
|
|
29168
29252
|
return -1;
|
|
29169
29253
|
}
|
|
@@ -29221,6 +29305,10 @@ function ac_form_save_record(gobj, event, kw, src) {
|
|
|
29221
29305
|
if (answer && answer.result >= 0 && answer.data) picked = answer.data;
|
|
29222
29306
|
const cols = Object.keys(picked);
|
|
29223
29307
|
if (cols.length === 0) {
|
|
29308
|
+
if (wait_for_write_answer(gobj)) {
|
|
29309
|
+
publish_treedb_write(gobj, mode, kw, null, priv.awaiting_write);
|
|
29310
|
+
return 0;
|
|
29311
|
+
}
|
|
29224
29312
|
publish_treedb_write(gobj, mode, kw);
|
|
29225
29313
|
(0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CLOSE_FORM", {}, gobj);
|
|
29226
29314
|
return 0;
|
|
@@ -29291,11 +29379,12 @@ function read_is_current(gobj, kw) {
|
|
|
29291
29379
|
/************************************************************
|
|
29292
29380
|
* The one place a write of this view leaves for the app.
|
|
29293
29381
|
************************************************************/
|
|
29294
|
-
function publish_treedb_write(gobj, mode, record, jn_files) {
|
|
29382
|
+
function publish_treedb_write(gobj, mode, record, jn_files, form_write) {
|
|
29295
29383
|
const kw_write = {
|
|
29296
29384
|
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
29297
29385
|
record: strip_read_only_cols(gobj, record)
|
|
29298
29386
|
};
|
|
29387
|
+
if (form_write) kw_write.form_write = form_write;
|
|
29299
29388
|
if (jn_files && Object.keys(jn_files).length > 0) kw_write.record.__files__ = jn_files;
|
|
29300
29389
|
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, mode === "create" ? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD", kw_write);
|
|
29301
29390
|
}
|
|
@@ -29314,11 +29403,53 @@ function ac_files_read(gobj, event, kw, src) {
|
|
|
29314
29403
|
if (!read_is_current(gobj, kw)) return -1;
|
|
29315
29404
|
gobj.priv.reading_files = 0;
|
|
29316
29405
|
const { record, __files__ } = yui_files_manifest(kw.picks, kw.record);
|
|
29406
|
+
if (wait_for_write_answer(gobj)) {
|
|
29407
|
+
publish_treedb_write(gobj, kw.mode, record, __files__, gobj.priv.awaiting_write);
|
|
29408
|
+
return 0;
|
|
29409
|
+
}
|
|
29317
29410
|
publish_treedb_write(gobj, kw.mode, record, __files__);
|
|
29318
29411
|
close_form_dialog(gobj);
|
|
29319
29412
|
return 0;
|
|
29320
29413
|
}
|
|
29321
29414
|
/************************************************************
|
|
29415
|
+
* Does the form wait for the answer of its write? Then it stays
|
|
29416
|
+
* open and BUSY, and the serial it waits for is taken here.
|
|
29417
|
+
*
|
|
29418
|
+
* A refused write used to close the form all the same and throw
|
|
29419
|
+
* away what was typed, although the README said it stayed open
|
|
29420
|
+
* (M25 of the 2026-09-21 review): the close was posted with the
|
|
29421
|
+
* publish, before the backend had answered anything.
|
|
29422
|
+
************************************************************/
|
|
29423
|
+
function wait_for_write_answer(gobj) {
|
|
29424
|
+
let priv = gobj.priv;
|
|
29425
|
+
if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "form_waits_for_answer") || !priv.form) return false;
|
|
29426
|
+
priv.awaiting_write = ++priv.write_serial;
|
|
29427
|
+
set_form_busy(gobj, true);
|
|
29428
|
+
return true;
|
|
29429
|
+
}
|
|
29430
|
+
/************************************************************
|
|
29431
|
+
* {form_write} -- the host says the form's write was done
|
|
29432
|
+
************************************************************/
|
|
29433
|
+
function ac_write_done(gobj, event, kw, src) {
|
|
29434
|
+
let priv = gobj.priv;
|
|
29435
|
+
if (!priv.awaiting_write || !kw || kw.form_write !== priv.awaiting_write) return 0;
|
|
29436
|
+
priv.awaiting_write = 0;
|
|
29437
|
+
set_form_busy(gobj, false);
|
|
29438
|
+
close_form_dialog(gobj);
|
|
29439
|
+
return 0;
|
|
29440
|
+
}
|
|
29441
|
+
/************************************************************
|
|
29442
|
+
* {form_write} -- the host says the form's write was refused.
|
|
29443
|
+
* The form stays OPEN on what was typed; the host has shown why.
|
|
29444
|
+
************************************************************/
|
|
29445
|
+
function ac_write_refused(gobj, event, kw, src) {
|
|
29446
|
+
let priv = gobj.priv;
|
|
29447
|
+
if (!priv.awaiting_write || !kw || kw.form_write !== priv.awaiting_write) return 0;
|
|
29448
|
+
priv.awaiting_write = 0;
|
|
29449
|
+
set_form_busy(gobj, false);
|
|
29450
|
+
return 0;
|
|
29451
|
+
}
|
|
29452
|
+
/************************************************************
|
|
29322
29453
|
* {reason} -- the browser could not read what was picked
|
|
29323
29454
|
*
|
|
29324
29455
|
* The form stays OPEN: the record is still there, and closing it would
|
|
@@ -30078,6 +30209,16 @@ function create_gclass$5(gclass_name) {
|
|
|
30078
30209
|
ac_close_form,
|
|
30079
30210
|
null
|
|
30080
30211
|
],
|
|
30212
|
+
[
|
|
30213
|
+
"EV_WRITE_DONE",
|
|
30214
|
+
ac_write_done,
|
|
30215
|
+
null
|
|
30216
|
+
],
|
|
30217
|
+
[
|
|
30218
|
+
"EV_WRITE_REFUSED",
|
|
30219
|
+
ac_write_refused,
|
|
30220
|
+
null
|
|
30221
|
+
],
|
|
30081
30222
|
[
|
|
30082
30223
|
"EV_FILES_READ",
|
|
30083
30224
|
ac_files_read,
|
|
@@ -30273,6 +30414,8 @@ function create_gclass$5(gclass_name) {
|
|
|
30273
30414
|
["EV_EDITION_MODE", 0],
|
|
30274
30415
|
["EV_SAVE_RECORD", 0],
|
|
30275
30416
|
["EV_CLOSE_FORM", 0],
|
|
30417
|
+
["EV_WRITE_DONE", 0],
|
|
30418
|
+
["EV_WRITE_REFUSED", 0],
|
|
30276
30419
|
["EV_FILES_READ", 0],
|
|
30277
30420
|
["EV_FILES_FAILED", 0],
|
|
30278
30421
|
["EV_NEW_ROW", 0],
|
|
@@ -30414,6 +30557,38 @@ function plan_graph_saves(live, saved) {
|
|
|
30414
30557
|
for (const [topic_name, properties] of Object.entries(live)) if (topic_arrangement_changed(properties, have[topic_name])) plan.push(topic_name);
|
|
30415
30558
|
return plan;
|
|
30416
30559
|
}
|
|
30560
|
+
/***************************************************************
|
|
30561
|
+
* An ECHO of one __graphs__ record arrived: the node event a save
|
|
30562
|
+
* of it sends back, from this browser or from another one.
|
|
30563
|
+
*
|
|
30564
|
+
* live {topic_name: properties} what the view shows
|
|
30565
|
+
* saved {topic_name: properties} what the backend has
|
|
30566
|
+
* records every __graphs__ record the view holds, `rec`
|
|
30567
|
+
* already in its place among them
|
|
30568
|
+
* rec the echoed record
|
|
30569
|
+
*
|
|
30570
|
+
* Only rec's TOPIC moves: it becomes the record, as shown and as
|
|
30571
|
+
* saved. The other topics are left alone -- rebuilding their saved
|
|
30572
|
+
* copies here took them from the live objects the view had already
|
|
30573
|
+
* rearranged in place, so their unsaved changes counted as saved.
|
|
30574
|
+
*
|
|
30575
|
+
* `live[topic]` IS the record's own object, as when the view loads:
|
|
30576
|
+
* the view arranges it in place. `saved[topic]` is a copy.
|
|
30577
|
+
***************************************************************/
|
|
30578
|
+
function apply_graphs_echo(live, saved, records, rec) {
|
|
30579
|
+
if (!(0, _yuneta_gobj_js.is_object)(rec) || !rec.topic) return;
|
|
30580
|
+
if (rec.active && (0, _yuneta_gobj_js.is_object)(rec.properties)) {
|
|
30581
|
+
live[rec.topic] = rec.properties;
|
|
30582
|
+
saved[rec.topic] = (0, _yuneta_gobj_js.json_deep_copy)(rec.properties);
|
|
30583
|
+
return;
|
|
30584
|
+
}
|
|
30585
|
+
if (!(records || []).some((r) => {
|
|
30586
|
+
return r && r !== rec && r.topic === rec.topic && r.active && (0, _yuneta_gobj_js.is_object)(r.properties);
|
|
30587
|
+
})) {
|
|
30588
|
+
delete live[rec.topic];
|
|
30589
|
+
delete saved[rec.topic];
|
|
30590
|
+
}
|
|
30591
|
+
}
|
|
30417
30592
|
//#endregion
|
|
30418
30593
|
//#region src/treedb_node_label.js
|
|
30419
30594
|
/***********************************************************************
|
|
@@ -65266,7 +65441,7 @@ function ac_node_updated(gobj, event, kw, src) {
|
|
|
65266
65441
|
break;
|
|
65267
65442
|
}
|
|
65268
65443
|
if (!found) priv.__graphs__.push(node);
|
|
65269
|
-
|
|
65444
|
+
apply_graphs_echo(priv._graph_properties, priv._saved_graph_properties, priv.__graphs__, node);
|
|
65270
65445
|
return 0;
|
|
65271
65446
|
}
|
|
65272
65447
|
let desc = priv.descs[topic_name];
|
|
@@ -65294,8 +65469,9 @@ function ac_node_updated(gobj, event, kw, src) {
|
|
|
65294
65469
|
update_local_node(gobj, topic_name, node);
|
|
65295
65470
|
let on_screen = false;
|
|
65296
65471
|
try {
|
|
65297
|
-
on_screen = !!graph.getNodeData(node_name);
|
|
65472
|
+
on_screen = !!priv.graph.getNodeData(node_name);
|
|
65298
65473
|
} catch (e) {
|
|
65474
|
+
if (e instanceof ReferenceError) throw e;
|
|
65299
65475
|
on_screen = false;
|
|
65300
65476
|
}
|
|
65301
65477
|
if (on_screen) update_topic_node(gobj, desc, node_name, node);
|