@yuneta/gobj-ui 7.23.35 → 7.23.36
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 +92 -10
- package/dist/gobj-ui.cjs.js +3296 -654
- package/dist/gobj-ui.es.js +3295 -655
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/c_yui_fsm_graph.css +128 -0
- package/src/c_yui_fsm_graph.js +967 -0
- package/src/c_yui_gclass.css +508 -0
- package/src/c_yui_gclass.js +1522 -0
- package/src/c_yui_gobj_tree_js.js +11 -27
- package/src/gclass_view_model.js +421 -0
- package/src/gclass_view_model.test.js +296 -0
- package/src/yui_gclass_view.js +42 -15
|
@@ -0,0 +1,1522 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* c_yui_gclass.js
|
|
3
|
+
*
|
|
4
|
+
* What a GClass IS, in zones.
|
|
5
|
+
*
|
|
6
|
+
* The framework answers `view-gclass` with a full description --
|
|
7
|
+
* attributes, commands, methods, trace levels and the FSM -- and until
|
|
8
|
+
* now the only thing that drew it was a JSON tree. Correct, and
|
|
9
|
+
* unreadable: the one question a reader arrives with ("what does this
|
|
10
|
+
* gclass DO in the state it is in?") took four expansions to reach.
|
|
11
|
+
*
|
|
12
|
+
* This viewer lays the same document out by zones, and reads BOTH
|
|
13
|
+
* dialects of it (the C kernel's and the browser registry's) through
|
|
14
|
+
* `gclass_view_model.js`, which is where every difference between them
|
|
15
|
+
* is resolved.
|
|
16
|
+
*
|
|
17
|
+
* THE MACHINE IS A MATRIX, rows = events and columns = states -- the
|
|
18
|
+
* shape the FSM is declared in, and the one that survives a gclass
|
|
19
|
+
* with 12 events and 3 states as well as one with 86 commands and one
|
|
20
|
+
* state. Two things it says that a JSON dump cannot:
|
|
21
|
+
*
|
|
22
|
+
* - an EMPTY CELL is information. An event with no action in the
|
|
23
|
+
* current state is not ignored in Yuneta, it is refused with
|
|
24
|
+
* "Event NOT DEFINED in state", so the empty cells are the map
|
|
25
|
+
* of what breaks. They are hatched, never left blank.
|
|
26
|
+
* - a state nothing declares a way INTO is marked. An action may
|
|
27
|
+
* jump with `gobj_change_state()` (C_IEVENT_CLI reaches
|
|
28
|
+
* ST_SESSION that way), and no description can see inside an
|
|
29
|
+
* action -- so the viewer flags the state instead of drawing
|
|
30
|
+
* the working half of a gclass as unreachable.
|
|
31
|
+
*
|
|
32
|
+
* TWO VIEWS over the same document, on the `view_mode` attr:
|
|
33
|
+
*
|
|
34
|
+
* zones the layout above.
|
|
35
|
+
* raw the description verbatim. It is the authoritative
|
|
36
|
+
* answer of the backend and is never thrown away.
|
|
37
|
+
*
|
|
38
|
+
* Container-agnostic, like C_YUI_JSON: the gclass owns the chrome and
|
|
39
|
+
* the host mounts `$container` wherever it wants (a C_YUI_WINDOW body
|
|
40
|
+
* on desktop, a dialog on a phone). `yui_gclass_view.js` does both.
|
|
41
|
+
*
|
|
42
|
+
* DOM is self-describing (UPPER_SNAKE logical classes): GCLASS_VIEWER /
|
|
43
|
+
* GCLASS_TOOLBAR / GCLASS_SEARCH / GCLASS_VIEW_SWITCH / GCLASS_ZONES /
|
|
44
|
+
* GCLASS_HEAD / GCLASS_ZONE / GCLASS_ZONE_TITLE / GCLASS_ZONE_BODY /
|
|
45
|
+
* GCLASS_TABLE / GCLASS_MATRIX / GCLASS_CELL / GCLASS_PUBLISHED /
|
|
46
|
+
* GCLASS_RAW.
|
|
47
|
+
*
|
|
48
|
+
* Copyright (c) 2026, ArtGins.
|
|
49
|
+
* All Rights Reserved.
|
|
50
|
+
***********************************************************************/
|
|
51
|
+
import {
|
|
52
|
+
SDATA,
|
|
53
|
+
SDATA_END,
|
|
54
|
+
data_type_t,
|
|
55
|
+
gclass_create,
|
|
56
|
+
gclass_find_by_name,
|
|
57
|
+
log_error,
|
|
58
|
+
gobj_read_pointer_attr,
|
|
59
|
+
gobj_parent,
|
|
60
|
+
gobj_subscribe_event,
|
|
61
|
+
gobj_read_attr,
|
|
62
|
+
gobj_write_attr,
|
|
63
|
+
gobj_read_str_attr,
|
|
64
|
+
gobj_send_event,
|
|
65
|
+
gobj_create_pure_child,
|
|
66
|
+
gobj_start,
|
|
67
|
+
gobj_stop,
|
|
68
|
+
gobj_destroy,
|
|
69
|
+
gobj_is_running,
|
|
70
|
+
gobj_short_name,
|
|
71
|
+
gobj_name,
|
|
72
|
+
clean_name,
|
|
73
|
+
createElement2,
|
|
74
|
+
json_deep_copy,
|
|
75
|
+
refresh_language,
|
|
76
|
+
} from "@yuneta/gobj-js";
|
|
77
|
+
|
|
78
|
+
import {gclass_view_model} from "./gclass_view_model.js";
|
|
79
|
+
import {register_c_yui_fsm_graph} from "./c_yui_fsm_graph.js";
|
|
80
|
+
import {json_text_dump} from "./json_view_helpers.js";
|
|
81
|
+
import {yui_toolbar} from "./yui_toolbar.js";
|
|
82
|
+
import {attach_clear} from "./yui_inputs.js";
|
|
83
|
+
import {yui_copy_json, yui_button_mark_done} from "./yui_clipboard.js";
|
|
84
|
+
|
|
85
|
+
import {t} from "i18next";
|
|
86
|
+
|
|
87
|
+
import "./c_yui_gclass.css";
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/***************************************************************
|
|
91
|
+
* Constants
|
|
92
|
+
***************************************************************/
|
|
93
|
+
const GCLASS_NAME = "C_YUI_GCLASS";
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* The two views, in switch order.
|
|
97
|
+
*/
|
|
98
|
+
const VIEWS = [
|
|
99
|
+
{mode: "zones", icon: "yi-table", key: "zones view"},
|
|
100
|
+
{mode: "raw", icon: "yi-code", key: "raw view"},
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
* The two ways to read the machine, in switch order.
|
|
105
|
+
*
|
|
106
|
+
* The matrix leads and stays the default: it is the shape the FSM is
|
|
107
|
+
* DECLARED in, it reads every gclass the same way, and it is the only
|
|
108
|
+
* one of the two that can show what is MISSING. The graph answers a
|
|
109
|
+
* different question -- where does this machine go -- and earns its
|
|
110
|
+
* place exactly where the matrix is worst: many states, few events
|
|
111
|
+
* each.
|
|
112
|
+
*/
|
|
113
|
+
const MACHINE_VIEWS = [
|
|
114
|
+
{mode: "matrix", icon: "yi-table", key: "matrix view"},
|
|
115
|
+
{mode: "graph", icon: "yi-hexagon-nodes", key: "graph view"},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Hard cap on the characters the raw view prints. The same guard the
|
|
120
|
+
* JSON viewer applies one layer down, and announced rather than
|
|
121
|
+
* applied silently.
|
|
122
|
+
*/
|
|
123
|
+
const MAX_RAW_CHARS = 2000000;
|
|
124
|
+
|
|
125
|
+
/*
|
|
126
|
+
* The zones, in the order they are laid out. `col` is which of the two
|
|
127
|
+
* columns a zone belongs to on a wide container -- the machine has one
|
|
128
|
+
* to itself because it is the only zone that grows sideways.
|
|
129
|
+
*/
|
|
130
|
+
const ZONES = [
|
|
131
|
+
{id: "attrs", key: "attributes", col: "left"},
|
|
132
|
+
{id: "commands", key: "commands", col: "left"},
|
|
133
|
+
{id: "methods", key: "methods", col: "left"},
|
|
134
|
+
{id: "traces", key: "trace levels", col: "left"},
|
|
135
|
+
{id: "machine", key: "machine", col: "right"},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
/***************************************************************
|
|
139
|
+
* Data
|
|
140
|
+
***************************************************************/
|
|
141
|
+
const attrs_table = [
|
|
142
|
+
/*---------------- Public Attributes ----------------*/
|
|
143
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
144
|
+
|
|
145
|
+
/*---------------- Config ----------------*/
|
|
146
|
+
SDATA(data_type_t.DTP_JSON, "description", 0, null, "The gclass description, as `view-gclass` answers it or `describe_local_gclass()` builds it"),
|
|
147
|
+
SDATA(data_type_t.DTP_STRING, "gclass_name", 0, "", "Name to head the view with when the description carries none"),
|
|
148
|
+
SDATA(data_type_t.DTP_STRING, "current_state",0, "", "State of the INSTANCE the host is showing, lit in the machine. Empty when the host is not showing one"),
|
|
149
|
+
SDATA(data_type_t.DTP_STRING, "view_mode", 0, "zones","View: 'zones' or 'raw'"),
|
|
150
|
+
|
|
151
|
+
/*---------------- UI ----------------*/
|
|
152
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "HTMLElement root, mounted by the parent"),
|
|
153
|
+
SDATA_END()
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
let PRIVATE_DATA = {
|
|
157
|
+
model: null, // the view model built from the description
|
|
158
|
+
search: "", // current filter term (lower-cased)
|
|
159
|
+
collapsed: null, // Set<string> of collapsed zone ids
|
|
160
|
+
machine_view: "matrix", // how the machine zone is read
|
|
161
|
+
graph_gobj: null, // hosted C_YUI_FSM_GRAPH child (built on first use)
|
|
162
|
+
$zones: null, // the scrollable zones body
|
|
163
|
+
$raw: null, // the scrollable raw body
|
|
164
|
+
$raw_body: null, // the <pre> inside it
|
|
165
|
+
$search: null, // the search input
|
|
166
|
+
$mode_btns: null, // Map<mode, HTMLElement> of the view switch
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
let __gclass__ = null;
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
/******************************
|
|
175
|
+
* Framework Methods
|
|
176
|
+
******************************/
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
/***************************************************************
|
|
182
|
+
* Framework Method: Create
|
|
183
|
+
***************************************************************/
|
|
184
|
+
function mt_create(gobj)
|
|
185
|
+
{
|
|
186
|
+
let priv = gobj.priv;
|
|
187
|
+
|
|
188
|
+
priv.collapsed = new Set();
|
|
189
|
+
priv.search = "";
|
|
190
|
+
priv.machine_view = "matrix";
|
|
191
|
+
priv.model = gclass_view_model(gobj_read_attr(gobj, "description"));
|
|
192
|
+
|
|
193
|
+
build_ui(gobj);
|
|
194
|
+
apply_view_mode(gobj);
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* CHILD subscription model
|
|
198
|
+
*/
|
|
199
|
+
let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
200
|
+
if(!subscriber) {
|
|
201
|
+
subscriber = gobj_parent(gobj);
|
|
202
|
+
}
|
|
203
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/***************************************************************
|
|
207
|
+
* Framework Method: Start
|
|
208
|
+
***************************************************************/
|
|
209
|
+
function mt_start(gobj)
|
|
210
|
+
{
|
|
211
|
+
render_view(gobj);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/***************************************************************
|
|
215
|
+
* Framework Method: Stop
|
|
216
|
+
***************************************************************/
|
|
217
|
+
function mt_stop(gobj)
|
|
218
|
+
{
|
|
219
|
+
/*
|
|
220
|
+
* The graph child dies with this gobj's RUNNING state, not with
|
|
221
|
+
* its destruction: gobj_destroy() destroys the children BEFORE
|
|
222
|
+
* calling mt_destroy(), so tearing it down there arrives after
|
|
223
|
+
* the framework already destroyed it -- while it was running.
|
|
224
|
+
*/
|
|
225
|
+
teardown_graph_child(gobj);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/***************************************************************
|
|
229
|
+
* Framework Method: Destroy
|
|
230
|
+
***************************************************************/
|
|
231
|
+
function mt_destroy(gobj)
|
|
232
|
+
{
|
|
233
|
+
destroy_ui(gobj);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
/***************************
|
|
240
|
+
* Local Methods
|
|
241
|
+
***************************/
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
/************************************************************
|
|
247
|
+
* Build UI
|
|
248
|
+
************************************************************/
|
|
249
|
+
function build_ui(gobj)
|
|
250
|
+
{
|
|
251
|
+
let priv = gobj.priv;
|
|
252
|
+
|
|
253
|
+
let $toolbar = make_toolbar(gobj);
|
|
254
|
+
|
|
255
|
+
let $container = createElement2(
|
|
256
|
+
['div', {class: 'C_YUI_GCLASS GCLASS_VIEWER view-card',
|
|
257
|
+
style: 'height:100%; display:flex; flex-direction:column;'}, [
|
|
258
|
+
['div', {class: 'GCLASS_TOOLBAR is-flex-grow-0'}, [$toolbar]],
|
|
259
|
+
['div', {class: 'GCLASS_ZONES is-flex-grow-1',
|
|
260
|
+
style: 'flex:1 1 auto; min-height:0; overflow:auto;'}, []],
|
|
261
|
+
['div', {class: 'GCLASS_RAW is-flex-grow-1 is-hidden',
|
|
262
|
+
style: 'flex:1 1 auto; min-height:0; overflow:auto;'}, []]
|
|
263
|
+
]]
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
gobj_write_attr(gobj, "$container", $container);
|
|
267
|
+
priv.$zones = $container.querySelector('.GCLASS_ZONES');
|
|
268
|
+
priv.$raw = $container.querySelector('.GCLASS_RAW');
|
|
269
|
+
priv.$search = $container.querySelector('.GCLASS_SEARCH');
|
|
270
|
+
priv.$mode_btns = new Map();
|
|
271
|
+
VIEWS.forEach(function(v) {
|
|
272
|
+
let $btn = $container.querySelector('.GCLASS_VIEW_MODE_' + v.mode.toUpperCase());
|
|
273
|
+
if($btn) {
|
|
274
|
+
priv.$mode_btns.set(v.mode, $btn);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
refresh_language($container, t);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/************************************************************
|
|
282
|
+
* Destroy UI
|
|
283
|
+
************************************************************/
|
|
284
|
+
function destroy_ui(gobj)
|
|
285
|
+
{
|
|
286
|
+
let priv = gobj.priv;
|
|
287
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
288
|
+
if($container) {
|
|
289
|
+
if($container.parentNode) {
|
|
290
|
+
$container.parentNode.removeChild($container);
|
|
291
|
+
}
|
|
292
|
+
gobj_write_attr(gobj, "$container", null);
|
|
293
|
+
}
|
|
294
|
+
priv.$zones = null;
|
|
295
|
+
priv.$raw = null;
|
|
296
|
+
priv.$raw_body = null;
|
|
297
|
+
priv.$search = null;
|
|
298
|
+
priv.$mode_btns = null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/************************************************************
|
|
302
|
+
* Toolbar: a filter, the view switch and copy.
|
|
303
|
+
************************************************************/
|
|
304
|
+
function make_toolbar(gobj)
|
|
305
|
+
{
|
|
306
|
+
let $search_input = createElement2(
|
|
307
|
+
['input', {class: 'GCLASS_SEARCH input', type: 'text',
|
|
308
|
+
placeholder: t("search")}, [], {
|
|
309
|
+
input: function(evt) {
|
|
310
|
+
gobj_send_event(gobj, "EV_SEARCH", {text: evt.target.value}, gobj);
|
|
311
|
+
}
|
|
312
|
+
}]);
|
|
313
|
+
let $search_control = createElement2(
|
|
314
|
+
['div', {class: 'control has-icons-left GCLASS_SEARCH_CONTROL',
|
|
315
|
+
style: 'max-width:22em;'}, [
|
|
316
|
+
$search_input,
|
|
317
|
+
['span', {class: 'icon is-left'}, [['i', {class: 'yi-magnifying-glass'}]]]
|
|
318
|
+
]]);
|
|
319
|
+
attach_clear($search_control, $search_input);
|
|
320
|
+
|
|
321
|
+
let right_items = [
|
|
322
|
+
view_mode_switch(gobj),
|
|
323
|
+
icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json"),
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
const $toolbar = yui_toolbar({}, [
|
|
327
|
+
['div', {class: 'yui-horizontal-toolbar-section left is-flex is-align-items-center',
|
|
328
|
+
style: 'gap:.25rem;'}, [$search_control]],
|
|
329
|
+
['div', {class: 'yui-horizontal-toolbar-section center'}, []],
|
|
330
|
+
['div', {class: 'yui-horizontal-toolbar-section right is-flex is-align-items-center',
|
|
331
|
+
style: 'gap:.25rem;'}, right_items],
|
|
332
|
+
]);
|
|
333
|
+
|
|
334
|
+
refresh_language($toolbar, t);
|
|
335
|
+
return $toolbar;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/************************************************************
|
|
339
|
+
* A single icon toolbar button that fires `event_name`
|
|
340
|
+
************************************************************/
|
|
341
|
+
function icon_button(gobj, icon, event_name, label_key)
|
|
342
|
+
{
|
|
343
|
+
return ['button', {class: `button ${event_name}`, style: 'width:2.5em;',
|
|
344
|
+
title: t(label_key), 'data-i18n-title': label_key,
|
|
345
|
+
'aria-label': t(label_key), 'data-i18n-aria-label': label_key}, [
|
|
346
|
+
['span', {class: 'icon'}, [['i', {class: icon}]]]
|
|
347
|
+
], {
|
|
348
|
+
click: function(evt) {
|
|
349
|
+
evt.stopPropagation();
|
|
350
|
+
gobj_send_event(gobj, event_name, {}, gobj);
|
|
351
|
+
}
|
|
352
|
+
}];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/************************************************************
|
|
356
|
+
* The view switch: one button per view, the current one
|
|
357
|
+
* marked. Two buttons and not a toggle, for the same reason
|
|
358
|
+
* the JSON viewer gives three: a control has to say where it
|
|
359
|
+
* goes, not that it will change something.
|
|
360
|
+
************************************************************/
|
|
361
|
+
function view_mode_switch(gobj)
|
|
362
|
+
{
|
|
363
|
+
let buttons = VIEWS.map(function(v) {
|
|
364
|
+
return ['button', {class: `button GCLASS_VIEW_MODE GCLASS_VIEW_MODE_${v.mode.toUpperCase()}`,
|
|
365
|
+
type: 'button', style: 'width:2.5em;',
|
|
366
|
+
title: t(v.key), 'data-i18n-title': v.key,
|
|
367
|
+
'aria-label': t(v.key), 'data-i18n-aria-label': v.key}, [
|
|
368
|
+
['span', {class: 'icon'}, [['i', {class: v.icon}]]]
|
|
369
|
+
], {
|
|
370
|
+
click: function(evt) {
|
|
371
|
+
evt.stopPropagation();
|
|
372
|
+
gobj_send_event(gobj, "EV_SET_VIEW_MODE", {mode: v.mode}, gobj);
|
|
373
|
+
}
|
|
374
|
+
}];
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return ['div', {class: 'buttons has-addons is-flex-wrap-nowrap mb-0 GCLASS_VIEW_SWITCH'},
|
|
378
|
+
buttons];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/************************************************************
|
|
382
|
+
* The current view, normalised. Anything unknown is zones.
|
|
383
|
+
************************************************************/
|
|
384
|
+
function current_view_mode(gobj)
|
|
385
|
+
{
|
|
386
|
+
let mode = gobj_read_str_attr(gobj, "view_mode");
|
|
387
|
+
return VIEWS.some((v) => v.mode === mode)? mode: "zones";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/************************************************************
|
|
391
|
+
* Show the chrome of the current view and hide the other's.
|
|
392
|
+
************************************************************/
|
|
393
|
+
function apply_view_mode(gobj)
|
|
394
|
+
{
|
|
395
|
+
let priv = gobj.priv;
|
|
396
|
+
let mode = current_view_mode(gobj);
|
|
397
|
+
|
|
398
|
+
let bodies = {zones: priv.$zones, raw: priv.$raw};
|
|
399
|
+
for(let [m, $el] of Object.entries(bodies)) {
|
|
400
|
+
if($el) {
|
|
401
|
+
$el.classList.toggle('is-hidden', m !== mode);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if(priv.$mode_btns) {
|
|
406
|
+
for(let [m, $btn] of priv.$mode_btns) {
|
|
407
|
+
$btn.classList.toggle('is-active', m === mode);
|
|
408
|
+
$btn.setAttribute("aria-pressed", (m === mode)? "true": "false");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/************************************************************
|
|
414
|
+
* Render whichever view is current.
|
|
415
|
+
************************************************************/
|
|
416
|
+
function render_view(gobj)
|
|
417
|
+
{
|
|
418
|
+
if(current_view_mode(gobj) === "raw") {
|
|
419
|
+
render_raw(gobj);
|
|
420
|
+
} else {
|
|
421
|
+
render_zones(gobj);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/************************************************************
|
|
426
|
+
* The raw view: the description verbatim, indented four.
|
|
427
|
+
************************************************************/
|
|
428
|
+
function render_raw(gobj)
|
|
429
|
+
{
|
|
430
|
+
let priv = gobj.priv;
|
|
431
|
+
let $raw = priv.$raw;
|
|
432
|
+
if(!$raw) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
$raw.textContent = "";
|
|
437
|
+
priv.$raw_body = null;
|
|
438
|
+
|
|
439
|
+
let description = gobj_read_attr(gobj, "description");
|
|
440
|
+
if(description === null || description === undefined) {
|
|
441
|
+
$raw.appendChild(createElement2(
|
|
442
|
+
['div', {class: 'GCLASS_EMPTY p-4 has-text-grey',
|
|
443
|
+
i18n: 'no data'}, t("no data")]));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
let dump = json_text_dump(description, MAX_RAW_CHARS);
|
|
448
|
+
let children = [
|
|
449
|
+
['pre', {class: 'GCLASS_RAW_BODY'}, dump.text]
|
|
450
|
+
];
|
|
451
|
+
if(dump.capped) {
|
|
452
|
+
children.push(
|
|
453
|
+
['div', {class: 'GCLASS_RAW_CAPPED has-text-grey is-size-7 p-2',
|
|
454
|
+
i18n: 'text truncated'}, t("text truncated")]);
|
|
455
|
+
}
|
|
456
|
+
if(dump.error) {
|
|
457
|
+
children.push(
|
|
458
|
+
['div', {class: 'GCLASS_RAW_ERROR has-text-danger is-size-7 p-2'},
|
|
459
|
+
dump.error]);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let $body = createElement2(['div', {class: 'GCLASS_RAW_WRAP'}, children]);
|
|
463
|
+
$raw.appendChild($body);
|
|
464
|
+
priv.$raw_body = $body.querySelector('.GCLASS_RAW_BODY');
|
|
465
|
+
refresh_language($raw, t);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/************************************************************
|
|
469
|
+
* The zones view.
|
|
470
|
+
************************************************************/
|
|
471
|
+
function render_zones(gobj)
|
|
472
|
+
{
|
|
473
|
+
let priv = gobj.priv;
|
|
474
|
+
let $zones = priv.$zones;
|
|
475
|
+
if(!$zones) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* The subtree it lives in is about to be thrown away. */
|
|
480
|
+
teardown_graph_child(gobj);
|
|
481
|
+
|
|
482
|
+
$zones.textContent = "";
|
|
483
|
+
|
|
484
|
+
let model = priv.model;
|
|
485
|
+
if(!model) {
|
|
486
|
+
$zones.appendChild(createElement2(
|
|
487
|
+
['div', {class: 'GCLASS_EMPTY p-4 has-text-grey',
|
|
488
|
+
i18n: 'no data'}, t("no data")]));
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/*
|
|
493
|
+
* The machine takes the WHOLE width in graph mode.
|
|
494
|
+
*
|
|
495
|
+
* Half a column is enough for a matrix, which grows downwards and
|
|
496
|
+
* scrolls sideways inside its own box. A graph does not: it is
|
|
497
|
+
* laid out left to right, and 600px of a 1200px viewer is where
|
|
498
|
+
* six states stop fitting and start being panned. The rest of the
|
|
499
|
+
* zones then stack in one column under it, which is what they do
|
|
500
|
+
* on a phone anyway.
|
|
501
|
+
*/
|
|
502
|
+
let graph_mode = (priv.machine_view === "graph");
|
|
503
|
+
let left = [];
|
|
504
|
+
let right = [];
|
|
505
|
+
let full = [];
|
|
506
|
+
|
|
507
|
+
for(let zone of ZONES) {
|
|
508
|
+
let $zone = build_zone(gobj, zone);
|
|
509
|
+
if(!$zone) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if(zone.col === "right") {
|
|
513
|
+
if(graph_mode) {
|
|
514
|
+
full.push($zone);
|
|
515
|
+
} else {
|
|
516
|
+
right.push($zone);
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
left.push($zone);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
let $root = createElement2(
|
|
524
|
+
['div', {class: 'GCLASS_BODY'}, [
|
|
525
|
+
build_head(gobj, model),
|
|
526
|
+
['div', {class: 'GCLASS_FULL' + (graph_mode? '': ' is-hidden')}, full],
|
|
527
|
+
['div', {class: 'GCLASS_COLS' + (graph_mode? ' is-stacked': '')}, [
|
|
528
|
+
['div', {class: 'GCLASS_COL GCLASS_COL_LEFT'}, left],
|
|
529
|
+
['div', {class: 'GCLASS_COL GCLASS_COL_RIGHT' +
|
|
530
|
+
(graph_mode? ' is-hidden': '')}, right]
|
|
531
|
+
]]
|
|
532
|
+
]]
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
$zones.appendChild($root);
|
|
536
|
+
refresh_language($zones, t);
|
|
537
|
+
apply_search(gobj);
|
|
538
|
+
reveal_current_state(gobj);
|
|
539
|
+
|
|
540
|
+
if(priv.machine_view === "graph" && !priv.collapsed.has("machine")) {
|
|
541
|
+
build_graph_child(gobj);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/************************************************************
|
|
546
|
+
* The header: what this gclass is called, and the two
|
|
547
|
+
* numbers a reader asks for before anything else.
|
|
548
|
+
************************************************************/
|
|
549
|
+
function build_head(gobj, model)
|
|
550
|
+
{
|
|
551
|
+
let items = [
|
|
552
|
+
['span', {class: 'GCLASS_NAME'},
|
|
553
|
+
model.id || gobj_read_str_attr(gobj, "gclass_name") || "?"]
|
|
554
|
+
];
|
|
555
|
+
|
|
556
|
+
if(model.instances !== null) {
|
|
557
|
+
items.push(
|
|
558
|
+
['span', {class: 'GCLASS_INSTANCES tag'}, [
|
|
559
|
+
['span', {class: 'GCLASS_INSTANCES_N'}, String(model.instances)],
|
|
560
|
+
['span', {class: 'GCLASS_INSTANCES_LABEL', i18n: 'instances'},
|
|
561
|
+
t("instances")]
|
|
562
|
+
]]);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
for(let flag of model.gcflag) {
|
|
566
|
+
items.push(['span', {class: 'GCLASS_CHIP GCLASS_GCFLAG'}, flag]);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
let current = gobj_read_str_attr(gobj, "current_state");
|
|
570
|
+
if(current) {
|
|
571
|
+
items.push(
|
|
572
|
+
['span', {class: 'GCLASS_CHIP GCLASS_CURRENT',
|
|
573
|
+
title: t("current state"),
|
|
574
|
+
'data-i18n-title': 'current state'}, current]);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return ['div', {class: 'GCLASS_HEAD'}, items];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/************************************************************
|
|
581
|
+
* One zone: a heading that folds it, and its body.
|
|
582
|
+
*
|
|
583
|
+
* Every zone is drawn, empty ones included: "this gclass
|
|
584
|
+
* declares no commands" is an answer, and a zone that
|
|
585
|
+
* disappears makes the reader wonder whether the viewer
|
|
586
|
+
* simply failed to draw it.
|
|
587
|
+
************************************************************/
|
|
588
|
+
function build_zone(gobj, zone)
|
|
589
|
+
{
|
|
590
|
+
let priv = gobj.priv;
|
|
591
|
+
let model = priv.model;
|
|
592
|
+
|
|
593
|
+
let body = null;
|
|
594
|
+
let count = 0;
|
|
595
|
+
|
|
596
|
+
switch(zone.id) {
|
|
597
|
+
case "attrs":
|
|
598
|
+
count = model.attrs.length;
|
|
599
|
+
body = build_attrs(model.attrs);
|
|
600
|
+
break;
|
|
601
|
+
case "commands":
|
|
602
|
+
count = model.commands.filter((c) => c.kind === "command").length;
|
|
603
|
+
body = build_commands(model.commands);
|
|
604
|
+
break;
|
|
605
|
+
case "methods":
|
|
606
|
+
count = model.methods.length + model.internal_methods.length;
|
|
607
|
+
body = build_methods(model);
|
|
608
|
+
break;
|
|
609
|
+
case "traces":
|
|
610
|
+
count = model.trace_levels.length;
|
|
611
|
+
body = build_traces(model.trace_levels);
|
|
612
|
+
break;
|
|
613
|
+
case "machine":
|
|
614
|
+
/* Two numbers, because one of them alone says nothing
|
|
615
|
+
* about the shape: 13 events across 1 state and 13
|
|
616
|
+
* across 6 are different machines. */
|
|
617
|
+
count = model.fsm.states.length?
|
|
618
|
+
`${model.fsm.states.length} · ${model.fsm.rows.length}`:
|
|
619
|
+
model.fsm.rows.length;
|
|
620
|
+
body = build_machine(gobj, model.fsm);
|
|
621
|
+
break;
|
|
622
|
+
default:
|
|
623
|
+
log_error(`${GCLASS_NAME}: unknown zone '${zone.id}'`);
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
let collapsed = priv.collapsed.has(zone.id);
|
|
628
|
+
|
|
629
|
+
let $toggle = createElement2(
|
|
630
|
+
['button', {class: 'GCLASS_ZONE_TOGGLE', type: 'button',
|
|
631
|
+
'aria-expanded': collapsed? 'false': 'true'}, [
|
|
632
|
+
['span', {class: 'icon GCLASS_ZONE_ARROW'}, [
|
|
633
|
+
['i', {class: collapsed? 'yi-chevron-right': 'yi-chevron-down'}]
|
|
634
|
+
]],
|
|
635
|
+
['span', {class: 'GCLASS_ZONE_TITLE', i18n: zone.key}, t(zone.key)],
|
|
636
|
+
['span', {class: 'GCLASS_ZONE_COUNT'}, String(count)]
|
|
637
|
+
], {
|
|
638
|
+
click: function(evt) {
|
|
639
|
+
evt.stopPropagation();
|
|
640
|
+
gobj_send_event(gobj, "EV_TOGGLE_ZONE", {zone: zone.id}, gobj);
|
|
641
|
+
}
|
|
642
|
+
}]);
|
|
643
|
+
|
|
644
|
+
return createElement2(
|
|
645
|
+
['section', {class: `GCLASS_ZONE GCLASS_ZONE_${zone.id.toUpperCase()}` +
|
|
646
|
+
(collapsed? ' is-collapsed': ''),
|
|
647
|
+
'data-zone': zone.id}, [
|
|
648
|
+
$toggle,
|
|
649
|
+
['div', {class: 'GCLASS_ZONE_BODY' + (collapsed? ' is-hidden': '')},
|
|
650
|
+
[body]]
|
|
651
|
+
]]);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/************************************************************
|
|
655
|
+
* An "empty zone" line. It says WHY it is empty, which is
|
|
656
|
+
* never the same as saying nothing.
|
|
657
|
+
************************************************************/
|
|
658
|
+
function empty_line(key)
|
|
659
|
+
{
|
|
660
|
+
return ['div', {class: 'GCLASS_ZONE_EMPTY', i18n: key}, t(key)];
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/************************************************************
|
|
664
|
+
* A table in its own horizontal scroller.
|
|
665
|
+
*
|
|
666
|
+
* EVERY table, not just the matrix. An attrs table with a
|
|
667
|
+
* long description is as wide as a matrix with eight states,
|
|
668
|
+
* and on a phone the one that overflows takes the whole
|
|
669
|
+
* viewer sideways with it -- the page ends up scrolling, and
|
|
670
|
+
* the toolbar leaves the screen.
|
|
671
|
+
************************************************************/
|
|
672
|
+
function scroller(table)
|
|
673
|
+
{
|
|
674
|
+
return ['div', {class: 'GCLASS_SCROLL'}, [table]];
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/************************************************************
|
|
678
|
+
* A row's haystack: what the filter matches against. Built
|
|
679
|
+
* once, at render, and kept on the element -- filtering a
|
|
680
|
+
* table by reading its cells back out is how a filter starts
|
|
681
|
+
* matching the words of the CHROME.
|
|
682
|
+
************************************************************/
|
|
683
|
+
function row_attrs(haystack, extra)
|
|
684
|
+
{
|
|
685
|
+
let attrs = {class: 'GCLASS_ROW', 'data-find': String(haystack).toLowerCase()};
|
|
686
|
+
return Object.assign(attrs, extra || {});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/************************************************************
|
|
690
|
+
* Flags, as chips. A flag that changes what a reader may DO
|
|
691
|
+
* with the attr gets its own class; the rest are quiet.
|
|
692
|
+
************************************************************/
|
|
693
|
+
function flag_chips(flags)
|
|
694
|
+
{
|
|
695
|
+
return flags.map(function(flag) {
|
|
696
|
+
let cls = 'GCLASS_CHIP GCLASS_FLAG';
|
|
697
|
+
if(flag.indexOf("PERSIST") >= 0) {
|
|
698
|
+
cls += ' is-persist';
|
|
699
|
+
} else if(flag.indexOf("WR") >= 0) {
|
|
700
|
+
cls += ' is-writable';
|
|
701
|
+
} else if(flag.indexOf("AUTHZ") >= 0) {
|
|
702
|
+
cls += ' is-authz';
|
|
703
|
+
} else if(flag.indexOf("REQUIRED") >= 0) {
|
|
704
|
+
cls += ' is-required';
|
|
705
|
+
}
|
|
706
|
+
return ['span', {class: cls}, flag];
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/************************************************************
|
|
711
|
+
* The attrs zone.
|
|
712
|
+
************************************************************/
|
|
713
|
+
function build_attrs(attrs)
|
|
714
|
+
{
|
|
715
|
+
if(!attrs.length) {
|
|
716
|
+
return empty_line("no attributes declared");
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
let rows = attrs.map(function(a) {
|
|
720
|
+
return ['tr', row_attrs(`${a.id} ${a.type} ${a.flags.join(" ")} ${a.description}`), [
|
|
721
|
+
['td', {class: 'GCLASS_CELL_KEY'}, a.id],
|
|
722
|
+
['td', {class: 'GCLASS_CELL_TYPE'}, a.type],
|
|
723
|
+
['td', {class: 'GCLASS_CELL_FLAGS'}, flag_chips(a.flags)],
|
|
724
|
+
['td', {class: 'GCLASS_CELL_DEFAULT'}, a.default_value],
|
|
725
|
+
['td', {class: 'GCLASS_CELL_DESC'}, a.description]
|
|
726
|
+
]];
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
return scroller(['table', {class: 'GCLASS_TABLE GCLASS_TABLE_ATTRS'}, [
|
|
730
|
+
['thead', {}, [
|
|
731
|
+
['tr', {}, [
|
|
732
|
+
['th', {i18n: 'name'}, t("name")],
|
|
733
|
+
['th', {i18n: 'type'}, t("type")],
|
|
734
|
+
['th', {i18n: 'flags'}, t("flags")],
|
|
735
|
+
['th', {i18n: 'default'}, t("default")],
|
|
736
|
+
['th', {i18n: 'description'}, t("description")]
|
|
737
|
+
]]
|
|
738
|
+
]],
|
|
739
|
+
['tbody', {}, rows]
|
|
740
|
+
]]);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/************************************************************
|
|
744
|
+
* The commands zone.
|
|
745
|
+
*
|
|
746
|
+
* A command's parameters ride under it as their own rows,
|
|
747
|
+
* indented: they are what the reader needs the moment they
|
|
748
|
+
* decide to run it, and a second click to reach them buys
|
|
749
|
+
* nothing on a list this short.
|
|
750
|
+
************************************************************/
|
|
751
|
+
function build_commands(commands)
|
|
752
|
+
{
|
|
753
|
+
if(!commands.length) {
|
|
754
|
+
return empty_line("no commands declared");
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
let rows = [];
|
|
758
|
+
for(let c of commands) {
|
|
759
|
+
if(c.kind === "section") {
|
|
760
|
+
rows.push(['tr', row_attrs(c.label, {class: 'GCLASS_ROW GCLASS_CMD_SECTION'}), [
|
|
761
|
+
['td', {colspan: '2'}, c.label]
|
|
762
|
+
]]);
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
let name_items = [['span', {class: 'GCLASS_CMD_NAME'}, c.id]];
|
|
767
|
+
for(let alias of c.alias) {
|
|
768
|
+
name_items.push(['span', {class: 'GCLASS_CHIP GCLASS_ALIAS'}, alias]);
|
|
769
|
+
}
|
|
770
|
+
name_items = name_items.concat(flag_chips(c.flags));
|
|
771
|
+
|
|
772
|
+
rows.push(['tr', row_attrs(`${c.id} ${c.alias.join(" ")} ${c.description}`), [
|
|
773
|
+
['td', {class: 'GCLASS_CELL_KEY'}, name_items],
|
|
774
|
+
['td', {class: 'GCLASS_CELL_DESC'}, c.description]
|
|
775
|
+
]]);
|
|
776
|
+
|
|
777
|
+
for(let p of c.parameters) {
|
|
778
|
+
rows.push(['tr', row_attrs(`${c.id} ${p.id} ${p.description}`,
|
|
779
|
+
{class: 'GCLASS_ROW GCLASS_CMD_PARAM'}), [
|
|
780
|
+
['td', {class: 'GCLASS_CELL_KEY'}, [
|
|
781
|
+
['span', {class: 'GCLASS_PARAM_MARK'}, "↳"],
|
|
782
|
+
['span', {class: 'GCLASS_PARAM_NAME'}, p.id],
|
|
783
|
+
['span', {class: 'GCLASS_PARAM_TYPE'}, p.type]
|
|
784
|
+
].concat(flag_chips(p.flags))],
|
|
785
|
+
['td', {class: 'GCLASS_CELL_DESC'}, p.description]
|
|
786
|
+
]]);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return scroller(['table', {class: 'GCLASS_TABLE GCLASS_TABLE_COMMANDS'}, [
|
|
791
|
+
['tbody', {}, rows]
|
|
792
|
+
]]);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/************************************************************
|
|
796
|
+
* The methods zone: the ones the gclass IMPLEMENTS.
|
|
797
|
+
*
|
|
798
|
+
* A table of every possible method, mostly empty, tells the
|
|
799
|
+
* reader nothing -- so both sides send only what is wired,
|
|
800
|
+
* and this draws them as chips.
|
|
801
|
+
************************************************************/
|
|
802
|
+
function build_methods(model)
|
|
803
|
+
{
|
|
804
|
+
if(!model.methods.length && !model.internal_methods.length) {
|
|
805
|
+
return empty_line("no methods declared");
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
let blocks = [];
|
|
809
|
+
if(model.methods.length) {
|
|
810
|
+
blocks.push(['div', {class: 'GCLASS_CHIPS'},
|
|
811
|
+
model.methods.map((m) =>
|
|
812
|
+
['span', row_attrs(m, {class: 'GCLASS_CHIP GCLASS_METHOD'}), m])]);
|
|
813
|
+
}
|
|
814
|
+
if(model.internal_methods.length) {
|
|
815
|
+
blocks.push(['div', {class: 'GCLASS_SUBTITLE', i18n: 'internal methods'},
|
|
816
|
+
t("internal methods")]);
|
|
817
|
+
blocks.push(['div', {class: 'GCLASS_CHIPS'},
|
|
818
|
+
model.internal_methods.map((m) =>
|
|
819
|
+
['span', row_attrs(m, {class: 'GCLASS_CHIP GCLASS_METHOD'}), m])]);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return ['div', {class: 'GCLASS_METHODS'}, blocks];
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/************************************************************
|
|
826
|
+
* The trace levels zone.
|
|
827
|
+
************************************************************/
|
|
828
|
+
function build_traces(levels)
|
|
829
|
+
{
|
|
830
|
+
if(!levels.length) {
|
|
831
|
+
return empty_line("no trace levels declared");
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
let rows = levels.map(function(l) {
|
|
835
|
+
return ['tr', row_attrs(`${l.id} ${l.description}`), [
|
|
836
|
+
['td', {class: 'GCLASS_CELL_KEY'}, l.id],
|
|
837
|
+
['td', {class: 'GCLASS_CELL_DESC'}, l.description]
|
|
838
|
+
]];
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
return scroller(['table', {class: 'GCLASS_TABLE GCLASS_TABLE_TRACES'}, [
|
|
842
|
+
['tbody', {}, rows]
|
|
843
|
+
]]);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/************************************************************
|
|
847
|
+
* The machine: the matrix, then what it publishes.
|
|
848
|
+
************************************************************/
|
|
849
|
+
function build_machine(gobj, fsm)
|
|
850
|
+
{
|
|
851
|
+
let priv = gobj.priv;
|
|
852
|
+
|
|
853
|
+
if(!fsm.states.length) {
|
|
854
|
+
return empty_line("no machine declared");
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if(priv.machine_view === "graph") {
|
|
858
|
+
/* An empty mount: the child is created AFTER this subtree is
|
|
859
|
+
* in the document, because G6 sizes itself from its container
|
|
860
|
+
* and one that is still detached measures 0x0. */
|
|
861
|
+
return ['div', {class: 'GCLASS_MACHINE'}, [
|
|
862
|
+
machine_view_switch(gobj),
|
|
863
|
+
['div', {class: 'GCLASS_GRAPH_MOUNT'}, []]
|
|
864
|
+
]];
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
let current = gobj_read_str_attr(gobj, "current_state");
|
|
868
|
+
let unreachable = new Set(fsm.unreachable);
|
|
869
|
+
|
|
870
|
+
let head_cells = [
|
|
871
|
+
['th', {class: 'GCLASS_MX_CORNER', i18n: 'event'}, t("event")]
|
|
872
|
+
];
|
|
873
|
+
for(let state of fsm.states) {
|
|
874
|
+
let cls = 'GCLASS_MX_STATE';
|
|
875
|
+
let items = [['span', {class: 'GCLASS_MX_STATE_NAME'}, state.name]];
|
|
876
|
+
|
|
877
|
+
if(state.name === current) {
|
|
878
|
+
cls += ' is-current';
|
|
879
|
+
}
|
|
880
|
+
if(unreachable.has(state.name)) {
|
|
881
|
+
cls += ' is-unreachable';
|
|
882
|
+
items.push(['span', {class: 'GCLASS_MX_MARK',
|
|
883
|
+
title: t("state with no declared entry"),
|
|
884
|
+
'data-i18n-title': 'state with no declared entry',
|
|
885
|
+
'aria-label': t("state with no declared entry"),
|
|
886
|
+
'data-i18n-aria-label': 'state with no declared entry'},
|
|
887
|
+
"⚠"]);
|
|
888
|
+
}
|
|
889
|
+
head_cells.push(['th', {class: cls}, items]);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
let body_rows = fsm.rows.map(function(row) {
|
|
893
|
+
let cells = [
|
|
894
|
+
['td', {class: 'GCLASS_MX_EVENT'},
|
|
895
|
+
[['span', {}, row.event]].concat(event_flag_chips(row.flags))]
|
|
896
|
+
];
|
|
897
|
+
row.cells.forEach(function(cell, i) {
|
|
898
|
+
let state = fsm.states[i];
|
|
899
|
+
let is_current = (state && state.name === current);
|
|
900
|
+
|
|
901
|
+
if(!cell) {
|
|
902
|
+
cells.push(['td', {class: 'GCLASS_MX_CELL is-none' +
|
|
903
|
+
(is_current? ' is-current': ''),
|
|
904
|
+
title: t("not declared in this state"),
|
|
905
|
+
'data-i18n-title': 'not declared in this state'},
|
|
906
|
+
"·"]);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
let items = [];
|
|
911
|
+
if(cell.action) {
|
|
912
|
+
items.push(['span', {class: 'GCLASS_MX_ACTION'}, cell.action]);
|
|
913
|
+
} else {
|
|
914
|
+
/* The C side cannot name an action: `states2json()`
|
|
915
|
+
* writes the literal "action" for all of them. A mark
|
|
916
|
+
* is the honest drawing of "there is one". */
|
|
917
|
+
items.push(['span', {class: 'GCLASS_MX_ACTION is-unnamed',
|
|
918
|
+
title: t("action"),
|
|
919
|
+
'data-i18n-title': 'action'}, "●"]);
|
|
920
|
+
}
|
|
921
|
+
if(cell.next_state) {
|
|
922
|
+
items.push(['span', {class: 'GCLASS_MX_ARROW'}, "▸"]);
|
|
923
|
+
items.push(['span', {class: 'GCLASS_MX_NEXT'}, cell.next_state]);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
cells.push(['td', {class: 'GCLASS_MX_CELL' +
|
|
927
|
+
(is_current? ' is-current': '')}, items]);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
return ['tr', row_attrs(`${row.event} ${row.cells.map(
|
|
931
|
+
(c) => c? `${c.action} ${c.next_state}`: "").join(" ")}`), cells];
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
let blocks = [
|
|
935
|
+
machine_view_switch(gobj),
|
|
936
|
+
scroller(['table', {class: 'GCLASS_TABLE GCLASS_MATRIX'}, [
|
|
937
|
+
['thead', {}, [['tr', {}, head_cells]]],
|
|
938
|
+
['tbody', {}, body_rows]
|
|
939
|
+
]])
|
|
940
|
+
];
|
|
941
|
+
|
|
942
|
+
blocks.push(build_legend(fsm, current));
|
|
943
|
+
|
|
944
|
+
if(fsm.published.length) {
|
|
945
|
+
blocks.push(['div', {class: 'GCLASS_SUBTITLE', i18n: 'publishes'},
|
|
946
|
+
t("publishes")]);
|
|
947
|
+
blocks.push(['div', {class: 'GCLASS_CHIPS GCLASS_PUBLISHED'},
|
|
948
|
+
fsm.published.map(function(p) {
|
|
949
|
+
return ['span', row_attrs(p.event, {class: 'GCLASS_PUB_ROW'}), [
|
|
950
|
+
['span', {class: 'GCLASS_CHIP GCLASS_PUB_EVENT'}, p.event]
|
|
951
|
+
].concat(event_flag_chips(p.flags))];
|
|
952
|
+
})]);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return ['div', {class: 'GCLASS_MACHINE'}, blocks];
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/************************************************************
|
|
959
|
+
* Matrix or graph, for the machine zone alone.
|
|
960
|
+
*
|
|
961
|
+
* It lives INSIDE the zone and not in the toolbar: it
|
|
962
|
+
* changes one zone, and a control that changes one thing
|
|
963
|
+
* belongs beside that thing. The zone heading cannot hold it
|
|
964
|
+
* either -- the heading is itself a button, and a button
|
|
965
|
+
* inside a button is not a control, it is a bug.
|
|
966
|
+
************************************************************/
|
|
967
|
+
function machine_view_switch(gobj)
|
|
968
|
+
{
|
|
969
|
+
let priv = gobj.priv;
|
|
970
|
+
|
|
971
|
+
let buttons = MACHINE_VIEWS.map(function(v) {
|
|
972
|
+
let on = (priv.machine_view === v.mode);
|
|
973
|
+
return ['button', {class: 'button is-small GCLASS_MACHINE_VIEW ' +
|
|
974
|
+
`GCLASS_MACHINE_VIEW_${v.mode.toUpperCase()}` +
|
|
975
|
+
(on? ' is-active': ''),
|
|
976
|
+
type: 'button',
|
|
977
|
+
'aria-pressed': on? 'true': 'false',
|
|
978
|
+
title: t(v.key), 'data-i18n-title': v.key,
|
|
979
|
+
'aria-label': t(v.key), 'data-i18n-aria-label': v.key}, [
|
|
980
|
+
['span', {class: 'icon'}, [['i', {class: v.icon}]]]
|
|
981
|
+
], {
|
|
982
|
+
click: function(evt) {
|
|
983
|
+
evt.stopPropagation();
|
|
984
|
+
gobj_send_event(gobj, "EV_SET_MACHINE_VIEW",
|
|
985
|
+
{mode: v.mode}, gobj);
|
|
986
|
+
}
|
|
987
|
+
}];
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
return ['div', {class: 'GCLASS_MACHINE_SWITCH buttons has-addons mb-0'},
|
|
991
|
+
buttons];
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/************************************************************
|
|
995
|
+
* What the drawing means. Only the parts that are ON the
|
|
996
|
+
* screen: a legend for a mark nobody drew is noise.
|
|
997
|
+
************************************************************/
|
|
998
|
+
function build_legend(fsm, current)
|
|
999
|
+
{
|
|
1000
|
+
let items = [];
|
|
1001
|
+
|
|
1002
|
+
items.push(['span', {class: 'GCLASS_LEGEND_ITEM'}, [
|
|
1003
|
+
['i', {class: 'GCLASS_LEGEND_SWATCH is-none'}],
|
|
1004
|
+
['span', {i18n: 'not declared in this state'}, t("not declared in this state")]
|
|
1005
|
+
]]);
|
|
1006
|
+
|
|
1007
|
+
if(current && fsm.states.some((s) => s.name === current)) {
|
|
1008
|
+
items.push(['span', {class: 'GCLASS_LEGEND_ITEM'}, [
|
|
1009
|
+
['i', {class: 'GCLASS_LEGEND_SWATCH is-current'}],
|
|
1010
|
+
['span', {i18n: 'current state'}, t("current state")]
|
|
1011
|
+
]]);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if(fsm.unreachable.length) {
|
|
1015
|
+
items.push(['span', {class: 'GCLASS_LEGEND_ITEM'}, [
|
|
1016
|
+
['i', {class: 'GCLASS_LEGEND_SWATCH is-unreachable'}, "⚠"],
|
|
1017
|
+
['span', {i18n: 'state with no declared entry'},
|
|
1018
|
+
t("state with no declared entry")]
|
|
1019
|
+
]]);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
return ['div', {class: 'GCLASS_LEGEND'}, items];
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/************************************************************
|
|
1026
|
+
* Event flags, as chips. Only the ones that say something
|
|
1027
|
+
* about the event itself.
|
|
1028
|
+
************************************************************/
|
|
1029
|
+
function event_flag_chips(flags)
|
|
1030
|
+
{
|
|
1031
|
+
return flags.filter((f) => f.indexOf("OUTPUT") >= 0 || f.indexOf("PUBLIC") >= 0)
|
|
1032
|
+
.map(function(flag) {
|
|
1033
|
+
let cls = 'GCLASS_CHIP GCLASS_EVFLAG';
|
|
1034
|
+
if(flag.indexOf("OUTPUT") >= 0) {
|
|
1035
|
+
cls += ' is-output';
|
|
1036
|
+
} else {
|
|
1037
|
+
cls += ' is-public';
|
|
1038
|
+
}
|
|
1039
|
+
return ['span', {class: cls}, flag.replace("EVF_", "")];
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/************************************************************
|
|
1044
|
+
* Scroll the matrix so the lit column is on screen.
|
|
1045
|
+
*
|
|
1046
|
+
* The reader opened this viewer from a gobj that is in one
|
|
1047
|
+
* particular state, and a matrix wide enough to need a
|
|
1048
|
+
* scroller starts at ITS left edge -- which is where that
|
|
1049
|
+
* state usually is not. Only when it is off screen, and only
|
|
1050
|
+
* as far as it takes: a matrix that jumped every render
|
|
1051
|
+
* would fight the reader who scrolled it somewhere else.
|
|
1052
|
+
************************************************************/
|
|
1053
|
+
function reveal_current_state(gobj)
|
|
1054
|
+
{
|
|
1055
|
+
let priv = gobj.priv;
|
|
1056
|
+
let $zones = priv.$zones;
|
|
1057
|
+
if(!$zones) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
let $th = $zones.querySelector('.GCLASS_MX_STATE.is-current');
|
|
1062
|
+
if(!$th || typeof $th.closest !== "function") {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
let $scroll = $th.closest('.GCLASS_SCROLL');
|
|
1066
|
+
if(!$scroll) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
let cell = $th.getBoundingClientRect();
|
|
1071
|
+
let box = $scroll.getBoundingClientRect();
|
|
1072
|
+
if(cell.right > box.right) {
|
|
1073
|
+
$scroll.scrollLeft += (cell.right - box.right) + 12;
|
|
1074
|
+
} else if(cell.left < box.left) {
|
|
1075
|
+
$scroll.scrollLeft -= (box.left - cell.left) + 12;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/************************************************************
|
|
1080
|
+
* Build the hosted graph child on its mount.
|
|
1081
|
+
*
|
|
1082
|
+
* Here, and never in build_machine: the mount has to be in
|
|
1083
|
+
* the document before the child starts, and the child is
|
|
1084
|
+
* started last for the same reason -- mt_start builds the
|
|
1085
|
+
* G6 graph and measures the canvas.
|
|
1086
|
+
************************************************************/
|
|
1087
|
+
function build_graph_child(gobj)
|
|
1088
|
+
{
|
|
1089
|
+
let priv = gobj.priv;
|
|
1090
|
+
let $zones = priv.$zones;
|
|
1091
|
+
|
|
1092
|
+
if(!$zones) {
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
let $mount = $zones.querySelector('.GCLASS_GRAPH_MOUNT');
|
|
1097
|
+
if(!$mount) {
|
|
1098
|
+
return false; /* the matrix is showing: nothing to mount */
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
let graph = gobj_create_pure_child(
|
|
1102
|
+
"fsm_" + clean_name(gobj_name(gobj)),
|
|
1103
|
+
"C_YUI_FSM_GRAPH",
|
|
1104
|
+
{
|
|
1105
|
+
fsm: priv.model? priv.model.fsm: null,
|
|
1106
|
+
current_state: gobj_read_str_attr(gobj, "current_state"),
|
|
1107
|
+
},
|
|
1108
|
+
gobj
|
|
1109
|
+
);
|
|
1110
|
+
if(!graph) {
|
|
1111
|
+
log_error(`${gobj_short_name(gobj)}: cannot create the machine graph`);
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
let $box = gobj_read_attr(graph, "$container");
|
|
1116
|
+
if(!$box) {
|
|
1117
|
+
log_error(`${gobj_short_name(gobj)}: the machine graph built no $container`);
|
|
1118
|
+
gobj_destroy(graph);
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
$mount.appendChild($box);
|
|
1123
|
+
priv.graph_gobj = graph;
|
|
1124
|
+
gobj_start(graph);
|
|
1125
|
+
return true;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/************************************************************
|
|
1129
|
+
* Destroy the hosted graph child, if any.
|
|
1130
|
+
************************************************************/
|
|
1131
|
+
function teardown_graph_child(gobj)
|
|
1132
|
+
{
|
|
1133
|
+
let priv = gobj.priv;
|
|
1134
|
+
|
|
1135
|
+
if(priv.graph_gobj) {
|
|
1136
|
+
if(gobj_is_running(priv.graph_gobj)) {
|
|
1137
|
+
gobj_stop(priv.graph_gobj);
|
|
1138
|
+
}
|
|
1139
|
+
gobj_destroy(priv.graph_gobj);
|
|
1140
|
+
priv.graph_gobj = null;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/************************************************************
|
|
1145
|
+
* Apply the current filter to what is drawn.
|
|
1146
|
+
*
|
|
1147
|
+
* Rows carry their own haystack (`data-find`), so this hides
|
|
1148
|
+
* rows and never re-renders: a filter that rebuilt the view
|
|
1149
|
+
* would lose the scroll position on every keystroke.
|
|
1150
|
+
************************************************************/
|
|
1151
|
+
function apply_search(gobj)
|
|
1152
|
+
{
|
|
1153
|
+
let priv = gobj.priv;
|
|
1154
|
+
let $zones = priv.$zones;
|
|
1155
|
+
if(!$zones) {
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
let term = priv.search;
|
|
1160
|
+
|
|
1161
|
+
$zones.querySelectorAll('.GCLASS_ZONE').forEach(function($zone) {
|
|
1162
|
+
let $rows = $zone.querySelectorAll('[data-find]');
|
|
1163
|
+
let visible = 0;
|
|
1164
|
+
|
|
1165
|
+
$rows.forEach(function($row) {
|
|
1166
|
+
let hit = !term || $row.getAttribute('data-find').indexOf(term) >= 0;
|
|
1167
|
+
$row.classList.toggle('is-hidden', !hit);
|
|
1168
|
+
if(hit) {
|
|
1169
|
+
visible++;
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
/* A zone whose every row is filtered out reads as broken
|
|
1174
|
+
* unless it says what happened. */
|
|
1175
|
+
let $none = $zone.querySelector('.GCLASS_ZONE_NO_MATCH');
|
|
1176
|
+
let show_none = (term && $rows.length > 0 && visible === 0);
|
|
1177
|
+
if(show_none && !$none) {
|
|
1178
|
+
let $body = $zone.querySelector('.GCLASS_ZONE_BODY');
|
|
1179
|
+
if($body) {
|
|
1180
|
+
$body.appendChild(createElement2(
|
|
1181
|
+
['div', {class: 'GCLASS_ZONE_EMPTY GCLASS_ZONE_NO_MATCH',
|
|
1182
|
+
i18n: 'no matches'}, t("no matches")]));
|
|
1183
|
+
}
|
|
1184
|
+
} else if(!show_none && $none) {
|
|
1185
|
+
$none.parentNode.removeChild($none);
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
/***************************
|
|
1194
|
+
* Actions
|
|
1195
|
+
***************************/
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
/************************************************************
|
|
1201
|
+
* EV_SET_DESCRIPTION {description, current_state}
|
|
1202
|
+
************************************************************/
|
|
1203
|
+
function ac_set_description(gobj, event, kw, src)
|
|
1204
|
+
{
|
|
1205
|
+
let priv = gobj.priv;
|
|
1206
|
+
let description = kw.description;
|
|
1207
|
+
|
|
1208
|
+
if(description === undefined) {
|
|
1209
|
+
log_error(`${GCLASS_NAME}: EV_SET_DESCRIPTION without a description`);
|
|
1210
|
+
return -1;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
gobj_write_attr(gobj, "description",
|
|
1214
|
+
(description === null)? null: json_deep_copy(description));
|
|
1215
|
+
if(typeof kw.current_state === "string") {
|
|
1216
|
+
gobj_write_attr(gobj, "current_state", kw.current_state);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
priv.model = gclass_view_model(gobj_read_attr(gobj, "description"));
|
|
1220
|
+
render_view(gobj);
|
|
1221
|
+
return 0;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/************************************************************
|
|
1225
|
+
* EV_SET_VIEW_MODE {mode}
|
|
1226
|
+
************************************************************/
|
|
1227
|
+
function ac_set_view_mode(gobj, event, kw, src)
|
|
1228
|
+
{
|
|
1229
|
+
let mode = kw.mode;
|
|
1230
|
+
|
|
1231
|
+
if(!VIEWS.some((v) => v.mode === mode)) {
|
|
1232
|
+
log_error(`${GCLASS_NAME}: unknown view_mode '${mode}'`);
|
|
1233
|
+
return -1;
|
|
1234
|
+
}
|
|
1235
|
+
if(mode === current_view_mode(gobj)) {
|
|
1236
|
+
return 0;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
gobj_write_attr(gobj, "view_mode", mode);
|
|
1240
|
+
apply_view_mode(gobj);
|
|
1241
|
+
render_view(gobj);
|
|
1242
|
+
return 0;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/************************************************************
|
|
1246
|
+
* EV_TOGGLE_ZONE {zone}
|
|
1247
|
+
************************************************************/
|
|
1248
|
+
function ac_toggle_zone(gobj, event, kw, src)
|
|
1249
|
+
{
|
|
1250
|
+
let priv = gobj.priv;
|
|
1251
|
+
let zone = kw.zone;
|
|
1252
|
+
|
|
1253
|
+
if(!ZONES.some((z) => z.id === zone)) {
|
|
1254
|
+
log_error(`${GCLASS_NAME}: unknown zone '${zone}'`);
|
|
1255
|
+
return -1;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
let collapsed = !priv.collapsed.has(zone);
|
|
1259
|
+
if(collapsed) {
|
|
1260
|
+
priv.collapsed.add(zone);
|
|
1261
|
+
} else {
|
|
1262
|
+
priv.collapsed.delete(zone);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/*
|
|
1266
|
+
* The DOM is folded in place, never re-rendered: a rebuild would
|
|
1267
|
+
* throw away the hosted graph and its camera every time somebody
|
|
1268
|
+
* folded an unrelated zone.
|
|
1269
|
+
*/
|
|
1270
|
+
let $zone = priv.$zones?
|
|
1271
|
+
priv.$zones.querySelector(`.GCLASS_ZONE[data-zone="${zone}"]`): null;
|
|
1272
|
+
if(!$zone) {
|
|
1273
|
+
log_error(`${GCLASS_NAME}: zone '${zone}' is not drawn`);
|
|
1274
|
+
return -1;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
$zone.classList.toggle('is-collapsed', collapsed);
|
|
1278
|
+
|
|
1279
|
+
let $body = $zone.querySelector('.GCLASS_ZONE_BODY');
|
|
1280
|
+
if($body) {
|
|
1281
|
+
$body.classList.toggle('is-hidden', collapsed);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
let $toggle = $zone.querySelector('.GCLASS_ZONE_TOGGLE');
|
|
1285
|
+
if($toggle) {
|
|
1286
|
+
$toggle.setAttribute("aria-expanded", collapsed? "false": "true");
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
let $arrow = $zone.querySelector('.GCLASS_ZONE_ARROW i');
|
|
1290
|
+
if($arrow) {
|
|
1291
|
+
$arrow.className = collapsed? 'yi-chevron-right': 'yi-chevron-down';
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/* A graph built inside a folded zone measures 0x0 and keeps that
|
|
1295
|
+
* size for good, so it is built when the zone opens and dropped
|
|
1296
|
+
* when it closes. */
|
|
1297
|
+
if(zone === "machine" && priv.machine_view === "graph") {
|
|
1298
|
+
if(collapsed) {
|
|
1299
|
+
teardown_graph_child(gobj);
|
|
1300
|
+
} else if(!priv.graph_gobj) {
|
|
1301
|
+
build_graph_child(gobj);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
return 0;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/************************************************************
|
|
1309
|
+
* EV_SET_MACHINE_VIEW {mode}
|
|
1310
|
+
************************************************************/
|
|
1311
|
+
function ac_set_machine_view(gobj, event, kw, src)
|
|
1312
|
+
{
|
|
1313
|
+
let priv = gobj.priv;
|
|
1314
|
+
let mode = kw.mode;
|
|
1315
|
+
|
|
1316
|
+
if(!MACHINE_VIEWS.some((v) => v.mode === mode)) {
|
|
1317
|
+
log_error(`${GCLASS_NAME}: unknown machine view '${mode}'`);
|
|
1318
|
+
return -1;
|
|
1319
|
+
}
|
|
1320
|
+
if(mode === priv.machine_view) {
|
|
1321
|
+
return 0;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
priv.machine_view = mode;
|
|
1325
|
+
render_zones(gobj);
|
|
1326
|
+
return 0;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/************************************************************
|
|
1330
|
+
* EV_SEARCH {text}
|
|
1331
|
+
************************************************************/
|
|
1332
|
+
function ac_search(gobj, event, kw, src)
|
|
1333
|
+
{
|
|
1334
|
+
let priv = gobj.priv;
|
|
1335
|
+
priv.search = String(kw.text || "").trim().toLowerCase();
|
|
1336
|
+
apply_search(gobj);
|
|
1337
|
+
return 0;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/************************************************************
|
|
1341
|
+
* EV_COPY_ALL -- the description, as the backend answered it.
|
|
1342
|
+
************************************************************/
|
|
1343
|
+
function ac_copy_all(gobj, event, kw, src)
|
|
1344
|
+
{
|
|
1345
|
+
let description = gobj_read_attr(gobj, "description");
|
|
1346
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
1347
|
+
|
|
1348
|
+
if(description === null || description === undefined) {
|
|
1349
|
+
return 0;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
yui_copy_json(description);
|
|
1353
|
+
|
|
1354
|
+
if($container) {
|
|
1355
|
+
let $btn = $container.querySelector('.EV_COPY_ALL');
|
|
1356
|
+
if($btn) {
|
|
1357
|
+
yui_button_mark_done($btn, t("copied"));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return 0;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/************************************************************
|
|
1364
|
+
* EV_LANGUAGE_CHANGED -- re-translate chrome + re-render
|
|
1365
|
+
*
|
|
1366
|
+
* The tables are BUILT with t(): their headers and their
|
|
1367
|
+
* empty lines carry keys, but the matrix draws state and
|
|
1368
|
+
* event names that are not translatable and legend text that
|
|
1369
|
+
* is. Re-rendering is the only way both end up in the same
|
|
1370
|
+
* language.
|
|
1371
|
+
************************************************************/
|
|
1372
|
+
function ac_language_changed(gobj, event, kw, src)
|
|
1373
|
+
{
|
|
1374
|
+
let priv = gobj.priv;
|
|
1375
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
1376
|
+
|
|
1377
|
+
if($container) {
|
|
1378
|
+
refresh_language($container, t);
|
|
1379
|
+
if(priv.$search) {
|
|
1380
|
+
priv.$search.setAttribute("placeholder", t("search"));
|
|
1381
|
+
}
|
|
1382
|
+
apply_view_mode(gobj);
|
|
1383
|
+
}
|
|
1384
|
+
render_view(gobj);
|
|
1385
|
+
return 0;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/************************************************************
|
|
1389
|
+
* EV_REFRESH
|
|
1390
|
+
************************************************************/
|
|
1391
|
+
function ac_refresh(gobj, event, kw, src)
|
|
1392
|
+
{
|
|
1393
|
+
render_view(gobj);
|
|
1394
|
+
return 0;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/************************************************************
|
|
1398
|
+
* EV_SHOW / EV_HIDE -- host visibility
|
|
1399
|
+
************************************************************/
|
|
1400
|
+
function ac_show(gobj, event, kw, src)
|
|
1401
|
+
{
|
|
1402
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
1403
|
+
if($container) {
|
|
1404
|
+
$container.classList.remove('is-hidden');
|
|
1405
|
+
}
|
|
1406
|
+
return 0;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function ac_hide(gobj, event, kw, src)
|
|
1410
|
+
{
|
|
1411
|
+
let $container = gobj_read_attr(gobj, "$container");
|
|
1412
|
+
if($container) {
|
|
1413
|
+
$container.classList.add('is-hidden');
|
|
1414
|
+
}
|
|
1415
|
+
return 0;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
/***************************
|
|
1422
|
+
* FSM
|
|
1423
|
+
***************************/
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
/*---------------------------------------------*
|
|
1429
|
+
* Global methods table
|
|
1430
|
+
*---------------------------------------------*/
|
|
1431
|
+
const gmt = {
|
|
1432
|
+
mt_create: mt_create,
|
|
1433
|
+
mt_start: mt_start,
|
|
1434
|
+
mt_stop: mt_stop,
|
|
1435
|
+
mt_destroy: mt_destroy,
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
/***************************************************************
|
|
1439
|
+
* Create the GClass
|
|
1440
|
+
***************************************************************/
|
|
1441
|
+
function create_gclass(gclass_name)
|
|
1442
|
+
{
|
|
1443
|
+
if(__gclass__) {
|
|
1444
|
+
log_error(`GClass ALREADY created: ${gclass_name}`);
|
|
1445
|
+
return -1;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
/*---------------------------------------------*
|
|
1449
|
+
* States
|
|
1450
|
+
*---------------------------------------------*/
|
|
1451
|
+
const states = [
|
|
1452
|
+
["ST_IDLE", [
|
|
1453
|
+
["EV_SET_DESCRIPTION", ac_set_description, null],
|
|
1454
|
+
["EV_SET_VIEW_MODE", ac_set_view_mode, null],
|
|
1455
|
+
["EV_TOGGLE_ZONE", ac_toggle_zone, null],
|
|
1456
|
+
["EV_SET_MACHINE_VIEW", ac_set_machine_view, null],
|
|
1457
|
+
["EV_SEARCH", ac_search, null],
|
|
1458
|
+
["EV_COPY_ALL", ac_copy_all, null],
|
|
1459
|
+
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
1460
|
+
["EV_REFRESH", ac_refresh, null],
|
|
1461
|
+
["EV_SHOW", ac_show, null],
|
|
1462
|
+
["EV_HIDE", ac_hide, null]
|
|
1463
|
+
]]
|
|
1464
|
+
];
|
|
1465
|
+
|
|
1466
|
+
/*---------------------------------------------*
|
|
1467
|
+
* Events
|
|
1468
|
+
*---------------------------------------------*/
|
|
1469
|
+
const event_types = [
|
|
1470
|
+
["EV_SET_DESCRIPTION", 0],
|
|
1471
|
+
["EV_SET_VIEW_MODE", 0],
|
|
1472
|
+
["EV_TOGGLE_ZONE", 0],
|
|
1473
|
+
["EV_SET_MACHINE_VIEW", 0],
|
|
1474
|
+
["EV_SEARCH", 0],
|
|
1475
|
+
["EV_COPY_ALL", 0],
|
|
1476
|
+
["EV_LANGUAGE_CHANGED", 0],
|
|
1477
|
+
["EV_REFRESH", 0],
|
|
1478
|
+
["EV_SHOW", 0],
|
|
1479
|
+
["EV_HIDE", 0]
|
|
1480
|
+
];
|
|
1481
|
+
|
|
1482
|
+
__gclass__ = gclass_create(
|
|
1483
|
+
gclass_name,
|
|
1484
|
+
event_types,
|
|
1485
|
+
states,
|
|
1486
|
+
gmt,
|
|
1487
|
+
0, // lmt,
|
|
1488
|
+
attrs_table,
|
|
1489
|
+
PRIVATE_DATA,
|
|
1490
|
+
0, // authz_table,
|
|
1491
|
+
0, // command_table,
|
|
1492
|
+
0, // s_user_trace_level
|
|
1493
|
+
0 // gclass_flag
|
|
1494
|
+
);
|
|
1495
|
+
|
|
1496
|
+
if(!__gclass__) {
|
|
1497
|
+
return -1;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return 0;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/***************************************************************
|
|
1504
|
+
* Register GClass
|
|
1505
|
+
***************************************************************/
|
|
1506
|
+
function register_c_yui_gclass()
|
|
1507
|
+
{
|
|
1508
|
+
/* Idempotent: yui_gclass_view.js registers this gclass for any
|
|
1509
|
+
* host that offers a "view gclass" control, so an app that also
|
|
1510
|
+
* registers it explicitly must not trip "GClass ALREADY created". */
|
|
1511
|
+
if(gclass_find_by_name(GCLASS_NAME, false)) {
|
|
1512
|
+
return 0;
|
|
1513
|
+
}
|
|
1514
|
+
/* The machine's graph view hosts a C_YUI_FSM_GRAPH child: make
|
|
1515
|
+
* sure its gclass exists even if the app never registered it. */
|
|
1516
|
+
if(!gclass_find_by_name("C_YUI_FSM_GRAPH", false)) {
|
|
1517
|
+
register_c_yui_fsm_graph();
|
|
1518
|
+
}
|
|
1519
|
+
return create_gclass(GCLASS_NAME);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
export { register_c_yui_gclass };
|