mirror of
https://litchi.icu/ngc2207/judge4c-latest.git
synced 2025-07-12 21:53:50 +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>
|
||||
{tabsItems.map((tabsItem) => (
|
||||
<TabsContent value={tabsItem.value} key={tabsItem.value}>
|
||||
<TabsContent
|
||||
value={tabsItem.value}
|
||||
key={tabsItem.value}
|
||||
className="h-full mt-0"
|
||||
>
|
||||
{tabsItem.content}
|
||||
</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 ConsolePage from "./views/console-page";
|
||||
import { TabsCard } from "./components/tabs-card";
|
||||
import CodeEditorPage from "./views/code-editor-page";
|
||||
import DescriptionPage from "./views/description-page";
|
||||
|
||||
export default async function ProblemLayout({
|
||||
@ -44,6 +45,12 @@ export default async function ProblemLayout({
|
||||
value: "code",
|
||||
content: <AnswerPage slug={slug} />,
|
||||
},
|
||||
{
|
||||
icon: CodeXmlIcon,
|
||||
label: "代码编辑器",
|
||||
value: "editor",
|
||||
content: <CodeEditorPage slug={slug} />,
|
||||
},
|
||||
];
|
||||
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 }) {
|
||||
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 (
|
||||
<div className="p-4">
|
||||
<h1>Answer Page: {slug}</h1>
|
||||
<div className="h-full flex flex-col">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
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