diff --git a/bun.lock b/bun.lock
index e33e95c..a417029 100644
--- a/bun.lock
+++ b/bun.lock
@@ -37,6 +37,7 @@
"clsx": "^2.1.1",
"devicons-react": "^1.4.0",
"dockerode": "^4.0.4",
+ "dockview": "^4.2.1",
"github-markdown-css": "^5.8.1",
"lucide-react": "^0.482.0",
"monaco-editor": "<=0.36.1",
@@ -717,6 +718,10 @@
"dockerode": ["dockerode@4.0.4", "https://registry.npmmirror.com/dockerode/-/dockerode-4.0.4.tgz", { "dependencies": { "@balena/dockerignore": "^1.0.2", "@grpc/grpc-js": "^1.11.1", "@grpc/proto-loader": "^0.7.13", "docker-modem": "^5.0.6", "protobufjs": "^7.3.2", "tar-fs": "~2.0.1", "uuid": "^10.0.0" } }, "sha512-6GYP/EdzEY50HaOxTVTJ2p+mB5xDHTMJhS+UoGrVyS6VC+iQRh7kZ4FRpUYq6nziby7hPqWhOrFFUFTMUZJJ5w=="],
+ "dockview": ["dockview@4.2.1", "https://registry.npmmirror.com/dockview/-/dockview-4.2.1.tgz", { "dependencies": { "dockview-core": "^4.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-P6T4JiM5yuHa8JH0E/BuPpCv8EMLDoCXtvS169ITRRKfgi+zF98AUrnhW8F+FOXV6QS/5Dtt9ca5YxWgtcQsOQ=="],
+
+ "dockview-core": ["dockview-core@4.2.1", "https://registry.npmmirror.com/dockview-core/-/dockview-core-4.2.1.tgz", {}, "sha512-KaEOMzMdQvWB9e3iRQf9BqerB1sX43wAIhla5uGzkA+irag9wz0F5bkVZyJ5mVqJgqrQdWh+W8j94+L2wY0AmA=="],
+
"doctrine": ["doctrine@2.1.0", "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
"dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
diff --git a/package.json b/package.json
index 44dbd99..c8795d5 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"clsx": "^2.1.1",
"devicons-react": "^1.4.0",
"dockerode": "^4.0.4",
+ "dockview": "^4.2.1",
"github-markdown-css": "^5.8.1",
"lucide-react": "^0.482.0",
"monaco-editor": "<=0.36.1",
diff --git a/src/app/(app)/problems/[id]/@Code/layout.tsx b/src/app/(app)/problems/[id]/@Code/layout.tsx
new file mode 100644
index 0000000..337197a
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Code/layout.tsx
@@ -0,0 +1,18 @@
+import { WorkspaceEditorHeader } from "@/components/features/playground/workspace/editor/components/header";
+import { WorkspaceEditorFooter } from "@/components/features/playground/workspace/editor/components/footer";
+
+interface CodeLayoutProps {
+ children: React.ReactNode;
+}
+
+export default function CodeLayout({ children }: CodeLayoutProps) {
+ return (
+
+ );
+}
diff --git a/src/app/(app)/problems/[id]/@Code/page.tsx b/src/app/(app)/problems/[id]/@Code/page.tsx
new file mode 100644
index 0000000..7b1c9f7
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Code/page.tsx
@@ -0,0 +1,9 @@
+import { ProblemEditor } from "@/components/problem-editor";
+
+export default function CodePage() {
+ return (
+
+ );
+}
diff --git a/src/app/(app)/problems/[id]/@Description/layout.tsx b/src/app/(app)/problems/[id]/@Description/layout.tsx
new file mode 100644
index 0000000..d94c10e
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Description/layout.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { notFound } from "next/navigation";
+import { useProblem } from "@/hooks/use-problem";
+import ProblemDescriptionFooter from "@/components/features/playground/problem/description/footer";
+
+interface DescriptionLayoutProps {
+ children: React.ReactNode;
+}
+
+export default function DescriptionLayout({ children }: DescriptionLayoutProps) {
+ const { problem } = useProblem();
+
+ if (!problem) {
+ notFound();
+ }
+
+ return (
+
+ );
+}
diff --git a/src/app/(app)/problems/[id]/@Description/page.tsx b/src/app/(app)/problems/[id]/@Description/page.tsx
new file mode 100644
index 0000000..5a9b16a
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Description/page.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import { notFound } from "next/navigation";
+import { useProblem } from "@/hooks/use-problem";
+import MdxPreview from "@/components/mdx-preview";
+import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
+
+export default function DescriptionPage() {
+ const { problem } = useProblem();
+
+ if (!problem) {
+ notFound();
+ }
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/[id]/@Solutions/layout.tsx b/src/app/(app)/problems/[id]/@Solutions/layout.tsx
new file mode 100644
index 0000000..a1aa42a
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Solutions/layout.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import { notFound } from "next/navigation";
+import { useProblem } from "@/hooks/use-problem";
+import ProblemSolutionFooter from "@/components/features/playground/problem/solution/footer";
+
+interface SolutionsLayoutProps {
+ children: React.ReactNode;
+}
+
+export default function SolutionsLayout({
+ children,
+}: SolutionsLayoutProps) {
+ const { problem } = useProblem();
+
+ if (!problem) {
+ notFound();
+ }
+
+ return (
+
+ );
+}
diff --git a/src/app/(app)/problems/[id]/@Solutions/page.tsx b/src/app/(app)/problems/[id]/@Solutions/page.tsx
new file mode 100644
index 0000000..d76b819
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Solutions/page.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import { notFound } from "next/navigation";
+import { useProblem } from "@/hooks/use-problem";
+import MdxPreview from "@/components/mdx-preview";
+import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
+
+export default function SolutionsPage() {
+ const { problem } = useProblem();
+
+ if (!problem) {
+ notFound();
+ }
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/[id]/@Submissions/page.tsx b/src/app/(app)/problems/[id]/@Submissions/page.tsx
new file mode 100644
index 0000000..6c443b7
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Submissions/page.tsx
@@ -0,0 +1,3 @@
+export default function SubmissionsPage() {
+ return Submissions
;
+}
diff --git a/src/app/(app)/problems/[id]/@TestResult/page.tsx b/src/app/(app)/problems/[id]/@TestResult/page.tsx
new file mode 100644
index 0000000..f71967c
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@TestResult/page.tsx
@@ -0,0 +1,3 @@
+export default function TestResultPage() {
+ return Test Result
;
+}
diff --git a/src/app/(app)/problems/[id]/@Testcase/page.tsx b/src/app/(app)/problems/[id]/@Testcase/page.tsx
new file mode 100644
index 0000000..53b838e
--- /dev/null
+++ b/src/app/(app)/problems/[id]/@Testcase/page.tsx
@@ -0,0 +1,3 @@
+export default function TestcasePage() {
+ return Testcase
;
+}
diff --git a/src/app/(app)/problems/[id]/@problem/@description/layout.tsx b/src/app/(app)/problems/[id]/@problem/@description/layout.tsx
deleted file mode 100644
index c5072f7..0000000
--- a/src/app/(app)/problems/[id]/@problem/@description/layout.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-
-interface ProblemDescriptionLayoutProps {
- children: React.ReactNode;
-}
-
-export default function ProblemDescriptionLayout({
- children,
-}: ProblemDescriptionLayoutProps) {
- return (
-
- }>
- {children}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@problem/@description/page.tsx b/src/app/(app)/problems/[id]/@problem/@description/page.tsx
deleted file mode 100644
index 95bac59..0000000
--- a/src/app/(app)/problems/[id]/@problem/@description/page.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-"use client";
-
-import { notFound } from "next/navigation";
-import { useProblem } from "@/hooks/use-problem";
-import MdxPreview from "@/components/mdx-preview";
-import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
-import ProblemDescriptionFooter from "@/components/features/playground/problem/description/footer";
-
-export default function ProblemDescriptionPage() {
- const { problem } = useProblem();
-
- if (!problem) {
- notFound();
- }
-
- return (
- <>
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/src/app/(app)/problems/[id]/@problem/@solution/layout.tsx b/src/app/(app)/problems/[id]/@problem/@solution/layout.tsx
deleted file mode 100644
index 0fad412..0000000
--- a/src/app/(app)/problems/[id]/@problem/@solution/layout.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-
-interface ProblemSolutionLayoutProps {
- children: React.ReactNode;
-}
-
-export default function ProblemSolutionLayout({
- children,
-}: ProblemSolutionLayoutProps) {
- return (
-
- }>
- {children}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@problem/@solution/page.tsx b/src/app/(app)/problems/[id]/@problem/@solution/page.tsx
deleted file mode 100644
index 2776527..0000000
--- a/src/app/(app)/problems/[id]/@problem/@solution/page.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-"use client";
-
-import { useProblem } from "@/hooks/use-problem";
-import MdxPreview from "@/components/mdx-preview";
-import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
-import ProblemSolutionFooter from "@/components/features/playground/problem/solution/footer";
-
-export default function ProblemSolutionPage() {
- const { problem } = useProblem();
-
- return (
- <>
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/src/app/(app)/problems/[id]/@problem/layout.tsx b/src/app/(app)/problems/[id]/@problem/layout.tsx
deleted file mode 100644
index c691e09..0000000
--- a/src/app/(app)/problems/[id]/@problem/layout.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { CircleCheckBigIcon, FileTextIcon, FlaskConicalIcon } from "lucide-react";
-
-interface ProblemLayoutProps {
- description: React.ReactNode;
- solution: React.ReactNode;
- submission: React.ReactNode;
-}
-
-export default function ProblemLayout({
- description,
- solution,
- submission,
-}: ProblemLayoutProps) {
- return (
-
-
-
-
-
- Description
-
-
-
- Solution
-
-
-
- Submission
-
-
-
-
-
- {description}
-
-
- {solution}
-
-
- {submission}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@terminal/@testcase/layout.tsx b/src/app/(app)/problems/[id]/@terminal/@testcase/layout.tsx
deleted file mode 100644
index b236e82..0000000
--- a/src/app/(app)/problems/[id]/@terminal/@testcase/layout.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-
-interface TerminalTestcaseLayoutProps {
- children: React.ReactNode;
-}
-
-export default function TerminalTestcaseLayout({
- children,
-}: TerminalTestcaseLayoutProps) {
- return (
-
-
- }>
- {children}
-
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@terminal/@testcase/page.tsx b/src/app/(app)/problems/[id]/@terminal/@testcase/page.tsx
deleted file mode 100644
index 92342ef..0000000
--- a/src/app/(app)/problems/[id]/@terminal/@testcase/page.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { RadioIcon } from "lucide-react";
-
-export default function TerminalTestcasePage() {
- return (
-
-
-
-
-
-
-
-
Launching in v0.0.1
-
- Expected release date: March 16 at 6:00 PM.
-
-
-
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@terminal/layout.tsx b/src/app/(app)/problems/[id]/@terminal/layout.tsx
deleted file mode 100644
index de8dcde..0000000
--- a/src/app/(app)/problems/[id]/@terminal/layout.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { SquareCheckIcon } from "lucide-react";
-import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-
-interface TerminalLayoutProps {
- testcase: React.ReactNode;
-}
-
-export default function TerminalLayout({ testcase }: TerminalLayoutProps) {
- return (
-
-
-
-
-
- Testcase
-
-
-
-
-
- {testcase}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@workspace/@editor/layout.tsx b/src/app/(app)/problems/[id]/@workspace/@editor/layout.tsx
deleted file mode 100644
index f159a71..0000000
--- a/src/app/(app)/problems/[id]/@workspace/@editor/layout.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Suspense } from "react";
-import { Loading } from "@/components/loading";
-
-interface WorkspaceEditorLayoutProps {
- children: React.ReactNode;
-}
-
-export default function WorkspaceEditorLayout({
- children,
-}: WorkspaceEditorLayoutProps) {
- return (
-
- }>
- {children}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/@workspace/@editor/page.tsx b/src/app/(app)/problems/[id]/@workspace/@editor/page.tsx
deleted file mode 100644
index 1ce5bed..0000000
--- a/src/app/(app)/problems/[id]/@workspace/@editor/page.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ProblemEditor } from "@/components/problem-editor";
-import { WorkspaceEditorHeader } from "@/components/features/playground/workspace/editor/components/header";
-import { WorkspaceEditorFooter } from "@/components/features/playground/workspace/editor/components/footer";
-
-export default function WorkspaceEditorPage() {
- return (
- <>
-
-
-
- >
- );
-}
diff --git a/src/app/(app)/problems/[id]/@workspace/layout.tsx b/src/app/(app)/problems/[id]/@workspace/layout.tsx
deleted file mode 100644
index 93850be..0000000
--- a/src/app/(app)/problems/[id]/@workspace/layout.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { SquarePenIcon } from "lucide-react";
-import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-
-interface WorkspaceLayoutProps {
- editor: React.ReactNode;
-}
-
-export default function WorkspaceLayout({ editor }: WorkspaceLayoutProps) {
- return (
-
-
-
-
-
- Editor
-
-
-
-
-
- {editor}
-
-
- );
-}
diff --git a/src/app/(app)/problems/[id]/layout.tsx b/src/app/(app)/problems/[id]/layout.tsx
index 83edc23..95f5f1c 100644
--- a/src/app/(app)/problems/[id]/layout.tsx
+++ b/src/app/(app)/problems/[id]/layout.tsx
@@ -1,28 +1,28 @@
import prisma from "@/lib/prisma";
-import {
- ResizableHandle,
- ResizablePanel,
- ResizablePanelGroup,
-} from "@/components/ui/resizable";
import { notFound } from "next/navigation";
+import DockView from "@/components/dockview";
import { ProblemStoreProvider } from "@/providers/problem-store-provider";
import { PlaygroundHeader } from "@/components/features/playground/header";
-interface PlaygroundLayoutProps {
+interface ProblemProps {
params: Promise<{ id: string }>;
- problem: React.ReactNode;
- workspace: React.ReactNode;
- terminal: React.ReactNode;
- ai: React.ReactNode;
+ Description: React.ReactNode;
+ Solutions: React.ReactNode;
+ Submissions: React.ReactNode;
+ Code: React.ReactNode;
+ Testcase: React.ReactNode;
+ TestResult: React.ReactNode;
}
-export default async function PlaygroundLayout({
+export default async function ProblemLayout({
params,
- problem,
- workspace,
- terminal,
- ai,
-}: PlaygroundLayoutProps) {
+ Description,
+ Solutions,
+ Submissions,
+ Code,
+ Testcase,
+ TestResult,
+}: ProblemProps) {
const { id } = await params;
const [
@@ -45,7 +45,7 @@ export default async function PlaygroundLayout({
const { templates, ...problemWithoutTemplates } = problemData;
return (
-
+
-
-
- {problem}
-
-
-
-
-
- {workspace}
-
-
-
- {terminal}
-
-
-
-
-
- {ai}
-
-
+
diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts
deleted file mode 100644
index 4db317c..0000000
--- a/src/app/actions/auth.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-"use server";
-
-import bcrypt from "bcrypt";
-import prisma from "@/lib/prisma";
-import { signIn } from "@/lib/auth";
-import { authSchema } from "@/lib/zod";
-import { CredentialsSignInFormValues } from "@/components/credentials-sign-in-form";
-import { CredentialsSignUpFormValues } from "@/components/credentials-sign-up-form";
-
-const saltRounds = 10;
-
-export async function signInWithCredentials(formData: CredentialsSignInFormValues) {
- try {
- // Parse credentials using authSchema for validation
- const { email, password } = await authSchema.parseAsync(formData);
-
- // Find user by email
- const user = await prisma.user.findUnique({ where: { email } });
-
- // Check if the user exists
- if (!user) {
- throw new Error("User not found.");
- }
-
- // Check if the user has a password
- if (!user.password) {
- throw new Error("Invalid credentials.");
- }
-
- // Check if the password matches
- const passwordMatch = await bcrypt.compare(password, user.password);
- if (!passwordMatch) {
- throw new Error("Incorrect password.");
- }
-
- await signIn("credentials", { ...formData, redirect: false });
- return { success: true };
- } catch (error) {
- return { error: error instanceof Error ? error.message : "Failed to sign in. Please try again." };
- }
-}
-
-export async function signUpWithCredentials(formData: CredentialsSignUpFormValues) {
- try {
- const validatedData = await authSchema.parseAsync(formData);
-
- // Check if user already exists
- const existingUser = await prisma.user.findUnique({ where: { email: validatedData.email } });
- if (existingUser) {
- throw new Error("User already exists");
- }
-
- // Hash password and create user
- const pwHash = await bcrypt.hash(validatedData.password, saltRounds);
- const user = await prisma.user.create({
- data: { email: validatedData.email, password: pwHash },
- });
-
- // Assign admin role if first user
- const userCount = await prisma.user.count();
- if (userCount === 1) {
- await prisma.user.update({ where: { id: user.id }, data: { role: "ADMIN" } });
- }
-
- return { success: true };
- } catch (error) {
- return { error: error instanceof Error ? error.message : "Registration failed. Please try again." };
- }
-}
diff --git a/src/app/actions/judge.ts b/src/app/actions/judge.ts
deleted file mode 100644
index 0960a92..0000000
--- a/src/app/actions/judge.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-"use server";
-
-import fs from "fs";
-import tar from "tar-stream";
-import Docker from "dockerode";
-import prisma from "@/lib/prisma";
-import { v4 as uuid } from "uuid";
-import { auth } from "@/lib/auth";
-import { redirect } from "next/navigation";
-import { Readable, Writable } from "stream";
-import { ExitCode, EditorLanguage, JudgeResult } from "@/generated/client";
-
-const isRemote = process.env.DOCKER_HOST_MODE === "remote";
-
-// Docker client initialization
-const docker = isRemote
- ? new Docker({
- protocol: process.env.DOCKER_REMOTE_PROTOCOL as "https" | "http" | "ssh" | undefined,
- host: process.env.DOCKER_REMOTE_HOST,
- port: process.env.DOCKER_REMOTE_PORT,
- ca: fs.readFileSync(process.env.DOCKER_REMOTE_CA_PATH || "/certs/ca.pem"),
- cert: fs.readFileSync(process.env.DOCKER_REMOTE_CERT_PATH || "/certs/cert.pem"),
- key: fs.readFileSync(process.env.DOCKER_REMOTE_KEY_PATH || "/certs/key.pem"),
- })
- : new Docker({ socketPath: "/var/run/docker.sock" });
-
-// Prepare Docker image environment
-async function prepareEnvironment(image: string, tag: string) {
- const reference = `${image}:${tag}`;
- const filters = { reference: [reference] };
- const images = await docker.listImages({ filters });
- if (images.length === 0) await docker.pull(reference);
-}
-
-// Create Docker container with keep-alive
-async function createContainer(
- image: string,
- tag: string,
- workingDir: string,
- memoryLimit?: number
-) {
- const container = await docker.createContainer({
- Image: `${image}:${tag}`,
- Cmd: ["tail", "-f", "/dev/null"], // Keep container alive
- WorkingDir: workingDir,
- HostConfig: {
- Memory: memoryLimit ? memoryLimit * 1024 * 1024 : undefined,
- MemorySwap: memoryLimit ? memoryLimit * 1024 * 1024 : undefined,
- },
- NetworkDisabled: true,
- });
-
- await container.start();
- return container;
-}
-
-// Create tar stream for code submission
-function createTarStream(file: string, value: string) {
- const pack = tar.pack();
- pack.entry({ name: file }, value);
- pack.finalize();
- return Readable.from(pack);
-}
-
-export async function judge(
- language: EditorLanguage,
- value: string,
-): Promise
{
- const session = await auth();
- if (!session) redirect("/sign-in");
-
- let container: Docker.Container | null = null;
-
- try {
- const config = await prisma.editorLanguageConfig.findUnique({
- where: { language },
- include: {
- dockerConfig: true,
- },
- });
-
- if (!config || !config.dockerConfig) {
- return {
- id: uuid(),
- output: "Configuration Error: Missing editor or docker configuration",
- exitCode: ExitCode.SE,
- executionTime: null,
- memoryUsage: null,
- };
- }
-
- const {
- image,
- tag,
- workingDir,
- memoryLimit,
- timeLimit,
- compileOutputLimit,
- runOutputLimit,
- } = config.dockerConfig;
- const { fileName, fileExtension } = config;
- const file = `${fileName}.${fileExtension}`;
-
- // Prepare the environment and create a container
- await prepareEnvironment(image, tag);
- container = await createContainer(image, tag, workingDir, memoryLimit);
-
- // Upload code to the container
- const tarStream = createTarStream(file, value);
- await container.putArchive(tarStream, { path: workingDir });
-
- // Compile the code
- const compileResult = await compile(container, file, fileName, compileOutputLimit);
- if (compileResult.exitCode === ExitCode.CE) {
- return compileResult;
- }
-
- // Run the code
- const runResult = await run(container, fileName, timeLimit, runOutputLimit);
- return runResult;
- } catch (error) {
- console.error(error);
- return {
- id: uuid(),
- output: "System Error",
- exitCode: ExitCode.SE,
- executionTime: null,
- memoryUsage: null,
- };
- } finally {
- if (container) {
- await container.kill();
- await container.remove();
- }
- }
-}
-
-async function compile(
- container: Docker.Container,
- file: string,
- fileName: string,
- maxOutput: number = 1 * 1024 * 1024
-): Promise {
- const compileExec = await container.exec({
- Cmd: ["gcc", "-O2", file, "-o", fileName],
- AttachStdout: true,
- AttachStderr: true,
- });
-
- return new Promise((resolve, reject) => {
- compileExec.start({}, (error, stream) => {
- if (error || !stream) {
- return reject({ output: "System Error", exitCode: ExitCode.SE });
- }
-
- const stdoutChunks: string[] = [];
- let stdoutLength = 0;
- const stdoutStream = new Writable({
- write(chunk, _encoding, callback) {
- let text = chunk.toString();
- if (stdoutLength + text.length > maxOutput) {
- text = text.substring(0, maxOutput - stdoutLength);
- stdoutChunks.push(text);
- stdoutLength = maxOutput;
- callback();
- return;
- }
- stdoutChunks.push(text);
- stdoutLength += text.length;
- callback();
- },
- });
-
- const stderrChunks: string[] = [];
- let stderrLength = 0;
- const stderrStream = new Writable({
- write(chunk, _encoding, callback) {
- let text = chunk.toString();
- if (stderrLength + text.length > maxOutput) {
- text = text.substring(0, maxOutput - stderrLength);
- stderrChunks.push(text);
- stderrLength = maxOutput;
- callback();
- return;
- }
- stderrChunks.push(text);
- stderrLength += text.length;
- callback();
- },
- });
-
- docker.modem.demuxStream(stream, stdoutStream, stderrStream);
-
- stream.on("end", async () => {
- const stdout = stdoutChunks.join("");
- const stderr = stderrChunks.join("");
- const exitCode = (await compileExec.inspect()).ExitCode;
-
- let result: JudgeResult;
-
- if (exitCode !== 0 || stderr) {
- result = {
- id: uuid(),
- output: stderr || "Compilation Error",
- exitCode: ExitCode.CE,
- executionTime: null,
- memoryUsage: null,
- };
- } else {
- result = {
- id: uuid(),
- output: stdout,
- exitCode: ExitCode.CS,
- executionTime: null,
- memoryUsage: null,
- };
- }
-
- resolve(result);
- });
-
- stream.on("error", () => {
- reject({ output: "System Error", exitCode: ExitCode.SE });
- });
- });
- });
-}
-
-// Run code and implement timeout
-async function run(
- container: Docker.Container,
- fileName: string,
- timeLimit: number = 1000,
- maxOutput: number = 1 * 1024 * 1024,
-): Promise {
- const runExec = await container.exec({
- Cmd: [`./${fileName}`],
- AttachStdout: true,
- AttachStderr: true,
- AttachStdin: true,
- });
-
- return new Promise((resolve, reject) => {
- const stdoutChunks: string[] = [];
- let stdoutLength = 0;
- const stdoutStream = new Writable({
- write(chunk, _encoding, callback) {
- let text = chunk.toString();
- if (stdoutLength + text.length > maxOutput) {
- text = text.substring(0, maxOutput - stdoutLength);
- stdoutChunks.push(text);
- stdoutLength = maxOutput;
- callback();
- return;
- }
- stdoutChunks.push(text);
- stdoutLength += text.length;
- callback();
- },
- });
-
- const stderrChunks: string[] = [];
- let stderrLength = 0;
- const stderrStream = new Writable({
- write(chunk, _encoding, callback) {
- let text = chunk.toString();
- if (stderrLength + text.length > maxOutput) {
- text = text.substring(0, maxOutput - stderrLength);
- stderrChunks.push(text);
- stderrLength = maxOutput;
- callback();
- return;
- }
- stderrChunks.push(text);
- stderrLength += text.length;
- callback();
- },
- });
-
- // Start the exec stream
- runExec.start({ hijack: true }, (error, stream) => {
- if (error || !stream) {
- return reject({ output: "System Error", exitCode: ExitCode.SE });
- }
-
- stream.write("[2,7,11,15]\n9\n[3,2,4]\n6\n[3,3]\n6");
- stream.end();
-
- docker.modem.demuxStream(stream, stdoutStream, stderrStream);
-
- // Timeout mechanism
- const timeoutId = setTimeout(async () => {
- resolve({
- id: uuid(),
- output: "Time Limit Exceeded",
- exitCode: ExitCode.TLE,
- executionTime: null,
- memoryUsage: null,
- });
- }, timeLimit);
-
- stream.on("end", async () => {
- clearTimeout(timeoutId); // Clear the timeout if the program finishes before the time limit
- const stdout = stdoutChunks.join("");
- const stderr = stderrChunks.join("");
- const exitCode = (await runExec.inspect()).ExitCode;
-
- let result: JudgeResult;
-
- // Exit code 0 means successful execution
- if (exitCode === 0) {
- result = {
- id: uuid(),
- output: stdout,
- exitCode: ExitCode.AC,
- executionTime: null,
- memoryUsage: null,
- };
- } else if (exitCode === 137) {
- result = {
- id: uuid(),
- output: stderr || "Memory Limit Exceeded",
- exitCode: ExitCode.MLE,
- executionTime: null,
- memoryUsage: null,
- };
- } else {
- result = {
- id: uuid(),
- output: stderr || "Runtime Error",
- exitCode: ExitCode.RE,
- executionTime: null,
- memoryUsage: null,
- };
- }
-
- resolve(result);
- });
-
- stream.on("error", () => {
- clearTimeout(timeoutId); // Clear timeout in case of error
- reject({ output: "System Error", exitCode: ExitCode.SE });
- });
- });
- });
-}
diff --git a/src/app/actions/language-server.ts b/src/app/actions/language-server.ts
deleted file mode 100644
index cd823e2..0000000
--- a/src/app/actions/language-server.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-"use server";
-
-import prisma from "@/lib/prisma";
-import { EditorLanguage } from "@/generated/client";
-import { SettingsLanguageServerFormValues } from "@/app/(app)/dashboard/@admin/settings/language-server/form";
-
-export const getLanguageServerConfig = async (language: EditorLanguage) => {
- return await prisma.languageServerConfig.findUnique({
- where: { language },
- });
-};
-
-export const handleLanguageServerConfigSubmit = async (
- language: EditorLanguage,
- data: SettingsLanguageServerFormValues
-) => {
- const existing = await getLanguageServerConfig(language);
-
- if (existing) {
- await prisma.languageServerConfig.update({
- where: { language },
- data,
- });
- } else {
- await prisma.languageServerConfig.create({
- data: { ...data, language },
- });
- }
-};
diff --git a/src/components/dockview.tsx b/src/components/dockview.tsx
index 87f75df..dd1512f 100644
--- a/src/components/dockview.tsx
+++ b/src/components/dockview.tsx
@@ -43,7 +43,7 @@ const DefaultTab = ({ params }: IDockviewPanelHeaderProps<{ title: string }>) =>
const Icon = PanelIcons[title];
return (
-
+
{Icon && (
diff --git a/src/components/loading.tsx b/src/components/loading.tsx
index 95fff1f..f26da5e 100644
--- a/src/components/loading.tsx
+++ b/src/components/loading.tsx
@@ -12,7 +12,7 @@ export function Loading({
...props
}: LoadingProps) {
return (
-
+
);
diff --git a/src/components/problem-editor.tsx b/src/components/problem-editor.tsx
index 118f123..9d68736 100644
--- a/src/components/problem-editor.tsx
+++ b/src/components/problem-editor.tsx
@@ -137,7 +137,7 @@ export function ProblemEditor() {
onValidate={handleEditorValidation}
options={DefaultEditorOptionConfig}
loading={
}
- className="h-full w-full py-2"
+ className="h-full w-full"
/>
);
}
diff --git a/src/config/editor-option.ts b/src/config/editor-option.ts
index cc64f87..c8f9e6a 100644
--- a/src/config/editor-option.ts
+++ b/src/config/editor-option.ts
@@ -19,6 +19,9 @@ export const DefaultEditorOptionConfig: editor.IEditorConstructionOptions = {
hover: {
above: false,
},
+ padding: {
+ top: 8,
+ },
scrollbar: {
horizontalSliderSize: 10,
verticalSliderSize: 10,
diff --git a/src/style/mdx.css b/src/style/mdx.css
deleted file mode 100644
index e811089..0000000
--- a/src/style/mdx.css
+++ /dev/null
@@ -1,49 +0,0 @@
-[data-rehype-pretty-code-figure] pre {
- @apply px-0;
-}
-
-[data-rehype-pretty-code-figure] code {
- @apply text-sm !leading-loose md:text-base border-0 p-0;
-}
-
-[data-rehype-pretty-code-figure] code[data-line-numbers] {
- counter-reset: line;
-}
-
-[data-rehype-pretty-code-figure] code[data-line-numbers] > [data-line]::before {
- counter-increment: line;
- content: counter(line);
- @apply mr-4 inline-block w-4 text-right text-gray-500;
-}
-
-[data-rehype-pretty-code-figure] [data-line] {
- @apply border-l-2 border-l-transparent px-3;
-}
-
-[data-rehype-pretty-code-figure] [data-highlighted-line] {
- background: rgba(219, 234, 254, 0.5);
- @apply border-l-blue-400;
-}
-
-.dark [data-rehype-pretty-code-figure] [data-highlighted-line] {
- background: rgba(200, 200, 255, 0.1);
- @apply border-l-blue-400;
-}
-
-[data-rehype-pretty-code-figure] [data-highlighted-chars] {
- @apply rounded bg-zinc-400/50;
- box-shadow: 0 0 0 4px rgb(161 161 170 / 0.5);
-}
-
-.dark [data-rehype-pretty-code-figure] [data-highlighted-chars] {
- @apply rounded bg-zinc-500/50;
- box-shadow: 0 0 0 4px rgb(113 113 122 / 0.5);
-}
-
-[data-rehype-pretty-code-figure] [data-chars-id] {
- @apply border-b-2 p-1 shadow-none;
-}
-
-.subheading-anchor {
- @apply no-underline hover:underline;
-}