querysub 0.185.0 → 0.187.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/package.json +2 -2
- package/spec.txt +1 -0
- package/src/0-path-value-core/PathValueCommitter.ts +1 -1
- package/src/0-path-value-core/auditLogs.ts +4 -2
- package/src/1-path-client/RemoteWatcher.ts +7 -3
- package/src/1-path-client/pathValueClientWatcher.ts +86 -51
- package/src/2-proxy/PathValueProxyWatcher.ts +88 -11
- package/src/4-dom/qreact.tsx +47 -21
- package/src/4-querysub/Querysub.ts +116 -12
- package/src/5-diagnostics/FullscreenModal.tsx +56 -39
- package/src/5-diagnostics/Modal.tsx +6 -3
- package/src/5-diagnostics/qreactDebug.tsx +10 -5
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +4 -1
- package/src/errors.ts +1 -0
- package/src/library-components/ATag.tsx +9 -0
- package/src/library-components/icons.tsx +8 -0
- package/src/misc/format2.ts +19 -3
- package/src/user-implementation/UserPage.tsx +5 -3
- package/src/user-implementation/userData.ts +14 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.187.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -54,4 +54,4 @@
|
|
|
54
54
|
"resolutions": {
|
|
55
55
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
package/spec.txt
CHANGED
|
@@ -51,6 +51,7 @@ More corruption resistance files
|
|
|
51
51
|
- Value types will be string, float64, byte, Buffer[]
|
|
52
52
|
- and... we might as well add support for short, int, float, and uint (uint is a good way to store a guid, via storing 8 uint variables).
|
|
53
53
|
|
|
54
|
+
|
|
54
55
|
Schema/binary PathValues accesses
|
|
55
56
|
|
|
56
57
|
Base code
|
|
@@ -461,7 +461,7 @@ class PathValueCommitter {
|
|
|
461
461
|
// NOTE: specialInitialParentCausedRejections is a bit dangerous, because it can reject golden values.
|
|
462
462
|
// This means we have to ingest it immediately, otherwise authorityStorage might gc path values
|
|
463
463
|
// (that are before a golden value), when rejections might remove that golden value.
|
|
464
|
-
if (specialInitialParentCausedRejections) {
|
|
464
|
+
if (specialInitialParentCausedRejections?.length) {
|
|
465
465
|
this.ingestValidStates(specialInitialParentCausedRejections);
|
|
466
466
|
}
|
|
467
467
|
|
|
@@ -34,13 +34,15 @@ export function getLogHistoryIncludes(includes: string) {
|
|
|
34
34
|
return Object.values(x.values).some(y => String(y).includes(includes));
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
(globalThis as any).
|
|
38
|
-
(globalThis as any).
|
|
37
|
+
(globalThis as any).auditPath = getLogHistoryIncludes;
|
|
38
|
+
(globalThis as any).auditValue = getLogHistoryIncludes;
|
|
39
39
|
export function getLogHistoryEquals(value: string) {
|
|
40
40
|
return logHistory.getAllUnordered().filter(x => {
|
|
41
41
|
return Object.values(x.values).some(y => y === value);
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
+
(globalThis as any).getLogHistoryEquals = getLogHistoryEquals;
|
|
45
|
+
(globalThis as any).getPathAuditLogs = getLogHistoryEquals;
|
|
44
46
|
|
|
45
47
|
export function auditLog(type: string, values: { [key: string]: unknown }) {
|
|
46
48
|
debugLogFnc(type, values);
|
|
@@ -128,15 +128,19 @@ export class RemoteWatcher {
|
|
|
128
128
|
public watchLatest(config: WatchConfig & { debugName?: string }) {
|
|
129
129
|
logErrors(this.watchLatestPromise(config));
|
|
130
130
|
}
|
|
131
|
+
// IMPORTANT! If this shows up as being slow in a profile, try again with devtools closed! It is often slow because async calls are many times slower when devtools is open...
|
|
131
132
|
// NOTE: This is private, as who wants to wait for watch to finish?
|
|
132
133
|
// 1) The value won't be ready when it finishes.
|
|
133
134
|
// 2) If it errors out, they can't do anything to fix the error
|
|
134
135
|
// 3) We retry internally anyways
|
|
135
136
|
private watchLatestPromise(config: WatchConfig & { debugName?: string }) {
|
|
136
|
-
// NOTE: If none of the values are remote... early out. This has been
|
|
137
|
+
// NOTE: If none of the values are remote... early out. This has been profiled to save a bit of time,
|
|
137
138
|
// mostly due to avoiding the async call.
|
|
138
|
-
if (config.parentPaths.length === 0
|
|
139
|
-
|
|
139
|
+
if (config.parentPaths.length === 0) {
|
|
140
|
+
let isSelf = measureBlock(() => config.paths.every(x => pathValueAuthority2.isSelfAuthority(x)), "RemoteWatcher()|watchLatestPromise|isSelfCheck");
|
|
141
|
+
if (isSelf) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
140
144
|
}
|
|
141
145
|
return this.watchLatestBase(config);
|
|
142
146
|
}
|
|
@@ -440,6 +440,7 @@ export class ClientWatcher {
|
|
|
440
440
|
this.allWatchers.delete(callback);
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
+
@measureFnc
|
|
443
444
|
private updateUnwatches(watchSpec: WatchSpec) {
|
|
444
445
|
const { callback, paths, parentPaths } = watchSpec;
|
|
445
446
|
let prevSpec = this.allWatchers.get(callback);
|
|
@@ -488,14 +489,15 @@ export class ClientWatcher {
|
|
|
488
489
|
watchSpec = prevSpec;
|
|
489
490
|
|
|
490
491
|
let expiryTime = Date.now() + ClientWatcher.WATCH_STICK_TIME;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
492
|
+
// Commented out, as this is showing up as a bit slow in profiling
|
|
493
|
+
// if (isDevDebugbreak()) {
|
|
494
|
+
// for (let path of fullyUnwatchedPaths) {
|
|
495
|
+
// auditLog("clientWatcher UNWATCH", { path });
|
|
496
|
+
// }
|
|
497
|
+
// for (let path of fullyUnwatchedParents) {
|
|
498
|
+
// auditLog("clientWatcher UNWATCH PARENT", { path });
|
|
499
|
+
// }
|
|
500
|
+
// }
|
|
499
501
|
for (let path of fullyUnwatchedPaths) {
|
|
500
502
|
this.pendingUnwatches.set(path, expiryTime);
|
|
501
503
|
}
|
|
@@ -550,35 +552,39 @@ export class ClientWatcher {
|
|
|
550
552
|
this.updateUnwatches(watchSpec);
|
|
551
553
|
|
|
552
554
|
this.allWatchers.set(callback, watchSpec);
|
|
553
|
-
|
|
554
|
-
let
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
for (let path of parentPaths) {
|
|
563
|
-
let basePath = hack_stripPackedPath(path);
|
|
564
|
-
let watchersBase = this.parentValueFunctionWatchers.get(basePath);
|
|
565
|
-
if (!watchersBase) {
|
|
566
|
-
watchersBase = new Map();
|
|
567
|
-
this.parentValueFunctionWatchers.set(basePath, watchersBase);
|
|
555
|
+
measureBlock(() => {
|
|
556
|
+
for (let path of paths) {
|
|
557
|
+
let watchers = this.valueFunctionWatchers.get(path);
|
|
558
|
+
if (!watchers) {
|
|
559
|
+
watchers = new Map();
|
|
560
|
+
this.valueFunctionWatchers.set(path, watchers);
|
|
561
|
+
}
|
|
562
|
+
watchers.set(callback, watchSpec);
|
|
563
|
+
this.pendingUnwatches.delete(path);
|
|
568
564
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
565
|
+
}, "ClientWatcher()|setWatches|paths");
|
|
566
|
+
measureBlock(() => {
|
|
567
|
+
for (let path of parentPaths) {
|
|
568
|
+
let basePath = hack_stripPackedPath(path);
|
|
569
|
+
let watchersBase = this.parentValueFunctionWatchers.get(basePath);
|
|
570
|
+
if (!watchersBase) {
|
|
571
|
+
watchersBase = new Map();
|
|
572
|
+
this.parentValueFunctionWatchers.set(basePath, watchersBase);
|
|
573
|
+
}
|
|
574
|
+
let watchers = watchersBase.get(path);
|
|
575
|
+
if (!watchers) {
|
|
576
|
+
let range = decodeParentFilter(path) || { start: 0, end: 1 };
|
|
577
|
+
watchers = {
|
|
578
|
+
start: range.start,
|
|
579
|
+
end: range.end,
|
|
580
|
+
lookup: new Map()
|
|
581
|
+
};
|
|
582
|
+
watchersBase.set(path, watchers);
|
|
583
|
+
}
|
|
584
|
+
watchers.lookup.set(callback, watchSpec);
|
|
585
|
+
this.pendingParentUnwatches.delete(path);
|
|
578
586
|
}
|
|
579
|
-
|
|
580
|
-
this.pendingParentUnwatches.delete(path);
|
|
581
|
-
}
|
|
587
|
+
}, "ClientWatcher()|setWatches|parentPaths");
|
|
582
588
|
|
|
583
589
|
// Audit code to check if we failed to correctly maintain our paths
|
|
584
590
|
// OR (more likely), the watcher mutated the paths Set, which breaks our watching
|
|
@@ -604,27 +610,33 @@ export class ClientWatcher {
|
|
|
604
610
|
}
|
|
605
611
|
*/
|
|
606
612
|
|
|
607
|
-
let pathsArray
|
|
608
|
-
let parentPathsArray
|
|
613
|
+
let pathsArray!: string[];
|
|
614
|
+
let parentPathsArray!: string[];
|
|
615
|
+
measureBlock(() => {
|
|
616
|
+
pathsArray = Array.from(paths);
|
|
617
|
+
parentPathsArray = Array.from(parentPaths);
|
|
618
|
+
}, "ClientWatcher()|setWatches|Array.from");
|
|
609
619
|
let debugName = watchSpec.callback.name;
|
|
610
620
|
|
|
611
621
|
if (pathsArray.length === 0 && parentPathsArray.length === 0) {
|
|
612
622
|
return;
|
|
613
623
|
}
|
|
614
624
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
625
|
+
measureBlock(() => {
|
|
626
|
+
// Watch it locally as well, so when we get the value we know to trigger the client callback
|
|
627
|
+
pathWatcher.watchPath({
|
|
628
|
+
callback: getOwnNodeId(),
|
|
629
|
+
paths: pathsArray,
|
|
630
|
+
parentPaths: parentPathsArray,
|
|
631
|
+
debugName,
|
|
632
|
+
noInitialTrigger: watchSpec.noInitialTrigger,
|
|
633
|
+
});
|
|
634
|
+
remoteWatcher.watchLatest({
|
|
635
|
+
paths: pathsArray,
|
|
636
|
+
parentPaths: parentPathsArray,
|
|
637
|
+
debugName,
|
|
638
|
+
});
|
|
639
|
+
}, "ClientWatcher()|setWatches|watchCalls");
|
|
628
640
|
}
|
|
629
641
|
|
|
630
642
|
private lastVersions = registerResource("paths|lastVersion", new Map<number, number>());
|
|
@@ -764,7 +776,30 @@ export class ClientWatcher {
|
|
|
764
776
|
}
|
|
765
777
|
|
|
766
778
|
public pathHasAnyWatchers(path: string) {
|
|
767
|
-
|
|
779
|
+
let parentPath = getParentPathStr(path);
|
|
780
|
+
return !!this.valueFunctionWatchers.get(path)?.size || !!this.parentValueFunctionWatchers.get(parentPath)?.size;
|
|
781
|
+
}
|
|
782
|
+
public getWatchersForPath(path: string) {
|
|
783
|
+
let matched = new Set<WatchSpec>();
|
|
784
|
+
let parentPath = getParentPathStr(path);
|
|
785
|
+
let watchers = this.valueFunctionWatchers.get(path);
|
|
786
|
+
if (watchers) {
|
|
787
|
+
for (let watcher of watchers.values()) {
|
|
788
|
+
matched.add(watcher);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
let parentWatchers = this.parentValueFunctionWatchers.get(parentPath);
|
|
792
|
+
if (parentWatchers) {
|
|
793
|
+
for (let [_, watcherObj] of parentWatchers) {
|
|
794
|
+
for (let watcher of watcherObj.lookup.values()) {
|
|
795
|
+
if (!matchesParentRangeFilter({ parentPath, fullPath: path, start: watcherObj.start, end: watcherObj.end })) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
matched.add(watcher);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return matched;
|
|
768
803
|
}
|
|
769
804
|
}
|
|
770
805
|
export const clientWatcher = new ClientWatcher();
|
|
@@ -32,7 +32,7 @@ import { DEPTH_TO_DATA, MODULE_INDEX, getCurrentCall, getCurrentCallObj } from "
|
|
|
32
32
|
import { inlineNestedCalls } from "../3-path-functions/syncSchema";
|
|
33
33
|
import { interceptCallsBase, runCall } from "../3-path-functions/PathFunctionHelpers";
|
|
34
34
|
import { deepCloneCborx } from "../misc/cloneHelpers";
|
|
35
|
-
import { formatPercent } from "socket-function/src/formatting/format";
|
|
35
|
+
import { formatPercent, formatTime } from "socket-function/src/formatting/format";
|
|
36
36
|
import { addStatPeriodic, interceptCalls, onAllPredictionsFinished, onTimeProfile } from "../-0-hooks/hooks";
|
|
37
37
|
import { onNextPaint } from "../functional/onNextPaint";
|
|
38
38
|
|
|
@@ -202,6 +202,12 @@ export interface WatcherOptions<Result> {
|
|
|
202
202
|
* what you want, it depends on the usecase.
|
|
203
203
|
*/
|
|
204
204
|
runOncePerPaint?: string;
|
|
205
|
+
|
|
206
|
+
/** Logs time to go from unsynced to synced, and the paths that were unloaded.
|
|
207
|
+
* - In order to tell what paths to pre-load, and how much time we are waiting to load data.
|
|
208
|
+
* - Also to tell us how long and how many paths are loading on startup.
|
|
209
|
+
*/
|
|
210
|
+
logSyncTimings?: boolean;
|
|
205
211
|
}
|
|
206
212
|
|
|
207
213
|
let harvestableReadyLoopCount = 0;
|
|
@@ -330,6 +336,15 @@ export type SyncWatcher = {
|
|
|
330
336
|
hackHistory: { message: string; time: number }[];
|
|
331
337
|
|
|
332
338
|
createTime: number;
|
|
339
|
+
|
|
340
|
+
logSyncTimings?: {
|
|
341
|
+
startTime: number;
|
|
342
|
+
unsyncedPaths: Set<string>;
|
|
343
|
+
unsyncedStages: {
|
|
344
|
+
paths: string[];
|
|
345
|
+
time: number;
|
|
346
|
+
}[];
|
|
347
|
+
};
|
|
333
348
|
}
|
|
334
349
|
function addToHistory(watcher: SyncWatcher, message: string) {
|
|
335
350
|
watcher.hackHistory.push({ message, time: Date.now() });
|
|
@@ -360,7 +375,7 @@ export function atomicObjectRead<T>(obj: T): T {
|
|
|
360
375
|
return (obj as any)[readNoProxy];
|
|
361
376
|
}
|
|
362
377
|
export const atomic = atomicObjectRead;
|
|
363
|
-
(
|
|
378
|
+
(globalThis as any).atomic = atomic;
|
|
364
379
|
|
|
365
380
|
export function doUnatomicWrites<T>(callback: () => T): T {
|
|
366
381
|
return doProxyOptions({ atomicWrites: false }, callback);
|
|
@@ -860,7 +875,6 @@ export class PathValueProxyWatcher {
|
|
|
860
875
|
return getKeys(pathValue?.value) as string[];
|
|
861
876
|
}
|
|
862
877
|
|
|
863
|
-
let keys: string[] = [];
|
|
864
878
|
let childPaths = authorityStorage.getPathsFromParent(pathStr);
|
|
865
879
|
|
|
866
880
|
// We need to also get keys from pendingWrites
|
|
@@ -877,23 +891,26 @@ export class PathValueProxyWatcher {
|
|
|
877
891
|
}
|
|
878
892
|
}
|
|
879
893
|
|
|
894
|
+
let keys = new Set<string>();
|
|
880
895
|
if (childPaths) {
|
|
881
896
|
let targetDepth = getPathDepth(pathStr);
|
|
882
897
|
for (let childPath of childPaths) {
|
|
883
898
|
let key = getPathIndexAssert(childPath, targetDepth);
|
|
884
899
|
let childValue = this.getCallback(childPath, undefined, "readTransparent");
|
|
885
900
|
if (childValue?.value !== undefined) {
|
|
886
|
-
keys.
|
|
901
|
+
keys.add(key);
|
|
887
902
|
}
|
|
888
903
|
}
|
|
889
904
|
}
|
|
890
905
|
|
|
906
|
+
let keysArray = Array.from(keys);
|
|
907
|
+
|
|
891
908
|
// NOTE: Because getPathsFromParent does not preserve order, we have to sort here to ensure
|
|
892
909
|
// we provide a consistent order (which might not be the order the user wants, but at least
|
|
893
910
|
// it will always fail if it does fail).
|
|
894
|
-
|
|
911
|
+
keysArray.sort();
|
|
895
912
|
|
|
896
|
-
return
|
|
913
|
+
return keysArray;
|
|
897
914
|
};
|
|
898
915
|
|
|
899
916
|
private getSymbol = (pathStr: string, symbol: symbol): { value: unknown } | undefined => {
|
|
@@ -1119,6 +1136,8 @@ export class PathValueProxyWatcher {
|
|
|
1119
1136
|
for (let callback of disposeCallbacks) {
|
|
1120
1137
|
logErrors(((async () => { await callback(); }))());
|
|
1121
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
self.allWatchersLookup.delete(trigger);
|
|
1122
1141
|
}
|
|
1123
1142
|
function runWatcher() {
|
|
1124
1143
|
watcher.pendingWrites.clear();
|
|
@@ -1253,8 +1272,56 @@ export class PathValueProxyWatcher {
|
|
|
1253
1272
|
const specialPromiseUnsynced = watcher.specialPromiseUnsynced;
|
|
1254
1273
|
watcher.specialPromiseUnsynced = false;
|
|
1255
1274
|
|
|
1275
|
+
let anyUnsynced = watcher.hasAnyUnsyncedAccesses();
|
|
1276
|
+
if (watcher.options.logSyncTimings) {
|
|
1277
|
+
if (anyUnsynced) {
|
|
1278
|
+
watcher.logSyncTimings = watcher.logSyncTimings || {
|
|
1279
|
+
startTime: Date.now(),
|
|
1280
|
+
unsyncedPaths: new Set(),
|
|
1281
|
+
unsyncedStages: [],
|
|
1282
|
+
};
|
|
1283
|
+
let newPaths: string[] = [];
|
|
1284
|
+
let set = watcher.logSyncTimings.unsyncedPaths;
|
|
1285
|
+
function addPath(path: string) {
|
|
1286
|
+
if (set.has(path)) return;
|
|
1287
|
+
set.add(path);
|
|
1288
|
+
newPaths.push(path);
|
|
1289
|
+
}
|
|
1290
|
+
for (let path of watcher.lastUnsyncedAccesses) {
|
|
1291
|
+
addPath(path);
|
|
1292
|
+
}
|
|
1293
|
+
for (let path of watcher.lastUnsyncedParentAccesses) {
|
|
1294
|
+
addPath(path);
|
|
1295
|
+
}
|
|
1296
|
+
if (newPaths.length > 0) {
|
|
1297
|
+
watcher.logSyncTimings.unsyncedStages.push({
|
|
1298
|
+
paths: newPaths,
|
|
1299
|
+
time: Date.now(),
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
} else {
|
|
1303
|
+
if (watcher.logSyncTimings) {
|
|
1304
|
+
let now = Date.now();
|
|
1305
|
+
console.groupCollapsed(`${watcher.debugName} synced in ${formatTime(now - watcher.logSyncTimings.startTime)}`);
|
|
1306
|
+
// Log the stages
|
|
1307
|
+
let stages = watcher.logSyncTimings.unsyncedStages;
|
|
1308
|
+
for (let i = 0; i < stages.length; i++) {
|
|
1309
|
+
let nextTime = stages[i + 1]?.time || now;
|
|
1310
|
+
let stage = stages[i];
|
|
1311
|
+
console.groupCollapsed(`${formatTime(nextTime - stage.time)}`);
|
|
1312
|
+
for (let path of stage.paths) {
|
|
1313
|
+
console.log(path);
|
|
1314
|
+
}
|
|
1315
|
+
console.groupEnd();
|
|
1316
|
+
}
|
|
1317
|
+
console.groupEnd();
|
|
1318
|
+
watcher.logSyncTimings = undefined;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1256
1323
|
if (!watcher.options.static) {
|
|
1257
|
-
if (!
|
|
1324
|
+
if (!anyUnsynced) {
|
|
1258
1325
|
watcher.countSinceLastFullSync = 0;
|
|
1259
1326
|
watcher.lastSyncTime = Date.now();
|
|
1260
1327
|
watcher.syncRunCount++;
|
|
@@ -1701,6 +1768,8 @@ export class PathValueProxyWatcher {
|
|
|
1701
1768
|
trigger = measureWrap(trigger, `(watcher) ${watcher.debugName}`);
|
|
1702
1769
|
watcher.explicitlyTrigger = trigger;
|
|
1703
1770
|
|
|
1771
|
+
self.allWatchersLookup.set(trigger, watcher);
|
|
1772
|
+
|
|
1704
1773
|
if (options.static) {
|
|
1705
1774
|
// Do nothing, this will be explicitly triggered when needed
|
|
1706
1775
|
} else if (options.runImmediately) {
|
|
@@ -1880,6 +1949,10 @@ export class PathValueProxyWatcher {
|
|
|
1880
1949
|
|
|
1881
1950
|
|
|
1882
1951
|
private allWatchers = registerResource("paths|proxyWatcher.allWatchers", new Set<SyncWatcher>());
|
|
1952
|
+
private allWatchersLookup = registerResource("paths|proxyWatcher.allWatchersLookup", new Map<Function, SyncWatcher>());
|
|
1953
|
+
public getWatcherForTrigger(trigger: Function): SyncWatcher | undefined {
|
|
1954
|
+
return this.allWatchersLookup.get(trigger);
|
|
1955
|
+
}
|
|
1883
1956
|
public getAllWatchers(): Set<SyncWatcher> {
|
|
1884
1957
|
return this.allWatchers;
|
|
1885
1958
|
}
|
|
@@ -2025,6 +2098,10 @@ export class PathValueProxyWatcher {
|
|
|
2025
2098
|
}
|
|
2026
2099
|
return getPathFromProxy(proxy);
|
|
2027
2100
|
}
|
|
2101
|
+
|
|
2102
|
+
public ignoreWatches<T>(code: () => T) {
|
|
2103
|
+
return doProxyOptions({ noSyncing: true }, code);
|
|
2104
|
+
}
|
|
2028
2105
|
}
|
|
2029
2106
|
|
|
2030
2107
|
const PAINT_NOOP_FUNCTION = () => { };
|
|
@@ -2146,13 +2223,13 @@ export function noAtomicSchema<T>(code: () => T) {
|
|
|
2146
2223
|
|
|
2147
2224
|
|
|
2148
2225
|
export const proxyWatcher = new PathValueProxyWatcher();
|
|
2149
|
-
(
|
|
2150
|
-
(
|
|
2226
|
+
(globalThis as any).proxyWatcher = proxyWatcher;
|
|
2227
|
+
(globalThis as any).ProxyWatcher = PathValueProxyWatcher;
|
|
2151
2228
|
// Probably the most useful debug function for debugging watch based functions.
|
|
2152
|
-
(
|
|
2229
|
+
(globalThis as any).getCurrentTriggers = function () {
|
|
2153
2230
|
return proxyWatcher.getTriggeredWatcher().triggeredByChanges;
|
|
2154
2231
|
};
|
|
2155
|
-
(
|
|
2232
|
+
(globalThis as any).getAllWatchers = function () {
|
|
2156
2233
|
return proxyWatcher.getAllWatchers();
|
|
2157
2234
|
};
|
|
2158
2235
|
|
package/src/4-dom/qreact.tsx
CHANGED
|
@@ -134,6 +134,9 @@ export interface QComponentStatic {
|
|
|
134
134
|
* UNLESS the child explicitly sets this to false.
|
|
135
135
|
* */
|
|
136
136
|
multiRendersPerPaint?: boolean;
|
|
137
|
+
|
|
138
|
+
/** If true, logs time it takes for component to load synced data (if at all), and the paths that were unloaded. */
|
|
139
|
+
logLoadTime?: boolean;
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
/** See QComponentStatic.jsonComparePropUpdates */
|
|
@@ -172,6 +175,8 @@ export class qreact {
|
|
|
172
175
|
|
|
173
176
|
public static errorHandler: ErrorHandler;
|
|
174
177
|
|
|
178
|
+
public static domUpdateCount = 0;
|
|
179
|
+
|
|
175
180
|
public static debug = async () => {
|
|
176
181
|
const { enableDebugComponents } = await import("../5-diagnostics/qreactDebug");
|
|
177
182
|
enableDebugComponents();
|
|
@@ -414,7 +419,14 @@ function getAncestor(
|
|
|
414
419
|
}
|
|
415
420
|
function onDispose(fnc: () => void) {
|
|
416
421
|
let component = getRenderingComponent();
|
|
417
|
-
if (!component)
|
|
422
|
+
if (!component) {
|
|
423
|
+
if (QRenderClass.renderingComponentId !== undefined) {
|
|
424
|
+
// If it's already disposed... call the function immediately?
|
|
425
|
+
fnc();
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`Can only call onDispose if inside of a render (or componentDidMount) function. If an an event callback, call qreact.getRenderingComponent in render, and then call qreact.watchDispose(component, callback).`);
|
|
429
|
+
}
|
|
418
430
|
watchDispose(component, fnc);
|
|
419
431
|
}
|
|
420
432
|
function watchDispose(renderClass: ExternalRenderClass, fnc: () => void) {
|
|
@@ -490,6 +502,23 @@ class QRenderClass {
|
|
|
490
502
|
|
|
491
503
|
let vNode = config.vNode;
|
|
492
504
|
let self = this;
|
|
505
|
+
|
|
506
|
+
const contextCommit = (callback: () => void) => {
|
|
507
|
+
void Promise.resolve().finally(() => {
|
|
508
|
+
logErrors(proxyWatcher.commitFunction({
|
|
509
|
+
canWrite: true,
|
|
510
|
+
watchFunction() {
|
|
511
|
+
QRenderClass.renderingComponentId = self.id;
|
|
512
|
+
try {
|
|
513
|
+
callback();
|
|
514
|
+
} finally {
|
|
515
|
+
QRenderClass.renderingComponentId = undefined;
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
}));
|
|
519
|
+
});
|
|
520
|
+
};
|
|
521
|
+
|
|
493
522
|
// Set up watchers for virtual watching AND dom mounting
|
|
494
523
|
let debugName = vNode.type.name + `|${this.id}|`;
|
|
495
524
|
const getDebugName = (type: string) => {
|
|
@@ -564,7 +593,9 @@ class QRenderClass {
|
|
|
564
593
|
self.instance.componentWillMount && logErrors(proxyWatcher.commitFunction({
|
|
565
594
|
debugName: getDebugName("componentWillMount"),
|
|
566
595
|
watchFunction() {
|
|
567
|
-
|
|
596
|
+
contextCommit(() => {
|
|
597
|
+
self.instance.componentWillMount?.();
|
|
598
|
+
});
|
|
568
599
|
},
|
|
569
600
|
}));
|
|
570
601
|
|
|
@@ -595,6 +626,7 @@ class QRenderClass {
|
|
|
595
626
|
const renderWatcher = this.renderWatcher = proxyWatcher.createWatcher({
|
|
596
627
|
debugName: getDebugName("render"),
|
|
597
628
|
canWrite: true,
|
|
629
|
+
logSyncTimings: statics.logLoadTime,
|
|
598
630
|
runOncePerPaint: !multiRendersPerPaint && `${this.debugName}|${self.id}` || undefined,
|
|
599
631
|
watchFunction() {
|
|
600
632
|
|
|
@@ -702,7 +734,9 @@ class QRenderClass {
|
|
|
702
734
|
// ALSO, this stops infinite loops caused by self triggering, which is really useful.
|
|
703
735
|
if (!QRenderClass.areVNodesEqual(comparePrevVNode, vNode)) {
|
|
704
736
|
self.data().vNodeForRender = frozen;
|
|
705
|
-
|
|
737
|
+
if (Querysub.isAllSynced()) {
|
|
738
|
+
comparePrevVNode = frozen.value;
|
|
739
|
+
}
|
|
706
740
|
} else {
|
|
707
741
|
wastedRenders++;
|
|
708
742
|
QRenderClass.areVNodesEqual(comparePrevVNode, vNode);
|
|
@@ -836,6 +870,10 @@ class QRenderClass {
|
|
|
836
870
|
if ("data-break" in nextProps) {
|
|
837
871
|
debugger;
|
|
838
872
|
}
|
|
873
|
+
todonext;
|
|
874
|
+
// First of all, we are accessing deepProps on the WRONG value. We should be accessing it on the child statics, not our statics!
|
|
875
|
+
// Second of all... we need to check if our child is JSON comparing, and if so, do an atomic write of the props. Usually we don't want to do an atomic write, as this would force an update every render, but... if it is JSON comparing, it won't force an update.
|
|
876
|
+
// - Which is actually trivial. We just literally set .props = nextProps. Done.
|
|
839
877
|
if (statics.deepProps) {
|
|
840
878
|
// NOTE: By removing atomic we can leverage the proxy automatically decomposing
|
|
841
879
|
// the props to each individual primitive value, and checking each individual
|
|
@@ -1111,22 +1149,6 @@ class QRenderClass {
|
|
|
1111
1149
|
}
|
|
1112
1150
|
}
|
|
1113
1151
|
|
|
1114
|
-
const contextCommit = (callback: () => void) => {
|
|
1115
|
-
void Promise.resolve().finally(() => {
|
|
1116
|
-
logErrors(proxyWatcher.commitFunction({
|
|
1117
|
-
canWrite: true,
|
|
1118
|
-
watchFunction() {
|
|
1119
|
-
QRenderClass.renderingComponentId = self.id;
|
|
1120
|
-
try {
|
|
1121
|
-
callback();
|
|
1122
|
-
} finally {
|
|
1123
|
-
QRenderClass.renderingComponentId = undefined;
|
|
1124
|
-
}
|
|
1125
|
-
},
|
|
1126
|
-
}));
|
|
1127
|
-
});
|
|
1128
|
-
};
|
|
1129
|
-
|
|
1130
1152
|
// NOTE: This is a bit inefficient as it requires running twice, because the first run our child
|
|
1131
1153
|
// components will have no rootDOMNodes. But... it's probably fine...
|
|
1132
1154
|
QRenderClass.diffVNodes({
|
|
@@ -1946,14 +1968,17 @@ function updateDOMNodeFields(domNode: DOMNode, vNode: VirtualDOM, prevVNode: Vir
|
|
|
1946
1968
|
if (typeof v === "number" && !IS_NON_DIMENSIONAL.test(key)) {
|
|
1947
1969
|
v = v + "px";
|
|
1948
1970
|
}
|
|
1949
|
-
(
|
|
1971
|
+
if (String(v).endsWith("!important")) {
|
|
1972
|
+
(domNode as any).style.setProperty(key, String(v).slice(0, -"!important".length), "important");
|
|
1973
|
+
} else {
|
|
1974
|
+
(domNode as any).style[key] = v ?? "";
|
|
1975
|
+
}
|
|
1950
1976
|
}
|
|
1951
1977
|
if (canHaveChildren(prevValue)) {
|
|
1952
1978
|
for (let key in prevValue) {
|
|
1953
1979
|
if (!(key in value)) {
|
|
1954
1980
|
(domNode as any).style[key] = "";
|
|
1955
1981
|
}
|
|
1956
|
-
|
|
1957
1982
|
}
|
|
1958
1983
|
}
|
|
1959
1984
|
}
|
|
@@ -2675,6 +2700,7 @@ let baseTrigger = lazy(async () => {
|
|
|
2675
2700
|
}
|
|
2676
2701
|
});
|
|
2677
2702
|
function triggerGlobalOnMountWatch(component: QRenderClass) {
|
|
2703
|
+
qreact.domUpdateCount++;
|
|
2678
2704
|
pendingGlobalOnMountWatches.push(component);
|
|
2679
2705
|
void baseTrigger();
|
|
2680
2706
|
}
|
|
@@ -38,13 +38,13 @@ import { sha256 } from "js-sha256";
|
|
|
38
38
|
import { minify_sync } from "terser";
|
|
39
39
|
import { isClient } from "../config2";
|
|
40
40
|
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
41
|
-
import { logMeasureTable, measureBlock, measureFnc, startMeasure } from "socket-function/src/profiling/measure";
|
|
41
|
+
import { logMeasureTable, measureBlock, measureFnc, measureWrap, startMeasure } from "socket-function/src/profiling/measure";
|
|
42
42
|
import { delay } from "socket-function/src/batching";
|
|
43
43
|
import { MaybePromise } from "socket-function/src/types";
|
|
44
44
|
import { devDebugbreak, getDomain, isDynamicallyLoading, isPublic, noSyncing } from "../config";
|
|
45
45
|
import { Schema2, Schema2T, t } from "../2-proxy/schema2";
|
|
46
46
|
import { CALL_PERMISSIONS_KEY } from "./permissionsShared";
|
|
47
|
-
import yargs from "yargs";
|
|
47
|
+
import yargs, { check } from "yargs";
|
|
48
48
|
import { parseArgsFactory } from "../misc/rawParams";
|
|
49
49
|
|
|
50
50
|
import * as typesafecss from "typesafecss";
|
|
@@ -211,6 +211,8 @@ export class Querysub {
|
|
|
211
211
|
public static callRandom = () => hashRandom(Querysub.getCallId(), getNextCallIndex());
|
|
212
212
|
public static nextId = () => Querysub.getCallId() + "_" + getNextCallIndex();
|
|
213
213
|
|
|
214
|
+
public static getNextCallIndex = getNextCallIndex;
|
|
215
|
+
|
|
214
216
|
public static configRootDiscoveryLocation = configRootDiscoveryLocation;
|
|
215
217
|
|
|
216
218
|
|
|
@@ -280,13 +282,14 @@ export class Querysub {
|
|
|
280
282
|
await nodePathAuthority.waitUntilRoutingIsReady();
|
|
281
283
|
}
|
|
282
284
|
|
|
283
|
-
public static createWatcher(watcher: (obj: SyncWatcher) => void): {
|
|
285
|
+
public static createWatcher(watcher: (obj: SyncWatcher) => void, options?: Partial<WatcherOptions<unknown>>): {
|
|
284
286
|
dispose: () => void;
|
|
285
287
|
explicitlyTrigger: () => void;
|
|
286
288
|
} {
|
|
287
289
|
return proxyWatcher.createWatcher({
|
|
288
290
|
debugName: watcher.name,
|
|
289
291
|
canWrite: true,
|
|
292
|
+
...options,
|
|
290
293
|
watchFunction: () => {
|
|
291
294
|
return watcher(proxyWatcher.getTriggeredWatcher());
|
|
292
295
|
},
|
|
@@ -358,6 +361,9 @@ export class Querysub {
|
|
|
358
361
|
allowProxyResults: true,
|
|
359
362
|
});
|
|
360
363
|
}
|
|
364
|
+
public static fastReadAsync<T>(fnc: () => T, options?: Partial<WatcherOptions<T>>) {
|
|
365
|
+
return Querysub.serviceWrite(fnc, { ...options, allowProxyResults: true });
|
|
366
|
+
}
|
|
361
367
|
public static localRead<T>(fnc: () => T, options?: Partial<WatcherOptions<T>>) {
|
|
362
368
|
return proxyWatcher.runOnce({
|
|
363
369
|
watchFunction: fnc,
|
|
@@ -417,6 +423,18 @@ export class Querysub {
|
|
|
417
423
|
Querysub.commit(callback);
|
|
418
424
|
});
|
|
419
425
|
}
|
|
426
|
+
public static onSynced(callback: () => void) {
|
|
427
|
+
let watcher = proxyWatcher.getTriggeredWatcherMaybeUndefined();
|
|
428
|
+
Querysub.onCommitFinished(() => {
|
|
429
|
+
if (!watcher || !watcher.hasAnyUnsyncedAccesses()) {
|
|
430
|
+
callback();
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
/** Checks all recursive watches. However... if something writes to a value you watch (ex, DerivedCache), we can't track that, so this is of limited use. */
|
|
435
|
+
public static onNestedSynced(callback: () => void) {
|
|
436
|
+
onNestedSynced(callback);
|
|
437
|
+
}
|
|
420
438
|
|
|
421
439
|
/** A more powerful version of omCommitFinished, which even waits for call predictions (or tries to).
|
|
422
440
|
* - Also see afterPredictionsSynced, which runs the callback in a write.
|
|
@@ -432,7 +450,7 @@ export class Querysub {
|
|
|
432
450
|
* you can't call this at the start of your function (as nothing will have been called yet).
|
|
433
451
|
* NOTE: This can also be used to prevent
|
|
434
452
|
*/
|
|
435
|
-
public static afterPredictions(callback: () =>
|
|
453
|
+
public static afterPredictions(callback: () => MaybePromise<void>) {
|
|
436
454
|
let calls = proxyWatcher.getTriggeredWatcher().pendingCalls.map(x => x.call);
|
|
437
455
|
Querysub.onCommitFinished(async () => {
|
|
438
456
|
await Promise.all(calls.map(x => Querysub.onCallPredict(x)));
|
|
@@ -496,6 +514,10 @@ export class Querysub {
|
|
|
496
514
|
return isSynced(value);
|
|
497
515
|
}
|
|
498
516
|
|
|
517
|
+
public static ignoreWatches<T>(code: () => T) {
|
|
518
|
+
return proxyWatcher.ignoreWatches(code);
|
|
519
|
+
}
|
|
520
|
+
|
|
499
521
|
public static assertDomainAllowed(path: string) {
|
|
500
522
|
let domain = getPathIndexAssert(path, 0);
|
|
501
523
|
if (!Querysub.trustedDomains.has(domain)) {
|
|
@@ -1021,6 +1043,7 @@ function getNextCallIndex() {
|
|
|
1021
1043
|
if (triggeredWatcher !== triggered) {
|
|
1022
1044
|
triggered.onAfterTriggered.push(() => {
|
|
1023
1045
|
nextCallIndex = 1;
|
|
1046
|
+
triggeredWatcher = undefined;
|
|
1024
1047
|
});
|
|
1025
1048
|
triggeredWatcher = triggered;
|
|
1026
1049
|
}
|
|
@@ -1058,26 +1081,40 @@ let initInterval = cache((interval: number) => {
|
|
|
1058
1081
|
}, interval);
|
|
1059
1082
|
});
|
|
1060
1083
|
|
|
1084
|
+
function getSetTime() {
|
|
1085
|
+
let call = getCurrentCallAllowUndefined();
|
|
1086
|
+
if (call) {
|
|
1087
|
+
return call.runAtTime.time;
|
|
1088
|
+
}
|
|
1089
|
+
let watcher = proxyWatcher.getTriggeredWatcherMaybeUndefined();
|
|
1090
|
+
if (watcher?.options.temporary) {
|
|
1091
|
+
return watcher.createTime;
|
|
1092
|
+
}
|
|
1093
|
+
return undefined;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1061
1096
|
let lastTime = 0;
|
|
1062
1097
|
function getSyncedTimeUnique() {
|
|
1098
|
+
let setTime = getSetTime();
|
|
1099
|
+
if (setTime !== undefined) {
|
|
1100
|
+
return addEpsilons(setTime, getNextCallIndex());
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1063
1103
|
let time = getSyncedTime();
|
|
1064
1104
|
if (time <= lastTime) {
|
|
1065
|
-
time = addEpsilons(
|
|
1105
|
+
time = addEpsilons(lastTime, 1);
|
|
1066
1106
|
}
|
|
1067
1107
|
lastTime = time;
|
|
1068
1108
|
return time;
|
|
1069
1109
|
}
|
|
1070
1110
|
|
|
1071
1111
|
function getSyncedTime() {
|
|
1072
|
-
let
|
|
1073
|
-
if (
|
|
1074
|
-
return
|
|
1075
|
-
}
|
|
1076
|
-
let watcher = proxyWatcher.getTriggeredWatcherMaybeUndefined();
|
|
1077
|
-
if (watcher?.options.temporary) {
|
|
1078
|
-
return watcher.createTime;
|
|
1112
|
+
let setTime = getSetTime();
|
|
1113
|
+
if (setTime !== undefined) {
|
|
1114
|
+
return setTime;
|
|
1079
1115
|
}
|
|
1080
1116
|
if (isNode()) {
|
|
1117
|
+
let watcher = proxyWatcher.getTriggeredWatcherMaybeUndefined();
|
|
1081
1118
|
if (watcher) {
|
|
1082
1119
|
throw new Error(`Trying to access time in a serverside non-temporary watcher. Clientside this is allowed, as infinite loops (render every frame) makes sense. Serverside this is not allowed. Did you try to run a call-type operatin in a watcher? If you manually created a watcher, you might want to set "temporary: true" if you will be immediately disposing it. You almost might want to fork your write logic with setImmediate to detach this from your watcher, so your write can access a single non-changing time. In ${watcher.debugName}`);
|
|
1083
1120
|
}
|
|
@@ -1098,6 +1135,72 @@ function timeDelayed(interval: number) {
|
|
|
1098
1135
|
return Date.now();
|
|
1099
1136
|
}
|
|
1100
1137
|
|
|
1138
|
+
let nestedSyncDedupeHack = new Map<string, () => void>();
|
|
1139
|
+
|
|
1140
|
+
const onNestedSynced = measureWrap(function onNestedSynced(callback: () => void) {
|
|
1141
|
+
const watcher = proxyWatcher.getTriggeredWatcherMaybeUndefined();
|
|
1142
|
+
if (!watcher) {
|
|
1143
|
+
callback();
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
let key = watcher.debugName + "_" + callback.toString();
|
|
1148
|
+
if (nestedSyncDedupeHack.has(key)) {
|
|
1149
|
+
nestedSyncDedupeHack.set(key, callback);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
nestedSyncDedupeHack.set(key, callback);
|
|
1153
|
+
|
|
1154
|
+
const getPendingWatcher = (): SyncWatcher | undefined => {
|
|
1155
|
+
let checkWatchers: SyncWatcher[] = [];
|
|
1156
|
+
let checkedWatchers = new Set<SyncWatcher>();
|
|
1157
|
+
checkWatchers.push(watcher);
|
|
1158
|
+
while (true) {
|
|
1159
|
+
let watcher = checkWatchers.shift();
|
|
1160
|
+
if (!watcher) break;
|
|
1161
|
+
if (watcher.hasAnyUnsyncedAccesses()) return watcher;
|
|
1162
|
+
// Wait until it's had time to spawn children watchers
|
|
1163
|
+
if (watcher.syncRunCount === 0) return watcher;
|
|
1164
|
+
let writes = watcher.pendingWrites;
|
|
1165
|
+
for (let writePath of writes.keys()) {
|
|
1166
|
+
// Find any watchers of these writes
|
|
1167
|
+
let callbacks = clientWatcher.getWatchersForPath(writePath);
|
|
1168
|
+
for (let callback of callbacks) {
|
|
1169
|
+
let nestedWatcher = proxyWatcher.getWatcherForTrigger(callback.callback);
|
|
1170
|
+
if (!nestedWatcher) continue;
|
|
1171
|
+
if (checkedWatchers.has(nestedWatcher)) continue;
|
|
1172
|
+
checkedWatchers.add(nestedWatcher);
|
|
1173
|
+
checkWatchers.push(nestedWatcher);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return undefined;
|
|
1179
|
+
};
|
|
1180
|
+
const checkAgain = () => {
|
|
1181
|
+
const pendingWatcher = getPendingWatcher();
|
|
1182
|
+
if (!pendingWatcher) {
|
|
1183
|
+
callback();
|
|
1184
|
+
nestedSyncDedupeHack.delete(key);
|
|
1185
|
+
} else {
|
|
1186
|
+
//console.log(`Waiting for ${pendingWatcher.debugName} to finish`);
|
|
1187
|
+
getPendingWatcher();
|
|
1188
|
+
let waitPromise = clientWatcher.waitForTriggerFinished();
|
|
1189
|
+
if (!waitPromise) {
|
|
1190
|
+
pendingWatcher.onAfterTriggered.push(checkAgain);
|
|
1191
|
+
} else {
|
|
1192
|
+
void waitPromise.finally(() => {
|
|
1193
|
+
checkAgain();
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
Querysub.onCommitFinished(() => {
|
|
1199
|
+
checkAgain();
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
|
|
1101
1204
|
// Returns a value between 0 and 1
|
|
1102
1205
|
function hashRandom(base: string, offset: number): number {
|
|
1103
1206
|
let hashBuf = Buffer.from(sha256(base + "_" + offset), "hex");
|
|
@@ -1160,4 +1263,5 @@ import { formatNumber, formatTime } from "socket-function/src/formatting/format"
|
|
|
1160
1263
|
import { css } from "../4-dom/css";
|
|
1161
1264
|
import { getCountPerPaint } from "../functional/onNextPaint";
|
|
1162
1265
|
import { addEpsilons } from "../bits";
|
|
1266
|
+
import { blue } from "socket-function/src/formatting/logColors";
|
|
1163
1267
|
|
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
import preact from "preact";
|
|
2
2
|
import { qreact } from "../4-dom/qreact";
|
|
3
3
|
import { Button } from "../library-components/Button";
|
|
4
|
-
import { Modal } from "./Modal";
|
|
4
|
+
import { Modal, showModal } from "./Modal";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
export class FullscreenModal extends
|
|
7
|
+
export class FullscreenModal extends qreact.Component<{
|
|
8
8
|
parentState?: { open: boolean };
|
|
9
9
|
onCancel?: () => void | "abortClose";
|
|
10
10
|
style?: preact.JSX.CSSProperties;
|
|
11
11
|
outerStyle?: preact.JSX.CSSProperties;
|
|
12
12
|
onlyExplicitClose?: boolean;
|
|
13
|
+
noModalWrap?: boolean;
|
|
13
14
|
}> {
|
|
14
15
|
render() {
|
|
16
|
+
let base = <div
|
|
17
|
+
className="FullscreenModal"
|
|
18
|
+
style={{
|
|
19
|
+
position: "fixed",
|
|
20
|
+
top: 0,
|
|
21
|
+
left: 0,
|
|
22
|
+
width: "100vw",
|
|
23
|
+
height: "100vh",
|
|
24
|
+
background: "hsla(0, 0%, 30%, 0.5)",
|
|
25
|
+
padding: 100,
|
|
26
|
+
display: "flex",
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
justifyContent: "center",
|
|
29
|
+
overflow: "auto",
|
|
30
|
+
cursor: "pointer",
|
|
31
|
+
...this.props.outerStyle,
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
className="FullscreenModal-background keepModalsOpen"
|
|
36
|
+
style={{
|
|
37
|
+
background: "hsl(0, 0%, 100%)",
|
|
38
|
+
padding: 20,
|
|
39
|
+
color: "hsl(0, 0%, 7%)",
|
|
40
|
+
cursor: "default",
|
|
41
|
+
width: "100%",
|
|
42
|
+
display: "flex",
|
|
43
|
+
flexDirection: "column",
|
|
44
|
+
gap: 10,
|
|
45
|
+
maxHeight: "calc(100% - 200px)",
|
|
46
|
+
overflow: "auto",
|
|
47
|
+
...this.props.style
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{this.props.children}
|
|
51
|
+
</div>
|
|
52
|
+
</div>;
|
|
53
|
+
if (this.props.noModalWrap) return base;
|
|
15
54
|
return (
|
|
16
55
|
<Modal
|
|
17
56
|
onClose={() => {
|
|
@@ -19,43 +58,21 @@ export class FullscreenModal extends preact.Component<{
|
|
|
19
58
|
return this.props.onCancel?.();
|
|
20
59
|
}}
|
|
21
60
|
>
|
|
22
|
-
|
|
23
|
-
style={{
|
|
24
|
-
position: "fixed",
|
|
25
|
-
top: 0,
|
|
26
|
-
left: 0,
|
|
27
|
-
width: "100vw",
|
|
28
|
-
height: "100vh",
|
|
29
|
-
background: "hsla(0, 0%, 30%, 0.5)",
|
|
30
|
-
padding: 100,
|
|
31
|
-
display: "flex",
|
|
32
|
-
alignItems: "center",
|
|
33
|
-
justifyContent: "center",
|
|
34
|
-
overflow: "auto",
|
|
35
|
-
cursor: "pointer",
|
|
36
|
-
...this.props.outerStyle,
|
|
37
|
-
}}
|
|
38
|
-
>
|
|
39
|
-
<div
|
|
40
|
-
className="keepModalsOpen"
|
|
41
|
-
style={{
|
|
42
|
-
background: "hsl(0, 0%, 100%)",
|
|
43
|
-
padding: 20,
|
|
44
|
-
color: "hsl(0, 0%, 7%)",
|
|
45
|
-
cursor: "default",
|
|
46
|
-
width: "100%",
|
|
47
|
-
display: "flex",
|
|
48
|
-
flexDirection: "column",
|
|
49
|
-
gap: 10,
|
|
50
|
-
maxHeight: "calc(100% - 200px)",
|
|
51
|
-
overflow: "auto",
|
|
52
|
-
...this.props.style
|
|
53
|
-
}}
|
|
54
|
-
>
|
|
55
|
-
{this.props.children}
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
61
|
+
{base}
|
|
58
62
|
</Modal>
|
|
59
63
|
);
|
|
60
64
|
}
|
|
61
|
-
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function showFullscreenModal(config: {
|
|
68
|
+
content: preact.ComponentChild;
|
|
69
|
+
onClose?: () => void | "abortClose";
|
|
70
|
+
}): {
|
|
71
|
+
close: () => void;
|
|
72
|
+
} {
|
|
73
|
+
let { close } = showModal({
|
|
74
|
+
content: <FullscreenModal noModalWrap>{config.content}</FullscreenModal>,
|
|
75
|
+
onClose: config.onClose,
|
|
76
|
+
});
|
|
77
|
+
return { close };
|
|
78
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import preact from "preact";
|
|
2
1
|
import { nextId } from "socket-function/src/misc";
|
|
3
2
|
import { lazy } from "socket-function/src/caching";
|
|
4
3
|
import { css } from "typesafecss";
|
|
5
4
|
import { Querysub } from "../4-querysub/Querysub";
|
|
6
5
|
import { atomicObjectWrite, atomicObjectWriteNoFreeze } from "../2-proxy/PathValueProxyWatcher";
|
|
7
6
|
import { qreact } from "../4-dom/qreact";
|
|
7
|
+
import { FullscreenModal } from "./FullscreenModal";
|
|
8
8
|
|
|
9
9
|
const data = Querysub.createLocalSchema<{
|
|
10
10
|
modals: {
|
|
@@ -41,14 +41,16 @@ const ensureRendering = lazy(() => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
/** IMPORTANT! Use the .keepModalsOpen class to prevent clicks from closing the model */
|
|
44
|
-
export class Modal extends
|
|
44
|
+
export class Modal extends qreact.Component<{
|
|
45
45
|
onClose?: () => void | "abortClose";
|
|
46
46
|
}> {
|
|
47
47
|
id = nextId();
|
|
48
48
|
componentWillUnmount(): void {
|
|
49
|
+
console.log("Unmounting modal", this.id);
|
|
49
50
|
delete data().modals[this.id];
|
|
50
51
|
}
|
|
51
52
|
render() {
|
|
53
|
+
console.log("Rendering modal", this.id);
|
|
52
54
|
ensureRendering();
|
|
53
55
|
data().modals[this.id] = atomicObjectWriteNoFreeze({
|
|
54
56
|
value: this.props.children,
|
|
@@ -65,6 +67,7 @@ function closeModal(id: string) {
|
|
|
65
67
|
if (result === "abortClose") return;
|
|
66
68
|
delete data().modals[id];
|
|
67
69
|
}
|
|
70
|
+
|
|
68
71
|
export function showModal(config: {
|
|
69
72
|
content: preact.ComponentChild;
|
|
70
73
|
onClose?: () => void | "abortClose";
|
|
@@ -94,7 +97,7 @@ export function closeAllModals() {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
|
|
97
|
-
export class ModalHolder extends
|
|
100
|
+
export class ModalHolder extends qreact.Component {
|
|
98
101
|
render() {
|
|
99
102
|
let modals = Object.values(data().modals);
|
|
100
103
|
return (
|
|
@@ -186,10 +186,15 @@ class WatchModal extends qreact.Component<{
|
|
|
186
186
|
|| {}
|
|
187
187
|
}
|
|
188
188
|
style={
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|
|
193
198
|
}
|
|
194
199
|
onCancel={() => {
|
|
195
200
|
if (this.state.pos !== "fill") {
|
|
@@ -197,7 +202,7 @@ class WatchModal extends qreact.Component<{
|
|
|
197
202
|
}
|
|
198
203
|
}}
|
|
199
204
|
>
|
|
200
|
-
<div class={css.hsl(0, 0, 100).vbox(6)}>
|
|
205
|
+
<div class={css.hsl(0, 0, 100).hslcolor(0, 0, 10).vbox(6)}>
|
|
201
206
|
<div class={css.hbox(10).fillWidth}>
|
|
202
207
|
{component.debugName}
|
|
203
208
|
{parent && <Button onClick={() => this.state.parentNavigate++}>
|
|
@@ -53,7 +53,10 @@ export class ArchiveViewerTree extends qreact.Component<{
|
|
|
53
53
|
let { remainingWidth, nodePath, scaleType } = config;
|
|
54
54
|
const selectedPath = archiveTreeSelected.value;
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
let sumFormatter: (value: number) => string = formatNumber;
|
|
57
|
+
if (archiveTreeScale.value === "size") {
|
|
58
|
+
sumFormatter = this.props.sumFormatter || formatNumber;
|
|
59
|
+
}
|
|
57
60
|
|
|
58
61
|
let isSelected = selectedPath === nodePath;
|
|
59
62
|
let isDescendantSelected = selectedPath.startsWith(nodePath);
|
package/src/errors.ts
CHANGED
|
@@ -112,6 +112,7 @@ export function errorify(error: any, messageOverride?: string) {
|
|
|
112
112
|
errorObj.stack = error;
|
|
113
113
|
} else {
|
|
114
114
|
errorObj.message = error;
|
|
115
|
+
errorObj.stack = error + "\n" + errorObj.stack;
|
|
115
116
|
}
|
|
116
117
|
if (messageOverride) {
|
|
117
118
|
errorObj.message = messageOverride;
|
|
@@ -28,6 +28,8 @@ export type ATagProps = (
|
|
|
28
28
|
*/
|
|
29
29
|
rawLink?: boolean;
|
|
30
30
|
lightMode?: boolean;
|
|
31
|
+
noStyles?: boolean;
|
|
32
|
+
onRef?: (element: HTMLAnchorElement | null) => void;
|
|
31
33
|
}
|
|
32
34
|
);
|
|
33
35
|
|
|
@@ -50,6 +52,7 @@ export class ATag extends qreact.Component<ATagProps> {
|
|
|
50
52
|
<a
|
|
51
53
|
tabIndex={0}
|
|
52
54
|
{...props}
|
|
55
|
+
ref={props.onRef}
|
|
53
56
|
className={
|
|
54
57
|
(isCurrent ? css.color(`hsl(110, 75%, ${lightness}%)`) : css.color(`hsl(210, 100%, ${lightness}%)`))
|
|
55
58
|
+ css.textDecoration("none")
|
|
@@ -57,6 +60,12 @@ export class ATag extends qreact.Component<ATagProps> {
|
|
|
57
60
|
.outline("3px solid hsl(204, 100%, 50%)", "focus")
|
|
58
61
|
+ (props.className ?? props.class)
|
|
59
62
|
|
|
63
|
+
+ (props.noStyles && css
|
|
64
|
+
.textDecoration("none", "important")
|
|
65
|
+
.outline("none", "important")
|
|
66
|
+
.color("inherit", "important")
|
|
67
|
+
)
|
|
68
|
+
|
|
60
69
|
}
|
|
61
70
|
onClick={e => {
|
|
62
71
|
if (this.props.rawLink) return;
|
|
@@ -688,5 +688,13 @@ export const Icon = {
|
|
|
688
688
|
<path d="M15 15V14H14V15H15ZM20.2929 21.7071C20.6834 22.0976 21.3166 22.0976 21.7071 21.7071C22.0976 21.3166 22.0976 20.6834 21.7071 20.2929L20.2929 21.7071ZM15 9H14V10H15V9ZM21.7071 3.70711C22.0976 3.31658 22.0976 2.68342 21.7071 2.29289C21.3166 1.90237 20.6834 1.90237 20.2929 2.29289L21.7071 3.70711ZM9 15H10V14H9V15ZM2.29289 20.2929C1.90237 20.6834 1.90237 21.3166 2.29289 21.7071C2.68342 22.0976 3.31658 22.0976 3.70711 21.7071L2.29289 20.2929ZM9 9V10H10V9H9ZM3.70711 2.29289C3.31658 1.90237 2.68342 1.90237 2.29289 2.29289C1.90237 2.68342 1.90237 3.31658 2.29289 3.70711L3.70711 2.29289ZM16 20V15H14V20H16ZM15 16H20V14H15V16ZM14.2929 15.7071L20.2929 21.7071L21.7071 20.2929L15.7071 14.2929L14.2929 15.7071ZM14 4V9H16V4H14ZM15 10H20V8H15V10ZM15.7071 9.70711L21.7071 3.70711L20.2929 2.29289L14.2929 8.29289L15.7071 9.70711ZM10 20V15H8V20H10ZM9 14H4V16H9V14ZM8.29289 14.2929L2.29289 20.2929L3.70711 21.7071L9.70711 15.7071L8.29289 14.2929ZM8 4V9H10V4H8ZM9 8H4V10H9V8ZM9.70711 8.29289L3.70711 2.29289L2.29289 3.70711L8.29289 9.70711L9.70711 8.29289Z" fill="#33363F" />
|
|
689
689
|
</svg>
|
|
690
690
|
|
|
691
|
+
),
|
|
692
|
+
// TODO: Create a better warning (something that looks good at low sizes)
|
|
693
|
+
warning: fastSVG(
|
|
694
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 111.54" width="24" height="24">
|
|
695
|
+
<path fill="#cf1f25" d="M2.35,84.42,45.28,10.2l.17-.27h0A23,23,0,0,1,52.5,2.69,17,17,0,0,1,61.57,0a16.7,16.7,0,0,1,9.11,2.69,22.79,22.79,0,0,1,7,7.26q.19.32.36.63l42.23,73.34.24.44h0a22.48,22.48,0,0,1,2.37,10.19,17.63,17.63,0,0,1-2.17,8.35,15.94,15.94,0,0,1-6.93,6.6c-.19.1-.39.18-.58.26a21.19,21.19,0,0,1-9.11,1.75v0H17.61c-.22,0-.44,0-.65,0a18.07,18.07,0,0,1-6.2-1.15A16.42,16.42,0,0,1,3,104.24a17.53,17.53,0,0,1-3-9.57,23,23,0,0,1,1.57-8.74,7.66,7.66,0,0,1,.77-1.51Z" />
|
|
696
|
+
<path fill="#fec901" fill-rule="evenodd" d="M9,88.75,52.12,14.16c5.24-8.25,13.54-8.46,18.87,0l42.43,73.69c3.39,6.81,1.71,16-9.33,15.77H17.61C10.35,103.8,5.67,97.43,9,88.75Z" />
|
|
697
|
+
<path fill="#010101" d="M57.57,83.78A5.53,5.53,0,0,1,61,82.2a5.6,5.6,0,0,1,2.4.36,5.7,5.7,0,0,1,2,1.3,5.56,5.56,0,0,1,1.54,5,6.23,6.23,0,0,1-.42,1.35,5.57,5.57,0,0,1-5.22,3.26,5.72,5.72,0,0,1-2.27-.53A5.51,5.51,0,0,1,56.28,90a5.18,5.18,0,0,1-.36-1.27,5.83,5.83,0,0,1-.06-1.31h0a6.53,6.53,0,0,1,.57-2,4.7,4.7,0,0,1,1.14-1.56Zm8.15-10.24c-.19,4.79-8.31,4.8-8.49,0-.82-8.21-2.92-29.34-2.86-37.05.07-2.38,2-3.79,4.56-4.33a12.83,12.83,0,0,1,5,0c2.61.56,4.65,2,4.65,4.44v.24L65.72,73.54Z" />
|
|
698
|
+
</svg>
|
|
691
699
|
)
|
|
692
700
|
};
|
package/src/misc/format2.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
|
+
function canBeJSON(str: string) {
|
|
2
|
+
return (
|
|
3
|
+
str === "null"
|
|
4
|
+
|| str === "true"
|
|
5
|
+
|| str === "false"
|
|
6
|
+
|| str[0] === `"` && str[str.length - 1] === `"`
|
|
7
|
+
|| str[0] === `[` && str[str.length - 1] === `]`
|
|
8
|
+
|| str[0] === `{` && str[str.length - 1] === `}`
|
|
9
|
+
|| (48 <= str.charCodeAt(0) && str.charCodeAt(0) <= 57)
|
|
10
|
+
|| str.length > 1 && str[0] === "-" && (48 <= str.charCodeAt(1) && str.charCodeAt(1) <= 57)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
export function parseNice(text: string): unknown {
|
|
2
15
|
if (text === "undefined") return undefined;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
16
|
+
if (text === undefined) return undefined;
|
|
17
|
+
if (canBeJSON(text)) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(text);
|
|
20
|
+
} catch { }
|
|
21
|
+
}
|
|
6
22
|
return text;
|
|
7
23
|
}
|
|
8
24
|
export function stringifyNice(value: unknown): string {
|
|
@@ -251,16 +251,18 @@ export class UserPage extends qreact.Component {
|
|
|
251
251
|
<table>
|
|
252
252
|
<tr>
|
|
253
253
|
<th>User ID</th>
|
|
254
|
+
<th>Email</th>
|
|
254
255
|
<th>Time</th>
|
|
255
256
|
</tr>
|
|
256
|
-
{sort(Object.entries(userObj.
|
|
257
|
+
{sort(Object.entries(userObj.invitedUsers2), x => -x[1].time).map(([email, inviteData]) => {
|
|
257
258
|
return (
|
|
258
259
|
<tr>
|
|
259
260
|
<td>
|
|
260
|
-
<Anchor values={[{ param: viewingUserURL, value: userId }]}>
|
|
261
|
-
{userId}
|
|
261
|
+
<Anchor values={[{ param: viewingUserURL, value: inviteData.userId }]}>
|
|
262
|
+
{inviteData.userId}
|
|
262
263
|
</Anchor>
|
|
263
264
|
</td>
|
|
265
|
+
<td>{inviteData.email}</td>
|
|
264
266
|
<td>{inviteData.time}</td>
|
|
265
267
|
</tr>
|
|
266
268
|
);
|
|
@@ -133,8 +133,10 @@ export type User = {
|
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
invitesRemaining: number;
|
|
136
|
-
|
|
137
|
-
[
|
|
136
|
+
invitedUsers2: {
|
|
137
|
+
[email: string]: {
|
|
138
|
+
userId: string;
|
|
139
|
+
email: string;
|
|
138
140
|
time: number;
|
|
139
141
|
};
|
|
140
142
|
};
|
|
@@ -427,7 +429,7 @@ function getLoadingUserObj() {
|
|
|
427
429
|
email: "",
|
|
428
430
|
settings: { displayName: "" }, userType: "anonymous" as const,
|
|
429
431
|
createTime: 0,
|
|
430
|
-
machineIds: {}, allowedIPs: {}, loginTokens: {}, lastPageLoadsIPs: {},
|
|
432
|
+
machineIds: {}, allowedIPs: {}, loginTokens: {}, lastPageLoadsIPs: {}, invitedUsers2: {},
|
|
431
433
|
invitesRemaining: 0
|
|
432
434
|
};
|
|
433
435
|
}
|
|
@@ -452,7 +454,7 @@ export function getCurrentUserObj(): User | undefined {
|
|
|
452
454
|
allowedIPs: {},
|
|
453
455
|
loginTokens: {},
|
|
454
456
|
lastPageLoadsIPs: {},
|
|
455
|
-
|
|
457
|
+
invitedUsers2: {},
|
|
456
458
|
invitesRemaining: 0,
|
|
457
459
|
};
|
|
458
460
|
}
|
|
@@ -537,9 +539,10 @@ function internalCreateUser(config: {
|
|
|
537
539
|
allowedIPs: {},
|
|
538
540
|
loginTokens: {},
|
|
539
541
|
lastPageLoadsIPs: {},
|
|
540
|
-
|
|
542
|
+
invitedUsers2: {},
|
|
541
543
|
invitesRemaining: 0,
|
|
542
544
|
};
|
|
545
|
+
data().secure.emailToUserId[email] = userId;
|
|
543
546
|
}
|
|
544
547
|
return data().users[userId];
|
|
545
548
|
}
|
|
@@ -776,7 +779,7 @@ function registerPageLoadTime() {
|
|
|
776
779
|
function inviteUser(config: { email: string }) {
|
|
777
780
|
Querysub.ignorePermissionsChecks(() => {
|
|
778
781
|
let curUserObj = getUserObjAssert();
|
|
779
|
-
if (config.email in curUserObj.
|
|
782
|
+
if (config.email in curUserObj.invitedUsers2) {
|
|
780
783
|
console.info(`User ${config.email} already invited`);
|
|
781
784
|
return;
|
|
782
785
|
}
|
|
@@ -793,7 +796,9 @@ function inviteUser(config: { email: string }) {
|
|
|
793
796
|
throw new Error("No invites remaining");
|
|
794
797
|
}
|
|
795
798
|
|
|
796
|
-
curUserObj.
|
|
799
|
+
curUserObj.invitedUsers2[email] = atomicObjectWrite({
|
|
800
|
+
userId,
|
|
801
|
+
email,
|
|
797
802
|
time: Querysub.getCallTime(),
|
|
798
803
|
});
|
|
799
804
|
curUserObj.invitesRemaining--;
|
|
@@ -923,7 +928,7 @@ export function scriptCreateUser(config: {
|
|
|
923
928
|
allowedIPs: {},
|
|
924
929
|
loginTokens: {},
|
|
925
930
|
lastPageLoadsIPs: {},
|
|
926
|
-
|
|
931
|
+
invitedUsers2: {},
|
|
927
932
|
invitesRemaining: 0,
|
|
928
933
|
};
|
|
929
934
|
data().secure.emailToUserId[email] = userId;
|
|
@@ -980,7 +985,7 @@ export async function registerServicePermissions() {
|
|
|
980
985
|
allowedIPs: {},
|
|
981
986
|
loginTokens: {},
|
|
982
987
|
lastPageLoadsIPs: {},
|
|
983
|
-
|
|
988
|
+
invitedUsers2: {},
|
|
984
989
|
invitesRemaining: 0,
|
|
985
990
|
};
|
|
986
991
|
}
|