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 { Toaster } from "sonner";
|
||||
import type { Metadata } from "next";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { SettingsDialog } from "@/components/settings-dialog";
|
||||
import { Inter } from 'next/font/google';
|
||||
import { ThemeProvider } from '@/components/theme-provider';
|
||||
import { ThemeToggle } from '@/components/theme-toggle';
|
||||
import { EditorConfigPanel } from '@/components/editor-config-panel';
|
||||
import {useState} from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Judge4c",
|
||||
description:
|
||||
"A full-stack, open-source online judge platform designed to elevate college programming education.",
|
||||
};
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
interface RootLayoutProps {
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default async function RootLayout({ children }: RootLayoutProps) {
|
||||
const locale = await getLocale();
|
||||
}) {
|
||||
const [showConfigPanel, setShowConfigPanel] = useState(false);
|
||||
|
||||
return (
|
||||
<html lang={locale} className="h-full" suppressHydrationWarning>
|
||||
<body className="flex min-h-full antialiased">
|
||||
<NextIntlClientProvider>
|
||||
<html lang="zh-CN" suppressHydrationWarning>
|
||||
<head>
|
||||
<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
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<div className="w-full">{children}</div>
|
||||
<SettingsDialog />
|
||||
<Toaster position="top-right" />
|
||||
{/* 新增的配置面板按钮 */}
|
||||
<div className="fixed top-4 right-4 z-50">
|
||||
<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>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</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,9 +11,43 @@ import { useCallback, useEffect, useRef } from "react";
|
||||
import { connectToLanguageServer } from "@/lib/language-server";
|
||||
import type { MonacoLanguageClient } from "monaco-languageclient";
|
||||
import { DefaultEditorOptionConfig } from "@/config/editor-option";
|
||||
import { useEditorConfigStore } from '@/lib/store'; // 新增导入
|
||||
import * as monaco from 'monaco-editor';
|
||||
|
||||
// Dynamically import Monaco Editor with SSR disabled
|
||||
const Editor = dynamic(
|
||||
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
|
||||
const Editor = dynamic(
|
||||
async () => {
|
||||
await import("vscode");
|
||||
const monaco = await import("monaco-editor");
|
||||
@ -53,9 +87,8 @@ const Editor = dynamic(
|
||||
ssr: false,
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
export function ProblemEditor() {
|
||||
const {
|
||||
hydrated,
|
||||
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