@yuneta/gobj-ui 7.23.19 → 7.23.20
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/dist/gobj-ui.cjs.js +292 -233
- package/dist/gobj-ui.es.js +292 -233
- package/package.json +1 -1
- package/src/c_yui_json_graph.js +84 -29
- package/src/json_view_helpers.js +67 -0
- package/src/json_view_helpers.test.js +67 -0
package/src/c_yui_json_graph.js
CHANGED
|
@@ -72,6 +72,11 @@ import {
|
|
|
72
72
|
import { ensure_drag_canvas_patch } from "./g6_drag_canvas_touch.js";
|
|
73
73
|
import { ensure_pinch_zoom_patch } from "./g6_touch_gestures.js";
|
|
74
74
|
import { yui_is_dark, yui_theme_now, yui_watch_theme } from "./yui_theme.js";
|
|
75
|
+
import {
|
|
76
|
+
is_pure_collection,
|
|
77
|
+
has_branch,
|
|
78
|
+
count_branches,
|
|
79
|
+
} from "./json_view_helpers.js";
|
|
75
80
|
|
|
76
81
|
/***************************************************************
|
|
77
82
|
* Constants
|
|
@@ -770,7 +775,7 @@ function build_graph(gobj)
|
|
|
770
775
|
/************************************************************
|
|
771
776
|
* Build cell value HTML (matching old mx_json_viewer colors)
|
|
772
777
|
************************************************************/
|
|
773
|
-
function build_cell_html(key, value, type, dark, matched)
|
|
778
|
+
function build_cell_html(key, value, type, dark, matched, fold)
|
|
774
779
|
{
|
|
775
780
|
/*
|
|
776
781
|
* A match is a LIGHT chip on BOTH themes, so what sits on it is
|
|
@@ -813,6 +818,19 @@ function build_cell_html(key, value, type, dark, matched)
|
|
|
813
818
|
let row = `<span style="color:${key_color}">• ${escapeHtml(String(key))}: </span>` +
|
|
814
819
|
`<span style="color:${color}">${display_value}</span>`;
|
|
815
820
|
|
|
821
|
+
/*
|
|
822
|
+
* A fold chip ON THE ROW, for a container that gets no card of its
|
|
823
|
+
* own: the control belongs next to the key it folds, which is
|
|
824
|
+
* where a tree puts it, and it is the only handle those children
|
|
825
|
+
* have -- their parent's header chip folds every branch it has,
|
|
826
|
+
* not this one.
|
|
827
|
+
*/
|
|
828
|
+
if(fold) {
|
|
829
|
+
row += `<span style="margin-left:8px;">` +
|
|
830
|
+
fold_toggle_html(fold.path, true, fold.folded, fold.branches, fold.colors) +
|
|
831
|
+
`</span>`;
|
|
832
|
+
}
|
|
833
|
+
|
|
816
834
|
if(matched) {
|
|
817
835
|
/* The highlight is baked into the card's MARKUP and not set as
|
|
818
836
|
* a G6 node state: the key shape of an `html` node is a DOM
|
|
@@ -985,6 +1003,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
|
|
|
985
1003
|
let pending_complex = [];
|
|
986
1004
|
let card_matched = false;
|
|
987
1005
|
|
|
1006
|
+
let colors = json_card_style(
|
|
1007
|
+
is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark
|
|
1008
|
+
);
|
|
1009
|
+
|
|
988
1010
|
let entries = is_dict ? Object.entries(kw) : kw.map((v, i) => [i, v]);
|
|
989
1011
|
|
|
990
1012
|
for(let [key, value] of entries) {
|
|
@@ -996,32 +1018,64 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
|
|
|
996
1018
|
}
|
|
997
1019
|
|
|
998
1020
|
/*
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
* the same; an array of a thousand prints a thousand-row card
|
|
1004
|
-
* nobody can read, beside the thousand cards it duplicates.
|
|
1021
|
+
* EVERY key is a row, containers included: `cols` is one key of
|
|
1022
|
+
* the topic dict exactly like `pkey` and `tkey`, and a drawing
|
|
1023
|
+
* that leaves it out of the card does not say what the document
|
|
1024
|
+
* says.
|
|
1005
1025
|
*
|
|
1006
|
-
* What
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1026
|
+
* What a container key does NOT get is its contents repeated
|
|
1027
|
+
* underneath it -- the row says `[14]` and the fourteen cards
|
|
1028
|
+
* say the rest. That is the part which does not scale: an array
|
|
1029
|
+
* of a thousand dicts would otherwise print a thousand-row card
|
|
1030
|
+
* beside the thousand cards it duplicates.
|
|
1010
1031
|
*/
|
|
1011
|
-
|
|
1012
|
-
|
|
1032
|
+
let fold = null;
|
|
1033
|
+
if(has_branch(value)) {
|
|
1013
1034
|
pending_complex.push({key: key, value: value});
|
|
1014
|
-
|
|
1035
|
+
|
|
1036
|
+
/*
|
|
1037
|
+
* A child that gets no card of its own is folded from HERE
|
|
1038
|
+
* or from nowhere.
|
|
1039
|
+
*/
|
|
1040
|
+
if(is_pure_collection(value)) {
|
|
1041
|
+
let child_path = add_segment(path, key);
|
|
1042
|
+
fold = {
|
|
1043
|
+
path: child_path,
|
|
1044
|
+
folded: priv.collapsed.has(child_path),
|
|
1045
|
+
branches: count_branches(value),
|
|
1046
|
+
colors: colors,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1015
1049
|
}
|
|
1016
1050
|
|
|
1017
|
-
lines.push(build_cell_html(key, value, type, dark, matched));
|
|
1051
|
+
lines.push(build_cell_html(key, value, type, dark, matched, fold));
|
|
1018
1052
|
}
|
|
1019
1053
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1054
|
+
/*
|
|
1055
|
+
* A pure collection is not drawn: its row above names it and its
|
|
1056
|
+
* children hang from THIS card's parent. The root always gets a
|
|
1057
|
+
* card, or a document that is a plain list of records would have
|
|
1058
|
+
* nothing to hang anything from.
|
|
1059
|
+
*/
|
|
1060
|
+
if(parent_id && is_pure_collection(kw)) {
|
|
1061
|
+
if(priv.collapsed.has(path)) {
|
|
1062
|
+
return parent_id;
|
|
1063
|
+
}
|
|
1064
|
+
for(let pending of pending_complex) {
|
|
1065
|
+
build_json_nodes(
|
|
1066
|
+
gobj,
|
|
1067
|
+
add_segment(path, pending.key),
|
|
1068
|
+
pending.value,
|
|
1069
|
+
nodes,
|
|
1070
|
+
edges,
|
|
1071
|
+
parent_id
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
return parent_id;
|
|
1023
1075
|
}
|
|
1024
1076
|
|
|
1077
|
+
let label = get_group_label(path) || (is_dict ? "{}" : "[]");
|
|
1078
|
+
|
|
1025
1079
|
/*
|
|
1026
1080
|
* Folded: keep the card, drop the branch, and SAY how much went
|
|
1027
1081
|
* with it. A cut that is not marked reads as a document that ends
|
|
@@ -1032,9 +1086,6 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
|
|
|
1032
1086
|
*/
|
|
1033
1087
|
let foldable = pending_complex.length > 0;
|
|
1034
1088
|
let folded = foldable && priv.collapsed.has(path);
|
|
1035
|
-
let colors = json_card_style(
|
|
1036
|
-
is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark
|
|
1037
|
-
);
|
|
1038
1089
|
let border_color = card_matched? (dark? "#ffb300": "#ff8f00"): colors.border;
|
|
1039
1090
|
let border_width = card_matched? 2: 1;
|
|
1040
1091
|
|
|
@@ -1190,8 +1241,11 @@ function load_json(gobj)
|
|
|
1190
1241
|
|
|
1191
1242
|
/************************************************************
|
|
1192
1243
|
* Every path that HAS a branch to fold, in the same recursion
|
|
1193
|
-
* build_json_nodes uses — so "collapse all" can never name
|
|
1194
|
-
*
|
|
1244
|
+
* build_json_nodes uses — so "collapse all" can never name
|
|
1245
|
+
* something with no handle. The handle is a card's header chip
|
|
1246
|
+
* for a container that gets a card, and the chip on its ROW in
|
|
1247
|
+
* the parent for a pure collection, which gets none; both are
|
|
1248
|
+
* keyed by this same path. The root is excluded: folding it
|
|
1195
1249
|
* would leave an empty canvas.
|
|
1196
1250
|
************************************************************/
|
|
1197
1251
|
function collect_foldable_paths(value, path, out, is_root)
|
|
@@ -1202,20 +1256,21 @@ function collect_foldable_paths(value, path, out, is_root)
|
|
|
1202
1256
|
return false;
|
|
1203
1257
|
}
|
|
1204
1258
|
|
|
1205
|
-
|
|
1259
|
+
/* `found_branch` and not `has_branch`: that name belongs to the
|
|
1260
|
+
* helper above, and a local would shadow it here. */
|
|
1261
|
+
let found_branch = false;
|
|
1206
1262
|
let entries = is_dict ? Object.entries(value) : value.map((v, i) => [i, v]);
|
|
1207
1263
|
for(let [key, child] of entries) {
|
|
1208
|
-
if((
|
|
1209
|
-
|
|
1210
|
-
has_branch = true;
|
|
1264
|
+
if(has_branch(child)) {
|
|
1265
|
+
found_branch = true;
|
|
1211
1266
|
collect_foldable_paths(child, add_segment(path, key), out, false);
|
|
1212
1267
|
}
|
|
1213
1268
|
}
|
|
1214
1269
|
|
|
1215
|
-
if(
|
|
1270
|
+
if(found_branch && !is_root) {
|
|
1216
1271
|
out.push(path);
|
|
1217
1272
|
}
|
|
1218
|
-
return
|
|
1273
|
+
return found_branch;
|
|
1219
1274
|
}
|
|
1220
1275
|
|
|
1221
1276
|
/************************************************************
|
package/src/json_view_helpers.js
CHANGED
|
@@ -314,3 +314,70 @@ export function container_label(value, max_chars)
|
|
|
314
314
|
}
|
|
315
315
|
return text;
|
|
316
316
|
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
/************************************************************
|
|
320
|
+
* A dict, in the JSON sense: not an array and not null.
|
|
321
|
+
************************************************************/
|
|
322
|
+
function is_plain_object(v)
|
|
323
|
+
{
|
|
324
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/************************************************************
|
|
328
|
+
* Does this container hold ONLY other containers?
|
|
329
|
+
*
|
|
330
|
+
* Such a value is not a thing, it is a LIST of things -- and a
|
|
331
|
+
* list is fully described by the row that names it in its
|
|
332
|
+
* parent, plus the edges to what it holds. Drawing it as a
|
|
333
|
+
* card of its own put a node in the middle of the graph with no
|
|
334
|
+
* data in it, saying nothing the parent's row did not already
|
|
335
|
+
* say, and pushed everything below it one level down.
|
|
336
|
+
*
|
|
337
|
+
* An EMPTY container inside counts as content: `{0}` is
|
|
338
|
+
* something the card has to show, and there is no child card
|
|
339
|
+
* to show it instead.
|
|
340
|
+
************************************************************/
|
|
341
|
+
export function is_pure_collection(value)
|
|
342
|
+
{
|
|
343
|
+
let entries = is_plain_object(value)? Object.entries(value)
|
|
344
|
+
: Array.isArray(value)? value.map((v, i) => [i, v])
|
|
345
|
+
: [];
|
|
346
|
+
|
|
347
|
+
if(entries.length === 0) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
for(let [, v] of entries) {
|
|
351
|
+
if(!has_branch(v)) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/************************************************************
|
|
359
|
+
* A value that gets a card, or children, of its own.
|
|
360
|
+
************************************************************/
|
|
361
|
+
export function has_branch(value)
|
|
362
|
+
{
|
|
363
|
+
return (is_plain_object(value) && Object.keys(value).length > 0) ||
|
|
364
|
+
(Array.isArray(value) && value.length > 0);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/************************************************************
|
|
368
|
+
* How many branches a container has, which is what the fold
|
|
369
|
+
* chip counts.
|
|
370
|
+
************************************************************/
|
|
371
|
+
export function count_branches(value)
|
|
372
|
+
{
|
|
373
|
+
let entries = is_plain_object(value)? Object.values(value)
|
|
374
|
+
: Array.isArray(value)? value
|
|
375
|
+
: [];
|
|
376
|
+
let n = 0;
|
|
377
|
+
for(let v of entries) {
|
|
378
|
+
if(has_branch(v)) {
|
|
379
|
+
n++;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return n;
|
|
383
|
+
}
|
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
json_text_dump,
|
|
19
19
|
pick_view_mode,
|
|
20
20
|
container_label,
|
|
21
|
+
is_pure_collection,
|
|
22
|
+
has_branch,
|
|
23
|
+
count_branches,
|
|
21
24
|
} from "./json_view_helpers.js";
|
|
22
25
|
|
|
23
26
|
|
|
@@ -243,3 +246,67 @@ test("container_label: cut at max_chars, and said with an ellipsis", () => {
|
|
|
243
246
|
expect(container_label({id: uuid}, 0)).toBe(uuid);
|
|
244
247
|
expect(container_label({id: uuid}, undefined)).toBe(uuid);
|
|
245
248
|
});
|
|
249
|
+
|
|
250
|
+
/***************************************************************
|
|
251
|
+
* has_branch() / count_branches() / is_pure_collection()
|
|
252
|
+
***************************************************************/
|
|
253
|
+
test("has_branch: only a NON-EMPTY container branches", () => {
|
|
254
|
+
expect(has_branch({a: 1})).toBe(true);
|
|
255
|
+
expect(has_branch([1])).toBe(true);
|
|
256
|
+
expect(has_branch({})).toBe(false);
|
|
257
|
+
expect(has_branch([])).toBe(false);
|
|
258
|
+
expect(has_branch("x")).toBe(false);
|
|
259
|
+
expect(has_branch(0)).toBe(false);
|
|
260
|
+
expect(has_branch(null)).toBe(false);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("count_branches: counts what gets a card, not what is there", () => {
|
|
264
|
+
expect(count_branches({a: 1, b: {}, c: {x: 1}, d: [1]})).toBe(2);
|
|
265
|
+
expect(count_branches([{}, [], {a: 1}])).toBe(1);
|
|
266
|
+
expect(count_branches({a: 1})).toBe(0);
|
|
267
|
+
expect(count_branches("x")).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("is_pure_collection: a LIST of things, not a thing", () => {
|
|
271
|
+
/* Every entry a container with something in it. */
|
|
272
|
+
expect(is_pure_collection([{a: 1}, {b: 2}])).toBe(true);
|
|
273
|
+
expect(is_pure_collection({x: {a: 1}, y: [1]})).toBe(true);
|
|
274
|
+
|
|
275
|
+
/* One scalar is enough to make it a thing with content of its own,
|
|
276
|
+
* and so is an EMPTY container: `{0}` has no child card to show it. */
|
|
277
|
+
expect(is_pure_collection([{a: 1}, 2])).toBe(false);
|
|
278
|
+
expect(is_pure_collection({x: {a: 1}, n: 3})).toBe(false);
|
|
279
|
+
expect(is_pure_collection([{a: 1}, {}])).toBe(false);
|
|
280
|
+
|
|
281
|
+
expect(is_pure_collection([])).toBe(false);
|
|
282
|
+
expect(is_pure_collection({})).toBe(false);
|
|
283
|
+
expect(is_pure_collection("x")).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("is_pure_collection: the schema this came from", () => {
|
|
287
|
+
/* The `devices` topic descriptor, cut to shape: `cols` is one key of
|
|
288
|
+
* the dict like `pkey` is, and the only one that branches. */
|
|
289
|
+
let col = {id: "id", header: "Device", type: "string", flag: [], enum: {}};
|
|
290
|
+
let topic = {
|
|
291
|
+
topic_name: "devices",
|
|
292
|
+
pkey: "id",
|
|
293
|
+
pkey2s: [], /* empty: a row, not a branch */
|
|
294
|
+
tkey: "tm",
|
|
295
|
+
system_flag: 1,
|
|
296
|
+
topic_version: 3,
|
|
297
|
+
cols: [col, col, col],
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/* The topic is NOT a pure collection: it has six scalar rows, so it
|
|
301
|
+
* keeps its card. */
|
|
302
|
+
expect(is_pure_collection(topic)).toBe(false);
|
|
303
|
+
expect(count_branches(topic)).toBe(1);
|
|
304
|
+
|
|
305
|
+
/* `cols` IS one: nothing but columns. It gets no card of its own --
|
|
306
|
+
* its row in the topic names it, and the columns hang from the topic. */
|
|
307
|
+
expect(is_pure_collection(topic.cols)).toBe(true);
|
|
308
|
+
expect(count_branches(topic.cols)).toBe(3);
|
|
309
|
+
|
|
310
|
+
/* A column has content, so it keeps its card. */
|
|
311
|
+
expect(is_pure_collection(col)).toBe(false);
|
|
312
|
+
});
|