mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-07-04 09:20:53 +00:00
feat(panel-layout): add scrollable content support with isScroll prop
- Add ScrollArea and ScrollBar components from ui/scroll-area - Introduce optional isScroll prop (defaults to true) to control scrolling - Maintain backward compatibility with existing usage
This commit is contained in:
parent
b52d96b645
commit
573007398e
@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { CoreEditor } from "@/components/core-editor";
|
import { CoreEditor } from "@/components/core-editor";
|
||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { updateProblemTemplate } from "@/components/creater/problem-maintain";
|
import { updateProblemTemplate } from "@/components/creater/problem-maintain";
|
||||||
@ -70,50 +69,47 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<CardHeader className="px-6 py-4">
|
||||||
<CardHeader className="px-6 py-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<span>代码模板</span>
|
||||||
<span>代码模板</span>
|
<Button onClick={handleSave}>保存</Button>
|
||||||
<Button onClick={handleSave}>保存</Button>
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="language-select">编程语言</Label>
|
||||||
|
<select
|
||||||
|
id="language-select"
|
||||||
|
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
|
||||||
|
value={codeTemplate.language}
|
||||||
|
onChange={(e) => handleLanguageChange(e.target.value)}
|
||||||
|
>
|
||||||
|
{templates.map((t) => (
|
||||||
|
<option key={t.language} value={t.language}>
|
||||||
|
{t.language.toUpperCase()}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="language-select">编程语言</Label>
|
|
||||||
<select
|
|
||||||
id="language-select"
|
|
||||||
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
|
|
||||||
value={codeTemplate.language}
|
|
||||||
onChange={(e) => handleLanguageChange(e.target.value)}
|
|
||||||
>
|
|
||||||
{templates.map((t) => (
|
|
||||||
<option key={t.language} value={t.language}>
|
|
||||||
{t.language.toUpperCase()}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="code-editor">代码模板内容</Label>
|
<Label htmlFor="code-editor">代码模板内容</Label>
|
||||||
<div className="border rounded-md h-[500px]">
|
<div className="border rounded-md h-[500px]">
|
||||||
<CoreEditor
|
<CoreEditor
|
||||||
language={codeTemplate.language}
|
language={codeTemplate.language}
|
||||||
value={codeTemplate.content}
|
value={codeTemplate.content}
|
||||||
path={`/${problemId}.${codeTemplate.language}`}
|
path={`/${problemId}.${codeTemplate.language}`}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setCodeTemplate({ ...codeTemplate, content: value || "" })
|
setCodeTemplate({ ...codeTemplate, content: value || "" })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</CardContent>
|
||||||
<ScrollBar orientation="horizontal" />
|
</Card>
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import { CoreEditor } from "@/components/core-editor";
|
|||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { VideoEmbed } from "@/components/content/video-embed";
|
import { VideoEmbed } from "@/components/content/video-embed";
|
||||||
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
@ -105,114 +104,111 @@ export default function EditDescriptionPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<CardHeader>
|
||||||
<CardHeader>
|
<CardTitle>题目描述</CardTitle>
|
||||||
<CardTitle>题目描述</CardTitle>
|
</CardHeader>
|
||||||
</CardHeader>
|
<CardContent className="space-y-6">
|
||||||
<CardContent className="space-y-6">
|
{/* 语言切换 */}
|
||||||
{/* 语言切换 */}
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<Label>选择语言</Label>
|
||||||
<Label>选择语言</Label>
|
<div className="flex space-x-2">
|
||||||
<div className="flex space-x-2">
|
<select
|
||||||
<select
|
value={currentLocale}
|
||||||
value={currentLocale}
|
onChange={(e) => setCurrentLocale(e.target.value)}
|
||||||
onChange={(e) => setCurrentLocale(e.target.value)}
|
className="border rounded-md px-3 py-2"
|
||||||
className="border rounded-md px-3 py-2"
|
>
|
||||||
>
|
{locales.map((locale) => (
|
||||||
{locales.map((locale) => (
|
<option key={locale} value={locale}>
|
||||||
<option key={locale} value={locale}>
|
{locale}
|
||||||
{locale}
|
</option>
|
||||||
</option>
|
))}
|
||||||
))}
|
</select>
|
||||||
</select>
|
|
||||||
<Input
|
|
||||||
placeholder="添加新语言"
|
|
||||||
value={customLocale}
|
|
||||||
onChange={(e) => setCustomLocale(e.target.value)}
|
|
||||||
/>
|
|
||||||
<Button onClick={handleAddCustomLocale}>添加</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 标题输入 */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="description-title">标题</Label>
|
|
||||||
<Input
|
<Input
|
||||||
id="description-title"
|
placeholder="添加新语言"
|
||||||
value={description.title}
|
value={customLocale}
|
||||||
onChange={(e) =>
|
onChange={(e) => setCustomLocale(e.target.value)}
|
||||||
setDescription({ ...description, title: e.target.value })
|
|
||||||
}
|
|
||||||
placeholder="输入题目标题"
|
|
||||||
/>
|
/>
|
||||||
|
<Button onClick={handleAddCustomLocale}>添加</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 编辑/预览切换 */}
|
{/* 标题输入 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center space-x-2">
|
<Label htmlFor="description-title">标题</Label>
|
||||||
<Button
|
<Input
|
||||||
type="button"
|
id="description-title"
|
||||||
variant={viewMode === "edit" ? "default" : "outline"}
|
value={description.title}
|
||||||
onClick={() => setViewMode("edit")}
|
onChange={(e) =>
|
||||||
>
|
setDescription({ ...description, title: e.target.value })
|
||||||
编辑
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={viewMode === "preview" ? "default" : "outline"}
|
|
||||||
onClick={() =>
|
|
||||||
setViewMode(viewMode === "preview" ? "edit" : "preview")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{viewMode === "preview" ? "取消" : "预览"}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={viewMode === "compare" ? "default" : "outline"}
|
|
||||||
onClick={() => setViewMode("compare")}
|
|
||||||
>
|
|
||||||
对比
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button onClick={handleSave}>保存更改</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 编辑/预览区域 */}
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
viewMode === "compare"
|
|
||||||
? "grid grid-cols-2 gap-6"
|
|
||||||
: "flex flex-col gap-6"
|
|
||||||
}
|
}
|
||||||
>
|
placeholder="输入题目标题"
|
||||||
{(viewMode === "edit" || viewMode === "compare") && (
|
/>
|
||||||
<div className="relative h-[600px]">
|
</div>
|
||||||
<CoreEditor
|
|
||||||
value={description.content}
|
{/* 编辑/预览切换 */}
|
||||||
onChange={(newVal) =>
|
<div className="flex items-center justify-between">
|
||||||
setDescription({ ...description, content: newVal || "" })
|
<div className="flex items-center space-x-2">
|
||||||
}
|
<Button
|
||||||
language="markdown"
|
type="button"
|
||||||
className="absolute inset-0 rounded-md border border-input"
|
variant={viewMode === "edit" ? "default" : "outline"}
|
||||||
/>
|
onClick={() => setViewMode("edit")}
|
||||||
</div>
|
>
|
||||||
)}
|
编辑
|
||||||
{viewMode !== "edit" && (
|
</Button>
|
||||||
<div className="prose dark:prose-invert">
|
<Button
|
||||||
<MdxPreview
|
type="button"
|
||||||
source={description.content}
|
variant={viewMode === "preview" ? "default" : "outline"}
|
||||||
components={{ Accordion, VideoEmbed }}
|
onClick={() =>
|
||||||
/>
|
setViewMode(viewMode === "preview" ? "edit" : "preview")
|
||||||
</div>
|
}
|
||||||
)}
|
>
|
||||||
|
{viewMode === "preview" ? "取消" : "预览"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={viewMode === "compare" ? "default" : "outline"}
|
||||||
|
onClick={() => setViewMode("compare")}
|
||||||
|
>
|
||||||
|
对比
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
<div className="flex items-center">
|
||||||
</Card>
|
<Button onClick={handleSave}>保存更改</Button>
|
||||||
<ScrollBar orientation="horizontal" />
|
</div>
|
||||||
</ScrollArea>
|
</div>
|
||||||
|
|
||||||
|
{/* 编辑/预览区域 */}
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
viewMode === "compare"
|
||||||
|
? "grid grid-cols-2 gap-6"
|
||||||
|
: "flex flex-col gap-6"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(viewMode === "edit" || viewMode === "compare") && (
|
||||||
|
<div className="relative h-[600px]">
|
||||||
|
<CoreEditor
|
||||||
|
value={description.content}
|
||||||
|
onChange={(newVal) =>
|
||||||
|
setDescription({ ...description, content: newVal || "" })
|
||||||
|
}
|
||||||
|
language="markdown"
|
||||||
|
className="absolute inset-0 rounded-md border border-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{viewMode !== "edit" && (
|
||||||
|
<div className="prose dark:prose-invert">
|
||||||
|
<MdxPreview
|
||||||
|
source={description.content}
|
||||||
|
components={{ Accordion, VideoEmbed }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Difficulty } from "@/generated/client";
|
import { Difficulty } from "@/generated/client";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { updateProblemDetail } from "@/components/creater/problem-maintain";
|
import { updateProblemDetail } from "@/components/creater/problem-maintain";
|
||||||
@ -80,91 +79,88 @@ export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<CardHeader className="px-6 py-4">
|
||||||
<CardHeader className="px-6 py-4">
|
<div className="flex items-center justify-between">
|
||||||
|
<span>题目详情</span>
|
||||||
|
<Button type="button" onClick={handleSave}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="display-id">显示ID</Label>
|
||||||
|
<Input
|
||||||
|
id="display-id"
|
||||||
|
type="number"
|
||||||
|
value={problemDetails.displayId}
|
||||||
|
onChange={(e) => handleNumberInputChange(e, "displayId")}
|
||||||
|
placeholder="输入显示ID"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="difficulty-select">难度等级</Label>
|
||||||
|
<select
|
||||||
|
id="difficulty-select"
|
||||||
|
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
|
||||||
|
value={problemDetails.difficulty}
|
||||||
|
onChange={handleDifficultyChange}
|
||||||
|
>
|
||||||
|
<option value="EASY">简单</option>
|
||||||
|
<option value="MEDIUM">中等</option>
|
||||||
|
<option value="HARD">困难</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="time-limit">时间限制 (ms)</Label>
|
||||||
|
<Input
|
||||||
|
id="time-limit"
|
||||||
|
type="number"
|
||||||
|
value={problemDetails.timeLimit}
|
||||||
|
onChange={(e) => handleNumberInputChange(e, "timeLimit")}
|
||||||
|
placeholder="输入时间限制"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="memory-limit">内存限制 (字节)</Label>
|
||||||
|
<Input
|
||||||
|
id="memory-limit"
|
||||||
|
type="number"
|
||||||
|
value={problemDetails.memoryLimit}
|
||||||
|
onChange={(e) => handleNumberInputChange(e, "memoryLimit")}
|
||||||
|
placeholder="输入内存限制"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span>题目详情</span>
|
<div className="flex items-center space-x-2">
|
||||||
<Button type="button" onClick={handleSave}>
|
<input
|
||||||
保存
|
id="is-published"
|
||||||
</Button>
|
type="checkbox"
|
||||||
</div>
|
checked={problemDetails.isPublished}
|
||||||
</CardHeader>
|
onChange={(e) =>
|
||||||
<CardContent>
|
setProblemDetails({
|
||||||
<div className="space-y-6">
|
...problemDetails,
|
||||||
<div className="grid grid-cols-2 gap-4">
|
isPublished: e.target.checked,
|
||||||
<div className="space-y-2">
|
})
|
||||||
<Label htmlFor="display-id">显示ID</Label>
|
}
|
||||||
<Input
|
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2"
|
||||||
id="display-id"
|
/>
|
||||||
type="number"
|
<Label htmlFor="is-published" className="text-sm font-medium">
|
||||||
value={problemDetails.displayId}
|
是否发布
|
||||||
onChange={(e) => handleNumberInputChange(e, "displayId")}
|
</Label>
|
||||||
placeholder="输入显示ID"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="difficulty-select">难度等级</Label>
|
|
||||||
<select
|
|
||||||
id="difficulty-select"
|
|
||||||
className="block w-full p-2 border border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700"
|
|
||||||
value={problemDetails.difficulty}
|
|
||||||
onChange={handleDifficultyChange}
|
|
||||||
>
|
|
||||||
<option value="EASY">简单</option>
|
|
||||||
<option value="MEDIUM">中等</option>
|
|
||||||
<option value="HARD">困难</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="time-limit">时间限制 (ms)</Label>
|
|
||||||
<Input
|
|
||||||
id="time-limit"
|
|
||||||
type="number"
|
|
||||||
value={problemDetails.timeLimit}
|
|
||||||
onChange={(e) => handleNumberInputChange(e, "timeLimit")}
|
|
||||||
placeholder="输入时间限制"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="memory-limit">内存限制 (字节)</Label>
|
|
||||||
<Input
|
|
||||||
id="memory-limit"
|
|
||||||
type="number"
|
|
||||||
value={problemDetails.memoryLimit}
|
|
||||||
onChange={(e) => handleNumberInputChange(e, "memoryLimit")}
|
|
||||||
placeholder="输入内存限制"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
id="is-published"
|
|
||||||
type="checkbox"
|
|
||||||
checked={problemDetails.isPublished}
|
|
||||||
onChange={(e) =>
|
|
||||||
setProblemDetails({
|
|
||||||
...problemDetails,
|
|
||||||
isPublished: e.target.checked,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2"
|
|
||||||
/>
|
|
||||||
<Label htmlFor="is-published" className="text-sm font-medium">
|
|
||||||
是否发布
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</CardContent>
|
||||||
<ScrollBar orientation="horizontal" />
|
</Card>
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import { CoreEditor } from "@/components/core-editor";
|
|||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { VideoEmbed } from "@/components/content/video-embed";
|
import { VideoEmbed } from "@/components/content/video-embed";
|
||||||
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { updateProblemSolution } from "@/components/creater/problem-maintain";
|
import { updateProblemSolution } from "@/components/creater/problem-maintain";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
@ -96,116 +95,113 @@ export default function EditSolutionPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<CardHeader>
|
||||||
<CardHeader>
|
<CardTitle>题目解析</CardTitle>
|
||||||
<CardTitle>题目解析</CardTitle>
|
</CardHeader>
|
||||||
</CardHeader>
|
<CardContent className="space-y-6">
|
||||||
<CardContent className="space-y-6">
|
{/* 语言切换 */}
|
||||||
{/* 语言切换 */}
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<Label>选择语言</Label>
|
||||||
<Label>选择语言</Label>
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<select
|
|
||||||
value={currentLocale}
|
|
||||||
onChange={(e) => setCurrentLocale(e.target.value)}
|
|
||||||
className="border rounded-md px-3 py-2"
|
|
||||||
>
|
|
||||||
{locales.map((locale) => (
|
|
||||||
<option key={locale} value={locale}>
|
|
||||||
{locale}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<Input
|
|
||||||
placeholder="添加新语言"
|
|
||||||
value={customLocale}
|
|
||||||
onChange={(e) => setCustomLocale(e.target.value)}
|
|
||||||
/>
|
|
||||||
<Button type="button" onClick={handleAddCustomLocale}>
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 标题输入 (仅展示) */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="solution-title">题解标题</Label>
|
|
||||||
<Input
|
|
||||||
id="solution-title"
|
|
||||||
value={solution.title}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSolution({ ...solution, title: e.target.value })
|
|
||||||
}
|
|
||||||
placeholder="输入题解标题"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 编辑/预览切换 */}
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button
|
<select
|
||||||
type="button"
|
value={currentLocale}
|
||||||
variant={viewMode === "edit" ? "default" : "outline"}
|
onChange={(e) => setCurrentLocale(e.target.value)}
|
||||||
onClick={() => setViewMode("edit")}
|
className="border rounded-md px-3 py-2"
|
||||||
>
|
>
|
||||||
编辑
|
{locales.map((locale) => (
|
||||||
</Button>
|
<option key={locale} value={locale}>
|
||||||
<Button
|
{locale}
|
||||||
type="button"
|
</option>
|
||||||
variant={viewMode === "preview" ? "default" : "outline"}
|
))}
|
||||||
onClick={() =>
|
</select>
|
||||||
setViewMode(viewMode === "preview" ? "edit" : "preview")
|
<Input
|
||||||
}
|
placeholder="添加新语言"
|
||||||
>
|
value={customLocale}
|
||||||
{viewMode === "preview" ? "取消" : "预览"}
|
onChange={(e) => setCustomLocale(e.target.value)}
|
||||||
</Button>
|
/>
|
||||||
<Button
|
<Button type="button" onClick={handleAddCustomLocale}>
|
||||||
type="button"
|
添加
|
||||||
variant={viewMode === "compare" ? "default" : "outline"}
|
|
||||||
onClick={() => setViewMode("compare")}
|
|
||||||
>
|
|
||||||
对比
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 编辑/预览区域 */}
|
{/* 标题输入 (仅展示) */}
|
||||||
<div
|
<div className="space-y-2">
|
||||||
className={
|
<Label htmlFor="solution-title">题解标题</Label>
|
||||||
viewMode === "compare"
|
<Input
|
||||||
? "grid grid-cols-2 gap-6"
|
id="solution-title"
|
||||||
: "flex flex-col gap-6"
|
value={solution.title}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSolution({ ...solution, title: e.target.value })
|
||||||
|
}
|
||||||
|
placeholder="输入题解标题"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 编辑/预览切换 */}
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={viewMode === "edit" ? "default" : "outline"}
|
||||||
|
onClick={() => setViewMode("edit")}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant={viewMode === "preview" ? "default" : "outline"}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === "preview" ? "edit" : "preview")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(viewMode === "edit" || viewMode === "compare") && (
|
{viewMode === "preview" ? "取消" : "预览"}
|
||||||
<div className="relative h-[600px]">
|
|
||||||
<CoreEditor
|
|
||||||
value={solution.content}
|
|
||||||
onChange={(val) =>
|
|
||||||
setSolution({ ...solution, content: val || "" })
|
|
||||||
}
|
|
||||||
language="markdown"
|
|
||||||
className="absolute inset-0 rounded-md border border-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{viewMode !== "edit" && (
|
|
||||||
<div className="prose dark:prose-invert">
|
|
||||||
<MdxPreview
|
|
||||||
source={solution.content}
|
|
||||||
components={{ Accordion, VideoEmbed }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button type="button" onClick={handleSave}>
|
|
||||||
保存更改
|
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
<Button
|
||||||
</Card>
|
type="button"
|
||||||
<ScrollBar orientation="horizontal" />
|
variant={viewMode === "compare" ? "default" : "outline"}
|
||||||
</ScrollArea>
|
onClick={() => setViewMode("compare")}
|
||||||
|
>
|
||||||
|
对比
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 编辑/预览区域 */}
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
viewMode === "compare"
|
||||||
|
? "grid grid-cols-2 gap-6"
|
||||||
|
: "flex flex-col gap-6"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(viewMode === "edit" || viewMode === "compare") && (
|
||||||
|
<div className="relative h-[600px]">
|
||||||
|
<CoreEditor
|
||||||
|
value={solution.content}
|
||||||
|
onChange={(val) =>
|
||||||
|
setSolution({ ...solution, content: val || "" })
|
||||||
|
}
|
||||||
|
language="markdown"
|
||||||
|
className="absolute inset-0 rounded-md border border-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{viewMode !== "edit" && (
|
||||||
|
<div className="prose dark:prose-invert">
|
||||||
|
<MdxPreview
|
||||||
|
source={solution.content}
|
||||||
|
components={{ Accordion, VideoEmbed }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button type="button" onClick={handleSave}>
|
||||||
|
保存更改
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import { Button } from "@/components/ui/button";
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { generateAITestcase } from "@/app/actions/ai-testcase";
|
import { generateAITestcase } from "@/app/actions/ai-testcase";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
|
|
||||||
@ -185,95 +184,85 @@ export default function EditTestcasePanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<CardHeader className="px-6 py-4">
|
||||||
<CardHeader className="px-6 py-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<span>测试用例</span>
|
||||||
<span>测试用例</span>
|
<div className="flex items-center space-x-2">
|
||||||
<div className="flex items-center space-x-2">
|
<Button onClick={handleAITestcase} disabled={isGenerating}>
|
||||||
<Button onClick={handleAITestcase} disabled={isGenerating}>
|
{isGenerating ? "生成中..." : "AI生成"}
|
||||||
{isGenerating ? "生成中..." : "AI生成"}
|
</Button>
|
||||||
</Button>
|
<Button onClick={handleAddTestcase}>添加</Button>
|
||||||
<Button onClick={handleAddTestcase}>添加</Button>
|
<Button variant="secondary" onClick={handleSaveAll}>
|
||||||
<Button variant="secondary" onClick={handleSaveAll}>
|
保存
|
||||||
保存
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{testcases.map((tc, idx) => (
|
||||||
|
<div key={tc.id} className="border p-4 rounded space-y-4">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h3 className="font-medium">测试用例 {idx + 1}</h3>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => handleRemoveTestcase(idx)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>预期输出</Label>
|
||||||
|
<Input
|
||||||
|
value={tc.expectedOutput}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleExpectedOutputChange(idx, e.target.value)
|
||||||
|
}
|
||||||
|
placeholder="输入预期输出"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Label>输入参数</Label>
|
||||||
|
<Button onClick={() => handleAddInput(idx)}>添加输入</Button>
|
||||||
|
</div>
|
||||||
|
{tc.inputs.map((inp, iIdx) => (
|
||||||
|
<div key={iIdx} className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>名称</Label>
|
||||||
|
<Input
|
||||||
|
value={inp.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(idx, iIdx, "name", e.target.value)
|
||||||
|
}
|
||||||
|
placeholder="参数名称"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>值</Label>
|
||||||
|
<Input
|
||||||
|
value={inp.value}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(idx, iIdx, "value", e.target.value)
|
||||||
|
}
|
||||||
|
placeholder="参数值"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{iIdx > 0 && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => handleRemoveInput(idx, iIdx)}
|
||||||
|
>
|
||||||
|
删除输入
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
))}
|
||||||
<CardContent className="space-y-6">
|
</CardContent>
|
||||||
{testcases.map((tc, idx) => (
|
</Card>
|
||||||
<div key={tc.id} className="border p-4 rounded space-y-4">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<h3 className="font-medium">测试用例 {idx + 1}</h3>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => handleRemoveTestcase(idx)}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>预期输出</Label>
|
|
||||||
<Input
|
|
||||||
value={tc.expectedOutput}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleExpectedOutputChange(idx, e.target.value)
|
|
||||||
}
|
|
||||||
placeholder="输入预期输出"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<Label>输入参数</Label>
|
|
||||||
<Button onClick={() => handleAddInput(idx)}>
|
|
||||||
添加输入
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{tc.inputs.map((inp, iIdx) => (
|
|
||||||
<div key={iIdx} className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<Label>名称</Label>
|
|
||||||
<Input
|
|
||||||
value={inp.name}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleInputChange(idx, iIdx, "name", e.target.value)
|
|
||||||
}
|
|
||||||
placeholder="参数名称"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label>值</Label>
|
|
||||||
<Input
|
|
||||||
value={inp.value}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleInputChange(
|
|
||||||
idx,
|
|
||||||
iIdx,
|
|
||||||
"value",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
placeholder="参数值"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{iIdx > 0 && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => handleRemoveInput(idx, iIdx)}
|
|
||||||
>
|
|
||||||
删除输入
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,123 +1,14 @@
|
|||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
CardFooter,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
import {
|
|
||||||
ChartDataPoint,
|
|
||||||
CodeAnalysisRadarChart,
|
|
||||||
} from "@/features/problems/analysis/components/radar-chart";
|
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
|
import { AnalysisContent } from "@/features/problems/analysis/components/content";
|
||||||
export const description = "A server component to fetch code analysis data.";
|
|
||||||
|
|
||||||
interface AnalysisPanelProps {
|
interface AnalysisPanelProps {
|
||||||
submissionId: string | undefined;
|
submissionId: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AnalysisPanel = async ({ submissionId }: AnalysisPanelProps) => {
|
export const AnalysisPanel = ({ submissionId }: AnalysisPanelProps) => {
|
||||||
if (!submissionId) {
|
|
||||||
return (
|
|
||||||
<div className="p-4 text-center text-muted-foreground">
|
|
||||||
No submission ID provided.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeAnalysisData = await prisma.codeAnalysis.findUnique({
|
|
||||||
where: {
|
|
||||||
submissionId: submissionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!codeAnalysisData) {
|
|
||||||
return (
|
|
||||||
<div className="p-4 text-center text-muted-foreground">
|
|
||||||
No analysis data found for this submission.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform the data into a format suitable for the RadarChart
|
|
||||||
const chartData: ChartDataPoint[] = [
|
|
||||||
{
|
|
||||||
kind: "overall",
|
|
||||||
score: codeAnalysisData.overallScore ?? 0,
|
|
||||||
fullMark: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "style",
|
|
||||||
score: codeAnalysisData.styleScore ?? 0,
|
|
||||||
fullMark: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "readability",
|
|
||||||
score: codeAnalysisData.readabilityScore ?? 0,
|
|
||||||
fullMark: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "efficiency",
|
|
||||||
score: codeAnalysisData.efficiencyScore ?? 0,
|
|
||||||
fullMark: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "correctness",
|
|
||||||
score: codeAnalysisData.correctnessScore ?? 0,
|
|
||||||
fullMark: 100,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
<AnalysisContent submissionId={submissionId} />
|
||||||
<Card className="w-full max-w-2xl mx-auto shadow-lg rounded-xl overflow-hidden border-0 bg-background/50 backdrop-blur-sm animate-fade-in">
|
|
||||||
<CardHeader className="items-center pb-2 space-y-1 px-6 pt-6">
|
|
||||||
<CardTitle className="text-2xl font-bold bg-gradient-to-r from-primary to-foreground bg-clip-text text-transparent">
|
|
||||||
Code Analysis
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription className="text-muted-foreground">
|
|
||||||
Detailed evaluation of your code submission
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-6">
|
|
||||||
<CodeAnalysisRadarChart chartData={chartData} />
|
|
||||||
</CardContent>
|
|
||||||
|
|
||||||
<CardFooter className="flex-col items-start gap-4 p-6 pt-0">
|
|
||||||
<div className="w-full space-y-3">
|
|
||||||
<div className="flex justify-between text-sm font-medium">
|
|
||||||
<span className="text-muted-foreground">Overall Score</span>
|
|
||||||
<span className="text-primary">
|
|
||||||
{codeAnalysisData.overallScore ?? "N/A"}
|
|
||||||
<span className="text-muted-foreground">/100</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="relative h-2.5 w-full overflow-hidden rounded-full bg-muted">
|
|
||||||
<div
|
|
||||||
className="h-full bg-gradient-to-r from-primary to-purple-500 rounded-full transition-all duration-700 ease-out"
|
|
||||||
style={{
|
|
||||||
width: `${codeAnalysisData.overallScore ?? 0}%`,
|
|
||||||
transitionProperty: "width",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-muted-foreground bg-muted/40 p-4 rounded-lg w-full border">
|
|
||||||
<h3 className="font-medium mb-2 text-foreground">Feedback</h3>
|
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
|
||||||
{codeAnalysisData.feedback}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ interface BotPanelProps {
|
|||||||
|
|
||||||
export const BotPanel = ({ problemId }: BotPanelProps) => {
|
export const BotPanel = ({ problemId }: BotPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout isScroll={false}>
|
||||||
<Suspense fallback={<BotContentSkeleton />}>
|
<Suspense fallback={<BotContentSkeleton />}>
|
||||||
<BotContent problemId={problemId} />
|
<BotContent problemId={problemId} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
@ -13,7 +13,7 @@ interface CodePanelProps {
|
|||||||
|
|
||||||
export const CodePanel = ({ problemId }: CodePanelProps) => {
|
export const CodePanel = ({ problemId }: CodePanelProps) => {
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout isScroll={false}>
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<CodeToolbar className="border-b" />
|
<CodeToolbar className="border-b" />
|
||||||
<Suspense fallback={<CodeContentSkeleton />}>
|
<Suspense fallback={<CodeContentSkeleton />}>
|
||||||
|
@ -2,7 +2,6 @@ import prisma from "@/lib/prisma";
|
|||||||
import { getLocale } from "next-intl/server";
|
import { getLocale } from "next-intl/server";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import type { Locale, ProblemLocalization } from "@/generated/client";
|
import type { Locale, ProblemLocalization } from "@/generated/client";
|
||||||
|
|
||||||
const getLocalizedDescription = (
|
const getLocalizedDescription = (
|
||||||
@ -40,12 +39,7 @@ export const DescriptionContent = async ({
|
|||||||
|
|
||||||
const description = getLocalizedDescription(descriptions, locale as Locale);
|
const description = getLocalizedDescription(descriptions, locale as Locale);
|
||||||
|
|
||||||
return (
|
return <MdxRenderer source={description} className="p-4 md:p-6" />;
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<MdxRenderer source={description} className="p-4 md:p-6" />
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DescriptionContentSkeleton = () => {
|
export const DescriptionContentSkeleton = () => {
|
||||||
|
@ -16,7 +16,7 @@ export const DetailPanel = ({ submissionId }: DetailPanelProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout isScroll={false}>
|
||||||
<DetailHeader />
|
<DetailHeader />
|
||||||
<Suspense fallback={<DetailContentSkeleton />}>
|
<Suspense fallback={<DetailContentSkeleton />}>
|
||||||
<DetailContent submissionId={submissionId} />
|
<DetailContent submissionId={submissionId} />
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
interface PanelLayoutProps {
|
interface PanelLayoutProps {
|
||||||
|
isScroll?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelLayout = ({ children }: PanelLayoutProps) => {
|
export const PanelLayout = ({
|
||||||
|
isScroll = true,
|
||||||
|
children,
|
||||||
|
}: PanelLayoutProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-lg bg-background overflow-hidden">
|
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-lg bg-background overflow-hidden">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<div className="absolute h-full w-full">{children}</div>
|
<div className="absolute h-full w-full">
|
||||||
|
{isScroll ? (
|
||||||
|
<ScrollArea className="h-full">
|
||||||
|
{children}
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,6 @@ import prisma from "@/lib/prisma";
|
|||||||
import { getLocale } from "next-intl/server";
|
import { getLocale } from "next-intl/server";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import type { Locale, ProblemLocalization } from "@/generated/client";
|
import type { Locale, ProblemLocalization } from "@/generated/client";
|
||||||
|
|
||||||
const getLocalizedSolution = (
|
const getLocalizedSolution = (
|
||||||
@ -38,12 +37,7 @@ export const SolutionContent = async ({ problemId }: SolutionContentProps) => {
|
|||||||
|
|
||||||
const solution = getLocalizedSolution(solutions, locale as Locale);
|
const solution = getLocalizedSolution(solutions, locale as Locale);
|
||||||
|
|
||||||
return (
|
return <MdxRenderer source={solution} className="p-4 md:p-6" />;
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<MdxRenderer source={solution} className="p-4 md:p-6" />
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SolutionContentSkeleton = () => {
|
export const SolutionContentSkeleton = () => {
|
||||||
|
@ -3,7 +3,6 @@ import { CodeXmlIcon } from "lucide-react";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { SubmissionTable } from "@/features/problems/submission/components/table";
|
import { SubmissionTable } from "@/features/problems/submission/components/table";
|
||||||
|
|
||||||
interface SubmissionContentProps {
|
interface SubmissionContentProps {
|
||||||
@ -45,11 +44,12 @@ export const SubmissionContent = async ({
|
|||||||
const session = await auth();
|
const session = await auth();
|
||||||
const userId = session?.user?.id;
|
const userId = session?.user?.id;
|
||||||
|
|
||||||
return (
|
return userId ? (
|
||||||
<ScrollArea className="h-full px-3">
|
<div className="px-3">
|
||||||
{userId ? <SubmissionTable problemId={problemId} /> : <LoginPromptCard />}
|
<SubmissionTable problemId={problemId} />
|
||||||
<ScrollBar orientation="horizontal" />
|
</div>
|
||||||
</ScrollArea>
|
) : (
|
||||||
|
<LoginPromptCard />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { TestcaseTable } from "@/features/problems/testcase/table";
|
import { TestcaseTable } from "@/features/problems/testcase/table";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
|
|
||||||
interface TestcaseContentProps {
|
interface TestcaseContentProps {
|
||||||
problemId: string;
|
problemId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TestcaseContent = ({ problemId }: TestcaseContentProps) => {
|
export const TestcaseContent = ({ problemId }: TestcaseContentProps) => {
|
||||||
return (
|
return <TestcaseTable problemId={problemId} />;
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<TestcaseTable problemId={problemId} />
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TestcaseContentSkeleton = () => {
|
export const TestcaseContentSkeleton = () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user