aethex-studio/src/components/aethex/export-modal.tsx

211 lines
7.5 KiB
TypeScript

"use client";
import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Checkbox } from "@/components/ui/checkbox";
import { Progress } from "@/components/ui/progress";
import {
Download,
Folder,
FileCode,
Package,
Check,
Loader2,
} from "lucide-react";
import { cn } from "@/lib/utils";
interface ExportModalProps {
open: boolean;
onClose: () => void;
platform: "roblox" | "uefn" | "spatial" | "web";
}
const exportFormats = {
roblox: [
{ id: "rbxlx", name: "Roblox Place (.rbxlx)", description: "Full place file with all assets" },
{ id: "rbxmx", name: "Roblox Model (.rbxmx)", description: "Model file for importing" },
{ id: "lua", name: "Lua Scripts (.lua)", description: "Export scripts only" },
],
uefn: [
{ id: "uproject", name: "UEFN Project", description: "Full Unreal project structure" },
{ id: "verse", name: "Verse Scripts", description: "Export Verse code only" },
{ id: "pak", name: "Package (.pak)", description: "Compiled game package" },
],
spatial: [
{ id: "spatial", name: "Spatial Package", description: "Ready for Spatial.io upload" },
{ id: "unity", name: "Unity Export", description: "Unity asset format" },
{ id: "gltf", name: "glTF Models", description: "3D models only" },
],
web: [
{ id: "html", name: "Static HTML", description: "Single HTML file with embedded code" },
{ id: "zip", name: "Project ZIP", description: "Full project archive" },
{ id: "npm", name: "NPM Package", description: "Ready for npm publish" },
],
};
const platformColors = {
roblox: "text-red-400",
uefn: "text-purple-400",
spatial: "text-emerald-400",
web: "text-blue-400",
};
export function ExportModal({ open, onClose, platform }: ExportModalProps) {
const [selectedFormat, setSelectedFormat] = useState(exportFormats[platform][0].id);
const [exportName, setExportName] = useState("my-project");
const [includeAssets, setIncludeAssets] = useState(true);
const [minify, setMinify] = useState(false);
const [isExporting, setIsExporting] = useState(false);
const [exportProgress, setExportProgress] = useState(0);
const [exportComplete, setExportComplete] = useState(false);
const handleExport = async () => {
setIsExporting(true);
setExportProgress(0);
setExportComplete(false);
// Simulate export progress
for (let i = 0; i <= 100; i += 10) {
await new Promise(resolve => setTimeout(resolve, 200));
setExportProgress(i);
}
setIsExporting(false);
setExportComplete(true);
// Trigger download
const content = `-- Exported from AeThex Studio\n-- Platform: ${platform}\n-- Format: ${selectedFormat}\n\nprint("Hello from AeThex!")`;
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${exportName}.${selectedFormat === 'lua' ? 'lua' : 'zip'}`;
a.click();
URL.revokeObjectURL(url);
};
const resetAndClose = () => {
setExportComplete(false);
setExportProgress(0);
onClose();
};
return (
<Dialog open={open} onOpenChange={resetAndClose}>
<DialogContent className="max-w-lg p-0 gap-0 bg-[#12121a] border-white/10">
<DialogHeader className="px-6 py-4 border-b border-white/10">
<DialogTitle className="flex items-center gap-2 text-lg">
<Download className="h-5 w-5" />
Export Project
</DialogTitle>
</DialogHeader>
<div className="p-6 space-y-6">
{/* Platform indicator */}
<div className={cn("flex items-center gap-2 text-sm", platformColors[platform])}>
<Package className="h-4 w-4" />
<span className="capitalize font-medium">{platform} Export</span>
</div>
{/* Export name */}
<div className="space-y-2">
<Label>Export Name</Label>
<Input
value={exportName}
onChange={(e) => setExportName(e.target.value)}
placeholder="my-project"
className="bg-white/5 border-white/10"
/>
</div>
{/* Format selection */}
<div className="space-y-3">
<Label>Export Format</Label>
<RadioGroup value={selectedFormat} onValueChange={setSelectedFormat}>
{exportFormats[platform].map((format) => (
<div
key={format.id}
className={cn(
"flex items-start gap-3 p-3 rounded-lg border transition-colors cursor-pointer",
selectedFormat === format.id
? "border-purple-500 bg-purple-500/10"
: "border-white/10 hover:border-white/20"
)}
onClick={() => setSelectedFormat(format.id)}
>
<RadioGroupItem value={format.id} className="mt-0.5" />
<div>
<div className="font-medium text-sm">{format.name}</div>
<div className="text-xs text-muted-foreground">{format.description}</div>
</div>
</div>
))}
</RadioGroup>
</div>
{/* Options */}
<div className="space-y-3">
<Label>Options</Label>
<div className="space-y-2">
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={includeAssets}
onCheckedChange={(v) => setIncludeAssets(!!v)}
/>
<span className="text-sm">Include assets (images, sounds, models)</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={minify}
onCheckedChange={(v) => setMinify(!!v)}
/>
<span className="text-sm">Minify code for production</span>
</label>
</div>
</div>
{/* Progress */}
{(isExporting || exportComplete) && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span>{exportComplete ? "Export complete!" : "Exporting..."}</span>
<span>{exportProgress}%</span>
</div>
<Progress value={exportProgress} className="h-2" />
</div>
)}
</div>
<div className="flex items-center justify-end gap-2 px-6 py-4 border-t border-white/10">
<Button variant="ghost" onClick={resetAndClose}>Cancel</Button>
<Button
onClick={handleExport}
disabled={isExporting || !exportName.trim()}
className="bg-purple-600 hover:bg-purple-500 gap-2"
>
{isExporting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Exporting...
</>
) : exportComplete ? (
<>
<Check className="h-4 w-4" />
Downloaded
</>
) : (
<>
<Download className="h-4 w-4" />
Export
</>
)}
</Button>
</div>
</DialogContent>
</Dialog>
);
}