upgrades
This commit is contained in:
@@ -205,7 +205,7 @@ fn token(stream: codemirror::StringStream, state: &mut State) -> Result<Option<S
|
|||||||
if state.is_defining_identifier {
|
if state.is_defining_identifier {
|
||||||
"def"
|
"def"
|
||||||
} else {
|
} else {
|
||||||
"variable"
|
"identifier"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rhai::Token::CharConstant(_) => "string-2",
|
rhai::Token::CharConstant(_) => "string-2",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"codemirror": "^5.65.1",
|
"codemirror": "^5.65.1",
|
||||||
|
"lucide-react": "^0.548.0",
|
||||||
"next": "14.0.0",
|
"next": "14.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "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=="],
|
"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=="],
|
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||||
|
|
||||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"codemirror": "^5.65.1",
|
"codemirror": "^5.65.1",
|
||||||
|
"lucide-react": "^0.548.0",
|
||||||
"next": "14.0.0",
|
"next": "14.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "18.2.0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import "@/styles/globals.css";
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Rhai Playground",
|
title: "Minimax",
|
||||||
description: "An interactive Rhai scripting language playground",
|
description: "An interactive Rhai scripting language playground",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,12 +18,6 @@ export default function RootLayout({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.materialdesignicons.com/5.3.45/css/materialdesignicons.min.css"
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body>{children}</body>
|
<body>{children}</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import { loadAllWasm } from "@/utils/wasmLoader";
|
||||||
|
|
||||||
const Playground = dynamic(() => import("@/components/Playground"), {
|
const Playground = dynamic(() => import("@/components/Playground"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -20,14 +21,7 @@ const Playground = dynamic(() => import("@/components/Playground"), {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
Loading{" "}
|
Loading WASM...
|
||||||
<a
|
|
||||||
href="https://github.com/rhaiscript/playground"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Rhai Playground
|
|
||||||
</a>
|
|
||||||
...
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -35,12 +29,24 @@ const Playground = dynamic(() => import("@/components/Playground"), {
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [isClient, setIsClient] = useState(false);
|
const [isClient, setIsClient] = useState(false);
|
||||||
|
const [isWasmLoaded, setIsWasmLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsClient(true);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
id="loading"
|
id="loading"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ if (typeof window !== "undefined") {
|
|||||||
CodeMirror = cm.default;
|
CodeMirror = cm.default;
|
||||||
await import("codemirror/addon/edit/matchbrackets");
|
await import("codemirror/addon/edit/matchbrackets");
|
||||||
await import("codemirror/addon/edit/closebrackets");
|
await import("codemirror/addon/edit/closebrackets");
|
||||||
await import("codemirror/addon/selection/active-line");
|
//await import("codemirror/addon/selection/active-line");
|
||||||
await import("codemirror/addon/comment/comment");
|
await import("codemirror/addon/comment/comment");
|
||||||
// @ts-ignore - CodeMirror addon type issues
|
// @ts-ignore - CodeMirror addon type issues
|
||||||
await import("codemirror/addon/fold/brace-fold");
|
await import("codemirror/addon/fold/brace-fold");
|
||||||
@@ -23,25 +23,12 @@ if (typeof window !== "undefined") {
|
|||||||
// @ts-ignore - CodeMirror addon type issues
|
// @ts-ignore - CodeMirror addon type issues
|
||||||
await import("codemirror/addon/search/match-highlighter");
|
await import("codemirror/addon/search/match-highlighter");
|
||||||
require("codemirror/lib/codemirror.css");
|
require("codemirror/lib/codemirror.css");
|
||||||
require("codemirror/theme/monokai.css");
|
require("codemirror/theme/material-darker.css");
|
||||||
require("codemirror/addon/fold/foldgutter.css");
|
require("codemirror/addon/fold/foldgutter.css");
|
||||||
|
|
||||||
try {
|
|
||||||
await loadRhaiWasm();
|
await loadRhaiWasm();
|
||||||
initRhaiMode(CodeMirror);
|
initRhaiMode(CodeMirror);
|
||||||
console.log('✅ WASM-based Rhai mode initialized successfully');
|
console.log('✅ WASM-based Rhai mode initialized successfully');
|
||||||
} catch (error) {
|
|
||||||
console.warn('⚠️ Failed to load WASM Rhai mode, falling back to JavaScript mode:', error);
|
|
||||||
// Fallback to JavaScript mode if WASM fails
|
|
||||||
CodeMirror.defineMode("rhai", (config: any) => {
|
|
||||||
const jsMode = CodeMirror.getMode(config, "javascript");
|
|
||||||
return {
|
|
||||||
...jsMode,
|
|
||||||
name: "rhai",
|
|
||||||
helperType: "rhai"
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isCodeMirrorReady = true;
|
isCodeMirrorReady = true;
|
||||||
})
|
})
|
||||||
@@ -74,7 +61,7 @@ export const Editor = forwardRef<any, EditorProps>(function Editor(
|
|||||||
const editor = CodeMirror.fromTextArea(textareaRef.current, {
|
const editor = CodeMirror.fromTextArea(textareaRef.current, {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
mode: "rhai",
|
mode: "rhai",
|
||||||
theme: "monokai",
|
theme: "material-darker",
|
||||||
indentUnit: 4,
|
indentUnit: 4,
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
foldGutter: {
|
foldGutter: {
|
||||||
@@ -113,7 +100,7 @@ export const Editor = forwardRef<any, EditorProps>(function Editor(
|
|||||||
editorRef.current = null;
|
editorRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []); // Only run once
|
}, []); // DO NOT FILL ARRAY
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.editorContainer}>
|
<div className={styles.editorContainer}>
|
||||||
|
|||||||
@@ -2,8 +2,19 @@
|
|||||||
|
|
||||||
import { ButtonHTMLAttributes, ReactNode } from "react";
|
import { ButtonHTMLAttributes, ReactNode } from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { Play, Square, Loader2 } from "lucide-react";
|
||||||
import styles from "@/styles/Button.module.css";
|
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
|
interface ButtonProps
|
||||||
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
|
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
|
||||||
variant?:
|
variant?:
|
||||||
@@ -47,10 +58,16 @@ export function Button({
|
|||||||
title={tooltip}
|
title={tooltip}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{iconLeft && !loading && <i className={`mdi mdi-${iconLeft}`} />}
|
{iconLeft && !loading && (() => {
|
||||||
{loading && <i className="mdi mdi-loading mdi-spin" />}
|
const IconComponent = getIcon(iconLeft);
|
||||||
|
return IconComponent ? <IconComponent size={16} /> : null;
|
||||||
|
})()}
|
||||||
|
{loading && <Loader2 size={16} className={styles.spin} />}
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
{iconRight && !loading && <i className={`mdi mdi-${iconRight}`} />}
|
{iconRight && !loading && (() => {
|
||||||
|
const IconComponent = getIcon(iconRight);
|
||||||
|
return IconComponent ? <IconComponent size={16} /> : null;
|
||||||
|
})()}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,19 @@
|
|||||||
|
|
||||||
import { useState, useRef, useEffect, ReactNode } from "react";
|
import { useState, useRef, useEffect, ReactNode } from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { HelpCircle, MoreHorizontal, ChevronDown } from "lucide-react";
|
||||||
import styles from "@/styles/Dropdown.module.css";
|
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 {
|
interface DropdownItem {
|
||||||
text: string;
|
text: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -59,12 +70,13 @@ export function Dropdown({
|
|||||||
onClick={() => !disabled && setIsOpen(!isOpen)}
|
onClick={() => !disabled && setIsOpen(!isOpen)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{triggerIcon && <i className={`mdi mdi-${triggerIcon}`} />}
|
{triggerIcon && (() => {
|
||||||
|
const IconComponent = getIcon(triggerIcon);
|
||||||
|
return IconComponent ? <IconComponent size={16} /> : null;
|
||||||
|
})()}
|
||||||
{trigger && <span>{trigger}</span>}
|
{trigger && <span>{trigger}</span>}
|
||||||
{!triggerIcon && !trigger && (
|
{!triggerIcon && !trigger && <MoreHorizontal size={16} />}
|
||||||
<i className="mdi mdi-dots-horizontal" />
|
<ChevronDown size={16} />
|
||||||
)}
|
|
||||||
<i className="mdi mdi-menu-down" />
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
|
|||||||
@@ -22,7 +22,14 @@ async function initWasm(): Promise<void> {
|
|||||||
self.onmessage = async (event) => {
|
self.onmessage = async (event) => {
|
||||||
const { type, script } = event.data;
|
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 {
|
try {
|
||||||
await initWasm();
|
await initWasm();
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,19 @@
|
|||||||
cursor: wait;
|
cursor: wait;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Button types */
|
/* Button types */
|
||||||
.is-primary {
|
.is-primary {
|
||||||
background: #007bff;
|
background: #007bff;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
.editorContainer textarea {
|
.editorContainer textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 1px solid #ccc;
|
border: none;
|
||||||
font-family: "Consolas", "Monaco", "Courier New", monospace;
|
font-family: "Consolas", "Monaco", "Courier New", monospace;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
/* Enhanced CodeMirror styles from playground */
|
/* Enhanced CodeMirror styles from playground */
|
||||||
.editorContainer :global(.CodeMirror) {
|
.editorContainer :global(.CodeMirror) {
|
||||||
border: 1px solid #ccc;
|
border: none;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
|
|||||||
@@ -71,6 +71,8 @@
|
|||||||
.terminalContainer {
|
.terminalContainer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding: 8px;
|
||||||
|
background: #1D1F21;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ import init, { RhaiMode, init_codemirror_pass } from '@/wasm/rhai-codemirror/rha
|
|||||||
|
|
||||||
let wasmInitialized = false;
|
let wasmInitialized = false;
|
||||||
let wasmModule: any = null;
|
let wasmModule: any = null;
|
||||||
|
let wasmLoadPromise: Promise<any> | null = null;
|
||||||
|
|
||||||
export const loadRhaiWasm = async () => {
|
export const loadRhaiWasm = async () => {
|
||||||
if (wasmInitialized) {
|
if (wasmInitialized) {
|
||||||
return wasmModule;
|
return wasmModule;
|
||||||
}
|
}
|
||||||
|
if (wasmLoadPromise) {
|
||||||
|
return wasmLoadPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasmLoadPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
// Initialize the WASM module
|
// Initialize the WASM module
|
||||||
wasmModule = await init();
|
wasmModule = await init();
|
||||||
@@ -17,8 +22,12 @@ export const loadRhaiWasm = async () => {
|
|||||||
return wasmModule;
|
return wasmModule;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load Rhai WASM module:', error);
|
console.error('Failed to load Rhai WASM module:', error);
|
||||||
|
wasmLoadPromise = null; // Reset on error
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return wasmLoadPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initRhaiMode = (CodeMirror: any) => {
|
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<void> => {
|
||||||
|
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<void>((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 };
|
export { RhaiMode };
|
||||||
Reference in New Issue
Block a user