@yuneta/gobj-ui 7.23.170 → 7.23.172
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 +24 -0
- package/dist/gobj-ui.cjs.js +668 -737
- package/dist/gobj-ui.es.js +669 -738
- package/package.json +3 -3
- package/src/c_yui_treedb_schema.js +26 -5
- package/src/c_yui_treedb_topics.css +3 -0
- package/src/c_yui_treedb_topics.js +57 -35
- package/src/treedb_topic_keys.js +122 -0
- package/src/treedb_topic_keys.test.js +79 -0
- package/src/yui_dev.js +152 -363
- package/src/dev_machine_trace.js +0 -116
- package/src/dev_machine_trace.test.js +0 -123
package/dist/gobj-ui.es.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, delete_from_list, duplicate_objects, empty_json, empty_string, escapeHtml, event_flag_t, flat_diff, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_bottom_gobj, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_name, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json2flat, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
|
|
1
|
+
import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, delete_from_list, duplicate_objects, empty_json, empty_string, escapeHtml, event_flag_t, flat_diff, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_bottom_gobj, gobj_change_state, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_get_gclass_trace_level2, gobj_global_trace_level, gobj_global_trace_no_level, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_post_event, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_start_tree, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, gobj_yuno_name, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_string, json2flat, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_console_log_enabled, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_level_t, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
|
|
3
2
|
import i18next, { t } from "i18next";
|
|
4
3
|
import TomSelect from "tom-select";
|
|
5
4
|
import { createJSONEditor } from "vanilla-jsoneditor";
|
|
@@ -23046,13 +23045,455 @@ function nodes_answer(data) {
|
|
|
23046
23045
|
};
|
|
23047
23046
|
}
|
|
23048
23047
|
//#endregion
|
|
23048
|
+
//#region src/schema_model.js
|
|
23049
|
+
/***********************************************************************
|
|
23050
|
+
* schema_model.js
|
|
23051
|
+
*
|
|
23052
|
+
* A SCHEMA out of the three meta-topics that store it.
|
|
23053
|
+
*
|
|
23054
|
+
* `treedb_system_schema` keeps every schema of a yuno as DATA, in
|
|
23055
|
+
* three flat topics — `treedbs`, `topics`, `cols` — linked by
|
|
23056
|
+
* fkeys. That is the right STORAGE and the wrong thing to put in
|
|
23057
|
+
* front of an operator: adding one column to one topic means
|
|
23058
|
+
* finding it in a table holding every column of every topic of
|
|
23059
|
+
* every treedb the yuno has.
|
|
23060
|
+
*
|
|
23061
|
+
* This module turns those three lists back into what they
|
|
23062
|
+
* describe: treedb -> topics -> columns, each in its declared
|
|
23063
|
+
* `order`. Pure, so the editor's model can be tested with no DOM
|
|
23064
|
+
* and no backend — the gclass fetches and draws, this decides
|
|
23065
|
+
* what the records MEAN.
|
|
23066
|
+
*
|
|
23067
|
+
* TWO THINGS IT EXISTS TO STATE ONCE:
|
|
23068
|
+
*
|
|
23069
|
+
* 1. A record is grouped by its FKEY, not by its id. The id is
|
|
23070
|
+
* qualified (`<treedb>.<topic>.<column>`) and reading it back
|
|
23071
|
+
* with a split on '.' works right up to the first treedb or
|
|
23072
|
+
* topic whose name carries one. The fkey is the link the store
|
|
23073
|
+
* itself uses; the id is only the fallback for a record whose
|
|
23074
|
+
* fkey never arrived.
|
|
23075
|
+
*
|
|
23076
|
+
* 2. A fkey reaches this code in FOUR shapes, because `nodes`
|
|
23077
|
+
* answers differently depending on the options it was called
|
|
23078
|
+
* with: the bare ref string, the expanded object, a list of
|
|
23079
|
+
* either, or a dict keyed by the ref. All four are the same
|
|
23080
|
+
* fact and parse_fkey_ref() flattens them to it.
|
|
23081
|
+
*
|
|
23082
|
+
* Copyright (c) 2026, ArtGins.
|
|
23083
|
+
* All Rights Reserved.
|
|
23084
|
+
***********************************************************************/
|
|
23085
|
+
var DEFAULT_ORDER = 9999;
|
|
23086
|
+
/***************************************************************
|
|
23087
|
+
* parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
|
|
23088
|
+
*
|
|
23089
|
+
* Every shape a fkey column can arrive in, flattened to the
|
|
23090
|
+
* refs it names. Anything unrecognizable contributes nothing —
|
|
23091
|
+
* a schema drawn without one link beats a screen that throws.
|
|
23092
|
+
***************************************************************/
|
|
23093
|
+
function parse_fkey_ref(value) {
|
|
23094
|
+
let refs = [];
|
|
23095
|
+
let push_one = (v) => {
|
|
23096
|
+
if (typeof v === "string") {
|
|
23097
|
+
let parts = v.split("^");
|
|
23098
|
+
if (parts.length !== 3) return;
|
|
23099
|
+
refs.push({
|
|
23100
|
+
topic_name: parts[0],
|
|
23101
|
+
id: parts[1],
|
|
23102
|
+
hook_name: parts[2]
|
|
23103
|
+
});
|
|
23104
|
+
return;
|
|
23105
|
+
}
|
|
23106
|
+
if (v && typeof v === "object") {
|
|
23107
|
+
let id = v.id;
|
|
23108
|
+
if (typeof id !== "string" || id.length === 0) return;
|
|
23109
|
+
refs.push({
|
|
23110
|
+
topic_name: v.topic_name || "",
|
|
23111
|
+
id,
|
|
23112
|
+
hook_name: v.hook_name || ""
|
|
23113
|
+
});
|
|
23114
|
+
}
|
|
23115
|
+
};
|
|
23116
|
+
if (value === null || value === void 0) return refs;
|
|
23117
|
+
if (typeof value === "string") {
|
|
23118
|
+
push_one(value);
|
|
23119
|
+
return refs;
|
|
23120
|
+
}
|
|
23121
|
+
if (Array.isArray(value)) {
|
|
23122
|
+
for (let v of value) push_one(v);
|
|
23123
|
+
return refs;
|
|
23124
|
+
}
|
|
23125
|
+
if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
|
|
23126
|
+
else push_one(key);
|
|
23127
|
+
return refs;
|
|
23128
|
+
}
|
|
23129
|
+
/***************************************************************
|
|
23130
|
+
* The id of the FIRST parent a record names, or "" when it
|
|
23131
|
+
* names none. A record has one parent here — a column belongs to
|
|
23132
|
+
* one topic, a topic to one treedb — and a second ref would be a
|
|
23133
|
+
* store this editor cannot draw anyway.
|
|
23134
|
+
***************************************************************/
|
|
23135
|
+
function parent_id(record, fkey_col) {
|
|
23136
|
+
let refs = parse_fkey_ref(record ? record[fkey_col] : null);
|
|
23137
|
+
return refs.length > 0 ? refs[0].id : "";
|
|
23138
|
+
}
|
|
23139
|
+
/***************************************************************
|
|
23140
|
+
* The name a record is KNOWN by, which is not what it is keyed
|
|
23141
|
+
* by: `topics` and `cols` key by the qualified id and carry the
|
|
23142
|
+
* bare name in `value` (its pkey2). An older store that still
|
|
23143
|
+
* keyed by rowid has no `value` worth showing, so the id stands
|
|
23144
|
+
* in — the same fallback as node_label().
|
|
23145
|
+
***************************************************************/
|
|
23146
|
+
function record_name(record) {
|
|
23147
|
+
if (!record) return "";
|
|
23148
|
+
if (typeof record.value === "string" && record.value.length > 0) return record.value;
|
|
23149
|
+
return typeof record.id === "string" ? record.id : "";
|
|
23150
|
+
}
|
|
23151
|
+
/***************************************************************
|
|
23152
|
+
* The `order` of a record as a NUMBER, whatever the store put
|
|
23153
|
+
* there: an integer column read back from JSON may arrive as a
|
|
23154
|
+
* string, and comparing "10" with 9 sorts the schema wrong.
|
|
23155
|
+
***************************************************************/
|
|
23156
|
+
function record_order(record) {
|
|
23157
|
+
let order = record ? record.order : void 0;
|
|
23158
|
+
if (typeof order === "number" && isFinite(order)) return order;
|
|
23159
|
+
if (typeof order === "string" && order.trim().length > 0) {
|
|
23160
|
+
let n = Number(order);
|
|
23161
|
+
if (isFinite(n)) return n;
|
|
23162
|
+
}
|
|
23163
|
+
return DEFAULT_ORDER;
|
|
23164
|
+
}
|
|
23165
|
+
/***************************************************************
|
|
23166
|
+
* Sort in place by `order`, ties broken by name so a schema
|
|
23167
|
+
* whose records all default to 9999 still draws the same way
|
|
23168
|
+
* twice.
|
|
23169
|
+
***************************************************************/
|
|
23170
|
+
function sort_by_order(list) {
|
|
23171
|
+
list.sort((a, b) => {
|
|
23172
|
+
if (a.order !== b.order) return a.order - b.order;
|
|
23173
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
23174
|
+
});
|
|
23175
|
+
return list;
|
|
23176
|
+
}
|
|
23177
|
+
/***************************************************************
|
|
23178
|
+
* build_schema_model({treedbs, topics, cols}) -> model
|
|
23179
|
+
*
|
|
23180
|
+
* model = {
|
|
23181
|
+
* treedbs: [{
|
|
23182
|
+
* id, name, record,
|
|
23183
|
+
* schema_version, c_schema_version, system_schema_version,
|
|
23184
|
+
* topics: [{
|
|
23185
|
+
* id, name, order, record,
|
|
23186
|
+
* pkey, topic_version, system_topic, main_topic,
|
|
23187
|
+
* cols: [{id, name, order, record}]
|
|
23188
|
+
* }]
|
|
23189
|
+
* }],
|
|
23190
|
+
* orphan_topics: [...], topics whose treedb is not in the store
|
|
23191
|
+
* orphan_cols: [...] columns whose topic is not in the store
|
|
23192
|
+
* }
|
|
23193
|
+
*
|
|
23194
|
+
* An orphan is NOT dropped. A column whose topic was deleted is
|
|
23195
|
+
* exactly the kind of leftover an operator opens this editor to
|
|
23196
|
+
* find, and a model that hides it makes the editor lie.
|
|
23197
|
+
***************************************************************/
|
|
23198
|
+
function build_schema_model(records) {
|
|
23199
|
+
let r = records || {};
|
|
23200
|
+
let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
|
|
23201
|
+
let topic_rows = Array.isArray(r.topics) ? r.topics : [];
|
|
23202
|
+
let col_rows = Array.isArray(r.cols) ? r.cols : [];
|
|
23203
|
+
let treedbs = [];
|
|
23204
|
+
let by_treedb_id = {};
|
|
23205
|
+
for (let record of treedb_rows) {
|
|
23206
|
+
if (!record || typeof record.id !== "string") continue;
|
|
23207
|
+
let entry = {
|
|
23208
|
+
id: record.id,
|
|
23209
|
+
name: record.id,
|
|
23210
|
+
record,
|
|
23211
|
+
schema_version: record.schema_version,
|
|
23212
|
+
c_schema_version: record.c_schema_version,
|
|
23213
|
+
system_schema_version: record.system_schema_version,
|
|
23214
|
+
topics: []
|
|
23215
|
+
};
|
|
23216
|
+
treedbs.push(entry);
|
|
23217
|
+
by_treedb_id[entry.id] = entry;
|
|
23218
|
+
}
|
|
23219
|
+
let orphan_topics = [];
|
|
23220
|
+
let by_topic_id = {};
|
|
23221
|
+
for (let record of topic_rows) {
|
|
23222
|
+
if (!record || typeof record.id !== "string") continue;
|
|
23223
|
+
let entry = {
|
|
23224
|
+
id: record.id,
|
|
23225
|
+
name: record_name(record),
|
|
23226
|
+
order: record_order(record),
|
|
23227
|
+
record,
|
|
23228
|
+
treedb_id: parent_id(record, "treedbs"),
|
|
23229
|
+
pkey: record.pkey || "",
|
|
23230
|
+
pkey2s: record.pkey2s,
|
|
23231
|
+
system_flag: record.system_flag || "",
|
|
23232
|
+
tkey: record.tkey || "",
|
|
23233
|
+
topic_version: record.topic_version,
|
|
23234
|
+
system_topic: !!record.system_topic,
|
|
23235
|
+
main_topic: !!record.main_topic,
|
|
23236
|
+
cols: []
|
|
23237
|
+
};
|
|
23238
|
+
by_topic_id[entry.id] = entry;
|
|
23239
|
+
let parent = by_treedb_id[entry.treedb_id];
|
|
23240
|
+
if (parent) parent.topics.push(entry);
|
|
23241
|
+
else orphan_topics.push(entry);
|
|
23242
|
+
}
|
|
23243
|
+
let orphan_cols = [];
|
|
23244
|
+
for (let record of col_rows) {
|
|
23245
|
+
if (!record || typeof record.id !== "string") continue;
|
|
23246
|
+
let entry = {
|
|
23247
|
+
id: record.id,
|
|
23248
|
+
name: record_name(record),
|
|
23249
|
+
order: record_order(record),
|
|
23250
|
+
record,
|
|
23251
|
+
topic_id: parent_id(record, "topics")
|
|
23252
|
+
};
|
|
23253
|
+
let parent = by_topic_id[entry.topic_id];
|
|
23254
|
+
if (parent) parent.cols.push(entry);
|
|
23255
|
+
else orphan_cols.push(entry);
|
|
23256
|
+
}
|
|
23257
|
+
for (let treedb of treedbs) {
|
|
23258
|
+
sort_by_order(treedb.topics);
|
|
23259
|
+
for (let topic of treedb.topics) sort_by_order(topic.cols);
|
|
23260
|
+
}
|
|
23261
|
+
for (let topic of orphan_topics) sort_by_order(topic.cols);
|
|
23262
|
+
treedbs.sort((a, b) => {
|
|
23263
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
23264
|
+
});
|
|
23265
|
+
sort_by_order(orphan_topics);
|
|
23266
|
+
sort_by_order(orphan_cols);
|
|
23267
|
+
return {
|
|
23268
|
+
treedbs,
|
|
23269
|
+
orphan_topics,
|
|
23270
|
+
orphan_cols
|
|
23271
|
+
};
|
|
23272
|
+
}
|
|
23273
|
+
/***************************************************************
|
|
23274
|
+
* Is there a value here at all?
|
|
23275
|
+
*
|
|
23276
|
+
* The store answers a `blob` column that was never set with an
|
|
23277
|
+
* EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
|
|
23278
|
+
* `default: {}`. Read as "a value", those turn every export into
|
|
23279
|
+
* a schema full of empty objects — and `'hook': {}` in a literal
|
|
23280
|
+
* is a hook with no mapping, which is a link the treedb builds
|
|
23281
|
+
* and nothing writes.
|
|
23282
|
+
***************************************************************/
|
|
23283
|
+
function is_empty_value(value) {
|
|
23284
|
+
if (value === null || value === void 0 || value === "") return true;
|
|
23285
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
23286
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
23287
|
+
return false;
|
|
23288
|
+
}
|
|
23289
|
+
/***************************************************************
|
|
23290
|
+
* The fields of a column that are DECLARED as one thing and
|
|
23291
|
+
* STORED as another.
|
|
23292
|
+
*
|
|
23293
|
+
* `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
|
|
23294
|
+
* columns of the `cols` topic (treedb_system_schema.c), so what
|
|
23295
|
+
* comes back may be the value or the JSON text of the value,
|
|
23296
|
+
* depending on how it was written — a projection writes the
|
|
23297
|
+
* value, a hand edit through a plain text field writes the text.
|
|
23298
|
+
* `flag` is an array that a single-flag edit can leave as a bare
|
|
23299
|
+
* string. Reading these without saying so is how a hook drawn
|
|
23300
|
+
* from `{"users":"department"}` becomes no hook at all.
|
|
23301
|
+
***************************************************************/
|
|
23302
|
+
function as_json(value) {
|
|
23303
|
+
if (typeof value !== "string") return value;
|
|
23304
|
+
let text = value.trim();
|
|
23305
|
+
if (text.length === 0) return null;
|
|
23306
|
+
try {
|
|
23307
|
+
return JSON.parse(text);
|
|
23308
|
+
} catch (e) {
|
|
23309
|
+
return value;
|
|
23310
|
+
}
|
|
23311
|
+
}
|
|
23312
|
+
function col_flags(record) {
|
|
23313
|
+
let flag = record ? record.flag : null;
|
|
23314
|
+
if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
23315
|
+
if (typeof flag === "string" && flag.length > 0) {
|
|
23316
|
+
let parsed = as_json(flag);
|
|
23317
|
+
if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
|
|
23318
|
+
return [flag];
|
|
23319
|
+
}
|
|
23320
|
+
return [];
|
|
23321
|
+
}
|
|
23322
|
+
function col_hook(record) {
|
|
23323
|
+
let hook = as_json(record ? record.hook : null);
|
|
23324
|
+
if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
|
|
23325
|
+
return null;
|
|
23326
|
+
}
|
|
23327
|
+
function col_enum(record) {
|
|
23328
|
+
let e = as_json(record ? record.enum : null);
|
|
23329
|
+
if (Array.isArray(e)) return e;
|
|
23330
|
+
return [];
|
|
23331
|
+
}
|
|
23332
|
+
function topic_pkey2s(record) {
|
|
23333
|
+
let pkey2s = as_json(record ? record.pkey2s : null);
|
|
23334
|
+
if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
|
|
23335
|
+
if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
|
|
23336
|
+
return [];
|
|
23337
|
+
}
|
|
23338
|
+
/***************************************************************
|
|
23339
|
+
* Lookups by id. The editor addresses a position by NAME (the
|
|
23340
|
+
* url carries `<treedb>/<topic>`), so both are offered.
|
|
23341
|
+
***************************************************************/
|
|
23342
|
+
function find_treedb(model, treedb_id) {
|
|
23343
|
+
if (!model || !Array.isArray(model.treedbs)) return null;
|
|
23344
|
+
for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
|
|
23345
|
+
return null;
|
|
23346
|
+
}
|
|
23347
|
+
function find_topic(model, treedb_id, topic_name) {
|
|
23348
|
+
let treedb = find_treedb(model, treedb_id);
|
|
23349
|
+
if (!treedb) return null;
|
|
23350
|
+
for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
|
|
23351
|
+
return null;
|
|
23352
|
+
}
|
|
23353
|
+
function find_col(model, treedb_id, topic_name, col_name) {
|
|
23354
|
+
let topic = find_topic(model, treedb_id, topic_name);
|
|
23355
|
+
if (!topic) return null;
|
|
23356
|
+
for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
|
|
23357
|
+
return null;
|
|
23358
|
+
}
|
|
23359
|
+
/***************************************************************
|
|
23360
|
+
* The fkey a NEW record must carry to belong to `parent_id`.
|
|
23361
|
+
* Written here because the editor is the only writer that has to
|
|
23362
|
+
* compose one, and getting the hook name wrong links the record
|
|
23363
|
+
* to nothing while the write still succeeds.
|
|
23364
|
+
***************************************************************/
|
|
23365
|
+
function fkey_ref(parent_topic, parent_id_, hook_name) {
|
|
23366
|
+
return `${parent_topic}^${parent_id_}^${hook_name}`;
|
|
23367
|
+
}
|
|
23368
|
+
/***************************************************************
|
|
23369
|
+
* Where a new sibling goes: after the last one. The store's own
|
|
23370
|
+
* default (9999) would put every hand-made column in the same
|
|
23371
|
+
* place and let their names decide the schema's order.
|
|
23372
|
+
***************************************************************/
|
|
23373
|
+
function next_order(siblings) {
|
|
23374
|
+
let max = 0;
|
|
23375
|
+
if (Array.isArray(siblings)) for (let s of siblings) {
|
|
23376
|
+
let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
|
|
23377
|
+
if (order < 9999 && order > max) max = order;
|
|
23378
|
+
}
|
|
23379
|
+
return max + 1;
|
|
23380
|
+
}
|
|
23381
|
+
/***************************************************************
|
|
23382
|
+
* Renumber a list AFTER a move, as the writes it takes: only the
|
|
23383
|
+
* records whose order actually changed, so a drag of one row in a
|
|
23384
|
+
* 40-column topic is not 40 writes.
|
|
23385
|
+
*
|
|
23386
|
+
* moved_orders(list) -> [{id, order}, ...]
|
|
23387
|
+
***************************************************************/
|
|
23388
|
+
function moved_orders(list) {
|
|
23389
|
+
let writes = [];
|
|
23390
|
+
if (!Array.isArray(list)) return writes;
|
|
23391
|
+
for (let i = 0; i < list.length; i++) {
|
|
23392
|
+
let entry = list[i];
|
|
23393
|
+
let order = i + 1;
|
|
23394
|
+
if (!entry || typeof entry.id !== "string") continue;
|
|
23395
|
+
if (entry.order !== order) writes.push({
|
|
23396
|
+
id: entry.id,
|
|
23397
|
+
order
|
|
23398
|
+
});
|
|
23399
|
+
}
|
|
23400
|
+
return writes;
|
|
23401
|
+
}
|
|
23402
|
+
//#endregion
|
|
23403
|
+
//#region src/treedb_topic_keys.js
|
|
23404
|
+
/***********************************************************************
|
|
23405
|
+
* treedb_topic_keys.js
|
|
23406
|
+
*
|
|
23407
|
+
* The KEYS of a treedb topic, read from its desc: the pkey, the
|
|
23408
|
+
* secondary keys (`pkey2s`) and the time key (`tkey`), plus the
|
|
23409
|
+
* names behind its `system_flag` number.
|
|
23410
|
+
*
|
|
23411
|
+
* Every view that describes a topic -- the schema diagram, the
|
|
23412
|
+
* topic-info panel, the topic cards -- must say them, and say them
|
|
23413
|
+
* the same way. A topic with `pkey2s` is not a topic with one more
|
|
23414
|
+
* column: one id holds several INSTANCES, an update has to carry
|
|
23415
|
+
* the pkey2 or it lands on the primary, and `delete-instance`
|
|
23416
|
+
* exists. `tkey` names where the time of every record comes from.
|
|
23417
|
+
* The views said neither, so this is where they ask.
|
|
23418
|
+
*
|
|
23419
|
+
* Pure, so it is tested with no DOM.
|
|
23420
|
+
*
|
|
23421
|
+
* Copyright (c) 2026, ArtGins.
|
|
23422
|
+
* All Rights Reserved.
|
|
23423
|
+
***********************************************************************/
|
|
23424
|
+
var SF_NAMES = [
|
|
23425
|
+
"sf_string_key",
|
|
23426
|
+
"sf_rowid_key",
|
|
23427
|
+
"sf_int_key",
|
|
23428
|
+
"",
|
|
23429
|
+
"sf_zip_record",
|
|
23430
|
+
"sf_cipher_record",
|
|
23431
|
+
"",
|
|
23432
|
+
"",
|
|
23433
|
+
"sf_t_ms",
|
|
23434
|
+
"sf_tm_ms",
|
|
23435
|
+
"sf_deleted_instance",
|
|
23436
|
+
"sf_immutable_record",
|
|
23437
|
+
"sf_loading_from_disk",
|
|
23438
|
+
"",
|
|
23439
|
+
"",
|
|
23440
|
+
""
|
|
23441
|
+
];
|
|
23442
|
+
/************************************************************
|
|
23443
|
+
* The secondary keys of a topic, always a list. The desc
|
|
23444
|
+
* carries a list; a schema literal may declare a bare
|
|
23445
|
+
* string.
|
|
23446
|
+
************************************************************/
|
|
23447
|
+
function desc_pkey2s(desc) {
|
|
23448
|
+
return topic_pkey2s(desc);
|
|
23449
|
+
}
|
|
23450
|
+
/************************************************************
|
|
23451
|
+
* The time key of a topic, "" when it has none -- and then
|
|
23452
|
+
* the time of a record is the time it was appended.
|
|
23453
|
+
************************************************************/
|
|
23454
|
+
function desc_tkey(desc) {
|
|
23455
|
+
let tkey = desc ? desc.tkey : "";
|
|
23456
|
+
return typeof tkey === "string" ? tkey : "";
|
|
23457
|
+
}
|
|
23458
|
+
/************************************************************
|
|
23459
|
+
* Which keys of its topic a column IS, in the order the
|
|
23460
|
+
* views print them: "pkey", "pkey2", "tkey". Empty for a
|
|
23461
|
+
* column that is none.
|
|
23462
|
+
************************************************************/
|
|
23463
|
+
function col_key_roles(desc, col_id) {
|
|
23464
|
+
let roles = [];
|
|
23465
|
+
if (!desc || typeof col_id !== "string" || col_id.length === 0) return roles;
|
|
23466
|
+
if (col_id === (desc.pkey || "id")) roles.push("pkey");
|
|
23467
|
+
if (desc_pkey2s(desc).includes(col_id)) roles.push("pkey2");
|
|
23468
|
+
if (desc_tkey(desc) === col_id) roles.push("tkey");
|
|
23469
|
+
return roles;
|
|
23470
|
+
}
|
|
23471
|
+
/************************************************************
|
|
23472
|
+
* The names of a system_flag. The desc carries the NUMBER
|
|
23473
|
+
* (`1`), a schema literal carries the names
|
|
23474
|
+
* ("sf_string_key|sf_t_ms"); both come back as a list. A
|
|
23475
|
+
* set bit with no name is shown by its value, never
|
|
23476
|
+
* dropped.
|
|
23477
|
+
************************************************************/
|
|
23478
|
+
function system_flag_names(system_flag) {
|
|
23479
|
+
if (Array.isArray(system_flag)) return system_flag.filter((n) => typeof n === "string" && n.length > 0);
|
|
23480
|
+
if (typeof system_flag === "string") return system_flag.split(/[|,]/).map((n) => n.trim()).filter((n) => n.length > 0);
|
|
23481
|
+
if (typeof system_flag !== "number" || !Number.isInteger(system_flag) || system_flag <= 0) return [];
|
|
23482
|
+
let names = [];
|
|
23483
|
+
for (let bit = 0; bit < SF_NAMES.length; bit++) {
|
|
23484
|
+
let mask = 1 << bit;
|
|
23485
|
+
if (system_flag & mask) names.push(SF_NAMES[bit] || `0x${mask.toString(16).padStart(4, "0")}`);
|
|
23486
|
+
}
|
|
23487
|
+
return names;
|
|
23488
|
+
}
|
|
23489
|
+
//#endregion
|
|
23049
23490
|
//#region src/c_yui_treedb_topics.js
|
|
23050
23491
|
/***********************************************************************
|
|
23051
23492
|
* c_yui_treedb_topics.js
|
|
23052
23493
|
*
|
|
23053
23494
|
* Management of TreeDB's topics with Bulma tabs
|
|
23054
23495
|
*
|
|
23055
|
-
* Copyright (c) 2025, ArtGins.
|
|
23496
|
+
* Copyright (c) 2025-2026, ArtGins.
|
|
23056
23497
|
* All Rights Reserved.
|
|
23057
23498
|
***********************************************************************/
|
|
23058
23499
|
/***************************************************************
|
|
@@ -23566,6 +24007,29 @@ function topic_card_meta(desc) {
|
|
|
23566
24007
|
{ class: "TREEDB_TOPIC_CARD_META" },
|
|
23567
24008
|
head
|
|
23568
24009
|
]);
|
|
24010
|
+
let pkey2s = desc_pkey2s(desc);
|
|
24011
|
+
if (pkey2s.length) {
|
|
24012
|
+
let list = pkey2s.join(", ");
|
|
24013
|
+
meta.push([
|
|
24014
|
+
"div",
|
|
24015
|
+
{
|
|
24016
|
+
class: "TREEDB_TOPIC_CARD_REL TREEDB_TOPIC_CARD_PKEY2S",
|
|
24017
|
+
title: list
|
|
24018
|
+
},
|
|
24019
|
+
[[
|
|
24020
|
+
"span",
|
|
24021
|
+
{
|
|
24022
|
+
class: "TREEDB_TOPIC_CARD_REL_LABEL",
|
|
24023
|
+
i18n: "pkey2s"
|
|
24024
|
+
},
|
|
24025
|
+
"pkey2s"
|
|
24026
|
+
], [
|
|
24027
|
+
"span",
|
|
24028
|
+
{ class: "TREEDB_TOPIC_CARD_REL_LIST" },
|
|
24029
|
+
list
|
|
24030
|
+
]]
|
|
24031
|
+
]);
|
|
24032
|
+
}
|
|
23569
24033
|
let relation = (cls, key, arrow, names) => {
|
|
23570
24034
|
let list = names.join(", ");
|
|
23571
24035
|
return [
|
|
@@ -23829,33 +24293,26 @@ function show_topic_info(gobj, topic) {
|
|
|
23829
24293
|
}
|
|
23830
24294
|
/************************************************************
|
|
23831
24295
|
* Build the read-only topic-info panel from a topic desc: the
|
|
23832
|
-
* topic name, its
|
|
24296
|
+
* topic name, its KEYS, and a table of columns (name / type /
|
|
23833
24297
|
* key relationship). Everything is guarded — a malformed desc
|
|
23834
24298
|
* renders a shorter panel, never throws.
|
|
24299
|
+
*
|
|
24300
|
+
* The keys are what an operator has to know before touching a
|
|
24301
|
+
* record, so they are all said, and the empty ones too: a
|
|
24302
|
+
* topic with `pkey2s` keeps several instances under one id,
|
|
24303
|
+
* and an empty `tkey` means the time of a record is the time
|
|
24304
|
+
* it was appended -- a row missing is not the same as "none".
|
|
23835
24305
|
************************************************************/
|
|
23836
24306
|
function build_topic_info_panel(gobj, $info, topic, desc) {
|
|
23837
24307
|
while ($info.firstChild) $info.removeChild($info.firstChild);
|
|
23838
24308
|
let pkey = desc.pkey || "";
|
|
23839
|
-
let
|
|
23840
|
-
|
|
23841
|
-
if (is_object(v)) return Object.keys(v).join(", ");
|
|
23842
|
-
return v === void 0 || v === null ? "" : String(v);
|
|
23843
|
-
};
|
|
24309
|
+
let pkey2s = desc_pkey2s(desc);
|
|
24310
|
+
let tkey = desc_tkey(desc);
|
|
23844
24311
|
let $meta = [];
|
|
23845
|
-
let push_meta = (key,
|
|
23846
|
-
if (value === void 0 || value === null || value === "") return;
|
|
23847
|
-
let $val = highlight ? [
|
|
23848
|
-
"span",
|
|
23849
|
-
{ class: "tag is-info" },
|
|
23850
|
-
`${value}`
|
|
23851
|
-
] : [
|
|
23852
|
-
"code",
|
|
23853
|
-
{},
|
|
23854
|
-
`${value}`
|
|
23855
|
-
];
|
|
24312
|
+
let push_meta = (key, $val) => {
|
|
23856
24313
|
$meta.push([
|
|
23857
24314
|
"tr",
|
|
23858
|
-
{},
|
|
24315
|
+
{ class: `TREEDB_TOPIC_INFO_${key.toUpperCase()}` },
|
|
23859
24316
|
[[
|
|
23860
24317
|
"th",
|
|
23861
24318
|
{
|
|
@@ -23870,29 +24327,64 @@ function build_topic_info_panel(gobj, $info, topic, desc) {
|
|
|
23870
24327
|
]]
|
|
23871
24328
|
]);
|
|
23872
24329
|
};
|
|
23873
|
-
push_meta("version",
|
|
23874
|
-
|
|
23875
|
-
|
|
23876
|
-
|
|
24330
|
+
if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") push_meta("version", [
|
|
24331
|
+
"span",
|
|
24332
|
+
{ class: "tag is-info" },
|
|
24333
|
+
`${desc.topic_version}`
|
|
24334
|
+
]);
|
|
24335
|
+
let sf_names = system_flag_names(desc.system_flag);
|
|
24336
|
+
if (sf_names.length > 0) push_meta("system", [
|
|
24337
|
+
"code",
|
|
24338
|
+
{ title: `${desc.system_flag}` },
|
|
24339
|
+
sf_names.join(" | ")
|
|
24340
|
+
]);
|
|
24341
|
+
if (pkey) push_meta("pkey", [
|
|
24342
|
+
"code",
|
|
24343
|
+
{},
|
|
24344
|
+
pkey
|
|
24345
|
+
]);
|
|
24346
|
+
if (pkey2s.length > 0) push_meta("pkey2s", [
|
|
24347
|
+
"span",
|
|
24348
|
+
{ class: "tags" },
|
|
24349
|
+
pkey2s.map((name) => [
|
|
24350
|
+
"span",
|
|
24351
|
+
{ class: "tag is-warning" },
|
|
24352
|
+
name
|
|
24353
|
+
])
|
|
24354
|
+
]);
|
|
24355
|
+
push_meta("tkey", tkey ? [
|
|
24356
|
+
"code",
|
|
24357
|
+
{},
|
|
24358
|
+
tkey
|
|
24359
|
+
] : [
|
|
24360
|
+
"span",
|
|
24361
|
+
{
|
|
24362
|
+
class: "has-text-grey",
|
|
24363
|
+
i18n: "append time"
|
|
24364
|
+
},
|
|
24365
|
+
"append time"
|
|
24366
|
+
]);
|
|
23877
24367
|
let $rows = [];
|
|
23878
24368
|
let cols = is_array(desc.cols) ? desc.cols : [];
|
|
23879
24369
|
for (let col of cols) {
|
|
23880
24370
|
if (!col || !col.id || col.id.charAt(0) === "_") continue;
|
|
23881
24371
|
let type = col.type || (is_array(col.flag) ? col.flag.join(", ") : col.flag || "");
|
|
23882
|
-
let
|
|
23883
|
-
|
|
23884
|
-
|
|
23885
|
-
else if (is_object(col.hook))
|
|
24372
|
+
let roles = col_key_roles(desc, col.id);
|
|
24373
|
+
let parts = roles.slice();
|
|
24374
|
+
if (is_object(col.fkey)) parts.push("→ " + Object.keys(col.fkey).join(", "));
|
|
24375
|
+
else if (is_object(col.hook)) parts.push("hook → " + Object.keys(col.hook).join(", "));
|
|
24376
|
+
let rel = parts.join(" · ");
|
|
24377
|
+
let is_key = roles.length > 0;
|
|
23886
24378
|
$rows.push([
|
|
23887
24379
|
"tr",
|
|
23888
|
-
{},
|
|
24380
|
+
is_key ? { class: "TREEDB_TOPIC_INFO_KEY_COL" } : {},
|
|
23889
24381
|
[
|
|
23890
24382
|
[
|
|
23891
24383
|
"td",
|
|
23892
24384
|
{},
|
|
23893
24385
|
[[
|
|
23894
24386
|
"code",
|
|
23895
|
-
{},
|
|
24387
|
+
is_key ? { class: "has-text-weight-bold" } : {},
|
|
23896
24388
|
`${col.id}`
|
|
23897
24389
|
]]
|
|
23898
24390
|
],
|
|
@@ -23903,7 +24395,7 @@ function build_topic_info_panel(gobj, $info, topic, desc) {
|
|
|
23903
24395
|
],
|
|
23904
24396
|
[
|
|
23905
24397
|
"td",
|
|
23906
|
-
{},
|
|
24398
|
+
is_key ? { class: "has-text-weight-semibold" } : {},
|
|
23907
24399
|
`${rel}`
|
|
23908
24400
|
]
|
|
23909
24401
|
]
|
|
@@ -25638,361 +26130,6 @@ function row_matches(row, term) {
|
|
|
25638
26130
|
});
|
|
25639
26131
|
}
|
|
25640
26132
|
//#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
|
|
25996
26133
|
//#region src/treedb_write_plan.js
|
|
25997
26134
|
/***********************************************************************
|
|
25998
26135
|
* treedb_write_plan.js
|
|
@@ -68838,12 +68975,20 @@ function hook_mark(type) {
|
|
|
68838
68975
|
* (↖) 1 fkey (1 parent)
|
|
68839
68976
|
* [↖] n fkeys (n parents)
|
|
68840
68977
|
* {↖} N fkeys (N parents)
|
|
68978
|
+
* (2) a secondary key (pkey2)
|
|
68979
|
+
* (t) the time key (tkey)
|
|
68841
68980
|
* * required
|
|
68842
68981
|
* # the primary key
|
|
68843
68982
|
*
|
|
68844
|
-
*
|
|
68845
|
-
*
|
|
68846
|
-
*
|
|
68983
|
+
* The key marks lead the mark column, and a pkey2 is drawn in
|
|
68984
|
+
* bold like the pkey: a topic with secondary keys keeps several
|
|
68985
|
+
* instances under one id, which is the first thing to know
|
|
68986
|
+
* about it. `(t)` is new to the notation: the literals' legend
|
|
68987
|
+
* carries it too.
|
|
68988
|
+
*
|
|
68989
|
+
* The flags are read here rather than through
|
|
68990
|
+
* treedb_get_field_desc's single `type`, which keeps only the
|
|
68991
|
+
* last flag it saw.
|
|
68847
68992
|
************************************************************/
|
|
68848
68993
|
function schema_rows(desc) {
|
|
68849
68994
|
let rows = [];
|
|
@@ -68853,19 +68998,25 @@ function schema_rows(desc) {
|
|
|
68853
68998
|
let flags = Array.isArray(col.flag) ? col.flag : [];
|
|
68854
68999
|
let is_hook = flags.indexOf("hook") >= 0;
|
|
68855
69000
|
let is_fkey = flags.indexOf("fkey") >= 0;
|
|
69001
|
+
let roles = col_key_roles(desc, col.id);
|
|
69002
|
+
let key_marks = [];
|
|
69003
|
+
if (roles.includes("pkey2")) key_marks.push("(2)");
|
|
69004
|
+
if (roles.includes("tkey")) key_marks.push("(t)");
|
|
68856
69005
|
let mark = "";
|
|
68857
69006
|
if (is_hook) mark = hook_mark(col.type);
|
|
68858
69007
|
if (is_fkey) {
|
|
68859
69008
|
let fmark = hook_mark(col.type).replace("}", "↖}").replace("]", "↖]").replace(")", "↖)");
|
|
68860
69009
|
mark = mark ? mark + " " + fmark : fmark;
|
|
68861
69010
|
}
|
|
69011
|
+
if (key_marks.length) mark = mark ? key_marks.join(" ") + " " + mark : key_marks.join(" ");
|
|
68862
69012
|
rows.push({
|
|
68863
69013
|
name: col.id,
|
|
68864
69014
|
type: col.type || "",
|
|
68865
69015
|
mark,
|
|
68866
69016
|
is_hook,
|
|
68867
69017
|
is_fkey,
|
|
68868
|
-
is_pkey:
|
|
69018
|
+
is_pkey: roles.includes("pkey"),
|
|
69019
|
+
is_pkey2: roles.includes("pkey2"),
|
|
68869
69020
|
required: flags.indexOf("required") >= 0 || flags.indexOf("notnull") >= 0
|
|
68870
69021
|
});
|
|
68871
69022
|
}
|
|
@@ -68912,7 +69063,7 @@ function build_card_innerHTML(topic, rows, color, dark) {
|
|
|
68912
69063
|
let rows_html = "";
|
|
68913
69064
|
for (let row of rows) {
|
|
68914
69065
|
let lead = row.is_pkey ? "#" : row.required ? "*" : "";
|
|
68915
|
-
let name_weight = row.is_pkey || row.required ? "600" : "400";
|
|
69066
|
+
let name_weight = row.is_pkey || row.is_pkey2 ? "700" : row.required ? "600" : "400";
|
|
68916
69067
|
let name_color = row.is_hook || row.is_fkey ? title_color : text_color;
|
|
68917
69068
|
rows_html += ` <div class="TREEDB_SCHEMA_FIELD" style="
|
|
68918
69069
|
display: flex; align-items: center; gap: 6px;
|
|
@@ -76000,86 +76151,6 @@ function register_c_yui_period() {
|
|
|
76000
76151
|
return create_gclass(GCLASS_NAME);
|
|
76001
76152
|
}
|
|
76002
76153
|
//#endregion
|
|
76003
|
-
//#region src/dev_machine_trace.js
|
|
76004
|
-
/***********************************************************************
|
|
76005
|
-
* dev_machine_trace.js
|
|
76006
|
-
*
|
|
76007
|
-
* Reading a MACHINE trace line back.
|
|
76008
|
-
*
|
|
76009
|
-
* The developer window mirrors the runtime's log, and the
|
|
76010
|
-
* `machine` trace is most of what lands there once it is on.
|
|
76011
|
-
* Those lines arrive as text, so to the window they were just
|
|
76012
|
-
* "a debug log" -- which is why its `Periodic` filter could
|
|
76013
|
-
* not touch them: the filter works on a signature, and every
|
|
76014
|
-
* mirrored log carried the same one (`log:debug`).
|
|
76015
|
-
*
|
|
76016
|
-
* This gives a machine line back its EVENT, which is the only
|
|
76017
|
-
* thing that says whether it is noise. Both formats gobj-js
|
|
76018
|
-
* emits (`gobj_set_trace_machine_format`):
|
|
76019
|
-
*
|
|
76020
|
-
* 0 verbose "🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: …"
|
|
76021
|
-
* "<- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0"
|
|
76022
|
-
* 1 compact "🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t"
|
|
76023
|
-
*
|
|
76024
|
-
* Copyright (c) 2026, ArtGins.
|
|
76025
|
-
* All Rights Reserved.
|
|
76026
|
-
***********************************************************************/
|
|
76027
|
-
var MACHINE_MARK_RE = /\bmach\(/;
|
|
76028
|
-
var EVENT_FIELD_RE = /\bev:\s*([A-Za-z0-9_]+)/;
|
|
76029
|
-
var EVENT_HEAD_RE = /^\s*\S{0,4}\s*(EV_[A-Za-z0-9_]+)(?=\s|$)/;
|
|
76030
|
-
var PERIODIC_EVENT_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING|POLL/i;
|
|
76031
|
-
/************************************************************
|
|
76032
|
-
* The event of a machine trace line, or "" when the line is
|
|
76033
|
-
* not one.
|
|
76034
|
-
*
|
|
76035
|
-
* "" is also the answer for a machine line that carries no
|
|
76036
|
-
* event -- a state change ("new st(…), prev st(…)") -- and
|
|
76037
|
-
* that is right: what it reports is not repetitive.
|
|
76038
|
-
************************************************************/
|
|
76039
|
-
function machine_event_of(text) {
|
|
76040
|
-
if (typeof text !== "string" || text.length === 0) return "";
|
|
76041
|
-
if (MACHINE_MARK_RE.test(text)) {
|
|
76042
|
-
let m = EVENT_FIELD_RE.exec(text);
|
|
76043
|
-
return m ? m[1] : "";
|
|
76044
|
-
}
|
|
76045
|
-
let m = EVENT_HEAD_RE.exec(text);
|
|
76046
|
-
return m ? m[1] : "";
|
|
76047
|
-
}
|
|
76048
|
-
/************************************************************
|
|
76049
|
-
* Is this event one of the ones that arrive by themselves,
|
|
76050
|
-
* for ever, and say nothing about what the app is doing?
|
|
76051
|
-
************************************************************/
|
|
76052
|
-
function is_periodic_event(event_name) {
|
|
76053
|
-
if (!event_name) return false;
|
|
76054
|
-
return PERIODIC_EVENT_RE.test(event_name);
|
|
76055
|
-
}
|
|
76056
|
-
/************************************************************
|
|
76057
|
-
* The signature of a mirrored log line: what the window
|
|
76058
|
-
* counts it under and what its filters match on.
|
|
76059
|
-
*
|
|
76060
|
-
* A machine line is signed by its EVENT, so a hundred
|
|
76061
|
-
* EV_TIMEOUTs are one recurring thing rather than a hundred
|
|
76062
|
-
* anonymous debug lines. Everything else is signed by its
|
|
76063
|
-
* level, as before.
|
|
76064
|
-
************************************************************/
|
|
76065
|
-
function log_signature(level, text) {
|
|
76066
|
-
let event = machine_event_of(text);
|
|
76067
|
-
if (event) return "mach:" + event;
|
|
76068
|
-
return "log:" + (level || "debug");
|
|
76069
|
-
}
|
|
76070
|
-
/************************************************************
|
|
76071
|
-
* May the periodic filter hide this log line?
|
|
76072
|
-
*
|
|
76073
|
-
* Only a machine transition, and only below warning level.
|
|
76074
|
-
* An ERROR that names EV_TIMEOUT -- "Event NOT DEFINED in
|
|
76075
|
-
* state", which is exactly the loud one the framework wants
|
|
76076
|
-
* seen -- must never disappear because of the word in it.
|
|
76077
|
-
************************************************************/
|
|
76078
|
-
function log_is_periodic(level, text) {
|
|
76079
|
-
if (level === "error" || level === "warning") return false;
|
|
76080
|
-
return is_periodic_event(machine_event_of(text));
|
|
76081
|
-
}
|
|
76082
|
-
//#endregion
|
|
76083
76154
|
//#region src/yui_dev.js
|
|
76084
76155
|
/***********************************************************************
|
|
76085
76156
|
* ui_dev.js
|
|
@@ -76097,10 +76168,7 @@ function log_is_periodic(level, text) {
|
|
|
76097
76168
|
* losing history. Reopening the window repaints the buffer.
|
|
76098
76169
|
***********************************************************************/
|
|
76099
76170
|
var TRAFFIC_MAX = 600;
|
|
76100
|
-
var PERIODIC_THRESHOLD = 5;
|
|
76101
|
-
var PERIODIC_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING/i;
|
|
76102
76171
|
var TRAFFIC_LOG = [];
|
|
76103
|
-
var TRAFFIC_COUNTS = /* @__PURE__ */ new Map();
|
|
76104
76172
|
var SEARCH_TEXT = "";
|
|
76105
76173
|
var LOG_ERR_COUNT = 0;
|
|
76106
76174
|
var LOG_WARN_COUNT = 0;
|
|
@@ -76117,39 +76185,53 @@ var TRAFFIC_TS_FIELDS = {
|
|
|
76117
76185
|
};
|
|
76118
76186
|
var TRACE_DEFS = [
|
|
76119
76187
|
[
|
|
76120
|
-
"trace_automata",
|
|
76121
76188
|
"automata",
|
|
76122
|
-
|
|
76189
|
+
"automata",
|
|
76190
|
+
"trace every event of every automaton",
|
|
76191
|
+
automata_state,
|
|
76192
|
+
toggle_automata
|
|
76123
76193
|
],
|
|
76124
76194
|
[
|
|
76125
|
-
"trace_creation",
|
|
76126
76195
|
"creation",
|
|
76127
|
-
|
|
76196
|
+
"creation",
|
|
76197
|
+
"trace the creation and destruction of gobjs",
|
|
76198
|
+
() => global_bit(trace_level_t.TRACE_CREATE_DELETE),
|
|
76199
|
+
() => toggle_global("create_delete")
|
|
76128
76200
|
],
|
|
76129
76201
|
[
|
|
76130
|
-
"
|
|
76202
|
+
"start_stop",
|
|
76131
76203
|
"start / stop",
|
|
76132
|
-
|
|
76204
|
+
"trace the start and stop of gobjs",
|
|
76205
|
+
() => global_bit(trace_level_t.TRACE_START_STOP),
|
|
76206
|
+
() => toggle_global("start_stop")
|
|
76133
76207
|
],
|
|
76134
76208
|
[
|
|
76135
|
-
"trace_subscriptions",
|
|
76136
76209
|
"subscriptions",
|
|
76137
|
-
|
|
76210
|
+
"subscriptions",
|
|
76211
|
+
"trace subscriptions and publications",
|
|
76212
|
+
() => global_bit(trace_level_t.TRACE_SUBSCRIPTIONS),
|
|
76213
|
+
() => toggle_global("subscriptions")
|
|
76138
76214
|
],
|
|
76139
76215
|
[
|
|
76140
|
-
"trace_i18n",
|
|
76141
76216
|
"i18n",
|
|
76142
|
-
|
|
76217
|
+
"i18n",
|
|
76218
|
+
"i18next debug output",
|
|
76219
|
+
i18n_state,
|
|
76220
|
+
toggle_i18n
|
|
76143
76221
|
],
|
|
76144
76222
|
[
|
|
76145
|
-
"trace_traffic",
|
|
76146
76223
|
"traffic",
|
|
76147
|
-
|
|
76224
|
+
"traffic",
|
|
76225
|
+
"trace the messages to and from the backend",
|
|
76226
|
+
traffic_state,
|
|
76227
|
+
toggle_traffic
|
|
76148
76228
|
],
|
|
76149
76229
|
[
|
|
76150
|
-
"
|
|
76151
|
-
"
|
|
76152
|
-
|
|
76230
|
+
"periodic",
|
|
76231
|
+
"periodic",
|
|
76232
|
+
"trace the periodic timer event",
|
|
76233
|
+
periodic_state,
|
|
76234
|
+
toggle_periodic
|
|
76153
76235
|
]
|
|
76154
76236
|
];
|
|
76155
76237
|
/******************************
|
|
@@ -76216,45 +76298,24 @@ function dev_view() {
|
|
|
76216
76298
|
let v = kw_get_local_storage_value("dev_view_mode", "detailed", false);
|
|
76217
76299
|
return v === "compact" || v === "name" || v === "full" ? v : "detailed";
|
|
76218
76300
|
}
|
|
76219
|
-
var HIDE_PERIODIC_DEFAULT = 1;
|
|
76220
76301
|
var SIMPLE_MACH_DEFAULT = 1;
|
|
76221
76302
|
var SIMPLE_MACH_KEY = "dev_mach_simple";
|
|
76222
76303
|
function dev_simple_mach() {
|
|
76223
76304
|
return dev_num(SIMPLE_MACH_KEY, SIMPLE_MACH_DEFAULT) ? 1 : 0;
|
|
76224
76305
|
}
|
|
76225
|
-
function dev_hide_periodic() {
|
|
76226
|
-
return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
|
|
76227
|
-
}
|
|
76228
76306
|
function dev_route() {
|
|
76229
76307
|
let v = kw_get_local_storage_value("dev_output_route", "both", false);
|
|
76230
76308
|
return v === "window" || v === "console" ? v : "both";
|
|
76231
76309
|
}
|
|
76232
76310
|
function apply_console_route() {
|
|
76233
76311
|
set_console_log_enabled(dev_route() !== "window");
|
|
76234
|
-
if (typeof gobj_js.set_console_log_filter === "function") gobj_js.set_console_log_filter((level, msg) => {
|
|
76235
|
-
if (!dev_hide_periodic()) return true;
|
|
76236
|
-
return !log_is_periodic(level, is_string(msg) ? msg : String(msg));
|
|
76237
|
-
});
|
|
76238
|
-
else if (!__console_filter_warned__) {
|
|
76239
|
-
__console_filter_warned__ = true;
|
|
76240
|
-
console.warn("yui_dev: gobj-js has no set_console_log_filter (needs >= 7.13.6): the Periodic filter applies to this window only, not to the console");
|
|
76241
|
-
}
|
|
76242
76312
|
}
|
|
76243
|
-
var __console_filter_warned__ = false;
|
|
76244
76313
|
function console_traffic(title, jn, direction, size) {
|
|
76245
76314
|
let event = jn && jn.event ? String(jn.event) : "(no event)";
|
|
76246
76315
|
let kw = jn && jn.kw && typeof jn.kw === "object" ? jn.kw : jn;
|
|
76247
76316
|
let color = direction === 2 ? "#16a34a" : direction === 3 ? "#dc2626" : "#2563eb";
|
|
76248
76317
|
window.console.log("%c" + dir_arrow(direction) + " " + (title ? "[" + title + "] " : "") + event + (size ? " (" + size + "b)" : ""), "color:" + color, kw);
|
|
76249
76318
|
}
|
|
76250
|
-
function dev_muted() {
|
|
76251
|
-
let a = kw_get_local_storage_value("dev_muted_events", [], false);
|
|
76252
|
-
if (!Array.isArray(a)) a = [];
|
|
76253
|
-
return new Set(a);
|
|
76254
|
-
}
|
|
76255
|
-
function dev_set_muted(set) {
|
|
76256
|
-
kw_set_local_storage_value("dev_muted_events", Array.from(set));
|
|
76257
|
-
}
|
|
76258
76319
|
function set_view(v) {
|
|
76259
76320
|
kw_set_local_storage_value("dev_view_mode", v);
|
|
76260
76321
|
rerender_all();
|
|
@@ -76270,54 +76331,25 @@ function set_output_route(v) {
|
|
|
76270
76331
|
function toggle_pref(key, def) {
|
|
76271
76332
|
let v = dev_num(key, def) ? 0 : 1;
|
|
76272
76333
|
kw_set_local_storage_value(key, v);
|
|
76273
|
-
apply_console_route();
|
|
76274
|
-
rerender_all();
|
|
76275
|
-
refresh_dev_chrome();
|
|
76276
|
-
}
|
|
76277
|
-
function mute_signature(sig) {
|
|
76278
|
-
let set = dev_muted();
|
|
76279
|
-
set.add(sig);
|
|
76280
|
-
dev_set_muted(set);
|
|
76281
|
-
rerender_all();
|
|
76282
|
-
refresh_dev_chrome();
|
|
76283
|
-
}
|
|
76284
|
-
function unmute_signature(sig) {
|
|
76285
|
-
let set = dev_muted();
|
|
76286
|
-
set.delete(sig);
|
|
76287
|
-
dev_set_muted(set);
|
|
76288
76334
|
rerender_all();
|
|
76289
76335
|
refresh_dev_chrome();
|
|
76290
76336
|
}
|
|
76291
76337
|
/******************************
|
|
76292
76338
|
* Filtering
|
|
76293
76339
|
******************************/
|
|
76294
|
-
function traffic_signature(event, command) {
|
|
76295
|
-
return command ? event + " · " + command : event;
|
|
76296
|
-
}
|
|
76297
|
-
function traffic_is_periodic(sig) {
|
|
76298
|
-
if (PERIODIC_RE.test(sig)) return true;
|
|
76299
|
-
return (TRAFFIC_COUNTS.get(sig) || 0) >= PERIODIC_THRESHOLD;
|
|
76300
|
-
}
|
|
76301
76340
|
function build_filter_ctx() {
|
|
76302
76341
|
return {
|
|
76303
76342
|
out: dev_num("dev_filter_out", 1),
|
|
76304
76343
|
inc: dev_num("dev_filter_in", 1),
|
|
76305
76344
|
err: dev_num("dev_filter_err", 1),
|
|
76306
|
-
muted: dev_muted(),
|
|
76307
|
-
hide_periodic: dev_hide_periodic(),
|
|
76308
76345
|
search: SEARCH_TEXT
|
|
76309
76346
|
};
|
|
76310
76347
|
}
|
|
76311
76348
|
function entry_hidden(e, ctx) {
|
|
76312
|
-
if (e.kind === "log")
|
|
76313
|
-
if (ctx.hide_periodic && e.periodic) return true;
|
|
76314
|
-
return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
|
|
76315
|
-
}
|
|
76349
|
+
if (e.kind === "log") return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
|
|
76316
76350
|
if (e.dir === 1 && !ctx.out) return true;
|
|
76317
76351
|
if (e.dir === 2 && !ctx.inc) return true;
|
|
76318
76352
|
if (e.dir === 3 && !ctx.err) return true;
|
|
76319
|
-
if (ctx.muted.has(e.sig)) return true;
|
|
76320
|
-
if (ctx.hide_periodic && traffic_is_periodic(e.sig)) return true;
|
|
76321
76353
|
if (ctx.search && e.hay.indexOf(ctx.search) < 0) return true;
|
|
76322
76354
|
return false;
|
|
76323
76355
|
}
|
|
@@ -76341,11 +76373,6 @@ function ensure_dev_style() {
|
|
|
76341
76373
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
76342
76374
|
}
|
|
76343
76375
|
.YDEV_LOG { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 6px 10px; }
|
|
76344
|
-
.YDEV_MUTED {
|
|
76345
|
-
display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
|
|
76346
|
-
padding: 4px 10px; border-bottom: 1px solid rgba(0,0,0,0.08); font-size: 12px;
|
|
76347
|
-
}
|
|
76348
|
-
.YDEV_MUTED:empty { display: none; }
|
|
76349
76376
|
.YDEV_STATS {
|
|
76350
76377
|
flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 14px;
|
|
76351
76378
|
padding: 6px 10px; border-top: 1px solid rgba(0,0,0,0.1);
|
|
@@ -76377,7 +76404,6 @@ function ensure_dev_style() {
|
|
|
76377
76404
|
.YDEV_CHIP.s-in.is-active { background: rgba(5,150,105,0.16); border-color: #059669; color: #059669; }
|
|
76378
76405
|
.YDEV_CHIP.s-err.is-active { background: rgba(220,38,38,0.16); border-color: #dc2626; color: #dc2626; }
|
|
76379
76406
|
.YDEV_CHIP[data-dir]:not(.is-active) { opacity: 0.4; text-decoration: line-through; }
|
|
76380
|
-
.YDEV_CHIP[data-toggle="periodic"].is-active { background: rgba(217,119,6,0.16); border-color: #d97706; color: #b45309; font-weight: 600; }
|
|
76381
76407
|
.YDEV_SEG { display: inline-flex; border: 1px solid rgba(0,0,0,0.18); border-radius: 7px; overflow: hidden; }
|
|
76382
76408
|
.YDEV_SEG_BTN {
|
|
76383
76409
|
font: inherit; font-size: 12px; padding: 4px 10px; border: 0;
|
|
@@ -76391,12 +76417,6 @@ function ensure_dev_style() {
|
|
|
76391
76417
|
border: 1px solid rgba(0,0,0,0.18); border-radius: 7px;
|
|
76392
76418
|
background: transparent; color: inherit;
|
|
76393
76419
|
}
|
|
76394
|
-
.YDEV_MUTED_CHIP {
|
|
76395
|
-
font: inherit; font-family: "DejaVu Sans Mono", monospace; font-size: 12px;
|
|
76396
|
-
display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px;
|
|
76397
|
-
border: 1px solid rgba(217,119,6,0.5); border-radius: 999px;
|
|
76398
|
-
background: rgba(217,119,6,0.12); color: #b45309; cursor: pointer;
|
|
76399
|
-
}
|
|
76400
76420
|
.YDEV_STAT.s-out { color: #2563eb; } .YDEV_STAT.s-in { color: #059669; } .YDEV_STAT.s-err { color: #dc2626; }
|
|
76401
76421
|
.YDEV_STAT.s-warn { color: #d97706; }
|
|
76402
76422
|
.YDEV_STAT.is-strong { font-weight: 700; }
|
|
@@ -76424,10 +76444,7 @@ function ensure_dev_style() {
|
|
|
76424
76444
|
.dir-err .TRAFFIC_ARROW, .dir-err .TRAFFIC_EVENT { color: #dc2626; }
|
|
76425
76445
|
.TRAFFIC_SUMMARY { opacity: 0.6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
|
|
76426
76446
|
.TRAFFIC_META { margin-left: auto; opacity: 0.6; font-size: 11px; white-space: nowrap; }
|
|
76427
|
-
.
|
|
76428
|
-
.TRAFFIC_ENTRY:hover .TRAFFIC_MUTE, .TRAFFIC_LINE:hover .TRAFFIC_MUTE, .TRAFFIC_NAME:hover .TRAFFIC_MUTE { opacity: 0.5; }
|
|
76429
|
-
.TRAFFIC_MUTE:hover { opacity: 1 !important; color: #d97706; }
|
|
76430
|
-
.TRAFFIC_KW { margin: 2px 0 0 16px; }
|
|
76447
|
+
.TRAFFIC_ENTRY:hover .TRAFFIC_KW { margin: 2px 0 0 16px; }
|
|
76431
76448
|
.TRAFFIC_FULL { margin: 4px 0 0 16px; padding: 6px 8px; font-family: monospace; font-size: 11px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; background: rgba(0,0,0,0.04); border-radius: 4px; overflow-x: auto; }
|
|
76432
76449
|
.TRAFFIC_ROW { display: flex; gap: 6px; align-items: baseline; }
|
|
76433
76450
|
.TRAFFIC_BULLET { opacity: 0.45; flex: 0 0 auto; }
|
|
@@ -76471,9 +76488,7 @@ details.TRAFFIC_NEST > summary::-webkit-details-marker { display: none; }
|
|
|
76471
76488
|
:root[data-theme="dark"] .TRAFFIC_VAL.t-bool, :root[data-theme="dark"] .TRAFFIC_VAL.t-null { color: #c084fc; }
|
|
76472
76489
|
:root[data-theme="dark"] .YDEV_CHIP.is-active { background: rgba(96,165,250,0.2); border-color: #60a5fa; color: #93c5fd; }
|
|
76473
76490
|
:root[data-theme="dark"] .YDEV_SEG_BTN.is-active { background: #2563eb; color: #fff; }
|
|
76474
|
-
:root[data-theme="dark"]
|
|
76475
|
-
:root[data-theme="dark"] .YDEV_MUTED_CHIP { border-color: rgba(245,158,11,0.5); background: rgba(245,158,11,0.16); color: #fbbf24; }
|
|
76476
|
-
`;
|
|
76491
|
+
:root[data-theme="dark"] `;
|
|
76477
76492
|
let $style = document.createElement("style");
|
|
76478
76493
|
$style.id = "yui-dev-style";
|
|
76479
76494
|
$style.textContent = css;
|
|
@@ -76616,23 +76631,6 @@ function traffic_bullets(obj) {
|
|
|
76616
76631
|
/******************************
|
|
76617
76632
|
* Entry rendering (per view)
|
|
76618
76633
|
******************************/
|
|
76619
|
-
function mute_button(sig) {
|
|
76620
|
-
return [
|
|
76621
|
-
"button",
|
|
76622
|
-
{
|
|
76623
|
-
class: "TRAFFIC_MUTE",
|
|
76624
|
-
type: "button",
|
|
76625
|
-
title: t("mute this message"),
|
|
76626
|
-
"data-i18n-title": "mute this message"
|
|
76627
|
-
},
|
|
76628
|
-
"⊘",
|
|
76629
|
-
{ click: (ev) => {
|
|
76630
|
-
ev.stopPropagation();
|
|
76631
|
-
ev.preventDefault();
|
|
76632
|
-
mute_signature(sig);
|
|
76633
|
-
} }
|
|
76634
|
-
];
|
|
76635
|
-
}
|
|
76636
76634
|
function event_spans(e) {
|
|
76637
76635
|
let spans = [[
|
|
76638
76636
|
"span",
|
|
@@ -76670,7 +76668,6 @@ function compact_summary(kw) {
|
|
|
76670
76668
|
}
|
|
76671
76669
|
function render_detailed(e) {
|
|
76672
76670
|
let head = event_spans(e);
|
|
76673
|
-
head.push(mute_button(e.sig));
|
|
76674
76671
|
head.push([
|
|
76675
76672
|
"span",
|
|
76676
76673
|
{ class: "TRAFFIC_META" },
|
|
@@ -76729,7 +76726,6 @@ function full_sections(payload) {
|
|
|
76729
76726
|
}
|
|
76730
76727
|
function render_full(e) {
|
|
76731
76728
|
let head = event_spans(e);
|
|
76732
|
-
head.push(mute_button(e.sig));
|
|
76733
76729
|
head.push([
|
|
76734
76730
|
"span",
|
|
76735
76731
|
{ class: "TRAFFIC_META" },
|
|
@@ -76767,7 +76763,6 @@ function render_compact(e) {
|
|
|
76767
76763
|
{ class: "TRAFFIC_SUMMARY" },
|
|
76768
76764
|
compact_summary(e.kw)
|
|
76769
76765
|
]);
|
|
76770
|
-
kids.push(mute_button(e.sig));
|
|
76771
76766
|
kids.push([
|
|
76772
76767
|
"span",
|
|
76773
76768
|
{ class: "TRAFFIC_META" },
|
|
@@ -76784,7 +76779,6 @@ function render_compact(e) {
|
|
|
76784
76779
|
}
|
|
76785
76780
|
function render_name(e) {
|
|
76786
76781
|
let kids = event_spans(e);
|
|
76787
|
-
kids.push(mute_button(e.sig));
|
|
76788
76782
|
kids.push([
|
|
76789
76783
|
"span",
|
|
76790
76784
|
{ class: "TRAFFIC_META" },
|
|
@@ -76841,7 +76835,6 @@ function render_entry(e) {
|
|
|
76841
76835
|
******************************/
|
|
76842
76836
|
function clear_traffic() {
|
|
76843
76837
|
TRAFFIC_LOG.length = 0;
|
|
76844
|
-
TRAFFIC_COUNTS.clear();
|
|
76845
76838
|
LOG_ERR_COUNT = 0;
|
|
76846
76839
|
LOG_WARN_COUNT = 0;
|
|
76847
76840
|
let logger = document.getElementById("developer-traffic-logger");
|
|
@@ -76953,7 +76946,6 @@ function info_traffic(title, msg, direction, size) {
|
|
|
76953
76946
|
let event = jn && jn.event ? String(jn.event) : "(no event)";
|
|
76954
76947
|
let kw = jn && jn.kw && typeof jn.kw === "object" ? jn.kw : null;
|
|
76955
76948
|
let command = kw && typeof kw.command === "string" ? kw.command : "";
|
|
76956
|
-
let sig = traffic_signature(event, command);
|
|
76957
76949
|
let hay = "";
|
|
76958
76950
|
try {
|
|
76959
76951
|
hay = (event + " " + command + " " + (kw ? JSON.stringify(kw) : "")).toLowerCase();
|
|
@@ -76964,7 +76956,6 @@ function info_traffic(title, msg, direction, size) {
|
|
|
76964
76956
|
title: title || "",
|
|
76965
76957
|
event,
|
|
76966
76958
|
command,
|
|
76967
|
-
sig,
|
|
76968
76959
|
dir: direction,
|
|
76969
76960
|
size,
|
|
76970
76961
|
ts: traffic_now(),
|
|
@@ -76974,17 +76965,11 @@ function info_traffic(title, msg, direction, size) {
|
|
|
76974
76965
|
$node: null
|
|
76975
76966
|
};
|
|
76976
76967
|
TRAFFIC_LOG.push(entry);
|
|
76977
|
-
TRAFFIC_COUNTS.set(sig, (TRAFFIC_COUNTS.get(sig) || 0) + 1);
|
|
76978
|
-
let crossed = dev_hide_periodic() && TRAFFIC_COUNTS.get(sig) === PERIODIC_THRESHOLD;
|
|
76979
76968
|
if (TRAFFIC_LOG.length > TRAFFIC_MAX) {
|
|
76980
76969
|
let old = TRAFFIC_LOG.shift();
|
|
76981
|
-
let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
|
|
76982
|
-
if (c <= 0) TRAFFIC_COUNTS.delete(old.sig);
|
|
76983
|
-
else TRAFFIC_COUNTS.set(old.sig, c);
|
|
76984
76970
|
if (old.$node && old.$node.parentNode) old.$node.parentNode.removeChild(old.$node);
|
|
76985
76971
|
}
|
|
76986
|
-
if (
|
|
76987
|
-
else if (!entry_hidden(entry, build_filter_ctx())) {
|
|
76972
|
+
if (!entry_hidden(entry, build_filter_ctx())) {
|
|
76988
76973
|
let node = render_entry(entry);
|
|
76989
76974
|
entry.$node = node;
|
|
76990
76975
|
let ph = logger.querySelector(".YDEV_EMPTY");
|
|
@@ -77030,19 +77015,12 @@ function info_log(level, msg, hora) {
|
|
|
77030
77015
|
dir: 0,
|
|
77031
77016
|
size: 0,
|
|
77032
77017
|
ts: traffic_now(),
|
|
77033
|
-
sig: log_signature(lvl, text),
|
|
77034
|
-
periodic: log_is_periodic(lvl, text),
|
|
77035
77018
|
hay: (lvl + " " + text).toLowerCase(),
|
|
77036
77019
|
$node: null
|
|
77037
77020
|
};
|
|
77038
77021
|
TRAFFIC_LOG.push(entry);
|
|
77039
77022
|
if (TRAFFIC_LOG.length > TRAFFIC_MAX) {
|
|
77040
77023
|
let old = TRAFFIC_LOG.shift();
|
|
77041
|
-
if (old.kind !== "log") {
|
|
77042
|
-
let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
|
|
77043
|
-
if (c <= 0) TRAFFIC_COUNTS.delete(old.sig);
|
|
77044
|
-
else TRAFFIC_COUNTS.set(old.sig, c);
|
|
77045
|
-
}
|
|
77046
77024
|
if (old.$node && old.$node.parentNode) old.$node.parentNode.removeChild(old.$node);
|
|
77047
77025
|
}
|
|
77048
77026
|
if (!entry_hidden(entry, build_filter_ctx())) {
|
|
@@ -77060,72 +77038,97 @@ function info_log(level, msg, hora) {
|
|
|
77060
77038
|
/******************************
|
|
77061
77039
|
* Trace toggles
|
|
77062
77040
|
******************************/
|
|
77063
|
-
function
|
|
77064
|
-
let
|
|
77065
|
-
if (
|
|
77066
|
-
gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
|
|
77067
|
-
v = 0;
|
|
77068
|
-
} else {
|
|
77069
|
-
gobj_write_attr(gobj_yuno(), "trace_inter_event", true);
|
|
77070
|
-
gobj_write_attr(gobj_yuno(), "trace_ievent_callback", info_traffic);
|
|
77071
|
-
v = 1;
|
|
77072
|
-
}
|
|
77073
|
-
kw_set_local_storage_value("trace_traffic", v);
|
|
77041
|
+
function yuno_trace_command(command, kw) {
|
|
77042
|
+
let r = gobj_command(gobj_yuno(), command, kw, gobj_yuno());
|
|
77043
|
+
if (!r || r.result < 0) log_error(`yui_dev: ${command} ${JSON.stringify(kw)}: ${r ? r.comment : "no answer"}`);
|
|
77074
77044
|
refresh_dev_chrome();
|
|
77075
77045
|
}
|
|
77076
|
-
function
|
|
77077
|
-
|
|
77078
|
-
if (v === 0) v = 1;
|
|
77079
|
-
else if (v === 1) v = 2;
|
|
77080
|
-
else v = 0;
|
|
77081
|
-
gobj_write_attr(gobj_yuno(), "tracing", v);
|
|
77082
|
-
kw_set_local_storage_value("trace_automata", v);
|
|
77083
|
-
refresh_dev_chrome();
|
|
77046
|
+
function global_bit(bit) {
|
|
77047
|
+
return gobj_global_trace_level() & bit ? 1 : 0;
|
|
77084
77048
|
}
|
|
77085
|
-
|
|
77086
|
-
|
|
77087
|
-
|
|
77088
|
-
|
|
77089
|
-
|
|
77090
|
-
|
|
77049
|
+
var GLOBAL_TRACE_BITS = {
|
|
77050
|
+
"machine": trace_level_t.TRACE_MACHINE,
|
|
77051
|
+
"ev_kw": trace_level_t.TRACE_EV_KW,
|
|
77052
|
+
"create_delete": trace_level_t.TRACE_CREATE_DELETE,
|
|
77053
|
+
"start_stop": trace_level_t.TRACE_START_STOP,
|
|
77054
|
+
"subscriptions": trace_level_t.TRACE_SUBSCRIPTIONS
|
|
77055
|
+
};
|
|
77056
|
+
function toggle_global(level) {
|
|
77057
|
+
yuno_trace_command("set-global-trace", {
|
|
77058
|
+
level,
|
|
77059
|
+
set: global_bit(GLOBAL_TRACE_BITS[level]) ? 0 : 1
|
|
77060
|
+
});
|
|
77091
77061
|
}
|
|
77092
|
-
function
|
|
77093
|
-
|
|
77094
|
-
|
|
77095
|
-
gobj_write_attr(gobj_yuno(), "trace_start_stop", v);
|
|
77096
|
-
kw_set_local_storage_value("trace_start_stop", v);
|
|
77097
|
-
refresh_dev_chrome();
|
|
77062
|
+
function automata_state() {
|
|
77063
|
+
if (!global_bit(trace_level_t.TRACE_MACHINE)) return 0;
|
|
77064
|
+
return global_bit(trace_level_t.TRACE_EV_KW) ? 2 : 1;
|
|
77098
77065
|
}
|
|
77099
|
-
function
|
|
77100
|
-
let v =
|
|
77101
|
-
|
|
77102
|
-
|
|
77103
|
-
|
|
77104
|
-
|
|
77066
|
+
function toggle_automata() {
|
|
77067
|
+
let v = automata_state();
|
|
77068
|
+
if (v === 0) yuno_trace_command("set-global-trace", {
|
|
77069
|
+
level: "machine",
|
|
77070
|
+
set: 1
|
|
77071
|
+
});
|
|
77072
|
+
else if (v === 1) yuno_trace_command("set-global-trace", {
|
|
77073
|
+
level: "ev_kw",
|
|
77074
|
+
set: 1
|
|
77075
|
+
});
|
|
77076
|
+
else {
|
|
77077
|
+
yuno_trace_command("set-global-trace", {
|
|
77078
|
+
level: "ev_kw",
|
|
77079
|
+
set: 0
|
|
77080
|
+
});
|
|
77081
|
+
yuno_trace_command("set-global-trace", {
|
|
77082
|
+
level: "machine",
|
|
77083
|
+
set: 0
|
|
77084
|
+
});
|
|
77085
|
+
}
|
|
77086
|
+
}
|
|
77087
|
+
function traffic_state() {
|
|
77088
|
+
return gobj_get_gclass_trace_level2("C_IEVENT_CLI").includes("ievents") ? 1 : 0;
|
|
77105
77089
|
}
|
|
77106
|
-
function
|
|
77107
|
-
|
|
77108
|
-
|
|
77090
|
+
function toggle_traffic() {
|
|
77091
|
+
yuno_trace_command("set-gclass-trace", {
|
|
77092
|
+
gclass_name: "C_IEVENT_CLI",
|
|
77093
|
+
level: "ievents",
|
|
77094
|
+
set: traffic_state() ? 0 : 1
|
|
77095
|
+
});
|
|
77096
|
+
}
|
|
77097
|
+
function periodic_state() {
|
|
77098
|
+
let on = gobj_global_trace_level() & trace_level_t.TRACE_TIMER_PERIODIC;
|
|
77099
|
+
let off = gobj_global_trace_no_level() & trace_level_t.TRACE_TIMER_PERIODIC;
|
|
77100
|
+
return on && !off ? 1 : 0;
|
|
77101
|
+
}
|
|
77102
|
+
function toggle_periodic() {
|
|
77103
|
+
let v = periodic_state() ? 0 : 1;
|
|
77104
|
+
yuno_trace_command("set-global-trace", {
|
|
77105
|
+
level: "timer_periodic",
|
|
77106
|
+
set: v
|
|
77107
|
+
});
|
|
77108
|
+
yuno_trace_command("set-global-no-trace", {
|
|
77109
|
+
level: "timer_periodic",
|
|
77110
|
+
set: v ? 0 : 1
|
|
77111
|
+
});
|
|
77112
|
+
}
|
|
77113
|
+
function i18n_state() {
|
|
77114
|
+
return Number(kw_get_local_storage_value("trace_i18n", 0, false)) ? 1 : 0;
|
|
77115
|
+
}
|
|
77116
|
+
function toggle_i18n() {
|
|
77117
|
+
let v = i18n_state() ? 0 : 1;
|
|
77109
77118
|
i18next.options.debug = v ? true : false;
|
|
77110
77119
|
kw_set_local_storage_value("trace_i18n", v);
|
|
77111
77120
|
refresh_dev_chrome();
|
|
77112
77121
|
}
|
|
77113
|
-
function set_no_poll() {
|
|
77114
|
-
let v = Number(kw_get_local_storage_value("no_poll"));
|
|
77115
|
-
v = v ? 0 : 1;
|
|
77116
|
-
gobj_write_attr(gobj_yuno(), "no_poll", v);
|
|
77117
|
-
kw_set_local_storage_value("no_poll", v);
|
|
77118
|
-
refresh_dev_chrome();
|
|
77119
|
-
}
|
|
77120
77122
|
/******************************
|
|
77121
77123
|
* Chrome (controls)
|
|
77122
77124
|
******************************/
|
|
77123
77125
|
function refresh_dev_chrome() {
|
|
77124
77126
|
document.querySelectorAll(".YDEV_CHIP[data-trace]").forEach(($b) => {
|
|
77125
77127
|
let key = $b.getAttribute("data-trace");
|
|
77128
|
+
let def = TRACE_DEFS.find((d) => d[0] === key);
|
|
77126
77129
|
let label = t($b.getAttribute("data-label") || "");
|
|
77127
|
-
let v =
|
|
77128
|
-
$b.textContent = key === "
|
|
77130
|
+
let v = def ? def[3]() : 0;
|
|
77131
|
+
$b.textContent = key === "automata" && v > 0 ? label + " " + v : label;
|
|
77129
77132
|
$b.classList.toggle("is-active", v > 0);
|
|
77130
77133
|
});
|
|
77131
77134
|
let view = dev_view();
|
|
@@ -77144,40 +77147,9 @@ function refresh_dev_chrome() {
|
|
|
77144
77147
|
document.querySelectorAll(".YDEV_CHIP[data-dir]").forEach(($b) => {
|
|
77145
77148
|
$b.classList.toggle("is-active", !!dev_num($b.getAttribute("data-dir"), 1));
|
|
77146
77149
|
});
|
|
77147
|
-
document.querySelectorAll(".YDEV_CHIP[data-toggle=\"periodic\"]").forEach(($b) => {
|
|
77148
|
-
$b.classList.toggle("is-active", dev_hide_periodic());
|
|
77149
|
-
});
|
|
77150
77150
|
document.querySelectorAll(".YDEV_CHIP[data-toggle=\"automata-simple\"]").forEach(($b) => {
|
|
77151
77151
|
$b.classList.toggle("is-active", !!dev_simple_mach());
|
|
77152
77152
|
});
|
|
77153
|
-
let $m = document.getElementById("ydev-muted");
|
|
77154
|
-
if ($m) {
|
|
77155
|
-
$m.replaceChildren();
|
|
77156
|
-
let set = dev_muted();
|
|
77157
|
-
if (set.size) {
|
|
77158
|
-
let $lbl = document.createElement("span");
|
|
77159
|
-
$lbl.className = "YDEV_LABEL";
|
|
77160
|
-
$lbl.textContent = t("muted");
|
|
77161
|
-
$lbl.setAttribute("data-i18n", "muted");
|
|
77162
|
-
$m.appendChild($lbl);
|
|
77163
|
-
set.forEach((sig) => {
|
|
77164
|
-
$m.appendChild(createElement2([
|
|
77165
|
-
"button",
|
|
77166
|
-
{
|
|
77167
|
-
class: "YDEV_MUTED_CHIP",
|
|
77168
|
-
type: "button",
|
|
77169
|
-
title: t("unmute"),
|
|
77170
|
-
"data-i18n-title": "unmute"
|
|
77171
|
-
},
|
|
77172
|
-
"⊘ " + sig + " ✕",
|
|
77173
|
-
{ click: (ev) => {
|
|
77174
|
-
ev.stopPropagation();
|
|
77175
|
-
unmute_signature(sig);
|
|
77176
|
-
} }
|
|
77177
|
-
]));
|
|
77178
|
-
});
|
|
77179
|
-
}
|
|
77180
|
-
}
|
|
77181
77153
|
update_stats();
|
|
77182
77154
|
}
|
|
77183
77155
|
function traffic_to_text() {
|
|
@@ -77222,19 +77194,23 @@ function dev_fallback_copy(text) {
|
|
|
77222
77194
|
document.body.removeChild(ta);
|
|
77223
77195
|
}
|
|
77224
77196
|
function build_control_bar() {
|
|
77225
|
-
let trace_chips = TRACE_DEFS.map(([key, label_key,
|
|
77197
|
+
let trace_chips = TRACE_DEFS.map(([key, label_key, title_key, state, toggle]) => [
|
|
77226
77198
|
"button",
|
|
77227
77199
|
{
|
|
77228
77200
|
class: "YDEV_CHIP",
|
|
77229
77201
|
"data-trace": key,
|
|
77230
77202
|
"data-label": label_key,
|
|
77231
77203
|
"data-i18n": label_key,
|
|
77232
|
-
type: "button"
|
|
77204
|
+
type: "button",
|
|
77205
|
+
title: t(title_key),
|
|
77206
|
+
"data-i18n-title": title_key,
|
|
77207
|
+
"aria-label": t(label_key),
|
|
77208
|
+
"data-i18n-aria-label": label_key
|
|
77233
77209
|
},
|
|
77234
77210
|
t(label_key),
|
|
77235
77211
|
{ click: (ev) => {
|
|
77236
77212
|
ev.stopPropagation();
|
|
77237
|
-
|
|
77213
|
+
toggle();
|
|
77238
77214
|
} }
|
|
77239
77215
|
]);
|
|
77240
77216
|
let simple_mach = [
|
|
@@ -77372,29 +77348,6 @@ function build_control_bar() {
|
|
|
77372
77348
|
mk_dir("in", "⇠", "dev_filter_in", "incoming"),
|
|
77373
77349
|
mk_dir("err", "⚠", "dev_filter_err", "errors")
|
|
77374
77350
|
];
|
|
77375
|
-
let periodic_chip = [
|
|
77376
|
-
"button",
|
|
77377
|
-
{
|
|
77378
|
-
class: "YDEV_CHIP",
|
|
77379
|
-
"data-toggle": "periodic",
|
|
77380
|
-
type: "button",
|
|
77381
|
-
title: t("hide the timers and the recurring traffic"),
|
|
77382
|
-
"data-i18n-title": "hide the timers and the recurring traffic"
|
|
77383
|
-
},
|
|
77384
|
-
[[
|
|
77385
|
-
"span",
|
|
77386
|
-
{ class: "YDEV_GLYPH" },
|
|
77387
|
-
"⊘"
|
|
77388
|
-
], [
|
|
77389
|
-
"span",
|
|
77390
|
-
{ "data-i18n": "periodic" },
|
|
77391
|
-
t("periodic")
|
|
77392
|
-
]],
|
|
77393
|
-
{ click: (ev) => {
|
|
77394
|
-
ev.stopPropagation();
|
|
77395
|
-
toggle_pref("dev_hide_periodic", HIDE_PERIODIC_DEFAULT);
|
|
77396
|
-
} }
|
|
77397
|
-
];
|
|
77398
77351
|
let search = [
|
|
77399
77352
|
"input",
|
|
77400
77353
|
{
|
|
@@ -77475,7 +77428,7 @@ function build_control_bar() {
|
|
|
77475
77428
|
grp("view", [view_seg]),
|
|
77476
77429
|
expand_grp,
|
|
77477
77430
|
sep(),
|
|
77478
|
-
grp("show",
|
|
77431
|
+
grp("show", dir_chips),
|
|
77479
77432
|
sep(),
|
|
77480
77433
|
grp("find", [search]),
|
|
77481
77434
|
sep(),
|
|
@@ -77510,14 +77463,6 @@ function build_dev_body() {
|
|
|
77510
77463
|
{ class: "YDEV_BODY" },
|
|
77511
77464
|
[
|
|
77512
77465
|
build_control_bar(),
|
|
77513
|
-
[
|
|
77514
|
-
"div",
|
|
77515
|
-
{
|
|
77516
|
-
class: "YDEV_MUTED",
|
|
77517
|
-
id: "ydev-muted"
|
|
77518
|
-
},
|
|
77519
|
-
[]
|
|
77520
|
-
],
|
|
77521
77466
|
[
|
|
77522
77467
|
"div",
|
|
77523
77468
|
{
|
|
@@ -77550,28 +77495,16 @@ function dev_window_was_open() {
|
|
|
77550
77495
|
return Number(kw_get_local_storage_value("open_developer_window", 0, false)) ? true : false;
|
|
77551
77496
|
}
|
|
77552
77497
|
/************************************************************
|
|
77553
|
-
*
|
|
77554
|
-
*
|
|
77555
|
-
*
|
|
77498
|
+
* Wire the developer window's sinks and the browser-side prefs.
|
|
77499
|
+
* Call it once at app startup.
|
|
77500
|
+
*
|
|
77501
|
+
* The TRACE LEVELS are not here: the yuno restores the ones the user
|
|
77502
|
+
* persisted in its own mt_create (`trace_levels` / `no_trace_levels`),
|
|
77503
|
+
* as a C yuno does. What is left is where the output goes.
|
|
77556
77504
|
************************************************************/
|
|
77557
77505
|
function apply_dev_traces() {
|
|
77558
|
-
|
|
77559
|
-
|
|
77560
|
-
let creation = Number(kw_get_local_storage_value("trace_creation", 0, false));
|
|
77561
|
-
let start_stop = Number(kw_get_local_storage_value("trace_start_stop", 0, false));
|
|
77562
|
-
let subscriptions = Number(kw_get_local_storage_value("trace_subscriptions", 0, false));
|
|
77563
|
-
let i18n = Number(kw_get_local_storage_value("trace_i18n", 0, false));
|
|
77564
|
-
let no_poll = Number(kw_get_local_storage_value("no_poll", 0, false));
|
|
77565
|
-
if (traffic) {
|
|
77566
|
-
gobj_write_attr(gobj_yuno(), "trace_inter_event", true);
|
|
77567
|
-
gobj_write_attr(gobj_yuno(), "trace_ievent_callback", info_traffic);
|
|
77568
|
-
} else gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
|
|
77569
|
-
gobj_write_attr(gobj_yuno(), "tracing", trace);
|
|
77570
|
-
gobj_write_attr(gobj_yuno(), "trace_creation", creation);
|
|
77571
|
-
gobj_write_attr(gobj_yuno(), "trace_start_stop", start_stop);
|
|
77572
|
-
gobj_write_attr(gobj_yuno(), "trace_subscriptions", subscriptions);
|
|
77573
|
-
gobj_write_attr(gobj_yuno(), "no_poll", no_poll);
|
|
77574
|
-
i18next.options.debug = i18n ? true : false;
|
|
77506
|
+
gobj_write_attr(gobj_yuno(), "trace_ievent_callback", info_traffic);
|
|
77507
|
+
i18next.options.debug = i18n_state() ? true : false;
|
|
77575
77508
|
gobj_set_trace_machine_format(dev_simple_mach());
|
|
77576
77509
|
set_log_callback(info_log);
|
|
77577
77510
|
apply_console_route();
|
|
@@ -77622,8 +77555,8 @@ function setup_dev(self, show) {
|
|
|
77622
77555
|
*
|
|
77623
77556
|
* Returns { $el, dispose }:
|
|
77624
77557
|
* - $el: the panel element (control bar + log + stats).
|
|
77625
|
-
* - dispose:
|
|
77626
|
-
* the
|
|
77558
|
+
* - dispose: call it from the modal's on_close (kept for the
|
|
77559
|
+
* contract; the traces outlive the panel).
|
|
77627
77560
|
************************************************************/
|
|
77628
77561
|
function build_dev_panel() {
|
|
77629
77562
|
ensure_dev_style();
|
|
@@ -77644,9 +77577,7 @@ function build_dev_panel() {
|
|
|
77644
77577
|
refresh_dev_chrome();
|
|
77645
77578
|
rerender_all();
|
|
77646
77579
|
}, 0);
|
|
77647
|
-
let dispose = function() {
|
|
77648
|
-
gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
|
|
77649
|
-
};
|
|
77580
|
+
let dispose = function() {};
|
|
77650
77581
|
return {
|
|
77651
77582
|
$el,
|
|
77652
77583
|
dispose
|