mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-25 21:22:20 +08:00
* 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
170 lines
4.4 KiB
TypeScript
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>
|
|
)
|
|
},
|
|
)
|