querysub 0.152.0 → 0.154.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 (65) hide show
  1. package/package.json +6 -6
  2. package/src/-b-authorities/cloudflareHelpers.ts +11 -2
  3. package/src/3-path-functions/PathFunctionRunner.ts +168 -97
  4. package/src/3-path-functions/PathFunctionRunnerMain.ts +8 -2
  5. package/src/3-path-functions/pathFunctionLoader.ts +11 -6
  6. package/src/3-path-functions/syncSchema.ts +10 -1
  7. package/src/4-deploy/edgeBootstrap.ts +10 -1
  8. package/src/4-querysub/Querysub.ts +77 -3
  9. package/src/4-querysub/QuerysubController.ts +22 -2
  10. package/src/4-querysub/permissions.ts +33 -2
  11. package/src/4-querysub/querysubPrediction.ts +52 -18
  12. package/src/archiveapps/archiveGCEntry.tsx +38 -0
  13. package/src/archiveapps/archiveJoinEntry.ts +121 -0
  14. package/src/archiveapps/archiveMergeEntry.tsx +47 -0
  15. package/src/archiveapps/compressTest.tsx +59 -0
  16. package/src/archiveapps/lockTest.ts +127 -0
  17. package/src/config.ts +5 -0
  18. package/src/diagnostics/managementPages.tsx +55 -0
  19. package/src/diagnostics/misc-pages/ArchiveInspect.tsx +325 -0
  20. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +781 -0
  21. package/src/diagnostics/misc-pages/ArchiveViewerTable.tsx +156 -0
  22. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +573 -0
  23. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +129 -0
  24. package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +431 -0
  25. package/src/diagnostics/misc-pages/RequireAuditPage.tsx +218 -0
  26. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +206 -0
  27. package/src/diagnostics/misc-pages/TimeRangeView.tsx +648 -0
  28. package/src/diagnostics/misc-pages/archiveViewerFilter.tsx +221 -0
  29. package/src/diagnostics/misc-pages/archiveViewerShared.tsx +76 -0
  30. package/src/email/postmark.tsx +40 -0
  31. package/src/email/sendgrid.tsx +44 -0
  32. package/src/functional/UndoWatch.tsx +133 -0
  33. package/src/functional/diff.ts +858 -0
  34. package/src/functional/promiseCache.ts +67 -0
  35. package/src/functional/random.ts +9 -0
  36. package/src/functional/runCommand.ts +42 -0
  37. package/src/functional/runOnce.ts +7 -0
  38. package/src/functional/stats.ts +61 -0
  39. package/src/functional/throttleRerender.tsx +80 -0
  40. package/src/library-components/AspectSizedComponent.tsx +88 -0
  41. package/src/library-components/Histogram.tsx +338 -0
  42. package/src/library-components/InlinePopup.tsx +67 -0
  43. package/src/library-components/Notifications.tsx +153 -0
  44. package/src/library-components/RenderIfVisible.tsx +80 -0
  45. package/src/library-components/SimpleNotification.tsx +133 -0
  46. package/src/library-components/TabbedUI.tsx +39 -0
  47. package/src/library-components/animateAnyElement.tsx +65 -0
  48. package/src/library-components/errorNotifications.tsx +81 -0
  49. package/src/library-components/placeholder.ts +18 -0
  50. package/src/misc/format2.ts +48 -0
  51. package/src/misc.ts +33 -0
  52. package/src/misc2.ts +5 -0
  53. package/src/server.ts +2 -1
  54. package/src/storage/diskCache.ts +227 -0
  55. package/src/storage/diskCache2.ts +122 -0
  56. package/src/storage/fileSystemPointer.ts +72 -0
  57. package/src/user-implementation/LoginPage.tsx +78 -0
  58. package/src/user-implementation/RequireAuditPage.tsx +219 -0
  59. package/src/user-implementation/SecurityPage.tsx +212 -0
  60. package/src/user-implementation/UserPage.tsx +320 -0
  61. package/src/user-implementation/addSuperUser.ts +21 -0
  62. package/src/user-implementation/canSeeSource.ts +41 -0
  63. package/src/user-implementation/loginEmail.tsx +159 -0
  64. package/src/user-implementation/setEmailKey.ts +20 -0
  65. package/src/user-implementation/userData.ts +974 -0
