jaml-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -27
- package/dist/components/JamlIde.d.ts +17 -0
- package/dist/components/JamlIde.js +61 -0
- package/dist/components/JamlIdeToolbar.d.ts +8 -0
- package/dist/components/JamlIdeToolbar.js +36 -0
- package/dist/components/JamlMapPreview.d.ts +10 -0
- package/dist/components/JamlMapPreview.js +117 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/utils/jamlMapPreview.d.ts +12 -0
- package/dist/utils/jamlMapPreview.js +105 -0
- package/package.json +19 -31
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Balatro rendering components, sprite metadata, and optional Motely helpers for React apps.
|
|
4
4
|
|
|
5
|
+
`jaml-ui` is the shared UI layer for Balatro/JAML surfaces: low-level renderers, asset helpers, visual JAML previews, and a lightweight browser-first JAML IDE shell.
|
|
6
|
+
|
|
5
7
|
## Package shape
|
|
6
8
|
|
|
7
9
|
- `jaml-ui`
|
|
@@ -17,8 +19,6 @@ Balatro rendering components, sprite metadata, and optional Motely helpers for R
|
|
|
17
19
|
npm install jaml-ui react react-dom
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
**Latest published major features:** `0.2.x` adds `jaml-ui/r3f` (Three.js card meshes). If you need `0.2.0` from npm and hit Windows publish issues, see [PUBLISHING.md](./PUBLISHING.md) and use GitHub Actions or Linux to `npm publish`.
|
|
21
|
-
|
|
22
22
|
If you want the Motely-specific helpers too:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
@@ -47,6 +47,39 @@ export function Example() {
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
## JAML preview
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
"use client";
|
|
54
|
+
|
|
55
|
+
import { JamlMapPreview } from "jaml-ui";
|
|
56
|
+
|
|
57
|
+
export function PreviewExample({ jaml }: { jaml: string }) {
|
|
58
|
+
return <JamlMapPreview jaml={jaml} title="JAML Intent Preview" />;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Lightweight JAML IDE shell
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
"use client";
|
|
66
|
+
|
|
67
|
+
import { useState } from "react";
|
|
68
|
+
import { JamlIde } from "jaml-ui";
|
|
69
|
+
|
|
70
|
+
export function IdeExample() {
|
|
71
|
+
const [jaml, setJaml] = useState("must:\n joker: Blueprint");
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<JamlIde
|
|
75
|
+
jaml={jaml}
|
|
76
|
+
onChange={setJaml}
|
|
77
|
+
results={[]}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
50
83
|
## Asset handling
|
|
51
84
|
|
|
52
85
|
By default, `jaml-ui` resolves its packaged sprite assets from the package `assets/` directory using `import.meta.url`.
|
|
@@ -90,34 +123,9 @@ const itemName = decodeMotelyItemName(0x5001);
|
|
|
90
123
|
const enumKey = motelyItemTypeName(0x5001);
|
|
91
124
|
```
|
|
92
125
|
|
|
93
|
-
## Three.js / R3F (optional)
|
|
94
|
-
|
|
95
|
-
`jaml-ui/r3f` provides **magnetic-tilt** meshes for playing cards and jokers. Peer dependencies (install in your app):
|
|
96
|
-
|
|
97
|
-
- `three`
|
|
98
|
-
- `@react-three/fiber`
|
|
99
|
-
- `@react-spring/three`
|
|
100
|
-
|
|
101
|
-
```tsx
|
|
102
|
-
"use client";
|
|
103
|
-
|
|
104
|
-
import { BalatroPlayingCard3D, BalatroJokerPreview3D } from "jaml-ui/r3f";
|
|
105
|
-
|
|
106
|
-
// In an existing <Canvas>: BalatroPlayingCard3D with card={{ suit, rank, ... }}
|
|
107
|
-
// Standalone joker preview:
|
|
108
|
-
export function Preview() {
|
|
109
|
-
return <BalatroJokerPreview3D displayName="Blueprint" />;
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Atlas URLs default to packaged sprites; override with `setJamlAssetBaseUrl("/your/public/images/")` or per-component `deckUrl` / `jokersImageUrl` props.
|
|
114
|
-
|
|
115
|
-
Bundled data: `balatro-jokers.json` ships in `dist/data/` for joker grid lookup.
|
|
116
|
-
|
|
117
126
|
## Next.js notes
|
|
118
127
|
|
|
119
128
|
- The root `jaml-ui` entry is client-oriented and preserves the `"use client"` boundary for component consumers.
|
|
120
|
-
- Add `jaml-ui/r3f` to `transpilePackages` when you use the R3F entry.
|
|
121
129
|
- Import pure helpers from `jaml-ui/core` when you want server-safe metadata and asset utilities.
|
|
122
130
|
- If you are consuming `jaml-ui` from a local workspace package in a Next.js app, you may need:
|
|
123
131
|
|
|
@@ -135,3 +143,5 @@ export default nextConfig;
|
|
|
135
143
|
## Browser-first runtime direction
|
|
136
144
|
|
|
137
145
|
`jaml-ui` is designed for browser/React consumers. The optional `jaml-ui/motely` entry targets plain `motely-wasm` and does not assume threaded WASM, SAB, or COEP setup.
|
|
146
|
+
|
|
147
|
+
The built-in `JamlIde` intentionally stays lightweight. Rich editor integrations like Monaco, custom language servers, or extension-host-specific tooling should live in app-level packages on top of `jaml-ui`, not in the base renderer package.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type JamlIdeMode } from "./JamlIdeToolbar.js";
|
|
3
|
+
export interface JamlIdeSearchResult {
|
|
4
|
+
seed: string;
|
|
5
|
+
score?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface JamlIdeProps {
|
|
8
|
+
jaml: string;
|
|
9
|
+
onChange: (jaml: string) => void;
|
|
10
|
+
defaultMode?: JamlIdeMode;
|
|
11
|
+
searchResults?: JamlIdeSearchResult[];
|
|
12
|
+
className?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
actions?: React.ReactNode;
|
|
15
|
+
codePlaceholder?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function JamlIde({ jaml, onChange, defaultMode, searchResults, className, title, actions, codePlaceholder, }: JamlIdeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
5
|
+
import { JamlIdeToolbar } from "./JamlIdeToolbar.js";
|
|
6
|
+
function ResultsView({ results }) {
|
|
7
|
+
if (results.length === 0) {
|
|
8
|
+
return (_jsx("div", { style: {
|
|
9
|
+
border: "1px dashed rgba(255,255,255,0.18)",
|
|
10
|
+
borderRadius: 12,
|
|
11
|
+
padding: 14,
|
|
12
|
+
fontSize: 12,
|
|
13
|
+
opacity: 0.72,
|
|
14
|
+
background: "rgba(255,255,255,0.03)",
|
|
15
|
+
}, children: "No results yet." }));
|
|
16
|
+
}
|
|
17
|
+
return (_jsx("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: results.map((result, index) => (_jsxs("div", { style: {
|
|
18
|
+
display: "flex",
|
|
19
|
+
alignItems: "center",
|
|
20
|
+
justifyContent: "space-between",
|
|
21
|
+
gap: 12,
|
|
22
|
+
borderRadius: 12,
|
|
23
|
+
border: "1px solid rgba(255,255,255,0.08)",
|
|
24
|
+
background: "rgba(255,255,255,0.03)",
|
|
25
|
+
padding: "10px 12px",
|
|
26
|
+
}, children: [_jsx("div", { style: { fontWeight: 700, letterSpacing: 0.4 }, children: result.seed }), _jsx("div", { style: { fontSize: 12, opacity: 0.7 }, children: result.score !== undefined ? result.score : "-" })] }, `${result.seed}-${index}`))) }));
|
|
27
|
+
}
|
|
28
|
+
export function JamlIde({ jaml, onChange, defaultMode = "code", searchResults = [], className = "", title = "JAML IDE", actions, codePlaceholder = "Enter JAML...", }) {
|
|
29
|
+
const [mode, setMode] = useState(defaultMode);
|
|
30
|
+
const results = useMemo(() => searchResults, [searchResults]);
|
|
31
|
+
return (_jsxs("div", { className: className, style: {
|
|
32
|
+
display: "flex",
|
|
33
|
+
flexDirection: "column",
|
|
34
|
+
minHeight: 420,
|
|
35
|
+
borderRadius: 16,
|
|
36
|
+
overflow: "hidden",
|
|
37
|
+
border: "1px solid rgba(255,255,255,0.08)",
|
|
38
|
+
background: "#17181c",
|
|
39
|
+
color: "#f5f5f5",
|
|
40
|
+
}, children: [_jsxs("div", { style: {
|
|
41
|
+
display: "flex",
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
justifyContent: "space-between",
|
|
44
|
+
gap: 12,
|
|
45
|
+
padding: "12px 14px",
|
|
46
|
+
borderBottom: "1px solid rgba(255,255,255,0.08)",
|
|
47
|
+
background: "rgba(255,255,255,0.03)",
|
|
48
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 16, fontWeight: 800 }, children: title }), _jsx("div", { style: { fontSize: 11, opacity: 0.66 }, children: "Reusable JAML authoring and preview surface." })] }), actions ? _jsx("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: actions }) : null] }), _jsx(JamlIdeToolbar, { mode: mode, onModeChange: setMode, resultCount: results.length }), _jsxs("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: [mode === "code" ? (_jsx("textarea", { title: "JAML IDE Editor", value: jaml, onChange: (event) => onChange(event.target.value), placeholder: codePlaceholder, spellCheck: false, autoCapitalize: "off", autoCorrect: "off", style: {
|
|
49
|
+
width: "100%",
|
|
50
|
+
minHeight: 320,
|
|
51
|
+
resize: "vertical",
|
|
52
|
+
border: 0,
|
|
53
|
+
outline: 0,
|
|
54
|
+
padding: 16,
|
|
55
|
+
background: "transparent",
|
|
56
|
+
color: "inherit",
|
|
57
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
58
|
+
fontSize: 13,
|
|
59
|
+
lineHeight: 1.7,
|
|
60
|
+
} })) : null, mode === "map" ? _jsx(JamlMapPreview, { jaml: jaml }) : null, mode === "results" ? _jsx("div", { style: { padding: 16 }, children: _jsx(ResultsView, { results: results }) }) : null] })] }));
|
|
61
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type JamlIdeMode = "code" | "map" | "results";
|
|
2
|
+
export interface JamlIdeToolbarProps {
|
|
3
|
+
mode: JamlIdeMode;
|
|
4
|
+
onModeChange: (mode: JamlIdeMode) => void;
|
|
5
|
+
resultCount?: number;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function JamlIdeToolbar({ mode, onModeChange, resultCount, className }: JamlIdeToolbarProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const TABS = [
|
|
4
|
+
{ id: "code", label: "Code" },
|
|
5
|
+
{ id: "map", label: "Map" },
|
|
6
|
+
{ id: "results", label: "Results" },
|
|
7
|
+
];
|
|
8
|
+
export function JamlIdeToolbar({ mode, onModeChange, resultCount = 0, className = "" }) {
|
|
9
|
+
return (_jsx("div", { className: className, style: {
|
|
10
|
+
display: "flex",
|
|
11
|
+
alignItems: "center",
|
|
12
|
+
justifyContent: "space-between",
|
|
13
|
+
gap: 8,
|
|
14
|
+
padding: "8px 10px",
|
|
15
|
+
borderBottom: "1px solid rgba(255,255,255,0.08)",
|
|
16
|
+
background: "rgba(255,255,255,0.04)",
|
|
17
|
+
}, children: _jsx("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: TABS.map((tab) => {
|
|
18
|
+
const selected = mode === tab.id;
|
|
19
|
+
return (_jsxs("button", { type: "button", onClick: () => onModeChange(tab.id), style: {
|
|
20
|
+
cursor: "pointer",
|
|
21
|
+
borderRadius: 8,
|
|
22
|
+
border: selected ? "1px solid rgba(247,185,85,0.55)" : "1px solid transparent",
|
|
23
|
+
background: selected ? "rgba(247,185,85,0.16)" : "transparent",
|
|
24
|
+
color: selected ? "#f7b955" : "rgba(255,255,255,0.58)",
|
|
25
|
+
padding: "6px 10px",
|
|
26
|
+
fontSize: 11,
|
|
27
|
+
fontWeight: 600,
|
|
28
|
+
}, children: [tab.label, tab.id === "results" && resultCount > 0 ? (_jsx("span", { style: {
|
|
29
|
+
marginLeft: 6,
|
|
30
|
+
borderRadius: 999,
|
|
31
|
+
background: "rgba(0,0,0,0.25)",
|
|
32
|
+
padding: "1px 6px",
|
|
33
|
+
fontSize: 10,
|
|
34
|
+
}, children: resultCount })) : null] }, tab.id));
|
|
35
|
+
}) }) }));
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface JamlMapPreviewProps {
|
|
2
|
+
jaml: string;
|
|
3
|
+
className?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
emptyMessage?: string;
|
|
6
|
+
cardScale?: number;
|
|
7
|
+
tagScale?: number;
|
|
8
|
+
bossScale?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function JamlMapPreview({ jaml, className, title, emptyMessage, cardScale, tagScale, bossScale, }: JamlMapPreviewProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { JamlBoss, JamlGameCard, JamlTag, JamlVoucher } from "./GameCard.js";
|
|
5
|
+
import { extractVisualJamlItems } from "../utils/jamlMapPreview.js";
|
|
6
|
+
const SECTION_ORDER = ["must", "should", "mustNot"];
|
|
7
|
+
const SECTION_LABELS = {
|
|
8
|
+
must: "Must Requirements",
|
|
9
|
+
should: "Should Requirements",
|
|
10
|
+
mustNot: "Must Not Requirements",
|
|
11
|
+
};
|
|
12
|
+
const SECTION_ACCENTS = {
|
|
13
|
+
must: { color: "#ff6b6b", border: "rgba(255,107,107,0.35)", panel: "rgba(255,107,107,0.08)" },
|
|
14
|
+
should: { color: "#f7b955", border: "rgba(247,185,85,0.35)", panel: "rgba(247,185,85,0.08)" },
|
|
15
|
+
mustNot: { color: "#8d7dff", border: "rgba(141,125,255,0.35)", panel: "rgba(141,125,255,0.08)" },
|
|
16
|
+
};
|
|
17
|
+
function renderPreviewItem(item, cardScale, tagScale, bossScale) {
|
|
18
|
+
switch (item.visualType) {
|
|
19
|
+
case "joker":
|
|
20
|
+
return _jsx(JamlGameCard, { card: { name: item.value, scale: cardScale }, type: "joker" });
|
|
21
|
+
case "consumable":
|
|
22
|
+
return _jsx(JamlGameCard, { card: { name: item.value, scale: cardScale }, type: "consumable" });
|
|
23
|
+
case "voucher":
|
|
24
|
+
return _jsx(JamlVoucher, { voucherName: item.value, scale: cardScale });
|
|
25
|
+
case "tag":
|
|
26
|
+
return _jsx(JamlTag, { tagName: item.value, scale: tagScale });
|
|
27
|
+
case "boss":
|
|
28
|
+
return _jsx(JamlBoss, { bossName: item.value, scale: bossScale });
|
|
29
|
+
default:
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function JamlMapPreview({ jaml, className = "", title = "JAML Map Preview", emptyMessage = "No visual JAML clauses found yet.", cardScale = 0.8, tagScale = 1.1, bossScale = 1.1, }) {
|
|
34
|
+
const groups = useMemo(() => extractVisualJamlItems(jaml), [jaml]);
|
|
35
|
+
const totalItems = SECTION_ORDER.reduce((sum, section) => sum + groups[section].length, 0);
|
|
36
|
+
return (_jsxs("div", { className: className, style: {
|
|
37
|
+
display: "flex",
|
|
38
|
+
flexDirection: "column",
|
|
39
|
+
gap: 16,
|
|
40
|
+
padding: 16,
|
|
41
|
+
color: "#f5f5f5",
|
|
42
|
+
borderRadius: 20,
|
|
43
|
+
border: "1px solid rgba(152,152,192,0.18)",
|
|
44
|
+
background: "linear-gradient(180deg, rgba(28,26,52,0.96) 0%, rgba(18,16,36,0.96) 100%)",
|
|
45
|
+
boxShadow: "0 14px 32px rgba(0,0,0,0.16)",
|
|
46
|
+
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, flexWrap: "wrap" }, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, fontWeight: 700, marginBottom: 4 }, children: title }), _jsx("div", { style: { fontSize: 12, opacity: 0.74, maxWidth: 520 }, children: "Preview the visual targets described directly in your JAML without running a full search." })] }), _jsxs("div", { style: {
|
|
47
|
+
display: "inline-flex",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
gap: 6,
|
|
50
|
+
borderRadius: 999,
|
|
51
|
+
border: "1px solid rgba(141,125,255,0.3)",
|
|
52
|
+
background: "rgba(141,125,255,0.12)",
|
|
53
|
+
padding: "6px 10px",
|
|
54
|
+
fontSize: 11,
|
|
55
|
+
fontWeight: 700,
|
|
56
|
+
color: "#c8bcff",
|
|
57
|
+
letterSpacing: "0.04em",
|
|
58
|
+
textTransform: "uppercase",
|
|
59
|
+
}, children: [_jsx("span", { children: totalItems }), _jsx("span", { children: totalItems === 1 ? "visual target" : "visual targets" })] })] }), totalItems === 0 ? (_jsx("div", { style: {
|
|
60
|
+
border: "1px dashed rgba(152,152,192,0.28)",
|
|
61
|
+
borderRadius: 16,
|
|
62
|
+
padding: 16,
|
|
63
|
+
fontSize: 12,
|
|
64
|
+
opacity: 0.72,
|
|
65
|
+
background: "rgba(255,255,255,0.035)",
|
|
66
|
+
}, children: emptyMessage })) : null, SECTION_ORDER.map((section) => {
|
|
67
|
+
const items = groups[section];
|
|
68
|
+
if (items.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
const accent = SECTION_ACCENTS[section];
|
|
71
|
+
return (_jsxs("section", { style: {
|
|
72
|
+
display: "flex",
|
|
73
|
+
flexDirection: "column",
|
|
74
|
+
gap: 10,
|
|
75
|
+
borderRadius: 18,
|
|
76
|
+
border: `1px solid ${accent.border}`,
|
|
77
|
+
background: `linear-gradient(180deg, ${accent.panel} 0%, rgba(255,255,255,0.02) 100%)`,
|
|
78
|
+
padding: 12,
|
|
79
|
+
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [_jsx("div", { style: {
|
|
80
|
+
width: 6,
|
|
81
|
+
height: 18,
|
|
82
|
+
borderRadius: 999,
|
|
83
|
+
background: accent.color,
|
|
84
|
+
} }), _jsx("div", { style: { fontSize: 12, fontWeight: 700, color: accent.color }, children: SECTION_LABELS[section] }), _jsx("div", { style: {
|
|
85
|
+
marginLeft: "auto",
|
|
86
|
+
borderRadius: 999,
|
|
87
|
+
padding: "3px 8px",
|
|
88
|
+
fontSize: 10,
|
|
89
|
+
fontWeight: 700,
|
|
90
|
+
color: accent.color,
|
|
91
|
+
background: "rgba(255,255,255,0.04)",
|
|
92
|
+
}, children: items.length })] }), _jsx("div", { style: {
|
|
93
|
+
display: "flex",
|
|
94
|
+
flexWrap: "wrap",
|
|
95
|
+
gap: 12,
|
|
96
|
+
}, children: items.map((item) => (_jsxs("div", { style: {
|
|
97
|
+
display: "flex",
|
|
98
|
+
flexDirection: "column",
|
|
99
|
+
alignItems: "center",
|
|
100
|
+
gap: 8,
|
|
101
|
+
minWidth: 96,
|
|
102
|
+
padding: 12,
|
|
103
|
+
borderRadius: 16,
|
|
104
|
+
border: `1px solid ${accent.border}`,
|
|
105
|
+
background: "rgba(10,10,20,0.24)",
|
|
106
|
+
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.04)",
|
|
107
|
+
}, title: `${item.clauseKey}: ${item.value}`, children: [_jsx("div", { style: { minHeight: 72, display: "flex", alignItems: "center", justifyContent: "center" }, children: renderPreviewItem(item, cardScale, tagScale, bossScale) }), _jsx("div", { style: { fontSize: 11, textAlign: "center", lineHeight: 1.35, fontWeight: 600 }, children: item.value }), _jsx("div", { style: { fontSize: 10, opacity: 0.62, textTransform: "uppercase", letterSpacing: "0.06em" }, children: item.clauseKey })] }, item.id))) })] }, section));
|
|
108
|
+
}), _jsx("div", { style: {
|
|
109
|
+
borderRadius: 16,
|
|
110
|
+
border: "1px dashed rgba(141,125,255,0.28)",
|
|
111
|
+
background: "rgba(141,125,255,0.08)",
|
|
112
|
+
padding: 14,
|
|
113
|
+
fontSize: 11,
|
|
114
|
+
lineHeight: 1.5,
|
|
115
|
+
opacity: 0.84,
|
|
116
|
+
}, children: "This preview shows direct visual targets that can be read from the JAML text itself. Advanced scoring logic, positional constraints, nested groups, and runtime-only search behavior are not fully represented here yet." })] }));
|
|
117
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,9 @@ export { JAML_ASSET_FILES, clearJamlAssetBaseUrl, getDefaultJamlAssetUrlMap, res
|
|
|
2
2
|
export { Layer, type LayerOptions } from "./render/Layer.js";
|
|
3
3
|
export { JamlCardRenderer, type JamlCardRendererProps } from "./render/CanvasRenderer.js";
|
|
4
4
|
export { JamlGameCard, JamlVoucher, JamlTag, JamlBoss, resolveAnalyzerShopItem, type JamlGameCardProps, type AnalyzerShopItem, type AnalyzerResolvedItem, } from "./components/GameCard.js";
|
|
5
|
+
export { JamlMapPreview, type JamlMapPreviewProps } from "./components/JamlMapPreview.js";
|
|
6
|
+
export { JamlIde, type JamlIdeProps, type JamlIdeSearchResult, } from "./components/JamlIde.js";
|
|
7
|
+
export { JamlIdeToolbar, type JamlIdeMode, type JamlIdeToolbarProps, } from "./components/JamlIdeToolbar.js";
|
|
5
8
|
export { CardList, type CardListProps } from "./components/CardList.js";
|
|
9
|
+
export { extractVisualJamlItems, type JamlPreviewGroups, type JamlPreviewItem, type JamlPreviewSection, type JamlPreviewVisualType, } from "./utils/jamlMapPreview.js";
|
|
6
10
|
export { useMotelyStream, type StreamItem, type StreamState } from "./hooks/useShopStream.js";
|
package/dist/index.js
CHANGED
|
@@ -3,5 +3,9 @@ export { JAML_ASSET_FILES, clearJamlAssetBaseUrl, getDefaultJamlAssetUrlMap, res
|
|
|
3
3
|
export { Layer } from "./render/Layer.js";
|
|
4
4
|
export { JamlCardRenderer } from "./render/CanvasRenderer.js";
|
|
5
5
|
export { JamlGameCard, JamlVoucher, JamlTag, JamlBoss, resolveAnalyzerShopItem, } from "./components/GameCard.js";
|
|
6
|
+
export { JamlMapPreview } from "./components/JamlMapPreview.js";
|
|
7
|
+
export { JamlIde, } from "./components/JamlIde.js";
|
|
8
|
+
export { JamlIdeToolbar, } from "./components/JamlIdeToolbar.js";
|
|
6
9
|
export { CardList } from "./components/CardList.js";
|
|
10
|
+
export { extractVisualJamlItems, } from "./utils/jamlMapPreview.js";
|
|
7
11
|
export { useMotelyStream } from "./hooks/useShopStream.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type JamlPreviewSection = "must" | "should" | "mustNot";
|
|
2
|
+
export type JamlPreviewVisualType = "joker" | "consumable" | "voucher" | "tag" | "boss";
|
|
3
|
+
export interface JamlPreviewItem {
|
|
4
|
+
id: string;
|
|
5
|
+
section: JamlPreviewSection;
|
|
6
|
+
clauseKey: string;
|
|
7
|
+
visualType: JamlPreviewVisualType;
|
|
8
|
+
value: string;
|
|
9
|
+
source: string;
|
|
10
|
+
}
|
|
11
|
+
export type JamlPreviewGroups = Record<JamlPreviewSection, JamlPreviewItem[]>;
|
|
12
|
+
export declare function extractVisualJamlItems(jaml: string): JamlPreviewGroups;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const CLAUSE_VISUAL_TYPES = {
|
|
2
|
+
joker: "joker",
|
|
3
|
+
jokers: "joker",
|
|
4
|
+
commonJoker: "joker",
|
|
5
|
+
commonJokers: "joker",
|
|
6
|
+
uncommonJoker: "joker",
|
|
7
|
+
uncommonJokers: "joker",
|
|
8
|
+
rareJoker: "joker",
|
|
9
|
+
rareJokers: "joker",
|
|
10
|
+
mixedJoker: "joker",
|
|
11
|
+
mixedJokers: "joker",
|
|
12
|
+
soulJoker: "joker",
|
|
13
|
+
legendaryJoker: "joker",
|
|
14
|
+
voucher: "voucher",
|
|
15
|
+
vouchers: "voucher",
|
|
16
|
+
tarot: "consumable",
|
|
17
|
+
tarotCard: "consumable",
|
|
18
|
+
spectral: "consumable",
|
|
19
|
+
spectralCard: "consumable",
|
|
20
|
+
planet: "consumable",
|
|
21
|
+
planetCard: "consumable",
|
|
22
|
+
boss: "boss",
|
|
23
|
+
bosses: "boss",
|
|
24
|
+
tag: "tag",
|
|
25
|
+
tags: "tag",
|
|
26
|
+
smallBlindTag: "tag",
|
|
27
|
+
bigBlindTag: "tag",
|
|
28
|
+
};
|
|
29
|
+
function createEmptyGroups() {
|
|
30
|
+
return { must: [], should: [], mustNot: [] };
|
|
31
|
+
}
|
|
32
|
+
function stripWrappingQuotes(value) {
|
|
33
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
34
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
35
|
+
return value.slice(1, -1).trim();
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function normalizeCandidateValue(value) {
|
|
40
|
+
const normalized = stripWrappingQuotes(value.trim()).replace(/,$/, "").trim();
|
|
41
|
+
if (!normalized)
|
|
42
|
+
return null;
|
|
43
|
+
if (normalized === "Any")
|
|
44
|
+
return null;
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
function parseInlineValues(raw) {
|
|
48
|
+
const trimmed = raw.trim();
|
|
49
|
+
if (!trimmed)
|
|
50
|
+
return [];
|
|
51
|
+
if (trimmed.startsWith("[") && trimmed.includes("]")) {
|
|
52
|
+
const body = trimmed.slice(1, trimmed.indexOf("]"));
|
|
53
|
+
return body
|
|
54
|
+
.split(",")
|
|
55
|
+
.map((value) => normalizeCandidateValue(value))
|
|
56
|
+
.filter((value) => Boolean(value));
|
|
57
|
+
}
|
|
58
|
+
const normalized = normalizeCandidateValue(trimmed);
|
|
59
|
+
return normalized ? [normalized] : [];
|
|
60
|
+
}
|
|
61
|
+
export function extractVisualJamlItems(jaml) {
|
|
62
|
+
const groups = createEmptyGroups();
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
const lines = jaml.replace(/\r\n/g, "\n").split("\n");
|
|
65
|
+
let currentSection = null;
|
|
66
|
+
for (const rawLine of lines) {
|
|
67
|
+
const trimmed = rawLine.trim();
|
|
68
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
69
|
+
continue;
|
|
70
|
+
const sectionMatch = /^(must|should|mustNot):\s*$/.exec(trimmed);
|
|
71
|
+
if (sectionMatch) {
|
|
72
|
+
currentSection = sectionMatch[1];
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const indent = rawLine.search(/\S|$/);
|
|
76
|
+
if (indent === 0 && /^[A-Za-z][A-Za-z0-9]*:\s*/.test(trimmed)) {
|
|
77
|
+
currentSection = null;
|
|
78
|
+
}
|
|
79
|
+
if (!currentSection)
|
|
80
|
+
continue;
|
|
81
|
+
const clauseMatch = /^-?\s*([A-Za-z][A-Za-z0-9]*):\s*(.*?)\s*$/.exec(trimmed);
|
|
82
|
+
if (!clauseMatch)
|
|
83
|
+
continue;
|
|
84
|
+
const clauseKey = clauseMatch[1];
|
|
85
|
+
const visualType = CLAUSE_VISUAL_TYPES[clauseKey];
|
|
86
|
+
if (!visualType)
|
|
87
|
+
continue;
|
|
88
|
+
const values = parseInlineValues(clauseMatch[2]);
|
|
89
|
+
for (const value of values) {
|
|
90
|
+
const dedupeKey = `${currentSection}:${visualType}:${value.toLowerCase()}`;
|
|
91
|
+
if (seen.has(dedupeKey))
|
|
92
|
+
continue;
|
|
93
|
+
seen.add(dedupeKey);
|
|
94
|
+
groups[currentSection].push({
|
|
95
|
+
id: dedupeKey,
|
|
96
|
+
section: currentSection,
|
|
97
|
+
clauseKey,
|
|
98
|
+
visualType,
|
|
99
|
+
value,
|
|
100
|
+
source: rawLine,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return groups;
|
|
105
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaml-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -18,10 +18,6 @@
|
|
|
18
18
|
"types": "./dist/motely.d.ts",
|
|
19
19
|
"import": "./dist/motely.js"
|
|
20
20
|
},
|
|
21
|
-
"./r3f": {
|
|
22
|
-
"types": "./dist/r3f/index.d.ts",
|
|
23
|
-
"import": "./dist/r3f/index.js"
|
|
24
|
-
},
|
|
25
21
|
"./assets/*": "./assets/*",
|
|
26
22
|
"./package.json": "./package.json"
|
|
27
23
|
},
|
|
@@ -32,12 +28,25 @@
|
|
|
32
28
|
"README.md",
|
|
33
29
|
"LICENSE"
|
|
34
30
|
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc --pretty false",
|
|
33
|
+
"dev": "tsc --watch",
|
|
34
|
+
"typecheck": "tsc --noEmit --pretty false",
|
|
35
|
+
"prepack": "npm run build"
|
|
36
|
+
},
|
|
35
37
|
"engines": {
|
|
36
38
|
"node": ">=18"
|
|
37
39
|
},
|
|
38
40
|
"publishConfig": {
|
|
39
|
-
"access": "public"
|
|
40
|
-
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/OptimusPi/jaml-ui"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/OptimusPi/jaml-ui#readme",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/OptimusPi/jaml-ui/issues"
|
|
41
50
|
},
|
|
42
51
|
"keywords": [
|
|
43
52
|
"balatro",
|
|
@@ -51,42 +60,21 @@
|
|
|
51
60
|
"author": "pifreak",
|
|
52
61
|
"license": "MIT",
|
|
53
62
|
"peerDependencies": {
|
|
54
|
-
"
|
|
55
|
-
"@react-three/fiber": "^9.0.0",
|
|
56
|
-
"motely-wasm": ">=5.7.0",
|
|
63
|
+
"motely-wasm": "^10.2.0",
|
|
57
64
|
"react": "^18.2.0 || ^19.0.0",
|
|
58
|
-
"react-dom": "^18.2.0 || ^19.0.0"
|
|
59
|
-
"three": ">=0.170.0 <0.200.0"
|
|
65
|
+
"react-dom": "^18.2.0 || ^19.0.0"
|
|
60
66
|
},
|
|
61
67
|
"peerDependenciesMeta": {
|
|
62
68
|
"motely-wasm": {
|
|
63
69
|
"optional": true
|
|
64
|
-
},
|
|
65
|
-
"@react-spring/three": {
|
|
66
|
-
"optional": true
|
|
67
|
-
},
|
|
68
|
-
"@react-three/fiber": {
|
|
69
|
-
"optional": true
|
|
70
|
-
},
|
|
71
|
-
"three": {
|
|
72
|
-
"optional": true
|
|
73
70
|
}
|
|
74
71
|
},
|
|
75
72
|
"devDependencies": {
|
|
76
|
-
"@react-spring/three": "^10.0.3",
|
|
77
|
-
"@react-three/fiber": "^9.5.0",
|
|
78
73
|
"@types/react": "^19.2.14",
|
|
79
74
|
"@types/react-dom": "^19.2.3",
|
|
80
|
-
"
|
|
81
|
-
"motely-wasm": "^7.0.8",
|
|
75
|
+
"motely-wasm": "^10.2.0",
|
|
82
76
|
"react": "^19.2.4",
|
|
83
77
|
"react-dom": "^19.2.4",
|
|
84
|
-
"three": "^0.183.2",
|
|
85
78
|
"typescript": "^5.9.3"
|
|
86
|
-
},
|
|
87
|
-
"scripts": {
|
|
88
|
-
"build": "tsc --pretty false && node scripts/copy-data.mjs",
|
|
89
|
-
"dev": "tsc --watch",
|
|
90
|
-
"typecheck": "tsc --noEmit --pretty false"
|
|
91
79
|
}
|
|
92
80
|
}
|