@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
package/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export { register_c_yui_map } from "./src/c_yui_map.js";
|
|
|
23
23
|
export { register_c_yui_uplot } from "./src/c_yui_uplot.js";
|
|
24
24
|
export { register_c_yui_json_graph } from "./src/c_yui_json_graph.js";
|
|
25
25
|
export { register_c_yui_json } from "./src/c_yui_json.js";
|
|
26
|
+
export { register_c_yui_json_pad } from "./src/c_yui_json_pad.js";
|
|
26
27
|
export { register_c_yui_gclass } from "./src/c_yui_gclass.js";
|
|
27
28
|
export { register_c_yui_fsm_graph } from "./src/c_yui_fsm_graph.js";
|
|
28
29
|
export { register_c_yui_gobj_tree_js } from "./src/c_yui_gobj_tree_js.js";
|
|
@@ -129,6 +130,7 @@ export { yui_theme_now, yui_is_dark, yui_watch_theme } from "./src/yui_theme.js"
|
|
|
129
130
|
export { attach_clear, refresh_clear } from "./src/yui_inputs.js";
|
|
130
131
|
export { info_traffic, setup_dev, build_dev_panel, apply_dev_traces, dev_window_was_open } from "./src/yui_dev.js";
|
|
131
132
|
export { setup_frontend_view } from "./src/yui_frontend_view.js";
|
|
133
|
+
export { setup_json_pad } from "./src/yui_json_pad.js";
|
|
132
134
|
|
|
133
135
|
/*
|
|
134
136
|
* CSS - import these in your main entry point
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -122,6 +122,7 @@ import {
|
|
|
122
122
|
fold_open_levels,
|
|
123
123
|
fold_close_levels,
|
|
124
124
|
fold_level_clamp,
|
|
125
|
+
fold_is_hierarchical,
|
|
125
126
|
fold_toggle,
|
|
126
127
|
fold_show_more,
|
|
127
128
|
fold_reveal,
|
|
@@ -301,14 +302,14 @@ const PORT_LINE_WIDTH = 2;
|
|
|
301
302
|
const NODE_MODES = ['expanded', 'compact', 'shape'];
|
|
302
303
|
|
|
303
304
|
const PILL_SIZE = {
|
|
304
|
-
|
|
305
|
+
entity: [200, 30],
|
|
305
306
|
extended: [180, 28],
|
|
306
307
|
child: [160, 26],
|
|
307
308
|
};
|
|
308
309
|
const PORT_R_PILL = 6;
|
|
309
310
|
|
|
310
311
|
const SHAPE_SIZE = {
|
|
311
|
-
|
|
312
|
+
entity: 32,
|
|
312
313
|
extended: 28,
|
|
313
314
|
child: 22,
|
|
314
315
|
};
|
|
@@ -436,7 +437,7 @@ SDATA(data_type_t.DTP_STRING, "hook_port_position", 0, "bottom", "Hook po
|
|
|
436
437
|
SDATA(data_type_t.DTP_STRING, "fkey_port_position", 0, "top", "Fkey port position"),
|
|
437
438
|
|
|
438
439
|
/*---------------- Folding ----------------*/
|
|
439
|
-
SDATA(data_type_t.DTP_INTEGER, "expand_depth", 0,
|
|
440
|
+
SDATA(data_type_t.DTP_INTEGER, "expand_depth", 0, 1, "Levels open when a treedb loads, until the reader steps (`fold_level`): 1 = the roots alone (the default), 2 = the roots and their children. Every open hook shows its first page"),
|
|
440
441
|
SDATA(data_type_t.DTP_INTEGER, "fold_level", 0, 0, "The level of the MAIN tree the toolbar's stepper stands on: 1 = its roots, N = N-1 levels under them; 0 = `expand_depth`. The host persists it and sends EV_SET_FOLD_LEVEL; it is published back in EV_LEGEND_STATE (`fold`) with the levels the tree has"),
|
|
441
442
|
SDATA(data_type_t.DTP_INTEGER, "fold_page_size", 0, 24, "Children of one hook shown per page; the `+N` chip after the page opens the next one"),
|
|
442
443
|
SDATA(data_type_t.DTP_LIST, "hidden_topics", 0, "[]", "Topics left out of the tree: no card, no pill counts them, no edge reaches them"),
|
|
@@ -545,7 +546,7 @@ let PRIVATE_DATA = {
|
|
|
545
546
|
_nodes_placed: 0, // ...of which carry a saved position
|
|
546
547
|
|
|
547
548
|
/*---------------- folding ----------------*/
|
|
548
|
-
expand_depth:
|
|
549
|
+
expand_depth: 1,
|
|
549
550
|
fold_level: 0,
|
|
550
551
|
fold_page_size: 24,
|
|
551
552
|
hidden_topics: null,
|
|
@@ -1859,7 +1860,12 @@ function configure_behaviour(gobj)
|
|
|
1859
1860
|
}
|
|
1860
1861
|
|
|
1861
1862
|
/************************************************************
|
|
1862
|
-
* Count hooks and fkeys in topic desc, classify node type
|
|
1863
|
+
* Count hooks and fkeys in topic desc, classify node type:
|
|
1864
|
+
* `child` (no hooks), `extended` (hooks, no fkeys), `entity`
|
|
1865
|
+
* (both). It sizes the card and nothing else. It used to be
|
|
1866
|
+
* called `hierarchical`, and that word means something else now:
|
|
1867
|
+
* a topic hooked to ITSELF, the only kind a tree can hang from
|
|
1868
|
+
* (treedb_fold_model).
|
|
1863
1869
|
************************************************************/
|
|
1864
1870
|
function calculate_hooks_fkeys_counter(desc)
|
|
1865
1871
|
{
|
|
@@ -1885,7 +1891,7 @@ function calculate_hooks_fkeys_counter(desc)
|
|
|
1885
1891
|
} else if(desc.fkeys_counter === 0) {
|
|
1886
1892
|
desc.node_treedb_type = 'extended';
|
|
1887
1893
|
} else {
|
|
1888
|
-
desc.node_treedb_type = '
|
|
1894
|
+
desc.node_treedb_type = 'entity';
|
|
1889
1895
|
}
|
|
1890
1896
|
}
|
|
1891
1897
|
|
|
@@ -2306,7 +2312,7 @@ function card_shape_of(gobj, desc, record, geometry, pills_html)
|
|
|
2306
2312
|
desc.topic_name, true, record.id, false, false, false, pills_html
|
|
2307
2313
|
);
|
|
2308
2314
|
} else {
|
|
2309
|
-
//
|
|
2315
|
+
// Entity, hooks and fkeys (ROOT / container): largest tier.
|
|
2310
2316
|
style.innerHTML = build_node_innerHTML(
|
|
2311
2317
|
paint.fill, priv.theme, record.icon, label,
|
|
2312
2318
|
desc.topic_name, false, record.id, false, false, false, pills_html
|
|
@@ -2409,7 +2415,7 @@ function pill_shape_of(gobj, desc, record, geometry)
|
|
|
2409
2415
|
{
|
|
2410
2416
|
let priv = gobj.priv;
|
|
2411
2417
|
let tier = desc.node_treedb_type;
|
|
2412
|
-
let size = PILL_SIZE[tier] || PILL_SIZE.
|
|
2418
|
+
let size = PILL_SIZE[tier] || PILL_SIZE.entity;
|
|
2413
2419
|
let ports = build_ports(gobj, desc);
|
|
2414
2420
|
let paint = node_paint_of(gobj, desc, geometry);
|
|
2415
2421
|
|
|
@@ -2472,7 +2478,7 @@ function figure_shape_of(gobj, desc, record, geometry, flags)
|
|
|
2472
2478
|
let dark = (priv.theme === "dark");
|
|
2473
2479
|
let f = flags || {};
|
|
2474
2480
|
let tier = desc.node_treedb_type;
|
|
2475
|
-
let side = SHAPE_SIZE[tier] || SHAPE_SIZE.
|
|
2481
|
+
let side = SHAPE_SIZE[tier] || SHAPE_SIZE.entity;
|
|
2476
2482
|
let labels = gobj_read_bool_attr(gobj, "node_labels");
|
|
2477
2483
|
let figure = node_figure_of(gobj, desc, geometry || {});
|
|
2478
2484
|
let paint = node_paint_of(gobj, desc, geometry);
|
|
@@ -7042,7 +7048,7 @@ function show_node_detail_popover(gobj, node_id)
|
|
|
7042
7048
|
}
|
|
7043
7049
|
|
|
7044
7050
|
/************************************************************
|
|
7045
|
-
* Re-render every HTML (
|
|
7051
|
+
* Re-render every HTML (entity) node so its card adopts
|
|
7046
7052
|
* the given theme: entity cards and leaf chips (baked innerHTML,
|
|
7047
7053
|
* not re-themed by setTheme()) are regenerated; structural
|
|
7048
7054
|
* junction diamonds get their neutral fill/stroke re-themed.
|
|
@@ -7258,7 +7264,7 @@ function refresh_minimap(gobj)
|
|
|
7258
7264
|
|
|
7259
7265
|
/************************************************************
|
|
7260
7266
|
* The innerHTML of ONE node, in the given theme and highlight state.
|
|
7261
|
-
* The three treedb tiers (
|
|
7267
|
+
* The three treedb tiers (entity / extended / child) each have
|
|
7262
7268
|
* their own card, and both the theme refresh and the highlight need the
|
|
7263
7269
|
* same three-way choice — it lived inline in the theme refresh and is
|
|
7264
7270
|
* shared now. Returns null for a node that carries no desc.
|
|
@@ -7295,7 +7301,7 @@ function node_innerHTML_of(nd, theme, highlight, selected, anchored, pills_html)
|
|
|
7295
7301
|
desc.topic_name, true, record.id, highlight, selected, anchored,
|
|
7296
7302
|
pills_html
|
|
7297
7303
|
);
|
|
7298
|
-
case '
|
|
7304
|
+
case 'entity':
|
|
7299
7305
|
return build_node_innerHTML(
|
|
7300
7306
|
fill, theme, record.icon, label,
|
|
7301
7307
|
desc.topic_name, false, record.id, highlight, selected, anchored,
|
|
@@ -7569,7 +7575,7 @@ function build_chip_innerHTML(color, theme, icon, label, key, highlight, selecte
|
|
|
7569
7575
|
}
|
|
7570
7576
|
|
|
7571
7577
|
/************************************************************
|
|
7572
|
-
* Build innerHTML for
|
|
7578
|
+
* Build innerHTML for entity (HTML) nodes.
|
|
7573
7579
|
*
|
|
7574
7580
|
* Card style modelled on the documentation schema graphs
|
|
7575
7581
|
* (docs gen_treedb_graphs.py): rounded card, soft tint of the
|
|
@@ -9538,12 +9544,16 @@ function publish_legend_state(gobj, visible)
|
|
|
9538
9544
|
loose_shown: model.loose_shown.has(topic_name),
|
|
9539
9545
|
hidden: model.hidden.has(topic_name),
|
|
9540
9546
|
linked: model.linked.has(topic_name),
|
|
9547
|
+
hierarchical: fold_is_hierarchical(priv.descs, topic_name),
|
|
9541
9548
|
});
|
|
9542
9549
|
}
|
|
9543
9550
|
|
|
9544
9551
|
gobj_publish_event(gobj, "EV_LEGEND_STATE", {
|
|
9545
9552
|
main_topic: model.main_topic,
|
|
9546
|
-
|
|
9553
|
+
/* CHOSEN only when the reader's pick is the one in force: a
|
|
9554
|
+
* pick the rule refuses (not hierarchical) is ignored. */
|
|
9555
|
+
main_chosen: !!model.main_topic &&
|
|
9556
|
+
gobj_read_str_attr(gobj, "main_topic") === model.main_topic,
|
|
9547
9557
|
topics: topics,
|
|
9548
9558
|
/* The toolbar's stepper: the tree it walks, where it stands,
|
|
9549
9559
|
* and how deep it goes. `levels` 0 = nothing to step through. */
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* c_yui_json_pad.js
|
|
3
|
+
*
|
|
4
|
+
* C_YUI_JSON_PAD -- a pad to paste JSON from OUTSIDE and read it
|
|
5
|
+
* with the library's own viewer.
|
|
6
|
+
*
|
|
7
|
+
* The viewer (C_YUI_JSON: the lazy tree, the raw text and the
|
|
8
|
+
* graph) only ever showed what an app fetched. A JSON that came
|
|
9
|
+
* from anywhere else -- a log line, a config file, an answer copied
|
|
10
|
+
* from a terminal -- had to be read in whatever the operator had
|
|
11
|
+
* at hand. This is the blank page for it: paste, and read it the
|
|
12
|
+
* way every other document of the app is read.
|
|
13
|
+
*
|
|
14
|
+
* DOM (UPPER_SNAKE logical classes):
|
|
15
|
+
*
|
|
16
|
+
* JSON_PAD the root
|
|
17
|
+
* JSON_PAD_BAR the text area and its two buttons
|
|
18
|
+
* JSON_PAD_INPUT where the text goes
|
|
19
|
+
* JSON_PAD_VIEW read what is in the text area
|
|
20
|
+
* JSON_PAD_CLEAR empty the pad
|
|
21
|
+
* JSON_PAD_ERROR "invalid JSON" and the parser's reason
|
|
22
|
+
* JSON_PAD_BODY the C_YUI_JSON child
|
|
23
|
+
*
|
|
24
|
+
* Every way in is an event:
|
|
25
|
+
*
|
|
26
|
+
* EV_PASTE {text} a paste ANYWHERE in the pad replaces the
|
|
27
|
+
* document and shows it at once -- there
|
|
28
|
+
* is one document here, not a text to edit
|
|
29
|
+
* EV_VIEW the text area as it is (typed, or edited
|
|
30
|
+
* after a paste); also Ctrl+Enter
|
|
31
|
+
* EV_CLEAR back to the blank pad
|
|
32
|
+
*
|
|
33
|
+
* FSM: ST_EMPTY (nothing shown) and ST_SHOWING. EV_EXPAND_PATH,
|
|
34
|
+
* which the viewer publishes when a `__collapsed__` sentinel is
|
|
35
|
+
* opened, only exists in ST_SHOWING, and is answered with
|
|
36
|
+
* EV_SUBTREE_ERROR: a pasted document has no backend to fetch the
|
|
37
|
+
* rest from -- the source truncated it, and the pad says so.
|
|
38
|
+
*
|
|
39
|
+
* Hosted by setup_json_pad() (yui_json_pad.js) in a C_YUI_WINDOW;
|
|
40
|
+
* any host can mount `$container` itself.
|
|
41
|
+
*
|
|
42
|
+
* Copyright (c) 2026, ArtGins.
|
|
43
|
+
* All Rights Reserved.
|
|
44
|
+
***********************************************************************/
|
|
45
|
+
import {
|
|
46
|
+
SDATA,
|
|
47
|
+
SDATA_END,
|
|
48
|
+
data_type_t,
|
|
49
|
+
gclass_create,
|
|
50
|
+
gclass_find_by_name,
|
|
51
|
+
log_error,
|
|
52
|
+
log_warning,
|
|
53
|
+
gobj_create_pure_child,
|
|
54
|
+
gobj_read_pointer_attr,
|
|
55
|
+
gobj_read_attr,
|
|
56
|
+
gobj_write_attr,
|
|
57
|
+
gobj_subscribe_event,
|
|
58
|
+
gobj_send_event,
|
|
59
|
+
gobj_change_state,
|
|
60
|
+
gobj_start,
|
|
61
|
+
gobj_stop,
|
|
62
|
+
gobj_is_running,
|
|
63
|
+
gobj_short_name,
|
|
64
|
+
gobj_parent,
|
|
65
|
+
createElement2,
|
|
66
|
+
} from "@yuneta/gobj-js";
|
|
67
|
+
|
|
68
|
+
import {register_c_yui_json} from "./c_yui_json.js";
|
|
69
|
+
import {yui_toolbar_icon} from "./yui_toolbar.js";
|
|
70
|
+
|
|
71
|
+
import {t} from "i18next";
|
|
72
|
+
|
|
73
|
+
/***************************************************************
|
|
74
|
+
* Constants
|
|
75
|
+
***************************************************************/
|
|
76
|
+
const GCLASS_NAME = "C_YUI_JSON_PAD";
|
|
77
|
+
|
|
78
|
+
/***************************************************************
|
|
79
|
+
* Data
|
|
80
|
+
***************************************************************/
|
|
81
|
+
const attrs_table = [
|
|
82
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
83
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "HTMLElement root, mounted by the host"),
|
|
84
|
+
SDATA_END()
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
let PRIVATE_DATA = {
|
|
88
|
+
$input: null, /* the text area */
|
|
89
|
+
$error: null, /* the "invalid JSON" line */
|
|
90
|
+
$reason: null, /* ...and the parser's reason inside it */
|
|
91
|
+
gobj_json: null, /* the C_YUI_JSON child */
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let __gclass__ = null;
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/******************************
|
|
100
|
+
* Framework Methods
|
|
101
|
+
******************************/
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
/***************************************************************
|
|
107
|
+
* Framework Method: Create
|
|
108
|
+
***************************************************************/
|
|
109
|
+
function mt_create(gobj)
|
|
110
|
+
{
|
|
111
|
+
let priv = gobj.priv;
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* CHILD subscription model
|
|
115
|
+
*/
|
|
116
|
+
let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
117
|
+
if(!subscriber) {
|
|
118
|
+
subscriber = gobj_parent(gobj);
|
|
119
|
+
}
|
|
120
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
121
|
+
|
|
122
|
+
build_ui(gobj);
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
* The viewer, as a pure child: it publishes EV_EXPAND_PATH to its
|
|
126
|
+
* parent (this gobj), which declares it in ST_SHOWING.
|
|
127
|
+
*/
|
|
128
|
+
priv.gobj_json = gobj_create_pure_child("json_pad_view", "C_YUI_JSON", {}, gobj);
|
|
129
|
+
let $view = gobj_read_attr(priv.gobj_json, "$container");
|
|
130
|
+
if(!$view) {
|
|
131
|
+
log_error(`${gobj_short_name(gobj)}: C_YUI_JSON without $container`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
gobj_read_attr(gobj, "$container").querySelector(".JSON_PAD_BODY").appendChild($view);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/***************************************************************
|
|
138
|
+
* Framework Method: Start
|
|
139
|
+
***************************************************************/
|
|
140
|
+
function mt_start(gobj)
|
|
141
|
+
{
|
|
142
|
+
let priv = gobj.priv;
|
|
143
|
+
|
|
144
|
+
if(priv.gobj_json) {
|
|
145
|
+
gobj_start(priv.gobj_json);
|
|
146
|
+
}
|
|
147
|
+
/* A blank pad is waiting for a paste: the caret goes where it lands. */
|
|
148
|
+
if(priv.$input) {
|
|
149
|
+
priv.$input.focus();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/***************************************************************
|
|
154
|
+
* Framework Method: Stop
|
|
155
|
+
***************************************************************/
|
|
156
|
+
function mt_stop(gobj)
|
|
157
|
+
{
|
|
158
|
+
let priv = gobj.priv;
|
|
159
|
+
|
|
160
|
+
if(priv.gobj_json && gobj_is_running(priv.gobj_json)) {
|
|
161
|
+
gobj_stop(priv.gobj_json);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/***************************************************************
|
|
166
|
+
* Framework Method: Destroy
|
|
167
|
+
***************************************************************/
|
|
168
|
+
function mt_destroy(gobj)
|
|
169
|
+
{
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
/***************************
|
|
176
|
+
* Local Methods
|
|
177
|
+
***************************/
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
/***************************************************************
|
|
183
|
+
* The pad: the text area and its buttons, the error line, and
|
|
184
|
+
* the body the viewer is mounted in.
|
|
185
|
+
***************************************************************/
|
|
186
|
+
function build_ui(gobj)
|
|
187
|
+
{
|
|
188
|
+
let priv = gobj.priv;
|
|
189
|
+
|
|
190
|
+
let button = (cls, icon, key, event_name) => {
|
|
191
|
+
return ['button', {class: `${cls} button`, type: 'button',
|
|
192
|
+
title: t(key), 'data-i18n-title': key,
|
|
193
|
+
'aria-label': t(key), 'data-i18n-aria-label': key},
|
|
194
|
+
[
|
|
195
|
+
yui_toolbar_icon(icon),
|
|
196
|
+
['span', {style: 'padding-left:5px;', i18n: key}, key]
|
|
197
|
+
],
|
|
198
|
+
{
|
|
199
|
+
click: (evt) => {
|
|
200
|
+
evt.stopPropagation();
|
|
201
|
+
gobj_send_event(gobj, event_name, {}, gobj);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
priv.$input = createElement2(
|
|
208
|
+
['textarea', {class: 'JSON_PAD_INPUT textarea', rows: 3, spellcheck: 'false',
|
|
209
|
+
placeholder: t('paste json here'), 'data-i18n-placeholder': 'paste json here',
|
|
210
|
+
title: t('paste json here'), 'data-i18n-title': 'paste json here',
|
|
211
|
+
'aria-label': t('paste json here'), 'data-i18n-aria-label': 'paste json here',
|
|
212
|
+
style: 'font-family:monospace; resize:vertical; min-height:4.5em;'}, '', {
|
|
213
|
+
keydown: (evt) => {
|
|
214
|
+
if((evt.ctrlKey || evt.metaKey) && evt.key === 'Enter') {
|
|
215
|
+
evt.preventDefault();
|
|
216
|
+
gobj_send_event(gobj, "EV_VIEW", {}, gobj);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}]
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
priv.$reason = createElement2(
|
|
223
|
+
['span', {class: 'JSON_PAD_ERROR_REASON', style: 'margin-left:.5em; font-family:monospace;'}, '']
|
|
224
|
+
);
|
|
225
|
+
priv.$error = createElement2(
|
|
226
|
+
['div', {class: 'JSON_PAD_ERROR is-hidden has-text-danger is-size-7', role: 'alert'}, [
|
|
227
|
+
['span', {class: 'JSON_PAD_ERROR_LABEL', style: 'font-weight:600;', i18n: 'invalid json'},
|
|
228
|
+
'invalid json'],
|
|
229
|
+
priv.$reason
|
|
230
|
+
]]
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
let $container = createElement2(
|
|
234
|
+
['div', {class: 'C_YUI_JSON_PAD JSON_PAD',
|
|
235
|
+
style: 'height:100%; display:flex; flex-direction:column; gap:.5rem; padding:.5rem;'}, [
|
|
236
|
+
['div', {class: 'JSON_PAD_BAR',
|
|
237
|
+
style: 'display:flex; gap:.5rem; align-items:flex-start;'}, [
|
|
238
|
+
['div', {style: 'flex:1 1 auto; min-width:0;'}, [priv.$input]],
|
|
239
|
+
['div', {style: 'flex:0 0 auto; display:flex; flex-direction:column; gap:.5rem;'}, [
|
|
240
|
+
button('JSON_PAD_VIEW', 'yi-eye', 'view', 'EV_VIEW'),
|
|
241
|
+
button('JSON_PAD_CLEAR', 'yi-broom', 'clear', 'EV_CLEAR'),
|
|
242
|
+
]]
|
|
243
|
+
]],
|
|
244
|
+
priv.$error,
|
|
245
|
+
['div', {class: 'JSON_PAD_BODY',
|
|
246
|
+
style: 'flex:1 1 auto; min-height:0; display:flex; flex-direction:column;'}, []]
|
|
247
|
+
], {
|
|
248
|
+
/* A paste anywhere in the pad is the document: it replaces
|
|
249
|
+
* what the text area held instead of landing at the caret. */
|
|
250
|
+
paste: (evt) => {
|
|
251
|
+
let text = evt.clipboardData? evt.clipboardData.getData("text") : "";
|
|
252
|
+
if(!text) {
|
|
253
|
+
return; /* an image or nothing: the browser's business */
|
|
254
|
+
}
|
|
255
|
+
evt.preventDefault();
|
|
256
|
+
gobj_send_event(gobj, "EV_PASTE", {text: text}, gobj);
|
|
257
|
+
}
|
|
258
|
+
}]
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
gobj_write_attr(gobj, "$container", $container);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/***************************************************************
|
|
265
|
+
* Read `text` as JSON and show it, or say why it is not.
|
|
266
|
+
***************************************************************/
|
|
267
|
+
function show_text(gobj, text)
|
|
268
|
+
{
|
|
269
|
+
let priv = gobj.priv;
|
|
270
|
+
let src = String(text || "").trim();
|
|
271
|
+
|
|
272
|
+
if(!src) {
|
|
273
|
+
clear_pad(gobj, false);
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let doc;
|
|
278
|
+
try {
|
|
279
|
+
doc = JSON.parse(src);
|
|
280
|
+
} catch(e) {
|
|
281
|
+
/* The reader's text, not a fault of the app: said on the pad, and
|
|
282
|
+
* a warning for whoever reads the console. */
|
|
283
|
+
log_warning(`${gobj_short_name(gobj)}: the pasted text is not JSON: ${e.message}`);
|
|
284
|
+
priv.$reason.textContent = e.message;
|
|
285
|
+
priv.$error.classList.remove('is-hidden');
|
|
286
|
+
return -1;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
priv.$error.classList.add('is-hidden');
|
|
290
|
+
priv.$reason.textContent = "";
|
|
291
|
+
gobj_send_event(priv.gobj_json, "EV_SET_JSON", {json: doc}, gobj);
|
|
292
|
+
gobj_change_state(gobj, "ST_SHOWING");
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/***************************************************************
|
|
297
|
+
* Back to the blank pad; `with_text` empties the text area too.
|
|
298
|
+
***************************************************************/
|
|
299
|
+
function clear_pad(gobj, with_text)
|
|
300
|
+
{
|
|
301
|
+
let priv = gobj.priv;
|
|
302
|
+
|
|
303
|
+
if(with_text) {
|
|
304
|
+
priv.$input.value = "";
|
|
305
|
+
}
|
|
306
|
+
priv.$error.classList.add('is-hidden');
|
|
307
|
+
priv.$reason.textContent = "";
|
|
308
|
+
gobj_send_event(priv.gobj_json, "EV_SET_JSON", {json: null}, gobj);
|
|
309
|
+
gobj_change_state(gobj, "ST_EMPTY");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
/***************************
|
|
316
|
+
* Actions
|
|
317
|
+
***************************/
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
/***************************************************************
|
|
323
|
+
* A paste: it becomes the text area and the document at once.
|
|
324
|
+
***************************************************************/
|
|
325
|
+
function ac_paste(gobj, event, kw, src)
|
|
326
|
+
{
|
|
327
|
+
let priv = gobj.priv;
|
|
328
|
+
let text = (kw && typeof kw.text === "string")? kw.text : "";
|
|
329
|
+
|
|
330
|
+
priv.$input.value = text;
|
|
331
|
+
return show_text(gobj, text);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/***************************************************************
|
|
335
|
+
* The text area as it is now.
|
|
336
|
+
***************************************************************/
|
|
337
|
+
function ac_view(gobj, event, kw, src)
|
|
338
|
+
{
|
|
339
|
+
let priv = gobj.priv;
|
|
340
|
+
return show_text(gobj, priv.$input.value);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/***************************************************************
|
|
344
|
+
* The blank pad again.
|
|
345
|
+
***************************************************************/
|
|
346
|
+
function ac_clear(gobj, event, kw, src)
|
|
347
|
+
{
|
|
348
|
+
let priv = gobj.priv;
|
|
349
|
+
|
|
350
|
+
clear_pad(gobj, true);
|
|
351
|
+
priv.$input.focus();
|
|
352
|
+
return 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/***************************************************************
|
|
356
|
+
* The viewer asks for a `__collapsed__` subtree. A pasted
|
|
357
|
+
* document has no backend to ask for it: the SOURCE truncated
|
|
358
|
+
* it, and that is the answer.
|
|
359
|
+
***************************************************************/
|
|
360
|
+
function ac_expand_path(gobj, event, kw, src)
|
|
361
|
+
{
|
|
362
|
+
let priv = gobj.priv;
|
|
363
|
+
|
|
364
|
+
gobj_send_event(priv.gobj_json, "EV_SUBTREE_ERROR", {
|
|
365
|
+
path: (kw && kw.path) || "",
|
|
366
|
+
error: t("collapsed in the source")
|
|
367
|
+
}, gobj);
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/***************************************************************
|
|
372
|
+
* FSM
|
|
373
|
+
***************************************************************/
|
|
374
|
+
/*---------------------------------------------*
|
|
375
|
+
* Global methods table
|
|
376
|
+
*---------------------------------------------*/
|
|
377
|
+
const gmt = {
|
|
378
|
+
mt_create: mt_create,
|
|
379
|
+
mt_start: mt_start,
|
|
380
|
+
mt_stop: mt_stop,
|
|
381
|
+
mt_destroy: mt_destroy
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/***************************************************************
|
|
385
|
+
* Create the GClass
|
|
386
|
+
***************************************************************/
|
|
387
|
+
function create_gclass(gclass_name)
|
|
388
|
+
{
|
|
389
|
+
if(__gclass__) {
|
|
390
|
+
log_error(`GClass ALREADY created: ${gclass_name}`);
|
|
391
|
+
return -1;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/*---------------------------------------------*
|
|
395
|
+
* States
|
|
396
|
+
*---------------------------------------------*/
|
|
397
|
+
const states = [
|
|
398
|
+
["ST_EMPTY", [
|
|
399
|
+
["EV_PASTE", ac_paste, null],
|
|
400
|
+
["EV_VIEW", ac_view, null],
|
|
401
|
+
["EV_CLEAR", ac_clear, null],
|
|
402
|
+
]],
|
|
403
|
+
["ST_SHOWING", [
|
|
404
|
+
["EV_PASTE", ac_paste, null],
|
|
405
|
+
["EV_VIEW", ac_view, null],
|
|
406
|
+
["EV_CLEAR", ac_clear, null],
|
|
407
|
+
["EV_EXPAND_PATH", ac_expand_path, null],
|
|
408
|
+
]]
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
/*---------------------------------------------*
|
|
412
|
+
* Events
|
|
413
|
+
*---------------------------------------------*/
|
|
414
|
+
const event_types = [
|
|
415
|
+
["EV_PASTE", 0],
|
|
416
|
+
["EV_VIEW", 0],
|
|
417
|
+
["EV_CLEAR", 0],
|
|
418
|
+
["EV_EXPAND_PATH", 0],
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
__gclass__ = gclass_create(
|
|
422
|
+
gclass_name,
|
|
423
|
+
event_types,
|
|
424
|
+
states,
|
|
425
|
+
gmt,
|
|
426
|
+
0, // lmt,
|
|
427
|
+
attrs_table,
|
|
428
|
+
PRIVATE_DATA,
|
|
429
|
+
0, // authz_table,
|
|
430
|
+
0, // command_table,
|
|
431
|
+
0, // s_user_trace_level
|
|
432
|
+
0 // gclass_flag
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
if(!__gclass__) {
|
|
436
|
+
return -1;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/***************************************************************
|
|
443
|
+
* Register GClass
|
|
444
|
+
***************************************************************/
|
|
445
|
+
function register_c_yui_json_pad()
|
|
446
|
+
{
|
|
447
|
+
/* the viewer it hosts, if the app did not register it */
|
|
448
|
+
if(!gclass_find_by_name("C_YUI_JSON", false)) {
|
|
449
|
+
register_c_yui_json();
|
|
450
|
+
}
|
|
451
|
+
return create_gclass(GCLASS_NAME);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export { register_c_yui_json_pad };
|
|
@@ -1061,6 +1061,13 @@ function render_topics(gobj, $body)
|
|
|
1061
1061
|
? ["span", {class: "SCHEMA_TOPIC_SYSTEM tag ml-2",
|
|
1062
1062
|
i18n: "system"}, t("system")]
|
|
1063
1063
|
: ["span", {}, ""],
|
|
1064
|
+
/* The schema's mark of the topic the tree hangs from: the
|
|
1065
|
+
* same gold star the treedb graph's legend wears. */
|
|
1066
|
+
topic.main_topic
|
|
1067
|
+
? ["span", {class: "SCHEMA_TOPIC_MAIN ml-2",
|
|
1068
|
+
style: "color:#d4a017;",
|
|
1069
|
+
title: t("main topic"), "data-i18n-title": "main topic"}, "★"]
|
|
1070
|
+
: ["span", {}, ""],
|
|
1064
1071
|
ambiguous
|
|
1065
1072
|
? ["div", {class: "SCHEMA_TOPIC_ID is-size-7 yui-text-quiet"},
|
|
1066
1073
|
[["code", {}, `${topic.id}`]]]
|
|
@@ -1898,6 +1905,14 @@ function open_topic_form(gobj, treedb, topic)
|
|
|
1898
1905
|
["span", {class: "ml-1", i18n: "system topic"}, t("system topic")]
|
|
1899
1906
|
]]
|
|
1900
1907
|
]],
|
|
1908
|
+
/* A LABEL that wraps its control names it: no aria-label. */
|
|
1909
|
+
["div", {class: "SCHEMA_TOPIC_FORM_MAIN field mb-3"}, [
|
|
1910
|
+
["label", {class: "checkbox"}, [
|
|
1911
|
+
["input", el_attrs({type: "checkbox", "data-name": "main_topic",
|
|
1912
|
+
checked: record.main_topic ? "checked" : undefined})],
|
|
1913
|
+
["span", {class: "ml-1", i18n: "main topic"}, t("main topic")]
|
|
1914
|
+
]]
|
|
1915
|
+
]],
|
|
1901
1916
|
["div", {class: "SCHEMA_TOPIC_FORM_ACTIONS is-flex mt-4",
|
|
1902
1917
|
style: "gap:.5rem; justify-content:flex-end;"}, [
|
|
1903
1918
|
["button", {class: "SCHEMA_TOPIC_FORM_CANCEL button", type: "button"},
|
|
@@ -2501,6 +2516,7 @@ function topic_record(gobj, treedb, topic, values)
|
|
|
2501
2516
|
record.tkey = values.tkey || "";
|
|
2502
2517
|
record.topic_version = values.topic_version === "" ? 1 : Number(values.topic_version);
|
|
2503
2518
|
record.system_topic = !!values.system_topic;
|
|
2519
|
+
record.main_topic = !!values.main_topic;
|
|
2504
2520
|
|
|
2505
2521
|
return record;
|
|
2506
2522
|
}
|