mirror of
https://litchi.icu/ngc2207/judge4c-latest.git
synced 2025-07-13 03:43:48 +00:00
feat: add language selector and Monaco editor components; update answer page with language selection and editor functionality
This commit is contained in:
parent
b474b6be10
commit
026b49a89f
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
|
interface LanguageOption {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LanguageSelectorProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (language: string) => void;
|
||||||
|
options: LanguageOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LanguageSelector = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
options,
|
||||||
|
}: LanguageSelectorProps) => {
|
||||||
|
// 检查 options 数组是否为空或未定义
|
||||||
|
if (!options || options.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select value={value} onValueChange={onChange}>
|
||||||
|
<SelectTrigger className="h-6 w-auto border-none px-1.5 py-0.5">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={options[0].label}
|
||||||
|
defaultValue={options[0].value}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{options.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LanguageSelector;
|
28
src/app/(app)/problems/[slug]/components/monaco-editor.tsx
Normal file
28
src/app/(app)/problems/[slug]/components/monaco-editor.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Editor from "@monaco-editor/react";
|
||||||
|
|
||||||
|
interface MonacoEditorProps {
|
||||||
|
language: string;
|
||||||
|
template: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MonacoEditor({
|
||||||
|
language,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: MonacoEditorProps) {
|
||||||
|
return (
|
||||||
|
<div className="h-full">
|
||||||
|
<Editor
|
||||||
|
theme="vs-light"
|
||||||
|
language={language}
|
||||||
|
value={value}
|
||||||
|
onChange={(value) => onChange(value ?? "")}
|
||||||
|
options={{ minimap: { enabled: false } }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -32,7 +32,11 @@ export function TabsCard({ tabsItems }: TabsCardProps) {
|
|||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
{tabsItems.map((tabsItem) => (
|
{tabsItems.map((tabsItem) => (
|
||||||
<TabsContent value={tabsItem.value} key={tabsItem.value}>
|
<TabsContent
|
||||||
|
value={tabsItem.value}
|
||||||
|
key={tabsItem.value}
|
||||||
|
className="h-full mt-0"
|
||||||
|
>
|
||||||
{tabsItem.content}
|
{tabsItem.content}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
))}
|
))}
|
||||||
|
27
src/app/(app)/problems/[slug]/components/tooltip-button.tsx
Normal file
27
src/app/(app)/problems/[slug]/components/tooltip-button.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
|
||||||
|
interface TooltipButtonProps {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
tooltipText: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TooltipButton = ({ icon, tooltipText, onClick }: TooltipButtonProps) => (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild onClick={onClick}>
|
||||||
|
{icon}
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" sideOffset={9}>
|
||||||
|
<p>{tooltipText}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TooltipButton;
|
@ -13,6 +13,7 @@ import CommitPage from "./views/commit-page";
|
|||||||
import AnswerPage from "./views/answer-page";
|
import AnswerPage from "./views/answer-page";
|
||||||
import ConsolePage from "./views/console-page";
|
import ConsolePage from "./views/console-page";
|
||||||
import { TabsCard } from "./components/tabs-card";
|
import { TabsCard } from "./components/tabs-card";
|
||||||
|
import CodeEditorPage from "./views/code-editor-page";
|
||||||
import DescriptionPage from "./views/description-page";
|
import DescriptionPage from "./views/description-page";
|
||||||
|
|
||||||
export default async function ProblemLayout({
|
export default async function ProblemLayout({
|
||||||
@ -44,6 +45,12 @@ export default async function ProblemLayout({
|
|||||||
value: "code",
|
value: "code",
|
||||||
content: <AnswerPage slug={slug} />,
|
content: <AnswerPage slug={slug} />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: CodeXmlIcon,
|
||||||
|
label: "代码编辑器",
|
||||||
|
value: "editor",
|
||||||
|
content: <CodeEditorPage slug={slug} />,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const consoleTabsItems = [
|
const consoleTabsItems = [
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,138 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { useState } from "react";
|
||||||
|
import MonacoEditor from "../components/monaco-editor";
|
||||||
|
import TooltipButton from "../components/tooltip-button";
|
||||||
|
import { Play, RefreshCw, Save, Settings } from "lucide-react";
|
||||||
|
|
||||||
export default function AnswerPage({ slug }: { slug: string }) {
|
export default function AnswerPage({ slug }: { slug: string }) {
|
||||||
|
const templates = [
|
||||||
|
{
|
||||||
|
language: "c",
|
||||||
|
template: `/**
|
||||||
|
* Note: The returned array must be malloced, assume caller calls free().
|
||||||
|
*/
|
||||||
|
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
|
||||||
|
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: "java",
|
||||||
|
template: `class Solution {
|
||||||
|
public int[] twoSum(int[] nums, int target) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 初始化当前选择的语言和模板
|
||||||
|
const [selectedLanguage, setSelectedLanguage] = useState(
|
||||||
|
templates[0].language
|
||||||
|
);
|
||||||
|
const [languageValues, setLanguageValues] = useState<{
|
||||||
|
[key: string]: string;
|
||||||
|
}>({});
|
||||||
|
|
||||||
|
// 根据选择的语言更新模板
|
||||||
|
const handleLanguageChange = (language: string) => {
|
||||||
|
setSelectedLanguage(language);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前语言的值,如果状态中没有,则使用模板
|
||||||
|
const getCurrentValue = () => {
|
||||||
|
return (
|
||||||
|
languageValues[selectedLanguage] ||
|
||||||
|
templates.find((t) => t.language === selectedLanguage)?.template ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理编辑器内容变化
|
||||||
|
const handleEditorChange = (value: string | undefined) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
setLanguageValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedLanguage]: value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置代码为当前语言的模板
|
||||||
|
const handleResetCode = () => {
|
||||||
|
const template = templates.find(
|
||||||
|
(t) => t.language === selectedLanguage
|
||||||
|
)?.template;
|
||||||
|
if (template) {
|
||||||
|
setLanguageValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedLanguage]: template,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从 templates 中提取语言作为选项
|
||||||
|
const options = templates.map((template) => ({
|
||||||
|
value: template.language,
|
||||||
|
label: template.language.toUpperCase(),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="h-full flex flex-col">
|
||||||
<h1>Answer Page: {slug}</h1>
|
<header className="h-8 border-b p-1 flex flex-row items-center justify-between">
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Select value={selectedLanguage} onValueChange={handleLanguageChange}>
|
||||||
|
<SelectTrigger className="h-6 w-[75px] border-none shadow-none px-1.5 py-0.5">
|
||||||
|
<SelectValue placeholder={options[0].label} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{options.map((option) => (
|
||||||
|
<SelectItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center gap-2 mr-4">
|
||||||
|
<TooltipButton
|
||||||
|
icon={<RefreshCw className="h-4 w-auto cursor-pointer" />}
|
||||||
|
tooltipText="重置"
|
||||||
|
onClick={handleResetCode}
|
||||||
|
/>
|
||||||
|
<TooltipButton
|
||||||
|
icon={<Settings className="h-4 w-auto cursor-pointer" />}
|
||||||
|
tooltipText="设置"
|
||||||
|
/>
|
||||||
|
<TooltipButton
|
||||||
|
icon={<Play className="h-4 w-auto cursor-pointer" />}
|
||||||
|
tooltipText="运行"
|
||||||
|
/>
|
||||||
|
<TooltipButton
|
||||||
|
icon={<Save className="h-4 w-auto cursor-pointer" />}
|
||||||
|
tooltipText="保存"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main className="flex-grow">
|
||||||
|
<MonacoEditor
|
||||||
|
language={selectedLanguage}
|
||||||
|
template={
|
||||||
|
templates.find((t) => t.language === selectedLanguage)?.template ||
|
||||||
|
""
|
||||||
|
}
|
||||||
|
value={getCurrentValue()}
|
||||||
|
onChange={handleEditorChange}
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
<footer className="h-9 bg-muted rounded-b-lg px-3 py-2"></footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
3
src/app/(app)/problems/[slug]/views/code-editor-page.tsx
Normal file
3
src/app/(app)/problems/[slug]/views/code-editor-page.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function CodeEditorPage({ slug }: { slug: string }) {
|
||||||
|
return <div className="h-full flex flex-col"></div>;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user