@yuneta/gobj-ui 7.25.2 → 7.25.4
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 +16 -0
- package/dist/gobj-ui.cjs.js +74 -1
- package/dist/gobj-ui.es.js +74 -1
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +12 -0
- package/src/c_yui_schema_editor.js +30 -1
- package/src/c_yui_treedb_topic_with_form.js +8 -0
- package/src/host_drafts.js +68 -0
- package/src/host_drafts.test.js +62 -0
package/README.md
CHANGED
|
@@ -2021,6 +2021,22 @@ and the column screen says so in a banner; the host sends `EV_REFRESH` after a
|
|
|
2021
2021
|
save, and the reload forgets the drafts. The export (C literal and JSON) warns
|
|
2022
2022
|
while there are drafts: the literal carries the versions a save publishes.
|
|
2023
2023
|
|
|
2024
|
+
**A draft survives a reload** (7.25.3). The mark of a write lived in the
|
|
2025
|
+
session's memory only, so a reload of the page, a reconnect or a refresh of
|
|
2026
|
+
the model showed no draft while `__system__` still differed from the file in
|
|
2027
|
+
use. The host tells the editor what IS a draft, from data: `C_TREEDB`'s
|
|
2028
|
+
`saved-schema` answers `draft_changed` per treedb (yunetas 7.25.3+), and the
|
|
2029
|
+
host sends it as `EV_DRAFTS`; `drafts_of_saved_answer()` reads the unnamed
|
|
2030
|
+
answer into the shape the event takes. What the session wrote stays marked;
|
|
2031
|
+
what the host said is applied again after every reload of the model.
|
|
2032
|
+
|
|
2033
|
+
```js
|
|
2034
|
+
// The host, when its saved-schema answers land (rows = the unnamed answer's data)
|
|
2035
|
+
import {drafts_of_saved_answer} from "@yuneta/gobj-ui/src/host_drafts.js";
|
|
2036
|
+
gobj_send_event(editor, "EV_DRAFTS", {drafts: drafts_of_saved_answer(rows)}, gobj);
|
|
2037
|
+
// -> {drafts: {treedb_authzs: ["users"]}}
|
|
2038
|
+
```
|
|
2039
|
+
|
|
2024
2040
|
What the screens offer:
|
|
2025
2041
|
|
|
2026
2042
|
| Screen | What it is for |
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -29108,6 +29108,7 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation) {
|
|
|
29108
29108
|
switch (field_desc.real_type) {
|
|
29109
29109
|
case "string":
|
|
29110
29110
|
if (value && (0, _yuneta_gobj_js.is_date)(value)) value = value.toISOString();
|
|
29111
|
+
else if (typeof value === "number" && value > 0) value = (/* @__PURE__ */ new Date(value * 1e3)).toISOString();
|
|
29111
29112
|
break;
|
|
29112
29113
|
case "integer":
|
|
29113
29114
|
if (value && (0, _yuneta_gobj_js.is_date)(value)) value = value.getTime() / 1e3;
|
|
@@ -29440,7 +29441,10 @@ function ac_confirmed$2(gobj, event, kw, src) {
|
|
|
29440
29441
|
let topic_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name");
|
|
29441
29442
|
if (what === "delete_selection" || what === "delete_row") {
|
|
29442
29443
|
let found = yui_rows_by_ids(tabulator, Array.isArray(kw.ids) ? kw.ids : []);
|
|
29443
|
-
if (found.missing.length)
|
|
29444
|
+
if (found.missing.length) {
|
|
29445
|
+
(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(", ")}`);
|
|
29446
|
+
yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t: i18next.t });
|
|
29447
|
+
}
|
|
29444
29448
|
if (!found.rows.length) return -1;
|
|
29445
29449
|
for (let row of found.rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
29446
29450
|
topic_name,
|
|
@@ -66451,6 +66455,7 @@ function ac_confirmed$1(gobj, event, kw, src) {
|
|
|
66451
66455
|
if (what === "delete_selection") {
|
|
66452
66456
|
let ids = kw && Array.isArray(kw.ids) ? kw.ids : [];
|
|
66453
66457
|
let nodes = [];
|
|
66458
|
+
let missing = [];
|
|
66454
66459
|
for (let id of ids) {
|
|
66455
66460
|
let nd = null;
|
|
66456
66461
|
try {
|
|
@@ -66459,6 +66464,11 @@ function ac_confirmed$1(gobj, event, kw, src) {
|
|
|
66459
66464
|
nd = null;
|
|
66460
66465
|
}
|
|
66461
66466
|
if (nd && nd.data && nd.data.desc) nodes.push(nd);
|
|
66467
|
+
else missing.push(id);
|
|
66468
|
+
}
|
|
66469
|
+
if (missing.length) {
|
|
66470
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: cards gone while confirming the delete, not deleted: ${missing.join(", ")}`);
|
|
66471
|
+
yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t: i18next.t });
|
|
66462
66472
|
}
|
|
66463
66473
|
if (!nodes.length) {
|
|
66464
66474
|
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no node any more`);
|
|
@@ -70255,6 +70265,44 @@ function register_c_yui_treedb_schema() {
|
|
|
70255
70265
|
return create_gclass$2(GCLASS_NAME$2);
|
|
70256
70266
|
}
|
|
70257
70267
|
//#endregion
|
|
70268
|
+
//#region src/host_drafts.js
|
|
70269
|
+
/***********************************************************************
|
|
70270
|
+
* host_drafts.js
|
|
70271
|
+
*
|
|
70272
|
+
* The topics a schema editor shows as DRAFTS (edited in
|
|
70273
|
+
* __system__, not yet saved), told by the host from data. The
|
|
70274
|
+
* editor marks a topic when a write of this session lands on
|
|
70275
|
+
* it, and that mark lived in the memory of the session only:
|
|
70276
|
+
* a reload of the page, a reconnect or a refresh of the model
|
|
70277
|
+
* lost the chip, the banner and the export warning while
|
|
70278
|
+
* __system__ still differed from the file in use (N13 of the
|
|
70279
|
+
* 2026-09-22 review). The host reads that difference from
|
|
70280
|
+
* C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
|
|
70281
|
+
* hands it here; the editor applies it to its model.
|
|
70282
|
+
*
|
|
70283
|
+
* Copyright (c) 2026, ArtGins.
|
|
70284
|
+
* All Rights Reserved.
|
|
70285
|
+
***********************************************************************/
|
|
70286
|
+
/************************************************************
|
|
70287
|
+
* Mark in `written` the topics of `model` the host names as
|
|
70288
|
+
* drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
|
|
70289
|
+
* topic the model does not hold is ignored. Returns how many were
|
|
70290
|
+
* marked.
|
|
70291
|
+
************************************************************/
|
|
70292
|
+
function mark_host_drafts(written, model, drafts) {
|
|
70293
|
+
let marked = 0;
|
|
70294
|
+
if (!written || !model || !Array.isArray(model.treedbs) || !drafts) return marked;
|
|
70295
|
+
for (let treedb of model.treedbs) {
|
|
70296
|
+
const names = drafts[treedb.id];
|
|
70297
|
+
if (!Array.isArray(names) || names.length === 0) continue;
|
|
70298
|
+
for (let topic of treedb.topics || []) if (names.includes(topic.name)) {
|
|
70299
|
+
written[topic.id] = true;
|
|
70300
|
+
marked++;
|
|
70301
|
+
}
|
|
70302
|
+
}
|
|
70303
|
+
return marked;
|
|
70304
|
+
}
|
|
70305
|
+
//#endregion
|
|
70258
70306
|
//#region src/schema_validate.js
|
|
70259
70307
|
/***********************************************************************
|
|
70260
70308
|
* schema_validate.js
|
|
@@ -71981,6 +72029,7 @@ function mt_create$1(gobj) {
|
|
|
71981
72029
|
let priv = gobj.priv;
|
|
71982
72030
|
priv.baseline = {};
|
|
71983
72031
|
priv.written = {};
|
|
72032
|
+
priv.host_drafts = {};
|
|
71984
72033
|
priv.save_queue = [];
|
|
71985
72034
|
build_ui$1(gobj);
|
|
71986
72035
|
let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
|
|
@@ -72222,6 +72271,7 @@ function start_measuring(gobj) {
|
|
|
72222
72271
|
priv.written = {};
|
|
72223
72272
|
if (!priv.model) return;
|
|
72224
72273
|
for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
|
|
72274
|
+
mark_host_drafts(priv.written, priv.model, priv.host_drafts);
|
|
72225
72275
|
}
|
|
72226
72276
|
/***************************************************************
|
|
72227
72277
|
* "This topic was written in this session and is not saved."
|
|
@@ -74659,6 +74709,23 @@ function ac_refresh$1(gobj, event, kw, src) {
|
|
|
74659
74709
|
return request_model(gobj);
|
|
74660
74710
|
}
|
|
74661
74711
|
/***************************************************************
|
|
74712
|
+
* {drafts: {treedb_name: [topic names]}} -- the host says which
|
|
74713
|
+
* topics hold a draft in __system__ that the file in use does not
|
|
74714
|
+
* have (C_TREEDB's saved-schema, `draft_changed`). The mark of a
|
|
74715
|
+
* write lived in this session's memory only, so a reload of the
|
|
74716
|
+
* page, a reconnect or a refresh showed no draft while __system__
|
|
74717
|
+
* still differed from the file (N13 of the 2026-09-22 review).
|
|
74718
|
+
* Replaces what the host said before; what this session wrote
|
|
74719
|
+
* stays marked.
|
|
74720
|
+
***************************************************************/
|
|
74721
|
+
function ac_drafts(gobj, event, kw, src) {
|
|
74722
|
+
let priv = gobj.priv;
|
|
74723
|
+
priv.host_drafts = kw && kw.drafts && typeof kw.drafts === "object" ? kw.drafts : {};
|
|
74724
|
+
mark_host_drafts(priv.written, priv.model, priv.host_drafts);
|
|
74725
|
+
if (priv.model) render(gobj);
|
|
74726
|
+
return 0;
|
|
74727
|
+
}
|
|
74728
|
+
/***************************************************************
|
|
74662
74729
|
* Moving about.
|
|
74663
74730
|
***************************************************************/
|
|
74664
74731
|
function ac_select_treedb(gobj, event, kw, src) {
|
|
@@ -75132,6 +75199,11 @@ function create_gclass$1(gclass_name) {
|
|
|
75132
75199
|
"EV_REFRESH",
|
|
75133
75200
|
ac_refresh$1,
|
|
75134
75201
|
null
|
|
75202
|
+
],
|
|
75203
|
+
[
|
|
75204
|
+
"EV_DRAFTS",
|
|
75205
|
+
ac_drafts,
|
|
75206
|
+
null
|
|
75135
75207
|
]
|
|
75136
75208
|
];
|
|
75137
75209
|
const TREEDB_WIDE = [
|
|
@@ -75280,6 +75352,7 @@ function create_gclass$1(gclass_name) {
|
|
|
75280
75352
|
["EV_TRANSPORT_STATE", 0],
|
|
75281
75353
|
["EV_LANGUAGE_CHANGED", 0],
|
|
75282
75354
|
["EV_REFRESH", 0],
|
|
75355
|
+
["EV_DRAFTS", 0],
|
|
75283
75356
|
["EV_SELECT_TREEDB", 0],
|
|
75284
75357
|
["EV_SELECT_TOPIC", 0],
|
|
75285
75358
|
["EV_BACK", 0],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -29103,6 +29103,7 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation) {
|
|
|
29103
29103
|
switch (field_desc.real_type) {
|
|
29104
29104
|
case "string":
|
|
29105
29105
|
if (value && is_date(value)) value = value.toISOString();
|
|
29106
|
+
else if (typeof value === "number" && value > 0) value = (/* @__PURE__ */ new Date(value * 1e3)).toISOString();
|
|
29106
29107
|
break;
|
|
29107
29108
|
case "integer":
|
|
29108
29109
|
if (value && is_date(value)) value = value.getTime() / 1e3;
|
|
@@ -29435,7 +29436,10 @@ function ac_confirmed$2(gobj, event, kw, src) {
|
|
|
29435
29436
|
let topic_name = gobj_read_str_attr(gobj, "topic_name");
|
|
29436
29437
|
if (what === "delete_selection" || what === "delete_row") {
|
|
29437
29438
|
let found = yui_rows_by_ids(tabulator, Array.isArray(kw.ids) ? kw.ids : []);
|
|
29438
|
-
if (found.missing.length)
|
|
29439
|
+
if (found.missing.length) {
|
|
29440
|
+
log_error(`${gobj_short_name(gobj)}: ${event}: rows gone while confirming the delete, not deleted: ${found.missing.join(", ")}`);
|
|
29441
|
+
yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t });
|
|
29442
|
+
}
|
|
29439
29443
|
if (!found.rows.length) return -1;
|
|
29440
29444
|
for (let row of found.rows) gobj_publish_event(gobj, "EV_DELETE_RECORD", {
|
|
29441
29445
|
topic_name,
|
|
@@ -66446,6 +66450,7 @@ function ac_confirmed$1(gobj, event, kw, src) {
|
|
|
66446
66450
|
if (what === "delete_selection") {
|
|
66447
66451
|
let ids = kw && Array.isArray(kw.ids) ? kw.ids : [];
|
|
66448
66452
|
let nodes = [];
|
|
66453
|
+
let missing = [];
|
|
66449
66454
|
for (let id of ids) {
|
|
66450
66455
|
let nd = null;
|
|
66451
66456
|
try {
|
|
@@ -66454,6 +66459,11 @@ function ac_confirmed$1(gobj, event, kw, src) {
|
|
|
66454
66459
|
nd = null;
|
|
66455
66460
|
}
|
|
66456
66461
|
if (nd && nd.data && nd.data.desc) nodes.push(nd);
|
|
66462
|
+
else missing.push(id);
|
|
66463
|
+
}
|
|
66464
|
+
if (missing.length) {
|
|
66465
|
+
log_error(`${gobj_short_name(gobj)}: ${event}: cards gone while confirming the delete, not deleted: ${missing.join(", ")}`);
|
|
66466
|
+
yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", { t });
|
|
66457
66467
|
}
|
|
66458
66468
|
if (!nodes.length) {
|
|
66459
66469
|
log_error(`${gobj_short_name(gobj)}: ${event} names no node any more`);
|
|
@@ -70250,6 +70260,44 @@ function register_c_yui_treedb_schema() {
|
|
|
70250
70260
|
return create_gclass$2(GCLASS_NAME$2);
|
|
70251
70261
|
}
|
|
70252
70262
|
//#endregion
|
|
70263
|
+
//#region src/host_drafts.js
|
|
70264
|
+
/***********************************************************************
|
|
70265
|
+
* host_drafts.js
|
|
70266
|
+
*
|
|
70267
|
+
* The topics a schema editor shows as DRAFTS (edited in
|
|
70268
|
+
* __system__, not yet saved), told by the host from data. The
|
|
70269
|
+
* editor marks a topic when a write of this session lands on
|
|
70270
|
+
* it, and that mark lived in the memory of the session only:
|
|
70271
|
+
* a reload of the page, a reconnect or a refresh of the model
|
|
70272
|
+
* lost the chip, the banner and the export warning while
|
|
70273
|
+
* __system__ still differed from the file in use (N13 of the
|
|
70274
|
+
* 2026-09-22 review). The host reads that difference from
|
|
70275
|
+
* C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
|
|
70276
|
+
* hands it here; the editor applies it to its model.
|
|
70277
|
+
*
|
|
70278
|
+
* Copyright (c) 2026, ArtGins.
|
|
70279
|
+
* All Rights Reserved.
|
|
70280
|
+
***********************************************************************/
|
|
70281
|
+
/************************************************************
|
|
70282
|
+
* Mark in `written` the topics of `model` the host names as
|
|
70283
|
+
* drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
|
|
70284
|
+
* topic the model does not hold is ignored. Returns how many were
|
|
70285
|
+
* marked.
|
|
70286
|
+
************************************************************/
|
|
70287
|
+
function mark_host_drafts(written, model, drafts) {
|
|
70288
|
+
let marked = 0;
|
|
70289
|
+
if (!written || !model || !Array.isArray(model.treedbs) || !drafts) return marked;
|
|
70290
|
+
for (let treedb of model.treedbs) {
|
|
70291
|
+
const names = drafts[treedb.id];
|
|
70292
|
+
if (!Array.isArray(names) || names.length === 0) continue;
|
|
70293
|
+
for (let topic of treedb.topics || []) if (names.includes(topic.name)) {
|
|
70294
|
+
written[topic.id] = true;
|
|
70295
|
+
marked++;
|
|
70296
|
+
}
|
|
70297
|
+
}
|
|
70298
|
+
return marked;
|
|
70299
|
+
}
|
|
70300
|
+
//#endregion
|
|
70253
70301
|
//#region src/schema_validate.js
|
|
70254
70302
|
/***********************************************************************
|
|
70255
70303
|
* schema_validate.js
|
|
@@ -71976,6 +72024,7 @@ function mt_create$1(gobj) {
|
|
|
71976
72024
|
let priv = gobj.priv;
|
|
71977
72025
|
priv.baseline = {};
|
|
71978
72026
|
priv.written = {};
|
|
72027
|
+
priv.host_drafts = {};
|
|
71979
72028
|
priv.save_queue = [];
|
|
71980
72029
|
build_ui$1(gobj);
|
|
71981
72030
|
let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
@@ -72217,6 +72266,7 @@ function start_measuring(gobj) {
|
|
|
72217
72266
|
priv.written = {};
|
|
72218
72267
|
if (!priv.model) return;
|
|
72219
72268
|
for (let treedb of priv.model.treedbs) for (let topic of treedb.topics) priv.baseline[topic.id] = topic.topic_version;
|
|
72269
|
+
mark_host_drafts(priv.written, priv.model, priv.host_drafts);
|
|
72220
72270
|
}
|
|
72221
72271
|
/***************************************************************
|
|
72222
72272
|
* "This topic was written in this session and is not saved."
|
|
@@ -74654,6 +74704,23 @@ function ac_refresh$1(gobj, event, kw, src) {
|
|
|
74654
74704
|
return request_model(gobj);
|
|
74655
74705
|
}
|
|
74656
74706
|
/***************************************************************
|
|
74707
|
+
* {drafts: {treedb_name: [topic names]}} -- the host says which
|
|
74708
|
+
* topics hold a draft in __system__ that the file in use does not
|
|
74709
|
+
* have (C_TREEDB's saved-schema, `draft_changed`). The mark of a
|
|
74710
|
+
* write lived in this session's memory only, so a reload of the
|
|
74711
|
+
* page, a reconnect or a refresh showed no draft while __system__
|
|
74712
|
+
* still differed from the file (N13 of the 2026-09-22 review).
|
|
74713
|
+
* Replaces what the host said before; what this session wrote
|
|
74714
|
+
* stays marked.
|
|
74715
|
+
***************************************************************/
|
|
74716
|
+
function ac_drafts(gobj, event, kw, src) {
|
|
74717
|
+
let priv = gobj.priv;
|
|
74718
|
+
priv.host_drafts = kw && kw.drafts && typeof kw.drafts === "object" ? kw.drafts : {};
|
|
74719
|
+
mark_host_drafts(priv.written, priv.model, priv.host_drafts);
|
|
74720
|
+
if (priv.model) render(gobj);
|
|
74721
|
+
return 0;
|
|
74722
|
+
}
|
|
74723
|
+
/***************************************************************
|
|
74657
74724
|
* Moving about.
|
|
74658
74725
|
***************************************************************/
|
|
74659
74726
|
function ac_select_treedb(gobj, event, kw, src) {
|
|
@@ -75127,6 +75194,11 @@ function create_gclass$1(gclass_name) {
|
|
|
75127
75194
|
"EV_REFRESH",
|
|
75128
75195
|
ac_refresh$1,
|
|
75129
75196
|
null
|
|
75197
|
+
],
|
|
75198
|
+
[
|
|
75199
|
+
"EV_DRAFTS",
|
|
75200
|
+
ac_drafts,
|
|
75201
|
+
null
|
|
75130
75202
|
]
|
|
75131
75203
|
];
|
|
75132
75204
|
const TREEDB_WIDE = [
|
|
@@ -75275,6 +75347,7 @@ function create_gclass$1(gclass_name) {
|
|
|
75275
75347
|
["EV_TRANSPORT_STATE", 0],
|
|
75276
75348
|
["EV_LANGUAGE_CHANGED", 0],
|
|
75277
75349
|
["EV_REFRESH", 0],
|
|
75350
|
+
["EV_DRAFTS", 0],
|
|
75278
75351
|
["EV_SELECT_TREEDB", 0],
|
|
75279
75352
|
["EV_SELECT_TOPIC", 0],
|
|
75280
75353
|
["EV_BACK", 0],
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -174,6 +174,7 @@ import {
|
|
|
174
174
|
} from "./g6_touch_gestures.js";
|
|
175
175
|
import {yui_theme_now, yui_watch_theme} from "./yui_theme.js";
|
|
176
176
|
import {yui_shell_of} from "./c_yui_shell.js";
|
|
177
|
+
import {yui_shell_show_error} from "./shell_modals.js";
|
|
177
178
|
|
|
178
179
|
/***************************************************************
|
|
179
180
|
* YuiToolbar — G6 Toolbar subclass that adds per-item className
|
|
@@ -11761,6 +11762,7 @@ function ac_confirmed(gobj, event, kw, src)
|
|
|
11761
11762
|
if(what === "delete_selection") {
|
|
11762
11763
|
let ids = (kw && Array.isArray(kw.ids))? kw.ids : [];
|
|
11763
11764
|
let nodes = [];
|
|
11765
|
+
let missing = [];
|
|
11764
11766
|
for(let id of ids) {
|
|
11765
11767
|
let nd = null;
|
|
11766
11768
|
try {
|
|
@@ -11770,8 +11772,18 @@ function ac_confirmed(gobj, event, kw, src)
|
|
|
11770
11772
|
}
|
|
11771
11773
|
if(nd && nd.data && nd.data.desc) {
|
|
11772
11774
|
nodes.push(nd);
|
|
11775
|
+
} else {
|
|
11776
|
+
missing.push(id);
|
|
11773
11777
|
}
|
|
11774
11778
|
}
|
|
11779
|
+
if(missing.length) {
|
|
11780
|
+
/* Gone while the question was open (another writer deleted
|
|
11781
|
+
* them): said, to the log and to the person (a low of the
|
|
11782
|
+
* 2026-09-22 review); the rest go on. */
|
|
11783
|
+
log_error(`${gobj_short_name(gobj)}: ${event}: cards gone while ` +
|
|
11784
|
+
`confirming the delete, not deleted: ${missing.join(", ")}`);
|
|
11785
|
+
yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", {t: t});
|
|
11786
|
+
}
|
|
11775
11787
|
if(!nodes.length) {
|
|
11776
11788
|
log_error(`${gobj_short_name(gobj)}: ${event} names no node any more`);
|
|
11777
11789
|
return -1;
|
|
@@ -113,6 +113,7 @@ import {
|
|
|
113
113
|
empty_string,
|
|
114
114
|
} from "@yuneta/gobj-js";
|
|
115
115
|
import {yui_tint} from "./bulma_tint.js";
|
|
116
|
+
import {mark_host_drafts} from "./host_drafts.js";
|
|
116
117
|
|
|
117
118
|
import "./c_yui_schema_editor.css";
|
|
118
119
|
|
|
@@ -248,6 +249,7 @@ function mt_create(gobj)
|
|
|
248
249
|
|
|
249
250
|
priv.baseline = {};
|
|
250
251
|
priv.written = {};
|
|
252
|
+
priv.host_drafts = {}; /* what the host says is a draft, {treedb: [topics]} (EV_DRAFTS) */
|
|
251
253
|
priv.save_queue = [];
|
|
252
254
|
|
|
253
255
|
build_ui(gobj);
|
|
@@ -571,6 +573,9 @@ function start_measuring(gobj)
|
|
|
571
573
|
priv.baseline[topic.id] = topic.topic_version;
|
|
572
574
|
}
|
|
573
575
|
}
|
|
576
|
+
/* A draft is a draft after a reload too: what the host said stays
|
|
577
|
+
* marked (host_drafts.js) until the host says otherwise. */
|
|
578
|
+
mark_host_drafts(priv.written, priv.model, priv.host_drafts);
|
|
574
579
|
}
|
|
575
580
|
|
|
576
581
|
/***************************************************************
|
|
@@ -2755,6 +2760,28 @@ function ac_refresh(gobj, event, kw, src)
|
|
|
2755
2760
|
return request_model(gobj);
|
|
2756
2761
|
}
|
|
2757
2762
|
|
|
2763
|
+
/***************************************************************
|
|
2764
|
+
* {drafts: {treedb_name: [topic names]}} -- the host says which
|
|
2765
|
+
* topics hold a draft in __system__ that the file in use does not
|
|
2766
|
+
* have (C_TREEDB's saved-schema, `draft_changed`). The mark of a
|
|
2767
|
+
* write lived in this session's memory only, so a reload of the
|
|
2768
|
+
* page, a reconnect or a refresh showed no draft while __system__
|
|
2769
|
+
* still differed from the file (N13 of the 2026-09-22 review).
|
|
2770
|
+
* Replaces what the host said before; what this session wrote
|
|
2771
|
+
* stays marked.
|
|
2772
|
+
***************************************************************/
|
|
2773
|
+
function ac_drafts(gobj, event, kw, src)
|
|
2774
|
+
{
|
|
2775
|
+
let priv = gobj.priv;
|
|
2776
|
+
|
|
2777
|
+
priv.host_drafts = (kw && kw.drafts && typeof kw.drafts === "object")? kw.drafts: {};
|
|
2778
|
+
mark_host_drafts(priv.written, priv.model, priv.host_drafts);
|
|
2779
|
+
if(priv.model) {
|
|
2780
|
+
render(gobj);
|
|
2781
|
+
}
|
|
2782
|
+
return 0;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2758
2785
|
/***************************************************************
|
|
2759
2786
|
* Moving about.
|
|
2760
2787
|
***************************************************************/
|
|
@@ -3328,7 +3355,8 @@ function create_gclass(gclass_name)
|
|
|
3328
3355
|
["EV_HIDE", ac_hide, null],
|
|
3329
3356
|
["EV_TRANSPORT_STATE", ac_transport_state, null],
|
|
3330
3357
|
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
3331
|
-
["EV_REFRESH", ac_refresh, null]
|
|
3358
|
+
["EV_REFRESH", ac_refresh, null],
|
|
3359
|
+
["EV_DRAFTS", ac_drafts, null]
|
|
3332
3360
|
];
|
|
3333
3361
|
|
|
3334
3362
|
/* What a treedb is open for: reading it whole, and writing it
|
|
@@ -3411,6 +3439,7 @@ function create_gclass(gclass_name)
|
|
|
3411
3439
|
["EV_TRANSPORT_STATE", 0],
|
|
3412
3440
|
["EV_LANGUAGE_CHANGED", 0],
|
|
3413
3441
|
["EV_REFRESH", 0],
|
|
3442
|
+
["EV_DRAFTS", 0],
|
|
3414
3443
|
|
|
3415
3444
|
/* From this view's own screens. */
|
|
3416
3445
|
["EV_SELECT_TREEDB", 0],
|
|
@@ -110,6 +110,7 @@ import {
|
|
|
110
110
|
import {delete_impact} from "./delete_impact.js";
|
|
111
111
|
|
|
112
112
|
import "./c_yui_treedb_topic_with_form.css";
|
|
113
|
+
import {yui_shell_show_error} from "./shell_modals.js";
|
|
113
114
|
import {set_toolbar_busy} from "./form_busy.js";
|
|
114
115
|
import "./tabulator.css";
|
|
115
116
|
import {cell_text, hook_cell_spec} from "./table_cell_text.js";
|
|
@@ -3506,6 +3507,10 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation)
|
|
|
3506
3507
|
case "string":
|
|
3507
3508
|
if(value && is_date(value)) {
|
|
3508
3509
|
value = value.toISOString();
|
|
3510
|
+
} else if(typeof value === "number" && value > 0) {
|
|
3511
|
+
/* The form hands an epoch in seconds (form_time_value);
|
|
3512
|
+
* a string column stores the instant as ISO text. */
|
|
3513
|
+
value = new Date(value * 1000).toISOString();
|
|
3509
3514
|
}
|
|
3510
3515
|
break;
|
|
3511
3516
|
case "integer":
|
|
@@ -4092,6 +4097,9 @@ function ac_confirmed(gobj, event, kw, src)
|
|
|
4092
4097
|
* them): never replaced by the row that took their place. */
|
|
4093
4098
|
log_error(`${gobj_short_name(gobj)}: ${event}: rows gone while ` +
|
|
4094
4099
|
`confirming the delete, not deleted: ${found.missing.join(", ")}`);
|
|
4100
|
+
/* The person who answered "yes" is told, not only the log
|
|
4101
|
+
* (a low of the 2026-09-22 review). */
|
|
4102
|
+
yui_shell_show_error(yui_shell_of(gobj), "some records were gone before the delete", {t: t});
|
|
4095
4103
|
}
|
|
4096
4104
|
if(!found.rows.length) {
|
|
4097
4105
|
return -1; /* Error already logged, or nothing was named */
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* host_drafts.js
|
|
3
|
+
*
|
|
4
|
+
* The topics a schema editor shows as DRAFTS (edited in
|
|
5
|
+
* __system__, not yet saved), told by the host from data. The
|
|
6
|
+
* editor marks a topic when a write of this session lands on
|
|
7
|
+
* it, and that mark lived in the memory of the session only:
|
|
8
|
+
* a reload of the page, a reconnect or a refresh of the model
|
|
9
|
+
* lost the chip, the banner and the export warning while
|
|
10
|
+
* __system__ still differed from the file in use (N13 of the
|
|
11
|
+
* 2026-09-22 review). The host reads that difference from
|
|
12
|
+
* C_TREEDB's `saved-schema` (`draft_changed`, by treedb) and
|
|
13
|
+
* hands it here; the editor applies it to its model.
|
|
14
|
+
*
|
|
15
|
+
* Copyright (c) 2026, ArtGins.
|
|
16
|
+
* All Rights Reserved.
|
|
17
|
+
***********************************************************************/
|
|
18
|
+
|
|
19
|
+
/************************************************************
|
|
20
|
+
* Mark in `written` the topics of `model` the host names as
|
|
21
|
+
* drafts: `drafts` is {treedb_name: [topic names]}. A treedb or a
|
|
22
|
+
* topic the model does not hold is ignored. Returns how many were
|
|
23
|
+
* marked.
|
|
24
|
+
************************************************************/
|
|
25
|
+
function mark_host_drafts(written, model, drafts)
|
|
26
|
+
{
|
|
27
|
+
let marked = 0;
|
|
28
|
+
if(!written || !model || !Array.isArray(model.treedbs) || !drafts) {
|
|
29
|
+
return marked;
|
|
30
|
+
}
|
|
31
|
+
for(let treedb of model.treedbs) {
|
|
32
|
+
const names = drafts[treedb.id];
|
|
33
|
+
if(!Array.isArray(names) || names.length === 0) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
for(let topic of treedb.topics || []) {
|
|
37
|
+
if(names.includes(topic.name)) {
|
|
38
|
+
written[topic.id] = true;
|
|
39
|
+
marked++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return marked;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/************************************************************
|
|
47
|
+
* What a `saved-schema` answer says, as {treedb_name: [topic names]}:
|
|
48
|
+
* its `data` is one row per treedb (the unnamed form of the command),
|
|
49
|
+
* each carrying `draft_changed` {topic: true}.
|
|
50
|
+
************************************************************/
|
|
51
|
+
function drafts_of_saved_answer(rows, into)
|
|
52
|
+
{
|
|
53
|
+
const drafts = into || {};
|
|
54
|
+
for(let row of Array.isArray(rows)? rows: []) {
|
|
55
|
+
const treedb_name = row && row.treedb_name;
|
|
56
|
+
const changed = row && row.data && row.data.draft_changed;
|
|
57
|
+
if(!treedb_name || !changed || typeof changed !== "object") {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
drafts[treedb_name] = Object.keys(changed).filter((k) => !!changed[k]);
|
|
61
|
+
}
|
|
62
|
+
return drafts;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
mark_host_drafts,
|
|
67
|
+
drafts_of_saved_answer,
|
|
68
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {describe, it, expect} from "vitest";
|
|
2
|
+
import {mark_host_drafts, drafts_of_saved_answer} from "./host_drafts.js";
|
|
3
|
+
|
|
4
|
+
const model = {
|
|
5
|
+
treedbs: [
|
|
6
|
+
{id: "treedb_a", name: "treedb_a", topics: [
|
|
7
|
+
{id: "treedb_a^users", name: "users"},
|
|
8
|
+
{id: "treedb_a^roles", name: "roles"},
|
|
9
|
+
]},
|
|
10
|
+
{id: "treedb_b", name: "treedb_b", topics: [
|
|
11
|
+
{id: "treedb_b^users", name: "users"},
|
|
12
|
+
]},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe("mark_host_drafts", () => {
|
|
17
|
+
|
|
18
|
+
it("marks the topics the host names, by treedb (N13)", () => {
|
|
19
|
+
/* The mark lived in the memory of one session: after a reload the
|
|
20
|
+
* editor showed no draft while __system__ still differed from the
|
|
21
|
+
* file in use. Rebuilt from the host's data. */
|
|
22
|
+
const written = {};
|
|
23
|
+
const n = mark_host_drafts(written, model, {treedb_a: ["users"]});
|
|
24
|
+
expect(n).toBe(1);
|
|
25
|
+
expect(written).toEqual({"treedb_a^users": true});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("a topic of the same name in another treedb is another topic", () => {
|
|
29
|
+
const written = {};
|
|
30
|
+
mark_host_drafts(written, model, {treedb_b: ["users"]});
|
|
31
|
+
expect(written).toEqual({"treedb_b^users": true});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("keeps what the session wrote, ignores what the model does not hold", () => {
|
|
35
|
+
const written = {"treedb_a^roles": true};
|
|
36
|
+
const n = mark_host_drafts(written, model, {treedb_a: ["nobody"], treedb_z: ["users"]});
|
|
37
|
+
expect(n).toBe(0);
|
|
38
|
+
expect(written).toEqual({"treedb_a^roles": true});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("no model, no drafts: nothing", () => {
|
|
42
|
+
expect(mark_host_drafts({}, null, {treedb_a: ["users"]})).toBe(0);
|
|
43
|
+
expect(mark_host_drafts({}, model, null)).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("drafts_of_saved_answer", () => {
|
|
48
|
+
|
|
49
|
+
it("reads draft_changed of each treedb row", () => {
|
|
50
|
+
const rows = [
|
|
51
|
+
{treedb_name: "treedb_a", result: 0, data: {draft_changed: {users: true, roles: false}}},
|
|
52
|
+
{treedb_name: "treedb_b", result: 0, data: {draft_changed: {}}},
|
|
53
|
+
{treedb_name: "treedb_c", result: -1, comment: "not open here"},
|
|
54
|
+
];
|
|
55
|
+
expect(drafts_of_saved_answer(rows)).toEqual({treedb_a: ["users"], treedb_b: []});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("a node older than draft_changed says nothing", () => {
|
|
59
|
+
expect(drafts_of_saved_answer([{treedb_name: "t", data: {can_apply: false}}])).toEqual({});
|
|
60
|
+
expect(drafts_of_saved_answer(null)).toEqual({});
|
|
61
|
+
});
|
|
62
|
+
});
|