codeverse / components /workspace /VSCodeFrame.tsx
shubhjn's picture
fix loop
15b7bb6
"use client";
import { useEffect, useState } from "react";
import { Loader2, ServerCrash, Smartphone } from "lucide-react";
import { EmulatorPane } from "./EmulatorPane";
import { useEmulator } from "@/hooks/useEmulator";
interface VSCodeFrameProps {
workspaceId: string;
}
export function VSCodeFrame({ workspaceId }: VSCodeFrameProps) {
const [status, setStatus] = useState<"loading" | "ready" | "error">("loading");
const [androidPort, setAndroidPort] = useState<string | null>(null);
const [appetizeUrl, setAppetizeUrl] = useState<string | null>(null);
const [buildLogs, setBuildLogs] = useState<string[]>([]);
const {
isOpen,
platform,
refreshKey,
isLoading,
setIsOpen,
toggleOpen,
changePlatform,
refreshIframe,
} = useEmulator("android");
useEffect(() => {
let isMounted = true;
let events: EventSource | null = null;
const checkStatus = async () => {
try {
const res = await fetch(`/api/workspace/status?id=${workspaceId}`);
const data = await res.json();
if (data.ready && isMounted) {
setStatus("ready");
return true;
}
} catch (e) {
console.error("Status probe failed:", e);
}
return false;
};
const init = async () => {
const isReady = await checkStatus();
if (isReady || !isMounted) return;
events = new EventSource(`/api/workspace/stream?id=${workspaceId}&withAndroid=true`);
const handleLog = (e: Event) => {
if (!isMounted) return;
const me = e as MessageEvent;
try {
const msg = JSON.parse(me.data);
setBuildLogs((prev) => [...prev, msg]);
} catch {
setBuildLogs((prev) => [...prev, me.data]);
}
};
const handleReady = (e: Event) => {
if (!isMounted) return;
const me = e as MessageEvent;
try {
const data = JSON.parse(me.data);
if (data.success) {
if (data.appetizeUrl) setAppetizeUrl(data.appetizeUrl);
if (data.androidPort) {
setAndroidPort(String(data.androidPort));
setIsOpen(true);
}
setStatus("ready"); // Instant ready instead of 1.5s delay
} else {
setStatus("error");
}
} catch (e: unknown) {
console.error(`[WATCHDOG:ERR] ${e instanceof Error ? e.message : String(e)}`);
}
if (events) {
events.close();
}
};
const handleError = () => {
if (isMounted) setStatus("error");
if (events) {
events.close();
}
};
events.addEventListener("log", handleLog);
events.addEventListener("ready", handleReady);
events.addEventListener("error", handleError);
};
void init();
return () => {
isMounted = false;
if (events) {
events.close();
events = null;
}
};
}, [workspaceId, setIsOpen]);
if (status === "loading") {
return (
<div className="flex flex-col items-center pt-24 w-full h-full bg-[#050505] text-(--text-muted) space-y-6 overflow-hidden relative">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,rgba(57,211,83,0.03)_0%,transparent_70%)] pointer-events-none" />
<div className="z-10 flex flex-col items-center gap-4">
<Loader2 size={32} className="animate-spin text-(--accent)" />
<p className="text-sm font-bold tracking-widest uppercase animate-pulse text-(--accent)">Syncing Environment...</p>
</div>
<div className="w-full max-w-2xl bg-[#0a0a0a] rounded-xl p-4 font-mono text-[11px] overflow-y-auto h-72 z-10 border border-[#222] shadow-[inset_0_0_40px_rgba(0,0,0,0.8)] relative group">
<div className="absolute top-4 right-4 text-[#333] group-hover:text-zinc-600 transition-colors pointer-events-none uppercase text-[10px] tracking-widest font-bold flex items-center gap-2">
<Smartphone size={12} />
CodeVerse Studio Engine
</div>
{buildLogs.map((log, i) => (
<div key={i} className="mb-0.5 leading-relaxed tracking-tight text-zinc-400">
<span className="text-(--accent) mr-2 opacity-50"></span>
{log}
</div>
))}
<div className="animate-pulse text-(--accent) mt-2 font-bold">_</div>
</div>
<div className="text-[10px] opacity-40 mt-4 italic z-10 font-mono tracking-tighter">
[ORCHESTRATOR] Binding LSPs, mapping virtual volumes, and hydrating Nix profile.
</div>
</div>
);
}
if (status === "error") {
return (
<div className="flex flex-col items-center justify-center w-full h-full bg-(--bg) text-(--error) space-y-4 p-8 text-center">
<div className="p-4 bg-(--error)/10 rounded-full mb-2">
<ServerCrash size={48} className="text-(--error)" />
</div>
<h3 className="text-lg font-bold text-(--text)">Deployment Engine Failure</h3>
<p className="text-sm opacity-80 max-w-md leading-relaxed">
The orchestration layer failed to provision workspace <span className="font-mono text-xs bg-(--border) px-1 rounded">{workspaceId}</span>.
This usually occurs due to Docker socket timeouts or resource exhaustion in limited cloud environments.
</p>
<button
onClick={() => window.location.reload()}
className="mt-4 px-6 py-2 bg-(--error) text-white rounded-lg text-sm font-medium hover:opacity-90 transition-all"
>
Retry Sequence
</button>
</div>
);
}
const targetUrl = `/workspace/${encodeURIComponent(workspaceId)}/`;
return (
<div className="w-full h-full flex overflow-hidden bg-(--bg)">
<div className={`relative h-full transition-all duration-500 ease-in-out ${isOpen ? 'w-[60%]' : 'w-full'}`}>
<iframe
src={targetUrl}
className="w-full h-full border-0 bg-(--bg)"
allow="clipboard-read; clipboard-write; display-capture"
title="CodeVerse Remote Engine"
/>
{!isOpen && (
<button
onClick={toggleOpen}
className="absolute bottom-6 right-6 p-4 bg-(--accent) text-white rounded-full shadow-2xl hover:opacity-90 hover:scale-110 active:scale-95 transition-all z-50 flex items-center justify-center border border-white/20"
title="Open Built-in Emulators"
>
<Smartphone size={22} />
</button>
)}
</div>
{/* Emulator Side Panel */}
{isOpen && (
<div className="w-[40%] h-full flex flex-col min-w-[360px] border-l border-(--border-subtle) bg-(--bg-2) animate-in slide-in-from-right duration-300">
<EmulatorPane
platform={platform}
setPlatform={changePlatform}
refreshKey={refreshKey}
isLoading={isLoading}
onRefresh={refreshIframe}
onClose={() => setIsOpen(false)}
androidPort={androidPort}
appetizeUrl={appetizeUrl}
workspaceId={workspaceId}
/>
</div>
)}
</div>
);
}