@fluixi/devtools 0.2.0-alpha.2 → 0.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -2
- package/dist/callsite.d.ts +22 -0
- package/dist/callsite.d.ts.map +1 -0
- package/dist/callsite.js +82 -0
- package/dist/change-view.d.ts +10 -0
- package/dist/change-view.d.ts.map +1 -0
- package/dist/change-view.js +102 -0
- package/dist/copy.d.ts +9 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +52 -0
- package/dist/describe.d.ts +3 -0
- package/dist/describe.d.ts.map +1 -0
- package/dist/describe.js +47 -0
- package/dist/detail-panel.d.ts +17 -0
- package/dist/detail-panel.d.ts.map +1 -0
- package/dist/detail-panel.js +217 -0
- package/dist/diff.d.ts +31 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +132 -0
- package/dist/dom.d.ts +81 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +269 -0
- package/dist/export.d.ts +55 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +90 -0
- package/dist/float.d.ts +54 -0
- package/dist/float.d.ts.map +1 -0
- package/dist/float.js +256 -0
- package/dist/format.d.ts +16 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +188 -0
- package/dist/graph-view.d.ts +79 -2
- package/dist/graph-view.d.ts.map +1 -1
- package/dist/graph-view.js +866 -50
- package/dist/highlight.d.ts +56 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/highlight.js +154 -0
- package/dist/history.d.ts +59 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +85 -0
- package/dist/hook.d.ts +60 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +142 -0
- package/dist/index.d.ts +44 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/inspector.d.ts +60 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +415 -0
- package/dist/instrument.d.ts +147 -5
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +164 -44
- package/dist/kind-filter.d.ts +39 -0
- package/dist/kind-filter.d.ts.map +1 -0
- package/dist/kind-filter.js +151 -0
- package/dist/menu.d.ts +37 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +125 -0
- package/dist/observe.d.ts +80 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +893 -0
- package/dist/palette.d.ts +11 -0
- package/dist/palette.d.ts.map +1 -0
- package/dist/palette.js +27 -0
- package/dist/path.d.ts +19 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +49 -0
- package/dist/plugin.d.ts +121 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +74 -0
- package/dist/plugins/context.d.ts +10 -0
- package/dist/plugins/context.d.ts.map +1 -0
- package/dist/plugins/context.js +80 -0
- package/dist/plugins/directives.d.ts +25 -0
- package/dist/plugins/directives.d.ts.map +1 -0
- package/dist/plugins/directives.js +78 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +9 -0
- package/dist/source.d.ts +72 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +153 -0
- package/dist/timeline.d.ts +30 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +271 -0
- package/dist/tree.d.ts +128 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +645 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/value-pane.d.ts +10 -0
- package/dist/value-pane.d.ts.map +1 -0
- package/dist/value-pane.js +61 -0
- package/dist/wire.d.ts +100 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +93 -0
- package/package.json +7 -2
package/dist/dom.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/** Kept in step with LOC_TAG in @fluixi/dom's runtime.ts. */
|
|
2
|
+
const LOC_TAG = '__fx_loc';
|
|
3
|
+
/** The position the runtime stamped on an element, when there is one. */
|
|
4
|
+
function locationOf(element) {
|
|
5
|
+
return element[LOC_TAG];
|
|
6
|
+
}
|
|
7
|
+
/** Elements described per tag. Caps how large a snapshot can get. */
|
|
8
|
+
export const DOM_BUDGET = 200;
|
|
9
|
+
/**
|
|
10
|
+
* Static attributes only: a class computed at runtime is not in the source.
|
|
11
|
+
*
|
|
12
|
+
* Written out rather than spread-and-join. This runs once per element of the page on every
|
|
13
|
+
* sample, and building three throwaway arrays to read at most two class names cost more
|
|
14
|
+
* than everything else the walk does.
|
|
15
|
+
*/
|
|
16
|
+
export function describeElement(element) {
|
|
17
|
+
let out = element.tagName.toLowerCase();
|
|
18
|
+
if (element.id)
|
|
19
|
+
out += `#${element.id}`;
|
|
20
|
+
const classes = element.classList;
|
|
21
|
+
if (classes.length > 0) {
|
|
22
|
+
out += `.${classes[0]}`;
|
|
23
|
+
if (classes.length > 1)
|
|
24
|
+
out += `.${classes[1]}`;
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Describe what every tag rendered, in one pass.
|
|
30
|
+
*
|
|
31
|
+
* Per-tag would rebuild the element maps each time, which is enough work to stall a render
|
|
32
|
+
* loop on a page of any size.
|
|
33
|
+
*/
|
|
34
|
+
export function describeAll(graph,
|
|
35
|
+
/** What a tag returned. Must not fall back to anything, or two tags claim one element. */
|
|
36
|
+
roots,
|
|
37
|
+
/** Where a value writes. */
|
|
38
|
+
writtenBy, kinds,
|
|
39
|
+
/** What plugins attached to an element, when any did. */
|
|
40
|
+
extraFor) {
|
|
41
|
+
const owners = new Map();
|
|
42
|
+
const writers = new Map();
|
|
43
|
+
const rendered = new Map();
|
|
44
|
+
for (const node of graph.values()) {
|
|
45
|
+
if (kinds.has(node.kind)) {
|
|
46
|
+
const found = roots(node.id);
|
|
47
|
+
rendered.set(node.id, found);
|
|
48
|
+
for (const element of found)
|
|
49
|
+
if (!owners.has(element))
|
|
50
|
+
owners.set(element, node.id);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
for (const element of writtenBy(node.id)) {
|
|
54
|
+
const held = writers.get(element);
|
|
55
|
+
if (held)
|
|
56
|
+
held.push(node.id);
|
|
57
|
+
else
|
|
58
|
+
writers.set(element, [node.id]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const { claimed, containers } = claimOrphans(graph, rendered, writtenBy, kinds, owners);
|
|
62
|
+
const claims = new Map();
|
|
63
|
+
for (const [element, id] of claimed) {
|
|
64
|
+
const held = claims.get(id);
|
|
65
|
+
if (held)
|
|
66
|
+
held.push(element);
|
|
67
|
+
else
|
|
68
|
+
claims.set(id, [element]);
|
|
69
|
+
}
|
|
70
|
+
const landing = landings(graph, rendered, writtenBy, kinds, claimed, containers);
|
|
71
|
+
const out = new Map();
|
|
72
|
+
for (const node of graph.values()) {
|
|
73
|
+
if (!kinds.has(node.kind))
|
|
74
|
+
continue;
|
|
75
|
+
const budget = { left: DOM_BUDGET };
|
|
76
|
+
const walk = (element) => {
|
|
77
|
+
const owner = owners.get(element) ?? claimed.get(element);
|
|
78
|
+
// Another tag starts here, so stop. Its DOM goes under its own row.
|
|
79
|
+
const where = locationOf(element);
|
|
80
|
+
if (owner !== undefined && owner !== node.id) {
|
|
81
|
+
return { label: describeElement(element), children: [], owner, element, ...(where ? { at: where } : {}) };
|
|
82
|
+
}
|
|
83
|
+
// The element the application was mounted into. It goes above the tag rather than
|
|
84
|
+
// inside it, and what it holds is reached through the tags themselves, so the walk
|
|
85
|
+
// stops rather than describing the whole application a second time.
|
|
86
|
+
if (containers.has(element)) {
|
|
87
|
+
return { label: describeElement(element), children: [], container: true, element, ...(where ? { at: where } : {}) };
|
|
88
|
+
}
|
|
89
|
+
const children = [];
|
|
90
|
+
// Read once: `children` is live, so indexing it in the condition re-resolves the
|
|
91
|
+
// collection on every step and a parent with a thousand of them goes quadratic.
|
|
92
|
+
const kids = element.children;
|
|
93
|
+
for (let i = 0, n = kids.length; i < n && budget.left > 0; i++) {
|
|
94
|
+
budget.left -= 1;
|
|
95
|
+
children.push(walk(kids[i]));
|
|
96
|
+
}
|
|
97
|
+
const writes = writers.get(element);
|
|
98
|
+
const extra = extraFor?.(element);
|
|
99
|
+
// Only tags nested inside this one. A tag further out writes through this element on
|
|
100
|
+
// its way down, and putting it here would nest it under its own child.
|
|
101
|
+
const inside = landing.get(element)?.filter((id) => within(graph, id, node.id));
|
|
102
|
+
return {
|
|
103
|
+
label: describeElement(element),
|
|
104
|
+
children,
|
|
105
|
+
element,
|
|
106
|
+
...(where ? { at: where } : {}),
|
|
107
|
+
...(writes?.length ? { writes } : {}),
|
|
108
|
+
...(inside?.length ? { inside } : {}),
|
|
109
|
+
...(extra ? { extra } : {}),
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
out.set(node.id, [...(rendered.get(node.id) ?? []), ...(claims.get(node.id) ?? [])].map(walk));
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** Whether `id` sits under `ancestor` in the component nesting. */
|
|
117
|
+
function within(graph, id, ancestor) {
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
for (let at = graph.get(id)?.createdIn; at !== undefined && !seen.has(at); at = graph.get(at)?.createdIn) {
|
|
120
|
+
if (at === ancestor)
|
|
121
|
+
return true;
|
|
122
|
+
seen.add(at);
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Where a tag that rendered nothing of its own goes: the shallowest element it writes into.
|
|
128
|
+
* A tag writes to its own insertion point and, through whatever it renders, to everything
|
|
129
|
+
* below that, so the shallowest is the one it is responsible for.
|
|
130
|
+
*
|
|
131
|
+
* Where several land on the same element only the outermost is kept, because the rest come
|
|
132
|
+
* with it as its children.
|
|
133
|
+
*/
|
|
134
|
+
function landings(graph, rendered, writtenBy, kinds, claimed, containers) {
|
|
135
|
+
const claimers = new Set(claimed.values());
|
|
136
|
+
const landing = new Map();
|
|
137
|
+
for (const node of graph.values()) {
|
|
138
|
+
if (!kinds.has(node.kind) || (rendered.get(node.id) ?? []).length)
|
|
139
|
+
continue;
|
|
140
|
+
if (claimers.has(node.id))
|
|
141
|
+
continue;
|
|
142
|
+
let shallowest;
|
|
143
|
+
let best = Infinity;
|
|
144
|
+
for (const element of writtenBy(node.id)) {
|
|
145
|
+
// The mount container holds the whole application; landing a tag in it would put that
|
|
146
|
+
// tag beside the root instead of under it.
|
|
147
|
+
if (containers.has(element))
|
|
148
|
+
continue;
|
|
149
|
+
const depth = depthInDocument(element);
|
|
150
|
+
if (depth < best) {
|
|
151
|
+
best = depth;
|
|
152
|
+
shallowest = element;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!shallowest)
|
|
156
|
+
continue;
|
|
157
|
+
const held = landing.get(shallowest);
|
|
158
|
+
if (held)
|
|
159
|
+
held.push(node.id);
|
|
160
|
+
else
|
|
161
|
+
landing.set(shallowest, [node.id]);
|
|
162
|
+
}
|
|
163
|
+
for (const [element, ids] of landing) {
|
|
164
|
+
landing.set(element, ids.filter((id) => !ids.some((other) => other !== id && within(graph, id, other))));
|
|
165
|
+
}
|
|
166
|
+
return landing;
|
|
167
|
+
}
|
|
168
|
+
/** How deep a tag sits in the component nesting, following what created it. */
|
|
169
|
+
export function tagDepths(graph, kinds) {
|
|
170
|
+
const depth = new Map();
|
|
171
|
+
const of = (id, seen) => {
|
|
172
|
+
const held = depth.get(id);
|
|
173
|
+
if (held !== undefined)
|
|
174
|
+
return held;
|
|
175
|
+
if (seen.has(id))
|
|
176
|
+
return 0;
|
|
177
|
+
seen.add(id);
|
|
178
|
+
const parent = graph.get(id)?.createdIn;
|
|
179
|
+
const value = parent === undefined ? 0 : of(parent, seen) + 1;
|
|
180
|
+
depth.set(id, value);
|
|
181
|
+
return value;
|
|
182
|
+
};
|
|
183
|
+
for (const node of graph.values())
|
|
184
|
+
if (kinds.has(node.kind))
|
|
185
|
+
of(node.id, new Set());
|
|
186
|
+
return depth;
|
|
187
|
+
}
|
|
188
|
+
function depthInDocument(element) {
|
|
189
|
+
let steps = 0;
|
|
190
|
+
for (let at = element.parentElement; at; at = at.parentElement)
|
|
191
|
+
steps++;
|
|
192
|
+
return steps;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Where to put a tag that returned no element of its own, one whose body is another
|
|
196
|
+
* component, or a boundary like Suspense. What it writes into is the only evidence of where
|
|
197
|
+
* its content went.
|
|
198
|
+
*
|
|
199
|
+
* Two rules keep this from doing damage. Only elements with no owned ancestor are taken: one
|
|
200
|
+
* below an owned element is already inside that tag's tree, and claiming it there would cut
|
|
201
|
+
* the tree in half at an arbitrary point. And where several tags write to the same place, the
|
|
202
|
+
* innermost wins. Every tag on the path from the root writes through to the leaves, so the
|
|
203
|
+
* outermost is always a match and never the interesting one.
|
|
204
|
+
*/
|
|
205
|
+
function claimOrphans(graph, rendered, writtenBy, kinds, owners) {
|
|
206
|
+
const depth = tagDepths(graph, kinds);
|
|
207
|
+
// An element holding something a tag rendered is a wrapper the application was put into,
|
|
208
|
+
// not content a tag produced. The outermost tag writing there is the one it belongs to;
|
|
209
|
+
// for anything else it is the innermost.
|
|
210
|
+
const wraps = (element) => {
|
|
211
|
+
for (const owned of owners.keys())
|
|
212
|
+
if (element !== owned && element.contains(owned))
|
|
213
|
+
return true;
|
|
214
|
+
return false;
|
|
215
|
+
};
|
|
216
|
+
const writing = new Map();
|
|
217
|
+
for (const node of graph.values()) {
|
|
218
|
+
if (!kinds.has(node.kind) || (rendered.get(node.id) ?? []).length)
|
|
219
|
+
continue;
|
|
220
|
+
for (const element of writtenBy(node.id)) {
|
|
221
|
+
if (owners.has(element))
|
|
222
|
+
continue;
|
|
223
|
+
const held = writing.get(element);
|
|
224
|
+
if (held)
|
|
225
|
+
held.push(node.id);
|
|
226
|
+
else
|
|
227
|
+
writing.set(element, [node.id]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const claimed = new Map();
|
|
231
|
+
const containers = new Set();
|
|
232
|
+
const covered = (element) => {
|
|
233
|
+
for (let at = element.parentElement; at; at = at.parentElement) {
|
|
234
|
+
if (owners.has(at) || claimed.has(at))
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
};
|
|
239
|
+
// Shallowest first, so each one is only tested against ancestors already decided.
|
|
240
|
+
const order = [...writing.keys()].sort((a, b) => depthInDocument(a) - depthInDocument(b));
|
|
241
|
+
for (const element of order) {
|
|
242
|
+
if (covered(element))
|
|
243
|
+
continue;
|
|
244
|
+
const ids = writing.get(element);
|
|
245
|
+
const container = wraps(element);
|
|
246
|
+
const pick = ids.reduce((best, id) => {
|
|
247
|
+
const a = depth.get(id) ?? 0;
|
|
248
|
+
const b = depth.get(best) ?? 0;
|
|
249
|
+
return container ? (a < b ? id : best) : a > b ? id : best;
|
|
250
|
+
});
|
|
251
|
+
claimed.set(element, pick);
|
|
252
|
+
if (container)
|
|
253
|
+
containers.add(element);
|
|
254
|
+
}
|
|
255
|
+
return { claimed, containers };
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Drop the Elements so the result can be serialised.
|
|
259
|
+
*
|
|
260
|
+
* Everything else is copied as-is rather than field by field. The copy that describes and the
|
|
261
|
+
* copy that strips are built separately, since an extension bundles its own, so a list of fields
|
|
262
|
+
* here goes stale against a description made elsewhere, and the field just disappears.
|
|
263
|
+
*/
|
|
264
|
+
export function withoutElements(nodes) {
|
|
265
|
+
return nodes.map(({ element, children, ...rest }) => ({
|
|
266
|
+
...rest,
|
|
267
|
+
children: withoutElements(children),
|
|
268
|
+
}));
|
|
269
|
+
}
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph as something to paste somewhere else.
|
|
3
|
+
*
|
|
4
|
+
* A panel shows one node at a time; a bug report needs all of them at once. This is the whole
|
|
5
|
+
* graph as plain json — values, kinds, ownership, sources, what each node reaches in the
|
|
6
|
+
* document and where it is written from.
|
|
7
|
+
*
|
|
8
|
+
* Names rather than ids wherever a reader has to follow a reference: `deps: ["count"]` can be
|
|
9
|
+
* read, `deps: [3]` has to be looked up. Ids stay on the nodes, and a name shared by two nodes
|
|
10
|
+
* is disambiguated so the export is never ambiguous.
|
|
11
|
+
*/
|
|
12
|
+
import { type RNode } from './instrument.js';
|
|
13
|
+
import type { GraphEvent } from './history.js';
|
|
14
|
+
export interface ExportOptions {
|
|
15
|
+
/** How much of each value to include. */
|
|
16
|
+
limit?: number;
|
|
17
|
+
/** The history, when the host has one. */
|
|
18
|
+
events?: readonly GraphEvent[];
|
|
19
|
+
/** Says the graph was read from source rather than from a running application. */
|
|
20
|
+
fromSource?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface ExportedNode {
|
|
23
|
+
id: number;
|
|
24
|
+
label: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
/** Absent when the framework named it — see RNode.authored. */
|
|
27
|
+
authored?: boolean;
|
|
28
|
+
value?: string;
|
|
29
|
+
runs?: number;
|
|
30
|
+
version?: number;
|
|
31
|
+
state?: string;
|
|
32
|
+
reads?: string[];
|
|
33
|
+
writes?: string[];
|
|
34
|
+
madeIn?: string;
|
|
35
|
+
source?: string;
|
|
36
|
+
declaredAt?: string;
|
|
37
|
+
usedAt?: string;
|
|
38
|
+
/** Where it reaches the document, with the line each binding was written on. */
|
|
39
|
+
updates?: string[];
|
|
40
|
+
/** Where it is written from, outside any computation. */
|
|
41
|
+
setAt?: string[];
|
|
42
|
+
}
|
|
43
|
+
export interface ExportedGraph {
|
|
44
|
+
t: number;
|
|
45
|
+
nodes: ExportedNode[];
|
|
46
|
+
/** Present only when the host passed a history. */
|
|
47
|
+
events?: GraphEvent[];
|
|
48
|
+
from: 'source' | 'running application';
|
|
49
|
+
}
|
|
50
|
+
/** The graph as a plain object. */
|
|
51
|
+
export declare function exportGraph(graph: Map<number, RNode>, options?: ExportOptions): ExportedGraph;
|
|
52
|
+
/** The same thing as text, indented so it can be read where it is pasted. */
|
|
53
|
+
export declare function graphToJson(graph: Map<number, RNode>, options?: ExportOptions): string;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAA0B,KAAK,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAErE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,kFAAkF;IAClF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAsBD,MAAM,WAAW,aAAa;IAC5B,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,mDAAmD;IACnD,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;IACtB,IAAI,EAAE,QAAQ,GAAG,qBAAqB,CAAC;CACxC;AAED,mCAAmC;AACnC,wBAAgB,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAE,aAAkB,GAAG,aAAa,CAoDjG;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAE1F"}
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph as something to paste somewhere else.
|
|
3
|
+
*
|
|
4
|
+
* A panel shows one node at a time; a bug report needs all of them at once. This is the whole
|
|
5
|
+
* graph as plain json — values, kinds, ownership, sources, what each node reaches in the
|
|
6
|
+
* document and where it is written from.
|
|
7
|
+
*
|
|
8
|
+
* Names rather than ids wherever a reader has to follow a reference: `deps: ["count"]` can be
|
|
9
|
+
* read, `deps: [3]` has to be looked up. Ids stay on the nodes, and a name shared by two nodes
|
|
10
|
+
* is disambiguated so the export is never ambiguous.
|
|
11
|
+
*/
|
|
12
|
+
import { nodeState, nodeVersion } from './instrument.js';
|
|
13
|
+
import { formatValue } from './format.js';
|
|
14
|
+
const at = (source) => {
|
|
15
|
+
if (!source)
|
|
16
|
+
return undefined;
|
|
17
|
+
const where = `${source.file ?? ''}:${source.line ?? ''}:${source.column ?? ''}`;
|
|
18
|
+
// Both when they differ: a template's holes share one original position.
|
|
19
|
+
const out = source.compiled;
|
|
20
|
+
const same = out?.line === source.line && out?.column === source.column;
|
|
21
|
+
return out && !same ? `${where} (out ${out.line ?? ''}:${out.column ?? ''})` : where;
|
|
22
|
+
};
|
|
23
|
+
/** A name, made unique when two nodes share it. */
|
|
24
|
+
function naming(graph) {
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
for (const node of graph.values())
|
|
27
|
+
seen.set(node.label, (seen.get(node.label) ?? 0) + 1);
|
|
28
|
+
return (id) => {
|
|
29
|
+
const node = graph.get(id);
|
|
30
|
+
if (!node)
|
|
31
|
+
return `#${id}`;
|
|
32
|
+
return (seen.get(node.label) ?? 0) > 1 ? `${node.label}#${node.id}` : node.label;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** The graph as a plain object. */
|
|
36
|
+
export function exportGraph(graph, options = {}) {
|
|
37
|
+
const limit = options.limit ?? 400;
|
|
38
|
+
const name = naming(graph);
|
|
39
|
+
const nodes = [...graph.values()].map((node) => {
|
|
40
|
+
let value = node.detail ?? node.value;
|
|
41
|
+
if (node.read) {
|
|
42
|
+
try {
|
|
43
|
+
value = formatValue(node.read(), limit);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// A reader that throws is still a node worth exporting.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const feeds = [...node.deps].map(name);
|
|
50
|
+
const wrote = [...node.writes].map(name);
|
|
51
|
+
const state = nodeState(node);
|
|
52
|
+
return {
|
|
53
|
+
id: node.id,
|
|
54
|
+
label: node.label,
|
|
55
|
+
kind: node.kind,
|
|
56
|
+
...(node.authored === false ? { authored: false } : {}),
|
|
57
|
+
...(value ? { value } : {}),
|
|
58
|
+
...(node.runs ? { runs: node.runs } : {}),
|
|
59
|
+
// Only a node with a core has one; a static node would report a version of 0.
|
|
60
|
+
...(node.core?.version !== undefined ? { version: nodeVersion(node) } : {}),
|
|
61
|
+
...(state ? { state } : {}),
|
|
62
|
+
...(feeds.length ? { reads: feeds } : {}),
|
|
63
|
+
...(wrote.length ? { writes: wrote } : {}),
|
|
64
|
+
...(node.createdIn !== undefined ? { madeIn: name(node.createdIn) } : {}),
|
|
65
|
+
...(at(node.source) ? { source: at(node.source) } : {}),
|
|
66
|
+
...(at(node.declaredAt) ? { declaredAt: at(node.declaredAt) } : {}),
|
|
67
|
+
...(at(node.usedAt) ? { usedAt: at(node.usedAt) } : {}),
|
|
68
|
+
...(node.targets?.length
|
|
69
|
+
? {
|
|
70
|
+
updates: node.targets.map((target) => {
|
|
71
|
+
const what = target.name ? `${target.name} of ${target.element}` : target.element;
|
|
72
|
+
const where = at(target.source);
|
|
73
|
+
return where ? `${what} — ${where}` : what;
|
|
74
|
+
}),
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
77
|
+
...(node.setAt?.length ? { setAt: node.setAt.map((s) => at(s)).filter(Boolean) } : {}),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
t: Date.now(),
|
|
82
|
+
from: options.fromSource ? 'source' : 'running application',
|
|
83
|
+
nodes,
|
|
84
|
+
...(options.events?.length ? { events: [...options.events] } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** The same thing as text, indented so it can be read where it is pasted. */
|
|
88
|
+
export function graphToJson(graph, options = {}) {
|
|
89
|
+
return JSON.stringify(exportGraph(graph, options), null, 2);
|
|
90
|
+
}
|
package/dist/float.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taking a pane out of the layout and putting it back.
|
|
3
|
+
*
|
|
4
|
+
* The inspector, the timeline and the tree each live in a fixed corner of the panel. Reading
|
|
5
|
+
* one of them properly means giving it room, and the room has to come from the others. So
|
|
6
|
+
* each carries a button that lifts it into a card over everything, and the same button puts
|
|
7
|
+
* it back exactly where it was.
|
|
8
|
+
*
|
|
9
|
+
* The pane element itself is moved rather than re-rendered. Whatever drew into it keeps its
|
|
10
|
+
* handle on it, its listeners, and its scroll position — a floated timeline is the same
|
|
11
|
+
* timeline, not a copy of one.
|
|
12
|
+
*/
|
|
13
|
+
export interface FloatOptions {
|
|
14
|
+
/** Shown on the card's bar, and on the button that lifts it. */
|
|
15
|
+
title: string;
|
|
16
|
+
/** Where a card opens the first time. Defaults to a cascade from the top left. */
|
|
17
|
+
at?: {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
};
|
|
23
|
+
/** Told each way round, for a host that wants to remember or relayout. */
|
|
24
|
+
onChange?: (floating: boolean) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface Floatable {
|
|
27
|
+
readonly floating: boolean;
|
|
28
|
+
/** Lift it out, or put it back. Returns what it now is. */
|
|
29
|
+
toggle(): boolean;
|
|
30
|
+
dock(): void;
|
|
31
|
+
destroy(): void;
|
|
32
|
+
}
|
|
33
|
+
/** A card the reader can move and resize, with the pane's own content inside it. */
|
|
34
|
+
export interface FloatCard {
|
|
35
|
+
readonly card: HTMLElement;
|
|
36
|
+
/** The bar along the top, for a title or a close button. */
|
|
37
|
+
readonly bar: HTMLElement;
|
|
38
|
+
remove(): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The moving and resizing, on its own.
|
|
42
|
+
*
|
|
43
|
+
* Both a floated pane and the value pane want the same card, and writing the drag twice is
|
|
44
|
+
* how two of them end up behaving differently.
|
|
45
|
+
*/
|
|
46
|
+
export declare function floatCard(doc: Document, title: string, at?: {
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
}): FloatCard;
|
|
52
|
+
export declare function makeFloatable(pane: HTMLElement, options: FloatOptions): Floatable;
|
|
53
|
+
export declare const FLOAT_CSS = "\n.fx-floatable { position: relative; }\n.fx-float-btn {\n position: absolute; top: 2px; right: 2px; z-index: 5;\n width: 18px; height: 18px; padding: 0; line-height: 1;\n font-size: 11px; color: var(--fx-muted, #8a90a2);\n background: var(--fx-panel, #14161c);\n border: 1px solid var(--fx-line, #262a35); border-radius: 4px;\n cursor: pointer; opacity: 0; transition: opacity .12s;\n}\n.fx-floatable:hover .fx-float-btn, .fx-float-btn:focus-visible { opacity: 1; }\n.fx-float-btn:hover { color: var(--fx-accent, #5eead4); border-color: var(--fx-accent, #5eead4); }\n\n.fx-float {\n position: fixed; z-index: 60; display: flex; flex-direction: column;\n background: var(--fx-panel, #14161c);\n border: 1px solid var(--fx-line, #262a35); border-radius: 8px;\n box-shadow: 0 12px 34px rgba(0, 0, 0, .5); overflow: hidden;\n}\n.fx-float-bar {\n display: flex; align-items: center; padding: 4px 8px; cursor: move;\n font-size: 11px; color: var(--fx-muted, #8a90a2);\n background: var(--fx-bg, #0f1115); border-bottom: 1px solid var(--fx-line, #262a35);\n user-select: none;\n}\n/* The pane fills the card, whatever it was sized to in the layout it came from. */\n.fx-float > .fx-floated {\n flex: 1; min-height: 0; width: auto; height: auto; overflow: auto;\n}\n/* Edges and corners. Thin, and over everything, so a card can be resized from whichever\n side is reachable. */\n.fx-float-h { position: absolute; z-index: 1; }\n.fx-float-n, .fx-float-s { left: 8px; right: 8px; height: 6px; cursor: ns-resize; }\n.fx-float-e, .fx-float-w { top: 8px; bottom: 8px; width: 6px; cursor: ew-resize; }\n.fx-float-n { top: -3px; }\n.fx-float-s { bottom: -3px; }\n.fx-float-e { right: -3px; }\n.fx-float-w { left: -3px; }\n.fx-float-ne, .fx-float-nw, .fx-float-se, .fx-float-sw { width: 14px; height: 14px; }\n.fx-float-nw { top: -3px; left: -3px; cursor: nwse-resize; }\n.fx-float-ne { top: -3px; right: -3px; cursor: nesw-resize; }\n.fx-float-sw { bottom: -3px; left: -3px; cursor: nesw-resize; }\n.fx-float-se {\n bottom: 0; right: 0; cursor: nwse-resize;\n background: linear-gradient(135deg, transparent 50%, var(--fx-line, #262a35) 50%);\n}\n";
|
|
54
|
+
//# sourceMappingURL=float.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"float.d.ts","sourceRoot":"","sources":["../src/float.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA+CH,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,EAAE,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,2DAA2D;IAC3D,MAAM,IAAI,OAAO,CAAC;IAClB,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;CACjB;AAMD,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,EAAE,CAAC,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3D,SAAS,CAsFX;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,GAAG,SAAS,CA2EjF;AAED,eAAO,MAAM,SAAS,8mEA8CrB,CAAC"}
|