@yuneta/gobj-ui 7.23.159 → 7.23.161
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 +35 -0
- package/dist/gobj-ui.cjs.js +225 -47
- package/dist/gobj-ui.es.js +225 -47
- package/package.json +1 -1
- package/src/c_yui_treedb_schema.js +134 -10
- package/src/c_yui_treedb_topic_with_form.js +10 -4
- package/src/yui_graph_camera.js +64 -0
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
* via the host-supplied `node_route`). An alternate landing to the
|
|
26
26
|
* topic cards, in the spirit of "every treedb is a graph".
|
|
27
27
|
*
|
|
28
|
+
* The CAMERA is the family's (yui_graph_camera.js): the same
|
|
29
|
+
* toolbar cluster -- zoom in/out, readout, fit, 1:1 -- and the
|
|
30
|
+
* same wheel: it SCROLLS, and Ctrl + wheel zooms, over the cards
|
|
31
|
+
* too (yui_graph_forward_wheel). This view zoomed on a bare wheel
|
|
32
|
+
* and had no toolbar, beside graphs that did neither.
|
|
33
|
+
*
|
|
28
34
|
* Copyright (c) 2026, ArtGins.
|
|
29
35
|
* All Rights Reserved.
|
|
30
36
|
***********************************************************************/
|
|
@@ -49,10 +55,20 @@ import {
|
|
|
49
55
|
gobj_read_str_attr,
|
|
50
56
|
is_object,
|
|
51
57
|
escapeHtml,
|
|
58
|
+
refresh_language,
|
|
52
59
|
} from "@yuneta/gobj-js";
|
|
53
60
|
|
|
54
61
|
import {Graph, NodeEvent} from "@antv/g6";
|
|
55
62
|
|
|
63
|
+
import {yui_toolbar} from "./yui_toolbar.js";
|
|
64
|
+
import {
|
|
65
|
+
yui_graph_camera_items,
|
|
66
|
+
yui_graph_camera_behaviors,
|
|
67
|
+
yui_graph_forward_wheel,
|
|
68
|
+
yui_graph_update_zoom,
|
|
69
|
+
} from "./yui_graph_camera.js";
|
|
70
|
+
import {ensure_pinch_zoom_patch} from "./g6_touch_gestures.js";
|
|
71
|
+
|
|
56
72
|
import {getStrokeColor} from "./lib_graph.js";
|
|
57
73
|
|
|
58
74
|
import {yui_is_dark, yui_watch_theme} from "./yui_theme.js";
|
|
@@ -98,14 +114,17 @@ SDATA(data_type_t.DTP_JSON, "descs", 0, null, "Treedb schema: {top
|
|
|
98
114
|
SDATA(data_type_t.DTP_STRING, "node_route", 0, "", "Hash-route template with a {topic} placeholder: a node click opens that topic (e.g. '#/topics/db/<sel>/{topic}')"),
|
|
99
115
|
SDATA(data_type_t.DTP_BOOLEAN, "with_node_click", 0, false, "With no `node_route`, PUBLISH a node click as EV_NODE_CLICK instead of dropping it. Opt-in, because a subscriber has to DECLARE the event: the hosts that route by hash, and the ones that want nothing, must not have it appear underneath them"),
|
|
100
116
|
SDATA(data_type_t.DTP_BOOLEAN, "system", 0, false, "Include system topics (__*__) too"),
|
|
117
|
+
SDATA(data_type_t.DTP_STRING, "wide", 0, "40px", "Height of the toolbar buttons: the family's (C_YUI_TREEDB_GRAPH, C_YUI_JSON_GRAPH, C_YUI_GOBJ_TREE_JS)"),
|
|
101
118
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTML element"),
|
|
102
119
|
SDATA_END()
|
|
103
120
|
];
|
|
104
121
|
|
|
105
122
|
let PRIVATE_DATA = {
|
|
106
123
|
$container: null,
|
|
124
|
+
$canvas: null, // the element G6 is mounted in, under the toolbar
|
|
107
125
|
graph: null,
|
|
108
126
|
theme_observer: null, // MutationObserver on <html data-theme>
|
|
127
|
+
unforward_wheel: null, // removes the wheel forwarding of the cards
|
|
109
128
|
};
|
|
110
129
|
|
|
111
130
|
let __gclass__ = null;
|
|
@@ -122,6 +141,9 @@ let __gclass__ = null;
|
|
|
122
141
|
|
|
123
142
|
function mt_create(gobj)
|
|
124
143
|
{
|
|
144
|
+
/* Pinch to zoom on a touch screen, as in the other graphs. */
|
|
145
|
+
ensure_pinch_zoom_patch();
|
|
146
|
+
|
|
125
147
|
build_ui(gobj);
|
|
126
148
|
|
|
127
149
|
/*
|
|
@@ -179,9 +201,31 @@ function mt_destroy(gobj)
|
|
|
179
201
|
************************************************************/
|
|
180
202
|
function build_ui(gobj)
|
|
181
203
|
{
|
|
204
|
+
let priv = gobj.priv;
|
|
205
|
+
let wide = gobj_read_str_attr(gobj, "wide");
|
|
206
|
+
|
|
207
|
+
/* The camera cluster and nothing else: this view has no folding,
|
|
208
|
+
* no layouts and no find -- a schema is a handful of cards. */
|
|
209
|
+
const $toolbar = yui_toolbar({}, [
|
|
210
|
+
['div', {class: 'yui-horizontal-toolbar-section left'}, []],
|
|
211
|
+
['div', {class: 'yui-horizontal-toolbar-section center'},
|
|
212
|
+
yui_graph_camera_items(gobj, null, wide)],
|
|
213
|
+
['div', {class: 'yui-horizontal-toolbar-section right'}, []],
|
|
214
|
+
]);
|
|
215
|
+
refresh_language($toolbar, t);
|
|
216
|
+
|
|
217
|
+
priv.$canvas = createElement2(
|
|
218
|
+
['div', {class: 'TREEDB_SCHEMA_CANVAS',
|
|
219
|
+
style: 'position:relative; flex:1 1 auto; min-height:0;'}, []]
|
|
220
|
+
);
|
|
221
|
+
|
|
182
222
|
let $container = createElement2(
|
|
183
223
|
['div', {class: 'C_YUI_TREEDB_SCHEMA TREEDB_SCHEMA_VIEW',
|
|
184
|
-
style: 'position:relative; height:100%; min-height:0;'
|
|
224
|
+
style: 'position:relative; height:100%; min-height:0; ' +
|
|
225
|
+
'display:flex; flex-direction:column;'}, [
|
|
226
|
+
['div', {class: 'TREEDB_SCHEMA_TOOLBAR', style: 'flex:0 0 auto;'}, [$toolbar]],
|
|
227
|
+
priv.$canvas
|
|
228
|
+
]]
|
|
185
229
|
);
|
|
186
230
|
gobj_write_attr(gobj, "$container", $container);
|
|
187
231
|
}
|
|
@@ -550,7 +594,8 @@ function build_graph(gobj)
|
|
|
550
594
|
{
|
|
551
595
|
let priv = gobj.priv;
|
|
552
596
|
let $container = gobj_read_attr(gobj, "$container");
|
|
553
|
-
if(!$container) {
|
|
597
|
+
if(!$container || !priv.$canvas) {
|
|
598
|
+
log_error(`${gobj_short_name(gobj)}: no UI to draw the schema in`);
|
|
554
599
|
return;
|
|
555
600
|
}
|
|
556
601
|
destroy_graph(gobj);
|
|
@@ -561,7 +606,7 @@ function build_graph(gobj)
|
|
|
561
606
|
['div', {class: 'TREEDB_SCHEMA_EMPTY p-4 yui-text-quiet',
|
|
562
607
|
i18n: 'no topics'}, t('no topics', {defaultValue: 'No topics'})]
|
|
563
608
|
);
|
|
564
|
-
|
|
609
|
+
priv.$canvas.appendChild($empty);
|
|
565
610
|
return;
|
|
566
611
|
}
|
|
567
612
|
|
|
@@ -569,8 +614,9 @@ function build_graph(gobj)
|
|
|
569
614
|
let graph;
|
|
570
615
|
try {
|
|
571
616
|
graph = new Graph({
|
|
572
|
-
container:
|
|
617
|
+
container: priv.$canvas,
|
|
573
618
|
autoResize: true,
|
|
619
|
+
animation: false,
|
|
574
620
|
data: data,
|
|
575
621
|
edge: {
|
|
576
622
|
style: edge_style(dark),
|
|
@@ -581,7 +627,8 @@ function build_graph(gobj)
|
|
|
581
627
|
nodesep: 28,
|
|
582
628
|
ranksep: 90,
|
|
583
629
|
},
|
|
584
|
-
|
|
630
|
+
/* The family's camera: the wheel scrolls, Ctrl + wheel zooms. */
|
|
631
|
+
behaviors: ["drag-canvas", ...yui_graph_camera_behaviors(), "drag-element"],
|
|
585
632
|
});
|
|
586
633
|
} catch(e) {
|
|
587
634
|
log_error(`${gobj_short_name(gobj)}: schema graph create failed: ${e}`);
|
|
@@ -591,6 +638,16 @@ function build_graph(gobj)
|
|
|
591
638
|
|
|
592
639
|
graph.setTheme(dark ? "dark" : "light");
|
|
593
640
|
|
|
641
|
+
/* The readout follows ANY camera change, the wheel included -- a
|
|
642
|
+
* wheel notch passes through no action of ours. */
|
|
643
|
+
graph.on('aftertransform', () => {
|
|
644
|
+
yui_graph_update_zoom($container, priv.graph);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
/* The cards cover most of this drawing, and a card does not pass
|
|
648
|
+
* the wheel on: without this it works only between them. */
|
|
649
|
+
priv.unforward_wheel = yui_graph_forward_wheel(graph, priv.$canvas);
|
|
650
|
+
|
|
594
651
|
/* A node click opens that topic — a real hash navigation, so it is
|
|
595
652
|
* deep-linkable and Back-friendly like the cards. Crosses the FSM. */
|
|
596
653
|
graph.on(NodeEvent.CLICK, (evt) => {
|
|
@@ -601,7 +658,9 @@ function build_graph(gobj)
|
|
|
601
658
|
/* No fitView: the diagram is drawn at its own scale and stays there.
|
|
602
659
|
* Zooming it to the container the moment it appears rewrites the size
|
|
603
660
|
* the reader just saw, and does it differently for every treedb. */
|
|
604
|
-
graph.render().
|
|
661
|
+
graph.render().then(() => {
|
|
662
|
+
yui_graph_update_zoom($container, priv.graph);
|
|
663
|
+
}).catch((e) => {
|
|
605
664
|
log_error(`${gobj_short_name(gobj)}: schema graph render failed: ${e}`);
|
|
606
665
|
});
|
|
607
666
|
}
|
|
@@ -612,6 +671,10 @@ function build_graph(gobj)
|
|
|
612
671
|
function destroy_graph(gobj)
|
|
613
672
|
{
|
|
614
673
|
let priv = gobj.priv;
|
|
674
|
+
if(priv.unforward_wheel) {
|
|
675
|
+
priv.unforward_wheel();
|
|
676
|
+
priv.unforward_wheel = null;
|
|
677
|
+
}
|
|
615
678
|
if(priv.graph) {
|
|
616
679
|
try {
|
|
617
680
|
priv.graph.destroy();
|
|
@@ -620,10 +683,10 @@ function destroy_graph(gobj)
|
|
|
620
683
|
}
|
|
621
684
|
priv.graph = null;
|
|
622
685
|
}
|
|
623
|
-
|
|
624
|
-
if(
|
|
625
|
-
while(
|
|
626
|
-
|
|
686
|
+
/* The CANVAS only: the toolbar is built once and stays. */
|
|
687
|
+
if(priv.$canvas) {
|
|
688
|
+
while(priv.$canvas.firstChild) {
|
|
689
|
+
priv.$canvas.removeChild(priv.$canvas.firstChild);
|
|
627
690
|
}
|
|
628
691
|
}
|
|
629
692
|
}
|
|
@@ -753,6 +816,59 @@ function ac_theme(gobj, event, kw, src)
|
|
|
753
816
|
return 0;
|
|
754
817
|
}
|
|
755
818
|
|
|
819
|
+
/************************************************************
|
|
820
|
+
* The camera, from the toolbar. Same moves as the family's
|
|
821
|
+
* (C_YUI_GOBJ_TREE_JS): +/- 20%, fit, and actual size at the
|
|
822
|
+
* layout's origin.
|
|
823
|
+
************************************************************/
|
|
824
|
+
function camera_move(gobj, what, promise)
|
|
825
|
+
{
|
|
826
|
+
Promise.resolve(promise).then(() => {
|
|
827
|
+
yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), gobj.priv.graph);
|
|
828
|
+
}).catch((e) => {
|
|
829
|
+
log_error(`${gobj_short_name(gobj)}: schema graph ${what} failed: ${e}`);
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function ac_zoom_in(gobj, event, kw, src)
|
|
834
|
+
{
|
|
835
|
+
let graph = gobj.priv.graph;
|
|
836
|
+
if(graph) {
|
|
837
|
+
camera_move(gobj, "zoom in", graph.zoomTo(graph.getZoom() * 1.2));
|
|
838
|
+
}
|
|
839
|
+
return 0;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function ac_zoom_out(gobj, event, kw, src)
|
|
843
|
+
{
|
|
844
|
+
let graph = gobj.priv.graph;
|
|
845
|
+
if(graph) {
|
|
846
|
+
camera_move(gobj, "zoom out", graph.zoomTo(graph.getZoom() * 0.8));
|
|
847
|
+
}
|
|
848
|
+
return 0;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function ac_zoom_reset(gobj, event, kw, src)
|
|
852
|
+
{
|
|
853
|
+
let graph = gobj.priv.graph;
|
|
854
|
+
if(graph && graph.rendered) {
|
|
855
|
+
camera_move(gobj, "actual size",
|
|
856
|
+
Promise.resolve(graph.zoomTo(1)).then(() => graph.translateTo([0, 0])));
|
|
857
|
+
}
|
|
858
|
+
return 0;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/* A fit ASKED for is fine: what this view refuses is fitting on its
|
|
862
|
+
* own, the moment it appears (see build_graph). */
|
|
863
|
+
function ac_center(gobj, event, kw, src)
|
|
864
|
+
{
|
|
865
|
+
let graph = gobj.priv.graph;
|
|
866
|
+
if(graph) {
|
|
867
|
+
camera_move(gobj, "fit", graph.fitView());
|
|
868
|
+
}
|
|
869
|
+
return 0;
|
|
870
|
+
}
|
|
871
|
+
|
|
756
872
|
/************************************************************
|
|
757
873
|
* Rebuild from a fresh schema (descs arrived / changed).
|
|
758
874
|
************************************************************/
|
|
@@ -795,6 +911,10 @@ function create_gclass(gclass_name)
|
|
|
795
911
|
["EV_SHOW", ac_show, null],
|
|
796
912
|
["EV_THEME", ac_theme, null],
|
|
797
913
|
["EV_REBUILD", ac_rebuild, null],
|
|
914
|
+
["EV_ZOOM_IN", ac_zoom_in, null],
|
|
915
|
+
["EV_ZOOM_OUT", ac_zoom_out, null],
|
|
916
|
+
["EV_ZOOM_RESET", ac_zoom_reset, null],
|
|
917
|
+
["EV_CENTER", ac_center, null],
|
|
798
918
|
]]
|
|
799
919
|
];
|
|
800
920
|
|
|
@@ -805,6 +925,10 @@ function create_gclass(gclass_name)
|
|
|
805
925
|
["EV_SHOW", 0],
|
|
806
926
|
["EV_THEME", 0],
|
|
807
927
|
["EV_REBUILD", 0],
|
|
928
|
+
["EV_ZOOM_IN", 0],
|
|
929
|
+
["EV_ZOOM_OUT", 0],
|
|
930
|
+
["EV_ZOOM_RESET", 0],
|
|
931
|
+
["EV_CENTER", 0],
|
|
808
932
|
];
|
|
809
933
|
|
|
810
934
|
__gclass__ = gclass_create(
|
|
@@ -2928,13 +2928,19 @@ function show_edit_form(gobj, row, index)
|
|
|
2928
2928
|
* Does a click on this column already mean something else?
|
|
2929
2929
|
*
|
|
2930
2930
|
* The `_` columns are the table's own scaffolding (the selection
|
|
2931
|
-
* checkbox, the Op buttons); a hook opens its children
|
|
2932
|
-
* holding a json document opens it in a viewer
|
|
2933
|
-
*
|
|
2931
|
+
* checkbox, the Op buttons); a hook opens its children, a col
|
|
2932
|
+
* holding a json document opens it in a viewer, and a `file` col
|
|
2933
|
+
* opens its asset in a popup. Anything else is plain data, and a
|
|
2934
|
+
* click on it means "show me this record".
|
|
2935
|
+
*
|
|
2936
|
+
* `file` was missing when its popup arrived (7.23.159), so a click
|
|
2937
|
+
* on a photo opened the popup AND the record on top of it: Tabulator
|
|
2938
|
+
* fires `rowClick` from its own dispatcher, and the cell's
|
|
2939
|
+
* stopPropagation() does not reach it.
|
|
2934
2940
|
************************************************************/
|
|
2935
2941
|
const CELL_WITH_ACTION = [
|
|
2936
2942
|
"hook", "object", "dict", "template", "array", "list",
|
|
2937
|
-
"coordinates", "blob", "gbuffer"
|
|
2943
|
+
"coordinates", "blob", "gbuffer", "file"
|
|
2938
2944
|
];
|
|
2939
2945
|
|
|
2940
2946
|
function cell_has_its_own_action(gobj, field)
|
package/src/yui_graph_camera.js
CHANGED
|
@@ -149,6 +149,70 @@ export function yui_graph_camera_behaviors()
|
|
|
149
149
|
];
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/************************************************************
|
|
153
|
+
* Make the wheel work over HTML nodes too.
|
|
154
|
+
*
|
|
155
|
+
* G6's HTML nodes live in a DOM layer BESIDE the canvas
|
|
156
|
+
* element, not in it, and they forward six pointer events and
|
|
157
|
+
* not the wheel. `scroll-canvas` listens on the canvas element
|
|
158
|
+
* and `zoom-canvas` reads G's own wheel event, which is fed
|
|
159
|
+
* from that same element -- so a wheel over a card reaches
|
|
160
|
+
* neither, and on a graph drawn with big cards the wheel only
|
|
161
|
+
* works in the gaps between them.
|
|
162
|
+
*
|
|
163
|
+
* The wheel is caught on `$host` (the element G6 was mounted
|
|
164
|
+
* in) and dispatched again ON the canvas element, carrying the
|
|
165
|
+
* deltas, the position and the modifiers: both behaviors then
|
|
166
|
+
* take it exactly as a wheel over empty canvas. Control is
|
|
167
|
+
* read by G6 from the real keydown, which is still held.
|
|
168
|
+
*
|
|
169
|
+
* A wheel whose target is the canvas passes untouched, which
|
|
170
|
+
* is also what keeps the re-dispatched one from looping. An
|
|
171
|
+
* element that must scroll by itself (a popover) stops the
|
|
172
|
+
* wheel before it gets here.
|
|
173
|
+
*
|
|
174
|
+
* Returns the function that removes the listener.
|
|
175
|
+
************************************************************/
|
|
176
|
+
export function yui_graph_forward_wheel(graph, $host)
|
|
177
|
+
{
|
|
178
|
+
if(!graph || !$host) {
|
|
179
|
+
return () => {};
|
|
180
|
+
}
|
|
181
|
+
const on_wheel = (e) => {
|
|
182
|
+
let dom = null;
|
|
183
|
+
try {
|
|
184
|
+
dom = graph.getCanvas().getContextService().getDomElement();
|
|
185
|
+
} catch(err) {
|
|
186
|
+
return; /* the graph is gone: nothing to hand it to */
|
|
187
|
+
}
|
|
188
|
+
if(!dom || e.target === dom) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
e.stopPropagation();
|
|
193
|
+
dom.dispatchEvent(new WheelEvent("wheel", {
|
|
194
|
+
deltaX: e.deltaX,
|
|
195
|
+
deltaY: e.deltaY,
|
|
196
|
+
deltaZ: e.deltaZ,
|
|
197
|
+
deltaMode: e.deltaMode,
|
|
198
|
+
clientX: e.clientX,
|
|
199
|
+
clientY: e.clientY,
|
|
200
|
+
screenX: e.screenX,
|
|
201
|
+
screenY: e.screenY,
|
|
202
|
+
ctrlKey: e.ctrlKey,
|
|
203
|
+
shiftKey: e.shiftKey,
|
|
204
|
+
altKey: e.altKey,
|
|
205
|
+
metaKey: e.metaKey,
|
|
206
|
+
bubbles: true,
|
|
207
|
+
cancelable: true,
|
|
208
|
+
}));
|
|
209
|
+
};
|
|
210
|
+
$host.addEventListener("wheel", on_wheel, {passive: false});
|
|
211
|
+
return () => {
|
|
212
|
+
$host.removeEventListener("wheel", on_wheel);
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
152
216
|
/************************************************************
|
|
153
217
|
* The camera cluster: zoom in, zoom out, readout, fit,
|
|
154
218
|
* actual size. Returns createElement2 specs, in order.
|