jaml-ui 0.24.11 → 0.24.14
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/JamlCodeEditor.js +1 -1
- package/dist/components/JamlIde.d.ts +3 -1
- package/dist/components/JamlIde.js +37 -2
- package/dist/components/JamlIdeVisual.d.ts +1 -0
- package/dist/components/JamlIdeVisual.js +12 -2
- package/dist/components/jamlMap/JamlMapEditor.js +128 -9
- package/dist/components/jamlMap/MysterySlot.d.ts +4 -0
- package/dist/components/jamlMap/MysterySlot.js +6 -3
- package/dist/hooks/searchWorker.d.ts +29 -1
- package/dist/hooks/searchWorker.js +85 -36
- package/dist/hooks/useSearch.js +1 -99
- package/dist/ui/jimbo.css +12 -1
- package/dist/ui/showcase.js +2 -1
- package/dist/utils/jamlVisualFilter.js +15 -0
- package/fonts.css +9 -0
- package/jaml.schema.json +1 -1
- package/package.json +5 -5
|
@@ -26,7 +26,7 @@ const balatroTheme = EditorView.theme({
|
|
|
26
26
|
height: "100%",
|
|
27
27
|
},
|
|
28
28
|
".cm-content": {
|
|
29
|
-
fontFamily: "
|
|
29
|
+
fontFamily: "var(--j-font-code, 'JetBrains Mono', ui-monospace, monospace)",
|
|
30
30
|
lineHeight: "22px",
|
|
31
31
|
padding: "12px 0",
|
|
32
32
|
caretColor: JimboColorOption.GOLD_TEXT,
|
|
@@ -25,6 +25,8 @@ export interface JamlIdeProps {
|
|
|
25
25
|
codePlaceholder?: string;
|
|
26
26
|
onSearch?: () => void;
|
|
27
27
|
isSearching?: boolean;
|
|
28
|
+
/** Hide the Balatro attribution footer. Default: false (always shown). */
|
|
29
|
+
hideFooter?: boolean;
|
|
28
30
|
/**
|
|
29
31
|
* Controlled visual filter. When provided alongside `onVisualFilterChange`, the Visual tab
|
|
30
32
|
* is fully controlled by the parent. When absent, the Visual tab auto-derives from the text.
|
|
@@ -34,4 +36,4 @@ export interface JamlIdeProps {
|
|
|
34
36
|
}
|
|
35
37
|
export type { JamlVisualFilter } from "./JamlIdeVisual.js";
|
|
36
38
|
export type { JamlVisualClause, JamlZone } from "./JamlIdeVisual.js";
|
|
37
|
-
export declare function JamlIde({ jaml, defaultJaml, onChange, defaultMode, searchResults, className, style, title, subtitle, compactHeader, actions, codePlaceholder, onSearch, isSearching, visualFilter, onVisualFilterChange, }: JamlIdeProps): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
export declare function JamlIde({ jaml, defaultJaml, onChange, defaultMode, searchResults, className, style, title, subtitle, compactHeader, actions, codePlaceholder, onSearch, isSearching, hideFooter, visualFilter, onVisualFilterChange, }: JamlIdeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
|
+
import { JimboBalatroFooter } from "../ui/footer.js";
|
|
4
5
|
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
5
6
|
import { JamlMapEditor, CategoryMenu, JokerPicker, CategoryPicker, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./jamlMap/index.js";
|
|
6
7
|
import { JamlIdeToolbar } from "./JamlIdeToolbar.js";
|
|
@@ -9,6 +10,8 @@ import { JamlCodeEditor } from "./JamlCodeEditor.js";
|
|
|
9
10
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
10
11
|
import { JimboModal } from "../ui/panel.js";
|
|
11
12
|
import { jamlTextToVisualFilter, visualFilterToJamlText } from "../utils/jamlVisualFilter.js";
|
|
13
|
+
import { DeckSprite } from "./DeckSprite.js";
|
|
14
|
+
import { DECK_OPTIONS, STAKE_OPTIONS } from "../lib/data/constants.js";
|
|
12
15
|
const CATEGORY_CONFIG_MAP = {
|
|
13
16
|
voucher: VOUCHER_PICKER_CONFIG,
|
|
14
17
|
tag: TAG_PICKER_CONFIG,
|
|
@@ -88,7 +91,39 @@ function ResultsView({ results, jaml }) {
|
|
|
88
91
|
})] })] })) : null] }, result.seed));
|
|
89
92
|
}) }));
|
|
90
93
|
}
|
|
91
|
-
|
|
94
|
+
function readRootValue(jaml, key, fallback) {
|
|
95
|
+
const match = jaml.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
96
|
+
return match?.[1]?.trim() || fallback;
|
|
97
|
+
}
|
|
98
|
+
function setRootValue(jaml, key, value) {
|
|
99
|
+
const line = `${key}: ${value}`;
|
|
100
|
+
const pattern = new RegExp(`^${key}:\\s*.*$`, "m");
|
|
101
|
+
if (pattern.test(jaml)) {
|
|
102
|
+
return jaml.replace(pattern, line);
|
|
103
|
+
}
|
|
104
|
+
const trimmed = jaml.trimEnd();
|
|
105
|
+
return trimmed.length > 0 ? `${line}\n${trimmed}` : line;
|
|
106
|
+
}
|
|
107
|
+
function DeckStakeSelector({ jaml, onChange, }) {
|
|
108
|
+
const deck = readRootValue(jaml, "deck", "Red");
|
|
109
|
+
const stake = readRootValue(jaml, "stake", "White");
|
|
110
|
+
const setDeck = (nextDeck) => onChange(setRootValue(jaml, "deck", nextDeck));
|
|
111
|
+
const setStake = (nextStake) => onChange(setRootValue(jaml, "stake", nextStake));
|
|
112
|
+
return (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 }, children: [_jsx(DeckSprite, { deck: deck, stake: stake, size: compactDeckSpriteSize }), _jsx("select", { value: deck, onChange: (event) => setDeck(event.currentTarget.value), style: selectorStyle, children: DECK_OPTIONS.map((option) => (_jsx("option", { value: option, children: option }, option))) }), _jsx("select", { value: stake, onChange: (event) => setStake(event.currentTarget.value), style: selectorStyle, children: STAKE_OPTIONS.map((option) => (_jsx("option", { value: option, children: option }, option))) })] }));
|
|
113
|
+
}
|
|
114
|
+
const compactDeckSpriteSize = 24;
|
|
115
|
+
const selectorStyle = {
|
|
116
|
+
minWidth: 0,
|
|
117
|
+
height: 28,
|
|
118
|
+
borderRadius: 8,
|
|
119
|
+
border: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
120
|
+
background: JimboColorOption.DARKEST,
|
|
121
|
+
color: JimboColorOption.WHITE,
|
|
122
|
+
fontFamily: "m6x11plus, monospace",
|
|
123
|
+
fontSize: 12,
|
|
124
|
+
padding: "0 8px",
|
|
125
|
+
};
|
|
126
|
+
export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", searchResults = [], className = "", style, title = "JAML IDE", subtitle = "Jimbo's Ante Markup Language", compactHeader = false, actions, codePlaceholder = "Enter JAML...", onSearch, isSearching = false, hideFooter = false, visualFilter, onVisualFilterChange, }) {
|
|
92
127
|
const [mode, setMode] = useState(defaultMode);
|
|
93
128
|
const [internalText, setInternalText] = useState(jaml ?? defaultJaml ?? "");
|
|
94
129
|
const [lastJamlProp, setLastJamlProp] = useState(jaml);
|
|
@@ -188,5 +223,5 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
188
223
|
padding: compactHeader ? "8px 10px" : "10px 14px",
|
|
189
224
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
190
225
|
background: JimboColorOption.TEAL_GREY,
|
|
191
|
-
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, fontWeight: "normal", fontFamily: "m6x11plus, monospace", color: JimboColorOption.GOLD_TEXT }, children: title }), subtitle ? _jsx("div", { style: { fontSize: 11, color: JimboColorOption.GREY }, children: subtitle }) : null] }),
|
|
226
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, fontWeight: "normal", fontFamily: "m6x11plus, monospace", color: JimboColorOption.GOLD_TEXT }, children: title }), subtitle ? _jsx("div", { style: { fontSize: 11, color: JimboColorOption.GREY }, children: subtitle }) : null] }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", justifyContent: "flex-end" }, children: [_jsx(DeckStakeSelector, { jaml: text, onChange: handleTextChange }), actions] })] }), _jsx(JamlIdeToolbar, { mode: mode, onModeChange: setMode, resultCount: results.length, onSearch: onSearch, isSearching: isSearching }), _jsxs("div", { style: { flex: 1, minHeight: 0, overflow: mode === "map" ? "hidden" : "auto", background: JimboColorOption.DARKEST }, children: [mode === "visual" ? (_jsx(JamlIdeVisual, { filter: activeFilter, onChange: handleVisualFilterChange, onAddClause: handleAddClause })) : null, mode === "code" ? (_jsx(JamlCodeEditor, { value: text, onChange: handleTextChange, placeholder: codePlaceholder })) : null, mode === "map" ? _jsx(JamlMapEditor, { onChange: handleTextChange }) : null, mode === "results" ? (_jsx("div", { style: { padding: 12 }, children: _jsx(ResultsView, { results: results, jaml: text }) })) : null] }), !hideFooter && _jsx(JimboBalatroFooter, {}), _jsx(JimboModal, { open: addZone !== null, onClose: handlePickerClose, children: addZone !== null && (pickerFlow === "category" ? (_jsx(CategoryMenu, { onSelect: (cat) => setPickerFlow(cat) })) : pickerFlow === "joker" ? (_jsx(JokerPicker, { onSelect: handlePickerSelect, onCancel: handlePickerClose })) : (_jsx(CategoryPicker, { config: CATEGORY_CONFIG_MAP[pickerFlow], onSelect: handlePickerSelect, onCancel: handlePickerClose }))) })] }));
|
|
192
227
|
}
|
|
@@ -21,11 +21,21 @@ function clauseSpriteSheet(type) {
|
|
|
21
21
|
return "Jokers";
|
|
22
22
|
if (type === "voucher")
|
|
23
23
|
return "Vouchers";
|
|
24
|
-
if (type === "
|
|
24
|
+
if (type === "tag" ||
|
|
25
|
+
type === "tags" ||
|
|
26
|
+
type === "smallBlindTag" ||
|
|
27
|
+
type === "bigBlindTag" ||
|
|
28
|
+
type === "smallblindtag" ||
|
|
29
|
+
type === "bigblindtag")
|
|
25
30
|
return "tags";
|
|
26
31
|
if (type === "boss")
|
|
27
32
|
return "BlindChips";
|
|
28
|
-
if (type === "tarot" ||
|
|
33
|
+
if (type === "tarot" ||
|
|
34
|
+
type === "tarotCard" ||
|
|
35
|
+
type === "spectral" ||
|
|
36
|
+
type === "spectralCard" ||
|
|
37
|
+
type === "planet" ||
|
|
38
|
+
type === "planetCard")
|
|
29
39
|
return "Tarots";
|
|
30
40
|
return undefined;
|
|
31
41
|
}
|
|
@@ -6,7 +6,7 @@ import { JokerPicker } from "./JokerPicker.js";
|
|
|
6
6
|
import { CategoryPicker, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./CategoryPicker.js";
|
|
7
7
|
import { JimboButton, JimboModal } from "../../ui/panel.js";
|
|
8
8
|
import { JimboText } from "../../ui/jimboText.js";
|
|
9
|
-
import { JimboColorOption } from "../../ui/tokens.js";
|
|
9
|
+
import { JimboColorOption, withAlpha } from "../../ui/tokens.js";
|
|
10
10
|
import { JimboSprite } from "../../ui/sprites.js";
|
|
11
11
|
// ─── Category menu items ─────────────────────────────────────────────────────
|
|
12
12
|
const C = JimboColorOption;
|
|
@@ -48,12 +48,19 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
48
48
|
const [ante, setAnte] = useState(1);
|
|
49
49
|
const [antesState, setAntesState] = useState({});
|
|
50
50
|
const [activeSlot, setActiveSlot] = useState(null);
|
|
51
|
+
const [activePackDetail, setActivePackDetail] = useState(null);
|
|
51
52
|
const [pickerFlow, setPickerFlow] = useState("category");
|
|
53
|
+
const [activePackSelection, setActivePackSelection] = useState(null);
|
|
52
54
|
const currentAnteSelections = antesState[ante] || {};
|
|
53
55
|
const handleSlotTap = useCallback((anteIndex, id, forceCategory) => {
|
|
56
|
+
const existing = (antesState[anteIndex] || {})[id];
|
|
57
|
+
if (forceCategory === "pack" && existing?.packName) {
|
|
58
|
+
setActivePackDetail({ ante: anteIndex, id });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
54
61
|
setActiveSlot({ ante: anteIndex, id, forceCategory });
|
|
55
62
|
setPickerFlow(forceCategory || "category");
|
|
56
|
-
}, []);
|
|
63
|
+
}, [antesState]);
|
|
57
64
|
const handleSlotClear = useCallback((anteIndex, id) => {
|
|
58
65
|
setAntesState((prev) => {
|
|
59
66
|
const next = { ...prev };
|
|
@@ -65,25 +72,51 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
65
72
|
onChangeRef.current?.(buildJamlText(next));
|
|
66
73
|
return next;
|
|
67
74
|
});
|
|
75
|
+
setActivePackDetail((prev) => prev && prev.ante === anteIndex && prev.id === id ? null : prev);
|
|
68
76
|
}, []);
|
|
77
|
+
const handlePackChange = useCallback(() => {
|
|
78
|
+
if (!activePackDetail)
|
|
79
|
+
return;
|
|
80
|
+
setActivePackDetail(null);
|
|
81
|
+
setActivePackSelection(null);
|
|
82
|
+
setActiveSlot({ ante: activePackDetail.ante, id: activePackDetail.id, forceCategory: "pack" });
|
|
83
|
+
setPickerFlow("pack");
|
|
84
|
+
}, [activePackDetail]);
|
|
69
85
|
const handleCategorySelect = useCallback((cat) => {
|
|
70
86
|
setPickerFlow(cat);
|
|
71
87
|
}, []);
|
|
72
88
|
const handleItemSelect = useCallback((selection) => {
|
|
73
89
|
if (!activeSlot)
|
|
74
90
|
return;
|
|
91
|
+
if (activeSlot.forceCategory === "pack" && selection.category === "pack") {
|
|
92
|
+
const slotIndex = getPackSlotIndex(activeSlot.id);
|
|
93
|
+
if (slotIndex === null)
|
|
94
|
+
return;
|
|
95
|
+
const nextFlow = getPackFollowupFlow(selection.value);
|
|
96
|
+
setActivePackSelection({ packName: selection.value, slotIndex });
|
|
97
|
+
setPickerFlow(nextFlow);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const finalSelection = activePackSelection
|
|
101
|
+
? { ...selection, packName: activePackSelection.packName, boosterPacks: [activePackSelection.slotIndex] }
|
|
102
|
+
: selection;
|
|
75
103
|
setAntesState((prev) => {
|
|
76
104
|
const next = { ...prev };
|
|
77
105
|
const nextAnte = { ...(next[activeSlot.ante] || {}) };
|
|
78
|
-
nextAnte[activeSlot.id] = { ...
|
|
106
|
+
nextAnte[activeSlot.id] = { ...finalSelection, zone: currentZone };
|
|
79
107
|
next[activeSlot.ante] = nextAnte;
|
|
80
108
|
onChangeRef.current?.(buildJamlText(next));
|
|
81
109
|
return next;
|
|
82
110
|
});
|
|
111
|
+
setActivePackSelection(null);
|
|
83
112
|
setActiveSlot(null);
|
|
84
|
-
}, [activeSlot, currentZone]);
|
|
113
|
+
}, [activePackSelection, activeSlot, currentZone]);
|
|
85
114
|
const handlePickerCancel = useCallback(() => {
|
|
86
|
-
if (activeSlot?.forceCategory) {
|
|
115
|
+
if (activeSlot?.forceCategory === "pack" && activePackSelection) {
|
|
116
|
+
setActivePackSelection(null);
|
|
117
|
+
setPickerFlow("pack");
|
|
118
|
+
}
|
|
119
|
+
else if (activeSlot?.forceCategory) {
|
|
87
120
|
setActiveSlot(null);
|
|
88
121
|
}
|
|
89
122
|
else if (pickerFlow !== "category") {
|
|
@@ -92,11 +125,16 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
92
125
|
else {
|
|
93
126
|
setActiveSlot(null);
|
|
94
127
|
}
|
|
95
|
-
}, [activeSlot, pickerFlow]);
|
|
128
|
+
}, [activePackSelection, activeSlot, pickerFlow]);
|
|
96
129
|
const handleOverlayClose = useCallback(() => {
|
|
130
|
+
setActivePackSelection(null);
|
|
97
131
|
setActiveSlot(null);
|
|
132
|
+
setActivePackDetail(null);
|
|
98
133
|
}, []);
|
|
99
134
|
const jamlText = useMemo(() => buildJamlText(antesState), [antesState]);
|
|
135
|
+
const activePackDetailSelection = activePackDetail
|
|
136
|
+
? (antesState[activePackDetail.ante] || {})[activePackDetail.id]
|
|
137
|
+
: undefined;
|
|
100
138
|
const handleScrollAttach = useCallback((node) => {
|
|
101
139
|
if (!node)
|
|
102
140
|
return;
|
|
@@ -126,7 +164,10 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
126
164
|
flexDirection: "column",
|
|
127
165
|
gap: 24,
|
|
128
166
|
borderBottom: `2px solid ${C.DARK_GREY}`
|
|
129
|
-
}, children: [_jsxs(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 8 }, children: ["Ante ", a] }), _jsxs("div", { className: "j-flex j-justify-between j-items-end", children: [_jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Voucher" }), renderSlot(a, `ante_${a}_voucher`, 42, "Vouchers", "voucher")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Small" }), renderSlot(a, `ante_${a}_tag_small`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Big" }), renderSlot(a, `ante_${a}_tag_big`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Boss" }), renderSlot(a, `ante_${a}_boss`, 42, "BlindChips", "boss")] })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Shop Items" }), _jsx("div", { className: "j-flex hide-scrollbar j-gap-sm", style: { overflowX: "auto", paddingBottom: 8 }, children: [1, 2, 3, 4, 5, 6, 7, 8].map(i => renderSlot(a, `ante_${a}_shop_${i}`, 52, "Jokers")) })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Packs" }), _jsx("div", { className: "j-flex j-gap-sm",
|
|
167
|
+
}, children: [_jsxs(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 8 }, children: ["Ante ", a] }), _jsxs("div", { className: "j-flex j-justify-between j-items-end", children: [_jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Voucher" }), renderSlot(a, `ante_${a}_voucher`, 42, "Vouchers", "voucher")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Small" }), renderSlot(a, `ante_${a}_tag_small`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Big" }), renderSlot(a, `ante_${a}_tag_big`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Boss" }), renderSlot(a, `ante_${a}_boss`, 42, "BlindChips", "boss")] })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Shop Items" }), _jsx("div", { className: "j-flex hide-scrollbar j-gap-sm", style: { overflowX: "auto", paddingBottom: 8 }, children: [1, 2, 3, 4, 5, 6, 7, 8].map(i => renderSlot(a, `ante_${a}_shop_${i}`, 52, "Jokers")) })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Packs" }), _jsx("div", { className: "j-flex-col j-gap-sm", children: getPackRows(a).map((row, rowIndex) => (_jsx("div", { className: "j-flex j-gap-sm", style: { flexWrap: "nowrap" }, children: row.map(i => renderSlot(a, `ante_${a}_pack_${i}`, 64, "Boosters", "pack")) }, rowIndex))) })] })] }, a))) }), _jsx(JimboModal, { open: activeSlot !== null, onClose: handlePickerCancel, title: pickerFlow === "category" ? "Select Category" : undefined, className: "j-picker-modal", children: activeSlot !== null && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "8px 10px", marginBottom: 2 }, children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginBottom: 6 }, children: "This pick will be added as" }), _jsx("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: ["must", "should", "mustnot"].map((z) => (_jsx(JimboButton, { tone: currentZone === z ? ZONE_TONE[z] : "grey", size: "xs", onClick: () => setCurrentZone(z), children: ZONE_LABEL[z] }, z))) })] }), pickerFlow === "category" ? (_jsx(CategoryMenu, { onSelect: handleCategorySelect })) : pickerFlow === "joker" ? (_jsx(JokerPicker, { onSelect: handleItemSelect, onCancel: handlePickerCancel })) : pickerFlow === "packUnsupported" ? (_jsxs("div", { className: "j-flex-col j-gap-sm", style: { padding: 10, maxWidth: 360 }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "10px 12px" }, children: [_jsx(JimboText, { size: "sm", tone: "orange", children: "Standard Packs need a dedicated playing-card picker." }), _jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginTop: 6 }, children: "Arcana, Celestial, Spectral, and Buffoon pack flows are wired. Standard Pack support can come next without faking the JAML shape." })] }), _jsx(JimboButton, { tone: "orange", size: "sm", fullWidth: true, onClick: () => {
|
|
168
|
+
setActivePackSelection(null);
|
|
169
|
+
setPickerFlow("pack");
|
|
170
|
+
}, children: "Back to Packs" })] })) : (_jsx(CategoryPicker, { config: CATEGORY_CONFIG_MAP[pickerFlow], onSelect: handleItemSelect, onCancel: handlePickerCancel }))] })) }), _jsx(JimboModal, { open: activePackDetail !== null && !!activePackDetailSelection?.packName, onClose: handleOverlayClose, title: activePackDetailSelection?.packName ?? "Pack", className: "j-picker-modal", children: activePackDetail !== null && activePackDetailSelection?.packName && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10, maxWidth: 360 }, children: [_jsx("div", { className: "j-inner-panel", style: { padding: "10px 12px", background: withAlpha(C.DARKEST, 0.84) }, children: _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [_jsx(JimboSprite, { name: activePackDetailSelection.packName, sheet: "Boosters", width: 56 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [_jsxs(JimboText, { size: "sm", tone: "white", children: ["Ante ", activePackDetail.ante, " pack ", ((activePackDetailSelection.boosterPacks?.[0] ?? 0) + 1)] }), _jsx(JimboText, { size: "xs", tone: "grey", children: getPackHelperText(activePackDetailSelection.packName) })] })] }) }), _jsxs("div", { className: "j-inner-panel", style: { padding: "10px 12px" }, children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginBottom: 8 }, children: "Peek" }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }, children: [_jsx(JimboSprite, { name: activePackDetailSelection.packName, sheet: "Boosters", width: 44 }), _jsx(JimboText, { size: "sm", tone: "grey", children: "\u2192" }), _jsx(JimboSprite, { name: activePackDetailSelection.value, sheet: categoryToPreviewSheet(activePackDetailSelection.category), width: 48 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 3 }, children: [_jsx(JimboText, { size: "sm", tone: "white", children: activePackDetailSelection.value }), _jsxs(JimboText, { size: "xs", tone: "grey", children: [getSelectionCategoryLabel(activePackDetailSelection.category), " from ", activePackDetailSelection.packName] })] })] }), _jsxs("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 96 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Zone" }), _jsx(JimboText, { size: "xs", tone: getZoneTextTone(activePackDetailSelection.zone), children: ZONE_LABEL[activePackDetailSelection.zone] })] }), _jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 120 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Clause" }), _jsx(JimboText, { size: "xs", tone: "white", children: activePackDetailSelection.clauseKey })] }), _jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 96 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Source" }), _jsxs(JimboText, { size: "xs", tone: "white", children: ["boosterPacks: [", activePackDetailSelection.boosterPacks?.join(", ") ?? "", "]"] })] })] })] }), _jsxs("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: [_jsx(JimboButton, { tone: "blue", size: "sm", fullWidth: true, onClick: handlePackChange, children: "Re-pick Contents" }), _jsx(JimboButton, { tone: "red", size: "sm", fullWidth: true, onClick: () => handleSlotClear(activePackDetail.ante, activePackDetail.id), children: "Clear This Pack" })] })] })) })] }));
|
|
130
171
|
}
|
|
131
172
|
// ─── Category Selection Menu ─────────────────────────────────────────────────
|
|
132
173
|
export function CategoryMenu({ onSelect, }) {
|
|
@@ -140,6 +181,79 @@ export function CategoryMenu({ onSelect, }) {
|
|
|
140
181
|
}, children: CATEGORIES.map((cat) => (_jsx(JimboButton, { tone: cat.tone, size: "sm", fullWidth: true, onClick: () => onSelect(cat.key), children: _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, width: "100%", textAlign: "left" }, children: [_jsx(JimboSprite, { name: cat.sprite, sheet: cat.sheet, width: 24 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 1 }, children: [_jsx("span", { style: { fontSize: 11 }, children: cat.label }), _jsx("span", { style: { fontSize: 8, opacity: 0.7, letterSpacing: "0.04em", lineHeight: 1, whiteSpace: "normal" }, children: cat.hint })] })] }) }, cat.key))) }));
|
|
141
182
|
}
|
|
142
183
|
// ─── Build JAML text from slots ──────────────────────────────────────────────
|
|
184
|
+
function getPackSlotIndex(slotId) {
|
|
185
|
+
const match = /_pack_(\d+)$/.exec(slotId);
|
|
186
|
+
if (!match)
|
|
187
|
+
return null;
|
|
188
|
+
return Number(match[1]) - 1;
|
|
189
|
+
}
|
|
190
|
+
function getPackRows(ante) {
|
|
191
|
+
const packCount = ante < 2 ? 4 : 6;
|
|
192
|
+
const rows = [];
|
|
193
|
+
for (let i = 1; i <= packCount; i += 2) {
|
|
194
|
+
rows.push([i, i + 1].filter((slot) => slot <= packCount));
|
|
195
|
+
}
|
|
196
|
+
return rows;
|
|
197
|
+
}
|
|
198
|
+
function getPackFollowupFlow(packName) {
|
|
199
|
+
if (packName.includes("Buffoon"))
|
|
200
|
+
return "joker";
|
|
201
|
+
if (packName.includes("Arcana"))
|
|
202
|
+
return "tarot";
|
|
203
|
+
if (packName.includes("Celestial"))
|
|
204
|
+
return "planet";
|
|
205
|
+
if (packName.includes("Spectral"))
|
|
206
|
+
return "spectral";
|
|
207
|
+
return "packUnsupported";
|
|
208
|
+
}
|
|
209
|
+
function categoryToPreviewSheet(category) {
|
|
210
|
+
if (category === "joker")
|
|
211
|
+
return "Jokers";
|
|
212
|
+
if (category === "voucher")
|
|
213
|
+
return "Vouchers";
|
|
214
|
+
if (category === "tag")
|
|
215
|
+
return "tags";
|
|
216
|
+
if (category === "boss")
|
|
217
|
+
return "BlindChips";
|
|
218
|
+
if (category === "pack")
|
|
219
|
+
return "Boosters";
|
|
220
|
+
return "Tarots";
|
|
221
|
+
}
|
|
222
|
+
function getPackHelperText(packName) {
|
|
223
|
+
if (packName.includes("Buffoon"))
|
|
224
|
+
return "Peek the joker this pack is meant to carry.";
|
|
225
|
+
if (packName.includes("Arcana"))
|
|
226
|
+
return "Peek the tarot card currently attached to this pack.";
|
|
227
|
+
if (packName.includes("Celestial"))
|
|
228
|
+
return "Peek the planet card currently attached to this pack.";
|
|
229
|
+
if (packName.includes("Spectral"))
|
|
230
|
+
return "Peek the spectral card currently attached to this pack.";
|
|
231
|
+
return "Peek the item currently attached to this pack.";
|
|
232
|
+
}
|
|
233
|
+
function getSelectionCategoryLabel(category) {
|
|
234
|
+
if (category === "joker")
|
|
235
|
+
return "Joker";
|
|
236
|
+
if (category === "voucher")
|
|
237
|
+
return "Voucher";
|
|
238
|
+
if (category === "tag")
|
|
239
|
+
return "Tag";
|
|
240
|
+
if (category === "boss")
|
|
241
|
+
return "Boss Blind";
|
|
242
|
+
if (category === "tarot")
|
|
243
|
+
return "Tarot Card";
|
|
244
|
+
if (category === "planet")
|
|
245
|
+
return "Planet Card";
|
|
246
|
+
if (category === "spectral")
|
|
247
|
+
return "Spectral Card";
|
|
248
|
+
return "Pack";
|
|
249
|
+
}
|
|
250
|
+
function getZoneTextTone(zone) {
|
|
251
|
+
if (zone === "must")
|
|
252
|
+
return "blue";
|
|
253
|
+
if (zone === "should")
|
|
254
|
+
return "green";
|
|
255
|
+
return "red";
|
|
256
|
+
}
|
|
143
257
|
function buildJamlText(antes) {
|
|
144
258
|
const byZone = {
|
|
145
259
|
must: {}, should: {}, mustnot: {}
|
|
@@ -152,13 +266,14 @@ function buildJamlText(antes) {
|
|
|
152
266
|
if (!byZone[zone][key]) {
|
|
153
267
|
byZone[zone][key] = [];
|
|
154
268
|
}
|
|
155
|
-
const existing = byZone[zone][key].find(item => item.value === sel.value
|
|
269
|
+
const existing = byZone[zone][key].find(item => item.value === sel.value &&
|
|
270
|
+
(item.boosterPacks ?? []).join(",") === (sel.boosterPacks ?? []).join(","));
|
|
156
271
|
if (existing) {
|
|
157
272
|
if (!existing.antes.includes(anteNum))
|
|
158
273
|
existing.antes.push(anteNum);
|
|
159
274
|
}
|
|
160
275
|
else {
|
|
161
|
-
byZone[zone][key].push({ value: sel.value, antes: [anteNum] });
|
|
276
|
+
byZone[zone][key].push({ value: sel.value, antes: [anteNum], boosterPacks: sel.boosterPacks });
|
|
162
277
|
}
|
|
163
278
|
}
|
|
164
279
|
}
|
|
@@ -180,6 +295,10 @@ function buildJamlText(antes) {
|
|
|
180
295
|
if (item.antes.length < 8) {
|
|
181
296
|
lines.push(` antes: [${item.antes.sort((a, b) => a - b).join(", ")}]`);
|
|
182
297
|
}
|
|
298
|
+
if (item.boosterPacks && item.boosterPacks.length > 0) {
|
|
299
|
+
lines.push(` sources:`);
|
|
300
|
+
lines.push(` boosterPacks: [${item.boosterPacks.join(", ")}]`);
|
|
301
|
+
}
|
|
183
302
|
}
|
|
184
303
|
}
|
|
185
304
|
}
|
|
@@ -10,6 +10,10 @@ export interface SlotSelection {
|
|
|
10
10
|
value: string;
|
|
11
11
|
/** JAML clause key (e.g. "commonJoker", "legendaryJoker", "voucher"). */
|
|
12
12
|
clauseKey: string;
|
|
13
|
+
/** Optional selected pack display name for pack slots. */
|
|
14
|
+
packName?: string;
|
|
15
|
+
/** Optional source pack indices for pack-derived item clauses. */
|
|
16
|
+
boosterPacks?: number[];
|
|
13
17
|
/** Optional rarity for jokers. */
|
|
14
18
|
rarity?: "common" | "uncommon" | "rare" | "legendary";
|
|
15
19
|
}
|
|
@@ -35,9 +35,12 @@ export function MysterySlot({ zone, sheetType, selection, width = 56, onTap, onC
|
|
|
35
35
|
const borderColor = ZONE_BORDER[zone];
|
|
36
36
|
const isEmpty = !selection;
|
|
37
37
|
const cardH = Math.round((width * 95) / 71);
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
38
|
+
const spriteName = selection?.packName ?? selection?.value ?? "";
|
|
39
|
+
const spriteSheet = selection?.packName
|
|
40
|
+
? "Boosters"
|
|
41
|
+
: selection
|
|
42
|
+
? categoryToSheet(selection.category) ?? sheetType
|
|
43
|
+
: sheetType;
|
|
41
44
|
const scale = pressed
|
|
42
45
|
? 0.95
|
|
43
46
|
: hover
|
|
@@ -1 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
declare let MotelyWasm: any;
|
|
2
|
+
declare let MotelyWasmEvents: any;
|
|
3
|
+
declare let activeSearch: {
|
|
4
|
+
cancel(): void;
|
|
5
|
+
} | null;
|
|
6
|
+
declare let activeSearchRunId: number;
|
|
7
|
+
type WorkerMessage = {
|
|
8
|
+
type: "init";
|
|
9
|
+
url: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "start";
|
|
12
|
+
jaml: string;
|
|
13
|
+
mode?: string;
|
|
14
|
+
count?: number;
|
|
15
|
+
aesthetic?: number;
|
|
16
|
+
seeds?: string[];
|
|
17
|
+
keywords?: string;
|
|
18
|
+
padding?: string;
|
|
19
|
+
batchCharCount?: number;
|
|
20
|
+
startBatch?: string;
|
|
21
|
+
endBatch?: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: "stop";
|
|
24
|
+
} | {
|
|
25
|
+
type: "get_tally_labels";
|
|
26
|
+
jaml: string;
|
|
27
|
+
};
|
|
28
|
+
declare function post(message: Record<string, unknown>): void;
|
|
29
|
+
declare function resetEventHandlers(): void;
|
|
@@ -1,68 +1,117 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
let MotelyWasm = null;
|
|
3
|
+
let MotelyWasmEvents = null;
|
|
4
4
|
let activeSearch = null;
|
|
5
|
-
|
|
5
|
+
let activeSearchRunId = 0;
|
|
6
|
+
function post(message) {
|
|
7
|
+
self.postMessage(message);
|
|
8
|
+
}
|
|
9
|
+
function resetEventHandlers() {
|
|
10
|
+
if (!MotelyWasmEvents)
|
|
11
|
+
return;
|
|
12
|
+
MotelyWasmEvents.notifyResult = () => { };
|
|
13
|
+
MotelyWasmEvents.notifyProgress = () => { };
|
|
14
|
+
MotelyWasmEvents.notifyComplete = () => { };
|
|
15
|
+
}
|
|
16
|
+
self.addEventListener("message", async (e) => {
|
|
6
17
|
const msg = e.data;
|
|
7
|
-
if (msg.type ===
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
if (msg.type === "init") {
|
|
19
|
+
try {
|
|
20
|
+
const mod = await import(/* @vite-ignore */ msg.url);
|
|
21
|
+
await mod.default.boot();
|
|
22
|
+
const motely = mod.Motely;
|
|
23
|
+
MotelyWasm = motely.MotelyWasm;
|
|
24
|
+
MotelyWasmEvents = motely.MotelyWasmEvents;
|
|
25
|
+
post({ type: "ready" });
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
post({ type: "error", message: String(err) });
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (msg.type === "start") {
|
|
33
|
+
if (!MotelyWasm) {
|
|
34
|
+
post({ type: "error", message: "Not initialized" });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const validation = MotelyWasm.validateJaml(msg.jaml);
|
|
38
|
+
if (validation !== "valid") {
|
|
39
|
+
post({ type: "error", message: validation });
|
|
11
40
|
return;
|
|
12
41
|
}
|
|
42
|
+
const runId = ++activeSearchRunId;
|
|
13
43
|
function cleanup() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
44
|
+
resetEventHandlers();
|
|
45
|
+
if (runId === activeSearchRunId) {
|
|
46
|
+
activeSearch = null;
|
|
47
|
+
}
|
|
18
48
|
}
|
|
19
|
-
|
|
20
|
-
|
|
49
|
+
MotelyWasmEvents.notifyResult = (seed, score, tallyColumns) => {
|
|
50
|
+
if (runId !== activeSearchRunId)
|
|
51
|
+
return;
|
|
52
|
+
post({ type: "result", seed, score, tallyColumns: Array.from(tallyColumns) });
|
|
21
53
|
};
|
|
22
|
-
|
|
23
|
-
|
|
54
|
+
MotelyWasmEvents.notifyProgress = (searched, matching) => {
|
|
55
|
+
if (runId !== activeSearchRunId)
|
|
56
|
+
return;
|
|
57
|
+
post({ type: "progress", searched: searched.toString(), matching: matching.toString() });
|
|
24
58
|
};
|
|
25
|
-
|
|
59
|
+
MotelyWasmEvents.notifyComplete = (status, searched, matched) => {
|
|
60
|
+
if (runId !== activeSearchRunId)
|
|
61
|
+
return;
|
|
26
62
|
cleanup();
|
|
27
|
-
|
|
63
|
+
post({ type: "complete", status, searched: searched.toString(), matched: matched.toString() });
|
|
28
64
|
};
|
|
29
65
|
try {
|
|
30
|
-
const mode = msg.mode ||
|
|
31
|
-
if (mode ===
|
|
32
|
-
activeSearch =
|
|
66
|
+
const mode = msg.mode || "random";
|
|
67
|
+
if (mode === "random") {
|
|
68
|
+
activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
|
|
33
69
|
}
|
|
34
|
-
else if (mode ===
|
|
35
|
-
activeSearch =
|
|
70
|
+
else if (mode === "aesthetic") {
|
|
71
|
+
activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);
|
|
36
72
|
}
|
|
37
|
-
else if (mode ===
|
|
38
|
-
activeSearch =
|
|
73
|
+
else if (mode === "seedList") {
|
|
74
|
+
activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);
|
|
39
75
|
}
|
|
40
|
-
else if (mode ===
|
|
41
|
-
activeSearch =
|
|
76
|
+
else if (mode === "keyword") {
|
|
77
|
+
activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || "");
|
|
42
78
|
}
|
|
43
|
-
else if (mode ===
|
|
44
|
-
activeSearch =
|
|
79
|
+
else if (mode === "sequential") {
|
|
80
|
+
activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch || "0"), BigInt(msg.endBatch || "0"));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
post({ type: "error", message: `Unknown search mode: ${mode}` });
|
|
84
|
+
cleanup();
|
|
85
|
+
return;
|
|
45
86
|
}
|
|
46
87
|
}
|
|
47
88
|
catch (err) {
|
|
48
89
|
cleanup();
|
|
49
|
-
|
|
90
|
+
post({ type: "error", message: String(err) });
|
|
50
91
|
}
|
|
92
|
+
return;
|
|
51
93
|
}
|
|
52
|
-
|
|
94
|
+
if (msg.type === "stop") {
|
|
95
|
+
activeSearchRunId++;
|
|
96
|
+
resetEventHandlers();
|
|
53
97
|
if (activeSearch) {
|
|
54
98
|
activeSearch.cancel();
|
|
55
99
|
activeSearch = null;
|
|
56
|
-
self.postMessage({ type: 'cancelled' });
|
|
57
100
|
}
|
|
101
|
+
post({ type: "cancelled" });
|
|
102
|
+
return;
|
|
58
103
|
}
|
|
59
|
-
|
|
104
|
+
if (msg.type === "get_tally_labels") {
|
|
105
|
+
if (!MotelyWasm) {
|
|
106
|
+
post({ type: "error", message: "Not initialized" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
60
109
|
try {
|
|
61
|
-
const labels =
|
|
62
|
-
|
|
110
|
+
const labels = MotelyWasm.getTallyLabels(msg.jaml);
|
|
111
|
+
post({ type: "tally_labels", labels: Array.from(labels) });
|
|
63
112
|
}
|
|
64
113
|
catch (err) {
|
|
65
|
-
|
|
114
|
+
post({ type: "error", message: String(err) });
|
|
66
115
|
}
|
|
67
116
|
}
|
|
68
117
|
});
|
package/dist/hooks/useSearch.js
CHANGED
|
@@ -9,106 +9,8 @@ const INITIAL_STATE = {
|
|
|
9
9
|
seedsPerSecond: 0,
|
|
10
10
|
tallyLabels: [],
|
|
11
11
|
};
|
|
12
|
-
const SEARCH_WORKER_CODE = `
|
|
13
|
-
let MotelyWasm = null;
|
|
14
|
-
let MotelyWasmEvents = null;
|
|
15
|
-
let activeSearch = null;
|
|
16
|
-
let activeSearchRunId = 0;
|
|
17
|
-
|
|
18
|
-
self.addEventListener('message', async function(e) {
|
|
19
|
-
const msg = e.data;
|
|
20
|
-
|
|
21
|
-
if (msg.type === 'init') {
|
|
22
|
-
try {
|
|
23
|
-
const mod = await import(msg.url);
|
|
24
|
-
await mod.default.boot();
|
|
25
|
-
const motely = mod.Motely;
|
|
26
|
-
MotelyWasm = motely.MotelyWasm;
|
|
27
|
-
MotelyWasmEvents = motely.MotelyWasmEvents;
|
|
28
|
-
self.postMessage({ type: 'ready' });
|
|
29
|
-
} catch (err) {
|
|
30
|
-
self.postMessage({ type: 'error', message: String(err) });
|
|
31
|
-
}
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (msg.type === 'start') {
|
|
36
|
-
if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
|
|
37
|
-
const validation = MotelyWasm.validateJaml(msg.jaml);
|
|
38
|
-
if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }
|
|
39
|
-
const runId = ++activeSearchRunId;
|
|
40
|
-
|
|
41
|
-
function cleanup() {
|
|
42
|
-
MotelyWasmEvents.notifyResult = () => {};
|
|
43
|
-
MotelyWasmEvents.notifyProgress = () => {};
|
|
44
|
-
MotelyWasmEvents.notifyComplete = () => {};
|
|
45
|
-
if (runId === activeSearchRunId) {
|
|
46
|
-
activeSearch = null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
MotelyWasmEvents.notifyResult = function(seed, score, tallyColumns) {
|
|
51
|
-
if (runId !== activeSearchRunId) return;
|
|
52
|
-
self.postMessage({ type: 'result', seed, score, tallyColumns: Array.from(tallyColumns) });
|
|
53
|
-
};
|
|
54
|
-
MotelyWasmEvents.notifyProgress = function(searched, matching) {
|
|
55
|
-
if (runId !== activeSearchRunId) return;
|
|
56
|
-
self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });
|
|
57
|
-
};
|
|
58
|
-
MotelyWasmEvents.notifyComplete = function(status, searched, matched) {
|
|
59
|
-
if (runId !== activeSearchRunId) return;
|
|
60
|
-
cleanup();
|
|
61
|
-
self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const mode = msg.mode || 'random';
|
|
66
|
-
|
|
67
|
-
if (mode === 'random') {
|
|
68
|
-
activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
|
|
69
|
-
} else if (mode === 'aesthetic') {
|
|
70
|
-
activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);
|
|
71
|
-
} else if (mode === 'seedList') {
|
|
72
|
-
activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);
|
|
73
|
-
} else if (mode === 'keyword') {
|
|
74
|
-
activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || '');
|
|
75
|
-
} else if (mode === 'sequential') {
|
|
76
|
-
activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch), BigInt(msg.endBatch));
|
|
77
|
-
} else {
|
|
78
|
-
self.postMessage({ type: 'error', message: 'Unknown search mode: ' + mode });
|
|
79
|
-
cleanup();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
} catch (err) {
|
|
83
|
-
cleanup();
|
|
84
|
-
self.postMessage({ type: 'error', message: String(err) });
|
|
85
|
-
}
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (msg.type === 'stop') {
|
|
90
|
-
activeSearchRunId++;
|
|
91
|
-
MotelyWasmEvents.notifyResult = () => {};
|
|
92
|
-
MotelyWasmEvents.notifyProgress = () => {};
|
|
93
|
-
MotelyWasmEvents.notifyComplete = () => {};
|
|
94
|
-
if (activeSearch) { activeSearch.cancel(); activeSearch = null; }
|
|
95
|
-
self.postMessage({ type: 'cancelled' });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (msg.type === 'get_tally_labels') {
|
|
99
|
-
if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
|
|
100
|
-
try {
|
|
101
|
-
const labels = MotelyWasm.getTallyLabels(msg.jaml);
|
|
102
|
-
self.postMessage({ type: 'tally_labels', labels: Array.from(labels) });
|
|
103
|
-
} catch (err) {
|
|
104
|
-
self.postMessage({ type: 'error', message: String(err) });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
`;
|
|
109
12
|
function createWorker() {
|
|
110
|
-
|
|
111
|
-
return new Worker(URL.createObjectURL(blob), { type: "module" });
|
|
13
|
+
return new Worker(new URL("./searchWorker.js", import.meta.url), { type: "module" });
|
|
112
14
|
}
|
|
113
15
|
export function useSearch(motelyWasmUrl) {
|
|
114
16
|
const [state, setState] = useState(INITIAL_STATE);
|
package/dist/ui/jimbo.css
CHANGED
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
|
|
57
57
|
/* Typography */
|
|
58
58
|
--j-font: 'm6x11plus', 'Courier New', monospace;
|
|
59
|
+
/* Coding font — used by JamlCodeEditor and .j-code-block. JetBrains Mono
|
|
60
|
+
with a long OS-native fallback chain so the UI still reads as code even
|
|
61
|
+
when the Google Fonts stylesheet fails to load. */
|
|
62
|
+
--j-font-code: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', 'SF Mono', SFMono-Regular, Menlo, Consolas, ui-monospace, 'Courier New', monospace;
|
|
59
63
|
--j-text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
|
|
60
64
|
|
|
61
65
|
/* Spacing */
|
|
@@ -756,7 +760,7 @@
|
|
|
756
760
|
.j-code-block__pre {
|
|
757
761
|
padding: 12px;
|
|
758
762
|
overflow-x: auto;
|
|
759
|
-
font-family:
|
|
763
|
+
font-family: var(--j-font-code);
|
|
760
764
|
font-size: 0.875rem;
|
|
761
765
|
line-height: 1.6;
|
|
762
766
|
color: #f6f0d5;
|
|
@@ -915,11 +919,18 @@
|
|
|
915
919
|
max-height: 90vh;
|
|
916
920
|
display: flex;
|
|
917
921
|
flex-direction: column;
|
|
922
|
+
overflow: hidden;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.j-modal .j-panel__body {
|
|
926
|
+
overflow-y: auto;
|
|
927
|
+
min-height: 0;
|
|
918
928
|
}
|
|
919
929
|
|
|
920
930
|
.j-modal__title {
|
|
921
931
|
text-align: center;
|
|
922
932
|
margin: 0 0 var(--j-space-xl);
|
|
933
|
+
flex-shrink: 0;
|
|
923
934
|
}
|
|
924
935
|
|
|
925
936
|
|
package/dist/ui/showcase.js
CHANGED
|
@@ -4,6 +4,7 @@ import { JimboButton } from './panel.js';
|
|
|
4
4
|
import { JimboSprite } from './sprites.js';
|
|
5
5
|
import { JimboText } from './jimboText.js';
|
|
6
6
|
import { JimboApp, JimboAppFooter } from './jimboApp.js';
|
|
7
|
+
import { JimboBalatroFooter } from './footer.js';
|
|
7
8
|
import { JimboSectionHeader } from './jimboSectionHeader.js';
|
|
8
9
|
import { JimboInfoCard, JimboInfoCardBody, JimboInfoCardTitle, JimboInfoCardSub, JimboInfoCardAside } from './jimboInfoCard.js';
|
|
9
10
|
/**
|
|
@@ -15,5 +16,5 @@ export function Showcase({ title = 'Balatro', subtitle = 'Seed Curator', hotFilt
|
|
|
15
16
|
padding: '3px 8px',
|
|
16
17
|
background: 'var(--j-dark-grey)', borderRadius: 4,
|
|
17
18
|
border: '1px solid var(--j-panel-edge)',
|
|
18
|
-
}, children: [_jsx(JimboText, { size: "micro", tone: "purple", children: mcpInfo.engine }), _jsx(JimboText, { size: "micro", tone: "grey", children: mcpInfo.features })] })), hotFilters.length > 0 && (_jsxs(_Fragment, { children: [_jsx(JimboSectionHeader, { label: "Filters", tone: "blue" }), _jsx("div", { className: "j-flex-col", style: { gap: 4 }, children: hotFilters.slice(0, 4).map((f, i) => (_jsxs(JimboInfoCard, { tone: f.tone, onClick: () => onFilterClick?.(f, i), style: { cursor: onFilterClick ? 'pointer' : undefined }, children: [_jsx("div", { className: "j-flex j-gap-xs", children: f.sample.slice(0, 2).map((name, j) => (_jsx("div", { style: { width: 22, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx(JimboSprite, { name: name, width: 20 }) }, j))) }), _jsxs(JimboInfoCardBody, { children: [_jsx(JimboInfoCardTitle, { children: f.name }), _jsxs(JimboInfoCardSub, { children: ["by ", f.author] })] }), _jsx(JimboInfoCardAside, { children: _jsx(JimboText, { size: "xs", tone: f.tone === 'gold' ? 'gold' : f.tone, children: f.hits }) })] }, i))) })] })), recentFinds.length > 0 && (_jsxs(_Fragment, { children: [_jsx(JimboSectionHeader, { label: "Recent", tone: "green" }), _jsx("div", { style: { lineHeight: 1.5 }, children: recentFinds.slice(0, 3).map((r, i) => (_jsxs("div", { className: "j-flex j-gap-sm", children: [_jsx(JimboText, { size: "micro", tone: "gold", children: r.seed }), _jsx(JimboText, { size: "micro", tone: "grey", children: r.filterName }), r.score > 0 && _jsxs(JimboText, { size: "micro", tone: "green", children: ["+", r.score] })] }, i))) })] }))] }), _jsxs(JimboAppFooter, { children: [_jsx(JimboButton, { tone: "green", fullWidth: true, size: "lg", onClick: onNewSearch, children: "New Search" }), _jsx(JimboButton, { tone: "blue", fullWidth: true, size: "lg", onClick: onBrowseFilters, children: "Browse Filters" })] })] }));
|
|
19
|
+
}, children: [_jsx(JimboText, { size: "micro", tone: "purple", children: mcpInfo.engine }), _jsx(JimboText, { size: "micro", tone: "grey", children: mcpInfo.features })] })), hotFilters.length > 0 && (_jsxs(_Fragment, { children: [_jsx(JimboSectionHeader, { label: "Filters", tone: "blue" }), _jsx("div", { className: "j-flex-col", style: { gap: 4 }, children: hotFilters.slice(0, 4).map((f, i) => (_jsxs(JimboInfoCard, { tone: f.tone, onClick: () => onFilterClick?.(f, i), style: { cursor: onFilterClick ? 'pointer' : undefined }, children: [_jsx("div", { className: "j-flex j-gap-xs", children: f.sample.slice(0, 2).map((name, j) => (_jsx("div", { style: { width: 22, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx(JimboSprite, { name: name, width: 20 }) }, j))) }), _jsxs(JimboInfoCardBody, { children: [_jsx(JimboInfoCardTitle, { children: f.name }), _jsxs(JimboInfoCardSub, { children: ["by ", f.author] })] }), _jsx(JimboInfoCardAside, { children: _jsx(JimboText, { size: "xs", tone: f.tone === 'gold' ? 'gold' : f.tone, children: f.hits }) })] }, i))) })] })), recentFinds.length > 0 && (_jsxs(_Fragment, { children: [_jsx(JimboSectionHeader, { label: "Recent", tone: "green" }), _jsx("div", { style: { lineHeight: 1.5 }, children: recentFinds.slice(0, 3).map((r, i) => (_jsxs("div", { className: "j-flex j-gap-sm", children: [_jsx(JimboText, { size: "micro", tone: "gold", children: r.seed }), _jsx(JimboText, { size: "micro", tone: "grey", children: r.filterName }), r.score > 0 && _jsxs(JimboText, { size: "micro", tone: "green", children: ["+", r.score] })] }, i))) })] }))] }), _jsxs(JimboAppFooter, { children: [_jsx(JimboButton, { tone: "green", fullWidth: true, size: "lg", onClick: onNewSearch, children: "New Search" }), _jsx(JimboButton, { tone: "blue", fullWidth: true, size: "lg", onClick: onBrowseFilters, children: "Browse Filters" }), _jsx(JimboBalatroFooter, {})] })] }));
|
|
19
20
|
}
|
|
@@ -78,6 +78,8 @@ export function jamlTextToVisualFilter(text) {
|
|
|
78
78
|
const clause = { id: uid(), type: current.type, value: current.value };
|
|
79
79
|
if (current.antes && current.antes.length > 0)
|
|
80
80
|
clause.antes = current.antes;
|
|
81
|
+
if (current.boosterPacks && current.boosterPacks.length > 0)
|
|
82
|
+
clause.boosterPacks = current.boosterPacks;
|
|
81
83
|
if (current.score !== undefined)
|
|
82
84
|
clause.score = current.score;
|
|
83
85
|
if (current.edition)
|
|
@@ -132,6 +134,12 @@ export function jamlTextToVisualFilter(text) {
|
|
|
132
134
|
.filter((n) => !isNaN(n));
|
|
133
135
|
current.antes = nums;
|
|
134
136
|
}
|
|
137
|
+
else if (key === "boosterPacks") {
|
|
138
|
+
const nums = parseInlineList(val)
|
|
139
|
+
.map(Number)
|
|
140
|
+
.filter((n) => !isNaN(n));
|
|
141
|
+
current.boosterPacks = nums;
|
|
142
|
+
}
|
|
135
143
|
else if (key === "score") {
|
|
136
144
|
const n = Number(val);
|
|
137
145
|
if (!isNaN(n))
|
|
@@ -140,6 +148,9 @@ export function jamlTextToVisualFilter(text) {
|
|
|
140
148
|
else if (key === "edition") {
|
|
141
149
|
current.edition = parseScalarValue(val) ?? undefined;
|
|
142
150
|
}
|
|
151
|
+
else if (key === "sources") {
|
|
152
|
+
// handled by subsequent nested lines like `boosterPacks: [0, 1]`
|
|
153
|
+
}
|
|
143
154
|
}
|
|
144
155
|
}
|
|
145
156
|
}
|
|
@@ -157,6 +168,10 @@ function serializeClause(clause) {
|
|
|
157
168
|
if (clause.antes && clause.antes.length > 0) {
|
|
158
169
|
out += ` antes: [${clause.antes.join(", ")}]\n`;
|
|
159
170
|
}
|
|
171
|
+
if (clause.boosterPacks && clause.boosterPacks.length > 0) {
|
|
172
|
+
out += ` sources:\n`;
|
|
173
|
+
out += ` boosterPacks: [${clause.boosterPacks.join(", ")}]\n`;
|
|
174
|
+
}
|
|
160
175
|
if (clause.score !== undefined) {
|
|
161
176
|
out += ` score: ${clause.score}\n`;
|
|
162
177
|
}
|
package/fonts.css
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Coding font: JetBrains Mono via Google Fonts (woff2, variable axis).
|
|
3
|
+
* Falls through to Cascadia Code / Fira Code / ui-monospace when offline.
|
|
4
|
+
* Using @import instead of @font-face keeps this stylesheet bundler-safe —
|
|
5
|
+
* no absolute URLs to self-host, no extra asset copy step for consumers.
|
|
6
|
+
* Must be the first rule in the file per CSS @import spec.
|
|
7
|
+
*/
|
|
8
|
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,500;0,700;1,400&display=swap');
|
|
9
|
+
|
|
1
10
|
@font-face {
|
|
2
11
|
font-family: 'm6x11plus';
|
|
3
12
|
src: local('m6x11plus'), local('m6x11plusplus'), url('./assets/fonts/m6x11plusplus.otf') format('opentype');
|
package/jaml.schema.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://www.seedfinder.app/jaml.schema.json",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "15.1.2",
|
|
5
5
|
"title": "JAML — Jimbo's Ante Markup Language",
|
|
6
6
|
"description": "JSON Schema for JAML (.jaml), Motely's Balatro seed search language. Use it for validation, completions, and editor tooling.",
|
|
7
7
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaml-ui",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.14",
|
|
4
4
|
"description": "Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"@react-spring/three": ">=9.0.0",
|
|
97
97
|
"@react-three/drei": ">=9.0.0",
|
|
98
98
|
"@react-three/fiber": ">=8.0.0",
|
|
99
|
-
"motely-wasm": "^15.1.
|
|
99
|
+
"motely-wasm": "^15.1.2",
|
|
100
100
|
"react": "^18.2.0 || ^19.0.0",
|
|
101
101
|
"react-dom": "^18.2.0 || ^19.0.0",
|
|
102
102
|
"react-icons": ">=5.0.0",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"@vitejs/plugin-react": "^5.0.4",
|
|
141
141
|
"@vitest/browser-playwright": "^4.1.5",
|
|
142
142
|
"@vitest/coverage-v8": "^4.1.5",
|
|
143
|
-
"motely-wasm": "^15.1.
|
|
143
|
+
"motely-wasm": "^15.1.2",
|
|
144
144
|
"playwright": "^1.59.1",
|
|
145
145
|
"react": "^19.2.4",
|
|
146
146
|
"react-dom": "^19.2.4",
|
|
@@ -159,11 +159,11 @@
|
|
|
159
159
|
"@codemirror/view": "^6.41.1",
|
|
160
160
|
"@json-render/core": "^0.18.0",
|
|
161
161
|
"@lezer/highlight": "^1.2.3",
|
|
162
|
+
"@types/js-yaml": "^4.0.9",
|
|
162
163
|
"clsx": "^2.1.1",
|
|
163
164
|
"js-yaml": "^4.1.1",
|
|
164
165
|
"lucide-react": "^1.14.0",
|
|
165
166
|
"tailwind-merge": "^2.6.1",
|
|
166
|
-
"zustand": "^5.0.0"
|
|
167
|
-
"@types/js-yaml": "^4.0.9"
|
|
167
|
+
"zustand": "^5.0.0"
|
|
168
168
|
}
|
|
169
169
|
}
|