@shadow-js/devtools 0.0.0-stage → 1.0.1
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +576 -2
- package/dist/dev/agent.d.ts +239 -0
- package/dist/dev/agent.js +626 -0
- package/dist/dev/agent.js.map +1 -0
- package/dist/dev/backend.d.ts +53 -0
- package/dist/dev/backend.js +53 -0
- package/dist/dev/backend.js.map +1 -0
- package/dist/dev/hook.d.ts +83 -0
- package/dist/dev/hook.js +136 -0
- package/dist/dev/hook.js.map +1 -0
- package/dist/dev/index.d.ts +17 -0
- package/dist/dev/index.js +17 -0
- package/dist/dev/inspection.d.ts +226 -0
- package/dist/dev/inspection.js +263 -0
- package/dist/dev/inspection.js.map +1 -0
- package/dist/dev/inspector.d.ts +331 -0
- package/dist/dev/inspector.js +429 -0
- package/dist/dev/inspector.js.map +1 -0
- package/dist/dev/model.d.ts +418 -0
- package/dist/dev/model.js +559 -0
- package/dist/dev/model.js.map +1 -0
- package/dist/dev/mount.d.ts +207 -0
- package/dist/dev/mount.js +256 -0
- package/dist/dev/mount.js.map +1 -0
- package/dist/dev/overlay.d.ts +102 -0
- package/dist/dev/overlay.js +176 -0
- package/dist/dev/overlay.js.map +1 -0
- package/dist/dev/preview.d.ts +74 -0
- package/dist/dev/preview.js +122 -0
- package/dist/dev/preview.js.map +1 -0
- package/dist/dev/protocol.d.ts +288 -0
- package/dist/dev/protocol.js +109 -0
- package/dist/dev/protocol.js.map +1 -0
- package/dist/dev/ring.d.ts +50 -0
- package/dist/dev/ring.js +94 -0
- package/dist/dev/ring.js.map +1 -0
- package/dist/dev/transport.d.ts +347 -0
- package/dist/dev/transport.js +394 -0
- package/dist/dev/transport.js.map +1 -0
- package/dist/dev/ui/details.js +294 -0
- package/dist/dev/ui/details.js.map +1 -0
- package/dist/dev/ui/inspection-state.d.ts +43 -0
- package/dist/dev/ui/inspection-state.js +62 -0
- package/dist/dev/ui/inspection-state.js.map +1 -0
- package/dist/dev/ui/issues.js +85 -0
- package/dist/dev/ui/issues.js.map +1 -0
- package/dist/dev/ui/panel.d.ts +55 -0
- package/dist/dev/ui/panel.js +160 -0
- package/dist/dev/ui/panel.js.map +1 -0
- package/dist/dev/ui/rows.d.ts +44 -0
- package/dist/dev/ui/rows.js +132 -0
- package/dist/dev/ui/rows.js.map +1 -0
- package/dist/dev/ui/state-tab.js +88 -0
- package/dist/dev/ui/state-tab.js.map +1 -0
- package/dist/dev/ui/state.d.ts +192 -0
- package/dist/dev/ui/state.js +147 -0
- package/dist/dev/ui/state.js.map +1 -0
- package/dist/dev/ui/styles.d.ts +31 -0
- package/dist/dev/ui/styles.js +113 -0
- package/dist/dev/ui/styles.js.map +1 -0
- package/dist/dev/ui/timeline.js +135 -0
- package/dist/dev/ui/timeline.js.map +1 -0
- package/dist/dev/ui/tree.js +186 -0
- package/dist/dev/ui/tree.js.map +1 -0
- package/dist/prod/agent.js +2 -0
- package/dist/prod/agent.js.map +1 -0
- package/dist/prod/backend.js +2 -0
- package/dist/prod/backend.js.map +1 -0
- package/dist/prod/hook.js +2 -0
- package/dist/prod/hook.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/inspection.js +2 -0
- package/dist/prod/inspection.js.map +1 -0
- package/dist/prod/inspector.js +2 -0
- package/dist/prod/inspector.js.map +1 -0
- package/dist/prod/model.js +2 -0
- package/dist/prod/model.js.map +1 -0
- package/dist/prod/mount.js +2 -0
- package/dist/prod/mount.js.map +1 -0
- package/dist/prod/overlay.js +11 -0
- package/dist/prod/overlay.js.map +1 -0
- package/dist/prod/preview.js +2 -0
- package/dist/prod/preview.js.map +1 -0
- package/dist/prod/protocol.js +2 -0
- package/dist/prod/protocol.js.map +1 -0
- package/dist/prod/ring.js +2 -0
- package/dist/prod/ring.js.map +1 -0
- package/dist/prod/transport.js +2 -0
- package/dist/prod/transport.js.map +1 -0
- package/dist/prod/ui/details.js +2 -0
- package/dist/prod/ui/details.js.map +1 -0
- package/dist/prod/ui/inspection-state.js +2 -0
- package/dist/prod/ui/inspection-state.js.map +1 -0
- package/dist/prod/ui/issues.js +2 -0
- package/dist/prod/ui/issues.js.map +1 -0
- package/dist/prod/ui/panel.js +2 -0
- package/dist/prod/ui/panel.js.map +1 -0
- package/dist/prod/ui/rows.js +2 -0
- package/dist/prod/ui/rows.js.map +1 -0
- package/dist/prod/ui/state-tab.js +2 -0
- package/dist/prod/ui/state-tab.js.map +1 -0
- package/dist/prod/ui/state.js +2 -0
- package/dist/prod/ui/state.js.map +1 -0
- package/dist/prod/ui/styles.js +82 -0
- package/dist/prod/ui/styles.js.map +1 -0
- package/dist/prod/ui/timeline.js +2 -0
- package/dist/prod/ui/timeline.js.map +1 -0
- package/dist/prod/ui/tree.js +2 -0
- package/dist/prod/ui/tree.js.map +1 -0
- package/package.json +69 -4
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { For, Show, addEventListener, anchor, className, createComponent, effect, insert, memo, setAttribute, template } from "@shadow-js/web";
|
|
2
|
+
import { untrack, useEffect, useSignal } from "@shadow-js/core";
|
|
3
|
+
//#region src/ui/details.tsx
|
|
4
|
+
const _tmpl$ = /*#__PURE__*/ template("<button type=\"button\" class=\"btn\">Apply</button>");
|
|
5
|
+
const _tmpl$2 = /*#__PURE__*/ template("<button type=\"button\" class=\"btn\">Revert</button>");
|
|
6
|
+
const _tmpl$3 = /*#__PURE__*/ template("<div class=\"section\"><h4>Value</h4><textarea class=\"textarea\" aria-label=\"Value\"></textarea><div><span role=\"status\"></span></div></div>");
|
|
7
|
+
const _tmpl$4 = /*#__PURE__*/ template("<span><button type=\"button\" class=\"link\"></button> </span>");
|
|
8
|
+
const _tmpl$5 = /*#__PURE__*/ template("<li><strong></strong></li>");
|
|
9
|
+
const _tmpl$6 = /*#__PURE__*/ template("<div class=\"status\">Also changed: </div>");
|
|
10
|
+
const _tmpl$7 = /*#__PURE__*/ template("<div class=\"status\">Older history was dropped.</div>");
|
|
11
|
+
const _tmpl$8 = /*#__PURE__*/ template("<div class=\"section\" data-why><h4>Why did it run?</h4><ol class=\"chain\"></ol></div>");
|
|
12
|
+
const _tmpl$9 = /*#__PURE__*/ template("<div class=\"status\"> runs · updates · last ms · total ms</div>");
|
|
13
|
+
const _tmpl$10 = /*#__PURE__*/ template("<div class=\"section\"><h4>Reads</h4></div>");
|
|
14
|
+
const _tmpl$11 = /*#__PURE__*/ template("<div class=\"section\"><h4>Read by</h4></div>");
|
|
15
|
+
const _tmpl$12 = /*#__PURE__*/ template("<section class=\"detail\" aria-label=\"Details\"><div class=\"section\"><strong></strong> <span class=\"status\"> #</span></div></section>");
|
|
16
|
+
/**
|
|
17
|
+
* Shows the value of a signal or store as indented JSON, and edits it. Props: `state` is the panel's
|
|
18
|
+
* {@link PanelState} and `node` is the selected node. Apply writes the text back through the agent, and a
|
|
19
|
+
* successful write re-renders the app like any other write. Revert discards the edit. While the text is
|
|
20
|
+
* being edited, the view does not overwrite it. A resource or a memo has no editor here.
|
|
21
|
+
*
|
|
22
|
+
* Internal to the panel: the package index does not export this component. Used in development builds
|
|
23
|
+
* only.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
const ValueEditor = (props) => {
|
|
28
|
+
const [text, setText] = useSignal("");
|
|
29
|
+
const [dirty, setDirty] = useSignal(false);
|
|
30
|
+
const [editable, setEditable] = useSignal(false);
|
|
31
|
+
const [status, setStatus] = useSignal({
|
|
32
|
+
kind: "idle",
|
|
33
|
+
text: ""
|
|
34
|
+
});
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
props.state.version();
|
|
37
|
+
const id = props.node.id;
|
|
38
|
+
const store = props.node.type === "store";
|
|
39
|
+
if (dirty()) return;
|
|
40
|
+
untrack(() => props.state.backend.request(store ? {
|
|
41
|
+
type: "store",
|
|
42
|
+
id
|
|
43
|
+
} : {
|
|
44
|
+
type: "value",
|
|
45
|
+
id
|
|
46
|
+
})).then((response) => {
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
setStatus({
|
|
49
|
+
kind: "bad",
|
|
50
|
+
text: response.error
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const result = response.result;
|
|
55
|
+
setText(result.text);
|
|
56
|
+
setEditable(result.editable);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const apply = () => {
|
|
60
|
+
const id = props.node.id;
|
|
61
|
+
const request = props.node.type === "store" ? {
|
|
62
|
+
type: "store-set",
|
|
63
|
+
id,
|
|
64
|
+
path: [],
|
|
65
|
+
text: text()
|
|
66
|
+
} : {
|
|
67
|
+
type: "write",
|
|
68
|
+
id,
|
|
69
|
+
text: text()
|
|
70
|
+
};
|
|
71
|
+
props.state.backend.request(request).then((response) => {
|
|
72
|
+
if (response.ok) {
|
|
73
|
+
setDirty(false);
|
|
74
|
+
setStatus({
|
|
75
|
+
kind: "good",
|
|
76
|
+
text: "Applied."
|
|
77
|
+
});
|
|
78
|
+
props.state.refresh();
|
|
79
|
+
} else setStatus({
|
|
80
|
+
kind: "bad",
|
|
81
|
+
text: response.error
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
{
|
|
86
|
+
const _el$ = _tmpl$3(), _el$2 = _el$.firstChild.nextSibling, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$5 = anchor(_el$3, _el$4);
|
|
87
|
+
effect(() => {
|
|
88
|
+
setAttribute(_el$, "data-editor", props.node.type);
|
|
89
|
+
});
|
|
90
|
+
setAttribute(_el$2, "spellcheck", false);
|
|
91
|
+
effect(() => {
|
|
92
|
+
_el$2.readOnly = !editable();
|
|
93
|
+
});
|
|
94
|
+
addEventListener(_el$2, "input", (event) => {
|
|
95
|
+
setText(event.currentTarget.value);
|
|
96
|
+
setDirty(true);
|
|
97
|
+
setStatus({
|
|
98
|
+
kind: "idle",
|
|
99
|
+
text: ""
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
effect(() => {
|
|
103
|
+
_el$2.value = text() ?? "";
|
|
104
|
+
});
|
|
105
|
+
insert(_el$3, createComponent(Show, {
|
|
106
|
+
get when() {
|
|
107
|
+
return editable();
|
|
108
|
+
},
|
|
109
|
+
get children() {
|
|
110
|
+
return [(() => {
|
|
111
|
+
const _el$ = _tmpl$();
|
|
112
|
+
effect(() => {
|
|
113
|
+
_el$.disabled = !dirty();
|
|
114
|
+
});
|
|
115
|
+
addEventListener(_el$, "click", apply);
|
|
116
|
+
return _el$;
|
|
117
|
+
})(), " "];
|
|
118
|
+
}
|
|
119
|
+
}, "Show"), _el$5);
|
|
120
|
+
insert(_el$3, createComponent(Show, {
|
|
121
|
+
get when() {
|
|
122
|
+
return dirty();
|
|
123
|
+
},
|
|
124
|
+
get children() {
|
|
125
|
+
return [(() => {
|
|
126
|
+
const _el$ = _tmpl$2();
|
|
127
|
+
addEventListener(_el$, "click", () => {
|
|
128
|
+
setDirty(false);
|
|
129
|
+
props.state.refresh();
|
|
130
|
+
});
|
|
131
|
+
return _el$;
|
|
132
|
+
})(), " "];
|
|
133
|
+
}
|
|
134
|
+
}, "Show"), _el$4);
|
|
135
|
+
effect(() => {
|
|
136
|
+
className(_el$4, `status ${status().kind}`);
|
|
137
|
+
});
|
|
138
|
+
insert(_el$4, () => status().text);
|
|
139
|
+
return _el$;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* The selected node: its name and kind, its counters (runs, updates and durations for memos and effects),
|
|
144
|
+
* what it reads, who reads it, why it last ran (memos and effects), and a {@link ValueEditor} for signals
|
|
145
|
+
* and stores. Props: `state` is the panel's {@link PanelState}. It renders nothing when no node is
|
|
146
|
+
* selected.
|
|
147
|
+
*
|
|
148
|
+
* Internal to the panel: the package index does not export this component. Used in development builds
|
|
149
|
+
* only.
|
|
150
|
+
*
|
|
151
|
+
* @internal
|
|
152
|
+
*/
|
|
153
|
+
const Details = (props) => createComponent(Show, {
|
|
154
|
+
get when() {
|
|
155
|
+
return props.state.selectedNode();
|
|
156
|
+
},
|
|
157
|
+
children: (node) => {
|
|
158
|
+
const _el$ = _tmpl$12(), _el$2 = _el$.firstChild, _el$6 = _el$2.firstChild, _el$7 = _el$6.nextSibling.nextSibling, _el$8 = _el$7.firstChild, _el$3 = anchor(_el$, null), _el$4 = anchor(_el$, null), _el$5 = anchor(_el$, null);
|
|
159
|
+
insert(_el$6, () => props.state.nameOf(node().id));
|
|
160
|
+
insert(_el$7, () => node().type, _el$8);
|
|
161
|
+
insert(_el$7, () => node().id);
|
|
162
|
+
insert(_el$2, createComponent(Show, {
|
|
163
|
+
get when() {
|
|
164
|
+
return node().type === "memo" || node().type === "effect";
|
|
165
|
+
},
|
|
166
|
+
get children() {
|
|
167
|
+
const _el$ = _tmpl$9(), _el$2 = _el$.firstChild, _el$3 = _el$2.splitText(8), _el$4 = _el$3.splitText(16), _el$5 = _el$4.splitText(12);
|
|
168
|
+
insert(_el$, () => node().runs, _el$2);
|
|
169
|
+
insert(_el$, () => node().updates, _el$3);
|
|
170
|
+
insert(_el$, () => node().lastDuration.toFixed(2), _el$4);
|
|
171
|
+
insert(_el$, () => node().totalDuration.toFixed(2), _el$5);
|
|
172
|
+
return _el$;
|
|
173
|
+
}
|
|
174
|
+
}, "Show"));
|
|
175
|
+
insert(_el$, createComponent(Show, {
|
|
176
|
+
get when() {
|
|
177
|
+
return node().sources.length > 0;
|
|
178
|
+
},
|
|
179
|
+
get children() {
|
|
180
|
+
const _el$ = _tmpl$10();
|
|
181
|
+
insert(_el$, createComponent(For, {
|
|
182
|
+
get each() {
|
|
183
|
+
return node().sources;
|
|
184
|
+
},
|
|
185
|
+
children: (id) => {
|
|
186
|
+
const _el$ = _tmpl$4(), _el$2 = _el$.firstChild;
|
|
187
|
+
addEventListener(_el$2, "click", () => props.state.select(id));
|
|
188
|
+
insert(_el$2, () => props.state.nameOf(id));
|
|
189
|
+
return _el$;
|
|
190
|
+
}
|
|
191
|
+
}, "For"));
|
|
192
|
+
return _el$;
|
|
193
|
+
}
|
|
194
|
+
}, "Show"), _el$3);
|
|
195
|
+
insert(_el$, createComponent(Show, {
|
|
196
|
+
get when() {
|
|
197
|
+
return node().observers.length > 0;
|
|
198
|
+
},
|
|
199
|
+
get children() {
|
|
200
|
+
const _el$ = _tmpl$11();
|
|
201
|
+
insert(_el$, createComponent(For, {
|
|
202
|
+
get each() {
|
|
203
|
+
return node().observers;
|
|
204
|
+
},
|
|
205
|
+
children: (id) => {
|
|
206
|
+
const _el$ = _tmpl$4(), _el$2 = _el$.firstChild;
|
|
207
|
+
addEventListener(_el$2, "click", () => props.state.select(id));
|
|
208
|
+
insert(_el$2, () => props.state.nameOf(id));
|
|
209
|
+
return _el$;
|
|
210
|
+
}
|
|
211
|
+
}, "For"));
|
|
212
|
+
return _el$;
|
|
213
|
+
}
|
|
214
|
+
}, "Show"), _el$4);
|
|
215
|
+
insert(_el$, createComponent(Show, {
|
|
216
|
+
get when() {
|
|
217
|
+
return props.state.explanation();
|
|
218
|
+
},
|
|
219
|
+
children: (why) => {
|
|
220
|
+
const _el$ = _tmpl$8(), _el$2 = _el$.firstChild.nextSibling, _el$3 = anchor(_el$, null);
|
|
221
|
+
insert(_el$2, createComponent(For, {
|
|
222
|
+
get each() {
|
|
223
|
+
return why().chain;
|
|
224
|
+
},
|
|
225
|
+
children: (step) => {
|
|
226
|
+
const _el$ = _tmpl$5(), _el$2 = _el$.firstChild, _el$3 = anchor(_el$, null);
|
|
227
|
+
insert(_el$, () => step.kind === "write" ? "write " : step.kind === "run" ? "ran " : "", _el$2);
|
|
228
|
+
insert(_el$2, () => step.label);
|
|
229
|
+
insert(_el$, createComponent(Show, {
|
|
230
|
+
get when() {
|
|
231
|
+
return step.preview !== void 0;
|
|
232
|
+
},
|
|
233
|
+
get children() {
|
|
234
|
+
return [" = ", memo(() => step.preview)];
|
|
235
|
+
}
|
|
236
|
+
}, "Show"), _el$3);
|
|
237
|
+
insert(_el$, createComponent(Show, {
|
|
238
|
+
get when() {
|
|
239
|
+
return step.writer !== void 0;
|
|
240
|
+
},
|
|
241
|
+
get children() {
|
|
242
|
+
return [
|
|
243
|
+
" (written by ",
|
|
244
|
+
memo(() => step.writer),
|
|
245
|
+
")"
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
}, "Show"));
|
|
249
|
+
return _el$;
|
|
250
|
+
}
|
|
251
|
+
}, "For"));
|
|
252
|
+
insert(_el$, createComponent(Show, {
|
|
253
|
+
get when() {
|
|
254
|
+
return why().alsoCausedBy.length > 0;
|
|
255
|
+
},
|
|
256
|
+
get children() {
|
|
257
|
+
const _el$ = _tmpl$6();
|
|
258
|
+
insert(_el$, () => why().alsoCausedBy.map((step) => step.label).join(", "));
|
|
259
|
+
return _el$;
|
|
260
|
+
}
|
|
261
|
+
}, "Show"), _el$3);
|
|
262
|
+
insert(_el$, createComponent(Show, {
|
|
263
|
+
get when() {
|
|
264
|
+
return why().truncated;
|
|
265
|
+
},
|
|
266
|
+
get children() {
|
|
267
|
+
return _tmpl$7();
|
|
268
|
+
}
|
|
269
|
+
}, "Show"));
|
|
270
|
+
return _el$;
|
|
271
|
+
}
|
|
272
|
+
}, "Show"), _el$5);
|
|
273
|
+
insert(_el$, createComponent(Show, {
|
|
274
|
+
get when() {
|
|
275
|
+
return node().type === "signal" || node().type === "store";
|
|
276
|
+
},
|
|
277
|
+
get children() {
|
|
278
|
+
return createComponent(ValueEditor, {
|
|
279
|
+
get state() {
|
|
280
|
+
return props.state;
|
|
281
|
+
},
|
|
282
|
+
get node() {
|
|
283
|
+
return node();
|
|
284
|
+
}
|
|
285
|
+
}, "ValueEditor");
|
|
286
|
+
}
|
|
287
|
+
}, "Show"));
|
|
288
|
+
return _el$;
|
|
289
|
+
}
|
|
290
|
+
}, "Show");
|
|
291
|
+
//#endregion
|
|
292
|
+
export { Details, ValueEditor };
|
|
293
|
+
|
|
294
|
+
//# sourceMappingURL=details.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"details.js","names":["_$anchor","_$createComponent","_$memo"],"sources":["../../../src/ui/details.tsx"],"sourcesContent":["import { untrack, useEffect, useSignal } from \"@shadow-js/core\";\nimport { For, Show } from \"@shadow-js/web\";\nimport type { Component } from \"@shadow-js/web/jsx-runtime\";\nimport type { AgentRequest } from \"../agent\";\nimport type { SnapshotNode } from \"../inspector\";\nimport type { PanelState } from \"./state\";\n\ninterface NodeProps {\n state: PanelState;\n node: SnapshotNode;\n}\n\n/**\n * Shows the value of a signal or store as indented JSON, and edits it. Props: `state` is the panel's\n * {@link PanelState} and `node` is the selected node. Apply writes the text back through the agent, and a\n * successful write re-renders the app like any other write. Revert discards the edit. While the text is\n * being edited, the view does not overwrite it. A resource or a memo has no editor here.\n *\n * Internal to the panel: the package index does not export this component. Used in development builds\n * only.\n *\n * @internal\n */\nexport const ValueEditor: Component<NodeProps> = (props) => {\n const [text, setText] = useSignal(\"\");\n const [dirty, setDirty] = useSignal(false);\n const [editable, setEditable] = useSignal(false);\n const [status, setStatus] = useSignal({ kind: \"idle\", text: \"\" });\n\n useEffect(() => {\n // Follow the value while it changes, unless the user is typing.\n props.state.version();\n const id = props.node.id;\n const store = props.node.type === \"store\";\n if (dirty()) return;\n // The request reads the value through the agent; the reads above are the only dependencies.\n const request = untrack(() =>\n props.state.backend.request(store ? { type: \"store\", id } : { type: \"value\", id }),\n );\n void request.then((response) => {\n if (!response.ok) {\n setStatus({ kind: \"bad\", text: response.error });\n return;\n }\n const result = response.result as { text: string; editable: boolean };\n setText(result.text);\n setEditable(result.editable);\n });\n });\n\n const apply = (): void => {\n const id = props.node.id;\n const request: AgentRequest =\n props.node.type === \"store\"\n ? { type: \"store-set\", id, path: [], text: text() }\n : { type: \"write\", id, text: text() };\n void props.state.backend.request(request).then((response) => {\n if (response.ok) {\n setDirty(false);\n setStatus({ kind: \"good\", text: \"Applied.\" });\n props.state.refresh();\n } else {\n setStatus({ kind: \"bad\", text: response.error });\n }\n });\n };\n\n return (\n <div class=\"section\" data-editor={props.node.type}>\n <h4>Value</h4>\n <textarea\n class=\"textarea\"\n aria-label=\"Value\"\n spellcheck={false}\n readOnly={!editable()}\n value={text()}\n on:input={(event) => {\n setText(event.currentTarget.value);\n setDirty(true);\n setStatus({ kind: \"idle\", text: \"\" });\n }}\n />\n <div>\n <Show when={editable()}>\n <button type=\"button\" class=\"btn\" disabled={!dirty()} on:click={apply}>\n Apply\n </button>{\" \"}\n </Show>\n <Show when={dirty()}>\n <button\n type=\"button\"\n class=\"btn\"\n on:click={() => {\n setDirty(false);\n props.state.refresh();\n }}\n >\n Revert\n </button>{\" \"}\n </Show>\n <span class={`status ${status().kind}`} role=\"status\">\n {status().text}\n </span>\n </div>\n </div>\n );\n};\n\n/** Props of {@link Details}. */\ninterface DetailsProps {\n state: PanelState;\n}\n\n/**\n * The selected node: its name and kind, its counters (runs, updates and durations for memos and effects),\n * what it reads, who reads it, why it last ran (memos and effects), and a {@link ValueEditor} for signals\n * and stores. Props: `state` is the panel's {@link PanelState}. It renders nothing when no node is\n * selected.\n *\n * Internal to the panel: the package index does not export this component. Used in development builds\n * only.\n *\n * @internal\n */\nexport const Details: Component<DetailsProps> = (props) => (\n <Show when={props.state.selectedNode()}>\n {(node) => (\n <section class=\"detail\" aria-label=\"Details\">\n <div class=\"section\">\n <strong>{props.state.nameOf(node().id)}</strong>{\" \"}\n <span class=\"status\">\n {node().type} #{node().id}\n </span>\n <Show when={node().type === \"memo\" || node().type === \"effect\"}>\n <div class=\"status\">\n {node().runs} runs · {node().updates} updates · last {node().lastDuration.toFixed(2)}{\" \"}\n ms · total {node().totalDuration.toFixed(2)} ms\n </div>\n </Show>\n </div>\n <Show when={node().sources.length > 0}>\n <div class=\"section\">\n <h4>Reads</h4>\n <For each={node().sources}>\n {(id) => (\n <span>\n <button type=\"button\" class=\"link\" on:click={() => props.state.select(id)}>\n {props.state.nameOf(id)}\n </button>{\" \"}\n </span>\n )}\n </For>\n </div>\n </Show>\n <Show when={node().observers.length > 0}>\n <div class=\"section\">\n <h4>Read by</h4>\n <For each={node().observers}>\n {(id) => (\n <span>\n <button type=\"button\" class=\"link\" on:click={() => props.state.select(id)}>\n {props.state.nameOf(id)}\n </button>{\" \"}\n </span>\n )}\n </For>\n </div>\n </Show>\n <Show when={props.state.explanation()}>\n {(why) => (\n <div class=\"section\" data-why>\n <h4>Why did it run?</h4>\n <ol class=\"chain\">\n <For each={why().chain}>\n {(step) => (\n <li>\n {step.kind === \"write\" ? \"write \" : step.kind === \"run\" ? \"ran \" : \"\"}\n <strong>{step.label}</strong>\n <Show when={step.preview !== undefined}> = {step.preview}</Show>\n <Show when={step.writer !== undefined}> (written by {step.writer})</Show>\n </li>\n )}\n </For>\n </ol>\n <Show when={why().alsoCausedBy.length > 0}>\n <div class=\"status\">\n Also changed:{\" \"}\n {why()\n .alsoCausedBy.map((step) => step.label)\n .join(\", \")}\n </div>\n </Show>\n <Show when={why().truncated}>\n <div class=\"status\">Older history was dropped.</div>\n </Show>\n </div>\n )}\n </Show>\n <Show when={node().type === \"signal\" || node().type === \"store\"}>\n <ValueEditor state={props.state} node={node()} />\n </Show>\n </section>\n )}\n </Show>\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { InspectionSnapshot } from "../inspection.js";
|
|
2
|
+
import { Backend } from "../backend.js";
|
|
3
|
+
import { Accessor } from "@shadow-js/core";
|
|
4
|
+
//#region src/ui/inspection-state.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* What the inspection views of a remote panel read and write: the registered sources, the one on
|
|
7
|
+
* screen, and the state of the last request. Built by {@link createInspectionState}.
|
|
8
|
+
*
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
export interface InspectionState {
|
|
12
|
+
/** The sources the page answered with at the last refresh (empty before the first one). */
|
|
13
|
+
readonly sources: Accessor<readonly InspectionSnapshot[]>;
|
|
14
|
+
/** The name of the source on screen, or `""` when there is none. */
|
|
15
|
+
readonly active: Accessor<string>;
|
|
16
|
+
/** The reason the last refresh failed, or `""` when it did not. */
|
|
17
|
+
readonly error: Accessor<string>;
|
|
18
|
+
/** True while a refresh is waiting for the page. */
|
|
19
|
+
readonly loading: Accessor<boolean>;
|
|
20
|
+
/** Shows the source registered as `name`. Ignored when no source has that name. */
|
|
21
|
+
select(name: string): void;
|
|
22
|
+
/** Asks the page for its sources now. Resolves when the answer arrived (or failed). */
|
|
23
|
+
refresh(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The state of the inspection views of a panel (the router and query tabs): it asks the agent for the
|
|
27
|
+
* registered sources, keeps the answer, and remembers which source is shown. It talks only to a
|
|
28
|
+
* {@link Backend}, so it works for a local agent and for a remote one. An answer that does not have
|
|
29
|
+
* the expected shape is refused and reported as an error; the previous sources stay.
|
|
30
|
+
*
|
|
31
|
+
* @param backend The backend to ask.
|
|
32
|
+
* @returns The inspection state.
|
|
33
|
+
* @example
|
|
34
|
+
* import { createInspectionState, createLocalBackend, createAgent } from "@shadow-js/devtools";
|
|
35
|
+
*
|
|
36
|
+
* const state = createInspectionState(createLocalBackend(createAgent()));
|
|
37
|
+
* await state.refresh();
|
|
38
|
+
* state.sources(); // the registered sources
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
*/
|
|
41
|
+
export declare function createInspectionState(backend: Backend): InspectionState;
|
|
42
|
+
//#endregion
|
|
43
|
+
//# sourceMappingURL=inspection-state.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { parseInspectResult } from "../inspection.js";
|
|
2
|
+
import { useSignal } from "@shadow-js/core";
|
|
3
|
+
//#region src/ui/inspection-state.ts
|
|
4
|
+
/**
|
|
5
|
+
* The state of the inspection views of a panel (the router and query tabs): it asks the agent for the
|
|
6
|
+
* registered sources, keeps the answer, and remembers which source is shown. It talks only to a
|
|
7
|
+
* {@link Backend}, so it works for a local agent and for a remote one. An answer that does not have
|
|
8
|
+
* the expected shape is refused and reported as an error; the previous sources stay.
|
|
9
|
+
*
|
|
10
|
+
* @param backend The backend to ask.
|
|
11
|
+
* @returns The inspection state.
|
|
12
|
+
* @example
|
|
13
|
+
* import { createInspectionState, createLocalBackend, createAgent } from "@shadow-js/devtools";
|
|
14
|
+
*
|
|
15
|
+
* const state = createInspectionState(createLocalBackend(createAgent()));
|
|
16
|
+
* await state.refresh();
|
|
17
|
+
* state.sources(); // the registered sources
|
|
18
|
+
* @since 1.0.0
|
|
19
|
+
*/
|
|
20
|
+
function createInspectionState(backend) {
|
|
21
|
+
const [sources, setSources] = useSignal([]);
|
|
22
|
+
const [active, setActive] = useSignal("");
|
|
23
|
+
const [error, setError] = useSignal("");
|
|
24
|
+
const [loading, setLoading] = useSignal(false);
|
|
25
|
+
const select = (name) => {
|
|
26
|
+
if (sources().some((source) => source.name === name)) setActive(name);
|
|
27
|
+
};
|
|
28
|
+
const refresh = async () => {
|
|
29
|
+
setLoading(true);
|
|
30
|
+
try {
|
|
31
|
+
const response = await backend.request({ type: "inspect" });
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
setError(response.error);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const parsed = parseInspectResult(response.result);
|
|
37
|
+
if (parsed === null) {
|
|
38
|
+
setError("The page answered the inspect request with an unexpected value.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
setSources(parsed);
|
|
42
|
+
setError("");
|
|
43
|
+
if (!parsed.some((source) => source.name === active())) setActive(parsed[0]?.name ?? "");
|
|
44
|
+
} catch (failure) {
|
|
45
|
+
setError(failure instanceof Error ? failure.message : "The request failed.");
|
|
46
|
+
} finally {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
sources,
|
|
52
|
+
active,
|
|
53
|
+
error,
|
|
54
|
+
loading,
|
|
55
|
+
select,
|
|
56
|
+
refresh
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { createInspectionState };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=inspection-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspection-state.js","names":[],"sources":["../../../src/ui/inspection-state.ts"],"sourcesContent":["import { type Accessor, useSignal } from \"@shadow-js/core\";\nimport type { Backend } from \"../backend\";\nimport { type InspectionSnapshot, parseInspectResult } from \"../inspection\";\n\n/**\n * What the inspection views of a remote panel read and write: the registered sources, the one on\n * screen, and the state of the last request. Built by {@link createInspectionState}.\n *\n * @since 1.0.0\n */\nexport interface InspectionState {\n /** The sources the page answered with at the last refresh (empty before the first one). */\n readonly sources: Accessor<readonly InspectionSnapshot[]>;\n /** The name of the source on screen, or `\"\"` when there is none. */\n readonly active: Accessor<string>;\n /** The reason the last refresh failed, or `\"\"` when it did not. */\n readonly error: Accessor<string>;\n /** True while a refresh is waiting for the page. */\n readonly loading: Accessor<boolean>;\n /** Shows the source registered as `name`. Ignored when no source has that name. */\n select(name: string): void;\n /** Asks the page for its sources now. Resolves when the answer arrived (or failed). */\n refresh(): Promise<void>;\n}\n\n/**\n * The state of the inspection views of a panel (the router and query tabs): it asks the agent for the\n * registered sources, keeps the answer, and remembers which source is shown. It talks only to a\n * {@link Backend}, so it works for a local agent and for a remote one. An answer that does not have\n * the expected shape is refused and reported as an error; the previous sources stay.\n *\n * @param backend The backend to ask.\n * @returns The inspection state.\n * @example\n * import { createInspectionState, createLocalBackend, createAgent } from \"@shadow-js/devtools\";\n *\n * const state = createInspectionState(createLocalBackend(createAgent()));\n * await state.refresh();\n * state.sources(); // the registered sources\n * @since 1.0.0\n */\nexport function createInspectionState(backend: Backend): InspectionState {\n const [sources, setSources] = useSignal<readonly InspectionSnapshot[]>([]);\n const [active, setActive] = useSignal(\"\");\n const [error, setError] = useSignal(\"\");\n const [loading, setLoading] = useSignal(false);\n\n const select = (name: string): void => {\n if (sources().some((source) => source.name === name)) setActive(name);\n };\n\n const refresh = async (): Promise<void> => {\n setLoading(true);\n try {\n const response = await backend.request({ type: \"inspect\" });\n if (!response.ok) {\n setError(response.error);\n return;\n }\n const parsed = parseInspectResult(response.result);\n if (parsed === null) {\n setError(\"The page answered the inspect request with an unexpected value.\");\n return;\n }\n setSources(parsed);\n setError(\"\");\n if (!parsed.some((source) => source.name === active())) {\n setActive(parsed[0]?.name ?? \"\");\n }\n } catch (failure) {\n setError(failure instanceof Error ? failure.message : \"The request failed.\");\n } finally {\n setLoading(false);\n }\n };\n\n return { sources, active, error, loading, select, refresh };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,sBAAsB,SAAmC;CACvE,MAAM,CAAC,SAAS,cAAc,UAAyC,CAAC,CAAC;CACzE,MAAM,CAAC,QAAQ,aAAa,UAAU,EAAE;CACxC,MAAM,CAAC,OAAO,YAAY,UAAU,EAAE;CACtC,MAAM,CAAC,SAAS,cAAc,UAAU,KAAK;CAE7C,MAAM,UAAU,SAAuB;EACrC,IAAI,QAAQ,CAAC,CAAC,MAAM,WAAW,OAAO,SAAS,IAAI,GAAG,UAAU,IAAI;CACtE;CAEA,MAAM,UAAU,YAA2B;EACzC,WAAW,IAAI;EACf,IAAI;GACF,MAAM,WAAW,MAAM,QAAQ,QAAQ,EAAE,MAAM,UAAU,CAAC;GAC1D,IAAI,CAAC,SAAS,IAAI;IAChB,SAAS,SAAS,KAAK;IACvB;GACF;GACA,MAAM,SAAS,mBAAmB,SAAS,MAAM;GACjD,IAAI,WAAW,MAAM;IACnB,SAAS,iEAAiE;IAC1E;GACF;GACA,WAAW,MAAM;GACjB,SAAS,EAAE;GACX,IAAI,CAAC,OAAO,MAAM,WAAW,OAAO,SAAS,OAAO,CAAC,GACnD,UAAU,OAAO,EAAE,EAAE,QAAQ,EAAE;EAEnC,SAAS,SAAS;GAChB,SAAS,mBAAmB,QAAQ,QAAQ,UAAU,qBAAqB;EAC7E,UAAU;GACR,WAAW,KAAK;EAClB;CACF;CAEA,OAAO;EAAE;EAAS;EAAQ;EAAO;EAAS;EAAQ;CAAQ;AAC5D"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { For, Show, addEventListener, anchor, createComponent, insert, template } from "@shadow-js/web";
|
|
2
|
+
//#region src/ui/issues.tsx
|
|
3
|
+
const _tmpl$ = /*#__PURE__*/ template("<div class=\"empty\">No errors or hydration mismatches.</div>");
|
|
4
|
+
const _tmpl$2 = /*#__PURE__*/ template("<code></code>");
|
|
5
|
+
const _tmpl$3 = /*#__PURE__*/ template("<button type=\"button\" class=\"link\"></button>");
|
|
6
|
+
const _tmpl$4 = /*#__PURE__*/ template("<div class=\"issue\" data-issue=\"error\"></div>");
|
|
7
|
+
const _tmpl$5 = /*#__PURE__*/ template("<div class=\"issue\" data-issue=\"hydration\"><strong>Hydration mismatch:</strong> <div class=\"status\">server: </div><div class=\"status\">client: </div></div>");
|
|
8
|
+
const _tmpl$6 = /*#__PURE__*/ template("<section class=\"list\" aria-label=\"Issues\"></section>");
|
|
9
|
+
/**
|
|
10
|
+
* The Issues tab. Props: `state` is the panel's {@link PanelState}. It lists the errors the runtime reported,
|
|
11
|
+
* with their diagnostic code when there is one and a link to the node they belong to, then the hydration
|
|
12
|
+
* mismatches: the runtime's message, the expected value (labeled server) and the actual value (labeled
|
|
13
|
+
* client).
|
|
14
|
+
*
|
|
15
|
+
* Internal to the panel: the package index does not export this component. Used in development builds
|
|
16
|
+
* only.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
const IssuesTab = (props) => {
|
|
21
|
+
const _el$ = _tmpl$6();
|
|
22
|
+
insert(_el$, createComponent(Show, {
|
|
23
|
+
get when() {
|
|
24
|
+
return props.state.errors().length + props.state.hydration().length > 0;
|
|
25
|
+
},
|
|
26
|
+
get fallback() {
|
|
27
|
+
return _tmpl$();
|
|
28
|
+
},
|
|
29
|
+
get children() {
|
|
30
|
+
return [createComponent(For, {
|
|
31
|
+
get each() {
|
|
32
|
+
return props.state.errors();
|
|
33
|
+
},
|
|
34
|
+
key: (error) => error.seq,
|
|
35
|
+
children: (error) => {
|
|
36
|
+
const _el$ = _tmpl$4(), _el$2 = anchor(_el$, null), _el$3 = anchor(_el$, null);
|
|
37
|
+
insert(_el$, createComponent(Show, {
|
|
38
|
+
get when() {
|
|
39
|
+
return error().code !== void 0;
|
|
40
|
+
},
|
|
41
|
+
get children() {
|
|
42
|
+
return [(() => {
|
|
43
|
+
const _el$ = _tmpl$2();
|
|
44
|
+
insert(_el$, () => error().code);
|
|
45
|
+
return _el$;
|
|
46
|
+
})(), " "];
|
|
47
|
+
}
|
|
48
|
+
}, "Show"), _el$2);
|
|
49
|
+
insert(_el$, () => error().message, _el$3);
|
|
50
|
+
insert(_el$, createComponent(Show, {
|
|
51
|
+
get when() {
|
|
52
|
+
return error().id !== 0;
|
|
53
|
+
},
|
|
54
|
+
get children() {
|
|
55
|
+
return [" ", (() => {
|
|
56
|
+
const _el$ = _tmpl$3();
|
|
57
|
+
addEventListener(_el$, "click", () => props.state.select(error().id));
|
|
58
|
+
insert(_el$, () => props.state.nameOf(error().id));
|
|
59
|
+
return _el$;
|
|
60
|
+
})()];
|
|
61
|
+
}
|
|
62
|
+
}, "Show"));
|
|
63
|
+
return _el$;
|
|
64
|
+
}
|
|
65
|
+
}, "For"), createComponent(For, {
|
|
66
|
+
get each() {
|
|
67
|
+
return props.state.hydration();
|
|
68
|
+
},
|
|
69
|
+
key: (mismatch) => mismatch.seq,
|
|
70
|
+
children: (mismatch) => {
|
|
71
|
+
const _el$ = _tmpl$5(), _el$2 = _el$.firstChild.nextSibling.nextSibling, _el$3 = _el$2.nextSibling;
|
|
72
|
+
insert(_el$, () => mismatch().message, _el$2);
|
|
73
|
+
insert(_el$2, () => mismatch().expected);
|
|
74
|
+
insert(_el$3, () => mismatch().actual);
|
|
75
|
+
return _el$;
|
|
76
|
+
}
|
|
77
|
+
}, "For")];
|
|
78
|
+
}
|
|
79
|
+
}, "Show"));
|
|
80
|
+
return _el$;
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { IssuesTab };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues.js","names":["_$createComponent","_$anchor"],"sources":["../../../src/ui/issues.tsx"],"sourcesContent":["import { For, Show } from \"@shadow-js/web\";\nimport type { Component } from \"@shadow-js/web/jsx-runtime\";\nimport type { TabProps } from \"./tree\";\n\n/**\n * The Issues tab. Props: `state` is the panel's {@link PanelState}. It lists the errors the runtime reported,\n * with their diagnostic code when there is one and a link to the node they belong to, then the hydration\n * mismatches: the runtime's message, the expected value (labeled server) and the actual value (labeled\n * client).\n *\n * Internal to the panel: the package index does not export this component. Used in development builds\n * only.\n *\n * @internal\n */\nexport const IssuesTab: Component<TabProps> = (props) => (\n <section class=\"list\" aria-label=\"Issues\">\n <Show\n when={props.state.errors().length + props.state.hydration().length > 0}\n fallback={<div class=\"empty\">No errors or hydration mismatches.</div>}\n >\n <For each={props.state.errors()} key={(error) => error.seq}>\n {(error) => (\n <div class=\"issue\" data-issue=\"error\">\n <Show when={error().code !== undefined}>\n <code>{error().code}</code>{\" \"}\n </Show>\n {error().message}\n <Show when={error().id !== 0}>\n {\" \"}\n <button type=\"button\" class=\"link\" on:click={() => props.state.select(error().id)}>\n {props.state.nameOf(error().id)}\n </button>\n </Show>\n </div>\n )}\n </For>\n <For each={props.state.hydration()} key={(mismatch) => mismatch.seq}>\n {(mismatch) => (\n <div class=\"issue\" data-issue=\"hydration\">\n <strong>Hydration mismatch:</strong> {mismatch().message}\n <div class=\"status\">server: {mismatch().expected}</div>\n <div class=\"status\">client: {mismatch().actual}</div>\n </div>\n )}\n </For>\n </Show>\n </section>\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAeA,MAAa,aAAkC,UAAU;CAAA,MAAA,OAAA,QAAA;CAAA,OAAA,MAAAA,gBAAA,MAAA;EAAA,IAAA,OAAA;GAAA,OAAA,MAAA,MAAA,OAAA,CAAA,CAAA,SAAA,MAAA,MAAA,UAAA,CAAA,CAAA,SAAA;EAAA;EAAA,IAAA,WAAA;GAAA,OAAA,OAAA;EAAA;EAAA,IAAA,WAAA;GAAA,OAAA,CAAAA,gBAAA,KAAA;IAAA,IAAA,OAAA;KAAA,OAAA,MAAA,MAAA,OAAA;IAAA;IAAA,MAAA,UAAA,MAAA;IAAA,WAAA,UAAA;KAAA,MAAA,OAAA,QAAA,GAAA,QAAAC,OAAA,MAAA,IAAA,GAAA,QAAAA,OAAA,MAAA,IAAA;KAAA,OAAA,MAAAD,gBAAA,MAAA;MAAA,IAAA,OAAA;OAAA,OAAA,MAAA,CAAA,CAAA,SAAA,KAAA;MAAA;MAAA,IAAA,WAAA;OAAA,OAAA,QAAA;QAAA,MAAA,OAAA,QAAA;QAAA,OAAA,YAAA,MAAA,CAAA,CAAA,IAAA;QAAA,OAAA;OAAA,EAAA,CAAA,GAAA,GAAA;MAAA;KAAA,GAAA,MAAA,GAAA,KAAA;KAAA,OAAA,YAAA,MAAA,CAAA,CAAA,SAAA,KAAA;KAAA,OAAA,MAAAA,gBAAA,MAAA;MAAA,IAAA,OAAA;OAAA,OAAA,MAAA,CAAA,CAAA,OAAA;MAAA;MAAA,IAAA,WAAA;OAAA,OAAA,CAAA,YAAA;QAAA,MAAA,OAAA,QAAA;QAAA,iBAAA,MAAA,eAAA,MAAA,MAAA,OAAA,MAAA,CAAA,CAAA,EAAA,CAAA;QAAA,OAAA,YAAA,MAAA,MAAA,OAAA,MAAA,CAAA,CAAA,EAAA,CAAA;QAAA,OAAA;OAAA,EAAA,CAAA,CAAA;MAAA;KAAA,GAAA,MAAA,CAAA;KAAA,OAAA;IAAA;GAAA,GAAA,KAAA,GAAAA,gBAAA,KAAA;IAAA,IAAA,OAAA;KAAA,OAAA,MAAA,MAAA,UAAA;IAAA;IAAA,MAAA,aAAA,SAAA;IAAA,WAAA,aAAA;KAAA,MAAA,OAAA,QAAA,GAAA,QAAA,KAAA,WAAA,YAAA,aAAA,QAAA,MAAA;KAAA,OAAA,YAAA,SAAA,CAAA,CAAA,SAAA,KAAA;KAAA,OAAA,aAAA,SAAA,CAAA,CAAA,QAAA;KAAA,OAAA,aAAA,SAAA,CAAA,CAAA,MAAA;KAAA,OAAA;IAAA;GAAA,GAAA,KAAA,CAAA;EAAA;CAAA,GAAA,MAAA,CAAA;CAAA,OAAA;AAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { PanelState } from "./state.js";
|
|
2
|
+
import { Component } from "@shadow-js/web/jsx-runtime";
|
|
3
|
+
//#region src/ui/panel.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Props of {@link Panel}.
|
|
6
|
+
*
|
|
7
|
+
* @since 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
export interface PanelProps {
|
|
10
|
+
/**
|
|
11
|
+
* The panel's state, from {@link createPanelState}.
|
|
12
|
+
*/
|
|
13
|
+
state: PanelState;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the panel is shown. A closed panel keeps its state and is hidden.
|
|
16
|
+
*/
|
|
17
|
+
open: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Left edge of the panel in pixels.
|
|
20
|
+
*/
|
|
21
|
+
x: number;
|
|
22
|
+
/**
|
|
23
|
+
* Top edge of the panel in pixels.
|
|
24
|
+
*/
|
|
25
|
+
y: number;
|
|
26
|
+
/**
|
|
27
|
+
* Called while the header is dragged, with the new position in pixels. The panel does not move itself;
|
|
28
|
+
* the owner sets `x` and `y`.
|
|
29
|
+
*/
|
|
30
|
+
onMove(x: number, y: number): void;
|
|
31
|
+
/**
|
|
32
|
+
* Called when the close button is pressed.
|
|
33
|
+
*/
|
|
34
|
+
onClose(): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The devtools panel: a header with the drag handle and the Pause, Flash, Clear and close controls, the
|
|
38
|
+
* tabs (Components, Timeline, State, Issues), the active view and a footer with counts. It only talks to
|
|
39
|
+
* {@link PanelState}, so it works the same on a local agent and on a remote one.
|
|
40
|
+
*
|
|
41
|
+
* Development tool: `mountDevtools()` renders it in development builds only, and it never ships in
|
|
42
|
+
* production builds.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* import { createAgent, createLocalBackend, createPanelState, Panel } from "@shadow-js/devtools";
|
|
46
|
+
* const backend = createLocalBackend(createAgent());
|
|
47
|
+
* export const Devtools = () => {
|
|
48
|
+
* const state = createPanelState(backend);
|
|
49
|
+
* return <Panel state={state} open={true} x={16} y={16} onMove={() => undefined} onClose={() => undefined} />;
|
|
50
|
+
* };
|
|
51
|
+
* @since 1.0.0
|
|
52
|
+
*/
|
|
53
|
+
export declare const Panel: Component<PanelProps>;
|
|
54
|
+
//#endregion
|
|
55
|
+
//# sourceMappingURL=panel.d.ts.map
|