jaml-ui 0.11.0 → 0.11.1
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.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
3
4
|
import Editor from "@monaco-editor/react";
|
|
4
5
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
6
|
// Monaco needs hex strings for its colors API. We strip the leading `#` from
|
|
@@ -40,10 +41,77 @@ const defineBalatroTheme = (monaco) => {
|
|
|
40
41
|
});
|
|
41
42
|
};
|
|
42
43
|
export function JamlCodeEditor({ value, onChange, minHeight = 320, }) {
|
|
44
|
+
const editorRef = useRef(null);
|
|
45
|
+
const monacoRef = useRef(null);
|
|
46
|
+
// Suppress our onChange while we're applying a programmatic edit, so the
|
|
47
|
+
// streamed parent value doesn't loop back through onChange and bounce.
|
|
48
|
+
const suppressEmitRef = useRef(false);
|
|
49
|
+
// Capture initial value for the uncontrolled editor mount; subsequent
|
|
50
|
+
// updates flow through the useEffect below.
|
|
51
|
+
const initialValueRef = useRef(value);
|
|
52
|
+
// Track value across renders so we can apply only the streamed delta when
|
|
53
|
+
// the new value is a strict suffix-extension of what's already in the model.
|
|
54
|
+
const lastSyncedValueRef = useRef(value);
|
|
55
|
+
const handleMount = (editor, m) => {
|
|
56
|
+
editorRef.current = editor;
|
|
57
|
+
monacoRef.current = m;
|
|
58
|
+
};
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const editor = editorRef.current;
|
|
61
|
+
const m = monacoRef.current;
|
|
62
|
+
if (!editor || !m)
|
|
63
|
+
return;
|
|
64
|
+
const model = editor.getModel();
|
|
65
|
+
if (!model)
|
|
66
|
+
return;
|
|
67
|
+
const current = editor.getValue();
|
|
68
|
+
if (current === value) {
|
|
69
|
+
lastSyncedValueRef.current = value;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
suppressEmitRef.current = true;
|
|
73
|
+
try {
|
|
74
|
+
// Streaming-friendly path: when the new value just appends to what
|
|
75
|
+
// Monaco already has, push an insert at end-of-document. Monaco
|
|
76
|
+
// re-tokenizes only from the insertion point — no full-doc churn,
|
|
77
|
+
// no syntax-color strobe, no cursor reset.
|
|
78
|
+
if (value.length > current.length && value.startsWith(current)) {
|
|
79
|
+
const suffix = value.slice(current.length);
|
|
80
|
+
const lastLine = model.getLineCount();
|
|
81
|
+
const lastCol = model.getLineMaxColumn(lastLine);
|
|
82
|
+
model.applyEdits([
|
|
83
|
+
{
|
|
84
|
+
range: new m.Range(lastLine, lastCol, lastLine, lastCol),
|
|
85
|
+
text: suffix,
|
|
86
|
+
forceMoveMarkers: false,
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Real replacement (paste from outside, parent rewrite, undo, etc.).
|
|
92
|
+
editor.executeEdits("", [
|
|
93
|
+
{
|
|
94
|
+
range: model.getFullModelRange(),
|
|
95
|
+
text: value,
|
|
96
|
+
forceMoveMarkers: true,
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
editor.pushUndoStop();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
suppressEmitRef.current = false;
|
|
104
|
+
lastSyncedValueRef.current = value;
|
|
105
|
+
}
|
|
106
|
+
}, [value]);
|
|
43
107
|
return (_jsxs("div", { style: { width: "100%", minHeight, background: JimboColorOption.DARKEST }, children: [_jsx("style", { children: `
|
|
44
108
|
.monaco-editor .iPadShowKeyboard,
|
|
45
109
|
.monaco-editor [class*="iPadShowKeyboard"] { display: none !important; }
|
|
46
|
-
` }), _jsx(Editor, { height: `${minHeight}px`, defaultLanguage: "yaml",
|
|
110
|
+
` }), _jsx(Editor, { height: `${minHeight}px`, defaultLanguage: "yaml", defaultValue: initialValueRef.current, theme: "jaml-balatro-dark", onChange: (next) => {
|
|
111
|
+
if (suppressEmitRef.current)
|
|
112
|
+
return;
|
|
113
|
+
onChange(next ?? "");
|
|
114
|
+
}, onMount: handleMount, beforeMount: defineBalatroTheme, options: {
|
|
47
115
|
minimap: { enabled: false },
|
|
48
116
|
scrollBeyondLastLine: false,
|
|
49
117
|
fontSize: 13,
|
|
@@ -111,8 +111,10 @@ export function JamlIde({ jaml, defaultJaml, onChange, defaultMode = "code", sea
|
|
|
111
111
|
// doesn't flash the visual panel empty.
|
|
112
112
|
const [lastParsedText, setLastParsedText] = useState("");
|
|
113
113
|
const [lastParsedFilter, setLastParsedFilter] = useState(() => jamlTextToVisualFilter(jaml ?? defaultJaml ?? ""));
|
|
114
|
-
// Adjust-state-during-render: reparse when text changes (only if not
|
|
115
|
-
|
|
114
|
+
// Adjust-state-during-render: reparse when text changes (only if not
|
|
115
|
+
// controlled). Gated on `mode === "visual"` so we don't burn CPU parsing
|
|
116
|
+
// on every streamed token while the user is in the .jaml/map/results tab.
|
|
117
|
+
if (visualFilter === undefined && mode === "visual" && text !== lastParsedText) {
|
|
116
118
|
try {
|
|
117
119
|
const parsed = jamlTextToVisualFilter(text);
|
|
118
120
|
setLastParsedText(text);
|
|
@@ -1,62 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { JimboButton } from "../ui/panel.js";
|
|
4
|
+
import { JimboTabs } from "../ui/jimboTabs.js";
|
|
4
5
|
import { JimboColorOption } from "../ui/tokens.js";
|
|
5
|
-
const TABS = [
|
|
6
|
-
{ id: "visual", label: "Visual" },
|
|
7
|
-
{ id: "code", label: ".jaml" },
|
|
8
|
-
{ id: "map", label: "Map" },
|
|
9
|
-
{ id: "results", label: "Results" },
|
|
10
|
-
];
|
|
11
6
|
export function JamlIdeToolbar({ mode, onModeChange, resultCount = 0, className = "", onSearch, isSearching = false }) {
|
|
7
|
+
const tabs = [
|
|
8
|
+
{ id: "visual", label: "Visual" },
|
|
9
|
+
{ id: "code", label: ".jaml" },
|
|
10
|
+
{ id: "map", label: "Map" },
|
|
11
|
+
{ id: "results", label: resultCount > 0 ? `Results (${resultCount})` : "Results" },
|
|
12
|
+
];
|
|
12
13
|
return (_jsxs("div", { className: className, style: {
|
|
13
14
|
display: "flex",
|
|
14
15
|
alignItems: "center",
|
|
15
16
|
justifyContent: "space-between",
|
|
16
17
|
gap: 8,
|
|
17
|
-
padding: "
|
|
18
|
+
padding: "10px 10px 6px",
|
|
18
19
|
borderBottom: `1px solid ${JimboColorOption.PANEL_EDGE}`,
|
|
19
20
|
background: JimboColorOption.DARKEST,
|
|
20
|
-
}, children: [_jsx("
|
|
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
|
+
}, children: [_jsx(JimboTabs, { tabs: tabs, activeTab: mode, onTabChange: (id) => onModeChange(id) }), onSearch ? (_jsx(JimboButton, { tone: isSearching ? "red" : "blue", size: "xs", onClick: onSearch, children: isSearching ? "Stop" : "Search" })) : null] }));
|
|
62
22
|
}
|