querysub 0.437.0 → 0.438.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/.eslintrc.js +50 -50
- package/bin/deploy.js +0 -0
- package/bin/function.js +0 -0
- package/bin/server.js +0 -0
- package/costsBenefits.txt +115 -115
- package/deploy.ts +2 -2
- package/package.json +1 -1
- package/spec.txt +1192 -1192
- package/src/-a-archives/archives.ts +202 -202
- package/src/-a-archives/archivesDisk.ts +454 -454
- package/src/-a-auth/certs.ts +540 -540
- package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
- package/src/-b-authorities/dnsAuthority.ts +138 -138
- package/src/-c-identity/IdentityController.ts +258 -258
- package/src/-d-trust/NetworkTrust2.ts +180 -180
- package/src/-e-certs/EdgeCertController.ts +252 -252
- package/src/-e-certs/certAuthority.ts +201 -201
- package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
- package/src/-g-core-values/NodeCapabilities.ts +200 -200
- package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
- package/src/0-path-value-core/PathValueCommitter.ts +468 -468
- package/src/0-path-value-core/PathValueController.ts +0 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
- package/src/2-proxy/TransactionDelayer.ts +94 -94
- package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
- package/src/2-proxy/pathValueProxy.ts +159 -159
- package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
- package/src/3-path-functions/pathFunctionLoader.ts +516 -516
- package/src/3-path-functions/tests/rejectTest.ts +76 -76
- package/src/4-deploy/deployCheck.ts +6 -6
- package/src/4-dom/css.tsx +29 -29
- package/src/4-dom/cssTypes.d.ts +211 -211
- package/src/4-dom/qreact.tsx +2799 -2799
- package/src/4-dom/qreactTest.tsx +410 -410
- package/src/4-querysub/permissions.ts +335 -335
- package/src/4-querysub/querysubPrediction.ts +483 -483
- package/src/5-diagnostics/qreactDebug.tsx +377 -346
- package/src/TestController.ts +34 -34
- package/src/bits.ts +104 -104
- package/src/buffers.ts +69 -69
- package/src/diagnostics/ActionsHistory.ts +57 -57
- package/src/diagnostics/listenOnDebugger.ts +71 -71
- package/src/diagnostics/periodic.ts +111 -111
- package/src/diagnostics/trackResources.ts +91 -91
- package/src/diagnostics/watchdog.ts +120 -120
- package/src/errors.ts +133 -133
- package/src/forceProduction.ts +2 -2
- package/src/fs.ts +80 -80
- package/src/functional/diff.ts +857 -857
- package/src/functional/promiseCache.ts +78 -78
- package/src/functional/random.ts +8 -8
- package/src/functional/stats.ts +60 -60
- package/src/heapDumps.ts +665 -665
- package/src/https.ts +1 -1
- package/src/library-components/AspectSizedComponent.tsx +87 -87
- package/src/library-components/ButtonSelector.tsx +64 -64
- package/src/library-components/DropdownCustom.tsx +150 -150
- package/src/library-components/DropdownSelector.tsx +31 -31
- package/src/library-components/InlinePopup.tsx +66 -66
- package/src/misc/color.ts +29 -29
- package/src/misc/hash.ts +83 -83
- package/src/misc/ipPong.js +13 -13
- package/src/misc/networking.ts +1 -1
- package/src/misc/random.ts +44 -44
- package/src/misc.ts +196 -196
- package/src/path.ts +255 -255
- package/src/persistentLocalStore.ts +41 -41
- package/src/promise.ts +14 -14
- package/src/storage/fileSystemPointer.ts +71 -71
- package/src/test/heapProcess.ts +35 -35
- package/src/zip.ts +15 -15
- package/tsconfig.json +26 -26
- package/yarnSpec.txt +56 -56
package/src/https.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { httpsRequest } from "socket-function/src/https";
|
|
1
|
+
import { httpsRequest } from "socket-function/src/https";
|
|
2
2
|
export { httpsRequest };
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
import { Querysub } from "../4-querysub/QuerysubController";
|
|
2
|
-
import { css } from "../4-dom/css";
|
|
3
|
-
import { qreact } from "../4-dom/qreact";
|
|
4
|
-
|
|
5
|
-
export class AspectSizedComponent extends qreact.Component<{
|
|
6
|
-
aspectRatio: number;
|
|
7
|
-
onNewSize?: (width: number, height: number) => void;
|
|
8
|
-
center?: { x: number; y: number };
|
|
9
|
-
className?: string;
|
|
10
|
-
innerClassName?: string;
|
|
11
|
-
defaultWidth?: number;
|
|
12
|
-
defaultHeight?: number;
|
|
13
|
-
onClick?: (e: MouseEvent) => void;
|
|
14
|
-
}> {
|
|
15
|
-
state = {
|
|
16
|
-
width: this.props.defaultWidth ?? 100,
|
|
17
|
-
height: this.props.defaultHeight ?? 100,
|
|
18
|
-
};
|
|
19
|
-
holder: HTMLDivElement | null = null;
|
|
20
|
-
getSize() {
|
|
21
|
-
let { aspectRatio } = this.props;
|
|
22
|
-
let { width, height } = this.state;
|
|
23
|
-
let leftFraction = 0;
|
|
24
|
-
let topFraction = 0;
|
|
25
|
-
let widthFraction = 1;
|
|
26
|
-
let heightFraction = 1;
|
|
27
|
-
|
|
28
|
-
let center = this.props.center ?? { x: 0.5, y: 0.5 };
|
|
29
|
-
|
|
30
|
-
if (width / height > aspectRatio) {
|
|
31
|
-
// Too wide
|
|
32
|
-
let newWidth = height * aspectRatio;
|
|
33
|
-
leftFraction = (width - newWidth) * center.x / width;
|
|
34
|
-
widthFraction = newWidth / width;
|
|
35
|
-
} else {
|
|
36
|
-
// Too tall
|
|
37
|
-
let newHeight = width / aspectRatio;
|
|
38
|
-
topFraction = (height - newHeight) * center.y / height;
|
|
39
|
-
heightFraction = newHeight / height;
|
|
40
|
-
}
|
|
41
|
-
return { leftFraction, topFraction, widthFraction, heightFraction };
|
|
42
|
-
}
|
|
43
|
-
observer: ResizeObserver = new ResizeObserver((val) => {
|
|
44
|
-
for (let t of val) {
|
|
45
|
-
if (t.target === this.holder) {
|
|
46
|
-
Querysub.serviceWriteDetached(() => {
|
|
47
|
-
let width = t.contentRect.width;
|
|
48
|
-
let height = t.contentRect.height;
|
|
49
|
-
this.state.width = width;
|
|
50
|
-
this.state.height = height;
|
|
51
|
-
let { widthFraction, heightFraction } = this.getSize();
|
|
52
|
-
let innerWidth = widthFraction * width;
|
|
53
|
-
let innerHeight = heightFraction * height;
|
|
54
|
-
this.props.onNewSize?.(innerWidth, innerHeight);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
componentWillUnmount() {
|
|
60
|
-
this.observer.disconnect();
|
|
61
|
-
}
|
|
62
|
-
render() {
|
|
63
|
-
let { leftFraction, topFraction, widthFraction, heightFraction } = this.getSize();
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<div
|
|
67
|
-
className={css.size("100%", "100%").position("relative", "soft") + " " + this.props.className}
|
|
68
|
-
key="maindiv"
|
|
69
|
-
ref2={(x) => {
|
|
70
|
-
if (this.holder) {
|
|
71
|
-
this.observer.unobserve(this.holder);
|
|
72
|
-
}
|
|
73
|
-
this.observer.observe(x);
|
|
74
|
-
this.holder = x;
|
|
75
|
-
}}
|
|
76
|
-
onClick={this.props.onClick}
|
|
77
|
-
>
|
|
78
|
-
<div className={this.props.innerClassName} style={{
|
|
79
|
-
position: "absolute",
|
|
80
|
-
left: `${leftFraction * 100}%`,
|
|
81
|
-
top: `${topFraction * 100}%`,
|
|
82
|
-
width: `${widthFraction * 100}%`,
|
|
83
|
-
height: `${heightFraction * 100}%`,
|
|
84
|
-
}}>{this.props.children}</div>
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
1
|
+
import { Querysub } from "../4-querysub/QuerysubController";
|
|
2
|
+
import { css } from "../4-dom/css";
|
|
3
|
+
import { qreact } from "../4-dom/qreact";
|
|
4
|
+
|
|
5
|
+
export class AspectSizedComponent extends qreact.Component<{
|
|
6
|
+
aspectRatio: number;
|
|
7
|
+
onNewSize?: (width: number, height: number) => void;
|
|
8
|
+
center?: { x: number; y: number };
|
|
9
|
+
className?: string;
|
|
10
|
+
innerClassName?: string;
|
|
11
|
+
defaultWidth?: number;
|
|
12
|
+
defaultHeight?: number;
|
|
13
|
+
onClick?: (e: MouseEvent) => void;
|
|
14
|
+
}> {
|
|
15
|
+
state = {
|
|
16
|
+
width: this.props.defaultWidth ?? 100,
|
|
17
|
+
height: this.props.defaultHeight ?? 100,
|
|
18
|
+
};
|
|
19
|
+
holder: HTMLDivElement | null = null;
|
|
20
|
+
getSize() {
|
|
21
|
+
let { aspectRatio } = this.props;
|
|
22
|
+
let { width, height } = this.state;
|
|
23
|
+
let leftFraction = 0;
|
|
24
|
+
let topFraction = 0;
|
|
25
|
+
let widthFraction = 1;
|
|
26
|
+
let heightFraction = 1;
|
|
27
|
+
|
|
28
|
+
let center = this.props.center ?? { x: 0.5, y: 0.5 };
|
|
29
|
+
|
|
30
|
+
if (width / height > aspectRatio) {
|
|
31
|
+
// Too wide
|
|
32
|
+
let newWidth = height * aspectRatio;
|
|
33
|
+
leftFraction = (width - newWidth) * center.x / width;
|
|
34
|
+
widthFraction = newWidth / width;
|
|
35
|
+
} else {
|
|
36
|
+
// Too tall
|
|
37
|
+
let newHeight = width / aspectRatio;
|
|
38
|
+
topFraction = (height - newHeight) * center.y / height;
|
|
39
|
+
heightFraction = newHeight / height;
|
|
40
|
+
}
|
|
41
|
+
return { leftFraction, topFraction, widthFraction, heightFraction };
|
|
42
|
+
}
|
|
43
|
+
observer: ResizeObserver = new ResizeObserver((val) => {
|
|
44
|
+
for (let t of val) {
|
|
45
|
+
if (t.target === this.holder) {
|
|
46
|
+
Querysub.serviceWriteDetached(() => {
|
|
47
|
+
let width = t.contentRect.width;
|
|
48
|
+
let height = t.contentRect.height;
|
|
49
|
+
this.state.width = width;
|
|
50
|
+
this.state.height = height;
|
|
51
|
+
let { widthFraction, heightFraction } = this.getSize();
|
|
52
|
+
let innerWidth = widthFraction * width;
|
|
53
|
+
let innerHeight = heightFraction * height;
|
|
54
|
+
this.props.onNewSize?.(innerWidth, innerHeight);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
componentWillUnmount() {
|
|
60
|
+
this.observer.disconnect();
|
|
61
|
+
}
|
|
62
|
+
render() {
|
|
63
|
+
let { leftFraction, topFraction, widthFraction, heightFraction } = this.getSize();
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
className={css.size("100%", "100%").position("relative", "soft") + " " + this.props.className}
|
|
68
|
+
key="maindiv"
|
|
69
|
+
ref2={(x) => {
|
|
70
|
+
if (this.holder) {
|
|
71
|
+
this.observer.unobserve(this.holder);
|
|
72
|
+
}
|
|
73
|
+
this.observer.observe(x);
|
|
74
|
+
this.holder = x;
|
|
75
|
+
}}
|
|
76
|
+
onClick={this.props.onClick}
|
|
77
|
+
>
|
|
78
|
+
<div className={this.props.innerClassName} style={{
|
|
79
|
+
position: "absolute",
|
|
80
|
+
left: `${leftFraction * 100}%`,
|
|
81
|
+
top: `${topFraction * 100}%`,
|
|
82
|
+
width: `${widthFraction * 100}%`,
|
|
83
|
+
height: `${heightFraction * 100}%`,
|
|
84
|
+
}}>{this.props.children}</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
88
|
}
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import preact from "preact"; import { qreact } from "../../src/4-dom/qreact";
|
|
2
|
-
import { Querysub } from "../../src/4-querysub/QuerysubController";
|
|
3
|
-
import { css } from "../../src/4-dom/css";
|
|
4
|
-
import { Button } from "../../src/library-components/Button";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export class ButtonSelector<T> extends qreact.Component<{
|
|
8
|
-
title?: string;
|
|
9
|
-
value: T;
|
|
10
|
-
options: { value: T; title: preact.ComponentChild; isDefault?: boolean; hotkeys?: string[] }[];
|
|
11
|
-
onChange: (value: T) => void;
|
|
12
|
-
noPadding?: boolean;
|
|
13
|
-
noDefault?: boolean;
|
|
14
|
-
noUI?: boolean;
|
|
15
|
-
|
|
16
|
-
classWrapper?: string;
|
|
17
|
-
}> {
|
|
18
|
-
render() {
|
|
19
|
-
const { options, onChange, title } = this.props;
|
|
20
|
-
const selectedValue = this.props.value;
|
|
21
|
-
let selectedOption = (
|
|
22
|
-
options.find(o => o.value === selectedValue)
|
|
23
|
-
|| (!this.props.noDefault ? (options.find(o => o.isDefault) || options[0]) : undefined)
|
|
24
|
-
)?.value;
|
|
25
|
-
return (
|
|
26
|
-
<div class={css.hbox(2).wrap + this.props.classWrapper}>
|
|
27
|
-
{title && <div
|
|
28
|
-
class={
|
|
29
|
-
css.fontWeight("bold")
|
|
30
|
-
.hsl(0, 0, 25)
|
|
31
|
-
.border("1px solid hsl(0, 0%, 5%)").pad(4, 6)
|
|
32
|
-
.color("white")
|
|
33
|
-
}
|
|
34
|
-
title={String(selectedValue)}
|
|
35
|
-
>
|
|
36
|
-
{title}
|
|
37
|
-
</div>}
|
|
38
|
-
{options.map(({ value, title, hotkeys }) =>
|
|
39
|
-
<Button
|
|
40
|
-
style={{
|
|
41
|
-
background: (
|
|
42
|
-
this.props.noUI && "transparent"
|
|
43
|
-
|| selectedOption === value && "hsl(110, 75%, 40%)"
|
|
44
|
-
|| this.props.noPadding && "hsl(0, 0%, 40%)"
|
|
45
|
-
|| ""
|
|
46
|
-
),
|
|
47
|
-
border: this.props.noUI ? "none" : undefined,
|
|
48
|
-
}}
|
|
49
|
-
onClick={() => onChange(value)}
|
|
50
|
-
hotkeys={hotkeys}
|
|
51
|
-
showHotkeys
|
|
52
|
-
className={
|
|
53
|
-
css.button.flex
|
|
54
|
-
+ ((this.props.noPadding || this.props.noUI) && css.pad(0))
|
|
55
|
-
}
|
|
56
|
-
flavor={(this.props.noPadding || this.props.noUI) ? "noui" : undefined}
|
|
57
|
-
title={String(value)}
|
|
58
|
-
>
|
|
59
|
-
{title}
|
|
60
|
-
</Button>
|
|
61
|
-
)}
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
1
|
+
import preact from "preact"; import { qreact } from "../../src/4-dom/qreact";
|
|
2
|
+
import { Querysub } from "../../src/4-querysub/QuerysubController";
|
|
3
|
+
import { css } from "../../src/4-dom/css";
|
|
4
|
+
import { Button } from "../../src/library-components/Button";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class ButtonSelector<T> extends qreact.Component<{
|
|
8
|
+
title?: string;
|
|
9
|
+
value: T;
|
|
10
|
+
options: { value: T; title: preact.ComponentChild; isDefault?: boolean; hotkeys?: string[] }[];
|
|
11
|
+
onChange: (value: T) => void;
|
|
12
|
+
noPadding?: boolean;
|
|
13
|
+
noDefault?: boolean;
|
|
14
|
+
noUI?: boolean;
|
|
15
|
+
|
|
16
|
+
classWrapper?: string;
|
|
17
|
+
}> {
|
|
18
|
+
render() {
|
|
19
|
+
const { options, onChange, title } = this.props;
|
|
20
|
+
const selectedValue = this.props.value;
|
|
21
|
+
let selectedOption = (
|
|
22
|
+
options.find(o => o.value === selectedValue)
|
|
23
|
+
|| (!this.props.noDefault ? (options.find(o => o.isDefault) || options[0]) : undefined)
|
|
24
|
+
)?.value;
|
|
25
|
+
return (
|
|
26
|
+
<div class={css.hbox(2).wrap + this.props.classWrapper}>
|
|
27
|
+
{title && <div
|
|
28
|
+
class={
|
|
29
|
+
css.fontWeight("bold")
|
|
30
|
+
.hsl(0, 0, 25)
|
|
31
|
+
.border("1px solid hsl(0, 0%, 5%)").pad(4, 6)
|
|
32
|
+
.color("white")
|
|
33
|
+
}
|
|
34
|
+
title={String(selectedValue)}
|
|
35
|
+
>
|
|
36
|
+
{title}
|
|
37
|
+
</div>}
|
|
38
|
+
{options.map(({ value, title, hotkeys }) =>
|
|
39
|
+
<Button
|
|
40
|
+
style={{
|
|
41
|
+
background: (
|
|
42
|
+
this.props.noUI && "transparent"
|
|
43
|
+
|| selectedOption === value && "hsl(110, 75%, 40%)"
|
|
44
|
+
|| this.props.noPadding && "hsl(0, 0%, 40%)"
|
|
45
|
+
|| ""
|
|
46
|
+
),
|
|
47
|
+
border: this.props.noUI ? "none" : undefined,
|
|
48
|
+
}}
|
|
49
|
+
onClick={() => onChange(value)}
|
|
50
|
+
hotkeys={hotkeys}
|
|
51
|
+
showHotkeys
|
|
52
|
+
className={
|
|
53
|
+
css.button.flex
|
|
54
|
+
+ ((this.props.noPadding || this.props.noUI) && css.pad(0))
|
|
55
|
+
}
|
|
56
|
+
flavor={(this.props.noPadding || this.props.noUI) ? "noui" : undefined}
|
|
57
|
+
title={String(value)}
|
|
58
|
+
>
|
|
59
|
+
{title}
|
|
60
|
+
</Button>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
65
|
}
|
|
@@ -1,151 +1,151 @@
|
|
|
1
|
-
import preact from "preact"; import { qreact } from "../../src/4-dom/qreact";
|
|
2
|
-
|
|
3
|
-
import { Querysub } from "../../src/4-querysub/QuerysubController";
|
|
4
|
-
import { Icon } from "../../src/library-components/icons";
|
|
5
|
-
import { css } from "../../src/4-dom/css";
|
|
6
|
-
import { LengthOrPercentage } from "../../src/4-dom/cssTypes";
|
|
7
|
-
import { Button } from "../../src/library-components/Button";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export class DropdownCustom<T> extends qreact.Component<{
|
|
11
|
-
class?: string;
|
|
12
|
-
optionClass?: string;
|
|
13
|
-
title?: string;
|
|
14
|
-
value: T;
|
|
15
|
-
onChange: (value: T, index: number) => void;
|
|
16
|
-
maxWidth?: LengthOrPercentage;
|
|
17
|
-
options: { value: T; label: (isOpen: boolean) => preact.ComponentChild; }[];
|
|
18
|
-
}> {
|
|
19
|
-
state = {
|
|
20
|
-
isOpen: false,
|
|
21
|
-
tempIndexSelected: null as null | number,
|
|
22
|
-
};
|
|
23
|
-
base: HTMLElement | null = null;
|
|
24
|
-
componentDidMount(): void {
|
|
25
|
-
const handler = (e: MouseEvent) => {
|
|
26
|
-
Querysub.commit(() => {
|
|
27
|
-
if (!this.state.isOpen) return;
|
|
28
|
-
let el = e.target as HTMLElement | null;
|
|
29
|
-
while (el) {
|
|
30
|
-
if (el === this.base) return;
|
|
31
|
-
el = el.parentElement;
|
|
32
|
-
}
|
|
33
|
-
this.state.isOpen = false;
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
window.addEventListener("click", handler);
|
|
37
|
-
qreact.onUnmount(() => window.removeEventListener("click", handler));
|
|
38
|
-
}
|
|
39
|
-
render() {
|
|
40
|
-
const { options, value, title, onChange } = this.props;
|
|
41
|
-
let selectedIndex = options.findIndex(o => o.value === value);
|
|
42
|
-
if (selectedIndex === -1) selectedIndex = 0;
|
|
43
|
-
let selectedItem = options[selectedIndex] || { value: undefined, label: () => { } };
|
|
44
|
-
|
|
45
|
-
let renderOptions = () => {
|
|
46
|
-
let selIndex = this.state.tempIndexSelected ?? selectedIndex;
|
|
47
|
-
return (
|
|
48
|
-
<div class={css.absolute.width(this.props.maxWidth || "50vw")}>
|
|
49
|
-
<Button
|
|
50
|
-
class={css.opacity(0).visibility("hidden").absolute}
|
|
51
|
-
hotkeys={["Enter", "Escape"]}
|
|
52
|
-
onClick={() => {
|
|
53
|
-
if (this.state.tempIndexSelected !== null) {
|
|
54
|
-
this.props.onChange(options[this.state.tempIndexSelected].value, this.state.tempIndexSelected);
|
|
55
|
-
}
|
|
56
|
-
this.state.isOpen = false;
|
|
57
|
-
this.state.tempIndexSelected = null;
|
|
58
|
-
}}
|
|
59
|
-
/>
|
|
60
|
-
<Button
|
|
61
|
-
class={css.opacity(0).visibility("hidden").absolute}
|
|
62
|
-
hotkeys={["ArrowUp"]}
|
|
63
|
-
onClick={(e) => {
|
|
64
|
-
e.stopPropagation();
|
|
65
|
-
this.state.tempIndexSelected = (this.state.tempIndexSelected ?? selectedIndex) - 1;
|
|
66
|
-
if (this.state.tempIndexSelected < 0) this.state.tempIndexSelected = options.length - 1;
|
|
67
|
-
}}
|
|
68
|
-
/>
|
|
69
|
-
<Button
|
|
70
|
-
class={css.opacity(0).visibility("hidden").absolute}
|
|
71
|
-
hotkeys={["ArrowDown"]}
|
|
72
|
-
onClick={(e) => {
|
|
73
|
-
e.stopPropagation();
|
|
74
|
-
this.state.tempIndexSelected = (this.state.tempIndexSelected ?? selectedIndex) + 1;
|
|
75
|
-
if (this.state.tempIndexSelected >= options.length) this.state.tempIndexSelected = 0;
|
|
76
|
-
}}
|
|
77
|
-
/>
|
|
78
|
-
<div
|
|
79
|
-
class={
|
|
80
|
-
css.pad(2).margin(2)
|
|
81
|
-
.absolute.pos(0, 0).zIndex(1)
|
|
82
|
-
.hsl(0, 0, 25)
|
|
83
|
-
.vbox(2).alignItems("stretch")
|
|
84
|
-
.overflow("auto")
|
|
85
|
-
.maxHeight("50vh")
|
|
86
|
-
.border("1px solid hsl(0, 0%, 10%)")
|
|
87
|
-
+ this.props.optionClass
|
|
88
|
-
}
|
|
89
|
-
>
|
|
90
|
-
{this.props.options.map(({ value, label }, index, list) =>
|
|
91
|
-
<>
|
|
92
|
-
{index !== 0 &&
|
|
93
|
-
<div class={css.height(1).hsl(0, 0, 20)} />
|
|
94
|
-
}
|
|
95
|
-
<div
|
|
96
|
-
onClick={() => {
|
|
97
|
-
this.props.onChange(value, index);
|
|
98
|
-
this.state.isOpen = false;
|
|
99
|
-
this.state.tempIndexSelected = null;
|
|
100
|
-
}}
|
|
101
|
-
class={
|
|
102
|
-
" "
|
|
103
|
-
+ (
|
|
104
|
-
index === selIndex && css.hsl(0, 0, 40)
|
|
105
|
-
|| index % 2 === 0 && css.hsl(0, 0, 25)
|
|
106
|
-
|| index % 2 === 1 && css.hsl(0, 0, 20)
|
|
107
|
-
|| ""
|
|
108
|
-
)
|
|
109
|
-
+ css.button.pad(2, 6)
|
|
110
|
-
+ css.background("hsl(0, 0%, 60%)", "hover")
|
|
111
|
-
+ this.props.optionClass
|
|
112
|
-
}
|
|
113
|
-
>
|
|
114
|
-
{label(true)}
|
|
115
|
-
</div>
|
|
116
|
-
</>
|
|
117
|
-
)}
|
|
118
|
-
</div>
|
|
119
|
-
</div >
|
|
120
|
-
);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<div
|
|
125
|
-
ref={e => this.base = e}
|
|
126
|
-
className={(this.state.isOpen && css.zIndex(1)) + (this.props.class || "")}
|
|
127
|
-
>
|
|
128
|
-
{this.props.title && (
|
|
129
|
-
<div
|
|
130
|
-
style={{
|
|
131
|
-
fontWeight: "bold",
|
|
132
|
-
}}
|
|
133
|
-
onClick={() => this.setState({ isOpen: !this.state.isOpen })}
|
|
134
|
-
>
|
|
135
|
-
{this.props.title}
|
|
136
|
-
</div>
|
|
137
|
-
)}
|
|
138
|
-
<div class={css.relative.vbox0.maxWidth(this.props.maxWidth)}>
|
|
139
|
-
<div
|
|
140
|
-
class={css.hbox(10).hsl(0, 0, 25).pad(4, 10).button + this.props.optionClass}
|
|
141
|
-
onClick={() => this.setState({ isOpen: !this.state.isOpen })}
|
|
142
|
-
>
|
|
143
|
-
{selectedItem?.label(false)}
|
|
144
|
-
{Icon.chevronDoubleDown()}
|
|
145
|
-
</div>
|
|
146
|
-
{this.state.isOpen && renderOptions()}
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
1
|
+
import preact from "preact"; import { qreact } from "../../src/4-dom/qreact";
|
|
2
|
+
|
|
3
|
+
import { Querysub } from "../../src/4-querysub/QuerysubController";
|
|
4
|
+
import { Icon } from "../../src/library-components/icons";
|
|
5
|
+
import { css } from "../../src/4-dom/css";
|
|
6
|
+
import { LengthOrPercentage } from "../../src/4-dom/cssTypes";
|
|
7
|
+
import { Button } from "../../src/library-components/Button";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export class DropdownCustom<T> extends qreact.Component<{
|
|
11
|
+
class?: string;
|
|
12
|
+
optionClass?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
value: T;
|
|
15
|
+
onChange: (value: T, index: number) => void;
|
|
16
|
+
maxWidth?: LengthOrPercentage;
|
|
17
|
+
options: { value: T; label: (isOpen: boolean) => preact.ComponentChild; }[];
|
|
18
|
+
}> {
|
|
19
|
+
state = {
|
|
20
|
+
isOpen: false,
|
|
21
|
+
tempIndexSelected: null as null | number,
|
|
22
|
+
};
|
|
23
|
+
base: HTMLElement | null = null;
|
|
24
|
+
componentDidMount(): void {
|
|
25
|
+
const handler = (e: MouseEvent) => {
|
|
26
|
+
Querysub.commit(() => {
|
|
27
|
+
if (!this.state.isOpen) return;
|
|
28
|
+
let el = e.target as HTMLElement | null;
|
|
29
|
+
while (el) {
|
|
30
|
+
if (el === this.base) return;
|
|
31
|
+
el = el.parentElement;
|
|
32
|
+
}
|
|
33
|
+
this.state.isOpen = false;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
window.addEventListener("click", handler);
|
|
37
|
+
qreact.onUnmount(() => window.removeEventListener("click", handler));
|
|
38
|
+
}
|
|
39
|
+
render() {
|
|
40
|
+
const { options, value, title, onChange } = this.props;
|
|
41
|
+
let selectedIndex = options.findIndex(o => o.value === value);
|
|
42
|
+
if (selectedIndex === -1) selectedIndex = 0;
|
|
43
|
+
let selectedItem = options[selectedIndex] || { value: undefined, label: () => { } };
|
|
44
|
+
|
|
45
|
+
let renderOptions = () => {
|
|
46
|
+
let selIndex = this.state.tempIndexSelected ?? selectedIndex;
|
|
47
|
+
return (
|
|
48
|
+
<div class={css.absolute.width(this.props.maxWidth || "50vw")}>
|
|
49
|
+
<Button
|
|
50
|
+
class={css.opacity(0).visibility("hidden").absolute}
|
|
51
|
+
hotkeys={["Enter", "Escape"]}
|
|
52
|
+
onClick={() => {
|
|
53
|
+
if (this.state.tempIndexSelected !== null) {
|
|
54
|
+
this.props.onChange(options[this.state.tempIndexSelected].value, this.state.tempIndexSelected);
|
|
55
|
+
}
|
|
56
|
+
this.state.isOpen = false;
|
|
57
|
+
this.state.tempIndexSelected = null;
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
<Button
|
|
61
|
+
class={css.opacity(0).visibility("hidden").absolute}
|
|
62
|
+
hotkeys={["ArrowUp"]}
|
|
63
|
+
onClick={(e) => {
|
|
64
|
+
e.stopPropagation();
|
|
65
|
+
this.state.tempIndexSelected = (this.state.tempIndexSelected ?? selectedIndex) - 1;
|
|
66
|
+
if (this.state.tempIndexSelected < 0) this.state.tempIndexSelected = options.length - 1;
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
<Button
|
|
70
|
+
class={css.opacity(0).visibility("hidden").absolute}
|
|
71
|
+
hotkeys={["ArrowDown"]}
|
|
72
|
+
onClick={(e) => {
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
this.state.tempIndexSelected = (this.state.tempIndexSelected ?? selectedIndex) + 1;
|
|
75
|
+
if (this.state.tempIndexSelected >= options.length) this.state.tempIndexSelected = 0;
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
<div
|
|
79
|
+
class={
|
|
80
|
+
css.pad(2).margin(2)
|
|
81
|
+
.absolute.pos(0, 0).zIndex(1)
|
|
82
|
+
.hsl(0, 0, 25)
|
|
83
|
+
.vbox(2).alignItems("stretch")
|
|
84
|
+
.overflow("auto")
|
|
85
|
+
.maxHeight("50vh")
|
|
86
|
+
.border("1px solid hsl(0, 0%, 10%)")
|
|
87
|
+
+ this.props.optionClass
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
{this.props.options.map(({ value, label }, index, list) =>
|
|
91
|
+
<>
|
|
92
|
+
{index !== 0 &&
|
|
93
|
+
<div class={css.height(1).hsl(0, 0, 20)} />
|
|
94
|
+
}
|
|
95
|
+
<div
|
|
96
|
+
onClick={() => {
|
|
97
|
+
this.props.onChange(value, index);
|
|
98
|
+
this.state.isOpen = false;
|
|
99
|
+
this.state.tempIndexSelected = null;
|
|
100
|
+
}}
|
|
101
|
+
class={
|
|
102
|
+
" "
|
|
103
|
+
+ (
|
|
104
|
+
index === selIndex && css.hsl(0, 0, 40)
|
|
105
|
+
|| index % 2 === 0 && css.hsl(0, 0, 25)
|
|
106
|
+
|| index % 2 === 1 && css.hsl(0, 0, 20)
|
|
107
|
+
|| ""
|
|
108
|
+
)
|
|
109
|
+
+ css.button.pad(2, 6)
|
|
110
|
+
+ css.background("hsl(0, 0%, 60%)", "hover")
|
|
111
|
+
+ this.props.optionClass
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
{label(true)}
|
|
115
|
+
</div>
|
|
116
|
+
</>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
</div >
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
ref={e => this.base = e}
|
|
126
|
+
className={(this.state.isOpen && css.zIndex(1)) + (this.props.class || "")}
|
|
127
|
+
>
|
|
128
|
+
{this.props.title && (
|
|
129
|
+
<div
|
|
130
|
+
style={{
|
|
131
|
+
fontWeight: "bold",
|
|
132
|
+
}}
|
|
133
|
+
onClick={() => this.setState({ isOpen: !this.state.isOpen })}
|
|
134
|
+
>
|
|
135
|
+
{this.props.title}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
<div class={css.relative.vbox0.maxWidth(this.props.maxWidth)}>
|
|
139
|
+
<div
|
|
140
|
+
class={css.hbox(10).hsl(0, 0, 25).pad(4, 10).button + this.props.optionClass}
|
|
141
|
+
onClick={() => this.setState({ isOpen: !this.state.isOpen })}
|
|
142
|
+
>
|
|
143
|
+
{selectedItem?.label(false)}
|
|
144
|
+
{Icon.chevronDoubleDown()}
|
|
145
|
+
</div>
|
|
146
|
+
{this.state.isOpen && renderOptions()}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
151
|
}
|