querysub 0.433.0 → 0.437.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/.eslintrc.js +50 -50
- package/bin/deploy.js +0 -0
- package/bin/function.js +0 -0
- package/bin/server.js +0 -0
- package/costsBenefits.txt +115 -115
- package/deploy.ts +2 -2
- package/package.json +1 -1
- package/spec.txt +1192 -1192
- package/src/-a-archives/archives.ts +202 -202
- package/src/-a-archives/archivesDisk.ts +454 -454
- package/src/-a-auth/certs.ts +540 -540
- package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
- package/src/-b-authorities/dnsAuthority.ts +138 -138
- package/src/-c-identity/IdentityController.ts +258 -258
- package/src/-d-trust/NetworkTrust2.ts +180 -180
- package/src/-e-certs/EdgeCertController.ts +252 -252
- package/src/-e-certs/certAuthority.ts +201 -201
- package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
- package/src/-g-core-values/NodeCapabilities.ts +200 -200
- package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
- package/src/0-path-value-core/PathValueCommitter.ts +468 -468
- package/src/0-path-value-core/pathValueCore.ts +2 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
- package/src/2-proxy/TransactionDelayer.ts +94 -94
- package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
- package/src/2-proxy/pathValueProxy.ts +159 -159
- package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
- package/src/3-path-functions/pathFunctionLoader.ts +516 -516
- package/src/3-path-functions/tests/rejectTest.ts +76 -76
- package/src/4-deploy/deployCheck.ts +6 -6
- package/src/4-dom/css.tsx +29 -29
- package/src/4-dom/cssTypes.d.ts +211 -211
- package/src/4-dom/qreact.tsx +2799 -2799
- package/src/4-dom/qreactTest.tsx +410 -410
- package/src/4-querysub/permissions.ts +335 -335
- package/src/4-querysub/querysubPrediction.ts +483 -483
- package/src/5-diagnostics/qreactDebug.tsx +346 -346
- package/src/TestController.ts +34 -34
- package/src/bits.ts +104 -104
- package/src/buffers.ts +69 -69
- package/src/diagnostics/ActionsHistory.ts +57 -57
- package/src/diagnostics/listenOnDebugger.ts +71 -71
- package/src/diagnostics/periodic.ts +111 -111
- package/src/diagnostics/trackResources.ts +91 -91
- package/src/diagnostics/watchdog.ts +120 -120
- package/src/errors.ts +133 -133
- package/src/forceProduction.ts +2 -2
- package/src/fs.ts +80 -80
- package/src/functional/diff.ts +857 -857
- package/src/functional/promiseCache.ts +78 -78
- package/src/functional/random.ts +8 -8
- package/src/functional/stats.ts +60 -60
- package/src/heapDumps.ts +665 -665
- package/src/https.ts +1 -1
- package/src/library-components/AspectSizedComponent.tsx +87 -87
- package/src/library-components/ButtonSelector.tsx +64 -64
- package/src/library-components/DropdownCustom.tsx +150 -150
- package/src/library-components/DropdownSelector.tsx +31 -31
- package/src/library-components/InlinePopup.tsx +66 -66
- package/src/misc/color.ts +29 -29
- package/src/misc/hash.ts +83 -83
- package/src/misc/ipPong.js +13 -13
- package/src/misc/networking.ts +1 -1
- package/src/misc/random.ts +44 -44
- package/src/misc.ts +196 -196
- package/src/path.ts +255 -255
- package/src/persistentLocalStore.ts +41 -41
- package/src/promise.ts +14 -14
- package/src/storage/fileSystemPointer.ts +71 -71
- package/src/test/heapProcess.ts +35 -35
- package/src/zip.ts +15 -15
- package/tsconfig.json +26 -26
- package/yarnSpec.txt +56 -56
|
@@ -1,347 +1,347 @@
|
|
|
1
|
-
import { lazy } from "socket-function/src/caching";
|
|
2
|
-
import { ExternalRenderClass, __INTERNAL__QRenderClass, getSchemaPrefix, getSourceVSCodeLink, qreact, triggerRerenderAll } from "../4-dom/qreact";
|
|
3
|
-
import { blue, green, magenta, yellow } from "socket-function/src/formatting/logColors";
|
|
4
|
-
import { getPathFromStr, getPathStr } from "../path";
|
|
5
|
-
import { delay } from "socket-function/src/batching";
|
|
6
|
-
import { logErrors } from "../errors";
|
|
7
|
-
import { clientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
8
|
-
import { Querysub } from "../4-querysub/QuerysubController";
|
|
9
|
-
import { closeAllModals, showModal } from "./Modal";
|
|
10
|
-
import { FullscreenModal } from "./FullscreenModal";
|
|
11
|
-
import { css } from "typesafecss";
|
|
12
|
-
import { Button } from "../library-components/Button";
|
|
13
|
-
import { authorityStorage } from "../0-path-value-core/pathValueCore";
|
|
14
|
-
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
15
|
-
import { PathValueProxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
16
|
-
import { InputLabel, InputLabelURL } from "../library-components/InputLabel";
|
|
17
|
-
import { URLParam } from "../library-components/URLParam";
|
|
18
|
-
import { hotReloadingGuard, isHotReloading, onHotReload } from "socket-function/hot/HotReloadController";
|
|
19
|
-
import { isCallerDynamicModule, isDynamicModule } from "../3-path-functions/pathFunctionLoader";
|
|
20
|
-
|
|
21
|
-
// Map, so hot reloading doesn't break things
|
|
22
|
-
let componentButtons = new Map<string, { title: string, callback: (component: ExternalRenderClass) => void }>();
|
|
23
|
-
let componentUIs = new Map<string, (component: ExternalRenderClass) => qreact.ComponentChildren>();
|
|
24
|
-
|
|
25
|
-
export function addComponentButton(config: {
|
|
26
|
-
title: string;
|
|
27
|
-
callback: (component: ExternalRenderClass) => void;
|
|
28
|
-
}) {
|
|
29
|
-
if (isCallerDynamicModule()) return;
|
|
30
|
-
if (!isHotReloading() && componentButtons.has(config.title)) {
|
|
31
|
-
throw new Error(`Component button with title ${config.title} already exists`);
|
|
32
|
-
}
|
|
33
|
-
componentButtons.set(config.title, config);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function addComponentUI(config: {
|
|
37
|
-
key: string;
|
|
38
|
-
createUI: (component: ExternalRenderClass) => qreact.ComponentChildren;
|
|
39
|
-
}) {
|
|
40
|
-
if (isCallerDynamicModule()) return;
|
|
41
|
-
if (!isHotReloading() && componentUIs.has(config.key)) {
|
|
42
|
-
throw new Error(`Component UI with key ${config.key} already exists`);
|
|
43
|
-
}
|
|
44
|
-
componentUIs.set(config.key, config.createUI);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export const enableDebugComponents = lazy(function enableDebugComponents() {
|
|
48
|
-
console.log(magenta(`middleclick + shift on components to debug them`));
|
|
49
|
-
// mousedown is more reliable than click, because the click will be aborted if they middle click
|
|
50
|
-
// in something with a scrollbar.
|
|
51
|
-
document.addEventListener("mousedown", function (e) {
|
|
52
|
-
// Middle click to debug
|
|
53
|
-
if (e.button === 1 && (e.altKey || e.shiftKey)) {
|
|
54
|
-
logErrors(triggerDebug(e.target as HTMLElement, e));
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
async function triggerDebug(target: HTMLElement, event: MouseEvent) {
|
|
59
|
-
const component = __INTERNAL__QRenderClass.getInstanceFromDOM(target);
|
|
60
|
-
if (!component) return;
|
|
61
|
-
event.preventDefault();
|
|
62
|
-
event.stopPropagation();
|
|
63
|
-
|
|
64
|
-
let prevInject = qreact.INJECT_LINE_NUMBERS;
|
|
65
|
-
try {
|
|
66
|
-
qreact.INJECT_LINE_NUMBERS = true;
|
|
67
|
-
triggerRerenderAll();
|
|
68
|
-
await clientWatcher.waitForTriggerFinished();
|
|
69
|
-
} finally {
|
|
70
|
-
qreact.INJECT_LINE_NUMBERS = prevInject;
|
|
71
|
-
}
|
|
72
|
-
// Get new target under the mouse
|
|
73
|
-
target = document.elementFromPoint(event.clientX, event.clientY) as HTMLElement;
|
|
74
|
-
|
|
75
|
-
let source = getSourceVSCodeLink(target);
|
|
76
|
-
if (event.shiftKey) {
|
|
77
|
-
let newComponent = __INTERNAL__QRenderClass.getInstanceFromDOM(target) || component;
|
|
78
|
-
showModal({ content: <WatchModal component={newComponent} />, onClose: () => "abortClose" });
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let ancestors = component.getDebugComponentStack().slice(1).reverse();
|
|
83
|
-
let schemaPrefix = getSchemaPrefix();
|
|
84
|
-
let schemaLength = getPathFromStr(schemaPrefix).length;
|
|
85
|
-
function encodePath(pathStr: string) {
|
|
86
|
-
let path = getPathFromStr(pathStr);
|
|
87
|
-
if (pathStr.startsWith(schemaPrefix)) {
|
|
88
|
-
let underPath = path.slice(schemaLength);
|
|
89
|
-
let componentId = Number(underPath[0]);
|
|
90
|
-
let component = __INTERNAL__QRenderClass.getInstanceAllowedUndefined(componentId);
|
|
91
|
-
if (component) {
|
|
92
|
-
path = [component.debugName].concat(underPath.slice(1));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// If paths are to other components, replace the prefix with the component debugName
|
|
96
|
-
return path.map(x => green(x)).join(".");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log(" ");
|
|
100
|
-
console.log(" ");
|
|
101
|
-
console.log(" ");
|
|
102
|
-
if (event.altKey) {
|
|
103
|
-
window.open(source);
|
|
104
|
-
}
|
|
105
|
-
// VSCode link
|
|
106
|
-
if (source) {
|
|
107
|
-
console.log(source);
|
|
108
|
-
}
|
|
109
|
-
console.group(`Component ${blue(component.debugName)}`);
|
|
110
|
-
console.groupCollapsed(` Ancestors (${ancestors.length})`);
|
|
111
|
-
for (let ancestor of ancestors) {
|
|
112
|
-
console.log(" " + ancestor.debugName);
|
|
113
|
-
let source = ancestor.getVSCodeLink();
|
|
114
|
-
if (source) {
|
|
115
|
-
console.log(` ${source}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
console.groupEnd();
|
|
119
|
-
let lastWatches = component.renderWatcher!.lastWatches;
|
|
120
|
-
|
|
121
|
-
let watchPaths = Array.from(lastWatches.paths).filter(x => getPathFromStr(x).length > schemaLength);
|
|
122
|
-
console.groupCollapsed(` Watches (${watchPaths.length}):`);
|
|
123
|
-
for (let key of watchPaths) {
|
|
124
|
-
console.log(" " + encodePath(key));
|
|
125
|
-
}
|
|
126
|
-
console.groupEnd();
|
|
127
|
-
|
|
128
|
-
let parentPaths = Array.from(lastWatches.parentPaths).filter(x => getPathFromStr(x).length > schemaLength);
|
|
129
|
-
if (parentPaths.length > 0) {
|
|
130
|
-
console.groupCollapsed(` Parent Watches (${parentPaths.length}):`);
|
|
131
|
-
for (let key of parentPaths) {
|
|
132
|
-
if (getPathFromStr(key).length <= schemaLength) continue;
|
|
133
|
-
console.log(" " + encodePath(key));
|
|
134
|
-
}
|
|
135
|
-
console.groupEnd();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
console.log(` debugHolder = `, component);
|
|
139
|
-
(globalThis as any).debugHolder = component;
|
|
140
|
-
console.log(` debugInstance`, component.instance);
|
|
141
|
-
(globalThis as any).debugInstance = component.instance;
|
|
142
|
-
|
|
143
|
-
console.groupEnd();
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const pathFilter = new URLParam("pathfilter", "");
|
|
149
|
-
class WatchModal extends qreact.Component<{
|
|
150
|
-
component: ExternalRenderClass;
|
|
151
|
-
}> {
|
|
152
|
-
state = {
|
|
153
|
-
parentNavigate: 0,
|
|
154
|
-
pos: "fill" as "top" | "fill" | "bottom",
|
|
155
|
-
};
|
|
156
|
-
render() {
|
|
157
|
-
let component = this.props.component;
|
|
158
|
-
for (let i = 0; i < this.state.parentNavigate; i++) {
|
|
159
|
-
component = component.getParent() || component;
|
|
160
|
-
}
|
|
161
|
-
let lastWatches = component.renderWatcher?.lastWatches || {
|
|
162
|
-
paths: new Set(),
|
|
163
|
-
parentPaths: new Set(),
|
|
164
|
-
};
|
|
165
|
-
let parent: ExternalRenderClass | undefined;
|
|
166
|
-
try {
|
|
167
|
-
parent = component.getParent();
|
|
168
|
-
} catch (e) {
|
|
169
|
-
console.log(`Failed to get parent`, e);
|
|
170
|
-
}
|
|
171
|
-
function nicerPath(path: string[]) {
|
|
172
|
-
if (path[1] === "PathFunctionRunner") {
|
|
173
|
-
path.splice(1, 1);
|
|
174
|
-
}
|
|
175
|
-
return path;
|
|
176
|
-
}
|
|
177
|
-
let pos = this.state.pos;
|
|
178
|
-
let filter = pathFilter.value.toLowerCase();
|
|
179
|
-
return (
|
|
180
|
-
<FullscreenModal
|
|
181
|
-
onlyExplicitClose
|
|
182
|
-
outerStyle={
|
|
183
|
-
pos === "fill" && {}
|
|
184
|
-
|| pos === "top" && { height: "25vh", padding: 10 }
|
|
185
|
-
|| pos === "bottom" && { height: "25vh", padding: 10, top: undefined, bottom: 0 }
|
|
186
|
-
|| {}
|
|
187
|
-
}
|
|
188
|
-
style={
|
|
189
|
-
{
|
|
190
|
-
...(
|
|
191
|
-
pos === "fill" && {}
|
|
192
|
-
|| pos === "top" && { maxHeight: "100%" }
|
|
193
|
-
|| pos === "bottom" && { maxHeight: "100%" }
|
|
194
|
-
|| {}
|
|
195
|
-
),
|
|
196
|
-
background: "rgb(255, 255, 255)!important",
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
onCancel={() => {
|
|
200
|
-
if (this.state.pos !== "fill") {
|
|
201
|
-
return "abortClose";
|
|
202
|
-
}
|
|
203
|
-
}}
|
|
204
|
-
>
|
|
205
|
-
<div class={css.hsl(0, 0, 100).hslcolor(0, 0, 10).vbox(6)}>
|
|
206
|
-
<div class={css.hbox(10).fillWidth}>
|
|
207
|
-
{component.debugName}
|
|
208
|
-
{parent && <Button onClick={() => this.state.parentNavigate++}>
|
|
209
|
-
Go to parent ({parent.debugName})
|
|
210
|
-
</Button>}
|
|
211
|
-
{this.state.parentNavigate > 0 && <Button onClick={() => this.state.parentNavigate--}>
|
|
212
|
-
Child
|
|
213
|
-
</Button>}
|
|
214
|
-
<div class={css.marginAuto} />
|
|
215
|
-
<Button onClick={() => this.state.pos = "top"}>
|
|
216
|
-
Top
|
|
217
|
-
</Button>
|
|
218
|
-
<Button onClick={() => this.state.pos = "fill"}>
|
|
219
|
-
Fill
|
|
220
|
-
</Button>
|
|
221
|
-
<Button onClick={() => this.state.pos = "bottom"}>
|
|
222
|
-
Bottom
|
|
223
|
-
</Button>
|
|
224
|
-
<Button onClick={() => {
|
|
225
|
-
closeAllModals();
|
|
226
|
-
}}>
|
|
227
|
-
Close
|
|
228
|
-
</Button>
|
|
229
|
-
</div>
|
|
230
|
-
|
|
231
|
-
<div class={css.hbox(8)}>
|
|
232
|
-
<Button onClick={() => component.instance.forceUpdate()}>
|
|
233
|
-
Rerender
|
|
234
|
-
</Button>
|
|
235
|
-
{Array.from(componentButtons.values()).map(({ title, callback }) => (
|
|
236
|
-
<Button onClick={() => callback(component)}>
|
|
237
|
-
{title}
|
|
238
|
-
</Button>
|
|
239
|
-
))}
|
|
240
|
-
</div>
|
|
241
|
-
{Array.from(componentUIs.values()).map((createUI) => createUI(component))}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
<InputLabelURL hot label="Filter" url={pathFilter} />
|
|
245
|
-
|
|
246
|
-
<h4>Parent Paths ({lastWatches.parentPaths.size})</h4>
|
|
247
|
-
<div class={css.vbox(2)}>
|
|
248
|
-
{Array.from(lastWatches.parentPaths).map(path =>
|
|
249
|
-
<div class={css.hbox(4).wrap.button} onClick={() => {
|
|
250
|
-
console.log(path);
|
|
251
|
-
return navigator.clipboard.writeText(path);
|
|
252
|
-
}}>
|
|
253
|
-
{nicerPath(getPathFromStr(path)).map(x => <span class={css.pad2(3, 0).hsl(0, 0, 80)}>{x}</span>)}
|
|
254
|
-
|
|
255
|
-
<span class={css.pad2(3, 2).hsl(180, 75, 75)}>({authorityStorage.getPathsFromParent(path)?.size})</span>
|
|
256
|
-
</div>
|
|
257
|
-
)}
|
|
258
|
-
</div>
|
|
259
|
-
<h4>Paths ({lastWatches.paths.size})</h4>
|
|
260
|
-
{/* TODO: If we render a hierarchy, we will have space where the indent is, to show buttons. */}
|
|
261
|
-
<div class={css.vbox(2).fillWidth}>
|
|
262
|
-
{Array.from(lastWatches.paths).map(path => {
|
|
263
|
-
if (filter && !path.toLowerCase().includes(filter)) {
|
|
264
|
-
return undefined;
|
|
265
|
-
}
|
|
266
|
-
let value = pathValueSerializer.getPathValue(authorityStorage.getValueAtTime(path));
|
|
267
|
-
let valueStr = String(value);
|
|
268
|
-
try {
|
|
269
|
-
valueStr = String(JSON.stringify(value));
|
|
270
|
-
} catch { }
|
|
271
|
-
if (valueStr === "undefined" && typeof value === "function") {
|
|
272
|
-
valueStr = String(value).slice(0, 100);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (valueStr.length > 500) {
|
|
276
|
-
valueStr = valueStr.slice(0, 500);
|
|
277
|
-
}
|
|
278
|
-
let pathArray = nicerPath(getPathFromStr(path));
|
|
279
|
-
let breakOnWrites = PathValueProxyWatcher.BREAK_ON_WRITES.has(path);
|
|
280
|
-
let breakOnReads = PathValueProxyWatcher.BREAK_ON_READS.has(path);
|
|
281
|
-
let logOnRead = PathValueProxyWatcher.LOG_WRITES_INCLUDES.has(path);
|
|
282
|
-
let fncBreak = PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.has(path);
|
|
283
|
-
let selectedButton = css.hsl(120, 75, 75);
|
|
284
|
-
return (
|
|
285
|
-
<div class={css.hbox(10, 0).fillWidth.wrap}>
|
|
286
|
-
<button class={breakOnWrites && selectedButton || ""} onClick={() => {
|
|
287
|
-
if (breakOnWrites) {
|
|
288
|
-
PathValueProxyWatcher.BREAK_ON_WRITES.delete(path);
|
|
289
|
-
} else {
|
|
290
|
-
PathValueProxyWatcher.BREAK_ON_WRITES.add(path);
|
|
291
|
-
}
|
|
292
|
-
this.forceUpdate();
|
|
293
|
-
}}>
|
|
294
|
-
=
|
|
295
|
-
</button>
|
|
296
|
-
<button class={breakOnReads && selectedButton || ""} onClick={() => {
|
|
297
|
-
if (breakOnReads) {
|
|
298
|
-
PathValueProxyWatcher.BREAK_ON_READS.delete(path);
|
|
299
|
-
} else {
|
|
300
|
-
PathValueProxyWatcher.BREAK_ON_READS.add(path);
|
|
301
|
-
}
|
|
302
|
-
this.forceUpdate();
|
|
303
|
-
}}>
|
|
304
|
-
===
|
|
305
|
-
</button>
|
|
306
|
-
<button class={logOnRead && selectedButton || ""} onClick={() => {
|
|
307
|
-
if (logOnRead) {
|
|
308
|
-
PathValueProxyWatcher.LOG_WRITES_INCLUDES.delete(path);
|
|
309
|
-
} else {
|
|
310
|
-
PathValueProxyWatcher.LOG_WRITES_INCLUDES.add(path);
|
|
311
|
-
}
|
|
312
|
-
this.forceUpdate();
|
|
313
|
-
}}>
|
|
314
|
-
Log
|
|
315
|
-
</button>
|
|
316
|
-
<button class={fncBreak && selectedButton || ""} title="Breaks on the caller of the function that mutates this. Only works on the second call (and only if the function is input predicted)." onClick={() => {
|
|
317
|
-
if (breakOnWrites) {
|
|
318
|
-
PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.delete(path);
|
|
319
|
-
} else {
|
|
320
|
-
PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.add(path);
|
|
321
|
-
}
|
|
322
|
-
this.forceUpdate();
|
|
323
|
-
}}>
|
|
324
|
-
Caller*
|
|
325
|
-
</button>
|
|
326
|
-
<div class={css.hbox(4).wrap.button} onClick={() => {
|
|
327
|
-
console.log(path);
|
|
328
|
-
return navigator.clipboard.writeText(path);
|
|
329
|
-
}}>
|
|
330
|
-
{pathArray.map((x, index) => <span class={css.pad2(3, 2).hsl(0, 0, 80)}>
|
|
331
|
-
{x}
|
|
332
|
-
</span>)}
|
|
333
|
-
</div>
|
|
334
|
-
<div class={css.pad2(3, 2).hsl(180, 75, 75).ellipsis.button} onClick={() => {
|
|
335
|
-
console.log(value);
|
|
336
|
-
}}>
|
|
337
|
-
{valueStr}
|
|
338
|
-
</div>
|
|
339
|
-
</div>
|
|
340
|
-
);
|
|
341
|
-
})}
|
|
342
|
-
</div>
|
|
343
|
-
</div>
|
|
344
|
-
</FullscreenModal>
|
|
345
|
-
);
|
|
346
|
-
}
|
|
1
|
+
import { lazy } from "socket-function/src/caching";
|
|
2
|
+
import { ExternalRenderClass, __INTERNAL__QRenderClass, getSchemaPrefix, getSourceVSCodeLink, qreact, triggerRerenderAll } from "../4-dom/qreact";
|
|
3
|
+
import { blue, green, magenta, yellow } from "socket-function/src/formatting/logColors";
|
|
4
|
+
import { getPathFromStr, getPathStr } from "../path";
|
|
5
|
+
import { delay } from "socket-function/src/batching";
|
|
6
|
+
import { logErrors } from "../errors";
|
|
7
|
+
import { clientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
8
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
9
|
+
import { closeAllModals, showModal } from "./Modal";
|
|
10
|
+
import { FullscreenModal } from "./FullscreenModal";
|
|
11
|
+
import { css } from "typesafecss";
|
|
12
|
+
import { Button } from "../library-components/Button";
|
|
13
|
+
import { authorityStorage } from "../0-path-value-core/pathValueCore";
|
|
14
|
+
import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
|
|
15
|
+
import { PathValueProxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
16
|
+
import { InputLabel, InputLabelURL } from "../library-components/InputLabel";
|
|
17
|
+
import { URLParam } from "../library-components/URLParam";
|
|
18
|
+
import { hotReloadingGuard, isHotReloading, onHotReload } from "socket-function/hot/HotReloadController";
|
|
19
|
+
import { isCallerDynamicModule, isDynamicModule } from "../3-path-functions/pathFunctionLoader";
|
|
20
|
+
|
|
21
|
+
// Map, so hot reloading doesn't break things
|
|
22
|
+
let componentButtons = new Map<string, { title: string, callback: (component: ExternalRenderClass) => void }>();
|
|
23
|
+
let componentUIs = new Map<string, (component: ExternalRenderClass) => qreact.ComponentChildren>();
|
|
24
|
+
|
|
25
|
+
export function addComponentButton(config: {
|
|
26
|
+
title: string;
|
|
27
|
+
callback: (component: ExternalRenderClass) => void;
|
|
28
|
+
}) {
|
|
29
|
+
if (isCallerDynamicModule()) return;
|
|
30
|
+
if (!isHotReloading() && componentButtons.has(config.title)) {
|
|
31
|
+
throw new Error(`Component button with title ${config.title} already exists`);
|
|
32
|
+
}
|
|
33
|
+
componentButtons.set(config.title, config);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function addComponentUI(config: {
|
|
37
|
+
key: string;
|
|
38
|
+
createUI: (component: ExternalRenderClass) => qreact.ComponentChildren;
|
|
39
|
+
}) {
|
|
40
|
+
if (isCallerDynamicModule()) return;
|
|
41
|
+
if (!isHotReloading() && componentUIs.has(config.key)) {
|
|
42
|
+
throw new Error(`Component UI with key ${config.key} already exists`);
|
|
43
|
+
}
|
|
44
|
+
componentUIs.set(config.key, config.createUI);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const enableDebugComponents = lazy(function enableDebugComponents() {
|
|
48
|
+
console.log(magenta(`middleclick + shift on components to debug them`));
|
|
49
|
+
// mousedown is more reliable than click, because the click will be aborted if they middle click
|
|
50
|
+
// in something with a scrollbar.
|
|
51
|
+
document.addEventListener("mousedown", function (e) {
|
|
52
|
+
// Middle click to debug
|
|
53
|
+
if (e.button === 1 && (e.altKey || e.shiftKey)) {
|
|
54
|
+
logErrors(triggerDebug(e.target as HTMLElement, e));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
async function triggerDebug(target: HTMLElement, event: MouseEvent) {
|
|
59
|
+
const component = __INTERNAL__QRenderClass.getInstanceFromDOM(target);
|
|
60
|
+
if (!component) return;
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
|
|
64
|
+
let prevInject = qreact.INJECT_LINE_NUMBERS;
|
|
65
|
+
try {
|
|
66
|
+
qreact.INJECT_LINE_NUMBERS = true;
|
|
67
|
+
triggerRerenderAll();
|
|
68
|
+
await clientWatcher.waitForTriggerFinished();
|
|
69
|
+
} finally {
|
|
70
|
+
qreact.INJECT_LINE_NUMBERS = prevInject;
|
|
71
|
+
}
|
|
72
|
+
// Get new target under the mouse
|
|
73
|
+
target = document.elementFromPoint(event.clientX, event.clientY) as HTMLElement;
|
|
74
|
+
|
|
75
|
+
let source = getSourceVSCodeLink(target);
|
|
76
|
+
if (event.shiftKey) {
|
|
77
|
+
let newComponent = __INTERNAL__QRenderClass.getInstanceFromDOM(target) || component;
|
|
78
|
+
showModal({ content: <WatchModal component={newComponent} />, onClose: () => "abortClose" });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let ancestors = component.getDebugComponentStack().slice(1).reverse();
|
|
83
|
+
let schemaPrefix = getSchemaPrefix();
|
|
84
|
+
let schemaLength = getPathFromStr(schemaPrefix).length;
|
|
85
|
+
function encodePath(pathStr: string) {
|
|
86
|
+
let path = getPathFromStr(pathStr);
|
|
87
|
+
if (pathStr.startsWith(schemaPrefix)) {
|
|
88
|
+
let underPath = path.slice(schemaLength);
|
|
89
|
+
let componentId = Number(underPath[0]);
|
|
90
|
+
let component = __INTERNAL__QRenderClass.getInstanceAllowedUndefined(componentId);
|
|
91
|
+
if (component) {
|
|
92
|
+
path = [component.debugName].concat(underPath.slice(1));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// If paths are to other components, replace the prefix with the component debugName
|
|
96
|
+
return path.map(x => green(x)).join(".");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(" ");
|
|
100
|
+
console.log(" ");
|
|
101
|
+
console.log(" ");
|
|
102
|
+
if (event.altKey) {
|
|
103
|
+
window.open(source);
|
|
104
|
+
}
|
|
105
|
+
// VSCode link
|
|
106
|
+
if (source) {
|
|
107
|
+
console.log(source);
|
|
108
|
+
}
|
|
109
|
+
console.group(`Component ${blue(component.debugName)}`);
|
|
110
|
+
console.groupCollapsed(` Ancestors (${ancestors.length})`);
|
|
111
|
+
for (let ancestor of ancestors) {
|
|
112
|
+
console.log(" " + ancestor.debugName);
|
|
113
|
+
let source = ancestor.getVSCodeLink();
|
|
114
|
+
if (source) {
|
|
115
|
+
console.log(` ${source}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
console.groupEnd();
|
|
119
|
+
let lastWatches = component.renderWatcher!.lastWatches;
|
|
120
|
+
|
|
121
|
+
let watchPaths = Array.from(lastWatches.paths).filter(x => getPathFromStr(x).length > schemaLength);
|
|
122
|
+
console.groupCollapsed(` Watches (${watchPaths.length}):`);
|
|
123
|
+
for (let key of watchPaths) {
|
|
124
|
+
console.log(" " + encodePath(key));
|
|
125
|
+
}
|
|
126
|
+
console.groupEnd();
|
|
127
|
+
|
|
128
|
+
let parentPaths = Array.from(lastWatches.parentPaths).filter(x => getPathFromStr(x).length > schemaLength);
|
|
129
|
+
if (parentPaths.length > 0) {
|
|
130
|
+
console.groupCollapsed(` Parent Watches (${parentPaths.length}):`);
|
|
131
|
+
for (let key of parentPaths) {
|
|
132
|
+
if (getPathFromStr(key).length <= schemaLength) continue;
|
|
133
|
+
console.log(" " + encodePath(key));
|
|
134
|
+
}
|
|
135
|
+
console.groupEnd();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(` debugHolder = `, component);
|
|
139
|
+
(globalThis as any).debugHolder = component;
|
|
140
|
+
console.log(` debugInstance`, component.instance);
|
|
141
|
+
(globalThis as any).debugInstance = component.instance;
|
|
142
|
+
|
|
143
|
+
console.groupEnd();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
const pathFilter = new URLParam("pathfilter", "");
|
|
149
|
+
class WatchModal extends qreact.Component<{
|
|
150
|
+
component: ExternalRenderClass;
|
|
151
|
+
}> {
|
|
152
|
+
state = {
|
|
153
|
+
parentNavigate: 0,
|
|
154
|
+
pos: "fill" as "top" | "fill" | "bottom",
|
|
155
|
+
};
|
|
156
|
+
render() {
|
|
157
|
+
let component = this.props.component;
|
|
158
|
+
for (let i = 0; i < this.state.parentNavigate; i++) {
|
|
159
|
+
component = component.getParent() || component;
|
|
160
|
+
}
|
|
161
|
+
let lastWatches = component.renderWatcher?.lastWatches || {
|
|
162
|
+
paths: new Set(),
|
|
163
|
+
parentPaths: new Set(),
|
|
164
|
+
};
|
|
165
|
+
let parent: ExternalRenderClass | undefined;
|
|
166
|
+
try {
|
|
167
|
+
parent = component.getParent();
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.log(`Failed to get parent`, e);
|
|
170
|
+
}
|
|
171
|
+
function nicerPath(path: string[]) {
|
|
172
|
+
if (path[1] === "PathFunctionRunner") {
|
|
173
|
+
path.splice(1, 1);
|
|
174
|
+
}
|
|
175
|
+
return path;
|
|
176
|
+
}
|
|
177
|
+
let pos = this.state.pos;
|
|
178
|
+
let filter = pathFilter.value.toLowerCase();
|
|
179
|
+
return (
|
|
180
|
+
<FullscreenModal
|
|
181
|
+
onlyExplicitClose
|
|
182
|
+
outerStyle={
|
|
183
|
+
pos === "fill" && {}
|
|
184
|
+
|| pos === "top" && { height: "25vh", padding: 10 }
|
|
185
|
+
|| pos === "bottom" && { height: "25vh", padding: 10, top: undefined, bottom: 0 }
|
|
186
|
+
|| {}
|
|
187
|
+
}
|
|
188
|
+
style={
|
|
189
|
+
{
|
|
190
|
+
...(
|
|
191
|
+
pos === "fill" && {}
|
|
192
|
+
|| pos === "top" && { maxHeight: "100%" }
|
|
193
|
+
|| pos === "bottom" && { maxHeight: "100%" }
|
|
194
|
+
|| {}
|
|
195
|
+
),
|
|
196
|
+
background: "rgb(255, 255, 255)!important",
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
onCancel={() => {
|
|
200
|
+
if (this.state.pos !== "fill") {
|
|
201
|
+
return "abortClose";
|
|
202
|
+
}
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
<div class={css.hsl(0, 0, 100).hslcolor(0, 0, 10).vbox(6)}>
|
|
206
|
+
<div class={css.hbox(10).fillWidth}>
|
|
207
|
+
{component.debugName}
|
|
208
|
+
{parent && <Button onClick={() => this.state.parentNavigate++}>
|
|
209
|
+
Go to parent ({parent.debugName})
|
|
210
|
+
</Button>}
|
|
211
|
+
{this.state.parentNavigate > 0 && <Button onClick={() => this.state.parentNavigate--}>
|
|
212
|
+
Child
|
|
213
|
+
</Button>}
|
|
214
|
+
<div class={css.marginAuto} />
|
|
215
|
+
<Button onClick={() => this.state.pos = "top"}>
|
|
216
|
+
Top
|
|
217
|
+
</Button>
|
|
218
|
+
<Button onClick={() => this.state.pos = "fill"}>
|
|
219
|
+
Fill
|
|
220
|
+
</Button>
|
|
221
|
+
<Button onClick={() => this.state.pos = "bottom"}>
|
|
222
|
+
Bottom
|
|
223
|
+
</Button>
|
|
224
|
+
<Button onClick={() => {
|
|
225
|
+
closeAllModals();
|
|
226
|
+
}}>
|
|
227
|
+
Close
|
|
228
|
+
</Button>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div class={css.hbox(8)}>
|
|
232
|
+
<Button onClick={() => component.instance.forceUpdate()}>
|
|
233
|
+
Rerender
|
|
234
|
+
</Button>
|
|
235
|
+
{Array.from(componentButtons.values()).map(({ title, callback }) => (
|
|
236
|
+
<Button onClick={() => callback(component)}>
|
|
237
|
+
{title}
|
|
238
|
+
</Button>
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
{Array.from(componentUIs.values()).map((createUI) => createUI(component))}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
<InputLabelURL hot label="Filter" url={pathFilter} />
|
|
245
|
+
|
|
246
|
+
<h4>Parent Paths ({lastWatches.parentPaths.size})</h4>
|
|
247
|
+
<div class={css.vbox(2)}>
|
|
248
|
+
{Array.from(lastWatches.parentPaths).map(path =>
|
|
249
|
+
<div class={css.hbox(4).wrap.button} onClick={() => {
|
|
250
|
+
console.log(path);
|
|
251
|
+
return navigator.clipboard.writeText(path);
|
|
252
|
+
}}>
|
|
253
|
+
{nicerPath(getPathFromStr(path)).map(x => <span class={css.pad2(3, 0).hsl(0, 0, 80)}>{x}</span>)}
|
|
254
|
+
|
|
255
|
+
<span class={css.pad2(3, 2).hsl(180, 75, 75)}>({authorityStorage.getPathsFromParent(path)?.size})</span>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
<h4>Paths ({lastWatches.paths.size})</h4>
|
|
260
|
+
{/* TODO: If we render a hierarchy, we will have space where the indent is, to show buttons. */}
|
|
261
|
+
<div class={css.vbox(2).fillWidth}>
|
|
262
|
+
{Array.from(lastWatches.paths).map(path => {
|
|
263
|
+
if (filter && !path.toLowerCase().includes(filter)) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
let value = pathValueSerializer.getPathValue(authorityStorage.getValueAtTime(path));
|
|
267
|
+
let valueStr = String(value);
|
|
268
|
+
try {
|
|
269
|
+
valueStr = String(JSON.stringify(value));
|
|
270
|
+
} catch { }
|
|
271
|
+
if (valueStr === "undefined" && typeof value === "function") {
|
|
272
|
+
valueStr = String(value).slice(0, 100);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (valueStr.length > 500) {
|
|
276
|
+
valueStr = valueStr.slice(0, 500);
|
|
277
|
+
}
|
|
278
|
+
let pathArray = nicerPath(getPathFromStr(path));
|
|
279
|
+
let breakOnWrites = PathValueProxyWatcher.BREAK_ON_WRITES.has(path);
|
|
280
|
+
let breakOnReads = PathValueProxyWatcher.BREAK_ON_READS.has(path);
|
|
281
|
+
let logOnRead = PathValueProxyWatcher.LOG_WRITES_INCLUDES.has(path);
|
|
282
|
+
let fncBreak = PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.has(path);
|
|
283
|
+
let selectedButton = css.hsl(120, 75, 75);
|
|
284
|
+
return (
|
|
285
|
+
<div class={css.hbox(10, 0).fillWidth.wrap}>
|
|
286
|
+
<button class={breakOnWrites && selectedButton || ""} onClick={() => {
|
|
287
|
+
if (breakOnWrites) {
|
|
288
|
+
PathValueProxyWatcher.BREAK_ON_WRITES.delete(path);
|
|
289
|
+
} else {
|
|
290
|
+
PathValueProxyWatcher.BREAK_ON_WRITES.add(path);
|
|
291
|
+
}
|
|
292
|
+
this.forceUpdate();
|
|
293
|
+
}}>
|
|
294
|
+
=
|
|
295
|
+
</button>
|
|
296
|
+
<button class={breakOnReads && selectedButton || ""} onClick={() => {
|
|
297
|
+
if (breakOnReads) {
|
|
298
|
+
PathValueProxyWatcher.BREAK_ON_READS.delete(path);
|
|
299
|
+
} else {
|
|
300
|
+
PathValueProxyWatcher.BREAK_ON_READS.add(path);
|
|
301
|
+
}
|
|
302
|
+
this.forceUpdate();
|
|
303
|
+
}}>
|
|
304
|
+
===
|
|
305
|
+
</button>
|
|
306
|
+
<button class={logOnRead && selectedButton || ""} onClick={() => {
|
|
307
|
+
if (logOnRead) {
|
|
308
|
+
PathValueProxyWatcher.LOG_WRITES_INCLUDES.delete(path);
|
|
309
|
+
} else {
|
|
310
|
+
PathValueProxyWatcher.LOG_WRITES_INCLUDES.add(path);
|
|
311
|
+
}
|
|
312
|
+
this.forceUpdate();
|
|
313
|
+
}}>
|
|
314
|
+
Log
|
|
315
|
+
</button>
|
|
316
|
+
<button class={fncBreak && selectedButton || ""} title="Breaks on the caller of the function that mutates this. Only works on the second call (and only if the function is input predicted)." onClick={() => {
|
|
317
|
+
if (breakOnWrites) {
|
|
318
|
+
PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.delete(path);
|
|
319
|
+
} else {
|
|
320
|
+
PathValueProxyWatcher.SET_FUNCTION_WATCH_ON_WRITES.add(path);
|
|
321
|
+
}
|
|
322
|
+
this.forceUpdate();
|
|
323
|
+
}}>
|
|
324
|
+
Caller*
|
|
325
|
+
</button>
|
|
326
|
+
<div class={css.hbox(4).wrap.button} onClick={() => {
|
|
327
|
+
console.log(path);
|
|
328
|
+
return navigator.clipboard.writeText(path);
|
|
329
|
+
}}>
|
|
330
|
+
{pathArray.map((x, index) => <span class={css.pad2(3, 2).hsl(0, 0, 80)}>
|
|
331
|
+
{x}
|
|
332
|
+
</span>)}
|
|
333
|
+
</div>
|
|
334
|
+
<div class={css.pad2(3, 2).hsl(180, 75, 75).ellipsis.button} onClick={() => {
|
|
335
|
+
console.log(value);
|
|
336
|
+
}}>
|
|
337
|
+
{valueStr}
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
})}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
</FullscreenModal>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
347
|
}
|