mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-17 23:12:23 +00:00
refactor(layouts): overhaul problem and problemset page structures
- Simplify ProblemLayout to use children prop and remove ProblemStoreProvider - Replace PlaygroundHeader with dedicated ProblemHeader component - Streamline ProblemsetLayout with new ProblemsetHeader - Remove deprecated BackButton in favor of NavigateBackButton - Delete unused ProblemStoreProvider and related dependencies
This commit is contained in:
parent
829370fafd
commit
aed942e7e2
@ -1,90 +1,25 @@
|
|||||||
import prisma from "@/lib/prisma";
|
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getUserLocale } from "@/i18n/locale";
|
import { ProblemHeader } from "@/features/problems/components/problem-header";
|
||||||
import ProblemPage from "@/app/(app)/problems/[id]/page";
|
|
||||||
import { ProblemStoreProvider } from "@/providers/problem-store-provider";
|
|
||||||
import { PlaygroundHeader } from "@/components/features/playground/header";
|
|
||||||
|
|
||||||
interface ProblemProps {
|
interface ProblemLayoutProps {
|
||||||
|
children: React.ReactNode;
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
Description: React.ReactNode;
|
|
||||||
Solutions: React.ReactNode;
|
|
||||||
Submissions: React.ReactNode;
|
|
||||||
Details: React.ReactNode;
|
|
||||||
Code: React.ReactNode;
|
|
||||||
Testcase: React.ReactNode;
|
|
||||||
Bot: React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function ProblemLayout({
|
export default async function ProblemLayout({
|
||||||
|
children,
|
||||||
params,
|
params,
|
||||||
Description,
|
}: ProblemLayoutProps) {
|
||||||
Solutions,
|
|
||||||
Submissions,
|
|
||||||
Details,
|
|
||||||
Code,
|
|
||||||
Testcase,
|
|
||||||
Bot,
|
|
||||||
}: ProblemProps) {
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const [
|
|
||||||
problem,
|
|
||||||
editorLanguageConfigs,
|
|
||||||
languageServerConfigs,
|
|
||||||
submissions,
|
|
||||||
] = await Promise.all([
|
|
||||||
prisma.problem.findUnique({
|
|
||||||
where: { id },
|
|
||||||
include: {
|
|
||||||
templates: true,
|
|
||||||
testcases: {
|
|
||||||
include: {
|
|
||||||
data: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
prisma.editorLanguageConfig.findMany(),
|
|
||||||
prisma.languageServerConfig.findMany(),
|
|
||||||
prisma.submission.findMany({
|
|
||||||
where: { problemId: id },
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!problem) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const locale = await getUserLocale();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen">
|
<div className="flex flex-col h-screen">
|
||||||
<ProblemStoreProvider
|
<ProblemHeader />
|
||||||
problemId={id}
|
{children}
|
||||||
problem={problem}
|
|
||||||
editorLanguageConfigs={editorLanguageConfigs}
|
|
||||||
languageServerConfigs={languageServerConfigs}
|
|
||||||
submissions={submissions}
|
|
||||||
>
|
|
||||||
<PlaygroundHeader />
|
|
||||||
<main className="flex flex-grow overflow-y-hidden p-2.5 pt-0">
|
|
||||||
<ProblemPage
|
|
||||||
locale={locale}
|
|
||||||
Description={Description}
|
|
||||||
Solutions={Solutions}
|
|
||||||
Submissions={Submissions}
|
|
||||||
Details={Details}
|
|
||||||
Code={Code}
|
|
||||||
Testcase={Testcase}
|
|
||||||
Bot={Bot}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
</ProblemStoreProvider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Banner } from "@/components/banner";
|
import { ProblemsetHeader } from "@/features/problemset/components/problemset-header";
|
||||||
import { AvatarButton } from "@/components/avatar-button";
|
|
||||||
|
|
||||||
interface ProblemsetLayoutProps {
|
interface ProblemsetLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -7,14 +6,9 @@ interface ProblemsetLayoutProps {
|
|||||||
|
|
||||||
export default function ProblemsetLayout({ children }: ProblemsetLayoutProps) {
|
export default function ProblemsetLayout({ children }: ProblemsetLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative h-screen flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<Banner />
|
<ProblemsetHeader />
|
||||||
<div className="absolute top-2 right-4">
|
|
||||||
<AvatarButton />
|
|
||||||
</div>
|
|
||||||
<main className="h-full container mx-auto p-4">
|
|
||||||
{children}
|
{children}
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { auth } from "@/lib/auth";
|
|
||||||
import BackButton from "@/components/back-button";
|
|
||||||
import { RunCodeButton } from "@/components/run-code";
|
|
||||||
import { AvatarButton } from "@/components/avatar-button";
|
|
||||||
import BotVisibilityToggle from "@/components/bot-visibility-toggle";
|
|
||||||
|
|
||||||
interface PlaygroundHeaderProps {
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function PlaygroundHeader({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: PlaygroundHeaderProps) {
|
|
||||||
const session = await auth();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<header
|
|
||||||
{...props}
|
|
||||||
className={cn("relative", className)}
|
|
||||||
>
|
|
||||||
<nav className="z-0 relative h-12 w-full flex shrink-0 items-center px-2.5">
|
|
||||||
<div className="w-full flex justify-between">
|
|
||||||
<div className="w-full flex items-center justify-between">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<BackButton href="/problemset" />
|
|
||||||
</div>
|
|
||||||
<div className="relative flex items-center gap-2">
|
|
||||||
<BotVisibilityToggle />
|
|
||||||
<AvatarButton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div className="z-10 absolute left-1/2 top-0 h-full -translate-x-1/2 py-2">
|
|
||||||
<div className="relative flex">
|
|
||||||
<div className="relative flex overflow-hidden rounded">
|
|
||||||
<RunCodeButton session={session} className="bg-muted text-muted-foreground hover:bg-muted/50" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,20 +1,21 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { ArrowLeftIcon } from "lucide-react";
|
import { ArrowLeftIcon } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
||||||
|
|
||||||
interface BackButtonProps {
|
interface NavigateBackButtonProps {
|
||||||
href: string;
|
href: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BackButton({
|
const NavigateBackButton = ({ href, className }: NavigateBackButtonProps) => {
|
||||||
href,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: BackButtonProps) {
|
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -25,7 +26,6 @@ export default function BackButton({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={cn("h-8 w-auto p-2", className)}
|
className={cn("h-8 w-auto p-2", className)}
|
||||||
asChild
|
asChild
|
||||||
{...props}
|
|
||||||
>
|
>
|
||||||
<Link href={href}>
|
<Link href={href}>
|
||||||
<ArrowLeftIcon size={16} aria-hidden="true" />
|
<ArrowLeftIcon size={16} aria-hidden="true" />
|
||||||
@ -38,4 +38,6 @@ export default function BackButton({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export { NavigateBackButton };
|
33
src/features/problems/components/problem-header.tsx
Normal file
33
src/features/problems/components/problem-header.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { UserAvatar, UserAvatarSkeleton } from "@/components/user-avatar";
|
||||||
|
import { JudgeCodeButton } from "@/features/problems/components/judge-code-button";
|
||||||
|
import { NavigateBackButton } from "@/features/problems/components/navigate-back-button";
|
||||||
|
|
||||||
|
interface ProblemHeaderProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProblemHeader = ({ className }: ProblemHeaderProps) => {
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={cn("relative flex h-12 flex-none items-center", className)}
|
||||||
|
>
|
||||||
|
<div className="container mx-auto flex h-full items-center justify-between px-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<NavigateBackButton href="/problemset" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Suspense fallback={<UserAvatarSkeleton />}>
|
||||||
|
<UserAvatar />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute inset-y-0 left-1/2 z-10 flex -translate-x-1/2 items-center">
|
||||||
|
<JudgeCodeButton />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ProblemHeader };
|
18
src/features/problemset/components/problemset-header.tsx
Normal file
18
src/features/problemset/components/problemset-header.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
|
import { UserAvatar, UserAvatarSkeleton } from "@/components/user-avatar";
|
||||||
|
|
||||||
|
const ProblemsetHeader = () => {
|
||||||
|
return (
|
||||||
|
<header className="flex h-12 flex-none items-center">
|
||||||
|
<div className="container mx-auto flex h-full items-center justify-end px-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Suspense fallback={<UserAvatarSkeleton />}>
|
||||||
|
<UserAvatar />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ProblemsetHeader };
|
@ -1,69 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
|
||||||
EditorLanguage,
|
|
||||||
type Submission,
|
|
||||||
type EditorLanguageConfig,
|
|
||||||
type LanguageServerConfig,
|
|
||||||
} from "@/generated/client";
|
|
||||||
import { useStore } from "zustand";
|
|
||||||
import { type ProblemWithDetails } from "@/types/prisma";
|
|
||||||
import { type ReactNode, createContext, useRef, useContext } from "react";
|
|
||||||
import { type ProblemStore, createProblemStore } from "@/stores/problem-store";
|
|
||||||
|
|
||||||
export type ProblemStoreApi = ReturnType<typeof createProblemStore>;
|
|
||||||
|
|
||||||
export const ProblemStoreContext = createContext<ProblemStoreApi | undefined>(undefined);
|
|
||||||
|
|
||||||
export interface ProblemStoreProviderProps {
|
|
||||||
children: ReactNode;
|
|
||||||
problemId: string;
|
|
||||||
problem: ProblemWithDetails;
|
|
||||||
editorLanguageConfigs: EditorLanguageConfig[];
|
|
||||||
languageServerConfigs: LanguageServerConfig[];
|
|
||||||
submissions: Submission[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProblemStoreProvider = ({
|
|
||||||
children,
|
|
||||||
problemId,
|
|
||||||
problem,
|
|
||||||
editorLanguageConfigs,
|
|
||||||
languageServerConfigs,
|
|
||||||
submissions,
|
|
||||||
}: ProblemStoreProviderProps) => {
|
|
||||||
const storeRef = useRef<ProblemStoreApi | null>(null);
|
|
||||||
|
|
||||||
if (storeRef.current === null) {
|
|
||||||
storeRef.current = createProblemStore({
|
|
||||||
hydrated: false,
|
|
||||||
editor: null,
|
|
||||||
markers: [],
|
|
||||||
webSocket: null,
|
|
||||||
globalLang: EditorLanguage.c,
|
|
||||||
currentLang: EditorLanguage.c,
|
|
||||||
currentValue: "",
|
|
||||||
problemId,
|
|
||||||
problem,
|
|
||||||
editorLanguageConfigs,
|
|
||||||
languageServerConfigs,
|
|
||||||
submissions,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProblemStoreContext.Provider value={storeRef.current}>
|
|
||||||
{children}
|
|
||||||
</ProblemStoreContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useProblemStore = <T,>(selector: (store: ProblemStore) => T): T => {
|
|
||||||
const problemStoreContext = useContext(ProblemStoreContext);
|
|
||||||
|
|
||||||
if (!problemStoreContext) {
|
|
||||||
throw new Error("useProblemStore must be used within ProblemStoreProvider");
|
|
||||||
}
|
|
||||||
|
|
||||||
return useStore(problemStoreContext, selector);
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user