@yuneta/gobj-ui 7.20.1 → 7.21.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 +51 -27
- package/dist/gobj-ui.cjs.js +230 -63
- package/dist/gobj-ui.es.js +230 -63
- package/package.json +1 -1
- package/src/c_yui_json.css +78 -1
- package/src/c_yui_json.js +264 -72
- package/src/c_yui_json_graph.js +8 -0
package/src/c_yui_json.js
CHANGED
|
@@ -24,19 +24,32 @@
|
|
|
24
24
|
* Only expanded containers are materialised in the DOM, so the tree
|
|
25
25
|
* stays bounded no matter how large the source document is.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
* attr ("tree" | "text") and switched from the
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
27
|
+
* THREE VIEWS over the same working document, chosen by the
|
|
28
|
+
* `view_mode` attr ("tree" | "text" | "graph") and switched from the
|
|
29
|
+
* toolbar. They answer three different questions:
|
|
30
|
+
*
|
|
31
|
+
* tree where is this value, and what is around it — the lazy
|
|
32
|
+
* view above, the only one that can drill.
|
|
33
|
+
* text what does this document SAY, verbatim — for reading it
|
|
34
|
+
* as written, selecting a slab of it, or searching it with
|
|
35
|
+
* the browser's own Ctrl+F.
|
|
36
|
+
* graph what SHAPE is this — nesting as a hierarchy, drawn by a
|
|
37
|
+
* hosted C_YUI_JSON_GRAPH child (AntV/G6).
|
|
38
|
+
*
|
|
39
|
+
* Neither text nor graph is lazy: both show what the client currently
|
|
40
|
+
* holds, `__collapsed__` sentinels included, because that is honestly
|
|
41
|
+
* what it has. Drill in the tree and they grow with it. The
|
|
34
42
|
* tree-only controls (search, expand/collapse) hide with the tree.
|
|
35
43
|
*
|
|
44
|
+
* The graph child is built on FIRST entry into graph mode and not
|
|
45
|
+
* before: G6 measures its container, so a graph created behind
|
|
46
|
+
* `is-hidden` comes up 0x0, and a viewer nobody switches to graph
|
|
47
|
+
* should not pay for a canvas.
|
|
48
|
+
*
|
|
36
49
|
* DOM is self-describing (UPPER_SNAKE logical classes): JSON_VIEWER /
|
|
37
|
-
* JSON_TOOLBAR / JSON_SEARCH /
|
|
38
|
-
*
|
|
39
|
-
* JSON_COLLAPSED / JSON_TIME.
|
|
50
|
+
* JSON_TOOLBAR / JSON_SEARCH / JSON_VIEW_SWITCH / JSON_VIEW_MODE /
|
|
51
|
+
* JSON_TREE / JSON_TEXT / JSON_TEXT_BODY / JSON_GRAPH / JSON_ROW /
|
|
52
|
+
* JSON_KEY / JSON_VALUE / JSON_SUMMARY / JSON_COLLAPSED / JSON_TIME.
|
|
40
53
|
*
|
|
41
54
|
* Copyright (c) 2026, ArtGins.
|
|
42
55
|
* All Rights Reserved.
|
|
@@ -52,6 +65,14 @@ import {
|
|
|
52
65
|
gobj_read_pointer_attr,
|
|
53
66
|
gobj_parent,
|
|
54
67
|
gobj_subscribe_event,
|
|
68
|
+
gobj_create_pure_child,
|
|
69
|
+
gobj_start,
|
|
70
|
+
gobj_stop,
|
|
71
|
+
gobj_destroy,
|
|
72
|
+
gobj_is_running,
|
|
73
|
+
gobj_short_name,
|
|
74
|
+
clean_name,
|
|
75
|
+
gobj_name,
|
|
55
76
|
gobj_read_attr,
|
|
56
77
|
gobj_write_attr,
|
|
57
78
|
gobj_read_str_attr,
|
|
@@ -76,6 +97,7 @@ import {
|
|
|
76
97
|
} from "./json_view_helpers.js";
|
|
77
98
|
|
|
78
99
|
import {yui_toolbar} from "./yui_toolbar.js";
|
|
100
|
+
import {register_c_yui_json_graph} from "./c_yui_json_graph.js";
|
|
79
101
|
import {attach_clear} from "./yui_inputs.js";
|
|
80
102
|
|
|
81
103
|
import {t} from "i18next";
|
|
@@ -103,6 +125,17 @@ const MAX_RENDER_ROWS = 5000;
|
|
|
103
125
|
*/
|
|
104
126
|
const MAX_TEXT_CHARS = 2000000;
|
|
105
127
|
|
|
128
|
+
/*
|
|
129
|
+
* The three views, in switch order. EV_SET_VIEW_MODE with no mode
|
|
130
|
+
* advances along this list, which is what the old two-view toggle did
|
|
131
|
+
* when the list was two long.
|
|
132
|
+
*/
|
|
133
|
+
const VIEWS = [
|
|
134
|
+
{mode: "tree", icon: "yi-sitemap", key: "tree view"},
|
|
135
|
+
{mode: "text", icon: "yi-code", key: "text view"},
|
|
136
|
+
{mode: "graph", icon: "yi-hexagon-nodes", key: "graph view"},
|
|
137
|
+
];
|
|
138
|
+
|
|
106
139
|
/***************************************************************
|
|
107
140
|
* Data
|
|
108
141
|
***************************************************************/
|
|
@@ -113,7 +146,7 @@ SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of outpu
|
|
|
113
146
|
/*---------------- Config ----------------*/
|
|
114
147
|
SDATA(data_type_t.DTP_STRING, "title", 0, "", "Optional header title (i18n key)"),
|
|
115
148
|
SDATA(data_type_t.DTP_JSON, "json_data", 0, null, "Initial JSON to render (usually already collapsed)"),
|
|
116
|
-
SDATA(data_type_t.DTP_STRING, "view_mode", 0, "tree", "View: 'tree' (lazy tree)
|
|
149
|
+
SDATA(data_type_t.DTP_STRING, "view_mode", 0, "tree", "View: 'tree' (lazy tree), 'text' (raw dump) or 'graph'"),
|
|
117
150
|
|
|
118
151
|
/*---------------- UI ----------------*/
|
|
119
152
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "HTMLElement root, mounted by the parent"),
|
|
@@ -129,11 +162,13 @@ let PRIVATE_DATA = {
|
|
|
129
162
|
$tree: null, // the scrollable tree body element
|
|
130
163
|
$text: null, // the scrollable text body element
|
|
131
164
|
$text_body: null, // the <pre> inside it
|
|
165
|
+
$graph: null, // the graph body element
|
|
166
|
+
graph_gobj: null, // hosted C_YUI_JSON_GRAPH child (built on first use)
|
|
132
167
|
$search: null, // the search input element
|
|
133
168
|
$search_ctl: null, // the search control (hidden in text view)
|
|
134
169
|
$expand_btn: null, // "expand loaded" button (hidden in text view)
|
|
135
170
|
$collapse_btn: null,// "collapse all" button (hidden in text view)
|
|
136
|
-
$
|
|
171
|
+
$mode_btns: null, // Map<mode, HTMLElement> of the view switch
|
|
137
172
|
};
|
|
138
173
|
|
|
139
174
|
let __gclass__ = null;
|
|
@@ -198,6 +233,7 @@ function mt_stop(gobj)
|
|
|
198
233
|
***************************************************************/
|
|
199
234
|
function mt_destroy(gobj)
|
|
200
235
|
{
|
|
236
|
+
teardown_graph_child(gobj);
|
|
201
237
|
destroy_ui(gobj);
|
|
202
238
|
}
|
|
203
239
|
|
|
@@ -227,7 +263,12 @@ function build_ui(gobj)
|
|
|
227
263
|
['div', {class: 'JSON_TREE is-flex-grow-1',
|
|
228
264
|
style: 'flex:1 1 auto; min-height:0; overflow:auto;'}, []],
|
|
229
265
|
['div', {class: 'JSON_TEXT is-flex-grow-1 is-hidden',
|
|
230
|
-
style: 'flex:1 1 auto; min-height:0; overflow:auto;'}, []]
|
|
266
|
+
style: 'flex:1 1 auto; min-height:0; overflow:auto;'}, []],
|
|
267
|
+
/* The graph body does NOT scroll: G6 owns its viewport and
|
|
268
|
+
* pans inside it. An `overflow:auto` here would give the
|
|
269
|
+
* canvas a second, competing scroller. */
|
|
270
|
+
['div', {class: 'JSON_GRAPH is-flex-grow-1 is-hidden',
|
|
271
|
+
style: 'flex:1 1 auto; overflow:hidden;'}, []]
|
|
231
272
|
]]
|
|
232
273
|
);
|
|
233
274
|
|
|
@@ -238,7 +279,14 @@ function build_ui(gobj)
|
|
|
238
279
|
priv.$search_ctl = $container.querySelector('.JSON_SEARCH_CONTROL');
|
|
239
280
|
priv.$expand_btn = $container.querySelector('.EV_EXPAND_ALL');
|
|
240
281
|
priv.$collapse_btn = $container.querySelector('.EV_COLLAPSE_ALL');
|
|
241
|
-
priv.$
|
|
282
|
+
priv.$graph = $container.querySelector('.JSON_GRAPH');
|
|
283
|
+
priv.$mode_btns = new Map();
|
|
284
|
+
VIEWS.forEach(function(v) {
|
|
285
|
+
let $btn = $container.querySelector('.JSON_VIEW_MODE_' + v.mode.toUpperCase());
|
|
286
|
+
if($btn) {
|
|
287
|
+
priv.$mode_btns.set(v.mode, $btn);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
242
290
|
|
|
243
291
|
refresh_language($container, t);
|
|
244
292
|
}
|
|
@@ -259,11 +307,12 @@ function destroy_ui(gobj)
|
|
|
259
307
|
priv.$tree = null;
|
|
260
308
|
priv.$text = null;
|
|
261
309
|
priv.$text_body = null;
|
|
310
|
+
priv.$graph = null;
|
|
262
311
|
priv.$search = null;
|
|
263
312
|
priv.$search_ctl = null;
|
|
264
313
|
priv.$expand_btn = null;
|
|
265
314
|
priv.$collapse_btn = null;
|
|
266
|
-
priv.$
|
|
315
|
+
priv.$mode_btns = null;
|
|
267
316
|
}
|
|
268
317
|
|
|
269
318
|
/************************************************************
|
|
@@ -277,7 +326,11 @@ function make_toolbar(gobj)
|
|
|
277
326
|
let left_items = [];
|
|
278
327
|
if(title) {
|
|
279
328
|
left_items.push(
|
|
280
|
-
|
|
329
|
+
/* is-hidden-mobile: on a phone the toolbar cannot hold the
|
|
330
|
+
* title AND a usable search AND six buttons, and the title
|
|
331
|
+
* is the one thing the host usually repeats — a dialog
|
|
332
|
+
* header, a card heading — so it is the one that goes. */
|
|
333
|
+
['span', {class: 'JSON_TITLE is-flex is-align-items-center px-2 is-hidden-mobile',
|
|
281
334
|
style: 'font-weight:600;', 'data-i18n': title}, title]
|
|
282
335
|
);
|
|
283
336
|
}
|
|
@@ -301,7 +354,7 @@ function make_toolbar(gobj)
|
|
|
301
354
|
left_items.push($search_control);
|
|
302
355
|
|
|
303
356
|
let right_items = [
|
|
304
|
-
|
|
357
|
+
view_mode_switch(gobj),
|
|
305
358
|
icon_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand loaded"),
|
|
306
359
|
icon_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all"),
|
|
307
360
|
icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json"),
|
|
@@ -337,34 +390,64 @@ function icon_button(gobj, icon, event_name, label_key)
|
|
|
337
390
|
}
|
|
338
391
|
|
|
339
392
|
/************************************************************
|
|
340
|
-
* The
|
|
393
|
+
* The view switch: one button per view, the current one marked.
|
|
341
394
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
395
|
+
* Three views do not fit a toggle. A button that CYCLES cannot be
|
|
396
|
+
* aimed — reaching the graph from the tree would mean passing
|
|
397
|
+
* through the text and rebuilding it on the way — so each view gets
|
|
398
|
+
* its own button and says where it goes. apply_view_mode() moves
|
|
399
|
+
* the `is-active` mark.
|
|
347
400
|
************************************************************/
|
|
348
|
-
function
|
|
401
|
+
function view_mode_switch(gobj)
|
|
349
402
|
{
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
403
|
+
let buttons = VIEWS.map(function(v) {
|
|
404
|
+
let label = view_label(v.mode);
|
|
405
|
+
return ['button', {class: `button JSON_VIEW_MODE JSON_VIEW_MODE_${v.mode.toUpperCase()}`,
|
|
406
|
+
type: 'button', style: 'width:2.5em;',
|
|
407
|
+
title: label, 'data-i18n-title': v.key,
|
|
408
|
+
'aria-label': label, 'data-i18n-aria-label': v.key}, [
|
|
409
|
+
['span', {class: 'icon'}, [['i', {class: v.icon}]]]
|
|
410
|
+
], {
|
|
411
|
+
click: function(evt) {
|
|
412
|
+
evt.stopPropagation();
|
|
413
|
+
gobj_send_event(gobj, "EV_SET_VIEW_MODE", {mode: v.mode}, gobj);
|
|
414
|
+
}
|
|
415
|
+
}];
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
return ['div', {class: 'buttons has-addons is-flex-wrap-nowrap mb-0 JSON_VIEW_SWITCH'},
|
|
419
|
+
buttons];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/************************************************************
|
|
423
|
+
* The label of a view, with every key SPELLED OUT inside t().
|
|
424
|
+
*
|
|
425
|
+
* Never t(VIEWS[i].key): the apps' validate-locales scans for
|
|
426
|
+
* t("literal"), so a key reached through a variable is a key it
|
|
427
|
+
* cannot demand — and i18next answers an undefined key with the key
|
|
428
|
+
* ITSELF, which renders in lower-case English and never changes
|
|
429
|
+
* language. That shipped once already (7.20.0, `tree view`); this
|
|
430
|
+
* table would have hidden all three.
|
|
431
|
+
************************************************************/
|
|
432
|
+
function view_label(mode)
|
|
433
|
+
{
|
|
434
|
+
switch(mode) {
|
|
435
|
+
case "text":
|
|
436
|
+
return t("text view");
|
|
437
|
+
case "graph":
|
|
438
|
+
return t("graph view");
|
|
439
|
+
default:
|
|
440
|
+
return t("tree view");
|
|
441
|
+
}
|
|
360
442
|
}
|
|
361
443
|
|
|
362
444
|
/************************************************************
|
|
363
|
-
* The current view, normalised. Anything
|
|
445
|
+
* The current view, normalised. Anything unknown is the tree.
|
|
364
446
|
************************************************************/
|
|
365
447
|
function current_view_mode(gobj)
|
|
366
448
|
{
|
|
367
|
-
|
|
449
|
+
let mode = gobj_read_str_attr(gobj, "view_mode");
|
|
450
|
+
return VIEWS.some((v) => v.mode === mode)? mode: "tree";
|
|
368
451
|
}
|
|
369
452
|
|
|
370
453
|
/************************************************************
|
|
@@ -373,45 +456,32 @@ function current_view_mode(gobj)
|
|
|
373
456
|
function apply_view_mode(gobj)
|
|
374
457
|
{
|
|
375
458
|
let priv = gobj.priv;
|
|
376
|
-
let
|
|
459
|
+
let mode = current_view_mode(gobj);
|
|
377
460
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
461
|
+
let bodies = {tree: priv.$tree, text: priv.$text, graph: priv.$graph};
|
|
462
|
+
for(let [m, $el] of Object.entries(bodies)) {
|
|
463
|
+
if($el) {
|
|
464
|
+
$el.classList.toggle('is-hidden', m !== mode);
|
|
465
|
+
}
|
|
383
466
|
}
|
|
384
467
|
|
|
385
468
|
/*
|
|
386
|
-
* Search and expand/collapse act on TREE rows;
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
469
|
+
* Search and expand/collapse act on TREE rows; the other two views
|
|
470
|
+
* have no rows to act on, and a control that answers nothing is
|
|
471
|
+
* worse than an absent one. A <pre> is searched with the browser's
|
|
472
|
+
* own Ctrl+F, and the graph carries its own controls.
|
|
390
473
|
*/
|
|
391
474
|
[priv.$search_ctl, priv.$expand_btn, priv.$collapse_btn].forEach(function($el) {
|
|
392
475
|
if($el) {
|
|
393
|
-
$el.classList.toggle('is-hidden',
|
|
476
|
+
$el.classList.toggle('is-hidden', mode !== "tree");
|
|
394
477
|
}
|
|
395
478
|
});
|
|
396
479
|
|
|
397
|
-
if(priv.$
|
|
398
|
-
let
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
* see is a key it cannot demand — i18next then answers an
|
|
402
|
-
* undefined key with the key ITSELF, so the button renders in
|
|
403
|
-
* lower-case English and never changes language. `key` still
|
|
404
|
-
* carries the data-i18n-* attrs refresh_language() reads. */
|
|
405
|
-
let key = text_mode? "tree view": "text view";
|
|
406
|
-
let label = text_mode? t("tree view"): t("text view");
|
|
407
|
-
let $i = priv.$mode_btn.querySelector('i');
|
|
408
|
-
if($i) {
|
|
409
|
-
$i.className = icon;
|
|
480
|
+
if(priv.$mode_btns) {
|
|
481
|
+
for(let [m, $btn] of priv.$mode_btns) {
|
|
482
|
+
$btn.classList.toggle('is-active', m === mode);
|
|
483
|
+
$btn.setAttribute("aria-pressed", (m === mode)? "true": "false");
|
|
410
484
|
}
|
|
411
|
-
priv.$mode_btn.setAttribute("data-i18n-title", key);
|
|
412
|
-
priv.$mode_btn.setAttribute("data-i18n-aria-label", key);
|
|
413
|
-
priv.$mode_btn.setAttribute("title", label);
|
|
414
|
-
priv.$mode_btn.setAttribute("aria-label", label);
|
|
415
485
|
}
|
|
416
486
|
}
|
|
417
487
|
|
|
@@ -420,10 +490,105 @@ function apply_view_mode(gobj)
|
|
|
420
490
|
************************************************************/
|
|
421
491
|
function render_view(gobj)
|
|
422
492
|
{
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
493
|
+
switch(current_view_mode(gobj)) {
|
|
494
|
+
case "text":
|
|
495
|
+
render_text(gobj);
|
|
496
|
+
break;
|
|
497
|
+
case "graph":
|
|
498
|
+
render_graph(gobj);
|
|
499
|
+
break;
|
|
500
|
+
default:
|
|
501
|
+
render_tree(gobj);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/************************************************************
|
|
507
|
+
* Render the graph view.
|
|
508
|
+
*
|
|
509
|
+
* The C_YUI_JSON_GRAPH child is built HERE, on first entry, and
|
|
510
|
+
* never in build_ui: G6 sizes itself from its container, and a
|
|
511
|
+
* container behind `is-hidden` measures 0x0. By the time this
|
|
512
|
+
* runs apply_view_mode() has already revealed the body.
|
|
513
|
+
************************************************************/
|
|
514
|
+
function render_graph(gobj)
|
|
515
|
+
{
|
|
516
|
+
let priv = gobj.priv;
|
|
517
|
+
let $graph = priv.$graph;
|
|
518
|
+
if(!$graph) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if(priv.root === null || priv.root === undefined) {
|
|
523
|
+
teardown_graph_child(gobj);
|
|
524
|
+
$graph.textContent = "";
|
|
525
|
+
$graph.appendChild(createElement2(
|
|
526
|
+
['div', {class: 'JSON_EMPTY has-text-grey p-3', 'data-i18n': 'no data'}, 'no data']
|
|
527
|
+
));
|
|
528
|
+
refresh_language($graph, t);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if(!priv.graph_gobj) {
|
|
533
|
+
$graph.textContent = "";
|
|
534
|
+
if(!build_graph_child(gobj)) {
|
|
535
|
+
return; // Error already logged
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* The document, and then the size: the container may have changed
|
|
540
|
+
* while this view was hidden, and G6 does not watch it. */
|
|
541
|
+
gobj_send_event(priv.graph_gobj, "EV_LOAD_DATA",
|
|
542
|
+
{data: priv.root, path: gobj_read_str_attr(gobj, "title") || ""}, gobj);
|
|
543
|
+
gobj_send_event(priv.graph_gobj, "EV_RESIZE", {}, gobj);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/************************************************************
|
|
547
|
+
* Build and mount the hosted graph child. Returns true on
|
|
548
|
+
* success; every failure path logs.
|
|
549
|
+
************************************************************/
|
|
550
|
+
function build_graph_child(gobj)
|
|
551
|
+
{
|
|
552
|
+
let priv = gobj.priv;
|
|
553
|
+
|
|
554
|
+
let graph = gobj_create_pure_child(
|
|
555
|
+
"graph_" + clean_name(gobj_name(gobj)),
|
|
556
|
+
"C_YUI_JSON_GRAPH",
|
|
557
|
+
{},
|
|
558
|
+
gobj
|
|
559
|
+
);
|
|
560
|
+
if(!graph) {
|
|
561
|
+
log_error(`${gobj_short_name(gobj)}: cannot create the graph viewer`);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let $box = gobj_read_attr(graph, "$container");
|
|
566
|
+
if(!$box) {
|
|
567
|
+
log_error(`${gobj_short_name(gobj)}: the graph viewer built no $container`);
|
|
568
|
+
gobj_destroy(graph);
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/* Mounted BEFORE gobj_start: mt_start builds the G6 graph and
|
|
573
|
+
* measures the canvas, which is 0x0 while it is still detached. */
|
|
574
|
+
priv.$graph.appendChild($box);
|
|
575
|
+
priv.graph_gobj = graph;
|
|
576
|
+
gobj_start(graph);
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/************************************************************
|
|
581
|
+
* Destroy the hosted graph child, if any.
|
|
582
|
+
************************************************************/
|
|
583
|
+
function teardown_graph_child(gobj)
|
|
584
|
+
{
|
|
585
|
+
let priv = gobj.priv;
|
|
586
|
+
if(priv.graph_gobj) {
|
|
587
|
+
if(gobj_is_running(priv.graph_gobj)) {
|
|
588
|
+
gobj_stop(priv.graph_gobj);
|
|
589
|
+
}
|
|
590
|
+
gobj_destroy(priv.graph_gobj);
|
|
591
|
+
priv.graph_gobj = null;
|
|
427
592
|
}
|
|
428
593
|
}
|
|
429
594
|
|
|
@@ -977,21 +1142,28 @@ function ac_copy_all(gobj, event, kw, src)
|
|
|
977
1142
|
}
|
|
978
1143
|
|
|
979
1144
|
/************************************************************
|
|
980
|
-
* EV_SET_VIEW_MODE { mode } — "tree" | "text".
|
|
1145
|
+
* EV_SET_VIEW_MODE { mode } — "tree" | "text" | "graph".
|
|
981
1146
|
*
|
|
982
|
-
* With no mode it
|
|
1147
|
+
* With no mode it ADVANCES to the next view in VIEWS order, which
|
|
1148
|
+
* is what the two-view toggle did when the list was two long. The
|
|
1149
|
+
* toolbar buttons always name their mode.
|
|
983
1150
|
************************************************************/
|
|
984
1151
|
function ac_set_view_mode(gobj, event, kw, src)
|
|
985
1152
|
{
|
|
986
1153
|
let mode = kw.mode;
|
|
987
1154
|
|
|
988
1155
|
if(mode === undefined || mode === null || mode === "") {
|
|
989
|
-
|
|
990
|
-
|
|
1156
|
+
let i = VIEWS.findIndex((v) => v.mode === current_view_mode(gobj));
|
|
1157
|
+
mode = VIEWS[(i + 1) % VIEWS.length].mode;
|
|
1158
|
+
} else if(!VIEWS.some((v) => v.mode === mode)) {
|
|
991
1159
|
log_error(`${GCLASS_NAME}: unknown view_mode '${mode}'`);
|
|
992
1160
|
return -1;
|
|
993
1161
|
}
|
|
994
1162
|
|
|
1163
|
+
if(mode === current_view_mode(gobj)) {
|
|
1164
|
+
return 0; // already there; re-rendering the graph would relayout it
|
|
1165
|
+
}
|
|
1166
|
+
|
|
995
1167
|
gobj_write_attr(gobj, "view_mode", mode);
|
|
996
1168
|
|
|
997
1169
|
apply_view_mode(gobj);
|
|
@@ -999,6 +1171,18 @@ function ac_set_view_mode(gobj, event, kw, src)
|
|
|
999
1171
|
return 0;
|
|
1000
1172
|
}
|
|
1001
1173
|
|
|
1174
|
+
/************************************************************
|
|
1175
|
+
* EV_JSON_ITEM_CLICKED — from the hosted graph child.
|
|
1176
|
+
*
|
|
1177
|
+
* Republished under this gclass's own name so the host has ONE
|
|
1178
|
+
* contract and never has to know the child exists.
|
|
1179
|
+
************************************************************/
|
|
1180
|
+
function ac_json_item_clicked(gobj, event, kw, src)
|
|
1181
|
+
{
|
|
1182
|
+
gobj_publish_event(gobj, "EV_JSON_ITEM_CLICKED", kw);
|
|
1183
|
+
return 0;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1002
1186
|
/************************************************************
|
|
1003
1187
|
* EV_LANGUAGE_CHANGED — re-translate chrome + re-render
|
|
1004
1188
|
************************************************************/
|
|
@@ -1091,6 +1275,7 @@ function create_gclass(gclass_name)
|
|
|
1091
1275
|
]],
|
|
1092
1276
|
["ST_READY", [
|
|
1093
1277
|
["EV_SET_JSON", ac_set_json, null],
|
|
1278
|
+
["EV_JSON_ITEM_CLICKED", ac_json_item_clicked, null],
|
|
1094
1279
|
["EV_SUBTREE_LOADED", ac_subtree_loaded, null],
|
|
1095
1280
|
["EV_SUBTREE_ERROR", ac_subtree_error, null],
|
|
1096
1281
|
["EV_TOGGLE_NODE", ac_toggle_node, null],
|
|
@@ -1121,6 +1306,7 @@ function create_gclass(gclass_name)
|
|
|
1121
1306
|
["EV_COLLAPSE_ALL", 0],
|
|
1122
1307
|
["EV_COPY_ALL", 0],
|
|
1123
1308
|
["EV_SET_VIEW_MODE", 0],
|
|
1309
|
+
["EV_JSON_ITEM_CLICKED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
1124
1310
|
["EV_LANGUAGE_CHANGED", 0],
|
|
1125
1311
|
["EV_REFRESH", 0],
|
|
1126
1312
|
["EV_SHOW", 0],
|
|
@@ -1161,6 +1347,12 @@ function register_c_yui_json()
|
|
|
1161
1347
|
if(gclass_find_by_name(GCLASS_NAME, false)) {
|
|
1162
1348
|
return 0;
|
|
1163
1349
|
}
|
|
1350
|
+
/* The graph view hosts a C_YUI_JSON_GRAPH child: make sure its
|
|
1351
|
+
* gclass exists even if the app never registered it (same
|
|
1352
|
+
* arrangement C_YUI_TREEDB_TOPIC_WITH_FORM makes for this one). */
|
|
1353
|
+
if(!gclass_find_by_name("C_YUI_JSON_GRAPH", false)) {
|
|
1354
|
+
register_c_yui_json_graph();
|
|
1355
|
+
}
|
|
1164
1356
|
return create_gclass(GCLASS_NAME);
|
|
1165
1357
|
}
|
|
1166
1358
|
|
package/src/c_yui_json_graph.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
sdata_flag_t,
|
|
16
16
|
event_flag_t,
|
|
17
17
|
gclass_create,
|
|
18
|
+
gclass_find_by_name,
|
|
18
19
|
log_error,
|
|
19
20
|
gobj_read_pointer_attr,
|
|
20
21
|
gobj_parent,
|
|
@@ -1109,6 +1110,13 @@ function create_gclass(gclass_name)
|
|
|
1109
1110
|
***************************************************************/
|
|
1110
1111
|
function register_c_yui_json_graph()
|
|
1111
1112
|
{
|
|
1113
|
+
/* Idempotent: C_YUI_JSON auto-registers this gclass for its graph
|
|
1114
|
+
* view, so an app that also registers it explicitly (in either
|
|
1115
|
+
* order) must not trip "GClass ALREADY created". Same arrangement
|
|
1116
|
+
* C_YUI_JSON itself makes for C_YUI_FORM's hosts. */
|
|
1117
|
+
if(gclass_find_by_name(GCLASS_NAME, false)) {
|
|
1118
|
+
return 0;
|
|
1119
|
+
}
|
|
1112
1120
|
return create_gclass(GCLASS_NAME);
|
|
1113
1121
|
}
|
|
1114
1122
|
|