jaml-ui 0.7.2 → 0.9.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/dist/assets.js +1 -1
- package/dist/components/JamlCodeEditor.d.ts +7 -0
- package/dist/components/JamlCodeEditor.js +58 -0
- package/dist/components/JamlIde.d.ts +4 -0
- package/dist/components/JamlIde.js +36 -13
- package/dist/components/JamlIdeToolbar.js +43 -2
- package/dist/components/JamlIdeVisual.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/jamlVisualFilter.d.ts +9 -0
- package/dist/utils/jamlVisualFilter.js +194 -0
- package/package.json +9 -2
package/dist/assets.js
CHANGED
|
@@ -12,7 +12,7 @@ export const JAML_ASSET_FILES = {
|
|
|
12
12
|
};
|
|
13
13
|
const assetKeyByFileName = Object.fromEntries(Object.entries(JAML_ASSET_FILES).map(([key, fileName]) => [fileName, key]));
|
|
14
14
|
// Keep in lockstep with package.json version. Upload assets to this path when publishing.
|
|
15
|
-
const JAML_UI_VERSION = "0.
|
|
15
|
+
const JAML_UI_VERSION = "0.8.0";
|
|
16
16
|
const CDN_BASE = `https://cdn.seedfinder.app/jaml-ui/${JAML_UI_VERSION}/assets/`;
|
|
17
17
|
const defaultAssetUrls = {
|
|
18
18
|
deck: `${CDN_BASE}${JAML_ASSET_FILES.deck}`,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface JamlCodeEditorProps {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange: (value: string) => void;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
minHeight?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function JamlCodeEditor({ value, onChange, minHeight, }: JamlCodeEditorProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import Editor from "@monaco-editor/react";
|
|
4
|
+
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
|
+
// Monaco needs hex strings for its colors API. We strip the leading `#` from
|
|
6
|
+
// JimboColor tokens where Monaco expects raw hex for syntax rules (token
|
|
7
|
+
// foreground), and pass the full `#...` form for UI colors. Alpha suffix
|
|
8
|
+
// (e.g. WHITE + "20") is valid for Monaco colors but not for rules.
|
|
9
|
+
const hex = (token) => token.replace(/^#/, "");
|
|
10
|
+
const defineBalatroTheme = (monaco) => {
|
|
11
|
+
monaco.editor.defineTheme("jaml-balatro-dark", {
|
|
12
|
+
base: "vs-dark",
|
|
13
|
+
inherit: true,
|
|
14
|
+
rules: [
|
|
15
|
+
{ token: "comment", foreground: hex(JimboColorOption.GREY), fontStyle: "italic" },
|
|
16
|
+
{ token: "keyword", foreground: hex(JimboColorOption.RED) },
|
|
17
|
+
{ token: "string", foreground: hex(JimboColorOption.GOLD_TEXT) },
|
|
18
|
+
{ token: "number", foreground: hex(JimboColorOption.BLUE) },
|
|
19
|
+
{ token: "type", foreground: hex(JimboColorOption.GREEN_TEXT) },
|
|
20
|
+
],
|
|
21
|
+
colors: {
|
|
22
|
+
"editor.background": JimboColorOption.DARKEST,
|
|
23
|
+
"editor.foreground": JimboColorOption.WHITE,
|
|
24
|
+
"editorLineNumber.foreground": JimboColorOption.GREY,
|
|
25
|
+
"editorLineNumber.activeForeground": JimboColorOption.GOLD_TEXT,
|
|
26
|
+
"editor.selectionBackground": `${JimboColorOption.WHITE}20`,
|
|
27
|
+
"editor.inactiveSelectionBackground": `${JimboColorOption.WHITE}10`,
|
|
28
|
+
"editor.lineHighlightBackground": `${JimboColorOption.BLACK}20`,
|
|
29
|
+
"editorCursor.foreground": JimboColorOption.GOLD_TEXT,
|
|
30
|
+
"editorWidget.background": JimboColorOption.DARK_GREY,
|
|
31
|
+
"editorWidget.border": `${JimboColorOption.WHITE}20`,
|
|
32
|
+
"editorWidget.foreground": JimboColorOption.WHITE,
|
|
33
|
+
"list.activeSelectionBackground": JimboColorOption.GOLD,
|
|
34
|
+
"list.activeSelectionForeground": JimboColorOption.DARKEST,
|
|
35
|
+
"list.hoverBackground": JimboColorOption.PANEL_EDGE,
|
|
36
|
+
"list.hoverForeground": JimboColorOption.WHITE,
|
|
37
|
+
"list.focusBackground": JimboColorOption.GOLD,
|
|
38
|
+
"list.focusForeground": JimboColorOption.DARKEST,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
export function JamlCodeEditor({ value, onChange, minHeight = 320, }) {
|
|
43
|
+
return (_jsx("div", { style: { width: "100%", minHeight, background: JimboColorOption.DARKEST }, children: _jsx(Editor, { height: `${minHeight}px`, defaultLanguage: "yaml", value: value, theme: "jaml-balatro-dark", onChange: (next) => onChange(next ?? ""), beforeMount: defineBalatroTheme, options: {
|
|
44
|
+
minimap: { enabled: false },
|
|
45
|
+
scrollBeyondLastLine: false,
|
|
46
|
+
fontSize: 13,
|
|
47
|
+
lineHeight: 22,
|
|
48
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
49
|
+
lineNumbers: "on",
|
|
50
|
+
automaticLayout: true,
|
|
51
|
+
padding: { top: 12, bottom: 12 },
|
|
52
|
+
wordWrap: "on",
|
|
53
|
+
formatOnPaste: true,
|
|
54
|
+
formatOnType: true,
|
|
55
|
+
renderLineHighlight: "line",
|
|
56
|
+
scrollbar: { verticalScrollbarSize: 8, horizontalScrollbarSize: 8 },
|
|
57
|
+
} }) }));
|
|
58
|
+
}
|
|
@@ -22,6 +22,10 @@ export interface JamlIdeProps {
|
|
|
22
22
|
codePlaceholder?: string;
|
|
23
23
|
onSearch?: () => void;
|
|
24
24
|
isSearching?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Controlled visual filter. When provided alongside `onVisualFilterChange`, the Visual tab
|
|
27
|
+
* is fully controlled by the parent. When absent, the Visual tab auto-derives from the text.
|
|
28
|
+
*/
|
|
25
29
|
visualFilter?: JamlVisualFilter;
|
|
26
30
|
onVisualFilterChange?: (filter: JamlVisualFilter) => void;
|
|
27
31
|
}
|
|
@@ -4,7 +4,9 @@ import { useMemo, useState } from "react";
|
|
|
4
4
|
import { JamlMapPreview } from "./JamlMapPreview.js";
|
|
5
5
|
import { JamlIdeToolbar } from "./JamlIdeToolbar.js";
|
|
6
6
|
import { JamlIdeVisual } from "./JamlIdeVisual.js";
|
|
7
|
+
import { JamlCodeEditor } from "./JamlCodeEditor.js";
|
|
7
8
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
9
|
+
import { jamlTextToVisualFilter, visualFilterToJamlText } from "../utils/jamlVisualFilter.js";
|
|
8
10
|
function TallyBar({ value, max }) {
|
|
9
11
|
const pct = max > 0 ? Math.min(1, value / max) : 0;
|
|
10
12
|
return (_jsx("div", { style: { flex: 1, height: 4, borderRadius: 999, background: `${JimboColorOption.DARK_GREY}88`, overflow: "hidden" }, children: _jsx("div", { style: {
|
|
@@ -93,6 +95,7 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
93
95
|
const [mode, setMode] = useState(defaultMode);
|
|
94
96
|
const [internalText, setInternalText] = useState(jaml ?? defaultJaml ?? "");
|
|
95
97
|
const [lastJamlProp, setLastJamlProp] = useState(jaml);
|
|
98
|
+
// Adjust-state-during-render: sync controlled `jaml` prop into internal text.
|
|
96
99
|
if (jaml !== lastJamlProp) {
|
|
97
100
|
setLastJamlProp(jaml);
|
|
98
101
|
if (jaml !== undefined)
|
|
@@ -103,6 +106,38 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
103
106
|
setInternalText(next);
|
|
104
107
|
onChange?.(next);
|
|
105
108
|
};
|
|
109
|
+
// Derived visual filter state (used only when not externally controlled).
|
|
110
|
+
// Cache the last successfully parsed filter so a mid-edit invalid state
|
|
111
|
+
// doesn't flash the visual panel empty.
|
|
112
|
+
const [lastParsedText, setLastParsedText] = useState("");
|
|
113
|
+
const [lastParsedFilter, setLastParsedFilter] = useState(() => jamlTextToVisualFilter(jaml ?? defaultJaml ?? ""));
|
|
114
|
+
// Adjust-state-during-render: reparse when text changes (only if not controlled).
|
|
115
|
+
if (visualFilter === undefined && text !== lastParsedText) {
|
|
116
|
+
try {
|
|
117
|
+
const parsed = jamlTextToVisualFilter(text);
|
|
118
|
+
setLastParsedText(text);
|
|
119
|
+
setLastParsedFilter(parsed);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Keep previous filter on parse error — don't flash empty.
|
|
123
|
+
setLastParsedText(text);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const activeFilter = visualFilter ?? lastParsedFilter;
|
|
127
|
+
const handleVisualFilterChange = (next) => {
|
|
128
|
+
if (onVisualFilterChange) {
|
|
129
|
+
// Controlled: let parent own both.
|
|
130
|
+
onVisualFilterChange(next);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Uncontrolled: round-trip through text so textarea stays source of truth.
|
|
134
|
+
const nextText = visualFilterToJamlText(next);
|
|
135
|
+
setInternalText(nextText);
|
|
136
|
+
setLastParsedFilter(next);
|
|
137
|
+
setLastParsedText(nextText);
|
|
138
|
+
onChange?.(nextText);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
106
141
|
const results = useMemo(() => searchResults, [searchResults]);
|
|
107
142
|
return (_jsxs("div", { className: className, style: {
|
|
108
143
|
display: "flex",
|
|
@@ -122,17 +157,5 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
122
157
|
padding: "10px 14px",
|
|
123
158
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
124
159
|
background: JimboColorOption.TEAL_GREY,
|
|
125
|
-
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, fontWeight: 800, 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"
|
|
126
|
-
width: "100%",
|
|
127
|
-
minHeight: 320,
|
|
128
|
-
resize: "vertical",
|
|
129
|
-
border: 0,
|
|
130
|
-
outline: 0,
|
|
131
|
-
padding: 16,
|
|
132
|
-
background: "transparent",
|
|
133
|
-
color: JimboColorOption.WHITE,
|
|
134
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
135
|
-
fontSize: 13,
|
|
136
|
-
lineHeight: 1.7,
|
|
137
|
-
} })) : null, mode === "map" ? _jsx(JamlMapPreview, { jaml: text }) : null, mode === "results" ? (_jsx("div", { style: { padding: 12 }, children: _jsx(ResultsView, { results: results }) })) : null] })] }));
|
|
160
|
+
}, children: [_jsxs("div", { children: [_jsx("div", { style: { fontSize: 14, fontWeight: 800, 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(JamlMapPreview, { jaml: text }) : null, mode === "results" ? (_jsx("div", { style: { padding: 12 }, children: _jsx(ResultsView, { results: results }) })) : null] })] }));
|
|
138
161
|
}
|
|
@@ -4,7 +4,7 @@ import { JimboButton } from "../ui/panel.js";
|
|
|
4
4
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
5
|
const TABS = [
|
|
6
6
|
{ id: "visual", label: "Visual" },
|
|
7
|
-
{ id: "code", label: "
|
|
7
|
+
{ id: "code", label: ".jaml" },
|
|
8
8
|
{ id: "map", label: "Map" },
|
|
9
9
|
{ id: "results", label: "Results" },
|
|
10
10
|
];
|
|
@@ -17,5 +17,46 @@ export function JamlIdeToolbar({ mode, onModeChange, resultCount = 0, className
|
|
|
17
17
|
padding: "6px 10px",
|
|
18
18
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
19
19
|
background: JimboColorOption.DARKEST,
|
|
20
|
-
}, children: [_jsx("div", { style: {
|
|
20
|
+
}, children: [_jsx("div", { style: {
|
|
21
|
+
display: "flex",
|
|
22
|
+
alignItems: "stretch",
|
|
23
|
+
gap: 0,
|
|
24
|
+
background: `${JimboColorOption.DARK_GREY}cc`,
|
|
25
|
+
borderRadius: 7,
|
|
26
|
+
border: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
27
|
+
padding: 2,
|
|
28
|
+
overflow: "hidden",
|
|
29
|
+
}, children: TABS.map((tab) => {
|
|
30
|
+
const isActive = mode === tab.id;
|
|
31
|
+
return (_jsxs("button", { type: "button", onClick: () => onModeChange(tab.id), style: {
|
|
32
|
+
display: "flex",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
gap: 5,
|
|
35
|
+
padding: "3px 10px",
|
|
36
|
+
border: "none",
|
|
37
|
+
borderRadius: 5,
|
|
38
|
+
cursor: "pointer",
|
|
39
|
+
fontFamily: "m6x11plus, monospace",
|
|
40
|
+
fontSize: 10,
|
|
41
|
+
letterSpacing: 1,
|
|
42
|
+
textTransform: "uppercase",
|
|
43
|
+
lineHeight: 1.2,
|
|
44
|
+
transition: "background 80ms, color 80ms, box-shadow 80ms",
|
|
45
|
+
color: isActive ? JimboColorOption.DARKEST : JimboColorOption.GREY,
|
|
46
|
+
background: isActive ? JimboColorOption.GOLD : "transparent",
|
|
47
|
+
boxShadow: isActive ? `0 2px 0 #8a6a1e` : "none",
|
|
48
|
+
textShadow: isActive ? "none" : "none",
|
|
49
|
+
userSelect: "none",
|
|
50
|
+
position: "relative",
|
|
51
|
+
transform: isActive ? "translateY(0)" : "translateY(0)",
|
|
52
|
+
}, children: [tab.label, tab.id === "results" && resultCount > 0 ? (_jsx("span", { style: {
|
|
53
|
+
borderRadius: 999,
|
|
54
|
+
background: isActive ? `${JimboColorOption.DARKEST}44` : `${JimboColorOption.GOLD}33`,
|
|
55
|
+
color: isActive ? JimboColorOption.DARKEST : JimboColorOption.GOLD_TEXT,
|
|
56
|
+
padding: "0px 5px",
|
|
57
|
+
fontSize: 9,
|
|
58
|
+
fontFamily: "monospace",
|
|
59
|
+
letterSpacing: 0,
|
|
60
|
+
}, children: resultCount })) : null] }, tab.id));
|
|
61
|
+
}) }), onSearch ? (_jsx(JimboButton, { tone: isSearching ? "red" : "blue", size: "xs", onClick: onSearch, children: isSearching ? "Stop" : "Search" })) : null] }));
|
|
21
62
|
}
|
|
@@ -35,7 +35,7 @@ function DragClausePill({ clause, zone, onDragStart, }) {
|
|
|
35
35
|
borderRadius: 6, padding: "5px 8px 5px 4px",
|
|
36
36
|
boxShadow: `0 2px 0 ${JimboColorOption.BLACK}`,
|
|
37
37
|
cursor: "grab", userSelect: "none", touchAction: "none",
|
|
38
|
-
}, children: [_jsx("div", { style: { color: JimboColorOption.GREY, fontSize: 12, lineHeight: 1, padding: "0 2px" }, children: "\u22EE\u22EE" }), _jsx(ClauseSprite, { clause: clause, size: 26 }), _jsx("div", { style: { fontSize: 10, color: JimboColorOption.WHITE, letterSpacing: 1, textShadow:
|
|
38
|
+
}, children: [_jsx("div", { style: { color: JimboColorOption.GREY, fontSize: 12, lineHeight: 1, padding: "0 2px" }, children: "\u22EE\u22EE" }), _jsx(ClauseSprite, { clause: clause, size: 26 }), _jsx("div", { style: { fontSize: 10, color: JimboColorOption.WHITE, letterSpacing: 1, textShadow: `1px 1px 0 ${JimboColorOption.BLACK}cc` }, children: clause.label || clause.value }), clause.antes && clause.antes.length > 0 && (_jsxs("div", { style: { display: "flex", gap: 2 }, children: [clause.antes.slice(0, 3).map((a) => (_jsx("div", { style: { fontSize: 8, padding: "0 3px", background: JimboColorOption.DARKEST, color: z.color, borderRadius: 2 }, children: a }, a))), clause.antes.length > 3 && _jsxs("div", { style: { fontSize: 8, color: JimboColorOption.GREY }, children: ["+", clause.antes.length - 3] })] })), clause.score != null && (_jsxs("div", { style: { fontSize: 9, padding: "0 4px", background: JimboColorOption.RED, color: JimboColorOption.WHITE, borderRadius: 2 }, children: ["+", clause.score] }))] }));
|
|
39
39
|
}
|
|
40
40
|
function ZoneDropRail({ zone, clauses, onDragStart, highlight, }) {
|
|
41
41
|
const z = ZONE_META[zone];
|
|
@@ -47,7 +47,7 @@ function ZoneDropRail({ zone, clauses, onDragStart, highlight, }) {
|
|
|
47
47
|
}, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }, children: [_jsx("div", { style: {
|
|
48
48
|
fontSize: 10, letterSpacing: 2, padding: "2px 8px",
|
|
49
49
|
background: z.color, color: JimboColorOption.WHITE, borderRadius: 3,
|
|
50
|
-
textShadow:
|
|
50
|
+
textShadow: `1px 1px 0 ${JimboColorOption.BLACK}cc`,
|
|
51
51
|
}, children: z.label }), _jsx("div", { style: { flex: 1, height: 1, background: `${z.color}44` } }), _jsx("div", { style: { fontSize: 8, color: JimboColorOption.GREY }, children: clauses.length })] }), _jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: [clauses.map((c) => (_jsx(DragClausePill, { clause: c, zone: zone, onDragStart: onDragStart }, c.id))), clauses.length === 0 && (_jsx("div", { style: { fontSize: 10, color: JimboColorOption.GREY, padding: 10, fontStyle: "italic" }, children: "drop clauses here" }))] })] }));
|
|
52
52
|
}
|
|
53
53
|
export function JamlIdeVisual({ filter, onChange, onSave, onBack }) {
|
|
@@ -103,11 +103,11 @@ export function JamlIdeVisual({ filter, onChange, onSave, onBack }) {
|
|
|
103
103
|
return (_jsxs("div", { ref: rootRef, style: { display: "flex", flexDirection: "column", gap: 10, padding: 10 }, children: [_jsxs("div", { style: {
|
|
104
104
|
background: JimboColorOption.DARK_GREY, border: `2px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
105
105
|
borderRadius: 6, padding: 8, boxShadow: `0 2px 0 ${JimboColorOption.BLACK}`,
|
|
106
|
-
}, children: [_jsx("div", { style: { fontSize: 9, color: JimboColorOption.GREY, letterSpacing: 2 }, children: "FILE" }), _jsxs("div", { style: { fontSize: 14, color: JimboColorOption.WHITE, textShadow:
|
|
106
|
+
}, children: [_jsx("div", { style: { fontSize: 9, color: JimboColorOption.GREY, letterSpacing: 2 }, children: "FILE" }), _jsxs("div", { style: { fontSize: 14, color: JimboColorOption.WHITE, textShadow: `1px 1px 0 ${JimboColorOption.BLACK}cc` }, children: [filter.name || "Untitled", ".jaml"] }), filter.author && (_jsxs("div", { style: { fontSize: 9, color: JimboColorOption.GOLD_TEXT, marginTop: 2 }, children: ["by ", filter.author] }))] }), _jsx(ZoneDropRail, { zone: "must", clauses: filter.must, onDragStart: onDragStart, highlight: hoverZone === "must" }), _jsx(ZoneDropRail, { zone: "should", clauses: filter.should, onDragStart: onDragStart, highlight: hoverZone === "should" }), _jsx(ZoneDropRail, { zone: "mustnot", clauses: filter.mustnot, onDragStart: onDragStart, highlight: hoverZone === "mustnot" }), drag && (_jsx("div", { style: {
|
|
107
107
|
position: "fixed",
|
|
108
108
|
left: drag.x - drag.offX, top: drag.y - drag.offY,
|
|
109
109
|
pointerEvents: "none", zIndex: 999,
|
|
110
110
|
transform: "rotate(-2deg) scale(1.05)",
|
|
111
|
-
filter:
|
|
111
|
+
filter: `drop-shadow(0 4px 6px ${JimboColorOption.BLACK}99)`, opacity: 0.92,
|
|
112
112
|
}, children: _jsx(DragClausePill, { clause: drag.clause, zone: drag.fromZone, onDragStart: () => { } }) }))] }));
|
|
113
113
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { AnalyzerExplorer, type AnalyzerAnteView, type AnalyzerBadge, type Analy
|
|
|
6
6
|
export { JamlMapPreview, type JamlMapPreviewProps } from "./components/JamlMapPreview.js";
|
|
7
7
|
export { JamlIde, type JamlIdeProps, type JamlIdeSearchResult, type JamlVisualFilter, type JamlVisualClause, type JamlZone, } from "./components/JamlIde.js";
|
|
8
8
|
export { JamlIdeVisual, type JamlIdeVisualProps, } from "./components/JamlIdeVisual.js";
|
|
9
|
+
export { JamlCodeEditor, type JamlCodeEditorProps, } from "./components/JamlCodeEditor.js";
|
|
9
10
|
export { JamlIdeToolbar, type JamlIdeMode, type JamlIdeToolbarProps, } from "./components/JamlIdeToolbar.js";
|
|
10
11
|
export { CardList, type CardListProps } from "./components/CardList.js";
|
|
11
12
|
export { extractVisualJamlItems, type JamlPreviewGroups, type JamlPreviewItem, type JamlPreviewSection, type JamlPreviewVisualType, } from "./utils/jamlMapPreview.js";
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { AnalyzerExplorer, } from "./components/AnalyzerExplorer.js";
|
|
|
7
7
|
export { JamlMapPreview } from "./components/JamlMapPreview.js";
|
|
8
8
|
export { JamlIde, } from "./components/JamlIde.js";
|
|
9
9
|
export { JamlIdeVisual, } from "./components/JamlIdeVisual.js";
|
|
10
|
+
export { JamlCodeEditor, } from "./components/JamlCodeEditor.js";
|
|
10
11
|
export { JamlIdeToolbar, } from "./components/JamlIdeToolbar.js";
|
|
11
12
|
export { CardList } from "./components/CardList.js";
|
|
12
13
|
export { extractVisualJamlItems, } from "./utils/jamlMapPreview.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for converting between JAML text and JamlVisualFilter.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally does NOT depend on a YAML library — uses the same
|
|
5
|
+
* line-by-line approach as jamlMapPreview.ts to stay zero-dep.
|
|
6
|
+
*/
|
|
7
|
+
import type { JamlVisualFilter } from "../components/JamlIdeVisual.js";
|
|
8
|
+
export declare function jamlTextToVisualFilter(text: string): JamlVisualFilter;
|
|
9
|
+
export declare function visualFilterToJamlText(filter: JamlVisualFilter): string;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for converting between JAML text and JamlVisualFilter.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally does NOT depend on a YAML library — uses the same
|
|
5
|
+
* line-by-line approach as jamlMapPreview.ts to stay zero-dep.
|
|
6
|
+
*/
|
|
7
|
+
// ─── Text → Filter ────────────────────────────────────────────────────────────
|
|
8
|
+
function stripQuotes(s) {
|
|
9
|
+
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
10
|
+
return s.slice(1, -1).trim();
|
|
11
|
+
}
|
|
12
|
+
return s;
|
|
13
|
+
}
|
|
14
|
+
function parseScalarValue(raw) {
|
|
15
|
+
const v = stripQuotes(raw.trim().replace(/,$/, "").trim());
|
|
16
|
+
return v || null;
|
|
17
|
+
}
|
|
18
|
+
function parseInlineList(raw) {
|
|
19
|
+
const t = raw.trim();
|
|
20
|
+
if (t.startsWith("[") && t.includes("]")) {
|
|
21
|
+
const body = t.slice(1, t.indexOf("]"));
|
|
22
|
+
return body
|
|
23
|
+
.split(",")
|
|
24
|
+
.map((s) => parseScalarValue(s))
|
|
25
|
+
.filter((s) => s !== null);
|
|
26
|
+
}
|
|
27
|
+
const v = parseScalarValue(t);
|
|
28
|
+
return v ? [v] : [];
|
|
29
|
+
}
|
|
30
|
+
function topLevelScalar(lines, key) {
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
const m = new RegExp(`^${key}:\\s*(.+)$`).exec(line.trim());
|
|
33
|
+
if (m)
|
|
34
|
+
return stripQuotes(m[1].trim());
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const CLAUSE_ZONE_KEYS = new Set([
|
|
39
|
+
"joker", "jokers", "commonJoker", "commonJokers", "uncommonJoker", "uncommonJokers",
|
|
40
|
+
"rareJoker", "rareJokers", "mixedJoker", "mixedJokers", "soulJoker", "legendaryJoker",
|
|
41
|
+
"voucher", "vouchers",
|
|
42
|
+
"tarot", "tarotCard", "spectral", "spectralCard", "planet", "planetCard",
|
|
43
|
+
"boss", "bosses",
|
|
44
|
+
"tag", "tags", "smallBlindTag", "bigBlindTag", "smallblindtag", "bigblindtag",
|
|
45
|
+
]);
|
|
46
|
+
// JAML uses "mustnot" as zone key in some contexts; the visual filter uses "mustnot".
|
|
47
|
+
// The text format may use "mustnot" or "must_not" — handle both, normalise to "mustnot".
|
|
48
|
+
function sectionToZone(raw) {
|
|
49
|
+
if (raw === "must")
|
|
50
|
+
return "must";
|
|
51
|
+
if (raw === "should")
|
|
52
|
+
return "should";
|
|
53
|
+
if (raw === "mustnot" || raw === "must_not" || raw === "mustNot")
|
|
54
|
+
return "mustnot";
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let _uid = 0;
|
|
58
|
+
function uid() {
|
|
59
|
+
return `clause-${++_uid}`;
|
|
60
|
+
}
|
|
61
|
+
export function jamlTextToVisualFilter(text) {
|
|
62
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
63
|
+
const filter = { must: [], should: [], mustnot: [] };
|
|
64
|
+
filter.name = topLevelScalar(lines, "name");
|
|
65
|
+
filter.author = topLevelScalar(lines, "author");
|
|
66
|
+
filter.description = topLevelScalar(lines, "description");
|
|
67
|
+
filter.deck = topLevelScalar(lines, "deck");
|
|
68
|
+
filter.stake = topLevelScalar(lines, "stake");
|
|
69
|
+
let zone = null;
|
|
70
|
+
let current = null;
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
function flushClause() {
|
|
73
|
+
if (!current || !zone)
|
|
74
|
+
return;
|
|
75
|
+
const dedupeKey = `${zone}:${current.type}:${current.value.toLowerCase()}`;
|
|
76
|
+
if (!seen.has(dedupeKey)) {
|
|
77
|
+
seen.add(dedupeKey);
|
|
78
|
+
const clause = { id: uid(), type: current.type, value: current.value };
|
|
79
|
+
if (current.antes && current.antes.length > 0)
|
|
80
|
+
clause.antes = current.antes;
|
|
81
|
+
if (current.score !== undefined)
|
|
82
|
+
clause.score = current.score;
|
|
83
|
+
if (current.edition)
|
|
84
|
+
clause.edition = current.edition;
|
|
85
|
+
clause.label = current.value;
|
|
86
|
+
filter[zone].push(clause);
|
|
87
|
+
}
|
|
88
|
+
current = null;
|
|
89
|
+
}
|
|
90
|
+
for (const rawLine of lines) {
|
|
91
|
+
const trimmed = rawLine.trim();
|
|
92
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
93
|
+
continue;
|
|
94
|
+
// Top-level section header: must:/should:/mustnot:
|
|
95
|
+
const sectionMatch = /^(must|should|mustnot|must_not|mustNot):\s*$/.exec(trimmed);
|
|
96
|
+
if (sectionMatch) {
|
|
97
|
+
flushClause();
|
|
98
|
+
zone = sectionToZone(sectionMatch[1]);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Top-level key (non-section) resets zone
|
|
102
|
+
const indent = rawLine.search(/\S|$/);
|
|
103
|
+
if (indent === 0 && /^[A-Za-z]/.test(trimmed) && !trimmed.startsWith("-")) {
|
|
104
|
+
flushClause();
|
|
105
|
+
if (!sectionToZone(trimmed.replace(/:.*/, "")))
|
|
106
|
+
zone = null;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!zone)
|
|
110
|
+
continue;
|
|
111
|
+
// New clause start: " - rareJoker: Blueprint"
|
|
112
|
+
const clauseStart = /^-\s*([A-Za-z][A-Za-z0-9]*):\s*(.*?)\s*$/.exec(trimmed);
|
|
113
|
+
if (clauseStart) {
|
|
114
|
+
flushClause();
|
|
115
|
+
const type = clauseStart[1];
|
|
116
|
+
const rawVal = clauseStart[2];
|
|
117
|
+
if (!CLAUSE_ZONE_KEYS.has(type))
|
|
118
|
+
continue;
|
|
119
|
+
const value = parseScalarValue(rawVal) ?? "Any";
|
|
120
|
+
current = { type, value };
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
// Continuation line inside a clause: " antes: [1,2,3]"
|
|
124
|
+
if (current && indent > 0 && !trimmed.startsWith("-")) {
|
|
125
|
+
const contMatch = /^([A-Za-z_][A-Za-z0-9_]*):\s*(.*?)\s*$/.exec(trimmed);
|
|
126
|
+
if (contMatch) {
|
|
127
|
+
const key = contMatch[1];
|
|
128
|
+
const val = contMatch[2];
|
|
129
|
+
if (key === "antes") {
|
|
130
|
+
const nums = parseInlineList(val)
|
|
131
|
+
.map(Number)
|
|
132
|
+
.filter((n) => !isNaN(n));
|
|
133
|
+
current.antes = nums;
|
|
134
|
+
}
|
|
135
|
+
else if (key === "score") {
|
|
136
|
+
const n = Number(val);
|
|
137
|
+
if (!isNaN(n))
|
|
138
|
+
current.score = n;
|
|
139
|
+
}
|
|
140
|
+
else if (key === "edition") {
|
|
141
|
+
current.edition = parseScalarValue(val) ?? undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
flushClause();
|
|
147
|
+
return filter;
|
|
148
|
+
}
|
|
149
|
+
// ─── Filter → Text ───────────────────────────────────────────────────────────
|
|
150
|
+
function q(s) {
|
|
151
|
+
if (!s)
|
|
152
|
+
return "";
|
|
153
|
+
return /[:#\[\]{}|>&*!,'"?]/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
|
|
154
|
+
}
|
|
155
|
+
function serializeClause(clause) {
|
|
156
|
+
let out = ` - ${clause.type}: ${q(clause.value)}\n`;
|
|
157
|
+
if (clause.antes && clause.antes.length > 0) {
|
|
158
|
+
out += ` antes: [${clause.antes.join(", ")}]\n`;
|
|
159
|
+
}
|
|
160
|
+
if (clause.score !== undefined) {
|
|
161
|
+
out += ` score: ${clause.score}\n`;
|
|
162
|
+
}
|
|
163
|
+
if (clause.edition) {
|
|
164
|
+
out += ` edition: ${q(clause.edition)}\n`;
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
export function visualFilterToJamlText(filter) {
|
|
169
|
+
const parts = [];
|
|
170
|
+
if (filter.name)
|
|
171
|
+
parts.push(`name: ${q(filter.name)}`);
|
|
172
|
+
if (filter.author)
|
|
173
|
+
parts.push(`author: ${q(filter.author)}`);
|
|
174
|
+
if (filter.description)
|
|
175
|
+
parts.push(`description: ${q(filter.description)}`);
|
|
176
|
+
if (filter.deck)
|
|
177
|
+
parts.push(`deck: ${q(filter.deck)}`);
|
|
178
|
+
if (filter.stake)
|
|
179
|
+
parts.push(`stake: ${q(filter.stake)}`);
|
|
180
|
+
const zones = [
|
|
181
|
+
{ key: "must", label: "must", clauses: filter.must },
|
|
182
|
+
{ key: "should", label: "should", clauses: filter.should },
|
|
183
|
+
{ key: "mustnot", label: "mustnot", clauses: filter.mustnot },
|
|
184
|
+
];
|
|
185
|
+
for (const { label, clauses } of zones) {
|
|
186
|
+
if (clauses.length > 0) {
|
|
187
|
+
parts.push(`${label}:`);
|
|
188
|
+
for (const c of clauses) {
|
|
189
|
+
parts.push(serializeClause(c).trimEnd());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return parts.join("\n") + "\n";
|
|
194
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaml-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc --pretty false",
|
|
39
39
|
"dev": "tsc --watch",
|
|
40
|
+
"demo": "vite --config demo/vite.config.ts",
|
|
40
41
|
"typecheck": "tsc --noEmit --pretty false",
|
|
41
42
|
"prepack": "npm run build"
|
|
42
43
|
},
|
|
@@ -66,8 +67,10 @@
|
|
|
66
67
|
"author": "pifreak",
|
|
67
68
|
"license": "MIT",
|
|
68
69
|
"peerDependencies": {
|
|
70
|
+
"@monaco-editor/react": ">=4.0.0",
|
|
69
71
|
"@react-spring/three": ">=9.0.0",
|
|
70
72
|
"@react-three/fiber": ">=8.0.0",
|
|
73
|
+
"monaco-editor": ">=0.50.0",
|
|
71
74
|
"motely-wasm": "^10.2.0 || ^11.0.0 || ^12.0.0",
|
|
72
75
|
"react": "^18.2.0 || ^19.0.0",
|
|
73
76
|
"react-dom": "^18.2.0 || ^19.0.0",
|
|
@@ -92,16 +95,20 @@
|
|
|
92
95
|
}
|
|
93
96
|
},
|
|
94
97
|
"devDependencies": {
|
|
98
|
+
"@monaco-editor/react": "^4.7.0",
|
|
95
99
|
"@react-spring/three": "^10.0.3",
|
|
96
100
|
"@react-three/fiber": "^9.6.0",
|
|
97
101
|
"@types/react": "^19.2.14",
|
|
98
102
|
"@types/react-dom": "^19.2.3",
|
|
99
103
|
"@types/three": "^0.184.0",
|
|
104
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
105
|
+
"monaco-editor": "^0.55.1",
|
|
100
106
|
"motely-wasm": "^12.0.0",
|
|
101
107
|
"react": "^19.2.4",
|
|
102
108
|
"react-dom": "^19.2.4",
|
|
103
109
|
"react-icons": "^5.6.0",
|
|
104
110
|
"three": "^0.184.0",
|
|
105
|
-
"typescript": "^5.9.3"
|
|
111
|
+
"typescript": "^5.9.3",
|
|
112
|
+
"vite": "^8.0.9"
|
|
106
113
|
}
|
|
107
114
|
}
|