mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-18 07:16:34 +00:00
feat(editor): 添加编辑器配置功能并优化布局
- 在根布局中添加编辑器配置面板按钮- 实现编辑器配置面板组件,支持字体、字号、行高等设置 - 新增 useEditorConfigStore 钩子,用于管理编辑器配置状态 - 在问题编辑器中应用用户配置,并支持 TypeScript 语法
This commit is contained in:
parent
443adf055b
commit
b36c4af282
@ -1,39 +1,68 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
import { Toaster } from "sonner";
|
import { Inter } from 'next/font/google';
|
||||||
import type { Metadata } from "next";
|
import { ThemeProvider } from '@/components/theme-provider';
|
||||||
import { getLocale } from "next-intl/server";
|
import { ThemeToggle } from '@/components/theme-toggle';
|
||||||
import { NextIntlClientProvider } from "next-intl";
|
import { EditorConfigPanel } from '@/components/editor-config-panel';
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import {useState} from "react";
|
||||||
import { SettingsDialog } from "@/components/settings-dialog";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
const inter = Inter({
|
||||||
title: "Judge4c",
|
subsets: ['latin'],
|
||||||
description:
|
display: 'swap',
|
||||||
"A full-stack, open-source online judge platform designed to elevate college programming education.",
|
});
|
||||||
};
|
|
||||||
|
|
||||||
interface RootLayoutProps {
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}) {
|
||||||
|
const [showConfigPanel, setShowConfigPanel] = useState(false);
|
||||||
export default async function RootLayout({ children }: RootLayoutProps) {
|
|
||||||
const locale = await getLocale();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale} className="h-full" suppressHydrationWarning>
|
<html lang="zh-CN" suppressHydrationWarning>
|
||||||
<body className="flex min-h-full antialiased">
|
<head>
|
||||||
<NextIntlClientProvider>
|
<title>Judge4C</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/icon.svg" />
|
||||||
|
</head>
|
||||||
|
<body className={inter.className}>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="system"
|
defaultTheme="system"
|
||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<div className="w-full">{children}</div>
|
{/* 新增的配置面板按钮 */}
|
||||||
<SettingsDialog />
|
<div className="fixed top-4 right-4 z-50">
|
||||||
<Toaster position="top-right" />
|
<ThemeToggle />
|
||||||
|
<button
|
||||||
|
onClick={() => setShowConfigPanel(true)}
|
||||||
|
className="mt-2 px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-md hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
编辑器设置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 配置面板 */}
|
||||||
|
{showConfigPanel && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50">
|
||||||
|
<div className="relative w-full max-w-md bg-white dark:bg-gray-800 rounded-lg shadow-xl">
|
||||||
|
<div className="absolute top-2 right-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowConfigPanel(false)}
|
||||||
|
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<EditorConfigPanel />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{children}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</NextIntlClientProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
71
src/components/editor-config-panel.tsx
Normal file
71
src/components/editor-config-panel.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useEditorConfigStore } from '@/lib/store';
|
||||||
|
|
||||||
|
export const EditorConfigPanel = () => {
|
||||||
|
const { config, updateConfig } = useEditorConfigStore();
|
||||||
|
|
||||||
|
const handleFontFamilyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateConfig({ fontFamily: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFontSizeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateConfig({ fontSize: parseInt(e.target.value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLineHeightChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateConfig({ lineHeight: parseInt(e.target.value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
useEditorConfigStore.getState().resetConfig();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4 space-y-4">
|
||||||
|
<h2 className="text-lg font-semibold">编辑器配置</h2>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">字体</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={config.fontFamily}
|
||||||
|
onChange={handleFontFamilyChange}
|
||||||
|
className="w-full p-2 border rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">字体大小</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="10"
|
||||||
|
max="24"
|
||||||
|
value={config.fontSize}
|
||||||
|
onChange={handleFontSizeChange}
|
||||||
|
className="w-full p-2 border rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">行高</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="18"
|
||||||
|
max="36"
|
||||||
|
value={config.lineHeight}
|
||||||
|
onChange={handleLineHeightChange}
|
||||||
|
className="w-full p-2 border rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<button
|
||||||
|
onClick={handleReset}
|
||||||
|
className="px-4 py-2 text-sm font-medium text-white bg-red-500 rounded-md hover:bg-red-600"
|
||||||
|
>
|
||||||
|
重置默认配置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -11,6 +11,40 @@ import { useCallback, useEffect, useRef } from "react";
|
|||||||
import { connectToLanguageServer } from "@/lib/language-server";
|
import { connectToLanguageServer } from "@/lib/language-server";
|
||||||
import type { MonacoLanguageClient } from "monaco-languageclient";
|
import type { MonacoLanguageClient } from "monaco-languageclient";
|
||||||
import { DefaultEditorOptionConfig } from "@/config/editor-option";
|
import { DefaultEditorOptionConfig } from "@/config/editor-option";
|
||||||
|
import { useEditorConfigStore } from '@/lib/store'; // 新增导入
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
|
||||||
|
export const ProblemEditor = () => {
|
||||||
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||||
|
const monacoRef = useRef<typeof monaco | null>(null);
|
||||||
|
|
||||||
|
// 使用配置状态
|
||||||
|
const { config } = useEditorConfigStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!monacoRef.current || !editorRef.current) return;
|
||||||
|
|
||||||
|
// 设置语言和主题
|
||||||
|
const { languages } = monaco;
|
||||||
|
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||||
|
// target保持使用ScriptTarget
|
||||||
|
target: monaco.languages.typescript.ScriptTarget.ESNext,
|
||||||
|
// module使用正确的ModuleKind类型
|
||||||
|
module: monaco.languages.typescript.ModuleKind.ESNext,
|
||||||
|
strict: true,
|
||||||
|
jsx: monaco.languages.typescript.JsxEmit.React,
|
||||||
|
esModuleInterop: true,
|
||||||
|
isolatedModules: true,
|
||||||
|
experimentalDecorators: true,
|
||||||
|
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||||
|
allowJs: true,
|
||||||
|
allowSyntheticDefaultImports: true,
|
||||||
|
typeRoots: ["../node_modules/@types"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 应用用户配置
|
||||||
|
editorRef.current.updateOptions(config);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Dynamically import Monaco Editor with SSR disabled
|
// Dynamically import Monaco Editor with SSR disabled
|
||||||
const Editor = dynamic(
|
const Editor = dynamic(
|
||||||
@ -55,7 +89,6 @@ const Editor = dynamic(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export function ProblemEditor() {
|
|
||||||
const {
|
const {
|
||||||
hydrated,
|
hydrated,
|
||||||
editor,
|
editor,
|
||||||
|
37
src/lib/store.ts
Normal file
37
src/lib/store.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { editor } from 'monaco-editor';
|
||||||
|
import { DefaultEditorOptionConfig } from '@/config/editor-option';
|
||||||
|
|
||||||
|
interface EditorConfigState {
|
||||||
|
config: editor.IEditorConstructionOptions;
|
||||||
|
updateConfig: (newConfig: Partial<editor.IEditorConstructionOptions>) => void;
|
||||||
|
resetConfig: () => void;
|
||||||
|
defaultConfig: editor.IEditorConstructionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useEditorConfigStore = create<EditorConfigState>((set) => {
|
||||||
|
// 从localStorage读取保存的配置
|
||||||
|
const savedConfig = localStorage.getItem('editorConfig');
|
||||||
|
const parsedConfig = savedConfig ? JSON.parse(savedConfig) : {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
config: {
|
||||||
|
...DefaultEditorOptionConfig,
|
||||||
|
...parsedConfig,
|
||||||
|
},
|
||||||
|
defaultConfig: DefaultEditorOptionConfig,
|
||||||
|
updateConfig: (newConfig) => set((state) => {
|
||||||
|
const updatedConfig = {
|
||||||
|
...state.config,
|
||||||
|
...newConfig,
|
||||||
|
};
|
||||||
|
// 保存到localStorage
|
||||||
|
localStorage.setItem('editorConfig', JSON.stringify(updatedConfig));
|
||||||
|
return { config: updatedConfig };
|
||||||
|
}),
|
||||||
|
resetConfig: () => set((state) => {
|
||||||
|
localStorage.removeItem('editorConfig');
|
||||||
|
return { config: state.defaultConfig };
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user