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