From 85d80d720c38a36067ea3ca269721ac75690ff09 Mon Sep 17 00:00:00 2001 From: Jonson Petard <41122242+greenhat616@users.noreply.github.com> Date: Sat, 7 Sep 2024 21:16:29 +0800 Subject: [PATCH] feat(monaco): add onValidation before submit, and close #1491 --- .../components/profiles/profile-dialog.tsx | 27 +++++++++++++++---- .../profiles/profile-monaco-viewer.tsx | 4 +++ .../src/components/profiles/script-dialog.tsx | 19 ++++++++++++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/frontend/nyanpasu/src/components/profiles/profile-dialog.tsx b/frontend/nyanpasu/src/components/profiles/profile-dialog.tsx index 683500998c..c6aaa07acc 100644 --- a/frontend/nyanpasu/src/components/profiles/profile-dialog.tsx +++ b/frontend/nyanpasu/src/components/profiles/profile-dialog.tsx @@ -1,5 +1,6 @@ import { version } from "~/package.json"; import { useAsyncEffect } from "ahooks"; +import { type editor } from "monaco-editor"; import { createContext, lazy, @@ -18,6 +19,7 @@ import { } from "react-hook-form-mui"; import { useTranslation } from "react-i18next"; import { useLatest } from "react-use"; +import { message } from "@/utils/notification"; import { Divider, InputAdornment } from "@mui/material"; import { Profile, useClash } from "@nyanpasu/interface"; import { BaseDialog } from "@nyanpasu/ui"; @@ -86,11 +88,14 @@ export const ProfileDialog = ({ setIsEdit(!!profile); }, [profile]); - const commonProps = { - autoComplete: "off", - autoCorrect: "off", - fullWidth: true, - }; + const commonProps = useMemo( + () => ({ + autoComplete: "off", + autoCorrect: "off", + fullWidth: true, + }), + [], + ); const handleProfileSelected = (content: string) => { localProfile.current = content; @@ -105,7 +110,18 @@ export const ProfileDialog = ({ const latestEditor = useLatest(editor); + const editorMarks = useRef([]); + const editorHasError = () => + editorMarks.current.length > 0 && + editorMarks.current.some((m) => m.severity === 8); + const onSubmit = handleSubmit(async (form) => { + if (editorHasError()) { + message("Please fix the error before saving", { + type: "error", + }); + return; + } const toCreate = async () => { if (isRemote) { await createProfile(form); @@ -310,6 +326,7 @@ export const ProfileDialog = ({ onChange={(value) => setEditor((editor) => ({ ...editor, value })) } + onValidate={(marks) => (editorMarks.current = marks)} language={editor.language} /> )} diff --git a/frontend/nyanpasu/src/components/profiles/profile-monaco-viewer.tsx b/frontend/nyanpasu/src/components/profiles/profile-monaco-viewer.tsx index 169d106f7a..372809b41b 100644 --- a/frontend/nyanpasu/src/components/profiles/profile-monaco-viewer.tsx +++ b/frontend/nyanpasu/src/components/profiles/profile-monaco-viewer.tsx @@ -4,6 +4,7 @@ import { useAtomValue } from "jotai"; import { type JSONSchema7 } from "json-schema"; import nyanpasuMergeSchema from "meta-json-schema/schemas/clash-nyanpasu-merge-json-schema.json"; import clashMetaSchema from "meta-json-schema/schemas/meta-json-schema.json"; +import { type editor } from "monaco-editor"; import { configureMonacoYaml } from "monaco-yaml"; import { nanoid } from "nanoid"; import { useCallback, useMemo } from "react"; @@ -19,6 +20,7 @@ export interface ProfileMonacoViewProps { className?: string; readonly?: boolean; schemaType?: "clash" | "merge"; + onValidate?: (markers: editor.IMarker[]) => void; } export interface ProfileMonacoViewRef { @@ -65,6 +67,7 @@ export default function ProfileMonacoViewer({ readonly = false, schemaType, className, + onValidate, ...others }: ProfileMonacoViewProps) { const mode = useAtomValue(themeMode); @@ -92,6 +95,7 @@ export default function ProfileMonacoViewer({ theme={mode === "light" ? "vs" : "vs-dark"} beforeMount={beforeEditorMount} onChange={onChange} + onValidate={onValidate} options={{ readOnly: readonly, mouseWheelZoom: true, diff --git a/frontend/nyanpasu/src/components/profiles/script-dialog.tsx b/frontend/nyanpasu/src/components/profiles/script-dialog.tsx index abe1365906..ba8d6d6ad1 100644 --- a/frontend/nyanpasu/src/components/profiles/script-dialog.tsx +++ b/frontend/nyanpasu/src/components/profiles/script-dialog.tsx @@ -1,7 +1,9 @@ import { useAsyncEffect, useReactive } from "ahooks"; -import { lazy, Suspense, useEffect, useState } from "react"; +import { type editor } from "monaco-editor"; +import { lazy, Suspense, useEffect, useRef, useState } from "react"; import { SelectElement, TextFieldElement, useForm } from "react-hook-form-mui"; import { useTranslation } from "react-i18next"; +import { message } from "@/utils/notification"; import { Divider } from "@mui/material"; import { Profile, useClash } from "@nyanpasu/interface"; import { BaseDialog, BaseDialogProps } from "@nyanpasu/ui"; @@ -93,7 +95,19 @@ export const ScriptDialog = ({ rawType: "merge", }); + const editorMarks = useRef([]); + const editorHasError = () => + editorMarks.current.length > 0 && + editorMarks.current.some((m) => m.severity === 8); + const onSubmit = form.handleSubmit(async (data) => { + if (editorHasError()) { + message("Please fix the error before submitting", { + type: "error", + }); + return; + } + convertTypeMapping(data); const editorValue = editor.value; @@ -224,6 +238,9 @@ export const ScriptDialog = ({ editor.value = value; }} language={editor.language} + onValidate={(marks) => { + editorMarks.current = marks; + }} schemaType={ editor.rawType === Profile.Type.Merge ? "merge" : undefined }