@yuneta/gobj-ui 5.14.2 → 5.16.0
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 +39 -0
- package/dist/gobj-ui.cjs.js +179 -17
- package/dist/gobj-ui.es.js +179 -17
- package/package.json +1 -1
- package/src/c_yui_treedb_graph.js +99 -1
- package/src/c_yui_treedb_topic_with_form.js +87 -7
- package/src/c_yui_treedb_topics.js +39 -5
- package/src/treedb_write_plan.js +73 -0
- package/src/treedb_write_plan.test.js +83 -0
package/README.md
CHANGED
|
@@ -430,6 +430,45 @@ Logical DOM classes: `JSON_VIEWER`, `JSON_TOOLBAR`, `JSON_SEARCH`, `JSON_TREE`,
|
|
|
430
430
|
`JSON_ROW`, `JSON_KEY`, `JSON_VALUE`, `JSON_SUMMARY`, `JSON_COLLAPSED`,
|
|
431
431
|
`JSON_TIME`. The gclass imports its own `c_yui_json.css`.
|
|
432
432
|
|
|
433
|
+
### Read-only treedbs: `readonly`
|
|
434
|
+
|
|
435
|
+
`C_YUI_TREEDB_TOPICS` and `C_YUI_TREEDB_GRAPH` take a **`readonly`** attr; the
|
|
436
|
+
topics view propagates it to every topic it builds. It is not one more button
|
|
437
|
+
flag: it is the STATE of the treedb and it beats each `with_*` flag at once,
|
|
438
|
+
because a treedb whose tranger the yuno does not master answers **every** write
|
|
439
|
+
with
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
ERROR -1: <yuno>: treedb '<name>' is READ-ONLY, this yuno is not the master of its tranger
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
(the yuno refuses since SDK 7.13.0), so offering the buttons anyway turns a
|
|
446
|
+
fact into an error message per click. Ask the yuno which it is with
|
|
447
|
+
`command-yuno id=<yuno> service=<treedb> command=treedb-info`, which answers
|
|
448
|
+
`{treedb_name, master, schema_version, topics}` — and remember the flag is per
|
|
449
|
+
TREEDB and is runtime state: a yuno is routinely the master of its
|
|
450
|
+
`treedb_system_schema` and a replica of a data treedb it shares.
|
|
451
|
+
|
|
452
|
+
What `readonly` takes away: the edition mode, the *new* / *delete* / *paste*
|
|
453
|
+
buttons, the in-row edit icons, and the write half of the record form's toolbar
|
|
454
|
+
(`copy` stays — reading a record includes taking it with you) with the cells not
|
|
455
|
+
editable. The record form still OPENS: looking is the point of a replica.
|
|
456
|
+
|
|
457
|
+
In the **graph** it takes away the `edition` operation mode, which is the only
|
|
458
|
+
one that draws the create / delete / link affordances — the mode select stops
|
|
459
|
+
offering it, and a graph left in edition on a master comes back in `reading` on
|
|
460
|
+
a replica (the mode is a persisted preference). The other modes are untouched:
|
|
461
|
+
panning, zooming and opening a node are reading.
|
|
462
|
+
|
|
463
|
+
Two implementation notes worth keeping:
|
|
464
|
+
|
|
465
|
+
- the decision lives in **`treedb_write_plan.js`** (pure, tested), not in five
|
|
466
|
+
`!readonly && with_x` expressions — five places to forget the sixth;
|
|
467
|
+
- and the write **events** are refused as well, in every gclass, with a
|
|
468
|
+
`log_error`. Hiding a button is not the same as refusing a write: an event can
|
|
469
|
+
still arrive from a keyboard path or a form that outlived the flag, and an
|
|
470
|
+
ignored write is exactly the behaviour this whole change exists to stop.
|
|
471
|
+
|
|
433
472
|
### C_YUI_TREEDB_SCHEMA — the treedb as a graph of topics (prototype)
|
|
434
473
|
|
|
435
474
|
A landing view that draws a treedb as a **graph of topics** — one node per
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -14404,6 +14404,7 @@ var attrs_table$5 = [
|
|
|
14404
14404
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system topics (true) or user topics (false)"),
|
|
14405
14405
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "tabs_style", 0, "is-toggle is-fullwidth", "Bulma tab styling"),
|
|
14406
14406
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_cards_landing", 0, false, "Land on a grid of topic cards (list->detail): a card opens its table, with the tabs bar + a back-to-grid button. Off = tabs only (legacy)."),
|
|
14407
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: every topic opens without its write affordances and the write events are refused. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
|
|
14407
14408
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "card_action_routes", 0, null, "Per-card hash-route templates {info, table, graph} with a {topic} placeholder (host-supplied, route-agnostic). Present ⇒ cards show 3 icon actions; absent ⇒ a single card that opens the table."),
|
|
14408
14409
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "landing_routes", 0, null, "Host-supplied hashes for the two landing sub-views {cards, schema}; the toggle navigates to them so the landing is URL-addressable (ROUTING.md). Absent ⇒ toggle flips in-view only (legacy)."),
|
|
14409
14410
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its sub-routes (topics / info / schema) to the site map (ROUTING.md contributor)."),
|
|
@@ -15184,12 +15185,14 @@ function process_treedb_descs$1(gobj) {
|
|
|
15184
15185
|
let descs = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "descs");
|
|
15185
15186
|
let system = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "system");
|
|
15186
15187
|
let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
|
|
15188
|
+
let readonly = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly");
|
|
15187
15189
|
for (const [key, desc] of Object.entries(descs)) {
|
|
15188
15190
|
if (system) {} else if (key.substring(0, 2) === "__") continue;
|
|
15189
15191
|
let kw_topic_form = {
|
|
15190
15192
|
subscriber: gobj,
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
+
readonly,
|
|
15194
|
+
with_new_button: !readonly,
|
|
15195
|
+
with_delete_button: !readonly,
|
|
15193
15196
|
treedb_name,
|
|
15194
15197
|
topic_name: key,
|
|
15195
15198
|
desc
|
|
@@ -15494,6 +15497,19 @@ function request_print_tranger$1(gobj, path) {
|
|
|
15494
15497
|
}, gobj);
|
|
15495
15498
|
if (ret) (0, _yuneta_gobj_js.log_error)(ret);
|
|
15496
15499
|
}
|
|
15500
|
+
/***************************************************************
|
|
15501
|
+
* The last gate before a write leaves for the yuno. The per-topic
|
|
15502
|
+
* children already hide their write buttons when the treedb is
|
|
15503
|
+
* read-only, but the record events converge HERE, and this is the
|
|
15504
|
+
* gclass that owns the treedb: a write that got this far on a replica
|
|
15505
|
+
* would travel, be refused by the yuno, and come back as a toast --
|
|
15506
|
+
* after the table had already drawn the row.
|
|
15507
|
+
***************************************************************/
|
|
15508
|
+
function refuse_if_readonly$2(gobj, event) {
|
|
15509
|
+
if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) return false;
|
|
15510
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, treedb '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")}' is READ-ONLY`);
|
|
15511
|
+
return true;
|
|
15512
|
+
}
|
|
15497
15513
|
/***************************
|
|
15498
15514
|
* Actions
|
|
15499
15515
|
***************************/
|
|
@@ -15561,7 +15577,8 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
|
|
|
15561
15577
|
treedb_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "treedb_name", "", 0),
|
|
15562
15578
|
topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
|
|
15563
15579
|
record: (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0),
|
|
15564
|
-
created: command === "create-node"
|
|
15580
|
+
created: command === "create-node",
|
|
15581
|
+
command
|
|
15565
15582
|
});
|
|
15566
15583
|
break;
|
|
15567
15584
|
case "delete-node": break;
|
|
@@ -15766,6 +15783,7 @@ function ac_treedb_node_deleted$1(gobj, event, kw, src) {
|
|
|
15766
15783
|
* }
|
|
15767
15784
|
********************************************/
|
|
15768
15785
|
function ac_create_record(gobj, event, kw, src) {
|
|
15786
|
+
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
15769
15787
|
let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
|
|
15770
15788
|
let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
|
|
15771
15789
|
let record = kw.record;
|
|
@@ -15783,6 +15801,7 @@ function ac_create_record(gobj, event, kw, src) {
|
|
|
15783
15801
|
* }
|
|
15784
15802
|
********************************************/
|
|
15785
15803
|
function ac_update_record(gobj, event, kw, src) {
|
|
15804
|
+
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
15786
15805
|
let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
|
|
15787
15806
|
let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
|
|
15788
15807
|
let record = kw.record;
|
|
@@ -15799,6 +15818,7 @@ function ac_update_record(gobj, event, kw, src) {
|
|
|
15799
15818
|
* }
|
|
15800
15819
|
********************************************/
|
|
15801
15820
|
function ac_delete_record(gobj, event, kw, src) {
|
|
15821
|
+
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
15802
15822
|
let treedb_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name");
|
|
15803
15823
|
let topic_name = (0, _yuneta_gobj_js.gobj_read_attr)(src, "topic_name");
|
|
15804
15824
|
let record = kw.record;
|
|
@@ -16086,6 +16106,68 @@ function yui_tabulator_relocalize(table, t) {
|
|
|
16086
16106
|
}
|
|
16087
16107
|
}
|
|
16088
16108
|
//#endregion
|
|
16109
|
+
//#region src/treedb_write_plan.js
|
|
16110
|
+
/***********************************************************************
|
|
16111
|
+
* treedb_write_plan.js
|
|
16112
|
+
*
|
|
16113
|
+
* Which write affordances a treedb topic offers.
|
|
16114
|
+
*
|
|
16115
|
+
* Pure, so it can be tested without a DOM: the gclass turns the
|
|
16116
|
+
* plan into buttons and columns, this decides the plan. Same split
|
|
16117
|
+
* as form_toolbar_plan.js.
|
|
16118
|
+
*
|
|
16119
|
+
* The rule it exists to state ONCE: `readonly` is not one more
|
|
16120
|
+
* button flag, it is the STATE of the topic and it wins over all of
|
|
16121
|
+
* them. A treedb whose tranger this yuno does not master answers
|
|
16122
|
+
* every write with "READ-ONLY" (the yuno refuses since SDK 7.13.0),
|
|
16123
|
+
* so offering the buttons anyway only turns a fact into an error
|
|
16124
|
+
* message per click. Written as `!readonly && with_x` in five
|
|
16125
|
+
* places it is five places to forget the sixth.
|
|
16126
|
+
*
|
|
16127
|
+
* Copyright (c) 2026, ArtGins.
|
|
16128
|
+
* All Rights Reserved.
|
|
16129
|
+
***********************************************************************/
|
|
16130
|
+
var READONLY_FORM_TOOLBAR = ["copy"];
|
|
16131
|
+
/***************************************************************
|
|
16132
|
+
* plan_treedb_writes(flags) -> plan
|
|
16133
|
+
*
|
|
16134
|
+
* flags {readonly, with_edition_mode, with_new_button,
|
|
16135
|
+
* with_delete_button, with_paste_button,
|
|
16136
|
+
* with_in_row_edit_icons}
|
|
16137
|
+
* Every with_* defaults to TRUE when absent, which is
|
|
16138
|
+
* what the gclass attrs default to: a caller that sets
|
|
16139
|
+
* nothing gets the toolbar this view always had.
|
|
16140
|
+
*
|
|
16141
|
+
* plan the same names without the `with_` prefix, plus
|
|
16142
|
+
* `form_toolbar`: the list to hand C_YUI_FORM, or null
|
|
16143
|
+
* to leave the form's own default alone.
|
|
16144
|
+
***************************************************************/
|
|
16145
|
+
function plan_treedb_writes(flags) {
|
|
16146
|
+
let f = flags || {};
|
|
16147
|
+
let readonly = !!f.readonly;
|
|
16148
|
+
let want = (name) => {
|
|
16149
|
+
return f[name] === void 0 ? true : !!f[name];
|
|
16150
|
+
};
|
|
16151
|
+
if (readonly) return {
|
|
16152
|
+
readonly: true,
|
|
16153
|
+
edition_mode: false,
|
|
16154
|
+
new_button: false,
|
|
16155
|
+
delete_button: false,
|
|
16156
|
+
paste_button: false,
|
|
16157
|
+
in_row_icons: false,
|
|
16158
|
+
form_toolbar: READONLY_FORM_TOOLBAR.slice()
|
|
16159
|
+
};
|
|
16160
|
+
return {
|
|
16161
|
+
readonly: false,
|
|
16162
|
+
edition_mode: want("with_edition_mode"),
|
|
16163
|
+
new_button: want("with_new_button"),
|
|
16164
|
+
delete_button: want("with_delete_button"),
|
|
16165
|
+
paste_button: want("with_paste_button"),
|
|
16166
|
+
in_row_icons: want("with_in_row_edit_icons"),
|
|
16167
|
+
form_toolbar: null
|
|
16168
|
+
};
|
|
16169
|
+
}
|
|
16170
|
+
//#endregion
|
|
16089
16171
|
//#region src/c_yui_treedb_topic_with_form.js
|
|
16090
16172
|
/***********************************************************************
|
|
16091
16173
|
* c_yui_treedb_topic_with_form.js
|
|
@@ -16107,6 +16189,7 @@ var attrs_table$4 = [
|
|
|
16107
16189
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
16108
16190
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "topic_name", 0, null, "Topic name"),
|
|
16109
16191
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "desc", 0, null, "Description of topics"),
|
|
16192
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The topic cannot be written: no edition mode, no new/delete/paste, no in-row icons, and the record form opens without its save toolbar. Set it when the treedb is not the master of its tranger (only the master can write; the yuno refuses otherwise), or when the user lacks the authz"),
|
|
16110
16193
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_edition_mode", 0, true, "Enable EDITION mode showing EDIT Button toolbar"),
|
|
16111
16194
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_new_button", 0, true, "Button toolbar NEW"),
|
|
16112
16195
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_delete_button", 0, true, "Button toolbar DELETE"),
|
|
@@ -16269,11 +16352,12 @@ async function readClipboard(gobj) {
|
|
|
16269
16352
|
function build_ui$4(gobj) {
|
|
16270
16353
|
function create_table_toolbar() {
|
|
16271
16354
|
let $table_toolbar = [];
|
|
16272
|
-
let
|
|
16273
|
-
let
|
|
16274
|
-
let
|
|
16355
|
+
let plan = write_plan(gobj);
|
|
16356
|
+
let with_edition_mode = plan.edition_mode;
|
|
16357
|
+
let with_new_button = plan.new_button;
|
|
16358
|
+
let with_delete_button = plan.delete_button;
|
|
16275
16359
|
let with_copy_button = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_copy_button");
|
|
16276
|
-
let with_paste_button =
|
|
16360
|
+
let with_paste_button = plan.paste_button;
|
|
16277
16361
|
let toolbar_id = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "toolbar_id");
|
|
16278
16362
|
if (with_edition_mode) {
|
|
16279
16363
|
$table_toolbar = (0, _yuneta_gobj_js.createElement2)(["div", {
|
|
@@ -16716,7 +16800,7 @@ function create_tabulator(gobj) {
|
|
|
16716
16800
|
"</button>"
|
|
16717
16801
|
].join("");
|
|
16718
16802
|
}
|
|
16719
|
-
if ((
|
|
16803
|
+
if (write_plan(gobj).in_row_icons) columns.push({
|
|
16720
16804
|
field: "_operation",
|
|
16721
16805
|
title: "Op",
|
|
16722
16806
|
hozAlign: "center",
|
|
@@ -16995,15 +17079,17 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
16995
17079
|
},
|
|
16996
17080
|
[]
|
|
16997
17081
|
]);
|
|
17082
|
+
let form_plan = write_plan(gobj);
|
|
16998
17083
|
let form = (0, _yuneta_gobj_js.gobj_create_pure_child)("form_" + (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj)), "C_YUI_FORM", {
|
|
16999
17084
|
template: build_form_template(gobj),
|
|
17000
17085
|
record,
|
|
17001
17086
|
fkey_options: build_fkey_options(gobj),
|
|
17002
17087
|
form_mode: mode,
|
|
17088
|
+
...form_plan.form_toolbar ? { toolbar: form_plan.form_toolbar } : {},
|
|
17003
17089
|
render_mode: "edit",
|
|
17004
17090
|
pkey: desc.pkey || "id",
|
|
17005
17091
|
topic_name,
|
|
17006
|
-
editable:
|
|
17092
|
+
editable: !form_plan.readonly,
|
|
17007
17093
|
$parent: $body
|
|
17008
17094
|
}, gobj);
|
|
17009
17095
|
priv.form = form;
|
|
@@ -17530,6 +17616,34 @@ function show_dropdown_popup_menu(gobj, x, y, items, callback) {
|
|
|
17530
17616
|
let $with_focus = $element.querySelector(".with-focus");
|
|
17531
17617
|
if ($with_focus) $with_focus.focus();
|
|
17532
17618
|
}
|
|
17619
|
+
/***************************************************************
|
|
17620
|
+
* A read-only topic refuses the write EVENTS too, not only their
|
|
17621
|
+
* buttons. The buttons are gone, but an event can still arrive: a
|
|
17622
|
+
* keyboard path, a hosted form that outlived the flag, a caller that
|
|
17623
|
+
* sends EV_SAVE_RECORD itself. Refusing here is what makes `readonly`
|
|
17624
|
+
* a state of the gclass rather than a way of drawing it -- and it
|
|
17625
|
+
* fails LOUDLY, because an ignored write is how the backend used to
|
|
17626
|
+
* behave and what this whole change exists to stop.
|
|
17627
|
+
***************************************************************/
|
|
17628
|
+
function write_plan(gobj) {
|
|
17629
|
+
return plan_treedb_writes({
|
|
17630
|
+
readonly: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly"),
|
|
17631
|
+
with_edition_mode: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_edition_mode"),
|
|
17632
|
+
with_new_button: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_new_button"),
|
|
17633
|
+
with_delete_button: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_delete_button"),
|
|
17634
|
+
with_paste_button: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_paste_button"),
|
|
17635
|
+
with_in_row_edit_icons: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_in_row_edit_icons")
|
|
17636
|
+
});
|
|
17637
|
+
}
|
|
17638
|
+
/***************************************************************
|
|
17639
|
+
* A read-only topic refuses the write EVENTS too, not only their
|
|
17640
|
+
* buttons.
|
|
17641
|
+
***************************************************************/
|
|
17642
|
+
function refuse_if_readonly$1(gobj, event) {
|
|
17643
|
+
if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) return false;
|
|
17644
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, topic '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name")}' is READ-ONLY`);
|
|
17645
|
+
return true;
|
|
17646
|
+
}
|
|
17533
17647
|
/***************************
|
|
17534
17648
|
* Actions
|
|
17535
17649
|
***************************/
|
|
@@ -17597,7 +17711,10 @@ function ac_load_node_updated(gobj, event, kw, src) {
|
|
|
17597
17711
|
return -1;
|
|
17598
17712
|
}
|
|
17599
17713
|
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
17600
|
-
if (tabulator)
|
|
17714
|
+
if (tabulator) {
|
|
17715
|
+
for (let record of data) if (tabulator.getRow(record.id)) tabulator.updateData([record]);
|
|
17716
|
+
else if (tabulator.getDataCount() > 0) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: updated node is not in the loaded table: ${record.id}`);
|
|
17717
|
+
}
|
|
17601
17718
|
return 0;
|
|
17602
17719
|
}
|
|
17603
17720
|
/************************************************************
|
|
@@ -17622,6 +17739,7 @@ function ac_node_deleted$1(gobj, event, kw, src) {
|
|
|
17622
17739
|
* and the Op column is visible (with row's edit/delete icons)
|
|
17623
17740
|
************************************************************/
|
|
17624
17741
|
function ac_edition_mode(gobj, event, kw, src) {
|
|
17742
|
+
if (refuse_if_readonly$1(gobj, event)) return -1;
|
|
17625
17743
|
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
17626
17744
|
let editable = (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "editable");
|
|
17627
17745
|
editable = !editable;
|
|
@@ -17665,6 +17783,7 @@ function ac_edition_mode(gobj, event, kw, src) {
|
|
|
17665
17783
|
* - the backend will broadcast the new record that will be added to table
|
|
17666
17784
|
************************************************************/
|
|
17667
17785
|
function ac_new_row(gobj, event, kw, src) {
|
|
17786
|
+
if (refuse_if_readonly$1(gobj, event)) return -1;
|
|
17668
17787
|
let row = {};
|
|
17669
17788
|
let desc = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc");
|
|
17670
17789
|
for (let i = 0; i < desc.cols.length; i++) {
|
|
@@ -17694,6 +17813,7 @@ function ac_new_row(gobj, event, kw, src) {
|
|
|
17694
17813
|
* kw {index:, row:}
|
|
17695
17814
|
************************************************************/
|
|
17696
17815
|
function ac_delete_rows(gobj, event, kw, src) {
|
|
17816
|
+
if (refuse_if_readonly$1(gobj, event)) return -1;
|
|
17697
17817
|
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
17698
17818
|
if ((0, _yuneta_gobj_js.json_size)(kw) === 0) {
|
|
17699
17819
|
let rows = tabulator.getSelectedData();
|
|
@@ -17757,6 +17877,7 @@ function ac_copy_rows(gobj, event, kw, src) {
|
|
|
17757
17877
|
* From internal
|
|
17758
17878
|
************************************************************/
|
|
17759
17879
|
function ac_paste_rows(gobj, event, kw, src) {
|
|
17880
|
+
if (refuse_if_readonly$1(gobj, event)) return -1;
|
|
17760
17881
|
if (kw.topic_name === (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name") && kw.treedb_name === (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")) for (let row of kw.rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_CREATE_RECORD", {
|
|
17761
17882
|
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
17762
17883
|
record: row
|
|
@@ -17769,6 +17890,7 @@ function ac_paste_rows(gobj, event, kw, src) {
|
|
|
17769
17890
|
* fkeys, times and numbers). Route by the form's mode.
|
|
17770
17891
|
************************************************************/
|
|
17771
17892
|
function ac_form_save_record(gobj, event, kw, src) {
|
|
17893
|
+
if (refuse_if_readonly$1(gobj, event)) return -1;
|
|
17772
17894
|
let mode = (0, _yuneta_gobj_js.gobj_read_str_attr)(src, "form_mode");
|
|
17773
17895
|
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, mode === "create" ? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD", {
|
|
17774
17896
|
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
@@ -18184,6 +18306,7 @@ var attrs_table$3 = [
|
|
|
18184
18306
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "treedb_name", 0, null, "Remote service treedb name"),
|
|
18185
18307
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "descs", 0, null, "Descriptions of topics obtained"),
|
|
18186
18308
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "system", 0, false, "Manage system topics (true) or user topics (false)"),
|
|
18309
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "readonly", 0, false, "The whole treedb is read-only: the graph drops its `edition` mode (the only one that draws write affordances) and refuses every write event. Set it when this yuno is not the MASTER of the treedb's tranger -- only the master can write, and the yuno answers 'READ-ONLY' to a write since SDK 7.13.0 (ask `treedb-info`)"),
|
|
18187
18310
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "back_route", 0, "", "Optional hash route for a '← topics' button back to the topics grid (host-supplied; empty = no button)"),
|
|
18188
18311
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "base_route", 0, "", "This view's base route (host-supplied); used to declare its per-topic focus sub-routes to the site map (ROUTING.md contributor)."),
|
|
18189
18312
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "records", 0, "{}", "Data of topics"),
|
|
@@ -18234,6 +18357,7 @@ function mt_create$3(gobj) {
|
|
|
18234
18357
|
if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
|
|
18235
18358
|
(0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
|
|
18236
18359
|
if (!priv.treedb_name) (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_name)(gobj)} -> treedb_name not configured`);
|
|
18360
|
+
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly") && priv.operation_mode === "edition") (0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "operation_mode", "reading");
|
|
18237
18361
|
let canvas_id = (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj)) + "-canvas";
|
|
18238
18362
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "canvas_id", canvas_id);
|
|
18239
18363
|
let $container_canvas = build_ui$3(gobj).querySelector(`#${priv.canvas_id}`);
|
|
@@ -18388,6 +18512,8 @@ function destroy_ui(gobj) {
|
|
|
18388
18512
|
************************************************************/
|
|
18389
18513
|
function make_toolbar(gobj) {
|
|
18390
18514
|
gobj.priv;
|
|
18515
|
+
let modes = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "operation_modes");
|
|
18516
|
+
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) modes = modes.filter((mode) => mode !== "edition");
|
|
18391
18517
|
const $toolbar_header = yui_toolbar({}, [
|
|
18392
18518
|
[
|
|
18393
18519
|
"div",
|
|
@@ -18426,7 +18552,7 @@ function make_toolbar(gobj) {
|
|
|
18426
18552
|
[[
|
|
18427
18553
|
"select",
|
|
18428
18554
|
{ class: "GRAPH_MODE_SELECT" },
|
|
18429
|
-
|
|
18555
|
+
modes.map((item) => [
|
|
18430
18556
|
"option",
|
|
18431
18557
|
{},
|
|
18432
18558
|
item
|
|
@@ -18978,6 +19104,19 @@ function refresh_data(gobj) {
|
|
|
18978
19104
|
if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_CLEAR_DATA", {}, gobj);
|
|
18979
19105
|
request_treedb_descs(gobj);
|
|
18980
19106
|
}
|
|
19107
|
+
/********************************************
|
|
19108
|
+
* The last gate before a write leaves for the yuno. The `edition`
|
|
19109
|
+
* mode is gone on a replica, so no button can raise these -- but the
|
|
19110
|
+
* G6 child also publishes them from its undo/redo history and from
|
|
19111
|
+
* saving the node geometry, and a write that got this far would
|
|
19112
|
+
* travel, be refused by the yuno, and come back as a toast with the
|
|
19113
|
+
* graph already redrawn.
|
|
19114
|
+
********************************************/
|
|
19115
|
+
function refuse_if_readonly(gobj, event) {
|
|
19116
|
+
if (!(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) return false;
|
|
19117
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} refused, treedb '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")}' is READ-ONLY`);
|
|
19118
|
+
return true;
|
|
19119
|
+
}
|
|
18981
19120
|
/***************************
|
|
18982
19121
|
* Actions
|
|
18983
19122
|
***************************/
|
|
@@ -19046,7 +19185,15 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
19046
19185
|
case "update-node":
|
|
19047
19186
|
case "delete-node":
|
|
19048
19187
|
case "link-nodes":
|
|
19049
|
-
case "unlink-nodes":
|
|
19188
|
+
case "unlink-nodes":
|
|
19189
|
+
if (result >= 0 && (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0) !== "__graphs__") (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_RECORD_WRITTEN", {
|
|
19190
|
+
treedb_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "treedb_name", "", 0),
|
|
19191
|
+
topic_name: (0, _yuneta_gobj_js.kw_get_str)(gobj, kw_command, "topic_name", "", 0),
|
|
19192
|
+
record: (0, _yuneta_gobj_js.kw_get_dict)(gobj, kw_command, "record", {}, 0),
|
|
19193
|
+
created: command === "create-node",
|
|
19194
|
+
command
|
|
19195
|
+
});
|
|
19196
|
+
break;
|
|
19050
19197
|
default: (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)} Command unknown: ${command}`);
|
|
19051
19198
|
}
|
|
19052
19199
|
return 0;
|
|
@@ -19265,7 +19412,9 @@ function ac_edge_clicked(gobj, event, kw, src) {
|
|
|
19265
19412
|
* Send to backend
|
|
19266
19413
|
********************************************/
|
|
19267
19414
|
function ac_create_node(gobj, event, kw, src) {
|
|
19268
|
-
let
|
|
19415
|
+
let priv = gobj.priv;
|
|
19416
|
+
if (refuse_if_readonly(gobj, event)) return -1;
|
|
19417
|
+
let treedb_name = priv.treedb_name;
|
|
19269
19418
|
let topic_name = kw.topic_name;
|
|
19270
19419
|
let record = kw.record;
|
|
19271
19420
|
return treedb_create_node(gobj, treedb_name, topic_name, record, kw.options || {});
|
|
@@ -19280,7 +19429,9 @@ function ac_create_node(gobj, event, kw, src) {
|
|
|
19280
19429
|
* Send to backend
|
|
19281
19430
|
********************************************/
|
|
19282
19431
|
function ac_update_node(gobj, event, kw, src) {
|
|
19283
|
-
let
|
|
19432
|
+
let priv = gobj.priv;
|
|
19433
|
+
if (refuse_if_readonly(gobj, event)) return -1;
|
|
19434
|
+
let treedb_name = priv.treedb_name;
|
|
19284
19435
|
let topic_name = kw.topic_name;
|
|
19285
19436
|
let record = kw.record;
|
|
19286
19437
|
return treedb_update_node(gobj, treedb_name, topic_name, record, kw.options || {});
|
|
@@ -19295,7 +19446,9 @@ function ac_update_node(gobj, event, kw, src) {
|
|
|
19295
19446
|
* Send to backend
|
|
19296
19447
|
********************************************/
|
|
19297
19448
|
function ac_delete_node(gobj, event, kw, src) {
|
|
19298
|
-
let
|
|
19449
|
+
let priv = gobj.priv;
|
|
19450
|
+
if (refuse_if_readonly(gobj, event)) return -1;
|
|
19451
|
+
let treedb_name = priv.treedb_name;
|
|
19299
19452
|
let topic_name = kw.topic_name;
|
|
19300
19453
|
let record = kw.record;
|
|
19301
19454
|
return treedb_delete_node(gobj, treedb_name, topic_name, record, kw.options || {});
|
|
@@ -19310,7 +19463,9 @@ function ac_delete_node(gobj, event, kw, src) {
|
|
|
19310
19463
|
* Send to backend
|
|
19311
19464
|
********************************************/
|
|
19312
19465
|
function ac_link_nodes(gobj, event, kw, src) {
|
|
19313
|
-
let
|
|
19466
|
+
let priv = gobj.priv;
|
|
19467
|
+
if (refuse_if_readonly(gobj, event)) return -1;
|
|
19468
|
+
let treedb_name = priv.treedb_name;
|
|
19314
19469
|
let parent_ref = kw.parent_ref;
|
|
19315
19470
|
let child_ref = kw.child_ref;
|
|
19316
19471
|
return treedb_link_nodes(gobj, treedb_name, parent_ref, child_ref, kw.options || {});
|
|
@@ -19325,7 +19480,9 @@ function ac_link_nodes(gobj, event, kw, src) {
|
|
|
19325
19480
|
* Send to backend
|
|
19326
19481
|
********************************************/
|
|
19327
19482
|
function ac_unlink_nodes(gobj, event, kw, src) {
|
|
19328
|
-
let
|
|
19483
|
+
let priv = gobj.priv;
|
|
19484
|
+
if (refuse_if_readonly(gobj, event)) return -1;
|
|
19485
|
+
let treedb_name = priv.treedb_name;
|
|
19329
19486
|
let parent_ref = kw.parent_ref;
|
|
19330
19487
|
let child_ref = kw.child_ref;
|
|
19331
19488
|
return treedb_unlink_nodes(gobj, treedb_name, parent_ref, child_ref, kw.options || {});
|
|
@@ -19365,6 +19522,10 @@ function ac_set_layout$1(gobj, event, kw, src) {
|
|
|
19365
19522
|
function ac_set_operation_mode$1(gobj, event, kw, src) {
|
|
19366
19523
|
let priv = gobj.priv;
|
|
19367
19524
|
let operation_mode = kw.operation_mode;
|
|
19525
|
+
if (operation_mode === "edition" && (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly")) {
|
|
19526
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: edition mode refused, treedb '${(0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "treedb_name")}' is READ-ONLY`);
|
|
19527
|
+
return -1;
|
|
19528
|
+
}
|
|
19368
19529
|
(0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "operation_mode", operation_mode);
|
|
19369
19530
|
(0, _yuneta_gobj_js.gobj_save_persistent_attrs)(gobj, "operation_mode");
|
|
19370
19531
|
let $operation_mode_select = priv.$container.querySelector(".GRAPH_MODE_SELECT");
|
|
@@ -19586,6 +19747,7 @@ function create_gclass$3(gclass_name) {
|
|
|
19586
19747
|
["EV_SET_OPERATION_MODE", 0],
|
|
19587
19748
|
["EV_SET_FOCUS_TOPIC", 0],
|
|
19588
19749
|
["EV_OPERATION_MODE_CHANGED", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
19750
|
+
["EV_RECORD_WRITTEN", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT | _yuneta_gobj_js.event_flag_t.EVF_NO_WARN_SUBS],
|
|
19589
19751
|
["EV_SHOW", 0],
|
|
19590
19752
|
["EV_HIDE", 0],
|
|
19591
19753
|
["EV_TRANSPORT_STATE", 0]
|