jaml-ui 0.24.10 → 0.24.13
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 +5 -1
- package/dist/components/JamlIde.js +5 -4
- package/dist/components/JamlIdeVisual.d.ts +1 -0
- package/dist/components/JamlIdeVisual.js +12 -2
- package/dist/components/jamlMap/JamlMapEditor.js +136 -12
- 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,
|
|
@@ -19,10 +19,14 @@ export interface JamlIdeProps {
|
|
|
19
19
|
className?: string;
|
|
20
20
|
style?: React.CSSProperties;
|
|
21
21
|
title?: string;
|
|
22
|
+
subtitle?: React.ReactNode;
|
|
23
|
+
compactHeader?: boolean;
|
|
22
24
|
actions?: React.ReactNode;
|
|
23
25
|
codePlaceholder?: string;
|
|
24
26
|
onSearch?: () => void;
|
|
25
27
|
isSearching?: boolean;
|
|
28
|
+
/** Hide the Balatro attribution footer. Default: false (always shown). */
|
|
29
|
+
hideFooter?: boolean;
|
|
26
30
|
/**
|
|
27
31
|
* Controlled visual filter. When provided alongside `onVisualFilterChange`, the Visual tab
|
|
28
32
|
* is fully controlled by the parent. When absent, the Visual tab auto-derives from the text.
|
|
@@ -32,4 +36,4 @@ export interface JamlIdeProps {
|
|
|
32
36
|
}
|
|
33
37
|
export type { JamlVisualFilter } from "./JamlIdeVisual.js";
|
|
34
38
|
export type { JamlVisualClause, JamlZone } from "./JamlIdeVisual.js";
|
|
35
|
-
export declare function JamlIde({ jaml, defaultJaml, onChange, defaultMode, searchResults, className, style, title, 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";
|
|
@@ -88,7 +89,7 @@ function ResultsView({ results, jaml }) {
|
|
|
88
89
|
})] })] })) : null] }, result.seed));
|
|
89
90
|
}) }));
|
|
90
91
|
}
|
|
91
|
-
export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", searchResults = [], className = "", style, title = "JAML IDE", actions, codePlaceholder = "Enter JAML...", onSearch, isSearching = false, visualFilter, onVisualFilterChange, }) {
|
|
92
|
+
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
93
|
const [mode, setMode] = useState(defaultMode);
|
|
93
94
|
const [internalText, setInternalText] = useState(jaml ?? defaultJaml ?? "");
|
|
94
95
|
const [lastJamlProp, setLastJamlProp] = useState(jaml);
|
|
@@ -184,9 +185,9 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
184
185
|
alignItems: "center",
|
|
185
186
|
justifyContent: "space-between",
|
|
186
187
|
flexWrap: "wrap",
|
|
187
|
-
gap: 12,
|
|
188
|
-
padding: "10px 14px",
|
|
188
|
+
gap: compactHeader ? 8 : 12,
|
|
189
|
+
padding: compactHeader ? "8px 10px" : "10px 14px",
|
|
189
190
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
190
191
|
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 }), _jsx("div", { style: { fontSize: 11, color: JimboColorOption.GREY }, children:
|
|
192
|
+
}, 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] }), actions ? _jsx("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: actions }) : null] }), _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
193
|
}
|
|
@@ -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;
|
|
@@ -110,18 +148,26 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
110
148
|
};
|
|
111
149
|
return (_jsxs("div", { style: { width: "100%", height: "100%", display: "flex", flexDirection: "column" }, children: [_jsx("div", { ref: handleScrollAttach, className: "hide-scrollbar", style: {
|
|
112
150
|
flex: 1,
|
|
113
|
-
overflowY: "
|
|
151
|
+
overflowY: "scroll",
|
|
152
|
+
overflowX: "hidden",
|
|
114
153
|
scrollSnapType: "y mandatory",
|
|
115
|
-
scrollBehavior: "smooth"
|
|
154
|
+
scrollBehavior: "smooth",
|
|
155
|
+
WebkitOverflowScrolling: "touch",
|
|
156
|
+
overscrollBehaviorY: "contain",
|
|
116
157
|
}, children: Array.from({ length: 40 }, (_, i) => i).map((a) => (_jsxs("div", { style: {
|
|
117
158
|
scrollSnapAlign: "start",
|
|
159
|
+
scrollSnapStop: "always",
|
|
118
160
|
padding: "24px 8px 64px 8px",
|
|
119
|
-
minHeight: "100%",
|
|
161
|
+
minHeight: "100%",
|
|
162
|
+
boxSizing: "border-box",
|
|
120
163
|
display: "flex",
|
|
121
164
|
flexDirection: "column",
|
|
122
165
|
gap: 24,
|
|
123
166
|
borderBottom: `2px solid ${C.DARK_GREY}`
|
|
124
|
-
}, 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" })] })] })) })] }));
|
|
125
171
|
}
|
|
126
172
|
// ─── Category Selection Menu ─────────────────────────────────────────────────
|
|
127
173
|
export function CategoryMenu({ onSelect, }) {
|
|
@@ -135,6 +181,79 @@ export function CategoryMenu({ onSelect, }) {
|
|
|
135
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))) }));
|
|
136
182
|
}
|
|
137
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
|
+
}
|
|
138
257
|
function buildJamlText(antes) {
|
|
139
258
|
const byZone = {
|
|
140
259
|
must: {}, should: {}, mustnot: {}
|
|
@@ -147,13 +266,14 @@ function buildJamlText(antes) {
|
|
|
147
266
|
if (!byZone[zone][key]) {
|
|
148
267
|
byZone[zone][key] = [];
|
|
149
268
|
}
|
|
150
|
-
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(","));
|
|
151
271
|
if (existing) {
|
|
152
272
|
if (!existing.antes.includes(anteNum))
|
|
153
273
|
existing.antes.push(anteNum);
|
|
154
274
|
}
|
|
155
275
|
else {
|
|
156
|
-
byZone[zone][key].push({ value: sel.value, antes: [anteNum] });
|
|
276
|
+
byZone[zone][key].push({ value: sel.value, antes: [anteNum], boosterPacks: sel.boosterPacks });
|
|
157
277
|
}
|
|
158
278
|
}
|
|
159
279
|
}
|
|
@@ -175,6 +295,10 @@ function buildJamlText(antes) {
|
|
|
175
295
|
if (item.antes.length < 8) {
|
|
176
296
|
lines.push(` antes: [${item.antes.sort((a, b) => a - b).join(", ")}]`);
|
|
177
297
|
}
|
|
298
|
+
if (item.boosterPacks && item.boosterPacks.length > 0) {
|
|
299
|
+
lines.push(` sources:`);
|
|
300
|
+
lines.push(` boosterPacks: [${item.boosterPacks.join(", ")}]`);
|
|
301
|
+
}
|
|
178
302
|
}
|
|
179
303
|
}
|
|
180
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.13",
|
|
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
|
}
|