jaml-ui 0.13.1 → 0.14.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/dist/components/JamlAestheticSelector.d.ts +9 -0
- package/dist/components/JamlAestheticSelector.js +36 -0
- package/dist/components/JamlSeedInput.d.ts +9 -0
- package/dist/components/JamlSeedInput.js +30 -0
- package/dist/components/JamlSpeedometer.d.ts +11 -0
- package/dist/components/JamlSpeedometer.js +54 -0
- package/dist/hooks/searchWorkerCode.d.ts +1 -1
- package/dist/hooks/searchWorkerCode.js +31 -5
- package/dist/hooks/useSearch.d.ts +9 -0
- package/dist/hooks/useSearch.js +73 -17
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type JamlAestheticOption = "Palindrome" | "Psychosis" | "Gross" | "Nsfw" | "Funny" | "Balatro";
|
|
3
|
+
export interface JamlAestheticSelectorProps {
|
|
4
|
+
value?: JamlAestheticOption | null;
|
|
5
|
+
onChange: (aesthetic: JamlAestheticOption | null, numericValue: number) => void;
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
}
|
|
9
|
+
export declare function JamlAestheticSelector({ value, onChange, className, style }: JamlAestheticSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { JimboColorOption } from "../ui/tokens.js";
|
|
4
|
+
import { JimboText } from "../ui/jimboText.js";
|
|
5
|
+
const AESTHETICS = [
|
|
6
|
+
{ id: "Palindrome", value: 0, label: "Palindrome", desc: "Seeds that read the same forwards and backwards" },
|
|
7
|
+
{ id: "Psychosis", value: 1, label: "Psychosis", desc: "Unsettling or eerie seed patterns" },
|
|
8
|
+
{ id: "Gross", value: 2, label: "Gross", desc: "Seeds with crude or disgusting words" },
|
|
9
|
+
{ id: "Nsfw", value: 3, label: "NSFW", desc: "Seeds with adult content" },
|
|
10
|
+
{ id: "Funny", value: 4, label: "Funny", desc: "Seeds that spell funny words" },
|
|
11
|
+
{ id: "Balatro", value: 5, label: "Balatro", desc: "Seeds referencing the game itself" },
|
|
12
|
+
];
|
|
13
|
+
export function JamlAestheticSelector({ value, onChange, className, style }) {
|
|
14
|
+
return (_jsxs("div", { className: className, style: {
|
|
15
|
+
display: "flex",
|
|
16
|
+
flexDirection: "column",
|
|
17
|
+
gap: 4,
|
|
18
|
+
...style,
|
|
19
|
+
}, children: [_jsx(JimboText, { size: "xs", tone: "grey", uppercase: true, children: "Seed Aesthetics" }), _jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4 }, children: AESTHETICS.map((a) => {
|
|
20
|
+
const isActive = value === a.id;
|
|
21
|
+
return (_jsx("button", { type: "button", onClick: () => onChange(isActive ? null : a.id, a.value), title: a.desc, style: {
|
|
22
|
+
padding: "4px 10px",
|
|
23
|
+
borderRadius: 6,
|
|
24
|
+
border: `2px solid ${isActive ? JimboColorOption.GOLD : JimboColorOption.PANEL_EDGE}`,
|
|
25
|
+
background: isActive ? `${JimboColorOption.GOLD}22` : JimboColorOption.DARKEST,
|
|
26
|
+
color: isActive ? JimboColorOption.GOLD_TEXT : JimboColorOption.GREY,
|
|
27
|
+
cursor: "pointer",
|
|
28
|
+
fontSize: 11,
|
|
29
|
+
fontWeight: 700,
|
|
30
|
+
fontFamily: "m6x11plus, monospace",
|
|
31
|
+
textTransform: "uppercase",
|
|
32
|
+
letterSpacing: 0.5,
|
|
33
|
+
transition: "border-color 100ms, background 100ms",
|
|
34
|
+
}, children: a.label }, a.id));
|
|
35
|
+
}) })] }));
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface JamlSeedInputProps {
|
|
3
|
+
value?: string;
|
|
4
|
+
onChange?: (seed: string) => void;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
}
|
|
9
|
+
export declare function JamlSeedInput({ value, onChange, placeholder, className, style }: JamlSeedInputProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
|
+
import { JimboText } from "../ui/jimboText.js";
|
|
6
|
+
const SEED_PATTERN = /^[A-Z0-9]{0,8}$/;
|
|
7
|
+
export function JamlSeedInput({ value, onChange, placeholder = "Enter seed (e.g. J4SPZMWW)", className, style }) {
|
|
8
|
+
const [internal, setInternal] = useState(value ?? "");
|
|
9
|
+
const display = value ?? internal;
|
|
10
|
+
const isValid = display.length === 0 || SEED_PATTERN.test(display);
|
|
11
|
+
const handleChange = (e) => {
|
|
12
|
+
const raw = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 8);
|
|
13
|
+
setInternal(raw);
|
|
14
|
+
onChange?.(raw);
|
|
15
|
+
};
|
|
16
|
+
return (_jsxs("div", { className: className, style: { display: "flex", flexDirection: "column", gap: 4, ...style }, children: [_jsx(JimboText, { size: "xs", tone: "grey", uppercase: true, children: "Seed" }), _jsx("input", { type: "text", value: display, onChange: handleChange, placeholder: placeholder, maxLength: 8, spellCheck: false, autoComplete: "off", style: {
|
|
17
|
+
padding: "6px 10px",
|
|
18
|
+
borderRadius: 6,
|
|
19
|
+
border: `2px solid ${!isValid ? JimboColorOption.RED : display.length === 8 ? JimboColorOption.GREEN : JimboColorOption.PANEL_EDGE}`,
|
|
20
|
+
background: JimboColorOption.DARKEST,
|
|
21
|
+
color: JimboColorOption.GOLD_TEXT,
|
|
22
|
+
fontSize: 16,
|
|
23
|
+
fontWeight: 900,
|
|
24
|
+
fontFamily: "m6x11plus, monospace",
|
|
25
|
+
letterSpacing: 2,
|
|
26
|
+
textTransform: "uppercase",
|
|
27
|
+
outline: "none",
|
|
28
|
+
transition: "border-color 100ms",
|
|
29
|
+
} }), display.length > 0 && display.length < 8 && (_jsxs(JimboText, { size: "xs", tone: "grey", children: [8 - display.length, " more characters"] }))] }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SearchStatus } from "../hooks/useSearch.js";
|
|
3
|
+
export interface JamlSpeedometerProps {
|
|
4
|
+
seedsPerSecond: number;
|
|
5
|
+
totalSearched: bigint;
|
|
6
|
+
matchingSeeds: bigint;
|
|
7
|
+
status: SearchStatus;
|
|
8
|
+
className?: string;
|
|
9
|
+
style?: React.CSSProperties;
|
|
10
|
+
}
|
|
11
|
+
export declare function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, className, style, }: JamlSpeedometerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { JimboColorOption } from "../ui/tokens.js";
|
|
4
|
+
import { JimboText } from "../ui/jimboText.js";
|
|
5
|
+
function formatCount(n) {
|
|
6
|
+
if (n >= 1000000000n)
|
|
7
|
+
return `${(Number(n / 1000000n) / 1000).toFixed(1)}B`;
|
|
8
|
+
if (n >= 1000000n)
|
|
9
|
+
return `${(Number(n / 1000n) / 1000).toFixed(1)}M`;
|
|
10
|
+
if (n >= 1000n)
|
|
11
|
+
return `${(Number(n) / 1000).toFixed(1)}K`;
|
|
12
|
+
return n.toString();
|
|
13
|
+
}
|
|
14
|
+
function formatSpeed(sps) {
|
|
15
|
+
if (sps >= 1_000_000)
|
|
16
|
+
return `${(sps / 1_000_000).toFixed(1)}M`;
|
|
17
|
+
if (sps >= 1_000)
|
|
18
|
+
return `${(sps / 1_000).toFixed(0)}K`;
|
|
19
|
+
return sps.toString();
|
|
20
|
+
}
|
|
21
|
+
function needleAngle(sps) {
|
|
22
|
+
if (sps <= 0)
|
|
23
|
+
return -90;
|
|
24
|
+
const maxLog = Math.log10(5_000_000);
|
|
25
|
+
const clamped = Math.min(sps, 5_000_000);
|
|
26
|
+
const pct = Math.log10(Math.max(clamped, 1)) / maxLog;
|
|
27
|
+
return -90 + pct * 180;
|
|
28
|
+
}
|
|
29
|
+
export function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, className, style, }) {
|
|
30
|
+
const isActive = status === "running" || status === "booting";
|
|
31
|
+
const angle = needleAngle(seedsPerSecond);
|
|
32
|
+
const speedColor = seedsPerSecond >= 500_000
|
|
33
|
+
? JimboColorOption.GREEN
|
|
34
|
+
: seedsPerSecond >= 100_000
|
|
35
|
+
? JimboColorOption.GOLD
|
|
36
|
+
: seedsPerSecond > 0
|
|
37
|
+
? JimboColorOption.ORANGE
|
|
38
|
+
: JimboColorOption.GREY;
|
|
39
|
+
return (_jsxs("div", { className: className, style: {
|
|
40
|
+
display: "flex",
|
|
41
|
+
flexDirection: "column",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
gap: 6,
|
|
44
|
+
padding: "12px 16px",
|
|
45
|
+
borderRadius: 10,
|
|
46
|
+
background: `${JimboColorOption.DARKEST}cc`,
|
|
47
|
+
border: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
48
|
+
...style,
|
|
49
|
+
}, children: [_jsx("div", { style: { position: "relative", width: 120, height: 68, overflow: "hidden" }, children: _jsxs("svg", { viewBox: "0 0 120 68", width: 120, height: 68, children: [_jsx("path", { d: "M 10 65 A 50 50 0 0 1 110 65", fill: "none", stroke: JimboColorOption.DARK_GREY, strokeWidth: 6, strokeLinecap: "round" }), isActive && (_jsx("path", { d: "M 10 65 A 50 50 0 0 1 110 65", fill: "none", stroke: speedColor, strokeWidth: 6, strokeLinecap: "round", strokeDasharray: "157", strokeDashoffset: 157 - (157 * ((angle + 90) / 180)), style: { transition: "stroke-dashoffset 300ms ease, stroke 300ms ease" } })), _jsx("line", { x1: 60, y1: 65, x2: 60, y2: 20, stroke: isActive ? JimboColorOption.RED : JimboColorOption.GREY, strokeWidth: 2, strokeLinecap: "round", style: {
|
|
50
|
+
transformOrigin: "60px 65px",
|
|
51
|
+
transform: `rotate(${angle}deg)`,
|
|
52
|
+
transition: "transform 300ms ease",
|
|
53
|
+
} }), _jsx("circle", { cx: 60, cy: 65, r: 4, fill: JimboColorOption.RED })] }) }), _jsxs("div", { style: { textAlign: "center" }, children: [_jsx("div", { style: { fontSize: 20, fontWeight: 900, fontFamily: "m6x11plus, monospace", color: isActive ? speedColor : JimboColorOption.GREY }, children: isActive ? formatSpeed(seedsPerSecond) : "---" }), _jsx(JimboText, { size: "xs", tone: "grey", uppercase: true, children: "seeds / sec" })] }), _jsxs("div", { style: { display: "flex", gap: 16, marginTop: 2 }, children: [_jsxs("div", { style: { textAlign: "center" }, children: [_jsx("div", { style: { fontSize: 13, fontWeight: 700, fontFamily: "m6x11plus, monospace", color: JimboColorOption.WHITE }, children: formatCount(totalSearched) }), _jsx(JimboText, { size: "xs", tone: "grey", uppercase: true, children: "searched" })] }), _jsxs("div", { style: { textAlign: "center" }, children: [_jsx("div", { style: { fontSize: 13, fontWeight: 700, fontFamily: "m6x11plus, monospace", color: JimboColorOption.GREEN_TEXT }, children: formatCount(matchingSeeds) }), _jsx(JimboText, { size: "xs", tone: "grey", uppercase: true, children: "matches" })] })] })] }));
|
|
54
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SEARCH_WORKER_CODE = "\nlet MotelyWasm = null;\nlet MotelyWasmEvents = null;\nlet activeSearch = null;\n\nself.addEventListener('message', async function(e) {\n const msg = e.data;\n\n if (msg.type === 'init') {\n try {\n const mod = await import(msg.url);\n await mod.default.boot();\n MotelyWasm = mod.MotelyWasm;\n MotelyWasmEvents = mod.MotelyWasmEvents;\n self.postMessage({ type: 'ready' });\n } catch (err) {\n self.postMessage({ type: 'error', message: String(err) });\n }\n return;\n }\n\n if (msg.type === 'start') {\n if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }\n const validation = MotelyWasm.validateJaml(msg.jaml);\n if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }\n\n let rId, pId, cId;\n function cleanup() {\n MotelyWasmEvents.onResult.unsubscribeById(rId);\n MotelyWasmEvents.onProgress.unsubscribeById(pId);\n MotelyWasmEvents.onComplete.unsubscribeById(cId);\n activeSearch = null;\n }\n\n rId = MotelyWasmEvents.onResult.subscribe(function(seed, score) {\n self.postMessage({ type: 'result', seed, score });\n });\n pId = MotelyWasmEvents.onProgress.subscribe(function(searched, matching) {\n self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });\n });\n cId = MotelyWasmEvents.onComplete.subscribe(function(status, searched, matched) {\n cleanup();\n self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });\n });\n\n try {\n activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);\n } catch (err) {\n cleanup();\n self.postMessage({ type: 'error', message: String(err) });\n }\n return;\n }\n\n if (msg.type === 'stop') {\n if (activeSearch) { activeSearch.cancel(); activeSearch = null; }\n self.postMessage({ type: 'cancelled' });\n }\n});\n";
|
|
1
|
+
export declare const SEARCH_WORKER_CODE = "\nlet MotelyWasm = null;\nlet MotelyWasmEvents = null;\nlet Filters = null;\nlet activeSearch = null;\n\nself.addEventListener('message', async function(e) {\n const msg = e.data;\n\n if (msg.type === 'init') {\n try {\n const mod = await import(msg.url);\n await mod.default.boot();\n MotelyWasm = mod.MotelyWasm;\n MotelyWasmEvents = mod.MotelyWasmEvents;\n Filters = mod.Filters;\n self.postMessage({ type: 'ready' });\n } catch (err) {\n self.postMessage({ type: 'error', message: String(err) });\n }\n return;\n }\n\n if (msg.type === 'start') {\n if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }\n const validation = MotelyWasm.validateJaml(msg.jaml);\n if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }\n\n let rId, pId, cId;\n function cleanup() {\n MotelyWasmEvents.onResult.unsubscribeById(rId);\n MotelyWasmEvents.onProgress.unsubscribeById(pId);\n MotelyWasmEvents.onComplete.unsubscribeById(cId);\n activeSearch = null;\n }\n\n rId = MotelyWasmEvents.onResult.subscribe(function(seed, score, tallyColumns) {\n self.postMessage({ type: 'result', seed, score, tallyColumns: Array.from(tallyColumns) });\n });\n pId = MotelyWasmEvents.onProgress.subscribe(function(searched, matching) {\n self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });\n });\n cId = MotelyWasmEvents.onComplete.subscribe(function(status, searched, matched) {\n cleanup();\n self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });\n });\n\n try {\n const mode = msg.mode || 'random';\n\n if (mode === 'random') {\n activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);\n } else if (mode === 'aesthetic') {\n activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);\n } else if (mode === 'seedList') {\n activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);\n } else if (mode === 'keyword') {\n activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || '');\n } else if (mode === 'sequential') {\n activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch), BigInt(msg.endBatch));\n } else {\n self.postMessage({ type: 'error', message: 'Unknown search mode: ' + mode });\n cleanup();\n return;\n }\n } catch (err) {\n cleanup();\n self.postMessage({ type: 'error', message: String(err) });\n }\n return;\n }\n\n if (msg.type === 'stop') {\n if (activeSearch) { activeSearch.cancel(); activeSearch = null; }\n self.postMessage({ type: 'cancelled' });\n }\n\n if (msg.type === 'get_tally_labels') {\n if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }\n try {\n const labels = MotelyWasm.getTallyLabels(msg.jaml);\n self.postMessage({ type: 'tally_labels', labels });\n } catch (err) {\n self.postMessage({ type: 'error', message: String(err) });\n }\n }\n});\n";
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
// Worker code as an inline string — created as a Blob URL at runtime.
|
|
2
|
-
// This avoids bundler/import.meta.url issues when shipped as an npm package.
|
|
3
1
|
export const SEARCH_WORKER_CODE = `
|
|
4
2
|
let MotelyWasm = null;
|
|
5
3
|
let MotelyWasmEvents = null;
|
|
4
|
+
let Filters = null;
|
|
6
5
|
let activeSearch = null;
|
|
7
6
|
|
|
8
7
|
self.addEventListener('message', async function(e) {
|
|
@@ -14,6 +13,7 @@ self.addEventListener('message', async function(e) {
|
|
|
14
13
|
await mod.default.boot();
|
|
15
14
|
MotelyWasm = mod.MotelyWasm;
|
|
16
15
|
MotelyWasmEvents = mod.MotelyWasmEvents;
|
|
16
|
+
Filters = mod.Filters;
|
|
17
17
|
self.postMessage({ type: 'ready' });
|
|
18
18
|
} catch (err) {
|
|
19
19
|
self.postMessage({ type: 'error', message: String(err) });
|
|
@@ -34,8 +34,8 @@ self.addEventListener('message', async function(e) {
|
|
|
34
34
|
activeSearch = null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
rId = MotelyWasmEvents.onResult.subscribe(function(seed, score) {
|
|
38
|
-
self.postMessage({ type: 'result', seed, score });
|
|
37
|
+
rId = MotelyWasmEvents.onResult.subscribe(function(seed, score, tallyColumns) {
|
|
38
|
+
self.postMessage({ type: 'result', seed, score, tallyColumns: Array.from(tallyColumns) });
|
|
39
39
|
});
|
|
40
40
|
pId = MotelyWasmEvents.onProgress.subscribe(function(searched, matching) {
|
|
41
41
|
self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });
|
|
@@ -46,7 +46,23 @@ self.addEventListener('message', async function(e) {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
try {
|
|
49
|
-
|
|
49
|
+
const mode = msg.mode || 'random';
|
|
50
|
+
|
|
51
|
+
if (mode === 'random') {
|
|
52
|
+
activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
|
|
53
|
+
} else if (mode === 'aesthetic') {
|
|
54
|
+
activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);
|
|
55
|
+
} else if (mode === 'seedList') {
|
|
56
|
+
activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);
|
|
57
|
+
} else if (mode === 'keyword') {
|
|
58
|
+
activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || '');
|
|
59
|
+
} else if (mode === 'sequential') {
|
|
60
|
+
activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch), BigInt(msg.endBatch));
|
|
61
|
+
} else {
|
|
62
|
+
self.postMessage({ type: 'error', message: 'Unknown search mode: ' + mode });
|
|
63
|
+
cleanup();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
50
66
|
} catch (err) {
|
|
51
67
|
cleanup();
|
|
52
68
|
self.postMessage({ type: 'error', message: String(err) });
|
|
@@ -58,5 +74,15 @@ self.addEventListener('message', async function(e) {
|
|
|
58
74
|
if (activeSearch) { activeSearch.cancel(); activeSearch = null; }
|
|
59
75
|
self.postMessage({ type: 'cancelled' });
|
|
60
76
|
}
|
|
77
|
+
|
|
78
|
+
if (msg.type === 'get_tally_labels') {
|
|
79
|
+
if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
|
|
80
|
+
try {
|
|
81
|
+
const labels = MotelyWasm.getTallyLabels(msg.jaml);
|
|
82
|
+
self.postMessage({ type: 'tally_labels', labels });
|
|
83
|
+
} catch (err) {
|
|
84
|
+
self.postMessage({ type: 'error', message: String(err) });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
61
87
|
});
|
|
62
88
|
`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface SearchResult {
|
|
2
2
|
seed: string;
|
|
3
3
|
score: number;
|
|
4
|
+
tallyColumns?: number[];
|
|
4
5
|
}
|
|
5
6
|
export type SearchStatus = "idle" | "booting" | "running" | "completed" | "cancelled" | "error";
|
|
6
7
|
export interface UseSearchState {
|
|
@@ -9,14 +10,22 @@ export interface UseSearchState {
|
|
|
9
10
|
matchingSeeds: bigint;
|
|
10
11
|
status: SearchStatus;
|
|
11
12
|
error: string | null;
|
|
13
|
+
seedsPerSecond: number;
|
|
14
|
+
tallyLabels: string[];
|
|
12
15
|
}
|
|
13
16
|
export declare function useSearch(motelyWasmUrl: string): {
|
|
14
17
|
start: (jaml: string, count: number) => void;
|
|
18
|
+
startAesthetic: (jaml: string, aesthetic: number) => void;
|
|
19
|
+
startSeedList: (jaml: string, seeds: string[]) => void;
|
|
20
|
+
startKeyword: (jaml: string, keywords: string, padding?: string) => void;
|
|
15
21
|
cancel: () => void;
|
|
16
22
|
clearError: () => void;
|
|
23
|
+
fetchTallyLabels: (jaml: string) => void;
|
|
17
24
|
results: SearchResult[];
|
|
18
25
|
totalSearched: bigint;
|
|
19
26
|
matchingSeeds: bigint;
|
|
20
27
|
status: SearchStatus;
|
|
21
28
|
error: string | null;
|
|
29
|
+
seedsPerSecond: number;
|
|
30
|
+
tallyLabels: string[];
|
|
22
31
|
};
|
package/dist/hooks/useSearch.js
CHANGED
|
@@ -8,16 +8,20 @@ function createWorker(motelyWasmUrl) {
|
|
|
8
8
|
worker.postMessage({ type: "init", url: motelyWasmUrl });
|
|
9
9
|
return worker;
|
|
10
10
|
}
|
|
11
|
+
const INITIAL_STATE = {
|
|
12
|
+
results: [],
|
|
13
|
+
totalSearched: 0n,
|
|
14
|
+
matchingSeeds: 0n,
|
|
15
|
+
status: "idle",
|
|
16
|
+
error: null,
|
|
17
|
+
seedsPerSecond: 0,
|
|
18
|
+
tallyLabels: [],
|
|
19
|
+
};
|
|
11
20
|
export function useSearch(motelyWasmUrl) {
|
|
12
|
-
const [state, setState] = useState(
|
|
13
|
-
results: [],
|
|
14
|
-
totalSearched: 0n,
|
|
15
|
-
matchingSeeds: 0n,
|
|
16
|
-
status: "idle",
|
|
17
|
-
error: null,
|
|
18
|
-
});
|
|
21
|
+
const [state, setState] = useState(INITIAL_STATE);
|
|
19
22
|
const workerRef = useRef(null);
|
|
20
23
|
const readyRef = useRef(false);
|
|
24
|
+
const speedRef = useRef({ lastSearched: 0n, lastTime: 0, ema: 0 });
|
|
21
25
|
useEffect(() => {
|
|
22
26
|
setState((s) => ({ ...s, status: "booting" }));
|
|
23
27
|
const worker = createWorker(motelyWasmUrl);
|
|
@@ -30,16 +34,51 @@ export function useSearch(motelyWasmUrl) {
|
|
|
30
34
|
setState((s) => s.status === "booting" ? { ...s, status: "idle" } : s);
|
|
31
35
|
}
|
|
32
36
|
else if (msg.type === "result") {
|
|
33
|
-
setState((s) => ({
|
|
37
|
+
setState((s) => ({
|
|
38
|
+
...s,
|
|
39
|
+
results: [...s.results, {
|
|
40
|
+
seed: msg.seed,
|
|
41
|
+
score: msg.score,
|
|
42
|
+
tallyColumns: msg.tallyColumns,
|
|
43
|
+
}],
|
|
44
|
+
}));
|
|
34
45
|
}
|
|
35
46
|
else if (msg.type === "progress") {
|
|
36
|
-
|
|
47
|
+
const searched = BigInt(msg.searched);
|
|
48
|
+
const matching = BigInt(msg.matching);
|
|
49
|
+
const now = performance.now();
|
|
50
|
+
const ref = speedRef.current;
|
|
51
|
+
let sps = ref.ema;
|
|
52
|
+
if (ref.lastTime > 0) {
|
|
53
|
+
const dtMs = now - ref.lastTime;
|
|
54
|
+
if (dtMs > 0) {
|
|
55
|
+
const delta = Number(searched - ref.lastSearched);
|
|
56
|
+
const instantSps = delta / (dtMs / 1000);
|
|
57
|
+
sps = ref.ema === 0 ? instantSps : ref.ema * 0.7 + instantSps * 0.3;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
ref.lastSearched = searched;
|
|
61
|
+
ref.lastTime = now;
|
|
62
|
+
ref.ema = sps;
|
|
63
|
+
setState((s) => ({ ...s, totalSearched: searched, matchingSeeds: matching, seedsPerSecond: Math.round(sps) }));
|
|
37
64
|
}
|
|
38
65
|
else if (msg.type === "complete") {
|
|
39
|
-
|
|
66
|
+
speedRef.current = { lastSearched: 0n, lastTime: 0, ema: 0 };
|
|
67
|
+
setState((s) => ({
|
|
68
|
+
...s,
|
|
69
|
+
status: msg.status === "Completed" ? "completed" : "error",
|
|
70
|
+
error: msg.status !== "Completed" ? msg.status : null,
|
|
71
|
+
totalSearched: BigInt(msg.searched),
|
|
72
|
+
matchingSeeds: BigInt(msg.matched),
|
|
73
|
+
seedsPerSecond: 0,
|
|
74
|
+
}));
|
|
40
75
|
}
|
|
41
76
|
else if (msg.type === "cancelled") {
|
|
42
|
-
|
|
77
|
+
speedRef.current = { lastSearched: 0n, lastTime: 0, ema: 0 };
|
|
78
|
+
setState((s) => ({ ...s, status: "cancelled", seedsPerSecond: 0 }));
|
|
79
|
+
}
|
|
80
|
+
else if (msg.type === "tally_labels") {
|
|
81
|
+
setState((s) => ({ ...s, tallyLabels: msg.labels }));
|
|
43
82
|
}
|
|
44
83
|
else if (msg.type === "error") {
|
|
45
84
|
setState((s) => ({ ...s, status: "error", error: msg.message }));
|
|
@@ -50,13 +89,15 @@ export function useSearch(motelyWasmUrl) {
|
|
|
50
89
|
workerRef.current = null;
|
|
51
90
|
};
|
|
52
91
|
}, [motelyWasmUrl]);
|
|
53
|
-
const
|
|
92
|
+
const sendStart = useCallback((payload) => {
|
|
54
93
|
const worker = workerRef.current;
|
|
55
94
|
if (!worker)
|
|
56
95
|
return;
|
|
57
|
-
|
|
96
|
+
speedRef.current = { lastSearched: 0n, lastTime: 0, ema: 0 };
|
|
97
|
+
setState({ ...INITIAL_STATE, status: "running", tallyLabels: state.tallyLabels });
|
|
98
|
+
const send = () => worker.postMessage(payload);
|
|
58
99
|
if (readyRef.current) {
|
|
59
|
-
|
|
100
|
+
send();
|
|
60
101
|
}
|
|
61
102
|
else {
|
|
62
103
|
const orig = worker.onmessage;
|
|
@@ -64,16 +105,31 @@ export function useSearch(motelyWasmUrl) {
|
|
|
64
105
|
orig?.call(worker, e);
|
|
65
106
|
if (e.data.type === "ready") {
|
|
66
107
|
worker.onmessage = orig;
|
|
67
|
-
|
|
108
|
+
send();
|
|
68
109
|
}
|
|
69
110
|
};
|
|
70
111
|
}
|
|
71
|
-
}, []);
|
|
112
|
+
}, [state.tallyLabels]);
|
|
113
|
+
const start = useCallback((jaml, count) => {
|
|
114
|
+
sendStart({ type: "start", mode: "random", jaml, count });
|
|
115
|
+
}, [sendStart]);
|
|
116
|
+
const startAesthetic = useCallback((jaml, aesthetic) => {
|
|
117
|
+
sendStart({ type: "start", mode: "aesthetic", jaml, aesthetic });
|
|
118
|
+
}, [sendStart]);
|
|
119
|
+
const startSeedList = useCallback((jaml, seeds) => {
|
|
120
|
+
sendStart({ type: "start", mode: "seedList", jaml, seeds });
|
|
121
|
+
}, [sendStart]);
|
|
122
|
+
const startKeyword = useCallback((jaml, keywords, padding) => {
|
|
123
|
+
sendStart({ type: "start", mode: "keyword", jaml, keywords, padding });
|
|
124
|
+
}, [sendStart]);
|
|
72
125
|
const cancel = useCallback(() => {
|
|
73
126
|
workerRef.current?.postMessage({ type: "stop" });
|
|
74
127
|
}, []);
|
|
75
128
|
const clearError = useCallback(() => {
|
|
76
129
|
setState((s) => (s.error || s.status === "error" ? { ...s, error: null, status: "idle" } : s));
|
|
77
130
|
}, []);
|
|
78
|
-
|
|
131
|
+
const fetchTallyLabels = useCallback((jaml) => {
|
|
132
|
+
workerRef.current?.postMessage({ type: "get_tally_labels", jaml });
|
|
133
|
+
}, []);
|
|
134
|
+
return { ...state, start, startAesthetic, startSeedList, startKeyword, cancel, clearError, fetchTallyLabels };
|
|
79
135
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,3 +25,6 @@ export { extractVisualJamlItems, type JamlPreviewGroups, type JamlPreviewItem, t
|
|
|
25
25
|
export { useMotelyStream, type StreamItem, type StreamState } from "./hooks/useShopStream.js";
|
|
26
26
|
export { useSearch, type SearchResult, type SearchStatus, type UseSearchState, } from "./hooks/useSearch.js";
|
|
27
27
|
export { useAnalyzer, type AnalyzerStatus, type AnalyzerLive, type MotelyJsRunState, } from "./hooks/useAnalyzer.js";
|
|
28
|
+
export { JamlSpeedometer, type JamlSpeedometerProps, } from "./components/JamlSpeedometer.js";
|
|
29
|
+
export { JamlAestheticSelector, type JamlAestheticSelectorProps, type JamlAestheticOption, } from "./components/JamlAestheticSelector.js";
|
|
30
|
+
export { JamlSeedInput, type JamlSeedInputProps, } from "./components/JamlSeedInput.js";
|
package/dist/index.js
CHANGED
|
@@ -26,3 +26,6 @@ export { extractVisualJamlItems, } from "./utils/jamlMapPreview.js";
|
|
|
26
26
|
export { useMotelyStream } from "./hooks/useShopStream.js";
|
|
27
27
|
export { useSearch, } from "./hooks/useSearch.js";
|
|
28
28
|
export { useAnalyzer, } from "./hooks/useAnalyzer.js";
|
|
29
|
+
export { JamlSpeedometer, } from "./components/JamlSpeedometer.js";
|
|
30
|
+
export { JamlAestheticSelector, } from "./components/JamlAestheticSelector.js";
|
|
31
|
+
export { JamlSeedInput, } from "./components/JamlSeedInput.js";
|