From b36c4af282cb952696fc2160c588bfac54c72860 Mon Sep 17 00:00:00 2001 From: fly6516 Date: Sat, 17 May 2025 03:36:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E6=B7=BB=E5=8A=A0=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在根布局中添加编辑器配置面板按钮- 实现编辑器配置面板组件,支持字体、字号、行高等设置 - 新增 useEditorConfigStore 钩子,用于管理编辑器配置状态 - 在问题编辑器中应用用户配置,并支持 TypeScript 语法 --- src/app/layout.tsx | 91 ++++++++++++------- src/components/editor-config-panel.tsx | 71 +++++++++++++++ src/components/problem-editor.tsx | 117 ++++++++++++++++--------- src/lib/store.ts | 37 ++++++++ 4 files changed, 243 insertions(+), 73 deletions(-) create mode 100644 src/components/editor-config-panel.tsx create mode 100644 src/lib/store.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d50795c..5e42c90 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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 ( - - - - -
{children}
- - -
-
+ + + Judge4C + + + + + + {/* 新增的配置面板按钮 */} +
+ + +
+ + {/* 配置面板 */} + {showConfigPanel && ( +
+
+
+ +
+ +
+
+ )} + + {children} +
); diff --git a/src/components/editor-config-panel.tsx b/src/components/editor-config-panel.tsx new file mode 100644 index 0000000..2b7a535 --- /dev/null +++ b/src/components/editor-config-panel.tsx @@ -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) => { + updateConfig({ fontFamily: e.target.value }); + }; + + const handleFontSizeChange = (e: React.ChangeEvent) => { + updateConfig({ fontSize: parseInt(e.target.value) }); + }; + + const handleLineHeightChange = (e: React.ChangeEvent) => { + updateConfig({ lineHeight: parseInt(e.target.value) }); + }; + + const handleReset = () => { + useEditorConfigStore.getState().resetConfig(); + }; + + return ( +
+

编辑器配置

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src/components/problem-editor.tsx b/src/components/problem-editor.tsx index e0f8d8e..6b89159 100644 --- a/src/components/problem-editor.tsx +++ b/src/components/problem-editor.tsx @@ -11,51 +11,84 @@ 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( - async () => { - await import("vscode"); - const monaco = await import("monaco-editor"); +export const ProblemEditor = () => { + const editorRef = useRef(null); + const monacoRef = useRef(null); + + // 使用配置状态 + const { config } = useEditorConfigStore(); - self.MonacoEnvironment = { - getWorker(_, label) { - if (label === "json") { - return new Worker( - new URL("monaco-editor/esm/vs/language/json/json.worker.js", import.meta.url) - ); - } - if (label === "css" || label === "scss" || label === "less") { - return new Worker( - new URL("monaco-editor/esm/vs/language/css/css.worker.js", import.meta.url) - ); - } - if (label === "html" || label === "handlebars" || label === "razor") { - return new Worker( - new URL("monaco-editor/esm/vs/language/html/html.worker.js", import.meta.url) - ); - } - if (label === "typescript" || label === "javascript") { - return new Worker( - new URL("monaco-editor/esm/vs/language/typescript/ts.worker.js", import.meta.url) - ); - } - return new Worker( - new URL("monaco-editor/esm/vs/editor/editor.worker.js", import.meta.url) - ); - }, - }; - const { loader } = await import("@monaco-editor/react"); - loader.config({ monaco }); - return (await import("@monaco-editor/react")).Editor; - }, - { - ssr: false, - loading: () => , - } -); + 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"); + + self.MonacoEnvironment = { + getWorker(_, label) { + if (label === "json") { + return new Worker( + new URL("monaco-editor/esm/vs/language/json/json.worker.js", import.meta.url) + ); + } + if (label === "css" || label === "scss" || label === "less") { + return new Worker( + new URL("monaco-editor/esm/vs/language/css/css.worker.js", import.meta.url) + ); + } + if (label === "html" || label === "handlebars" || label === "razor") { + return new Worker( + new URL("monaco-editor/esm/vs/language/html/html.worker.js", import.meta.url) + ); + } + if (label === "typescript" || label === "javascript") { + return new Worker( + new URL("monaco-editor/esm/vs/language/typescript/ts.worker.js", import.meta.url) + ); + } + return new Worker( + new URL("monaco-editor/esm/vs/editor/editor.worker.js", import.meta.url) + ); + }, + }; + const { loader } = await import("@monaco-editor/react"); + loader.config({ monaco }); + return (await import("@monaco-editor/react")).Editor; + }, + { + ssr: false, + loading: () => , + } + ); -export function ProblemEditor() { const { hydrated, editor, diff --git a/src/lib/store.ts b/src/lib/store.ts new file mode 100644 index 0000000..f090145 --- /dev/null +++ b/src/lib/store.ts @@ -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) => void; + resetConfig: () => void; + defaultConfig: editor.IEditorConstructionOptions; +} + +export const useEditorConfigStore = create((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 }; + }), + }; +}); \ No newline at end of file