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