diff --git a/rust/rhai-codemirror/src/rhai_mode.rs b/rust/rhai-codemirror/src/rhai_mode.rs index d4b14ed..61e1fe0 100644 --- a/rust/rhai-codemirror/src/rhai_mode.rs +++ b/rust/rhai-codemirror/src/rhai_mode.rs @@ -205,7 +205,7 @@ fn token(stream: codemirror::StringStream, state: &mut State) -> Result "string-2", diff --git a/webui/bun.lock b/webui/bun.lock index 6340466..a490d93 100644 --- a/webui/bun.lock +++ b/webui/bun.lock @@ -7,6 +7,7 @@ "@xterm/xterm": "^5.5.0", "clsx": "^2.0.0", "codemirror": "^5.65.1", + "lucide-react": "^0.548.0", "next": "14.0.0", "react": "18.2.0", "react-dom": "18.2.0", @@ -477,6 +478,8 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lucide-react": ["lucide-react@0.548.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], diff --git a/webui/package.json b/webui/package.json index 8d9711e..4196a55 100644 --- a/webui/package.json +++ b/webui/package.json @@ -13,6 +13,7 @@ "@xterm/xterm": "^5.5.0", "clsx": "^2.0.0", "codemirror": "^5.65.1", + "lucide-react": "^0.548.0", "next": "14.0.0", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/webui/src/app/layout.tsx b/webui/src/app/layout.tsx index 5c60c84..492c740 100644 --- a/webui/src/app/layout.tsx +++ b/webui/src/app/layout.tsx @@ -2,7 +2,7 @@ import "@/styles/globals.css"; import { Metadata } from "next"; export const metadata: Metadata = { - title: "Rhai Playground", + title: "Minimax", description: "An interactive Rhai scripting language playground", }; @@ -18,12 +18,6 @@ export default function RootLayout({ }) { return ( - - - {children} ); diff --git a/webui/src/app/page.tsx b/webui/src/app/page.tsx index 6f7d1da..bdb40d8 100644 --- a/webui/src/app/page.tsx +++ b/webui/src/app/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; +import { loadAllWasm } from "@/utils/wasmLoader"; const Playground = dynamic(() => import("@/components/Playground"), { ssr: false, @@ -20,14 +21,7 @@ const Playground = dynamic(() => import("@/components/Playground"), { }} >
- Loading{" "} - - Rhai Playground - - ... + Loading WASM...
), @@ -35,12 +29,24 @@ const Playground = dynamic(() => import("@/components/Playground"), { export default function Home() { const [isClient, setIsClient] = useState(false); + const [isWasmLoaded, setIsWasmLoaded] = useState(false); useEffect(() => { setIsClient(true); + + // Load all WASM modules + loadAllWasm() + .then(() => { + setIsWasmLoaded(true); + }) + .catch((error) => { + console.error('Failed to load WASM modules:', error); + // Still allow the app to load, but WASM features may not work + setIsWasmLoaded(true); + }); }, []); - if (!isClient) { + if (!isClient || !isWasmLoaded) { return (
{ - const jsMode = CodeMirror.getMode(config, "javascript"); - return { - ...jsMode, - name: "rhai", - helperType: "rhai" - }; - }); - } + await loadRhaiWasm(); + initRhaiMode(CodeMirror); + console.log('✅ WASM-based Rhai mode initialized successfully'); isCodeMirrorReady = true; }) @@ -74,7 +61,7 @@ export const Editor = forwardRef(function Editor( const editor = CodeMirror.fromTextArea(textareaRef.current, { lineNumbers: true, mode: "rhai", - theme: "monokai", + theme: "material-darker", indentUnit: 4, matchBrackets: true, foldGutter: { @@ -113,7 +100,7 @@ export const Editor = forwardRef(function Editor( editorRef.current = null; } }; - }, []); // Only run once + }, []); // DO NOT FILL ARRAY return (
diff --git a/webui/src/components/ui/Button.tsx b/webui/src/components/ui/Button.tsx index 18993db..5c32734 100644 --- a/webui/src/components/ui/Button.tsx +++ b/webui/src/components/ui/Button.tsx @@ -2,8 +2,19 @@ import { ButtonHTMLAttributes, ReactNode } from "react"; import clsx from "clsx"; +import { Play, Square, Loader2 } from "lucide-react"; import styles from "@/styles/Button.module.css"; +const iconMap = { + play: Play, + stop: Square, + loading: Loader2, +}; + +function getIcon(iconName: string) { + return iconMap[iconName as keyof typeof iconMap]; +} + interface ButtonProps extends Omit, "type"> { variant?: @@ -47,10 +58,16 @@ export function Button({ title={tooltip} {...props} > - {iconLeft && !loading && } - {loading && } + {iconLeft && !loading && (() => { + const IconComponent = getIcon(iconLeft); + return IconComponent ? : null; + })()} + {loading && } {children} - {iconRight && !loading && } + {iconRight && !loading && (() => { + const IconComponent = getIcon(iconRight); + return IconComponent ? : null; + })()} ); } diff --git a/webui/src/components/ui/Dropdown.tsx b/webui/src/components/ui/Dropdown.tsx index 99036ac..61ff6aa 100644 --- a/webui/src/components/ui/Dropdown.tsx +++ b/webui/src/components/ui/Dropdown.tsx @@ -2,8 +2,19 @@ import { useState, useRef, useEffect, ReactNode } from "react"; import clsx from "clsx"; +import { HelpCircle, MoreHorizontal, ChevronDown } from "lucide-react"; import styles from "@/styles/Dropdown.module.css"; +const iconMap = { + "help-circle": HelpCircle, + "dots-horizontal": MoreHorizontal, + "menu-down": ChevronDown, +}; + +function getIcon(iconName: string) { + return iconMap[iconName as keyof typeof iconMap]; +} + interface DropdownItem { text: string; onClick: () => void; @@ -59,12 +70,13 @@ export function Dropdown({ onClick={() => !disabled && setIsOpen(!isOpen)} disabled={disabled} > - {triggerIcon && } + {triggerIcon && (() => { + const IconComponent = getIcon(triggerIcon); + return IconComponent ? : null; + })()} {trigger && {trigger}} - {!triggerIcon && !trigger && ( - - )} - + {!triggerIcon && !trigger && } + {isOpen && ( diff --git a/webui/src/lib/script-runner.worker.ts b/webui/src/lib/script-runner.worker.ts index 4cbf5bf..995babf 100644 --- a/webui/src/lib/script-runner.worker.ts +++ b/webui/src/lib/script-runner.worker.ts @@ -22,7 +22,14 @@ async function initWasm(): Promise { self.onmessage = async (event) => { const { type, script } = event.data; - if (type === "run") { + if (type === "init") { + try { + await initWasm(); + self.postMessage({ type: "ready" }); + } catch (error) { + self.postMessage({ type: "error", error: String(error) }); + } + } else if (type === "run") { try { await initWasm(); diff --git a/webui/src/styles/Button.module.css b/webui/src/styles/Button.module.css index 20180ab..f8d0462 100644 --- a/webui/src/styles/Button.module.css +++ b/webui/src/styles/Button.module.css @@ -39,6 +39,19 @@ cursor: wait; } +.spin { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + /* Button types */ .is-primary { background: #007bff; diff --git a/webui/src/styles/Editor.module.css b/webui/src/styles/Editor.module.css index 867a0eb..31eb883 100644 --- a/webui/src/styles/Editor.module.css +++ b/webui/src/styles/Editor.module.css @@ -6,7 +6,7 @@ .editorContainer textarea { width: 100%; height: 100%; - border: 1px solid #ccc; + border: none; font-family: "Consolas", "Monaco", "Courier New", monospace; font-size: 14px; line-height: 1.4; @@ -18,7 +18,7 @@ /* Enhanced CodeMirror styles from playground */ .editorContainer :global(.CodeMirror) { - border: 1px solid #ccc; + border: none; height: 100% !important; box-sizing: border-box; font-size: 0.95em; diff --git a/webui/src/styles/Playground.module.css b/webui/src/styles/Playground.module.css index 7a526c1..3924e65 100644 --- a/webui/src/styles/Playground.module.css +++ b/webui/src/styles/Playground.module.css @@ -71,6 +71,8 @@ .terminalContainer { flex: 1; overflow: hidden; + padding: 8px; + background: #1D1F21; } .result { diff --git a/webui/src/utils/wasmLoader.ts b/webui/src/utils/wasmLoader.ts index 908b1b7..51cf413 100644 --- a/webui/src/utils/wasmLoader.ts +++ b/webui/src/utils/wasmLoader.ts @@ -3,22 +3,31 @@ import init, { RhaiMode, init_codemirror_pass } from '@/wasm/rhai-codemirror/rha let wasmInitialized = false; let wasmModule: any = null; +let wasmLoadPromise: Promise | null = null; export const loadRhaiWasm = async () => { if (wasmInitialized) { return wasmModule; } - - try { - // Initialize the WASM module - wasmModule = await init(); - wasmInitialized = true; - - return wasmModule; - } catch (error) { - console.error('Failed to load Rhai WASM module:', error); - throw error; + if (wasmLoadPromise) { + return wasmLoadPromise; } + + wasmLoadPromise = (async () => { + try { + // Initialize the WASM module + wasmModule = await init(); + wasmInitialized = true; + + return wasmModule; + } catch (error) { + console.error('Failed to load Rhai WASM module:', error); + wasmLoadPromise = null; // Reset on error + throw error; + } + })(); + + return wasmLoadPromise; }; export const initRhaiMode = (CodeMirror: any) => { @@ -35,4 +44,41 @@ export const initRhaiMode = (CodeMirror: any) => { }); }; +// Function to preload all WASM modules used by the application +export const loadAllWasm = async (): Promise => { + try { + // Load Rhai CodeMirror WASM + await loadRhaiWasm(); + + // Load Script Runner WASM by creating and immediately terminating a worker + const worker = new Worker(new URL('../lib/script-runner.worker.ts', import.meta.url)); + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + worker.terminate(); + reject(new Error('Script runner WASM load timeout')); + }, 10000); + + worker.postMessage({ type: 'init' }); + worker.onmessage = (event) => { + if (event.data.type === 'ready') { + clearTimeout(timeout); + worker.terminate(); + resolve(); + } + }; + + worker.onerror = (error) => { + clearTimeout(timeout); + worker.terminate(); + reject(error); + }; + }); + + console.log('✅ All WASM modules loaded successfully'); + } catch (error) { + console.error('❌ Failed to load WASM modules:', error); + throw error; + } +}; + export { RhaiMode }; \ No newline at end of file