feat: add language selector and Monaco editor components; update answer page with language selection and editor functionality

This commit is contained in:
ngc2207 2024-11-23 13:25:41 +08:00
parent b474b6be10
commit 026b49a89f
8 changed files with 252 additions and 3 deletions

View File

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

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

View File

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

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

View File

@ -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 = [
{

View File

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

View File

@ -0,0 +1,3 @@
export default function CodeEditorPage({ slug }: { slug: string }) {
return <div className="h-full flex flex-col"></div>;
}