@yuneta/gobj-ui 7.23.1 → 7.23.3
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 +20 -2
- package/dist/gobj-ui.cjs.js +356 -75
- package/dist/gobj-ui.es.js +356 -75
- package/package.json +1 -1
- package/src/c_yui_json.js +15 -1
- package/src/c_yui_json_graph.js +249 -31
- package/src/c_yui_treedb_topic_with_form.js +152 -10
- package/src/yui_icons.css +10 -0
package/src/c_yui_json_graph.js
CHANGED
|
@@ -74,6 +74,73 @@ const GCLASS_NAME = "C_YUI_JSON_GRAPH";
|
|
|
74
74
|
*/
|
|
75
75
|
const FOLD_SWALLOWED_EVENTS = ["pointerdown", "pointerup", "mousedown", "click"];
|
|
76
76
|
|
|
77
|
+
/*
|
|
78
|
+
* Layouts, same shape the gobj tree's registry uses
|
|
79
|
+
* (`c_yui_gobj_tree_js.js`): a G6 layout config plus the edge type
|
|
80
|
+
* that matches its direction — a vertical layout with horizontal
|
|
81
|
+
* cubics draws every edge going the wrong way round.
|
|
82
|
+
*
|
|
83
|
+
* `json-tree` is this gclass's own layout, the one it always had, and
|
|
84
|
+
* it stays the default: it sizes each level from the real card widths,
|
|
85
|
+
* which a generic layout cannot do with `html` nodes. The dagre pair
|
|
86
|
+
* is there for the documents it does badly — a wide, shallow object
|
|
87
|
+
* reads better left→right, which is also the house direction for a
|
|
88
|
+
* topology.
|
|
89
|
+
*/
|
|
90
|
+
const LAYOUTS = {
|
|
91
|
+
"json-tree": {
|
|
92
|
+
label: "vertical tree",
|
|
93
|
+
g6_layout: {type: 'json-tree-layout'},
|
|
94
|
+
edge_type: 'cubic-vertical',
|
|
95
|
+
},
|
|
96
|
+
"dagre-tb": {
|
|
97
|
+
label: "dagre top-down",
|
|
98
|
+
g6_layout: {type: 'antv-dagre', rankdir: 'TB', nodesep: 20, ranksep: 40},
|
|
99
|
+
edge_type: 'polyline',
|
|
100
|
+
},
|
|
101
|
+
"dagre-lr": {
|
|
102
|
+
label: "dagre left-right",
|
|
103
|
+
g6_layout: {type: 'antv-dagre', rankdir: 'LR', nodesep: 20, ranksep: 40},
|
|
104
|
+
edge_type: 'polyline',
|
|
105
|
+
},
|
|
106
|
+
/* `compact-box` is NOT here on purpose: it lays out from a fixed
|
|
107
|
+
* node size, and an `html` card is nothing like it — every card
|
|
108
|
+
* landed on top of the next. A layout that draws the document
|
|
109
|
+
* unreadable is worse than one option fewer. dagre reads the real
|
|
110
|
+
* sizes, which is why both of its directions are here. */
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const DEFAULT_LAYOUT = "json-tree";
|
|
114
|
+
|
|
115
|
+
function get_layout_cfg(key)
|
|
116
|
+
{
|
|
117
|
+
return LAYOUTS[key] || LAYOUTS[DEFAULT_LAYOUT];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/************************************************************
|
|
121
|
+
* A layout's label, with every key SPELLED OUT inside t().
|
|
122
|
+
*
|
|
123
|
+
* Never t(LAYOUTS[key].label) nor a `data-i18n` fed from that
|
|
124
|
+
* table: the apps' validate-locales scans for t("literal"),
|
|
125
|
+
* so a key reached through a variable is a key it cannot
|
|
126
|
+
* demand — and i18next answers an undefined key with the key
|
|
127
|
+
* itself, which renders in lower-case English and never
|
|
128
|
+
* changes language. Twice already in this component's
|
|
129
|
+
* history (7.20.0 `tree view`, and this table on its first
|
|
130
|
+
* draft).
|
|
131
|
+
************************************************************/
|
|
132
|
+
function layout_label(key)
|
|
133
|
+
{
|
|
134
|
+
switch(key) {
|
|
135
|
+
case "dagre-tb":
|
|
136
|
+
return t("dagre top-down");
|
|
137
|
+
case "dagre-lr":
|
|
138
|
+
return t("dagre left-right");
|
|
139
|
+
default:
|
|
140
|
+
return t("vertical tree");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
77
144
|
/***************************************************************
|
|
78
145
|
* Data
|
|
79
146
|
***************************************************************/
|
|
@@ -91,6 +158,7 @@ SDATA(data_type_t.DTP_STRING, "canvas_id", 0, "", "Canvas ID"),
|
|
|
91
158
|
|
|
92
159
|
/*---------------- Graph Settings ----------------*/
|
|
93
160
|
SDATA(data_type_t.DTP_STRING, "wide", 0, "36px", "Height of header"),
|
|
161
|
+
SDATA(data_type_t.DTP_STRING, "layout", 0, "json-tree", "Layout key (see LAYOUTS)"),
|
|
94
162
|
|
|
95
163
|
SDATA_END()
|
|
96
164
|
];
|
|
@@ -110,6 +178,8 @@ let PRIVATE_DATA = {
|
|
|
110
178
|
match_count: 0, // matches of the last find, for the count chip
|
|
111
179
|
find_timer: null, // rate-limits the find box
|
|
112
180
|
fold_listener: null, // delegated click on the card fold handles
|
|
181
|
+
$layout_select: null, // the layout picker
|
|
182
|
+
$zoom_readout: null, // the zoom percentage
|
|
113
183
|
$find_input: null,
|
|
114
184
|
$find_result: null,
|
|
115
185
|
$find_count: null,
|
|
@@ -351,6 +421,7 @@ function build_ui(gobj)
|
|
|
351
421
|
log_error(`${gobj_short_name(gobj)}: no canvas mount, fold handles are dead`);
|
|
352
422
|
}
|
|
353
423
|
|
|
424
|
+
priv.$layout_select = $container.querySelector('.JSON_GRAPH_LAYOUT');
|
|
354
425
|
priv.$find_input = $container.querySelector('.JSON_GRAPH_FIND_INPUT');
|
|
355
426
|
priv.$find_result = $container.querySelector('.JSON_GRAPH_FIND_RESULT');
|
|
356
427
|
priv.$find_count = $container.querySelector('.JSON_GRAPH_FIND_COUNT');
|
|
@@ -384,6 +455,53 @@ function destroy_ui(gobj)
|
|
|
384
455
|
}
|
|
385
456
|
}
|
|
386
457
|
|
|
458
|
+
/************************************************************
|
|
459
|
+
* One camera button.
|
|
460
|
+
************************************************************/
|
|
461
|
+
function camera_button(gobj, icon, event_name, label_key, wide)
|
|
462
|
+
{
|
|
463
|
+
return ['button', {class: `button ${event_name}`, type: 'button',
|
|
464
|
+
style: {height: wide, width: '2.5em'},
|
|
465
|
+
title: t(label_key), 'data-i18n-title': label_key,
|
|
466
|
+
'aria-label': t(label_key), 'data-i18n-aria-label': label_key},
|
|
467
|
+
['i', {style: 'font-size:1.5em; color:inherit;', class: icon}],
|
|
468
|
+
{
|
|
469
|
+
click: (evt) => {
|
|
470
|
+
evt.stopPropagation();
|
|
471
|
+
gobj_send_event(gobj, event_name, {evt: evt}, gobj);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/************************************************************
|
|
478
|
+
* The zoom, as a percentage. What the two magnifiers
|
|
479
|
+
* change: an editor that offers a zoom shows the number.
|
|
480
|
+
************************************************************/
|
|
481
|
+
function zoom_percent_text(gobj)
|
|
482
|
+
{
|
|
483
|
+
let graph = gobj.priv.graph;
|
|
484
|
+
if(!graph) {
|
|
485
|
+
return "100%";
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
return Math.round(graph.getZoom() * 100) + "%";
|
|
489
|
+
} catch(e) {
|
|
490
|
+
return "100%";
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/************************************************************
|
|
495
|
+
* Repaint the readout after anything that moves the camera.
|
|
496
|
+
************************************************************/
|
|
497
|
+
function update_zoom_readout(gobj)
|
|
498
|
+
{
|
|
499
|
+
let priv = gobj.priv;
|
|
500
|
+
if(priv.$zoom_readout) {
|
|
501
|
+
priv.$zoom_readout.textContent = zoom_percent_text(gobj);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
387
505
|
/************************************************************
|
|
388
506
|
* One fold button. `open` rotates the chevron to the open
|
|
389
507
|
* state, mirroring the per-node toggle of the lazy tree.
|
|
@@ -474,39 +592,56 @@ function make_toolbar(gobj)
|
|
|
474
592
|
];
|
|
475
593
|
let center_items = [];
|
|
476
594
|
|
|
595
|
+
/*
|
|
596
|
+
* The camera, in the SAME vocabulary the treedb graph uses
|
|
597
|
+
* (`c_g6_nodes_tree.js`): zoom in / zoom out, then what those two
|
|
598
|
+
* change — the zoom READOUT, because without it "1:1" is a jump to
|
|
599
|
+
* a value nobody was told — then fit, then actual size.
|
|
600
|
+
*
|
|
601
|
+
* Two of these used to be a different picture for the same action
|
|
602
|
+
* in a sibling view, which is the worst thing an icon can be:
|
|
603
|
+
* `EV_ZOOM_RESET` was a bare magnifier and `EV_CENTER` was
|
|
604
|
+
* `yi-arrows-to-eye`. Now fit is the same bracket glyph as the
|
|
605
|
+
* sprite's `g6-icon-fit`, and actual size is WRITTEN — `1:1` is
|
|
606
|
+
* written in every editor that offers it, never drawn, because
|
|
607
|
+
* there is no glyph for it.
|
|
608
|
+
*/
|
|
477
609
|
let c_icons = [
|
|
478
|
-
["yi-magnifying-glass-plus",
|
|
479
|
-
["yi-magnifying-glass",
|
|
480
|
-
["yi-magnifying-glass-minus","EV_ZOOM_OUT", false, 'i'],
|
|
481
|
-
["yi-arrows-to-eye", "EV_CENTER", false, 'i'],
|
|
482
|
-
["yi-arrows-rotate", "EV_REFRESH", false, 'i'],
|
|
610
|
+
["yi-magnifying-glass-plus", "EV_ZOOM_IN", "zoom in"],
|
|
611
|
+
["yi-magnifying-glass-minus", "EV_ZOOM_OUT", "zoom out"],
|
|
483
612
|
];
|
|
484
613
|
|
|
485
614
|
for(let item of c_icons) {
|
|
486
|
-
|
|
487
|
-
let event_name = item[1];
|
|
488
|
-
let disabled = item[2];
|
|
489
|
-
|
|
490
|
-
let button = {
|
|
491
|
-
class: `button ${event_name}`,
|
|
492
|
-
style: {height: toolbar_wide, width: '2.5em'}
|
|
493
|
-
};
|
|
494
|
-
if(disabled) {
|
|
495
|
-
button.disabled = true;
|
|
496
|
-
}
|
|
497
|
-
center_items.push(
|
|
498
|
-
['button', button,
|
|
499
|
-
['i', {style: 'font-size:1.5em; color:inherit;', class: icon_name}],
|
|
500
|
-
{
|
|
501
|
-
click: (evt) => {
|
|
502
|
-
evt.stopPropagation();
|
|
503
|
-
gobj_send_event(gobj, event_name, {evt: evt}, gobj);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
]
|
|
507
|
-
);
|
|
615
|
+
center_items.push(camera_button(gobj, item[0], item[1], item[2], toolbar_wide));
|
|
508
616
|
}
|
|
509
617
|
|
|
618
|
+
priv.$zoom_readout = createElement2(
|
|
619
|
+
['span', {class: 'JSON_GRAPH_ZOOM_LEVEL is-flex is-align-items-center px-2 has-text-grey',
|
|
620
|
+
style: 'font-size:.85rem; min-width:3.5em; justify-content:center;',
|
|
621
|
+
title: t('zoom level'), 'data-i18n-title': 'zoom level'},
|
|
622
|
+
zoom_percent_text(gobj)]
|
|
623
|
+
);
|
|
624
|
+
center_items.push(priv.$zoom_readout);
|
|
625
|
+
|
|
626
|
+
center_items.push(camera_button(gobj, "yi-fit", "EV_CENTER", "auto fit", toolbar_wide));
|
|
627
|
+
|
|
628
|
+
/* Written, not drawn — see above. */
|
|
629
|
+
center_items.push(
|
|
630
|
+
['button', {class: 'button EV_ZOOM_RESET', type: 'button',
|
|
631
|
+
style: {height: toolbar_wide, width: '2.5em'},
|
|
632
|
+
title: t('actual size'), 'data-i18n-title': 'actual size',
|
|
633
|
+
'aria-label': t('actual size'), 'data-i18n-aria-label': 'actual size'},
|
|
634
|
+
['span', {style: 'font-weight:700;'}, '1:1'],
|
|
635
|
+
{
|
|
636
|
+
click: (evt) => {
|
|
637
|
+
evt.stopPropagation();
|
|
638
|
+
gobj_send_event(gobj, "EV_ZOOM_RESET", {evt: evt}, gobj);
|
|
639
|
+
}
|
|
640
|
+
}]
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
center_items.push(camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", toolbar_wide));
|
|
644
|
+
|
|
510
645
|
/*
|
|
511
646
|
* Right: fold the tree.
|
|
512
647
|
*
|
|
@@ -516,7 +651,38 @@ function make_toolbar(gobj)
|
|
|
516
651
|
* went; "expand all" puts it back. Same chevron as the lazy tree,
|
|
517
652
|
* rotated to point down for the open state.
|
|
518
653
|
*/
|
|
654
|
+
/*
|
|
655
|
+
* A layout picker, same shape as the gobj tree's. One layout
|
|
656
|
+
* cannot serve every document: the built-in one sizes each level
|
|
657
|
+
* from the real card widths, dagre packs a wide shallow object
|
|
658
|
+
* far better, and left→right is the house direction for reading a
|
|
659
|
+
* topology.
|
|
660
|
+
*/
|
|
661
|
+
let current_layout = gobj_read_str_attr(gobj, "layout") || DEFAULT_LAYOUT;
|
|
662
|
+
let options = Object.keys(LAYOUTS).map(function(key) {
|
|
663
|
+
let attrs = {value: key, 'data-i18n': LAYOUTS[key].label};
|
|
664
|
+
if(key === current_layout) {
|
|
665
|
+
attrs.selected = "selected";
|
|
666
|
+
}
|
|
667
|
+
return ['option', attrs, layout_label(key)];
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
let $layout_select = createElement2(
|
|
671
|
+
['select', {
|
|
672
|
+
class: 'JSON_GRAPH_LAYOUT select',
|
|
673
|
+
style: {height: toolbar_wide, "margin-left": "0.5em"},
|
|
674
|
+
title: t("layout"), 'data-i18n-title': "layout",
|
|
675
|
+
'aria-label': t("layout"), 'data-i18n-aria-label': "layout"
|
|
676
|
+
}, options, {
|
|
677
|
+
change: (evt) => {
|
|
678
|
+
evt.stopPropagation();
|
|
679
|
+
gobj_send_event(gobj, "EV_CHANGE_LAYOUT", {layout: evt.target.value}, gobj);
|
|
680
|
+
}
|
|
681
|
+
}]
|
|
682
|
+
);
|
|
683
|
+
|
|
519
684
|
let right_items = [
|
|
685
|
+
$layout_select,
|
|
520
686
|
graph_fold_button(gobj, "EV_EXPAND_ALL", "expand all", true),
|
|
521
687
|
graph_fold_button(gobj, "EV_COLLAPSE_ALL", "collapse all", false),
|
|
522
688
|
];
|
|
@@ -537,6 +703,7 @@ function make_toolbar(gobj)
|
|
|
537
703
|
function build_graph(gobj)
|
|
538
704
|
{
|
|
539
705
|
let priv = gobj.priv;
|
|
706
|
+
let layout_cfg = get_layout_cfg(gobj_read_str_attr(gobj, "layout"));
|
|
540
707
|
|
|
541
708
|
const graph = priv.graph = new Graph({
|
|
542
709
|
container: priv.canvas_id,
|
|
@@ -551,7 +718,7 @@ function build_graph(gobj)
|
|
|
551
718
|
},
|
|
552
719
|
|
|
553
720
|
edge: {
|
|
554
|
-
type:
|
|
721
|
+
type: layout_cfg.edge_type,
|
|
555
722
|
style: {
|
|
556
723
|
stroke: '#999',
|
|
557
724
|
lineWidth: 1,
|
|
@@ -559,9 +726,7 @@ function build_graph(gobj)
|
|
|
559
726
|
},
|
|
560
727
|
},
|
|
561
728
|
|
|
562
|
-
layout:
|
|
563
|
-
type: 'json-tree-layout',
|
|
564
|
-
},
|
|
729
|
+
layout: layout_cfg.g6_layout,
|
|
565
730
|
|
|
566
731
|
behaviors: [
|
|
567
732
|
'drag-canvas',
|
|
@@ -577,6 +742,15 @@ function build_graph(gobj)
|
|
|
577
742
|
gobj_send_event(gobj, "EV_NODE_CLICK", {evt: evt}, gobj);
|
|
578
743
|
});
|
|
579
744
|
|
|
745
|
+
/* ONE hook for the readout, the same one the treedb graph uses:
|
|
746
|
+
* G6 fires it on any camera change, and the WHEEL is a camera
|
|
747
|
+
* change that passes through no action of ours — patching the text
|
|
748
|
+
* node from each zoom action would have left the readout lying
|
|
749
|
+
* after every notch. */
|
|
750
|
+
graph.on('aftertransform', () => {
|
|
751
|
+
update_zoom_readout(gobj);
|
|
752
|
+
});
|
|
753
|
+
|
|
580
754
|
graph.on(CanvasEvent.CLICK, (evt) => {
|
|
581
755
|
// Canvas click
|
|
582
756
|
});
|
|
@@ -971,6 +1145,7 @@ function load_json(gobj)
|
|
|
971
1145
|
if(!preserve_view) {
|
|
972
1146
|
graph.fitView();
|
|
973
1147
|
}
|
|
1148
|
+
update_zoom_readout(gobj);
|
|
974
1149
|
});
|
|
975
1150
|
}
|
|
976
1151
|
}
|
|
@@ -1207,6 +1382,47 @@ function ac_refresh(gobj, event, kw, src)
|
|
|
1207
1382
|
|
|
1208
1383
|
/************************************************************
|
|
1209
1384
|
* {theme: "dark"|"light"} — the app switched theme.
|
|
1385
|
+
* EV_CHANGE_LAYOUT { layout }
|
|
1386
|
+
*
|
|
1387
|
+
* The layout AND the edge type move together — a vertical
|
|
1388
|
+
* layout with horizontal cubics draws every edge the wrong way
|
|
1389
|
+
* round — and then the graph is rebuilt and refitted, because a
|
|
1390
|
+
* new layout is a new shape and the old camera framed the old
|
|
1391
|
+
* one.
|
|
1392
|
+
************************************************************/
|
|
1393
|
+
function ac_change_layout(gobj, event, kw, src)
|
|
1394
|
+
{
|
|
1395
|
+
let priv = gobj.priv;
|
|
1396
|
+
let key = (kw && kw.layout) || "";
|
|
1397
|
+
|
|
1398
|
+
if(!LAYOUTS[key]) {
|
|
1399
|
+
log_error(`${gobj_short_name(gobj)}: unknown layout '${key}'`);
|
|
1400
|
+
return -1;
|
|
1401
|
+
}
|
|
1402
|
+
if(key === gobj_read_str_attr(gobj, "layout")) {
|
|
1403
|
+
return 0;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
gobj_write_str_attr(gobj, "layout", key);
|
|
1407
|
+
|
|
1408
|
+
/* Keep the picker in step when the event came from elsewhere. */
|
|
1409
|
+
if(priv.$layout_select && priv.$layout_select.value !== key) {
|
|
1410
|
+
priv.$layout_select.value = key;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
let cfg = get_layout_cfg(key);
|
|
1414
|
+
if(priv.graph) {
|
|
1415
|
+
priv.graph.setOptions({
|
|
1416
|
+
layout: cfg.g6_layout,
|
|
1417
|
+
edge: {type: cfg.edge_type},
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
refresh_json(gobj);
|
|
1421
|
+
update_find_result(gobj);
|
|
1422
|
+
return 0;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/************************************************************
|
|
1210
1426
|
* EV_TOGGLE_FOLD { path } — fold or unfold ONE card.
|
|
1211
1427
|
*
|
|
1212
1428
|
* The camera does not move: folding one branch is a local
|
|
@@ -1489,6 +1705,7 @@ function create_gclass(gclass_name)
|
|
|
1489
1705
|
["EV_REFRESH", ac_refresh, null],
|
|
1490
1706
|
["EV_FIND_NODES", ac_find_nodes, null],
|
|
1491
1707
|
["EV_TOGGLE_FOLD", ac_toggle_fold, null],
|
|
1708
|
+
["EV_CHANGE_LAYOUT", ac_change_layout, null],
|
|
1492
1709
|
["EV_EXPAND_ALL", ac_expand_all, null],
|
|
1493
1710
|
["EV_COLLAPSE_ALL", ac_collapse_all, null],
|
|
1494
1711
|
["EV_THEME", ac_theme, null],
|
|
@@ -1511,6 +1728,7 @@ function create_gclass(gclass_name)
|
|
|
1511
1728
|
["EV_REFRESH", 0],
|
|
1512
1729
|
["EV_FIND_NODES", 0],
|
|
1513
1730
|
["EV_TOGGLE_FOLD", 0],
|
|
1731
|
+
["EV_CHANGE_LAYOUT", 0],
|
|
1514
1732
|
["EV_EXPAND_ALL", 0],
|
|
1515
1733
|
["EV_COLLAPSE_ALL", 0],
|
|
1516
1734
|
["EV_THEME", 0],
|
|
@@ -42,6 +42,8 @@ import {
|
|
|
42
42
|
kwid_get_ids,
|
|
43
43
|
gobj_create_pure_child,
|
|
44
44
|
gobj_start,
|
|
45
|
+
gobj_create_service,
|
|
46
|
+
gobj_is_destroying,
|
|
45
47
|
gobj_stop,
|
|
46
48
|
gobj_destroy,
|
|
47
49
|
gobj_is_running,
|
|
@@ -170,9 +172,11 @@ let PRIVATE_DATA = {
|
|
|
170
172
|
form: null, // hosted C_YUI_FORM child (while dialog open)
|
|
171
173
|
form_modal: null, // { close } handle of the adaptive dialog
|
|
172
174
|
schema_gobj: null, // hosted C_YUI_JSON child (while schema open)
|
|
173
|
-
schema_modal: null, // { close } handle of the schema dialog
|
|
175
|
+
schema_modal: null, // { close } handle of the schema dialog (phone)
|
|
176
|
+
schema_win: null, // C_YUI_WINDOW presenting it (laptop)
|
|
174
177
|
cell_json_gobj: null, // hosted C_YUI_JSON child (while a cell is open)
|
|
175
|
-
cell_json_modal: null, // { close } handle of the cell dialog
|
|
178
|
+
cell_json_modal: null, // { close } handle of the cell dialog (phone)
|
|
179
|
+
cell_json_win: null, // C_YUI_WINDOW presenting it (laptop)
|
|
176
180
|
};
|
|
177
181
|
|
|
178
182
|
let __gclass__ = null;
|
|
@@ -184,6 +188,120 @@ let __gclass__ = null;
|
|
|
184
188
|
* stacking context below body-level modals). The legacy
|
|
185
189
|
* C_YUI_MAIN "#popup-layer" and document.body (Bulma `.modal` is
|
|
186
190
|
* position:fixed) remain the shell-less fallbacks. */
|
|
191
|
+
/* True on a phone-width viewport (Bulma's mobile breakpoint). */
|
|
192
|
+
function is_mobile()
|
|
193
|
+
{
|
|
194
|
+
return typeof window !== "undefined" && window.innerWidth <= 768;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/************************************************************
|
|
198
|
+
* Present a JSON viewer's $container.
|
|
199
|
+
*
|
|
200
|
+
* On a LAPTOP a floating C_YUI_WINDOW: a JSON document is
|
|
201
|
+
* something you read WHILE looking at the table behind it —
|
|
202
|
+
* you move it, you resize it, you maximise it when the
|
|
203
|
+
* document is big. A modal can do none of that, and it
|
|
204
|
+
* darkens exactly the rows you opened the cell from.
|
|
205
|
+
*
|
|
206
|
+
* On a phone the modal sheet stays: there is no room to
|
|
207
|
+
* arrange anything, and the sheet already carries the shell's
|
|
208
|
+
* Escape / Back handling.
|
|
209
|
+
*
|
|
210
|
+
* Returns {win, modal} — exactly one of them is set.
|
|
211
|
+
************************************************************/
|
|
212
|
+
function present_json_popup(gobj, $box, opts)
|
|
213
|
+
{
|
|
214
|
+
let shell = yui_shell_of(gobj);
|
|
215
|
+
if(!shell) {
|
|
216
|
+
log_error(`${gobj_short_name(gobj)}: no shell, cannot present the json`);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if(is_mobile()) {
|
|
221
|
+
return {
|
|
222
|
+
modal: yui_shell_show_modal(shell, $box, {
|
|
223
|
+
dialog: true,
|
|
224
|
+
logical_class: opts.logical_class + "_SHEET",
|
|
225
|
+
title_prefix: opts.title_prefix,
|
|
226
|
+
title: opts.title,
|
|
227
|
+
t: t,
|
|
228
|
+
on_close: opts.on_close
|
|
229
|
+
}),
|
|
230
|
+
win: null
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let win = gobj_create_service(
|
|
235
|
+
opts.name,
|
|
236
|
+
"C_YUI_WINDOW",
|
|
237
|
+
{
|
|
238
|
+
$parent: popup_mount_layer(gobj),
|
|
239
|
+
subscriber: null,
|
|
240
|
+
modal: false,
|
|
241
|
+
showMax: true,
|
|
242
|
+
showFooter: false,
|
|
243
|
+
resizable: true,
|
|
244
|
+
center: true,
|
|
245
|
+
auto_save_size_and_position: true,
|
|
246
|
+
width: 720,
|
|
247
|
+
height: 620,
|
|
248
|
+
logical_class: opts.logical_class + "_WINDOW",
|
|
249
|
+
title_prefix: opts.title_prefix,
|
|
250
|
+
title: opts.title,
|
|
251
|
+
icon: opts.icon,
|
|
252
|
+
body: $box,
|
|
253
|
+
manager: null,
|
|
254
|
+
/* C_YUI_WINDOW.close_window() calls this and THEN stops and
|
|
255
|
+
* destroys itself, so a teardown that also destroys the
|
|
256
|
+
* window destroys it twice — two "gobj NULL or DESTROYED".
|
|
257
|
+
* `opts.forget_window()` drops the host's reference first;
|
|
258
|
+
* the ✕ path is the window retiring ITSELF. */
|
|
259
|
+
on_close: function() {
|
|
260
|
+
if(gobj_is_destroying(gobj)) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
opts.forget_window();
|
|
264
|
+
opts.on_close();
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
gobj
|
|
268
|
+
);
|
|
269
|
+
if(!win) {
|
|
270
|
+
log_error(`${gobj_short_name(gobj)}: cannot create the json window`);
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
gobj_start(win);
|
|
274
|
+
return {win: win, modal: null};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/************************************************************
|
|
278
|
+
* Retire a presenting window: reference cleared FIRST, then
|
|
279
|
+
* stop, then destroy.
|
|
280
|
+
*
|
|
281
|
+
* The order is the whole content of this function. The ✕ of
|
|
282
|
+
* the window calls back into the teardown that calls this, so
|
|
283
|
+
* clearing the reference first is what stops the recursion.
|
|
284
|
+
* And it is STOP then destroy because the window was started
|
|
285
|
+
* on open: handing a running gobj to gobj_destroy() makes the
|
|
286
|
+
* framework rescue it and say so — "Destroying a RUNNING
|
|
287
|
+
* gobj" — which is a caller's bug, not a framework message.
|
|
288
|
+
* Same lesson `c_tranger_view.close_json_viewer` carries.
|
|
289
|
+
************************************************************/
|
|
290
|
+
function retire_popup_window(win)
|
|
291
|
+
{
|
|
292
|
+
if(!win) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
if(gobj_is_running(win)) {
|
|
297
|
+
gobj_stop(win);
|
|
298
|
+
}
|
|
299
|
+
gobj_destroy(win);
|
|
300
|
+
} catch(e) {
|
|
301
|
+
log_warning(`${GCLASS_NAME}: json window already gone: ${e}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
187
305
|
function popup_mount_layer(gobj)
|
|
188
306
|
{
|
|
189
307
|
let $layer = yui_shell_popup_layer(yui_shell_of(gobj));
|
|
@@ -1864,16 +1982,25 @@ function open_schema_dialog(gobj)
|
|
|
1864
1982
|
/* Title split in two halves: the topic name is DATA (never
|
|
1865
1983
|
* translated) and "schema" is the kind (carries its i18n key), so
|
|
1866
1984
|
* the header re-translates on a language switch. */
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
logical_class: "
|
|
1985
|
+
let presented = present_json_popup(gobj, $box, {
|
|
1986
|
+
name: "schemawin_" + clean_name(gobj_name(gobj)),
|
|
1987
|
+
logical_class: "TREEDB_SCHEMA",
|
|
1870
1988
|
title_prefix: gobj_read_str_attr(gobj, "topic_name"),
|
|
1871
1989
|
title: "schema",
|
|
1872
|
-
|
|
1990
|
+
icon: "yi-table",
|
|
1991
|
+
forget_window: function() {
|
|
1992
|
+
priv.schema_win = null;
|
|
1993
|
+
},
|
|
1873
1994
|
on_close: function() {
|
|
1874
1995
|
teardown_schema_child(gobj);
|
|
1875
1996
|
}
|
|
1876
1997
|
});
|
|
1998
|
+
if(!presented) {
|
|
1999
|
+
teardown_schema_child(gobj); // Error already logged
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
priv.schema_modal = presented.modal;
|
|
2003
|
+
priv.schema_win = presented.win;
|
|
1877
2004
|
|
|
1878
2005
|
/* EV_SET_JSON and not the `json_data` attr: the attr renders the
|
|
1879
2006
|
* tree but leaves the viewer in ST_IDLE, where a click to expand a
|
|
@@ -1896,6 +2023,9 @@ function teardown_schema_child(gobj)
|
|
|
1896
2023
|
gobj_destroy(priv.schema_gobj);
|
|
1897
2024
|
priv.schema_gobj = null;
|
|
1898
2025
|
}
|
|
2026
|
+
let win = priv.schema_win;
|
|
2027
|
+
priv.schema_win = null;
|
|
2028
|
+
retire_popup_window(win);
|
|
1899
2029
|
priv.schema_modal = null;
|
|
1900
2030
|
}
|
|
1901
2031
|
|
|
@@ -1982,16 +2112,25 @@ function open_cell_json_dialog(gobj, row_id, col_id)
|
|
|
1982
2112
|
* i18n key — the shared `col.id` one, so the header re-translates on a
|
|
1983
2113
|
* language switch and reads as the raw id where the app defines no key
|
|
1984
2114
|
* (exactly what an untranslated column header already shows). */
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
logical_class: "
|
|
2115
|
+
let presented = present_json_popup(gobj, $box, {
|
|
2116
|
+
name: "cellwin_" + clean_name(gobj_name(gobj)),
|
|
2117
|
+
logical_class: "TREEDB_CELL_JSON",
|
|
1988
2118
|
title_prefix: String(row_id),
|
|
1989
2119
|
title: col_id,
|
|
1990
|
-
|
|
2120
|
+
icon: "yi-js-square",
|
|
2121
|
+
forget_window: function() {
|
|
2122
|
+
priv.cell_json_win = null;
|
|
2123
|
+
},
|
|
1991
2124
|
on_close: function() {
|
|
1992
2125
|
teardown_cell_json_child(gobj);
|
|
1993
2126
|
}
|
|
1994
2127
|
});
|
|
2128
|
+
if(!presented) {
|
|
2129
|
+
teardown_cell_json_child(gobj); // Error already logged
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
priv.cell_json_modal = presented.modal;
|
|
2133
|
+
priv.cell_json_win = presented.win;
|
|
1995
2134
|
|
|
1996
2135
|
/* EV_SET_JSON and not the `json_data` attr: the attr renders the
|
|
1997
2136
|
* tree but leaves the viewer in ST_IDLE, where a click to expand a
|
|
@@ -2014,6 +2153,9 @@ function teardown_cell_json_child(gobj)
|
|
|
2014
2153
|
gobj_destroy(priv.cell_json_gobj);
|
|
2015
2154
|
priv.cell_json_gobj = null;
|
|
2016
2155
|
}
|
|
2156
|
+
let win = priv.cell_json_win;
|
|
2157
|
+
priv.cell_json_win = null;
|
|
2158
|
+
retire_popup_window(win);
|
|
2017
2159
|
priv.cell_json_modal = null;
|
|
2018
2160
|
}
|
|
2019
2161
|
|
package/src/yui_icons.css
CHANGED
|
@@ -376,3 +376,13 @@
|
|
|
376
376
|
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M208 80c0-26.5 21.5-48 48-48l64 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-8 0 0 40 152 0c30.9 0 56 25.1 56 56l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-4.4-3.6-8-8-8l-152 0 0 40 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-40-152 0c-4.4 0-8 3.6-8 8l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-30.9 25.1-56 56-56l152 0 0-40-8 0c-26.5 0-48-21.5-48-48l0-64z'/%3E%3C/svg%3E");
|
|
377
377
|
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M208 80c0-26.5 21.5-48 48-48l64 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-8 0 0 40 152 0c30.9 0 56 25.1 56 56l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-4.4-3.6-8-8-8l-152 0 0 40 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-40-152 0c-4.4 0-8 3.6-8 8l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-30.9 25.1-56 56-56l152 0 0-40-8 0c-26.5 0-48-21.5-48-48l0-64z'/%3E%3C/svg%3E");
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
/* The camera's "fit" bracket, the SAME glyph the treedb graph's SVG
|
|
381
|
+
* sprite uses for the same action (`g6-icon-fit` in lib_icons.js).
|
|
382
|
+
* One action, one drawing: the JSON graph used to say it with
|
|
383
|
+
* `yi-arrows-to-eye`, which is a different picture for the same
|
|
384
|
+
* button in a sibling view. */
|
|
385
|
+
.yi-fit::before {
|
|
386
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32S0 334.3 0 352l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z'/%3E%3C/svg%3E");
|
|
387
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32S0 334.3 0 352l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z'/%3E%3C/svg%3E");
|
|
388
|
+
}
|