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.
Files changed (73) hide show
  1. package/.eslintrc.js +50 -50
  2. package/bin/deploy.js +0 -0
  3. package/bin/function.js +0 -0
  4. package/bin/server.js +0 -0
  5. package/costsBenefits.txt +115 -115
  6. package/deploy.ts +2 -2
  7. package/package.json +1 -1
  8. package/spec.txt +1192 -1192
  9. package/src/-a-archives/archives.ts +202 -202
  10. package/src/-a-archives/archivesDisk.ts +454 -454
  11. package/src/-a-auth/certs.ts +540 -540
  12. package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
  13. package/src/-b-authorities/dnsAuthority.ts +138 -138
  14. package/src/-c-identity/IdentityController.ts +258 -258
  15. package/src/-d-trust/NetworkTrust2.ts +180 -180
  16. package/src/-e-certs/EdgeCertController.ts +252 -252
  17. package/src/-e-certs/certAuthority.ts +201 -201
  18. package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
  19. package/src/-g-core-values/NodeCapabilities.ts +200 -200
  20. package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
  21. package/src/0-path-value-core/PathValueCommitter.ts +468 -468
  22. package/src/0-path-value-core/pathValueCore.ts +2 -2
  23. package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
  24. package/src/2-proxy/TransactionDelayer.ts +94 -94
  25. package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
  26. package/src/2-proxy/pathValueProxy.ts +159 -159
  27. package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
  28. package/src/3-path-functions/pathFunctionLoader.ts +516 -516
  29. package/src/3-path-functions/tests/rejectTest.ts +76 -76
  30. package/src/4-deploy/deployCheck.ts +6 -6
  31. package/src/4-dom/css.tsx +29 -29
  32. package/src/4-dom/cssTypes.d.ts +211 -211
  33. package/src/4-dom/qreact.tsx +2799 -2799
  34. package/src/4-dom/qreactTest.tsx +410 -410
  35. package/src/4-querysub/permissions.ts +335 -335
  36. package/src/4-querysub/querysubPrediction.ts +483 -483
  37. package/src/5-diagnostics/qreactDebug.tsx +346 -346
  38. package/src/TestController.ts +34 -34
  39. package/src/bits.ts +104 -104
  40. package/src/buffers.ts +69 -69
  41. package/src/diagnostics/ActionsHistory.ts +57 -57
  42. package/src/diagnostics/listenOnDebugger.ts +71 -71
  43. package/src/diagnostics/periodic.ts +111 -111
  44. package/src/diagnostics/trackResources.ts +91 -91
  45. package/src/diagnostics/watchdog.ts +120 -120
  46. package/src/errors.ts +133 -133
  47. package/src/forceProduction.ts +2 -2
  48. package/src/fs.ts +80 -80
  49. package/src/functional/diff.ts +857 -857
  50. package/src/functional/promiseCache.ts +78 -78
  51. package/src/functional/random.ts +8 -8
  52. package/src/functional/stats.ts +60 -60
  53. package/src/heapDumps.ts +665 -665
  54. package/src/https.ts +1 -1
  55. package/src/library-components/AspectSizedComponent.tsx +87 -87
  56. package/src/library-components/ButtonSelector.tsx +64 -64
  57. package/src/library-components/DropdownCustom.tsx +150 -150
  58. package/src/library-components/DropdownSelector.tsx +31 -31
  59. package/src/library-components/InlinePopup.tsx +66 -66
  60. package/src/misc/color.ts +29 -29
  61. package/src/misc/hash.ts +83 -83
  62. package/src/misc/ipPong.js +13 -13
  63. package/src/misc/networking.ts +1 -1
  64. package/src/misc/random.ts +44 -44
  65. package/src/misc.ts +196 -196
  66. package/src/path.ts +255 -255
  67. package/src/persistentLocalStore.ts +41 -41
  68. package/src/promise.ts +14 -14
  69. package/src/storage/fileSystemPointer.ts +71 -71
  70. package/src/test/heapProcess.ts +35 -35
  71. package/src/zip.ts +15 -15
  72. package/tsconfig.json +26 -26
  73. 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
  }