mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2026-05-31 10:18:52 +00:00
feat(settings): add code editor preferences
This commit is contained in:
parent
705e0f29e4
commit
12c2963447
@ -75,6 +75,15 @@
|
|||||||
"showPassword": "Show password",
|
"showPassword": "Show password",
|
||||||
"hidePassword": "Hide password"
|
"hidePassword": "Hide password"
|
||||||
},
|
},
|
||||||
|
"CodeEditorSettings": {
|
||||||
|
"fontSize": "Font size",
|
||||||
|
"wordWrap": "Wrap long lines",
|
||||||
|
"wordWrapDescription": "Move code to the next line when it does not fit in one line.",
|
||||||
|
"minimap": "Minimap",
|
||||||
|
"minimapDescription": "Show a code overview on the right side of the editor.",
|
||||||
|
"stickyScroll": "Sticky scroll",
|
||||||
|
"stickyScrollDescription": "Keep the current code scope visible while scrolling."
|
||||||
|
},
|
||||||
"DetailsPage": {
|
"DetailsPage": {
|
||||||
"BackButton": "All Submissions",
|
"BackButton": "All Submissions",
|
||||||
"Time": "Submitted on",
|
"Time": "Submitted on",
|
||||||
@ -129,8 +138,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"Appearance": "Appearance",
|
"Appearance": "Appearance",
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
"CodeEditor": "CodeEditor",
|
"CodeEditor": "CodeEditor"
|
||||||
"Advanced": "Advanced"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SignInForm": {
|
"SignInForm": {
|
||||||
|
|||||||
@ -75,6 +75,15 @@
|
|||||||
"showPassword": "显示密码",
|
"showPassword": "显示密码",
|
||||||
"hidePassword": "隐藏密码"
|
"hidePassword": "隐藏密码"
|
||||||
},
|
},
|
||||||
|
"CodeEditorSettings": {
|
||||||
|
"fontSize": "字体大小",
|
||||||
|
"wordWrap": "超长行换行显示",
|
||||||
|
"wordWrapDescription": "一行代码显示不下时,自动换到下一行显示。",
|
||||||
|
"minimap": "小地图",
|
||||||
|
"minimapDescription": "在编辑器右侧显示代码缩略导航。",
|
||||||
|
"stickyScroll": "粘性滚动",
|
||||||
|
"stickyScrollDescription": "滚动时固定显示当前所在的代码结构。"
|
||||||
|
},
|
||||||
"DetailsPage": {
|
"DetailsPage": {
|
||||||
"BackButton": "所有提交记录",
|
"BackButton": "所有提交记录",
|
||||||
"Time": "提交于",
|
"Time": "提交于",
|
||||||
@ -129,8 +138,7 @@
|
|||||||
"nav": {
|
"nav": {
|
||||||
"Appearance": "外观",
|
"Appearance": "外观",
|
||||||
"Language": "语言",
|
"Language": "语言",
|
||||||
"CodeEditor": "代码编辑器",
|
"CodeEditor": "代码编辑器"
|
||||||
"Advanced": "高级设置"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SignInForm": {
|
"SignInForm": {
|
||||||
|
|||||||
88
src/components/code-editor-settings.tsx
Normal file
88
src/components/code-editor-settings.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||||
|
|
||||||
|
const fontSizes = [12, 13, 14, 15, 16, 18, 20];
|
||||||
|
|
||||||
|
export function CodeEditorSettings() {
|
||||||
|
const t = useTranslations("CodeEditorSettings");
|
||||||
|
const { editorSettings, setEditorSetting } = useSettingsStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-md space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>{t("fontSize")}</Label>
|
||||||
|
<Select
|
||||||
|
value={editorSettings.fontSize.toString()}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setEditorSetting("fontSize", Number(value))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[160px] shadow-none focus:ring-0">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{fontSizes.map((fontSize) => (
|
||||||
|
<SelectItem key={fontSize} value={fontSize.toString()}>
|
||||||
|
{fontSize}px
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EditorSwitch
|
||||||
|
label={t("wordWrap")}
|
||||||
|
description={t("wordWrapDescription")}
|
||||||
|
checked={editorSettings.wordWrap}
|
||||||
|
onCheckedChange={(checked) => setEditorSetting("wordWrap", checked)}
|
||||||
|
/>
|
||||||
|
<EditorSwitch
|
||||||
|
label={t("minimap")}
|
||||||
|
description={t("minimapDescription")}
|
||||||
|
checked={editorSettings.minimap}
|
||||||
|
onCheckedChange={(checked) => setEditorSetting("minimap", checked)}
|
||||||
|
/>
|
||||||
|
<EditorSwitch
|
||||||
|
label={t("stickyScroll")}
|
||||||
|
description={t("stickyScrollDescription")}
|
||||||
|
checked={editorSettings.stickyScroll}
|
||||||
|
onCheckedChange={(checked) => setEditorSetting("stickyScroll", checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditorSwitchProps {
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
checked: boolean;
|
||||||
|
onCheckedChange: (checked: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditorSwitch({
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
checked,
|
||||||
|
onCheckedChange,
|
||||||
|
}: EditorSwitchProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>{label}</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">{description}</p>
|
||||||
|
</div>
|
||||||
|
<Switch checked={checked} onCheckedChange={onCheckedChange} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { shikiToMonaco } from "@shikijs/monaco";
|
|||||||
import type { Monaco } from "@monaco-editor/react";
|
import type { Monaco } from "@monaco-editor/react";
|
||||||
import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor";
|
import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor";
|
||||||
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
|
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
|
||||||
|
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||||
|
|
||||||
const MonacoEditor = dynamic(
|
const MonacoEditor = dynamic(
|
||||||
async () => {
|
async () => {
|
||||||
@ -38,6 +39,7 @@ export const CoreDiffEditor = ({
|
|||||||
className,
|
className,
|
||||||
}: CoreDiffEditorProps) => {
|
}: CoreDiffEditorProps) => {
|
||||||
const { theme } = useMonacoTheme();
|
const { theme } = useMonacoTheme();
|
||||||
|
const { editorSettings } = useSettingsStore();
|
||||||
|
|
||||||
const editorRef = useRef<editor.IStandaloneDiffEditor | null>(null);
|
const editorRef = useRef<editor.IStandaloneDiffEditor | null>(null);
|
||||||
|
|
||||||
@ -62,7 +64,21 @@ export const CoreDiffEditor = ({
|
|||||||
modified={modified}
|
modified={modified}
|
||||||
beforeMount={handleBeforeMount}
|
beforeMount={handleBeforeMount}
|
||||||
onMount={handleOnMount}
|
onMount={handleOnMount}
|
||||||
options={{ ...DEFAULT_EDITOR_OPTIONS, readOnly: true }}
|
options={{
|
||||||
|
...DEFAULT_EDITOR_OPTIONS,
|
||||||
|
readOnly: true,
|
||||||
|
fontSize: editorSettings.fontSize,
|
||||||
|
lineHeight: Math.round(editorSettings.fontSize * 1.5),
|
||||||
|
wordWrap: editorSettings.wordWrap ? "on" : "off",
|
||||||
|
minimap: {
|
||||||
|
...DEFAULT_EDITOR_OPTIONS.minimap,
|
||||||
|
enabled: editorSettings.minimap,
|
||||||
|
},
|
||||||
|
stickyScroll: {
|
||||||
|
...DEFAULT_EDITOR_OPTIONS.stickyScroll,
|
||||||
|
enabled: editorSettings.stickyScroll,
|
||||||
|
},
|
||||||
|
}}
|
||||||
loading={<Loading />}
|
loading={<Loading />}
|
||||||
className={className}
|
className={className}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import type { Monaco } from "@monaco-editor/react";
|
|||||||
import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor";
|
import { DEFAULT_EDITOR_OPTIONS } from "@/config/editor";
|
||||||
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
|
import { useMonacoTheme } from "@/hooks/use-monaco-theme";
|
||||||
import { LanguageServerConfig } from "@/generated/client";
|
import { LanguageServerConfig } from "@/generated/client";
|
||||||
|
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||||
import type { MessageTransports } from "vscode-languageclient";
|
import type { MessageTransports } from "vscode-languageclient";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import type { MonacoLanguageClient } from "monaco-languageclient";
|
import type { MonacoLanguageClient } from "monaco-languageclient";
|
||||||
@ -72,6 +73,7 @@ export const CoreEditor = ({
|
|||||||
className,
|
className,
|
||||||
}: CoreEditorProps) => {
|
}: CoreEditorProps) => {
|
||||||
const { theme } = useMonacoTheme();
|
const { theme } = useMonacoTheme();
|
||||||
|
const { editorSettings } = useSettingsStore();
|
||||||
|
|
||||||
const [isEditorMounted, setIsEditorMounted] = useState(false);
|
const [isEditorMounted, setIsEditorMounted] = useState(false);
|
||||||
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||||
@ -159,7 +161,20 @@ export const CoreEditor = ({
|
|||||||
onMount={handleOnMount}
|
onMount={handleOnMount}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
onValidate={handleOnValidate}
|
onValidate={handleOnValidate}
|
||||||
options={DEFAULT_EDITOR_OPTIONS}
|
options={{
|
||||||
|
...DEFAULT_EDITOR_OPTIONS,
|
||||||
|
fontSize: editorSettings.fontSize,
|
||||||
|
lineHeight: Math.round(editorSettings.fontSize * 1.5),
|
||||||
|
wordWrap: editorSettings.wordWrap ? "on" : "off",
|
||||||
|
minimap: {
|
||||||
|
...DEFAULT_EDITOR_OPTIONS.minimap,
|
||||||
|
enabled: editorSettings.minimap,
|
||||||
|
},
|
||||||
|
stickyScroll: {
|
||||||
|
...DEFAULT_EDITOR_OPTIONS.stickyScroll,
|
||||||
|
enabled: editorSettings.stickyScroll,
|
||||||
|
},
|
||||||
|
}}
|
||||||
loading={<Loading />}
|
loading={<Loading />}
|
||||||
className={className}
|
className={className}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -30,7 +30,8 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
|||||||
import { useSettingsStore } from "@/stores/useSettingsStore";
|
import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||||
import { LocaleSwitcher } from "@/components/locale-switcher";
|
import { LocaleSwitcher } from "@/components/locale-switcher";
|
||||||
import AppearanceSettings from "@/components/appearance-settings";
|
import AppearanceSettings from "@/components/appearance-settings";
|
||||||
import { CodeXml, Globe, Paintbrush, Settings } from "lucide-react";
|
import { CodeEditorSettings } from "@/components/code-editor-settings";
|
||||||
|
import { CodeXml, Globe, Paintbrush } from "lucide-react";
|
||||||
|
|
||||||
export const SettingsDialog = () => {
|
export const SettingsDialog = () => {
|
||||||
const t = useTranslations("SettingsDialog");
|
const t = useTranslations("SettingsDialog");
|
||||||
@ -39,7 +40,6 @@ export const SettingsDialog = () => {
|
|||||||
{ id: "Appearance", name: t("nav.Appearance"), icon: Paintbrush },
|
{ id: "Appearance", name: t("nav.Appearance"), icon: Paintbrush },
|
||||||
{ id: "Language", name: t("nav.Language"), icon: Globe },
|
{ id: "Language", name: t("nav.Language"), icon: Globe },
|
||||||
{ id: "CodeEditor", name: t("nav.CodeEditor"), icon: CodeXml },
|
{ id: "CodeEditor", name: t("nav.CodeEditor"), icon: CodeXml },
|
||||||
{ id: "Advanced", name: t("nav.Advanced"), icon: Settings },
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } =
|
const { isDialogOpen, activeSetting, setDialogOpen, setActiveSetting } =
|
||||||
@ -99,6 +99,7 @@ export const SettingsDialog = () => {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{activeSetting === "Appearance" && <AppearanceSettings />}
|
{activeSetting === "Appearance" && <AppearanceSettings />}
|
||||||
{activeSetting === "Language" && <LocaleSwitcher />}
|
{activeSetting === "Language" && <LocaleSwitcher />}
|
||||||
|
{activeSetting === "CodeEditor" && <CodeEditorSettings />}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
export interface EditorSettings {
|
||||||
|
fontSize: number;
|
||||||
|
wordWrap: boolean;
|
||||||
|
minimap: boolean;
|
||||||
|
stickyScroll: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface SettingsState {
|
interface SettingsState {
|
||||||
activeSetting: string;
|
activeSetting: string;
|
||||||
isDialogOpen: boolean;
|
isDialogOpen: boolean;
|
||||||
|
editorSettings: EditorSettings;
|
||||||
setActiveSetting: (setting: string) => void;
|
setActiveSetting: (setting: string) => void;
|
||||||
setDialogOpen: (open: boolean) => void;
|
setDialogOpen: (open: boolean) => void;
|
||||||
|
setEditorSetting: <K extends keyof EditorSettings>(
|
||||||
|
key: K,
|
||||||
|
value: EditorSettings[K]
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSettingsStore = create<SettingsState>()(
|
export const useSettingsStore = create<SettingsState>()(
|
||||||
@ -13,14 +25,28 @@ export const useSettingsStore = create<SettingsState>()(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
activeSetting: "Appearance",
|
activeSetting: "Appearance",
|
||||||
isDialogOpen: false,
|
isDialogOpen: false,
|
||||||
|
editorSettings: {
|
||||||
|
fontSize: 14,
|
||||||
|
wordWrap: true,
|
||||||
|
minimap: false,
|
||||||
|
stickyScroll: true,
|
||||||
|
},
|
||||||
setActiveSetting: (setting) => set({ activeSetting: setting }),
|
setActiveSetting: (setting) => set({ activeSetting: setting }),
|
||||||
setDialogOpen: (open) => set({ isDialogOpen: open }),
|
setDialogOpen: (open) => set({ isDialogOpen: open }),
|
||||||
|
setEditorSetting: (key, value) =>
|
||||||
|
set((state) => ({
|
||||||
|
editorSettings: {
|
||||||
|
...state.editorSettings,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
})),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "settings-state",
|
name: "settings-state",
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
activeNav: state.activeSetting,
|
activeSetting: state.activeSetting,
|
||||||
isDialogOpen: state.isDialogOpen,
|
isDialogOpen: state.isDialogOpen,
|
||||||
|
editorSettings: state.editorSettings,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user