@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.
@@ -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
+ };