querysub 0.186.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 +1 -1
- package/src/1-path-client/RemoteWatcher.ts +7 -3
- package/src/1-path-client/pathValueClientWatcher.ts +62 -50
- package/src/2-proxy/PathValueProxyWatcher.ts +71 -6
- package/src/4-dom/qreact.tsx +21 -3
- package/src/4-querysub/Querysub.ts +4 -1
- 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 +2 -0
- package/src/user-implementation/UserPage.tsx +5 -3
- package/src/user-implementation/userData.ts +14 -9
package/package.json
CHANGED
|
@@ -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>());
|
|
@@ -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() });
|
|
@@ -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 => {
|
|
@@ -1255,8 +1272,56 @@ export class PathValueProxyWatcher {
|
|
|
1255
1272
|
const specialPromiseUnsynced = watcher.specialPromiseUnsynced;
|
|
1256
1273
|
watcher.specialPromiseUnsynced = false;
|
|
1257
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
|
+
|
|
1258
1323
|
if (!watcher.options.static) {
|
|
1259
|
-
if (!
|
|
1324
|
+
if (!anyUnsynced) {
|
|
1260
1325
|
watcher.countSinceLastFullSync = 0;
|
|
1261
1326
|
watcher.lastSyncTime = Date.now();
|
|
1262
1327
|
watcher.syncRunCount++;
|
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 */
|
|
@@ -416,7 +419,14 @@ function getAncestor(
|
|
|
416
419
|
}
|
|
417
420
|
function onDispose(fnc: () => void) {
|
|
418
421
|
let component = getRenderingComponent();
|
|
419
|
-
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
|
+
}
|
|
420
430
|
watchDispose(component, fnc);
|
|
421
431
|
}
|
|
422
432
|
function watchDispose(renderClass: ExternalRenderClass, fnc: () => void) {
|
|
@@ -616,6 +626,7 @@ class QRenderClass {
|
|
|
616
626
|
const renderWatcher = this.renderWatcher = proxyWatcher.createWatcher({
|
|
617
627
|
debugName: getDebugName("render"),
|
|
618
628
|
canWrite: true,
|
|
629
|
+
logSyncTimings: statics.logLoadTime,
|
|
619
630
|
runOncePerPaint: !multiRendersPerPaint && `${this.debugName}|${self.id}` || undefined,
|
|
620
631
|
watchFunction() {
|
|
621
632
|
|
|
@@ -859,6 +870,10 @@ class QRenderClass {
|
|
|
859
870
|
if ("data-break" in nextProps) {
|
|
860
871
|
debugger;
|
|
861
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.
|
|
862
877
|
if (statics.deepProps) {
|
|
863
878
|
// NOTE: By removing atomic we can leverage the proxy automatically decomposing
|
|
864
879
|
// the props to each individual primitive value, and checking each individual
|
|
@@ -1953,14 +1968,17 @@ function updateDOMNodeFields(domNode: DOMNode, vNode: VirtualDOM, prevVNode: Vir
|
|
|
1953
1968
|
if (typeof v === "number" && !IS_NON_DIMENSIONAL.test(key)) {
|
|
1954
1969
|
v = v + "px";
|
|
1955
1970
|
}
|
|
1956
|
-
(
|
|
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
|
+
}
|
|
1957
1976
|
}
|
|
1958
1977
|
if (canHaveChildren(prevValue)) {
|
|
1959
1978
|
for (let key in prevValue) {
|
|
1960
1979
|
if (!(key in value)) {
|
|
1961
1980
|
(domNode as any).style[key] = "";
|
|
1962
1981
|
}
|
|
1963
|
-
|
|
1964
1982
|
}
|
|
1965
1983
|
}
|
|
1966
1984
|
}
|
|
@@ -361,6 +361,9 @@ export class Querysub {
|
|
|
361
361
|
allowProxyResults: true,
|
|
362
362
|
});
|
|
363
363
|
}
|
|
364
|
+
public static fastReadAsync<T>(fnc: () => T, options?: Partial<WatcherOptions<T>>) {
|
|
365
|
+
return Querysub.serviceWrite(fnc, { ...options, allowProxyResults: true });
|
|
366
|
+
}
|
|
364
367
|
public static localRead<T>(fnc: () => T, options?: Partial<WatcherOptions<T>>) {
|
|
365
368
|
return proxyWatcher.runOnce({
|
|
366
369
|
watchFunction: fnc,
|
|
@@ -447,7 +450,7 @@ export class Querysub {
|
|
|
447
450
|
* you can't call this at the start of your function (as nothing will have been called yet).
|
|
448
451
|
* NOTE: This can also be used to prevent
|
|
449
452
|
*/
|
|
450
|
-
public static afterPredictions(callback: () =>
|
|
453
|
+
public static afterPredictions(callback: () => MaybePromise<void>) {
|
|
451
454
|
let calls = proxyWatcher.getTriggeredWatcher().pendingCalls.map(x => x.call);
|
|
452
455
|
Querysub.onCommitFinished(async () => {
|
|
453
456
|
await Promise.all(calls.map(x => Querysub.onCallPredict(x)));
|
|
@@ -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;
|
|
@@ -29,6 +29,7 @@ export type ATagProps = (
|
|
|
29
29
|
rawLink?: boolean;
|
|
30
30
|
lightMode?: boolean;
|
|
31
31
|
noStyles?: boolean;
|
|
32
|
+
onRef?: (element: HTMLAnchorElement | null) => void;
|
|
32
33
|
}
|
|
33
34
|
);
|
|
34
35
|
|
|
@@ -51,6 +52,7 @@ export class ATag extends qreact.Component<ATagProps> {
|
|
|
51
52
|
<a
|
|
52
53
|
tabIndex={0}
|
|
53
54
|
{...props}
|
|
55
|
+
ref={props.onRef}
|
|
54
56
|
className={
|
|
55
57
|
(isCurrent ? css.color(`hsl(110, 75%, ${lightness}%)`) : css.color(`hsl(210, 100%, ${lightness}%)`))
|
|
56
58
|
+ css.textDecoration("none")
|
|
@@ -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
|
}
|