mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-07-04 09:20:53 +00:00
refactor: flexlayout
This commit is contained in:
parent
598ac222ed
commit
56618fd335
@ -1,15 +1,12 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { ProblemHeader } from "@/features/problems/components/header";
|
||||
|
||||
interface ProblemLayoutProps {
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ problemId: string }>;
|
||||
}
|
||||
|
||||
export default async function ProblemLayout({
|
||||
children,
|
||||
params,
|
||||
}: ProblemLayoutProps) {
|
||||
const Layout = async ({ children, params }: LayoutProps) => {
|
||||
const { problemId } = await params;
|
||||
|
||||
if (!problemId) {
|
||||
@ -24,4 +21,6 @@ export default async function ProblemLayout({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
@ -1,39 +1,17 @@
|
||||
import { TestcasePanel } from "@/features/problems/testcase/panel";
|
||||
import { BotPanel } from "@/features/problems/bot/components/panel";
|
||||
import { CodePanel } from "@/features/problems/code/components/panel";
|
||||
import { DetailPanel } from "@/features/problems/detail/components/panel";
|
||||
import { SolutionPanel } from "@/features/problems/solution/components/panel";
|
||||
import { SubmissionPanel } from "@/features/problems/submission/components/panel";
|
||||
import { DescriptionPanel } from "@/features/problems/description/components/panel";
|
||||
import { ProblemFlexLayout } from "@/features/problems/components/problem-flexlayout";
|
||||
import { ProblemView } from "@/features/problems/ui/views/problem-view";
|
||||
|
||||
interface ProblemPageProps {
|
||||
interface PageProps {
|
||||
params: Promise<{ problemId: string }>;
|
||||
searchParams: Promise<{
|
||||
submissionId: string | undefined;
|
||||
}>;
|
||||
}
|
||||
|
||||
export default async function ProblemPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: ProblemPageProps) {
|
||||
const Page = async ({ params, searchParams }: PageProps) => {
|
||||
const { problemId } = await params;
|
||||
const { submissionId } = await searchParams;
|
||||
|
||||
const components: Record<string, React.ReactNode> = {
|
||||
description: <DescriptionPanel problemId={problemId} />,
|
||||
solution: <SolutionPanel problemId={problemId} />,
|
||||
submission: <SubmissionPanel problemId={problemId} />,
|
||||
detail: <DetailPanel submissionId={submissionId} />,
|
||||
code: <CodePanel problemId={problemId} />,
|
||||
testcase: <TestcasePanel problemId={problemId} />,
|
||||
bot: <BotPanel problemId={problemId} />,
|
||||
};
|
||||
return <ProblemView problemId={problemId} submissionId={submissionId} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full">
|
||||
<ProblemFlexLayout components={components} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Page;
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { ProblemEditLayout } from "@/features/admin/ui/layouts/problem-edit-layout";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ problemId: string }>;
|
||||
}
|
||||
|
||||
const Layout = async ({ children, params }: LayoutProps) => {
|
||||
const { problemId } = await params;
|
||||
|
||||
if (!problemId) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
return <ProblemEditLayout>{children}</ProblemEditLayout>;
|
||||
};
|
||||
|
||||
export default Layout;
|
13
src/app/(protected)/admin/problems/[problemId]/edit/page.tsx
Normal file
13
src/app/(protected)/admin/problems/[problemId]/edit/page.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { ProblemEditView } from "@/features/admin/ui/views/problem-edit-view";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ problemId: string }>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: PageProps) => {
|
||||
const { problemId } = await params;
|
||||
|
||||
return <ProblemEditView problemId={problemId} />;
|
||||
};
|
||||
|
||||
export default Page;
|
11
src/app/(protected)/layout.tsx
Normal file
11
src/app/(protected)/layout.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { AdminProtectedLayout } from "@/features/admin/ui/layouts/admin-protected-layout";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Layout = ({ children }: LayoutProps) => {
|
||||
return <AdminProtectedLayout>{children}</AdminProtectedLayout>;
|
||||
};
|
||||
|
||||
export default Layout;
|
@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Actions } from "flexlayout-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ReactNode, useRef, useState } from "react";
|
||||
import { CheckIcon, CopyIcon, RepeatIcon } from "lucide-react";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import { useProblemEditorStore } from "@/stores/problem-editor";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
|
||||
import { Actions } from "flexlayout-react";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
|
||||
interface PreDetailProps {
|
||||
children?: ReactNode;
|
||||
|
26
src/features/admin/ui/components/problem-edit-flexlayout.tsx
Normal file
26
src/features/admin/ui/components/problem-edit-flexlayout.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { useProblemEditFlexLayoutStore } from "@/stores/flexlayout";
|
||||
import { FlexLayout } from "@/features/problems/components/flexlayout";
|
||||
|
||||
interface ProblemEditFlexLayoutProps {
|
||||
components: Record<string, React.ReactNode>;
|
||||
}
|
||||
|
||||
export const ProblemEditFlexLayout = ({
|
||||
components,
|
||||
}: ProblemEditFlexLayoutProps) => {
|
||||
const { hasHydrated, model, jsonModel, setModel, setJsonModel } =
|
||||
useProblemEditFlexLayoutStore();
|
||||
|
||||
return (
|
||||
<FlexLayout
|
||||
components={components}
|
||||
hasHydrated={hasHydrated}
|
||||
model={model}
|
||||
jsonModel={jsonModel}
|
||||
setModel={setModel}
|
||||
setJsonModel={setJsonModel}
|
||||
/>
|
||||
);
|
||||
};
|
30
src/features/admin/ui/layouts/admin-protected-layout.tsx
Normal file
30
src/features/admin/ui/layouts/admin-protected-layout.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { auth, signIn } from "@/lib/auth";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
interface AdminProtectedLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AdminProtectedLayout = async ({
|
||||
children,
|
||||
}: AdminProtectedLayoutProps) => {
|
||||
const session = await auth();
|
||||
const userId = session?.user?.id;
|
||||
if (!userId) {
|
||||
await signIn();
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
select: {
|
||||
role: true,
|
||||
},
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user?.role !== "ADMIN") redirect("/unauthorized");
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
29
src/features/admin/ui/layouts/problem-edit-layout.tsx
Normal file
29
src/features/admin/ui/layouts/problem-edit-layout.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Suspense } from "react";
|
||||
import { BackButton } from "@/components/back-button";
|
||||
import { UserAvatar, UserAvatarSkeleton } from "@/components/user-avatar";
|
||||
|
||||
interface ProblemEditLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ProblemEditLayout = ({ children }: ProblemEditLayoutProps) => {
|
||||
return (
|
||||
<div className="flex flex-col h-screen">
|
||||
<header className="relative flex h-12 flex-none items-center">
|
||||
<div className="container mx-auto flex h-full items-center justify-between px-4">
|
||||
<div className="flex items-center">
|
||||
<BackButton href="/problem" />
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Suspense fallback={<UserAvatarSkeleton />}>
|
||||
<UserAvatar />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex w-full flex-grow overflow-y-hidden p-2.5 pt-0">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
26
src/features/admin/ui/views/problem-edit-view.tsx
Normal file
26
src/features/admin/ui/views/problem-edit-view.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import EditCodePanel from "@/components/creater/edit-code-panel";
|
||||
import EditDetailPanel from "@/components/creater/edit-detail-panel";
|
||||
import EditSolutionPanel from "@/components/creater/edit-solution-panel";
|
||||
import EditTestcasePanel from "@/components/creater/edit-testcase-panel";
|
||||
import EditDescriptionPanel from "@/components/creater/edit-description-panel";
|
||||
import { ProblemEditFlexLayout } from "@/features/admin/ui/components/problem-edit-flexlayout";
|
||||
|
||||
interface ProblemEditViewProps {
|
||||
problemId: string;
|
||||
}
|
||||
|
||||
export const ProblemEditView = ({ problemId }: ProblemEditViewProps) => {
|
||||
const components: Record<string, React.ReactNode> = {
|
||||
description: <EditDescriptionPanel problemId={problemId} />,
|
||||
solution: <EditSolutionPanel problemId={problemId} />,
|
||||
detail: <EditDetailPanel problemId={problemId} />,
|
||||
code: <EditCodePanel problemId={problemId} />,
|
||||
testcase: <EditTestcasePanel problemId={problemId} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full">
|
||||
<ProblemEditFlexLayout components={components} />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -11,7 +11,7 @@ import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { Actions, DockLocation } from "flexlayout-react";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
|
||||
|
||||
export const ViewBotButton = () => {
|
||||
const t = useTranslations();
|
||||
|
111
src/features/problems/components/flexlayout.tsx
Normal file
111
src/features/problems/components/flexlayout.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
BotIcon,
|
||||
CircleCheckBigIcon,
|
||||
FileTextIcon,
|
||||
FlaskConicalIcon,
|
||||
SquareCheckIcon,
|
||||
SquarePenIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
IJsonModel,
|
||||
ITabRenderValues,
|
||||
Layout,
|
||||
Model,
|
||||
TabNode,
|
||||
} from "flexlayout-react";
|
||||
import "@/styles/flexlayout.css";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
interface FlexLayoutProps {
|
||||
components: Record<string, React.ReactNode>;
|
||||
hasHydrated: boolean;
|
||||
model: Model | null;
|
||||
jsonModel: IJsonModel;
|
||||
setModel: (model: Model) => void;
|
||||
setJsonModel: (jsonModel: IJsonModel) => void;
|
||||
}
|
||||
|
||||
export const FlexLayout = ({
|
||||
components,
|
||||
hasHydrated,
|
||||
model,
|
||||
jsonModel,
|
||||
setModel,
|
||||
setJsonModel,
|
||||
}: FlexLayoutProps) => {
|
||||
const t = useTranslations("ProblemPage");
|
||||
|
||||
useEffect(() => {
|
||||
if (hasHydrated && !model) {
|
||||
const model = Model.fromJson(jsonModel);
|
||||
setModel(model);
|
||||
}
|
||||
}, [hasHydrated, jsonModel, model, setModel]);
|
||||
|
||||
const onModelChange = useCallback(
|
||||
(model: Model) => {
|
||||
const jsonModel = model.toJson();
|
||||
setJsonModel(jsonModel);
|
||||
},
|
||||
[setJsonModel]
|
||||
);
|
||||
|
||||
const factory = useCallback(
|
||||
(node: TabNode) => {
|
||||
const component = node.getComponent();
|
||||
return component ? components[component] : null;
|
||||
},
|
||||
[components]
|
||||
);
|
||||
|
||||
const onRenderTab = useCallback(
|
||||
(node: TabNode, renderValues: ITabRenderValues) => {
|
||||
const Icon = getIconForTab(node.getId());
|
||||
renderValues.leading = Icon ? (
|
||||
<Icon className="opacity-60" size={16} aria-hidden="true" />
|
||||
) : null;
|
||||
renderValues.content = (
|
||||
<span className="text-sm font-medium">
|
||||
{t(node.getName()) || node.getName()}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
[t]
|
||||
);
|
||||
|
||||
if (!model || !hasHydrated)
|
||||
return <Skeleton className="h-full w-full rounded-2xl" />;
|
||||
|
||||
return (
|
||||
<Layout
|
||||
model={model}
|
||||
factory={factory}
|
||||
onRenderTab={onRenderTab}
|
||||
onModelChange={onModelChange}
|
||||
realtimeResize={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getIconForTab = (id: string) => {
|
||||
switch (id) {
|
||||
case "description":
|
||||
return FileTextIcon;
|
||||
case "solution":
|
||||
return FlaskConicalIcon;
|
||||
case "submission":
|
||||
return CircleCheckBigIcon;
|
||||
case "detail":
|
||||
return CircleCheckBigIcon;
|
||||
case "code":
|
||||
return SquarePenIcon;
|
||||
case "testcase":
|
||||
return SquareCheckIcon;
|
||||
case "bot":
|
||||
return BotIcon;
|
||||
}
|
||||
};
|
@ -10,7 +10,7 @@ import { LoaderCircleIcon, PlayIcon } from "lucide-react";
|
||||
import { TooltipButton } from "@/components/tooltip-button";
|
||||
import { useProblemEditorStore } from "@/stores/problem-editor";
|
||||
import { JudgeToast } from "@/features/problems/components/judge-toast";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
|
||||
|
||||
interface JudgeButtonProps {
|
||||
className?: string;
|
||||
|
@ -1,94 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
BotIcon,
|
||||
CircleCheckBigIcon,
|
||||
FileTextIcon,
|
||||
FlaskConicalIcon,
|
||||
SquareCheckIcon,
|
||||
SquarePenIcon,
|
||||
} from "lucide-react";
|
||||
import "@/styles/flexlayout.css";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
|
||||
import { ITabRenderValues, Layout, Model, TabNode } from "flexlayout-react";
|
||||
import { FlexLayout } from "./flexlayout";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
|
||||
|
||||
interface ProblemFlexLayoutProps {
|
||||
components: Record<string, React.ReactNode>;
|
||||
}
|
||||
|
||||
export const ProblemFlexLayout = ({ components }: ProblemFlexLayoutProps) => {
|
||||
const t = useTranslations("ProblemPage");
|
||||
const { model, setModel, jsonModel, setJsonModel } =
|
||||
const { hasHydrated, model, jsonModel, setModel, setJsonModel } =
|
||||
useProblemFlexLayoutStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!model) {
|
||||
const model = Model.fromJson(jsonModel);
|
||||
setModel(model);
|
||||
}
|
||||
}, [jsonModel, model, setModel]);
|
||||
|
||||
const onModelChange = useCallback(
|
||||
(model: Model) => {
|
||||
const jsonModel = model.toJson();
|
||||
setJsonModel(jsonModel);
|
||||
},
|
||||
[setJsonModel]
|
||||
);
|
||||
|
||||
const factory = useCallback(
|
||||
(node: TabNode) => {
|
||||
const component = node.getComponent();
|
||||
return component ? components[component] : null;
|
||||
},
|
||||
[components]
|
||||
);
|
||||
|
||||
const onRenderTab = useCallback(
|
||||
(node: TabNode, renderValues: ITabRenderValues) => {
|
||||
const Icon = getIconForTab(node.getId());
|
||||
renderValues.leading = Icon ? (
|
||||
<Icon className="opacity-60" size={16} aria-hidden="true" />
|
||||
) : null;
|
||||
renderValues.content = (
|
||||
<span className="text-sm font-medium">
|
||||
{t(node.getName()) || node.getName()}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
[t]
|
||||
);
|
||||
|
||||
if (!model) return null;
|
||||
|
||||
return (
|
||||
<Layout
|
||||
<FlexLayout
|
||||
components={components}
|
||||
hasHydrated={hasHydrated}
|
||||
model={model}
|
||||
factory={factory}
|
||||
onRenderTab={onRenderTab}
|
||||
onModelChange={onModelChange}
|
||||
realtimeResize={true}
|
||||
jsonModel={jsonModel}
|
||||
setModel={setModel}
|
||||
setJsonModel={setJsonModel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getIconForTab = (id: string) => {
|
||||
switch (id) {
|
||||
case "description":
|
||||
return FileTextIcon;
|
||||
case "solution":
|
||||
return FlaskConicalIcon;
|
||||
case "submission":
|
||||
return CircleCheckBigIcon;
|
||||
case "detail":
|
||||
return CircleCheckBigIcon;
|
||||
case "code":
|
||||
return SquarePenIcon;
|
||||
case "testcase":
|
||||
return SquareCheckIcon;
|
||||
case "bot":
|
||||
return BotIcon;
|
||||
}
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import { Actions } from "flexlayout-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
export const DetailHeader = () => {
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { Actions } from "flexlayout-react";
|
||||
import { BookOpenIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
|
||||
|
||||
export const ViewSolutionButton = () => {
|
||||
const { model } = useProblemFlexLayoutStore();
|
||||
|
@ -7,7 +7,7 @@ import { Locale, Submission } from "@/generated/client";
|
||||
import { Actions, DockLocation } from "flexlayout-react";
|
||||
import { getColorClassForStatus } from "@/config/status";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout";
|
||||
import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { getIconForLanguage, getLabelForLanguage } from "@/config/language";
|
||||
|
||||
|
31
src/features/problems/ui/views/problem-view.tsx
Normal file
31
src/features/problems/ui/views/problem-view.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { TestcasePanel } from "@/features/problems/testcase/panel";
|
||||
import { BotPanel } from "@/features/problems/bot/components/panel";
|
||||
import { CodePanel } from "@/features/problems/code/components/panel";
|
||||
import { DetailPanel } from "@/features/problems/detail/components/panel";
|
||||
import { SolutionPanel } from "@/features/problems/solution/components/panel";
|
||||
import { SubmissionPanel } from "@/features/problems/submission/components/panel";
|
||||
import { DescriptionPanel } from "@/features/problems/description/components/panel";
|
||||
import { ProblemFlexLayout } from "@/features/problems/components/problem-flexlayout";
|
||||
|
||||
interface ProblemViewProps {
|
||||
problemId: string;
|
||||
submissionId: string | undefined;
|
||||
}
|
||||
|
||||
export const ProblemView = ({ problemId, submissionId }: ProblemViewProps) => {
|
||||
const components: Record<string, React.ReactNode> = {
|
||||
description: <DescriptionPanel problemId={problemId} />,
|
||||
solution: <SolutionPanel problemId={problemId} />,
|
||||
submission: <SubmissionPanel problemId={problemId} />,
|
||||
detail: <DetailPanel submissionId={submissionId} />,
|
||||
code: <CodePanel problemId={problemId} />,
|
||||
testcase: <TestcasePanel problemId={problemId} />,
|
||||
bot: <BotPanel problemId={problemId} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full">
|
||||
<ProblemFlexLayout components={components} />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -44,6 +44,9 @@ export const ProblemsetTable = async () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
isPublished: true,
|
||||
},
|
||||
});
|
||||
|
||||
const completedProblems = new Set<string>();
|
||||
@ -85,7 +88,9 @@ export const ProblemsetTable = async () => {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="[&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg">
|
||||
{problems.map((problem, index) => (
|
||||
{problems
|
||||
.sort((a, b) => a.displayId - b.displayId)
|
||||
.map((problem) => (
|
||||
<TableRow
|
||||
key={problem.id}
|
||||
className="h-10 border-b-0 odd:bg-muted/50 hover:text-blue-500 hover:bg-muted"
|
||||
@ -111,7 +116,7 @@ export const ProblemsetTable = async () => {
|
||||
href={`/problems/${problem.id}`}
|
||||
className="hover:text-blue-500"
|
||||
>
|
||||
{index + 1}.{" "}
|
||||
{problem.displayId}.{" "}
|
||||
{getLocalizedTitle(problem.localizations, locale as Locale)}
|
||||
</Link>
|
||||
</TableCell>
|
||||
|
211
src/stores/flexlayout.ts
Normal file
211
src/stores/flexlayout.ts
Normal file
@ -0,0 +1,211 @@
|
||||
import { create } from "zustand";
|
||||
import { IJsonModel, Model } from "flexlayout-react";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
|
||||
type FlexLayoutState = {
|
||||
hasHydrated: boolean;
|
||||
model: Model | null;
|
||||
jsonModel: IJsonModel;
|
||||
};
|
||||
|
||||
type FlexLayoutAction = {
|
||||
setHasHydrated: (hasHydrated: boolean) => void;
|
||||
setModel: (model: Model) => void;
|
||||
setJsonModel: (jsonModel: IJsonModel) => void;
|
||||
};
|
||||
|
||||
type FlexLayoutStore = FlexLayoutState & FlexLayoutAction;
|
||||
|
||||
const createFlexLayoutStore = (storageKey: string, jsonModel: IJsonModel) =>
|
||||
create<FlexLayoutStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
hasHydrated: false,
|
||||
model: null,
|
||||
jsonModel: jsonModel,
|
||||
setHasHydrated: (hasHydrated) => set({ hasHydrated }),
|
||||
setModel: (model) => set({ model }),
|
||||
setJsonModel: (jsonModel) => set({ jsonModel }),
|
||||
}),
|
||||
{
|
||||
name: storageKey,
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => ({
|
||||
jsonModel: state.jsonModel,
|
||||
}),
|
||||
onRehydrateStorage: () => {
|
||||
return (state) => {
|
||||
state?.setHasHydrated(true);
|
||||
};
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const initialProblemFlexLayoutJsonModel: IJsonModel = {
|
||||
global: {
|
||||
// tabEnableClose: false,
|
||||
tabEnableRename: false,
|
||||
},
|
||||
borders: [],
|
||||
layout: {
|
||||
type: "row",
|
||||
weight: 100,
|
||||
children: [
|
||||
{
|
||||
type: "tabset",
|
||||
id: "1",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "description",
|
||||
name: "Description",
|
||||
component: "description",
|
||||
enableClose: false,
|
||||
},
|
||||
{
|
||||
type: "tab",
|
||||
id: "solution",
|
||||
name: "Solutions",
|
||||
component: "solution",
|
||||
enableClose: false,
|
||||
},
|
||||
{
|
||||
type: "tab",
|
||||
id: "submission",
|
||||
name: "Submissions",
|
||||
component: "submission",
|
||||
enableClose: false,
|
||||
},
|
||||
// {
|
||||
// type: "tab",
|
||||
// id: "detail",
|
||||
// name: "Details",
|
||||
// component: "detail",
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tabset",
|
||||
id: "2",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "code",
|
||||
name: "Code",
|
||||
component: "code",
|
||||
enableClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tabset",
|
||||
id: "3",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "testcase",
|
||||
name: "Testcase",
|
||||
component: "testcase",
|
||||
enableClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const useProblemFlexLayoutStore = createFlexLayoutStore(
|
||||
"problem-flexlayout",
|
||||
initialProblemFlexLayoutJsonModel
|
||||
);
|
||||
|
||||
const initialProblemEditFlexLayoutJsonModel: IJsonModel = {
|
||||
global: {
|
||||
// tabEnableClose: false,
|
||||
tabEnableRename: false,
|
||||
},
|
||||
borders: [],
|
||||
layout: {
|
||||
type: "row",
|
||||
weight: 100,
|
||||
children: [
|
||||
{
|
||||
type: "tabset",
|
||||
id: "1",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "description",
|
||||
name: "Description",
|
||||
component: "description",
|
||||
enableClose: false,
|
||||
},
|
||||
{
|
||||
type: "tab",
|
||||
id: "solution",
|
||||
name: "Solutions",
|
||||
component: "solution",
|
||||
enableClose: false,
|
||||
},
|
||||
{
|
||||
type: "tab",
|
||||
id: "detail",
|
||||
name: "Details",
|
||||
component: "detail",
|
||||
enableClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tabset",
|
||||
id: "2",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "code",
|
||||
name: "Code",
|
||||
component: "code",
|
||||
enableClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tabset",
|
||||
id: "3",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "testcase",
|
||||
name: "Testcase",
|
||||
component: "testcase",
|
||||
enableClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const useProblemEditFlexLayoutStore = createFlexLayoutStore(
|
||||
"problem-edit-flexlayout",
|
||||
initialProblemEditFlexLayoutJsonModel
|
||||
);
|
@ -1,115 +0,0 @@
|
||||
import { create } from "zustand";
|
||||
import { IJsonModel, Model } from "flexlayout-react";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
|
||||
const initialJsonModel: IJsonModel = {
|
||||
global: {
|
||||
// tabEnableClose: false,
|
||||
tabEnableRename: false,
|
||||
},
|
||||
borders: [],
|
||||
layout: {
|
||||
type: "row",
|
||||
weight: 100,
|
||||
children: [
|
||||
{
|
||||
type: "tabset",
|
||||
id: "1",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "description",
|
||||
name: "Description",
|
||||
component: "description",
|
||||
enableClose: false,
|
||||
},
|
||||
{
|
||||
type: "tab",
|
||||
id: "solution",
|
||||
name: "Solutions",
|
||||
component: "solution",
|
||||
enableClose: false,
|
||||
},
|
||||
{
|
||||
type: "tab",
|
||||
id: "submission",
|
||||
name: "Submissions",
|
||||
component: "submission",
|
||||
enableClose: false,
|
||||
},
|
||||
// {
|
||||
// type: "tab",
|
||||
// id: "detail",
|
||||
// name: "Details",
|
||||
// component: "detail",
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tabset",
|
||||
id: "2",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "code",
|
||||
name: "Code",
|
||||
component: "code",
|
||||
enableClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tabset",
|
||||
id: "3",
|
||||
weight: 50,
|
||||
children: [
|
||||
{
|
||||
type: "tab",
|
||||
id: "testcase",
|
||||
name: "Testcase",
|
||||
component: "testcase",
|
||||
enableClose: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
type ProblemFlexLayoutState = {
|
||||
model: Model | null;
|
||||
jsonModel: IJsonModel;
|
||||
};
|
||||
|
||||
type ProblemFlexLayoutAction = {
|
||||
setModel: (model: Model) => void;
|
||||
setJsonModel: (jsonModel: IJsonModel) => void;
|
||||
};
|
||||
|
||||
type ProblemFlexLayoutStore = ProblemFlexLayoutState & ProblemFlexLayoutAction;
|
||||
|
||||
export const useProblemFlexLayoutStore = create<ProblemFlexLayoutStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
model: null,
|
||||
jsonModel: initialJsonModel,
|
||||
setModel: (model) => set({ model }),
|
||||
setJsonModel: (jsonModel) => set({ jsonModel }),
|
||||
}),
|
||||
{
|
||||
name: "problem-flexlayout",
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => ({
|
||||
jsonModel: state.jsonModel,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
Loading…
Reference in New Issue
Block a user