@yuneta/gobj-ui 7.23.168 → 7.23.170
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 +2 -2
- package/dist/gobj-ui.cjs.js +784 -478
- package/dist/gobj-ui.es.js +784 -478
- package/package.json +2 -2
- package/src/broadcast_attrs.test.js +115 -0
- package/src/c_g6_nodes_tree.js +208 -19
- package/src/c_yui_treedb_topic_with_form.js +206 -107
- package/src/treedb_write_plan.js +47 -1
- package/src/treedb_write_plan.test.js +53 -1
- package/src/yui_row_search.js +56 -27
- package/src/yui_row_search.test.js +51 -1
package/dist/gobj-ui.es.js
CHANGED
|
@@ -25558,49 +25558,65 @@ function yui_icon_is_defined(name) {
|
|
|
25558
25558
|
/***********************************************************************
|
|
25559
25559
|
* yui_row_search.js
|
|
25560
25560
|
*
|
|
25561
|
-
*
|
|
25562
|
-
*
|
|
25563
|
-
*
|
|
25564
|
-
*
|
|
25565
|
-
*
|
|
25566
|
-
* `[
|
|
25567
|
-
*
|
|
25568
|
-
*
|
|
25569
|
-
*
|
|
25570
|
-
*
|
|
25571
|
-
*
|
|
25572
|
-
*
|
|
25573
|
-
*
|
|
25574
|
-
*
|
|
25575
|
-
*
|
|
25576
|
-
*
|
|
25577
|
-
*
|
|
25578
|
-
*
|
|
25579
|
-
*
|
|
25580
|
-
*
|
|
25581
|
-
*
|
|
25582
|
-
*
|
|
25583
|
-
*
|
|
25584
|
-
*
|
|
25585
|
-
*
|
|
25561
|
+
* IS THIS TERM IN THIS ROW?
|
|
25562
|
+
*
|
|
25563
|
+
* The search box of a treedb table looked at the row's values with
|
|
25564
|
+
* `String(val)`, and a treedb row is not flat: an fkey arrives as a
|
|
25565
|
+
* LIST OF OBJECTS `[{id, topic_name, hook_name}]`, and `String()`
|
|
25566
|
+
* of that is `"[object Object]"`. So looking for the workshop of
|
|
25567
|
+
* a device -- which is where the datum an operator has in mind
|
|
25568
|
+
* lives -- never found anything, and the box gave no hint that it
|
|
25569
|
+
* had looked somewhere else.
|
|
25570
|
+
*
|
|
25571
|
+
* THREE RULES, AND THE LAST TWO ARE WHAT MAKE IT USEFUL:
|
|
25572
|
+
*
|
|
25573
|
+
* - It walks down lists and objects to a short depth: an fkey is
|
|
25574
|
+
* two levels down and nothing anybody searches for is deeper.
|
|
25575
|
+
* - **Of an fkey only the `id` is looked at.** `topic_name` and
|
|
25576
|
+
* `hook_name` are the SAME two words on every row, so looking at
|
|
25577
|
+
* them turns the term into a wildcard: searching "devices" would
|
|
25578
|
+
* bring the whole topic. The `id` is the only thing that names
|
|
25579
|
+
* the linked thing.
|
|
25580
|
+
* - **A hook read as its COUNT is not looked at at all.** Since
|
|
25581
|
+
* `hook_size` (7.23.163) a topic table loads a hook as
|
|
25582
|
+
* `[{"size": N}]` instead of the id of every child, and that
|
|
25583
|
+
* number is not a value of the row: matching it made "5" answer
|
|
25584
|
+
* every row whose hook happens to hold 5 children, next to the
|
|
25585
|
+
* rows that really say 5. A count nobody can see in the cell as
|
|
25586
|
+
* text is not what a person typing in a search box is after.
|
|
25587
|
+
*
|
|
25588
|
+
* Keys that start with `_` are not looked at on any level: they
|
|
25589
|
+
* belong to the scaffolding (`_check_box_state_`, `_operation`) or
|
|
25590
|
+
* are metadata (`__md_treedb__`), and nobody searches by them.
|
|
25586
25591
|
*
|
|
25587
|
-
* Copyright (c) 2026, ArtGins.
|
|
25592
|
+
* Copyright (c) 2024-2026, ArtGins.
|
|
25588
25593
|
* All Rights Reserved.
|
|
25589
25594
|
***********************************************************************/
|
|
25590
25595
|
var MAX_DEPTH = 4;
|
|
25591
25596
|
/***************************************************************
|
|
25592
|
-
*
|
|
25597
|
+
* An fkey, as `nodes` answers it with `list_dict`.
|
|
25593
25598
|
***************************************************************/
|
|
25594
25599
|
function is_fkey_ref(value) {
|
|
25595
25600
|
return !!value && typeof value === "object" && !Array.isArray(value) && "id" in value && "topic_name" in value && "hook_name" in value;
|
|
25596
25601
|
}
|
|
25597
25602
|
/***************************************************************
|
|
25598
|
-
*
|
|
25599
|
-
*
|
|
25603
|
+
* A hook read with `hook_size`: the COUNT of its children,
|
|
25604
|
+
* `[{"size": N}]`, in the place of the children themselves.
|
|
25605
|
+
* The same shape `delete_impact`'s `ref_count()` reads.
|
|
25606
|
+
***************************************************************/
|
|
25607
|
+
function is_hook_size(value) {
|
|
25608
|
+
if (!Array.isArray(value) || value.length !== 1) return false;
|
|
25609
|
+
const first = value[0];
|
|
25610
|
+
return !!first && typeof first === "object" && !Array.isArray(first) && typeof first.size === "number" && Object.keys(first).length === 1;
|
|
25611
|
+
}
|
|
25612
|
+
/***************************************************************
|
|
25613
|
+
* Does `value` hold the term? The term comes ALREADY in lower
|
|
25614
|
+
* case: whoever searches lowers it once, not once per cell.
|
|
25600
25615
|
***************************************************************/
|
|
25601
25616
|
function value_matches(value, term, depth) {
|
|
25602
25617
|
if (value === null || value === void 0) return false;
|
|
25603
25618
|
if (depth > MAX_DEPTH) return false;
|
|
25619
|
+
if (is_hook_size(value)) return false;
|
|
25604
25620
|
if (Array.isArray(value)) return value.some((v) => value_matches(v, term, depth + 1));
|
|
25605
25621
|
if (typeof value === "object") {
|
|
25606
25622
|
if (is_fkey_ref(value)) return value_matches(value.id, term, depth + 1);
|
|
@@ -25612,7 +25628,7 @@ function value_matches(value, term, depth) {
|
|
|
25612
25628
|
return String(value).toLowerCase().includes(term);
|
|
25613
25629
|
}
|
|
25614
25630
|
/***************************************************************
|
|
25615
|
-
*
|
|
25631
|
+
* Is the term anywhere in this row?
|
|
25616
25632
|
***************************************************************/
|
|
25617
25633
|
function row_matches(row, term) {
|
|
25618
25634
|
if (!row || typeof row !== "object" || !term) return false;
|
|
@@ -25622,6 +25638,361 @@ function row_matches(row, term) {
|
|
|
25622
25638
|
});
|
|
25623
25639
|
}
|
|
25624
25640
|
//#endregion
|
|
25641
|
+
//#region src/schema_model.js
|
|
25642
|
+
/***********************************************************************
|
|
25643
|
+
* schema_model.js
|
|
25644
|
+
*
|
|
25645
|
+
* A SCHEMA out of the three meta-topics that store it.
|
|
25646
|
+
*
|
|
25647
|
+
* `treedb_system_schema` keeps every schema of a yuno as DATA, in
|
|
25648
|
+
* three flat topics — `treedbs`, `topics`, `cols` — linked by
|
|
25649
|
+
* fkeys. That is the right STORAGE and the wrong thing to put in
|
|
25650
|
+
* front of an operator: adding one column to one topic means
|
|
25651
|
+
* finding it in a table holding every column of every topic of
|
|
25652
|
+
* every treedb the yuno has.
|
|
25653
|
+
*
|
|
25654
|
+
* This module turns those three lists back into what they
|
|
25655
|
+
* describe: treedb -> topics -> columns, each in its declared
|
|
25656
|
+
* `order`. Pure, so the editor's model can be tested with no DOM
|
|
25657
|
+
* and no backend — the gclass fetches and draws, this decides
|
|
25658
|
+
* what the records MEAN.
|
|
25659
|
+
*
|
|
25660
|
+
* TWO THINGS IT EXISTS TO STATE ONCE:
|
|
25661
|
+
*
|
|
25662
|
+
* 1. A record is grouped by its FKEY, not by its id. The id is
|
|
25663
|
+
* qualified (`<treedb>.<topic>.<column>`) and reading it back
|
|
25664
|
+
* with a split on '.' works right up to the first treedb or
|
|
25665
|
+
* topic whose name carries one. The fkey is the link the store
|
|
25666
|
+
* itself uses; the id is only the fallback for a record whose
|
|
25667
|
+
* fkey never arrived.
|
|
25668
|
+
*
|
|
25669
|
+
* 2. A fkey reaches this code in FOUR shapes, because `nodes`
|
|
25670
|
+
* answers differently depending on the options it was called
|
|
25671
|
+
* with: the bare ref string, the expanded object, a list of
|
|
25672
|
+
* either, or a dict keyed by the ref. All four are the same
|
|
25673
|
+
* fact and parse_fkey_ref() flattens them to it.
|
|
25674
|
+
*
|
|
25675
|
+
* Copyright (c) 2026, ArtGins.
|
|
25676
|
+
* All Rights Reserved.
|
|
25677
|
+
***********************************************************************/
|
|
25678
|
+
var DEFAULT_ORDER = 9999;
|
|
25679
|
+
/***************************************************************
|
|
25680
|
+
* parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
|
|
25681
|
+
*
|
|
25682
|
+
* Every shape a fkey column can arrive in, flattened to the
|
|
25683
|
+
* refs it names. Anything unrecognizable contributes nothing —
|
|
25684
|
+
* a schema drawn without one link beats a screen that throws.
|
|
25685
|
+
***************************************************************/
|
|
25686
|
+
function parse_fkey_ref(value) {
|
|
25687
|
+
let refs = [];
|
|
25688
|
+
let push_one = (v) => {
|
|
25689
|
+
if (typeof v === "string") {
|
|
25690
|
+
let parts = v.split("^");
|
|
25691
|
+
if (parts.length !== 3) return;
|
|
25692
|
+
refs.push({
|
|
25693
|
+
topic_name: parts[0],
|
|
25694
|
+
id: parts[1],
|
|
25695
|
+
hook_name: parts[2]
|
|
25696
|
+
});
|
|
25697
|
+
return;
|
|
25698
|
+
}
|
|
25699
|
+
if (v && typeof v === "object") {
|
|
25700
|
+
let id = v.id;
|
|
25701
|
+
if (typeof id !== "string" || id.length === 0) return;
|
|
25702
|
+
refs.push({
|
|
25703
|
+
topic_name: v.topic_name || "",
|
|
25704
|
+
id,
|
|
25705
|
+
hook_name: v.hook_name || ""
|
|
25706
|
+
});
|
|
25707
|
+
}
|
|
25708
|
+
};
|
|
25709
|
+
if (value === null || value === void 0) return refs;
|
|
25710
|
+
if (typeof value === "string") {
|
|
25711
|
+
push_one(value);
|
|
25712
|
+
return refs;
|
|
25713
|
+
}
|
|
25714
|
+
if (Array.isArray(value)) {
|
|
25715
|
+
for (let v of value) push_one(v);
|
|
25716
|
+
return refs;
|
|
25717
|
+
}
|
|
25718
|
+
if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
|
|
25719
|
+
else push_one(key);
|
|
25720
|
+
return refs;
|
|
25721
|
+
}
|
|
25722
|
+
/***************************************************************
|
|
25723
|
+
* The id of the FIRST parent a record names, or "" when it
|
|
25724
|
+
* names none. A record has one parent here — a column belongs to
|
|
25725
|
+
* one topic, a topic to one treedb — and a second ref would be a
|
|
25726
|
+
* store this editor cannot draw anyway.
|
|
25727
|
+
***************************************************************/
|
|
25728
|
+
function parent_id(record, fkey_col) {
|
|
25729
|
+
let refs = parse_fkey_ref(record ? record[fkey_col] : null);
|
|
25730
|
+
return refs.length > 0 ? refs[0].id : "";
|
|
25731
|
+
}
|
|
25732
|
+
/***************************************************************
|
|
25733
|
+
* The name a record is KNOWN by, which is not what it is keyed
|
|
25734
|
+
* by: `topics` and `cols` key by the qualified id and carry the
|
|
25735
|
+
* bare name in `value` (its pkey2). An older store that still
|
|
25736
|
+
* keyed by rowid has no `value` worth showing, so the id stands
|
|
25737
|
+
* in — the same fallback as node_label().
|
|
25738
|
+
***************************************************************/
|
|
25739
|
+
function record_name(record) {
|
|
25740
|
+
if (!record) return "";
|
|
25741
|
+
if (typeof record.value === "string" && record.value.length > 0) return record.value;
|
|
25742
|
+
return typeof record.id === "string" ? record.id : "";
|
|
25743
|
+
}
|
|
25744
|
+
/***************************************************************
|
|
25745
|
+
* The `order` of a record as a NUMBER, whatever the store put
|
|
25746
|
+
* there: an integer column read back from JSON may arrive as a
|
|
25747
|
+
* string, and comparing "10" with 9 sorts the schema wrong.
|
|
25748
|
+
***************************************************************/
|
|
25749
|
+
function record_order(record) {
|
|
25750
|
+
let order = record ? record.order : void 0;
|
|
25751
|
+
if (typeof order === "number" && isFinite(order)) return order;
|
|
25752
|
+
if (typeof order === "string" && order.trim().length > 0) {
|
|
25753
|
+
let n = Number(order);
|
|
25754
|
+
if (isFinite(n)) return n;
|
|
25755
|
+
}
|
|
25756
|
+
return DEFAULT_ORDER;
|
|
25757
|
+
}
|
|
25758
|
+
/***************************************************************
|
|
25759
|
+
* Sort in place by `order`, ties broken by name so a schema
|
|
25760
|
+
* whose records all default to 9999 still draws the same way
|
|
25761
|
+
* twice.
|
|
25762
|
+
***************************************************************/
|
|
25763
|
+
function sort_by_order(list) {
|
|
25764
|
+
list.sort((a, b) => {
|
|
25765
|
+
if (a.order !== b.order) return a.order - b.order;
|
|
25766
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
25767
|
+
});
|
|
25768
|
+
return list;
|
|
25769
|
+
}
|
|
25770
|
+
/***************************************************************
|
|
25771
|
+
* build_schema_model({treedbs, topics, cols}) -> model
|
|
25772
|
+
*
|
|
25773
|
+
* model = {
|
|
25774
|
+
* treedbs: [{
|
|
25775
|
+
* id, name, record,
|
|
25776
|
+
* schema_version, c_schema_version, system_schema_version,
|
|
25777
|
+
* topics: [{
|
|
25778
|
+
* id, name, order, record,
|
|
25779
|
+
* pkey, topic_version, system_topic, main_topic,
|
|
25780
|
+
* cols: [{id, name, order, record}]
|
|
25781
|
+
* }]
|
|
25782
|
+
* }],
|
|
25783
|
+
* orphan_topics: [...], topics whose treedb is not in the store
|
|
25784
|
+
* orphan_cols: [...] columns whose topic is not in the store
|
|
25785
|
+
* }
|
|
25786
|
+
*
|
|
25787
|
+
* An orphan is NOT dropped. A column whose topic was deleted is
|
|
25788
|
+
* exactly the kind of leftover an operator opens this editor to
|
|
25789
|
+
* find, and a model that hides it makes the editor lie.
|
|
25790
|
+
***************************************************************/
|
|
25791
|
+
function build_schema_model(records) {
|
|
25792
|
+
let r = records || {};
|
|
25793
|
+
let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
|
|
25794
|
+
let topic_rows = Array.isArray(r.topics) ? r.topics : [];
|
|
25795
|
+
let col_rows = Array.isArray(r.cols) ? r.cols : [];
|
|
25796
|
+
let treedbs = [];
|
|
25797
|
+
let by_treedb_id = {};
|
|
25798
|
+
for (let record of treedb_rows) {
|
|
25799
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25800
|
+
let entry = {
|
|
25801
|
+
id: record.id,
|
|
25802
|
+
name: record.id,
|
|
25803
|
+
record,
|
|
25804
|
+
schema_version: record.schema_version,
|
|
25805
|
+
c_schema_version: record.c_schema_version,
|
|
25806
|
+
system_schema_version: record.system_schema_version,
|
|
25807
|
+
topics: []
|
|
25808
|
+
};
|
|
25809
|
+
treedbs.push(entry);
|
|
25810
|
+
by_treedb_id[entry.id] = entry;
|
|
25811
|
+
}
|
|
25812
|
+
let orphan_topics = [];
|
|
25813
|
+
let by_topic_id = {};
|
|
25814
|
+
for (let record of topic_rows) {
|
|
25815
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25816
|
+
let entry = {
|
|
25817
|
+
id: record.id,
|
|
25818
|
+
name: record_name(record),
|
|
25819
|
+
order: record_order(record),
|
|
25820
|
+
record,
|
|
25821
|
+
treedb_id: parent_id(record, "treedbs"),
|
|
25822
|
+
pkey: record.pkey || "",
|
|
25823
|
+
pkey2s: record.pkey2s,
|
|
25824
|
+
system_flag: record.system_flag || "",
|
|
25825
|
+
tkey: record.tkey || "",
|
|
25826
|
+
topic_version: record.topic_version,
|
|
25827
|
+
system_topic: !!record.system_topic,
|
|
25828
|
+
main_topic: !!record.main_topic,
|
|
25829
|
+
cols: []
|
|
25830
|
+
};
|
|
25831
|
+
by_topic_id[entry.id] = entry;
|
|
25832
|
+
let parent = by_treedb_id[entry.treedb_id];
|
|
25833
|
+
if (parent) parent.topics.push(entry);
|
|
25834
|
+
else orphan_topics.push(entry);
|
|
25835
|
+
}
|
|
25836
|
+
let orphan_cols = [];
|
|
25837
|
+
for (let record of col_rows) {
|
|
25838
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25839
|
+
let entry = {
|
|
25840
|
+
id: record.id,
|
|
25841
|
+
name: record_name(record),
|
|
25842
|
+
order: record_order(record),
|
|
25843
|
+
record,
|
|
25844
|
+
topic_id: parent_id(record, "topics")
|
|
25845
|
+
};
|
|
25846
|
+
let parent = by_topic_id[entry.topic_id];
|
|
25847
|
+
if (parent) parent.cols.push(entry);
|
|
25848
|
+
else orphan_cols.push(entry);
|
|
25849
|
+
}
|
|
25850
|
+
for (let treedb of treedbs) {
|
|
25851
|
+
sort_by_order(treedb.topics);
|
|
25852
|
+
for (let topic of treedb.topics) sort_by_order(topic.cols);
|
|
25853
|
+
}
|
|
25854
|
+
for (let topic of orphan_topics) sort_by_order(topic.cols);
|
|
25855
|
+
treedbs.sort((a, b) => {
|
|
25856
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
25857
|
+
});
|
|
25858
|
+
sort_by_order(orphan_topics);
|
|
25859
|
+
sort_by_order(orphan_cols);
|
|
25860
|
+
return {
|
|
25861
|
+
treedbs,
|
|
25862
|
+
orphan_topics,
|
|
25863
|
+
orphan_cols
|
|
25864
|
+
};
|
|
25865
|
+
}
|
|
25866
|
+
/***************************************************************
|
|
25867
|
+
* Is there a value here at all?
|
|
25868
|
+
*
|
|
25869
|
+
* The store answers a `blob` column that was never set with an
|
|
25870
|
+
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
25871
|
+
* `default: {}`. Read as "a value", those turn every export into
|
|
25872
|
+
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
25873
|
+
* is a hook with no mapping, which is a link the treedb builds
|
|
25874
|
+
* and nothing writes.
|
|
25875
|
+
***************************************************************/
|
|
25876
|
+
function is_empty_value(value) {
|
|
25877
|
+
if (value === null || value === void 0 || value === "") return true;
|
|
25878
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
25879
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
25880
|
+
return false;
|
|
25881
|
+
}
|
|
25882
|
+
/***************************************************************
|
|
25883
|
+
* The fields of a column that are DECLARED as one thing and
|
|
25884
|
+
* STORED as another.
|
|
25885
|
+
*
|
|
25886
|
+
* `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
|
|
25887
|
+
* columns of the `cols` topic (treedb_system_schema.c), so what
|
|
25888
|
+
* comes back may be the value or the JSON text of the value,
|
|
25889
|
+
* depending on how it was written — a projection writes the
|
|
25890
|
+
* value, a hand edit through a plain text field writes the text.
|
|
25891
|
+
* `flag` is an array that a single-flag edit can leave as a bare
|
|
25892
|
+
* string. Reading these without saying so is how a hook drawn
|
|
25893
|
+
* from `{"users":"department"}` becomes no hook at all.
|
|
25894
|
+
***************************************************************/
|
|
25895
|
+
function as_json(value) {
|
|
25896
|
+
if (typeof value !== "string") return value;
|
|
25897
|
+
let text = value.trim();
|
|
25898
|
+
if (text.length === 0) return null;
|
|
25899
|
+
try {
|
|
25900
|
+
return JSON.parse(text);
|
|
25901
|
+
} catch (e) {
|
|
25902
|
+
return value;
|
|
25903
|
+
}
|
|
25904
|
+
}
|
|
25905
|
+
function col_flags(record) {
|
|
25906
|
+
let flag = record ? record.flag : null;
|
|
25907
|
+
if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
25908
|
+
if (typeof flag === "string" && flag.length > 0) {
|
|
25909
|
+
let parsed = as_json(flag);
|
|
25910
|
+
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
|
|
25911
|
+
return [flag];
|
|
25912
|
+
}
|
|
25913
|
+
return [];
|
|
25914
|
+
}
|
|
25915
|
+
function col_hook(record) {
|
|
25916
|
+
let hook = as_json(record ? record.hook : null);
|
|
25917
|
+
if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
|
|
25918
|
+
return null;
|
|
25919
|
+
}
|
|
25920
|
+
function col_enum(record) {
|
|
25921
|
+
let e = as_json(record ? record.enum : null);
|
|
25922
|
+
if (Array.isArray(e)) return e;
|
|
25923
|
+
return [];
|
|
25924
|
+
}
|
|
25925
|
+
function topic_pkey2s(record) {
|
|
25926
|
+
let pkey2s = as_json(record ? record.pkey2s : null);
|
|
25927
|
+
if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
|
|
25928
|
+
if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
|
|
25929
|
+
return [];
|
|
25930
|
+
}
|
|
25931
|
+
/***************************************************************
|
|
25932
|
+
* Lookups by id. The editor addresses a position by NAME (the
|
|
25933
|
+
* url carries `<treedb>/<topic>`), so both are offered.
|
|
25934
|
+
***************************************************************/
|
|
25935
|
+
function find_treedb(model, treedb_id) {
|
|
25936
|
+
if (!model || !Array.isArray(model.treedbs)) return null;
|
|
25937
|
+
for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
|
|
25938
|
+
return null;
|
|
25939
|
+
}
|
|
25940
|
+
function find_topic(model, treedb_id, topic_name) {
|
|
25941
|
+
let treedb = find_treedb(model, treedb_id);
|
|
25942
|
+
if (!treedb) return null;
|
|
25943
|
+
for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
|
|
25944
|
+
return null;
|
|
25945
|
+
}
|
|
25946
|
+
function find_col(model, treedb_id, topic_name, col_name) {
|
|
25947
|
+
let topic = find_topic(model, treedb_id, topic_name);
|
|
25948
|
+
if (!topic) return null;
|
|
25949
|
+
for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
|
|
25950
|
+
return null;
|
|
25951
|
+
}
|
|
25952
|
+
/***************************************************************
|
|
25953
|
+
* The fkey a NEW record must carry to belong to `parent_id`.
|
|
25954
|
+
* Written here because the editor is the only writer that has to
|
|
25955
|
+
* compose one, and getting the hook name wrong links the record
|
|
25956
|
+
* to nothing while the write still succeeds.
|
|
25957
|
+
***************************************************************/
|
|
25958
|
+
function fkey_ref(parent_topic, parent_id_, hook_name) {
|
|
25959
|
+
return `${parent_topic}^${parent_id_}^${hook_name}`;
|
|
25960
|
+
}
|
|
25961
|
+
/***************************************************************
|
|
25962
|
+
* Where a new sibling goes: after the last one. The store's own
|
|
25963
|
+
* default (9999) would put every hand-made column in the same
|
|
25964
|
+
* place and let their names decide the schema's order.
|
|
25965
|
+
***************************************************************/
|
|
25966
|
+
function next_order(siblings) {
|
|
25967
|
+
let max = 0;
|
|
25968
|
+
if (Array.isArray(siblings)) for (let s of siblings) {
|
|
25969
|
+
let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
|
|
25970
|
+
if (order < 9999 && order > max) max = order;
|
|
25971
|
+
}
|
|
25972
|
+
return max + 1;
|
|
25973
|
+
}
|
|
25974
|
+
/***************************************************************
|
|
25975
|
+
* Renumber a list AFTER a move, as the writes it takes: only the
|
|
25976
|
+
* records whose order actually changed, so a drag of one row in a
|
|
25977
|
+
* 40-column topic is not 40 writes.
|
|
25978
|
+
*
|
|
25979
|
+
* moved_orders(list) -> [{id, order}, ...]
|
|
25980
|
+
***************************************************************/
|
|
25981
|
+
function moved_orders(list) {
|
|
25982
|
+
let writes = [];
|
|
25983
|
+
if (!Array.isArray(list)) return writes;
|
|
25984
|
+
for (let i = 0; i < list.length; i++) {
|
|
25985
|
+
let entry = list[i];
|
|
25986
|
+
let order = i + 1;
|
|
25987
|
+
if (!entry || typeof entry.id !== "string") continue;
|
|
25988
|
+
if (entry.order !== order) writes.push({
|
|
25989
|
+
id: entry.id,
|
|
25990
|
+
order
|
|
25991
|
+
});
|
|
25992
|
+
}
|
|
25993
|
+
return writes;
|
|
25994
|
+
}
|
|
25995
|
+
//#endregion
|
|
25625
25996
|
//#region src/treedb_write_plan.js
|
|
25626
25997
|
/***********************************************************************
|
|
25627
25998
|
* treedb_write_plan.js
|
|
@@ -25683,6 +26054,43 @@ function plan_treedb_writes(flags) {
|
|
|
25683
26054
|
form_toolbar: null
|
|
25684
26055
|
};
|
|
25685
26056
|
}
|
|
26057
|
+
/************************************************************
|
|
26058
|
+
* Does column `col` travel back to treedb on a write?
|
|
26059
|
+
*
|
|
26060
|
+
* The form SHOWS every field and sends back only what the topic
|
|
26061
|
+
* accepts: the writable cols, the fkeys (a link is edited by
|
|
26062
|
+
* linking) and the pkey, which is not written but is what
|
|
26063
|
+
* ADDRESSES the record.
|
|
26064
|
+
*
|
|
26065
|
+
* It matters because `treedb_update_node()` does not check
|
|
26066
|
+
* `writable` -- it writes any col it is handed -- so a read-only
|
|
26067
|
+
* field travelling back is written with whatever the form made of
|
|
26068
|
+
* it, and the fields that describe a record are exactly the ones
|
|
26069
|
+
* that do not survive a round trip through a widget: a `time` is an
|
|
26070
|
+
* integer rendered as a `datetime-local`, with no seconds, so every
|
|
26071
|
+
* save moved it back up to 59 s. Nothing looked wrong until the
|
|
26072
|
+
* stored timestamp had moved.
|
|
26073
|
+
*
|
|
26074
|
+
* `is_file` and not the type: a `file` column IS an fkey (it is
|
|
26075
|
+
* flagged ['fkey','file']) but answers `type: "file"`, and the write
|
|
26076
|
+
* goes out with `autolink`, which rebuilds the links from what the
|
|
26077
|
+
* record carries. Dropped, a read-only `file` column -- the one only
|
|
26078
|
+
* a load fills -- was UNLINKED by every save of any other field.
|
|
26079
|
+
*
|
|
26080
|
+
* The pkey2s go back for the same reason as the pkey: a secondary key
|
|
26081
|
+
* names the INSTANCE the update is for (`yunos.yuno_release` in the
|
|
26082
|
+
* agent), and it is `persistent, required`, not `writable`. Dropped,
|
|
26083
|
+
* the update reached the backend with no pkey2 and C_NODE resolved it
|
|
26084
|
+
* to the PRIMARY instance -- right by chance while the table lists
|
|
26085
|
+
* primaries, wrong from a form opened on a row of `instances`.
|
|
26086
|
+
************************************************************/
|
|
26087
|
+
function col_goes_back_to_treedb(desc, col) {
|
|
26088
|
+
let d = desc || {};
|
|
26089
|
+
let pkey = d.pkey || "id";
|
|
26090
|
+
const field_desc = treedb_get_field_desc(col);
|
|
26091
|
+
if (field_desc.is_writable || field_desc.type === "fkey" || field_desc.is_file || col.id === pkey) return true;
|
|
26092
|
+
return topic_pkey2s(d).includes(col.id);
|
|
26093
|
+
}
|
|
25686
26094
|
//#endregion
|
|
25687
26095
|
//#region src/delete_impact.js
|
|
25688
26096
|
/***********************************************************************
|
|
@@ -25818,8 +26226,8 @@ var attrs_table$5 = [
|
|
|
25818
26226
|
SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
|
|
25819
26227
|
SDATA(data_type_t.DTP_BOOLEAN, "with_radio", 0, false, "Auxiliary first column to select one row"),
|
|
25820
26228
|
SDATA(data_type_t.DTP_BOOLEAN, "with_selection_bar", 0, false, "Show the shared selection bar (\"N selected\" + a way out) above the table while in edition mode. OFF by default: the bar takes its words from the HOST's i18n, and a host that has not defined \"{{n}} selected\" and \"clear selection\" would render the keys"),
|
|
25821
|
-
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_select_rows_event", 0, false, "
|
|
25822
|
-
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_unselect_rows_event", 0, false, "
|
|
26229
|
+
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_select_rows_event", 0, false, "Publish EV_SELECT_ROWS to the subscriber. OFF by default: the event is an OUTPUT event, so a host that turns it on must declare EV_SELECT_ROWS in its own FSM or it answers \"Event NOT DEFINED in state\" on every selected row"),
|
|
26230
|
+
SDATA(data_type_t.DTP_BOOLEAN, "broadcast_unselect_rows_event", 0, false, "Publish EV_UNSELECT_ROWS to the subscriber. Same contract as broadcast_select_rows_event, and INDEPENDENT of it: a host that wants both sets both"),
|
|
25823
26231
|
SDATA(data_type_t.DTP_JSON, "tabulator_settings", 0, {
|
|
25824
26232
|
layout: "fitDataFill",
|
|
25825
26233
|
columnDefaults: { resizable: true },
|
|
@@ -26098,7 +26506,11 @@ function build_ui$5(gobj) {
|
|
|
26098
26506
|
"button",
|
|
26099
26507
|
{
|
|
26100
26508
|
id: ``,
|
|
26101
|
-
class: "button button-edit-record mr-1"
|
|
26509
|
+
class: "button button-edit-record mr-1",
|
|
26510
|
+
title: t("edit"),
|
|
26511
|
+
"data-i18n-title": "edit",
|
|
26512
|
+
"aria-label": t("edit"),
|
|
26513
|
+
"data-i18n-aria-label": "edit"
|
|
26102
26514
|
},
|
|
26103
26515
|
[yui_toolbar_icon("yi-pen"), [
|
|
26104
26516
|
"span",
|
|
@@ -26121,6 +26533,10 @@ function build_ui$5(gobj) {
|
|
|
26121
26533
|
{
|
|
26122
26534
|
id: ``,
|
|
26123
26535
|
class: "button button-new-record mr-1",
|
|
26536
|
+
title: t("new"),
|
|
26537
|
+
"data-i18n-title": "new",
|
|
26538
|
+
"aria-label": t("new"),
|
|
26539
|
+
"data-i18n-aria-label": "new",
|
|
26124
26540
|
disabled: true
|
|
26125
26541
|
},
|
|
26126
26542
|
[yui_toolbar_icon("yi-plus"), [
|
|
@@ -26145,6 +26561,10 @@ function build_ui$5(gobj) {
|
|
|
26145
26561
|
{
|
|
26146
26562
|
id: ``,
|
|
26147
26563
|
class: "button button-delete-record mr-1",
|
|
26564
|
+
title: t("delete"),
|
|
26565
|
+
"data-i18n-title": "delete",
|
|
26566
|
+
"aria-label": t("delete"),
|
|
26567
|
+
"data-i18n-aria-label": "delete",
|
|
26148
26568
|
disabled: true
|
|
26149
26569
|
},
|
|
26150
26570
|
[yui_toolbar_icon("yi-trash"), [
|
|
@@ -26169,6 +26589,10 @@ function build_ui$5(gobj) {
|
|
|
26169
26589
|
{
|
|
26170
26590
|
id: ``,
|
|
26171
26591
|
class: "button button-copy-record mr-1",
|
|
26592
|
+
title: t("copy"),
|
|
26593
|
+
"data-i18n-title": "copy",
|
|
26594
|
+
"aria-label": t("copy"),
|
|
26595
|
+
"data-i18n-aria-label": "copy",
|
|
26172
26596
|
disabled: true
|
|
26173
26597
|
},
|
|
26174
26598
|
[yui_toolbar_icon("yi-copy"), [
|
|
@@ -26193,6 +26617,10 @@ function build_ui$5(gobj) {
|
|
|
26193
26617
|
{
|
|
26194
26618
|
id: ``,
|
|
26195
26619
|
class: "button button-paste-record mr-1",
|
|
26620
|
+
title: t("paste"),
|
|
26621
|
+
"data-i18n-title": "paste",
|
|
26622
|
+
"aria-label": t("paste"),
|
|
26623
|
+
"data-i18n-aria-label": "paste",
|
|
26196
26624
|
disabled: true
|
|
26197
26625
|
},
|
|
26198
26626
|
[yui_toolbar_icon("yi-paste"), [
|
|
@@ -26232,7 +26660,11 @@ function build_ui$5(gobj) {
|
|
|
26232
26660
|
class: "input",
|
|
26233
26661
|
type: "text",
|
|
26234
26662
|
placeholder: t("search"),
|
|
26235
|
-
"data-i18n-placeholder": "search"
|
|
26663
|
+
"data-i18n-placeholder": "search",
|
|
26664
|
+
title: t("search"),
|
|
26665
|
+
"data-i18n-title": "search",
|
|
26666
|
+
"aria-label": t("search"),
|
|
26667
|
+
"data-i18n-aria-label": "search"
|
|
26236
26668
|
}], [
|
|
26237
26669
|
"span",
|
|
26238
26670
|
{ class: "icon is-left" },
|
|
@@ -26252,7 +26684,9 @@ function build_ui$5(gobj) {
|
|
|
26252
26684
|
{
|
|
26253
26685
|
class: "button mr-1",
|
|
26254
26686
|
title: t("refresh"),
|
|
26255
|
-
"data-i18n-title": "refresh"
|
|
26687
|
+
"data-i18n-title": "refresh",
|
|
26688
|
+
"aria-label": t("refresh"),
|
|
26689
|
+
"data-i18n-aria-label": "refresh"
|
|
26256
26690
|
},
|
|
26257
26691
|
[yui_toolbar_icon("yi-arrows-rotate"), [
|
|
26258
26692
|
"span",
|
|
@@ -26785,7 +27219,7 @@ function create_tabulator(gobj) {
|
|
|
26785
27219
|
let index = cell.getRow().getPosition();
|
|
26786
27220
|
if (e.target.closest(".edit")) {
|
|
26787
27221
|
e.stopPropagation();
|
|
26788
|
-
|
|
27222
|
+
gobj_send_event(gobj, "EV_EDIT_RECORD", { index }, gobj);
|
|
26789
27223
|
} else if (e.target.closest(".remove")) {
|
|
26790
27224
|
e.stopPropagation();
|
|
26791
27225
|
gobj_send_event(gobj, "EV_DELETE_ROWS", {
|
|
@@ -27247,11 +27681,11 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
27247
27681
|
return;
|
|
27248
27682
|
}
|
|
27249
27683
|
let pkey = desc.pkey || "id";
|
|
27250
|
-
let title;
|
|
27251
|
-
|
|
27252
|
-
|
|
27684
|
+
let title = mode === "create" ? "new" : mode === "view" ? "view" : "edit";
|
|
27685
|
+
let title_prefix = "";
|
|
27686
|
+
if (mode !== "create") {
|
|
27253
27687
|
let rid = record ? String(record[pkey] ?? "") : "";
|
|
27254
|
-
|
|
27688
|
+
title_prefix = empty_string(rid) ? "" : rid;
|
|
27255
27689
|
}
|
|
27256
27690
|
let view_only = mode === "view";
|
|
27257
27691
|
let form_mode = view_only ? "update" : mode;
|
|
@@ -27286,6 +27720,7 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
27286
27720
|
priv.form_modal = yui_shell_show_modal(shell, $body, {
|
|
27287
27721
|
dialog: true,
|
|
27288
27722
|
logical_class: "TREEDB_FORM_SHEET",
|
|
27723
|
+
title_prefix,
|
|
27289
27724
|
title,
|
|
27290
27725
|
t,
|
|
27291
27726
|
before_close: function() {
|
|
@@ -27314,14 +27749,7 @@ function form_may_close(gobj) {
|
|
|
27314
27749
|
let kw = {};
|
|
27315
27750
|
gobj_send_event(priv.form, "EV_WINDOW_TO_CLOSE", kw, gobj);
|
|
27316
27751
|
if (kw.abort_close) {
|
|
27317
|
-
|
|
27318
|
-
t,
|
|
27319
|
-
yes_label: "yes",
|
|
27320
|
-
no_label: "no",
|
|
27321
|
-
cancel_label: "cancel"
|
|
27322
|
-
}).then(function(answer) {
|
|
27323
|
-
if (answer === "yes" && priv.form_modal) priv.form_modal.close();
|
|
27324
|
-
});
|
|
27752
|
+
confirm_then$1(gobj, kw.warning || "all changes will be lost", { what: "close_form" });
|
|
27325
27753
|
return false;
|
|
27326
27754
|
}
|
|
27327
27755
|
}
|
|
@@ -27343,6 +27771,31 @@ function teardown_form_child(gobj) {
|
|
|
27343
27771
|
priv.form_modal = null;
|
|
27344
27772
|
}
|
|
27345
27773
|
/************************************************************
|
|
27774
|
+
* Ask, and turn the answer into an EVENT.
|
|
27775
|
+
*
|
|
27776
|
+
* A dialog's promise settles outside the machine: acting from its
|
|
27777
|
+
* `.then` puts the decision -- delete these records, throw these
|
|
27778
|
+
* edits away -- in a callback the `machine` trace never sees, and
|
|
27779
|
+
* in a view that may be gone by the time the person answers.
|
|
27780
|
+
*
|
|
27781
|
+
* `kw` carries an IDENTITY and never an object: the trace dumps
|
|
27782
|
+
* it, and a gobj, a Tabulator row or a DOM node in there is a
|
|
27783
|
+
* circular structure that breaks the very trace the FSM feeds.
|
|
27784
|
+
*
|
|
27785
|
+
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then().
|
|
27786
|
+
************************************************************/
|
|
27787
|
+
function confirm_then$1(gobj, message, kw) {
|
|
27788
|
+
yui_shell_confirm_yesnocancel(yui_shell_of(gobj), message, {
|
|
27789
|
+
t,
|
|
27790
|
+
yes_label: "yes",
|
|
27791
|
+
no_label: "no",
|
|
27792
|
+
cancel_label: "cancel"
|
|
27793
|
+
}).then(function(answer) {
|
|
27794
|
+
if (answer !== "yes" || gobj_is_destroying(gobj)) return;
|
|
27795
|
+
gobj_send_event(gobj, "EV_CONFIRMED", kw, gobj);
|
|
27796
|
+
});
|
|
27797
|
+
}
|
|
27798
|
+
/************************************************************
|
|
27346
27799
|
* Force the dialog down (bypasses the unsaved guard): used after a
|
|
27347
27800
|
* successful save and on teardown. Closing the modal runs its
|
|
27348
27801
|
* on_close, which tears the form child down.
|
|
@@ -27923,7 +28376,7 @@ function pkey_is_rowid(gobj) {
|
|
|
27923
28376
|
* Show the edit "update" form of a row (record)
|
|
27924
28377
|
* internally called from the icon edit at Op column
|
|
27925
28378
|
************************************************************/
|
|
27926
|
-
function show_edit_form(gobj, row
|
|
28379
|
+
function show_edit_form(gobj, row) {
|
|
27927
28380
|
open_form_dialog(gobj, pkey_is_rowid(gobj) ? "create" : "update", row);
|
|
27928
28381
|
}
|
|
27929
28382
|
/************************************************************
|
|
@@ -27985,35 +28438,6 @@ function get_schema_col(gobj, id) {
|
|
|
27985
28438
|
return null;
|
|
27986
28439
|
}
|
|
27987
28440
|
/************************************************************
|
|
27988
|
-
* Does column `col` travel back to treedb on a write?
|
|
27989
|
-
*
|
|
27990
|
-
* The form SHOWS every field and sends back only what the topic
|
|
27991
|
-
* accepts: the writable cols, the fkeys (a link is edited by
|
|
27992
|
-
* linking) and the pkey, which is not written but is what
|
|
27993
|
-
* ADDRESSES the record.
|
|
27994
|
-
*
|
|
27995
|
-
* It matters because `treedb_update_node()` does not check
|
|
27996
|
-
* `writable` -- it writes any col it is handed -- so a read-only
|
|
27997
|
-
* field travelling back is written with whatever the form made of
|
|
27998
|
-
* it, and the fields that describe a record are exactly the ones
|
|
27999
|
-
* that do not survive a round trip through a widget: a `time` is an
|
|
28000
|
-
* integer rendered as a `datetime-local`, with no seconds, so every
|
|
28001
|
-
* save moved it back up to 59 s. Nothing looked wrong until the
|
|
28002
|
-
* stored timestamp had moved.
|
|
28003
|
-
*
|
|
28004
|
-
* `is_file` and not the type: a `file` column IS an fkey (it is
|
|
28005
|
-
* flagged ['fkey','file']) but answers `type: "file"`, and the write
|
|
28006
|
-
* goes out with `autolink`, which rebuilds the links from what the
|
|
28007
|
-
* record carries. Dropped, a read-only `file` column -- the one only
|
|
28008
|
-
* a load fills -- was UNLINKED by every save of any other field.
|
|
28009
|
-
************************************************************/
|
|
28010
|
-
function col_goes_back_to_treedb(gobj, col) {
|
|
28011
|
-
let pkey = gobj_read_attr(gobj, "desc").pkey || "id";
|
|
28012
|
-
const field_desc = treedb_get_field_desc(col);
|
|
28013
|
-
if (field_desc.is_writable || field_desc.type === "fkey" || field_desc.is_file || col.id === pkey) return true;
|
|
28014
|
-
return false;
|
|
28015
|
-
}
|
|
28016
|
-
/************************************************************
|
|
28017
28441
|
* The record the FORM hands over, without the columns that do
|
|
28018
28442
|
* not go back to treedb (see col_goes_back_to_treedb()). The
|
|
28019
28443
|
* form has already encoded the values, so only the columns are
|
|
@@ -28023,7 +28447,7 @@ function strip_read_only_cols(gobj, record) {
|
|
|
28023
28447
|
let row = {};
|
|
28024
28448
|
for (let field_name of Object.keys(record || {})) {
|
|
28025
28449
|
let col = get_schema_col(gobj, field_name);
|
|
28026
|
-
if (col && !col_goes_back_to_treedb(gobj, col)) continue;
|
|
28450
|
+
if (col && !col_goes_back_to_treedb(gobj_read_attr(gobj, "desc"), col)) continue;
|
|
28027
28451
|
row[field_name] = record[field_name];
|
|
28028
28452
|
}
|
|
28029
28453
|
return row;
|
|
@@ -28039,7 +28463,7 @@ function transform__form_record_2_treedb_record(gobj, kw, operation) {
|
|
|
28039
28463
|
if (empty_string(field_name)) continue;
|
|
28040
28464
|
let col = get_schema_col(gobj, field_name);
|
|
28041
28465
|
if (!col) continue;
|
|
28042
|
-
if (!col_goes_back_to_treedb(gobj, col)) continue;
|
|
28466
|
+
if (!col_goes_back_to_treedb(gobj_read_attr(gobj, "desc"), col)) continue;
|
|
28043
28467
|
let value = kw[field_name];
|
|
28044
28468
|
row[field_name] = transform__form_value_2_treedb_value(gobj, col, value, operation);
|
|
28045
28469
|
}
|
|
@@ -28422,31 +28846,57 @@ function ac_delete_rows(gobj, event, kw, src) {
|
|
|
28422
28846
|
});
|
|
28423
28847
|
return 0;
|
|
28424
28848
|
}
|
|
28425
|
-
|
|
28426
|
-
|
|
28427
|
-
|
|
28428
|
-
|
|
28429
|
-
cancel_label: "cancel"
|
|
28430
|
-
}).then(function(answer) {
|
|
28431
|
-
if (answer === "yes") for (let row of rows) gobj_publish_event(gobj, "EV_DELETE_RECORD", {
|
|
28432
|
-
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
28433
|
-
record: row
|
|
28434
|
-
});
|
|
28435
|
-
});
|
|
28436
|
-
} else yui_shell_confirm_yesnocancel(yui_shell_of(gobj), build_delete_question(gobj, kw.row), {
|
|
28437
|
-
t,
|
|
28438
|
-
yes_label: "yes",
|
|
28439
|
-
no_label: "no",
|
|
28440
|
-
cancel_label: "cancel"
|
|
28441
|
-
}).then(function(answer) {
|
|
28442
|
-
if (answer === "yes") gobj_publish_event(gobj, "EV_DELETE_RECORD", {
|
|
28443
|
-
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
28444
|
-
record: kw.row
|
|
28445
|
-
});
|
|
28849
|
+
confirm_then$1(gobj, build_delete_question(gobj, rows), { what: "delete_selection" });
|
|
28850
|
+
} else confirm_then$1(gobj, build_delete_question(gobj, kw.row), {
|
|
28851
|
+
what: "delete_row",
|
|
28852
|
+
index: kw.index
|
|
28446
28853
|
});
|
|
28447
28854
|
return 0;
|
|
28448
28855
|
}
|
|
28449
28856
|
/************************************************************
|
|
28857
|
+
* {what, ...} -- the person said yes to what confirm_then()
|
|
28858
|
+
* asked. The rows are read again HERE and not carried in the
|
|
28859
|
+
* kw: the kw is plain json, and a Tabulator row is not.
|
|
28860
|
+
************************************************************/
|
|
28861
|
+
function ac_confirmed$2(gobj, event, kw, src) {
|
|
28862
|
+
let priv = gobj.priv;
|
|
28863
|
+
let what = kw ? kw.what : "";
|
|
28864
|
+
if (what === "close_form") {
|
|
28865
|
+
if (priv.form_modal) priv.form_modal.close();
|
|
28866
|
+
return 0;
|
|
28867
|
+
}
|
|
28868
|
+
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
28869
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
28870
|
+
let topic_name = gobj_read_str_attr(gobj, "topic_name");
|
|
28871
|
+
if (what === "delete_selection") {
|
|
28872
|
+
let rows = yui_selected_rows(tabulator);
|
|
28873
|
+
if (!rows.length) {
|
|
28874
|
+
log_error(`${gobj_short_name(gobj)}: ${event} with the selection gone`);
|
|
28875
|
+
return -1;
|
|
28876
|
+
}
|
|
28877
|
+
for (let row of rows) gobj_publish_event(gobj, "EV_DELETE_RECORD", {
|
|
28878
|
+
topic_name,
|
|
28879
|
+
record: row
|
|
28880
|
+
});
|
|
28881
|
+
return 0;
|
|
28882
|
+
}
|
|
28883
|
+
if (what === "delete_row") {
|
|
28884
|
+
let index = kw.index;
|
|
28885
|
+
let row = tabulator && typeof index === "number" ? tabulator.getRowFromPosition(index) : null;
|
|
28886
|
+
if (!row) {
|
|
28887
|
+
log_error(`${gobj_short_name(gobj)}: ${event} names no row: ${index}`);
|
|
28888
|
+
return -1;
|
|
28889
|
+
}
|
|
28890
|
+
gobj_publish_event(gobj, "EV_DELETE_RECORD", {
|
|
28891
|
+
topic_name,
|
|
28892
|
+
record: row.getData()
|
|
28893
|
+
});
|
|
28894
|
+
return 0;
|
|
28895
|
+
}
|
|
28896
|
+
log_error(`${gobj_short_name(gobj)}: ${event} of nothing this view asked about: ${what}`);
|
|
28897
|
+
return -1;
|
|
28898
|
+
}
|
|
28899
|
+
/************************************************************
|
|
28450
28900
|
* From internal
|
|
28451
28901
|
************************************************************/
|
|
28452
28902
|
function ac_copy_rows(gobj, event, kw, src) {
|
|
@@ -28501,9 +28951,7 @@ function ac_form_save_record(gobj, event, kw, src) {
|
|
|
28501
28951
|
const cols = Object.keys(picked);
|
|
28502
28952
|
if (cols.length === 0) {
|
|
28503
28953
|
publish_treedb_write(gobj, mode, kw);
|
|
28504
|
-
|
|
28505
|
-
close_form_dialog(gobj);
|
|
28506
|
-
}, 0);
|
|
28954
|
+
gobj_post_event(gobj, "EV_CLOSE_FORM", {}, gobj);
|
|
28507
28955
|
return 0;
|
|
28508
28956
|
}
|
|
28509
28957
|
const serial = ++priv.read_serial;
|
|
@@ -28581,6 +29029,14 @@ function publish_treedb_write(gobj, mode, record, jn_files) {
|
|
|
28581
29029
|
gobj_publish_event(gobj, mode === "create" ? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD", kw_write);
|
|
28582
29030
|
}
|
|
28583
29031
|
/************************************************************
|
|
29032
|
+
* The deferred close of the form dialog, posted by the save
|
|
29033
|
+
* from inside the form child's own publish stack.
|
|
29034
|
+
************************************************************/
|
|
29035
|
+
function ac_close_form(gobj, event, kw, src) {
|
|
29036
|
+
close_form_dialog(gobj);
|
|
29037
|
+
return 0;
|
|
29038
|
+
}
|
|
29039
|
+
/************************************************************
|
|
28584
29040
|
* {mode, record, picks} -- the bytes are here now
|
|
28585
29041
|
************************************************************/
|
|
28586
29042
|
function ac_files_read(gobj, event, kw, src) {
|
|
@@ -28621,7 +29077,7 @@ function ac_select_rows(gobj, event, kw, src) {
|
|
|
28621
29077
|
************************************************************/
|
|
28622
29078
|
function ac_unselect_rows(gobj, event, kw, src) {
|
|
28623
29079
|
render_selection_state(gobj);
|
|
28624
|
-
if (gobj_read_bool_attr(gobj, "
|
|
29080
|
+
if (gobj_read_bool_attr(gobj, "broadcast_unselect_rows_event")) gobj_publish_event(gobj, event, kw);
|
|
28625
29081
|
return 0;
|
|
28626
29082
|
}
|
|
28627
29083
|
/************************************************************
|
|
@@ -29101,6 +29557,26 @@ function ac_show_record(gobj, event, kw, src) {
|
|
|
29101
29557
|
return 0;
|
|
29102
29558
|
}
|
|
29103
29559
|
/************************************************************
|
|
29560
|
+
* {index} -- the pencil of the Op column. Same shape as
|
|
29561
|
+
* EV_SHOW_RECORD: the kw carries WHICH row, and the row itself
|
|
29562
|
+
* is resolved here, where the table is.
|
|
29563
|
+
************************************************************/
|
|
29564
|
+
function ac_edit_record(gobj, event, kw, src) {
|
|
29565
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
29566
|
+
let index = kw ? kw.index : void 0;
|
|
29567
|
+
if (!tabulator || typeof index !== "number") {
|
|
29568
|
+
log_error(`${gobj_short_name(gobj)}: no row ${index} to edit`);
|
|
29569
|
+
return -1;
|
|
29570
|
+
}
|
|
29571
|
+
let row = tabulator.getRowFromPosition(index);
|
|
29572
|
+
if (!row) {
|
|
29573
|
+
log_error(`${gobj_short_name(gobj)}: row ${index} is gone`);
|
|
29574
|
+
return -1;
|
|
29575
|
+
}
|
|
29576
|
+
show_edit_form(gobj, row.getData());
|
|
29577
|
+
return 0;
|
|
29578
|
+
}
|
|
29579
|
+
/************************************************************
|
|
29104
29580
|
* The reader picked another page size: remember it, so coming back to
|
|
29105
29581
|
* this topic opens where they left it instead of at 200.
|
|
29106
29582
|
************************************************************/
|
|
@@ -29225,6 +29701,11 @@ function create_gclass$5(gclass_name) {
|
|
|
29225
29701
|
ac_form_save_record,
|
|
29226
29702
|
null
|
|
29227
29703
|
],
|
|
29704
|
+
[
|
|
29705
|
+
"EV_CLOSE_FORM",
|
|
29706
|
+
ac_close_form,
|
|
29707
|
+
null
|
|
29708
|
+
],
|
|
29228
29709
|
[
|
|
29229
29710
|
"EV_FILES_READ",
|
|
29230
29711
|
ac_files_read,
|
|
@@ -29245,6 +29726,11 @@ function create_gclass$5(gclass_name) {
|
|
|
29245
29726
|
ac_delete_rows,
|
|
29246
29727
|
null
|
|
29247
29728
|
],
|
|
29729
|
+
[
|
|
29730
|
+
"EV_CONFIRMED",
|
|
29731
|
+
ac_confirmed$2,
|
|
29732
|
+
null
|
|
29733
|
+
],
|
|
29248
29734
|
[
|
|
29249
29735
|
"EV_SELECT_ROWS",
|
|
29250
29736
|
ac_select_rows,
|
|
@@ -29370,6 +29856,11 @@ function create_gclass$5(gclass_name) {
|
|
|
29370
29856
|
ac_show_record,
|
|
29371
29857
|
null
|
|
29372
29858
|
],
|
|
29859
|
+
[
|
|
29860
|
+
"EV_EDIT_RECORD",
|
|
29861
|
+
ac_edit_record,
|
|
29862
|
+
null
|
|
29863
|
+
],
|
|
29373
29864
|
[
|
|
29374
29865
|
"EV_TOGGLE_COLUMN",
|
|
29375
29866
|
ac_toggle_column,
|
|
@@ -29399,10 +29890,12 @@ function create_gclass$5(gclass_name) {
|
|
|
29399
29890
|
["EV_NODE_DELETED", 0],
|
|
29400
29891
|
["EV_EDITION_MODE", 0],
|
|
29401
29892
|
["EV_SAVE_RECORD", 0],
|
|
29893
|
+
["EV_CLOSE_FORM", 0],
|
|
29402
29894
|
["EV_FILES_READ", 0],
|
|
29403
29895
|
["EV_FILES_FAILED", 0],
|
|
29404
29896
|
["EV_NEW_ROW", 0],
|
|
29405
29897
|
["EV_DELETE_ROWS", 0],
|
|
29898
|
+
["EV_CONFIRMED", 0],
|
|
29406
29899
|
["EV_COPY_ROWS", 0],
|
|
29407
29900
|
["EV_PASTE_ROWS", 0],
|
|
29408
29901
|
["EV_CLEAR_SELECTION", 0],
|
|
@@ -29432,6 +29925,7 @@ function create_gclass$5(gclass_name) {
|
|
|
29432
29925
|
["EV_OPEN_COLUMNS", 0],
|
|
29433
29926
|
["EV_PAGE_SIZE_CHANGED", 0],
|
|
29434
29927
|
["EV_SHOW_RECORD", 0],
|
|
29928
|
+
["EV_EDIT_RECORD", 0],
|
|
29435
29929
|
["EV_TOGGLE_COLUMN", 0],
|
|
29436
29930
|
["EV_EXPORT_TABLE", 0],
|
|
29437
29931
|
["EV_CREATE_RECORD", event_flag_t.EVF_OUTPUT_EVENT],
|
|
@@ -57965,13 +58459,38 @@ function build_graph$1(gobj) {
|
|
|
57965
58459
|
graph.setLayout(layout);
|
|
57966
58460
|
priv.graph_rendered = false;
|
|
57967
58461
|
graph_render(gobj).then(() => {
|
|
58462
|
+
if (gobj_is_destroying(gobj) || !priv.graph) return;
|
|
57968
58463
|
configure_events(gobj);
|
|
57969
58464
|
configure_behaviour(gobj);
|
|
57970
58465
|
configure_plugins(gobj);
|
|
57971
58466
|
priv.graph_rendered = true;
|
|
58467
|
+
}).catch((e) => {
|
|
58468
|
+
log_error(`${gobj_short_name(gobj)}: graph render failed: ${e}`);
|
|
57972
58469
|
});
|
|
57973
58470
|
}
|
|
57974
58471
|
/************************************************************
|
|
58472
|
+
* A G6 event, as PLAIN JSON.
|
|
58473
|
+
*
|
|
58474
|
+
* A kw is dumped by the `machine` trace (`trace_json(kw)`), and a
|
|
58475
|
+
* G6 / @antv/g event is circular: serializing one THROWS, so the
|
|
58476
|
+
* first thing a kw carrying `evt` breaks is the trace the FSM
|
|
58477
|
+
* exists to feed. It also drags a live graph element into a
|
|
58478
|
+
* message that may be read after that element is gone.
|
|
58479
|
+
*
|
|
58480
|
+
* What the actions read of it is an id and three scalars. The
|
|
58481
|
+
* event itself never leaves the callback that received it.
|
|
58482
|
+
************************************************************/
|
|
58483
|
+
function g6_event_kw(evt) {
|
|
58484
|
+
let target = evt && evt.target ? evt.target : null;
|
|
58485
|
+
let client = evt && evt.client ? evt.client : null;
|
|
58486
|
+
return {
|
|
58487
|
+
id: target && target.id ? String(target.id) : "",
|
|
58488
|
+
shift: !!(evt && evt.shiftKey),
|
|
58489
|
+
client_x: client && typeof client.x === "number" ? client.x : 0,
|
|
58490
|
+
client_y: client && typeof client.y === "number" ? client.y : 0
|
|
58491
|
+
};
|
|
58492
|
+
}
|
|
58493
|
+
/************************************************************
|
|
57975
58494
|
* Configure G6 event handlers
|
|
57976
58495
|
************************************************************/
|
|
57977
58496
|
function configure_events(gobj) {
|
|
@@ -57979,7 +58498,7 @@ function configure_events(gobj) {
|
|
|
57979
58498
|
let graph = priv.graph;
|
|
57980
58499
|
graph.on(CanvasEvent.CLICK, (evt) => {
|
|
57981
58500
|
if (consume_long_press_click(graph)) return;
|
|
57982
|
-
gobj_send_event(gobj, "EV_CANVAS_CLICK",
|
|
58501
|
+
gobj_send_event(gobj, "EV_CANVAS_CLICK", g6_event_kw(evt), gobj);
|
|
57983
58502
|
});
|
|
57984
58503
|
graph.on(NodeEvent.DRAG, (evt) => {
|
|
57985
58504
|
update_resize_handles_position(gobj);
|
|
@@ -57990,21 +58509,21 @@ function configure_events(gobj) {
|
|
|
57990
58509
|
update_port_icon_position(gobj);
|
|
57991
58510
|
});
|
|
57992
58511
|
graph.on(NodeEvent.DRAG_END, (evt) => {
|
|
57993
|
-
gobj_send_event(gobj, "EV_NODE_DRAG_END",
|
|
58512
|
+
gobj_send_event(gobj, "EV_NODE_DRAG_END", g6_event_kw(evt), gobj);
|
|
57994
58513
|
});
|
|
57995
58514
|
graph.on(NodeEvent.CLICK, (evt) => {
|
|
57996
58515
|
if (consume_long_press_click(graph)) return;
|
|
57997
|
-
gobj_send_event(gobj, "EV_NODE_CLICK",
|
|
58516
|
+
gobj_send_event(gobj, "EV_NODE_CLICK", g6_event_kw(evt), gobj);
|
|
57998
58517
|
});
|
|
57999
58518
|
graph.on(NodeEvent.CONTEXT_MENU, (evt) => {
|
|
58000
|
-
gobj_send_event(gobj, "EV_NODE_CONTEXT_MENU",
|
|
58519
|
+
gobj_send_event(gobj, "EV_NODE_CONTEXT_MENU", g6_event_kw(evt), gobj);
|
|
58001
58520
|
});
|
|
58002
58521
|
graph.on(NodeEvent.DBLCLICK, (evt) => {
|
|
58003
|
-
gobj_send_event(gobj, "EV_NODE_DBLCLICK",
|
|
58522
|
+
gobj_send_event(gobj, "EV_NODE_DBLCLICK", g6_event_kw(evt), gobj);
|
|
58004
58523
|
});
|
|
58005
58524
|
graph.on(EdgeEvent.CLICK, (evt) => {
|
|
58006
58525
|
if (consume_long_press_click(graph)) return;
|
|
58007
|
-
gobj_send_event(gobj, "EV_EDGE_CLICK",
|
|
58526
|
+
gobj_send_event(gobj, "EV_EDGE_CLICK", g6_event_kw(evt), gobj);
|
|
58008
58527
|
});
|
|
58009
58528
|
graph.on("aftertransform", () => {
|
|
58010
58529
|
update_resize_handles_position(gobj);
|
|
@@ -58088,7 +58607,7 @@ function configure_plugins(gobj) {
|
|
|
58088
58607
|
graph_add_plugin(gobj, "contextmenu", {
|
|
58089
58608
|
trigger: "contextmenu",
|
|
58090
58609
|
onClick: (value) => {
|
|
58091
|
-
|
|
58610
|
+
gobj_send_event(gobj, "EV_CONTEXT_MENU_ITEM", { value: String(value) }, gobj);
|
|
58092
58611
|
},
|
|
58093
58612
|
getItems: (e) => {
|
|
58094
58613
|
return build_context_menu_items(gobj, e);
|
|
@@ -61804,7 +62323,7 @@ function show_edge_icon(gobj) {
|
|
|
61804
62323
|
if (!priv._selected_edge_id) return;
|
|
61805
62324
|
let mid = get_edge_viewport_midpoint(gobj, priv._selected_edge_id);
|
|
61806
62325
|
if (!mid) return;
|
|
61807
|
-
let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(), t("edge properties"), () => toggle_edge_popover(gobj), t("unlink"), () =>
|
|
62326
|
+
let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(), t("edge properties"), () => toggle_edge_popover(gobj), t("unlink"), () => gobj_send_event(gobj, "EV_REQUEST_UNLINK_EDGE", {}, gobj));
|
|
61808
62327
|
priv._edge_icon_el = icons.icon_el;
|
|
61809
62328
|
priv._edge_delete_el = icons.delete_el;
|
|
61810
62329
|
}
|
|
@@ -61959,7 +62478,7 @@ function show_node_icon(gobj) {
|
|
|
61959
62478
|
if (!priv._selected_node_id || priv._selected_port_key) return;
|
|
61960
62479
|
let rect = get_node_viewport_rect(gobj, priv._selected_node_id);
|
|
61961
62480
|
if (!rect) return;
|
|
61962
|
-
let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(), t("node properties"), () => toggle_node_popover(gobj), t("delete node"), () =>
|
|
62481
|
+
let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(), t("node properties"), () => toggle_node_popover(gobj), t("delete node"), () => gobj_send_event(gobj, "EV_REQUEST_DELETE_NODE", {}, gobj));
|
|
61963
62482
|
priv._node_icon_el = icons.icon_el;
|
|
61964
62483
|
priv._node_delete_el = icons.delete_el;
|
|
61965
62484
|
}
|
|
@@ -62672,7 +63191,10 @@ function request_delete_selection(gobj) {
|
|
|
62672
63191
|
execute_delete_selection(gobj, nodes);
|
|
62673
63192
|
return;
|
|
62674
63193
|
}
|
|
62675
|
-
show_confirm_popover(gobj, null, delete_question(nodes), "delete", "#ff4d4f", () =>
|
|
63194
|
+
show_confirm_popover(gobj, null, delete_question(nodes), "delete", "#ff4d4f", () => gobj_send_event(gobj, "EV_CONFIRMED", {
|
|
63195
|
+
what: "delete_selection",
|
|
63196
|
+
ids: nodes.map((nd) => String(nd.id))
|
|
63197
|
+
}, gobj), "_delete_confirm_el");
|
|
62676
63198
|
}
|
|
62677
63199
|
function execute_delete_selection(gobj, nodes) {
|
|
62678
63200
|
let priv = gobj.priv;
|
|
@@ -62689,7 +63211,10 @@ function execute_delete_selection(gobj, nodes) {
|
|
|
62689
63211
|
************************************************************/
|
|
62690
63212
|
function show_delete_confirm(gobj, nodeData) {
|
|
62691
63213
|
let priv = gobj.priv;
|
|
62692
|
-
show_confirm_popover(gobj, priv._node_delete_el, delete_question([nodeData]), "delete", "#ff4d4f", () =>
|
|
63214
|
+
show_confirm_popover(gobj, priv._node_delete_el, delete_question([nodeData]), "delete", "#ff4d4f", () => gobj_send_event(gobj, "EV_CONFIRMED", {
|
|
63215
|
+
what: "delete_node",
|
|
63216
|
+
id: String(nodeData.id)
|
|
63217
|
+
}, gobj), "_delete_confirm_el");
|
|
62693
63218
|
}
|
|
62694
63219
|
function hide_delete_confirm(gobj) {
|
|
62695
63220
|
hide_overlay(gobj, "_delete_confirm_el");
|
|
@@ -62787,7 +63312,10 @@ function show_create_popover(gobj) {
|
|
|
62787
63312
|
idInput.focus();
|
|
62788
63313
|
return;
|
|
62789
63314
|
}
|
|
62790
|
-
|
|
63315
|
+
gobj_send_event(gobj, "EV_SUBMIT_NEW_NODE", {
|
|
63316
|
+
topic_name: String(topicSelect.value),
|
|
63317
|
+
id: node_id
|
|
63318
|
+
}, gobj);
|
|
62791
63319
|
}
|
|
62792
63320
|
}]);
|
|
62793
63321
|
createBtn = popover.querySelector("button:last-child");
|
|
@@ -62847,7 +63375,10 @@ function execute_unlink_edge(gobj, edgeData) {
|
|
|
62847
63375
|
function show_unlink_confirm(gobj, edgeData) {
|
|
62848
63376
|
let priv = gobj.priv;
|
|
62849
63377
|
const d = edgeData.data;
|
|
62850
|
-
show_confirm_popover(gobj, priv._edge_delete_el, t("unlink") + " " + d.child_id + " → " + d.parent_id + "?\n" + t("neither record is deleted"), "unlink", "#ff4d4f", () =>
|
|
63378
|
+
show_confirm_popover(gobj, priv._edge_delete_el, t("unlink") + " " + d.child_id + " → " + d.parent_id + "?\n" + t("neither record is deleted"), "unlink", "#ff4d4f", () => gobj_send_event(gobj, "EV_CONFIRMED", {
|
|
63379
|
+
what: "unlink_edge",
|
|
63380
|
+
id: String(edgeData.id)
|
|
63381
|
+
}, gobj), "_unlink_confirm_el");
|
|
62851
63382
|
}
|
|
62852
63383
|
function hide_unlink_confirm(gobj) {
|
|
62853
63384
|
hide_overlay(gobj, "_unlink_confirm_el");
|
|
@@ -64730,7 +65261,7 @@ function ac_toggle_node_mode(gobj, event, kw, src) {
|
|
|
64730
65261
|
************************************************************/
|
|
64731
65262
|
function ac_node_dblclick(gobj, event, kw, src) {
|
|
64732
65263
|
let priv = gobj.priv;
|
|
64733
|
-
let node_id = kw && kw.
|
|
65264
|
+
let node_id = kw && kw.id ? kw.id : "";
|
|
64734
65265
|
let nd = null;
|
|
64735
65266
|
try {
|
|
64736
65267
|
nd = priv.graph.getNodeData(node_id);
|
|
@@ -64926,7 +65457,7 @@ function ac_brush_select(gobj, event, kw, src) {
|
|
|
64926
65457
|
function ac_node_click$2(gobj, event, kw, src) {
|
|
64927
65458
|
let priv = gobj.priv;
|
|
64928
65459
|
let graph = priv.graph;
|
|
64929
|
-
let node_id = kw.
|
|
65460
|
+
let node_id = kw && kw.id ? kw.id : "";
|
|
64930
65461
|
let more_nd = null;
|
|
64931
65462
|
try {
|
|
64932
65463
|
more_nd = graph.getNodeData(node_id);
|
|
@@ -64953,10 +65484,10 @@ function ac_node_click$2(gobj, event, kw, src) {
|
|
|
64953
65484
|
topic_name: nodedata.data.desc.topic_name,
|
|
64954
65485
|
record: nodedata.data.record
|
|
64955
65486
|
});
|
|
64956
|
-
if (priv.edit_mode && (kw.
|
|
65487
|
+
if (priv.edit_mode && (kw.shift || priv.selection_mode)) toggle_in_selection(gobj, node_id);
|
|
64957
65488
|
else if (priv.edit_mode) {
|
|
64958
65489
|
let containerRect = priv.$container.getBoundingClientRect();
|
|
64959
|
-
let canvasPoint = graph.getCanvasByViewport([kw.
|
|
65490
|
+
let canvasPoint = graph.getCanvasByViewport([kw.client_x - containerRect.left, kw.client_y - containerRect.top]);
|
|
64960
65491
|
let port_key = detect_port_click(gobj, node_id, canvasPoint[0], canvasPoint[1]);
|
|
64961
65492
|
if (priv._linking_mode) {
|
|
64962
65493
|
if (port_key && try_complete_link(gobj, node_id, port_key)) return 0;
|
|
@@ -64979,7 +65510,7 @@ function ac_node_click$2(gobj, event, kw, src) {
|
|
|
64979
65510
|
function ac_edge_click(gobj, event, kw, src) {
|
|
64980
65511
|
let priv = gobj.priv;
|
|
64981
65512
|
let graph = priv.graph;
|
|
64982
|
-
let edge_id = kw.
|
|
65513
|
+
let edge_id = kw && kw.id ? kw.id : "";
|
|
64983
65514
|
try {
|
|
64984
65515
|
let edgedata = graph.getEdgeData(edge_id);
|
|
64985
65516
|
if (edgedata && edgedata.data) {
|
|
@@ -65008,6 +65539,106 @@ function ac_node_context_menu(gobj, event, kw, src) {
|
|
|
65008
65539
|
return 0;
|
|
65009
65540
|
}
|
|
65010
65541
|
/************************************************************
|
|
65542
|
+
* {value} -- an item of the context menu was picked.
|
|
65543
|
+
************************************************************/
|
|
65544
|
+
function ac_context_menu_item(gobj, event, kw, src) {
|
|
65545
|
+
handle_context_menu_click(gobj, kw && kw.value ? kw.value : "");
|
|
65546
|
+
return 0;
|
|
65547
|
+
}
|
|
65548
|
+
/************************************************************
|
|
65549
|
+
* The delete icon floating beside the selected node.
|
|
65550
|
+
************************************************************/
|
|
65551
|
+
function ac_request_delete_node(gobj, event, kw, src) {
|
|
65552
|
+
request_delete_node(gobj);
|
|
65553
|
+
return 0;
|
|
65554
|
+
}
|
|
65555
|
+
/************************************************************
|
|
65556
|
+
* The unlink icon floating beside the selected edge.
|
|
65557
|
+
************************************************************/
|
|
65558
|
+
function ac_request_unlink_edge(gobj, event, kw, src) {
|
|
65559
|
+
request_unlink_edge(gobj);
|
|
65560
|
+
return 0;
|
|
65561
|
+
}
|
|
65562
|
+
/************************************************************
|
|
65563
|
+
* {topic_name, id} -- the create popover was filled in.
|
|
65564
|
+
************************************************************/
|
|
65565
|
+
function ac_submit_new_node(gobj, event, kw, src) {
|
|
65566
|
+
let topic_name = kw && kw.topic_name ? kw.topic_name : "";
|
|
65567
|
+
let node_id = kw && kw.id ? kw.id : "";
|
|
65568
|
+
if (!topic_name || !node_id) {
|
|
65569
|
+
log_error(`${gobj_short_name(gobj)}: ${event} without topic or id`);
|
|
65570
|
+
return -1;
|
|
65571
|
+
}
|
|
65572
|
+
execute_create_node(gobj, topic_name, node_id);
|
|
65573
|
+
return 0;
|
|
65574
|
+
}
|
|
65575
|
+
/************************************************************
|
|
65576
|
+
* {what, id|ids} -- the person said yes in a confirm popover.
|
|
65577
|
+
*
|
|
65578
|
+
* The kw carries IDS and not the graph elements they name: a kw
|
|
65579
|
+
* is plain json, and a node/edge datum of a live graph is not
|
|
65580
|
+
* something to park in a message. They are resolved here, where
|
|
65581
|
+
* the graph is -- and an id that no longer names anything is a
|
|
65582
|
+
* loud refusal instead of a silent nothing.
|
|
65583
|
+
************************************************************/
|
|
65584
|
+
function ac_confirmed$1(gobj, event, kw, src) {
|
|
65585
|
+
let graph = gobj.priv.graph;
|
|
65586
|
+
let what = kw && kw.what ? kw.what : "";
|
|
65587
|
+
if (!graph) {
|
|
65588
|
+
log_error(`${gobj_short_name(gobj)}: ${event} with no graph`);
|
|
65589
|
+
return -1;
|
|
65590
|
+
}
|
|
65591
|
+
if (what === "delete_selection") {
|
|
65592
|
+
let ids = kw && Array.isArray(kw.ids) ? kw.ids : [];
|
|
65593
|
+
let nodes = [];
|
|
65594
|
+
for (let id of ids) {
|
|
65595
|
+
let nd = null;
|
|
65596
|
+
try {
|
|
65597
|
+
nd = graph.getNodeData(id);
|
|
65598
|
+
} catch (e) {
|
|
65599
|
+
nd = null;
|
|
65600
|
+
}
|
|
65601
|
+
if (nd && nd.data && nd.data.desc) nodes.push(nd);
|
|
65602
|
+
}
|
|
65603
|
+
if (!nodes.length) {
|
|
65604
|
+
log_error(`${gobj_short_name(gobj)}: ${event} names no node any more`);
|
|
65605
|
+
return -1;
|
|
65606
|
+
}
|
|
65607
|
+
execute_delete_selection(gobj, nodes);
|
|
65608
|
+
return 0;
|
|
65609
|
+
}
|
|
65610
|
+
if (what === "delete_node") {
|
|
65611
|
+
let nd = null;
|
|
65612
|
+
try {
|
|
65613
|
+
nd = graph.getNodeData(kw.id);
|
|
65614
|
+
} catch (e) {
|
|
65615
|
+
nd = null;
|
|
65616
|
+
}
|
|
65617
|
+
if (!nd || !nd.data || !nd.data.desc) {
|
|
65618
|
+
log_error(`${gobj_short_name(gobj)}: ${event} names no node: ${kw.id}`);
|
|
65619
|
+
return -1;
|
|
65620
|
+
}
|
|
65621
|
+
execute_delete_node(gobj, nd);
|
|
65622
|
+
return 0;
|
|
65623
|
+
}
|
|
65624
|
+
if (what === "unlink_edge") {
|
|
65625
|
+
let ed = null;
|
|
65626
|
+
try {
|
|
65627
|
+
ed = graph.getEdgeData(kw.id);
|
|
65628
|
+
} catch (e) {
|
|
65629
|
+
ed = null;
|
|
65630
|
+
}
|
|
65631
|
+
if (!ed || !ed.data) {
|
|
65632
|
+
log_error(`${gobj_short_name(gobj)}: ${event} names no edge: ${kw.id}`);
|
|
65633
|
+
return -1;
|
|
65634
|
+
}
|
|
65635
|
+
execute_unlink_edge(gobj, ed);
|
|
65636
|
+
return 0;
|
|
65637
|
+
}
|
|
65638
|
+
log_error(`${gobj_short_name(gobj)}: ${event} of nothing this view asked about: ${what}`);
|
|
65639
|
+
return -1;
|
|
65640
|
+
}
|
|
65641
|
+
/************************************************************
|
|
65011
65642
|
* Canvas click
|
|
65012
65643
|
************************************************************/
|
|
65013
65644
|
function ac_canvas_click(gobj, event, kw, src) {
|
|
@@ -65171,6 +65802,31 @@ function create_gclass$4(gclass_name) {
|
|
|
65171
65802
|
ac_node_context_menu,
|
|
65172
65803
|
null
|
|
65173
65804
|
],
|
|
65805
|
+
[
|
|
65806
|
+
"EV_CONTEXT_MENU_ITEM",
|
|
65807
|
+
ac_context_menu_item,
|
|
65808
|
+
null
|
|
65809
|
+
],
|
|
65810
|
+
[
|
|
65811
|
+
"EV_REQUEST_DELETE_NODE",
|
|
65812
|
+
ac_request_delete_node,
|
|
65813
|
+
null
|
|
65814
|
+
],
|
|
65815
|
+
[
|
|
65816
|
+
"EV_REQUEST_UNLINK_EDGE",
|
|
65817
|
+
ac_request_unlink_edge,
|
|
65818
|
+
null
|
|
65819
|
+
],
|
|
65820
|
+
[
|
|
65821
|
+
"EV_SUBMIT_NEW_NODE",
|
|
65822
|
+
ac_submit_new_node,
|
|
65823
|
+
null
|
|
65824
|
+
],
|
|
65825
|
+
[
|
|
65826
|
+
"EV_CONFIRMED",
|
|
65827
|
+
ac_confirmed$1,
|
|
65828
|
+
null
|
|
65829
|
+
],
|
|
65174
65830
|
[
|
|
65175
65831
|
"EV_CANVAS_CLICK",
|
|
65176
65832
|
ac_canvas_click,
|
|
@@ -65387,6 +66043,11 @@ function create_gclass$4(gclass_name) {
|
|
|
65387
66043
|
["EV_NODE_CLICK", 0],
|
|
65388
66044
|
["EV_EDGE_CLICK", 0],
|
|
65389
66045
|
["EV_NODE_CONTEXT_MENU", 0],
|
|
66046
|
+
["EV_CONTEXT_MENU_ITEM", 0],
|
|
66047
|
+
["EV_REQUEST_DELETE_NODE", 0],
|
|
66048
|
+
["EV_REQUEST_UNLINK_EDGE", 0],
|
|
66049
|
+
["EV_SUBMIT_NEW_NODE", 0],
|
|
66050
|
+
["EV_CONFIRMED", 0],
|
|
65390
66051
|
["EV_CANVAS_CLICK", 0],
|
|
65391
66052
|
["EV_NODE_DRAG_END", 0],
|
|
65392
66053
|
["EV_BRUSH_SELECT", 0],
|
|
@@ -68720,361 +69381,6 @@ function register_c_yui_treedb_schema() {
|
|
|
68720
69381
|
return create_gclass$2(GCLASS_NAME$2);
|
|
68721
69382
|
}
|
|
68722
69383
|
//#endregion
|
|
68723
|
-
//#region src/schema_model.js
|
|
68724
|
-
/***********************************************************************
|
|
68725
|
-
* schema_model.js
|
|
68726
|
-
*
|
|
68727
|
-
* A SCHEMA out of the three meta-topics that store it.
|
|
68728
|
-
*
|
|
68729
|
-
* `treedb_system_schema` keeps every schema of a yuno as DATA, in
|
|
68730
|
-
* three flat topics — `treedbs`, `topics`, `cols` — linked by
|
|
68731
|
-
* fkeys. That is the right STORAGE and the wrong thing to put in
|
|
68732
|
-
* front of an operator: adding one column to one topic means
|
|
68733
|
-
* finding it in a table holding every column of every topic of
|
|
68734
|
-
* every treedb the yuno has.
|
|
68735
|
-
*
|
|
68736
|
-
* This module turns those three lists back into what they
|
|
68737
|
-
* describe: treedb -> topics -> columns, each in its declared
|
|
68738
|
-
* `order`. Pure, so the editor's model can be tested with no DOM
|
|
68739
|
-
* and no backend — the gclass fetches and draws, this decides
|
|
68740
|
-
* what the records MEAN.
|
|
68741
|
-
*
|
|
68742
|
-
* TWO THINGS IT EXISTS TO STATE ONCE:
|
|
68743
|
-
*
|
|
68744
|
-
* 1. A record is grouped by its FKEY, not by its id. The id is
|
|
68745
|
-
* qualified (`<treedb>.<topic>.<column>`) and reading it back
|
|
68746
|
-
* with a split on '.' works right up to the first treedb or
|
|
68747
|
-
* topic whose name carries one. The fkey is the link the store
|
|
68748
|
-
* itself uses; the id is only the fallback for a record whose
|
|
68749
|
-
* fkey never arrived.
|
|
68750
|
-
*
|
|
68751
|
-
* 2. A fkey reaches this code in FOUR shapes, because `nodes`
|
|
68752
|
-
* answers differently depending on the options it was called
|
|
68753
|
-
* with: the bare ref string, the expanded object, a list of
|
|
68754
|
-
* either, or a dict keyed by the ref. All four are the same
|
|
68755
|
-
* fact and parse_fkey_ref() flattens them to it.
|
|
68756
|
-
*
|
|
68757
|
-
* Copyright (c) 2026, ArtGins.
|
|
68758
|
-
* All Rights Reserved.
|
|
68759
|
-
***********************************************************************/
|
|
68760
|
-
var DEFAULT_ORDER = 9999;
|
|
68761
|
-
/***************************************************************
|
|
68762
|
-
* parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
|
|
68763
|
-
*
|
|
68764
|
-
* Every shape a fkey column can arrive in, flattened to the
|
|
68765
|
-
* refs it names. Anything unrecognizable contributes nothing —
|
|
68766
|
-
* a schema drawn without one link beats a screen that throws.
|
|
68767
|
-
***************************************************************/
|
|
68768
|
-
function parse_fkey_ref(value) {
|
|
68769
|
-
let refs = [];
|
|
68770
|
-
let push_one = (v) => {
|
|
68771
|
-
if (typeof v === "string") {
|
|
68772
|
-
let parts = v.split("^");
|
|
68773
|
-
if (parts.length !== 3) return;
|
|
68774
|
-
refs.push({
|
|
68775
|
-
topic_name: parts[0],
|
|
68776
|
-
id: parts[1],
|
|
68777
|
-
hook_name: parts[2]
|
|
68778
|
-
});
|
|
68779
|
-
return;
|
|
68780
|
-
}
|
|
68781
|
-
if (v && typeof v === "object") {
|
|
68782
|
-
let id = v.id;
|
|
68783
|
-
if (typeof id !== "string" || id.length === 0) return;
|
|
68784
|
-
refs.push({
|
|
68785
|
-
topic_name: v.topic_name || "",
|
|
68786
|
-
id,
|
|
68787
|
-
hook_name: v.hook_name || ""
|
|
68788
|
-
});
|
|
68789
|
-
}
|
|
68790
|
-
};
|
|
68791
|
-
if (value === null || value === void 0) return refs;
|
|
68792
|
-
if (typeof value === "string") {
|
|
68793
|
-
push_one(value);
|
|
68794
|
-
return refs;
|
|
68795
|
-
}
|
|
68796
|
-
if (Array.isArray(value)) {
|
|
68797
|
-
for (let v of value) push_one(v);
|
|
68798
|
-
return refs;
|
|
68799
|
-
}
|
|
68800
|
-
if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
|
|
68801
|
-
else push_one(key);
|
|
68802
|
-
return refs;
|
|
68803
|
-
}
|
|
68804
|
-
/***************************************************************
|
|
68805
|
-
* The id of the FIRST parent a record names, or "" when it
|
|
68806
|
-
* names none. A record has one parent here — a column belongs to
|
|
68807
|
-
* one topic, a topic to one treedb — and a second ref would be a
|
|
68808
|
-
* store this editor cannot draw anyway.
|
|
68809
|
-
***************************************************************/
|
|
68810
|
-
function parent_id(record, fkey_col) {
|
|
68811
|
-
let refs = parse_fkey_ref(record ? record[fkey_col] : null);
|
|
68812
|
-
return refs.length > 0 ? refs[0].id : "";
|
|
68813
|
-
}
|
|
68814
|
-
/***************************************************************
|
|
68815
|
-
* The name a record is KNOWN by, which is not what it is keyed
|
|
68816
|
-
* by: `topics` and `cols` key by the qualified id and carry the
|
|
68817
|
-
* bare name in `value` (its pkey2). An older store that still
|
|
68818
|
-
* keyed by rowid has no `value` worth showing, so the id stands
|
|
68819
|
-
* in — the same fallback as node_label().
|
|
68820
|
-
***************************************************************/
|
|
68821
|
-
function record_name(record) {
|
|
68822
|
-
if (!record) return "";
|
|
68823
|
-
if (typeof record.value === "string" && record.value.length > 0) return record.value;
|
|
68824
|
-
return typeof record.id === "string" ? record.id : "";
|
|
68825
|
-
}
|
|
68826
|
-
/***************************************************************
|
|
68827
|
-
* The `order` of a record as a NUMBER, whatever the store put
|
|
68828
|
-
* there: an integer column read back from JSON may arrive as a
|
|
68829
|
-
* string, and comparing "10" with 9 sorts the schema wrong.
|
|
68830
|
-
***************************************************************/
|
|
68831
|
-
function record_order(record) {
|
|
68832
|
-
let order = record ? record.order : void 0;
|
|
68833
|
-
if (typeof order === "number" && isFinite(order)) return order;
|
|
68834
|
-
if (typeof order === "string" && order.trim().length > 0) {
|
|
68835
|
-
let n = Number(order);
|
|
68836
|
-
if (isFinite(n)) return n;
|
|
68837
|
-
}
|
|
68838
|
-
return DEFAULT_ORDER;
|
|
68839
|
-
}
|
|
68840
|
-
/***************************************************************
|
|
68841
|
-
* Sort in place by `order`, ties broken by name so a schema
|
|
68842
|
-
* whose records all default to 9999 still draws the same way
|
|
68843
|
-
* twice.
|
|
68844
|
-
***************************************************************/
|
|
68845
|
-
function sort_by_order(list) {
|
|
68846
|
-
list.sort((a, b) => {
|
|
68847
|
-
if (a.order !== b.order) return a.order - b.order;
|
|
68848
|
-
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
68849
|
-
});
|
|
68850
|
-
return list;
|
|
68851
|
-
}
|
|
68852
|
-
/***************************************************************
|
|
68853
|
-
* build_schema_model({treedbs, topics, cols}) -> model
|
|
68854
|
-
*
|
|
68855
|
-
* model = {
|
|
68856
|
-
* treedbs: [{
|
|
68857
|
-
* id, name, record,
|
|
68858
|
-
* schema_version, c_schema_version, system_schema_version,
|
|
68859
|
-
* topics: [{
|
|
68860
|
-
* id, name, order, record,
|
|
68861
|
-
* pkey, topic_version, system_topic, main_topic,
|
|
68862
|
-
* cols: [{id, name, order, record}]
|
|
68863
|
-
* }]
|
|
68864
|
-
* }],
|
|
68865
|
-
* orphan_topics: [...], topics whose treedb is not in the store
|
|
68866
|
-
* orphan_cols: [...] columns whose topic is not in the store
|
|
68867
|
-
* }
|
|
68868
|
-
*
|
|
68869
|
-
* An orphan is NOT dropped. A column whose topic was deleted is
|
|
68870
|
-
* exactly the kind of leftover an operator opens this editor to
|
|
68871
|
-
* find, and a model that hides it makes the editor lie.
|
|
68872
|
-
***************************************************************/
|
|
68873
|
-
function build_schema_model(records) {
|
|
68874
|
-
let r = records || {};
|
|
68875
|
-
let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
|
|
68876
|
-
let topic_rows = Array.isArray(r.topics) ? r.topics : [];
|
|
68877
|
-
let col_rows = Array.isArray(r.cols) ? r.cols : [];
|
|
68878
|
-
let treedbs = [];
|
|
68879
|
-
let by_treedb_id = {};
|
|
68880
|
-
for (let record of treedb_rows) {
|
|
68881
|
-
if (!record || typeof record.id !== "string") continue;
|
|
68882
|
-
let entry = {
|
|
68883
|
-
id: record.id,
|
|
68884
|
-
name: record.id,
|
|
68885
|
-
record,
|
|
68886
|
-
schema_version: record.schema_version,
|
|
68887
|
-
c_schema_version: record.c_schema_version,
|
|
68888
|
-
system_schema_version: record.system_schema_version,
|
|
68889
|
-
topics: []
|
|
68890
|
-
};
|
|
68891
|
-
treedbs.push(entry);
|
|
68892
|
-
by_treedb_id[entry.id] = entry;
|
|
68893
|
-
}
|
|
68894
|
-
let orphan_topics = [];
|
|
68895
|
-
let by_topic_id = {};
|
|
68896
|
-
for (let record of topic_rows) {
|
|
68897
|
-
if (!record || typeof record.id !== "string") continue;
|
|
68898
|
-
let entry = {
|
|
68899
|
-
id: record.id,
|
|
68900
|
-
name: record_name(record),
|
|
68901
|
-
order: record_order(record),
|
|
68902
|
-
record,
|
|
68903
|
-
treedb_id: parent_id(record, "treedbs"),
|
|
68904
|
-
pkey: record.pkey || "",
|
|
68905
|
-
pkey2s: record.pkey2s,
|
|
68906
|
-
system_flag: record.system_flag || "",
|
|
68907
|
-
tkey: record.tkey || "",
|
|
68908
|
-
topic_version: record.topic_version,
|
|
68909
|
-
system_topic: !!record.system_topic,
|
|
68910
|
-
main_topic: !!record.main_topic,
|
|
68911
|
-
cols: []
|
|
68912
|
-
};
|
|
68913
|
-
by_topic_id[entry.id] = entry;
|
|
68914
|
-
let parent = by_treedb_id[entry.treedb_id];
|
|
68915
|
-
if (parent) parent.topics.push(entry);
|
|
68916
|
-
else orphan_topics.push(entry);
|
|
68917
|
-
}
|
|
68918
|
-
let orphan_cols = [];
|
|
68919
|
-
for (let record of col_rows) {
|
|
68920
|
-
if (!record || typeof record.id !== "string") continue;
|
|
68921
|
-
let entry = {
|
|
68922
|
-
id: record.id,
|
|
68923
|
-
name: record_name(record),
|
|
68924
|
-
order: record_order(record),
|
|
68925
|
-
record,
|
|
68926
|
-
topic_id: parent_id(record, "topics")
|
|
68927
|
-
};
|
|
68928
|
-
let parent = by_topic_id[entry.topic_id];
|
|
68929
|
-
if (parent) parent.cols.push(entry);
|
|
68930
|
-
else orphan_cols.push(entry);
|
|
68931
|
-
}
|
|
68932
|
-
for (let treedb of treedbs) {
|
|
68933
|
-
sort_by_order(treedb.topics);
|
|
68934
|
-
for (let topic of treedb.topics) sort_by_order(topic.cols);
|
|
68935
|
-
}
|
|
68936
|
-
for (let topic of orphan_topics) sort_by_order(topic.cols);
|
|
68937
|
-
treedbs.sort((a, b) => {
|
|
68938
|
-
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
68939
|
-
});
|
|
68940
|
-
sort_by_order(orphan_topics);
|
|
68941
|
-
sort_by_order(orphan_cols);
|
|
68942
|
-
return {
|
|
68943
|
-
treedbs,
|
|
68944
|
-
orphan_topics,
|
|
68945
|
-
orphan_cols
|
|
68946
|
-
};
|
|
68947
|
-
}
|
|
68948
|
-
/***************************************************************
|
|
68949
|
-
* Is there a value here at all?
|
|
68950
|
-
*
|
|
68951
|
-
* The store answers a `blob` column that was never set with an
|
|
68952
|
-
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
68953
|
-
* `default: {}`. Read as "a value", those turn every export into
|
|
68954
|
-
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
68955
|
-
* is a hook with no mapping, which is a link the treedb builds
|
|
68956
|
-
* and nothing writes.
|
|
68957
|
-
***************************************************************/
|
|
68958
|
-
function is_empty_value(value) {
|
|
68959
|
-
if (value === null || value === void 0 || value === "") return true;
|
|
68960
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
68961
|
-
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
68962
|
-
return false;
|
|
68963
|
-
}
|
|
68964
|
-
/***************************************************************
|
|
68965
|
-
* The fields of a column that are DECLARED as one thing and
|
|
68966
|
-
* STORED as another.
|
|
68967
|
-
*
|
|
68968
|
-
* `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
|
|
68969
|
-
* columns of the `cols` topic (treedb_system_schema.c), so what
|
|
68970
|
-
* comes back may be the value or the JSON text of the value,
|
|
68971
|
-
* depending on how it was written — a projection writes the
|
|
68972
|
-
* value, a hand edit through a plain text field writes the text.
|
|
68973
|
-
* `flag` is an array that a single-flag edit can leave as a bare
|
|
68974
|
-
* string. Reading these without saying so is how a hook drawn
|
|
68975
|
-
* from `{"users":"department"}` becomes no hook at all.
|
|
68976
|
-
***************************************************************/
|
|
68977
|
-
function as_json(value) {
|
|
68978
|
-
if (typeof value !== "string") return value;
|
|
68979
|
-
let text = value.trim();
|
|
68980
|
-
if (text.length === 0) return null;
|
|
68981
|
-
try {
|
|
68982
|
-
return JSON.parse(text);
|
|
68983
|
-
} catch (e) {
|
|
68984
|
-
return value;
|
|
68985
|
-
}
|
|
68986
|
-
}
|
|
68987
|
-
function col_flags(record) {
|
|
68988
|
-
let flag = record ? record.flag : null;
|
|
68989
|
-
if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
68990
|
-
if (typeof flag === "string" && flag.length > 0) {
|
|
68991
|
-
let parsed = as_json(flag);
|
|
68992
|
-
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
|
|
68993
|
-
return [flag];
|
|
68994
|
-
}
|
|
68995
|
-
return [];
|
|
68996
|
-
}
|
|
68997
|
-
function col_hook(record) {
|
|
68998
|
-
let hook = as_json(record ? record.hook : null);
|
|
68999
|
-
if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
|
|
69000
|
-
return null;
|
|
69001
|
-
}
|
|
69002
|
-
function col_enum(record) {
|
|
69003
|
-
let e = as_json(record ? record.enum : null);
|
|
69004
|
-
if (Array.isArray(e)) return e;
|
|
69005
|
-
return [];
|
|
69006
|
-
}
|
|
69007
|
-
function topic_pkey2s(record) {
|
|
69008
|
-
let pkey2s = as_json(record ? record.pkey2s : null);
|
|
69009
|
-
if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
|
|
69010
|
-
if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
|
|
69011
|
-
return [];
|
|
69012
|
-
}
|
|
69013
|
-
/***************************************************************
|
|
69014
|
-
* Lookups by id. The editor addresses a position by NAME (the
|
|
69015
|
-
* url carries `<treedb>/<topic>`), so both are offered.
|
|
69016
|
-
***************************************************************/
|
|
69017
|
-
function find_treedb(model, treedb_id) {
|
|
69018
|
-
if (!model || !Array.isArray(model.treedbs)) return null;
|
|
69019
|
-
for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
|
|
69020
|
-
return null;
|
|
69021
|
-
}
|
|
69022
|
-
function find_topic(model, treedb_id, topic_name) {
|
|
69023
|
-
let treedb = find_treedb(model, treedb_id);
|
|
69024
|
-
if (!treedb) return null;
|
|
69025
|
-
for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
|
|
69026
|
-
return null;
|
|
69027
|
-
}
|
|
69028
|
-
function find_col(model, treedb_id, topic_name, col_name) {
|
|
69029
|
-
let topic = find_topic(model, treedb_id, topic_name);
|
|
69030
|
-
if (!topic) return null;
|
|
69031
|
-
for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
|
|
69032
|
-
return null;
|
|
69033
|
-
}
|
|
69034
|
-
/***************************************************************
|
|
69035
|
-
* The fkey a NEW record must carry to belong to `parent_id`.
|
|
69036
|
-
* Written here because the editor is the only writer that has to
|
|
69037
|
-
* compose one, and getting the hook name wrong links the record
|
|
69038
|
-
* to nothing while the write still succeeds.
|
|
69039
|
-
***************************************************************/
|
|
69040
|
-
function fkey_ref(parent_topic, parent_id_, hook_name) {
|
|
69041
|
-
return `${parent_topic}^${parent_id_}^${hook_name}`;
|
|
69042
|
-
}
|
|
69043
|
-
/***************************************************************
|
|
69044
|
-
* Where a new sibling goes: after the last one. The store's own
|
|
69045
|
-
* default (9999) would put every hand-made column in the same
|
|
69046
|
-
* place and let their names decide the schema's order.
|
|
69047
|
-
***************************************************************/
|
|
69048
|
-
function next_order(siblings) {
|
|
69049
|
-
let max = 0;
|
|
69050
|
-
if (Array.isArray(siblings)) for (let s of siblings) {
|
|
69051
|
-
let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
|
|
69052
|
-
if (order < 9999 && order > max) max = order;
|
|
69053
|
-
}
|
|
69054
|
-
return max + 1;
|
|
69055
|
-
}
|
|
69056
|
-
/***************************************************************
|
|
69057
|
-
* Renumber a list AFTER a move, as the writes it takes: only the
|
|
69058
|
-
* records whose order actually changed, so a drag of one row in a
|
|
69059
|
-
* 40-column topic is not 40 writes.
|
|
69060
|
-
*
|
|
69061
|
-
* moved_orders(list) -> [{id, order}, ...]
|
|
69062
|
-
***************************************************************/
|
|
69063
|
-
function moved_orders(list) {
|
|
69064
|
-
let writes = [];
|
|
69065
|
-
if (!Array.isArray(list)) return writes;
|
|
69066
|
-
for (let i = 0; i < list.length; i++) {
|
|
69067
|
-
let entry = list[i];
|
|
69068
|
-
let order = i + 1;
|
|
69069
|
-
if (!entry || typeof entry.id !== "string") continue;
|
|
69070
|
-
if (entry.order !== order) writes.push({
|
|
69071
|
-
id: entry.id,
|
|
69072
|
-
order
|
|
69073
|
-
});
|
|
69074
|
-
}
|
|
69075
|
-
return writes;
|
|
69076
|
-
}
|
|
69077
|
-
//#endregion
|
|
69078
69384
|
//#region src/schema_validate.js
|
|
69079
69385
|
/***********************************************************************
|
|
69080
69386
|
* schema_validate.js
|