File size: 3,638 Bytes
5a0b87c 5dc2f11 2e54937 8c27738 5a0b87c 8c27738 5a0b87c 5dc2f11 e9a73cb 5a0b87c 5dc2f11 5a0b87c 5dc2f11 5a0b87c 5dc2f11 2e54937 5a0b87c 5dc2f11 5a0b87c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | "use client";
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
import { useSearchParams } from "next/navigation";
import { Toaster, toast } from "sonner";
import { VSCodeFrame } from "@/components/workspace/VSCodeFrame";
import { AIAssistantSidebar } from "@/components/workspace/AIAssistantSidebar";
import { WorkspaceHeader } from "@/components/workspace/WorkspaceHeader";
import type { Session } from "next-auth";
// Dynamic Dashboard import
const Dashboard = dynamic(() => import("@/components/dashboard/Dashboard"), { ssr: false });
export default function IDEClient({ session }: { session: Session | null }) {
const searchParams = useSearchParams();
const workspaceParam = searchParams?.get("workspace");
const [isAiOpen, setIsAiOpen] = useState(false);
const [theme] = useState<"dark" | "light">("dark");
const [refreshKey, setRefreshKey] = useState(0);
// Apply theme globally
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
// Cleanup if needed
}, [theme]);
// Keyboard shortcut for AI
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === "i") {
e.preventDefault();
setIsAiOpen(prev => !prev);
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
// If no specific workspace is requested, render the Firebase-style Dashboard Control Plane
if (!workspaceParam) {
return (
<div data-theme={theme} className="h-dvh flex flex-col overflow-hidden bg-(--bg)">
<div className="flex-1 overflow-hidden">
<Dashboard />
</div>
<Toaster position="bottom-right" theme={theme} richColors />
</div>
);
}
const handleRebuild = async () => {
const promise = fetch("/api/workspace", {
method: "POST",
headers: { "Content-Type": "application/json" },
// Hard-code Android true for demo purposes to match VSCodeFrame auto-starts
body: JSON.stringify({ action: "rebuild", id: workspaceParam, image: 'codercom/code-server:latest', withAndroidEmulator: true })
}).then(async res => {
const data = await res.json();
if (!data.success) throw new Error(data.error);
return data;
});
toast.promise(promise, {
loading: "Rebuilding Environment... This may take a minute.",
success: () => {
// Force VSCodeFrame to remount to fetch the new ports
setRefreshKey(k => k + 1);
return "Rebuild complete!";
},
error: "Failed to rebuild workspace."
});
};
// Otherwise, render the dedicated VS Code Server instance mapped to this workspace
return (
<div data-theme={theme} className="h-dvh w-screen flex flex-col bg-(--bg) overflow-hidden relative">
<WorkspaceHeader
workspaceId={workspaceParam}
session={session}
isAiOpen={isAiOpen}
onAiToggle={() => setIsAiOpen(!isAiOpen)}
onRebuild={handleRebuild}
/>
<div className="flex-1 flex relative w-full h-full overflow-hidden">
<main className={`flex-1 relative transition-all duration-300 ${isAiOpen ? "mr-0 md:mr-80 lg:mr-96" : "mr-0"}`}>
<VSCodeFrame key={refreshKey} workspaceId={workspaceParam} />
</main>
<AIAssistantSidebar
workspaceName={workspaceParam}
isOpen={isAiOpen}
onClose={() => setIsAiOpen(false)}
/>
</div>
<Toaster position="bottom-right" theme={theme} richColors />
</div>
);
}
|