@nice-code/state 0.4.4 → 0.4.6
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/build/devtools/browser/index.js +412 -67
- package/build/types/devtools/browser/components/ChangeDetailPanel.d.ts +8 -1
- package/build/types/devtools/browser/components/ChangeList.d.ts +1 -1
- package/build/types/devtools/browser/components/DiffView.d.ts +2 -1
- package/build/types/devtools/browser/components/JsonDiffView.d.ts +5 -2
- package/build/types/devtools/browser/components/utils.d.ts +29 -0
- package/package.json +1 -1
|
@@ -161,7 +161,7 @@ class StateDevtoolsCore {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
// src/devtools/browser/NiceStateDevtools.tsx
|
|
164
|
-
import { useEffect as useEffect2, useId, useMemo, useReducer, useState as
|
|
164
|
+
import { useEffect as useEffect2, useId, useMemo as useMemo2, useReducer, useState as useState3 } from "react";
|
|
165
165
|
|
|
166
166
|
// src/devtools/core/devtools_colors.ts
|
|
167
167
|
var DEVTOOL_COLOR_SEMANTIC_ERROR = "#FF5C5C";
|
|
@@ -191,9 +191,6 @@ var DEVTOOL_JSON_PUNCTUATION = "#475569";
|
|
|
191
191
|
var MONO_FONT = "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace";
|
|
192
192
|
var SANS_FONT = "ui-sans-serif, system-ui, sans-serif";
|
|
193
193
|
|
|
194
|
-
// src/devtools/browser/components/ChangeDetailPanel.tsx
|
|
195
|
-
import { useState as useState2 } from "react";
|
|
196
|
-
|
|
197
194
|
// src/devtools/browser/components/utils.ts
|
|
198
195
|
function safeStringify(value, indent = 2) {
|
|
199
196
|
if (value === undefined)
|
|
@@ -327,6 +324,143 @@ function computeLineDiff(beforeText, afterText) {
|
|
|
327
324
|
ops.push({ kind: "added", text: b[j++] });
|
|
328
325
|
return ops;
|
|
329
326
|
}
|
|
327
|
+
function orderLineDiffOps(ops, latestFirst) {
|
|
328
|
+
if (!latestFirst)
|
|
329
|
+
return ops;
|
|
330
|
+
const out = [];
|
|
331
|
+
let i = 0;
|
|
332
|
+
while (i < ops.length) {
|
|
333
|
+
if (ops[i].kind === "common") {
|
|
334
|
+
out.push(ops[i]);
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const added = [];
|
|
339
|
+
const removed = [];
|
|
340
|
+
while (i < ops.length && ops[i].kind !== "common") {
|
|
341
|
+
(ops[i].kind === "added" ? added : removed).push(ops[i]);
|
|
342
|
+
i++;
|
|
343
|
+
}
|
|
344
|
+
out.push(...added, ...removed);
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
function computeCompressedDiff(before, after, side, latestFirst = false) {
|
|
349
|
+
const lines = [];
|
|
350
|
+
emitChangedNode(lines, 0, null, before, after, side, latestFirst);
|
|
351
|
+
return lines;
|
|
352
|
+
}
|
|
353
|
+
function keyPrefix(keyLabel) {
|
|
354
|
+
return keyLabel != null ? `${keyLabel}: ` : "";
|
|
355
|
+
}
|
|
356
|
+
function emitChangedNode(lines, depth, keyLabel, before, after, side, latestFirst) {
|
|
357
|
+
const bothContainers = isPlainObjectOrArray(before) && isPlainObjectOrArray(after);
|
|
358
|
+
const sameShape = bothContainers && Array.isArray(before) === Array.isArray(after);
|
|
359
|
+
if (sameShape) {
|
|
360
|
+
emitContainer(lines, depth, keyLabel, before, after, side, latestFirst);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const removed = () => pushValueLines(lines, depth, keyLabel, before, "−", "removed");
|
|
364
|
+
const added = () => pushValueLines(lines, depth, keyLabel, after, "+", "added");
|
|
365
|
+
if (side === "before") {
|
|
366
|
+
removed();
|
|
367
|
+
} else if (side === "after") {
|
|
368
|
+
added();
|
|
369
|
+
} else if (latestFirst) {
|
|
370
|
+
added();
|
|
371
|
+
removed();
|
|
372
|
+
} else {
|
|
373
|
+
removed();
|
|
374
|
+
added();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function emitContainer(lines, depth, keyLabel, before, after, side, latestFirst) {
|
|
378
|
+
const isArray = Array.isArray(before);
|
|
379
|
+
lines.push({ depth, sign: " ", tone: "common", text: `${keyPrefix(keyLabel)}${isArray ? "[" : "{"}` });
|
|
380
|
+
let unchanged = 0;
|
|
381
|
+
const flush = () => {
|
|
382
|
+
if (unchanged > 0) {
|
|
383
|
+
lines.push({
|
|
384
|
+
depth: depth + 1,
|
|
385
|
+
sign: " ",
|
|
386
|
+
tone: "placeholder",
|
|
387
|
+
text: placeholderText(unchanged, isArray)
|
|
388
|
+
});
|
|
389
|
+
unchanged = 0;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
for (const child of childEntries(before, after, isArray)) {
|
|
393
|
+
if (child.kind === "added" && side === "before")
|
|
394
|
+
continue;
|
|
395
|
+
if (child.kind === "removed" && side === "after")
|
|
396
|
+
continue;
|
|
397
|
+
if (child.kind === "unchanged") {
|
|
398
|
+
unchanged++;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
flush();
|
|
402
|
+
if (child.kind === "changed") {
|
|
403
|
+
emitChangedNode(lines, depth + 1, child.keyLabel, child.before, child.after, side, latestFirst);
|
|
404
|
+
} else if (child.kind === "added") {
|
|
405
|
+
pushValueLines(lines, depth + 1, child.keyLabel, child.after, "+", "added");
|
|
406
|
+
} else {
|
|
407
|
+
pushValueLines(lines, depth + 1, child.keyLabel, child.before, "−", "removed");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
flush();
|
|
411
|
+
lines.push({ depth, sign: " ", tone: "common", text: isArray ? "]" : "}" });
|
|
412
|
+
}
|
|
413
|
+
function childEntries(before, after, isArray) {
|
|
414
|
+
if (isArray) {
|
|
415
|
+
const b2 = before;
|
|
416
|
+
const a2 = after;
|
|
417
|
+
const len = Math.max(b2.length, a2.length);
|
|
418
|
+
const out = [];
|
|
419
|
+
for (let i = 0;i < len; i++) {
|
|
420
|
+
const inB = i < b2.length;
|
|
421
|
+
const inA = i < a2.length;
|
|
422
|
+
if (inB && inA) {
|
|
423
|
+
const kind = Object.is(b2[i], a2[i]) ? "unchanged" : "changed";
|
|
424
|
+
out.push({ keyLabel: null, kind, before: b2[i], after: a2[i] });
|
|
425
|
+
} else if (inB) {
|
|
426
|
+
out.push({ keyLabel: null, kind: "removed", before: b2[i], after: undefined });
|
|
427
|
+
} else {
|
|
428
|
+
out.push({ keyLabel: null, kind: "added", before: undefined, after: a2[i] });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
const b = before;
|
|
434
|
+
const a = after;
|
|
435
|
+
const keys = [...new Set([...Object.keys(b), ...Object.keys(a)])];
|
|
436
|
+
return keys.map((key) => {
|
|
437
|
+
const keyLabel = JSON.stringify(key);
|
|
438
|
+
const inB = key in b;
|
|
439
|
+
const inA = key in a;
|
|
440
|
+
if (inB && inA) {
|
|
441
|
+
const kind = Object.is(b[key], a[key]) ? "unchanged" : "changed";
|
|
442
|
+
return { keyLabel, kind, before: b[key], after: a[key] };
|
|
443
|
+
}
|
|
444
|
+
if (inB)
|
|
445
|
+
return { keyLabel, kind: "removed", before: b[key], after: undefined };
|
|
446
|
+
return { keyLabel, kind: "added", before: undefined, after: a[key] };
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function placeholderText(count, isArray) {
|
|
450
|
+
const noun = isArray ? count === 1 ? "item" : "items" : count === 1 ? "property" : "properties";
|
|
451
|
+
return `… ${count} unchanged ${noun}`;
|
|
452
|
+
}
|
|
453
|
+
function pushValueLines(lines, baseDepth, keyLabel, value, sign, tone) {
|
|
454
|
+
const prefix = keyPrefix(keyLabel);
|
|
455
|
+
const raw = safeStringify(value, 2).split(`
|
|
456
|
+
`);
|
|
457
|
+
raw.forEach((line, idx) => {
|
|
458
|
+
const leading = /^ */.exec(line)[0].length;
|
|
459
|
+
const content = line.slice(leading);
|
|
460
|
+
const text = idx === 0 ? `${prefix}${content}` : content;
|
|
461
|
+
lines.push({ depth: baseDepth + leading / 2, sign, tone, text });
|
|
462
|
+
});
|
|
463
|
+
}
|
|
330
464
|
function isScalar(value) {
|
|
331
465
|
return value == null || typeof value !== "object";
|
|
332
466
|
}
|
|
@@ -415,7 +549,11 @@ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
|
415
549
|
var ADDED_BG = "rgba(163, 230, 53, 0.08)";
|
|
416
550
|
var REMOVED_BG = "rgba(255, 92, 92, 0.08)";
|
|
417
551
|
var INLINE_MAX = 40;
|
|
418
|
-
function DiffView({
|
|
552
|
+
function DiffView({
|
|
553
|
+
before,
|
|
554
|
+
after,
|
|
555
|
+
latestFirst = false
|
|
556
|
+
}) {
|
|
419
557
|
const entries = computeDiff(before, after);
|
|
420
558
|
if (entries.length === 0) {
|
|
421
559
|
return /* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -434,7 +572,8 @@ function DiffView({ before, after }) {
|
|
|
434
572
|
return /* @__PURE__ */ jsxDEV2("div", {
|
|
435
573
|
style: { display: "flex", flexDirection: "column", gap: "6px" },
|
|
436
574
|
children: roots.map((node) => /* @__PURE__ */ jsxDEV2(DiffNode, {
|
|
437
|
-
node
|
|
575
|
+
node,
|
|
576
|
+
latestFirst
|
|
438
577
|
}, node.key, false, undefined, this))
|
|
439
578
|
}, undefined, false, undefined, this);
|
|
440
579
|
}
|
|
@@ -469,11 +608,12 @@ function collapseChains(node) {
|
|
|
469
608
|
}
|
|
470
609
|
return current;
|
|
471
610
|
}
|
|
472
|
-
function DiffNode({ node }) {
|
|
611
|
+
function DiffNode({ node, latestFirst }) {
|
|
473
612
|
if (node.entry != null && node.children.size === 0) {
|
|
474
613
|
return /* @__PURE__ */ jsxDEV2(DiffLeaf, {
|
|
475
614
|
label: node.key,
|
|
476
|
-
entry: node.entry
|
|
615
|
+
entry: node.entry,
|
|
616
|
+
latestFirst
|
|
477
617
|
}, undefined, false, undefined, this);
|
|
478
618
|
}
|
|
479
619
|
return /* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -493,15 +633,44 @@ function DiffNode({ node }) {
|
|
|
493
633
|
gap: "5px"
|
|
494
634
|
},
|
|
495
635
|
children: [...node.children.values()].map((child) => /* @__PURE__ */ jsxDEV2(DiffNode, {
|
|
496
|
-
node: child
|
|
636
|
+
node: child,
|
|
637
|
+
latestFirst
|
|
497
638
|
}, child.key, false, undefined, this))
|
|
498
639
|
}, undefined, false, undefined, this)
|
|
499
640
|
]
|
|
500
641
|
}, undefined, true, undefined, this);
|
|
501
642
|
}
|
|
502
|
-
function DiffLeaf({
|
|
643
|
+
function DiffLeaf({
|
|
644
|
+
label,
|
|
645
|
+
entry,
|
|
646
|
+
latestFirst
|
|
647
|
+
}) {
|
|
503
648
|
const showRemoved = entry.kind === "removed" || entry.kind === "changed";
|
|
504
649
|
const showAdded = entry.kind === "added" || entry.kind === "changed";
|
|
650
|
+
const removed = showRemoved && /* @__PURE__ */ jsxDEV2(InlineValue, {
|
|
651
|
+
sign: "−",
|
|
652
|
+
color: DEVTOOL_COLOR_SEMANTIC_ERROR,
|
|
653
|
+
background: REMOVED_BG,
|
|
654
|
+
value: entry.before
|
|
655
|
+
}, undefined, false, undefined, this);
|
|
656
|
+
const added = showAdded && /* @__PURE__ */ jsxDEV2(InlineValue, {
|
|
657
|
+
sign: "+",
|
|
658
|
+
color: DEVTOOL_COLOR_SEMANTIC_SUCCESS,
|
|
659
|
+
background: ADDED_BG,
|
|
660
|
+
value: entry.after
|
|
661
|
+
}, undefined, false, undefined, this);
|
|
662
|
+
const removedLine = showRemoved && /* @__PURE__ */ jsxDEV2(DiffLine, {
|
|
663
|
+
sign: "−",
|
|
664
|
+
color: DEVTOOL_COLOR_SEMANTIC_ERROR,
|
|
665
|
+
background: REMOVED_BG,
|
|
666
|
+
value: entry.before
|
|
667
|
+
}, undefined, false, undefined, this);
|
|
668
|
+
const addedLine = showAdded && /* @__PURE__ */ jsxDEV2(DiffLine, {
|
|
669
|
+
sign: "+",
|
|
670
|
+
color: DEVTOOL_COLOR_SEMANTIC_SUCCESS,
|
|
671
|
+
background: ADDED_BG,
|
|
672
|
+
value: entry.after
|
|
673
|
+
}, undefined, false, undefined, this);
|
|
505
674
|
if (isInlineLeaf(entry)) {
|
|
506
675
|
return /* @__PURE__ */ jsxDEV2("div", {
|
|
507
676
|
style: { display: "flex", flexWrap: "wrap", alignItems: "baseline", gap: "6px" },
|
|
@@ -513,22 +682,12 @@ function DiffLeaf({ label, entry }) {
|
|
|
513
682
|
":"
|
|
514
683
|
]
|
|
515
684
|
}, undefined, true, undefined, this),
|
|
516
|
-
|
|
517
|
-
sign: "−",
|
|
518
|
-
color: DEVTOOL_COLOR_SEMANTIC_ERROR,
|
|
519
|
-
background: REMOVED_BG,
|
|
520
|
-
value: entry.before
|
|
521
|
-
}, undefined, false, undefined, this),
|
|
685
|
+
latestFirst ? added : removed,
|
|
522
686
|
entry.kind === "changed" && /* @__PURE__ */ jsxDEV2("span", {
|
|
523
687
|
style: { color: DEVTOOL_COLOR_TEXT_MUTED },
|
|
524
|
-
children: "→"
|
|
688
|
+
children: latestFirst ? "←" : "→"
|
|
525
689
|
}, undefined, false, undefined, this),
|
|
526
|
-
|
|
527
|
-
sign: "+",
|
|
528
|
-
color: DEVTOOL_COLOR_SEMANTIC_SUCCESS,
|
|
529
|
-
background: ADDED_BG,
|
|
530
|
-
value: entry.after
|
|
531
|
-
}, undefined, false, undefined, this)
|
|
690
|
+
latestFirst ? removed : added
|
|
532
691
|
]
|
|
533
692
|
}, undefined, true, undefined, this);
|
|
534
693
|
}
|
|
@@ -549,18 +708,8 @@ function DiffLeaf({ label, entry }) {
|
|
|
549
708
|
},
|
|
550
709
|
children: label
|
|
551
710
|
}, undefined, false, undefined, this),
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
color: DEVTOOL_COLOR_SEMANTIC_ERROR,
|
|
555
|
-
background: REMOVED_BG,
|
|
556
|
-
value: entry.before
|
|
557
|
-
}, undefined, false, undefined, this),
|
|
558
|
-
showAdded && /* @__PURE__ */ jsxDEV2(DiffLine, {
|
|
559
|
-
sign: "+",
|
|
560
|
-
color: DEVTOOL_COLOR_SEMANTIC_SUCCESS,
|
|
561
|
-
background: ADDED_BG,
|
|
562
|
-
value: entry.after
|
|
563
|
-
}, undefined, false, undefined, this)
|
|
711
|
+
latestFirst ? addedLine : removedLine,
|
|
712
|
+
latestFirst ? removedLine : addedLine
|
|
564
713
|
]
|
|
565
714
|
}, undefined, true, undefined, this);
|
|
566
715
|
}
|
|
@@ -683,8 +832,21 @@ function Line({ sign, color, background, text }) {
|
|
|
683
832
|
]
|
|
684
833
|
}, undefined, true, undefined, this);
|
|
685
834
|
}
|
|
686
|
-
function JsonDiffView({
|
|
687
|
-
|
|
835
|
+
function JsonDiffView({
|
|
836
|
+
before,
|
|
837
|
+
after,
|
|
838
|
+
compress = false,
|
|
839
|
+
latestFirst = false
|
|
840
|
+
}) {
|
|
841
|
+
if (compress) {
|
|
842
|
+
return /* @__PURE__ */ jsxDEV3(CompressedDiffView, {
|
|
843
|
+
before,
|
|
844
|
+
after,
|
|
845
|
+
side: "unified",
|
|
846
|
+
latestFirst
|
|
847
|
+
}, undefined, false, undefined, this);
|
|
848
|
+
}
|
|
849
|
+
const ops = orderLineDiffOps(computeLineDiff(safeStringify(before, 2), safeStringify(after, 2)), latestFirst);
|
|
688
850
|
if (!ops.some((op) => op.kind !== "common")) {
|
|
689
851
|
return emptyNotice("No differences — before and after are structurally equal.");
|
|
690
852
|
}
|
|
@@ -698,11 +860,70 @@ function JsonDiffView({ before, after }) {
|
|
|
698
860
|
}, i, false, undefined, this))
|
|
699
861
|
}, undefined, false, undefined, this);
|
|
700
862
|
}
|
|
863
|
+
function CompressedDiffView({
|
|
864
|
+
before,
|
|
865
|
+
after,
|
|
866
|
+
side,
|
|
867
|
+
latestFirst = false
|
|
868
|
+
}) {
|
|
869
|
+
const lines = computeCompressedDiff(before, after, side, latestFirst);
|
|
870
|
+
if (!lines.some((line) => line.tone === "added" || line.tone === "removed")) {
|
|
871
|
+
return emptyNotice("No differences — before and after are structurally equal.");
|
|
872
|
+
}
|
|
873
|
+
return /* @__PURE__ */ jsxDEV3("pre", {
|
|
874
|
+
style: SURFACE_STYLE,
|
|
875
|
+
children: lines.map((line, i) => /* @__PURE__ */ jsxDEV3(CompressedLine, {
|
|
876
|
+
line
|
|
877
|
+
}, i, false, undefined, this))
|
|
878
|
+
}, undefined, false, undefined, this);
|
|
879
|
+
}
|
|
880
|
+
function CompressedLine({ line }) {
|
|
881
|
+
const indent = " ".repeat(line.depth);
|
|
882
|
+
if (line.tone === "placeholder") {
|
|
883
|
+
return /* @__PURE__ */ jsxDEV3("div", {
|
|
884
|
+
style: { display: "flex", padding: "0 10px", whiteSpace: "pre" },
|
|
885
|
+
children: [
|
|
886
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
887
|
+
style: { width: "12px", flexShrink: 0 }
|
|
888
|
+
}, undefined, false, undefined, this),
|
|
889
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
890
|
+
style: { flex: 1, minWidth: 0, color: DEVTOOL_COLOR_TEXT_MUTED, fontStyle: "italic" },
|
|
891
|
+
children: [
|
|
892
|
+
indent,
|
|
893
|
+
line.text
|
|
894
|
+
]
|
|
895
|
+
}, undefined, true, undefined, this)
|
|
896
|
+
]
|
|
897
|
+
}, undefined, true, undefined, this);
|
|
898
|
+
}
|
|
899
|
+
const background = line.tone === "removed" ? REMOVED_BG2 : line.tone === "added" ? ADDED_BG2 : "transparent";
|
|
900
|
+
const color = line.tone === "removed" ? DEVTOOL_COLOR_SEMANTIC_ERROR : DEVTOOL_COLOR_SEMANTIC_SUCCESS;
|
|
901
|
+
return /* @__PURE__ */ jsxDEV3("div", {
|
|
902
|
+
style: { display: "flex", background, padding: "0 10px", whiteSpace: "pre" },
|
|
903
|
+
children: [
|
|
904
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
905
|
+
style: { width: "12px", flexShrink: 0, color, fontWeight: 700, userSelect: "none" },
|
|
906
|
+
children: line.sign
|
|
907
|
+
}, undefined, false, undefined, this),
|
|
908
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
909
|
+
style: { flex: 1, minWidth: 0 },
|
|
910
|
+
children: renderColoredJson(`${indent}${line.text}`)
|
|
911
|
+
}, undefined, false, undefined, this)
|
|
912
|
+
]
|
|
913
|
+
}, undefined, true, undefined, this);
|
|
914
|
+
}
|
|
701
915
|
function HighlightedJsonView({
|
|
702
916
|
before,
|
|
703
917
|
after,
|
|
704
|
-
side
|
|
918
|
+
side,
|
|
919
|
+
compress = false
|
|
705
920
|
}) {
|
|
921
|
+
if (compress)
|
|
922
|
+
return /* @__PURE__ */ jsxDEV3(CompressedDiffView, {
|
|
923
|
+
before,
|
|
924
|
+
after,
|
|
925
|
+
side
|
|
926
|
+
}, undefined, false, undefined, this);
|
|
706
927
|
const ops = computeLineDiff(safeStringify(before, 2), safeStringify(after, 2));
|
|
707
928
|
const dropKind = side === "before" ? "added" : "removed";
|
|
708
929
|
const highlightKind = side === "before" ? "removed" : "added";
|
|
@@ -1055,10 +1276,17 @@ function DevtoolsLauncher({ items }) {
|
|
|
1055
1276
|
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
1056
1277
|
function ChangeDetailPanel({
|
|
1057
1278
|
change,
|
|
1058
|
-
onRevert
|
|
1279
|
+
onRevert,
|
|
1280
|
+
view,
|
|
1281
|
+
onViewChange,
|
|
1282
|
+
compress,
|
|
1283
|
+
onCompressChange,
|
|
1284
|
+
latestFirst,
|
|
1285
|
+
onLatestFirstChange
|
|
1059
1286
|
}) {
|
|
1060
|
-
const [view, setView] = useState2("props");
|
|
1061
1287
|
const canRevert = change.inversePatches.length > 0;
|
|
1288
|
+
const showCompressToggle = view !== "props";
|
|
1289
|
+
const showOrderToggle = view === "props" || view === "diff";
|
|
1062
1290
|
return /* @__PURE__ */ jsxDEV5("div", {
|
|
1063
1291
|
style: {
|
|
1064
1292
|
flex: 1,
|
|
@@ -1139,13 +1367,13 @@ function ChangeDetailPanel({
|
|
|
1139
1367
|
},
|
|
1140
1368
|
children: /* @__PURE__ */ jsxDEV5(SegmentedControl, {
|
|
1141
1369
|
options: [
|
|
1142
|
-
{ value: "props", label: "Diff Props" },
|
|
1143
1370
|
{ value: "diff", label: "Diff View" },
|
|
1371
|
+
{ value: "props", label: "Diff Props" },
|
|
1144
1372
|
{ value: "before", label: "Before" },
|
|
1145
1373
|
{ value: "after", label: "After" }
|
|
1146
1374
|
],
|
|
1147
1375
|
value: view,
|
|
1148
|
-
onChange:
|
|
1376
|
+
onChange: onViewChange
|
|
1149
1377
|
}, undefined, false, undefined, this)
|
|
1150
1378
|
}, undefined, false, undefined, this),
|
|
1151
1379
|
/* @__PURE__ */ jsxDEV5("div", {
|
|
@@ -1156,32 +1384,110 @@ function ChangeDetailPanel({
|
|
|
1156
1384
|
padding: "10px 12px"
|
|
1157
1385
|
},
|
|
1158
1386
|
children: [
|
|
1159
|
-
|
|
1387
|
+
(showCompressToggle || showOrderToggle) && /* @__PURE__ */ jsxDEV5("div", {
|
|
1388
|
+
style: {
|
|
1389
|
+
display: "flex",
|
|
1390
|
+
flexWrap: "wrap",
|
|
1391
|
+
alignItems: "center",
|
|
1392
|
+
gap: "14px",
|
|
1393
|
+
marginBottom: "8px"
|
|
1394
|
+
},
|
|
1395
|
+
children: [
|
|
1396
|
+
showCompressToggle && /* @__PURE__ */ jsxDEV5(ToggleCheckbox, {
|
|
1397
|
+
checked: compress,
|
|
1398
|
+
onChange: onCompressChange,
|
|
1399
|
+
label: "Compress to changed paths only",
|
|
1400
|
+
title: "Collapse unchanged branches, showing only the address of what changed"
|
|
1401
|
+
}, undefined, false, undefined, this),
|
|
1402
|
+
showOrderToggle && /* @__PURE__ */ jsxDEV5(ToggleCheckbox, {
|
|
1403
|
+
checked: latestFirst,
|
|
1404
|
+
onChange: onLatestFirstChange,
|
|
1405
|
+
label: "Latest (+) change first",
|
|
1406
|
+
title: "Show the new value (+) above the previous value (−); off shows previous first"
|
|
1407
|
+
}, undefined, false, undefined, this)
|
|
1408
|
+
]
|
|
1409
|
+
}, undefined, true, undefined, this),
|
|
1410
|
+
view === "diff" && /* @__PURE__ */ jsxDEV5(JsonDiffView, {
|
|
1160
1411
|
before: change.prevSnapshot,
|
|
1161
|
-
after: change.snapshot
|
|
1412
|
+
after: change.snapshot,
|
|
1413
|
+
compress,
|
|
1414
|
+
latestFirst
|
|
1162
1415
|
}, undefined, false, undefined, this),
|
|
1163
|
-
view === "
|
|
1416
|
+
view === "props" && /* @__PURE__ */ jsxDEV5(DiffView, {
|
|
1164
1417
|
before: change.prevSnapshot,
|
|
1165
|
-
after: change.snapshot
|
|
1418
|
+
after: change.snapshot,
|
|
1419
|
+
latestFirst
|
|
1166
1420
|
}, undefined, false, undefined, this),
|
|
1167
1421
|
view === "before" && /* @__PURE__ */ jsxDEV5(HighlightedJsonView, {
|
|
1168
1422
|
before: change.prevSnapshot,
|
|
1169
1423
|
after: change.snapshot,
|
|
1170
|
-
side: "before"
|
|
1424
|
+
side: "before",
|
|
1425
|
+
compress
|
|
1171
1426
|
}, undefined, false, undefined, this),
|
|
1172
1427
|
view === "after" && /* @__PURE__ */ jsxDEV5(HighlightedJsonView, {
|
|
1173
1428
|
before: change.prevSnapshot,
|
|
1174
1429
|
after: change.snapshot,
|
|
1175
|
-
side: "after"
|
|
1430
|
+
side: "after",
|
|
1431
|
+
compress
|
|
1176
1432
|
}, undefined, false, undefined, this)
|
|
1177
1433
|
]
|
|
1178
1434
|
}, undefined, true, undefined, this)
|
|
1179
1435
|
]
|
|
1180
1436
|
}, undefined, true, undefined, this);
|
|
1181
1437
|
}
|
|
1438
|
+
function ToggleCheckbox({
|
|
1439
|
+
checked,
|
|
1440
|
+
onChange,
|
|
1441
|
+
label,
|
|
1442
|
+
title
|
|
1443
|
+
}) {
|
|
1444
|
+
return /* @__PURE__ */ jsxDEV5("label", {
|
|
1445
|
+
style: {
|
|
1446
|
+
display: "flex",
|
|
1447
|
+
alignItems: "center",
|
|
1448
|
+
gap: "6px",
|
|
1449
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
1450
|
+
fontSize: "10px",
|
|
1451
|
+
fontFamily: SANS_FONT,
|
|
1452
|
+
cursor: "pointer",
|
|
1453
|
+
userSelect: "none"
|
|
1454
|
+
},
|
|
1455
|
+
title,
|
|
1456
|
+
children: [
|
|
1457
|
+
/* @__PURE__ */ jsxDEV5("input", {
|
|
1458
|
+
type: "checkbox",
|
|
1459
|
+
checked,
|
|
1460
|
+
onChange: (e) => onChange(e.target.checked),
|
|
1461
|
+
style: { cursor: "pointer", margin: 0 }
|
|
1462
|
+
}, undefined, false, undefined, this),
|
|
1463
|
+
label
|
|
1464
|
+
]
|
|
1465
|
+
}, undefined, true, undefined, this);
|
|
1466
|
+
}
|
|
1182
1467
|
|
|
1183
1468
|
// src/devtools/browser/components/ChangeList.tsx
|
|
1469
|
+
import { useMemo } from "react";
|
|
1184
1470
|
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
1471
|
+
function changeGroupKey(change) {
|
|
1472
|
+
const shape = change.patches.length > 0 ? safeStringify(change.patches, 0) : safeStringify(change.snapshot, 0);
|
|
1473
|
+
return `${change.storeId}|${change.source}|${shape}`;
|
|
1474
|
+
}
|
|
1475
|
+
function groupChanges(changes) {
|
|
1476
|
+
const groups = [];
|
|
1477
|
+
let lastKey = null;
|
|
1478
|
+
for (const change of changes) {
|
|
1479
|
+
const key = changeGroupKey(change);
|
|
1480
|
+
const last = groups[groups.length - 1];
|
|
1481
|
+
if (last != null && key === lastKey) {
|
|
1482
|
+
last.count++;
|
|
1483
|
+
last.oldestCuid = change.cuid;
|
|
1484
|
+
} else {
|
|
1485
|
+
groups.push({ representative: change, oldestCuid: change.cuid, count: 1 });
|
|
1486
|
+
}
|
|
1487
|
+
lastKey = key;
|
|
1488
|
+
}
|
|
1489
|
+
return groups;
|
|
1490
|
+
}
|
|
1185
1491
|
function ChangeList({
|
|
1186
1492
|
changes,
|
|
1187
1493
|
selectedCuid,
|
|
@@ -1189,6 +1495,7 @@ function ChangeList({
|
|
|
1189
1495
|
showStore,
|
|
1190
1496
|
style
|
|
1191
1497
|
}) {
|
|
1498
|
+
const groups = useMemo(() => groupChanges(changes), [changes]);
|
|
1192
1499
|
if (changes.length === 0) {
|
|
1193
1500
|
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1194
1501
|
style: {
|
|
@@ -1203,16 +1510,18 @@ function ChangeList({
|
|
|
1203
1510
|
}
|
|
1204
1511
|
return /* @__PURE__ */ jsxDEV6("div", {
|
|
1205
1512
|
style,
|
|
1206
|
-
children:
|
|
1207
|
-
change,
|
|
1208
|
-
|
|
1209
|
-
|
|
1513
|
+
children: groups.map((group) => /* @__PURE__ */ jsxDEV6(ChangeRow, {
|
|
1514
|
+
change: group.representative,
|
|
1515
|
+
count: group.count,
|
|
1516
|
+
selected: group.representative.cuid === selectedCuid,
|
|
1517
|
+
onClick: () => onSelect(group.representative.cuid),
|
|
1210
1518
|
showStore
|
|
1211
|
-
},
|
|
1519
|
+
}, group.oldestCuid, false, undefined, this))
|
|
1212
1520
|
}, undefined, false, undefined, this);
|
|
1213
1521
|
}
|
|
1214
1522
|
function ChangeRow({
|
|
1215
1523
|
change,
|
|
1524
|
+
count,
|
|
1216
1525
|
selected,
|
|
1217
1526
|
onClick,
|
|
1218
1527
|
showStore
|
|
@@ -1247,6 +1556,23 @@ function ChangeRow({
|
|
|
1247
1556
|
color: sourceColor,
|
|
1248
1557
|
children: SOURCE_LABEL[change.source]
|
|
1249
1558
|
}, undefined, false, undefined, this),
|
|
1559
|
+
count > 1 && /* @__PURE__ */ jsxDEV6("span", {
|
|
1560
|
+
title: `${count} consecutive identical updates`,
|
|
1561
|
+
style: {
|
|
1562
|
+
flexShrink: 0,
|
|
1563
|
+
padding: "0 5px",
|
|
1564
|
+
borderRadius: "999px",
|
|
1565
|
+
background: DEVTOOL_SECTION_BACKGROUND,
|
|
1566
|
+
color: DEVTOOL_COLOR_TEXT_SECONDARY,
|
|
1567
|
+
fontSize: "9px",
|
|
1568
|
+
fontWeight: 700,
|
|
1569
|
+
fontFamily: MONO_FONT
|
|
1570
|
+
},
|
|
1571
|
+
children: [
|
|
1572
|
+
"×",
|
|
1573
|
+
count
|
|
1574
|
+
]
|
|
1575
|
+
}, undefined, true, undefined, this),
|
|
1250
1576
|
showStore && /* @__PURE__ */ jsxDEV6("span", {
|
|
1251
1577
|
style: {
|
|
1252
1578
|
color: DEVTOOL_COLOR_SEMANTIC_SYSTEM,
|
|
@@ -1303,7 +1629,7 @@ function Badge({ color, children }) {
|
|
|
1303
1629
|
}
|
|
1304
1630
|
|
|
1305
1631
|
// src/devtools/browser/components/StateInspector.tsx
|
|
1306
|
-
import { useEffect, useState as
|
|
1632
|
+
import { useEffect, useState as useState2 } from "react";
|
|
1307
1633
|
|
|
1308
1634
|
// src/devtools/browser/components/SectionLabel.tsx
|
|
1309
1635
|
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
@@ -1332,9 +1658,9 @@ function StateInspector({
|
|
|
1332
1658
|
onApply
|
|
1333
1659
|
}) {
|
|
1334
1660
|
const liveText = safeStringify(store.currentState, 2);
|
|
1335
|
-
const [draft, setDraft] =
|
|
1336
|
-
const [dirty, setDirty] =
|
|
1337
|
-
const [error, setError] =
|
|
1661
|
+
const [draft, setDraft] = useState2(liveText);
|
|
1662
|
+
const [dirty, setDirty] = useState2(false);
|
|
1663
|
+
const [error, setError] = useState2(null);
|
|
1338
1664
|
useEffect(() => {
|
|
1339
1665
|
setDraft(liveText);
|
|
1340
1666
|
setDirty(false);
|
|
@@ -1708,7 +2034,10 @@ function readPrefs(defaultPosition, initialOpen) {
|
|
|
1708
2034
|
dockedHeight: DOCKED_HEIGHT_DEFAULT,
|
|
1709
2035
|
dockedWidth: DOCKED_WIDTH_DEFAULT,
|
|
1710
2036
|
detailRatio: DETAIL_RATIO_DEFAULT,
|
|
1711
|
-
stayOnLatest:
|
|
2037
|
+
stayOnLatest: true,
|
|
2038
|
+
compressDiff: true,
|
|
2039
|
+
detailView: "diff",
|
|
2040
|
+
diffLatestFirst: true
|
|
1712
2041
|
};
|
|
1713
2042
|
try {
|
|
1714
2043
|
if (typeof localStorage === "undefined")
|
|
@@ -1741,11 +2070,11 @@ function NiceStateDevtools_Panel({
|
|
|
1741
2070
|
position: defaultPosition = "dock-bottom",
|
|
1742
2071
|
initialOpen = false
|
|
1743
2072
|
}) {
|
|
1744
|
-
const [prefs, setPrefsRaw] =
|
|
1745
|
-
const [snapshot, setSnapshot] =
|
|
1746
|
-
const [mode, setMode] =
|
|
1747
|
-
const [storeFilter, setStoreFilter] =
|
|
1748
|
-
const [selectedChangeCuid, setSelectedChangeCuid] =
|
|
2073
|
+
const [prefs, setPrefsRaw] = useState3(() => readPrefs(defaultPosition, initialOpen));
|
|
2074
|
+
const [snapshot, setSnapshot] = useState3(EMPTY_SNAPSHOT);
|
|
2075
|
+
const [mode, setMode] = useState3("timeline");
|
|
2076
|
+
const [storeFilter, setStoreFilter] = useState3(null);
|
|
2077
|
+
const [selectedChangeCuid, setSelectedChangeCuid] = useState3(null);
|
|
1749
2078
|
useEffect2(() => core.subscribe(setSnapshot), [core]);
|
|
1750
2079
|
const setPrefs = (update) => {
|
|
1751
2080
|
setPrefsRaw((prev) => ({ ...prev, ...update }));
|
|
@@ -1755,11 +2084,21 @@ function NiceStateDevtools_Panel({
|
|
|
1755
2084
|
return () => clearTimeout(timer);
|
|
1756
2085
|
}, [prefs]);
|
|
1757
2086
|
const { stores, changes, paused } = snapshot;
|
|
1758
|
-
const {
|
|
2087
|
+
const {
|
|
2088
|
+
position,
|
|
2089
|
+
isOpen,
|
|
2090
|
+
dockedHeight,
|
|
2091
|
+
dockedWidth,
|
|
2092
|
+
detailRatio,
|
|
2093
|
+
stayOnLatest,
|
|
2094
|
+
compressDiff,
|
|
2095
|
+
detailView,
|
|
2096
|
+
diffLatestFirst
|
|
2097
|
+
} = prefs;
|
|
1759
2098
|
const dockSide = getDockSide(position);
|
|
1760
2099
|
const isHorizDock = dockSide === "top" || dockSide === "bottom";
|
|
1761
2100
|
const dockedSize = isHorizDock ? dockedHeight : dockedWidth;
|
|
1762
|
-
const filteredChanges =
|
|
2101
|
+
const filteredChanges = useMemo2(() => storeFilter == null ? changes : changes.filter((c) => c.storeId === storeFilter), [changes, storeFilter]);
|
|
1763
2102
|
const selectedChange = selectedChangeCuid != null ? changes.find((c) => c.cuid === selectedChangeCuid) ?? null : null;
|
|
1764
2103
|
const latestCuid = filteredChanges.length > 0 ? filteredChanges[0].cuid : null;
|
|
1765
2104
|
useEffect2(() => {
|
|
@@ -1767,7 +2106,7 @@ function NiceStateDevtools_Panel({
|
|
|
1767
2106
|
setSelectedChangeCuid(latestCuid);
|
|
1768
2107
|
}, [stayOnLatest, latestCuid]);
|
|
1769
2108
|
const activeStore = stores.find((s) => s.id === storeFilter) ?? (stores.length > 0 ? stores[0] : null);
|
|
1770
|
-
const dock =
|
|
2109
|
+
const dock = useMemo2(() => getDevtoolsDockCoordinator(), []);
|
|
1771
2110
|
const panelId = useId();
|
|
1772
2111
|
const [, bumpView] = useReducer((n) => n + 1, 0);
|
|
1773
2112
|
const badge = changes.length > 0 ? String(changes.length) : undefined;
|
|
@@ -1947,7 +2286,13 @@ function NiceStateDevtools_Panel({
|
|
|
1947
2286
|
},
|
|
1948
2287
|
children: /* @__PURE__ */ jsxDEV10(ChangeDetailPanel, {
|
|
1949
2288
|
change: selectedChange,
|
|
1950
|
-
onRevert: (c) => core.revertChange(c)
|
|
2289
|
+
onRevert: (c) => core.revertChange(c),
|
|
2290
|
+
view: detailView,
|
|
2291
|
+
onViewChange: (v) => setPrefs({ detailView: v }),
|
|
2292
|
+
compress: compressDiff,
|
|
2293
|
+
onCompressChange: (v) => setPrefs({ compressDiff: v }),
|
|
2294
|
+
latestFirst: diffLatestFirst,
|
|
2295
|
+
onLatestFirstChange: (v) => setPrefs({ diffLatestFirst: v })
|
|
1951
2296
|
}, undefined, false, undefined, this)
|
|
1952
2297
|
}, undefined, false, undefined, this)
|
|
1953
2298
|
]
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { IDevtoolsStateChange } from "../../core/StateDevtools.types";
|
|
2
|
-
export
|
|
2
|
+
export type TChangeView = "diff" | "props" | "before" | "after";
|
|
3
|
+
export declare function ChangeDetailPanel({ change, onRevert, view, onViewChange, compress, onCompressChange, latestFirst, onLatestFirstChange, }: {
|
|
3
4
|
change: IDevtoolsStateChange;
|
|
4
5
|
onRevert: (change: IDevtoolsStateChange) => void;
|
|
6
|
+
view: TChangeView;
|
|
7
|
+
onViewChange: (view: TChangeView) => void;
|
|
8
|
+
compress: boolean;
|
|
9
|
+
onCompressChange: (compress: boolean) => void;
|
|
10
|
+
latestFirst: boolean;
|
|
11
|
+
onLatestFirstChange: (latestFirst: boolean) => void;
|
|
5
12
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
2
|
import type { IDevtoolsStateChange } from "../../core/StateDevtools.types";
|
|
3
3
|
export declare function ChangeList({ changes, selectedCuid, onSelect, showStore, style, }: {
|
|
4
4
|
changes: IDevtoolsStateChange[];
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* lone deep changes stay on one line. Short values render inline with the key
|
|
7
7
|
* (`count: 0 → 1`); long ones keep the removed/added line block.
|
|
8
8
|
*/
|
|
9
|
-
export declare function DiffView({ before, after }: {
|
|
9
|
+
export declare function DiffView({ before, after, latestFirst, }: {
|
|
10
10
|
before: unknown;
|
|
11
11
|
after: unknown;
|
|
12
|
+
latestFirst?: boolean;
|
|
12
13
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* context while removed lines (red, `−`) and added lines (green, `+`) call out
|
|
5
5
|
* exactly which sections of the whole state moved.
|
|
6
6
|
*/
|
|
7
|
-
export declare function JsonDiffView({ before, after }: {
|
|
7
|
+
export declare function JsonDiffView({ before, after, compress, latestFirst, }: {
|
|
8
8
|
before: unknown;
|
|
9
9
|
after: unknown;
|
|
10
|
+
compress?: boolean;
|
|
11
|
+
latestFirst?: boolean;
|
|
10
12
|
}): import("react/jsx-runtime").JSX.Element;
|
|
11
13
|
/**
|
|
12
14
|
* The full JSON of one snapshot with the lines that differ from the other side
|
|
@@ -14,8 +16,9 @@ export declare function JsonDiffView({ before, after }: {
|
|
|
14
16
|
* behind added lines on the "after" side. Lines common to both render plainly so
|
|
15
17
|
* the highlight reads as "here is what changed within the whole state".
|
|
16
18
|
*/
|
|
17
|
-
export declare function HighlightedJsonView({ before, after, side, }: {
|
|
19
|
+
export declare function HighlightedJsonView({ before, after, side, compress, }: {
|
|
18
20
|
before: unknown;
|
|
19
21
|
after: unknown;
|
|
20
22
|
side: "before" | "after";
|
|
23
|
+
compress?: boolean;
|
|
21
24
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -40,3 +40,32 @@ export interface ILineDiffOp {
|
|
|
40
40
|
* comfortably cheap.
|
|
41
41
|
*/
|
|
42
42
|
export declare function computeLineDiff(beforeText: string, afterText: string): ILineDiffOp[];
|
|
43
|
+
/**
|
|
44
|
+
* Reorder each contiguous changed hunk so additions sit above removals when
|
|
45
|
+
* `latestFirst` is set (the new state on top). Common lines anchor the hunks in
|
|
46
|
+
* place; only the −/+ lines within a run are regrouped, so the surrounding
|
|
47
|
+
* context never moves. With `latestFirst` off the ops are returned untouched.
|
|
48
|
+
*/
|
|
49
|
+
export declare function orderLineDiffOps(ops: ILineDiffOp[], latestFirst: boolean): ILineDiffOp[];
|
|
50
|
+
export type TCompressedTone = "common" | "added" | "removed" | "placeholder";
|
|
51
|
+
/** Which document a compressed diff is being rendered for. */
|
|
52
|
+
export type TDiffSide = "unified" | "before" | "after";
|
|
53
|
+
export interface ICompressedLine {
|
|
54
|
+
/** Nesting depth — the renderer turns this into leading indentation. */
|
|
55
|
+
depth: number;
|
|
56
|
+
/** Gutter glyph: `+` added, `−` removed, or a space for structure/placeholder. */
|
|
57
|
+
sign: string;
|
|
58
|
+
tone: TCompressedTone;
|
|
59
|
+
/** The line's JSON (or placeholder) text, without indentation. */
|
|
60
|
+
text: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A structure-aware "address" diff: the JSON tree pruned down to just the
|
|
64
|
+
* branches that actually changed. Parents of a change keep their brackets so the
|
|
65
|
+
* path stays legible, and each run of untouched siblings collapses into a single
|
|
66
|
+
* `… N unchanged …` placeholder (which, for arrays, naturally reports the counts
|
|
67
|
+
* before and after a change). `side` picks which document we're rebuilding —
|
|
68
|
+
* `"unified"` shows removed (−) and added (+) together, while `"before"` /
|
|
69
|
+
* `"after"` show only the lines that belong to that one snapshot.
|
|
70
|
+
*/
|
|
71
|
+
export declare function computeCompressedDiff(before: unknown, after: unknown, side: TDiffSide, latestFirst?: boolean): ICompressedLine[];
|