@yuneta/gobj-ui 7.23.143 → 7.23.145
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 +16 -2
- package/dist/gobj-ui.cjs.js +765 -162
- package/dist/gobj-ui.es.js +766 -163
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +50 -2
- package/src/c_yui_json_pad.css +94 -0
- package/src/c_yui_json_pad.js +567 -114
- package/src/json_view_helpers.js +56 -1
- package/src/json_view_helpers.test.js +43 -0
- package/src/yui_icons.css +12 -0
package/src/json_view_helpers.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* viewer). Kept out of the gclass so it can be unit-tested with
|
|
6
6
|
* no DOM: collapsed-sentinel detection, path (segments) algebra,
|
|
7
7
|
* search matching, timestamp recognition/formatting, the JSON
|
|
8
|
-
* type discriminator and the text view's capped dump
|
|
8
|
+
* type discriminator and the text view's capped dump -- and the
|
|
9
|
+
* differences of two documents, for the JSON pad's compare.
|
|
9
10
|
*
|
|
10
11
|
* Path convention mirrors the C kernel (kw_collapse / kw_find_path
|
|
11
12
|
* in kwid.c): segments are joined by the backtick delimiter, arrays
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
* Copyright (c) 2026, ArtGins.
|
|
15
16
|
* All Rights Reserved.
|
|
16
17
|
***********************************************************************/
|
|
18
|
+
import {json2flat, flat_diff} from "@yuneta/gobj-js";
|
|
17
19
|
|
|
18
20
|
/*
|
|
19
21
|
* Path delimiter, identical to the kernel's `delimiter` in kwid.c.
|
|
@@ -397,3 +399,56 @@ export function count_branches(value)
|
|
|
397
399
|
}
|
|
398
400
|
return n;
|
|
399
401
|
}
|
|
402
|
+
|
|
403
|
+
/************************************************************
|
|
404
|
+
* What differs between two documents: one row per id of
|
|
405
|
+
* their flat form (json2flat), sorted by id in natural order,
|
|
406
|
+
* so `[2]` comes before `[10]`:
|
|
407
|
+
*
|
|
408
|
+
* [{id, kind: "added"|"removed"|"changed", from?, to?}]
|
|
409
|
+
*
|
|
410
|
+
* `from` is the first document's value and `to` the second's:
|
|
411
|
+
* an added row has no `from`, a removed row no `to`.
|
|
412
|
+
*
|
|
413
|
+
* Two roots that are not containers of the same kind have no
|
|
414
|
+
* ids in common to compare by: they are one value each, and
|
|
415
|
+
* the row that says they differ has the empty id.
|
|
416
|
+
*
|
|
417
|
+
* Throws what json2flat throws (a document too deep).
|
|
418
|
+
************************************************************/
|
|
419
|
+
export function json_diff_rows(doc_a, doc_b)
|
|
420
|
+
{
|
|
421
|
+
const kind_of = (v) => {
|
|
422
|
+
if(Array.isArray(v)) {
|
|
423
|
+
return "array";
|
|
424
|
+
}
|
|
425
|
+
if(v !== null && typeof v === "object") {
|
|
426
|
+
return "object";
|
|
427
|
+
}
|
|
428
|
+
return "scalar";
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
let kind_a = kind_of(doc_a);
|
|
432
|
+
if(kind_a === "scalar" || kind_a !== kind_of(doc_b)) {
|
|
433
|
+
if(JSON.stringify(doc_a) === JSON.stringify(doc_b)) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
return [{id: "", kind: "changed", from: doc_a, to: doc_b}];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let diff = flat_diff(json2flat(doc_a), json2flat(doc_b));
|
|
440
|
+
let rows = [];
|
|
441
|
+
for(const id of Object.keys(diff.added)) {
|
|
442
|
+
rows.push({id: id, kind: "added", to: diff.added[id]});
|
|
443
|
+
}
|
|
444
|
+
for(const id of Object.keys(diff.removed)) {
|
|
445
|
+
rows.push({id: id, kind: "removed", from: diff.removed[id]});
|
|
446
|
+
}
|
|
447
|
+
for(const id of Object.keys(diff.changed)) {
|
|
448
|
+
rows.push({id: id, kind: "changed", from: diff.changed[id].from, to: diff.changed[id].to});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const collator = new Intl.Collator("en", {numeric: true});
|
|
452
|
+
rows.sort((x, y) => collator.compare(x.id, y.id));
|
|
453
|
+
return rows;
|
|
454
|
+
}
|
|
@@ -21,9 +21,52 @@ import {
|
|
|
21
21
|
is_pure_collection,
|
|
22
22
|
has_branch,
|
|
23
23
|
count_branches,
|
|
24
|
+
json_diff_rows,
|
|
24
25
|
} from "./json_view_helpers.js";
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
/*============================================================
|
|
29
|
+
* json_diff_rows
|
|
30
|
+
*============================================================*/
|
|
31
|
+
test("json_diff_rows: two equal documents have no rows, whatever their key order", () => {
|
|
32
|
+
expect(json_diff_rows({a: 1, b: [1, {c: null}]}, {b: [1, {c: null}], a: 1})).toEqual([]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("json_diff_rows: one row per differing id, sorted by id", () => {
|
|
36
|
+
let a = {a: 1, b: {c: [1, 2]}, e: "x"};
|
|
37
|
+
let b = {a: 2, b: {c: [1]}, d: {}, e: "x"};
|
|
38
|
+
expect(json_diff_rows(a, b)).toEqual([
|
|
39
|
+
{id: "a", kind: "changed", from: 1, to: 2},
|
|
40
|
+
{id: "b`c`[1]", kind: "removed", from: 2},
|
|
41
|
+
{id: "d", kind: "added", to: {}},
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("json_diff_rows: array indexes sort as numbers, [2] before [10]", () => {
|
|
46
|
+
let a = [];
|
|
47
|
+
let b = [];
|
|
48
|
+
for(let i = 0; i < 12; i++) {
|
|
49
|
+
a.push(i);
|
|
50
|
+
b.push((i === 2 || i === 10)? -1 : i);
|
|
51
|
+
}
|
|
52
|
+
expect(json_diff_rows(a, b).map((r) => r.id)).toEqual(["[2]", "[10]"]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("json_diff_rows: a leaf that becomes a container is one id gone and one new", () => {
|
|
56
|
+
expect(json_diff_rows({a: 1}, {a: {b: 2}})).toEqual([
|
|
57
|
+
{id: "a", kind: "removed", from: 1},
|
|
58
|
+
{id: "a`b", kind: "added", to: 2},
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("json_diff_rows: roots that are not containers of one kind are one value each", () => {
|
|
63
|
+
expect(json_diff_rows(1, 1)).toEqual([]);
|
|
64
|
+
expect(json_diff_rows("x", "y")).toEqual([{id: "", kind: "changed", from: "x", to: "y"}]);
|
|
65
|
+
expect(json_diff_rows({}, [])).toEqual([{id: "", kind: "changed", from: {}, to: []}]);
|
|
66
|
+
expect(json_diff_rows(null, {a: 1})).toEqual([{id: "", kind: "changed", from: null, to: {a: 1}}]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
|
|
27
70
|
/*============================================================
|
|
28
71
|
* json_type
|
|
29
72
|
*============================================================*/
|
package/src/yui_icons.css
CHANGED
|
@@ -392,3 +392,15 @@
|
|
|
392
392
|
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath fill-rule='evenodd' d='M190 32h68l150 448h-72l-40-120H152l-40 120H40L190 32zM172 300h104l-52-158-52 158z'/%3E%3C/svg%3E");
|
|
393
393
|
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath fill-rule='evenodd' d='M190 32h68l150 448h-72l-40-120H152l-40 120H40L190 32zM172 300h104l-52-158-52 158z'/%3E%3C/svg%3E");
|
|
394
394
|
}
|
|
395
|
+
|
|
396
|
+
/* Two columns, for "a second pane beside the first" (the JSON pad). */
|
|
397
|
+
.yi-columns::before {
|
|
398
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill-rule='evenodd' d='M0 96C0 60.7 28.7 32 64 32h384c35.3 0 64 28.7 64 64v320c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zm64 32v288h160V128H64zm224 0v288h160V128H288z'/%3E%3C/svg%3E");
|
|
399
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill-rule='evenodd' d='M0 96C0 60.7 28.7 32 64 32h384c35.3 0 64 28.7 64 64v320c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zm64 32v288h160V128H64zm224 0v288h160V128H288z'/%3E%3C/svg%3E");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Two opposite arrows, for "compare" (the JSON pad's differences). */
|
|
403
|
+
.yi-right-left::before {
|
|
404
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M352 64L480 160 352 256V192H64V128H352ZM160 256L32 352 160 448V384H448V320H160Z'/%3E%3C/svg%3E");
|
|
405
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M352 64L480 160 352 256V192H64V128H352ZM160 256L32 352 160 448V384H448V320H160Z'/%3E%3C/svg%3E");
|
|
406
|
+
}
|