@nice-code/state 0.8.0 → 0.10.0
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/Store-B65MojT2.d.ts +201 -0
- package/build/Store-CI9N0P6I.js +366 -0
- package/build/Store-CI9N0P6I.js.map +1 -0
- package/build/Store-PjfFkZ2I.js +349 -0
- package/build/Store-PjfFkZ2I.js.map +1 -0
- package/build/devtools/browser/index.d.ts +120 -0
- package/build/devtools/browser/index.js +2724 -1751
- package/build/devtools/browser/index.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +2 -244
- package/build/react/index.d.ts +58 -0
- package/build/react/index.js +59 -308
- package/build/react/index.js.map +1 -0
- package/package.json +7 -7
- package/build/types/core/Store.d.ts +0 -136
- package/build/types/core/index.d.ts +0 -1
- package/build/types/devtools/browser/NiceStateDevtools.d.ts +0 -10
- package/build/types/devtools/browser/components/ChangeDetailPanel.d.ts +0 -12
- package/build/types/devtools/browser/components/ChangeList.d.ts +0 -9
- package/build/types/devtools/browser/components/DiffView.d.ts +0 -13
- package/build/types/devtools/browser/components/JsonDiffView.d.ts +0 -24
- package/build/types/devtools/browser/components/JsonView.d.ts +0 -1
- package/build/types/devtools/browser/components/SectionLabel.d.ts +0 -1
- package/build/types/devtools/browser/components/StateInspector.d.ts +0 -16
- package/build/types/devtools/browser/components/StoreTabs.d.ts +0 -12
- package/build/types/devtools/browser/components/utils.d.ts +0 -96
- package/build/types/devtools/browser/index.d.ts +0 -3
- package/build/types/devtools/core/StateDevtools.types.d.ts +0 -43
- package/build/types/devtools/core/StateDevtoolsCore.d.ts +0 -56
- package/build/types/devtools/core/devtools_colors.d.ts +0 -2
- package/build/types/index.d.ts +0 -1
- package/build/types/react/InjectStoreState.d.ts +0 -18
- package/build/types/react/index.d.ts +0 -3
- package/build/types/react/useLocalStore.d.ts +0 -8
- package/build/types/react/useStoreState.d.ts +0 -23
|
@@ -1,1494 +1,2471 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
7
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/devtools/core/StateDevtoolsCore.ts
|
|
10
|
+
/**
|
|
11
|
+
* Framework-agnostic collector for nice-state stores.
|
|
12
|
+
*
|
|
13
|
+
* Register any {@link Store} and the core hooks its patch + update streams,
|
|
14
|
+
* pairing the two so each committed mutation becomes a single
|
|
15
|
+
* {@link IDevtoolsStateChange} with patches and full before/after snapshots.
|
|
16
|
+
* The browser panel ({@link NiceStateDevtools}) renders whatever this exposes.
|
|
17
|
+
*
|
|
18
|
+
* Registering a store attaches a patch listener, which makes Immer compute
|
|
19
|
+
* patches for that store's updates — a negligible dev-time cost. Keep this out
|
|
20
|
+
* of production bundles (the panel is dev-gated, but the core is not).
|
|
21
|
+
*/
|
|
22
|
+
var StateDevtoolsCore = class {
|
|
23
|
+
_stores = /* @__PURE__ */ new Map();
|
|
24
|
+
_changes = [];
|
|
25
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
26
|
+
_maxChanges;
|
|
27
|
+
_paused = false;
|
|
28
|
+
_cuidCounter = 0;
|
|
29
|
+
_sourceOverride = null;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this._maxChanges = options.maxChanges ?? 250;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Start observing a store under a human-readable label. Returns an
|
|
35
|
+
* unregister function. If the label collides with an existing store, a numeric
|
|
36
|
+
* suffix is appended to keep ids unique.
|
|
37
|
+
*/
|
|
38
|
+
registerStore(label, store) {
|
|
39
|
+
const id = this._uniqueId(label);
|
|
40
|
+
let lastSnapshot = store.getRawState();
|
|
41
|
+
let pendingPatches = [];
|
|
42
|
+
let pendingInverse = [];
|
|
43
|
+
const unsubPatches = store.listenToPatches((patches, inverse) => {
|
|
44
|
+
for (const p of patches) pendingPatches.push(p);
|
|
45
|
+
for (const p of inverse) pendingInverse.push(p);
|
|
46
|
+
});
|
|
47
|
+
const unsubUpdate = store.subscribe(() => {
|
|
48
|
+
const snapshot = store.getRawState();
|
|
49
|
+
const patches = pendingPatches;
|
|
50
|
+
const inverse = pendingInverse;
|
|
51
|
+
pendingPatches = [];
|
|
52
|
+
pendingInverse = [];
|
|
53
|
+
const reg = this._stores.get(id);
|
|
54
|
+
if (reg != null) reg.currentState = snapshot;
|
|
55
|
+
const source = this._sourceOverride ?? (patches.length > 0 ? "update" : "replace");
|
|
56
|
+
const prevSnapshot = lastSnapshot;
|
|
57
|
+
lastSnapshot = snapshot;
|
|
58
|
+
if (this._paused) {
|
|
59
|
+
this._notify();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this._recordChange(id, {
|
|
63
|
+
cuid: `chg_${++this._cuidCounter}`,
|
|
64
|
+
storeId: id,
|
|
65
|
+
storeLabel: label,
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
patches,
|
|
68
|
+
inversePatches: inverse,
|
|
69
|
+
prevSnapshot,
|
|
70
|
+
snapshot,
|
|
71
|
+
source
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
this._stores.set(id, {
|
|
75
|
+
id,
|
|
76
|
+
label,
|
|
77
|
+
store,
|
|
78
|
+
currentState: lastSnapshot,
|
|
79
|
+
changeCount: 0,
|
|
80
|
+
unsubscribe: () => {
|
|
81
|
+
unsubPatches();
|
|
82
|
+
unsubUpdate();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
this._notify();
|
|
86
|
+
return () => this.unregisterStore(id);
|
|
87
|
+
}
|
|
88
|
+
unregisterStore(id) {
|
|
89
|
+
const reg = this._stores.get(id);
|
|
90
|
+
if (reg == null) return;
|
|
91
|
+
reg.unsubscribe();
|
|
92
|
+
this._stores.delete(id);
|
|
93
|
+
this._changes = this._changes.filter((c) => c.storeId !== id);
|
|
94
|
+
this._notify();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Replace a registered store's state wholesale — the primary "edit directly
|
|
98
|
+
* for testing" entry point. The resulting change is tagged `devtools-edit`.
|
|
99
|
+
*/
|
|
100
|
+
applyEdit(storeId, newState) {
|
|
101
|
+
const reg = this._stores.get(storeId);
|
|
102
|
+
if (reg == null) return;
|
|
103
|
+
this._sourceOverride = "devtools-edit";
|
|
104
|
+
try {
|
|
105
|
+
reg.store.replace(newState);
|
|
106
|
+
} finally {
|
|
107
|
+
this._sourceOverride = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Undo a change by applying its inverse patches. Cleanest for the most recent
|
|
112
|
+
* change of a store; older reverts may not apply if later changes touched the
|
|
113
|
+
* same paths. The resulting change is tagged `devtools-revert`.
|
|
114
|
+
*/
|
|
115
|
+
revertChange(change) {
|
|
116
|
+
const reg = this._stores.get(change.storeId);
|
|
117
|
+
if (reg == null || change.inversePatches.length === 0) return;
|
|
118
|
+
this._sourceOverride = "devtools-revert";
|
|
119
|
+
try {
|
|
120
|
+
reg.store.applyPatches(change.inversePatches);
|
|
121
|
+
} finally {
|
|
122
|
+
this._sourceOverride = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
setPaused(paused) {
|
|
126
|
+
if (this._paused === paused) return;
|
|
127
|
+
this._paused = paused;
|
|
128
|
+
this._notify();
|
|
129
|
+
}
|
|
130
|
+
togglePaused() {
|
|
131
|
+
this.setPaused(!this._paused);
|
|
132
|
+
}
|
|
133
|
+
/** Drop the recorded timeline; registered stores and their state remain. */
|
|
134
|
+
clear() {
|
|
135
|
+
this._changes = [];
|
|
136
|
+
for (const reg of this._stores.values()) {
|
|
137
|
+
reg.changeCount = 0;
|
|
138
|
+
reg.lastChangeTime = void 0;
|
|
139
|
+
}
|
|
140
|
+
this._notify();
|
|
141
|
+
}
|
|
142
|
+
getSnapshot() {
|
|
143
|
+
return this._buildSnapshot();
|
|
144
|
+
}
|
|
145
|
+
subscribe(listener) {
|
|
146
|
+
this._listeners.add(listener);
|
|
147
|
+
listener(this._buildSnapshot());
|
|
148
|
+
return () => {
|
|
149
|
+
this._listeners.delete(listener);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
_recordChange(id, change) {
|
|
153
|
+
const reg = this._stores.get(id);
|
|
154
|
+
if (reg != null) {
|
|
155
|
+
reg.changeCount += 1;
|
|
156
|
+
reg.lastChangeTime = change.timestamp;
|
|
157
|
+
}
|
|
158
|
+
this._changes = [change, ...this._changes];
|
|
159
|
+
if (this._changes.length > this._maxChanges) this._changes.length = this._maxChanges;
|
|
160
|
+
this._notify();
|
|
161
|
+
}
|
|
162
|
+
_uniqueId(label) {
|
|
163
|
+
if (!this._stores.has(label)) return label;
|
|
164
|
+
let n = 2;
|
|
165
|
+
while (this._stores.has(`${label} (${n})`)) n += 1;
|
|
166
|
+
return `${label} (${n})`;
|
|
167
|
+
}
|
|
168
|
+
_buildSnapshot() {
|
|
169
|
+
const stores = [];
|
|
170
|
+
for (const reg of this._stores.values()) stores.push({
|
|
171
|
+
id: reg.id,
|
|
172
|
+
label: reg.label,
|
|
173
|
+
currentState: reg.currentState,
|
|
174
|
+
changeCount: reg.changeCount,
|
|
175
|
+
lastChangeTime: reg.lastChangeTime
|
|
176
|
+
});
|
|
177
|
+
return {
|
|
178
|
+
stores,
|
|
179
|
+
changes: this._changes,
|
|
180
|
+
paused: this._paused
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
_notify() {
|
|
184
|
+
const snapshot = this._buildSnapshot();
|
|
185
|
+
for (const listener of this._listeners) listener(snapshot);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region ../nice-devtools-shared/src/colors.ts
|
|
190
|
+
const DEVTOOL_COLOR_SEMANTIC_ERROR = "#FF5C5C";
|
|
191
|
+
const DEVTOOL_COLOR_SEMANTIC_SUCCESS = "#A3E635";
|
|
192
|
+
const DEVTOOL_COLOR_SEMANTIC_SYSTEM = "#38BDF8";
|
|
193
|
+
const DEVTOOL_COLOR_SEMANTIC_WARNING = "#FB923C";
|
|
194
|
+
const DEVTOOL_COLOR_SEMANTIC_METADATA = "#A1A1AA";
|
|
195
|
+
const DEVTOOL_COLOR_TEXT_EMPHASIS = "#f1f5f9";
|
|
196
|
+
const DEVTOOL_COLOR_TEXT_SECONDARY = "#cbd5e1";
|
|
197
|
+
const DEVTOOL_COLOR_TEXT_MUTED = "#64748b";
|
|
198
|
+
const DEVTOOL_COLOR_TEXT_FAINT = "#334155";
|
|
199
|
+
const DEVTOOL_LIST_BASE_BACKGROUND = "#0f172a";
|
|
200
|
+
const DEVTOOL_LIST_SELECTED_BACKGROUND = "#1d2942";
|
|
201
|
+
const DEVTOOL_DETAIL_BASE_BACKGROUND = "#0d1729";
|
|
202
|
+
const DEVTOOL_DETAIL_HEADER_BACKGROUND = "#131f35";
|
|
203
|
+
const DEVTOOL_SECTION_BACKGROUND = "#1e293b";
|
|
204
|
+
const DEVTOOL_SECTION_STRING_BACKGROUND = "#0d131f";
|
|
205
|
+
const DEVTOOL_PANEL_BORDER = "#1e293b";
|
|
206
|
+
const DEVTOOL_PANEL_DIVIDER_BORDER = "#1d3352";
|
|
207
|
+
const DEVTOOL_TOOLTIP_BACKGROUND = "#0c1526";
|
|
208
|
+
const DEVTOOL_JSON_KEY = "#a5b4fc";
|
|
209
|
+
const DEVTOOL_JSON_STRING = "#fbbf24";
|
|
210
|
+
const DEVTOOL_JSON_NUMBER = "#34d399";
|
|
211
|
+
const DEVTOOL_JSON_KEYWORD = "#a78bfa";
|
|
212
|
+
const DEVTOOL_JSON_PUNCTUATION = "#475569";
|
|
213
|
+
const MONO_FONT = "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace";
|
|
214
|
+
const SANS_FONT = "ui-sans-serif, system-ui, sans-serif";
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region ../nice-devtools-shared/src/format.ts
|
|
217
|
+
/** Pretty-print any value to JSON, degrading gracefully for cyclic / non-JSON values. */
|
|
218
|
+
function safeStringify(value, indent = 2) {
|
|
219
|
+
if (value === void 0) return "undefined";
|
|
220
|
+
if (value === null) return "null";
|
|
221
|
+
try {
|
|
222
|
+
return JSON.stringify(value, null, indent);
|
|
223
|
+
} catch {
|
|
224
|
+
return String(value);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Wall-clock time of day, e.g. `14:03:09`. */
|
|
228
|
+
function formatTimestamp(ms) {
|
|
229
|
+
return new Date(ms).toLocaleTimeString([], {
|
|
230
|
+
hour: "2-digit",
|
|
231
|
+
minute: "2-digit",
|
|
232
|
+
second: "2-digit",
|
|
233
|
+
hour12: false
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region ../nice-devtools-shared/src/json.tsx
|
|
238
|
+
const JSON_TOKEN_RE = /("(?:\\.|[^"\\])*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|(\btrue\b|\bfalse\b|\bnull\b|\bundefined\b)|([{}[\],])/g;
|
|
239
|
+
/** Tokenize a JSON string into coloured <span> nodes for inline rendering. */
|
|
240
|
+
function renderColoredJson(text) {
|
|
241
|
+
const nodes = [];
|
|
242
|
+
let last = 0;
|
|
243
|
+
let i = 0;
|
|
244
|
+
JSON_TOKEN_RE.lastIndex = 0;
|
|
245
|
+
for (let m = JSON_TOKEN_RE.exec(text); m !== null; m = JSON_TOKEN_RE.exec(text)) {
|
|
246
|
+
if (m.index > last) nodes.push(text.slice(last, m.index));
|
|
247
|
+
const [, str, colon, num, kw, punct] = m;
|
|
248
|
+
if (str != null) if (colon != null) {
|
|
249
|
+
nodes.push(/* @__PURE__ */ jsx("span", {
|
|
250
|
+
style: { color: DEVTOOL_JSON_KEY },
|
|
251
|
+
children: str
|
|
252
|
+
}, i++));
|
|
253
|
+
nodes.push(/* @__PURE__ */ jsx("span", {
|
|
254
|
+
style: { color: DEVTOOL_JSON_PUNCTUATION },
|
|
255
|
+
children: colon
|
|
256
|
+
}, i++));
|
|
257
|
+
} else nodes.push(/* @__PURE__ */ jsx("span", {
|
|
258
|
+
style: { color: DEVTOOL_JSON_STRING },
|
|
259
|
+
children: str
|
|
260
|
+
}, i++));
|
|
261
|
+
else if (num != null) nodes.push(/* @__PURE__ */ jsx("span", {
|
|
262
|
+
style: { color: DEVTOOL_JSON_NUMBER },
|
|
263
|
+
children: num
|
|
264
|
+
}, i++));
|
|
265
|
+
else if (kw != null) nodes.push(/* @__PURE__ */ jsx("span", {
|
|
266
|
+
style: { color: DEVTOOL_JSON_KEYWORD },
|
|
267
|
+
children: kw
|
|
268
|
+
}, i++));
|
|
269
|
+
else if (punct != null) nodes.push(/* @__PURE__ */ jsx("span", {
|
|
270
|
+
style: { color: DEVTOOL_JSON_PUNCTUATION },
|
|
271
|
+
children: punct
|
|
272
|
+
}, i++));
|
|
273
|
+
last = JSON_TOKEN_RE.lastIndex;
|
|
274
|
+
}
|
|
275
|
+
if (last < text.length) nodes.push(text.slice(last));
|
|
276
|
+
return nodes;
|
|
277
|
+
}
|
|
278
|
+
/** A pre-formatted, syntax-highlighted JSON block. */
|
|
279
|
+
function JsonView({ value, indent = 2, style }) {
|
|
280
|
+
const text = safeStringify(value, indent);
|
|
281
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
282
|
+
style: {
|
|
283
|
+
margin: 0,
|
|
284
|
+
padding: "8px 10px",
|
|
285
|
+
borderRadius: "4px",
|
|
286
|
+
fontSize: "11px",
|
|
287
|
+
lineHeight: 1.5,
|
|
288
|
+
fontFamily: MONO_FONT,
|
|
289
|
+
background: DEVTOOL_SECTION_STRING_BACKGROUND,
|
|
290
|
+
overflowX: "auto",
|
|
291
|
+
whiteSpace: "pre-wrap",
|
|
292
|
+
wordBreak: "break-word",
|
|
293
|
+
...style
|
|
294
|
+
},
|
|
295
|
+
children: renderColoredJson(text)
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region ../nice-devtools-shared/src/dock.ts
|
|
300
|
+
const GLOBAL_KEY = "__NICE_DEVTOOLS_DOCK__";
|
|
301
|
+
const VERSION = 4;
|
|
302
|
+
function createCoordinator() {
|
|
303
|
+
const panels = /* @__PURE__ */ new Map();
|
|
304
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
305
|
+
function toRef(panel) {
|
|
306
|
+
return {
|
|
307
|
+
id: panel.id,
|
|
308
|
+
label: panel.label,
|
|
309
|
+
icon: panel.icon,
|
|
310
|
+
badge: panel.badge,
|
|
311
|
+
onOpen: panel.onOpen
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function applyBodyMargins() {
|
|
315
|
+
if (typeof document === "undefined") return;
|
|
316
|
+
const margins = {
|
|
317
|
+
top: 0,
|
|
318
|
+
bottom: 0,
|
|
319
|
+
left: 0,
|
|
320
|
+
right: 0
|
|
321
|
+
};
|
|
322
|
+
for (const panel of panels.values()) if (panel.open) margins[panel.side] += panel.size;
|
|
323
|
+
for (const side of [
|
|
324
|
+
"top",
|
|
325
|
+
"bottom",
|
|
326
|
+
"left",
|
|
327
|
+
"right"
|
|
328
|
+
]) if (margins[side] > 0) document.body.style.setProperty(`margin-${side}`, `${margins[side]}px`);
|
|
329
|
+
else document.body.style.removeProperty(`margin-${side}`);
|
|
330
|
+
}
|
|
331
|
+
function notify() {
|
|
332
|
+
applyBodyMargins();
|
|
333
|
+
for (const listener of listeners) listener();
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
version: VERSION,
|
|
337
|
+
register(panel) {
|
|
338
|
+
panels.set(panel.id, { ...panel });
|
|
339
|
+
notify();
|
|
340
|
+
return () => {
|
|
341
|
+
panels.delete(panel.id);
|
|
342
|
+
notify();
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
update(id, next) {
|
|
346
|
+
const existing = panels.get(id);
|
|
347
|
+
if (existing == null) return;
|
|
348
|
+
if (existing.side === next.side && existing.size === next.size && existing.open === next.open && existing.badge === next.badge) return;
|
|
349
|
+
panels.set(id, {
|
|
350
|
+
...existing,
|
|
351
|
+
...next
|
|
352
|
+
});
|
|
353
|
+
notify();
|
|
354
|
+
},
|
|
355
|
+
getView(id) {
|
|
356
|
+
const list = [...panels.values()];
|
|
357
|
+
const anyOpen = list.some((p) => p.open);
|
|
358
|
+
const firstId = list.length > 0 ? list[0].id : null;
|
|
359
|
+
let dockOffset = 0;
|
|
360
|
+
let stacked = false;
|
|
361
|
+
const self = panels.get(id);
|
|
362
|
+
if (self != null && self.open) {
|
|
363
|
+
let seenSelf = false;
|
|
364
|
+
for (const panel of list) {
|
|
365
|
+
if (panel.id === id) {
|
|
366
|
+
seenSelf = true;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (panel.open && panel.side === self.side) {
|
|
370
|
+
stacked = true;
|
|
371
|
+
if (!seenSelf) dockOffset += panel.size;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
dockOffset,
|
|
377
|
+
stacked,
|
|
378
|
+
anyOpen,
|
|
379
|
+
isPrimary: id === firstId,
|
|
380
|
+
devtools: list.map(toRef),
|
|
381
|
+
otherClosed: list.filter((p) => !p.open && p.id !== id).map(toRef)
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
subscribe(listener) {
|
|
385
|
+
listeners.add(listener);
|
|
386
|
+
return () => {
|
|
387
|
+
listeners.delete(listener);
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Returns the page-wide dock coordinator, installing it on `window` the first
|
|
394
|
+
* time it is requested. On the server (no `window`) a throwaway instance is
|
|
395
|
+
* returned so callers can use it unconditionally.
|
|
396
|
+
*/
|
|
397
|
+
function getDevtoolsDockCoordinator() {
|
|
398
|
+
if (typeof window === "undefined") return createCoordinator();
|
|
399
|
+
const host = window;
|
|
400
|
+
const existing = host[GLOBAL_KEY];
|
|
401
|
+
if (existing != null && existing.version === VERSION) return existing;
|
|
402
|
+
const created = createCoordinator();
|
|
403
|
+
host[GLOBAL_KEY] = created;
|
|
404
|
+
return created;
|
|
405
|
+
}
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region ../nice-devtools-shared/src/list.ts
|
|
408
|
+
/**
|
|
409
|
+
* Pin a virtualized list's viewport to a stable anchor row across list
|
|
410
|
+
* mutations. Both devtools timelines are newest-first, so new entries (and
|
|
411
|
+
* merges) prepend at the top; without this they shove whatever the user is
|
|
412
|
+
* looking at downward. The anchor is the selected row when one is visible,
|
|
413
|
+
* otherwise the first visible row, so "the thing I selected / am reading" stays
|
|
414
|
+
* put at the same screen position. Returns an `onScroll` handler that must be
|
|
415
|
+
* wired to the scroll container.
|
|
416
|
+
*
|
|
417
|
+
* `hoveringRef` is optional. When provided, the hook keeps the viewport still
|
|
418
|
+
* even across selection changes while the cursor is over the list — the caller
|
|
419
|
+
* is expected to suppress its own scroll-into-view in that case and let this
|
|
420
|
+
* hold the view rock-still (nice-action's pause-on-hover behaviour). When
|
|
421
|
+
* omitted, a selection change yields to the caller's scroll-into-view effect.
|
|
422
|
+
*/
|
|
423
|
+
function useListScrollAnchor({ containerRef, virtualizer, itemKeys, selectedKey, hoveringRef }) {
|
|
424
|
+
const anchorRef = useRef(null);
|
|
425
|
+
const selectedKeyRef = useRef(selectedKey);
|
|
426
|
+
selectedKeyRef.current = selectedKey;
|
|
427
|
+
const captureAnchor = useCallback(() => {
|
|
428
|
+
const el = containerRef.current;
|
|
429
|
+
if (el == null) return;
|
|
430
|
+
const items = virtualizer.getVirtualItems();
|
|
431
|
+
if (items.length === 0) {
|
|
432
|
+
anchorRef.current = null;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const scrollTop = el.scrollTop;
|
|
436
|
+
const sel = selectedKeyRef.current;
|
|
437
|
+
const chosen = (sel != null ? items.find((vi) => String(vi.key) === sel) : void 0) ?? items.find((vi) => vi.end > scrollTop) ?? items[0];
|
|
438
|
+
anchorRef.current = {
|
|
439
|
+
key: String(chosen.key),
|
|
440
|
+
delta: chosen.start - scrollTop
|
|
441
|
+
};
|
|
442
|
+
}, [containerRef, virtualizer]);
|
|
443
|
+
const prevSelectedRef = useRef(selectedKey);
|
|
444
|
+
useLayoutEffect(() => {
|
|
445
|
+
const selectionChanged = prevSelectedRef.current !== selectedKey;
|
|
446
|
+
prevSelectedRef.current = selectedKey;
|
|
447
|
+
if (selectionChanged && !(hoveringRef?.current ?? false)) return;
|
|
448
|
+
const anchor = anchorRef.current;
|
|
449
|
+
if (anchor == null) return;
|
|
450
|
+
const index = itemKeys.indexOf(anchor.key);
|
|
451
|
+
if (index < 0) return;
|
|
452
|
+
const offset = virtualizer.getOffsetForIndex(index, "start")?.[0];
|
|
453
|
+
if (offset == null) return;
|
|
454
|
+
const target = Math.max(0, offset - anchor.delta);
|
|
455
|
+
const current = virtualizer.scrollOffset ?? 0;
|
|
456
|
+
if (Math.abs(target - current) > 1) virtualizer.scrollToOffset(target);
|
|
457
|
+
}, [
|
|
458
|
+
itemKeys,
|
|
459
|
+
selectedKey,
|
|
460
|
+
virtualizer,
|
|
461
|
+
hoveringRef
|
|
462
|
+
]);
|
|
463
|
+
return captureAnchor;
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region ../nice-devtools-shared/src/components/SectionLabel.tsx
|
|
467
|
+
/** Small uppercase heading used above a detail/section block. */
|
|
468
|
+
function SectionLabel({ label, color = DEVTOOL_COLOR_SEMANTIC_SYSTEM }) {
|
|
469
|
+
return /* @__PURE__ */ jsx("div", {
|
|
470
|
+
style: {
|
|
471
|
+
color,
|
|
472
|
+
fontSize: "0.85em",
|
|
473
|
+
marginBottom: "3px",
|
|
474
|
+
textTransform: "uppercase",
|
|
475
|
+
letterSpacing: "0.05em",
|
|
476
|
+
fontWeight: 500,
|
|
477
|
+
textAlign: "left"
|
|
478
|
+
},
|
|
479
|
+
children: label
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region ../../node_modules/.bun/react-dom@19.2.7+e14d3f224186685e/node_modules/react-dom/cjs/react-dom.production.js
|
|
484
|
+
/**
|
|
485
|
+
* @license React
|
|
486
|
+
* react-dom.production.js
|
|
487
|
+
*
|
|
488
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
489
|
+
*
|
|
490
|
+
* This source code is licensed under the MIT license found in the
|
|
491
|
+
* LICENSE file in the root directory of this source tree.
|
|
492
|
+
*/
|
|
493
|
+
var require_react_dom_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
494
|
+
__require("react").__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
|
495
|
+
}));
|
|
496
|
+
(/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
497
|
+
function checkDCE() {
|
|
498
|
+
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === "undefined" || typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== "function") return;
|
|
499
|
+
try {
|
|
500
|
+
__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE);
|
|
501
|
+
} catch (err) {
|
|
502
|
+
console.error(err);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
checkDCE();
|
|
506
|
+
module.exports = require_react_dom_production();
|
|
507
|
+
})))();
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region ../nice-devtools-shared/src/components/PanelChrome.tsx
|
|
510
|
+
const DOCKED_SIZE_MIN = 140;
|
|
511
|
+
const POSITION_GRID = [
|
|
512
|
+
{
|
|
513
|
+
key: "tl",
|
|
514
|
+
pos: null
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
key: "tc",
|
|
518
|
+
pos: "dock-top"
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
key: "tr",
|
|
522
|
+
pos: null
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
key: "ml",
|
|
526
|
+
pos: "dock-left"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
key: "mc",
|
|
530
|
+
pos: null
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
key: "mr",
|
|
534
|
+
pos: "dock-right"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
key: "bl",
|
|
538
|
+
pos: null
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
key: "bc",
|
|
542
|
+
pos: "dock-bottom"
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
key: "br",
|
|
546
|
+
pos: null
|
|
547
|
+
}
|
|
548
|
+
];
|
|
549
|
+
function getDockSide(pos) {
|
|
550
|
+
switch (pos) {
|
|
551
|
+
case "dock-top": return "top";
|
|
552
|
+
case "dock-left": return "left";
|
|
553
|
+
case "dock-right": return "right";
|
|
554
|
+
default: return "bottom";
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function chromeButtonStyle(color) {
|
|
558
|
+
return {
|
|
559
|
+
background: "none",
|
|
560
|
+
border: "none",
|
|
561
|
+
color,
|
|
562
|
+
cursor: "pointer",
|
|
563
|
+
fontSize: "11px",
|
|
564
|
+
padding: 0,
|
|
565
|
+
fontFamily: SANS_FONT,
|
|
566
|
+
whiteSpace: "nowrap"
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* The panel's top chrome: a brand/title on the left (plus pills to open any other
|
|
571
|
+
* closed devtools), and the panel controls on the right. The controls column
|
|
572
|
+
* holds any caller-provided `children` (e.g. a mode switch) above a row of the
|
|
573
|
+
* pause/clear actions, then the dock-position picker and the close button.
|
|
574
|
+
*
|
|
575
|
+
* `onTogglePause` is optional — when omitted no pause control is shown (the
|
|
576
|
+
* nice-action timeline has nothing to pause), keeping a single header component
|
|
577
|
+
* consistent across both devtools suites.
|
|
578
|
+
*/
|
|
579
|
+
function PanelHeader({ title, position, onPositionChange, onClose, onClear, paused, onTogglePause, openOthers, children }) {
|
|
580
|
+
const hasActionsRow = onTogglePause != null || onClear != null;
|
|
581
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
582
|
+
style: {
|
|
583
|
+
display: "flex",
|
|
584
|
+
alignItems: "center",
|
|
585
|
+
justifyContent: "space-between",
|
|
586
|
+
padding: "6px 11px",
|
|
587
|
+
gap: "10px",
|
|
588
|
+
background: DEVTOOL_SECTION_BACKGROUND,
|
|
589
|
+
borderBottom: `1px solid ${DEVTOOL_LIST_BASE_BACKGROUND}`,
|
|
590
|
+
flexShrink: 0
|
|
591
|
+
},
|
|
592
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
593
|
+
style: {
|
|
594
|
+
display: "flex",
|
|
595
|
+
alignItems: "center",
|
|
596
|
+
gap: "8px",
|
|
597
|
+
minWidth: 0
|
|
598
|
+
},
|
|
599
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
600
|
+
style: {
|
|
601
|
+
color: DEVTOOL_COLOR_SEMANTIC_SYSTEM,
|
|
602
|
+
fontWeight: "bold",
|
|
603
|
+
fontSize: "11px",
|
|
604
|
+
whiteSpace: "nowrap"
|
|
605
|
+
},
|
|
606
|
+
children: title
|
|
607
|
+
}), openOthers?.map((item) => /* @__PURE__ */ jsxs("button", {
|
|
608
|
+
onClick: item.onOpen,
|
|
609
|
+
title: `Open ${item.label} devtools`,
|
|
610
|
+
style: {
|
|
611
|
+
display: "flex",
|
|
612
|
+
alignItems: "center",
|
|
613
|
+
gap: "4px",
|
|
614
|
+
background: DEVTOOL_LIST_BASE_BACKGROUND,
|
|
615
|
+
border: `1px solid ${DEVTOOL_COLOR_TEXT_FAINT}`,
|
|
616
|
+
borderRadius: "999px",
|
|
617
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
618
|
+
cursor: "pointer",
|
|
619
|
+
fontSize: "10px",
|
|
620
|
+
padding: "1px 7px 1px 6px",
|
|
621
|
+
fontFamily: SANS_FONT,
|
|
622
|
+
whiteSpace: "nowrap"
|
|
623
|
+
},
|
|
624
|
+
children: [
|
|
625
|
+
/* @__PURE__ */ jsx("span", { children: item.icon }),
|
|
626
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
627
|
+
item.badge != null && /* @__PURE__ */ jsx("span", {
|
|
628
|
+
style: { color: "#38BDF8" },
|
|
629
|
+
children: item.badge
|
|
630
|
+
})
|
|
631
|
+
]
|
|
632
|
+
}, item.id))]
|
|
633
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
634
|
+
style: {
|
|
635
|
+
display: "flex",
|
|
636
|
+
gap: "10px",
|
|
637
|
+
alignItems: "center"
|
|
638
|
+
},
|
|
639
|
+
children: [
|
|
640
|
+
/* @__PURE__ */ jsxs("div", {
|
|
641
|
+
style: {
|
|
642
|
+
display: "flex",
|
|
643
|
+
flexDirection: "column",
|
|
644
|
+
alignItems: "stretch"
|
|
645
|
+
},
|
|
646
|
+
children: [children, hasActionsRow && /* @__PURE__ */ jsxs("div", {
|
|
647
|
+
style: {
|
|
648
|
+
display: "flex",
|
|
649
|
+
gap: "10px",
|
|
650
|
+
alignItems: "center",
|
|
651
|
+
justifyContent: "space-between",
|
|
652
|
+
padding: "3px"
|
|
653
|
+
},
|
|
654
|
+
children: [onTogglePause != null && /* @__PURE__ */ jsx("button", {
|
|
655
|
+
onClick: onTogglePause,
|
|
656
|
+
title: paused ? "Resume recording" : "Pause recording",
|
|
657
|
+
style: chromeButtonStyle(paused ? "#38BDF8" : "#64748b"),
|
|
658
|
+
children: paused ? "▶ resume" : "⏸ pause"
|
|
659
|
+
}), onClear != null && /* @__PURE__ */ jsx("button", {
|
|
660
|
+
onClick: onClear,
|
|
661
|
+
style: chromeButtonStyle("#64748b"),
|
|
662
|
+
children: "clear"
|
|
663
|
+
})]
|
|
664
|
+
})]
|
|
665
|
+
}),
|
|
666
|
+
/* @__PURE__ */ jsx(PositionPicker, {
|
|
667
|
+
position,
|
|
668
|
+
onChange: onPositionChange
|
|
669
|
+
}),
|
|
670
|
+
/* @__PURE__ */ jsx("button", {
|
|
671
|
+
onClick: onClose,
|
|
672
|
+
style: {
|
|
673
|
+
...chromeButtonStyle(DEVTOOL_COLOR_TEXT_MUTED),
|
|
674
|
+
fontSize: "16px",
|
|
675
|
+
lineHeight: "1"
|
|
676
|
+
},
|
|
677
|
+
children: "×"
|
|
678
|
+
})
|
|
679
|
+
]
|
|
680
|
+
})]
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function PositionPicker({ position, onChange }) {
|
|
684
|
+
return /* @__PURE__ */ jsx("div", {
|
|
685
|
+
title: "Move / dock panel",
|
|
686
|
+
style: {
|
|
687
|
+
display: "grid",
|
|
688
|
+
gridTemplateColumns: "repeat(3, 9px)",
|
|
689
|
+
gap: "2px",
|
|
690
|
+
padding: "2px"
|
|
691
|
+
},
|
|
692
|
+
children: POSITION_GRID.map(({ key, pos }) => {
|
|
693
|
+
if (pos == null) return /* @__PURE__ */ jsx("div", { style: {
|
|
694
|
+
width: "9px",
|
|
695
|
+
height: "9px"
|
|
696
|
+
} }, key);
|
|
697
|
+
const isTopBottom = pos === "dock-top" || pos === "dock-bottom";
|
|
698
|
+
return /* @__PURE__ */ jsx("div", {
|
|
699
|
+
title: pos,
|
|
700
|
+
onClick: () => onChange(pos),
|
|
701
|
+
style: {
|
|
702
|
+
width: "9px",
|
|
703
|
+
height: "9px",
|
|
704
|
+
display: "flex",
|
|
705
|
+
alignItems: "center",
|
|
706
|
+
justifyContent: "center",
|
|
707
|
+
cursor: "pointer"
|
|
708
|
+
},
|
|
709
|
+
children: /* @__PURE__ */ jsx("div", { style: {
|
|
710
|
+
width: isTopBottom ? "9px" : "3px",
|
|
711
|
+
height: isTopBottom ? "3px" : "9px",
|
|
712
|
+
borderRadius: "1px",
|
|
713
|
+
background: pos === position ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : DEVTOOL_COLOR_TEXT_FAINT
|
|
714
|
+
} })
|
|
715
|
+
}, key);
|
|
716
|
+
})
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
function ResizeHandle({ dockSide, dockedSize, onChange }) {
|
|
720
|
+
const isHoriz = dockSide === "left" || dockSide === "right";
|
|
721
|
+
const onMouseDown = (e) => {
|
|
722
|
+
e.preventDefault();
|
|
723
|
+
const startCoord = isHoriz ? e.clientX : e.clientY;
|
|
724
|
+
const startSize = dockedSize;
|
|
725
|
+
const maxSize = isHoriz ? window.innerWidth * .85 : window.innerHeight * .85;
|
|
726
|
+
const sign = dockSide === "bottom" || dockSide === "right" ? -1 : 1;
|
|
727
|
+
const onMove = (me) => {
|
|
728
|
+
const delta = (isHoriz ? me.clientX : me.clientY) - startCoord;
|
|
729
|
+
onChange(Math.max(DOCKED_SIZE_MIN, Math.min(maxSize, startSize + sign * delta)));
|
|
730
|
+
};
|
|
731
|
+
const onUp = () => {
|
|
732
|
+
window.removeEventListener("mousemove", onMove);
|
|
733
|
+
window.removeEventListener("mouseup", onUp);
|
|
734
|
+
};
|
|
735
|
+
window.addEventListener("mousemove", onMove);
|
|
736
|
+
window.addEventListener("mouseup", onUp);
|
|
737
|
+
};
|
|
738
|
+
return /* @__PURE__ */ jsx("div", {
|
|
739
|
+
onMouseDown,
|
|
740
|
+
style: {
|
|
741
|
+
position: "absolute",
|
|
742
|
+
zIndex: 10,
|
|
743
|
+
background: "transparent",
|
|
744
|
+
...dockSide === "bottom" ? {
|
|
745
|
+
top: 0,
|
|
746
|
+
left: 0,
|
|
747
|
+
right: 0,
|
|
748
|
+
height: "5px",
|
|
749
|
+
cursor: "ns-resize"
|
|
750
|
+
} : dockSide === "top" ? {
|
|
751
|
+
bottom: 0,
|
|
752
|
+
left: 0,
|
|
753
|
+
right: 0,
|
|
754
|
+
height: "5px",
|
|
755
|
+
cursor: "ns-resize"
|
|
756
|
+
} : dockSide === "right" ? {
|
|
757
|
+
top: 0,
|
|
758
|
+
bottom: 0,
|
|
759
|
+
left: 0,
|
|
760
|
+
width: "5px",
|
|
761
|
+
cursor: "ew-resize"
|
|
762
|
+
} : {
|
|
763
|
+
top: 0,
|
|
764
|
+
bottom: 0,
|
|
765
|
+
right: 0,
|
|
766
|
+
width: "5px",
|
|
767
|
+
cursor: "ew-resize"
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
const SPLIT_RATIO_MIN = .15;
|
|
773
|
+
const SPLIT_RATIO_MAX = .85;
|
|
774
|
+
/**
|
|
775
|
+
* Draggable divider between the list and the detail pane. `horizontal` refers to
|
|
776
|
+
* the split axis: a row layout (dock top/bottom) splits horizontally and drags
|
|
777
|
+
* left/right; a column layout (dock left/right) splits vertically. The reported
|
|
778
|
+
* ratio is the fraction of the container the *detail* pane should occupy.
|
|
779
|
+
*/
|
|
780
|
+
function SplitHandle({ horizontal, onRatioChange }) {
|
|
781
|
+
const [hovered, setHovered] = useState(false);
|
|
782
|
+
const onMouseDown = (e) => {
|
|
783
|
+
e.preventDefault();
|
|
784
|
+
const container = e.currentTarget.parentElement;
|
|
785
|
+
if (container == null) return;
|
|
786
|
+
const onMove = (me) => {
|
|
787
|
+
const rect = container.getBoundingClientRect();
|
|
788
|
+
const ratio = horizontal ? (rect.right - me.clientX) / rect.width : (rect.bottom - me.clientY) / rect.height;
|
|
789
|
+
onRatioChange(Math.max(SPLIT_RATIO_MIN, Math.min(SPLIT_RATIO_MAX, ratio)));
|
|
790
|
+
};
|
|
791
|
+
const onUp = () => {
|
|
792
|
+
window.removeEventListener("mousemove", onMove);
|
|
793
|
+
window.removeEventListener("mouseup", onUp);
|
|
794
|
+
};
|
|
795
|
+
window.addEventListener("mousemove", onMove);
|
|
796
|
+
window.addEventListener("mouseup", onUp);
|
|
797
|
+
};
|
|
798
|
+
return /* @__PURE__ */ jsx("div", {
|
|
799
|
+
onMouseDown,
|
|
800
|
+
onMouseEnter: () => setHovered(true),
|
|
801
|
+
onMouseLeave: () => setHovered(false),
|
|
802
|
+
style: {
|
|
803
|
+
flex: "0 0 5px",
|
|
804
|
+
alignSelf: "stretch",
|
|
805
|
+
cursor: horizontal ? "ew-resize" : "ns-resize",
|
|
806
|
+
background: hovered ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : "transparent",
|
|
807
|
+
opacity: hovered ? .6 : 1,
|
|
808
|
+
zIndex: 5
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/** A compact segmented toggle (e.g. the nice-state Timeline / State switch). */
|
|
813
|
+
function SegmentedControl({ options, value, onChange }) {
|
|
814
|
+
return /* @__PURE__ */ jsx("div", {
|
|
815
|
+
style: {
|
|
816
|
+
display: "flex",
|
|
817
|
+
border: `1px solid ${DEVTOOL_COLOR_TEXT_FAINT}`,
|
|
818
|
+
borderRadius: "5px",
|
|
819
|
+
overflow: "hidden"
|
|
820
|
+
},
|
|
821
|
+
children: options.map((opt) => {
|
|
822
|
+
const active = opt.value === value;
|
|
823
|
+
return /* @__PURE__ */ jsx("button", {
|
|
824
|
+
onClick: () => onChange(opt.value),
|
|
825
|
+
style: {
|
|
826
|
+
background: active ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : "transparent",
|
|
827
|
+
color: active ? "#0f172a" : DEVTOOL_COLOR_TEXT_MUTED,
|
|
828
|
+
border: "none",
|
|
829
|
+
cursor: "pointer",
|
|
830
|
+
fontSize: "10px",
|
|
831
|
+
fontWeight: active ? 700 : 500,
|
|
832
|
+
padding: "3px 9px",
|
|
833
|
+
fontFamily: SANS_FONT
|
|
834
|
+
},
|
|
835
|
+
children: opt.label
|
|
836
|
+
}, opt.value);
|
|
837
|
+
})
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* The combined, page-wide launcher shown while every devtool is collapsed — one
|
|
842
|
+
* grouped pill with a segment per registered devtool, so the buttons never
|
|
843
|
+
* overlap or hide behind each other. Rendered by the coordinator's "primary"
|
|
844
|
+
* devtool only.
|
|
845
|
+
*/
|
|
846
|
+
function DevtoolsLauncher({ items }) {
|
|
847
|
+
return /* @__PURE__ */ jsx("div", {
|
|
848
|
+
style: {
|
|
849
|
+
position: "fixed",
|
|
850
|
+
bottom: "16px",
|
|
851
|
+
right: "16px",
|
|
852
|
+
zIndex: 2147483647,
|
|
853
|
+
display: "flex",
|
|
854
|
+
fontFamily: MONO_FONT,
|
|
855
|
+
fontSize: "12px"
|
|
856
|
+
},
|
|
857
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
858
|
+
style: {
|
|
859
|
+
display: "flex",
|
|
860
|
+
background: DEVTOOL_SECTION_BACKGROUND,
|
|
861
|
+
border: `1px solid ${DEVTOOL_COLOR_TEXT_FAINT}`,
|
|
862
|
+
borderRadius: "6px",
|
|
863
|
+
overflow: "hidden",
|
|
864
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.35)"
|
|
865
|
+
},
|
|
866
|
+
children: items.map((item, i) => /* @__PURE__ */ jsxs("button", {
|
|
867
|
+
onClick: item.onOpen,
|
|
868
|
+
style: {
|
|
869
|
+
display: "flex",
|
|
870
|
+
alignItems: "center",
|
|
871
|
+
gap: "5px",
|
|
872
|
+
background: "transparent",
|
|
873
|
+
color: DEVTOOL_COLOR_TEXT_SECONDARY,
|
|
874
|
+
border: "none",
|
|
875
|
+
borderLeft: i > 0 ? `1px solid ${DEVTOOL_COLOR_TEXT_FAINT}` : "none",
|
|
876
|
+
cursor: "pointer",
|
|
877
|
+
padding: "6px 11px",
|
|
878
|
+
fontFamily: MONO_FONT,
|
|
879
|
+
fontSize: "12px",
|
|
880
|
+
lineHeight: "1.5"
|
|
881
|
+
},
|
|
882
|
+
children: [
|
|
883
|
+
/* @__PURE__ */ jsx("span", { children: item.icon }),
|
|
884
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
885
|
+
item.badge != null && /* @__PURE__ */ jsx("span", {
|
|
886
|
+
style: { color: "#38BDF8" },
|
|
887
|
+
children: item.badge
|
|
888
|
+
})
|
|
889
|
+
]
|
|
890
|
+
}, item.id))
|
|
891
|
+
})
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
//#endregion
|
|
895
|
+
//#region ../nice-devtools-shared/src/components/FollowLatestToggles.tsx
|
|
896
|
+
/**
|
|
897
|
+
* The "Follow latest" toggle pair shown above a devtools timeline list, with its
|
|
898
|
+
* indented "clicking latest re-follows" sub-option. `noun` is the thing the
|
|
899
|
+
* timeline tracks ("action" in nice-action, "change" in nice-state) and is woven
|
|
900
|
+
* into the explanatory tooltips so both suites read naturally from one component.
|
|
901
|
+
*/
|
|
902
|
+
function FollowLatestToggles({ noun, stayOnLatest, onStayOnLatestChange, followLatestOnSelect, onFollowLatestOnSelectChange }) {
|
|
903
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
904
|
+
style: {
|
|
905
|
+
display: "flex",
|
|
906
|
+
flexDirection: "column",
|
|
907
|
+
flexShrink: 0,
|
|
908
|
+
paddingBottom: "3px",
|
|
909
|
+
background: DEVTOOL_SECTION_BACKGROUND,
|
|
910
|
+
borderBottom: `1px solid ${DEVTOOL_LIST_BASE_BACKGROUND}`
|
|
911
|
+
},
|
|
912
|
+
children: [/* @__PURE__ */ jsx(ToggleLabel, {
|
|
913
|
+
title: `Auto-select the most recent ${noun} so the detail pane keeps showing the latest as new ${noun}s land`,
|
|
914
|
+
checked: stayOnLatest,
|
|
915
|
+
onChange: onStayOnLatestChange,
|
|
916
|
+
children: "Follow latest"
|
|
917
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
918
|
+
style: {
|
|
919
|
+
display: "flex",
|
|
920
|
+
alignItems: "center",
|
|
921
|
+
paddingLeft: "12px",
|
|
922
|
+
marginTop: "-4px"
|
|
923
|
+
},
|
|
924
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
925
|
+
"aria-hidden": true,
|
|
926
|
+
style: {
|
|
927
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
928
|
+
fontFamily: SANS_FONT,
|
|
929
|
+
fontSize: "10px",
|
|
930
|
+
lineHeight: 1
|
|
931
|
+
},
|
|
932
|
+
children: "└"
|
|
933
|
+
}), /* @__PURE__ */ jsx(ToggleLabel, {
|
|
934
|
+
title: `When you click the latest ${noun}, turn 'Follow latest' back on so the view resumes tracking new ${noun}s. Turn this off to pin exactly to the ${noun} you click instead.`,
|
|
935
|
+
checked: followLatestOnSelect,
|
|
936
|
+
onChange: onFollowLatestOnSelectChange,
|
|
937
|
+
children: "clicking latest re-follows"
|
|
938
|
+
})]
|
|
939
|
+
})]
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
function ToggleLabel({ checked, onChange, title, children }) {
|
|
943
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
944
|
+
title,
|
|
945
|
+
style: {
|
|
946
|
+
display: "flex",
|
|
947
|
+
alignItems: "center",
|
|
948
|
+
gap: "6px",
|
|
949
|
+
padding: "5px 10px",
|
|
950
|
+
cursor: "pointer",
|
|
951
|
+
userSelect: "none",
|
|
952
|
+
color: checked ? DEVTOOL_COLOR_TEXT_SECONDARY : DEVTOOL_COLOR_TEXT_MUTED,
|
|
953
|
+
fontSize: "10px",
|
|
954
|
+
fontFamily: SANS_FONT
|
|
955
|
+
},
|
|
956
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
957
|
+
type: "checkbox",
|
|
958
|
+
checked,
|
|
959
|
+
onChange: (e) => onChange(e.target.checked),
|
|
960
|
+
style: {
|
|
961
|
+
accentColor: DEVTOOL_COLOR_SEMANTIC_SYSTEM,
|
|
962
|
+
cursor: "pointer",
|
|
963
|
+
margin: 0
|
|
964
|
+
}
|
|
965
|
+
}), children]
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region ../nice-devtools-shared/src/components/VirtualList.tsx
|
|
970
|
+
/**
|
|
971
|
+
* The shared, virtualized timeline list used by both the nice-action and
|
|
972
|
+
* nice-state devtools. It owns everything about *how the list behaves* — windowed
|
|
973
|
+
* rendering, viewport anchoring across prepends, scroll-the-selection-into-view,
|
|
974
|
+
* and pause-on-hover with a "N new" catch-up hint — while each devtool supplies
|
|
975
|
+
* its own `renderItem` for the row content (which stays unique per devtool).
|
|
976
|
+
*
|
|
977
|
+
* Behaviour notes:
|
|
978
|
+
* • The list is assumed newest-first: new rows prepend at the top. While the
|
|
979
|
+
* cursor is over the list the viewport is held perfectly still (so reading
|
|
980
|
+
* isn't disrupted); fresh rows still render above and a sticky "↑ N new"
|
|
981
|
+
* badge hints they're there. Move the cursor away and normal follow resumes.
|
|
982
|
+
* • `selectedKey` is the stable item key of the selected row (NOT necessarily a
|
|
983
|
+
* sub-selection inside it). When it changes the row is scrolled into view —
|
|
984
|
+
* unless the cursor is hovering, in which case the view is left untouched.
|
|
985
|
+
*/
|
|
986
|
+
function DevtoolsVirtualList({ items, getItemKey, renderItem, selectedKey, estimateSize, overscan = 8, rowStyle, empty, footer, style }) {
|
|
987
|
+
const containerRef = useRef(null);
|
|
988
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
989
|
+
const hoveringRef = useRef(false);
|
|
990
|
+
hoveringRef.current = isHovering;
|
|
991
|
+
const hoverBaselineCountRef = useRef(0);
|
|
992
|
+
const pendingCount = isHovering ? Math.max(0, items.length - hoverBaselineCountRef.current) : 0;
|
|
993
|
+
const hasPendingActivity = isHovering && pendingCount > 0;
|
|
994
|
+
const getItemKeyRef = useRef(getItemKey);
|
|
995
|
+
getItemKeyRef.current = getItemKey;
|
|
996
|
+
const itemKeys = useMemo(() => items.map((item, i) => getItemKeyRef.current(item, i)), [items]);
|
|
997
|
+
const virtualizer = useVirtualizer({
|
|
998
|
+
count: items.length,
|
|
999
|
+
getScrollElement: () => containerRef.current,
|
|
1000
|
+
estimateSize: () => estimateSize,
|
|
1001
|
+
overscan,
|
|
1002
|
+
getItemKey: (index) => itemKeys[index]
|
|
1003
|
+
});
|
|
1004
|
+
const onScroll = useListScrollAnchor({
|
|
1005
|
+
containerRef,
|
|
1006
|
+
virtualizer,
|
|
1007
|
+
itemKeys,
|
|
1008
|
+
selectedKey,
|
|
1009
|
+
hoveringRef
|
|
1010
|
+
});
|
|
1011
|
+
const prevSelectedRef = useRef(selectedKey);
|
|
1012
|
+
useEffect(() => {
|
|
1013
|
+
if (selectedKey === prevSelectedRef.current) return;
|
|
1014
|
+
prevSelectedRef.current = selectedKey;
|
|
1015
|
+
if (selectedKey == null) return;
|
|
1016
|
+
if (hoveringRef.current) return;
|
|
1017
|
+
const index = itemKeys.indexOf(selectedKey);
|
|
1018
|
+
if (index >= 0) virtualizer.scrollToIndex(index, { align: "auto" });
|
|
1019
|
+
}, [
|
|
1020
|
+
selectedKey,
|
|
1021
|
+
itemKeys,
|
|
1022
|
+
virtualizer
|
|
1023
|
+
]);
|
|
1024
|
+
if (items.length === 0) return /* @__PURE__ */ jsx(Fragment, { children: empty });
|
|
1025
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1026
|
+
ref: containerRef,
|
|
1027
|
+
style,
|
|
1028
|
+
onScroll,
|
|
1029
|
+
onMouseEnter: () => {
|
|
1030
|
+
hoverBaselineCountRef.current = items.length;
|
|
1031
|
+
setIsHovering(true);
|
|
1032
|
+
},
|
|
1033
|
+
onMouseLeave: () => setIsHovering(false),
|
|
1034
|
+
children: [
|
|
1035
|
+
hasPendingActivity && /* @__PURE__ */ jsx(PendingIndicator, { count: pendingCount }),
|
|
1036
|
+
/* @__PURE__ */ jsx("div", {
|
|
1037
|
+
style: {
|
|
1038
|
+
position: "relative",
|
|
1039
|
+
width: "100%",
|
|
1040
|
+
height: virtualizer.getTotalSize()
|
|
1041
|
+
},
|
|
1042
|
+
children: virtualizer.getVirtualItems().map((vItem) => /* @__PURE__ */ jsx("div", {
|
|
1043
|
+
"data-index": vItem.index,
|
|
1044
|
+
ref: virtualizer.measureElement,
|
|
1045
|
+
style: {
|
|
1046
|
+
position: "absolute",
|
|
1047
|
+
top: 0,
|
|
1048
|
+
left: 0,
|
|
1049
|
+
width: "100%",
|
|
1050
|
+
transform: `translateY(${vItem.start}px)`,
|
|
1051
|
+
...rowStyle
|
|
1052
|
+
},
|
|
1053
|
+
children: renderItem(items[vItem.index], vItem.index)
|
|
1054
|
+
}, vItem.key))
|
|
1055
|
+
}),
|
|
1056
|
+
footer
|
|
1057
|
+
]
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* A sticky, zero-height overlay floating over the rows without taking layout space. Shown only once
|
|
1062
|
+
* new items have arrived while hovering, hinting they're above (newest-first) so the user can scroll
|
|
1063
|
+
* up without the view having jumped there.
|
|
1064
|
+
*/
|
|
1065
|
+
function PendingIndicator({ count }) {
|
|
1066
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1067
|
+
style: {
|
|
1068
|
+
position: "sticky",
|
|
1069
|
+
top: 0,
|
|
1070
|
+
height: 0,
|
|
1071
|
+
zIndex: 5,
|
|
1072
|
+
pointerEvents: "none"
|
|
1073
|
+
},
|
|
1074
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1075
|
+
style: {
|
|
1076
|
+
position: "absolute",
|
|
1077
|
+
top: "6px",
|
|
1078
|
+
right: "10px",
|
|
1079
|
+
display: "flex",
|
|
1080
|
+
alignItems: "center",
|
|
1081
|
+
gap: "5px",
|
|
1082
|
+
padding: "2px 7px",
|
|
1083
|
+
borderRadius: "10px",
|
|
1084
|
+
fontSize: "10px",
|
|
1085
|
+
fontFamily: SANS_FONT,
|
|
1086
|
+
color: DEVTOOL_COLOR_SEMANTIC_WARNING,
|
|
1087
|
+
background: DEVTOOL_TOOLTIP_BACKGROUND,
|
|
1088
|
+
border: `1px solid ${DEVTOOL_COLOR_SEMANTIC_WARNING}55`,
|
|
1089
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.5)",
|
|
1090
|
+
whiteSpace: "nowrap"
|
|
1091
|
+
},
|
|
1092
|
+
children: [
|
|
1093
|
+
"↑ ",
|
|
1094
|
+
count,
|
|
1095
|
+
" new"
|
|
1096
|
+
]
|
|
1097
|
+
})
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/devtools/core/devtools_colors.ts
|
|
1102
|
+
const DEVTOOL_EDITOR_BACKGROUND = "#08101f";
|
|
1103
|
+
//#endregion
|
|
1104
|
+
//#region src/devtools/browser/components/utils.ts
|
|
1105
|
+
const SOURCE_LABEL = {
|
|
1106
|
+
update: "UPDATE",
|
|
1107
|
+
replace: "REPLACE",
|
|
1108
|
+
"devtools-edit": "EDIT",
|
|
1109
|
+
"devtools-revert": "REVERT"
|
|
224
1110
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
1111
|
+
const SOURCE_COLOR = {
|
|
1112
|
+
update: DEVTOOL_COLOR_SEMANTIC_SYSTEM,
|
|
1113
|
+
replace: DEVTOOL_COLOR_SEMANTIC_METADATA,
|
|
1114
|
+
"devtools-edit": DEVTOOL_COLOR_SEMANTIC_WARNING,
|
|
1115
|
+
"devtools-revert": DEVTOOL_COLOR_SEMANTIC_WARNING
|
|
230
1116
|
};
|
|
1117
|
+
/** Render an Immer patch path (`["todos", 0, "done"]`) as `todos.0.done`. */
|
|
231
1118
|
function patchPathToString(path) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return path.map((seg) => String(seg)).join(".");
|
|
1119
|
+
if (path.length === 0) return "(root)";
|
|
1120
|
+
return path.map((seg) => String(seg)).join(".");
|
|
235
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* A terse, single-line summary of what a change touched, for the timeline row.
|
|
1124
|
+
* Prefers a `path: prev → next` form for single scalar replaces, otherwise lists
|
|
1125
|
+
* the distinct top-level paths affected.
|
|
1126
|
+
*/
|
|
236
1127
|
function summarizeChange(change) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return path;
|
|
252
|
-
}
|
|
253
|
-
const paths = patches.map((p) => topLevel(p.path)).filter((s) => s.length > 0);
|
|
254
|
-
const unique = [...new Set(paths)];
|
|
255
|
-
const shown = unique.slice(0, 3).join(", ");
|
|
256
|
-
return unique.length > 3 ? `${shown} +${unique.length - 3}` : shown;
|
|
1128
|
+
const { patches } = change;
|
|
1129
|
+
if (patches.length === 0) return change.source === "replace" || change.source === "devtools-edit" ? "whole state replaced" : "no patches";
|
|
1130
|
+
if (patches.length === 1) {
|
|
1131
|
+
const p = patches[0];
|
|
1132
|
+
const path = patchPathToString(p.path);
|
|
1133
|
+
if (p.op === "replace" && isScalar(p.value)) return `${path} → ${formatScalar(p.value)}`;
|
|
1134
|
+
if (p.op === "add") return `+ ${path}`;
|
|
1135
|
+
if (p.op === "remove") return `− ${path}`;
|
|
1136
|
+
return path;
|
|
1137
|
+
}
|
|
1138
|
+
const paths = patches.map((p) => topLevel(p.path)).filter((s) => s.length > 0);
|
|
1139
|
+
const unique = [...new Set(paths)];
|
|
1140
|
+
const shown = unique.slice(0, 3).join(", ");
|
|
1141
|
+
return unique.length > 3 ? `${shown} +${unique.length - 3}` : shown;
|
|
257
1142
|
}
|
|
258
1143
|
function topLevel(path) {
|
|
259
|
-
|
|
1144
|
+
return path.length > 0 ? String(path[0]) : "(root)";
|
|
260
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Structural identity of a change by its *resulting* state: same store, same
|
|
1148
|
+
* snapshot. The feature collapses updates that "didn't change the state from the
|
|
1149
|
+
* previous update", so we compare the committed state itself rather than the
|
|
1150
|
+
* mutation that produced it. Keying on patches would miss the no-op cases this is
|
|
1151
|
+
* meant to catch — a fresh object reference with identical content, or a
|
|
1152
|
+
* same-value write from a different source (e.g. a devtools edit followed by an
|
|
1153
|
+
* app update). Those carry patches yet leave the state untouched, so they belong
|
|
1154
|
+
* in the run that already reached this state regardless of source.
|
|
1155
|
+
*/
|
|
261
1156
|
function changeGroupKey(change) {
|
|
262
|
-
|
|
1157
|
+
return `${change.storeId}|${safeStringify(change.snapshot, 0)}`;
|
|
263
1158
|
}
|
|
1159
|
+
/** Collapse consecutive structurally-equal changes (list is newest-first). */
|
|
264
1160
|
function groupChanges(changes) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
1161
|
+
const groups = [];
|
|
1162
|
+
let lastKey = null;
|
|
1163
|
+
for (const change of changes) {
|
|
1164
|
+
const key = changeGroupKey(change);
|
|
1165
|
+
const last = groups[groups.length - 1];
|
|
1166
|
+
if (last != null && key === lastKey) {
|
|
1167
|
+
last.count++;
|
|
1168
|
+
last.oldest = change;
|
|
1169
|
+
} else groups.push({
|
|
1170
|
+
representative: change,
|
|
1171
|
+
oldest: change,
|
|
1172
|
+
count: 1
|
|
1173
|
+
});
|
|
1174
|
+
lastKey = key;
|
|
1175
|
+
}
|
|
1176
|
+
return groups;
|
|
279
1177
|
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Structural diff between two snapshots, flattened to the individual leaf/branch
|
|
1180
|
+
* paths that actually changed — the data behind the test-framework-style "Diff"
|
|
1181
|
+
* view. Unchanged branches are skipped entirely (Immer's structural sharing
|
|
1182
|
+
* means equal branches keep their reference, so this stays cheap).
|
|
1183
|
+
*/
|
|
280
1184
|
function computeDiff(before, after) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
1185
|
+
const out = [];
|
|
1186
|
+
walkDiff([], before, after, out);
|
|
1187
|
+
return out;
|
|
284
1188
|
}
|
|
285
1189
|
function isPlainObjectOrArray(value) {
|
|
286
|
-
|
|
1190
|
+
return value != null && typeof value === "object";
|
|
287
1191
|
}
|
|
288
1192
|
function walkDiff(path, before, after, out) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
1193
|
+
if (Object.is(before, after)) return;
|
|
1194
|
+
if (isPlainObjectOrArray(before) && isPlainObjectOrArray(after) && Array.isArray(before) === Array.isArray(after)) {
|
|
1195
|
+
const b = before;
|
|
1196
|
+
const a = after;
|
|
1197
|
+
const keys = new Set([...Object.keys(b), ...Object.keys(a)]);
|
|
1198
|
+
for (const key of keys) {
|
|
1199
|
+
const inB = key in b;
|
|
1200
|
+
const inA = key in a;
|
|
1201
|
+
const next = [...path, key];
|
|
1202
|
+
if (inB && !inA) out.push({
|
|
1203
|
+
path: formatDiffPath(next),
|
|
1204
|
+
segments: next,
|
|
1205
|
+
kind: "removed",
|
|
1206
|
+
before: b[key]
|
|
1207
|
+
});
|
|
1208
|
+
else if (!inB && inA) out.push({
|
|
1209
|
+
path: formatDiffPath(next),
|
|
1210
|
+
segments: next,
|
|
1211
|
+
kind: "added",
|
|
1212
|
+
after: a[key]
|
|
1213
|
+
});
|
|
1214
|
+
else walkDiff(next, b[key], a[key], out);
|
|
1215
|
+
}
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
out.push({
|
|
1219
|
+
path: formatDiffPath(path),
|
|
1220
|
+
segments: [...path],
|
|
1221
|
+
kind: "changed",
|
|
1222
|
+
before,
|
|
1223
|
+
after
|
|
1224
|
+
});
|
|
312
1225
|
}
|
|
313
1226
|
function formatDiffPath(path) {
|
|
314
|
-
|
|
1227
|
+
return path.length === 0 ? "(root)" : path.map((seg) => String(seg)).join(".");
|
|
315
1228
|
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Classic LCS line diff between two blocks of text — the data behind the unified
|
|
1231
|
+
* git-style "Diff View" and the per-line highlighting in the Before / After
|
|
1232
|
+
* panels. Snapshots rendered as pretty JSON stay small, so the O(n·m) table is
|
|
1233
|
+
* comfortably cheap.
|
|
1234
|
+
*/
|
|
316
1235
|
function computeLineDiff(beforeText, afterText) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
1236
|
+
const a = beforeText.split("\n");
|
|
1237
|
+
const b = afterText.split("\n");
|
|
1238
|
+
const n = a.length;
|
|
1239
|
+
const m = b.length;
|
|
1240
|
+
const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
1241
|
+
for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
1242
|
+
const ops = [];
|
|
1243
|
+
let i = 0;
|
|
1244
|
+
let j = 0;
|
|
1245
|
+
while (i < n && j < m) if (a[i] === b[j]) {
|
|
1246
|
+
ops.push({
|
|
1247
|
+
kind: "common",
|
|
1248
|
+
text: a[i]
|
|
1249
|
+
});
|
|
1250
|
+
i++;
|
|
1251
|
+
j++;
|
|
1252
|
+
} else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
1253
|
+
ops.push({
|
|
1254
|
+
kind: "removed",
|
|
1255
|
+
text: a[i]
|
|
1256
|
+
});
|
|
1257
|
+
i++;
|
|
1258
|
+
} else {
|
|
1259
|
+
ops.push({
|
|
1260
|
+
kind: "added",
|
|
1261
|
+
text: b[j]
|
|
1262
|
+
});
|
|
1263
|
+
j++;
|
|
1264
|
+
}
|
|
1265
|
+
while (i < n) ops.push({
|
|
1266
|
+
kind: "removed",
|
|
1267
|
+
text: a[i++]
|
|
1268
|
+
});
|
|
1269
|
+
while (j < m) ops.push({
|
|
1270
|
+
kind: "added",
|
|
1271
|
+
text: b[j++]
|
|
1272
|
+
});
|
|
1273
|
+
return ops;
|
|
350
1274
|
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Reorder each contiguous changed hunk so additions sit above removals when
|
|
1277
|
+
* `latestFirst` is set (the new state on top). Common lines anchor the hunks in
|
|
1278
|
+
* place; only the −/+ lines within a run are regrouped, so the surrounding
|
|
1279
|
+
* context never moves. With `latestFirst` off the ops are returned untouched.
|
|
1280
|
+
*/
|
|
351
1281
|
function orderLineDiffOps(ops, latestFirst) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return out;
|
|
1282
|
+
if (!latestFirst) return ops;
|
|
1283
|
+
const out = [];
|
|
1284
|
+
let i = 0;
|
|
1285
|
+
while (i < ops.length) {
|
|
1286
|
+
if (ops[i].kind === "common") {
|
|
1287
|
+
out.push(ops[i]);
|
|
1288
|
+
i++;
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
const added = [];
|
|
1292
|
+
const removed = [];
|
|
1293
|
+
while (i < ops.length && ops[i].kind !== "common") {
|
|
1294
|
+
(ops[i].kind === "added" ? added : removed).push(ops[i]);
|
|
1295
|
+
i++;
|
|
1296
|
+
}
|
|
1297
|
+
out.push(...added, ...removed);
|
|
1298
|
+
}
|
|
1299
|
+
return out;
|
|
371
1300
|
}
|
|
1301
|
+
/**
|
|
1302
|
+
* A structure-aware "address" diff: the JSON tree pruned down to just the
|
|
1303
|
+
* branches that actually changed. Parents of a change keep their brackets so the
|
|
1304
|
+
* path stays legible, and each run of untouched siblings collapses into a single
|
|
1305
|
+
* `… N unchanged …` placeholder (which, for arrays, naturally reports the counts
|
|
1306
|
+
* before and after a change). `side` picks which document we're rebuilding —
|
|
1307
|
+
* `"unified"` shows removed (−) and added (+) together, while `"before"` /
|
|
1308
|
+
* `"after"` show only the lines that belong to that one snapshot.
|
|
1309
|
+
*/
|
|
372
1310
|
function computeCompressedDiff(before, after, side, latestFirst = false) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
1311
|
+
const lines = [];
|
|
1312
|
+
emitChangedNode(lines, 0, null, before, after, side, latestFirst);
|
|
1313
|
+
return lines;
|
|
376
1314
|
}
|
|
377
1315
|
function keyPrefix(keyLabel) {
|
|
378
|
-
|
|
1316
|
+
return keyLabel != null ? `${keyLabel}: ` : "";
|
|
379
1317
|
}
|
|
1318
|
+
/** Emit a node already known to differ between the two sides. */
|
|
380
1319
|
function emitChangedNode(lines, depth, keyLabel, before, after, side, latestFirst) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
} else {
|
|
397
|
-
removed();
|
|
398
|
-
added();
|
|
399
|
-
}
|
|
1320
|
+
if (isPlainObjectOrArray(before) && isPlainObjectOrArray(after) && Array.isArray(before) === Array.isArray(after)) {
|
|
1321
|
+
emitContainer(lines, depth, keyLabel, before, after, side, latestFirst);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
const removed = () => pushValueLines(lines, depth, keyLabel, before, "−", "removed");
|
|
1325
|
+
const added = () => pushValueLines(lines, depth, keyLabel, after, "+", "added");
|
|
1326
|
+
if (side === "before") removed();
|
|
1327
|
+
else if (side === "after") added();
|
|
1328
|
+
else if (latestFirst) {
|
|
1329
|
+
added();
|
|
1330
|
+
removed();
|
|
1331
|
+
} else {
|
|
1332
|
+
removed();
|
|
1333
|
+
added();
|
|
1334
|
+
}
|
|
400
1335
|
}
|
|
401
1336
|
function emitContainer(lines, depth, keyLabel, before, after, side, latestFirst) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
1337
|
+
const isArray = Array.isArray(before);
|
|
1338
|
+
lines.push({
|
|
1339
|
+
depth,
|
|
1340
|
+
sign: " ",
|
|
1341
|
+
tone: "common",
|
|
1342
|
+
text: `${keyPrefix(keyLabel)}${isArray ? "[" : "{"}`
|
|
1343
|
+
});
|
|
1344
|
+
let unchanged = 0;
|
|
1345
|
+
const flush = () => {
|
|
1346
|
+
if (unchanged > 0) {
|
|
1347
|
+
lines.push({
|
|
1348
|
+
depth: depth + 1,
|
|
1349
|
+
sign: " ",
|
|
1350
|
+
tone: "placeholder",
|
|
1351
|
+
text: placeholderText(unchanged, isArray)
|
|
1352
|
+
});
|
|
1353
|
+
unchanged = 0;
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
for (const child of childEntries(before, after, isArray)) {
|
|
1357
|
+
if (child.kind === "added" && side === "before") continue;
|
|
1358
|
+
if (child.kind === "removed" && side === "after") continue;
|
|
1359
|
+
if (child.kind === "unchanged") {
|
|
1360
|
+
unchanged++;
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
flush();
|
|
1364
|
+
if (child.kind === "changed") emitChangedNode(lines, depth + 1, child.keyLabel, child.before, child.after, side, latestFirst);
|
|
1365
|
+
else if (child.kind === "added") pushValueLines(lines, depth + 1, child.keyLabel, child.after, "+", "added");
|
|
1366
|
+
else pushValueLines(lines, depth + 1, child.keyLabel, child.before, "−", "removed");
|
|
1367
|
+
}
|
|
1368
|
+
flush();
|
|
1369
|
+
lines.push({
|
|
1370
|
+
depth,
|
|
1371
|
+
sign: " ",
|
|
1372
|
+
tone: "common",
|
|
1373
|
+
text: isArray ? "]" : "}"
|
|
1374
|
+
});
|
|
436
1375
|
}
|
|
1376
|
+
/** Classify each direct child as unchanged / changed / added / removed. */
|
|
437
1377
|
function childEntries(before, after, isArray) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
1378
|
+
if (isArray) {
|
|
1379
|
+
const b = before;
|
|
1380
|
+
const a = after;
|
|
1381
|
+
const len = Math.max(b.length, a.length);
|
|
1382
|
+
const out = [];
|
|
1383
|
+
for (let i = 0; i < len; i++) {
|
|
1384
|
+
const inB = i < b.length;
|
|
1385
|
+
const inA = i < a.length;
|
|
1386
|
+
if (inB && inA) {
|
|
1387
|
+
const kind = Object.is(b[i], a[i]) ? "unchanged" : "changed";
|
|
1388
|
+
out.push({
|
|
1389
|
+
keyLabel: null,
|
|
1390
|
+
kind,
|
|
1391
|
+
before: b[i],
|
|
1392
|
+
after: a[i]
|
|
1393
|
+
});
|
|
1394
|
+
} else if (inB) out.push({
|
|
1395
|
+
keyLabel: null,
|
|
1396
|
+
kind: "removed",
|
|
1397
|
+
before: b[i],
|
|
1398
|
+
after: void 0
|
|
1399
|
+
});
|
|
1400
|
+
else out.push({
|
|
1401
|
+
keyLabel: null,
|
|
1402
|
+
kind: "added",
|
|
1403
|
+
before: void 0,
|
|
1404
|
+
after: a[i]
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
return out;
|
|
1408
|
+
}
|
|
1409
|
+
const b = before;
|
|
1410
|
+
const a = after;
|
|
1411
|
+
return [...new Set([...Object.keys(b), ...Object.keys(a)])].map((key) => {
|
|
1412
|
+
const keyLabel = JSON.stringify(key);
|
|
1413
|
+
const inB = key in b;
|
|
1414
|
+
const inA = key in a;
|
|
1415
|
+
if (inB && inA) return {
|
|
1416
|
+
keyLabel,
|
|
1417
|
+
kind: Object.is(b[key], a[key]) ? "unchanged" : "changed",
|
|
1418
|
+
before: b[key],
|
|
1419
|
+
after: a[key]
|
|
1420
|
+
};
|
|
1421
|
+
if (inB) return {
|
|
1422
|
+
keyLabel,
|
|
1423
|
+
kind: "removed",
|
|
1424
|
+
before: b[key],
|
|
1425
|
+
after: void 0
|
|
1426
|
+
};
|
|
1427
|
+
return {
|
|
1428
|
+
keyLabel,
|
|
1429
|
+
kind: "added",
|
|
1430
|
+
before: void 0,
|
|
1431
|
+
after: a[key]
|
|
1432
|
+
};
|
|
1433
|
+
});
|
|
472
1434
|
}
|
|
473
1435
|
function placeholderText(count, isArray) {
|
|
474
|
-
|
|
475
|
-
return `… ${count} unchanged ${noun}`;
|
|
1436
|
+
return `… ${count} unchanged ${isArray ? count === 1 ? "item" : "items" : count === 1 ? "property" : "properties"}`;
|
|
476
1437
|
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Render a value as pretty JSON and append it as one tone'd block, re-basing the
|
|
1440
|
+
* stringifier's own 2-space indentation onto `baseDepth` and prefixing the key
|
|
1441
|
+
* (if any) onto the first line.
|
|
1442
|
+
*/
|
|
477
1443
|
function pushValueLines(lines, baseDepth, keyLabel, value, sign, tone) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1444
|
+
const prefix = keyPrefix(keyLabel);
|
|
1445
|
+
safeStringify(value, 2).split("\n").forEach((line, idx) => {
|
|
1446
|
+
const leading = /^ */.exec(line)[0].length;
|
|
1447
|
+
const content = line.slice(leading);
|
|
1448
|
+
const text = idx === 0 ? `${prefix}${content}` : content;
|
|
1449
|
+
lines.push({
|
|
1450
|
+
depth: baseDepth + leading / 2,
|
|
1451
|
+
sign,
|
|
1452
|
+
tone,
|
|
1453
|
+
text
|
|
1454
|
+
});
|
|
1455
|
+
});
|
|
487
1456
|
}
|
|
488
1457
|
function isScalar(value) {
|
|
489
|
-
|
|
1458
|
+
return value == null || typeof value !== "object";
|
|
490
1459
|
}
|
|
491
1460
|
function formatScalar(value) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
return String(value);
|
|
1461
|
+
if (typeof value === "string") return `"${value.length > 24 ? `${value.slice(0, 24)}…` : value}"`;
|
|
1462
|
+
return String(value);
|
|
495
1463
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1464
|
+
//#endregion
|
|
1465
|
+
//#region src/devtools/browser/components/DiffView.tsx
|
|
1466
|
+
const ADDED_BG$1 = "rgba(163, 230, 53, 0.08)";
|
|
1467
|
+
const REMOVED_BG$1 = "rgba(255, 92, 92, 0.08)";
|
|
1468
|
+
const INLINE_MAX = 40;
|
|
1469
|
+
/**
|
|
1470
|
+
* Test-framework-style diff: only the paths that changed. Changed paths are
|
|
1471
|
+
* grouped into a hierarchy by their shared parents — `countHistory.0` and
|
|
1472
|
+
* `countHistory.1` nest under a single `countHistory` node, each shown by its
|
|
1473
|
+
* direct key only. Single-child chains collapse to a dotted path (`a.b.c`) so
|
|
1474
|
+
* lone deep changes stay on one line. Short values render inline with the key
|
|
1475
|
+
* (`count: 0 → 1`); long ones keep the removed/added line block.
|
|
1476
|
+
*/
|
|
1477
|
+
function DiffView({ before, after, latestFirst = false }) {
|
|
1478
|
+
const entries = computeDiff(before, after);
|
|
1479
|
+
if (entries.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1480
|
+
style: {
|
|
1481
|
+
padding: "10px",
|
|
1482
|
+
borderRadius: "4px",
|
|
1483
|
+
background: DEVTOOL_SECTION_STRING_BACKGROUND,
|
|
1484
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
1485
|
+
fontSize: "11px",
|
|
1486
|
+
fontStyle: "italic"
|
|
1487
|
+
},
|
|
1488
|
+
children: "No differences — before and after are structurally equal."
|
|
1489
|
+
});
|
|
1490
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1491
|
+
style: {
|
|
1492
|
+
display: "flex",
|
|
1493
|
+
flexDirection: "column",
|
|
1494
|
+
gap: "6px"
|
|
1495
|
+
},
|
|
1496
|
+
children: [...buildTree(entries).children.values()].map(collapseChains).map((node) => /* @__PURE__ */ jsx(DiffNode, {
|
|
1497
|
+
node,
|
|
1498
|
+
latestFirst
|
|
1499
|
+
}, node.key))
|
|
1500
|
+
});
|
|
529
1501
|
}
|
|
1502
|
+
/** Group flat diff entries into a tree keyed by their path segments. */
|
|
530
1503
|
function buildTree(entries) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1504
|
+
const root = {
|
|
1505
|
+
key: "",
|
|
1506
|
+
children: /* @__PURE__ */ new Map()
|
|
1507
|
+
};
|
|
1508
|
+
for (const entry of entries) {
|
|
1509
|
+
const segments = entry.segments.length > 0 ? entry.segments : ["(root)"];
|
|
1510
|
+
let node = root;
|
|
1511
|
+
for (const seg of segments) {
|
|
1512
|
+
const key = String(seg);
|
|
1513
|
+
let child = node.children.get(key);
|
|
1514
|
+
if (child == null) {
|
|
1515
|
+
child = {
|
|
1516
|
+
key,
|
|
1517
|
+
children: /* @__PURE__ */ new Map()
|
|
1518
|
+
};
|
|
1519
|
+
node.children.set(key, child);
|
|
1520
|
+
}
|
|
1521
|
+
node = child;
|
|
1522
|
+
}
|
|
1523
|
+
node.entry = entry;
|
|
1524
|
+
}
|
|
1525
|
+
return root;
|
|
547
1526
|
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Collapse single-child chains into one dotted key so a lone deep change reads
|
|
1529
|
+
* as `a.b.c` rather than three nested headers; branches with siblings keep their
|
|
1530
|
+
* hierarchy.
|
|
1531
|
+
*/
|
|
548
1532
|
function collapseChains(node) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1533
|
+
const children = /* @__PURE__ */ new Map();
|
|
1534
|
+
for (const child of node.children.values()) {
|
|
1535
|
+
const collapsed = collapseChains(child);
|
|
1536
|
+
children.set(collapsed.key, collapsed);
|
|
1537
|
+
}
|
|
1538
|
+
let current = {
|
|
1539
|
+
key: node.key,
|
|
1540
|
+
children,
|
|
1541
|
+
entry: node.entry
|
|
1542
|
+
};
|
|
1543
|
+
while (current.entry == null && current.children.size === 1) {
|
|
1544
|
+
const only = [...current.children.values()][0];
|
|
1545
|
+
current = {
|
|
1546
|
+
key: `${current.key}.${only.key}`,
|
|
1547
|
+
children: only.children,
|
|
1548
|
+
entry: only.entry
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
return current;
|
|
560
1552
|
}
|
|
561
1553
|
function DiffNode({ node, latestFirst }) {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
]
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
function DiffLeaf({
|
|
594
|
-
label,
|
|
595
|
-
entry,
|
|
596
|
-
latestFirst
|
|
597
|
-
}) {
|
|
598
|
-
const showRemoved = entry.kind === "removed" || entry.kind === "changed";
|
|
599
|
-
const showAdded = entry.kind === "added" || entry.kind === "changed";
|
|
600
|
-
const removed = showRemoved && /* @__PURE__ */ jsx(InlineValue, {
|
|
601
|
-
sign: "−",
|
|
602
|
-
color: DEVTOOL_COLOR_SEMANTIC_ERROR,
|
|
603
|
-
background: REMOVED_BG,
|
|
604
|
-
value: entry.before
|
|
605
|
-
});
|
|
606
|
-
const added = showAdded && /* @__PURE__ */ jsx(InlineValue, {
|
|
607
|
-
sign: "+",
|
|
608
|
-
color: DEVTOOL_COLOR_SEMANTIC_SUCCESS,
|
|
609
|
-
background: ADDED_BG,
|
|
610
|
-
value: entry.after
|
|
611
|
-
});
|
|
612
|
-
const removedLine = showRemoved && /* @__PURE__ */ jsx(DiffLine, {
|
|
613
|
-
sign: "−",
|
|
614
|
-
color: DEVTOOL_COLOR_SEMANTIC_ERROR,
|
|
615
|
-
background: REMOVED_BG,
|
|
616
|
-
value: entry.before
|
|
617
|
-
});
|
|
618
|
-
const addedLine = showAdded && /* @__PURE__ */ jsx(DiffLine, {
|
|
619
|
-
sign: "+",
|
|
620
|
-
color: DEVTOOL_COLOR_SEMANTIC_SUCCESS,
|
|
621
|
-
background: ADDED_BG,
|
|
622
|
-
value: entry.after
|
|
623
|
-
});
|
|
624
|
-
if (isInlineLeaf(entry)) {
|
|
625
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
626
|
-
style: { display: "flex", flexWrap: "wrap", alignItems: "baseline", gap: "6px" },
|
|
627
|
-
children: [
|
|
628
|
-
/* @__PURE__ */ jsxs("span", {
|
|
629
|
-
style: { color: DEVTOOL_JSON_KEY, fontFamily: MONO_FONT, fontSize: "11px" },
|
|
630
|
-
children: [
|
|
631
|
-
label,
|
|
632
|
-
":"
|
|
633
|
-
]
|
|
634
|
-
}),
|
|
635
|
-
latestFirst ? added : removed,
|
|
636
|
-
entry.kind === "changed" && /* @__PURE__ */ jsx("span", {
|
|
637
|
-
style: { color: DEVTOOL_COLOR_TEXT_MUTED },
|
|
638
|
-
children: latestFirst ? "←" : "→"
|
|
639
|
-
}),
|
|
640
|
-
latestFirst ? removed : added
|
|
641
|
-
]
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
645
|
-
style: {
|
|
646
|
-
borderRadius: "4px",
|
|
647
|
-
overflow: "hidden",
|
|
648
|
-
border: `1px solid ${DEVTOOL_SECTION_STRING_BACKGROUND}`
|
|
649
|
-
},
|
|
650
|
-
children: [
|
|
651
|
-
/* @__PURE__ */ jsx("div", {
|
|
652
|
-
style: {
|
|
653
|
-
padding: "3px 8px",
|
|
654
|
-
background: DEVTOOL_SECTION_STRING_BACKGROUND,
|
|
655
|
-
color: DEVTOOL_JSON_KEY,
|
|
656
|
-
fontFamily: MONO_FONT,
|
|
657
|
-
fontSize: "10px"
|
|
658
|
-
},
|
|
659
|
-
children: label
|
|
660
|
-
}),
|
|
661
|
-
latestFirst ? addedLine : removedLine,
|
|
662
|
-
latestFirst ? removedLine : addedLine
|
|
663
|
-
]
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
function InlineValue({
|
|
667
|
-
sign,
|
|
668
|
-
color,
|
|
669
|
-
background,
|
|
670
|
-
value
|
|
671
|
-
}) {
|
|
672
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
673
|
-
style: {
|
|
674
|
-
display: "inline-flex",
|
|
675
|
-
alignItems: "baseline",
|
|
676
|
-
gap: "4px",
|
|
677
|
-
padding: "1px 6px",
|
|
678
|
-
borderRadius: "3px",
|
|
679
|
-
background,
|
|
680
|
-
fontFamily: MONO_FONT,
|
|
681
|
-
fontSize: "11px",
|
|
682
|
-
whiteSpace: "pre-wrap",
|
|
683
|
-
wordBreak: "break-word"
|
|
684
|
-
},
|
|
685
|
-
children: [
|
|
686
|
-
/* @__PURE__ */ jsx("span", {
|
|
687
|
-
style: { color, fontWeight: 700, userSelect: "none" },
|
|
688
|
-
children: sign
|
|
689
|
-
}),
|
|
690
|
-
/* @__PURE__ */ jsx("span", {
|
|
691
|
-
children: renderColoredJson(compactStringify(value))
|
|
692
|
-
})
|
|
693
|
-
]
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
function DiffLine({
|
|
697
|
-
sign,
|
|
698
|
-
color,
|
|
699
|
-
background,
|
|
700
|
-
value
|
|
701
|
-
}) {
|
|
702
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
703
|
-
style: { display: "flex", gap: "6px", padding: "3px 8px", background },
|
|
704
|
-
children: [
|
|
705
|
-
/* @__PURE__ */ jsx("span", {
|
|
706
|
-
style: { color, fontWeight: 700, flexShrink: 0, userSelect: "none" },
|
|
707
|
-
children: sign
|
|
708
|
-
}),
|
|
709
|
-
/* @__PURE__ */ jsx("span", {
|
|
710
|
-
style: {
|
|
711
|
-
flex: 1,
|
|
712
|
-
minWidth: 0,
|
|
713
|
-
fontFamily: MONO_FONT,
|
|
714
|
-
fontSize: "11px",
|
|
715
|
-
lineHeight: 1.5,
|
|
716
|
-
whiteSpace: "pre-wrap",
|
|
717
|
-
wordBreak: "break-word"
|
|
718
|
-
},
|
|
719
|
-
children: renderColoredJson(safeStringify2(value, 2))
|
|
720
|
-
})
|
|
721
|
-
]
|
|
722
|
-
});
|
|
1554
|
+
if (node.entry != null && node.children.size === 0) return /* @__PURE__ */ jsx(DiffLeaf, {
|
|
1555
|
+
label: node.key,
|
|
1556
|
+
entry: node.entry,
|
|
1557
|
+
latestFirst
|
|
1558
|
+
});
|
|
1559
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
1560
|
+
style: {
|
|
1561
|
+
color: DEVTOOL_JSON_KEY,
|
|
1562
|
+
fontFamily: MONO_FONT,
|
|
1563
|
+
fontSize: "10px",
|
|
1564
|
+
fontWeight: 600
|
|
1565
|
+
},
|
|
1566
|
+
children: node.key
|
|
1567
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1568
|
+
style: {
|
|
1569
|
+
marginTop: "4px",
|
|
1570
|
+
marginLeft: "5px",
|
|
1571
|
+
paddingLeft: "8px",
|
|
1572
|
+
borderLeft: `1px solid ${DEVTOOL_SECTION_STRING_BACKGROUND}`,
|
|
1573
|
+
display: "flex",
|
|
1574
|
+
flexDirection: "column",
|
|
1575
|
+
gap: "5px"
|
|
1576
|
+
},
|
|
1577
|
+
children: [...node.children.values()].map((child) => /* @__PURE__ */ jsx(DiffNode, {
|
|
1578
|
+
node: child,
|
|
1579
|
+
latestFirst
|
|
1580
|
+
}, child.key))
|
|
1581
|
+
})] });
|
|
723
1582
|
}
|
|
1583
|
+
function DiffLeaf({ label, entry, latestFirst }) {
|
|
1584
|
+
const showRemoved = entry.kind === "removed" || entry.kind === "changed";
|
|
1585
|
+
const showAdded = entry.kind === "added" || entry.kind === "changed";
|
|
1586
|
+
const removed = showRemoved && /* @__PURE__ */ jsx(InlineValue, {
|
|
1587
|
+
sign: "−",
|
|
1588
|
+
color: "#FF5C5C",
|
|
1589
|
+
background: REMOVED_BG$1,
|
|
1590
|
+
value: entry.before
|
|
1591
|
+
});
|
|
1592
|
+
const added = showAdded && /* @__PURE__ */ jsx(InlineValue, {
|
|
1593
|
+
sign: "+",
|
|
1594
|
+
color: "#A3E635",
|
|
1595
|
+
background: ADDED_BG$1,
|
|
1596
|
+
value: entry.after
|
|
1597
|
+
});
|
|
1598
|
+
const removedLine = showRemoved && /* @__PURE__ */ jsx(DiffLine, {
|
|
1599
|
+
sign: "−",
|
|
1600
|
+
color: "#FF5C5C",
|
|
1601
|
+
background: REMOVED_BG$1,
|
|
1602
|
+
value: entry.before
|
|
1603
|
+
});
|
|
1604
|
+
const addedLine = showAdded && /* @__PURE__ */ jsx(DiffLine, {
|
|
1605
|
+
sign: "+",
|
|
1606
|
+
color: "#A3E635",
|
|
1607
|
+
background: ADDED_BG$1,
|
|
1608
|
+
value: entry.after
|
|
1609
|
+
});
|
|
1610
|
+
if (isInlineLeaf(entry)) return /* @__PURE__ */ jsxs("div", {
|
|
1611
|
+
style: {
|
|
1612
|
+
display: "flex",
|
|
1613
|
+
flexWrap: "wrap",
|
|
1614
|
+
alignItems: "baseline",
|
|
1615
|
+
gap: "6px"
|
|
1616
|
+
},
|
|
1617
|
+
children: [
|
|
1618
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1619
|
+
style: {
|
|
1620
|
+
color: DEVTOOL_JSON_KEY,
|
|
1621
|
+
fontFamily: MONO_FONT,
|
|
1622
|
+
fontSize: "11px"
|
|
1623
|
+
},
|
|
1624
|
+
children: [label, ":"]
|
|
1625
|
+
}),
|
|
1626
|
+
latestFirst ? added : removed,
|
|
1627
|
+
entry.kind === "changed" && /* @__PURE__ */ jsx("span", {
|
|
1628
|
+
style: { color: "#64748b" },
|
|
1629
|
+
children: latestFirst ? "←" : "→"
|
|
1630
|
+
}),
|
|
1631
|
+
latestFirst ? removed : added
|
|
1632
|
+
]
|
|
1633
|
+
});
|
|
1634
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1635
|
+
style: {
|
|
1636
|
+
borderRadius: "4px",
|
|
1637
|
+
overflow: "hidden",
|
|
1638
|
+
border: `1px solid ${DEVTOOL_SECTION_STRING_BACKGROUND}`
|
|
1639
|
+
},
|
|
1640
|
+
children: [
|
|
1641
|
+
/* @__PURE__ */ jsx("div", {
|
|
1642
|
+
style: {
|
|
1643
|
+
padding: "3px 8px",
|
|
1644
|
+
background: DEVTOOL_SECTION_STRING_BACKGROUND,
|
|
1645
|
+
color: DEVTOOL_JSON_KEY,
|
|
1646
|
+
fontFamily: MONO_FONT,
|
|
1647
|
+
fontSize: "10px"
|
|
1648
|
+
},
|
|
1649
|
+
children: label
|
|
1650
|
+
}),
|
|
1651
|
+
latestFirst ? addedLine : removedLine,
|
|
1652
|
+
latestFirst ? removedLine : addedLine
|
|
1653
|
+
]
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
function InlineValue({ sign, color, background, value }) {
|
|
1657
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1658
|
+
style: {
|
|
1659
|
+
display: "inline-flex",
|
|
1660
|
+
alignItems: "baseline",
|
|
1661
|
+
gap: "4px",
|
|
1662
|
+
padding: "1px 6px",
|
|
1663
|
+
borderRadius: "3px",
|
|
1664
|
+
background,
|
|
1665
|
+
fontFamily: MONO_FONT,
|
|
1666
|
+
fontSize: "11px",
|
|
1667
|
+
whiteSpace: "pre-wrap",
|
|
1668
|
+
wordBreak: "break-word"
|
|
1669
|
+
},
|
|
1670
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1671
|
+
style: {
|
|
1672
|
+
color,
|
|
1673
|
+
fontWeight: 700,
|
|
1674
|
+
userSelect: "none"
|
|
1675
|
+
},
|
|
1676
|
+
children: sign
|
|
1677
|
+
}), /* @__PURE__ */ jsx("span", { children: renderColoredJson(compactStringify(value)) })]
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
function DiffLine({ sign, color, background, value }) {
|
|
1681
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1682
|
+
style: {
|
|
1683
|
+
display: "flex",
|
|
1684
|
+
gap: "6px",
|
|
1685
|
+
padding: "3px 8px",
|
|
1686
|
+
background
|
|
1687
|
+
},
|
|
1688
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1689
|
+
style: {
|
|
1690
|
+
color,
|
|
1691
|
+
fontWeight: 700,
|
|
1692
|
+
flexShrink: 0,
|
|
1693
|
+
userSelect: "none"
|
|
1694
|
+
},
|
|
1695
|
+
children: sign
|
|
1696
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1697
|
+
style: {
|
|
1698
|
+
flex: 1,
|
|
1699
|
+
minWidth: 0,
|
|
1700
|
+
fontFamily: MONO_FONT,
|
|
1701
|
+
fontSize: "11px",
|
|
1702
|
+
lineHeight: 1.5,
|
|
1703
|
+
whiteSpace: "pre-wrap",
|
|
1704
|
+
wordBreak: "break-word"
|
|
1705
|
+
},
|
|
1706
|
+
children: renderColoredJson(safeStringify(value, 2))
|
|
1707
|
+
})]
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
/** A leaf renders inline when every side it shows is short and single-line. */
|
|
724
1711
|
function isInlineLeaf(entry) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
sides.push(compactStringify(entry.after));
|
|
730
|
-
return sides.every((s) => s.length <= INLINE_MAX && !s.includes(`
|
|
731
|
-
`));
|
|
1712
|
+
const sides = [];
|
|
1713
|
+
if (entry.kind !== "added") sides.push(compactStringify(entry.before));
|
|
1714
|
+
if (entry.kind !== "removed") sides.push(compactStringify(entry.after));
|
|
1715
|
+
return sides.every((s) => s.length <= INLINE_MAX && !s.includes("\n"));
|
|
732
1716
|
}
|
|
1717
|
+
/** Single-line JSON for inline rendering (`undefined` has no JSON form). */
|
|
733
1718
|
function compactStringify(value) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}
|
|
1719
|
+
if (value === void 0) return "undefined";
|
|
1720
|
+
try {
|
|
1721
|
+
return JSON.stringify(value) ?? "undefined";
|
|
1722
|
+
} catch {
|
|
1723
|
+
return String(value);
|
|
1724
|
+
}
|
|
741
1725
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
overflowX: "auto"
|
|
1726
|
+
//#endregion
|
|
1727
|
+
//#region src/devtools/browser/components/JsonDiffView.tsx
|
|
1728
|
+
const ADDED_BG = "rgba(163, 230, 53, 0.13)";
|
|
1729
|
+
const REMOVED_BG = "rgba(255, 92, 92, 0.13)";
|
|
1730
|
+
const SURFACE_STYLE = {
|
|
1731
|
+
margin: 0,
|
|
1732
|
+
padding: "8px 0",
|
|
1733
|
+
borderRadius: "4px",
|
|
1734
|
+
fontSize: "11px",
|
|
1735
|
+
lineHeight: 1.5,
|
|
1736
|
+
fontFamily: MONO_FONT,
|
|
1737
|
+
background: DEVTOOL_SECTION_STRING_BACKGROUND,
|
|
1738
|
+
overflowX: "auto"
|
|
756
1739
|
};
|
|
757
1740
|
function emptyNotice(text) {
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1741
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1742
|
+
style: {
|
|
1743
|
+
padding: "10px",
|
|
1744
|
+
borderRadius: "4px",
|
|
1745
|
+
background: DEVTOOL_SECTION_STRING_BACKGROUND,
|
|
1746
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
1747
|
+
fontSize: "11px",
|
|
1748
|
+
fontStyle: "italic"
|
|
1749
|
+
},
|
|
1750
|
+
children: text
|
|
1751
|
+
});
|
|
769
1752
|
}
|
|
1753
|
+
/**
|
|
1754
|
+
* A single rendered diff line: a fixed sign gutter (`+` / `−` / blank) followed
|
|
1755
|
+
* by the syntax-coloured JSON text, all on a highlighted background.
|
|
1756
|
+
*/
|
|
770
1757
|
function Line({ sign, color, background, text }) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
})
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1758
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1759
|
+
style: {
|
|
1760
|
+
display: "flex",
|
|
1761
|
+
background,
|
|
1762
|
+
padding: "0 10px",
|
|
1763
|
+
whiteSpace: "pre"
|
|
1764
|
+
},
|
|
1765
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1766
|
+
style: {
|
|
1767
|
+
width: "12px",
|
|
1768
|
+
flexShrink: 0,
|
|
1769
|
+
color,
|
|
1770
|
+
fontWeight: 700,
|
|
1771
|
+
userSelect: "none"
|
|
1772
|
+
},
|
|
1773
|
+
children: sign
|
|
1774
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1775
|
+
style: {
|
|
1776
|
+
flex: 1,
|
|
1777
|
+
minWidth: 0
|
|
1778
|
+
},
|
|
1779
|
+
children: renderColoredJson(text)
|
|
1780
|
+
})]
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Unified, git-style diff of the entire state snapshot. Both sides are rendered
|
|
1785
|
+
* as pretty JSON and line-diffed, so unchanged structure stays visible for
|
|
1786
|
+
* context while removed lines (red, `−`) and added lines (green, `+`) call out
|
|
1787
|
+
* exactly which sections of the whole state moved.
|
|
1788
|
+
*/
|
|
1789
|
+
function JsonDiffView({ before, after, compress = false, latestFirst = false }) {
|
|
1790
|
+
if (compress) return /* @__PURE__ */ jsx(CompressedDiffView, {
|
|
1791
|
+
before,
|
|
1792
|
+
after,
|
|
1793
|
+
side: "unified",
|
|
1794
|
+
latestFirst
|
|
1795
|
+
});
|
|
1796
|
+
const ops = orderLineDiffOps(computeLineDiff(safeStringify(before, 2), safeStringify(after, 2)), latestFirst);
|
|
1797
|
+
if (!ops.some((op) => op.kind !== "common")) return emptyNotice("No differences — before and after are structurally equal.");
|
|
1798
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
1799
|
+
style: SURFACE_STYLE,
|
|
1800
|
+
children: ops.map((op, i) => /* @__PURE__ */ jsx(Line, {
|
|
1801
|
+
sign: op.kind === "removed" ? "−" : op.kind === "added" ? "+" : " ",
|
|
1802
|
+
color: op.kind === "removed" ? DEVTOOL_COLOR_SEMANTIC_ERROR : DEVTOOL_COLOR_SEMANTIC_SUCCESS,
|
|
1803
|
+
background: op.kind === "removed" ? REMOVED_BG : op.kind === "added" ? ADDED_BG : "transparent",
|
|
1804
|
+
text: op.text
|
|
1805
|
+
}, i))
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* The structure-aware "address" view: the JSON tree pruned to just the changed
|
|
1810
|
+
* branches, with runs of untouched siblings collapsed into `… N unchanged …`
|
|
1811
|
+
* placeholders. Shared by all three diff tabs via `side`.
|
|
1812
|
+
*/
|
|
1813
|
+
function CompressedDiffView({ before, after, side, latestFirst = false }) {
|
|
1814
|
+
const lines = computeCompressedDiff(before, after, side, latestFirst);
|
|
1815
|
+
if (!lines.some((line) => line.tone === "added" || line.tone === "removed")) return emptyNotice("No differences — before and after are structurally equal.");
|
|
1816
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
1817
|
+
style: SURFACE_STYLE,
|
|
1818
|
+
children: lines.map((line, i) => /* @__PURE__ */ jsx(CompressedLine, { line }, i))
|
|
1819
|
+
});
|
|
829
1820
|
}
|
|
830
1821
|
function CompressedLine({ line }) {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
const ops = computeLineDiff(safeStringify2(before, 2), safeStringify2(after, 2));
|
|
878
|
-
const dropKind = side === "before" ? "added" : "removed";
|
|
879
|
-
const highlightKind = side === "before" ? "removed" : "added";
|
|
880
|
-
const background = side === "before" ? REMOVED_BG2 : ADDED_BG2;
|
|
881
|
-
const color = side === "before" ? DEVTOOL_COLOR_SEMANTIC_ERROR : DEVTOOL_COLOR_SEMANTIC_SUCCESS;
|
|
882
|
-
const visible = ops.filter((op) => op.kind !== dropKind);
|
|
883
|
-
return /* @__PURE__ */ jsx2("pre", {
|
|
884
|
-
style: SURFACE_STYLE,
|
|
885
|
-
children: visible.map((op, i) => {
|
|
886
|
-
const changed = op.kind === highlightKind;
|
|
887
|
-
return /* @__PURE__ */ jsx2(Line, {
|
|
888
|
-
sign: changed ? side === "before" ? "−" : "+" : " ",
|
|
889
|
-
color,
|
|
890
|
-
background: changed ? background : "transparent",
|
|
891
|
-
text: op.text
|
|
892
|
-
}, i);
|
|
893
|
-
})
|
|
894
|
-
});
|
|
1822
|
+
const indent = " ".repeat(line.depth);
|
|
1823
|
+
if (line.tone === "placeholder") return /* @__PURE__ */ jsxs("div", {
|
|
1824
|
+
style: {
|
|
1825
|
+
display: "flex",
|
|
1826
|
+
padding: "0 10px",
|
|
1827
|
+
whiteSpace: "pre"
|
|
1828
|
+
},
|
|
1829
|
+
children: [/* @__PURE__ */ jsx("span", { style: {
|
|
1830
|
+
width: "12px",
|
|
1831
|
+
flexShrink: 0
|
|
1832
|
+
} }), /* @__PURE__ */ jsxs("span", {
|
|
1833
|
+
style: {
|
|
1834
|
+
flex: 1,
|
|
1835
|
+
minWidth: 0,
|
|
1836
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
1837
|
+
fontStyle: "italic"
|
|
1838
|
+
},
|
|
1839
|
+
children: [indent, line.text]
|
|
1840
|
+
})]
|
|
1841
|
+
});
|
|
1842
|
+
const background = line.tone === "removed" ? REMOVED_BG : line.tone === "added" ? ADDED_BG : "transparent";
|
|
1843
|
+
const color = line.tone === "removed" ? DEVTOOL_COLOR_SEMANTIC_ERROR : DEVTOOL_COLOR_SEMANTIC_SUCCESS;
|
|
1844
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1845
|
+
style: {
|
|
1846
|
+
display: "flex",
|
|
1847
|
+
background,
|
|
1848
|
+
padding: "0 10px",
|
|
1849
|
+
whiteSpace: "pre"
|
|
1850
|
+
},
|
|
1851
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1852
|
+
style: {
|
|
1853
|
+
width: "12px",
|
|
1854
|
+
flexShrink: 0,
|
|
1855
|
+
color,
|
|
1856
|
+
fontWeight: 700,
|
|
1857
|
+
userSelect: "none"
|
|
1858
|
+
},
|
|
1859
|
+
children: line.sign
|
|
1860
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1861
|
+
style: {
|
|
1862
|
+
flex: 1,
|
|
1863
|
+
minWidth: 0
|
|
1864
|
+
},
|
|
1865
|
+
children: renderColoredJson(`${indent}${line.text}`)
|
|
1866
|
+
})]
|
|
1867
|
+
});
|
|
895
1868
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
background: DEVTOOL_DETAIL_HEADER_BACKGROUND,
|
|
926
|
-
borderBottom: `1px solid ${DEVTOOL_LIST_BASE_BACKGROUND}`,
|
|
927
|
-
display: "flex",
|
|
928
|
-
alignItems: "center",
|
|
929
|
-
gap: "8px",
|
|
930
|
-
flexShrink: 0
|
|
931
|
-
},
|
|
932
|
-
children: [
|
|
933
|
-
/* @__PURE__ */ jsx3("span", {
|
|
934
|
-
style: {
|
|
935
|
-
color: DEVTOOL_COLOR_TEXT_EMPHASIS,
|
|
936
|
-
fontWeight: 600,
|
|
937
|
-
fontFamily: MONO_FONT,
|
|
938
|
-
fontSize: "12px",
|
|
939
|
-
overflow: "hidden",
|
|
940
|
-
textOverflow: "ellipsis",
|
|
941
|
-
whiteSpace: "nowrap"
|
|
942
|
-
},
|
|
943
|
-
children: change.storeLabel
|
|
944
|
-
}),
|
|
945
|
-
/* @__PURE__ */ jsx3("span", {
|
|
946
|
-
style: {
|
|
947
|
-
padding: "1px 7px",
|
|
948
|
-
borderRadius: "999px",
|
|
949
|
-
background: SOURCE_COLOR[change.source],
|
|
950
|
-
color: "#0f172a",
|
|
951
|
-
fontSize: "8px",
|
|
952
|
-
fontWeight: 700,
|
|
953
|
-
letterSpacing: "0.08em",
|
|
954
|
-
fontFamily: SANS_FONT
|
|
955
|
-
},
|
|
956
|
-
children: SOURCE_LABEL[change.source]
|
|
957
|
-
}),
|
|
958
|
-
/* @__PURE__ */ jsx3("span", {
|
|
959
|
-
style: { flex: 1 }
|
|
960
|
-
}),
|
|
961
|
-
/* @__PURE__ */ jsx3("span", {
|
|
962
|
-
style: { color: DEVTOOL_COLOR_TEXT_MUTED, fontSize: "10px", fontFamily: MONO_FONT },
|
|
963
|
-
children: formatTimestamp(change.timestamp)
|
|
964
|
-
}),
|
|
965
|
-
/* @__PURE__ */ jsx3("button", {
|
|
966
|
-
onClick: () => onRevert(change),
|
|
967
|
-
disabled: !canRevert,
|
|
968
|
-
title: canRevert ? "Apply this change's inverse patches to undo it" : "No inverse patches available to revert",
|
|
969
|
-
style: {
|
|
970
|
-
background: "transparent",
|
|
971
|
-
border: `1px solid ${canRevert ? DEVTOOL_COLOR_SEMANTIC_WARNING : DEVTOOL_SECTION_BACKGROUND}`,
|
|
972
|
-
color: canRevert ? DEVTOOL_COLOR_SEMANTIC_WARNING : DEVTOOL_COLOR_TEXT_MUTED,
|
|
973
|
-
borderRadius: "4px",
|
|
974
|
-
cursor: canRevert ? "pointer" : "not-allowed",
|
|
975
|
-
fontSize: "10px",
|
|
976
|
-
padding: "2px 8px",
|
|
977
|
-
fontFamily: SANS_FONT,
|
|
978
|
-
flexShrink: 0
|
|
979
|
-
},
|
|
980
|
-
children: "⟲ revert"
|
|
981
|
-
})
|
|
982
|
-
]
|
|
983
|
-
}),
|
|
984
|
-
/* @__PURE__ */ jsx3("div", {
|
|
985
|
-
style: {
|
|
986
|
-
padding: "6px 12px",
|
|
987
|
-
borderBottom: `1px solid ${DEVTOOL_LIST_BASE_BACKGROUND}`,
|
|
988
|
-
flexShrink: 0
|
|
989
|
-
},
|
|
990
|
-
children: /* @__PURE__ */ jsx3(SegmentedControl, {
|
|
991
|
-
options: [
|
|
992
|
-
{ value: "props", label: "Diff Props" },
|
|
993
|
-
{ value: "diff", label: "Diff View" },
|
|
994
|
-
{ value: "before", label: "Before" },
|
|
995
|
-
{ value: "after", label: "After" }
|
|
996
|
-
],
|
|
997
|
-
value: view,
|
|
998
|
-
onChange: onViewChange
|
|
999
|
-
})
|
|
1000
|
-
}),
|
|
1001
|
-
/* @__PURE__ */ jsxs3("div", {
|
|
1002
|
-
style: {
|
|
1003
|
-
flex: 1,
|
|
1004
|
-
overflowY: "auto",
|
|
1005
|
-
minHeight: 0,
|
|
1006
|
-
padding: "10px 12px"
|
|
1007
|
-
},
|
|
1008
|
-
children: [
|
|
1009
|
-
(showCompressToggle || showOrderToggle) && /* @__PURE__ */ jsxs3("div", {
|
|
1010
|
-
style: {
|
|
1011
|
-
display: "flex",
|
|
1012
|
-
flexWrap: "wrap",
|
|
1013
|
-
alignItems: "center",
|
|
1014
|
-
gap: "14px",
|
|
1015
|
-
marginBottom: "8px"
|
|
1016
|
-
},
|
|
1017
|
-
children: [
|
|
1018
|
-
showCompressToggle && /* @__PURE__ */ jsx3(ToggleCheckbox, {
|
|
1019
|
-
checked: compress,
|
|
1020
|
-
onChange: onCompressChange,
|
|
1021
|
-
label: "Compress to changed paths only",
|
|
1022
|
-
title: "Collapse unchanged branches, showing only the address of what changed"
|
|
1023
|
-
}),
|
|
1024
|
-
showOrderToggle && /* @__PURE__ */ jsx3(ToggleCheckbox, {
|
|
1025
|
-
checked: latestFirst,
|
|
1026
|
-
onChange: onLatestFirstChange,
|
|
1027
|
-
label: "Latest (+) change first",
|
|
1028
|
-
title: "Show the new value (+) above the previous value (−); off shows previous first"
|
|
1029
|
-
})
|
|
1030
|
-
]
|
|
1031
|
-
}),
|
|
1032
|
-
view === "props" && /* @__PURE__ */ jsx3(DiffView, {
|
|
1033
|
-
before: change.prevSnapshot,
|
|
1034
|
-
after: change.snapshot,
|
|
1035
|
-
latestFirst
|
|
1036
|
-
}),
|
|
1037
|
-
view === "diff" && /* @__PURE__ */ jsx3(JsonDiffView, {
|
|
1038
|
-
before: change.prevSnapshot,
|
|
1039
|
-
after: change.snapshot,
|
|
1040
|
-
compress,
|
|
1041
|
-
latestFirst
|
|
1042
|
-
}),
|
|
1043
|
-
view === "before" && /* @__PURE__ */ jsx3(HighlightedJsonView, {
|
|
1044
|
-
before: change.prevSnapshot,
|
|
1045
|
-
after: change.snapshot,
|
|
1046
|
-
side: "before",
|
|
1047
|
-
compress
|
|
1048
|
-
}),
|
|
1049
|
-
view === "after" && /* @__PURE__ */ jsx3(HighlightedJsonView, {
|
|
1050
|
-
before: change.prevSnapshot,
|
|
1051
|
-
after: change.snapshot,
|
|
1052
|
-
side: "after",
|
|
1053
|
-
compress
|
|
1054
|
-
})
|
|
1055
|
-
]
|
|
1056
|
-
})
|
|
1057
|
-
]
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
function ToggleCheckbox({
|
|
1061
|
-
checked,
|
|
1062
|
-
onChange,
|
|
1063
|
-
label,
|
|
1064
|
-
title
|
|
1065
|
-
}) {
|
|
1066
|
-
return /* @__PURE__ */ jsxs3("label", {
|
|
1067
|
-
style: {
|
|
1068
|
-
display: "flex",
|
|
1069
|
-
alignItems: "center",
|
|
1070
|
-
gap: "6px",
|
|
1071
|
-
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
1072
|
-
fontSize: "10px",
|
|
1073
|
-
fontFamily: SANS_FONT,
|
|
1074
|
-
cursor: "pointer",
|
|
1075
|
-
userSelect: "none"
|
|
1076
|
-
},
|
|
1077
|
-
title,
|
|
1078
|
-
children: [
|
|
1079
|
-
/* @__PURE__ */ jsx3("input", {
|
|
1080
|
-
type: "checkbox",
|
|
1081
|
-
checked,
|
|
1082
|
-
onChange: (e) => onChange(e.target.checked),
|
|
1083
|
-
style: { cursor: "pointer", margin: 0 }
|
|
1084
|
-
}),
|
|
1085
|
-
label
|
|
1086
|
-
]
|
|
1087
|
-
});
|
|
1869
|
+
/**
|
|
1870
|
+
* The full JSON of one snapshot with the lines that differ from the other side
|
|
1871
|
+
* highlighted in place — red behind removed lines on the "before" side, green
|
|
1872
|
+
* behind added lines on the "after" side. Lines common to both render plainly so
|
|
1873
|
+
* the highlight reads as "here is what changed within the whole state".
|
|
1874
|
+
*/
|
|
1875
|
+
function HighlightedJsonView({ before, after, side, compress = false }) {
|
|
1876
|
+
if (compress) return /* @__PURE__ */ jsx(CompressedDiffView, {
|
|
1877
|
+
before,
|
|
1878
|
+
after,
|
|
1879
|
+
side
|
|
1880
|
+
});
|
|
1881
|
+
const ops = computeLineDiff(safeStringify(before, 2), safeStringify(after, 2));
|
|
1882
|
+
const dropKind = side === "before" ? "added" : "removed";
|
|
1883
|
+
const highlightKind = side === "before" ? "removed" : "added";
|
|
1884
|
+
const background = side === "before" ? REMOVED_BG : ADDED_BG;
|
|
1885
|
+
const color = side === "before" ? DEVTOOL_COLOR_SEMANTIC_ERROR : DEVTOOL_COLOR_SEMANTIC_SUCCESS;
|
|
1886
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
1887
|
+
style: SURFACE_STYLE,
|
|
1888
|
+
children: ops.filter((op) => op.kind !== dropKind).map((op, i) => {
|
|
1889
|
+
const changed = op.kind === highlightKind;
|
|
1890
|
+
return /* @__PURE__ */ jsx(Line, {
|
|
1891
|
+
sign: changed ? side === "before" ? "−" : "+" : " ",
|
|
1892
|
+
color,
|
|
1893
|
+
background: changed ? background : "transparent",
|
|
1894
|
+
text: op.text
|
|
1895
|
+
}, i);
|
|
1896
|
+
})
|
|
1897
|
+
});
|
|
1088
1898
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1899
|
+
//#endregion
|
|
1900
|
+
//#region src/devtools/browser/components/ChangeDetailPanel.tsx
|
|
1901
|
+
function ChangeDetailPanel({ change, onRevert, view, onViewChange, compress, onCompressChange, latestFirst, onLatestFirstChange }) {
|
|
1902
|
+
const canRevert = change.inversePatches.length > 0;
|
|
1903
|
+
const showCompressToggle = view !== "props";
|
|
1904
|
+
const showOrderToggle = view === "props" || view === "diff";
|
|
1905
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1906
|
+
style: {
|
|
1907
|
+
flex: 1,
|
|
1908
|
+
display: "flex",
|
|
1909
|
+
flexDirection: "column",
|
|
1910
|
+
overflow: "hidden",
|
|
1911
|
+
minHeight: 0,
|
|
1912
|
+
background: DEVTOOL_DETAIL_BASE_BACKGROUND
|
|
1913
|
+
},
|
|
1914
|
+
children: [
|
|
1915
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1916
|
+
style: {
|
|
1917
|
+
padding: "8px 12px",
|
|
1918
|
+
background: DEVTOOL_DETAIL_HEADER_BACKGROUND,
|
|
1919
|
+
borderBottom: `1px solid ${DEVTOOL_LIST_BASE_BACKGROUND}`,
|
|
1920
|
+
display: "flex",
|
|
1921
|
+
alignItems: "center",
|
|
1922
|
+
gap: "8px",
|
|
1923
|
+
flexShrink: 0
|
|
1924
|
+
},
|
|
1925
|
+
children: [
|
|
1926
|
+
/* @__PURE__ */ jsx("span", {
|
|
1927
|
+
style: {
|
|
1928
|
+
color: DEVTOOL_COLOR_TEXT_EMPHASIS,
|
|
1929
|
+
fontWeight: 600,
|
|
1930
|
+
fontFamily: MONO_FONT,
|
|
1931
|
+
fontSize: "12px",
|
|
1932
|
+
overflow: "hidden",
|
|
1933
|
+
textOverflow: "ellipsis",
|
|
1934
|
+
whiteSpace: "nowrap"
|
|
1935
|
+
},
|
|
1936
|
+
children: change.storeLabel
|
|
1937
|
+
}),
|
|
1938
|
+
/* @__PURE__ */ jsx("span", {
|
|
1939
|
+
style: {
|
|
1940
|
+
padding: "1px 7px",
|
|
1941
|
+
borderRadius: "999px",
|
|
1942
|
+
background: SOURCE_COLOR[change.source],
|
|
1943
|
+
color: "#0f172a",
|
|
1944
|
+
fontSize: "8px",
|
|
1945
|
+
fontWeight: 700,
|
|
1946
|
+
letterSpacing: "0.08em",
|
|
1947
|
+
fontFamily: SANS_FONT
|
|
1948
|
+
},
|
|
1949
|
+
children: SOURCE_LABEL[change.source]
|
|
1950
|
+
}),
|
|
1951
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
|
|
1952
|
+
/* @__PURE__ */ jsx("span", {
|
|
1953
|
+
style: {
|
|
1954
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
1955
|
+
fontSize: "10px",
|
|
1956
|
+
fontFamily: MONO_FONT
|
|
1957
|
+
},
|
|
1958
|
+
children: formatTimestamp(change.timestamp)
|
|
1959
|
+
}),
|
|
1960
|
+
/* @__PURE__ */ jsx("button", {
|
|
1961
|
+
onClick: () => onRevert(change),
|
|
1962
|
+
disabled: !canRevert,
|
|
1963
|
+
title: canRevert ? "Apply this change's inverse patches to undo it" : "No inverse patches available to revert",
|
|
1964
|
+
style: {
|
|
1965
|
+
background: "transparent",
|
|
1966
|
+
border: `1px solid ${canRevert ? DEVTOOL_COLOR_SEMANTIC_WARNING : DEVTOOL_SECTION_BACKGROUND}`,
|
|
1967
|
+
color: canRevert ? DEVTOOL_COLOR_SEMANTIC_WARNING : DEVTOOL_COLOR_TEXT_MUTED,
|
|
1968
|
+
borderRadius: "4px",
|
|
1969
|
+
cursor: canRevert ? "pointer" : "not-allowed",
|
|
1970
|
+
fontSize: "10px",
|
|
1971
|
+
padding: "2px 8px",
|
|
1972
|
+
fontFamily: SANS_FONT,
|
|
1973
|
+
flexShrink: 0
|
|
1974
|
+
},
|
|
1975
|
+
children: "⟲ revert"
|
|
1976
|
+
})
|
|
1977
|
+
]
|
|
1978
|
+
}),
|
|
1979
|
+
/* @__PURE__ */ jsx("div", {
|
|
1980
|
+
style: {
|
|
1981
|
+
padding: "6px 12px",
|
|
1982
|
+
borderBottom: `1px solid ${DEVTOOL_LIST_BASE_BACKGROUND}`,
|
|
1983
|
+
flexShrink: 0
|
|
1984
|
+
},
|
|
1985
|
+
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
1986
|
+
options: [
|
|
1987
|
+
{
|
|
1988
|
+
value: "props",
|
|
1989
|
+
label: "Diff Props"
|
|
1990
|
+
},
|
|
1991
|
+
{
|
|
1992
|
+
value: "diff",
|
|
1993
|
+
label: "Diff View"
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
value: "before",
|
|
1997
|
+
label: "Before"
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
value: "after",
|
|
2001
|
+
label: "After"
|
|
2002
|
+
}
|
|
2003
|
+
],
|
|
2004
|
+
value: view,
|
|
2005
|
+
onChange: onViewChange
|
|
2006
|
+
})
|
|
2007
|
+
}),
|
|
2008
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2009
|
+
style: {
|
|
2010
|
+
flex: 1,
|
|
2011
|
+
overflowY: "auto",
|
|
2012
|
+
minHeight: 0,
|
|
2013
|
+
padding: "10px 12px"
|
|
2014
|
+
},
|
|
2015
|
+
children: [
|
|
2016
|
+
(showCompressToggle || showOrderToggle) && /* @__PURE__ */ jsxs("div", {
|
|
2017
|
+
style: {
|
|
2018
|
+
display: "flex",
|
|
2019
|
+
flexWrap: "wrap",
|
|
2020
|
+
alignItems: "center",
|
|
2021
|
+
gap: "14px",
|
|
2022
|
+
marginBottom: "8px"
|
|
2023
|
+
},
|
|
2024
|
+
children: [showCompressToggle && /* @__PURE__ */ jsx(ToggleCheckbox, {
|
|
2025
|
+
checked: compress,
|
|
2026
|
+
onChange: onCompressChange,
|
|
2027
|
+
label: "Compress to changed paths only",
|
|
2028
|
+
title: "Collapse unchanged branches, showing only the address of what changed"
|
|
2029
|
+
}), showOrderToggle && /* @__PURE__ */ jsx(ToggleCheckbox, {
|
|
2030
|
+
checked: latestFirst,
|
|
2031
|
+
onChange: onLatestFirstChange,
|
|
2032
|
+
label: "Latest (+) change first",
|
|
2033
|
+
title: "Show the new value (+) above the previous value (−); off shows previous first"
|
|
2034
|
+
})]
|
|
2035
|
+
}),
|
|
2036
|
+
view === "props" && /* @__PURE__ */ jsx(DiffView, {
|
|
2037
|
+
before: change.prevSnapshot,
|
|
2038
|
+
after: change.snapshot,
|
|
2039
|
+
latestFirst
|
|
2040
|
+
}),
|
|
2041
|
+
view === "diff" && /* @__PURE__ */ jsx(JsonDiffView, {
|
|
2042
|
+
before: change.prevSnapshot,
|
|
2043
|
+
after: change.snapshot,
|
|
2044
|
+
compress,
|
|
2045
|
+
latestFirst
|
|
2046
|
+
}),
|
|
2047
|
+
view === "before" && /* @__PURE__ */ jsx(HighlightedJsonView, {
|
|
2048
|
+
before: change.prevSnapshot,
|
|
2049
|
+
after: change.snapshot,
|
|
2050
|
+
side: "before",
|
|
2051
|
+
compress
|
|
2052
|
+
}),
|
|
2053
|
+
view === "after" && /* @__PURE__ */ jsx(HighlightedJsonView, {
|
|
2054
|
+
before: change.prevSnapshot,
|
|
2055
|
+
after: change.snapshot,
|
|
2056
|
+
side: "after",
|
|
2057
|
+
compress
|
|
2058
|
+
})
|
|
2059
|
+
]
|
|
2060
|
+
})
|
|
2061
|
+
]
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
function ToggleCheckbox({ checked, onChange, label, title }) {
|
|
2065
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
2066
|
+
style: {
|
|
2067
|
+
display: "flex",
|
|
2068
|
+
alignItems: "center",
|
|
2069
|
+
gap: "6px",
|
|
2070
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
2071
|
+
fontSize: "10px",
|
|
2072
|
+
fontFamily: SANS_FONT,
|
|
2073
|
+
cursor: "pointer",
|
|
2074
|
+
userSelect: "none"
|
|
2075
|
+
},
|
|
2076
|
+
title,
|
|
2077
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
2078
|
+
type: "checkbox",
|
|
2079
|
+
checked,
|
|
2080
|
+
onChange: (e) => onChange(e.target.checked),
|
|
2081
|
+
style: {
|
|
2082
|
+
cursor: "pointer",
|
|
2083
|
+
margin: 0
|
|
2084
|
+
}
|
|
2085
|
+
}), label]
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
//#endregion
|
|
2089
|
+
//#region src/devtools/browser/components/ChangeList.tsx
|
|
2090
|
+
const getGroupKey = (group) => group.oldest.cuid;
|
|
2091
|
+
function ChangeList({ groups, selectedCuid, onSelect, showStore, style }) {
|
|
2092
|
+
return /* @__PURE__ */ jsx(DevtoolsVirtualList, {
|
|
2093
|
+
items: groups,
|
|
2094
|
+
getItemKey: getGroupKey,
|
|
2095
|
+
selectedKey: selectedCuid,
|
|
2096
|
+
estimateSize: 44,
|
|
2097
|
+
overscan: 12,
|
|
2098
|
+
style,
|
|
2099
|
+
empty: /* @__PURE__ */ jsx("div", {
|
|
2100
|
+
style: {
|
|
2101
|
+
padding: "24px",
|
|
2102
|
+
textAlign: "center",
|
|
2103
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
2104
|
+
fontSize: "11px",
|
|
2105
|
+
...style
|
|
2106
|
+
},
|
|
2107
|
+
children: "No changes recorded yet. Mutate a store to see it here."
|
|
2108
|
+
}),
|
|
2109
|
+
renderItem: (group) => /* @__PURE__ */ jsx(ChangeRow, {
|
|
2110
|
+
change: group.representative,
|
|
2111
|
+
count: group.count,
|
|
2112
|
+
selected: group.oldest.cuid === selectedCuid,
|
|
2113
|
+
onClick: () => onSelect(group.oldest.cuid),
|
|
2114
|
+
showStore
|
|
2115
|
+
})
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
function ChangeRow({ change, count, selected, onClick, showStore }) {
|
|
2119
|
+
const sourceColor = SOURCE_COLOR[change.source];
|
|
2120
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2121
|
+
onClick,
|
|
2122
|
+
style: {
|
|
2123
|
+
display: "flex",
|
|
2124
|
+
flexDirection: "column",
|
|
2125
|
+
gap: "3px",
|
|
2126
|
+
padding: "6px 10px",
|
|
2127
|
+
cursor: "pointer",
|
|
2128
|
+
borderBottom: `1px solid ${DEVTOOL_SECTION_BACKGROUND}`,
|
|
2129
|
+
borderLeft: `2px solid ${selected ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : "transparent"}`,
|
|
2130
|
+
background: selected ? DEVTOOL_LIST_SELECTED_BACKGROUND : DEVTOOL_LIST_BASE_BACKGROUND
|
|
2131
|
+
},
|
|
2132
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2133
|
+
style: {
|
|
2134
|
+
display: "flex",
|
|
2135
|
+
alignItems: "center",
|
|
2136
|
+
gap: "6px"
|
|
2137
|
+
},
|
|
2138
|
+
children: [
|
|
2139
|
+
/* @__PURE__ */ jsx("span", {
|
|
2140
|
+
style: {
|
|
2141
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
2142
|
+
fontSize: "10px",
|
|
2143
|
+
fontFamily: MONO_FONT,
|
|
2144
|
+
flexShrink: 0
|
|
2145
|
+
},
|
|
2146
|
+
children: formatTimestamp(change.timestamp)
|
|
2147
|
+
}),
|
|
2148
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
2149
|
+
color: sourceColor,
|
|
2150
|
+
children: SOURCE_LABEL[change.source]
|
|
2151
|
+
}),
|
|
2152
|
+
count > 1 && /* @__PURE__ */ jsxs("span", {
|
|
2153
|
+
title: `${count} consecutive identical updates`,
|
|
2154
|
+
style: {
|
|
2155
|
+
flexShrink: 0,
|
|
2156
|
+
padding: "0 5px",
|
|
2157
|
+
borderRadius: "999px",
|
|
2158
|
+
background: "#1e293b",
|
|
2159
|
+
color: "#cbd5e1",
|
|
2160
|
+
fontSize: "9px",
|
|
2161
|
+
fontWeight: 700,
|
|
2162
|
+
fontFamily: "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace"
|
|
2163
|
+
},
|
|
2164
|
+
children: ["×", count]
|
|
2165
|
+
}),
|
|
2166
|
+
showStore && /* @__PURE__ */ jsx("span", {
|
|
2167
|
+
style: {
|
|
2168
|
+
color: "#38BDF8",
|
|
2169
|
+
fontSize: "10px",
|
|
2170
|
+
fontFamily: "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace",
|
|
2171
|
+
overflow: "hidden",
|
|
2172
|
+
textOverflow: "ellipsis",
|
|
2173
|
+
whiteSpace: "nowrap"
|
|
2174
|
+
},
|
|
2175
|
+
children: change.storeLabel
|
|
2176
|
+
}),
|
|
2177
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
|
|
2178
|
+
change.patches.length > 0 && /* @__PURE__ */ jsxs("span", {
|
|
2179
|
+
style: {
|
|
2180
|
+
color: "#334155",
|
|
2181
|
+
fontSize: "10px",
|
|
2182
|
+
flexShrink: 0
|
|
2183
|
+
},
|
|
2184
|
+
children: [
|
|
2185
|
+
change.patches.length,
|
|
2186
|
+
" patch",
|
|
2187
|
+
change.patches.length === 1 ? "" : "es"
|
|
2188
|
+
]
|
|
2189
|
+
})
|
|
2190
|
+
]
|
|
2191
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2192
|
+
style: {
|
|
2193
|
+
color: DEVTOOL_COLOR_TEXT_SECONDARY,
|
|
2194
|
+
fontSize: "11px",
|
|
2195
|
+
fontFamily: MONO_FONT,
|
|
2196
|
+
overflow: "hidden",
|
|
2197
|
+
textOverflow: "ellipsis",
|
|
2198
|
+
whiteSpace: "nowrap"
|
|
2199
|
+
},
|
|
2200
|
+
children: summarizeChange(change)
|
|
2201
|
+
})]
|
|
2202
|
+
});
|
|
1218
2203
|
}
|
|
1219
2204
|
function Badge({ color, children }) {
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
2205
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2206
|
+
style: {
|
|
2207
|
+
flexShrink: 0,
|
|
2208
|
+
padding: "1px 6px",
|
|
2209
|
+
borderRadius: "999px",
|
|
2210
|
+
background: color,
|
|
2211
|
+
color: "#0f172a",
|
|
2212
|
+
fontSize: "8px",
|
|
2213
|
+
fontWeight: 700,
|
|
2214
|
+
letterSpacing: "0.08em",
|
|
2215
|
+
fontFamily: SANS_FONT
|
|
2216
|
+
},
|
|
2217
|
+
children
|
|
2218
|
+
});
|
|
1234
2219
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
]
|
|
1398
|
-
});
|
|
2220
|
+
//#endregion
|
|
2221
|
+
//#region src/devtools/browser/components/StateInspector.tsx
|
|
2222
|
+
/**
|
|
2223
|
+
* Live current-state view (top half) plus a direct JSON editor (bottom half) for
|
|
2224
|
+
* one store — a fixed 50/50 split so the editor is usable without manual
|
|
2225
|
+
* resizing. Editing is the "trigger edits directly for testing" capability: the
|
|
2226
|
+
* draft is parsed and handed to {@link StateDevtoolsCore.applyEdit}, which
|
|
2227
|
+
* replaces the store state.
|
|
2228
|
+
*
|
|
2229
|
+
* The draft is seeded from the live state only while it is clean — once the user
|
|
2230
|
+
* types, incoming external updates no longer clobber their edit (a "reload"
|
|
2231
|
+
* button re-syncs on demand).
|
|
2232
|
+
*/
|
|
2233
|
+
function StateInspector({ store, onApply }) {
|
|
2234
|
+
const liveText = safeStringify(store.currentState, 2);
|
|
2235
|
+
const [draft, setDraft] = useState(liveText);
|
|
2236
|
+
const [dirty, setDirty] = useState(false);
|
|
2237
|
+
const [error, setError] = useState(null);
|
|
2238
|
+
useEffect(() => {
|
|
2239
|
+
setDraft(liveText);
|
|
2240
|
+
setDirty(false);
|
|
2241
|
+
setError(null);
|
|
2242
|
+
}, [store.id]);
|
|
2243
|
+
useEffect(() => {
|
|
2244
|
+
if (!dirty) setDraft(liveText);
|
|
2245
|
+
}, [liveText]);
|
|
2246
|
+
const onEdit = (value) => {
|
|
2247
|
+
setDraft(value);
|
|
2248
|
+
setDirty(true);
|
|
2249
|
+
setError(null);
|
|
2250
|
+
};
|
|
2251
|
+
const reload = () => {
|
|
2252
|
+
setDraft(liveText);
|
|
2253
|
+
setDirty(false);
|
|
2254
|
+
setError(null);
|
|
2255
|
+
};
|
|
2256
|
+
const apply = () => {
|
|
2257
|
+
let parsed;
|
|
2258
|
+
try {
|
|
2259
|
+
parsed = JSON.parse(draft);
|
|
2260
|
+
} catch (e) {
|
|
2261
|
+
setError(e instanceof Error ? e.message : "Invalid JSON");
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
onApply(store.id, parsed);
|
|
2265
|
+
setDirty(false);
|
|
2266
|
+
setError(null);
|
|
2267
|
+
};
|
|
2268
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2269
|
+
style: {
|
|
2270
|
+
flex: 1,
|
|
2271
|
+
display: "flex",
|
|
2272
|
+
flexDirection: "column",
|
|
2273
|
+
overflow: "hidden",
|
|
2274
|
+
minHeight: 0,
|
|
2275
|
+
background: DEVTOOL_DETAIL_BASE_BACKGROUND
|
|
2276
|
+
},
|
|
2277
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2278
|
+
style: {
|
|
2279
|
+
flex: 1,
|
|
2280
|
+
minHeight: 0,
|
|
2281
|
+
display: "flex",
|
|
2282
|
+
flexDirection: "column",
|
|
2283
|
+
padding: "10px 12px 8px"
|
|
2284
|
+
},
|
|
2285
|
+
children: [/* @__PURE__ */ jsx(SectionLabel, { label: "Current state" }), /* @__PURE__ */ jsx("div", {
|
|
2286
|
+
style: {
|
|
2287
|
+
flex: 1,
|
|
2288
|
+
minHeight: 0,
|
|
2289
|
+
overflow: "auto"
|
|
2290
|
+
},
|
|
2291
|
+
children: /* @__PURE__ */ jsx(JsonView, {
|
|
2292
|
+
value: store.currentState,
|
|
2293
|
+
style: { minHeight: "100%" }
|
|
2294
|
+
})
|
|
2295
|
+
})]
|
|
2296
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2297
|
+
style: {
|
|
2298
|
+
flex: 1,
|
|
2299
|
+
minHeight: 0,
|
|
2300
|
+
display: "flex",
|
|
2301
|
+
flexDirection: "column",
|
|
2302
|
+
padding: "8px 12px 10px",
|
|
2303
|
+
borderTop: `1px solid ${DEVTOOL_PANEL_DIVIDER_BORDER}`
|
|
2304
|
+
},
|
|
2305
|
+
children: [
|
|
2306
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2307
|
+
style: {
|
|
2308
|
+
display: "flex",
|
|
2309
|
+
alignItems: "center",
|
|
2310
|
+
justifyContent: "space-between",
|
|
2311
|
+
marginBottom: "3px"
|
|
2312
|
+
},
|
|
2313
|
+
children: [/* @__PURE__ */ jsx(SectionLabel, {
|
|
2314
|
+
label: "Edit & apply",
|
|
2315
|
+
color: DEVTOOL_COLOR_SEMANTIC_WARNING
|
|
2316
|
+
}), dirty && /* @__PURE__ */ jsx("button", {
|
|
2317
|
+
onClick: reload,
|
|
2318
|
+
style: linkButtonStyle("#64748b"),
|
|
2319
|
+
children: "reload from store"
|
|
2320
|
+
})]
|
|
2321
|
+
}),
|
|
2322
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
2323
|
+
value: draft,
|
|
2324
|
+
spellCheck: false,
|
|
2325
|
+
onChange: (e) => onEdit(e.target.value),
|
|
2326
|
+
style: {
|
|
2327
|
+
flex: 1,
|
|
2328
|
+
minHeight: 0,
|
|
2329
|
+
width: "100%",
|
|
2330
|
+
resize: "none",
|
|
2331
|
+
boxSizing: "border-box",
|
|
2332
|
+
background: DEVTOOL_EDITOR_BACKGROUND,
|
|
2333
|
+
color: DEVTOOL_COLOR_TEXT_SECONDARY,
|
|
2334
|
+
border: `1px solid ${error != null ? DEVTOOL_COLOR_SEMANTIC_ERROR : DEVTOOL_SECTION_BACKGROUND}`,
|
|
2335
|
+
borderRadius: "4px",
|
|
2336
|
+
padding: "8px 10px",
|
|
2337
|
+
fontFamily: MONO_FONT,
|
|
2338
|
+
fontSize: "11px",
|
|
2339
|
+
lineHeight: 1.5
|
|
2340
|
+
}
|
|
2341
|
+
}),
|
|
2342
|
+
error != null && /* @__PURE__ */ jsx("div", {
|
|
2343
|
+
style: {
|
|
2344
|
+
marginTop: "6px",
|
|
2345
|
+
padding: "6px 8px",
|
|
2346
|
+
borderRadius: "4px",
|
|
2347
|
+
background: "#1e0a0a",
|
|
2348
|
+
color: "#FF5C5C",
|
|
2349
|
+
fontFamily: "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace",
|
|
2350
|
+
fontSize: "10px",
|
|
2351
|
+
flexShrink: 0
|
|
2352
|
+
},
|
|
2353
|
+
children: error
|
|
2354
|
+
}),
|
|
2355
|
+
/* @__PURE__ */ jsx("div", {
|
|
2356
|
+
style: {
|
|
2357
|
+
display: "flex",
|
|
2358
|
+
gap: "8px",
|
|
2359
|
+
marginTop: "8px",
|
|
2360
|
+
flexShrink: 0
|
|
2361
|
+
},
|
|
2362
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2363
|
+
onClick: apply,
|
|
2364
|
+
disabled: !dirty,
|
|
2365
|
+
style: {
|
|
2366
|
+
background: dirty ? DEVTOOL_COLOR_SEMANTIC_SUCCESS : DEVTOOL_SECTION_BACKGROUND,
|
|
2367
|
+
color: dirty ? "#0f172a" : DEVTOOL_COLOR_TEXT_MUTED,
|
|
2368
|
+
border: "none",
|
|
2369
|
+
borderRadius: "4px",
|
|
2370
|
+
cursor: dirty ? "pointer" : "not-allowed",
|
|
2371
|
+
fontSize: "11px",
|
|
2372
|
+
fontWeight: 600,
|
|
2373
|
+
padding: "5px 14px",
|
|
2374
|
+
fontFamily: SANS_FONT
|
|
2375
|
+
},
|
|
2376
|
+
children: "Apply to store"
|
|
2377
|
+
})
|
|
2378
|
+
})
|
|
2379
|
+
]
|
|
2380
|
+
})]
|
|
2381
|
+
});
|
|
1399
2382
|
}
|
|
1400
2383
|
function linkButtonStyle(color) {
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
2384
|
+
return {
|
|
2385
|
+
background: "none",
|
|
2386
|
+
border: "none",
|
|
2387
|
+
color,
|
|
2388
|
+
cursor: "pointer",
|
|
2389
|
+
fontSize: "10px",
|
|
2390
|
+
padding: 0,
|
|
2391
|
+
fontFamily: SANS_FONT
|
|
2392
|
+
};
|
|
1410
2393
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
onClick,
|
|
1453
|
-
label,
|
|
1454
|
-
count
|
|
1455
|
-
}) {
|
|
1456
|
-
return /* @__PURE__ */ jsxs6("button", {
|
|
1457
|
-
onClick,
|
|
1458
|
-
style: {
|
|
1459
|
-
display: "flex",
|
|
1460
|
-
alignItems: "center",
|
|
1461
|
-
gap: "5px",
|
|
1462
|
-
background: active ? DEVTOOL_SECTION_BACKGROUND : "transparent",
|
|
1463
|
-
color: active ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : DEVTOOL_COLOR_TEXT_MUTED,
|
|
1464
|
-
border: `1px solid ${active ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : DEVTOOL_COLOR_TEXT_FAINT}`,
|
|
1465
|
-
borderRadius: "5px",
|
|
1466
|
-
cursor: "pointer",
|
|
1467
|
-
fontSize: "11px",
|
|
1468
|
-
fontFamily: MONO_FONT,
|
|
1469
|
-
padding: "2px 8px",
|
|
1470
|
-
whiteSpace: "nowrap",
|
|
1471
|
-
flexShrink: 0
|
|
1472
|
-
},
|
|
1473
|
-
children: [
|
|
1474
|
-
label,
|
|
1475
|
-
count != null && count > 0 && /* @__PURE__ */ jsx6("span", {
|
|
1476
|
-
style: {
|
|
1477
|
-
color: active ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : DEVTOOL_COLOR_TEXT_FAINT,
|
|
1478
|
-
fontSize: "10px"
|
|
1479
|
-
},
|
|
1480
|
-
children: count
|
|
1481
|
-
})
|
|
1482
|
-
]
|
|
1483
|
-
});
|
|
2394
|
+
//#endregion
|
|
2395
|
+
//#region src/devtools/browser/components/StoreTabs.tsx
|
|
2396
|
+
/**
|
|
2397
|
+
* Horizontal store filter. `null` selection means "all stores". When
|
|
2398
|
+
* `includeAll` is false (the State inspector needs a concrete store) the All
|
|
2399
|
+
* pill is omitted.
|
|
2400
|
+
*/
|
|
2401
|
+
function StoreTabs({ stores, selectedStoreId, onSelect, includeAll = true }) {
|
|
2402
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2403
|
+
style: {
|
|
2404
|
+
display: "flex",
|
|
2405
|
+
gap: "6px",
|
|
2406
|
+
padding: "6px 10px",
|
|
2407
|
+
overflowX: "auto",
|
|
2408
|
+
background: DEVTOOL_LIST_BASE_BACKGROUND,
|
|
2409
|
+
borderBottom: `1px solid ${DEVTOOL_SECTION_BACKGROUND}`,
|
|
2410
|
+
flexWrap: "wrap",
|
|
2411
|
+
flexShrink: 0
|
|
2412
|
+
},
|
|
2413
|
+
children: [
|
|
2414
|
+
includeAll && /* @__PURE__ */ jsx(Pill, {
|
|
2415
|
+
active: selectedStoreId == null,
|
|
2416
|
+
onClick: () => onSelect(null),
|
|
2417
|
+
label: "all"
|
|
2418
|
+
}),
|
|
2419
|
+
stores.length === 0 && /* @__PURE__ */ jsx("span", {
|
|
2420
|
+
style: {
|
|
2421
|
+
color: "#64748b",
|
|
2422
|
+
fontSize: "11px",
|
|
2423
|
+
padding: "2px 0"
|
|
2424
|
+
},
|
|
2425
|
+
children: "no stores registered"
|
|
2426
|
+
}),
|
|
2427
|
+
stores.map((store) => /* @__PURE__ */ jsx(Pill, {
|
|
2428
|
+
active: selectedStoreId === store.id,
|
|
2429
|
+
onClick: () => onSelect(store.id),
|
|
2430
|
+
label: store.label,
|
|
2431
|
+
count: store.changeCount
|
|
2432
|
+
}, store.id))
|
|
2433
|
+
]
|
|
2434
|
+
});
|
|
1484
2435
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
2436
|
+
function Pill({ active, onClick, label, count }) {
|
|
2437
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2438
|
+
onClick,
|
|
2439
|
+
style: {
|
|
2440
|
+
display: "flex",
|
|
2441
|
+
alignItems: "center",
|
|
2442
|
+
gap: "5px",
|
|
2443
|
+
background: active ? DEVTOOL_SECTION_BACKGROUND : "transparent",
|
|
2444
|
+
color: active ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : DEVTOOL_COLOR_TEXT_MUTED,
|
|
2445
|
+
border: `1px solid ${active ? DEVTOOL_COLOR_SEMANTIC_SYSTEM : DEVTOOL_COLOR_TEXT_FAINT}`,
|
|
2446
|
+
borderRadius: "5px",
|
|
2447
|
+
cursor: "pointer",
|
|
2448
|
+
fontSize: "11px",
|
|
2449
|
+
fontFamily: MONO_FONT,
|
|
2450
|
+
padding: "2px 8px",
|
|
2451
|
+
whiteSpace: "nowrap",
|
|
2452
|
+
flexShrink: 0
|
|
2453
|
+
},
|
|
2454
|
+
children: [label, count != null && count > 0 && /* @__PURE__ */ jsx("span", {
|
|
2455
|
+
style: {
|
|
2456
|
+
color: active ? "#38BDF8" : "#334155",
|
|
2457
|
+
fontSize: "10px"
|
|
2458
|
+
},
|
|
2459
|
+
children: count
|
|
2460
|
+
})]
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
//#endregion
|
|
2464
|
+
//#region src/devtools/browser/NiceStateDevtools.tsx
|
|
1488
2465
|
if (typeof document !== "undefined" && !document.getElementById("__nice-state-devtools-styles")) {
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
2466
|
+
const style = document.createElement("style");
|
|
2467
|
+
style.id = "__nice-state-devtools-styles";
|
|
2468
|
+
style.textContent = `
|
|
1492
2469
|
@keyframes __nice-state-pulse {
|
|
1493
2470
|
0%, 100% { opacity: 1; }
|
|
1494
2471
|
50% { opacity: 0.35; }
|
|
@@ -1507,342 +2484,338 @@ if (typeof document !== "undefined" && !document.getElementById("__nice-state-de
|
|
|
1507
2484
|
#__nice-state-devtools-panel select,
|
|
1508
2485
|
#__nice-state-devtools-panel textarea { all: revert; font-family: inherit; }
|
|
1509
2486
|
`;
|
|
1510
|
-
|
|
2487
|
+
document.head?.appendChild(style);
|
|
1511
2488
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
2489
|
+
const PREFS_KEY = "__nice-state-devtools-prefs";
|
|
2490
|
+
const DOCKED_HEIGHT_DEFAULT = 340;
|
|
2491
|
+
const DOCKED_WIDTH_DEFAULT = 330;
|
|
2492
|
+
const DETAIL_RATIO_DEFAULT = .5;
|
|
2493
|
+
const DOCK_POSITIONS = [
|
|
2494
|
+
"dock-bottom",
|
|
2495
|
+
"dock-top",
|
|
2496
|
+
"dock-left",
|
|
2497
|
+
"dock-right"
|
|
2498
|
+
];
|
|
1517
2499
|
function isDockPosition(value) {
|
|
1518
|
-
|
|
2500
|
+
return typeof value === "string" && DOCK_POSITIONS.includes(value);
|
|
1519
2501
|
}
|
|
1520
2502
|
function readPrefs(defaultPosition, initialOpen) {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
2503
|
+
const fallback = {
|
|
2504
|
+
position: defaultPosition,
|
|
2505
|
+
isOpen: initialOpen,
|
|
2506
|
+
dockedHeight: DOCKED_HEIGHT_DEFAULT,
|
|
2507
|
+
dockedWidth: DOCKED_WIDTH_DEFAULT,
|
|
2508
|
+
detailRatio: DETAIL_RATIO_DEFAULT,
|
|
2509
|
+
stayOnLatest: true,
|
|
2510
|
+
followLatestOnSelect: true,
|
|
2511
|
+
compressDiff: true,
|
|
2512
|
+
detailView: "props",
|
|
2513
|
+
diffLatestFirst: true
|
|
2514
|
+
};
|
|
2515
|
+
try {
|
|
2516
|
+
if (typeof localStorage === "undefined") return fallback;
|
|
2517
|
+
const stored = localStorage.getItem(PREFS_KEY);
|
|
2518
|
+
const merged = stored != null ? {
|
|
2519
|
+
...fallback,
|
|
2520
|
+
...JSON.parse(stored)
|
|
2521
|
+
} : fallback;
|
|
2522
|
+
if (!isDockPosition(merged.position)) merged.position = defaultPosition;
|
|
2523
|
+
return merged;
|
|
2524
|
+
} catch {
|
|
2525
|
+
return fallback;
|
|
2526
|
+
}
|
|
1544
2527
|
}
|
|
1545
2528
|
function writePrefs(prefs) {
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
2529
|
+
try {
|
|
2530
|
+
localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
|
|
2531
|
+
} catch {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
1551
2534
|
}
|
|
1552
|
-
|
|
2535
|
+
const EMPTY_SNAPSHOT = {
|
|
2536
|
+
stores: [],
|
|
2537
|
+
changes: [],
|
|
2538
|
+
paused: false
|
|
2539
|
+
};
|
|
1553
2540
|
function NiceStateDevtools({ forceEnable, ...props }) {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
view: detailView,
|
|
1816
|
-
onViewChange: (v) => setPrefs({ detailView: v }),
|
|
1817
|
-
compress: compressDiff,
|
|
1818
|
-
onCompressChange: (v) => setPrefs({ compressDiff: v }),
|
|
1819
|
-
latestFirst: diffLatestFirst,
|
|
1820
|
-
onLatestFirstChange: (v) => setPrefs({ diffLatestFirst: v })
|
|
1821
|
-
})
|
|
1822
|
-
})
|
|
1823
|
-
]
|
|
1824
|
-
})
|
|
1825
|
-
]
|
|
1826
|
-
})
|
|
1827
|
-
]
|
|
1828
|
-
});
|
|
2541
|
+
if (!forceEnable && true) return null;
|
|
2542
|
+
return /* @__PURE__ */ jsx(NiceStateDevtools_Panel, { ...props });
|
|
2543
|
+
}
|
|
2544
|
+
function NiceStateDevtools_Panel({ core, position: defaultPosition = "dock-right", initialOpen = false }) {
|
|
2545
|
+
const [prefs, setPrefsRaw] = useState(() => readPrefs(defaultPosition, initialOpen));
|
|
2546
|
+
const [snapshot, setSnapshot] = useState(EMPTY_SNAPSHOT);
|
|
2547
|
+
const [mode, setMode] = useState("timeline");
|
|
2548
|
+
const [storeFilter, setStoreFilter] = useState(null);
|
|
2549
|
+
const [selectedChangeCuid, setSelectedChangeCuid] = useState(null);
|
|
2550
|
+
useEffect(() => core.subscribe(setSnapshot), [core]);
|
|
2551
|
+
const setPrefs = (update) => {
|
|
2552
|
+
setPrefsRaw((prev) => ({
|
|
2553
|
+
...prev,
|
|
2554
|
+
...update
|
|
2555
|
+
}));
|
|
2556
|
+
};
|
|
2557
|
+
useEffect(() => {
|
|
2558
|
+
const timer = setTimeout(() => writePrefs(prefs), 250);
|
|
2559
|
+
return () => clearTimeout(timer);
|
|
2560
|
+
}, [prefs]);
|
|
2561
|
+
const { stores, changes, paused } = snapshot;
|
|
2562
|
+
const { position, isOpen, dockedHeight, dockedWidth, detailRatio, stayOnLatest, followLatestOnSelect, compressDiff, detailView, diffLatestFirst } = prefs;
|
|
2563
|
+
const dockSide = getDockSide(position);
|
|
2564
|
+
const isHorizDock = dockSide === "top" || dockSide === "bottom";
|
|
2565
|
+
const dockedSize = isHorizDock ? dockedHeight : dockedWidth;
|
|
2566
|
+
const filteredChanges = useMemo(() => storeFilter == null ? changes : changes.filter((c) => c.storeId === storeFilter), [changes, storeFilter]);
|
|
2567
|
+
const groups = useMemo(() => groupChanges(filteredChanges), [filteredChanges]);
|
|
2568
|
+
const selectedChange = useMemo(() => {
|
|
2569
|
+
if (selectedChangeCuid == null) return null;
|
|
2570
|
+
const group = groups.find((g) => g.oldest.cuid === selectedChangeCuid);
|
|
2571
|
+
if (group == null) return changes.find((c) => c.cuid === selectedChangeCuid) ?? null;
|
|
2572
|
+
if (group.count > 1) return {
|
|
2573
|
+
...group.representative,
|
|
2574
|
+
prevSnapshot: group.oldest.prevSnapshot,
|
|
2575
|
+
inversePatches: group.oldest.inversePatches
|
|
2576
|
+
};
|
|
2577
|
+
return group.representative;
|
|
2578
|
+
}, [
|
|
2579
|
+
selectedChangeCuid,
|
|
2580
|
+
groups,
|
|
2581
|
+
changes
|
|
2582
|
+
]);
|
|
2583
|
+
const latestCuid = groups.length > 0 ? groups[0].oldest.cuid : null;
|
|
2584
|
+
useEffect(() => {
|
|
2585
|
+
if (stayOnLatest && latestCuid != null) setSelectedChangeCuid(latestCuid);
|
|
2586
|
+
}, [stayOnLatest, latestCuid]);
|
|
2587
|
+
const activeStore = stores.find((s) => s.id === storeFilter) ?? (stores.length > 0 ? stores[0] : null);
|
|
2588
|
+
const dock = useMemo(() => getDevtoolsDockCoordinator(), []);
|
|
2589
|
+
const panelId = useId();
|
|
2590
|
+
const [, bumpView] = useReducer((n) => n + 1, 0);
|
|
2591
|
+
const badge = changes.length > 0 ? String(changes.length) : void 0;
|
|
2592
|
+
useEffect(() => {
|
|
2593
|
+
const unregister = dock.register({
|
|
2594
|
+
id: panelId,
|
|
2595
|
+
label: "state",
|
|
2596
|
+
icon: "🧩",
|
|
2597
|
+
side: dockSide,
|
|
2598
|
+
size: dockedSize,
|
|
2599
|
+
open: isOpen,
|
|
2600
|
+
badge,
|
|
2601
|
+
onOpen: () => setPrefs({ isOpen: true })
|
|
2602
|
+
});
|
|
2603
|
+
const unsubscribe = dock.subscribe(bumpView);
|
|
2604
|
+
return () => {
|
|
2605
|
+
unregister();
|
|
2606
|
+
unsubscribe();
|
|
2607
|
+
};
|
|
2608
|
+
}, [dock, panelId]);
|
|
2609
|
+
useEffect(() => {
|
|
2610
|
+
dock.update(panelId, {
|
|
2611
|
+
side: dockSide,
|
|
2612
|
+
size: dockedSize,
|
|
2613
|
+
open: isOpen,
|
|
2614
|
+
badge
|
|
2615
|
+
});
|
|
2616
|
+
}, [
|
|
2617
|
+
dock,
|
|
2618
|
+
panelId,
|
|
2619
|
+
dockSide,
|
|
2620
|
+
dockedSize,
|
|
2621
|
+
isOpen,
|
|
2622
|
+
badge
|
|
2623
|
+
]);
|
|
2624
|
+
const view = dock.getView(panelId);
|
|
2625
|
+
const baseStyle = {
|
|
2626
|
+
position: "fixed",
|
|
2627
|
+
zIndex: 2147483646,
|
|
2628
|
+
fontFamily: "ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace",
|
|
2629
|
+
fontSize: "12px"
|
|
2630
|
+
};
|
|
2631
|
+
if (!isOpen) {
|
|
2632
|
+
if (view.isPrimary && !view.anyOpen) return /* @__PURE__ */ jsx(DevtoolsLauncher, { items: view.devtools });
|
|
2633
|
+
return null;
|
|
2634
|
+
}
|
|
2635
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2636
|
+
id: "__nice-state-devtools-panel",
|
|
2637
|
+
style: {
|
|
2638
|
+
...baseStyle,
|
|
2639
|
+
background: DEVTOOL_LIST_BASE_BACKGROUND,
|
|
2640
|
+
border: `1px solid ${DEVTOOL_PANEL_BORDER}`,
|
|
2641
|
+
color: DEVTOOL_COLOR_TEXT_SECONDARY,
|
|
2642
|
+
display: "flex",
|
|
2643
|
+
flexDirection: "column",
|
|
2644
|
+
boxShadow: "0 -4px 24px rgba(0,0,0,0.4)",
|
|
2645
|
+
overflow: "hidden",
|
|
2646
|
+
...dockSide === "bottom" ? {
|
|
2647
|
+
bottom: view.dockOffset,
|
|
2648
|
+
left: 0,
|
|
2649
|
+
right: 0,
|
|
2650
|
+
height: `${dockedSize}px`,
|
|
2651
|
+
borderRadius: view.stacked ? "0" : "8px 8px 0 0"
|
|
2652
|
+
} : dockSide === "top" ? {
|
|
2653
|
+
top: view.dockOffset,
|
|
2654
|
+
left: 0,
|
|
2655
|
+
right: 0,
|
|
2656
|
+
height: `${dockedSize}px`,
|
|
2657
|
+
borderRadius: view.stacked ? "0" : "0 0 8px 8px"
|
|
2658
|
+
} : dockSide === "left" ? {
|
|
2659
|
+
top: 0,
|
|
2660
|
+
left: view.dockOffset,
|
|
2661
|
+
bottom: 0,
|
|
2662
|
+
width: `${dockedSize}px`,
|
|
2663
|
+
borderRadius: view.stacked ? "0" : "0 8px 8px 0"
|
|
2664
|
+
} : {
|
|
2665
|
+
top: 0,
|
|
2666
|
+
right: view.dockOffset,
|
|
2667
|
+
bottom: 0,
|
|
2668
|
+
width: `${dockedSize}px`,
|
|
2669
|
+
borderRadius: view.stacked ? "0" : "8px 0 0 8px"
|
|
2670
|
+
}
|
|
2671
|
+
},
|
|
2672
|
+
children: [
|
|
2673
|
+
/* @__PURE__ */ jsx(ResizeHandle, {
|
|
2674
|
+
dockSide,
|
|
2675
|
+
dockedSize,
|
|
2676
|
+
onChange: (size) => setPrefs(isHorizDock ? { dockedHeight: size } : { dockedWidth: size })
|
|
2677
|
+
}),
|
|
2678
|
+
/* @__PURE__ */ jsx(PanelHeader, {
|
|
2679
|
+
title: "🧩 state",
|
|
2680
|
+
position,
|
|
2681
|
+
onPositionChange: (p) => setPrefs({ position: p }),
|
|
2682
|
+
onClose: () => setPrefs({ isOpen: false }),
|
|
2683
|
+
onClear: changes.length > 0 ? () => {
|
|
2684
|
+
core.clear();
|
|
2685
|
+
setSelectedChangeCuid(null);
|
|
2686
|
+
} : void 0,
|
|
2687
|
+
paused,
|
|
2688
|
+
onTogglePause: () => core.togglePaused(),
|
|
2689
|
+
openOthers: view.otherClosed,
|
|
2690
|
+
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
2691
|
+
options: [{
|
|
2692
|
+
value: "timeline",
|
|
2693
|
+
label: "Timeline"
|
|
2694
|
+
}, {
|
|
2695
|
+
value: "state",
|
|
2696
|
+
label: "State"
|
|
2697
|
+
}],
|
|
2698
|
+
value: mode,
|
|
2699
|
+
onChange: setMode
|
|
2700
|
+
})
|
|
2701
|
+
}),
|
|
2702
|
+
/* @__PURE__ */ jsx(StoreTabs, {
|
|
2703
|
+
stores,
|
|
2704
|
+
selectedStoreId: mode === "state" ? activeStore?.id ?? null : storeFilter,
|
|
2705
|
+
onSelect: setStoreFilter,
|
|
2706
|
+
includeAll: mode === "timeline"
|
|
2707
|
+
}),
|
|
2708
|
+
mode === "state" ? activeStore != null ? /* @__PURE__ */ jsx(StateInspector, {
|
|
2709
|
+
store: activeStore,
|
|
2710
|
+
onApply: (id, next) => core.applyEdit(id, next)
|
|
2711
|
+
}) : /* @__PURE__ */ jsx(EmptyMessage, { children: "No stores registered. Call core.registerStore(...)." }) : /* @__PURE__ */ jsxs("div", {
|
|
2712
|
+
style: {
|
|
2713
|
+
flex: 1,
|
|
2714
|
+
display: "flex",
|
|
2715
|
+
flexDirection: isHorizDock ? "row" : "column",
|
|
2716
|
+
overflow: "hidden",
|
|
2717
|
+
minHeight: 0
|
|
2718
|
+
},
|
|
2719
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2720
|
+
style: {
|
|
2721
|
+
flexGrow: selectedChange != null ? 1 - detailRatio : 1,
|
|
2722
|
+
flexShrink: 1,
|
|
2723
|
+
flexBasis: 0,
|
|
2724
|
+
minWidth: 0,
|
|
2725
|
+
minHeight: 0,
|
|
2726
|
+
display: "flex",
|
|
2727
|
+
flexDirection: "column",
|
|
2728
|
+
overflow: "hidden"
|
|
2729
|
+
},
|
|
2730
|
+
children: [/* @__PURE__ */ jsx(FollowLatestToggles, {
|
|
2731
|
+
noun: "change",
|
|
2732
|
+
stayOnLatest,
|
|
2733
|
+
onStayOnLatestChange: (next) => setPrefs({ stayOnLatest: next }),
|
|
2734
|
+
followLatestOnSelect,
|
|
2735
|
+
onFollowLatestOnSelectChange: (next) => {
|
|
2736
|
+
if (next && latestCuid != null && selectedChangeCuid === latestCuid && !stayOnLatest) setPrefs({
|
|
2737
|
+
followLatestOnSelect: next,
|
|
2738
|
+
stayOnLatest: true
|
|
2739
|
+
});
|
|
2740
|
+
else setPrefs({ followLatestOnSelect: next });
|
|
2741
|
+
}
|
|
2742
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2743
|
+
style: {
|
|
2744
|
+
flex: 1,
|
|
2745
|
+
minHeight: 0,
|
|
2746
|
+
display: "flex",
|
|
2747
|
+
flexDirection: "column"
|
|
2748
|
+
},
|
|
2749
|
+
children: /* @__PURE__ */ jsx(ChangeList, {
|
|
2750
|
+
style: {
|
|
2751
|
+
flex: 1,
|
|
2752
|
+
minHeight: 0,
|
|
2753
|
+
overflowY: "auto"
|
|
2754
|
+
},
|
|
2755
|
+
groups,
|
|
2756
|
+
selectedCuid: selectedChangeCuid,
|
|
2757
|
+
onSelect: (cuid) => {
|
|
2758
|
+
const next = selectedChangeCuid === cuid ? null : cuid;
|
|
2759
|
+
if (next != null && next === latestCuid && followLatestOnSelect) {
|
|
2760
|
+
if (!stayOnLatest) setPrefs({ stayOnLatest: true });
|
|
2761
|
+
} else if (stayOnLatest) setPrefs({ stayOnLatest: false });
|
|
2762
|
+
setSelectedChangeCuid(next);
|
|
2763
|
+
},
|
|
2764
|
+
showStore: storeFilter == null
|
|
2765
|
+
})
|
|
2766
|
+
})]
|
|
2767
|
+
}), selectedChange != null && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SplitHandle, {
|
|
2768
|
+
horizontal: isHorizDock,
|
|
2769
|
+
onRatioChange: (ratio) => setPrefs({ detailRatio: ratio })
|
|
2770
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2771
|
+
style: {
|
|
2772
|
+
flexGrow: detailRatio,
|
|
2773
|
+
flexShrink: 1,
|
|
2774
|
+
flexBasis: 0,
|
|
2775
|
+
minWidth: 0,
|
|
2776
|
+
minHeight: 0,
|
|
2777
|
+
display: "flex",
|
|
2778
|
+
flexDirection: "column",
|
|
2779
|
+
overflow: "hidden",
|
|
2780
|
+
...isHorizDock ? {
|
|
2781
|
+
borderLeft: `1px solid #1d3352`,
|
|
2782
|
+
boxShadow: "inset 18px 0 36px -14px rgba(0,0,0,0.8)"
|
|
2783
|
+
} : {
|
|
2784
|
+
borderTop: `1px solid #1d3352`,
|
|
2785
|
+
boxShadow: "inset 0 18px 36px -14px rgba(0,0,0,0.8)"
|
|
2786
|
+
}
|
|
2787
|
+
},
|
|
2788
|
+
children: /* @__PURE__ */ jsx(ChangeDetailPanel, {
|
|
2789
|
+
change: selectedChange,
|
|
2790
|
+
onRevert: (c) => core.revertChange(c),
|
|
2791
|
+
view: detailView,
|
|
2792
|
+
onViewChange: (v) => setPrefs({ detailView: v }),
|
|
2793
|
+
compress: compressDiff,
|
|
2794
|
+
onCompressChange: (v) => setPrefs({ compressDiff: v }),
|
|
2795
|
+
latestFirst: diffLatestFirst,
|
|
2796
|
+
onLatestFirstChange: (v) => setPrefs({ diffLatestFirst: v })
|
|
2797
|
+
})
|
|
2798
|
+
})] })]
|
|
2799
|
+
})
|
|
2800
|
+
]
|
|
2801
|
+
});
|
|
1829
2802
|
}
|
|
1830
2803
|
function EmptyMessage({ children }) {
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2804
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2805
|
+
style: {
|
|
2806
|
+
flex: 1,
|
|
2807
|
+
display: "flex",
|
|
2808
|
+
alignItems: "center",
|
|
2809
|
+
justifyContent: "center",
|
|
2810
|
+
padding: "24px",
|
|
2811
|
+
textAlign: "center",
|
|
2812
|
+
color: DEVTOOL_COLOR_TEXT_MUTED,
|
|
2813
|
+
fontSize: "11px"
|
|
2814
|
+
},
|
|
2815
|
+
children
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
//#endregion
|
|
2819
|
+
export { NiceStateDevtools, StateDevtoolsCore };
|
|
2820
|
+
|
|
2821
|
+
//# sourceMappingURL=index.js.map
|