@@ -0,0 +1,129 @@
1
+ import { ExternalRenderClass, qreact } from "../../4-dom/qreact";
2
+ import { throttleRerender } from "../../functional/throttleRerender";
3
+ import { pathWatcher } from "../../0-path-value-core/pathValueCore";
4
+ import { css } from "../../4-dom/css";
5
+ import { TimeRangeView } from "./TimeRangeView";
6
+ import { URLParam } from "../../library-components/URLParam";
7
+ import { InputLabelURL } from "../../library-components/InputLabel";
8
+ import { addEpsilons } from "../../bits";
9
+ import { formatNumber, formatPercent, formatTime } from "socket-function/src/formatting/format";
10
+ import { nextId, sort } from "socket-function/src/misc";
11
+ import { ATag } from "../../library-components/ATag";
12
+ import { managementPageURL, showingManagementURL } from "../../diagnostics/managementPages";
13
+ import { filterURL } from "../../diagnostics/logs/DiskLoggerPage";
14
+ import { watchShowLocalPaths, watchValueType } from "./LocalWatchViewer";
15
+ import { filtersURL, viewMode } from "./archiveViewerShared";
16
+ import { proxyWatcher } from "../../2-proxy/PathValueProxyWatcher";
17
+ import { LOCAL_DOMAIN_PATH } from "../../0-path-value-core/NodePathAuthorities";
18
+
19
+ const historySecondsURL = new URLParam("historySeconds", 10);
20
+
21
+ export class ComponentSyncStats extends qreact.Component<{ component: ExternalRenderClass }> {
22
+ state = {
23
+ expanded: false,
24
+ };
25
+ @throttleRerender({})
26
+ render() {
27
+ const { component } = this.props;
28
+
29
+ if (!this.state.expanded) {
30
+ return <button onClick={() => this.state.expanded = true}>View Syncing Time Chart</button>;
31
+ }
32
+
33
+ let allPaths = new Set<string>();
34
+ for (let watcher of proxyWatcher.getAllWatchers()) {
35
+ let { paths, parentPaths } = watcher.lastWatches;
36
+ for (let path of paths) {
37
+ if (path.startsWith(LOCAL_DOMAIN_PATH)) continue;
38
+ allPaths.add(path);
39
+ }
40
+ }
41
+
42
+ let nestedPaths = new Set<string>();
43
+ function addNestedPaths(component: ExternalRenderClass) {
44
+ for (let path of component.renderWatcher.lastWatches.paths) {
45
+ nestedPaths.add(path);
46
+ }
47
+ for (let child of component.childComponents.values()) {
48
+ let childComponent = component.getInstance(child);
49
+ if (childComponent) {
50
+ addNestedPaths(childComponent);
51
+ }
52
+ }
53
+ }
54
+ addNestedPaths(component);
55
+
56
+ let syncHistory = pathWatcher.debug_getSyncHistory();
57
+ syncHistory = syncHistory.filter(x => !x.path.startsWith(LOCAL_DOMAIN_PATH));
58
+ let endTime = addEpsilons(syncHistory.at(-1)?.end ?? Date.now(), 1);
59
+ let startTime = Math.max(syncHistory.at(0)?.start ?? 0, endTime - historySecondsURL.value * 1000);
60
+
61
+ let extentTime = (syncHistory.at(-1)?.end ?? 0) - (syncHistory.at(0)?.start ?? 0);
62
+
63
+ return (
64
+ <div className={css.pad2(6, 2).hsla(0, 0, 0, 0.2).vbox(6)}>
65
+ <button onClick={() => this.state.expanded = false}>Collapse</button>
66
+ <div className={css.hbox(20)}>
67
+ <button onClick={() => this.forceUpdate()}>Refresh</button>
68
+ <InputLabelURL
69
+ label={`History seconds (${formatTime(extentTime)} extent)`}
70
+ url={historySecondsURL}
71
+ />
72
+ <div>
73
+ <b>All paths</b>: {syncHistory.length}
74
+ </div>
75
+ </div>
76
+ <>
77
+ <b>
78
+ <ATag lightMode values={[
79
+ filtersURL.getOverride({}),
80
+ managementPageURL.getOverride("LocalWatchViewer"),
81
+ watchValueType.getOverride("watch"),
82
+ viewMode.getOverride("tree"),
83
+ showingManagementURL.getOverride(true),
84
+ watchShowLocalPaths.getOverride(false),
85
+ ]}>
86
+ All Paths
87
+ </ATag>
88
+ </b>
89
+ <ComponentSyncStatsBase startTime={startTime} endTime={endTime} paths={allPaths} />
90
+ </>
91
+ <b>
92
+ <ATag lightMode values={[
93
+ filtersURL.getOverride({ ...filtersURL.value, [nextId()]: { column: "path", match: component.debugName, type: "and" } }),
94
+ managementPageURL.getOverride("LocalWatchViewer"),
95
+ watchValueType.getOverride("component"),
96
+ viewMode.getOverride("tree"),
97
+ showingManagementURL.getOverride(true),
98
+ watchShowLocalPaths.getOverride(false),
99
+ ]}>
100
+ Current + NestedPaths
101
+ </ATag>
102
+ </b>
103
+ <ComponentSyncStatsBase startTime={startTime} endTime={endTime} paths={nestedPaths} />
104
+ </div>
105
+ );
106
+ }
107
+ }
108
+
109
+ export class ComponentSyncStatsBase extends qreact.Component<{
110
+ startTime: number;
111
+ endTime: number;
112
+ paths: Set<string>;
113
+ }> {
114
+ render() {
115
+ const { startTime, endTime, paths } = this.props;
116
+ let syncHistory = pathWatcher.debug_getSyncHistory();
117
+ syncHistory = syncHistory.filter(x => paths.has(x.path));
118
+ syncHistory = syncHistory.filter(x => !(x.end < startTime || x.start >= endTime));
119
+
120
+ return (
121
+ <TimeRangeView
122
+ ranges={syncHistory}
123
+ startTime={startTime}
124
+ endTime={endTime}
125
+ />
126
+ );
127
+ }
128
+ }
129
+
@@ -0,0 +1,431 @@
1
+ import { PathValue, ReadLock, authorityStorage, epochTime, pathValueArchives, pathWatcher } from "../../0-path-value-core/pathValueCore";
2
+ import { PathValueProxyWatcher, doAtomicWrites, proxyWatcher } from "../../2-proxy/PathValueProxyWatcher";
3
+ import { ExternalRenderClass, qreact } from "../../4-dom/qreact";
4
+ import { Querysub } from "../../4-querysub/Querysub";
5
+ import { Table } from "../../5-diagnostics/Table";
6
+ import { appendToPathStr, getPathFromStr, getPathStr } from "../../path";
7
+ import { css } from "typesafecss";
8
+ import { filtersURL, formatPath, viewMode } from "./archiveViewerShared";
9
+ import { nextId, sort } from "socket-function/src/misc";
10
+ import { ArchiveViewerFilterUI, filterDatums } from "./archiveViewerFilter";
11
+ import { ArchiveViewerTree } from "./ArchiveViewerTree";
12
+ import { Button } from "../../library-components/Button";
13
+ import { URLParam } from "../../library-components/URLParam";
14
+ import { Anchor } from "../../library-components/ATag";
15
+ import { ButtonSelector } from "../../library-components/ButtonSelector";
16
+ import { throttleRerender } from "../../functional/throttleRerender";
17
+ import { InputLabel, InputLabelURL } from "../../library-components/InputLabel";
18
+ import { formatPercent, formatTime } from "socket-function/src/formatting/format";
19
+ import { LOCAL_DOMAIN_PATH } from "../../0-path-value-core/NodePathAuthorities";
20
+ import { TimeRange, TimeRangeView, getTimeRangeStats, getUniqueTimeByPath, normalizeTimeRanges } from "./TimeRangeView";
21
+ import { ComponentSyncStatsBase } from "./ComponentSyncStats";
22
+ import { addEpsilons } from "../../bits";
23
+ import { pathValueSerializer } from "../../-h-path-value-serialize/PathValueSerializer";
24
+
25
+ export const watchValueType = new URLParam("valueType", "watch" as "watch" | "component");
26
+ export const watchShowLocalPaths = new URLParam("showLocalPaths", false);
27
+ const filterToComponentsWithPath = new URLParam("filterToComponentsWithPath", "");
28
+ const filterToPathsWithComponent = new URLParam("filterToPathsWithComponent", "");
29
+
30
+ export class LocalWatchViewer extends qreact.Component {
31
+ // NOTE: We don't save this in the URL, because on refresh the times will be entirely different,
32
+ // so old time filters are unlikely to be relevant
33
+ state = {
34
+ filterStartTime: 0,
35
+ filterEndTime: 0,
36
+ };
37
+ getValues(syncHistory: { path: string, start: number, end: number }[]) {
38
+ let watchesAdded = new Set<string>();
39
+ let parentPathsAdded = new Set<string>();
40
+ let fakePathsSet = new Set<string>();
41
+ let fakePaths: PathValue[] = [];
42
+ let time = epochTime;
43
+ let locks: ReadLock[] = [];
44
+
45
+ let componentToPaths = new Map<string, string[]>();
46
+ let pathToComponents = new Map<string, string[]>();
47
+ let showLocalPaths = watchShowLocalPaths.value;
48
+
49
+ let uniqueTimePerPath = getUniqueTimeByPath(syncHistory);
50
+
51
+ for (let watcher of proxyWatcher.getAllWatchers()) {
52
+ let { paths, parentPaths } = watcher.lastWatches;
53
+ for (let path of paths) {
54
+ if (watchesAdded.has(path)) continue;
55
+ if (!showLocalPaths && path.startsWith(LOCAL_DOMAIN_PATH)) continue;
56
+ watchesAdded.add(path);
57
+ if (!fakePathsSet.has(path)) {
58
+ fakePathsSet.add(path);
59
+ fakePaths.push({ path, value: uniqueTimePerPath.get(path) || 0, time, locks, lockCount: 0 });
60
+ }
61
+ }
62
+ for (let parentPath of parentPaths) {
63
+ if (parentPathsAdded.has(parentPath)) continue;
64
+ if (!showLocalPaths && parentPath.startsWith(LOCAL_DOMAIN_PATH)) continue;
65
+ parentPathsAdded.add(parentPath);
66
+ let path = appendToPathStr(parentPath, "%entries()");
67
+ if (!fakePathsSet.has(path)) {
68
+ fakePathsSet.add(path);
69
+ fakePaths.push({ path, value: uniqueTimePerPath.get(path) || 0, time, locks, lockCount: 0 });
70
+ }
71
+ }
72
+ }
73
+
74
+ let componentPaths: PathValue[] = [];
75
+ for (let component of qreact.allComponents) {
76
+ let path: string[] = [];
77
+ {
78
+ let cur: ExternalRenderClass | undefined = component;
79
+ while (cur) {
80
+ path.unshift(cur.debugName);
81
+ cur = cur.getParent();
82
+ }
83
+ }
84
+ let pathStr = getPathStr(path);
85
+ let watchCount = component.renderWatcher.lastWatches.paths.size;
86
+
87
+ let curPaths = Array.from(component.renderWatcher.lastWatches.paths);
88
+ if (!showLocalPaths) {
89
+ curPaths = curPaths.filter(a => !a.startsWith(LOCAL_DOMAIN_PATH));
90
+ if (curPaths.length === 0) continue;
91
+ }
92
+
93
+ componentPaths.push({ path: pathStr, value: watchCount, time, locks, lockCount: 0 });
94
+ componentToPaths.set(pathStr, curPaths);
95
+ for (let componentPath of curPaths) {
96
+ let components = pathToComponents.get(componentPath);
97
+ if (!components) {
98
+ components = [];
99
+ pathToComponents.set(componentPath, components);
100
+ }
101
+ components.push(pathStr);
102
+ }
103
+ }
104
+
105
+ return {
106
+ watches: fakePaths,
107
+ components: componentPaths,
108
+ componentToPaths,
109
+ pathToComponents,
110
+ };
111
+ }
112
+ renderTable(values: PathValue[], componentToPaths: Map<string, string[]>, pathToComponents: Map<string, string[]>, paths: Set<string>, syncHistory: { path: string, start: number, end: number }[]) {
113
+ let filters = filtersURL.value;
114
+ let timesByPath = new Map<string, TimeRange[]>();
115
+ for (let time of syncHistory) {
116
+ let path = time.path;
117
+ let times = timesByPath.get(path);
118
+ if (!times) {
119
+ times = [];
120
+ timesByPath.set(path, times);
121
+ }
122
+ times.push(time);
123
+ }
124
+ let allTimes = syncHistory.filter(x => paths.has(x.path));
125
+ let startTime = this.state.filterStartTime;
126
+ let endTime = this.state.filterEndTime;
127
+ //let allStats = getTimeRangeStats({ ranges: allTimes, startTime, endTime });
128
+ let uniqueTimes = getUniqueTimeByPath(syncHistory);
129
+ let totalUniqueTime = Array.from(uniqueTimes.values()).reduce((a, b) => a + b, 0);
130
+ return (
131
+ <div class={css.vbox(4).fillWidth}>
132
+ <Table
133
+ cellClass={css.pad2(4, 1).bord(1, { h: 0, s: 0, l: 50 }).hsla(0, 0, 95, 0.25)}
134
+ rows={values}
135
+ columns={{
136
+ canGCValue: {
137
+ title: "Debug",
138
+ formatter: (value, context) => {
139
+ const pathValue = context?.row;
140
+ if (!pathValue) return undefined;
141
+ return <div>
142
+ <button onClick={() => {
143
+ console.log(authorityStorage.getValue(pathValue.path));
144
+ }}>Log Current Value</button>
145
+ </div>;
146
+ }
147
+ },
148
+ value: {
149
+ title: "Debug Breakpoints",
150
+ formatter: (value, context) => {
151
+ let pathValue = context?.row;
152
+ if (!pathValue) return undefined;
153
+ let path = pathValue.path;
154
+ let breakOnWrites = PathValueProxyWatcher.BREAK_ON_WRITES.has(path);
155
+ let breakOnReads = PathValueProxyWatcher.BREAK_ON_READS.has(path);
156
+ let logOnRead = PathValueProxyWatcher.LOG_WRITES_INCLUDES.has(path);
157
+ let selectedButton = css.hsl(120, 75, 75);
158
+ return (
159
+ <div class={css.hbox(10, 0).wrap}>
160
+ <button class={breakOnWrites && selectedButton || ""} onClick={() => {
161
+ if (breakOnWrites) {
162
+ PathValueProxyWatcher.BREAK_ON_WRITES.delete(path);
163
+ } else {
164
+ PathValueProxyWatcher.BREAK_ON_WRITES.add(path);
165
+ }
166
+ this.forceUpdate();
167
+ }}>
168
+ =
169
+ </button>
170
+ <button class={breakOnReads && selectedButton || ""} onClick={() => {
171
+ if (breakOnReads) {
172
+ PathValueProxyWatcher.BREAK_ON_READS.delete(path);
173
+ } else {
174
+ PathValueProxyWatcher.BREAK_ON_READS.add(path);
175
+ }
176
+ this.forceUpdate();
177
+ }}>
178
+ ===
179
+ </button>
180
+ <button class={logOnRead && selectedButton || ""} onClick={() => {
181
+ if (logOnRead) {
182
+ PathValueProxyWatcher.LOG_WRITES_INCLUDES.delete(path);
183
+ } else {
184
+ PathValueProxyWatcher.LOG_WRITES_INCLUDES.add(path);
185
+ }
186
+ this.forceUpdate();
187
+ }}>
188
+ Log
189
+ </button>
190
+ </div>
191
+ );
192
+ }
193
+ },
194
+ event: {
195
+ title: "Syncing Time",
196
+ formatter: (value, context) => {
197
+ let pathValue = context?.row;
198
+ if (!pathValue) return undefined;
199
+ let paths = [pathValue.path];
200
+ if (watchValueType.value === "component") {
201
+ paths = componentToPaths.get(pathValue.path) ?? [];
202
+ }
203
+ let time = uniqueTimes.get(pathValue.path) || 0;
204
+ return <div className={css.relative.padding(0, "important")}>
205
+ <div className={
206
+ css.hsla(0, 0, 0, 0.2).absolute.pos(0, 0).fillHeight
207
+ .width(`${(time / totalUniqueTime) * 100}%`)
208
+ } />
209
+ <div className={css.relative.pad2(4, 1)}>
210
+ {formatTime(time)}
211
+ </div>
212
+ </div>;
213
+ }
214
+ },
215
+ time: (
216
+ watchValueType.value === "watch" && ({
217
+ title: "Components",
218
+ formatter: (value, context) => {
219
+ let pathValue = context?.row;
220
+ if (!pathValue) return undefined;
221
+ let components = pathToComponents.get(pathValue.path);
222
+ return (
223
+ <Anchor values={[
224
+ { param: filtersURL, value: undefined },
225
+ { param: watchValueType, value: "component" },
226
+ { param: filterToComponentsWithPath, value: pathValue.path }]
227
+ } onClick={() => {
228
+ this.state.filterStartTime = 0;
229
+ this.state.filterEndTime = 0;
230
+ }}>
231
+ {components?.length || 0}
232
+ </Anchor>
233
+ );
234
+ }
235
+ })
236
+ || ({
237
+ title: "Paths",
238
+ formatter: (value, context) => {
239
+ let pathValue = context?.row;
240
+ if (!pathValue) return undefined;
241
+ let paths = componentToPaths.get(pathValue.path);
242
+ return (
243
+ <Anchor values={[
244
+ { param: filtersURL, value: undefined },
245
+ { param: watchValueType, value: "watch" },
246
+ { param: filterToPathsWithComponent, value: pathValue.path },
247
+ ]}>
248
+ {paths?.length || 0}
249
+ </Anchor>
250
+ );
251
+ }
252
+ })
253
+ ),
254
+ source: {
255
+ title: "Name",
256
+ formatter: (value, context) => {
257
+ let row = context?.row;
258
+ if (!row) return undefined;
259
+ let pathParts = getPathFromStr(row.path);
260
+ let debugName = pathParts.at(-1);
261
+ return (
262
+ <div>
263
+ <div className={css.hbox(10).whiteSpace("nowrap")}>
264
+ {debugName}
265
+
266
+ <a href={"#"} onClick={(e) => {
267
+ e.preventDefault();
268
+ e.stopPropagation();
269
+ let component = Array.from(qreact.allComponents).find(a => a.debugName === debugName);
270
+ if (component) {
271
+ window.open(component.getVSCodeLink(), "_blank");
272
+ }
273
+ }}>
274
+ IDE
275
+ </a>
276
+ </div>
277
+ </div>
278
+ );
279
+ }
280
+ },
281
+ path: {
282
+ formatter: (value) => {
283
+ let pathParts = getPathFromStr(value);
284
+ return (
285
+ <div>
286
+ <div class={css.hbox(0).wrap}>
287
+ {formatPath(pathParts, (part, index) => {
288
+ let newPath = getPathStr(pathParts.slice(0, index + 1));
289
+ filters[nextId()] = { column: "path", match: newPath, type: "and" };
290
+ })}
291
+ </div>
292
+ </div>
293
+ );
294
+ }
295
+ },
296
+ }}
297
+ />
298
+ </div>
299
+ );
300
+ }
301
+ renderTree(values: PathValue[]) {
302
+ return <ArchiveViewerTree
303
+ data={values}
304
+ lite
305
+ valueLabel={"Sync Time"}
306
+ sumFormatter={formatTime}
307
+ />;
308
+ }
309
+ @throttleRerender({})
310
+ render() {
311
+ let now = Date.now();
312
+ let filterStartTime = this.state.filterStartTime;
313
+ let filterEndTime = this.state.filterEndTime;
314
+
315
+ let syncHistory = pathWatcher.debug_getSyncHistory();
316
+ let paths = new Set<string>();
317
+ if (filterStartTime || filterEndTime) {
318
+ syncHistory = normalizeTimeRanges(syncHistory, filterStartTime, filterEndTime);
319
+ paths = new Set(syncHistory.map(a => a.path));
320
+ }
321
+
322
+ let filters = filtersURL.value;
323
+ let state = this.getValues(syncHistory);
324
+ let values = (
325
+ watchValueType.value === "watch" && state.watches
326
+ || watchValueType.value === "component" && state.components
327
+ || []
328
+ );
329
+ let filterObj = filterDatums(values, Object.values(filters));
330
+ if (watchValueType.value === "component" && filterToComponentsWithPath.value) {
331
+ let filter = filterToComponentsWithPath.value;
332
+ filterObj = { ...filterObj };
333
+ filterObj.filtered = filterObj.filtered.filter(a => state.componentToPaths.get(a.path)?.includes(filter));
334
+ }
335
+ if (watchValueType.value === "watch" && filterToPathsWithComponent.value) {
336
+ let filter = filterToPathsWithComponent.value;
337
+ filterObj = { ...filterObj };
338
+ filterObj.filtered = filterObj.filtered.filter(a => state.pathToComponents.get(a.path)?.includes(filter));
339
+ }
340
+
341
+ if (filterStartTime || filterEndTime) {
342
+ filterObj.filtered = filterObj.filtered.filter(a => paths.has(a.path));
343
+ } else {
344
+ let paths = new Set(filterObj.filtered.map(a => a.path));
345
+ if (watchValueType.value === "component") {
346
+ paths = new Set(filterObj.filtered.map(a => a.path).flatMap(a => state.componentToPaths.get(a) ?? []));
347
+ }
348
+ syncHistory = syncHistory.filter(x => paths.has(x.path));
349
+ }
350
+
351
+ let firstTime = syncHistory.at(0)?.start ?? 0;
352
+ let lastTime = syncHistory.at(-1)?.end ?? 0;
353
+
354
+
355
+ if (!filterStartTime) {
356
+ filterStartTime = firstTime;
357
+ } else {
358
+ firstTime = filterStartTime;
359
+ }
360
+ if (!filterEndTime) {
361
+ filterEndTime = addEpsilons(lastTime, 1);
362
+ } else {
363
+ lastTime = filterEndTime;
364
+ }
365
+
366
+ return (
367
+ <div class={css.vbox(4).fillWidth.pad2(10, 4)}>
368
+ <div class={css.hbox(10)}>
369
+ <ButtonSelector<typeof watchValueType.value>
370
+ value={watchValueType.value}
371
+ onChange={value => watchValueType.value = value}
372
+ options={[
373
+ { value: "watch", title: "Watch" },
374
+ { value: "component", title: "Component" },
375
+ ]}
376
+ />
377
+ {filterToComponentsWithPath.value && <Button onClick={() => filterToComponentsWithPath.value = ""}>Clear Component Path Filter</Button>}
378
+ {filterToPathsWithComponent.value && <Button onClick={() => filterToPathsWithComponent.value = ""}>Clear Path Component Filter</Button>}
379
+ </div>
380
+ <ButtonSelector<typeof viewMode.value>
381
+ value={viewMode.value}
382
+ onChange={value => viewMode.value = value}
383
+ options={[
384
+ { value: "table", title: "Table" },
385
+ { value: "tree", title: "Tree" },
386
+ ]}
387
+ />
388
+ <div class={css.hbox(10)}>
389
+ <button onClick={() => this.forceUpdate()}>Refresh</button>
390
+ <InputLabelURL label="Show Local Paths" url={watchShowLocalPaths} checkbox />
391
+ <InputLabel
392
+ label="History Start Offset (s)" number
393
+ value={Math.round((now - filterStartTime) / 1000)}
394
+ onChangeValue={value => {
395
+ let duration = filterEndTime - filterStartTime;
396
+ this.state.filterStartTime = Date.now() - +value * 1000;
397
+ this.state.filterEndTime = this.state.filterStartTime + duration;
398
+ }}
399
+ />
400
+ <InputLabel
401
+ label="History Duration (s)" number
402
+ value={Math.round((filterEndTime - filterStartTime) / 1000)}
403
+ onChangeValue={value => {
404
+ this.state.filterStartTime = filterStartTime;
405
+ this.state.filterEndTime = filterStartTime + +value * 1000;
406
+ }}
407
+ />
408
+ <button onClick={() => {
409
+ this.state.filterStartTime = 0;
410
+ this.state.filterEndTime = 0;
411
+ }}>Reset Time Filter</button>
412
+ </div>
413
+ <TimeRangeView
414
+ ranges={syncHistory}
415
+ startTime={firstTime}
416
+ endTime={lastTime}
417
+ selectTimeRange={(startTime, endTime) => {
418
+ console.log("selectTimeRange", startTime, endTime);
419
+ this.state.filterStartTime = startTime;
420
+ this.state.filterEndTime = endTime;
421
+ }}
422
+ />
423
+ <ArchiveViewerFilterUI filterObj={filterObj} />
424
+ {
425
+ viewMode.value === "table" && this.renderTable(filterObj.filtered, state.componentToPaths, state.pathToComponents, paths, syncHistory)
426
+ || this.renderTree(filterObj.filtered)
427
+ }
428
+ </div>
429
+ );
430
+ }
431
+ }