mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 15:26:36 +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 { getUserLocale } from "@/i18n/locale";
|
||||
import ProblemPage from "@/app/(app)/problems/[id]/page";
|
||||
import { ProblemStoreProvider } from "@/providers/problem-store-provider";
|
||||
import { PlaygroundHeader } from "@/components/features/playground/header";
|
||||
import { ProblemHeader } from "@/features/problems/components/problem-header";
|
||||
|
||||
interface ProblemProps {
|
||||
interface ProblemLayoutProps {
|
||||
children: React.ReactNode;
|
||||
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({
|
||||
children,
|
||||
params,
|
||||
Description,
|
||||
Solutions,
|
||||
Submissions,
|
||||
Details,
|
||||
Code,
|
||||
Testcase,
|
||||
Bot,
|
||||
}: ProblemProps) {
|
||||
}: ProblemLayoutProps) {
|
||||
const { id } = await params;
|
||||
|
||||
if (!id) {
|
||||
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 (
|
||||
<div className="flex flex-col h-screen">
|
||||
<ProblemStoreProvider
|
||||
problemId={id}
|
||||
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>
|
||||
<ProblemHeader />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Banner } from "@/components/banner";
|
||||
import { AvatarButton } from "@/components/avatar-button";
|
||||
import { ProblemsetHeader } from "@/features/problemset/components/problemset-header";
|
||||
|
||||
interface ProblemsetLayoutProps {
|
||||
children: React.ReactNode;
|
||||
@ -7,14 +6,9 @@ interface ProblemsetLayoutProps {
|
||||
|
||||
export default function ProblemsetLayout({ children }: ProblemsetLayoutProps) {
|
||||
return (
|
||||
<div className="relative h-screen flex flex-col">
|
||||
<Banner />
|
||||
<div className="absolute top-2 right-4">
|
||||
<AvatarButton />
|
||||
</div>
|
||||
<main className="h-full container mx-auto p-4">
|
||||
{children}
|
||||
</main>
|
||||
<div className="h-full flex flex-col">
|
||||
<ProblemsetHeader />
|
||||
{children}
|
||||
</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 {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
|
||||
interface BackButtonProps {
|
||||
interface NavigateBackButtonProps {
|
||||
href: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function BackButton({
|
||||
href,
|
||||
className,
|
||||
...props
|
||||
}: BackButtonProps) {
|
||||
const NavigateBackButton = ({ href, className }: NavigateBackButtonProps) => {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
@ -25,7 +26,6 @@ export default function BackButton({
|
||||
variant="ghost"
|
||||
className={cn("h-8 w-auto p-2", className)}
|
||||
asChild
|
||||
{...props}
|
||||
>
|
||||
<Link href={href}>
|
||||
<ArrowLeftIcon size={16} aria-hidden="true" />
|
||||
@ -38,4 +38,6 @@ export default function BackButton({
|
||||
</Tooltip>
|
||||
</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