@yuneta/gobj-ui 7.23.141 → 7.23.143
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 +50 -11
- package/dist/gobj-ui.cjs.js +774 -248
- package/dist/gobj-ui.es.js +773 -249
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +24 -14
- package/src/c_yui_json_pad.js +454 -0
- package/src/c_yui_schema_editor.js +16 -0
- package/src/c_yui_treedb_graph.js +7 -2
- package/src/c_yui_treedb_topic_with_form.js +14 -0
- package/src/schema_import.js +2 -1
- package/src/schema_model.js +2 -1
- package/src/schema_to_c.js +1 -1
- package/src/schema_validate.js +20 -0
- package/src/schema_validate.test.js +44 -0
- package/src/treedb_fold_model.js +52 -20
- package/src/treedb_fold_model.test.js +40 -5
- package/src/yui_json_pad.js +109 -0
|
@@ -111,7 +111,7 @@ SDATA(data_type_t.DTP_LIST, "operation_modes", 0,
|
|
|
111
111
|
'["reading", "operation", "writing", "edition"]',
|
|
112
112
|
"Available **permission** or behaviour modes. These operation modes are required to be accomplish by the graph handler (G6 child). TODO permissions must match treedb permissions."),
|
|
113
113
|
SDATA(data_type_t.DTP_BOOLEAN, "with_treedb_tables",0, false, "Include treedb tables"),
|
|
114
|
-
SDATA(data_type_t.DTP_INTEGER, "expand_depth", 0,
|
|
114
|
+
SDATA(data_type_t.DTP_INTEGER, "expand_depth", 0, 1, "Levels of the treedb open when it loads, until the reader steps (`fold_level`): 1 = the roots alone (the default), 2 = the roots and their children. Forwarded to the G6 child"),
|
|
115
115
|
SDATA(data_type_t.DTP_INTEGER, "fold_page_size", 0, 24, "Children of one hook shown per page in the graph; a `+N` chip opens the next page. Forwarded to the G6 child"),
|
|
116
116
|
|
|
117
117
|
/*---------------- Forwarded to the G6 child (its own defaults) ----------------*/
|
|
@@ -2699,6 +2699,11 @@ function refresh_legend(gobj)
|
|
|
2699
2699
|
return String(a.topic).localeCompare(String(b.topic));
|
|
2700
2700
|
});
|
|
2701
2701
|
|
|
2702
|
+
/* Only a topic hooked to ITSELF can be the main one, so only those
|
|
2703
|
+
* offer the star -- and only when there is more than one to pick
|
|
2704
|
+
* from: with a single hierarchical topic the choice is made. */
|
|
2705
|
+
let hierarchical_count = topics.filter((e) => e.hierarchical && !e.hidden).length;
|
|
2706
|
+
|
|
2702
2707
|
for(let entry of topics) {
|
|
2703
2708
|
let topic_name = entry.topic;
|
|
2704
2709
|
let is_main = (topic_name === state.main_topic);
|
|
@@ -2813,7 +2818,7 @@ function refresh_legend(gobj)
|
|
|
2813
2818
|
style: 'padding:0 .5rem; cursor:default;',
|
|
2814
2819
|
title: t('main topic'), 'data-i18n-title': 'main topic'},
|
|
2815
2820
|
[['span', {style: LEGEND_STAR_MAIN_STYLE}, '★']]]);
|
|
2816
|
-
} else if(!hidden) {
|
|
2821
|
+
} else if(!hidden && entry.hierarchical && hierarchical_count > 1) {
|
|
2817
2822
|
extras.push(['button', {class: 'GRAPH_LEGEND_STAR button',
|
|
2818
2823
|
type: 'button', style: 'padding:0 .5rem;',
|
|
2819
2824
|
title: t('main topic'), 'data-i18n-title': 'main topic',
|
|
@@ -1522,6 +1522,20 @@ function create_tabulator(gobj)
|
|
|
1522
1522
|
* had once opened the table. Only a real change is written. */
|
|
1523
1523
|
gobj.priv._page_size = tabulator_settings.paginationSize;
|
|
1524
1524
|
|
|
1525
|
+
/* Normalized records are read by their KEY: a topic table opens
|
|
1526
|
+
* sorted by `id` (the pkey), ascending and natural (`d2` before
|
|
1527
|
+
* `d10`) -- unless the app set its own order, or the table pages
|
|
1528
|
+
* from the backend, where a sort in the browser would reorder one
|
|
1529
|
+
* page and say nothing about the others. */
|
|
1530
|
+
let pkey_col = columns.find((c) => c && c.field === pkey);
|
|
1531
|
+
if(pkey_col && !tabulator_settings.initialSort &&
|
|
1532
|
+
!gobj_read_bool_attr(gobj, "with_remote_paging")) {
|
|
1533
|
+
if(!pkey_col.sorter) {
|
|
1534
|
+
pkey_col.sorter = "alphanum";
|
|
1535
|
+
}
|
|
1536
|
+
tabulator_settings.initialSort = [{column: pkey, dir: "asc"}];
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1525
1539
|
Object.assign(tabulator_settings, {
|
|
1526
1540
|
index: pkey,
|
|
1527
1541
|
columns: columns,
|
package/src/schema_import.js
CHANGED
|
@@ -38,7 +38,8 @@ import {
|
|
|
38
38
|
* the fkey, `order`) is composed, and `_geometry` belongs to whoever
|
|
39
39
|
* laid the graph out — an import must not move their boxes. */
|
|
40
40
|
const TOPIC_FIELDS = [
|
|
41
|
-
"pkey", "pkey2s", "system_flag", "tkey", "topic_version", "system_topic"
|
|
41
|
+
"pkey", "pkey2s", "system_flag", "tkey", "topic_version", "system_topic",
|
|
42
|
+
"main_topic"
|
|
42
43
|
];
|
|
43
44
|
|
|
44
45
|
/* Same for a `cols` record. */
|
package/src/schema_model.js
CHANGED
|
@@ -183,7 +183,7 @@ function sort_by_order(list)
|
|
|
183
183
|
* schema_version, c_schema_version, system_schema_version,
|
|
184
184
|
* topics: [{
|
|
185
185
|
* id, name, order, record,
|
|
186
|
-
* pkey, topic_version, system_topic,
|
|
186
|
+
* pkey, topic_version, system_topic, main_topic,
|
|
187
187
|
* cols: [{id, name, order, record}]
|
|
188
188
|
* }]
|
|
189
189
|
* }],
|
|
@@ -239,6 +239,7 @@ function build_schema_model(records)
|
|
|
239
239
|
tkey: record.tkey || "",
|
|
240
240
|
topic_version: record.topic_version,
|
|
241
241
|
system_topic: !!record.system_topic,
|
|
242
|
+
main_topic: !!record.main_topic,
|
|
242
243
|
cols: []
|
|
243
244
|
};
|
|
244
245
|
by_topic_id[entry.id] = entry;
|
package/src/schema_to_c.js
CHANGED
|
@@ -47,7 +47,7 @@ const DEFAULT_PAD = 68;
|
|
|
47
47
|
* and every export diffs against the last one. */
|
|
48
48
|
const TOPIC_KEY_ORDER = [
|
|
49
49
|
"id", "pkey", "pkey2s", "system_flag", "tkey",
|
|
50
|
-
"topic_version", "system_topic"
|
|
50
|
+
"topic_version", "system_topic", "main_topic"
|
|
51
51
|
];
|
|
52
52
|
|
|
53
53
|
/* Same for a column, in the order the .c literals write it. */
|
package/src/schema_validate.js
CHANGED
|
@@ -189,6 +189,26 @@ function validate_schema(treedb, options)
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/* The topic the tree hangs from (`main_topic`): ONE per treedb, and
|
|
193
|
+
* only a topic hooked to itself. treedb_open_db logs either mistake
|
|
194
|
+
* and ignores the mark, so the restart succeeds and the graph goes
|
|
195
|
+
* on deducing -- better said here, before it. */
|
|
196
|
+
let marked = treedb.topics.filter((topic) => topic.main_topic);
|
|
197
|
+
if(marked.length > 1) {
|
|
198
|
+
add("error", "more than one main topic", marked[1].name, "",
|
|
199
|
+
marked.map((topic) => topic.name).join(", "));
|
|
200
|
+
}
|
|
201
|
+
for(let topic of marked) {
|
|
202
|
+
let hooks_itself = topic.cols.some((col) => {
|
|
203
|
+
let hook = col_hook(col.record);
|
|
204
|
+
return col_flags(col.record).indexOf("hook") >= 0 &&
|
|
205
|
+
!!hook && Object.prototype.hasOwnProperty.call(hook, topic.name);
|
|
206
|
+
});
|
|
207
|
+
if(!hooks_itself) {
|
|
208
|
+
add("error", "main topic not hooked to itself", topic.name, "", "");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
192
212
|
/* How many hooks name each fkey column. Both answers are a
|
|
193
213
|
* finding: NONE and the parent side of the link was renamed or
|
|
194
214
|
* deleted, so the references are written by nobody; TWO and the
|
|
@@ -94,6 +94,50 @@ describe("the topic will not open", () => {
|
|
|
94
94
|
});
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
describe("the main topic mark", () => {
|
|
98
|
+
/* departments hooked to itself: the one topic the mark can go on */
|
|
99
|
+
const self_hook = {
|
|
100
|
+
id: "db.departments.departments", value: "departments", order: 3,
|
|
101
|
+
type: "dict", flag: ["hook"], hook: {departments: "departments"},
|
|
102
|
+
topics: ["topics^db.departments^cols"]
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
test("on a topic hooked to itself, says nothing", () => {
|
|
106
|
+
const t = schema({extra_col: self_hook});
|
|
107
|
+
t.topics[0].main_topic = true;
|
|
108
|
+
const c = codes(validate_schema(t));
|
|
109
|
+
expect(c).not.toContain("main topic not hooked to itself");
|
|
110
|
+
expect(c).not.toContain("more than one main topic");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("on a topic that is not hooked to itself", () => {
|
|
114
|
+
const t = schema();
|
|
115
|
+
t.topics[1].main_topic = true;
|
|
116
|
+
const f = validate_schema(t);
|
|
117
|
+
expect(codes(f)).toContain("main topic not hooked to itself");
|
|
118
|
+
expect(has_errors(f)).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("on two topics", () => {
|
|
122
|
+
const t = schema({extra_col: self_hook});
|
|
123
|
+
t.topics[0].main_topic = true;
|
|
124
|
+
t.topics[1].main_topic = true;
|
|
125
|
+
const f = validate_schema(t);
|
|
126
|
+
expect(codes(f)).toContain("more than one main topic");
|
|
127
|
+
expect(f.find((x) => x.code === "more than one main topic").detail)
|
|
128
|
+
.toBe("departments, users");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("the model reads the mark from the record", () => {
|
|
132
|
+
const model = build_schema_model({
|
|
133
|
+
treedbs: [{id: "db"}],
|
|
134
|
+
topics: [{id: "db.a", value: "a", main_topic: true, treedbs: ["treedbs^db^topics"]}],
|
|
135
|
+
cols: [],
|
|
136
|
+
});
|
|
137
|
+
expect(model.treedbs[0].topics[0].main_topic).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
97
141
|
describe("the link does nothing, and the write succeeded", () => {
|
|
98
142
|
test("a hook with no mapping", () => {
|
|
99
143
|
expect(codes(validate_schema(schema({hook: null})))).toContain("hook has no mapping");
|
package/src/treedb_fold_model.js
CHANGED
|
@@ -152,7 +152,13 @@ export function fold_build_model(descs, records, opts)
|
|
|
152
152
|
|
|
153
153
|
let hidden = new Set(is_array(opts.hidden_topics)? opts.hidden_topics : []);
|
|
154
154
|
let loose_shown = new Set(is_array(opts.loose_topics)? opts.loose_topics : []);
|
|
155
|
-
let main_topic = opts.main_topic ||
|
|
155
|
+
let main_topic = opts.main_topic || "";
|
|
156
|
+
if(main_topic && !fold_is_hierarchical(descs, main_topic)) {
|
|
157
|
+
main_topic = ""; /* a pick the rule does not allow (see fold_main_topic): deduce */
|
|
158
|
+
}
|
|
159
|
+
if(!main_topic) {
|
|
160
|
+
main_topic = fold_main_topic(descs);
|
|
161
|
+
}
|
|
156
162
|
if(main_topic && (!descs[main_topic] || hidden.has(main_topic))) {
|
|
157
163
|
main_topic = ""; /* a main topic that is not drawn governs nothing */
|
|
158
164
|
}
|
|
@@ -355,34 +361,60 @@ function count_levels(model)
|
|
|
355
361
|
}
|
|
356
362
|
|
|
357
363
|
/************************************************************
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
364
|
+
* A HIERARCHICAL topic: one hooked to ITSELF -- places inside
|
|
365
|
+
* places, groups inside groups, roles inside roles. It is the
|
|
366
|
+
* only kind of topic a tree can hang from: a topic that only
|
|
367
|
+
* reaches OTHER topics is a list with children, and making it
|
|
368
|
+
* the trunk drew the agent's treedb from `yunos` instead of
|
|
369
|
+
* `realms`.
|
|
370
|
+
*
|
|
371
|
+
* (Not the engine's `entity` tier, which is any topic with
|
|
372
|
+
* hooks AND fkeys: that one sizes a card and says nothing about
|
|
373
|
+
* where a tree starts.)
|
|
374
|
+
************************************************************/
|
|
375
|
+
export function fold_is_hierarchical(descs, topic_name)
|
|
376
|
+
{
|
|
377
|
+
if(!is_object(descs) || !descs[topic_name]) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
return hook_targets(descs, topic_name).has(topic_name);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/************************************************************
|
|
384
|
+
* The topic the tree hangs from. Only a HIERARCHICAL topic can
|
|
385
|
+
* be it, and in this order:
|
|
386
|
+
*
|
|
387
|
+
* the one the schema MARKS (`main_topic: true` on the topic,
|
|
388
|
+
* sent by the backend in its desc since SDK 7.19; the first
|
|
389
|
+
* one in schema order if a schema marks two);
|
|
390
|
+
* else the one whose hooks reach the most OTHER topics,
|
|
391
|
+
* schema order breaking a tie.
|
|
392
|
+
*
|
|
393
|
+
* "" when no topic is hooked to itself: there is no trunk, and
|
|
394
|
+
* every topic is a tree of its own. (A pick by the reader comes
|
|
395
|
+
* first of all, and is fold_build_model's business.)
|
|
363
396
|
************************************************************/
|
|
364
397
|
export function fold_main_topic(descs)
|
|
365
398
|
{
|
|
366
399
|
descs = is_object(descs)? descs : {};
|
|
367
|
-
let
|
|
368
|
-
|
|
369
|
-
|
|
400
|
+
let candidates = Object.keys(descs).filter((topic_name) => {
|
|
401
|
+
return topic_name.substring(0, 2) !== "__" && fold_is_hierarchical(descs, topic_name);
|
|
402
|
+
});
|
|
370
403
|
|
|
371
|
-
for(let topic_name of
|
|
372
|
-
if(topic_name.
|
|
373
|
-
|
|
404
|
+
for(let topic_name of candidates) {
|
|
405
|
+
if(descs[topic_name].main_topic === true) {
|
|
406
|
+
return topic_name;
|
|
374
407
|
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
let best = "";
|
|
411
|
+
let best_score = -1;
|
|
412
|
+
for(let topic_name of candidates) {
|
|
375
413
|
let reach = hook_targets(descs, topic_name);
|
|
376
|
-
let self = reach.has(topic_name);
|
|
377
414
|
reach.delete(topic_name);
|
|
378
|
-
|
|
379
|
-
if(score === 0) {
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
if(score > best_score || (score === best_score && self && !best_self)) {
|
|
415
|
+
if(reach.size > best_score) {
|
|
383
416
|
best = topic_name;
|
|
384
|
-
best_score =
|
|
385
|
-
best_self = self;
|
|
417
|
+
best_score = reach.size;
|
|
386
418
|
}
|
|
387
419
|
}
|
|
388
420
|
return best;
|
|
@@ -31,11 +31,12 @@ import {
|
|
|
31
31
|
fold_open_levels,
|
|
32
32
|
fold_close_levels,
|
|
33
33
|
fold_expand_to_level,
|
|
34
|
+
fold_is_hierarchical,
|
|
34
35
|
} from "./treedb_fold_model.js";
|
|
35
36
|
|
|
36
37
|
const places_desc = {
|
|
37
38
|
topic_name: "places",
|
|
38
|
-
node_treedb_type: "
|
|
39
|
+
node_treedb_type: "entity",
|
|
39
40
|
cols: [
|
|
40
41
|
{id: "id", flag: []},
|
|
41
42
|
{id: "place", flag: ["fkey"], fkey: {places: "places"}},
|
|
@@ -56,13 +57,20 @@ const devices_desc = {
|
|
|
56
57
|
};
|
|
57
58
|
const controllers_desc = {
|
|
58
59
|
topic_name: "controllers",
|
|
59
|
-
node_treedb_type: "
|
|
60
|
+
node_treedb_type: "entity",
|
|
60
61
|
cols: [
|
|
61
62
|
{id: "id", flag: []},
|
|
62
63
|
{id: "place", flag: ["fkey"], fkey: {places: "controllers"}},
|
|
63
64
|
{id: "devices", flag: ["hook"], hook: {devices: "controller"}},
|
|
64
65
|
],
|
|
65
66
|
};
|
|
67
|
+
/* controllers inside controllers: a second HIERARCHICAL topic */
|
|
68
|
+
const controllers_self = Object.assign({}, controllers_desc, {
|
|
69
|
+
cols: controllers_desc.cols.concat([
|
|
70
|
+
{id: "controllers", flag: ["hook"], hook: {controllers: "parent"}},
|
|
71
|
+
{id: "parent", flag: ["fkey"], fkey: {controllers: "controllers"}},
|
|
72
|
+
]),
|
|
73
|
+
});
|
|
66
74
|
const groups_desc = {
|
|
67
75
|
topic_name: "device_groups",
|
|
68
76
|
node_treedb_type: "extended",
|
|
@@ -189,10 +197,33 @@ describe("main topic, hidden topics, loose records", () => {
|
|
|
189
197
|
expect(m.linked.has("device_groups")).toBe(false);
|
|
190
198
|
});
|
|
191
199
|
|
|
200
|
+
test("only a topic hooked to ITSELF can be the main one", () => {
|
|
201
|
+
expect(fold_is_hierarchical(descs, "places")).toBe(true);
|
|
202
|
+
expect(fold_is_hierarchical(descs, "controllers")).toBe(false);
|
|
203
|
+
expect(fold_is_hierarchical(descs, "device_groups")).toBe(false);
|
|
204
|
+
/* c reaches x and nothing reaches itself: no trunk at all */
|
|
205
|
+
let c = {topic_name: "c", cols: [{id: "id", flag: []},
|
|
206
|
+
{id: "xs", flag: ["hook"], hook: {x: "c"}}]};
|
|
207
|
+
let x = {topic_name: "x", cols: [{id: "id", flag: []}, {id: "c", flag: ["fkey"]}]};
|
|
208
|
+
expect(fold_main_topic({c: c, x: x})).toBe("");
|
|
209
|
+
/* a tree of places alone, reaching no other topic, is still a trunk */
|
|
210
|
+
expect(fold_main_topic({places: places_desc})).toBe("places");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("the schema's mark wins over the deduction, among hierarchical topics", () => {
|
|
214
|
+
let d = Object.assign({}, descs, {controllers: controllers_self});
|
|
215
|
+
expect(fold_main_topic(d)).toBe("places"); /* places reaches more */
|
|
216
|
+
let marked = Object.assign({}, d, {controllers: Object.assign({}, controllers_self, {main_topic: true})});
|
|
217
|
+
expect(fold_main_topic(marked)).toBe("controllers");
|
|
218
|
+
/* a mark on a topic that is not hierarchical is not a trunk */
|
|
219
|
+
let wrong = Object.assign({}, descs, {devices: Object.assign({}, devices_desc, {main_topic: true})});
|
|
220
|
+
expect(fold_main_topic(wrong)).toBe("places");
|
|
221
|
+
});
|
|
222
|
+
|
|
192
223
|
test("a self-referent hook breaks a tie", () => {
|
|
193
224
|
let a = {topic_name: "a", node_treedb_type: "extended",
|
|
194
225
|
cols: [{id: "id", flag: []}, {id: "xs", flag: ["hook"], hook: {x: "a"}}]};
|
|
195
|
-
let b = {topic_name: "b", node_treedb_type: "
|
|
226
|
+
let b = {topic_name: "b", node_treedb_type: "entity",
|
|
196
227
|
cols: [{id: "id", flag: []}, {id: "xs", flag: ["hook"], hook: {x: "b"}},
|
|
197
228
|
{id: "bs", flag: ["hook"], hook: {b: "parent"}},
|
|
198
229
|
{id: "parent", flag: ["fkey"], fkey: {b: "bs"}}]};
|
|
@@ -201,12 +232,16 @@ describe("main topic, hidden topics, loose records", () => {
|
|
|
201
232
|
expect(fold_main_topic({x: x})).toBe("");
|
|
202
233
|
});
|
|
203
234
|
|
|
204
|
-
test("
|
|
205
|
-
let
|
|
235
|
+
test("the reader's pick wins -- if it is a hierarchical topic", () => {
|
|
236
|
+
let d = Object.assign({}, descs, {controllers: controllers_self});
|
|
237
|
+
let m = fold_build_model(d, make_records(3), {main_topic: "controllers"});
|
|
206
238
|
expect(m.main_topic).toBe("controllers");
|
|
207
239
|
expect([...m.linked].sort()).toEqual(["controllers", "devices"]);
|
|
208
240
|
/* places is not linked to controllers, so its roots are roots */
|
|
209
241
|
expect(m.groups.get(fold_root_group_key("places"))).toEqual([K("places", "es")]);
|
|
242
|
+
/* a pick the rule refuses is ignored, and the trunk is deduced */
|
|
243
|
+
let m2 = fold_build_model(descs, make_records(3), {main_topic: "controllers"});
|
|
244
|
+
expect(m2.main_topic).toBe("places");
|
|
210
245
|
});
|
|
211
246
|
|
|
212
247
|
test("a device with no place is LOOSE: counted, not a root, unless asked for", () => {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* yui_json_pad.js
|
|
3
|
+
*
|
|
4
|
+
* JSON viewer -- a blank C_YUI_JSON_PAD in a floating window,
|
|
5
|
+
* to paste JSON from outside and read it with the library's own
|
|
6
|
+
* viewer. Peer of the frontend view (yui_frontend_view.js), and
|
|
7
|
+
* opened the same way: an entry of the account menu.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2026, ArtGins.
|
|
10
|
+
* All Rights Reserved.
|
|
11
|
+
***********************************************************************/
|
|
12
|
+
import {
|
|
13
|
+
createElement2,
|
|
14
|
+
gclass_find_by_name,
|
|
15
|
+
gobj_create_service,
|
|
16
|
+
gobj_create_pure_child,
|
|
17
|
+
gobj_find_service,
|
|
18
|
+
gobj_read_attr,
|
|
19
|
+
gobj_start_tree,
|
|
20
|
+
log_error,
|
|
21
|
+
} from "@yuneta/gobj-js";
|
|
22
|
+
|
|
23
|
+
import {register_c_yui_json_pad} from "./c_yui_json_pad.js";
|
|
24
|
+
|
|
25
|
+
/* Service name of the window. The host toggles the entry by looking
|
|
26
|
+
* it up, exactly as it does with "Frontend-View-Window". */
|
|
27
|
+
const WIN_NAME = "Json-Viewer-Window";
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/******************************
|
|
31
|
+
* Public API
|
|
32
|
+
******************************/
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/************************************************************
|
|
36
|
+
* Open a blank JSON pad (C_YUI_JSON_PAD) inside a non-modal
|
|
37
|
+
* C_YUI_WINDOW, the same shape as the frontend view.
|
|
38
|
+
*
|
|
39
|
+
* Self-contained: it registers C_YUI_JSON_PAD (and the
|
|
40
|
+
* C_YUI_JSON it hosts) if the app did not, so an app gets the
|
|
41
|
+
* viewer with one menu entry and one action.
|
|
42
|
+
*
|
|
43
|
+
* Returns the window gobj, or null.
|
|
44
|
+
************************************************************/
|
|
45
|
+
function setup_json_pad(self)
|
|
46
|
+
{
|
|
47
|
+
if(gobj_find_service(WIN_NAME, false)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if(!gclass_find_by_name("C_YUI_JSON_PAD", false)) {
|
|
51
|
+
register_c_yui_json_pad();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* The pad is created AFTER the window, as a pure child of it, so
|
|
55
|
+
* every teardown path (the ✕, or the host destroying the window to
|
|
56
|
+
* toggle the entry off) takes the pad down with it -- which is why
|
|
57
|
+
* the body is a placeholder: C_YUI_WINDOW builds its UI in
|
|
58
|
+
* mt_create, before the pad exists. */
|
|
59
|
+
let $body = createElement2(
|
|
60
|
+
["div", {class: "YJSONPAD_BODY",
|
|
61
|
+
style: "height:100%; display:flex; flex-direction:column;"}, []]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
let win = gobj_create_service(
|
|
65
|
+
WIN_NAME,
|
|
66
|
+
"C_YUI_WINDOW",
|
|
67
|
+
{
|
|
68
|
+
$parent: (typeof document !== "undefined" &&
|
|
69
|
+
document.getElementById("top-layer")) || null,
|
|
70
|
+
subscriber: null,
|
|
71
|
+
modal: false,
|
|
72
|
+
showMax: true,
|
|
73
|
+
showFooter: false,
|
|
74
|
+
resizable: true,
|
|
75
|
+
center: false,
|
|
76
|
+
auto_save_size_and_position: true,
|
|
77
|
+
width: 900,
|
|
78
|
+
height: 640,
|
|
79
|
+
logical_class: "JSON_VIEWER_WINDOW",
|
|
80
|
+
title: "json viewer",
|
|
81
|
+
icon: "yi-code",
|
|
82
|
+
body: $body,
|
|
83
|
+
/* `|| null`: an undefined attr value logs "attr undefined:
|
|
84
|
+
* manager" in an app without a window manager. */
|
|
85
|
+
manager: gobj_find_service("__window_manager__", false) || null,
|
|
86
|
+
},
|
|
87
|
+
self
|
|
88
|
+
);
|
|
89
|
+
if(!win) {
|
|
90
|
+
log_error("yui_json_pad: cannot create the json-viewer window");
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* The window is mounted by its mt_create, so $body is live DOM here:
|
|
95
|
+
* the viewer's graph measures its canvas when it is first shown. */
|
|
96
|
+
let pad = gobj_create_pure_child("json_pad", "C_YUI_JSON_PAD", {}, win);
|
|
97
|
+
let $pad = pad? gobj_read_attr(pad, "$container") : null;
|
|
98
|
+
if(!$pad) {
|
|
99
|
+
log_error("yui_json_pad: C_YUI_JSON_PAD without $container");
|
|
100
|
+
return win;
|
|
101
|
+
}
|
|
102
|
+
$body.appendChild($pad);
|
|
103
|
+
|
|
104
|
+
gobj_start_tree(win);
|
|
105
|
+
|
|
106
|
+
return win;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {setup_json_pad};
|