AeThex-OS/client/src/pages/mobile-aethex-studio.tsx

460 lines
16 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from "react";
import { useLocation } from "wouter";
import {
Code,
Play,
Save,
FileCode,
ArrowLeft,
CheckCircle,
XCircle,
Loader2,
Copy,
Zap,
} from "lucide-react";
import { haptics } from "@/lib/haptics";
const EXAMPLE_CODE = `reality HelloWorld {
platforms: all
}
journey Greet(name) {
platform: all
notify "Hello, " + name + "!"
}
journey Main() {
platform: all
Greet("World")
}`;
const PASSPORT_EXAMPLE = `import { Passport } from "@aethex.os/core"
reality AuthSystem {
platforms: [web, roblox]
}
journey Login(username) {
platform: all
let passport = Passport(username)
when passport.verify() {
sync passport across [web, roblox]
notify "Welcome, " + username
reveal passport
}
}`;
export default function MobileAethexStudio() {
const [, navigate] = useLocation();
const [code, setCode] = useState(EXAMPLE_CODE);
const [compiledOutput, setCompiledOutput] = useState("");
const [target, setTarget] = useState("javascript");
const [isCompiling, setIsCompiling] = useState(false);
const [compileStatus, setCompileStatus] = useState<"idle" | "success" | "error">("idle");
const [errorMessage, setErrorMessage] = useState("");
const [activeTab, setActiveTab] = useState<"editor" | "output" | "publish">("editor");
// Publishing fields
const [appName, setAppName] = useState("");
const [appDescription, setAppDescription] = useState("");
const [isSaving, setIsSaving] = useState(false);
const handleCompile = async () => {
haptics.medium();
setIsCompiling(true);
setCompileStatus("idle");
setErrorMessage("");
try {
const response = await fetch("/api/aethex/compile", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, target }),
credentials: "include",
});
const data = await response.json();
if (data.success) {
setCompiledOutput(data.output);
setCompileStatus("success");
haptics.success();
} else {
setErrorMessage(data.details || data.error || "Compilation failed");
setCompileStatus("error");
haptics.error();
}
} catch (error) {
console.error("Compilation error:", error);
setErrorMessage("Failed to connect to compiler");
setCompileStatus("error");
haptics.error();
} finally {
setIsCompiling(false);
}
};
const handleSaveApp = async () => {
if (!appName.trim()) {
alert("Please enter an app name");
return;
}
haptics.medium();
setIsSaving(true);
try {
const response = await fetch("/api/aethex/apps", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: appName,
description: appDescription,
source_code: code,
category: "utility",
is_public: true,
targets: [target],
tags: ["mobile-created"],
}),
credentials: "include",
});
const data = await response.json();
if (data.success) {
haptics.success();
alert("App published! Check the App Store.");
setAppName("");
setAppDescription("");
} else {
haptics.error();
alert(data.error || "Failed to publish app");
}
} catch (error) {
console.error("Save error:", error);
haptics.error();
alert("Failed to publish app");
} finally {
setIsSaving(false);
}
};
const handleRunCode = () => {
if (!compiledOutput) {
alert("Please compile the code first");
return;
}
haptics.light();
try {
const sandbox = {
console: {
log: (...args: any[]) => {
const output = args.map((a) => String(a)).join(" ");
alert(`Output: ${output}`);
},
},
};
new Function("console", compiledOutput)(sandbox.console);
haptics.success();
} catch (error) {
console.error("Runtime error:", error);
alert(`Runtime error: ${error}`);
haptics.error();
}
};
const loadExample = (example: string) => {
haptics.light();
if (example === "hello") {
setCode(EXAMPLE_CODE);
} else if (example === "passport") {
setCode(PASSPORT_EXAMPLE);
}
setCompiledOutput("");
setCompileStatus("idle");
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
haptics.success();
alert("Copied to clipboard!");
};
return (
<div className="min-h-screen bg-black text-white pb-20">
{/* Animated background */}
<div className="fixed inset-0 opacity-20 pointer-events-none">
<div className="absolute inset-0 bg-gradient-to-br from-purple-600/10 via-transparent to-pink-600/10" />
<div className="absolute top-0 left-1/4 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl" />
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-pink-500/5 rounded-full blur-3xl" />
</div>
{/* Header */}
<div className="sticky top-0 z-50 bg-black/90 backdrop-blur-xl border-b border-purple-500/20">
<div className="flex items-center justify-between px-4 py-4 safe-area-inset-top">
<button
onClick={() => {
haptics.light();
navigate("/");
}}
className="p-2 rounded-lg bg-purple-500/10 border border-purple-500/30 active:scale-95 transition-transform"
>
<ArrowLeft className="w-5 h-5 text-purple-300" />
</button>
<div className="flex-1 text-center">
<div className="flex items-center justify-center gap-2">
<Code className="w-5 h-5 text-purple-400" />
<h1 className="text-lg font-bold text-white uppercase tracking-wider">AeThex Studio</h1>
</div>
{compileStatus === "success" && (
<p className="text-xs text-green-400 font-mono flex items-center justify-center gap-1 mt-1">
<CheckCircle className="w-3 h-3" /> Compiled
</p>
)}
{compileStatus === "error" && (
<p className="text-xs text-red-400 font-mono flex items-center justify-center gap-1 mt-1">
<XCircle className="w-3 h-3" /> Error
</p>
)}
</div>
<div className="w-10" />
</div>
{/* Tab Navigation */}
<div className="flex border-t border-purple-500/20">
<button
onClick={() => {
haptics.light();
setActiveTab("editor");
}}
className={`flex-1 py-3 text-sm font-semibold uppercase tracking-wider transition-colors ${
activeTab === "editor"
? "text-purple-300 bg-purple-500/10 border-b-2 border-purple-400"
: "text-gray-400"
}`}
>
<FileCode className="w-4 h-4 mx-auto mb-1" />
Editor
</button>
<button
onClick={() => {
haptics.light();
setActiveTab("output");
}}
className={`flex-1 py-3 text-sm font-semibold uppercase tracking-wider transition-colors ${
activeTab === "output"
? "text-purple-300 bg-purple-500/10 border-b-2 border-purple-400"
: "text-gray-400"
}`}
>
<Code className="w-4 h-4 mx-auto mb-1" />
Output
</button>
<button
onClick={() => {
haptics.light();
setActiveTab("publish");
}}
className={`flex-1 py-3 text-sm font-semibold uppercase tracking-wider transition-colors ${
activeTab === "publish"
? "text-purple-300 bg-purple-500/10 border-b-2 border-purple-400"
: "text-gray-400"
}`}
>
<Zap className="w-4 h-4 mx-auto mb-1" />
Publish
</button>
</div>
</div>
{/* Content */}
<div className="relative p-4 space-y-4">
{activeTab === "editor" && (
<>
{/* Target Selection */}
<div className="bg-gradient-to-r from-purple-900/30 to-pink-900/30 border border-purple-500/30 rounded-xl p-4">
<label className="block text-xs text-purple-300 font-mono uppercase mb-2">
Compile Target
</label>
<select
value={target}
onChange={(e) => setTarget(e.target.value)}
className="w-full bg-black/50 border border-purple-500/30 rounded-lg px-4 py-3 text-white font-mono"
>
<option value="javascript">JavaScript (Web)</option>
<option value="roblox">Lua (Roblox)</option>
<option value="uefn">Verse (UEFN)</option>
<option value="unity">C# (Unity)</option>
</select>
</div>
{/* Examples */}
<div className="flex gap-2">
<button
onClick={() => loadExample("hello")}
className="flex-1 py-2 px-3 text-xs font-semibold bg-purple-500/10 border border-purple-500/30 rounded-lg text-purple-300 active:scale-95 transition-transform"
>
Hello World
</button>
<button
onClick={() => loadExample("passport")}
className="flex-1 py-2 px-3 text-xs font-semibold bg-purple-500/10 border border-purple-500/30 rounded-lg text-purple-300 active:scale-95 transition-transform"
>
Passport Auth
</button>
</div>
{/* Code Editor */}
<div className="bg-gradient-to-br from-gray-900 to-gray-800 border border-purple-500/30 rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<label className="text-xs text-purple-300 font-mono uppercase">Source Code</label>
<button
onClick={() => copyToClipboard(code)}
className="p-2 rounded-lg bg-purple-500/10 border border-purple-500/30 active:scale-95 transition-transform"
>
<Copy className="w-4 h-4 text-purple-300" />
</button>
</div>
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
className="w-full h-96 bg-black/50 border border-purple-500/20 rounded-lg p-3 text-sm font-mono text-white resize-none focus:outline-none focus:border-purple-400"
placeholder="Enter AeThex code..."
spellCheck={false}
/>
</div>
{/* Compile Button */}
<button
onClick={handleCompile}
disabled={isCompiling}
className="w-full bg-gradient-to-r from-purple-600 to-pink-600 text-white font-bold py-4 rounded-xl active:scale-98 transition-transform disabled:opacity-50 flex items-center justify-center gap-2"
>
{isCompiling ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Compiling...
</>
) : (
<>
<Zap className="w-5 h-5" />
Compile Code
</>
)}
</button>
</>
)}
{activeTab === "output" && (
<>
{/* Compiled Output */}
<div className="bg-gradient-to-br from-gray-900 to-gray-800 border border-purple-500/30 rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<label className="text-xs text-purple-300 font-mono uppercase">Compiled {target}</label>
{compiledOutput && (
<button
onClick={() => copyToClipboard(compiledOutput)}
className="p-2 rounded-lg bg-purple-500/10 border border-purple-500/30 active:scale-95 transition-transform"
>
<Copy className="w-4 h-4 text-purple-300" />
</button>
)}
</div>
<pre className="w-full h-96 bg-black/50 border border-purple-500/20 rounded-lg p-3 text-xs font-mono text-green-400 overflow-auto">
{compiledOutput || "No output yet. Compile your code first."}
</pre>
</div>
{/* Error Message */}
{errorMessage && (
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-4">
<div className="flex items-start gap-3">
<XCircle className="w-5 h-5 text-red-400 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<p className="text-sm font-semibold text-red-400 mb-1">Compilation Error</p>
<p className="text-xs text-red-300 font-mono">{errorMessage}</p>
</div>
</div>
</div>
)}
{/* Run Button */}
{compiledOutput && (
<button
onClick={handleRunCode}
className="w-full bg-gradient-to-r from-green-600 to-emerald-600 text-white font-bold py-4 rounded-xl active:scale-98 transition-transform flex items-center justify-center gap-2"
>
<Play className="w-5 h-5" />
Run Code
</button>
)}
</>
)}
{activeTab === "publish" && (
<>
{/* Publish Form */}
<div className="space-y-4">
<div className="bg-gradient-to-r from-purple-900/30 to-pink-900/30 border border-purple-500/30 rounded-xl p-4">
<label className="block text-xs text-purple-300 font-mono uppercase mb-2">
App Name
</label>
<input
type="text"
value={appName}
onChange={(e) => setAppName(e.target.value)}
placeholder="My Awesome Game"
className="w-full bg-black/50 border border-purple-500/30 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-purple-400"
/>
</div>
<div className="bg-gradient-to-r from-purple-900/30 to-pink-900/30 border border-purple-500/30 rounded-xl p-4">
<label className="block text-xs text-purple-300 font-mono uppercase mb-2">
Description
</label>
<textarea
value={appDescription}
onChange={(e) => setAppDescription(e.target.value)}
placeholder="Describe what your app does..."
rows={4}
className="w-full bg-black/50 border border-purple-500/30 rounded-lg px-4 py-3 text-white resize-none focus:outline-none focus:border-purple-400"
/>
</div>
<div className="bg-purple-500/10 border border-purple-500/30 rounded-xl p-4">
<p className="text-xs text-purple-300 font-mono">
Your app will be compiled and published to the App Store for others to install and use.
Make sure your code is tested!
</p>
</div>
<button
onClick={handleSaveApp}
disabled={isSaving || !appName.trim()}
className="w-full bg-gradient-to-r from-pink-600 to-purple-600 text-white font-bold py-4 rounded-xl active:scale-98 transition-transform disabled:opacity-50 flex items-center justify-center gap-2"
>
{isSaving ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Publishing...
</>
) : (
<>
<Save className="w-5 h-5" />
Publish to App Store
</>
)}
</button>
</div>
</>
)}
</div>
</div>
);
}