211 lines
7.5 KiB
TypeScript
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>
|
|
);
|
|
}
|