Files
clash-verge-rev/src/components/test/test-viewer.tsx
T
oomeow b06a966a4b fix(ui): use utf-8 encoding for inline SVG icons to support non-latin characters (#7892)
* fix(ui): use utf-8 encoding for inline SVG icons to support non-latin characters

* chore(test-item): re-add data-sortable-handle to TestItem box

* docs: update changelog
2026-09-09 12:00:10 +00:00

170 lines
4.4 KiB
TypeScript

import { TextField } from '@mui/material'
import { useLockFn } from 'ahooks'
import { nanoid } from 'nanoid'
import { forwardRef, useImperativeHandle, useState } from 'react'
import { useForm, Controller } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { BaseDialog } from '@/components/base'
import { useVerge } from '@/hooks/use-verge'
import { showNotice } from '@/services/notice-service'
interface Props {
onChange: (uid: string, patch?: Partial<IVergeTestItem>) => void
}
export interface TestViewerRef {
create: () => void
edit: (item: IVergeTestItem) => void
}
// create or edit the test item
export const TestViewer = forwardRef<TestViewerRef, Props>(
({ onChange }, ref) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const [openType, setOpenType] = useState<'new' | 'edit'>('new')
const [loading, setLoading] = useState(false)
const { verge, patchVerge } = useVerge()
const testList = verge?.test_list ?? []
const { control, ...formIns } = useForm<IVergeTestItem>({
defaultValues: {
name: '',
icon: '',
url: '',
},
})
const patchTestList = async (
uid: string,
patch: Partial<IVergeTestItem>,
) => {
const newList = testList.map((x) => {
if (x.uid === uid) {
return { ...x, ...patch }
}
return x
})
await patchVerge({ test_list: newList })
}
useImperativeHandle(ref, () => ({
create: () => {
setOpenType('new')
setOpen(true)
},
edit: (item) => {
if (item) {
Object.entries(item).forEach(([key, value]) => {
formIns.setValue(key as any, value)
})
}
setOpenType('edit')
setOpen(true)
},
}))
const handleOk = useLockFn(
formIns.handleSubmit(async (form) => {
setLoading(true)
try {
if (!form.name) {
throw new Error(t('tests.modals.test.errors.nameRequired'))
}
if (!form.url) {
throw new Error(t('tests.modals.test.errors.urlRequired'))
}
if (openType === 'new') {
const uid = nanoid()
const item = { ...form, uid }
const newList = [...testList, item]
await patchVerge({ test_list: newList })
onChange(uid)
} else {
if (!form.uid) {
throw new Error(t('tests.modals.test.errors.uidMissing'))
}
const uid = form.uid
await patchTestList(uid, form)
onChange(uid, form)
}
setOpen(false)
setLoading(false)
setTimeout(() => formIns.reset(), 500)
} catch (err: any) {
showNotice.error('tests.modals.test.errors.saveFailed', err)
setLoading(false)
}
}),
)
const handleClose = () => {
setOpen(false)
setTimeout(() => formIns.reset(), 500)
}
const text = {
fullWidth: true,
size: 'small',
margin: 'normal',
variant: 'outlined',
autoComplete: 'off',
autoCorrect: 'off',
} as const
return (
<BaseDialog
open={open}
title={
openType === 'new'
? t('tests.modals.test.title.create')
: t('tests.modals.test.title.edit')
}
contentSx={{ width: 375, pb: 0, maxHeight: '80%' }}
okBtn={t('shared.actions.save')}
cancelBtn={t('shared.actions.cancel')}
onClose={handleClose}
onCancel={handleClose}
onOk={handleOk}
loading={loading}
>
<Controller
name="name"
control={control}
render={({ field }) => (
<TextField {...text} {...field} label={t('shared.labels.name')} />
)}
/>
<Controller
name="icon"
control={control}
render={({ field }) => (
<TextField
{...text}
{...field}
multiline
maxRows={5}
label={t('shared.labels.icon')}
/>
)}
/>
<Controller
name="url"
control={control}
render={({ field }) => (
<TextField
{...text}
{...field}
multiline
maxRows={3}
label={t('tests.modals.test.fields.url')}
/>
)}
/>
</BaseDialog>
)
},
)