@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,234 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* gclass_describe.js
|
|
3
|
+
*
|
|
4
|
+
* A gclass, as plain JSON, in the shape the BACKEND answers.
|
|
5
|
+
*
|
|
6
|
+
* There is no gclass viewer in the framework: the C kernel
|
|
7
|
+
* answers `view-gclass` with `gclass2json()`
|
|
8
|
+
* (`kernel/c/gobj-c/src/gobj.c`) and nothing draws it. This
|
|
9
|
+
* module builds the same document for a gclass of the BROWSER
|
|
10
|
+
* yuno, out of the gobj-js registry, so one viewer can show a
|
|
11
|
+
* gclass of either side:
|
|
12
|
+
*
|
|
13
|
+
* {id, gcflag, attrs, commands, gclass_methods,
|
|
14
|
+
* internal_methods, FSM: {events, states}}
|
|
15
|
+
*
|
|
16
|
+
* Two keys the backend does not carry are added at the end,
|
|
17
|
+
* because the browser can answer them for free and the reader
|
|
18
|
+
* asks them first: `instances` and `trace_levels`.
|
|
19
|
+
*
|
|
20
|
+
* Copyright (c) 2026, ArtGins.
|
|
21
|
+
* All Rights Reserved.
|
|
22
|
+
***********************************************************************/
|
|
23
|
+
import {
|
|
24
|
+
data_type_t,
|
|
25
|
+
sdata_flag_t,
|
|
26
|
+
event_flag_t,
|
|
27
|
+
gclass_flag_t,
|
|
28
|
+
gclass_find_by_name,
|
|
29
|
+
} from "@yuneta/gobj-js";
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/***************************************************************
|
|
33
|
+
* Bit tables
|
|
34
|
+
***************************************************************/
|
|
35
|
+
const DATA_TYPE_NAMES = {
|
|
36
|
+
[data_type_t.DTP_STRING]: "DTP_STRING",
|
|
37
|
+
[data_type_t.DTP_BOOLEAN]: "DTP_BOOLEAN",
|
|
38
|
+
[data_type_t.DTP_INTEGER]: "DTP_INTEGER",
|
|
39
|
+
[data_type_t.DTP_REAL]: "DTP_REAL",
|
|
40
|
+
[data_type_t.DTP_LIST]: "DTP_LIST",
|
|
41
|
+
[data_type_t.DTP_DICT]: "DTP_DICT",
|
|
42
|
+
[data_type_t.DTP_JSON]: "DTP_JSON",
|
|
43
|
+
[data_type_t.DTP_POINTER]: "DTP_POINTER",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/************************************************************
|
|
47
|
+
* The names of the bits set in `bits`, in table order.
|
|
48
|
+
************************************************************/
|
|
49
|
+
function bits2names(table, bits)
|
|
50
|
+
{
|
|
51
|
+
let names = [];
|
|
52
|
+
let value = Number(bits) || 0;
|
|
53
|
+
|
|
54
|
+
for(let name of Object.keys(table)) {
|
|
55
|
+
if((value & table[name]) !== 0) {
|
|
56
|
+
names.push(name);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return names;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/************************************************************
|
|
63
|
+
* The name of a data type. An unknown type is reported as
|
|
64
|
+
* the number it is -- silently drawing "DTP_STRING" for a
|
|
65
|
+
* type nobody registered would be worse than saying "17".
|
|
66
|
+
************************************************************/
|
|
67
|
+
function data_type_name(type)
|
|
68
|
+
{
|
|
69
|
+
return DATA_TYPE_NAMES[type] || String(type);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/************************************************************
|
|
73
|
+
* One SDATA descriptor -> one row.
|
|
74
|
+
*
|
|
75
|
+
* A DTP_POINTER default is a live object (a DOM node, a
|
|
76
|
+
* gobj): it is never printed, only named, because a viewer
|
|
77
|
+
* that serializes it would either throw or dump the page.
|
|
78
|
+
************************************************************/
|
|
79
|
+
function sdatadesc2json(it)
|
|
80
|
+
{
|
|
81
|
+
let row = {
|
|
82
|
+
id: it.name,
|
|
83
|
+
type: data_type_name(it.type),
|
|
84
|
+
flag: bits2names(sdata_flag_t, it.flag),
|
|
85
|
+
description: it.description || "",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if(it.type === data_type_t.DTP_POINTER) {
|
|
89
|
+
row.default_value = (it.default_value === null ||
|
|
90
|
+
it.default_value === undefined)? null: "<pointer>";
|
|
91
|
+
} else {
|
|
92
|
+
row.default_value = (it.default_value === undefined)?
|
|
93
|
+
null: it.default_value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return row;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/************************************************************
|
|
100
|
+
* The attrs table of a gclass.
|
|
101
|
+
************************************************************/
|
|
102
|
+
function attrs2json(attrs_table)
|
|
103
|
+
{
|
|
104
|
+
let rows = [];
|
|
105
|
+
if(!Array.isArray(attrs_table)) {
|
|
106
|
+
return rows;
|
|
107
|
+
}
|
|
108
|
+
for(let it of attrs_table) {
|
|
109
|
+
if(!it || !it.name) {
|
|
110
|
+
continue; /* SDATA_END() */
|
|
111
|
+
}
|
|
112
|
+
rows.push(sdatadesc2json(it));
|
|
113
|
+
}
|
|
114
|
+
return rows;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/************************************************************
|
|
118
|
+
* The command table of a gclass, with the parameters of
|
|
119
|
+
* each command (the `schema` slot of SDATACM).
|
|
120
|
+
************************************************************/
|
|
121
|
+
function commands2json(command_table)
|
|
122
|
+
{
|
|
123
|
+
let rows = [];
|
|
124
|
+
if(!Array.isArray(command_table)) {
|
|
125
|
+
return rows;
|
|
126
|
+
}
|
|
127
|
+
for(let it of command_table) {
|
|
128
|
+
if(!it || !it.name) {
|
|
129
|
+
continue; /* SDATA_END() */
|
|
130
|
+
}
|
|
131
|
+
let row = {
|
|
132
|
+
id: it.name,
|
|
133
|
+
alias: it.alias || [],
|
|
134
|
+
description: it.description || "",
|
|
135
|
+
};
|
|
136
|
+
if(Array.isArray(it.schema)) {
|
|
137
|
+
row.parameters = attrs2json(it.schema);
|
|
138
|
+
}
|
|
139
|
+
rows.push(row);
|
|
140
|
+
}
|
|
141
|
+
return rows;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/************************************************************
|
|
145
|
+
* The methods the gclass actually implements. A table of
|
|
146
|
+
* every possible one, mostly empty, tells the reader nothing.
|
|
147
|
+
************************************************************/
|
|
148
|
+
function methods2json(mt)
|
|
149
|
+
{
|
|
150
|
+
let names = [];
|
|
151
|
+
if(!mt || typeof mt !== "object") {
|
|
152
|
+
return names;
|
|
153
|
+
}
|
|
154
|
+
for(let key of Object.keys(mt)) {
|
|
155
|
+
if(typeof mt[key] === "function") {
|
|
156
|
+
names.push(key);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return names;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/************************************************************
|
|
163
|
+
* The FSM: the events the gclass declares, and what each
|
|
164
|
+
* state does with them.
|
|
165
|
+
************************************************************/
|
|
166
|
+
function fsm2json(gclass)
|
|
167
|
+
{
|
|
168
|
+
let events = [];
|
|
169
|
+
for(let ev of (gclass.dl_events || [])) {
|
|
170
|
+
events.push({
|
|
171
|
+
event_name: ev.event_name,
|
|
172
|
+
event_flag: bits2names(event_flag_t, ev.event_flag),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let states = {};
|
|
177
|
+
for(let st of (gclass.dl_states || [])) {
|
|
178
|
+
let actions = [];
|
|
179
|
+
for(let ac of (st.dl_actions || [])) {
|
|
180
|
+
actions.push({
|
|
181
|
+
event_name: ac.event_name,
|
|
182
|
+
action: ac.action? (ac.action.name || "action"): "",
|
|
183
|
+
next_state: ac.next_state || "",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
states[st.state_name] = actions;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {events: events, states: states};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/************************************************************
|
|
193
|
+
* The description of a gclass REGISTERED IN THIS BROWSER YUNO,
|
|
194
|
+
* or null when the registry does not know that name.
|
|
195
|
+
*
|
|
196
|
+
* Null is the answer for a gclass of a BACKEND yuno too: the
|
|
197
|
+
* browser registry carries the GUI gclasses and nothing else,
|
|
198
|
+
* so the caller asks the backend `view-gclass` for those and
|
|
199
|
+
* shows whatever it answers -- this same shape.
|
|
200
|
+
************************************************************/
|
|
201
|
+
function describe_local_gclass(gclass_name)
|
|
202
|
+
{
|
|
203
|
+
if(!gclass_name) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let gclass = gclass_find_by_name(gclass_name, false);
|
|
208
|
+
if(!gclass) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
id: gclass.gclass_name,
|
|
214
|
+
gcflag: bits2names(gclass_flag_t, gclass.gclass_flag),
|
|
215
|
+
attrs: attrs2json(gclass.attrs_table),
|
|
216
|
+
commands: commands2json(gclass.command_table),
|
|
217
|
+
gclass_methods: methods2json(gclass.gmt),
|
|
218
|
+
internal_methods: methods2json(gclass.lmt),
|
|
219
|
+
FSM: fsm2json(gclass),
|
|
220
|
+
instances: Number(gclass.instances) || 0,
|
|
221
|
+
trace_levels: Array.isArray(gclass.s_user_trace_level)?
|
|
222
|
+
gclass.s_user_trace_level: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export {
|
|
227
|
+
bits2names,
|
|
228
|
+
data_type_name,
|
|
229
|
+
attrs2json,
|
|
230
|
+
commands2json,
|
|
231
|
+
methods2json,
|
|
232
|
+
fsm2json,
|
|
233
|
+
describe_local_gclass,
|
|
234
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {describe, it, expect, beforeAll} from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
SDATA,
|
|
4
|
+
SDATA_END,
|
|
5
|
+
SDATACM,
|
|
6
|
+
data_type_t,
|
|
7
|
+
event_flag_t,
|
|
8
|
+
gclass_create,
|
|
9
|
+
gclass_flag_t,
|
|
10
|
+
sdata_flag_t,
|
|
11
|
+
} from "@yuneta/gobj-js";
|
|
12
|
+
|
|
13
|
+
import {bits2names, describe_local_gclass} from "./gclass_describe.js";
|
|
14
|
+
|
|
15
|
+
/* A gclass with one of everything the viewer draws. */
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
const attrs_table = [
|
|
18
|
+
SDATA(data_type_t.DTP_STRING, "title", sdata_flag_t.SDF_RD, "", "The title"),
|
|
19
|
+
SDATA(data_type_t.DTP_INTEGER, "timeout",
|
|
20
|
+
sdata_flag_t.SDF_WR|sdata_flag_t.SDF_PERSIST, 1000, "Timeout"),
|
|
21
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "The DOM node"),
|
|
22
|
+
SDATA_END(),
|
|
23
|
+
];
|
|
24
|
+
const command_table = [
|
|
25
|
+
SDATACM(data_type_t.DTP_STRING, "help", ["h"], [
|
|
26
|
+
SDATA(data_type_t.DTP_STRING, "cmd", 0, "", "The command"),
|
|
27
|
+
SDATA_END(),
|
|
28
|
+
], null, "Ask for help"),
|
|
29
|
+
SDATA_END(),
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
gclass_create(
|
|
33
|
+
"C_TEST_DESCRIBE",
|
|
34
|
+
[
|
|
35
|
+
["EV_GO", 0],
|
|
36
|
+
["EV_DONE", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
["ST_IDLE", [["EV_GO", function ac_go() {}, "ST_WORKING"]]],
|
|
40
|
+
["ST_WORKING", [["EV_DONE", function ac_done() {}, "ST_IDLE"]]],
|
|
41
|
+
],
|
|
42
|
+
{mt_create: function() {}, mt_start: function() {}},
|
|
43
|
+
0,
|
|
44
|
+
attrs_table,
|
|
45
|
+
{},
|
|
46
|
+
0,
|
|
47
|
+
command_table,
|
|
48
|
+
0,
|
|
49
|
+
gclass_flag_t.gcflag_singleton
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("bits2names", () => {
|
|
54
|
+
it("names every bit set, and nothing else", () => {
|
|
55
|
+
expect(bits2names(sdata_flag_t,
|
|
56
|
+
sdata_flag_t.SDF_RD|sdata_flag_t.SDF_PERSIST))
|
|
57
|
+
.toEqual(["SDF_RD", "SDF_PERSIST"]);
|
|
58
|
+
expect(bits2names(sdata_flag_t, 0)).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("describe_local_gclass", () => {
|
|
63
|
+
it("an unregistered gclass is null, never an empty document", () => {
|
|
64
|
+
expect(describe_local_gclass("C_NOT_THERE")).toBe(null);
|
|
65
|
+
expect(describe_local_gclass("")).toBe(null);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("answers the shape the backend view-gclass answers", () => {
|
|
69
|
+
let g = describe_local_gclass("C_TEST_DESCRIBE");
|
|
70
|
+
expect(Object.keys(g)).toEqual([
|
|
71
|
+
"id", "gcflag", "attrs", "commands", "gclass_methods",
|
|
72
|
+
"internal_methods", "FSM", "instances", "trace_levels",
|
|
73
|
+
]);
|
|
74
|
+
expect(g.id).toBe("C_TEST_DESCRIBE");
|
|
75
|
+
expect(g.gcflag).toEqual(["gcflag_singleton"]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("names the type and the flags of every attr", () => {
|
|
79
|
+
let attrs = describe_local_gclass("C_TEST_DESCRIBE").attrs;
|
|
80
|
+
expect(attrs.length).toBe(3);
|
|
81
|
+
expect(attrs[0]).toEqual({
|
|
82
|
+
id: "title",
|
|
83
|
+
type: "DTP_STRING",
|
|
84
|
+
flag: ["SDF_RD"],
|
|
85
|
+
description: "The title",
|
|
86
|
+
default_value: "",
|
|
87
|
+
});
|
|
88
|
+
expect(attrs[1].flag).toEqual(["SDF_WR", "SDF_PERSIST"]);
|
|
89
|
+
expect(attrs[1].default_value).toBe(1000);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("never prints a pointer default -- it names it", () => {
|
|
93
|
+
let attrs = describe_local_gclass("C_TEST_DESCRIBE").attrs;
|
|
94
|
+
let container = attrs.find(a => a.id === "$container");
|
|
95
|
+
expect(container.type).toBe("DTP_POINTER");
|
|
96
|
+
expect(container.default_value).toBe(null);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("carries the commands with their parameters", () => {
|
|
100
|
+
let cmds = describe_local_gclass("C_TEST_DESCRIBE").commands;
|
|
101
|
+
expect(cmds.length).toBe(1);
|
|
102
|
+
expect(cmds[0].id).toBe("help");
|
|
103
|
+
expect(cmds[0].alias).toEqual(["h"]);
|
|
104
|
+
expect(cmds[0].parameters.map(p => p.id)).toEqual(["cmd"]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("lists only the methods the gclass implements", () => {
|
|
108
|
+
let g = describe_local_gclass("C_TEST_DESCRIBE");
|
|
109
|
+
expect(g.gclass_methods).toEqual(["mt_create", "mt_start"]);
|
|
110
|
+
expect(g.internal_methods).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("draws the FSM as events plus what each state does with them", () => {
|
|
114
|
+
let fsm = describe_local_gclass("C_TEST_DESCRIBE").FSM;
|
|
115
|
+
expect(fsm.events).toEqual([
|
|
116
|
+
{event_name: "EV_GO", event_flag: []},
|
|
117
|
+
{event_name: "EV_DONE",
|
|
118
|
+
event_flag: ["EVF_NO_WARN_SUBS", "EVF_OUTPUT_EVENT"]},
|
|
119
|
+
]);
|
|
120
|
+
expect(Object.keys(fsm.states)).toEqual(["ST_IDLE", "ST_WORKING"]);
|
|
121
|
+
expect(fsm.states["ST_IDLE"]).toEqual([
|
|
122
|
+
{event_name: "EV_GO", action: "ac_go", next_state: "ST_WORKING"},
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
});
|