@yuneta/gobj-ui 7.23.29 → 7.23.30
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 +163 -2
- package/dist/gobj-ui.cjs.js +950 -179
- package/dist/gobj-ui.es.js +951 -180
- package/package.json +1 -1
- package/src/c_yui_gobj_tree_js.js +435 -245
- package/src/gclass_describe.js +234 -0
- package/src/gclass_describe.test.js +125 -0
- package/src/gobj_tree_model.js +436 -0
- package/src/gobj_tree_model.test.js +184 -0
- package/src/yui_gclass_view.js +277 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* yui_gclass_view.js
|
|
3
|
+
*
|
|
4
|
+
* The gclass viewer: what a GClass IS, in a window.
|
|
5
|
+
*
|
|
6
|
+
* The framework had no such viewer. The C kernel answers
|
|
7
|
+
* `view-gclass` with a full description (attrs, commands,
|
|
8
|
+
* methods, FSM) and the only reader of it was a terminal.
|
|
9
|
+
* This one draws that same document with C_YUI_JSON, so the
|
|
10
|
+
* FSM of a gclass is a graph and its attrs table is a tree,
|
|
11
|
+
* and it takes the document from either side:
|
|
12
|
+
*
|
|
13
|
+
* - a gclass of the browser yuno -> describe_local_gclass()
|
|
14
|
+
* - a gclass of a backend yuno -> `opts.description`, the
|
|
15
|
+
* answer of `view-gclass`
|
|
16
|
+
*
|
|
17
|
+
* One window at a time per host: the caller keeps the handle
|
|
18
|
+
* and closes it, the way the treedb graph keeps its raw-JSON
|
|
19
|
+
* viewer.
|
|
20
|
+
*
|
|
21
|
+
* Copyright (c) 2026, ArtGins.
|
|
22
|
+
* All Rights Reserved.
|
|
23
|
+
***********************************************************************/
|
|
24
|
+
import {
|
|
25
|
+
clean_name,
|
|
26
|
+
gclass_find_by_name,
|
|
27
|
+
gobj_create_service,
|
|
28
|
+
gobj_destroy,
|
|
29
|
+
gobj_find_service,
|
|
30
|
+
gobj_is_running,
|
|
31
|
+
gobj_name,
|
|
32
|
+
gobj_read_pointer_attr,
|
|
33
|
+
gobj_short_name,
|
|
34
|
+
gobj_start,
|
|
35
|
+
gobj_stop,
|
|
36
|
+
is_gobj,
|
|
37
|
+
log_error,
|
|
38
|
+
log_warning,
|
|
39
|
+
} from "@yuneta/gobj-js";
|
|
40
|
+
|
|
41
|
+
import {describe_local_gclass} from "./gclass_describe.js";
|
|
42
|
+
import {yui_shell_show_error, yui_shell_show_modal, yui_shell_popup_layer} from "./shell_modals.js";
|
|
43
|
+
import {yui_shell_of} from "./c_yui_shell.js";
|
|
44
|
+
|
|
45
|
+
import {t} from "i18next";
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/************************************************************
|
|
49
|
+
* Can this app show a gclass at all?
|
|
50
|
+
*
|
|
51
|
+
* The viewer is C_YUI_JSON, and an app registers only the
|
|
52
|
+
* gclasses it mounts. The caller asks this BEFORE drawing a
|
|
53
|
+
* button, so an app that does not carry the viewer shows no
|
|
54
|
+
* control instead of a control that fails.
|
|
55
|
+
************************************************************/
|
|
56
|
+
function gclass_view_available()
|
|
57
|
+
{
|
|
58
|
+
return !!gclass_find_by_name("C_YUI_JSON", false);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/************************************************************
|
|
63
|
+
* Open the viewer on `gclass_name`.
|
|
64
|
+
*
|
|
65
|
+
* opts:
|
|
66
|
+
* description the document to draw. Omitted, it is
|
|
67
|
+
* built from the browser registry.
|
|
68
|
+
* title_prefix what the window says before the gclass
|
|
69
|
+
* name (the yuno it belongs to).
|
|
70
|
+
* on_close called when the reader dismisses it.
|
|
71
|
+
*
|
|
72
|
+
* Returns a handle for close_gclass_view(), or null (and a
|
|
73
|
+
* logged reason) when there is nothing to show.
|
|
74
|
+
************************************************************/
|
|
75
|
+
function open_gclass_view(host, gclass_name, opts)
|
|
76
|
+
{
|
|
77
|
+
opts = opts || {};
|
|
78
|
+
|
|
79
|
+
if(!host || !gclass_name) {
|
|
80
|
+
log_error(`yui_gclass_view: open without a host or a gclass name`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let shell = yui_shell_of(host);
|
|
85
|
+
|
|
86
|
+
if(!gclass_view_available()) {
|
|
87
|
+
log_error(`${gobj_short_name(host)}: C_YUI_JSON not registered by the app`);
|
|
88
|
+
yui_shell_show_error(shell, "gclass viewer unavailable", {t: t});
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let description = opts.description || describe_local_gclass(gclass_name);
|
|
93
|
+
if(!description) {
|
|
94
|
+
/* Not an error: a gclass of a BACKEND yuno is not in this
|
|
95
|
+
* registry and never will be. It is the caller's job to bring
|
|
96
|
+
* the description; say which gclass could not be found. */
|
|
97
|
+
log_warning(`${gobj_short_name(host)}: gclass not registered here: ${gclass_name}`);
|
|
98
|
+
yui_shell_show_error(shell, "gclass not registered in this yuno", {t: t});
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let jv = gobj_create_service(
|
|
103
|
+
`gclass-view-${clean_name(gobj_name(host))}`,
|
|
104
|
+
"C_YUI_JSON",
|
|
105
|
+
{
|
|
106
|
+
/* No `title`: the host titles it -- the window's title bar
|
|
107
|
+
* on desktop, the dialog's header on mobile. The viewer's
|
|
108
|
+
* own title would land INSIDE that host, doubling it. */
|
|
109
|
+
json_data: description,
|
|
110
|
+
subscriber: null,
|
|
111
|
+
},
|
|
112
|
+
host
|
|
113
|
+
);
|
|
114
|
+
if(!jv) {
|
|
115
|
+
log_error(`${gobj_short_name(host)}: cannot create the gclass viewer`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
gobj_start(jv);
|
|
119
|
+
|
|
120
|
+
let handle = {json_gobj: jv, win: null, modal: null};
|
|
121
|
+
let $box = gobj_read_pointer_attr(jv, "$container");
|
|
122
|
+
|
|
123
|
+
let on_close = () => {
|
|
124
|
+
if(typeof opts.on_close === "function") {
|
|
125
|
+
opts.on_close();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if(is_mobile()) {
|
|
130
|
+
if(!shell) {
|
|
131
|
+
log_error(`${gobj_short_name(host)}: no shell, cannot open the gclass sheet`);
|
|
132
|
+
close_gclass_view(handle);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
handle.modal = yui_shell_show_modal(shell, $box, {
|
|
136
|
+
dialog: true,
|
|
137
|
+
logical_class: "GCLASS_VIEW_SHEET",
|
|
138
|
+
title_prefix: opts.title_prefix || "",
|
|
139
|
+
title: gclass_name,
|
|
140
|
+
t: t,
|
|
141
|
+
on_close: on_close,
|
|
142
|
+
});
|
|
143
|
+
return handle;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let $win_parent = window_layer_of(host, shell);
|
|
147
|
+
|
|
148
|
+
handle.win = gobj_create_service(
|
|
149
|
+
`gclass-win-${clean_name(gobj_name(host))}`,
|
|
150
|
+
"C_YUI_WINDOW",
|
|
151
|
+
{
|
|
152
|
+
$parent: $win_parent,
|
|
153
|
+
subscriber: null,
|
|
154
|
+
modal: false,
|
|
155
|
+
showMax: true,
|
|
156
|
+
showFooter: false,
|
|
157
|
+
resizable: true,
|
|
158
|
+
center: true,
|
|
159
|
+
auto_save_size_and_position: true,
|
|
160
|
+
width: 720,
|
|
161
|
+
height: 640,
|
|
162
|
+
logical_class: "GCLASS_VIEW_WINDOW",
|
|
163
|
+
title_prefix: opts.title_prefix || "",
|
|
164
|
+
title: gclass_name,
|
|
165
|
+
icon: "yi-code",
|
|
166
|
+
body: $box,
|
|
167
|
+
/* The same dock the other windows are on: raising a window
|
|
168
|
+
* is the manager's job, and a viewer registered nowhere
|
|
169
|
+
* cannot come to the front when the reader clicks it. `||
|
|
170
|
+
* null` because gobj_find_service answers undefined when
|
|
171
|
+
* absent, and an undefined attr logs "attr undefined". */
|
|
172
|
+
manager: gobj_find_service("__window_manager__", false) || null,
|
|
173
|
+
on_close: on_close,
|
|
174
|
+
},
|
|
175
|
+
host
|
|
176
|
+
);
|
|
177
|
+
if(!handle.win) {
|
|
178
|
+
log_error(`${gobj_short_name(host)}: cannot create the gclass window`);
|
|
179
|
+
close_gclass_view(handle);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
gobj_start(handle.win);
|
|
183
|
+
|
|
184
|
+
return handle;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/************************************************************
|
|
188
|
+
* Close whatever open_gclass_view() opened. Safe on null and
|
|
189
|
+
* on a handle whose window the reader already dismissed.
|
|
190
|
+
************************************************************/
|
|
191
|
+
function close_gclass_view(handle)
|
|
192
|
+
{
|
|
193
|
+
if(!handle) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let jv = handle.json_gobj;
|
|
198
|
+
let win = handle.win;
|
|
199
|
+
let modal = handle.modal;
|
|
200
|
+
|
|
201
|
+
handle.json_gobj = null;
|
|
202
|
+
handle.win = null;
|
|
203
|
+
handle.modal = null;
|
|
204
|
+
|
|
205
|
+
if(win && is_gobj(win)) {
|
|
206
|
+
try {
|
|
207
|
+
/* STOP, then destroy: gobj_destroy() raises the `destroying`
|
|
208
|
+
* flag before it can stop a running gobj, so destroying it
|
|
209
|
+
* straight logs two errors and skips mt_stop. The ✕ path
|
|
210
|
+
* already stopped it (close_window) -- hence the guard. */
|
|
211
|
+
if(gobj_is_running(win)) {
|
|
212
|
+
gobj_stop(win);
|
|
213
|
+
}
|
|
214
|
+
gobj_destroy(win);
|
|
215
|
+
} catch(e) {
|
|
216
|
+
log_warning(`yui_gclass_view: window already gone: ${e}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if(modal && typeof modal.close === "function") {
|
|
220
|
+
try {
|
|
221
|
+
modal.close();
|
|
222
|
+
} catch(e) {
|
|
223
|
+
log_warning(`yui_gclass_view: modal already gone: ${e}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if(jv && is_gobj(jv)) {
|
|
227
|
+
try {
|
|
228
|
+
if(gobj_is_running(jv)) {
|
|
229
|
+
gobj_stop(jv);
|
|
230
|
+
}
|
|
231
|
+
gobj_destroy(jv);
|
|
232
|
+
} catch(e) {
|
|
233
|
+
log_warning(`yui_gclass_view: viewer already gone: ${e}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/************************************************************
|
|
239
|
+
* Where to mount the window: the SAME element the caller's
|
|
240
|
+
* own window hangs from.
|
|
241
|
+
*
|
|
242
|
+
* Not a preference -- a requirement. z-index only orders
|
|
243
|
+
* siblings of one stacking context, so a viewer opened from a
|
|
244
|
+
* window on `body` and mounted in the shell's popup layer
|
|
245
|
+
* lands BEHIND its opener whatever number it is given, and
|
|
246
|
+
* the button reads as one that did nothing.
|
|
247
|
+
************************************************************/
|
|
248
|
+
function window_layer_of(host, shell)
|
|
249
|
+
{
|
|
250
|
+
let $c = gobj_read_pointer_attr(host, "$container");
|
|
251
|
+
if($c && typeof $c.closest === "function") {
|
|
252
|
+
let $win = $c.closest(".C_YUI_WINDOW");
|
|
253
|
+
if($win && $win.parentNode) {
|
|
254
|
+
return $win.parentNode;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return (typeof document !== "undefined" &&
|
|
259
|
+
document.getElementById("top-layer")) ||
|
|
260
|
+
(shell && yui_shell_popup_layer(shell)) ||
|
|
261
|
+
null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/************************************************************
|
|
265
|
+
* A window is not a sheet: below this width there is no room
|
|
266
|
+
* for a title bar the reader can drag.
|
|
267
|
+
************************************************************/
|
|
268
|
+
function is_mobile()
|
|
269
|
+
{
|
|
270
|
+
return typeof window !== "undefined" && window.innerWidth <= 768;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export {
|
|
274
|
+
gclass_view_available,
|
|
275
|
+
open_gclass_view,
|
|
276
|
+
close_gclass_view,
|
|
277
|
+
};
|