jaml-ui 0.24.2 → 0.24.4
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/DESIGN.md +236 -236
- package/README.md +145 -147
- package/dist/components/DeckSprite.d.ts +1 -1
- package/dist/components/DeckSprite.js +1 -1
- package/dist/components/JamlCurator.d.ts +2 -1
- package/dist/components/JamlCurator.js +2 -2
- package/dist/components/JamlIde.js +39 -1
- package/dist/components/JamlMapPreview.js +9 -9
- package/dist/components/JamlSpeedometer.js +47 -2
- package/dist/components/jamlMap/JamlMapEditor.d.ts +4 -1
- package/dist/components/jamlMap/JamlMapEditor.js +16 -8
- package/dist/components/jamlMap/index.d.ts +1 -1
- package/dist/components/jamlMap/index.js +1 -1
- package/dist/hooks/useSearch.js +25 -4
- package/dist/lib/data/constants.js +11 -15
- package/dist/lib/jaml/jamlData.js +2 -4
- package/dist/ui/jimbo.css +1420 -1761
- package/dist/ui/jimboTabs.js +2 -2
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -1,147 +1,145 @@
|
|
|
1
|
-
# jaml-ui
|
|
2
|
-
|
|
3
|
-
React components, UI tokens, sprites, and utilities for Balatro/JAML apps.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install jaml-ui react react-dom
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Package exports
|
|
12
|
-
|
|
13
|
-
| Entry | Contents |
|
|
14
|
-
|-------|----------|
|
|
15
|
-
| `jaml-ui` | Game card components, JAML IDE, Analyzer Explorer, hooks |
|
|
16
|
-
| `jaml-ui/ui` | Jimbo design system — JimboPanel, JimboButton, JimboModal, tokens |
|
|
17
|
-
| `jaml-ui/core` | Pure asset helpers, sprite metadata, decode utilities (no React) |
|
|
18
|
-
| `jaml-ui/motely` | motely-wasm decode helpers (requires `motely-wasm` peer) |
|
|
19
|
-
| `jaml-ui/r3f` | 3D card component via React Three Fiber (requires r3f peers) |
|
|
20
|
-
|
|
21
|
-
## Quick start
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
import { JamlGameCard, AnalyzerExplorer, JamlIde } from "jaml-ui";
|
|
25
|
-
import { JimboPanel, JimboButton } from "jaml-ui/ui";
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Game card
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
import { JamlGameCard } from "jaml-ui";
|
|
32
|
-
|
|
33
|
-
<JamlGameCard
|
|
34
|
-
type="joker"
|
|
35
|
-
card={{ name: "Blueprint", edition: "Foil", isEternal: true, scale: 1.5 }}
|
|
36
|
-
/>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Jimbo UI (Balatro design system)
|
|
40
|
-
|
|
41
|
-
```tsx
|
|
42
|
-
import { JimboPanel, JimboButton, JimboModal } from "jaml-ui/ui";
|
|
43
|
-
import { JimboColorOption } from "jaml-ui/ui";
|
|
44
|
-
|
|
45
|
-
<JimboPanel sway onBack={() => setOpen(false)}>
|
|
46
|
-
<JimboButton variant="primary" onClick={handleSearch}>Search</JimboButton>
|
|
47
|
-
</JimboPanel>
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Available variants: `primary`, `secondary`, `danger`, `back`, `ghost`
|
|
51
|
-
|
|
52
|
-
### JAML IDE
|
|
53
|
-
|
|
54
|
-
```tsx
|
|
55
|
-
import { JamlIde } from "jaml-ui";
|
|
56
|
-
|
|
57
|
-
<JamlIde
|
|
58
|
-
jaml={jaml}
|
|
59
|
-
onChange={setJaml}
|
|
60
|
-
searchResults={results}
|
|
61
|
-
onSearch={handleSearch}
|
|
62
|
-
isSearching={isSearching}
|
|
63
|
-
/>
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Analyzer Explorer
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
import { AnalyzerExplorer } from "jaml-ui";
|
|
70
|
-
|
|
71
|
-
// antes: AnalyzerAnteView[] — stream from motely-wasm createSearchContext
|
|
72
|
-
<AnalyzerExplorer antes={antes} totalAntes={8} highlights={highlights} />
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### JAML Map Preview
|
|
76
|
-
|
|
77
|
-
```tsx
|
|
78
|
-
import { JamlMapPreview } from "jaml-ui";
|
|
79
|
-
|
|
80
|
-
<JamlMapPreview jaml={jaml} />
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Asset handling
|
|
84
|
-
|
|
85
|
-
By default sprites resolve from the package `assets/` directory via `import.meta.url`.
|
|
86
|
-
|
|
87
|
-
Override at app startup:
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
import { setJamlAssetBaseUrl, clearJamlAssetBaseUrl } from "jaml-ui";
|
|
91
|
-
|
|
92
|
-
setJamlAssetBaseUrl("/vendor/jaml-ui/");
|
|
93
|
-
clearJamlAssetBaseUrl(); // back to default
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Core utilities
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
import { SPRITE_SHEETS, getSpriteData, resolveJamlAssetUrl } from "jaml-ui/core";
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Motely decode helpers
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
import { decodeMotelyItemName, motelyItemTypeName } from "jaml-ui/motely";
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## 3D card (optional)
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
npm install three @react-three/fiber @react-three/drei @react-spring/three
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
```tsx
|
|
115
|
-
import { Card3D } from "jaml-ui/r3f";
|
|
116
|
-
|
|
117
|
-
<Card3D itemName="Blueprint" />
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Next.js
|
|
121
|
-
|
|
122
|
-
Import pure helpers from `jaml-ui/core` for server components. For local workspace installs add:
|
|
123
|
-
|
|
124
|
-
```ts
|
|
125
|
-
// next.config.ts
|
|
126
|
-
const nextConfig = { transpilePackages: ["jaml-ui"] };
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Search Worker Architecture
|
|
130
|
-
|
|
131
|
-
The library provides `useAnalyzer` to interact with `motely-wasm`'s search context natively.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
|
144
|
-
|
|
145
|
-
| `react`,
|
|
146
|
-
| `motely-wasm ^14.3.3` | `jaml-ui/motely`, `AnalyzerExplorer`, `useAnalyzer` data |
|
|
147
|
-
| `three`, `@react-three/fiber`, `@react-three/drei`, `@react-spring/three` | `jaml-ui/r3f` only |
|
|
1
|
+
# jaml-ui
|
|
2
|
+
|
|
3
|
+
React components, UI tokens, sprites, and utilities for Balatro/JAML apps.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install jaml-ui react react-dom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Package exports
|
|
12
|
+
|
|
13
|
+
| Entry | Contents |
|
|
14
|
+
|-------|----------|
|
|
15
|
+
| `jaml-ui` | Game card components, JAML IDE, Analyzer Explorer, hooks |
|
|
16
|
+
| `jaml-ui/ui` | Jimbo design system — JimboPanel, JimboButton, JimboModal, tokens |
|
|
17
|
+
| `jaml-ui/core` | Pure asset helpers, sprite metadata, decode utilities (no React) |
|
|
18
|
+
| `jaml-ui/motely` | motely-wasm decode helpers (requires `motely-wasm` peer) |
|
|
19
|
+
| `jaml-ui/r3f` | 3D card component via React Three Fiber (requires r3f peers) |
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { JamlGameCard, AnalyzerExplorer, JamlIde } from "jaml-ui";
|
|
25
|
+
import { JimboPanel, JimboButton } from "jaml-ui/ui";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Game card
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { JamlGameCard } from "jaml-ui";
|
|
32
|
+
|
|
33
|
+
<JamlGameCard
|
|
34
|
+
type="joker"
|
|
35
|
+
card={{ name: "Blueprint", edition: "Foil", isEternal: true, scale: 1.5 }}
|
|
36
|
+
/>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Jimbo UI (Balatro design system)
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { JimboPanel, JimboButton, JimboModal } from "jaml-ui/ui";
|
|
43
|
+
import { JimboColorOption } from "jaml-ui/ui";
|
|
44
|
+
|
|
45
|
+
<JimboPanel sway onBack={() => setOpen(false)}>
|
|
46
|
+
<JimboButton variant="primary" onClick={handleSearch}>Search</JimboButton>
|
|
47
|
+
</JimboPanel>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Available variants: `primary`, `secondary`, `danger`, `back`, `ghost`
|
|
51
|
+
|
|
52
|
+
### JAML IDE
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { JamlIde } from "jaml-ui";
|
|
56
|
+
|
|
57
|
+
<JamlIde
|
|
58
|
+
jaml={jaml}
|
|
59
|
+
onChange={setJaml}
|
|
60
|
+
searchResults={results}
|
|
61
|
+
onSearch={handleSearch}
|
|
62
|
+
isSearching={isSearching}
|
|
63
|
+
/>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Analyzer Explorer
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { AnalyzerExplorer } from "jaml-ui";
|
|
70
|
+
|
|
71
|
+
// antes: AnalyzerAnteView[] — stream from motely-wasm createSearchContext
|
|
72
|
+
<AnalyzerExplorer antes={antes} totalAntes={8} highlights={highlights} />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### JAML Map Preview
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { JamlMapPreview } from "jaml-ui";
|
|
79
|
+
|
|
80
|
+
<JamlMapPreview jaml={jaml} />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Asset handling
|
|
84
|
+
|
|
85
|
+
By default sprites resolve from the package `assets/` directory via `import.meta.url`.
|
|
86
|
+
|
|
87
|
+
Override at app startup:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { setJamlAssetBaseUrl, clearJamlAssetBaseUrl } from "jaml-ui";
|
|
91
|
+
|
|
92
|
+
setJamlAssetBaseUrl("/vendor/jaml-ui/");
|
|
93
|
+
clearJamlAssetBaseUrl(); // back to default
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Core utilities
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { SPRITE_SHEETS, getSpriteData, resolveJamlAssetUrl } from "jaml-ui/core";
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Motely decode helpers
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { decodeMotelyItemName, motelyItemTypeName } from "jaml-ui/motely";
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 3D card (optional)
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm install three @react-three/fiber @react-three/drei @react-spring/three
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { Card3D } from "jaml-ui/r3f";
|
|
116
|
+
|
|
117
|
+
<Card3D itemName="Blueprint" />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Next.js
|
|
121
|
+
|
|
122
|
+
Import pure helpers from `jaml-ui/core` for server components. For local workspace installs add:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// next.config.ts
|
|
126
|
+
const nextConfig = { transpilePackages: ["jaml-ui"] };
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Search Worker Architecture
|
|
130
|
+
|
|
131
|
+
The library provides `useAnalyzer` to interact with `motely-wasm`'s search context natively.
|
|
132
|
+
Ensure `motely-wasm` is imported and booted at the module level in your application:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { boot } from "motely-wasm";
|
|
136
|
+
boot(); // Call early in your application lifecycle
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Peer dependencies
|
|
140
|
+
|
|
141
|
+
| Peer | Required for |
|
|
142
|
+
|------|-------------|
|
|
143
|
+
| `react`, `react-dom` | All components |
|
|
144
|
+
| `motely-wasm ^14.3.3` | `jaml-ui/motely`, `AnalyzerExplorer`, `useAnalyzer` data |
|
|
145
|
+
| `three`, `@react-three/fiber`, `@react-three/drei`, `@react-spring/three` | `jaml-ui/r3f` only |
|
|
@@ -21,6 +21,6 @@ export interface DeckSpriteProps {
|
|
|
21
21
|
* Balatro deck box sprite — optionally overlaid with a stake sticker.
|
|
22
22
|
* Draws from the Enhancers.png atlas (deck thumbnails live in the bottom
|
|
23
23
|
* two rows) and stickers.png for the stake band. Uses jaml-ui's
|
|
24
|
-
* `resolveJamlAssetUrl` so consumers can override the
|
|
24
|
+
* `resolveJamlAssetUrl` so consumers can override the asset base URL.
|
|
25
25
|
*/
|
|
26
26
|
export declare function DeckSprite({ deck, stake, size, className, style }: DeckSpriteProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -40,7 +40,7 @@ const STICKER_ROWS = 3;
|
|
|
40
40
|
* Balatro deck box sprite — optionally overlaid with a stake sticker.
|
|
41
41
|
* Draws from the Enhancers.png atlas (deck thumbnails live in the bottom
|
|
42
42
|
* two rows) and stickers.png for the stake band. Uses jaml-ui's
|
|
43
|
-
* `resolveJamlAssetUrl` so consumers can override the
|
|
43
|
+
* `resolveJamlAssetUrl` so consumers can override the asset base URL.
|
|
44
44
|
*/
|
|
45
45
|
export function DeckSprite({ deck, stake, size = 50, className = '', style }) {
|
|
46
46
|
const deckKey = (deck || 'erratic').toLowerCase().replace(/\s*deck$/, '').trim();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Motely } from "motely-wasm";
|
|
2
2
|
export interface JamlCuratorProps {
|
|
3
3
|
motely: typeof Motely | null;
|
|
4
|
+
motelyWasmUrl: string;
|
|
4
5
|
}
|
|
5
|
-
export declare function JamlCurator({ motely }: JamlCuratorProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function JamlCurator({ motely, motelyWasmUrl }: JamlCuratorProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -11,10 +11,10 @@ import { useSearch } from "../hooks/useSearch.js";
|
|
|
11
11
|
import { useAnalyzer } from "../hooks/useAnalyzer.js";
|
|
12
12
|
import { JamlSpeedometer } from "./JamlSpeedometer.js";
|
|
13
13
|
const C = JimboColorOption;
|
|
14
|
-
export function JamlCurator({ motely }) {
|
|
14
|
+
export function JamlCurator({ motely, motelyWasmUrl }) {
|
|
15
15
|
// Use map editor by default to generate JAML
|
|
16
16
|
const [jamlText, setJamlText] = useState("");
|
|
17
|
-
const search = useSearch();
|
|
17
|
+
const search = useSearch(motelyWasmUrl);
|
|
18
18
|
const analyzer = useAnalyzer(motely);
|
|
19
19
|
// Search results pagination
|
|
20
20
|
const [resultIndex, setResultIndex] = useState(0);
|
|
@@ -2,11 +2,22 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
5
|
+
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";
|
|
5
6
|
import { JamlIdeToolbar } from "./JamlIdeToolbar.js";
|
|
6
7
|
import { JamlIdeVisual } from "./JamlIdeVisual.js";
|
|
7
8
|
import { JamlCodeEditor } from "./JamlCodeEditor.js";
|
|
8
9
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
10
|
+
import { JimboModal } from "../ui/panel.js";
|
|
9
11
|
import { jamlTextToVisualFilter, visualFilterToJamlText } from "../utils/jamlVisualFilter.js";
|
|
12
|
+
const CATEGORY_CONFIG_MAP = {
|
|
13
|
+
voucher: VOUCHER_PICKER_CONFIG,
|
|
14
|
+
tag: TAG_PICKER_CONFIG,
|
|
15
|
+
boss: BOSS_PICKER_CONFIG,
|
|
16
|
+
tarot: TAROT_PICKER_CONFIG,
|
|
17
|
+
planet: PLANET_PICKER_CONFIG,
|
|
18
|
+
spectral: SPECTRAL_PICKER_CONFIG,
|
|
19
|
+
pack: PACK_PICKER_CONFIG,
|
|
20
|
+
};
|
|
10
21
|
function TallyBar({ value, max }) {
|
|
11
22
|
const pct = max > 0 ? Math.min(1, value / max) : 0;
|
|
12
23
|
return (_jsx("div", { style: { flex: 1, height: 4, borderRadius: 999, background: `${JimboColorOption.DARK_GREY}88`, overflow: "hidden" }, children: _jsx("div", { style: {
|
|
@@ -127,6 +138,33 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
127
138
|
}
|
|
128
139
|
};
|
|
129
140
|
const results = useMemo(() => searchResults, [searchResults]);
|
|
141
|
+
// ── Add-clause picker state ──────────────────────────────────────────────
|
|
142
|
+
const [addZone, setAddZone] = useState(null);
|
|
143
|
+
const [pickerFlow, setPickerFlow] = useState("category");
|
|
144
|
+
const handleAddClause = (zone) => {
|
|
145
|
+
setAddZone(zone);
|
|
146
|
+
setPickerFlow("category");
|
|
147
|
+
};
|
|
148
|
+
const handlePickerSelect = (sel) => {
|
|
149
|
+
if (!addZone)
|
|
150
|
+
return;
|
|
151
|
+
const clause = {
|
|
152
|
+
id: `${Date.now()}-${Math.random()}`,
|
|
153
|
+
type: sel.clauseKey,
|
|
154
|
+
value: sel.value,
|
|
155
|
+
label: sel.value,
|
|
156
|
+
};
|
|
157
|
+
handleVisualFilterChange({ ...activeFilter, [addZone]: [...activeFilter[addZone], clause] });
|
|
158
|
+
setAddZone(null);
|
|
159
|
+
};
|
|
160
|
+
const handlePickerClose = () => {
|
|
161
|
+
if (pickerFlow !== "category") {
|
|
162
|
+
setPickerFlow("category");
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
setAddZone(null);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
130
168
|
return (_jsxs("div", { className: className, style: {
|
|
131
169
|
display: "flex",
|
|
132
170
|
flexDirection: "column",
|
|
@@ -147,5 +185,5 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
147
185
|
padding: "10px 14px",
|
|
148
186
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
149
187
|
background: JimboColorOption.TEAL_GREY,
|
|
150
|
-
}, 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: "Jimbo's Ante Markup Language" })] }), 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: "auto", background: JimboColorOption.DARKEST }, children: [mode === "visual" ? (_jsx(JamlIdeVisual, { filter: activeFilter, onChange: handleVisualFilterChange })) : null, mode === "code" ? (_jsx(JamlCodeEditor, { value: text, onChange: handleTextChange, placeholder: codePlaceholder })) : null, mode === "map" ? _jsx(
|
|
188
|
+
}, 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: "Jimbo's Ante Markup Language" })] }), 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: "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] }), _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 }))) })] }));
|
|
151
189
|
}
|
|
@@ -6,9 +6,9 @@ import { JimboColorOption } from "../ui/tokens.js";
|
|
|
6
6
|
import { extractVisualJamlItems, } from "../utils/jamlMapPreview.js";
|
|
7
7
|
const C = JimboColorOption;
|
|
8
8
|
const ZONES = {
|
|
9
|
-
must: { label: "
|
|
10
|
-
should: { label: "
|
|
11
|
-
mustNot: { label: "
|
|
9
|
+
must: { label: "Must", color: C.BLUE, glow: C.BLUE },
|
|
10
|
+
should: { label: "Should", color: C.RED, glow: C.GOLD },
|
|
11
|
+
mustNot: { label: "Must Not", color: C.ORANGE, glow: C.ORANGE },
|
|
12
12
|
};
|
|
13
13
|
const SECTION_ORDER = ["must", "should", "mustNot"];
|
|
14
14
|
const SHEET_FOR_VISUAL = {
|
|
@@ -22,12 +22,12 @@ const SHEET_FOR_VISUAL = {
|
|
|
22
22
|
* Pulsing glow animation for hits.
|
|
23
23
|
* Design ref: assets/...DesignsV2/src/v2/GlowRing.css
|
|
24
24
|
*/
|
|
25
|
-
const GLOW_ANIMATION = `
|
|
26
|
-
@keyframes j-glow-pulse {
|
|
27
|
-
0% { box-shadow: 0 0 0 1px var(--glow-color), 0 0 4px var(--glow-color); opacity: 0.8; }
|
|
28
|
-
50% { box-shadow: 0 0 0 2px var(--glow-color), 0 0 12px var(--glow-color); opacity: 1; }
|
|
29
|
-
100% { box-shadow: 0 0 0 1px var(--glow-color), 0 0 4px var(--glow-color); opacity: 0.8; }
|
|
30
|
-
}
|
|
25
|
+
const GLOW_ANIMATION = `
|
|
26
|
+
@keyframes j-glow-pulse {
|
|
27
|
+
0% { box-shadow: 0 0 0 1px var(--glow-color), 0 0 4px var(--glow-color); opacity: 0.8; }
|
|
28
|
+
50% { box-shadow: 0 0 0 2px var(--glow-color), 0 0 12px var(--glow-color); opacity: 1; }
|
|
29
|
+
100% { box-shadow: 0 0 0 1px var(--glow-color), 0 0 4px var(--glow-color); opacity: 0.8; }
|
|
30
|
+
}
|
|
31
31
|
`;
|
|
32
32
|
function ClausePill({ item, color, glow, matchCount }) {
|
|
33
33
|
const isHit = matchCount > 0;
|
|
@@ -20,6 +20,51 @@ function formatSpeed(value) {
|
|
|
20
20
|
*/
|
|
21
21
|
export function JamlSpeedometer({ seedsPerSecond, totalSearched, matchingSeeds, status, className = "", style, }) {
|
|
22
22
|
const active = status === "running" || status === "booting";
|
|
23
|
-
const statusTone = status === "error"
|
|
24
|
-
|
|
23
|
+
const statusTone = status === "error"
|
|
24
|
+
? "red"
|
|
25
|
+
: status === "completed"
|
|
26
|
+
? "green"
|
|
27
|
+
: status === "cancelled"
|
|
28
|
+
? "orange"
|
|
29
|
+
: active
|
|
30
|
+
? "gold"
|
|
31
|
+
: "grey";
|
|
32
|
+
const statusLabel = status === "booting"
|
|
33
|
+
? "booting"
|
|
34
|
+
: status === "running"
|
|
35
|
+
? "searching"
|
|
36
|
+
: status === "completed"
|
|
37
|
+
? "done"
|
|
38
|
+
: status === "cancelled"
|
|
39
|
+
? "cancelled"
|
|
40
|
+
: status === "error"
|
|
41
|
+
? "error"
|
|
42
|
+
: "idle";
|
|
43
|
+
const rootStyle = {
|
|
44
|
+
display: "flex",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
flexWrap: "wrap",
|
|
47
|
+
gap: 8,
|
|
48
|
+
padding: "8px 10px",
|
|
49
|
+
borderRadius: 12,
|
|
50
|
+
border: "1px solid var(--j-panel-edge, rgba(255,255,255,0.12))",
|
|
51
|
+
background: "rgba(0,0,0,0.28)",
|
|
52
|
+
...style,
|
|
53
|
+
};
|
|
54
|
+
const dotStyle = {
|
|
55
|
+
width: 8,
|
|
56
|
+
height: 8,
|
|
57
|
+
borderRadius: 999,
|
|
58
|
+
background: status === "error"
|
|
59
|
+
? "var(--j-red, #ff4c40)"
|
|
60
|
+
: status === "completed"
|
|
61
|
+
? "var(--j-green, #429f79)"
|
|
62
|
+
: status === "cancelled"
|
|
63
|
+
? "var(--j-orange, #ff9800)"
|
|
64
|
+
: active
|
|
65
|
+
? "var(--j-gold, #e4b643)"
|
|
66
|
+
: "var(--j-grey, #8b8b8b)",
|
|
67
|
+
flexShrink: 0,
|
|
68
|
+
};
|
|
69
|
+
return (_jsxs("div", { className: className, style: rootStyle, children: [_jsx("span", { style: dotStyle, "aria-hidden": "true" }), _jsx(JimboText, { size: "sm", tone: statusTone, children: statusLabel }), _jsx(JimboText, { size: "sm", tone: active ? "gold" : "grey", children: formatSpeed(seedsPerSecond) }), _jsxs(JimboText, { size: "sm", tone: "white", children: [formatCount(totalSearched), " searched"] }), _jsxs(JimboText, { size: "sm", tone: Number(matchingSeeds) > 0 ? "green" : "grey", children: [formatCount(matchingSeeds), " matches"] })] }));
|
|
25
70
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SlotSelection, type JamlZone } from "./MysterySlot.js";
|
|
1
|
+
import { type SlotSelection, type JamlZone, type SlotCategory } from "./MysterySlot.js";
|
|
2
2
|
export interface JamlMapEditorProps {
|
|
3
3
|
/** Initial zone for the demo. */
|
|
4
4
|
zone?: JamlZone;
|
|
@@ -9,3 +9,6 @@ export interface MapSlotSelection extends SlotSelection {
|
|
|
9
9
|
zone: JamlZone;
|
|
10
10
|
}
|
|
11
11
|
export declare function JamlMapEditor({ zone: initialZone, onChange, }: JamlMapEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function CategoryMenu({ onSelect, }: {
|
|
13
|
+
onSelect: (cat: SlotCategory) => void;
|
|
14
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback, useMemo,
|
|
3
|
+
import { useState, useCallback, useMemo, useRef } from "react";
|
|
4
4
|
import { MysterySlot } from "./MysterySlot.js";
|
|
5
5
|
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";
|
|
@@ -42,6 +42,8 @@ const CATEGORY_CONFIG_MAP = {
|
|
|
42
42
|
};
|
|
43
43
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
44
44
|
export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
45
|
+
const onChangeRef = useRef(onChange);
|
|
46
|
+
onChangeRef.current = onChange;
|
|
45
47
|
const [currentZone, setCurrentZone] = useState(initialZone);
|
|
46
48
|
const [ante, setAnte] = useState(1);
|
|
47
49
|
const [antesState, setAntesState] = useState({});
|
|
@@ -60,6 +62,7 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
60
62
|
const nextAnte = { ...next[anteIndex] };
|
|
61
63
|
delete nextAnte[id];
|
|
62
64
|
next[anteIndex] = nextAnte;
|
|
65
|
+
onChangeRef.current?.(buildJamlText(next));
|
|
63
66
|
return next;
|
|
64
67
|
});
|
|
65
68
|
}, []);
|
|
@@ -74,6 +77,7 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
74
77
|
const nextAnte = { ...(next[activeSlot.ante] || {}) };
|
|
75
78
|
nextAnte[activeSlot.id] = { ...selection, zone: currentZone };
|
|
76
79
|
next[activeSlot.ante] = nextAnte;
|
|
80
|
+
onChangeRef.current?.(buildJamlText(next));
|
|
77
81
|
return next;
|
|
78
82
|
});
|
|
79
83
|
setActiveSlot(null);
|
|
@@ -93,19 +97,23 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
93
97
|
setActiveSlot(null);
|
|
94
98
|
}, []);
|
|
95
99
|
const jamlText = useMemo(() => buildJamlText(antesState), [antesState]);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
const handleScrollAttach = useCallback((node) => {
|
|
101
|
+
if (!node)
|
|
102
|
+
return;
|
|
103
|
+
const firstChild = node.children[1];
|
|
104
|
+
if (firstChild)
|
|
105
|
+
node.scrollTop = firstChild.offsetTop;
|
|
106
|
+
}, []);
|
|
99
107
|
const renderSlot = (anteIndex, id, width, sheetType, forceCategory) => {
|
|
100
108
|
const sel = (antesState[anteIndex] || {})[id];
|
|
101
109
|
return (_jsx(MysterySlot, { zone: sel ? sel.zone : currentZone, sheetType: sheetType, selection: sel, width: width, onTap: () => handleSlotTap(anteIndex, id, forceCategory), onClear: sel ? () => handleSlotClear(anteIndex, id) : undefined, style: { flexShrink: 0 } }, id));
|
|
102
110
|
};
|
|
103
|
-
return (_jsxs("div", { style: { width: "100%", height: "100%", display: "flex", flexDirection: "column" }, children: [_jsxs("div", { style: { position: "sticky", top: 0, zIndex: 10, background: C.DARKEST, padding: "max(32px, env(safe-area-inset-top, 32px)) 0 8px 0", borderBottom: `2px solid ${C.PANEL_EDGE}` }, children: [_jsx(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 12 }, children: "
|
|
111
|
+
return (_jsxs("div", { style: { width: "100%", height: "100%", display: "flex", flexDirection: "column" }, children: [_jsxs("div", { style: { position: "sticky", top: 0, zIndex: 10, background: C.DARKEST, padding: "max(32px, env(safe-area-inset-top, 32px)) 0 8px 0", borderBottom: `2px solid ${C.PANEL_EDGE}` }, children: [_jsx(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 12 }, children: "Jaml Visual Builder" }), _jsx("div", { className: "j-flex j-gap-sm", style: { justifyContent: "center" }, children: ["must", "should", "mustnot"].map((z) => (_jsx(JimboButton, { tone: currentZone === z ? ZONE_TONE[z] : "blue", size: "sm", onClick: () => setCurrentZone(z), style: { opacity: currentZone === z ? 1 : 0.4 }, children: ZONE_LABEL[z] }, z))) })] }), _jsx("div", { ref: handleScrollAttach, className: "hide-scrollbar", style: {
|
|
104
112
|
flex: 1,
|
|
105
113
|
overflowY: "auto",
|
|
106
114
|
scrollSnapType: "y mandatory",
|
|
107
115
|
scrollBehavior: "smooth"
|
|
108
|
-
}, children:
|
|
116
|
+
}, children: Array.from({ length: 40 }, (_, i) => i).map((a) => (_jsxs("div", { style: {
|
|
109
117
|
scrollSnapAlign: "start",
|
|
110
118
|
padding: "24px 8px 64px 8px",
|
|
111
119
|
minHeight: "100%", // ensuring each ante takes at least full viewport height to snap cleanly
|
|
@@ -113,10 +121,10 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
|
|
|
113
121
|
flexDirection: "column",
|
|
114
122
|
gap: 24,
|
|
115
123
|
borderBottom: `2px solid ${C.DARK_GREY}`
|
|
116
|
-
}, children: [_jsxs(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 8 }, children: ["
|
|
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", style: { flexWrap: "wrap" }, children: [1, 2, 3, 4, 5, 6].map(i => renderSlot(a, `ante_${a}_pack_${i}`, 64, "Boosters", "pack")) })] })] }, a))) }), _jsx(JimboModal, { open: activeSlot !== null, onClose: handlePickerCancel, title: pickerFlow === "category" ? "Select Category" : undefined, className: "j-picker-modal", children: activeSlot !== null && (pickerFlow === "category" ? (_jsx(CategoryMenu, { onSelect: handleCategorySelect })) : pickerFlow === "joker" ? (_jsx(JokerPicker, { onSelect: handleItemSelect, onCancel: handlePickerCancel })) : (_jsx(CategoryPicker, { config: CATEGORY_CONFIG_MAP[pickerFlow], onSelect: handleItemSelect, onCancel: handlePickerCancel }))) })] }));
|
|
117
125
|
}
|
|
118
126
|
// ─── Category Selection Menu ─────────────────────────────────────────────────
|
|
119
|
-
function CategoryMenu({ onSelect, }) {
|
|
127
|
+
export function CategoryMenu({ onSelect, }) {
|
|
120
128
|
return (_jsx("div", { className: "hide-scrollbar", style: {
|
|
121
129
|
display: "flex",
|
|
122
130
|
flexDirection: "column",
|
|
@@ -1,4 +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
|
-
export { JamlMapEditor, type JamlMapEditorProps } from "./JamlMapEditor.js";
|
|
3
|
+
export { JamlMapEditor, CategoryMenu, type JamlMapEditorProps } from "./JamlMapEditor.js";
|
|
4
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,4 +1,4 @@
|
|
|
1
1
|
export { MysterySlot } from "./MysterySlot.js";
|
|
2
2
|
export { JokerPicker } from "./JokerPicker.js";
|
|
3
|
-
export { JamlMapEditor } from "./JamlMapEditor.js";
|
|
3
|
+
export { JamlMapEditor, CategoryMenu } from "./JamlMapEditor.js";
|
|
4
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";
|