@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.cjs.js
CHANGED
|
@@ -25563,49 +25563,65 @@ function yui_icon_is_defined(name) {
|
|
|
25563
25563
|
/***********************************************************************
|
|
25564
25564
|
* yui_row_search.js
|
|
25565
25565
|
*
|
|
25566
|
-
*
|
|
25567
|
-
*
|
|
25568
|
-
*
|
|
25569
|
-
*
|
|
25570
|
-
*
|
|
25571
|
-
* `[
|
|
25572
|
-
*
|
|
25573
|
-
*
|
|
25574
|
-
*
|
|
25575
|
-
*
|
|
25576
|
-
*
|
|
25577
|
-
*
|
|
25578
|
-
*
|
|
25579
|
-
*
|
|
25580
|
-
*
|
|
25581
|
-
*
|
|
25582
|
-
*
|
|
25583
|
-
*
|
|
25584
|
-
*
|
|
25585
|
-
*
|
|
25586
|
-
*
|
|
25587
|
-
*
|
|
25588
|
-
*
|
|
25589
|
-
*
|
|
25590
|
-
*
|
|
25566
|
+
* IS THIS TERM IN THIS ROW?
|
|
25567
|
+
*
|
|
25568
|
+
* The search box of a treedb table looked at the row's values with
|
|
25569
|
+
* `String(val)`, and a treedb row is not flat: an fkey arrives as a
|
|
25570
|
+
* LIST OF OBJECTS `[{id, topic_name, hook_name}]`, and `String()`
|
|
25571
|
+
* of that is `"[object Object]"`. So looking for the workshop of
|
|
25572
|
+
* a device -- which is where the datum an operator has in mind
|
|
25573
|
+
* lives -- never found anything, and the box gave no hint that it
|
|
25574
|
+
* had looked somewhere else.
|
|
25575
|
+
*
|
|
25576
|
+
* THREE RULES, AND THE LAST TWO ARE WHAT MAKE IT USEFUL:
|
|
25577
|
+
*
|
|
25578
|
+
* - It walks down lists and objects to a short depth: an fkey is
|
|
25579
|
+
* two levels down and nothing anybody searches for is deeper.
|
|
25580
|
+
* - **Of an fkey only the `id` is looked at.** `topic_name` and
|
|
25581
|
+
* `hook_name` are the SAME two words on every row, so looking at
|
|
25582
|
+
* them turns the term into a wildcard: searching "devices" would
|
|
25583
|
+
* bring the whole topic. The `id` is the only thing that names
|
|
25584
|
+
* the linked thing.
|
|
25585
|
+
* - **A hook read as its COUNT is not looked at at all.** Since
|
|
25586
|
+
* `hook_size` (7.23.163) a topic table loads a hook as
|
|
25587
|
+
* `[{"size": N}]` instead of the id of every child, and that
|
|
25588
|
+
* number is not a value of the row: matching it made "5" answer
|
|
25589
|
+
* every row whose hook happens to hold 5 children, next to the
|
|
25590
|
+
* rows that really say 5. A count nobody can see in the cell as
|
|
25591
|
+
* text is not what a person typing in a search box is after.
|
|
25592
|
+
*
|
|
25593
|
+
* Keys that start with `_` are not looked at on any level: they
|
|
25594
|
+
* belong to the scaffolding (`_check_box_state_`, `_operation`) or
|
|
25595
|
+
* are metadata (`__md_treedb__`), and nobody searches by them.
|
|
25591
25596
|
*
|
|
25592
|
-
* Copyright (c) 2026, ArtGins.
|
|
25597
|
+
* Copyright (c) 2024-2026, ArtGins.
|
|
25593
25598
|
* All Rights Reserved.
|
|
25594
25599
|
***********************************************************************/
|
|
25595
25600
|
var MAX_DEPTH = 4;
|
|
25596
25601
|
/***************************************************************
|
|
25597
|
-
*
|
|
25602
|
+
* An fkey, as `nodes` answers it with `list_dict`.
|
|
25598
25603
|
***************************************************************/
|
|
25599
25604
|
function is_fkey_ref(value) {
|
|
25600
25605
|
return !!value && typeof value === "object" && !Array.isArray(value) && "id" in value && "topic_name" in value && "hook_name" in value;
|
|
25601
25606
|
}
|
|
25602
25607
|
/***************************************************************
|
|
25603
|
-
*
|
|
25604
|
-
*
|
|
25608
|
+
* A hook read with `hook_size`: the COUNT of its children,
|
|
25609
|
+
* `[{"size": N}]`, in the place of the children themselves.
|
|
25610
|
+
* The same shape `delete_impact`'s `ref_count()` reads.
|
|
25611
|
+
***************************************************************/
|
|
25612
|
+
function is_hook_size(value) {
|
|
25613
|
+
if (!Array.isArray(value) || value.length !== 1) return false;
|
|
25614
|
+
const first = value[0];
|
|
25615
|
+
return !!first && typeof first === "object" && !Array.isArray(first) && typeof first.size === "number" && Object.keys(first).length === 1;
|
|
25616
|
+
}
|
|
25617
|
+
/***************************************************************
|
|
25618
|
+
* Does `value` hold the term? The term comes ALREADY in lower
|
|
25619
|
+
* case: whoever searches lowers it once, not once per cell.
|
|
25605
25620
|
***************************************************************/
|
|
25606
25621
|
function value_matches(value, term, depth) {
|
|
25607
25622
|
if (value === null || value === void 0) return false;
|
|
25608
25623
|
if (depth > MAX_DEPTH) return false;
|
|
25624
|
+
if (is_hook_size(value)) return false;
|
|
25609
25625
|
if (Array.isArray(value)) return value.some((v) => value_matches(v, term, depth + 1));
|
|
25610
25626
|
if (typeof value === "object") {
|
|
25611
25627
|
if (is_fkey_ref(value)) return value_matches(value.id, term, depth + 1);
|
|
@@ -25617,7 +25633,7 @@ function value_matches(value, term, depth) {
|
|
|
25617
25633
|
return String(value).toLowerCase().includes(term);
|
|
25618
25634
|
}
|
|
25619
25635
|
/***************************************************************
|
|
25620
|
-
*
|
|
25636
|
+
* Is the term anywhere in this row?
|
|
25621
25637
|
***************************************************************/
|
|
25622
25638
|
function row_matches(row, term) {
|
|
25623
25639
|
if (!row || typeof row !== "object" || !term) return false;
|
|
@@ -25627,6 +25643,361 @@ function row_matches(row, term) {
|
|
|
25627
25643
|
});
|
|
25628
25644
|
}
|
|
25629
25645
|
//#endregion
|
|
25646
|
+
//#region src/schema_model.js
|
|
25647
|
+
/***********************************************************************
|
|
25648
|
+
* schema_model.js
|
|
25649
|
+
*
|
|
25650
|
+
* A SCHEMA out of the three meta-topics that store it.
|
|
25651
|
+
*
|
|
25652
|
+
* `treedb_system_schema` keeps every schema of a yuno as DATA, in
|
|
25653
|
+
* three flat topics — `treedbs`, `topics`, `cols` — linked by
|
|
25654
|
+
* fkeys. That is the right STORAGE and the wrong thing to put in
|
|
25655
|
+
* front of an operator: adding one column to one topic means
|
|
25656
|
+
* finding it in a table holding every column of every topic of
|
|
25657
|
+
* every treedb the yuno has.
|
|
25658
|
+
*
|
|
25659
|
+
* This module turns those three lists back into what they
|
|
25660
|
+
* describe: treedb -> topics -> columns, each in its declared
|
|
25661
|
+
* `order`. Pure, so the editor's model can be tested with no DOM
|
|
25662
|
+
* and no backend — the gclass fetches and draws, this decides
|
|
25663
|
+
* what the records MEAN.
|
|
25664
|
+
*
|
|
25665
|
+
* TWO THINGS IT EXISTS TO STATE ONCE:
|
|
25666
|
+
*
|
|
25667
|
+
* 1. A record is grouped by its FKEY, not by its id. The id is
|
|
25668
|
+
* qualified (`<treedb>.<topic>.<column>`) and reading it back
|
|
25669
|
+
* with a split on '.' works right up to the first treedb or
|
|
25670
|
+
* topic whose name carries one. The fkey is the link the store
|
|
25671
|
+
* itself uses; the id is only the fallback for a record whose
|
|
25672
|
+
* fkey never arrived.
|
|
25673
|
+
*
|
|
25674
|
+
* 2. A fkey reaches this code in FOUR shapes, because `nodes`
|
|
25675
|
+
* answers differently depending on the options it was called
|
|
25676
|
+
* with: the bare ref string, the expanded object, a list of
|
|
25677
|
+
* either, or a dict keyed by the ref. All four are the same
|
|
25678
|
+
* fact and parse_fkey_ref() flattens them to it.
|
|
25679
|
+
*
|
|
25680
|
+
* Copyright (c) 2026, ArtGins.
|
|
25681
|
+
* All Rights Reserved.
|
|
25682
|
+
***********************************************************************/
|
|
25683
|
+
var DEFAULT_ORDER = 9999;
|
|
25684
|
+
/***************************************************************
|
|
25685
|
+
* parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
|
|
25686
|
+
*
|
|
25687
|
+
* Every shape a fkey column can arrive in, flattened to the
|
|
25688
|
+
* refs it names. Anything unrecognizable contributes nothing —
|
|
25689
|
+
* a schema drawn without one link beats a screen that throws.
|
|
25690
|
+
***************************************************************/
|
|
25691
|
+
function parse_fkey_ref(value) {
|
|
25692
|
+
let refs = [];
|
|
25693
|
+
let push_one = (v) => {
|
|
25694
|
+
if (typeof v === "string") {
|
|
25695
|
+
let parts = v.split("^");
|
|
25696
|
+
if (parts.length !== 3) return;
|
|
25697
|
+
refs.push({
|
|
25698
|
+
topic_name: parts[0],
|
|
25699
|
+
id: parts[1],
|
|
25700
|
+
hook_name: parts[2]
|
|
25701
|
+
});
|
|
25702
|
+
return;
|
|
25703
|
+
}
|
|
25704
|
+
if (v && typeof v === "object") {
|
|
25705
|
+
let id = v.id;
|
|
25706
|
+
if (typeof id !== "string" || id.length === 0) return;
|
|
25707
|
+
refs.push({
|
|
25708
|
+
topic_name: v.topic_name || "",
|
|
25709
|
+
id,
|
|
25710
|
+
hook_name: v.hook_name || ""
|
|
25711
|
+
});
|
|
25712
|
+
}
|
|
25713
|
+
};
|
|
25714
|
+
if (value === null || value === void 0) return refs;
|
|
25715
|
+
if (typeof value === "string") {
|
|
25716
|
+
push_one(value);
|
|
25717
|
+
return refs;
|
|
25718
|
+
}
|
|
25719
|
+
if (Array.isArray(value)) {
|
|
25720
|
+
for (let v of value) push_one(v);
|
|
25721
|
+
return refs;
|
|
25722
|
+
}
|
|
25723
|
+
if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
|
|
25724
|
+
else push_one(key);
|
|
25725
|
+
return refs;
|
|
25726
|
+
}
|
|
25727
|
+
/***************************************************************
|
|
25728
|
+
* The id of the FIRST parent a record names, or "" when it
|
|
25729
|
+
* names none. A record has one parent here — a column belongs to
|
|
25730
|
+
* one topic, a topic to one treedb — and a second ref would be a
|
|
25731
|
+
* store this editor cannot draw anyway.
|
|
25732
|
+
***************************************************************/
|
|
25733
|
+
function parent_id(record, fkey_col) {
|
|
25734
|
+
let refs = parse_fkey_ref(record ? record[fkey_col] : null);
|
|
25735
|
+
return refs.length > 0 ? refs[0].id : "";
|
|
25736
|
+
}
|
|
25737
|
+
/***************************************************************
|
|
25738
|
+
* The name a record is KNOWN by, which is not what it is keyed
|
|
25739
|
+
* by: `topics` and `cols` key by the qualified id and carry the
|
|
25740
|
+
* bare name in `value` (its pkey2). An older store that still
|
|
25741
|
+
* keyed by rowid has no `value` worth showing, so the id stands
|
|
25742
|
+
* in — the same fallback as node_label().
|
|
25743
|
+
***************************************************************/
|
|
25744
|
+
function record_name(record) {
|
|
25745
|
+
if (!record) return "";
|
|
25746
|
+
if (typeof record.value === "string" && record.value.length > 0) return record.value;
|
|
25747
|
+
return typeof record.id === "string" ? record.id : "";
|
|
25748
|
+
}
|
|
25749
|
+
/***************************************************************
|
|
25750
|
+
* The `order` of a record as a NUMBER, whatever the store put
|
|
25751
|
+
* there: an integer column read back from JSON may arrive as a
|
|
25752
|
+
* string, and comparing "10" with 9 sorts the schema wrong.
|
|
25753
|
+
***************************************************************/
|
|
25754
|
+
function record_order(record) {
|
|
25755
|
+
let order = record ? record.order : void 0;
|
|
25756
|
+
if (typeof order === "number" && isFinite(order)) return order;
|
|
25757
|
+
if (typeof order === "string" && order.trim().length > 0) {
|
|
25758
|
+
let n = Number(order);
|
|
25759
|
+
if (isFinite(n)) return n;
|
|
25760
|
+
}
|
|
25761
|
+
return DEFAULT_ORDER;
|
|
25762
|
+
}
|
|
25763
|
+
/***************************************************************
|
|
25764
|
+
* Sort in place by `order`, ties broken by name so a schema
|
|
25765
|
+
* whose records all default to 9999 still draws the same way
|
|
25766
|
+
* twice.
|
|
25767
|
+
***************************************************************/
|
|
25768
|
+
function sort_by_order(list) {
|
|
25769
|
+
list.sort((a, b) => {
|
|
25770
|
+
if (a.order !== b.order) return a.order - b.order;
|
|
25771
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
25772
|
+
});
|
|
25773
|
+
return list;
|
|
25774
|
+
}
|
|
25775
|
+
/***************************************************************
|
|
25776
|
+
* build_schema_model({treedbs, topics, cols}) -> model
|
|
25777
|
+
*
|
|
25778
|
+
* model = {
|
|
25779
|
+
* treedbs: [{
|
|
25780
|
+
* id, name, record,
|
|
25781
|
+
* schema_version, c_schema_version, system_schema_version,
|
|
25782
|
+
* topics: [{
|
|
25783
|
+
* id, name, order, record,
|
|
25784
|
+
* pkey, topic_version, system_topic, main_topic,
|
|
25785
|
+
* cols: [{id, name, order, record}]
|
|
25786
|
+
* }]
|
|
25787
|
+
* }],
|
|
25788
|
+
* orphan_topics: [...], topics whose treedb is not in the store
|
|
25789
|
+
* orphan_cols: [...] columns whose topic is not in the store
|
|
25790
|
+
* }
|
|
25791
|
+
*
|
|
25792
|
+
* An orphan is NOT dropped. A column whose topic was deleted is
|
|
25793
|
+
* exactly the kind of leftover an operator opens this editor to
|
|
25794
|
+
* find, and a model that hides it makes the editor lie.
|
|
25795
|
+
***************************************************************/
|
|
25796
|
+
function build_schema_model(records) {
|
|
25797
|
+
let r = records || {};
|
|
25798
|
+
let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
|
|
25799
|
+
let topic_rows = Array.isArray(r.topics) ? r.topics : [];
|
|
25800
|
+
let col_rows = Array.isArray(r.cols) ? r.cols : [];
|
|
25801
|
+
let treedbs = [];
|
|
25802
|
+
let by_treedb_id = {};
|
|
25803
|
+
for (let record of treedb_rows) {
|
|
25804
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25805
|
+
let entry = {
|
|
25806
|
+
id: record.id,
|
|
25807
|
+
name: record.id,
|
|
25808
|
+
record,
|
|
25809
|
+
schema_version: record.schema_version,
|
|
25810
|
+
c_schema_version: record.c_schema_version,
|
|
25811
|
+
system_schema_version: record.system_schema_version,
|
|
25812
|
+
topics: []
|
|
25813
|
+
};
|
|
25814
|
+
treedbs.push(entry);
|
|
25815
|
+
by_treedb_id[entry.id] = entry;
|
|
25816
|
+
}
|
|
25817
|
+
let orphan_topics = [];
|
|
25818
|
+
let by_topic_id = {};
|
|
25819
|
+
for (let record of topic_rows) {
|
|
25820
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25821
|
+
let entry = {
|
|
25822
|
+
id: record.id,
|
|
25823
|
+
name: record_name(record),
|
|
25824
|
+
order: record_order(record),
|
|
25825
|
+
record,
|
|
25826
|
+
treedb_id: parent_id(record, "treedbs"),
|
|
25827
|
+
pkey: record.pkey || "",
|
|
25828
|
+
pkey2s: record.pkey2s,
|
|
25829
|
+
system_flag: record.system_flag || "",
|
|
25830
|
+
tkey: record.tkey || "",
|
|
25831
|
+
topic_version: record.topic_version,
|
|
25832
|
+
system_topic: !!record.system_topic,
|
|
25833
|
+
main_topic: !!record.main_topic,
|
|
25834
|
+
cols: []
|
|
25835
|
+
};
|
|
25836
|
+
by_topic_id[entry.id] = entry;
|
|
25837
|
+
let parent = by_treedb_id[entry.treedb_id];
|
|
25838
|
+
if (parent) parent.topics.push(entry);
|
|
25839
|
+
else orphan_topics.push(entry);
|
|
25840
|
+
}
|
|
25841
|
+
let orphan_cols = [];
|
|
25842
|
+
for (let record of col_rows) {
|
|
25843
|
+
if (!record || typeof record.id !== "string") continue;
|
|
25844
|
+
let entry = {
|
|
25845
|
+
id: record.id,
|
|
25846
|
+
name: record_name(record),
|
|
25847
|
+
order: record_order(record),
|
|
25848
|
+
record,
|
|
25849
|
+
topic_id: parent_id(record, "topics")
|
|
25850
|
+
};
|
|
25851
|
+
let parent = by_topic_id[entry.topic_id];
|
|
25852
|
+
if (parent) parent.cols.push(entry);
|
|
25853
|
+
else orphan_cols.push(entry);
|
|
25854
|
+
}
|
|
25855
|
+
for (let treedb of treedbs) {
|
|
25856
|
+
sort_by_order(treedb.topics);
|
|
25857
|
+
for (let topic of treedb.topics) sort_by_order(topic.cols);
|
|
25858
|
+
}
|
|
25859
|
+
for (let topic of orphan_topics) sort_by_order(topic.cols);
|
|
25860
|
+
treedbs.sort((a, b) => {
|
|
25861
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
25862
|
+
});
|
|
25863
|
+
sort_by_order(orphan_topics);
|
|
25864
|
+
sort_by_order(orphan_cols);
|
|
25865
|
+
return {
|
|
25866
|
+
treedbs,
|
|
25867
|
+
orphan_topics,
|
|
25868
|
+
orphan_cols
|
|
25869
|
+
};
|
|
25870
|
+
}
|
|
25871
|
+
/***************************************************************
|
|
25872
|
+
* Is there a value here at all?
|
|
25873
|
+
*
|
|
25874
|
+
* The store answers a `blob` column that was never set with an
|
|
25875
|
+
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
25876
|
+
* `default: {}`. Read as "a value", those turn every export into
|
|
25877
|
+
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
25878
|
+
* is a hook with no mapping, which is a link the treedb builds
|
|
25879
|
+
* and nothing writes.
|
|
25880
|
+
***************************************************************/
|
|
25881
|
+
function is_empty_value(value) {
|
|
25882
|
+
if (value === null || value === void 0 || value === "") return true;
|
|
25883
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
25884
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
25885
|
+
return false;
|
|
25886
|
+
}
|
|
25887
|
+
/***************************************************************
|
|
25888
|
+
* The fields of a column that are DECLARED as one thing and
|
|
25889
|
+
* STORED as another.
|
|
25890
|
+
*
|
|
25891
|
+
* `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
|
|
25892
|
+
* columns of the `cols` topic (treedb_system_schema.c), so what
|
|
25893
|
+
* comes back may be the value or the JSON text of the value,
|
|
25894
|
+
* depending on how it was written — a projection writes the
|
|
25895
|
+
* value, a hand edit through a plain text field writes the text.
|
|
25896
|
+
* `flag` is an array that a single-flag edit can leave as a bare
|
|
25897
|
+
* string. Reading these without saying so is how a hook drawn
|
|
25898
|
+
* from `{"users":"department"}` becomes no hook at all.
|
|
25899
|
+
***************************************************************/
|
|
25900
|
+
function as_json(value) {
|
|
25901
|
+
if (typeof value !== "string") return value;
|
|
25902
|
+
let text = value.trim();
|
|
25903
|
+
if (text.length === 0) return null;
|
|
25904
|
+
try {
|
|
25905
|
+
return JSON.parse(text);
|
|
25906
|
+
} catch (e) {
|
|
25907
|
+
return value;
|
|
25908
|
+
}
|
|
25909
|
+
}
|
|
25910
|
+
function col_flags(record) {
|
|
25911
|
+
let flag = record ? record.flag : null;
|
|
25912
|
+
if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
25913
|
+
if (typeof flag === "string" && flag.length > 0) {
|
|
25914
|
+
let parsed = as_json(flag);
|
|
25915
|
+
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
|
|
25916
|
+
return [flag];
|
|
25917
|
+
}
|
|
25918
|
+
return [];
|
|
25919
|
+
}
|
|
25920
|
+
function col_hook(record) {
|
|
25921
|
+
let hook = as_json(record ? record.hook : null);
|
|
25922
|
+
if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
|
|
25923
|
+
return null;
|
|
25924
|
+
}
|
|
25925
|
+
function col_enum(record) {
|
|
25926
|
+
let e = as_json(record ? record.enum : null);
|
|
25927
|
+
if (Array.isArray(e)) return e;
|
|
25928
|
+
return [];
|
|
25929
|
+
}
|
|
25930
|
+
function topic_pkey2s(record) {
|
|
25931
|
+
let pkey2s = as_json(record ? record.pkey2s : null);
|
|
25932
|
+
if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
|
|
25933
|
+
if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
|
|
25934
|
+
return [];
|
|
25935
|
+
}
|
|
25936
|
+
/***************************************************************
|
|
25937
|
+
* Lookups by id. The editor addresses a position by NAME (the
|
|
25938
|
+
* url carries `<treedb>/<topic>`), so both are offered.
|
|
25939
|
+
***************************************************************/
|
|
25940
|
+
function find_treedb(model, treedb_id) {
|
|
25941
|
+
if (!model || !Array.isArray(model.treedbs)) return null;
|
|
25942
|
+
for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
|
|
25943
|
+
return null;
|
|
25944
|
+
}
|
|
25945
|
+
function find_topic(model, treedb_id, topic_name) {
|
|
25946
|
+
let treedb = find_treedb(model, treedb_id);
|
|
25947
|
+
if (!treedb) return null;
|
|
25948
|
+
for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
|
|
25949
|
+
return null;
|
|
25950
|
+
}
|
|
25951
|
+
function find_col(model, treedb_id, topic_name, col_name) {
|
|
25952
|
+
let topic = find_topic(model, treedb_id, topic_name);
|
|
25953
|
+
if (!topic) return null;
|
|
25954
|
+
for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
|
|
25955
|
+
return null;
|
|
25956
|
+
}
|
|
25957
|
+
/***************************************************************
|
|
25958
|
+
* The fkey a NEW record must carry to belong to `parent_id`.
|
|
25959
|
+
* Written here because the editor is the only writer that has to
|
|
25960
|
+
* compose one, and getting the hook name wrong links the record
|
|
25961
|
+
* to nothing while the write still succeeds.
|
|
25962
|
+
***************************************************************/
|
|
25963
|
+
function fkey_ref(parent_topic, parent_id_, hook_name) {
|
|
25964
|
+
return `${parent_topic}^${parent_id_}^${hook_name}`;
|
|
25965
|
+
}
|
|
25966
|
+
/***************************************************************
|
|
25967
|
+
* Where a new sibling goes: after the last one. The store's own
|
|
25968
|
+
* default (9999) would put every hand-made column in the same
|
|
25969
|
+
* place and let their names decide the schema's order.
|
|
25970
|
+
***************************************************************/
|
|
25971
|
+
function next_order(siblings) {
|
|
25972
|
+
let max = 0;
|
|
25973
|
+
if (Array.isArray(siblings)) for (let s of siblings) {
|
|
25974
|
+
let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
|
|
25975
|
+
if (order < 9999 && order > max) max = order;
|
|
25976
|
+
}
|
|
25977
|
+
return max + 1;
|
|
25978
|
+
}
|
|
25979
|
+
/***************************************************************
|
|
25980
|
+
* Renumber a list AFTER a move, as the writes it takes: only the
|
|
25981
|
+
* records whose order actually changed, so a drag of one row in a
|
|
25982
|
+
* 40-column topic is not 40 writes.
|
|
25983
|
+
*
|
|
25984
|
+
* moved_orders(list) -> [{id, order}, ...]
|
|
25985
|
+
***************************************************************/
|
|
25986
|
+
function moved_orders(list) {
|
|
25987
|
+
let writes = [];
|
|
25988
|
+
if (!Array.isArray(list)) return writes;
|
|
25989
|
+
for (let i = 0; i < list.length; i++) {
|
|
25990
|
+
let entry = list[i];
|
|
25991
|
+
let order = i + 1;
|
|
25992
|
+
if (!entry || typeof entry.id !== "string") continue;
|
|
25993
|
+
if (entry.order !== order) writes.push({
|
|
25994
|
+
id: entry.id,
|
|
25995
|
+
order
|
|
25996
|
+
});
|
|
25997
|
+
}
|
|
25998
|
+
return writes;
|
|
25999
|
+
}
|
|
26000
|
+
//#endregion
|
|
25630
26001
|
//#region src/treedb_write_plan.js
|
|
25631
26002
|
/***********************************************************************
|
|
25632
26003
|
* treedb_write_plan.js
|
|
@@ -25688,6 +26059,43 @@ function plan_treedb_writes(flags) {
|
|
|
25688
26059
|
form_toolbar: null
|
|
25689
26060
|
};
|
|
25690
26061
|
}
|
|
26062
|
+
/************************************************************
|
|
26063
|
+
* Does column `col` travel back to treedb on a write?
|
|
26064
|
+
*
|
|
26065
|
+
* The form SHOWS every field and sends back only what the topic
|
|
26066
|
+
* accepts: the writable cols, the fkeys (a link is edited by
|
|
26067
|
+
* linking) and the pkey, which is not written but is what
|
|
26068
|
+
* ADDRESSES the record.
|
|
26069
|
+
*
|
|
26070
|
+
* It matters because `treedb_update_node()` does not check
|
|
26071
|
+
* `writable` -- it writes any col it is handed -- so a read-only
|
|
26072
|
+
* field travelling back is written with whatever the form made of
|
|
26073
|
+
* it, and the fields that describe a record are exactly the ones
|
|
26074
|
+
* that do not survive a round trip through a widget: a `time` is an
|
|
26075
|
+
* integer rendered as a `datetime-local`, with no seconds, so every
|
|
26076
|
+
* save moved it back up to 59 s. Nothing looked wrong until the
|
|
26077
|
+
* stored timestamp had moved.
|
|
26078
|
+
*
|
|
26079
|
+
* `is_file` and not the type: a `file` column IS an fkey (it is
|
|
26080
|
+
* flagged ['fkey','file']) but answers `type: "file"`, and the write
|
|
26081
|
+
* goes out with `autolink`, which rebuilds the links from what the
|
|
26082
|
+
* record carries. Dropped, a read-only `file` column -- the one only
|
|
26083
|
+
* a load fills -- was UNLINKED by every save of any other field.
|
|
26084
|
+
*
|
|
26085
|
+
* The pkey2s go back for the same reason as the pkey: a secondary key
|
|
26086
|
+
* names the INSTANCE the update is for (`yunos.yuno_release` in the
|
|
26087
|
+
* agent), and it is `persistent, required`, not `writable`. Dropped,
|
|
26088
|
+
* the update reached the backend with no pkey2 and C_NODE resolved it
|
|
26089
|
+
* to the PRIMARY instance -- right by chance while the table lists
|
|
26090
|
+
* primaries, wrong from a form opened on a row of `instances`.
|
|
26091
|
+
************************************************************/
|
|
26092
|
+
function col_goes_back_to_treedb(desc, col) {
|
|
26093
|
+
let d = desc || {};
|
|
26094
|
+
let pkey = d.pkey || "id";
|
|
26095
|
+
const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
|
|
26096
|
+
if (field_desc.is_writable || field_desc.type === "fkey" || field_desc.is_file || col.id === pkey) return true;
|
|
26097
|
+
return topic_pkey2s(d).includes(col.id);
|
|
26098
|
+
}
|
|
25691
26099
|
//#endregion
|
|
25692
26100
|
//#region src/delete_impact.js
|
|
25693
26101
|
/***********************************************************************
|
|
@@ -25823,8 +26231,8 @@ var attrs_table$5 = [
|
|
|
25823
26231
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, true, "Auxiliary first column to select rows"),
|
|
25824
26232
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_radio", 0, false, "Auxiliary first column to select one row"),
|
|
25825
26233
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
|
|
25826
|
-
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "broadcast_select_rows_event", 0, false, "
|
|
25827
|
-
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "broadcast_unselect_rows_event", 0, false, "
|
|
26234
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
|
|
26235
|
+
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
|
|
25828
26236
|
(0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "tabulator_settings", 0, {
|
|
25829
26237
|
layout: "fitDataFill",
|
|
25830
26238
|
columnDefaults: { resizable: true },
|
|
@@ -26103,7 +26511,11 @@ function build_ui$5(gobj) {
|
|
|
26103
26511
|
"button",
|
|
26104
26512
|
{
|
|
26105
26513
|
id: ``,
|
|
26106
|
-
class: "button button-edit-record mr-1"
|
|
26514
|
+
class: "button button-edit-record mr-1",
|
|
26515
|
+
title: (0, i18next.t)("edit"),
|
|
26516
|
+
"data-i18n-title": "edit",
|
|
26517
|
+
"aria-label": (0, i18next.t)("edit"),
|
|
26518
|
+
"data-i18n-aria-label": "edit"
|
|
26107
26519
|
},
|
|
26108
26520
|
[yui_toolbar_icon("yi-pen"), [
|
|
26109
26521
|
"span",
|
|
@@ -26126,6 +26538,10 @@ function build_ui$5(gobj) {
|
|
|
26126
26538
|
{
|
|
26127
26539
|
id: ``,
|
|
26128
26540
|
class: "button button-new-record mr-1",
|
|
26541
|
+
title: (0, i18next.t)("new"),
|
|
26542
|
+
"data-i18n-title": "new",
|
|
26543
|
+
"aria-label": (0, i18next.t)("new"),
|
|
26544
|
+
"data-i18n-aria-label": "new",
|
|
26129
26545
|
disabled: true
|
|
26130
26546
|
},
|
|
26131
26547
|
[yui_toolbar_icon("yi-plus"), [
|
|
@@ -26150,6 +26566,10 @@ function build_ui$5(gobj) {
|
|
|
26150
26566
|
{
|
|
26151
26567
|
id: ``,
|
|
26152
26568
|
class: "button button-delete-record mr-1",
|
|
26569
|
+
title: (0, i18next.t)("delete"),
|
|
26570
|
+
"data-i18n-title": "delete",
|
|
26571
|
+
"aria-label": (0, i18next.t)("delete"),
|
|
26572
|
+
"data-i18n-aria-label": "delete",
|
|
26153
26573
|
disabled: true
|
|
26154
26574
|
},
|
|
26155
26575
|
[yui_toolbar_icon("yi-trash"), [
|
|
@@ -26174,6 +26594,10 @@ function build_ui$5(gobj) {
|
|
|
26174
26594
|
{
|
|
26175
26595
|
id: ``,
|
|
26176
26596
|
class: "button button-copy-record mr-1",
|
|
26597
|
+
title: (0, i18next.t)("copy"),
|
|
26598
|
+
"data-i18n-title": "copy",
|
|
26599
|
+
"aria-label": (0, i18next.t)("copy"),
|
|
26600
|
+
"data-i18n-aria-label": "copy",
|
|
26177
26601
|
disabled: true
|
|
26178
26602
|
},
|
|
26179
26603
|
[yui_toolbar_icon("yi-copy"), [
|
|
@@ -26198,6 +26622,10 @@ function build_ui$5(gobj) {
|
|
|
26198
26622
|
{
|
|
26199
26623
|
id: ``,
|
|
26200
26624
|
class: "button button-paste-record mr-1",
|
|
26625
|
+
title: (0, i18next.t)("paste"),
|
|
26626
|
+
"data-i18n-title": "paste",
|
|
26627
|
+
"aria-label": (0, i18next.t)("paste"),
|
|
26628
|
+
"data-i18n-aria-label": "paste",
|
|
26201
26629
|
disabled: true
|
|
26202
26630
|
},
|
|
26203
26631
|
[yui_toolbar_icon("yi-paste"), [
|
|
@@ -26237,7 +26665,11 @@ function build_ui$5(gobj) {
|
|
|
26237
26665
|
class: "input",
|
|
26238
26666
|
type: "text",
|
|
26239
26667
|
placeholder: (0, i18next.t)("search"),
|
|
26240
|
-
"data-i18n-placeholder": "search"
|
|
26668
|
+
"data-i18n-placeholder": "search",
|
|
26669
|
+
title: (0, i18next.t)("search"),
|
|
26670
|
+
"data-i18n-title": "search",
|
|
26671
|
+
"aria-label": (0, i18next.t)("search"),
|
|
26672
|
+
"data-i18n-aria-label": "search"
|
|
26241
26673
|
}], [
|
|
26242
26674
|
"span",
|
|
26243
26675
|
{ class: "icon is-left" },
|
|
@@ -26257,7 +26689,9 @@ function build_ui$5(gobj) {
|
|
|
26257
26689
|
{
|
|
26258
26690
|
class: "button mr-1",
|
|
26259
26691
|
title: (0, i18next.t)("refresh"),
|
|
26260
|
-
"data-i18n-title": "refresh"
|
|
26692
|
+
"data-i18n-title": "refresh",
|
|
26693
|
+
"aria-label": (0, i18next.t)("refresh"),
|
|
26694
|
+
"data-i18n-aria-label": "refresh"
|
|
26261
26695
|
},
|
|
26262
26696
|
[yui_toolbar_icon("yi-arrows-rotate"), [
|
|
26263
26697
|
"span",
|
|
@@ -26790,7 +27224,7 @@ function create_tabulator(gobj) {
|
|
|
26790
27224
|
let index = cell.getRow().getPosition();
|
|
26791
27225
|
if (e.target.closest(".edit")) {
|
|
26792
27226
|
e.stopPropagation();
|
|
26793
|
-
|
|
27227
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EDIT_RECORD", { index }, gobj);
|
|
26794
27228
|
} else if (e.target.closest(".remove")) {
|
|
26795
27229
|
e.stopPropagation();
|
|
26796
27230
|
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_DELETE_ROWS", {
|
|
@@ -27252,11 +27686,11 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
27252
27686
|
return;
|
|
27253
27687
|
}
|
|
27254
27688
|
let pkey = desc.pkey || "id";
|
|
27255
|
-
let title;
|
|
27256
|
-
|
|
27257
|
-
|
|
27689
|
+
let title = mode === "create" ? "new" : mode === "view" ? "view" : "edit";
|
|
27690
|
+
let title_prefix = "";
|
|
27691
|
+
if (mode !== "create") {
|
|
27258
27692
|
let rid = record ? String(record[pkey] ?? "") : "";
|
|
27259
|
-
|
|
27693
|
+
title_prefix = (0, _yuneta_gobj_js.empty_string)(rid) ? "" : rid;
|
|
27260
27694
|
}
|
|
27261
27695
|
let view_only = mode === "view";
|
|
27262
27696
|
let form_mode = view_only ? "update" : mode;
|
|
@@ -27291,6 +27725,7 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
27291
27725
|
priv.form_modal = yui_shell_show_modal(shell, $body, {
|
|
27292
27726
|
dialog: true,
|
|
27293
27727
|
logical_class: "TREEDB_FORM_SHEET",
|
|
27728
|
+
title_prefix,
|
|
27294
27729
|
title,
|
|
27295
27730
|
t: i18next.t,
|
|
27296
27731
|
before_close: function() {
|
|
@@ -27319,14 +27754,7 @@ function form_may_close(gobj) {
|
|
|
27319
27754
|
let kw = {};
|
|
27320
27755
|
(0, _yuneta_gobj_js.gobj_send_event)(priv.form, "EV_WINDOW_TO_CLOSE", kw, gobj);
|
|
27321
27756
|
if (kw.abort_close) {
|
|
27322
|
-
|
|
27323
|
-
t: i18next.t,
|
|
27324
|
-
yes_label: "yes",
|
|
27325
|
-
no_label: "no",
|
|
27326
|
-
cancel_label: "cancel"
|
|
27327
|
-
}).then(function(answer) {
|
|
27328
|
-
if (answer === "yes" && priv.form_modal) priv.form_modal.close();
|
|
27329
|
-
});
|
|
27757
|
+
confirm_then$1(gobj, kw.warning || "all changes will be lost", { what: "close_form" });
|
|
27330
27758
|
return false;
|
|
27331
27759
|
}
|
|
27332
27760
|
}
|
|
@@ -27348,6 +27776,31 @@ function teardown_form_child(gobj) {
|
|
|
27348
27776
|
priv.form_modal = null;
|
|
27349
27777
|
}
|
|
27350
27778
|
/************************************************************
|
|
27779
|
+
* Ask, and turn the answer into an EVENT.
|
|
27780
|
+
*
|
|
27781
|
+
* A dialog's promise settles outside the machine: acting from its
|
|
27782
|
+
* `.then` puts the decision -- delete these records, throw these
|
|
27783
|
+
* edits away -- in a callback the `machine` trace never sees, and
|
|
27784
|
+
* in a view that may be gone by the time the person answers.
|
|
27785
|
+
*
|
|
27786
|
+
* `kw` carries an IDENTITY and never an object: the trace dumps
|
|
27787
|
+
* it, and a gobj, a Tabulator row or a DOM node in there is a
|
|
27788
|
+
* circular structure that breaks the very trace the FSM feeds.
|
|
27789
|
+
*
|
|
27790
|
+
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then().
|
|
27791
|
+
************************************************************/
|
|
27792
|
+
function confirm_then$1(gobj, message, kw) {
|
|
27793
|
+
yui_shell_confirm_yesnocancel(yui_shell_of(gobj), message, {
|
|
27794
|
+
t: i18next.t,
|
|
27795
|
+
yes_label: "yes",
|
|
27796
|
+
no_label: "no",
|
|
27797
|
+
cancel_label: "cancel"
|
|
27798
|
+
}).then(function(answer) {
|
|
27799
|
+
if (answer !== "yes" || (0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
|
|
27800
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CONFIRMED", kw, gobj);
|
|
27801
|
+
});
|
|
27802
|
+
}
|
|
27803
|
+
/************************************************************
|
|
27351
27804
|
* Force the dialog down (bypasses the unsaved guard): used after a
|
|
27352
27805
|
* successful save and on teardown. Closing the modal runs its
|
|
27353
27806
|
* on_close, which tears the form child down.
|
|
@@ -27928,7 +28381,7 @@ function pkey_is_rowid(gobj) {
|
|
|
27928
28381
|
* Show the edit "update" form of a row (record)
|
|
27929
28382
|
* internally called from the icon edit at Op column
|
|
27930
28383
|
************************************************************/
|
|
27931
|
-
function show_edit_form(gobj, row
|
|
28384
|
+
function show_edit_form(gobj, row) {
|
|
27932
28385
|
open_form_dialog(gobj, pkey_is_rowid(gobj) ? "create" : "update", row);
|
|
27933
28386
|
}
|
|
27934
28387
|
/************************************************************
|
|
@@ -27990,35 +28443,6 @@ function get_schema_col(gobj, id) {
|
|
|
27990
28443
|
return null;
|
|
27991
28444
|
}
|
|
27992
28445
|
/************************************************************
|
|
27993
|
-
* Does column `col` travel back to treedb on a write?
|
|
27994
|
-
*
|
|
27995
|
-
* The form SHOWS every field and sends back only what the topic
|
|
27996
|
-
* accepts: the writable cols, the fkeys (a link is edited by
|
|
27997
|
-
* linking) and the pkey, which is not written but is what
|
|
27998
|
-
* ADDRESSES the record.
|
|
27999
|
-
*
|
|
28000
|
-
* It matters because `treedb_update_node()` does not check
|
|
28001
|
-
* `writable` -- it writes any col it is handed -- so a read-only
|
|
28002
|
-
* field travelling back is written with whatever the form made of
|
|
28003
|
-
* it, and the fields that describe a record are exactly the ones
|
|
28004
|
-
* that do not survive a round trip through a widget: a `time` is an
|
|
28005
|
-
* integer rendered as a `datetime-local`, with no seconds, so every
|
|
28006
|
-
* save moved it back up to 59 s. Nothing looked wrong until the
|
|
28007
|
-
* stored timestamp had moved.
|
|
28008
|
-
*
|
|
28009
|
-
* `is_file` and not the type: a `file` column IS an fkey (it is
|
|
28010
|
-
* flagged ['fkey','file']) but answers `type: "file"`, and the write
|
|
28011
|
-
* goes out with `autolink`, which rebuilds the links from what the
|
|
28012
|
-
* record carries. Dropped, a read-only `file` column -- the one only
|
|
28013
|
-
* a load fills -- was UNLINKED by every save of any other field.
|
|
28014
|
-
************************************************************/
|
|
28015
|
-
function col_goes_back_to_treedb(gobj, col) {
|
|
28016
|
-
let pkey = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc").pkey || "id";
|
|
28017
|
-
const field_desc = (0, _yuneta_gobj_js.treedb_get_field_desc)(col);
|
|
28018
|
-
if (field_desc.is_writable || field_desc.type === "fkey" || field_desc.is_file || col.id === pkey) return true;
|
|
28019
|
-
return false;
|
|
28020
|
-
}
|
|
28021
|
-
/************************************************************
|
|
28022
28446
|
* The record the FORM hands over, without the columns that do
|
|
28023
28447
|
* not go back to treedb (see col_goes_back_to_treedb()). The
|
|
28024
28448
|
* form has already encoded the values, so only the columns are
|
|
@@ -28028,7 +28452,7 @@ function strip_read_only_cols(gobj, record) {
|
|
|
28028
28452
|
let row = {};
|
|
28029
28453
|
for (let field_name of Object.keys(record || {})) {
|
|
28030
28454
|
let col = get_schema_col(gobj, field_name);
|
|
28031
|
-
if (col && !col_goes_back_to_treedb(gobj, col)) continue;
|
|
28455
|
+
if (col && !col_goes_back_to_treedb((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc"), col)) continue;
|
|
28032
28456
|
row[field_name] = record[field_name];
|
|
28033
28457
|
}
|
|
28034
28458
|
return row;
|
|
@@ -28044,7 +28468,7 @@ function transform__form_record_2_treedb_record(gobj, kw, operation) {
|
|
|
28044
28468
|
if ((0, _yuneta_gobj_js.empty_string)(field_name)) continue;
|
|
28045
28469
|
let col = get_schema_col(gobj, field_name);
|
|
28046
28470
|
if (!col) continue;
|
|
28047
|
-
if (!col_goes_back_to_treedb(gobj, col)) continue;
|
|
28471
|
+
if (!col_goes_back_to_treedb((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc"), col)) continue;
|
|
28048
28472
|
let value = kw[field_name];
|
|
28049
28473
|
row[field_name] = transform__form_value_2_treedb_value(gobj, col, value, operation);
|
|
28050
28474
|
}
|
|
@@ -28427,31 +28851,57 @@ function ac_delete_rows(gobj, event, kw, src) {
|
|
|
28427
28851
|
});
|
|
28428
28852
|
return 0;
|
|
28429
28853
|
}
|
|
28430
|
-
|
|
28431
|
-
|
|
28432
|
-
|
|
28433
|
-
|
|
28434
|
-
cancel_label: "cancel"
|
|
28435
|
-
}).then(function(answer) {
|
|
28436
|
-
if (answer === "yes") for (let row of rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
28437
|
-
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
28438
|
-
record: row
|
|
28439
|
-
});
|
|
28440
|
-
});
|
|
28441
|
-
} else yui_shell_confirm_yesnocancel(yui_shell_of(gobj), build_delete_question(gobj, kw.row), {
|
|
28442
|
-
t: i18next.t,
|
|
28443
|
-
yes_label: "yes",
|
|
28444
|
-
no_label: "no",
|
|
28445
|
-
cancel_label: "cancel"
|
|
28446
|
-
}).then(function(answer) {
|
|
28447
|
-
if (answer === "yes") (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
28448
|
-
topic_name: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"),
|
|
28449
|
-
record: kw.row
|
|
28450
|
-
});
|
|
28854
|
+
confirm_then$1(gobj, build_delete_question(gobj, rows), { what: "delete_selection" });
|
|
28855
|
+
} else confirm_then$1(gobj, build_delete_question(gobj, kw.row), {
|
|
28856
|
+
what: "delete_row",
|
|
28857
|
+
index: kw.index
|
|
28451
28858
|
});
|
|
28452
28859
|
return 0;
|
|
28453
28860
|
}
|
|
28454
28861
|
/************************************************************
|
|
28862
|
+
* {what, ...} -- the person said yes to what confirm_then()
|
|
28863
|
+
* asked. The rows are read again HERE and not carried in the
|
|
28864
|
+
* kw: the kw is plain json, and a Tabulator row is not.
|
|
28865
|
+
************************************************************/
|
|
28866
|
+
function ac_confirmed$2(gobj, event, kw, src) {
|
|
28867
|
+
let priv = gobj.priv;
|
|
28868
|
+
let what = kw ? kw.what : "";
|
|
28869
|
+
if (what === "close_form") {
|
|
28870
|
+
if (priv.form_modal) priv.form_modal.close();
|
|
28871
|
+
return 0;
|
|
28872
|
+
}
|
|
28873
|
+
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
28874
|
+
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
28875
|
+
let topic_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name");
|
|
28876
|
+
if (what === "delete_selection") {
|
|
28877
|
+
let rows = yui_selected_rows(tabulator);
|
|
28878
|
+
if (!rows.length) {
|
|
28879
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with the selection gone`);
|
|
28880
|
+
return -1;
|
|
28881
|
+
}
|
|
28882
|
+
for (let row of rows) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
28883
|
+
topic_name,
|
|
28884
|
+
record: row
|
|
28885
|
+
});
|
|
28886
|
+
return 0;
|
|
28887
|
+
}
|
|
28888
|
+
if (what === "delete_row") {
|
|
28889
|
+
let index = kw.index;
|
|
28890
|
+
let row = tabulator && typeof index === "number" ? tabulator.getRowFromPosition(index) : null;
|
|
28891
|
+
if (!row) {
|
|
28892
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no row: ${index}`);
|
|
28893
|
+
return -1;
|
|
28894
|
+
}
|
|
28895
|
+
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_DELETE_RECORD", {
|
|
28896
|
+
topic_name,
|
|
28897
|
+
record: row.getData()
|
|
28898
|
+
});
|
|
28899
|
+
return 0;
|
|
28900
|
+
}
|
|
28901
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} of nothing this view asked about: ${what}`);
|
|
28902
|
+
return -1;
|
|
28903
|
+
}
|
|
28904
|
+
/************************************************************
|
|
28455
28905
|
* From internal
|
|
28456
28906
|
************************************************************/
|
|
28457
28907
|
function ac_copy_rows(gobj, event, kw, src) {
|
|
@@ -28506,9 +28956,7 @@ function ac_form_save_record(gobj, event, kw, src) {
|
|
|
28506
28956
|
const cols = Object.keys(picked);
|
|
28507
28957
|
if (cols.length === 0) {
|
|
28508
28958
|
publish_treedb_write(gobj, mode, kw);
|
|
28509
|
-
|
|
28510
|
-
close_form_dialog(gobj);
|
|
28511
|
-
}, 0);
|
|
28959
|
+
(0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CLOSE_FORM", {}, gobj);
|
|
28512
28960
|
return 0;
|
|
28513
28961
|
}
|
|
28514
28962
|
const serial = ++priv.read_serial;
|
|
@@ -28586,6 +29034,14 @@ function publish_treedb_write(gobj, mode, record, jn_files) {
|
|
|
28586
29034
|
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, mode === "create" ? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD", kw_write);
|
|
28587
29035
|
}
|
|
28588
29036
|
/************************************************************
|
|
29037
|
+
* The deferred close of the form dialog, posted by the save
|
|
29038
|
+
* from inside the form child's own publish stack.
|
|
29039
|
+
************************************************************/
|
|
29040
|
+
function ac_close_form(gobj, event, kw, src) {
|
|
29041
|
+
close_form_dialog(gobj);
|
|
29042
|
+
return 0;
|
|
29043
|
+
}
|
|
29044
|
+
/************************************************************
|
|
28589
29045
|
* {mode, record, picks} -- the bytes are here now
|
|
28590
29046
|
************************************************************/
|
|
28591
29047
|
function ac_files_read(gobj, event, kw, src) {
|
|
@@ -28626,7 +29082,7 @@ function ac_select_rows(gobj, event, kw, src) {
|
|
|
28626
29082
|
************************************************************/
|
|
28627
29083
|
function ac_unselect_rows(gobj, event, kw, src) {
|
|
28628
29084
|
render_selection_state(gobj);
|
|
28629
|
-
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "
|
|
29085
|
+
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "broadcast_unselect_rows_event")) (0, _yuneta_gobj_js.gobj_publish_event)(gobj, event, kw);
|
|
28630
29086
|
return 0;
|
|
28631
29087
|
}
|
|
28632
29088
|
/************************************************************
|
|
@@ -29106,6 +29562,26 @@ function ac_show_record(gobj, event, kw, src) {
|
|
|
29106
29562
|
return 0;
|
|
29107
29563
|
}
|
|
29108
29564
|
/************************************************************
|
|
29565
|
+
* {index} -- the pencil of the Op column. Same shape as
|
|
29566
|
+
* EV_SHOW_RECORD: the kw carries WHICH row, and the row itself
|
|
29567
|
+
* is resolved here, where the table is.
|
|
29568
|
+
************************************************************/
|
|
29569
|
+
function ac_edit_record(gobj, event, kw, src) {
|
|
29570
|
+
let tabulator = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "tabulator");
|
|
29571
|
+
let index = kw ? kw.index : void 0;
|
|
29572
|
+
if (!tabulator || typeof index !== "number") {
|
|
29573
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no row ${index} to edit`);
|
|
29574
|
+
return -1;
|
|
29575
|
+
}
|
|
29576
|
+
let row = tabulator.getRowFromPosition(index);
|
|
29577
|
+
if (!row) {
|
|
29578
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: row ${index} is gone`);
|
|
29579
|
+
return -1;
|
|
29580
|
+
}
|
|
29581
|
+
show_edit_form(gobj, row.getData());
|
|
29582
|
+
return 0;
|
|
29583
|
+
}
|
|
29584
|
+
/************************************************************
|
|
29109
29585
|
* The reader picked another page size: remember it, so coming back to
|
|
29110
29586
|
* this topic opens where they left it instead of at 200.
|
|
29111
29587
|
************************************************************/
|
|
@@ -29230,6 +29706,11 @@ function create_gclass$5(gclass_name) {
|
|
|
29230
29706
|
ac_form_save_record,
|
|
29231
29707
|
null
|
|
29232
29708
|
],
|
|
29709
|
+
[
|
|
29710
|
+
"EV_CLOSE_FORM",
|
|
29711
|
+
ac_close_form,
|
|
29712
|
+
null
|
|
29713
|
+
],
|
|
29233
29714
|
[
|
|
29234
29715
|
"EV_FILES_READ",
|
|
29235
29716
|
ac_files_read,
|
|
@@ -29250,6 +29731,11 @@ function create_gclass$5(gclass_name) {
|
|
|
29250
29731
|
ac_delete_rows,
|
|
29251
29732
|
null
|
|
29252
29733
|
],
|
|
29734
|
+
[
|
|
29735
|
+
"EV_CONFIRMED",
|
|
29736
|
+
ac_confirmed$2,
|
|
29737
|
+
null
|
|
29738
|
+
],
|
|
29253
29739
|
[
|
|
29254
29740
|
"EV_SELECT_ROWS",
|
|
29255
29741
|
ac_select_rows,
|
|
@@ -29375,6 +29861,11 @@ function create_gclass$5(gclass_name) {
|
|
|
29375
29861
|
ac_show_record,
|
|
29376
29862
|
null
|
|
29377
29863
|
],
|
|
29864
|
+
[
|
|
29865
|
+
"EV_EDIT_RECORD",
|
|
29866
|
+
ac_edit_record,
|
|
29867
|
+
null
|
|
29868
|
+
],
|
|
29378
29869
|
[
|
|
29379
29870
|
"EV_TOGGLE_COLUMN",
|
|
29380
29871
|
ac_toggle_column,
|
|
@@ -29404,10 +29895,12 @@ function create_gclass$5(gclass_name) {
|
|
|
29404
29895
|
["EV_NODE_DELETED", 0],
|
|
29405
29896
|
["EV_EDITION_MODE", 0],
|
|
29406
29897
|
["EV_SAVE_RECORD", 0],
|
|
29898
|
+
["EV_CLOSE_FORM", 0],
|
|
29407
29899
|
["EV_FILES_READ", 0],
|
|
29408
29900
|
["EV_FILES_FAILED", 0],
|
|
29409
29901
|
["EV_NEW_ROW", 0],
|
|
29410
29902
|
["EV_DELETE_ROWS", 0],
|
|
29903
|
+
["EV_CONFIRMED", 0],
|
|
29411
29904
|
["EV_COPY_ROWS", 0],
|
|
29412
29905
|
["EV_PASTE_ROWS", 0],
|
|
29413
29906
|
["EV_CLEAR_SELECTION", 0],
|
|
@@ -29437,6 +29930,7 @@ function create_gclass$5(gclass_name) {
|
|
|
29437
29930
|
["EV_OPEN_COLUMNS", 0],
|
|
29438
29931
|
["EV_PAGE_SIZE_CHANGED", 0],
|
|
29439
29932
|
["EV_SHOW_RECORD", 0],
|
|
29933
|
+
["EV_EDIT_RECORD", 0],
|
|
29440
29934
|
["EV_TOGGLE_COLUMN", 0],
|
|
29441
29935
|
["EV_EXPORT_TABLE", 0],
|
|
29442
29936
|
["EV_CREATE_RECORD", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
|
|
@@ -57970,13 +58464,38 @@ function build_graph$1(gobj) {
|
|
|
57970
58464
|
graph.setLayout(layout);
|
|
57971
58465
|
priv.graph_rendered = false;
|
|
57972
58466
|
graph_render(gobj).then(() => {
|
|
58467
|
+
if ((0, _yuneta_gobj_js.gobj_is_destroying)(gobj) || !priv.graph) return;
|
|
57973
58468
|
configure_events(gobj);
|
|
57974
58469
|
configure_behaviour(gobj);
|
|
57975
58470
|
configure_plugins(gobj);
|
|
57976
58471
|
priv.graph_rendered = true;
|
|
58472
|
+
}).catch((e) => {
|
|
58473
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: graph render failed: ${e}`);
|
|
57977
58474
|
});
|
|
57978
58475
|
}
|
|
57979
58476
|
/************************************************************
|
|
58477
|
+
* A G6 event, as PLAIN JSON.
|
|
58478
|
+
*
|
|
58479
|
+
* A kw is dumped by the `machine` trace (`trace_json(kw)`), and a
|
|
58480
|
+
* G6 / @antv/g event is circular: serializing one THROWS, so the
|
|
58481
|
+
* first thing a kw carrying `evt` breaks is the trace the FSM
|
|
58482
|
+
* exists to feed. It also drags a live graph element into a
|
|
58483
|
+
* message that may be read after that element is gone.
|
|
58484
|
+
*
|
|
58485
|
+
* What the actions read of it is an id and three scalars. The
|
|
58486
|
+
* event itself never leaves the callback that received it.
|
|
58487
|
+
************************************************************/
|
|
58488
|
+
function g6_event_kw(evt) {
|
|
58489
|
+
let target = evt && evt.target ? evt.target : null;
|
|
58490
|
+
let client = evt && evt.client ? evt.client : null;
|
|
58491
|
+
return {
|
|
58492
|
+
id: target && target.id ? String(target.id) : "",
|
|
58493
|
+
shift: !!(evt && evt.shiftKey),
|
|
58494
|
+
client_x: client && typeof client.x === "number" ? client.x : 0,
|
|
58495
|
+
client_y: client && typeof client.y === "number" ? client.y : 0
|
|
58496
|
+
};
|
|
58497
|
+
}
|
|
58498
|
+
/************************************************************
|
|
57980
58499
|
* Configure G6 event handlers
|
|
57981
58500
|
************************************************************/
|
|
57982
58501
|
function configure_events(gobj) {
|
|
@@ -57984,7 +58503,7 @@ function configure_events(gobj) {
|
|
|
57984
58503
|
let graph = priv.graph;
|
|
57985
58504
|
graph.on(_antv_g6.CanvasEvent.CLICK, (evt) => {
|
|
57986
58505
|
if (consume_long_press_click(graph)) return;
|
|
57987
|
-
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CANVAS_CLICK",
|
|
58506
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CANVAS_CLICK", g6_event_kw(evt), gobj);
|
|
57988
58507
|
});
|
|
57989
58508
|
graph.on(_antv_g6.NodeEvent.DRAG, (evt) => {
|
|
57990
58509
|
update_resize_handles_position(gobj);
|
|
@@ -57995,21 +58514,21 @@ function configure_events(gobj) {
|
|
|
57995
58514
|
update_port_icon_position(gobj);
|
|
57996
58515
|
});
|
|
57997
58516
|
graph.on(_antv_g6.NodeEvent.DRAG_END, (evt) => {
|
|
57998
|
-
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_DRAG_END",
|
|
58517
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_DRAG_END", g6_event_kw(evt), gobj);
|
|
57999
58518
|
});
|
|
58000
58519
|
graph.on(_antv_g6.NodeEvent.CLICK, (evt) => {
|
|
58001
58520
|
if (consume_long_press_click(graph)) return;
|
|
58002
|
-
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_CLICK",
|
|
58521
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_CLICK", g6_event_kw(evt), gobj);
|
|
58003
58522
|
});
|
|
58004
58523
|
graph.on(_antv_g6.NodeEvent.CONTEXT_MENU, (evt) => {
|
|
58005
|
-
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_CONTEXT_MENU",
|
|
58524
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_CONTEXT_MENU", g6_event_kw(evt), gobj);
|
|
58006
58525
|
});
|
|
58007
58526
|
graph.on(_antv_g6.NodeEvent.DBLCLICK, (evt) => {
|
|
58008
|
-
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_DBLCLICK",
|
|
58527
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_DBLCLICK", g6_event_kw(evt), gobj);
|
|
58009
58528
|
});
|
|
58010
58529
|
graph.on(_antv_g6.EdgeEvent.CLICK, (evt) => {
|
|
58011
58530
|
if (consume_long_press_click(graph)) return;
|
|
58012
|
-
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EDGE_CLICK",
|
|
58531
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EDGE_CLICK", g6_event_kw(evt), gobj);
|
|
58013
58532
|
});
|
|
58014
58533
|
graph.on("aftertransform", () => {
|
|
58015
58534
|
update_resize_handles_position(gobj);
|
|
@@ -58093,7 +58612,7 @@ function configure_plugins(gobj) {
|
|
|
58093
58612
|
graph_add_plugin(gobj, "contextmenu", {
|
|
58094
58613
|
trigger: "contextmenu",
|
|
58095
58614
|
onClick: (value) => {
|
|
58096
|
-
|
|
58615
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CONTEXT_MENU_ITEM", { value: String(value) }, gobj);
|
|
58097
58616
|
},
|
|
58098
58617
|
getItems: (e) => {
|
|
58099
58618
|
return build_context_menu_items(gobj, e);
|
|
@@ -61809,7 +62328,7 @@ function show_edge_icon(gobj) {
|
|
|
61809
62328
|
if (!priv._selected_edge_id) return;
|
|
61810
62329
|
let mid = get_edge_viewport_midpoint(gobj, priv._selected_edge_id);
|
|
61811
62330
|
if (!mid) return;
|
|
61812
|
-
let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(), (0, i18next.t)("edge properties"), () => toggle_edge_popover(gobj), (0, i18next.t)("unlink"), () =>
|
|
62331
|
+
let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(), (0, i18next.t)("edge properties"), () => toggle_edge_popover(gobj), (0, i18next.t)("unlink"), () => (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REQUEST_UNLINK_EDGE", {}, gobj));
|
|
61813
62332
|
priv._edge_icon_el = icons.icon_el;
|
|
61814
62333
|
priv._edge_delete_el = icons.delete_el;
|
|
61815
62334
|
}
|
|
@@ -61964,7 +62483,7 @@ function show_node_icon(gobj) {
|
|
|
61964
62483
|
if (!priv._selected_node_id || priv._selected_port_key) return;
|
|
61965
62484
|
let rect = get_node_viewport_rect(gobj, priv._selected_node_id);
|
|
61966
62485
|
if (!rect) return;
|
|
61967
|
-
let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(), (0, i18next.t)("node properties"), () => toggle_node_popover(gobj), (0, i18next.t)("delete node"), () =>
|
|
62486
|
+
let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(), (0, i18next.t)("node properties"), () => toggle_node_popover(gobj), (0, i18next.t)("delete node"), () => (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REQUEST_DELETE_NODE", {}, gobj));
|
|
61968
62487
|
priv._node_icon_el = icons.icon_el;
|
|
61969
62488
|
priv._node_delete_el = icons.delete_el;
|
|
61970
62489
|
}
|
|
@@ -62677,7 +63196,10 @@ function request_delete_selection(gobj) {
|
|
|
62677
63196
|
execute_delete_selection(gobj, nodes);
|
|
62678
63197
|
return;
|
|
62679
63198
|
}
|
|
62680
|
-
show_confirm_popover(gobj, null, delete_question(nodes), "delete", "#ff4d4f", () =>
|
|
63199
|
+
show_confirm_popover(gobj, null, delete_question(nodes), "delete", "#ff4d4f", () => (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CONFIRMED", {
|
|
63200
|
+
what: "delete_selection",
|
|
63201
|
+
ids: nodes.map((nd) => String(nd.id))
|
|
63202
|
+
}, gobj), "_delete_confirm_el");
|
|
62681
63203
|
}
|
|
62682
63204
|
function execute_delete_selection(gobj, nodes) {
|
|
62683
63205
|
let priv = gobj.priv;
|
|
@@ -62694,7 +63216,10 @@ function execute_delete_selection(gobj, nodes) {
|
|
|
62694
63216
|
************************************************************/
|
|
62695
63217
|
function show_delete_confirm(gobj, nodeData) {
|
|
62696
63218
|
let priv = gobj.priv;
|
|
62697
|
-
show_confirm_popover(gobj, priv._node_delete_el, delete_question([nodeData]), "delete", "#ff4d4f", () =>
|
|
63219
|
+
show_confirm_popover(gobj, priv._node_delete_el, delete_question([nodeData]), "delete", "#ff4d4f", () => (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CONFIRMED", {
|
|
63220
|
+
what: "delete_node",
|
|
63221
|
+
id: String(nodeData.id)
|
|
63222
|
+
}, gobj), "_delete_confirm_el");
|
|
62698
63223
|
}
|
|
62699
63224
|
function hide_delete_confirm(gobj) {
|
|
62700
63225
|
hide_overlay(gobj, "_delete_confirm_el");
|
|
@@ -62792,7 +63317,10 @@ function show_create_popover(gobj) {
|
|
|
62792
63317
|
idInput.focus();
|
|
62793
63318
|
return;
|
|
62794
63319
|
}
|
|
62795
|
-
|
|
63320
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SUBMIT_NEW_NODE", {
|
|
63321
|
+
topic_name: String(topicSelect.value),
|
|
63322
|
+
id: node_id
|
|
63323
|
+
}, gobj);
|
|
62796
63324
|
}
|
|
62797
63325
|
}]);
|
|
62798
63326
|
createBtn = popover.querySelector("button:last-child");
|
|
@@ -62852,7 +63380,10 @@ function execute_unlink_edge(gobj, edgeData) {
|
|
|
62852
63380
|
function show_unlink_confirm(gobj, edgeData) {
|
|
62853
63381
|
let priv = gobj.priv;
|
|
62854
63382
|
const d = edgeData.data;
|
|
62855
|
-
show_confirm_popover(gobj, priv._edge_delete_el, (0, i18next.t)("unlink") + " " + d.child_id + " → " + d.parent_id + "?\n" + (0, i18next.t)("neither record is deleted"), "unlink", "#ff4d4f", () =>
|
|
63383
|
+
show_confirm_popover(gobj, priv._edge_delete_el, (0, i18next.t)("unlink") + " " + d.child_id + " → " + d.parent_id + "?\n" + (0, i18next.t)("neither record is deleted"), "unlink", "#ff4d4f", () => (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CONFIRMED", {
|
|
63384
|
+
what: "unlink_edge",
|
|
63385
|
+
id: String(edgeData.id)
|
|
63386
|
+
}, gobj), "_unlink_confirm_el");
|
|
62856
63387
|
}
|
|
62857
63388
|
function hide_unlink_confirm(gobj) {
|
|
62858
63389
|
hide_overlay(gobj, "_unlink_confirm_el");
|
|
@@ -64735,7 +65266,7 @@ function ac_toggle_node_mode(gobj, event, kw, src) {
|
|
|
64735
65266
|
************************************************************/
|
|
64736
65267
|
function ac_node_dblclick(gobj, event, kw, src) {
|
|
64737
65268
|
let priv = gobj.priv;
|
|
64738
|
-
let node_id = kw && kw.
|
|
65269
|
+
let node_id = kw && kw.id ? kw.id : "";
|
|
64739
65270
|
let nd = null;
|
|
64740
65271
|
try {
|
|
64741
65272
|
nd = priv.graph.getNodeData(node_id);
|
|
@@ -64931,7 +65462,7 @@ function ac_brush_select(gobj, event, kw, src) {
|
|
|
64931
65462
|
function ac_node_click$2(gobj, event, kw, src) {
|
|
64932
65463
|
let priv = gobj.priv;
|
|
64933
65464
|
let graph = priv.graph;
|
|
64934
|
-
let node_id = kw.
|
|
65465
|
+
let node_id = kw && kw.id ? kw.id : "";
|
|
64935
65466
|
let more_nd = null;
|
|
64936
65467
|
try {
|
|
64937
65468
|
more_nd = graph.getNodeData(node_id);
|
|
@@ -64958,10 +65489,10 @@ function ac_node_click$2(gobj, event, kw, src) {
|
|
|
64958
65489
|
topic_name: nodedata.data.desc.topic_name,
|
|
64959
65490
|
record: nodedata.data.record
|
|
64960
65491
|
});
|
|
64961
|
-
if (priv.edit_mode && (kw.
|
|
65492
|
+
if (priv.edit_mode && (kw.shift || priv.selection_mode)) toggle_in_selection(gobj, node_id);
|
|
64962
65493
|
else if (priv.edit_mode) {
|
|
64963
65494
|
let containerRect = priv.$container.getBoundingClientRect();
|
|
64964
|
-
let canvasPoint = graph.getCanvasByViewport([kw.
|
|
65495
|
+
let canvasPoint = graph.getCanvasByViewport([kw.client_x - containerRect.left, kw.client_y - containerRect.top]);
|
|
64965
65496
|
let port_key = detect_port_click(gobj, node_id, canvasPoint[0], canvasPoint[1]);
|
|
64966
65497
|
if (priv._linking_mode) {
|
|
64967
65498
|
if (port_key && try_complete_link(gobj, node_id, port_key)) return 0;
|
|
@@ -64984,7 +65515,7 @@ function ac_node_click$2(gobj, event, kw, src) {
|
|
|
64984
65515
|
function ac_edge_click(gobj, event, kw, src) {
|
|
64985
65516
|
let priv = gobj.priv;
|
|
64986
65517
|
let graph = priv.graph;
|
|
64987
|
-
let edge_id = kw.
|
|
65518
|
+
let edge_id = kw && kw.id ? kw.id : "";
|
|
64988
65519
|
try {
|
|
64989
65520
|
let edgedata = graph.getEdgeData(edge_id);
|
|
64990
65521
|
if (edgedata && edgedata.data) {
|
|
@@ -65013,6 +65544,106 @@ function ac_node_context_menu(gobj, event, kw, src) {
|
|
|
65013
65544
|
return 0;
|
|
65014
65545
|
}
|
|
65015
65546
|
/************************************************************
|
|
65547
|
+
* {value} -- an item of the context menu was picked.
|
|
65548
|
+
************************************************************/
|
|
65549
|
+
function ac_context_menu_item(gobj, event, kw, src) {
|
|
65550
|
+
handle_context_menu_click(gobj, kw && kw.value ? kw.value : "");
|
|
65551
|
+
return 0;
|
|
65552
|
+
}
|
|
65553
|
+
/************************************************************
|
|
65554
|
+
* The delete icon floating beside the selected node.
|
|
65555
|
+
************************************************************/
|
|
65556
|
+
function ac_request_delete_node(gobj, event, kw, src) {
|
|
65557
|
+
request_delete_node(gobj);
|
|
65558
|
+
return 0;
|
|
65559
|
+
}
|
|
65560
|
+
/************************************************************
|
|
65561
|
+
* The unlink icon floating beside the selected edge.
|
|
65562
|
+
************************************************************/
|
|
65563
|
+
function ac_request_unlink_edge(gobj, event, kw, src) {
|
|
65564
|
+
request_unlink_edge(gobj);
|
|
65565
|
+
return 0;
|
|
65566
|
+
}
|
|
65567
|
+
/************************************************************
|
|
65568
|
+
* {topic_name, id} -- the create popover was filled in.
|
|
65569
|
+
************************************************************/
|
|
65570
|
+
function ac_submit_new_node(gobj, event, kw, src) {
|
|
65571
|
+
let topic_name = kw && kw.topic_name ? kw.topic_name : "";
|
|
65572
|
+
let node_id = kw && kw.id ? kw.id : "";
|
|
65573
|
+
if (!topic_name || !node_id) {
|
|
65574
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} without topic or id`);
|
|
65575
|
+
return -1;
|
|
65576
|
+
}
|
|
65577
|
+
execute_create_node(gobj, topic_name, node_id);
|
|
65578
|
+
return 0;
|
|
65579
|
+
}
|
|
65580
|
+
/************************************************************
|
|
65581
|
+
* {what, id|ids} -- the person said yes in a confirm popover.
|
|
65582
|
+
*
|
|
65583
|
+
* The kw carries IDS and not the graph elements they name: a kw
|
|
65584
|
+
* is plain json, and a node/edge datum of a live graph is not
|
|
65585
|
+
* something to park in a message. They are resolved here, where
|
|
65586
|
+
* the graph is -- and an id that no longer names anything is a
|
|
65587
|
+
* loud refusal instead of a silent nothing.
|
|
65588
|
+
************************************************************/
|
|
65589
|
+
function ac_confirmed$1(gobj, event, kw, src) {
|
|
65590
|
+
let graph = gobj.priv.graph;
|
|
65591
|
+
let what = kw && kw.what ? kw.what : "";
|
|
65592
|
+
if (!graph) {
|
|
65593
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} with no graph`);
|
|
65594
|
+
return -1;
|
|
65595
|
+
}
|
|
65596
|
+
if (what === "delete_selection") {
|
|
65597
|
+
let ids = kw && Array.isArray(kw.ids) ? kw.ids : [];
|
|
65598
|
+
let nodes = [];
|
|
65599
|
+
for (let id of ids) {
|
|
65600
|
+
let nd = null;
|
|
65601
|
+
try {
|
|
65602
|
+
nd = graph.getNodeData(id);
|
|
65603
|
+
} catch (e) {
|
|
65604
|
+
nd = null;
|
|
65605
|
+
}
|
|
65606
|
+
if (nd && nd.data && nd.data.desc) nodes.push(nd);
|
|
65607
|
+
}
|
|
65608
|
+
if (!nodes.length) {
|
|
65609
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no node any more`);
|
|
65610
|
+
return -1;
|
|
65611
|
+
}
|
|
65612
|
+
execute_delete_selection(gobj, nodes);
|
|
65613
|
+
return 0;
|
|
65614
|
+
}
|
|
65615
|
+
if (what === "delete_node") {
|
|
65616
|
+
let nd = null;
|
|
65617
|
+
try {
|
|
65618
|
+
nd = graph.getNodeData(kw.id);
|
|
65619
|
+
} catch (e) {
|
|
65620
|
+
nd = null;
|
|
65621
|
+
}
|
|
65622
|
+
if (!nd || !nd.data || !nd.data.desc) {
|
|
65623
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no node: ${kw.id}`);
|
|
65624
|
+
return -1;
|
|
65625
|
+
}
|
|
65626
|
+
execute_delete_node(gobj, nd);
|
|
65627
|
+
return 0;
|
|
65628
|
+
}
|
|
65629
|
+
if (what === "unlink_edge") {
|
|
65630
|
+
let ed = null;
|
|
65631
|
+
try {
|
|
65632
|
+
ed = graph.getEdgeData(kw.id);
|
|
65633
|
+
} catch (e) {
|
|
65634
|
+
ed = null;
|
|
65635
|
+
}
|
|
65636
|
+
if (!ed || !ed.data) {
|
|
65637
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} names no edge: ${kw.id}`);
|
|
65638
|
+
return -1;
|
|
65639
|
+
}
|
|
65640
|
+
execute_unlink_edge(gobj, ed);
|
|
65641
|
+
return 0;
|
|
65642
|
+
}
|
|
65643
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} of nothing this view asked about: ${what}`);
|
|
65644
|
+
return -1;
|
|
65645
|
+
}
|
|
65646
|
+
/************************************************************
|
|
65016
65647
|
* Canvas click
|
|
65017
65648
|
************************************************************/
|
|
65018
65649
|
function ac_canvas_click(gobj, event, kw, src) {
|
|
@@ -65176,6 +65807,31 @@ function create_gclass$4(gclass_name) {
|
|
|
65176
65807
|
ac_node_context_menu,
|
|
65177
65808
|
null
|
|
65178
65809
|
],
|
|
65810
|
+
[
|
|
65811
|
+
"EV_CONTEXT_MENU_ITEM",
|
|
65812
|
+
ac_context_menu_item,
|
|
65813
|
+
null
|
|
65814
|
+
],
|
|
65815
|
+
[
|
|
65816
|
+
"EV_REQUEST_DELETE_NODE",
|
|
65817
|
+
ac_request_delete_node,
|
|
65818
|
+
null
|
|
65819
|
+
],
|
|
65820
|
+
[
|
|
65821
|
+
"EV_REQUEST_UNLINK_EDGE",
|
|
65822
|
+
ac_request_unlink_edge,
|
|
65823
|
+
null
|
|
65824
|
+
],
|
|
65825
|
+
[
|
|
65826
|
+
"EV_SUBMIT_NEW_NODE",
|
|
65827
|
+
ac_submit_new_node,
|
|
65828
|
+
null
|
|
65829
|
+
],
|
|
65830
|
+
[
|
|
65831
|
+
"EV_CONFIRMED",
|
|
65832
|
+
ac_confirmed$1,
|
|
65833
|
+
null
|
|
65834
|
+
],
|
|
65179
65835
|
[
|
|
65180
65836
|
"EV_CANVAS_CLICK",
|
|
65181
65837
|
ac_canvas_click,
|
|
@@ -65392,6 +66048,11 @@ function create_gclass$4(gclass_name) {
|
|
|
65392
66048
|
["EV_NODE_CLICK", 0],
|
|
65393
66049
|
["EV_EDGE_CLICK", 0],
|
|
65394
66050
|
["EV_NODE_CONTEXT_MENU", 0],
|
|
66051
|
+
["EV_CONTEXT_MENU_ITEM", 0],
|
|
66052
|
+
["EV_REQUEST_DELETE_NODE", 0],
|
|
66053
|
+
["EV_REQUEST_UNLINK_EDGE", 0],
|
|
66054
|
+
["EV_SUBMIT_NEW_NODE", 0],
|
|
66055
|
+
["EV_CONFIRMED", 0],
|
|
65395
66056
|
["EV_CANVAS_CLICK", 0],
|
|
65396
66057
|
["EV_NODE_DRAG_END", 0],
|
|
65397
66058
|
["EV_BRUSH_SELECT", 0],
|
|
@@ -68725,361 +69386,6 @@ function register_c_yui_treedb_schema() {
|
|
|
68725
69386
|
return create_gclass$2(GCLASS_NAME$2);
|
|
68726
69387
|
}
|
|
68727
69388
|
//#endregion
|
|
68728
|
-
//#region src/schema_model.js
|
|
68729
|
-
/***********************************************************************
|
|
68730
|
-
* schema_model.js
|
|
68731
|
-
*
|
|
68732
|
-
* A SCHEMA out of the three meta-topics that store it.
|
|
68733
|
-
*
|
|
68734
|
-
* `treedb_system_schema` keeps every schema of a yuno as DATA, in
|
|
68735
|
-
* three flat topics — `treedbs`, `topics`, `cols` — linked by
|
|
68736
|
-
* fkeys. That is the right STORAGE and the wrong thing to put in
|
|
68737
|
-
* front of an operator: adding one column to one topic means
|
|
68738
|
-
* finding it in a table holding every column of every topic of
|
|
68739
|
-
* every treedb the yuno has.
|
|
68740
|
-
*
|
|
68741
|
-
* This module turns those three lists back into what they
|
|
68742
|
-
* describe: treedb -> topics -> columns, each in its declared
|
|
68743
|
-
* `order`. Pure, so the editor's model can be tested with no DOM
|
|
68744
|
-
* and no backend — the gclass fetches and draws, this decides
|
|
68745
|
-
* what the records MEAN.
|
|
68746
|
-
*
|
|
68747
|
-
* TWO THINGS IT EXISTS TO STATE ONCE:
|
|
68748
|
-
*
|
|
68749
|
-
* 1. A record is grouped by its FKEY, not by its id. The id is
|
|
68750
|
-
* qualified (`<treedb>.<topic>.<column>`) and reading it back
|
|
68751
|
-
* with a split on '.' works right up to the first treedb or
|
|
68752
|
-
* topic whose name carries one. The fkey is the link the store
|
|
68753
|
-
* itself uses; the id is only the fallback for a record whose
|
|
68754
|
-
* fkey never arrived.
|
|
68755
|
-
*
|
|
68756
|
-
* 2. A fkey reaches this code in FOUR shapes, because `nodes`
|
|
68757
|
-
* answers differently depending on the options it was called
|
|
68758
|
-
* with: the bare ref string, the expanded object, a list of
|
|
68759
|
-
* either, or a dict keyed by the ref. All four are the same
|
|
68760
|
-
* fact and parse_fkey_ref() flattens them to it.
|
|
68761
|
-
*
|
|
68762
|
-
* Copyright (c) 2026, ArtGins.
|
|
68763
|
-
* All Rights Reserved.
|
|
68764
|
-
***********************************************************************/
|
|
68765
|
-
var DEFAULT_ORDER = 9999;
|
|
68766
|
-
/***************************************************************
|
|
68767
|
-
* parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
|
|
68768
|
-
*
|
|
68769
|
-
* Every shape a fkey column can arrive in, flattened to the
|
|
68770
|
-
* refs it names. Anything unrecognizable contributes nothing —
|
|
68771
|
-
* a schema drawn without one link beats a screen that throws.
|
|
68772
|
-
***************************************************************/
|
|
68773
|
-
function parse_fkey_ref(value) {
|
|
68774
|
-
let refs = [];
|
|
68775
|
-
let push_one = (v) => {
|
|
68776
|
-
if (typeof v === "string") {
|
|
68777
|
-
let parts = v.split("^");
|
|
68778
|
-
if (parts.length !== 3) return;
|
|
68779
|
-
refs.push({
|
|
68780
|
-
topic_name: parts[0],
|
|
68781
|
-
id: parts[1],
|
|
68782
|
-
hook_name: parts[2]
|
|
68783
|
-
});
|
|
68784
|
-
return;
|
|
68785
|
-
}
|
|
68786
|
-
if (v && typeof v === "object") {
|
|
68787
|
-
let id = v.id;
|
|
68788
|
-
if (typeof id !== "string" || id.length === 0) return;
|
|
68789
|
-
refs.push({
|
|
68790
|
-
topic_name: v.topic_name || "",
|
|
68791
|
-
id,
|
|
68792
|
-
hook_name: v.hook_name || ""
|
|
68793
|
-
});
|
|
68794
|
-
}
|
|
68795
|
-
};
|
|
68796
|
-
if (value === null || value === void 0) return refs;
|
|
68797
|
-
if (typeof value === "string") {
|
|
68798
|
-
push_one(value);
|
|
68799
|
-
return refs;
|
|
68800
|
-
}
|
|
68801
|
-
if (Array.isArray(value)) {
|
|
68802
|
-
for (let v of value) push_one(v);
|
|
68803
|
-
return refs;
|
|
68804
|
-
}
|
|
68805
|
-
if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
|
|
68806
|
-
else push_one(key);
|
|
68807
|
-
return refs;
|
|
68808
|
-
}
|
|
68809
|
-
/***************************************************************
|
|
68810
|
-
* The id of the FIRST parent a record names, or "" when it
|
|
68811
|
-
* names none. A record has one parent here — a column belongs to
|
|
68812
|
-
* one topic, a topic to one treedb — and a second ref would be a
|
|
68813
|
-
* store this editor cannot draw anyway.
|
|
68814
|
-
***************************************************************/
|
|
68815
|
-
function parent_id(record, fkey_col) {
|
|
68816
|
-
let refs = parse_fkey_ref(record ? record[fkey_col] : null);
|
|
68817
|
-
return refs.length > 0 ? refs[0].id : "";
|
|
68818
|
-
}
|
|
68819
|
-
/***************************************************************
|
|
68820
|
-
* The name a record is KNOWN by, which is not what it is keyed
|
|
68821
|
-
* by: `topics` and `cols` key by the qualified id and carry the
|
|
68822
|
-
* bare name in `value` (its pkey2). An older store that still
|
|
68823
|
-
* keyed by rowid has no `value` worth showing, so the id stands
|
|
68824
|
-
* in — the same fallback as node_label().
|
|
68825
|
-
***************************************************************/
|
|
68826
|
-
function record_name(record) {
|
|
68827
|
-
if (!record) return "";
|
|
68828
|
-
if (typeof record.value === "string" && record.value.length > 0) return record.value;
|
|
68829
|
-
return typeof record.id === "string" ? record.id : "";
|
|
68830
|
-
}
|
|
68831
|
-
/***************************************************************
|
|
68832
|
-
* The `order` of a record as a NUMBER, whatever the store put
|
|
68833
|
-
* there: an integer column read back from JSON may arrive as a
|
|
68834
|
-
* string, and comparing "10" with 9 sorts the schema wrong.
|
|
68835
|
-
***************************************************************/
|
|
68836
|
-
function record_order(record) {
|
|
68837
|
-
let order = record ? record.order : void 0;
|
|
68838
|
-
if (typeof order === "number" && isFinite(order)) return order;
|
|
68839
|
-
if (typeof order === "string" && order.trim().length > 0) {
|
|
68840
|
-
let n = Number(order);
|
|
68841
|
-
if (isFinite(n)) return n;
|
|
68842
|
-
}
|
|
68843
|
-
return DEFAULT_ORDER;
|
|
68844
|
-
}
|
|
68845
|
-
/***************************************************************
|
|
68846
|
-
* Sort in place by `order`, ties broken by name so a schema
|
|
68847
|
-
* whose records all default to 9999 still draws the same way
|
|
68848
|
-
* twice.
|
|
68849
|
-
***************************************************************/
|
|
68850
|
-
function sort_by_order(list) {
|
|
68851
|
-
list.sort((a, b) => {
|
|
68852
|
-
if (a.order !== b.order) return a.order - b.order;
|
|
68853
|
-
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
68854
|
-
});
|
|
68855
|
-
return list;
|
|
68856
|
-
}
|
|
68857
|
-
/***************************************************************
|
|
68858
|
-
* build_schema_model({treedbs, topics, cols}) -> model
|
|
68859
|
-
*
|
|
68860
|
-
* model = {
|
|
68861
|
-
* treedbs: [{
|
|
68862
|
-
* id, name, record,
|
|
68863
|
-
* schema_version, c_schema_version, system_schema_version,
|
|
68864
|
-
* topics: [{
|
|
68865
|
-
* id, name, order, record,
|
|
68866
|
-
* pkey, topic_version, system_topic, main_topic,
|
|
68867
|
-
* cols: [{id, name, order, record}]
|
|
68868
|
-
* }]
|
|
68869
|
-
* }],
|
|
68870
|
-
* orphan_topics: [...], topics whose treedb is not in the store
|
|
68871
|
-
* orphan_cols: [...] columns whose topic is not in the store
|
|
68872
|
-
* }
|
|
68873
|
-
*
|
|
68874
|
-
* An orphan is NOT dropped. A column whose topic was deleted is
|
|
68875
|
-
* exactly the kind of leftover an operator opens this editor to
|
|
68876
|
-
* find, and a model that hides it makes the editor lie.
|
|
68877
|
-
***************************************************************/
|
|
68878
|
-
function build_schema_model(records) {
|
|
68879
|
-
let r = records || {};
|
|
68880
|
-
let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
|
|
68881
|
-
let topic_rows = Array.isArray(r.topics) ? r.topics : [];
|
|
68882
|
-
let col_rows = Array.isArray(r.cols) ? r.cols : [];
|
|
68883
|
-
let treedbs = [];
|
|
68884
|
-
let by_treedb_id = {};
|
|
68885
|
-
for (let record of treedb_rows) {
|
|
68886
|
-
if (!record || typeof record.id !== "string") continue;
|
|
68887
|
-
let entry = {
|
|
68888
|
-
id: record.id,
|
|
68889
|
-
name: record.id,
|
|
68890
|
-
record,
|
|
68891
|
-
schema_version: record.schema_version,
|
|
68892
|
-
c_schema_version: record.c_schema_version,
|
|
68893
|
-
system_schema_version: record.system_schema_version,
|
|
68894
|
-
topics: []
|
|
68895
|
-
};
|
|
68896
|
-
treedbs.push(entry);
|
|
68897
|
-
by_treedb_id[entry.id] = entry;
|
|
68898
|
-
}
|
|
68899
|
-
let orphan_topics = [];
|
|
68900
|
-
let by_topic_id = {};
|
|
68901
|
-
for (let record of topic_rows) {
|
|
68902
|
-
if (!record || typeof record.id !== "string") continue;
|
|
68903
|
-
let entry = {
|
|
68904
|
-
id: record.id,
|
|
68905
|
-
name: record_name(record),
|
|
68906
|
-
order: record_order(record),
|
|
68907
|
-
record,
|
|
68908
|
-
treedb_id: parent_id(record, "treedbs"),
|
|
68909
|
-
pkey: record.pkey || "",
|
|
68910
|
-
pkey2s: record.pkey2s,
|
|
68911
|
-
system_flag: record.system_flag || "",
|
|
68912
|
-
tkey: record.tkey || "",
|
|
68913
|
-
topic_version: record.topic_version,
|
|
68914
|
-
system_topic: !!record.system_topic,
|
|
68915
|
-
main_topic: !!record.main_topic,
|
|
68916
|
-
cols: []
|
|
68917
|
-
};
|
|
68918
|
-
by_topic_id[entry.id] = entry;
|
|
68919
|
-
let parent = by_treedb_id[entry.treedb_id];
|
|
68920
|
-
if (parent) parent.topics.push(entry);
|
|
68921
|
-
else orphan_topics.push(entry);
|
|
68922
|
-
}
|
|
68923
|
-
let orphan_cols = [];
|
|
68924
|
-
for (let record of col_rows) {
|
|
68925
|
-
if (!record || typeof record.id !== "string") continue;
|
|
68926
|
-
let entry = {
|
|
68927
|
-
id: record.id,
|
|
68928
|
-
name: record_name(record),
|
|
68929
|
-
order: record_order(record),
|
|
68930
|
-
record,
|
|
68931
|
-
topic_id: parent_id(record, "topics")
|
|
68932
|
-
};
|
|
68933
|
-
let parent = by_topic_id[entry.topic_id];
|
|
68934
|
-
if (parent) parent.cols.push(entry);
|
|
68935
|
-
else orphan_cols.push(entry);
|
|
68936
|
-
}
|
|
68937
|
-
for (let treedb of treedbs) {
|
|
68938
|
-
sort_by_order(treedb.topics);
|
|
68939
|
-
for (let topic of treedb.topics) sort_by_order(topic.cols);
|
|
68940
|
-
}
|
|
68941
|
-
for (let topic of orphan_topics) sort_by_order(topic.cols);
|
|
68942
|
-
treedbs.sort((a, b) => {
|
|
68943
|
-
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
68944
|
-
});
|
|
68945
|
-
sort_by_order(orphan_topics);
|
|
68946
|
-
sort_by_order(orphan_cols);
|
|
68947
|
-
return {
|
|
68948
|
-
treedbs,
|
|
68949
|
-
orphan_topics,
|
|
68950
|
-
orphan_cols
|
|
68951
|
-
};
|
|
68952
|
-
}
|
|
68953
|
-
/***************************************************************
|
|
68954
|
-
* Is there a value here at all?
|
|
68955
|
-
*
|
|
68956
|
-
* The store answers a `blob` column that was never set with an
|
|
68957
|
-
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
68958
|
-
* `default: {}`. Read as "a value", those turn every export into
|
|
68959
|
-
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
68960
|
-
* is a hook with no mapping, which is a link the treedb builds
|
|
68961
|
-
* and nothing writes.
|
|
68962
|
-
***************************************************************/
|
|
68963
|
-
function is_empty_value(value) {
|
|
68964
|
-
if (value === null || value === void 0 || value === "") return true;
|
|
68965
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
68966
|
-
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
68967
|
-
return false;
|
|
68968
|
-
}
|
|
68969
|
-
/***************************************************************
|
|
68970
|
-
* The fields of a column that are DECLARED as one thing and
|
|
68971
|
-
* STORED as another.
|
|
68972
|
-
*
|
|
68973
|
-
* `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
|
|
68974
|
-
* columns of the `cols` topic (treedb_system_schema.c), so what
|
|
68975
|
-
* comes back may be the value or the JSON text of the value,
|
|
68976
|
-
* depending on how it was written — a projection writes the
|
|
68977
|
-
* value, a hand edit through a plain text field writes the text.
|
|
68978
|
-
* `flag` is an array that a single-flag edit can leave as a bare
|
|
68979
|
-
* string. Reading these without saying so is how a hook drawn
|
|
68980
|
-
* from `{"users":"department"}` becomes no hook at all.
|
|
68981
|
-
***************************************************************/
|
|
68982
|
-
function as_json(value) {
|
|
68983
|
-
if (typeof value !== "string") return value;
|
|
68984
|
-
let text = value.trim();
|
|
68985
|
-
if (text.length === 0) return null;
|
|
68986
|
-
try {
|
|
68987
|
-
return JSON.parse(text);
|
|
68988
|
-
} catch (e) {
|
|
68989
|
-
return value;
|
|
68990
|
-
}
|
|
68991
|
-
}
|
|
68992
|
-
function col_flags(record) {
|
|
68993
|
-
let flag = record ? record.flag : null;
|
|
68994
|
-
if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
68995
|
-
if (typeof flag === "string" && flag.length > 0) {
|
|
68996
|
-
let parsed = as_json(flag);
|
|
68997
|
-
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
|
|
68998
|
-
return [flag];
|
|
68999
|
-
}
|
|
69000
|
-
return [];
|
|
69001
|
-
}
|
|
69002
|
-
function col_hook(record) {
|
|
69003
|
-
let hook = as_json(record ? record.hook : null);
|
|
69004
|
-
if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
|
|
69005
|
-
return null;
|
|
69006
|
-
}
|
|
69007
|
-
function col_enum(record) {
|
|
69008
|
-
let e = as_json(record ? record.enum : null);
|
|
69009
|
-
if (Array.isArray(e)) return e;
|
|
69010
|
-
return [];
|
|
69011
|
-
}
|
|
69012
|
-
function topic_pkey2s(record) {
|
|
69013
|
-
let pkey2s = as_json(record ? record.pkey2s : null);
|
|
69014
|
-
if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
|
|
69015
|
-
if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
|
|
69016
|
-
return [];
|
|
69017
|
-
}
|
|
69018
|
-
/***************************************************************
|
|
69019
|
-
* Lookups by id. The editor addresses a position by NAME (the
|
|
69020
|
-
* url carries `<treedb>/<topic>`), so both are offered.
|
|
69021
|
-
***************************************************************/
|
|
69022
|
-
function find_treedb(model, treedb_id) {
|
|
69023
|
-
if (!model || !Array.isArray(model.treedbs)) return null;
|
|
69024
|
-
for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
|
|
69025
|
-
return null;
|
|
69026
|
-
}
|
|
69027
|
-
function find_topic(model, treedb_id, topic_name) {
|
|
69028
|
-
let treedb = find_treedb(model, treedb_id);
|
|
69029
|
-
if (!treedb) return null;
|
|
69030
|
-
for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
|
|
69031
|
-
return null;
|
|
69032
|
-
}
|
|
69033
|
-
function find_col(model, treedb_id, topic_name, col_name) {
|
|
69034
|
-
let topic = find_topic(model, treedb_id, topic_name);
|
|
69035
|
-
if (!topic) return null;
|
|
69036
|
-
for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
|
|
69037
|
-
return null;
|
|
69038
|
-
}
|
|
69039
|
-
/***************************************************************
|
|
69040
|
-
* The fkey a NEW record must carry to belong to `parent_id`.
|
|
69041
|
-
* Written here because the editor is the only writer that has to
|
|
69042
|
-
* compose one, and getting the hook name wrong links the record
|
|
69043
|
-
* to nothing while the write still succeeds.
|
|
69044
|
-
***************************************************************/
|
|
69045
|
-
function fkey_ref(parent_topic, parent_id_, hook_name) {
|
|
69046
|
-
return `${parent_topic}^${parent_id_}^${hook_name}`;
|
|
69047
|
-
}
|
|
69048
|
-
/***************************************************************
|
|
69049
|
-
* Where a new sibling goes: after the last one. The store's own
|
|
69050
|
-
* default (9999) would put every hand-made column in the same
|
|
69051
|
-
* place and let their names decide the schema's order.
|
|
69052
|
-
***************************************************************/
|
|
69053
|
-
function next_order(siblings) {
|
|
69054
|
-
let max = 0;
|
|
69055
|
-
if (Array.isArray(siblings)) for (let s of siblings) {
|
|
69056
|
-
let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
|
|
69057
|
-
if (order < 9999 && order > max) max = order;
|
|
69058
|
-
}
|
|
69059
|
-
return max + 1;
|
|
69060
|
-
}
|
|
69061
|
-
/***************************************************************
|
|
69062
|
-
* Renumber a list AFTER a move, as the writes it takes: only the
|
|
69063
|
-
* records whose order actually changed, so a drag of one row in a
|
|
69064
|
-
* 40-column topic is not 40 writes.
|
|
69065
|
-
*
|
|
69066
|
-
* moved_orders(list) -> [{id, order}, ...]
|
|
69067
|
-
***************************************************************/
|
|
69068
|
-
function moved_orders(list) {
|
|
69069
|
-
let writes = [];
|
|
69070
|
-
if (!Array.isArray(list)) return writes;
|
|
69071
|
-
for (let i = 0; i < list.length; i++) {
|
|
69072
|
-
let entry = list[i];
|
|
69073
|
-
let order = i + 1;
|
|
69074
|
-
if (!entry || typeof entry.id !== "string") continue;
|
|
69075
|
-
if (entry.order !== order) writes.push({
|
|
69076
|
-
id: entry.id,
|
|
69077
|
-
order
|
|
69078
|
-
});
|
|
69079
|
-
}
|
|
69080
|
-
return writes;
|
|
69081
|
-
}
|
|
69082
|
-
//#endregion
|
|
69083
69389
|
//#region src/schema_validate.js
|
|
69084
69390
|
/***********************************************************************
|
|
69085
69391
|
* schema_validate.js
|