refactor: flexlayout

This commit is contained in:
cfngc4594 2025-06-20 16:39:47 +08:00
parent efaadefa48
commit 13c4b57ecc
21 changed files with 578 additions and 275 deletions

View File

@ -1,15 +1,12 @@
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { ProblemHeader } from "@/features/problems/components/header"; import { ProblemHeader } from "@/features/problems/components/header";
interface ProblemLayoutProps { interface LayoutProps {
children: React.ReactNode; children: React.ReactNode;
params: Promise<{ problemId: string }>; params: Promise<{ problemId: string }>;
} }
export default async function ProblemLayout({ const Layout = async ({ children, params }: LayoutProps) => {
children,
params,
}: ProblemLayoutProps) {
const { problemId } = await params; const { problemId } = await params;
if (!problemId) { if (!problemId) {
@ -24,4 +21,6 @@ export default async function ProblemLayout({
</div> </div>
</div> </div>
); );
} };
export default Layout;

View File

@ -1,40 +1,17 @@
import { TestcasePanel } from "@/features/problems/testcase/panel"; import { ProblemView } from "@/features/problems/ui/views/problem-view";
// 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 { AnalysisPanel } from "@/features/problems/analysis/components/panel";
interface ProblemPageProps { interface PageProps {
params: Promise<{ problemId: string }>; params: Promise<{ problemId: string }>;
searchParams: Promise<{ searchParams: Promise<{
submissionId: string | undefined; submissionId: string | undefined;
}>; }>;
} }
export default async function ProblemPage({ const Page = async ({ params, searchParams }: PageProps) => {
params,
searchParams,
}: ProblemPageProps) {
const { problemId } = await params; const { problemId } = await params;
const { submissionId } = await searchParams; const { submissionId } = await searchParams;
const components: Record<string, React.ReactNode> = { return <ProblemView problemId={problemId} submissionId={submissionId} />;
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: <AnalysisPanel submissionId={submissionId} />,
};
return ( export default Page;
<div className="relative flex h-full w-full">
<ProblemFlexLayout components={components} />
</div>
);
}

View File

@ -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;

View 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;

View 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;

View File

@ -1,13 +1,13 @@
"use client"; "use client";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Actions } from "flexlayout-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ReactNode, useRef, useState } from "react"; import { ReactNode, useRef, useState } from "react";
import { CheckIcon, CopyIcon, RepeatIcon } from "lucide-react"; import { CheckIcon, CopyIcon, RepeatIcon } from "lucide-react";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { useProblemEditorStore } from "@/stores/problem-editor"; import { useProblemEditorStore } from "@/stores/problem-editor";
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout"; import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
import { Actions } from "flexlayout-react"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
interface PreDetailProps { interface PreDetailProps {
children?: ReactNode; children?: ReactNode;

View 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}
/>
);
};

View 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}</>;
};

View 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>
);
};

View 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>
);
};

View File

@ -11,7 +11,7 @@ import { useEffect, useState } from "react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Toggle } from "@/components/ui/toggle"; import { Toggle } from "@/components/ui/toggle";
import { Actions, DockLocation } from "flexlayout-react"; import { Actions, DockLocation } from "flexlayout-react";
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout"; import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
export const ViewBotButton = () => { export const ViewBotButton = () => {
const t = useTranslations(); const t = useTranslations();

View 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;
}
};

View File

@ -10,7 +10,7 @@ import { LoaderCircleIcon, PlayIcon } from "lucide-react";
import { TooltipButton } from "@/components/tooltip-button"; import { TooltipButton } from "@/components/tooltip-button";
import { useProblemEditorStore } from "@/stores/problem-editor"; import { useProblemEditorStore } from "@/stores/problem-editor";
import { JudgeToast } from "@/features/problems/components/judge-toast"; import { JudgeToast } from "@/features/problems/components/judge-toast";
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout"; import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
interface JudgeButtonProps { interface JudgeButtonProps {
className?: string; className?: string;

View File

@ -1,94 +1,24 @@
"use client"; "use client";
import { import { FlexLayout } from "./flexlayout";
BotIcon, import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
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";
interface ProblemFlexLayoutProps { interface ProblemFlexLayoutProps {
components: Record<string, React.ReactNode>; components: Record<string, React.ReactNode>;
} }
export const ProblemFlexLayout = ({ components }: ProblemFlexLayoutProps) => { export const ProblemFlexLayout = ({ components }: ProblemFlexLayoutProps) => {
const t = useTranslations("ProblemPage"); const { hasHydrated, model, jsonModel, setModel, setJsonModel } =
const { model, setModel, jsonModel, setJsonModel } =
useProblemFlexLayoutStore(); 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 ( return (
<Layout <FlexLayout
components={components}
hasHydrated={hasHydrated}
model={model} model={model}
factory={factory} jsonModel={jsonModel}
onRenderTab={onRenderTab} setModel={setModel}
onModelChange={onModelChange} setJsonModel={setJsonModel}
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;
}
};

View File

@ -4,7 +4,7 @@ import { Actions } from "flexlayout-react";
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 { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout"; import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { usePathname, useRouter, useSearchParams } from "next/navigation";
export const DetailHeader = () => { export const DetailHeader = () => {

View File

@ -3,7 +3,7 @@
import { Actions } from "flexlayout-react"; import { Actions } from "flexlayout-react";
import { BookOpenIcon } from "lucide-react"; import { BookOpenIcon } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useProblemFlexLayoutStore } from "@/stores/problem-flexlayout"; import { useProblemFlexLayoutStore } from "@/stores/flexlayout";
export const ViewSolutionButton = () => { export const ViewSolutionButton = () => {
const { model } = useProblemFlexLayoutStore(); const { model } = useProblemFlexLayoutStore();

View File

@ -7,7 +7,7 @@ import { Locale, Submission } from "@/generated/client";
import { Actions, DockLocation } from "flexlayout-react"; import { Actions, DockLocation } from "flexlayout-react";
import { getColorClassForStatus } from "@/config/status"; import { getColorClassForStatus } from "@/config/status";
import { TableCell, TableRow } from "@/components/ui/table"; 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 { usePathname, useRouter, useSearchParams } from "next/navigation";
import { getIconForLanguage, getLabelForLanguage } from "@/config/language"; import { getIconForLanguage, getLabelForLanguage } from "@/config/language";

View 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>
);
};

View File

@ -44,6 +44,9 @@ export const ProblemsetTable = async () => {
}, },
}, },
}, },
where: {
isPublished: true,
},
}); });
const completedProblems = new Set<string>(); const completedProblems = new Set<string>();
@ -85,7 +88,9 @@ export const ProblemsetTable = async () => {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody className="[&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg"> <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 <TableRow
key={problem.id} key={problem.id}
className="h-10 border-b-0 odd:bg-muted/50 hover:text-blue-500 hover:bg-muted" 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}`} href={`/problems/${problem.id}`}
className="hover:text-blue-500" className="hover:text-blue-500"
> >
{index + 1}.{" "} {problem.displayId}.{" "}
{getLocalizedTitle(problem.localizations, locale as Locale)} {getLocalizedTitle(problem.localizations, locale as Locale)}
</Link> </Link>
</TableCell> </TableCell>

211
src/stores/flexlayout.ts Normal file
View 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
);

View File

@ -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,
}),
}
)
);