jaml-ui 0.17.1 → 0.17.3
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/jamlMap/CategoryPicker.d.ts +32 -0
- package/dist/components/jamlMap/CategoryPicker.js +224 -0
- package/dist/components/jamlMap/JamlMapEditorDemo.d.ts +1 -1
- package/dist/components/jamlMap/JamlMapEditorDemo.js +174 -21
- package/dist/components/jamlMap/index.d.ts +1 -0
- package/dist/components/jamlMap/index.js +1 -0
- package/dist/hooks/useAnalyzer.d.ts +1 -1
- package/dist/hooks/useAnalyzer.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ui/jimbo.css +1040 -0
- package/package.json +3 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SpriteEntry } from "../../sprites/spriteData.js";
|
|
2
|
+
import type { SpriteSheetType } from "../../sprites/spriteMapper.js";
|
|
3
|
+
import type { SlotSelection, SlotCategory } from "./MysterySlot.js";
|
|
4
|
+
export interface CategoryPickerConfig {
|
|
5
|
+
/** Display title, e.g. "Vouchers" */
|
|
6
|
+
title: string;
|
|
7
|
+
/** The SlotCategory value */
|
|
8
|
+
category: SlotCategory;
|
|
9
|
+
/** JAML clause key emitted on select, e.g. "voucher" */
|
|
10
|
+
clauseKey: string;
|
|
11
|
+
/** Which sprite sheet to render from */
|
|
12
|
+
sheet: SpriteSheetType;
|
|
13
|
+
/** Full list of items for the grid */
|
|
14
|
+
items: SpriteEntry[];
|
|
15
|
+
/** Accent color for the header/buttons */
|
|
16
|
+
accent: string;
|
|
17
|
+
/** Optional tooltip hint shown in the "Any" button area */
|
|
18
|
+
hint?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CategoryPickerProps {
|
|
21
|
+
config: CategoryPickerConfig;
|
|
22
|
+
onSelect: (selection: SlotSelection) => void;
|
|
23
|
+
onCancel: () => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function CategoryPicker({ config, onSelect, onCancel }: CategoryPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare const VOUCHER_PICKER_CONFIG: CategoryPickerConfig;
|
|
27
|
+
export declare const TAG_PICKER_CONFIG: CategoryPickerConfig;
|
|
28
|
+
export declare const BOSS_PICKER_CONFIG: CategoryPickerConfig;
|
|
29
|
+
export declare const TAROT_PICKER_CONFIG: CategoryPickerConfig;
|
|
30
|
+
export declare const PLANET_PICKER_CONFIG: CategoryPickerConfig;
|
|
31
|
+
export declare const SPECTRAL_PICKER_CONFIG: CategoryPickerConfig;
|
|
32
|
+
export declare const PACK_PICKER_CONFIG: CategoryPickerConfig;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback, useMemo } from "react";
|
|
4
|
+
import { JimboSprite } from "../../ui/sprites.js";
|
|
5
|
+
import { JimboColorOption, withAlpha } from "../../ui/tokens.js";
|
|
6
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
7
|
+
export function CategoryPicker({ config, onSelect, onCancel }) {
|
|
8
|
+
const [search, setSearch] = useState("");
|
|
9
|
+
const filtered = useMemo(() => {
|
|
10
|
+
if (!search)
|
|
11
|
+
return config.items;
|
|
12
|
+
const q = search.toLowerCase();
|
|
13
|
+
return config.items.filter((item) => item.name.toLowerCase().includes(q));
|
|
14
|
+
}, [config.items, search]);
|
|
15
|
+
const handleSelect = useCallback((item) => {
|
|
16
|
+
onSelect({
|
|
17
|
+
category: config.category,
|
|
18
|
+
value: item.name,
|
|
19
|
+
clauseKey: config.clauseKey,
|
|
20
|
+
});
|
|
21
|
+
}, [onSelect, config]);
|
|
22
|
+
const handleAny = useCallback(() => {
|
|
23
|
+
onSelect({
|
|
24
|
+
category: config.category,
|
|
25
|
+
value: "Any",
|
|
26
|
+
clauseKey: config.clauseKey,
|
|
27
|
+
});
|
|
28
|
+
}, [onSelect, config]);
|
|
29
|
+
const C = JimboColorOption;
|
|
30
|
+
return (_jsxs("div", { style: styles.container, children: [_jsxs("div", { style: { ...styles.header, borderBottomColor: withAlpha(config.accent, 0.3) }, children: [_jsx("button", { onClick: onCancel, style: styles.backBtn, children: "\u2715" }), _jsx("span", { style: { ...styles.title, color: config.accent }, children: config.title }), _jsx("div", { style: { width: 44 } })] }), _jsxs("div", { style: styles.searchRow, children: [_jsx("input", { type: "text", placeholder: `Search ${config.title.toLowerCase()}...`, value: search, onChange: (e) => setSearch(e.target.value), style: styles.searchInput }), _jsx("button", { onClick: handleAny, style: styles.anyBtn, children: "Any" })] }), config.hint && (_jsxs("div", { style: styles.hint, children: [_jsx("span", { children: "\uD83D\uDCA1" }), _jsx("span", { children: config.hint })] })), _jsxs("div", { style: styles.grid, children: [filtered.map((item) => (_jsxs("div", { onClick: () => handleSelect(item), title: item.name, style: styles.cell, children: [_jsx(JimboSprite, { name: item.name, sheet: config.sheet, width: 48 }), _jsx("span", { style: styles.label, children: item.name })] }, item.name))), filtered.length === 0 && (_jsxs("div", { style: styles.emptyState, children: ["No matches for \"", search, "\""] }))] })] }));
|
|
31
|
+
}
|
|
32
|
+
// ─── Pre-built configs ───────────────────────────────────────────────────────
|
|
33
|
+
import { VOUCHERS, TAGS, BOSSES, BOOSTER_PACKS, TAROTS_AND_PLANETS } from "../../sprites/spriteData.js";
|
|
34
|
+
// Split consumables by type
|
|
35
|
+
const TAROT_CARDS = TAROTS_AND_PLANETS.filter((c) => {
|
|
36
|
+
const y = c.pos.y;
|
|
37
|
+
return y <= 2 && c.name !== "The Soul" && c.name !== "Black Hole";
|
|
38
|
+
}).filter((c) => {
|
|
39
|
+
// Tarots are y=0..1 + Judgement (y=2,x=0) + The World (y=2,x=1)
|
|
40
|
+
return c.pos.y <= 1 || (c.pos.y === 2 && c.pos.x <= 1);
|
|
41
|
+
});
|
|
42
|
+
const PLANET_CARDS = TAROTS_AND_PLANETS.filter((c) => {
|
|
43
|
+
return c.pos.y === 3 || // Main planets row
|
|
44
|
+
c.name === "Planet X" || c.name === "Ceres" || c.name === "Eris" ||
|
|
45
|
+
c.name === "Black Hole";
|
|
46
|
+
});
|
|
47
|
+
const SPECTRAL_CARDS = TAROTS_AND_PLANETS.filter((c) => {
|
|
48
|
+
return c.pos.y >= 4 || c.name === "The Soul";
|
|
49
|
+
});
|
|
50
|
+
const C = JimboColorOption;
|
|
51
|
+
export const VOUCHER_PICKER_CONFIG = {
|
|
52
|
+
title: "Vouchers",
|
|
53
|
+
category: "voucher",
|
|
54
|
+
clauseKey: "voucher",
|
|
55
|
+
sheet: "Vouchers",
|
|
56
|
+
items: VOUCHERS,
|
|
57
|
+
accent: C.GOLD,
|
|
58
|
+
};
|
|
59
|
+
export const TAG_PICKER_CONFIG = {
|
|
60
|
+
title: "Tags",
|
|
61
|
+
category: "tag",
|
|
62
|
+
clauseKey: "tag",
|
|
63
|
+
sheet: "tags",
|
|
64
|
+
items: TAGS,
|
|
65
|
+
accent: C.GREEN,
|
|
66
|
+
};
|
|
67
|
+
export const BOSS_PICKER_CONFIG = {
|
|
68
|
+
title: "Boss Blinds",
|
|
69
|
+
category: "boss",
|
|
70
|
+
clauseKey: "boss",
|
|
71
|
+
sheet: "BlindChips",
|
|
72
|
+
items: BOSSES,
|
|
73
|
+
accent: C.RED,
|
|
74
|
+
hint: "Boss Blinds appear at the end of each Ante.",
|
|
75
|
+
};
|
|
76
|
+
export const TAROT_PICKER_CONFIG = {
|
|
77
|
+
title: "Tarot Cards",
|
|
78
|
+
category: "tarot",
|
|
79
|
+
clauseKey: "tarotCard",
|
|
80
|
+
sheet: "Tarots",
|
|
81
|
+
items: TAROT_CARDS,
|
|
82
|
+
accent: C.PURPLE,
|
|
83
|
+
hint: "Found in Arcana Packs and shops.",
|
|
84
|
+
};
|
|
85
|
+
export const PLANET_PICKER_CONFIG = {
|
|
86
|
+
title: "Planet Cards",
|
|
87
|
+
category: "planet",
|
|
88
|
+
clauseKey: "planetCard",
|
|
89
|
+
sheet: "Tarots",
|
|
90
|
+
items: PLANET_CARDS,
|
|
91
|
+
accent: C.BLUE,
|
|
92
|
+
hint: "Found in Celestial Packs and shops.",
|
|
93
|
+
};
|
|
94
|
+
export const SPECTRAL_PICKER_CONFIG = {
|
|
95
|
+
title: "Spectral Cards",
|
|
96
|
+
category: "spectral",
|
|
97
|
+
clauseKey: "spectralCard",
|
|
98
|
+
sheet: "Tarots",
|
|
99
|
+
items: SPECTRAL_CARDS,
|
|
100
|
+
accent: C.TEAL_GREY,
|
|
101
|
+
hint: "Found in Spectral Packs. Ghost Deck only for shop spawns!",
|
|
102
|
+
};
|
|
103
|
+
export const PACK_PICKER_CONFIG = {
|
|
104
|
+
title: "Booster Packs",
|
|
105
|
+
category: "pack",
|
|
106
|
+
clauseKey: "pack",
|
|
107
|
+
sheet: "Boosters",
|
|
108
|
+
items: BOOSTER_PACKS,
|
|
109
|
+
accent: C.ORANGE,
|
|
110
|
+
};
|
|
111
|
+
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
112
|
+
const styles = {
|
|
113
|
+
container: {
|
|
114
|
+
background: JimboColorOption.DARKEST,
|
|
115
|
+
border: `2px solid ${JimboColorOption.TEAL_GREY}`,
|
|
116
|
+
borderRadius: 8,
|
|
117
|
+
padding: 0,
|
|
118
|
+
maxWidth: 420,
|
|
119
|
+
maxHeight: "80vh",
|
|
120
|
+
overflow: "hidden",
|
|
121
|
+
display: "flex",
|
|
122
|
+
flexDirection: "column",
|
|
123
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
124
|
+
boxShadow: `0 8px 32px ${withAlpha(JimboColorOption.BLACK, 0.6)}`,
|
|
125
|
+
},
|
|
126
|
+
header: {
|
|
127
|
+
display: "flex",
|
|
128
|
+
alignItems: "center",
|
|
129
|
+
justifyContent: "space-between",
|
|
130
|
+
padding: "10px 12px",
|
|
131
|
+
borderBottom: `1px solid ${JimboColorOption.TEAL_GREY}`,
|
|
132
|
+
background: withAlpha(JimboColorOption.DARK_GREY, 0.5),
|
|
133
|
+
},
|
|
134
|
+
backBtn: {
|
|
135
|
+
background: "none",
|
|
136
|
+
border: "none",
|
|
137
|
+
color: JimboColorOption.GREY,
|
|
138
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
139
|
+
fontSize: 14,
|
|
140
|
+
cursor: "pointer",
|
|
141
|
+
padding: "4px 8px",
|
|
142
|
+
},
|
|
143
|
+
title: {
|
|
144
|
+
fontSize: 16,
|
|
145
|
+
fontWeight: "bold",
|
|
146
|
+
letterSpacing: 0.5,
|
|
147
|
+
},
|
|
148
|
+
searchRow: {
|
|
149
|
+
display: "flex",
|
|
150
|
+
gap: 8,
|
|
151
|
+
padding: "10px 12px 6px",
|
|
152
|
+
},
|
|
153
|
+
searchInput: {
|
|
154
|
+
flex: 1,
|
|
155
|
+
padding: "6px 10px",
|
|
156
|
+
borderRadius: 4,
|
|
157
|
+
border: `1px solid ${JimboColorOption.TEAL_GREY}`,
|
|
158
|
+
background: withAlpha(JimboColorOption.DARK_GREY, 0.8),
|
|
159
|
+
color: JimboColorOption.WHITE,
|
|
160
|
+
fontSize: 13,
|
|
161
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
162
|
+
outline: "none",
|
|
163
|
+
},
|
|
164
|
+
anyBtn: {
|
|
165
|
+
padding: "6px 14px",
|
|
166
|
+
borderRadius: 4,
|
|
167
|
+
border: `1px solid ${JimboColorOption.GOLD}`,
|
|
168
|
+
background: withAlpha(JimboColorOption.GOLD, 0.15),
|
|
169
|
+
color: JimboColorOption.GOLD,
|
|
170
|
+
fontSize: 13,
|
|
171
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
172
|
+
cursor: "pointer",
|
|
173
|
+
fontWeight: "bold",
|
|
174
|
+
},
|
|
175
|
+
hint: {
|
|
176
|
+
display: "flex",
|
|
177
|
+
alignItems: "flex-start",
|
|
178
|
+
gap: 6,
|
|
179
|
+
margin: "4px 12px 8px",
|
|
180
|
+
padding: "8px 10px",
|
|
181
|
+
borderRadius: 4,
|
|
182
|
+
background: withAlpha(JimboColorOption.TEAL_GREY, 0.12),
|
|
183
|
+
border: `1px solid ${withAlpha(JimboColorOption.TEAL_GREY, 0.3)}`,
|
|
184
|
+
color: JimboColorOption.GREY,
|
|
185
|
+
fontSize: 11,
|
|
186
|
+
lineHeight: "1.4",
|
|
187
|
+
},
|
|
188
|
+
grid: {
|
|
189
|
+
display: "grid",
|
|
190
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(64px, 1fr))",
|
|
191
|
+
gap: 6,
|
|
192
|
+
padding: "8px 12px 12px",
|
|
193
|
+
overflowY: "auto",
|
|
194
|
+
flex: 1,
|
|
195
|
+
},
|
|
196
|
+
cell: {
|
|
197
|
+
display: "flex",
|
|
198
|
+
flexDirection: "column",
|
|
199
|
+
alignItems: "center",
|
|
200
|
+
gap: 3,
|
|
201
|
+
padding: 4,
|
|
202
|
+
borderRadius: 4,
|
|
203
|
+
cursor: "pointer",
|
|
204
|
+
transition: "background 120ms",
|
|
205
|
+
background: "transparent",
|
|
206
|
+
},
|
|
207
|
+
label: {
|
|
208
|
+
fontSize: 9,
|
|
209
|
+
color: JimboColorOption.GREY,
|
|
210
|
+
textAlign: "center",
|
|
211
|
+
lineHeight: "1.2",
|
|
212
|
+
maxWidth: 60,
|
|
213
|
+
overflow: "hidden",
|
|
214
|
+
textOverflow: "ellipsis",
|
|
215
|
+
whiteSpace: "nowrap",
|
|
216
|
+
},
|
|
217
|
+
emptyState: {
|
|
218
|
+
gridColumn: "1 / -1",
|
|
219
|
+
textAlign: "center",
|
|
220
|
+
color: JimboColorOption.GREY,
|
|
221
|
+
fontSize: 13,
|
|
222
|
+
padding: 20,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
@@ -2,7 +2,7 @@ import { type SlotSelection, type JamlZone } from "./MysterySlot.js";
|
|
|
2
2
|
export interface JamlMapEditorDemoProps {
|
|
3
3
|
/** Initial zone for the demo. */
|
|
4
4
|
zone?: JamlZone;
|
|
5
|
-
/** Callback when
|
|
5
|
+
/** Callback when selections change. */
|
|
6
6
|
onChange?: (slots: (SlotSelection | null)[]) => void;
|
|
7
7
|
}
|
|
8
8
|
export declare function JamlMapEditorDemo({ zone: initialZone, onChange, }: JamlMapEditorDemoProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback } from "react";
|
|
3
|
+
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { MysterySlot } from "./MysterySlot.js";
|
|
5
5
|
import { JokerPicker } from "./JokerPicker.js";
|
|
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";
|
|
6
7
|
import { JimboColorOption, withAlpha } from "../../ui/tokens.js";
|
|
7
|
-
// ───
|
|
8
|
+
// ─── Category menu items ─────────────────────────────────────────────────────
|
|
8
9
|
const C = JimboColorOption;
|
|
9
|
-
const
|
|
10
|
+
const CATEGORIES = [
|
|
11
|
+
{ key: "joker", label: "Joker", icon: "🃏", color: C.BLUE, hint: "Shop, Buffoon Pack" },
|
|
12
|
+
{ key: "voucher", label: "Voucher", icon: "🎫", color: C.GOLD, hint: "1 per Ante in shop" },
|
|
13
|
+
{ key: "tarot", label: "Tarot Card", icon: "🔮", color: C.PURPLE, hint: "Arcana Pack, shop" },
|
|
14
|
+
{ key: "planet", label: "Planet Card", icon: "🪐", color: C.BLUE, hint: "Celestial Pack, shop" },
|
|
15
|
+
{ key: "spectral", label: "Spectral Card", icon: "👻", color: C.TEAL_GREY, hint: "Ghost Deck, Spectral Pack" },
|
|
16
|
+
{ key: "tag", label: "Tag", icon: "🏷️", color: C.GREEN, hint: "Skip blind reward" },
|
|
17
|
+
{ key: "boss", label: "Boss Blind", icon: "👁️", color: C.RED, hint: "End of each Ante" },
|
|
18
|
+
{ key: "pack", label: "Booster Pack", icon: "📦", color: C.ORANGE, hint: "Arcana, Celestial, etc." },
|
|
19
|
+
];
|
|
20
|
+
// ─── Component ───────────────────────────────────────────────────────────────
|
|
21
|
+
const INITIAL_SLOTS = 8;
|
|
10
22
|
export function JamlMapEditorDemo({ zone: initialZone = "must", onChange, }) {
|
|
11
23
|
const [zone, setZone] = useState(initialZone);
|
|
12
24
|
const [slots, setSlots] = useState(Array(INITIAL_SLOTS).fill(null));
|
|
13
25
|
const [activeSlot, setActiveSlot] = useState(null);
|
|
26
|
+
const [pickerFlow, setPickerFlow] = useState("category");
|
|
14
27
|
const handleSlotTap = useCallback((index) => {
|
|
15
28
|
setActiveSlot(index);
|
|
29
|
+
setPickerFlow("category");
|
|
16
30
|
}, []);
|
|
17
31
|
const handleSlotClear = useCallback((index) => {
|
|
18
32
|
setSlots((prev) => {
|
|
@@ -22,7 +36,15 @@ export function JamlMapEditorDemo({ zone: initialZone = "must", onChange, }) {
|
|
|
22
36
|
return next;
|
|
23
37
|
});
|
|
24
38
|
}, [onChange]);
|
|
25
|
-
const
|
|
39
|
+
const handleCategorySelect = useCallback((cat) => {
|
|
40
|
+
if (cat === "joker") {
|
|
41
|
+
setPickerFlow("joker");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
setPickerFlow(cat);
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
const handleItemSelect = useCallback((selection) => {
|
|
26
48
|
if (activeSlot === null)
|
|
27
49
|
return;
|
|
28
50
|
setSlots((prev) => {
|
|
@@ -34,23 +56,64 @@ export function JamlMapEditorDemo({ zone: initialZone = "must", onChange, }) {
|
|
|
34
56
|
setActiveSlot(null);
|
|
35
57
|
}, [activeSlot, onChange]);
|
|
36
58
|
const handlePickerCancel = useCallback(() => {
|
|
59
|
+
if (pickerFlow !== "category") {
|
|
60
|
+
// Go back to category selection
|
|
61
|
+
setPickerFlow("category");
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
setActiveSlot(null);
|
|
65
|
+
}
|
|
66
|
+
}, [pickerFlow]);
|
|
67
|
+
const handleOverlayClose = useCallback(() => {
|
|
37
68
|
setActiveSlot(null);
|
|
38
69
|
}, []);
|
|
39
70
|
const filledCount = slots.filter(Boolean).length;
|
|
71
|
+
// Build the JSON tree from slots
|
|
72
|
+
const jsonTree = useMemo(() => buildJsonTree(zone, slots), [zone, slots]);
|
|
40
73
|
return (_jsxs("div", { style: styles.wrapper, children: [_jsx("div", { style: styles.zoneBar, children: ["must", "should", "mustnot"].map((z) => (_jsx("button", { onClick: () => setZone(z), style: {
|
|
41
74
|
...styles.zoneBtn,
|
|
42
75
|
borderColor: zone === z ? ZONE_COLORS[z] : C.TEAL_GREY,
|
|
43
76
|
color: zone === z ? ZONE_COLORS[z] : C.GREY,
|
|
44
77
|
background: zone === z ? withAlpha(ZONE_COLORS[z], 0.1) : "transparent",
|
|
45
|
-
}, children: z.toUpperCase() }, z))) }), _jsxs("div", { style: styles.sectionLabel, children: [_jsx("span", { style: { color: ZONE_COLORS[zone] }, children: "Shop Items" }), _jsxs("span", { style: { color: C.GREY, fontSize: 11 }, children: [filledCount, "/", INITIAL_SLOTS, " defined"] })] }), _jsx("div", { style: styles.slotRow, children: slots.map((selection, i) => (_jsx(MysterySlot, { zone: zone, sheetType: "Jokers", selection: selection ?? undefined, width: 48, onTap: () => handleSlotTap(i), onClear: selection ? () => handleSlotClear(i) : undefined }, i))) }), _jsx("div", { style: styles.scrollHint, children: "\u25C0 swipe \u25B6" }), activeSlot !== null && (_jsxs("div", { style: styles.overlay, children: [_jsx("div", { style: styles.overlayBackdrop, onClick:
|
|
78
|
+
}, children: z.toUpperCase() }, z))) }), _jsxs("div", { style: styles.sectionLabel, children: [_jsx("span", { style: { color: ZONE_COLORS[zone] }, children: "Shop Items" }), _jsxs("span", { style: { color: C.GREY, fontSize: 11 }, children: [filledCount, "/", INITIAL_SLOTS, " defined"] })] }), _jsx("div", { style: styles.slotRow, children: slots.map((selection, i) => (_jsx(MysterySlot, { zone: zone, sheetType: "Jokers", selection: selection ?? undefined, width: 48, onTap: () => handleSlotTap(i), onClear: selection ? () => handleSlotClear(i) : undefined }, i))) }), _jsx("div", { style: styles.scrollHint, children: "\u25C0 swipe \u25B6" }), activeSlot !== null && (_jsxs("div", { style: styles.overlay, children: [_jsx("div", { style: styles.overlayBackdrop, onClick: handleOverlayClose }), _jsx("div", { style: styles.pickerWrapper, children: pickerFlow === "category" ? (_jsx(CategoryMenu, { onSelect: handleCategorySelect, onCancel: handleOverlayClose })) : pickerFlow === "joker" ? (_jsx(JokerPicker, { onSelect: handleItemSelect, onCancel: handlePickerCancel })) : (_jsx(CategoryPicker, { config: CATEGORY_CONFIG_MAP[pickerFlow], onSelect: handleItemSelect, onCancel: handlePickerCancel })) })] })), filledCount > 0 && (_jsxs("div", { style: styles.jsonPreview, children: [_jsxs("div", { style: styles.jsonHeader, children: [_jsx("span", { children: "Filter Preview" }), _jsx("span", { style: { fontSize: 10, opacity: 0.6 }, children: "JSON" })] }), _jsx("div", { style: styles.jsonBody, children: _jsx(JsonTreeNode, { data: jsonTree, indent: 0 }) })] }))] }));
|
|
79
|
+
}
|
|
80
|
+
// ─── Category Selection Menu ─────────────────────────────────────────────────
|
|
81
|
+
function CategoryMenu({ onSelect, onCancel, }) {
|
|
82
|
+
return (_jsxs("div", { style: styles.catMenuContainer, children: [_jsxs("div", { style: styles.catMenuHeader, children: [_jsx("button", { onClick: onCancel, style: styles.backBtn, children: "\u2715" }), _jsx("span", { style: styles.catMenuTitle, children: "Select Category" }), _jsx("div", { style: { width: 44 } })] }), _jsx("div", { style: styles.catGrid, children: CATEGORIES.map((cat) => (_jsxs("button", { onClick: () => onSelect(cat.key), style: {
|
|
83
|
+
...styles.catBtn,
|
|
84
|
+
borderColor: withAlpha(cat.color, 0.3),
|
|
85
|
+
}, children: [_jsx("span", { style: styles.catIcon, children: cat.icon }), _jsxs("div", { style: styles.catText, children: [_jsx("span", { style: { ...styles.catLabel, color: cat.color }, children: cat.label }), _jsx("span", { style: styles.catHint, children: cat.hint })] })] }, cat.key))) })] }));
|
|
46
86
|
}
|
|
47
|
-
// ───
|
|
48
|
-
function
|
|
87
|
+
// ─── JSON tree renderer ──────────────────────────────────────────────────────
|
|
88
|
+
function JsonTreeNode({ data, indent }) {
|
|
89
|
+
if (data === null || data === undefined) {
|
|
90
|
+
return _jsx("span", { style: { color: C.GREY }, children: "null" });
|
|
91
|
+
}
|
|
92
|
+
if (typeof data === "string") {
|
|
93
|
+
return _jsxs("span", { style: { color: C.GREEN_TEXT }, children: ["\"", data, "\""] });
|
|
94
|
+
}
|
|
95
|
+
if (typeof data === "number" || typeof data === "boolean") {
|
|
96
|
+
return _jsx("span", { style: { color: C.GOLD }, children: String(data) });
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(data)) {
|
|
99
|
+
if (data.length === 0)
|
|
100
|
+
return _jsx("span", { style: { color: C.GREY }, children: "[]" });
|
|
101
|
+
return (_jsxs("span", { children: [_jsx("span", { style: { color: C.GREY }, children: "[" }), data.map((item, i) => (_jsxs("div", { style: { paddingLeft: (indent + 1) * 14 }, children: [_jsx(JsonTreeNode, { data: item, indent: indent + 1 }), i < data.length - 1 && _jsx("span", { style: { color: C.GREY }, children: "," })] }, i))), _jsx("div", { style: { paddingLeft: indent * 14 }, children: _jsx("span", { style: { color: C.GREY }, children: "]" }) })] }));
|
|
102
|
+
}
|
|
103
|
+
if (typeof data === "object") {
|
|
104
|
+
const entries = Object.entries(data);
|
|
105
|
+
if (entries.length === 0)
|
|
106
|
+
return _jsx("span", { style: { color: C.GREY }, children: "{}" });
|
|
107
|
+
return (_jsxs("span", { children: [_jsx("span", { style: { color: C.GREY }, children: "{" }), entries.map(([key, val], i) => (_jsxs("div", { style: { paddingLeft: (indent + 1) * 14 }, children: [_jsx("span", { style: { color: C.BLUE }, children: key }), _jsx("span", { style: { color: C.GREY }, children: ": " }), _jsx(JsonTreeNode, { data: val, indent: indent + 1 }), i < entries.length - 1 && _jsx("span", { style: { color: C.GREY }, children: "," })] }, key))), _jsx("div", { style: { paddingLeft: indent * 14 }, children: _jsx("span", { style: { color: C.GREY }, children: "}" }) })] }));
|
|
108
|
+
}
|
|
109
|
+
return _jsx("span", { children: String(data) });
|
|
110
|
+
}
|
|
111
|
+
// ─── Build JSON tree from slots ──────────────────────────────────────────────
|
|
112
|
+
function buildJsonTree(zone, slots) {
|
|
49
113
|
const filled = slots.filter(Boolean);
|
|
50
114
|
if (filled.length === 0)
|
|
51
|
-
return
|
|
115
|
+
return {};
|
|
52
116
|
const jamlZone = zone === "mustnot" ? "mustNot" : zone;
|
|
53
|
-
const lines = [`${jamlZone}:`];
|
|
54
117
|
// Group by clauseKey
|
|
55
118
|
const groups = new Map();
|
|
56
119
|
for (const s of filled) {
|
|
@@ -58,22 +121,35 @@ function generateJamlSnippet(zone, slots) {
|
|
|
58
121
|
existing.push(s.value);
|
|
59
122
|
groups.set(s.clauseKey, existing);
|
|
60
123
|
}
|
|
124
|
+
const clauses = [];
|
|
61
125
|
for (const [key, values] of groups) {
|
|
62
126
|
if (values.length === 1) {
|
|
63
|
-
|
|
127
|
+
clauses.push({ [key]: values[0] });
|
|
64
128
|
}
|
|
65
129
|
else {
|
|
66
|
-
|
|
130
|
+
clauses.push({ [key]: values });
|
|
67
131
|
}
|
|
68
132
|
}
|
|
69
|
-
return
|
|
133
|
+
return { [jamlZone]: clauses };
|
|
70
134
|
}
|
|
71
|
-
// ───
|
|
135
|
+
// ─── Category → picker config mapping ────────────────────────────────────────
|
|
136
|
+
const CATEGORY_CONFIG_MAP = {
|
|
137
|
+
joker: VOUCHER_PICKER_CONFIG, // Not used — routed to JokerPicker
|
|
138
|
+
voucher: VOUCHER_PICKER_CONFIG,
|
|
139
|
+
tag: TAG_PICKER_CONFIG,
|
|
140
|
+
boss: BOSS_PICKER_CONFIG,
|
|
141
|
+
tarot: TAROT_PICKER_CONFIG,
|
|
142
|
+
planet: PLANET_PICKER_CONFIG,
|
|
143
|
+
spectral: SPECTRAL_PICKER_CONFIG,
|
|
144
|
+
pack: PACK_PICKER_CONFIG,
|
|
145
|
+
};
|
|
146
|
+
// ─── Zone colors ─────────────────────────────────────────────────────────────
|
|
72
147
|
const ZONE_COLORS = {
|
|
73
148
|
must: C.BLUE,
|
|
74
149
|
should: C.RED,
|
|
75
150
|
mustnot: C.ORANGE,
|
|
76
151
|
};
|
|
152
|
+
// ─── Styles ──────────────────────────────────────────────────────────────────
|
|
77
153
|
const styles = {
|
|
78
154
|
wrapper: {
|
|
79
155
|
position: "relative",
|
|
@@ -141,15 +217,90 @@ const styles = {
|
|
|
141
217
|
position: "relative",
|
|
142
218
|
zIndex: 1,
|
|
143
219
|
width: "90%",
|
|
144
|
-
maxWidth:
|
|
220
|
+
maxWidth: 420,
|
|
221
|
+
},
|
|
222
|
+
// Category menu
|
|
223
|
+
catMenuContainer: {
|
|
224
|
+
background: C.DARKEST,
|
|
225
|
+
border: `2px solid ${C.TEAL_GREY}`,
|
|
226
|
+
borderRadius: 8,
|
|
227
|
+
overflow: "hidden",
|
|
228
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
229
|
+
boxShadow: `0 8px 32px ${withAlpha(C.BLACK, 0.6)}`,
|
|
145
230
|
},
|
|
146
|
-
|
|
231
|
+
catMenuHeader: {
|
|
232
|
+
display: "flex",
|
|
233
|
+
alignItems: "center",
|
|
234
|
+
justifyContent: "space-between",
|
|
235
|
+
padding: "10px 12px",
|
|
236
|
+
borderBottom: `1px solid ${C.TEAL_GREY}`,
|
|
237
|
+
background: withAlpha(C.DARK_GREY, 0.5),
|
|
238
|
+
},
|
|
239
|
+
catMenuTitle: {
|
|
240
|
+
color: C.WHITE,
|
|
241
|
+
fontSize: 16,
|
|
242
|
+
fontWeight: "bold",
|
|
243
|
+
letterSpacing: 0.5,
|
|
244
|
+
},
|
|
245
|
+
backBtn: {
|
|
246
|
+
background: "none",
|
|
247
|
+
border: "none",
|
|
248
|
+
color: C.GREY,
|
|
249
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
250
|
+
fontSize: 14,
|
|
251
|
+
cursor: "pointer",
|
|
252
|
+
padding: "4px 8px",
|
|
253
|
+
},
|
|
254
|
+
catGrid: {
|
|
255
|
+
display: "grid",
|
|
256
|
+
gridTemplateColumns: "1fr 1fr",
|
|
257
|
+
gap: 6,
|
|
258
|
+
padding: 10,
|
|
259
|
+
maxHeight: "70vh",
|
|
260
|
+
overflowY: "auto",
|
|
261
|
+
},
|
|
262
|
+
catBtn: {
|
|
263
|
+
display: "flex",
|
|
264
|
+
alignItems: "center",
|
|
265
|
+
gap: 8,
|
|
266
|
+
padding: "10px 10px",
|
|
267
|
+
border: "1px solid",
|
|
268
|
+
borderRadius: 6,
|
|
269
|
+
cursor: "pointer",
|
|
270
|
+
background: withAlpha(C.DARK_GREY, 0.3),
|
|
271
|
+
textAlign: "left",
|
|
272
|
+
transition: "background 150ms, transform 100ms",
|
|
273
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
274
|
+
},
|
|
275
|
+
catIcon: {
|
|
276
|
+
fontSize: 20,
|
|
277
|
+
flexShrink: 0,
|
|
278
|
+
},
|
|
279
|
+
catText: {
|
|
280
|
+
display: "flex",
|
|
281
|
+
flexDirection: "column",
|
|
282
|
+
gap: 2,
|
|
283
|
+
minWidth: 0,
|
|
284
|
+
},
|
|
285
|
+
catLabel: {
|
|
286
|
+
fontSize: 13,
|
|
287
|
+
fontWeight: "bold",
|
|
288
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
289
|
+
},
|
|
290
|
+
catHint: {
|
|
291
|
+
fontSize: 9,
|
|
292
|
+
color: C.GREY,
|
|
293
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
294
|
+
lineHeight: "1.2",
|
|
295
|
+
},
|
|
296
|
+
// JSON preview
|
|
297
|
+
jsonPreview: {
|
|
147
298
|
marginTop: 12,
|
|
148
299
|
borderRadius: 4,
|
|
149
300
|
border: `1px solid ${C.TEAL_GREY}`,
|
|
150
301
|
overflow: "hidden",
|
|
151
302
|
},
|
|
152
|
-
|
|
303
|
+
jsonHeader: {
|
|
153
304
|
padding: "6px 10px",
|
|
154
305
|
fontSize: 11,
|
|
155
306
|
color: C.GREY,
|
|
@@ -157,14 +308,16 @@ const styles = {
|
|
|
157
308
|
borderBottom: `1px solid ${C.TEAL_GREY}`,
|
|
158
309
|
letterSpacing: 1,
|
|
159
310
|
textTransform: "uppercase",
|
|
311
|
+
display: "flex",
|
|
312
|
+
justifyContent: "space-between",
|
|
313
|
+
alignItems: "center",
|
|
160
314
|
},
|
|
161
|
-
|
|
162
|
-
margin: 0,
|
|
315
|
+
jsonBody: {
|
|
163
316
|
padding: "8px 10px",
|
|
164
317
|
fontSize: 12,
|
|
165
|
-
color: C.GREEN_TEXT,
|
|
166
318
|
background: withAlpha(C.DARKEST, 0.8),
|
|
167
|
-
lineHeight: "1.
|
|
168
|
-
|
|
319
|
+
lineHeight: "1.6",
|
|
320
|
+
fontFamily: "m6x11plus, ui-monospace, monospace",
|
|
321
|
+
overflowX: "auto",
|
|
169
322
|
},
|
|
170
323
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { MysterySlot, type MysterySlotProps, type SlotSelection, type SlotCategory, type JamlZone } from "./MysterySlot.js";
|
|
2
2
|
export { JokerPicker, type JokerPickerProps, type JokerRarity } from "./JokerPicker.js";
|
|
3
3
|
export { JamlMapEditorDemo, type JamlMapEditorDemoProps } from "./JamlMapEditorDemo.js";
|
|
4
|
+
export { CategoryPicker, type CategoryPickerConfig, type CategoryPickerProps, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./CategoryPicker.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { MysterySlot } from "./MysterySlot.js";
|
|
2
2
|
export { JokerPicker } from "./JokerPicker.js";
|
|
3
3
|
export { JamlMapEditorDemo } from "./JamlMapEditorDemo.js";
|
|
4
|
+
export { 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";
|
|
@@ -26,7 +26,7 @@ export interface AnalyzerLive {
|
|
|
26
26
|
deck: string;
|
|
27
27
|
stake: string;
|
|
28
28
|
}
|
|
29
|
-
export declare function useAnalyzer(motelyRuntime: MotelyRuntime): {
|
|
29
|
+
export declare function useAnalyzer(motelyRuntime: MotelyRuntime | null): {
|
|
30
30
|
antes: AnalyzerAnteView[];
|
|
31
31
|
status: AnalyzerStatus;
|
|
32
32
|
error: string | null;
|
|
@@ -17,6 +17,9 @@ export function useAnalyzer(motelyRuntime) {
|
|
|
17
17
|
setStatus("running");
|
|
18
18
|
setError(null);
|
|
19
19
|
try {
|
|
20
|
+
if (!motelyRuntime) {
|
|
21
|
+
throw new Error("motely-wasm runtime is still booting.");
|
|
22
|
+
}
|
|
20
23
|
const { MotelyWasm, Motely } = motelyRuntime;
|
|
21
24
|
const deckEnum = Motely.MotelyDeck[deck] ?? Motely.MotelyDeck.Red;
|
|
22
25
|
const stakeEnum = Motely.MotelyStake[stake] ?? Motely.MotelyStake.White;
|
package/dist/index.d.ts
CHANGED
|
@@ -24,4 +24,4 @@ export { useSearch, type SearchResult, type SearchStatus, type UseSearchState, }
|
|
|
24
24
|
export { useAnalyzer, type AnalyzerStatus, type AnalyzerLive, type MotelyRuntime, type MotelyJsRunState, } from "./hooks/useAnalyzer.js";
|
|
25
25
|
export { JamlAestheticSelector, type JamlAestheticSelectorProps, type JamlAestheticOption, } from "./components/JamlAestheticSelector.js";
|
|
26
26
|
export { JamlSeedInput, type JamlSeedInputProps, } from "./components/JamlSeedInput.js";
|
|
27
|
-
export { JamlMapEditorDemo, JokerPicker, MysterySlot, type JamlMapEditorDemoProps, type JokerPickerProps, type JokerRarity, type MysterySlotProps, type SlotCategory, type SlotSelection, } from "./components/jamlMap/index.js";
|
|
27
|
+
export { JamlMapEditorDemo, JokerPicker, MysterySlot, CategoryPicker, type JamlMapEditorDemoProps, type JokerPickerProps, type JokerRarity, type MysterySlotProps, type SlotCategory, type SlotSelection, type CategoryPickerConfig, type CategoryPickerProps, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./components/jamlMap/index.js";
|
package/dist/index.js
CHANGED
|
@@ -25,4 +25,4 @@ export { useSearch, } from "./hooks/useSearch.js";
|
|
|
25
25
|
export { useAnalyzer, } from "./hooks/useAnalyzer.js";
|
|
26
26
|
export { JamlAestheticSelector, } from "./components/JamlAestheticSelector.js";
|
|
27
27
|
export { JamlSeedInput, } from "./components/JamlSeedInput.js";
|
|
28
|
-
export { JamlMapEditorDemo, JokerPicker, MysterySlot, } from "./components/jamlMap/index.js";
|
|
28
|
+
export { JamlMapEditorDemo, JokerPicker, MysterySlot, CategoryPicker, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./components/jamlMap/index.js";
|