@yuneta/gobj-ui 7.25.18 → 7.25.20
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 +87 -8
- package/dist/gobj-ui.cjs.js +151 -43
- package/dist/gobj-ui.es.js +151 -43
- package/package.json +2 -2
- package/src/c_g6_nodes_tree.js +52 -4
- package/src/c_yui_json.js +28 -5
- package/src/c_yui_json_pad.js +3 -2
- package/src/c_yui_node.js +71 -0
- package/src/c_yui_schema_editor.js +34 -11
- package/src/c_yui_treedb_graph.js +24 -10
- package/src/c_yui_treedb_topic_with_form.js +24 -0
- package/src/c_yui_treedb_topics.js +22 -9
- package/src/c_yui_uplot.js +10 -5
- package/src/form_json_expand.wiring.test.js +204 -0
- package/src/g6_graphs_write_owed.wiring.test.js +114 -0
- package/src/node_nav_events.wiring.test.js +150 -0
- package/src/schema_editor_reload.wiring.test.js +37 -0
- package/src/schema_editor_transport.wiring.test.js +89 -5
- package/src/treedb_graph_writes.wiring.test.js +38 -4
- package/src/treedb_topics_transport.wiring.test.js +89 -0
- package/src/uplot_add_serie.test.js +92 -0
package/src/c_yui_node.js
CHANGED
|
@@ -1278,6 +1278,65 @@ function ac_nav_clicked(gobj, event, kw, src)
|
|
|
1278
1278
|
return 0;
|
|
1279
1279
|
}
|
|
1280
1280
|
|
|
1281
|
+
/************************************************************
|
|
1282
|
+
* The ✕ of a closable item, from a projection nav. A node's own
|
|
1283
|
+
* items never carry `closable` (child_nav_items() builds them), so
|
|
1284
|
+
* this is an item the node does not own the fate of: whether it
|
|
1285
|
+
* goes away is the app's call, as for the shell's own closable
|
|
1286
|
+
* tabs. Handed to the shell, which re-publishes it to the app
|
|
1287
|
+
* (ac_nav_item_close there).
|
|
1288
|
+
************************************************************/
|
|
1289
|
+
function ac_nav_item_close(gobj, event, kw, src)
|
|
1290
|
+
{
|
|
1291
|
+
let shell = yui_shell_of(gobj);
|
|
1292
|
+
if(!shell) {
|
|
1293
|
+
log_error(`${GCLASS_NAME} '${node_path_str(gobj)}': no shell to hand ` +
|
|
1294
|
+
`the close of '${(kw && kw.item_id) || ""}' to`);
|
|
1295
|
+
return -1;
|
|
1296
|
+
}
|
|
1297
|
+
gobj_send_event(shell, "EV_NAV_ITEM_CLOSE", {
|
|
1298
|
+
item_id: (kw && kw.item_id) || "",
|
|
1299
|
+
route: (kw && kw.route) || "",
|
|
1300
|
+
menu_id: (kw && kw.menu_id) || "",
|
|
1301
|
+
zone: (kw && kw.zone) || ""
|
|
1302
|
+
}, gobj);
|
|
1303
|
+
return 0;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/************************************************************
|
|
1307
|
+
* The backdrop of a drawer projection was clicked. The drawer is
|
|
1308
|
+
* one of MY navs (a projection with `layout: "drawer"`), not one of
|
|
1309
|
+
* the shell's, so the shell's close_drawer() cannot find it: the
|
|
1310
|
+
* node closes it. A drawer opened through the shell carries the
|
|
1311
|
+
* shell's own close handler (escape stack, focus trap) on its
|
|
1312
|
+
* element, and that is what runs then; otherwise it is only hidden.
|
|
1313
|
+
************************************************************/
|
|
1314
|
+
function ac_drawer_close_requested(gobj, event, kw, src)
|
|
1315
|
+
{
|
|
1316
|
+
let priv = gobj.priv;
|
|
1317
|
+
let menu_id = (kw && kw.menu_id) || "";
|
|
1318
|
+
let navs = priv.navs.concat(Object.values(priv.zone_navs));
|
|
1319
|
+
|
|
1320
|
+
for(let nav of navs) {
|
|
1321
|
+
if(gobj_read_attr(nav, "layout") !== "drawer") {
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
if(menu_id && gobj_read_attr(nav, "menu_id") !== menu_id) {
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
let $c = gobj_read_attr(nav, "$container");
|
|
1328
|
+
if(!$c || !$c.classList.contains("is-active")) {
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
if(typeof $c.__yui_close_handler__ === "function") {
|
|
1332
|
+
$c.__yui_close_handler__();
|
|
1333
|
+
} else {
|
|
1334
|
+
$c.classList.remove("is-active");
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return 0;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1281
1340
|
/************************************************************
|
|
1282
1341
|
* Runtime API: add a child. Same path the declared tree takes.
|
|
1283
1342
|
************************************************************/
|
|
@@ -1666,6 +1725,12 @@ function create_gclass(gclass_name)
|
|
|
1666
1725
|
["ST_OFF", [
|
|
1667
1726
|
["EV_ROUTE_CHANGED", ac_route_changed, null],
|
|
1668
1727
|
["EV_ACTIVATE", ac_activate, null],
|
|
1728
|
+
/* A ZONE projection outlives the path (it is the app's
|
|
1729
|
+
* standing chrome, stopped only in mt_stop), so its clicks
|
|
1730
|
+
* reach a root that is off the path too. */
|
|
1731
|
+
["EV_NAV_CLICKED", ac_nav_clicked, null],
|
|
1732
|
+
["EV_NAV_ITEM_CLOSE", ac_nav_item_close, null],
|
|
1733
|
+
["EV_DRAWER_CLOSE_REQUESTED", ac_drawer_close_requested, null],
|
|
1669
1734
|
["EV_ADD_NODE", ac_add_node, null],
|
|
1670
1735
|
["EV_REMOVE_NODE", ac_remove_node, null],
|
|
1671
1736
|
["EV_SET_PROJECTION", ac_set_projection, null],
|
|
@@ -1679,6 +1744,8 @@ function create_gclass(gclass_name)
|
|
|
1679
1744
|
["EV_ACTIVATE", ac_activate, null],
|
|
1680
1745
|
["EV_DEACTIVATE", ac_deactivate, null],
|
|
1681
1746
|
["EV_NAV_CLICKED", ac_nav_clicked, null],
|
|
1747
|
+
["EV_NAV_ITEM_CLOSE", ac_nav_item_close, null],
|
|
1748
|
+
["EV_DRAWER_CLOSE_REQUESTED", ac_drawer_close_requested, null],
|
|
1682
1749
|
["EV_ADD_NODE", ac_add_node, null],
|
|
1683
1750
|
["EV_REMOVE_NODE", ac_remove_node, null],
|
|
1684
1751
|
["EV_SET_PROJECTION", ac_set_projection, null],
|
|
@@ -1692,6 +1759,8 @@ function create_gclass(gclass_name)
|
|
|
1692
1759
|
["EV_ACTIVATE", ac_activate, null],
|
|
1693
1760
|
["EV_DEACTIVATE", ac_deactivate, null],
|
|
1694
1761
|
["EV_NAV_CLICKED", ac_nav_clicked, null],
|
|
1762
|
+
["EV_NAV_ITEM_CLOSE", ac_nav_item_close, null],
|
|
1763
|
+
["EV_DRAWER_CLOSE_REQUESTED", ac_drawer_close_requested, null],
|
|
1695
1764
|
["EV_ADD_NODE", ac_add_node, null],
|
|
1696
1765
|
["EV_REMOVE_NODE", ac_remove_node, null],
|
|
1697
1766
|
["EV_SET_PROJECTION", ac_set_projection, null],
|
|
@@ -1710,6 +1779,8 @@ function create_gclass(gclass_name)
|
|
|
1710
1779
|
["EV_ACTIVATE", 0],
|
|
1711
1780
|
["EV_DEACTIVATE", 0],
|
|
1712
1781
|
["EV_NAV_CLICKED", 0],
|
|
1782
|
+
["EV_NAV_ITEM_CLOSE", 0],
|
|
1783
|
+
["EV_DRAWER_CLOSE_REQUESTED", 0],
|
|
1713
1784
|
["EV_ADD_NODE", 0],
|
|
1714
1785
|
["EV_REMOVE_NODE", 0],
|
|
1715
1786
|
["EV_SET_PROJECTION", 0],
|
|
@@ -755,8 +755,11 @@ function remote_command(gobj, command, kw, tag)
|
|
|
755
755
|
* echoes `__md_command__` AS that frame (c_agent_treedb_link.js).
|
|
756
756
|
* Same names in both places is what makes the answer readable
|
|
757
757
|
* whichever one is under this view — and `record_id` is here and
|
|
758
|
-
* NOT at the top level because a
|
|
759
|
-
*
|
|
758
|
+
* NOT at the top level because a parameter the treedb does not know
|
|
759
|
+
* has no business travelling. It is echoed at all because an answer
|
|
760
|
+
* does not always name its record: a delete answers the node deleted,
|
|
761
|
+
* but a REFUSED write answers none, and the view still has to know
|
|
762
|
+
* which record it was. */
|
|
760
763
|
full_kw.__md_command__ = Object.assign({
|
|
761
764
|
purpose: PURPOSE,
|
|
762
765
|
topic_name: (kw && kw.topic_name) || "",
|
|
@@ -981,8 +984,12 @@ function current_topic(gobj)
|
|
|
981
984
|
* One line for what the whole view has to say: loading, empty,
|
|
982
985
|
* or the reason nothing loaded. Carries its i18n key, so it
|
|
983
986
|
* follows a language change like any other text.
|
|
987
|
+
*
|
|
988
|
+
* `detail` is a NAME (a treedb, a topic) shown as it is, unless
|
|
989
|
+
* `detail_is_key` says it is a key: a name is not text to
|
|
990
|
+
* translate, and a topic called `nodes` is not "Nodos".
|
|
984
991
|
***************************************************************/
|
|
985
|
-
function show_notice(gobj, key, detail)
|
|
992
|
+
function show_notice(gobj, key, detail, detail_is_key)
|
|
986
993
|
{
|
|
987
994
|
let $notice = $of(gobj, ".SCHEMA_NOTICE");
|
|
988
995
|
|
|
@@ -998,14 +1005,17 @@ function show_notice(gobj, key, detail)
|
|
|
998
1005
|
$notice.appendChild(createElement2(
|
|
999
1006
|
["span", {class: "SCHEMA_NOTICE_TEXT", i18n: key}, t(key)]
|
|
1000
1007
|
));
|
|
1001
|
-
/*
|
|
1002
|
-
* translate to themselves when no locale has them)
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1008
|
+
/* A detail that is a KEY (the load error: ours, or the backend's
|
|
1009
|
+
* words, which translate to themselves when no locale has them)
|
|
1010
|
+
* changes language with the rest. One that repeats the title
|
|
1011
|
+
* says nothing. */
|
|
1005
1012
|
if(detail && detail !== key) {
|
|
1013
|
+
let attrs = {class: "SCHEMA_NOTICE_DETAIL yui-text-quiet ml-2"};
|
|
1014
|
+
if(detail_is_key) {
|
|
1015
|
+
attrs.i18n = `${detail}`;
|
|
1016
|
+
}
|
|
1006
1017
|
$notice.appendChild(createElement2(
|
|
1007
|
-
["span",
|
|
1008
|
-
t(`${detail}`)]
|
|
1018
|
+
["span", attrs, detail_is_key ? t(`${detail}`) : `${detail}`]
|
|
1009
1019
|
));
|
|
1010
1020
|
}
|
|
1011
1021
|
}
|
|
@@ -1188,7 +1198,7 @@ function render(gobj)
|
|
|
1188
1198
|
}
|
|
1189
1199
|
if(state === "ST_IDLE") {
|
|
1190
1200
|
show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected",
|
|
1191
|
-
priv.load_error);
|
|
1201
|
+
priv.load_error, true);
|
|
1192
1202
|
destroy_diagram(gobj);
|
|
1193
1203
|
clear($body);
|
|
1194
1204
|
return;
|
|
@@ -2810,6 +2820,19 @@ function run_next_write(gobj)
|
|
|
2810
2820
|
let command = write_command(write.op);
|
|
2811
2821
|
let options = write_options(write.op);
|
|
2812
2822
|
|
|
2823
|
+
/* Out of session a write is not sent, and ends here. A direct
|
|
2824
|
+
* C_IEVENT_CLI answers a command out of session with null -- the
|
|
2825
|
+
* answer of a request that LEFT -- so without this the editor
|
|
2826
|
+
* waited in ST_SAVING, inert, for an answer that never comes
|
|
2827
|
+
* (before gobj-ui 7.25.20). What the queue wrote before is not
|
|
2828
|
+
* known to the next session: it reads the model again. */
|
|
2829
|
+
if(!transport_in_session(gobj)) {
|
|
2830
|
+
log_warning(`${gobj_short_name(gobj)}: no session, '${command}' ` +
|
|
2831
|
+
`of '${write.topic_name}' was not sent`);
|
|
2832
|
+
priv.reload_on_open = true;
|
|
2833
|
+
return end_writes(gobj, "cannot reach the treedb");
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2813
2836
|
priv.write_tag++;
|
|
2814
2837
|
if(remote_command(gobj, command, {
|
|
2815
2838
|
topic_name: write.topic_name,
|
|
@@ -2817,7 +2840,7 @@ function run_next_write(gobj)
|
|
|
2817
2840
|
options: options,
|
|
2818
2841
|
record_id: write.record.id || ""
|
|
2819
2842
|
}, {write: priv.write_tag}) < 0) {
|
|
2820
|
-
return end_writes(gobj,
|
|
2843
|
+
return end_writes(gobj, "cannot reach the treedb"); /* a key: the toast translates it */
|
|
2821
2844
|
}
|
|
2822
2845
|
return 0;
|
|
2823
2846
|
}
|
|
@@ -1325,7 +1325,7 @@ function request_print_tranger(gobj, path)
|
|
|
1325
1325
|
let jv = priv.json_gobj;
|
|
1326
1326
|
if(path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) {
|
|
1327
1327
|
gobj_send_event(jv, "EV_SUBTREE_ERROR",
|
|
1328
|
-
{path: path,
|
|
1328
|
+
{path: path, i18n: "no session"}, gobj);
|
|
1329
1329
|
}
|
|
1330
1330
|
return;
|
|
1331
1331
|
}
|
|
@@ -1498,7 +1498,7 @@ function treedb_create_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1498
1498
|
/************************************************************
|
|
1499
1499
|
* Command to remote service
|
|
1500
1500
|
************************************************************/
|
|
1501
|
-
function treedb_update_node(gobj, treedb_name, topic_name, record, options)
|
|
1501
|
+
function treedb_update_node(gobj, treedb_name, topic_name, record, options, graphs_load)
|
|
1502
1502
|
{
|
|
1503
1503
|
let priv = gobj.priv;
|
|
1504
1504
|
|
|
@@ -1525,6 +1525,11 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1525
1525
|
* arranges, so that topic has to travel here. */
|
|
1526
1526
|
if(topic_name === "__graphs__") {
|
|
1527
1527
|
kw.__md_command__.graph_topic = graph_topic_of(record);
|
|
1528
|
+
/* And the engine's LOAD it was sent from: a refusal answered
|
|
1529
|
+
* after a reload is not about the arrangement shown now. */
|
|
1530
|
+
if(graphs_load !== undefined) {
|
|
1531
|
+
kw.__md_command__.graphs_load = graphs_load;
|
|
1532
|
+
}
|
|
1528
1533
|
}
|
|
1529
1534
|
|
|
1530
1535
|
let ret = gobj_command(priv.gobj_remote_yuno,
|
|
@@ -2162,7 +2167,8 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2162
2167
|
if(result < 0 && command === "update-node") {
|
|
2163
2168
|
graphs_write_refused(gobj,
|
|
2164
2169
|
kw_get_str(gobj, kw_command, "topic_name", "", 0),
|
|
2165
|
-
kw_get_str(gobj, kw_command, "graph_topic", "", 0)
|
|
2170
|
+
kw_get_str(gobj, kw_command, "graph_topic", "", 0),
|
|
2171
|
+
kw_command ? kw_command.graphs_load : undefined);
|
|
2166
2172
|
}
|
|
2167
2173
|
if(result >= 0 &&
|
|
2168
2174
|
kw_get_str(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") {
|
|
@@ -2533,9 +2539,10 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2533
2539
|
let topic_name = kw.topic_name;
|
|
2534
2540
|
let record = kw.record;
|
|
2535
2541
|
let options = kw.options || {};
|
|
2542
|
+
let graphs_load = kw.graphs_load;
|
|
2536
2543
|
|
|
2537
2544
|
if(refuse_if_readonly(gobj, event)) {
|
|
2538
|
-
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2545
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
|
|
2539
2546
|
return -1;
|
|
2540
2547
|
}
|
|
2541
2548
|
|
|
@@ -2545,7 +2552,7 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2545
2552
|
if(topic_name === "__graphs__" && !is_connected(gobj)) {
|
|
2546
2553
|
log_warning(`${gobj_short_name(gobj)}: no session, the arrangement ` +
|
|
2547
2554
|
`was not sent`);
|
|
2548
|
-
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2555
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
|
|
2549
2556
|
return -1;
|
|
2550
2557
|
}
|
|
2551
2558
|
|
|
@@ -2554,10 +2561,11 @@ function ac_update_node(gobj, event, kw, src)
|
|
|
2554
2561
|
treedb_name,
|
|
2555
2562
|
topic_name,
|
|
2556
2563
|
record,
|
|
2557
|
-
options
|
|
2564
|
+
options,
|
|
2565
|
+
graphs_load
|
|
2558
2566
|
);
|
|
2559
2567
|
if(ret < 0) {
|
|
2560
|
-
graphs_write_refused(gobj, topic_name, graph_topic_of(record));
|
|
2568
|
+
graphs_write_refused(gobj, topic_name, graph_topic_of(record), graphs_load);
|
|
2561
2569
|
}
|
|
2562
2570
|
return ret;
|
|
2563
2571
|
}
|
|
@@ -2578,9 +2586,11 @@ function graph_topic_of(record)
|
|
|
2578
2586
|
* (C_G6_NODES_TREE) took it for granted when it left -- nothing
|
|
2579
2587
|
* answers it when it lands -- so it is told, and its next Save
|
|
2580
2588
|
* writes the topic again. Other topics are the operator's data:
|
|
2581
|
-
* the answer's error says what happened to them.
|
|
2589
|
+
* the answer's error says what happened to them. `graphs_load` is
|
|
2590
|
+
* the engine's load the write was sent from, handed back so it can
|
|
2591
|
+
* tell a refusal of an earlier load from one of the current.
|
|
2582
2592
|
************************************************************/
|
|
2583
|
-
function graphs_write_refused(gobj, topic_name, topic)
|
|
2593
|
+
function graphs_write_refused(gobj, topic_name, topic, graphs_load)
|
|
2584
2594
|
{
|
|
2585
2595
|
let priv = gobj.priv;
|
|
2586
2596
|
|
|
@@ -2594,7 +2604,11 @@ function graphs_write_refused(gobj, topic_name, topic)
|
|
|
2594
2604
|
if(!priv.gobj_nodes_tree) {
|
|
2595
2605
|
return; /* no engine: nothing believes it was saved */
|
|
2596
2606
|
}
|
|
2597
|
-
|
|
2607
|
+
let kw = {topic: topic};
|
|
2608
|
+
if(graphs_load !== undefined) {
|
|
2609
|
+
kw.graphs_load = graphs_load;
|
|
2610
|
+
}
|
|
2611
|
+
gobj_send_event(priv.gobj_nodes_tree, "EV_GRAPHS_WRITE_REFUSED", kw, gobj);
|
|
2598
2612
|
}
|
|
2599
2613
|
|
|
2600
2614
|
/********************************************
|
|
@@ -3696,6 +3696,28 @@ function refuse_if_readonly(gobj, event)
|
|
|
3696
3696
|
/************************************************************
|
|
3697
3697
|
* From external, at the beginning, load all topic data
|
|
3698
3698
|
************************************************************/
|
|
3699
|
+
/***************************************************************
|
|
3700
|
+
* One of MY json viewers (the schema, a cell, the table's records)
|
|
3701
|
+
* met a `__collapsed__` sentinel and the reader opened it. The
|
|
3702
|
+
* viewer never fetches: it asks its host, and waits on "loading"
|
|
3703
|
+
* until it is answered. This view holds records, not a store it can
|
|
3704
|
+
* read a subtree of -- the lazy drill is `print-tranger`, which the
|
|
3705
|
+
* TOPICS view runs for its own viewer, over a whole tranger -- so the
|
|
3706
|
+
* answer is that it cannot, and the viewer shows that on the stub
|
|
3707
|
+
* instead of loading for ever.
|
|
3708
|
+
***************************************************************/
|
|
3709
|
+
function ac_expand_path(gobj, event, kw, src)
|
|
3710
|
+
{
|
|
3711
|
+
let path = (kw && kw.path) || "";
|
|
3712
|
+
|
|
3713
|
+
gobj_send_event(src, "EV_SUBTREE_ERROR", {
|
|
3714
|
+
path: path,
|
|
3715
|
+
i18n: "this part cannot be loaded here",
|
|
3716
|
+
by_design: true
|
|
3717
|
+
}, gobj);
|
|
3718
|
+
return 0;
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3699
3721
|
/***************************************************************
|
|
3700
3722
|
* The language changed (the shell publishes it after the app switched).
|
|
3701
3723
|
*
|
|
@@ -5343,6 +5365,7 @@ function create_gclass(gclass_name)
|
|
|
5343
5365
|
["EV_CHANGE_LOCALE", ac_change_locale, null],
|
|
5344
5366
|
["EV_REFRESH", ac_refresh, null],
|
|
5345
5367
|
["EV_SHOW_SCHEMA", ac_show_schema, null],
|
|
5368
|
+
["EV_EXPAND_PATH", ac_expand_path, null],
|
|
5346
5369
|
["EV_CELL_EDITED", ac_cell_edited, null],
|
|
5347
5370
|
["EV_REPULL_PAGE", ac_repull_page, null],
|
|
5348
5371
|
["EV_PAGE_LOADED", ac_page_loaded, null],
|
|
@@ -5400,6 +5423,7 @@ function create_gclass(gclass_name)
|
|
|
5400
5423
|
["EV_CHANGE_LOCALE", 0],
|
|
5401
5424
|
["EV_REFRESH", 0],
|
|
5402
5425
|
["EV_SHOW_SCHEMA", 0],
|
|
5426
|
+
["EV_EXPAND_PATH", 0],
|
|
5403
5427
|
["EV_CELL_EDITED", 0],
|
|
5404
5428
|
["EV_REPULL_PAGE", 0],
|
|
5405
5429
|
["EV_PAGE_LOADED", 0],
|
|
@@ -1676,7 +1676,7 @@ function treedb_create_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1676
1676
|
/************************************************************
|
|
1677
1677
|
* Command to remote service
|
|
1678
1678
|
************************************************************/
|
|
1679
|
-
function treedb_update_node(gobj, treedb_name, topic_name, record, options, form_write)
|
|
1679
|
+
function treedb_update_node(gobj, treedb_name, topic_name, record, options, form_write, created)
|
|
1680
1680
|
{
|
|
1681
1681
|
let command = "update-node";
|
|
1682
1682
|
|
|
@@ -1695,6 +1695,11 @@ function treedb_update_node(gobj, treedb_name, topic_name, record, options, form
|
|
|
1695
1695
|
if(form_write) {
|
|
1696
1696
|
kw.__md_command__.form_write = form_write;
|
|
1697
1697
|
}
|
|
1698
|
+
if(created) {
|
|
1699
|
+
/* A create goes out as an update-node (create_only); the answer
|
|
1700
|
+
* says `created` only if the request says it back. */
|
|
1701
|
+
kw.__md_command__.created = true;
|
|
1702
|
+
}
|
|
1698
1703
|
// TODO review msg_iev_write_key(kw, "__topic_name__", topic_name);
|
|
1699
1704
|
|
|
1700
1705
|
let ret = gobj_command(
|
|
@@ -1740,7 +1745,9 @@ function treedb_delete_node(gobj, treedb_name, topic_name, record, options)
|
|
|
1740
1745
|
);
|
|
1741
1746
|
if(ret) {
|
|
1742
1747
|
log_error(ret);
|
|
1748
|
+
return -1;
|
|
1743
1749
|
}
|
|
1750
|
+
return 0;
|
|
1744
1751
|
}
|
|
1745
1752
|
|
|
1746
1753
|
/************************************************************
|
|
@@ -1948,7 +1955,7 @@ function request_print_tranger(gobj, path)
|
|
|
1948
1955
|
let jv = priv.json_gobj;
|
|
1949
1956
|
if(path && jv && is_gobj(jv) && !gobj_is_destroying(jv)) {
|
|
1950
1957
|
gobj_send_event(jv, "EV_SUBTREE_ERROR",
|
|
1951
|
-
{path: path,
|
|
1958
|
+
{path: path, i18n: "no session"}, gobj);
|
|
1952
1959
|
}
|
|
1953
1960
|
return;
|
|
1954
1961
|
}
|
|
@@ -2217,6 +2224,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2217
2224
|
|
|
2218
2225
|
case "create-node":
|
|
2219
2226
|
case "update-node":
|
|
2227
|
+
case "delete-node":
|
|
2220
2228
|
/*
|
|
2221
2229
|
* The view refreshes itself from the treedb's own
|
|
2222
2230
|
* EV_TREEDB_NODE_* events, which arrive for EVERY writer. This
|
|
@@ -2230,14 +2238,23 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2230
2238
|
/* From what THIS view knows and from the answer, never from
|
|
2231
2239
|
* the request: the frame carries back only `__md_command__`,
|
|
2232
2240
|
* so `treedb_name` and `record` read there arrived empty.
|
|
2233
|
-
* `record` is the node the store answered, as it was written
|
|
2241
|
+
* `record` is the node the store answered, as it was written
|
|
2242
|
+
* (for a delete, the node deleted). The table's own +New goes
|
|
2243
|
+
* out as an update-node, so `created` is also what the request
|
|
2244
|
+
* echoes back in `__md_command__`. */
|
|
2234
2245
|
gobj_publish_event(gobj, "EV_RECORD_WRITTEN", {
|
|
2235
2246
|
treedb_name: gobj_read_str_attr(gobj, "treedb_name"),
|
|
2236
2247
|
topic_name: kw_get_str(gobj, kw_command, "topic_name", "", 0),
|
|
2237
2248
|
record: answered_record(data),
|
|
2238
|
-
created: (command === "create-node")
|
|
2249
|
+
created: (command === "create-node") ||
|
|
2250
|
+
kw_command.created === true,
|
|
2239
2251
|
command: command
|
|
2240
2252
|
});
|
|
2253
|
+
if(command === "delete-node") {
|
|
2254
|
+
/* The rows go away on the treedb's own EV_TREEDB_NODE_DELETED;
|
|
2255
|
+
* a delete has no form waiting for its answer. */
|
|
2256
|
+
break;
|
|
2257
|
+
}
|
|
2241
2258
|
settle_form_write(gobj.priv.form_writes,
|
|
2242
2259
|
kw_get_int(gobj, kw_command, "form_write", 0, 0),
|
|
2243
2260
|
kw_get_str(gobj, kw_command, "topic_name", "", 0));
|
|
@@ -2249,10 +2266,6 @@ function ac_mt_command_answer(gobj, event, kw, src)
|
|
|
2249
2266
|
);
|
|
2250
2267
|
break;
|
|
2251
2268
|
|
|
2252
|
-
case "delete-node":
|
|
2253
|
-
// Don't process by here, process on subscribed events.
|
|
2254
|
-
break;
|
|
2255
|
-
|
|
2256
2269
|
default:
|
|
2257
2270
|
log_error(`${gobj_short_name(gobj)} Command unknown: ${command}`);
|
|
2258
2271
|
}
|
|
@@ -2680,7 +2693,7 @@ function ac_create_record(gobj, event, kw, src)
|
|
|
2680
2693
|
};
|
|
2681
2694
|
|
|
2682
2695
|
if(treedb_update_node( // HACK use the powerful update_node
|
|
2683
|
-
gobj, treedb_name, topic_name, record, options, form_write) < 0) {
|
|
2696
|
+
gobj, treedb_name, topic_name, record, options, form_write, true) < 0) {
|
|
2684
2697
|
answer_form_write(gobj, src, form_write, false);
|
|
2685
2698
|
return -1; /* Error already logged */
|
|
2686
2699
|
}
|
package/src/c_yui_uplot.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Graphics, Charts with uPlot
|
|
5
5
|
*
|
|
6
|
-
* Copyright (c) 2025, ArtGins.
|
|
6
|
+
* Copyright (c) 2025-2026, ArtGins.
|
|
7
7
|
* All Rights Reserved.
|
|
8
8
|
***********************************************************************/
|
|
9
9
|
|
|
@@ -12,14 +12,12 @@ import {
|
|
|
12
12
|
SDATA_END,
|
|
13
13
|
data_type_t,
|
|
14
14
|
event_flag_t,
|
|
15
|
-
kw_flag_t,
|
|
16
15
|
gclass_create,
|
|
17
16
|
createElement2,
|
|
18
17
|
log_error,
|
|
19
18
|
gobj_read_pointer_attr,
|
|
20
19
|
gobj_parent,
|
|
21
20
|
gobj_subscribe_event,
|
|
22
|
-
kw_get_str,
|
|
23
21
|
gobj_read_attr,
|
|
24
22
|
gobj_read_str_attr,
|
|
25
23
|
gobj_write_attr,
|
|
@@ -457,8 +455,15 @@ function ac_add_serie(gobj, event, kw, src)
|
|
|
457
455
|
* colour: the old fallback was `Orange`, 1.97:1 on a white page,
|
|
458
456
|
* and it was what every series past the fourth got. */
|
|
459
457
|
let color = colors[Math.max(0, idx - 1) % colors.length];
|
|
460
|
-
|
|
461
|
-
|
|
458
|
+
/* Only when the host gave none: uPlot takes a stroke or a fill
|
|
459
|
+
* that is a FUNCTION (or a gradient) too, and kw_get_str() logs
|
|
460
|
+
* a value that is not a string (gobj-js 7.25.5, as C does). */
|
|
461
|
+
if(kw.stroke === undefined) {
|
|
462
|
+
kw.stroke = color.stroke;
|
|
463
|
+
}
|
|
464
|
+
if(kw.fill === undefined) {
|
|
465
|
+
kw.fill = color.fill;
|
|
466
|
+
}
|
|
462
467
|
//kw["paths"] = beziercurve_path; //rectangle_path;
|
|
463
468
|
uplot.addSeries(kw);
|
|
464
469
|
return 0;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* form_json_expand.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* C_YUI_TREEDB_TOPIC_WITH_FORM hosts C_YUI_JSON as a pure CHILD
|
|
5
|
+
* (the schema, a cell, the table's records), so it hears every
|
|
6
|
+
* event the viewer publishes -- and it did not declare
|
|
7
|
+
* EV_EXPAND_PATH, which the viewer publishes when the reader opens
|
|
8
|
+
* a `__collapsed__` sentinel. The click answered "Event NOT DEFINED
|
|
9
|
+
* in state" and the stub stayed on "loading" for good. The form
|
|
10
|
+
* cannot read a subtree (that is print-tranger, which the TOPICS
|
|
11
|
+
* view runs for its own viewer), so it says so, and the stub shows
|
|
12
|
+
* it.
|
|
13
|
+
*
|
|
14
|
+
* Driven through the real gclasses (C_YUI_SHELL,
|
|
15
|
+
* C_YUI_TREEDB_TOPIC_WITH_FORM, C_YUI_JSON) on a document double:
|
|
16
|
+
* the event is published by the real viewer the form created.
|
|
17
|
+
*
|
|
18
|
+
* Copyright (c) 2026, ArtGins.
|
|
19
|
+
* All Rights Reserved.
|
|
20
|
+
***********************************************************************/
|
|
21
|
+
import {describe, test, expect, beforeAll, beforeEach} from "vitest";
|
|
22
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
23
|
+
|
|
24
|
+
install_dom_double();
|
|
25
|
+
|
|
26
|
+
/* A phone: the viewer opens in the shell's sheet, not in a
|
|
27
|
+
* C_YUI_WINDOW, whose markup the document double cannot parse. */
|
|
28
|
+
globalThis.innerWidth = 400;
|
|
29
|
+
if(globalThis.window) {
|
|
30
|
+
globalThis.window.innerWidth = 400;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Tabulator watches its table; the document double has no observer. */
|
|
34
|
+
if(typeof globalThis.ResizeObserver === "undefined") {
|
|
35
|
+
globalThis.ResizeObserver = class {
|
|
36
|
+
observe() {}
|
|
37
|
+
unobserve() {}
|
|
38
|
+
disconnect() {}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
SDATA_END, gclass_create,
|
|
44
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
45
|
+
gobj_start, gobj_send_event, gobj_current_state,
|
|
46
|
+
gobj_create_pure_child, gobj_read_attr,
|
|
47
|
+
set_log_callback,
|
|
48
|
+
} = await import("@yuneta/gobj-js");
|
|
49
|
+
const {register_c_yui_shell} = await import("./c_yui_shell.js");
|
|
50
|
+
const {register_c_yui_json} = await import("./c_yui_json.js");
|
|
51
|
+
const {t} = await import("i18next");
|
|
52
|
+
const {register_c_yui_treedb_topic_with_form} = await import("./c_yui_treedb_topic_with_form.js");
|
|
53
|
+
|
|
54
|
+
const logged = [];
|
|
55
|
+
let yuno = null;
|
|
56
|
+
|
|
57
|
+
beforeAll(async () => {
|
|
58
|
+
/* The app's i18next, with no locale: a key answers itself. */
|
|
59
|
+
const i18next = (await import("i18next")).default;
|
|
60
|
+
await i18next.init({lng: "en", resources: {}});
|
|
61
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
62
|
+
set_log_callback((level, msg) => {
|
|
63
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
64
|
+
});
|
|
65
|
+
gclass_create("C_TEST_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
66
|
+
register_c_yui_shell();
|
|
67
|
+
register_c_yui_json();
|
|
68
|
+
register_c_yui_treedb_topic_with_form();
|
|
69
|
+
yuno = gobj_create_yuno("form_json_yuno", "C_TEST_HOST", {});
|
|
70
|
+
gobj_start(yuno);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
logged.length = 0;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const DESC = {
|
|
78
|
+
id: "users",
|
|
79
|
+
pkey: "id",
|
|
80
|
+
cols: {
|
|
81
|
+
id: {id: "id", header: "Id", type: "string", flag: ["persistent", "required"]},
|
|
82
|
+
name: {id: "name", header: "Name", type: "string", flag: ["persistent", "writable"]}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function errors()
|
|
87
|
+
{
|
|
88
|
+
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function warnings()
|
|
92
|
+
{
|
|
93
|
+
return logged.filter((l) => l.level === "warning").map((l) => l.msg);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
describe("a viewer of the form asks to load a truncated part", () => {
|
|
97
|
+
|
|
98
|
+
test("the form answers that it cannot, and the stub stops loading", () => {
|
|
99
|
+
const app = gobj_create("f1_app", "C_TEST_HOST", {}, yuno);
|
|
100
|
+
const shell = gobj_create("f1_shell", "C_YUI_SHELL",
|
|
101
|
+
{config: {shell: {}}, default_route: "/home", subscriber: app}, app);
|
|
102
|
+
gobj_start(shell);
|
|
103
|
+
const form = gobj_create_service("f1_form", "C_YUI_TREEDB_TOPIC_WITH_FORM", {
|
|
104
|
+
treedb_name: "treedb_test",
|
|
105
|
+
topic_name: "users",
|
|
106
|
+
desc: DESC
|
|
107
|
+
}, shell);
|
|
108
|
+
gobj_start(form);
|
|
109
|
+
|
|
110
|
+
logged.length = 0;
|
|
111
|
+
gobj_send_event(form, "EV_SHOW_SCHEMA", {}, form);
|
|
112
|
+
const viewer = form.priv.schema_gobj;
|
|
113
|
+
expect(viewer).toBeTruthy();
|
|
114
|
+
expect(gobj_current_state(viewer)).toBe("ST_READY");
|
|
115
|
+
logged.length = 0;
|
|
116
|
+
|
|
117
|
+
/* What the stub's click sends: the viewer then PUBLISHES
|
|
118
|
+
* EV_EXPAND_PATH to its host. */
|
|
119
|
+
gobj_send_event(viewer, "EV_EXPAND_COLLAPSED", {path: "cols`name", size: 40}, viewer);
|
|
120
|
+
|
|
121
|
+
expect(viewer.priv.pending.has("cols`name")).toBe(false);
|
|
122
|
+
/* The KEY travels, not its translation: the stub draws t(key)
|
|
123
|
+
* with data-i18n, so it changes language with the app. */
|
|
124
|
+
expect(viewer.priv.errors.get("cols`name")).toEqual(
|
|
125
|
+
{error: "this part cannot be loaded here", is_key: true});
|
|
126
|
+
/* A host saying it cannot, by design, is a WARNING: an ERROR
|
|
127
|
+
* on every click broke the clean-console check of a deploy and
|
|
128
|
+
* reached the backend through the remote log. */
|
|
129
|
+
expect(errors()).toEqual([]);
|
|
130
|
+
expect(warnings()).toEqual([
|
|
131
|
+
"C_YUI_JSON: subtree not loaded at 'cols`name': this part cannot be loaded here"
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("the stub of a subtree that was not loaded", () => {
|
|
138
|
+
|
|
139
|
+
/* A host answering with a KEY that it cannot, by design. */
|
|
140
|
+
function make_viewer(name, answer)
|
|
141
|
+
{
|
|
142
|
+
gclass_create(`C_TEST_JSON_HOST_${name}`, [["EV_EXPAND_PATH", 0]], [
|
|
143
|
+
["ST_IDLE", [["EV_EXPAND_PATH", function(gobj, event, kw, src) {
|
|
144
|
+
gobj_send_event(src, "EV_SUBTREE_ERROR",
|
|
145
|
+
Object.assign({path: kw.path}, answer), gobj);
|
|
146
|
+
return 0;
|
|
147
|
+
}, null]]]
|
|
148
|
+
], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
149
|
+
const host = gobj_create(`${name}_host`, `C_TEST_JSON_HOST_${name}`, {}, yuno);
|
|
150
|
+
gobj_start(host);
|
|
151
|
+
const viewer = gobj_create_pure_child(`${name}_jv`, "C_YUI_JSON", {
|
|
152
|
+
json_data: {a: {__collapsed__: {size: 3, path: "a"}}}
|
|
153
|
+
}, host);
|
|
154
|
+
gobj_start(viewer);
|
|
155
|
+
return viewer;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function stub_error(viewer)
|
|
159
|
+
{
|
|
160
|
+
return gobj_read_attr(viewer, "$container").querySelector(".JSON_STUB_ERR");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
test("a key is drawn translated, carries data-i18n, and follows the language", async () => {
|
|
164
|
+
const i18next = (await import("i18next")).default;
|
|
165
|
+
i18next.addResource("es", "translation",
|
|
166
|
+
"this part cannot be loaded here", "esta parte no se puede cargar aqui");
|
|
167
|
+
|
|
168
|
+
const viewer = make_viewer("s1",
|
|
169
|
+
{i18n: "this part cannot be loaded here", by_design: true});
|
|
170
|
+
logged.length = 0;
|
|
171
|
+
gobj_send_event(viewer, "EV_EXPAND_COLLAPSED", {path: "a", size: 3}, viewer);
|
|
172
|
+
|
|
173
|
+
let el = stub_error(viewer);
|
|
174
|
+
expect(el).toBeTruthy();
|
|
175
|
+
expect(el.getAttribute("data-i18n")).toBe("this part cannot be loaded here");
|
|
176
|
+
expect(el.textContent).toBe("this part cannot be loaded here");
|
|
177
|
+
expect(errors()).toEqual([]);
|
|
178
|
+
expect(warnings()).toEqual([
|
|
179
|
+
"C_YUI_JSON: subtree not loaded at 'a': this part cannot be loaded here"
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
await i18next.changeLanguage("es");
|
|
183
|
+
try {
|
|
184
|
+
gobj_send_event(viewer, "EV_LANGUAGE_CHANGED", {}, viewer);
|
|
185
|
+
expect(stub_error(viewer).textContent).toBe("esta parte no se puede cargar aqui");
|
|
186
|
+
expect(t("this part cannot be loaded here")).toBe("esta parte no se puede cargar aqui");
|
|
187
|
+
} finally {
|
|
188
|
+
await i18next.changeLanguage("en");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("a failure in free text is drawn as it came, and is an ERROR", () => {
|
|
193
|
+
const viewer = make_viewer("s2", {error: "print-tranger failed"});
|
|
194
|
+
logged.length = 0;
|
|
195
|
+
gobj_send_event(viewer, "EV_EXPAND_COLLAPSED", {path: "a", size: 3}, viewer);
|
|
196
|
+
|
|
197
|
+
let el = stub_error(viewer);
|
|
198
|
+
expect(el.getAttribute("data-i18n")).toBe(null);
|
|
199
|
+
expect(el.textContent).toBe("print-tranger failed");
|
|
200
|
+
expect(errors()).toEqual([
|
|
201
|
+
"C_YUI_JSON: subtree load failed at 'a': print-tranger failed"
|
|
202
|
+
]);
|
|
203
|
+
});
|
|
204
|
+
});
|