@yuneta/gobj-ui 6.1.0 → 6.1.2
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 +20 -7
- package/dist/gobj-ui.cjs.js +248 -84
- package/dist/gobj-ui.es.js +248 -84
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +124 -36
- package/src/c_yui_treedb_schema.js +17 -7
- package/src/schema_descs.js +14 -6
- package/src/schema_descs.test.js +25 -0
- package/src/schema_import.js +17 -6
- package/src/schema_import.test.js +21 -0
- package/src/schema_model.js +26 -0
- package/src/schema_model.test.js +25 -0
- package/src/schema_to_c.js +22 -8
- package/src/schema_to_c.test.js +47 -0
- package/src/schema_write_options.js +60 -0
- package/src/schema_write_options.test.js +65 -0
- package/src/yui_icons.css +14 -0
package/dist/gobj-ui.es.js
CHANGED
|
@@ -19818,6 +19818,7 @@ var attrs_table$3 = [
|
|
|
19818
19818
|
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
19819
19819
|
SDATA(data_type_t.DTP_JSON, "descs", 0, null, "Treedb schema: {topic_name: desc}"),
|
|
19820
19820
|
SDATA(data_type_t.DTP_STRING, "node_route", 0, "", "Hash-route template with a {topic} placeholder: a node click opens that topic (e.g. '#/topics/db/<sel>/{topic}')"),
|
|
19821
|
+
SDATA(data_type_t.DTP_BOOLEAN, "with_node_click", 0, false, "With no `node_route`, PUBLISH a node click as EV_NODE_CLICK instead of dropping it. Opt-in, because a subscriber has to DECLARE the event: the hosts that route by hash, and the ones that want nothing, must not have it appear underneath them"),
|
|
19821
19822
|
SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Include system topics (__*__) too"),
|
|
19822
19823
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTML element"),
|
|
19823
19824
|
SDATA_END()
|
|
@@ -20222,19 +20223,26 @@ function destroy_graph(gobj) {
|
|
|
20222
20223
|
* With a `node_route` the click IS a navigation and this view
|
|
20223
20224
|
* makes it: that is what the route was handed down for.
|
|
20224
20225
|
*
|
|
20225
|
-
* Without one it is still an action, and it
|
|
20226
|
-
* mounted this view — the schema editor draws the same
|
|
20227
|
-
* inside its own screens, where a topic opens in place
|
|
20228
|
-
* hash is involved.
|
|
20229
|
-
*
|
|
20230
|
-
*
|
|
20226
|
+
* Without one it is still an action, and it can belong to
|
|
20227
|
+
* whoever mounted this view — the schema editor draws the same
|
|
20228
|
+
* picture inside its own screens, where a topic opens in place
|
|
20229
|
+
* and no hash is involved. That host asks for it with
|
|
20230
|
+
* `with_node_click` and DECLARES EV_NODE_CLICK in its own FSM,
|
|
20231
|
+
* as with every event a child publishes.
|
|
20232
|
+
*
|
|
20233
|
+
* It is opt-in and not the default for exactly that reason: the
|
|
20234
|
+
* CHILD subscription model subscribes the host to ALL of this
|
|
20235
|
+
* view's events, so publishing one unasked turns a click into
|
|
20236
|
+
* "Event NOT DEFINED in state" in every host that never wanted
|
|
20237
|
+
* it — the topics view that mounts this as its schema landing,
|
|
20238
|
+
* and the offline demo that mounts it against a json file.
|
|
20231
20239
|
************************************************************/
|
|
20232
20240
|
function ac_node_click$2(gobj, event, kw, src) {
|
|
20233
20241
|
let topic = kw && kw.node_id;
|
|
20234
20242
|
let route = gobj_read_str_attr(gobj, "node_route");
|
|
20235
20243
|
if (!topic) return 0;
|
|
20236
20244
|
if (!route) {
|
|
20237
|
-
gobj_publish_event(gobj, "EV_NODE_CLICK", { topic });
|
|
20245
|
+
if (gobj_read_bool_attr(gobj, "with_node_click")) gobj_publish_event(gobj, "EV_NODE_CLICK", { topic });
|
|
20238
20246
|
return 0;
|
|
20239
20247
|
}
|
|
20240
20248
|
let href = route.replace("{topic}", topic);
|
|
@@ -20574,6 +20582,22 @@ function build_schema_model(records) {
|
|
|
20574
20582
|
};
|
|
20575
20583
|
}
|
|
20576
20584
|
/***************************************************************
|
|
20585
|
+
* Is there a value here at all?
|
|
20586
|
+
*
|
|
20587
|
+
* The store answers a `blob` column that was never set with an
|
|
20588
|
+
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
20589
|
+
* `default: {}`. Read as "a value", those turn every export into
|
|
20590
|
+
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
20591
|
+
* is a hook with no mapping, which is a link the treedb builds
|
|
20592
|
+
* and nothing writes.
|
|
20593
|
+
***************************************************************/
|
|
20594
|
+
function is_empty_value(value) {
|
|
20595
|
+
if (value === null || value === void 0 || value === "") return true;
|
|
20596
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
20597
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
20598
|
+
return false;
|
|
20599
|
+
}
|
|
20600
|
+
/***************************************************************
|
|
20577
20601
|
* The fields of a column that are DECLARED as one thing and
|
|
20578
20602
|
* STORED as another.
|
|
20579
20603
|
*
|
|
@@ -20911,6 +20935,7 @@ var COL_STORAGE_FIELDS = [
|
|
|
20911
20935
|
"value",
|
|
20912
20936
|
"topics",
|
|
20913
20937
|
"order",
|
|
20938
|
+
"_geometry",
|
|
20914
20939
|
"__md_treedb__"
|
|
20915
20940
|
];
|
|
20916
20941
|
var TOPIC_STORAGE_FIELDS = [
|
|
@@ -20919,6 +20944,7 @@ var TOPIC_STORAGE_FIELDS = [
|
|
|
20919
20944
|
"treedbs",
|
|
20920
20945
|
"cols",
|
|
20921
20946
|
"order",
|
|
20947
|
+
"_geometry",
|
|
20922
20948
|
"__md_treedb__"
|
|
20923
20949
|
];
|
|
20924
20950
|
var COL_JSON_FIELDS = [
|
|
@@ -20942,13 +20968,16 @@ function col_desc(col) {
|
|
|
20942
20968
|
let desc = {};
|
|
20943
20969
|
for (let [key, value] of Object.entries(record)) {
|
|
20944
20970
|
if (COL_STORAGE_FIELDS.indexOf(key) >= 0) continue;
|
|
20945
|
-
if (value
|
|
20946
|
-
|
|
20971
|
+
if (is_empty_value(value)) continue;
|
|
20972
|
+
let read = COL_JSON_FIELDS.indexOf(key) >= 0 ? as_json(value) : value;
|
|
20973
|
+
if (is_empty_value(read)) continue;
|
|
20974
|
+
desc[key] = read;
|
|
20947
20975
|
}
|
|
20948
20976
|
desc.id = col.name;
|
|
20949
20977
|
desc.flag = col_flags(record);
|
|
20950
20978
|
let hook = col_hook(record);
|
|
20951
|
-
if (hook) desc.hook = hook;
|
|
20979
|
+
if (hook && !is_empty_value(hook)) desc.hook = hook;
|
|
20980
|
+
else delete desc.hook;
|
|
20952
20981
|
let e = col_enum(record);
|
|
20953
20982
|
if (e.length > 0) desc.enum = e;
|
|
20954
20983
|
return desc;
|
|
@@ -20966,7 +20995,7 @@ function topic_descs(treedb) {
|
|
|
20966
20995
|
let desc = {};
|
|
20967
20996
|
for (let [key, value] of Object.entries(record)) {
|
|
20968
20997
|
if (TOPIC_STORAGE_FIELDS.indexOf(key) >= 0) continue;
|
|
20969
|
-
if (value
|
|
20998
|
+
if (is_empty_value(value)) continue;
|
|
20970
20999
|
desc[key] = value;
|
|
20971
21000
|
}
|
|
20972
21001
|
desc.topic_name = topic.name;
|
|
@@ -21023,6 +21052,7 @@ var COL_SKIP = [
|
|
|
21023
21052
|
"value",
|
|
21024
21053
|
"topics",
|
|
21025
21054
|
"order",
|
|
21055
|
+
"_geometry",
|
|
21026
21056
|
"__md_treedb__"
|
|
21027
21057
|
];
|
|
21028
21058
|
var TOPIC_SKIP = [
|
|
@@ -21031,6 +21061,7 @@ var TOPIC_SKIP = [
|
|
|
21031
21061
|
"treedbs",
|
|
21032
21062
|
"cols",
|
|
21033
21063
|
"order",
|
|
21064
|
+
"_geometry",
|
|
21034
21065
|
"__md_treedb__"
|
|
21035
21066
|
];
|
|
21036
21067
|
var JSON_FIELDS$1 = [
|
|
@@ -21084,8 +21115,10 @@ function schema_to_json(treedb) {
|
|
|
21084
21115
|
let jn_topic = { id: topic.name };
|
|
21085
21116
|
for (let [key, value] of Object.entries(record)) {
|
|
21086
21117
|
if (TOPIC_SKIP.indexOf(key) >= 0) continue;
|
|
21087
|
-
if (value
|
|
21088
|
-
|
|
21118
|
+
if (is_empty_value(value)) continue;
|
|
21119
|
+
let read = JSON_FIELDS$1.indexOf(key) >= 0 ? as_json(value) : value;
|
|
21120
|
+
if (is_empty_value(read)) continue;
|
|
21121
|
+
jn_topic[key] = read;
|
|
21089
21122
|
}
|
|
21090
21123
|
if (jn_topic.topic_version !== void 0) jn_topic.topic_version = version(jn_topic.topic_version);
|
|
21091
21124
|
let pkey2s = topic_pkey2s(record);
|
|
@@ -21098,14 +21131,17 @@ function schema_to_json(treedb) {
|
|
|
21098
21131
|
let jn_col = {};
|
|
21099
21132
|
for (let [key, value] of Object.entries(col_record)) {
|
|
21100
21133
|
if (COL_SKIP.indexOf(key) >= 0) continue;
|
|
21101
|
-
if (value
|
|
21102
|
-
|
|
21134
|
+
if (is_empty_value(value)) continue;
|
|
21135
|
+
let read = JSON_FIELDS$1.indexOf(key) >= 0 ? as_json(value) : value;
|
|
21136
|
+
if (is_empty_value(read)) continue;
|
|
21137
|
+
jn_col[key] = read;
|
|
21103
21138
|
}
|
|
21104
21139
|
let flags = col_flags(col_record);
|
|
21105
21140
|
if (flags.length > 0) jn_col.flag = flags;
|
|
21106
21141
|
else delete jn_col.flag;
|
|
21107
21142
|
let hook = col_hook(col_record);
|
|
21108
|
-
if (hook) jn_col.hook = hook;
|
|
21143
|
+
if (hook && !is_empty_value(hook)) jn_col.hook = hook;
|
|
21144
|
+
else delete jn_col.hook;
|
|
21109
21145
|
let e = col_enum(col_record);
|
|
21110
21146
|
if (e.length > 0) jn_col.enum = e;
|
|
21111
21147
|
let ordered_col = {};
|
|
@@ -21290,7 +21326,7 @@ function same_value(field, a, b) {
|
|
|
21290
21326
|
list = list.filter((f) => typeof f === "string" && f.length > 0).sort();
|
|
21291
21327
|
return JSON.stringify(list);
|
|
21292
21328
|
}
|
|
21293
|
-
if (v
|
|
21329
|
+
if (is_empty_value(v)) return "";
|
|
21294
21330
|
if (typeof v === "object") return JSON.stringify(v);
|
|
21295
21331
|
return String(v);
|
|
21296
21332
|
};
|
|
@@ -21303,8 +21339,10 @@ function incoming_col_fields(col) {
|
|
|
21303
21339
|
let out = {};
|
|
21304
21340
|
for (let field of COL_FIELDS) {
|
|
21305
21341
|
let value = col ? col[field] : void 0;
|
|
21306
|
-
if (value
|
|
21307
|
-
|
|
21342
|
+
if (is_empty_value(value)) continue;
|
|
21343
|
+
let read = JSON_FIELDS.indexOf(field) >= 0 ? as_json(value) : value;
|
|
21344
|
+
if (is_empty_value(read)) continue;
|
|
21345
|
+
out[field] = read;
|
|
21308
21346
|
}
|
|
21309
21347
|
if (Array.isArray(out.flag)) out.flag = out.flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
21310
21348
|
else if (typeof out.flag === "string" && out.flag) out.flag = [out.flag];
|
|
@@ -21317,14 +21355,17 @@ function stored_col_fields(record) {
|
|
|
21317
21355
|
let out = {};
|
|
21318
21356
|
for (let field of COL_FIELDS) {
|
|
21319
21357
|
let value = record ? record[field] : void 0;
|
|
21320
|
-
if (value
|
|
21321
|
-
|
|
21358
|
+
if (is_empty_value(value)) continue;
|
|
21359
|
+
let read = JSON_FIELDS.indexOf(field) >= 0 ? as_json(value) : value;
|
|
21360
|
+
if (is_empty_value(read)) continue;
|
|
21361
|
+
out[field] = read;
|
|
21322
21362
|
}
|
|
21323
21363
|
let flags = col_flags(record);
|
|
21324
21364
|
if (flags.length > 0) out.flag = flags;
|
|
21325
21365
|
else delete out.flag;
|
|
21326
21366
|
let hook = col_hook(record);
|
|
21327
|
-
if (hook) out.hook = hook;
|
|
21367
|
+
if (hook && !is_empty_value(hook)) out.hook = hook;
|
|
21368
|
+
else delete out.hook;
|
|
21328
21369
|
let e = col_enum(record);
|
|
21329
21370
|
if (e.length > 0) out.enum = e;
|
|
21330
21371
|
return out;
|
|
@@ -21949,6 +21990,61 @@ function toggle_flag(current, name, on) {
|
|
|
21949
21990
|
return out;
|
|
21950
21991
|
}
|
|
21951
21992
|
//#endregion
|
|
21993
|
+
//#region src/schema_write_options.js
|
|
21994
|
+
/***********************************************************************
|
|
21995
|
+
* schema_write_options.js
|
|
21996
|
+
*
|
|
21997
|
+
* The OPTIONS a schema write travels with, and why each one.
|
|
21998
|
+
*
|
|
21999
|
+
* Three words decide whether a write does what it says, and all
|
|
22000
|
+
* three are easy to get wrong in a way that succeeds:
|
|
22001
|
+
*
|
|
22002
|
+
* `create` a node that is not there yet is written by
|
|
22003
|
+
* `update-node` with this, not by `create-node`. The
|
|
22004
|
+
* two are not the same call: only the update path
|
|
22005
|
+
* carries `autolink`, and without a link a new column
|
|
22006
|
+
* belongs to no topic.
|
|
22007
|
+
*
|
|
22008
|
+
* `autolink` turns the fkey carried IN THE RECORD into a link.
|
|
22009
|
+
* It rewrites the node's links from the fkey fields
|
|
22010
|
+
* the record has — so on a PARTIAL update, which
|
|
22011
|
+
* carries none, it reads that as "no parents" and
|
|
22012
|
+
* UNLINKS the node. Raising a topic's version with it
|
|
22013
|
+
* on detached that topic from its treedb, and the
|
|
22014
|
+
* write returned success. It goes with a create,
|
|
22015
|
+
* where the record carries its fkey, and with nothing
|
|
22016
|
+
* else.
|
|
22017
|
+
*
|
|
22018
|
+
* `force` a delete of a node something points at. A column is
|
|
22019
|
+
* pointed at by the topic that owns it, which is not
|
|
22020
|
+
* a reason to refuse.
|
|
22021
|
+
*
|
|
22022
|
+
* Pure and tested, because the cost of getting it wrong is a
|
|
22023
|
+
* store that has to be repaired by hand, and nothing in the
|
|
22024
|
+
* answer says it happened.
|
|
22025
|
+
*
|
|
22026
|
+
* Copyright (c) 2026, ArtGins.
|
|
22027
|
+
* All Rights Reserved.
|
|
22028
|
+
***********************************************************************/
|
|
22029
|
+
/***************************************************************
|
|
22030
|
+
* write_command(op) -> the treedb command that performs it
|
|
22031
|
+
***************************************************************/
|
|
22032
|
+
function write_command(op) {
|
|
22033
|
+
return op === "delete" ? "delete-node" : "update-node";
|
|
22034
|
+
}
|
|
22035
|
+
/***************************************************************
|
|
22036
|
+
* write_options(op) -> the options it travels with
|
|
22037
|
+
***************************************************************/
|
|
22038
|
+
function write_options(op) {
|
|
22039
|
+
if (op === "delete") return { force: true };
|
|
22040
|
+
if (op === "create") return {
|
|
22041
|
+
list_dict: true,
|
|
22042
|
+
create: true,
|
|
22043
|
+
autolink: true
|
|
22044
|
+
};
|
|
22045
|
+
return { list_dict: true };
|
|
22046
|
+
}
|
|
22047
|
+
//#endregion
|
|
21952
22048
|
//#region src/c_yui_schema_editor.js
|
|
21953
22049
|
/***********************************************************************
|
|
21954
22050
|
* c_yui_schema_editor.js
|
|
@@ -22268,10 +22364,12 @@ function remote_command(gobj, command, kw) {
|
|
|
22268
22364
|
service: treedb_name,
|
|
22269
22365
|
treedb_name
|
|
22270
22366
|
}, kw || {});
|
|
22271
|
-
full_kw.__md_command__ =
|
|
22367
|
+
full_kw.__md_command__ = {
|
|
22272
22368
|
purpose: PURPOSE,
|
|
22273
|
-
|
|
22274
|
-
|
|
22369
|
+
topic_name: kw && kw.topic_name || "",
|
|
22370
|
+
record_id: kw && kw.record_id || ""
|
|
22371
|
+
};
|
|
22372
|
+
delete full_kw.record_id;
|
|
22275
22373
|
let ret = gobj_command(remote, command, full_kw, gobj);
|
|
22276
22374
|
if (ret) {
|
|
22277
22375
|
log_error(ret);
|
|
@@ -22520,9 +22618,9 @@ function render_toolbar(gobj) {
|
|
|
22520
22618
|
let $right = [];
|
|
22521
22619
|
if (priv.treedb_id && has_model) {
|
|
22522
22620
|
$right.push(toolbar_button("SCHEMA_DIAGRAM_BTN", "yi-hexagon-nodes", priv.diagram ? "topics" : "diagram", "EV_TOGGLE_DIAGRAM", false));
|
|
22523
|
-
$right.push(toolbar_button("SCHEMA_VALIDATE_BTN", "yi-
|
|
22524
|
-
$right.push(toolbar_button("SCHEMA_EXPORT_BTN", "yi-
|
|
22525
|
-
if (!readonly) $right.push(toolbar_button("SCHEMA_IMPORT_BTN", "yi-
|
|
22621
|
+
$right.push(toolbar_button("SCHEMA_VALIDATE_BTN", "yi-square-check", "check", "EV_VALIDATE", false));
|
|
22622
|
+
$right.push(toolbar_button("SCHEMA_EXPORT_BTN", "yi-download", "export", "EV_EXPORT", false));
|
|
22623
|
+
if (!readonly) $right.push(toolbar_button("SCHEMA_IMPORT_BTN", "yi-upload", "import", "EV_IMPORT", false));
|
|
22526
22624
|
}
|
|
22527
22625
|
if (priv.topic_name && !readonly && !priv.diagram) $right.push(toolbar_button("SCHEMA_ADD_COL_BTN", "yi-plus", "new column", "EV_ADD_COLUMN", false));
|
|
22528
22626
|
if (priv.treedb_id && !priv.topic_name && !readonly && !priv.diagram) $right.push(toolbar_button("SCHEMA_ADD_TOPIC_BTN", "yi-plus", "new topic", "EV_ADD_TOPIC", false));
|
|
@@ -22725,13 +22823,16 @@ function render_topics(gobj, $body) {
|
|
|
22725
22823
|
return;
|
|
22726
22824
|
}
|
|
22727
22825
|
let readonly = is_readonly(gobj);
|
|
22826
|
+
let twice = repeated_names(treedb.topics);
|
|
22728
22827
|
let $rows = [];
|
|
22729
22828
|
for (let topic of treedb.topics) {
|
|
22730
22829
|
let pending = priv.written[topic.id] && String(priv.baseline[topic.id]) === String(topic.topic_version);
|
|
22830
|
+
let ambiguous = !!twice[topic.name];
|
|
22831
|
+
let address = ambiguous ? topic.id : topic.name;
|
|
22731
22832
|
let $actions = [];
|
|
22732
22833
|
if (!readonly) {
|
|
22733
|
-
$actions.push(row_icon(gobj, "SCHEMA_TOPIC_EDIT", "yi-pen", "edit topic", "EV_EDIT_TOPIC", { topic:
|
|
22734
|
-
$actions.push(row_icon(gobj, "SCHEMA_TOPIC_DELETE", "yi-trash", "delete topic", "EV_DELETE_TOPIC", { topic:
|
|
22834
|
+
$actions.push(row_icon(gobj, "SCHEMA_TOPIC_EDIT", "yi-pen", "edit topic", "EV_EDIT_TOPIC", { topic: address }));
|
|
22835
|
+
$actions.push(row_icon(gobj, "SCHEMA_TOPIC_DELETE", "yi-trash", "delete topic", "EV_DELETE_TOPIC", { topic: address }));
|
|
22735
22836
|
}
|
|
22736
22837
|
$rows.push([
|
|
22737
22838
|
"tr",
|
|
@@ -22744,22 +22845,38 @@ function render_topics(gobj, $body) {
|
|
|
22744
22845
|
[
|
|
22745
22846
|
"td",
|
|
22746
22847
|
{ class: "SCHEMA_TOPIC_NAME" },
|
|
22747
|
-
[
|
|
22748
|
-
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
|
|
22752
|
-
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22848
|
+
[
|
|
22849
|
+
[
|
|
22850
|
+
"span",
|
|
22851
|
+
{ class: "has-text-weight-semibold" },
|
|
22852
|
+
`${topic.name}`
|
|
22853
|
+
],
|
|
22854
|
+
topic.system_topic ? [
|
|
22855
|
+
"span",
|
|
22856
|
+
{
|
|
22857
|
+
class: "SCHEMA_TOPIC_SYSTEM tag is-light ml-2",
|
|
22858
|
+
i18n: "system"
|
|
22859
|
+
},
|
|
22860
|
+
t("system")
|
|
22861
|
+
] : [
|
|
22862
|
+
"span",
|
|
22863
|
+
{},
|
|
22864
|
+
""
|
|
22865
|
+
],
|
|
22866
|
+
ambiguous ? [
|
|
22867
|
+
"div",
|
|
22868
|
+
{ class: "SCHEMA_TOPIC_ID is-size-7 has-text-grey" },
|
|
22869
|
+
[[
|
|
22870
|
+
"code",
|
|
22871
|
+
{},
|
|
22872
|
+
`${topic.id}`
|
|
22873
|
+
]]
|
|
22874
|
+
] : [
|
|
22875
|
+
"span",
|
|
22876
|
+
{},
|
|
22877
|
+
""
|
|
22878
|
+
]
|
|
22879
|
+
]
|
|
22763
22880
|
],
|
|
22764
22881
|
[
|
|
22765
22882
|
"td",
|
|
@@ -22793,12 +22910,12 @@ function render_topics(gobj, $body) {
|
|
|
22793
22910
|
{
|
|
22794
22911
|
click: (evt) => {
|
|
22795
22912
|
if (evt.target.closest(".SCHEMA_TOPIC_ACTIONS")) return;
|
|
22796
|
-
gobj_send_event(gobj, "EV_SELECT_TOPIC", { topic:
|
|
22913
|
+
gobj_send_event(gobj, "EV_SELECT_TOPIC", { topic: address }, gobj);
|
|
22797
22914
|
},
|
|
22798
22915
|
keydown: (evt) => {
|
|
22799
22916
|
if (evt.key !== "Enter" && evt.key !== " ") return;
|
|
22800
22917
|
evt.preventDefault();
|
|
22801
|
-
gobj_send_event(gobj, "EV_SELECT_TOPIC", { topic:
|
|
22918
|
+
gobj_send_event(gobj, "EV_SELECT_TOPIC", { topic: address }, gobj);
|
|
22802
22919
|
}
|
|
22803
22920
|
}
|
|
22804
22921
|
]);
|
|
@@ -22887,6 +23004,26 @@ function row_icon(gobj, logical, icon, key, event, kw) {
|
|
|
22887
23004
|
];
|
|
22888
23005
|
}
|
|
22889
23006
|
/***************************************************************
|
|
23007
|
+
* Which names appear more than once.
|
|
23008
|
+
*
|
|
23009
|
+
* A store can hold TWO GENERATIONS of the same schema: the
|
|
23010
|
+
* projector never deletes, so a treedb re-keyed by a newer SDK
|
|
23011
|
+
* keeps its old rowid-keyed rows next to the qualified ones, and
|
|
23012
|
+
* both are children of the same treedb with the same name. Drawn
|
|
23013
|
+
* as two identical rows that behave differently, that reads as a
|
|
23014
|
+
* bug in the editor; drawn with what tells them apart, it reads
|
|
23015
|
+
* as what it is.
|
|
23016
|
+
***************************************************************/
|
|
23017
|
+
function repeated_names(list) {
|
|
23018
|
+
let seen = {};
|
|
23019
|
+
let twice = {};
|
|
23020
|
+
for (let entry of list) {
|
|
23021
|
+
if (seen[entry.name]) twice[entry.name] = true;
|
|
23022
|
+
seen[entry.name] = true;
|
|
23023
|
+
}
|
|
23024
|
+
return twice;
|
|
23025
|
+
}
|
|
23026
|
+
/***************************************************************
|
|
22890
23027
|
* ST_COLUMNS — the columns of one topic, in schema order.
|
|
22891
23028
|
*
|
|
22892
23029
|
* The order is a FIELD (`order`), so the rows are draggable and
|
|
@@ -22912,8 +23049,9 @@ function render_columns(gobj, $body) {
|
|
|
22912
23049
|
]);
|
|
22913
23050
|
let $banner = version_banner(gobj, topic);
|
|
22914
23051
|
if ($banner) $wrap.appendChild($banner);
|
|
23052
|
+
let twice = repeated_names(topic.cols);
|
|
22915
23053
|
let $rows = [];
|
|
22916
|
-
for (let i = 0; i < topic.cols.length; i++) $rows.push(column_row(gobj, topic, topic.cols[i], i, readonly));
|
|
23054
|
+
for (let i = 0; i < topic.cols.length; i++) $rows.push(column_row(gobj, topic, topic.cols[i], i, readonly, twice));
|
|
22917
23055
|
let $table = createElement2([
|
|
22918
23056
|
"div",
|
|
22919
23057
|
{
|
|
@@ -23034,7 +23172,7 @@ function version_banner(gobj, topic) {
|
|
|
23034
23172
|
[[
|
|
23035
23173
|
"span",
|
|
23036
23174
|
{ class: "icon" },
|
|
23037
|
-
[["i", { class: "yi-
|
|
23175
|
+
[["i", { class: "yi-chevron-up" }]]
|
|
23038
23176
|
], [
|
|
23039
23177
|
"span",
|
|
23040
23178
|
{ i18n: "raise" },
|
|
@@ -23055,8 +23193,10 @@ function version_banner(gobj, topic) {
|
|
|
23055
23193
|
* Yuneta event is plain JSON, so a DOM node never travels in
|
|
23056
23194
|
* one.
|
|
23057
23195
|
***************************************************************/
|
|
23058
|
-
function column_row(gobj, topic, col, index, readonly) {
|
|
23196
|
+
function column_row(gobj, topic, col, index, readonly, twice) {
|
|
23059
23197
|
let record = col.record || {};
|
|
23198
|
+
let ambiguous = !!(twice && twice[col.name]);
|
|
23199
|
+
let address = ambiguous ? col.id : col.name;
|
|
23060
23200
|
let flags = col_flags(record);
|
|
23061
23201
|
let hook = col_hook(record);
|
|
23062
23202
|
let is_pkey = col.name === (topic.pkey || "id");
|
|
@@ -23068,13 +23208,13 @@ function column_row(gobj, topic, col, index, readonly) {
|
|
|
23068
23208
|
];
|
|
23069
23209
|
});
|
|
23070
23210
|
let link = "";
|
|
23071
|
-
if (hook) link = "→ " + Object.keys(hook).join(", ");
|
|
23211
|
+
if (hook && !is_empty_value(hook)) link = "→ " + Object.keys(hook).join(", ");
|
|
23072
23212
|
else if (flags.indexOf("fkey") >= 0) link = "↖";
|
|
23073
23213
|
let $actions = [];
|
|
23074
23214
|
if (!readonly) {
|
|
23075
|
-
$actions.push(row_icon(gobj, "SCHEMA_COL_EDIT", "yi-pen", "edit column", "EV_EDIT_COLUMN", { col:
|
|
23076
|
-
$actions.push(row_icon(gobj, "SCHEMA_COL_DUPLICATE", "yi-copy", "duplicate column", "EV_DUPLICATE_COLUMN", { col:
|
|
23077
|
-
$actions.push(row_icon(gobj, "SCHEMA_COL_DELETE", "yi-trash", "delete column", "EV_DELETE_COLUMN", { col:
|
|
23215
|
+
$actions.push(row_icon(gobj, "SCHEMA_COL_EDIT", "yi-pen", "edit column", "EV_EDIT_COLUMN", { col: address }));
|
|
23216
|
+
$actions.push(row_icon(gobj, "SCHEMA_COL_DUPLICATE", "yi-copy", "duplicate column", "EV_DUPLICATE_COLUMN", { col: address }));
|
|
23217
|
+
$actions.push(row_icon(gobj, "SCHEMA_COL_DELETE", "yi-trash", "delete column", "EV_DELETE_COLUMN", { col: address }));
|
|
23078
23218
|
}
|
|
23079
23219
|
let $row = createElement2([
|
|
23080
23220
|
"tr",
|
|
@@ -23096,22 +23236,38 @@ function column_row(gobj, topic, col, index, readonly) {
|
|
|
23096
23236
|
[
|
|
23097
23237
|
"td",
|
|
23098
23238
|
{ class: "SCHEMA_COL_NAME" },
|
|
23099
|
-
[
|
|
23100
|
-
|
|
23101
|
-
|
|
23102
|
-
|
|
23103
|
-
|
|
23104
|
-
|
|
23105
|
-
|
|
23106
|
-
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23111
|
-
|
|
23112
|
-
|
|
23113
|
-
|
|
23114
|
-
|
|
23239
|
+
[
|
|
23240
|
+
[
|
|
23241
|
+
"code",
|
|
23242
|
+
{},
|
|
23243
|
+
`${col.name}`
|
|
23244
|
+
],
|
|
23245
|
+
ambiguous ? [
|
|
23246
|
+
"span",
|
|
23247
|
+
{ class: "SCHEMA_COL_ID is-size-7 has-text-grey ml-2" },
|
|
23248
|
+
[[
|
|
23249
|
+
"code",
|
|
23250
|
+
{},
|
|
23251
|
+
`${col.id}`
|
|
23252
|
+
]]
|
|
23253
|
+
] : [
|
|
23254
|
+
"span",
|
|
23255
|
+
{},
|
|
23256
|
+
""
|
|
23257
|
+
],
|
|
23258
|
+
is_pkey ? [
|
|
23259
|
+
"span",
|
|
23260
|
+
{
|
|
23261
|
+
class: "SCHEMA_COL_PKEY tag is-info is-light ml-2",
|
|
23262
|
+
i18n: "pkey"
|
|
23263
|
+
},
|
|
23264
|
+
t("pkey")
|
|
23265
|
+
] : [
|
|
23266
|
+
"span",
|
|
23267
|
+
{},
|
|
23268
|
+
""
|
|
23269
|
+
]
|
|
23270
|
+
]
|
|
23115
23271
|
],
|
|
23116
23272
|
[
|
|
23117
23273
|
"td",
|
|
@@ -23154,7 +23310,7 @@ function column_row(gobj, topic, col, index, readonly) {
|
|
|
23154
23310
|
if (!readonly) wire_row_drag(gobj, $row);
|
|
23155
23311
|
$row.addEventListener("dblclick", (evt) => {
|
|
23156
23312
|
if (readonly || evt.target.closest(".SCHEMA_COL_ACTIONS")) return;
|
|
23157
|
-
gobj_send_event(gobj, "EV_EDIT_COLUMN", { col:
|
|
23313
|
+
gobj_send_event(gobj, "EV_EDIT_COLUMN", { col: address }, gobj);
|
|
23158
23314
|
});
|
|
23159
23315
|
return $row;
|
|
23160
23316
|
}
|
|
@@ -23254,6 +23410,7 @@ function render_diagram(gobj) {
|
|
|
23254
23410
|
subscriber: gobj,
|
|
23255
23411
|
descs,
|
|
23256
23412
|
node_route: "",
|
|
23413
|
+
with_node_click: true,
|
|
23257
23414
|
system: true
|
|
23258
23415
|
}, gobj);
|
|
23259
23416
|
if (!diagram) {
|
|
@@ -23491,7 +23648,8 @@ function open_column_form(gobj, topic, col, prefill) {
|
|
|
23491
23648
|
let record = prefill || col && col.record || {};
|
|
23492
23649
|
let creating = !col;
|
|
23493
23650
|
let type = record.type || "string";
|
|
23494
|
-
let hook = col_hook(record)
|
|
23651
|
+
let hook = col_hook(record);
|
|
23652
|
+
if (is_empty_value(hook)) hook = {};
|
|
23495
23653
|
let hook_topic = Object.keys(hook)[0] || "";
|
|
23496
23654
|
let hook_col = hook_topic ? hook[hook_topic] : "";
|
|
23497
23655
|
let treedb = current_treedb(gobj);
|
|
@@ -23641,7 +23799,7 @@ function open_topic_form(gobj, treedb, topic) {
|
|
|
23641
23799
|
[
|
|
23642
23800
|
field("SCHEMA_TOPIC_FORM_NAME", "value", "topic", text_input("value", creating ? "" : topic.name, t("topic name"), !creating), creating ? "" : "a topic is keyed by its whole path: renaming it is creating another one"),
|
|
23643
23801
|
field("SCHEMA_TOPIC_FORM_PKEY", "pkey", "pkey", creating ? text_input("pkey", "id", "id") : select_input("pkey", record.pkey || "id", col_names.length > 0 ? col_names : [record.pkey || "id"])),
|
|
23644
|
-
field("SCHEMA_TOPIC_FORM_PKEY2S", "pkey2s", "secondary keys", text_input("pkey2s", stringify_field(record.pkey2s), ""), "the column a record is
|
|
23802
|
+
field("SCHEMA_TOPIC_FORM_PKEY2S", "pkey2s", "secondary keys", text_input("pkey2s", stringify_field(record.pkey2s), ""), "the column a record is known by when its key is composed"),
|
|
23645
23803
|
field("SCHEMA_TOPIC_FORM_SYSTEM_FLAG", "system_flag", "system flag", text_input("system_flag", record.system_flag || "sf_string_key", "")),
|
|
23646
23804
|
field("SCHEMA_TOPIC_FORM_TKEY", "tkey", "time key", text_input("tkey", record.tkey, "")),
|
|
23647
23805
|
field("SCHEMA_TOPIC_FORM_VERSION", "topic_version", "version", number_input("topic_version", record.topic_version === void 0 ? 1 : record.topic_version), "raising it is what publishes a change of the columns"),
|
|
@@ -24056,7 +24214,7 @@ function open_import(gobj, treedb) {
|
|
|
24056
24214
|
[[
|
|
24057
24215
|
"span",
|
|
24058
24216
|
{ class: "icon" },
|
|
24059
|
-
[["i", { class: "yi-
|
|
24217
|
+
[["i", { class: "yi-upload" }]]
|
|
24060
24218
|
], [
|
|
24061
24219
|
"span",
|
|
24062
24220
|
{ i18n: "import" },
|
|
@@ -24321,10 +24479,12 @@ function run_next_write(gobj) {
|
|
|
24321
24479
|
let priv = gobj.priv;
|
|
24322
24480
|
if (priv.save_queue.length === 0) return end_writes(gobj, "");
|
|
24323
24481
|
let write = priv.save_queue[0];
|
|
24324
|
-
|
|
24482
|
+
let command = write_command(write.op);
|
|
24483
|
+
let options = write_options(write.op);
|
|
24484
|
+
if (remote_command(gobj, command, {
|
|
24325
24485
|
topic_name: write.topic_name,
|
|
24326
24486
|
record: write.record,
|
|
24327
|
-
options
|
|
24487
|
+
options,
|
|
24328
24488
|
record_id: write.record.id || ""
|
|
24329
24489
|
}) < 0) return end_writes(gobj, t("cannot reach the treedb"));
|
|
24330
24490
|
return 0;
|
|
@@ -24379,7 +24539,8 @@ function column_record(gobj, topic, col, values) {
|
|
|
24379
24539
|
record.header = values.header || "";
|
|
24380
24540
|
record.type = values.type || "string";
|
|
24381
24541
|
record.flag = flags;
|
|
24382
|
-
|
|
24542
|
+
if (String(values.fillspace).trim() !== "") record.fillspace = Number(values.fillspace);
|
|
24543
|
+
else if (!creating) record.fillspace = "";
|
|
24383
24544
|
record.placeholder = values.placeholder || "";
|
|
24384
24545
|
record.description = values.description || "";
|
|
24385
24546
|
record.default = values.default || "";
|
|
@@ -24477,7 +24638,7 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
24477
24638
|
let __command__ = msg_iev_get_stack(gobj, kw, "command_stack", true);
|
|
24478
24639
|
let command = kw_get_str(gobj, __command__, "command", "", 0);
|
|
24479
24640
|
let kw_command = kw_get_dict(gobj, __command__, "kw", {}, 0);
|
|
24480
|
-
let md =
|
|
24641
|
+
let md = is_object(kw_command.__md_command__) ? kw_command.__md_command__ : kw_command;
|
|
24481
24642
|
if (kw_get_str(gobj, md, "purpose", "", 0) !== PURPOSE) return 0;
|
|
24482
24643
|
if (command === "nodes") {
|
|
24483
24644
|
let topic_name = kw_get_str(gobj, md, "topic_name", "", 0);
|
|
@@ -24494,11 +24655,11 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
24494
24655
|
build_model(gobj);
|
|
24495
24656
|
return go(gobj, priv.treedb_id, priv.topic_name, priv.diagram, false);
|
|
24496
24657
|
}
|
|
24497
|
-
if (command === "
|
|
24498
|
-
let topic_name = kw_get_str(gobj,
|
|
24658
|
+
if (command === "update-node" || command === "delete-node") {
|
|
24659
|
+
let topic_name = kw_get_str(gobj, md, "topic_name", "", 0);
|
|
24499
24660
|
if (result < 0) return end_writes(gobj, comment || t("the treedb refused the write"));
|
|
24500
24661
|
priv.save_wrote = true;
|
|
24501
|
-
if (command === "delete-node") forget_record(gobj, topic_name, kw_get_str(gobj,
|
|
24662
|
+
if (command === "delete-node") forget_record(gobj, topic_name, kw_get_str(gobj, md, "record_id", "", 0));
|
|
24502
24663
|
else {
|
|
24503
24664
|
let record = is_object(data) ? data : Array.isArray(data) && is_object(data[0]) ? data[0] : null;
|
|
24504
24665
|
if (record) patch_record(gobj, topic_name, record);
|
|
@@ -24509,7 +24670,10 @@ function ac_mt_command_answer(gobj, event, kw, src) {
|
|
|
24509
24670
|
return 0;
|
|
24510
24671
|
}
|
|
24511
24672
|
}
|
|
24512
|
-
if (topic_name === T_TOPICS || topic_name === T_COLS)
|
|
24673
|
+
if (topic_name === T_TOPICS || topic_name === T_COLS) {
|
|
24674
|
+
let write = priv.save_queue[0];
|
|
24675
|
+
mark_written(gobj, topic_name, write ? write.record : null);
|
|
24676
|
+
}
|
|
24513
24677
|
priv.save_queue.shift();
|
|
24514
24678
|
return run_next_write(gobj);
|
|
24515
24679
|
}
|
|
@@ -24902,7 +25066,7 @@ function ac_preview_import(gobj, event, kw, src) {
|
|
|
24902
25066
|
writes: [],
|
|
24903
25067
|
summary: {},
|
|
24904
25068
|
conflicts: [{
|
|
24905
|
-
code: "that is not
|
|
25069
|
+
code: "that is not json",
|
|
24906
25070
|
topic: "",
|
|
24907
25071
|
col: ""
|
|
24908
25072
|
}]
|