@yuneta/gobj-ui 3.0.0 → 4.0.0
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 +324 -6
- package/dist/gobj-ui.cjs.js +10891 -5006
- package/dist/gobj-ui.es.js +10915 -5064
- package/index.js +45 -1
- package/package.json +4 -4
- package/src/c_g6_nodes_tree.js +128 -40
- package/src/c_yui_form.js +112 -53
- package/src/c_yui_gobj_tree_js.js +58 -36
- package/src/c_yui_json.css +139 -0
- package/src/c_yui_json.js +928 -0
- package/src/c_yui_json_graph.js +110 -20
- package/src/c_yui_map.js +18 -36
- package/src/c_yui_nav.js +6 -9
- package/src/c_yui_period.css +184 -0
- package/src/c_yui_period.js +1441 -0
- package/src/c_yui_shell.css +33 -0
- package/src/c_yui_shell.js +672 -111
- package/src/c_yui_treedb_graph.js +639 -37
- package/src/c_yui_treedb_schema.js +478 -0
- package/src/c_yui_treedb_topic_with_form.css +7 -42
- package/src/c_yui_treedb_topic_with_form.js +152 -103
- package/src/c_yui_treedb_topics.css +73 -0
- package/src/c_yui_treedb_topics.js +1070 -29
- package/src/c_yui_window.js +180 -97
- package/src/c_yui_window_manager.js +38 -4
- package/src/json_view_helpers.js +214 -0
- package/src/json_view_helpers.test.js +135 -0
- package/src/route_map_model.js +317 -0
- package/src/route_map_model.test.js +209 -0
- package/src/route_resolver.js +24 -1
- package/src/route_resolver.test.js +40 -1
- package/src/shell_modals.js +117 -39
- package/src/shell_route_map.css +225 -0
- package/src/shell_route_map.js +363 -0
- package/src/tabulator.css +67 -0
- package/src/yui_dev.js +130 -8
- package/src/yui_frontend_view.js +106 -0
- package/src/yui_icons.css +62 -0
- package/src/yui_inputs.css +8 -3
- package/src/yui_inputs.js +51 -8
- package/src/yui_tabulator_i18n.js +128 -0
- package/src/yui_theme.js +147 -0
- package/src/yui_time.js +666 -0
- package/src/yui_time.test.js +330 -0
- package/src/yui_toolbar.js +86 -43
package/src/c_yui_form.js
CHANGED
|
@@ -56,6 +56,11 @@ import TomSelect from "tom-select"; // Import Tom-Select JS
|
|
|
56
56
|
import { createJSONEditor } from 'vanilla-jsoneditor';
|
|
57
57
|
import "vanilla-jsoneditor/themes/jse-theme-dark.css";
|
|
58
58
|
import "./c_yui_form.css";
|
|
59
|
+
import {attach_clear, refresh_clear} from "./yui_inputs.js";
|
|
60
|
+
/* Read at field-build time only — deliberately NOT watched: the hosting
|
|
61
|
+
* dialog rebuilds the form on every open, and re-rendering a form under
|
|
62
|
+
* the user mid-edit would throw away what they typed. */
|
|
63
|
+
import {yui_is_dark} from "./yui_theme.js";
|
|
59
64
|
import "./tabulator.css";
|
|
60
65
|
|
|
61
66
|
import "tabulator-tables/dist/css/tabulator.min.css"; // Import Tabulator CSS
|
|
@@ -83,6 +88,7 @@ SDATA(data_type_t.DTP_JSON, "record", 0, "{}", "Data to form"),
|
|
|
83
88
|
SDATA(data_type_t.DTP_JSON, "placeholders", 0, "{}", "Placeholder values for form fields"),
|
|
84
89
|
SDATA(data_type_t.DTP_JSON, "fkey_options", 0, "{}", "Options for fkey fields: {topic_name: [ids or {id} records]}, read at build time"),
|
|
85
90
|
SDATA(data_type_t.DTP_STRING, "form_mode", 0, "", "'create' (pkey editable+required) or 'update' (pkey readonly); empty = no pkey handling"),
|
|
91
|
+
SDATA(data_type_t.DTP_STRING, "render_mode", 0, "exec", "'exec' interprets structured cols (template->sub-form, table->grid, coordinates->picker); 'edit' shows them as raw JSON editors (record-editing, e.g. the treedb topic form)"),
|
|
86
92
|
SDATA(data_type_t.DTP_STRING, "pkey", 0, "id", "Primary key field name, used by form_mode"),
|
|
87
93
|
SDATA(data_type_t.DTP_STRING, "topic_name", 0, "", "Treedb topic name: labels use the i18n cascade '<topic>.<col>' -> '<col>' -> header (like table headers)"),
|
|
88
94
|
SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Default is editable, set false to readonly"),
|
|
@@ -529,6 +535,12 @@ function build_form_field_conf(gobj, field_desc)
|
|
|
529
535
|
default_value: field_desc.default_value,
|
|
530
536
|
};
|
|
531
537
|
|
|
538
|
+
/* 'edit' mode edits the raw record: the structured cols
|
|
539
|
+
* (template / table / coordinates) are shown as raw JSON editors,
|
|
540
|
+
* like the pre-merge treedb topic form. 'exec' (default) interprets
|
|
541
|
+
* them into sub-widgets (sub-form / grid / coordinates picker). */
|
|
542
|
+
let raw_structured = gobj_read_str_attr(gobj, "render_mode") === "edit";
|
|
543
|
+
|
|
532
544
|
switch(field_desc.type) {
|
|
533
545
|
case "object":
|
|
534
546
|
case "dict":
|
|
@@ -538,13 +550,21 @@ function build_form_field_conf(gobj, field_desc)
|
|
|
538
550
|
break;
|
|
539
551
|
|
|
540
552
|
case "template":
|
|
541
|
-
|
|
542
|
-
|
|
553
|
+
if(raw_structured) {
|
|
554
|
+
field_conf.tag = "jsoneditor";
|
|
555
|
+
} else {
|
|
556
|
+
field_conf.tag = "fieldset";
|
|
557
|
+
field_conf.options = field_desc.enum_list;
|
|
558
|
+
}
|
|
543
559
|
break;
|
|
544
560
|
|
|
545
561
|
case "table":
|
|
546
|
-
|
|
547
|
-
|
|
562
|
+
if(raw_structured) {
|
|
563
|
+
field_conf.tag = "jsoneditor";
|
|
564
|
+
} else {
|
|
565
|
+
field_conf.tag = "table";
|
|
566
|
+
field_conf.options = field_desc.enum_list;
|
|
567
|
+
}
|
|
548
568
|
break;
|
|
549
569
|
|
|
550
570
|
case "blob":
|
|
@@ -684,7 +704,7 @@ function build_form_field_conf(gobj, field_desc)
|
|
|
684
704
|
break;
|
|
685
705
|
|
|
686
706
|
case "coordinates":
|
|
687
|
-
field_conf.tag =
|
|
707
|
+
field_conf.tag = raw_structured ? "jsoneditor" : "coordinates";
|
|
688
708
|
break;
|
|
689
709
|
|
|
690
710
|
default:
|
|
@@ -835,23 +855,10 @@ function apply_form_mode(gobj, $form)
|
|
|
835
855
|
$input.setAttribute("readonly", "readonly");
|
|
836
856
|
$input.removeAttribute("required");
|
|
837
857
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
* the OS scheme when the attribute is absent ("system" theme
|
|
843
|
-
* removes it — see themes.js). Read at field-build time; the
|
|
844
|
-
* hosting dialog rebuilds the form on every open.
|
|
845
|
-
******************************************************************/
|
|
846
|
-
function form_is_dark_theme()
|
|
847
|
-
{
|
|
848
|
-
let attr = document.documentElement.getAttribute("data-theme");
|
|
849
|
-
if(attr) {
|
|
850
|
-
return attr === "dark";
|
|
851
|
-
}
|
|
852
|
-
return typeof window !== "undefined" &&
|
|
853
|
-
window.matchMedia &&
|
|
854
|
-
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
858
|
+
/* The pkey's editability just changed but no `input` fired — resync its
|
|
859
|
+
* clear (✕): shown now that "create" made it editable, hidden in
|
|
860
|
+
* "update". */
|
|
861
|
+
refresh_clear($input);
|
|
855
862
|
}
|
|
856
863
|
|
|
857
864
|
/******************************************************************
|
|
@@ -1089,8 +1096,9 @@ function create_form_field(
|
|
|
1089
1096
|
switch (tag) {
|
|
1090
1097
|
case 'input':
|
|
1091
1098
|
{
|
|
1099
|
+
let is_pkey = (name === gobj_read_str_attr(gobj, "pkey"));
|
|
1092
1100
|
let attrs = {
|
|
1093
|
-
class: `input ${
|
|
1101
|
+
class: `input ${is_pkey?'with-focus':''}`,
|
|
1094
1102
|
name: name,
|
|
1095
1103
|
type: inputType,
|
|
1096
1104
|
placeholder: placeholder
|
|
@@ -1129,6 +1137,16 @@ function create_form_field(
|
|
|
1129
1137
|
|
|
1130
1138
|
$extend = createElement2(extend);
|
|
1131
1139
|
$control.appendChild($extend);
|
|
1140
|
+
/* NORM: every editable free-text field carries the clear (✕).
|
|
1141
|
+
* Skip the non-text pickers (color swatch, datetime-local) where
|
|
1142
|
+
* an emptied value makes no sense. The pkey is built `readonly`
|
|
1143
|
+
* from the schema but apply_form_mode() makes it editable in
|
|
1144
|
+
* "create" mode — attach the clear anyway (it self-hides while
|
|
1145
|
+
* readonly). The synthetic `input` re-fires EV_RECORD_CHANGED. */
|
|
1146
|
+
if(inputType !== 'color' && inputType !== 'datetime-local' &&
|
|
1147
|
+
(!readonly || is_pkey)) {
|
|
1148
|
+
attach_clear($control, $extend);
|
|
1149
|
+
}
|
|
1132
1150
|
break;
|
|
1133
1151
|
}
|
|
1134
1152
|
|
|
@@ -1156,12 +1174,20 @@ function create_form_field(
|
|
|
1156
1174
|
{
|
|
1157
1175
|
// ["item"]
|
|
1158
1176
|
// <option value="item" data-i18n:"item"> item </option>
|
|
1177
|
+
let opts = options;
|
|
1178
|
+
if(!Array.isArray(opts)) {
|
|
1179
|
+
log_error(sprintf(
|
|
1180
|
+
"%s: select field '%s' options not an array (type=%s, real_type=%s); rendering empty",
|
|
1181
|
+
gobj_short_name(gobj), name, field_desc.type, field_desc.real_type
|
|
1182
|
+
));
|
|
1183
|
+
opts = [];
|
|
1184
|
+
}
|
|
1159
1185
|
let extend = ['div', {class: "select" },
|
|
1160
1186
|
["select",
|
|
1161
1187
|
{
|
|
1162
1188
|
name: name
|
|
1163
1189
|
},
|
|
1164
|
-
|
|
1190
|
+
opts.map(option =>
|
|
1165
1191
|
['option', {value:option, i18n:option}, option]
|
|
1166
1192
|
),
|
|
1167
1193
|
{
|
|
@@ -1184,13 +1210,24 @@ function create_form_field(
|
|
|
1184
1210
|
/*
|
|
1185
1211
|
* https://tom-select.js.org/docs/api/
|
|
1186
1212
|
*/
|
|
1213
|
+
/* A malformed schema (enum without an array `enum_list`) must not
|
|
1214
|
+
* crash the whole form — render an empty select and name the
|
|
1215
|
+
* offender. */
|
|
1216
|
+
let opts = options;
|
|
1217
|
+
if(!Array.isArray(opts)) {
|
|
1218
|
+
log_error(sprintf(
|
|
1219
|
+
"%s: select2 field '%s' options not an array (type=%s, real_type=%s); rendering empty",
|
|
1220
|
+
gobj_short_name(gobj), name, field_desc.type, field_desc.real_type
|
|
1221
|
+
));
|
|
1222
|
+
opts = [];
|
|
1223
|
+
}
|
|
1187
1224
|
let extend = ["select",
|
|
1188
1225
|
{
|
|
1189
1226
|
name: name,
|
|
1190
1227
|
class: '',
|
|
1191
1228
|
multiple: true,
|
|
1192
1229
|
},
|
|
1193
|
-
|
|
1230
|
+
opts.map(option =>
|
|
1194
1231
|
['option', {value:option, i18n:option}, option]
|
|
1195
1232
|
)
|
|
1196
1233
|
];
|
|
@@ -1204,7 +1241,11 @@ function create_form_field(
|
|
|
1204
1241
|
field_desc.real_type === "string")? 1 : null,
|
|
1205
1242
|
plugins: {
|
|
1206
1243
|
'clear_button': {
|
|
1207
|
-
|
|
1244
|
+
/* i18n keys are lower-case by convention (the apps'
|
|
1245
|
+
validate-locales enforces it): this one was the only
|
|
1246
|
+
capitalized key in the library, so no locale could
|
|
1247
|
+
legally define it and it rendered as its own key. */
|
|
1248
|
+
'title': t('remove all selected options'),
|
|
1208
1249
|
},
|
|
1209
1250
|
'no_active_items': {},
|
|
1210
1251
|
'no_backspace_delete': {},
|
|
@@ -1255,7 +1296,7 @@ function create_form_field(
|
|
|
1255
1296
|
case "jsoneditor":
|
|
1256
1297
|
{
|
|
1257
1298
|
let attrs = {
|
|
1258
|
-
class: 'jsoneditor' + (
|
|
1299
|
+
class: 'jsoneditor' + (yui_is_dark() ? ' jse-theme-dark' : ''),
|
|
1259
1300
|
name: name,
|
|
1260
1301
|
style: ''
|
|
1261
1302
|
};
|
|
@@ -1417,19 +1458,13 @@ function create_coordinates(gobj, attrs)
|
|
|
1417
1458
|
}
|
|
1418
1459
|
|
|
1419
1460
|
let extend = ['div', {class: 'field is-flex is-align-items-center'}, [
|
|
1420
|
-
['div', {class: 'control has-icons-left
|
|
1461
|
+
['div', {class: 'control has-icons-left'}, [
|
|
1421
1462
|
[
|
|
1422
1463
|
'input',
|
|
1423
1464
|
{class: 'input', type: 'text', placeholder: t('coordinates') + '...'},
|
|
1424
1465
|
'',
|
|
1425
1466
|
{
|
|
1426
1467
|
'input': function (evt) {
|
|
1427
|
-
let $btn = this.closest('.control').querySelector('.clear-button');
|
|
1428
|
-
if (this.value) {
|
|
1429
|
-
$btn.style.display = 'inline-flex';
|
|
1430
|
-
} else {
|
|
1431
|
-
$btn.style.display = 'none';
|
|
1432
|
-
}
|
|
1433
1468
|
gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
|
|
1434
1469
|
}
|
|
1435
1470
|
}
|
|
@@ -1447,29 +1482,16 @@ function create_coordinates(gobj, attrs)
|
|
|
1447
1482
|
getLocation(gobj, $input);
|
|
1448
1483
|
}
|
|
1449
1484
|
}
|
|
1450
|
-
],
|
|
1451
|
-
[
|
|
1452
|
-
'span',
|
|
1453
|
-
{
|
|
1454
|
-
class: 'icon clear-button is-right is-clickable',
|
|
1455
|
-
style: 'display:none;'
|
|
1456
|
-
},
|
|
1457
|
-
'<i class="yi-xmark"></i>',
|
|
1458
|
-
{
|
|
1459
|
-
'click': function (evt) {
|
|
1460
|
-
evt.stopPropagation();
|
|
1461
|
-
let $input = this.closest('.control').querySelector('input');
|
|
1462
|
-
$input.value = '';
|
|
1463
|
-
this.style.display = 'none';
|
|
1464
|
-
// gobj.config.select_by_name = '';
|
|
1465
|
-
// gobj_send_event(gobj, "EV_SELECT", {}, gobj);
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
1485
|
]
|
|
1469
1486
|
]]
|
|
1470
1487
|
]];
|
|
1471
1488
|
|
|
1472
|
-
|
|
1489
|
+
/* NORM clear (✕): its synthetic `input` re-fires EV_RECORD_CHANGED, so
|
|
1490
|
+
* clearing the coordinates updates the record like any hand edit. */
|
|
1491
|
+
let $extend = createElement2(extend);
|
|
1492
|
+
let $control = $extend.querySelector('.control');
|
|
1493
|
+
attach_clear($control, $control.querySelector('input'));
|
|
1494
|
+
return $extend;
|
|
1473
1495
|
}
|
|
1474
1496
|
|
|
1475
1497
|
/******************************************************************
|
|
@@ -2270,6 +2292,9 @@ function set_form_values(gobj, template, $form, record)
|
|
|
2270
2292
|
);
|
|
2271
2293
|
break;
|
|
2272
2294
|
}
|
|
2295
|
+
/* Values were set programmatically (no `input` event) — resync the
|
|
2296
|
+
* field's clear (✕) so a loaded value shows it right away. */
|
|
2297
|
+
refresh_clear($input);
|
|
2273
2298
|
});
|
|
2274
2299
|
|
|
2275
2300
|
return modified;
|
|
@@ -2326,6 +2351,18 @@ function treedb_value_2_form_value(gobj, field_desc, value)
|
|
|
2326
2351
|
let type = field_desc.type;
|
|
2327
2352
|
let real_type = field_desc.real_type;
|
|
2328
2353
|
|
|
2354
|
+
/* 'edit' mode shows template/table/coordinates as raw JSON editors:
|
|
2355
|
+
* hand the raw value to jsoneditor in its Content shape, exactly like
|
|
2356
|
+
* object/dict/blob/array. In 'exec' mode these fall through to their
|
|
2357
|
+
* interpreted-widget conversions below. */
|
|
2358
|
+
if(gobj_read_str_attr(gobj, "render_mode") === "edit" &&
|
|
2359
|
+
(type === "template" || type === "table" || type === "coordinates")) {
|
|
2360
|
+
if(type === "table") {
|
|
2361
|
+
return {json: is_array(value)? value : []};
|
|
2362
|
+
}
|
|
2363
|
+
return {json: (is_object(value) || is_array(value))? value : {}};
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2329
2366
|
switch(type) {
|
|
2330
2367
|
case "fkey": {
|
|
2331
2368
|
/* Decode ref(s) ("topic^id^hook", bare "$id", dict keys or
|
|
@@ -2452,6 +2489,28 @@ function form_value_2_treedb_value(gobj, field_desc, value)
|
|
|
2452
2489
|
let type = field_desc.type;
|
|
2453
2490
|
let real_type = field_desc.real_type;
|
|
2454
2491
|
|
|
2492
|
+
/* 'edit' mode: template/table/coordinates come back as the raw JSON
|
|
2493
|
+
* string typed into the jsoneditor — parse it, don't run the
|
|
2494
|
+
* interpreted-widget conversions ('exec' mode) below. */
|
|
2495
|
+
if(gobj_read_str_attr(gobj, "render_mode") === "edit" &&
|
|
2496
|
+
(type === "template" || type === "table" || type === "coordinates")) {
|
|
2497
|
+
if(is_string(value)) {
|
|
2498
|
+
try {
|
|
2499
|
+
value = JSON.parse(value);
|
|
2500
|
+
} catch (e) {
|
|
2501
|
+
value = (type === "table")? [] : {};
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
if(type === "table") {
|
|
2505
|
+
if(!is_array(value)) {
|
|
2506
|
+
value = [];
|
|
2507
|
+
}
|
|
2508
|
+
} else if(!is_object(value) && !is_array(value)) {
|
|
2509
|
+
value = {};
|
|
2510
|
+
}
|
|
2511
|
+
return value;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2455
2514
|
switch(type) {
|
|
2456
2515
|
case "fkey":
|
|
2457
2516
|
value = build_fkey_ref(gobj, field_desc, value);
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
gobj_read_attr,
|
|
22
22
|
gobj_write_attr,
|
|
23
23
|
gobj_send_event,
|
|
24
|
-
gobj_find_service,
|
|
25
24
|
gobj_write_str_attr,
|
|
26
25
|
gobj_read_str_attr,
|
|
27
26
|
gobj_publish_event,
|
|
@@ -54,6 +53,7 @@ import {
|
|
|
54
53
|
} from '@antv/g6';
|
|
55
54
|
|
|
56
55
|
import { ensure_drag_canvas_patch } from "./g6_drag_canvas_touch.js";
|
|
56
|
+
import { yui_is_dark, yui_theme_now, yui_watch_theme } from "./yui_theme.js";
|
|
57
57
|
|
|
58
58
|
/***************************************************************
|
|
59
59
|
* Constants
|
|
@@ -84,6 +84,7 @@ let PRIVATE_DATA = {
|
|
|
84
84
|
graph: null,
|
|
85
85
|
node_counter: 0,
|
|
86
86
|
theme: null,
|
|
87
|
+
theme_observer: null, // MutationObserver on <html data-theme>
|
|
87
88
|
$layout_select: null,
|
|
88
89
|
$popover: null,
|
|
89
90
|
$popover_title: null,
|
|
@@ -139,15 +140,6 @@ const GT_FONT =
|
|
|
139
140
|
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, " +
|
|
140
141
|
"Helvetica, Arial, sans-serif";
|
|
141
142
|
|
|
142
|
-
/***************************************************************
|
|
143
|
-
* True when the app is in dark theme (<html data-theme>).
|
|
144
|
-
***************************************************************/
|
|
145
|
-
function gt_is_dark()
|
|
146
|
-
{
|
|
147
|
-
return (typeof document !== "undefined") &&
|
|
148
|
-
document.documentElement.getAttribute("data-theme") === "dark";
|
|
149
|
-
}
|
|
150
|
-
|
|
151
143
|
/***************************************************************
|
|
152
144
|
* Soft, theme-aware card palette derived from the role colour
|
|
153
145
|
* (same visual language as the treedb graph cards): light tint
|
|
@@ -294,16 +286,15 @@ function mt_create(gobj)
|
|
|
294
286
|
}
|
|
295
287
|
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
296
288
|
|
|
297
|
-
/*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
289
|
+
/* Follow the app theme. This used to read a legacy C_YUI_MAIN
|
|
290
|
+
* "__yui_main__" service's `theme` attr when one existed, falling
|
|
291
|
+
* back to <html data-theme> otherwise — but nothing ever WROTE that
|
|
292
|
+
* attr, so the service branch answered "light" for the life of the
|
|
293
|
+
* app. And neither branch WATCHED: the theme was read once, so
|
|
294
|
+
* toggling to dark with the view open left a white canvas on a dark
|
|
295
|
+
* app. Watch it, and restyle in ac_theme. */
|
|
296
|
+
priv.theme = yui_theme_now();
|
|
297
|
+
priv.theme_observer = yui_watch_theme(gobj);
|
|
307
298
|
|
|
308
299
|
build_ui(gobj);
|
|
309
300
|
|
|
@@ -341,6 +332,10 @@ function mt_destroy(gobj)
|
|
|
341
332
|
{
|
|
342
333
|
let priv = gobj.priv;
|
|
343
334
|
|
|
335
|
+
if(priv.theme_observer) {
|
|
336
|
+
priv.theme_observer.disconnect();
|
|
337
|
+
priv.theme_observer = null;
|
|
338
|
+
}
|
|
344
339
|
if(priv.resize_observer) {
|
|
345
340
|
priv.resize_observer.disconnect();
|
|
346
341
|
priv.resize_observer = null;
|
|
@@ -481,7 +476,7 @@ function build_popover(gobj, $container)
|
|
|
481
476
|
let $close = document.createElement("button");
|
|
482
477
|
$close.type = "button";
|
|
483
478
|
$close.textContent = "×";
|
|
484
|
-
$close.title = t("
|
|
479
|
+
$close.title = t("close");
|
|
485
480
|
$close.style.cssText = `
|
|
486
481
|
background: transparent;
|
|
487
482
|
border: none;
|
|
@@ -591,7 +586,7 @@ function make_toolbar(gobj)
|
|
|
591
586
|
['select', {
|
|
592
587
|
class: 'select',
|
|
593
588
|
style: {height: toolbar_wide, "margin-left": "0.5em"},
|
|
594
|
-
title: t("
|
|
589
|
+
title: t("layout"),
|
|
595
590
|
},
|
|
596
591
|
options,
|
|
597
592
|
{
|
|
@@ -638,7 +633,7 @@ function build_graph(gobj)
|
|
|
638
633
|
edge: {
|
|
639
634
|
type: layout_cfg.edge_type,
|
|
640
635
|
style: {
|
|
641
|
-
stroke:
|
|
636
|
+
stroke: yui_is_dark() ? '#8b94a3' : '#6b7280',
|
|
642
637
|
lineWidth: 1,
|
|
643
638
|
endArrow: true,
|
|
644
639
|
},
|
|
@@ -711,7 +706,7 @@ function apply_layout(gobj)
|
|
|
711
706
|
edge: {
|
|
712
707
|
type: layout_cfg.edge_type,
|
|
713
708
|
style: {
|
|
714
|
-
stroke:
|
|
709
|
+
stroke: yui_is_dark() ? '#8b94a3' : '#6b7280',
|
|
715
710
|
lineWidth: 1,
|
|
716
711
|
endArrow: true,
|
|
717
712
|
},
|
|
@@ -838,7 +833,7 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
|
|
|
838
833
|
let state = gobj_current_state(target_gobj) || "";
|
|
839
834
|
let full_name = gobj_full_name(target_gobj);
|
|
840
835
|
let colors = get_role_colors(target_gobj, is_root);
|
|
841
|
-
let dark =
|
|
836
|
+
let dark = yui_is_dark();
|
|
842
837
|
let cs = role_card_style(colors.stroke, dark);
|
|
843
838
|
let state_color = get_state_color(target_gobj);
|
|
844
839
|
|
|
@@ -1208,11 +1203,11 @@ function get_node_info_rows(node_data)
|
|
|
1208
1203
|
{
|
|
1209
1204
|
let rows = [];
|
|
1210
1205
|
|
|
1211
|
-
rows.push([t("
|
|
1212
|
-
rows.push([t("
|
|
1206
|
+
rows.push([t("gclass"), node_data.gclass || "—"]);
|
|
1207
|
+
rows.push([t("name"), node_data.name || "—"]);
|
|
1213
1208
|
|
|
1214
1209
|
if(node_data.full_name && node_data.full_name !== node_data.short_name) {
|
|
1215
|
-
rows.push([t("
|
|
1210
|
+
rows.push([t("full name"), node_data.full_name]);
|
|
1216
1211
|
}
|
|
1217
1212
|
|
|
1218
1213
|
let role;
|
|
@@ -1227,7 +1222,7 @@ function get_node_info_rows(node_data)
|
|
|
1227
1222
|
} else {
|
|
1228
1223
|
role = "child";
|
|
1229
1224
|
}
|
|
1230
|
-
rows.push([t("
|
|
1225
|
+
rows.push([t("role"), role]);
|
|
1231
1226
|
|
|
1232
1227
|
let status;
|
|
1233
1228
|
if(!node_data.is_running) {
|
|
@@ -1237,19 +1232,19 @@ function get_node_info_rows(node_data)
|
|
|
1237
1232
|
} else {
|
|
1238
1233
|
status = "running";
|
|
1239
1234
|
}
|
|
1240
|
-
rows.push([t("
|
|
1241
|
-
rows.push([t("
|
|
1235
|
+
rows.push([t("status"), status]);
|
|
1236
|
+
rows.push([t("state"), node_data.state || "—"]);
|
|
1242
1237
|
|
|
1243
1238
|
if(node_data.parent_short_name) {
|
|
1244
|
-
rows.push([t("
|
|
1239
|
+
rows.push([t("parent"), node_data.parent_short_name]);
|
|
1245
1240
|
}
|
|
1246
1241
|
|
|
1247
1242
|
if(typeof node_data.num_children === "number") {
|
|
1248
1243
|
let kids = node_data.num_children;
|
|
1249
1244
|
if(kids > 0 && node_data.is_collapsed) {
|
|
1250
|
-
rows.push([t("
|
|
1245
|
+
rows.push([t("children"), kids + " " + t("(collapsed)")]);
|
|
1251
1246
|
} else if(kids > 0) {
|
|
1252
|
-
rows.push([t("
|
|
1247
|
+
rows.push([t("children"), String(kids)]);
|
|
1253
1248
|
}
|
|
1254
1249
|
}
|
|
1255
1250
|
|
|
@@ -1269,10 +1264,14 @@ function render_popover_body(gobj, node_data)
|
|
|
1269
1264
|
|
|
1270
1265
|
let rows = get_node_info_rows(node_data);
|
|
1271
1266
|
|
|
1267
|
+
/* Bulma scheme vars, like the popover chrome around it (build_popover):
|
|
1268
|
+
* they flip with <html data-theme>, so the popover follows the theme
|
|
1269
|
+
* with no redraw. Hardcoding them is what made the value near-black
|
|
1270
|
+
* (#1A1A1A) on the near-black dark card background — invisible. */
|
|
1272
1271
|
let rows_html = rows.map(([label, value]) => `
|
|
1273
1272
|
<div style="display:grid; grid-template-columns: 90px 1fr; column-gap:10px; padding: 2px 0;">
|
|
1274
|
-
<span style="color
|
|
1275
|
-
<span style="color
|
|
1273
|
+
<span style="color:var(--bulma-text-weak, #6B7280); font-weight:500;">${escapeHtml(String(label))}</span>
|
|
1274
|
+
<span style="color:var(--bulma-text-strong, #1A1A1A); word-break:break-all;">${escapeHtml(String(value))}</span>
|
|
1276
1275
|
</div>
|
|
1277
1276
|
`).join("");
|
|
1278
1277
|
|
|
@@ -1712,6 +1711,27 @@ function ac_refresh(gobj, event, kw, src)
|
|
|
1712
1711
|
return 0;
|
|
1713
1712
|
}
|
|
1714
1713
|
|
|
1714
|
+
/************************************************************
|
|
1715
|
+
* {theme: "dark"|"light"} — the app switched theme.
|
|
1716
|
+
*
|
|
1717
|
+
* G6 gets the new theme, and the tree is rebuilt: the node/edge
|
|
1718
|
+
* colours are picked from yui_is_dark() as they are drawn, so only a
|
|
1719
|
+
* redraw actually repaints them.
|
|
1720
|
+
************************************************************/
|
|
1721
|
+
function ac_theme(gobj, event, kw, src)
|
|
1722
|
+
{
|
|
1723
|
+
let priv = gobj.priv;
|
|
1724
|
+
|
|
1725
|
+
priv.theme = kw.theme || yui_theme_now();
|
|
1726
|
+
if(priv.graph) {
|
|
1727
|
+
priv.graph.setTheme(priv.theme);
|
|
1728
|
+
}
|
|
1729
|
+
/* A restyle, not new data: keep the user's zoom/pan. */
|
|
1730
|
+
refresh_tree(gobj, {preserve_view: true});
|
|
1731
|
+
|
|
1732
|
+
return 0;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1715
1735
|
/************************************************************
|
|
1716
1736
|
* {layout: "<key>"}
|
|
1717
1737
|
************************************************************/
|
|
@@ -2040,6 +2060,7 @@ function create_gclass(gclass_name)
|
|
|
2040
2060
|
const states = [
|
|
2041
2061
|
["ST_IDLE", [
|
|
2042
2062
|
["EV_REFRESH", ac_refresh, null],
|
|
2063
|
+
["EV_THEME", ac_theme, null],
|
|
2043
2064
|
["EV_CHANGE_LAYOUT", ac_change_layout, null],
|
|
2044
2065
|
["EV_TOGGLE_COLLAPSE", ac_toggle_collapse, null],
|
|
2045
2066
|
["EV_EXPAND_ALL", ac_expand_all, null],
|
|
@@ -2060,6 +2081,7 @@ function create_gclass(gclass_name)
|
|
|
2060
2081
|
*---------------------------------------------*/
|
|
2061
2082
|
const event_types = [
|
|
2062
2083
|
["EV_REFRESH", 0],
|
|
2084
|
+
["EV_THEME", 0],
|
|
2063
2085
|
["EV_CHANGE_LAYOUT", 0],
|
|
2064
2086
|
["EV_TOGGLE_COLLAPSE", 0],
|
|
2065
2087
|
["EV_EXPAND_ALL", 0],
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* c_yui_json.css
|
|
3
|
+
*
|
|
4
|
+
* Styling for C_YUI_JSON (lazy JSON tree viewer).
|
|
5
|
+
* Monospace tree, type-coloured leaves, dark-aware.
|
|
6
|
+
* No transitions/animations by design (lib-yui convention).
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2026, ArtGins.
|
|
9
|
+
* All Rights Reserved.
|
|
10
|
+
***********************************************************************/
|
|
11
|
+
|
|
12
|
+
.C_YUI_JSON {
|
|
13
|
+
--json-string: #006000;
|
|
14
|
+
--json-number: #b5330b;
|
|
15
|
+
--json-boolean: #b36b00;
|
|
16
|
+
--json-null: #475ed0;
|
|
17
|
+
--json-key: var(--bulma-text-strong, #1a1a1a);
|
|
18
|
+
--json-row-hover: rgba(0, 0, 0, 0.05);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:root[data-theme="dark"] .C_YUI_JSON,
|
|
22
|
+
.C_YUI_JSON[data-theme="dark"] {
|
|
23
|
+
--json-string: #7ec87e;
|
|
24
|
+
--json-number: #ff8a65;
|
|
25
|
+
--json-boolean: #ffb74d;
|
|
26
|
+
--json-null: #90a4f4;
|
|
27
|
+
--json-key: var(--bulma-text-strong, #e6e6e6);
|
|
28
|
+
--json-row-hover: rgba(255, 255, 255, 0.06);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@media (prefers-color-scheme: dark) {
|
|
32
|
+
:root:not([data-theme="light"]) .C_YUI_JSON {
|
|
33
|
+
--json-string: #7ec87e;
|
|
34
|
+
--json-number: #ff8a65;
|
|
35
|
+
--json-boolean: #ffb74d;
|
|
36
|
+
--json-null: #90a4f4;
|
|
37
|
+
--json-key: var(--bulma-text-strong, #e6e6e6);
|
|
38
|
+
--json-row-hover: rgba(255, 255, 255, 0.06);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.C_YUI_JSON .JSON_TOOLBAR {
|
|
43
|
+
border-bottom: 1px solid var(--bulma-border-weak, #e0e0e0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.C_YUI_JSON .JSON_TREE {
|
|
47
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
48
|
+
font-size: 1em;
|
|
49
|
+
line-height: 1.5;
|
|
50
|
+
padding: 0.25rem 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.C_YUI_JSON .JSON_ROW {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: flex-start;
|
|
56
|
+
white-space: pre;
|
|
57
|
+
padding-right: 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.C_YUI_JSON .JSON_ROW:hover {
|
|
61
|
+
background: var(--json-row-hover);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.C_YUI_JSON .JSON_ROW_BODY {
|
|
65
|
+
white-space: pre-wrap;
|
|
66
|
+
word-break: break-word;
|
|
67
|
+
min-width: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* Toggle affordance: a fixed-width gutter so keys align across depths.
|
|
72
|
+
* The chevron is rotated 90deg when open (static transform, no
|
|
73
|
+
* transition — animation is disabled by convention).
|
|
74
|
+
*/
|
|
75
|
+
.C_YUI_JSON .JSON_TOGGLE {
|
|
76
|
+
flex: 0 0 auto;
|
|
77
|
+
width: 1.2em;
|
|
78
|
+
text-align: center;
|
|
79
|
+
color: var(--bulma-text-weak, #7a7a7a);
|
|
80
|
+
cursor: default;
|
|
81
|
+
user-select: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.C_YUI_JSON .JSON_CONTAINER,
|
|
85
|
+
.C_YUI_JSON .JSON_COLLAPSED {
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.C_YUI_JSON .JSON_CONTAINER .JSON_TOGGLE i {
|
|
90
|
+
display: inline-block;
|
|
91
|
+
font-size: 0.85em;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.C_YUI_JSON .JSON_TOGGLE.is-open i {
|
|
95
|
+
transform: rotate(90deg);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* "Expand all" reuses the chevron (the only one available) rotated to point
|
|
99
|
+
* DOWN — the open state — while "collapse all" keeps it pointing right (the
|
|
100
|
+
* closed state), mirroring the per-node toggle. */
|
|
101
|
+
.C_YUI_JSON .EV_EXPAND_ALL i {
|
|
102
|
+
transform: rotate(90deg);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.C_YUI_JSON .JSON_TOGGLE_REMOTE i {
|
|
106
|
+
font-size: 0.8em;
|
|
107
|
+
color: var(--bulma-link, #3273dc);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.C_YUI_JSON .JSON_KEY {
|
|
111
|
+
color: var(--json-key);
|
|
112
|
+
font-weight: 600;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.C_YUI_JSON .JSON_PUNCT {
|
|
116
|
+
color: var(--bulma-text-weak, #7a7a7a);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.C_YUI_JSON .JSON_TYPE_STRING { color: var(--json-string); }
|
|
120
|
+
.C_YUI_JSON .JSON_TYPE_NUMBER { color: var(--json-number); }
|
|
121
|
+
.C_YUI_JSON .JSON_TYPE_BOOLEAN { color: var(--json-boolean); }
|
|
122
|
+
.C_YUI_JSON .JSON_TYPE_NULL { color: var(--json-null); font-style: italic; }
|
|
123
|
+
|
|
124
|
+
.C_YUI_JSON .JSON_STUB {
|
|
125
|
+
color: var(--bulma-link, #3273dc);
|
|
126
|
+
font-weight: 600;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.C_YUI_JSON .JSON_COLLAPSED.is-pending {
|
|
130
|
+
cursor: progress;
|
|
131
|
+
opacity: 0.7;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* Disable any transition a host stylesheet might inject.
|
|
136
|
+
*/
|
|
137
|
+
.C_YUI_JSON * {
|
|
138
|
+
transition: none !important;
|
|
139
|
+
}
|