feat(settings): add searchable settings navigation

This commit is contained in:
Tunglies
2026-11-06 17:14:15 +08:00
parent 38faf20f90
commit 7fd537af08
47 changed files with 1832 additions and 337 deletions
@@ -133,7 +133,11 @@ export function AutoBackupSettings() {
return (
<Fragment>
<ListItem divider disableGutters>
<ListItem
data-setting-target="settings.modals.backup.auto.scheduleLabel"
divider
disableGutters
>
<Stack
direction="row"
spacing={1}
@@ -152,7 +156,11 @@ export function AutoBackupSettings() {
</Stack>
</ListItem>
<ListItem divider disableGutters>
<ListItem
data-setting-target="settings.modals.backup.auto.intervalLabel"
divider
disableGutters
>
<Stack
direction="row"
spacing={2}
@@ -194,7 +202,11 @@ export function AutoBackupSettings() {
</Stack>
</ListItem>
<ListItem divider disableGutters>
<ListItem
data-setting-target="settings.modals.backup.auto.changeLabel"
divider
disableGutters
>
<Stack
direction="row"
spacing={1}
@@ -173,6 +173,7 @@ export const BackupConfigViewer = memo(
<Grid container spacing={2}>
<Grid size={{ xs: 12 }}>
<TextField
data-setting-target="settings.modals.backup.fields.webdavUrl"
fullWidth
label={t('settings.modals.backup.fields.webdavUrl')}
variant="outlined"
@@ -187,6 +188,7 @@ export const BackupConfigViewer = memo(
</Grid>
<Grid size={{ xs: 6 }}>
<TextField
data-setting-target="settings.modals.backup.fields.username"
label={t('settings.modals.backup.fields.username')}
variant="outlined"
size="small"
@@ -199,6 +201,7 @@ export const BackupConfigViewer = memo(
</Grid>
<Grid size={{ xs: 6 }}>
<TextField
data-setting-target="webdav-password"
label={t('shared.labels.password')}
type="password"
variant="outlined"
@@ -230,6 +233,7 @@ export const BackupConfigViewer = memo(
color={'primary'}
sx={{ height: '100%' }}
type="button"
data-setting-target="webdav-save"
onClick={handleSubmit(save)}
>
{t('shared.actions.save')}
@@ -239,6 +243,7 @@ export const BackupConfigViewer = memo(
<Button
variant="contained"
color="success"
data-setting-target="webdav-backup"
onClick={handleBackup}
type="button"
size="large"
@@ -247,6 +252,7 @@ export const BackupConfigViewer = memo(
</Button>
<Button
variant="outlined"
data-setting-target="webdav-refresh"
onClick={onRefresh}
type="button"
size="large"
@@ -327,7 +327,13 @@ export const BackupHistoryViewer = ({
onCancel={onClose}
onClose={onClose}
>
<Box sx={{ position: 'relative', minHeight: 320 }}>
<Box
data-settings-dialog="backup-history"
data-search-ready={!loading}
data-setting-target="backup-history"
tabIndex={-1}
sx={{ position: 'relative', minHeight: 320 }}
>
<BaseLoadingOverlay isLoading={isBusy} />
<Stack spacing={2}>
<Stack
@@ -412,6 +418,7 @@ export const BackupHistoryViewer = ({
<IconButton
size="small"
disabled={isBusy}
data-setting-target="backup-history-export"
onClick={() => handleExport(row.filename)}
>
<DownloadRounded fontSize="small" />
@@ -420,6 +427,7 @@ export const BackupHistoryViewer = ({
<IconButton
size="small"
disabled={isBusy}
data-setting-target="backup-history-delete"
onClick={() => handleDelete(row.filename)}
>
<DeleteOutlined fontSize="small" />
@@ -427,6 +435,7 @@ export const BackupHistoryViewer = ({
<IconButton
size="small"
disabled={isBusy}
data-setting-target="backup-history-restore"
onClick={() => handleRestore(row.filename)}
>
<RestoreRounded fontSize="small" />
+36 -4
View File
@@ -9,7 +9,7 @@ import {
import { open as openDialog } from '@tauri-apps/plugin-dialog'
import { useLockFn } from 'ahooks'
import type { ReactNode, Ref } from 'react'
import { useCallback, useImperativeHandle, useState } from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, DialogRef } from '@/components/base'
@@ -23,6 +23,8 @@ import { mutate } from '@/services/mutate'
import { errorDetail, showNotice } from '@/services/notice-service'
import { buildWebdavSignature, setWebdavStatus } from '@/services/webdav-status'
import { useSettingDialog } from '../settings-search-context'
import { AutoBackupSettings } from './auto-backup-settings'
import { BackupHistoryViewer } from './backup-history-viewer'
import { BackupWebdavDialog } from './backup-webdav-dialog'
@@ -39,9 +41,18 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
const [historySource, setHistorySource] = useState<BackupSource>('local')
const [historyPage, setHistoryPage] = useState(0)
const [webdavDialogOpen, setWebdavDialogOpen] = useState(false)
const searchParentRef = useRef<'webdav' | 'history' | null>(null)
const closeSearchDetail = (detail: 'webdav' | 'history') => {
if (detail === 'webdav') setWebdavDialogOpen(false)
else setHistoryOpen(false)
if (searchParentRef.current === detail) {
searchParentRef.current = null
setOpen(false)
}
}
const webdavSignature = buildWebdavSignature(verge)
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'backup-viewer', open, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}))
@@ -52,6 +63,23 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
setHistoryOpen(true)
}
useSettingDialog(undefined, 'backup-webdav', webdavDialogOpen, () => ({
open: () => {
if (!open) searchParentRef.current = 'webdav'
setOpen(true)
setWebdavDialogOpen(true)
},
close: () => setWebdavDialogOpen(false),
}))
useSettingDialog(undefined, 'backup-history', historyOpen, () => ({
open: () => {
if (!open) searchParentRef.current = 'history'
setOpen(true)
openHistory('local')
},
close: () => setHistoryOpen(false),
}))
const handleBackup = useLockFn(async (target: BackupSource) => {
try {
setBusyAction(target)
@@ -129,6 +157,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
onCancel={() => setOpen(false)}
onClose={() => setOpen(false)}
>
<span data-settings-dialog="backup-viewer" />
<Stack spacing={2}>
<Stack
spacing={1}
@@ -166,6 +195,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
description: t('settings.modals.backup.manual.local'),
actions: [
<Button
data-setting-target="settings.modals.backup.actions.backup"
key="backup"
variant="contained"
size="small"
@@ -176,6 +206,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
{t('settings.modals.backup.actions.backup')}
</Button>,
<Button
data-setting-target="settings.modals.backup.actions.viewHistory"
key="history"
variant="outlined"
size="small"
@@ -185,6 +216,7 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
{t('settings.modals.backup.actions.viewHistory')}
</Button>,
<Button
data-setting-target="settings.modals.backup.actions.importBackup"
key="import"
variant="text"
size="small"
@@ -263,11 +295,11 @@ export function BackupViewer({ ref }: { ref?: Ref<DialogRef> }) {
page={historyPage}
onSourceChange={setHistorySource}
onPageChange={setHistoryPage}
onClose={() => setHistoryOpen(false)}
onClose={() => closeSearchDetail('history')}
/>
<BackupWebdavDialog
open={webdavDialogOpen}
onClose={() => setWebdavDialogOpen(false)}
onClose={() => closeSearchDetail('webdav')}
onBackupSuccess={() => openHistory('webdav')}
setBusy={setWebdavBusy}
/>
@@ -76,7 +76,13 @@ export const BackupWebdavDialog = ({
onCancel={onClose}
onClose={onClose}
>
<Box sx={{ position: 'relative' }}>
<Box
data-settings-dialog="backup-webdav"
data-search-ready={!loading}
data-setting-target="webdav-state"
tabIndex={-1}
sx={{ position: 'relative' }}
>
<BaseLoadingOverlay isLoading={loading} />
<BackupConfigViewer
setLoading={handleLoading}
@@ -13,7 +13,7 @@ import {
} from '@mui/material'
import { useLockFn } from 'ahooks'
import type { Ref } from 'react'
import { useImperativeHandle, useState } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { closeAllConnections } from 'tauri-plugin-mihomo-api'
@@ -24,6 +24,8 @@ import { changeClashCore, restartCore, upgradeClashCore } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
const VALID_CORE = [
{
name: 'Mihomo',
@@ -49,7 +51,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
const [restarting, setRestarting] = useState(false)
const [changingCore, setChangingCore] = useState<string | null>(null)
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'clash-core-viewer', open, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}))
@@ -139,6 +141,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
loading={upgrading}
disabled={restarting || changingCore !== null}
sx={{ marginRight: '8px' }}
data-setting-target="core-upgrade"
onClick={onUpgrade}
>
{t('shared.actions.upgrade')}
@@ -150,6 +153,7 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
loadingPosition="start"
loading={restarting}
disabled={upgrading}
data-setting-target="core-restart"
onClick={onRestart}
>
{t('shared.actions.restart')}
@@ -170,7 +174,12 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<List component="nav">
<List
component="nav"
data-settings-dialog="clash-core-viewer"
data-setting-target="core-variants"
tabIndex={-1}
>
{VALID_CORE.map((each) => (
<ListItemButton
key={each.core}
@@ -9,7 +9,7 @@ import {
TextField,
} from '@mui/material'
import { useLockFn, useRequest } from 'ahooks'
import { forwardRef, useImperativeHandle, useState } from 'react'
import { forwardRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, Switch } from '@/components/base'
@@ -22,6 +22,8 @@ import { showNotice } from '@/services/notice-service'
import type { TranslationKey } from '@/types/generated/i18n-keys'
import getSystem from '@/utils/get-system'
import { useSettingDialog } from '../settings-search-context'
const OS = getSystem()
interface ClashPortViewerRef {
@@ -87,6 +89,7 @@ const readPortSettings = (
})
interface PortRowProps {
searchTarget: string
label: string
secondary?: string
port: number
@@ -97,6 +100,7 @@ interface PortRowProps {
}
const PortRow = ({
searchTarget,
label,
secondary,
port,
@@ -107,7 +111,10 @@ const PortRow = ({
const { t } = useTranslation()
return (
<ListItem sx={{ padding: '4px 0', minHeight: 36 }}>
<ListItem
data-setting-target={searchTarget}
sx={{ padding: '4px 0', minHeight: 36 }}
>
<ListItemText
primary={label}
secondary={secondary}
@@ -129,6 +136,7 @@ const PortRow = ({
/>
<IconButton
size="small"
data-setting-target="port-random"
onClick={() => onPortChange(generateRandomPort())}
title={t('settings.modals.clashPort.actions.random')}
disabled={!enabled}
@@ -192,7 +200,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
},
)
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'clash-port-viewer', open, () => ({
open: () => {
setPorts(readPortSettings(verge, configuredMixedPort))
setOpen(true)
@@ -245,9 +253,11 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<span data-settings-dialog="clash-port-viewer" />
<List sx={{ width: '100%' }}>
<PortRow
label={t('settings.modals.clashPort.fields.mixed')}
searchTarget="settings.modals.clashPort.fields.mixed"
secondary={
displayedMixedPort !== configuredMixedPort
? t('settings.modals.clashPort.messages.runningPort', {
@@ -265,6 +275,7 @@ export const ClashPortViewer = forwardRef<ClashPortViewerRef>((_, ref) => {
<PortRow
key={key}
label={t(label)}
searchTarget={label}
port={ports[key].port}
enabled={ports[key].enabled}
onPortChange={(port) => updateListener(key, { port })}
@@ -12,7 +12,7 @@ import {
Tooltip,
} from '@mui/material'
import { useLockFn } from 'ahooks'
import { useImperativeHandle, useState, type Ref } from 'react'
import { useState, type Ref } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, DialogRef, Switch } from '@/components/base'
@@ -20,6 +20,8 @@ import { useClashInfo } from '@/hooks/use-clash'
import { useVerge } from '@/hooks/use-verge'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
@@ -35,7 +37,7 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
)
// 对话框打开时初始化配置
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'controller-viewer', open, () => ({
open: async () => {
setOpen(true)
setController(clashInfo?.server || '')
@@ -124,8 +126,10 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<span data-settings-dialog="controller-viewer" />
<List>
<ListItem
data-setting-target="settings.sections.externalController.fields.enable"
sx={{
padding: '5px 2px',
display: 'flex',
@@ -144,6 +148,7 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem
data-setting-target="settings.sections.externalController.fields.address"
sx={{
padding: '5px 2px',
display: 'flex',
@@ -184,6 +189,7 @@ export function ControllerViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
<ListItem
data-setting-target="settings.sections.externalController.fields.secret"
sx={{
padding: '5px 2px',
display: 'flex',
+79 -43
View File
@@ -16,14 +16,7 @@ import {
import { useLockFn } from 'ahooks'
import * as yaml from 'js-yaml'
import type { Ref } from 'react'
import {
useCallback,
useEffect,
useImperativeHandle,
useReducer,
useRef,
useState,
} from 'react'
import { useCallback, useEffect, useReducer, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -47,6 +40,8 @@ import { useThemeMode } from '@/services/states'
import type { MonacoEditorInstance } from '@/types/monaco'
import { MONACO_FONT_FAMILY } from '@/utils/font-family'
import { useSettingDialog } from '../settings-search-context'
const Item = styled(ListItem)(() => ({
padding: '5px 2px',
'& textarea': {
@@ -307,6 +302,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
const themeMode = useThemeMode()
const [open, setOpen] = useState(false)
const [initializing, setInitializing] = useState(false)
const [visualization, setVisualization] = useState(true)
const editorRef = useRef<MonacoEditorInstance | null>(null)
const [values, setValues] = useState<DnsFormValues>(DEFAULT_FORM_VALUES)
@@ -419,6 +415,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
}, [])
const initDnsConfig = useCallback(async () => {
setInitializing(true)
try {
const dnsConfig = await getDnsConfigContent()
@@ -433,20 +430,18 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
} catch (err) {
console.error('Failed to initialize DNS config', err)
resetToDefaults()
} finally {
setInitializing(false)
}
}, [resetToDefaults, updateValuesFromConfig])
useImperativeHandle(
ref,
() => ({
open: () => {
setOpen(true)
void initDnsConfig()
},
close: () => setOpen(false),
}),
[initDnsConfig],
)
useSettingDialog(ref, 'dns-viewer', open, () => ({
open: () => {
setOpen(true)
void initDnsConfig()
},
close: () => setOpen(false),
}))
const onSave = useLockFn(async () => {
try {
@@ -556,6 +551,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
{t('settings.modals.dns.dialog.title')}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Button
data-setting-target="dns-viewer-reset"
variant="outlined"
size="small"
color="warning"
@@ -567,6 +563,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Button
variant="contained"
size="small"
data-setting-target="dns-mode"
onClick={() => {
if (visualization) {
setYamlContent(buildDnsYaml(values))
@@ -596,6 +593,15 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<span
data-settings-dialog="dns-viewer"
data-search-ready={!initializing}
/>
{initializing && (
<Typography role="status" data-settings-loading tabIndex={-1}>
{t('shared.statuses.loading')}
</Typography>
)}
<Alert severity="info" sx={{ mb: 2 }}>
{t('settings.modals.dns.dialog.profileScope')}
</Alert>
@@ -616,7 +622,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
{t('settings.modals.dns.sections.general')}
</Typography>
<Item>
<Item data-setting-target="settings.modals.dns.fields.enable">
<ListItemText primary={t('settings.modals.dns.fields.enable')} />
<Switch
edge="end"
@@ -625,7 +631,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.listen">
<ListItemText primary={t('settings.modals.dns.fields.listen')} />
<TextField
size="small"
@@ -638,7 +644,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.enhancedMode">
<ListItemText
primary={t('settings.modals.dns.fields.enhancedMode')}
/>
@@ -653,7 +659,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
</FormControl>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.fakeIpRange">
<ListItemText
primary={t('settings.modals.dns.fields.fakeIpRange')}
/>
@@ -668,7 +674,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.fakeIpRange6">
<ListItemText
primary={t('settings.modals.dns.fields.fakeIpRange6')}
/>
@@ -683,7 +689,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.fakeIpFilterMode">
<ListItemText
primary={t('settings.modals.dns.fields.fakeIpFilterMode')}
/>
@@ -698,7 +704,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
</FormControl>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.ipv6.label">
<ListItemText
primary={t('settings.modals.dns.fields.ipv6.label')}
secondary={t('settings.modals.dns.fields.ipv6.description')}
@@ -710,7 +716,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.preferH3.label">
<ListItemText
primary={t('settings.modals.dns.fields.preferH3.label')}
secondary={t('settings.modals.dns.fields.preferH3.description')}
@@ -722,7 +728,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.respectRules.label">
<ListItemText
primary={t('settings.modals.dns.fields.respectRules.label')}
secondary={t(
@@ -736,7 +742,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.useHosts.label">
<ListItemText
primary={t('settings.modals.dns.fields.useHosts.label')}
secondary={t('settings.modals.dns.fields.useHosts.description')}
@@ -748,7 +754,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.useSystemHosts.label">
<ListItemText
primary={t('settings.modals.dns.fields.useSystemHosts.label')}
secondary={t(
@@ -762,7 +768,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.directPolicy.label">
<ListItemText
primary={t('settings.modals.dns.fields.directPolicy.label')}
secondary={t(
@@ -776,7 +782,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.defaultNameserver.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.defaultNameserver.label')}
secondary={t(
@@ -796,7 +805,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.nameserver.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.nameserver.label')}
secondary={t('settings.modals.dns.fields.nameserver.description')}
@@ -814,7 +826,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.fallback.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.fallback.label')}
secondary={t('settings.modals.dns.fields.fallback.description')}
@@ -832,7 +847,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.proxy.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.proxy.label')}
secondary={t('settings.modals.dns.fields.proxy.description')}
@@ -850,7 +868,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.directNameserver.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.directNameserver.label')}
secondary={t(
@@ -870,7 +891,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.fakeIpFilter.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.fakeIpFilter.label')}
secondary={t(
@@ -890,7 +914,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.nameserverPolicy.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.nameserverPolicy.label')}
secondary={t(
@@ -917,7 +944,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
{t('settings.modals.dns.sections.fallbackFilter')}
</Typography>
<Item>
<Item data-setting-target="settings.modals.dns.fields.geoipFiltering.label">
<ListItemText
primary={t('settings.modals.dns.fields.geoipFiltering.label')}
secondary={t(
@@ -931,7 +958,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item>
<Item data-setting-target="settings.modals.dns.fields.geoipCode">
<ListItemText primary={t('settings.modals.dns.fields.geoipCode')} />
<TextField
size="small"
@@ -944,7 +971,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.fallbackIpCidr.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.fallbackIpCidr.label')}
secondary={t(
@@ -964,7 +994,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</Item>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.fallbackDomain.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.fallbackDomain.label')}
secondary={t(
@@ -991,7 +1024,10 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
{t('settings.modals.dns.sections.hosts')}
</Typography>
<Item sx={{ flexDirection: 'column', alignItems: 'flex-start' }}>
<Item
data-setting-target="settings.modals.dns.fields.hosts.label"
sx={{ flexDirection: 'column', alignItems: 'flex-start' }}
>
<ListItemText
primary={t('settings.modals.dns.fields.hosts.label')}
secondary={t('settings.modals.dns.fields.hosts.description')}
@@ -1,7 +1,7 @@
import { Delete as DeleteIcon } from '@mui/icons-material'
import { Box, Button, Divider, List, ListItem, TextField } from '@mui/material'
import { useLockFn, useRequest } from 'ahooks'
import { forwardRef, useImperativeHandle, useRef, useState } from 'react'
import { forwardRef, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, Switch } from '@/components/base'
@@ -10,6 +10,8 @@ import { restartCore } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
// Development origins must never be persisted into production configuration.
const DEV_URLS = [
'tauri://localhost',
@@ -154,7 +156,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
},
)
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'external-controller-cors', open, () => ({
open: () => {
const cors = clash?.['external-controller-cors']
const origins = cors?.['allow-origins'] ?? []
@@ -186,8 +188,12 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
onCancel={() => setOpen(false)}
onOk={handleSave}
>
<span data-settings-dialog="external-controller-cors" />
<List sx={{ width: '90%', padding: 2 }}>
<ListItem sx={{ padding: '8px 0' }}>
<ListItem
data-setting-target="settings.sections.externalCors.fields.allowPrivateNetwork"
sx={{ padding: '8px 0' }}
>
<Box
sx={{
display: 'flex',
@@ -214,7 +220,10 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
<Divider sx={{ my: 2 }} />
<ListItem sx={{ padding: '8px 0' }}>
<ListItem
data-setting-target="settings.sections.externalCors.fields.allowedOrigins"
sx={{ padding: '8px 0' }}
>
<div style={{ width: '100%' }}>
<div style={{ marginBottom: 8, fontWeight: 'bold' }}>
{t('settings.sections.externalCors.fields.allowedOrigins')}
@@ -252,6 +261,7 @@ export const HeaderConfiguration = forwardRef<ClashHeaderConfigingRef>(
</div>
))}
<Button
data-setting-target="settings.sections.externalCors.actions.add"
variant="contained"
size="small"
onClick={handleAddOrigin}
+10 -4
View File
@@ -1,12 +1,14 @@
import { styled, Typography } from '@mui/material'
import { useLockFn } from 'ahooks'
import { forwardRef, useImperativeHandle, useState } from 'react'
import { forwardRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, DialogRef, Switch } from '@/components/base'
import { useVerge } from '@/hooks/use-verge'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
import { HotkeyInput } from './hotkey-input'
const ItemWrapper = styled('div')`
@@ -51,7 +53,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
verge?.enable_global_hotkey ?? true,
)
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'hotkey-viewer', open, () => ({
open: () => {
setOpen(true)
@@ -111,7 +113,11 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<ItemWrapper style={{ marginBottom: 16 }}>
<span data-settings-dialog="hotkey-viewer" />
<ItemWrapper
data-setting-target="settings.modals.hotkey.toggles.enableGlobal"
style={{ marginBottom: 16 }}
>
<Typography>
{t('settings.modals.hotkey.toggles.enableGlobal')}
</Typography>
@@ -123,7 +129,7 @@ export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {
</ItemWrapper>
{HOTKEY_FUNC.map((func) => (
<ItemWrapper key={func}>
<ItemWrapper key={func} data-setting-target={HOTKEY_FUNC_LABELS[func]}>
<Typography>{t(HOTKEY_FUNC_LABELS[func])}</Typography>
<HotkeyInput
value={hotkeyMap[func] ?? []}
+23 -19
View File
@@ -14,7 +14,7 @@ import { convertFileSrc } from '@tauri-apps/api/core'
import { join } from '@tauri-apps/api/path'
import { open as openDialog } from '@tauri-apps/plugin-dialog'
import { exists } from '@tauri-apps/plugin-fs'
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
import { forwardRef, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
@@ -26,6 +26,8 @@ import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service'
import getSystem from '@/utils/get-system'
import { useSettingDialog } from '../settings-search-context'
import { GuardState } from './guard-state'
const OS = getSystem()
@@ -97,7 +99,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
}
}
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'layout-viewer', open, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}))
@@ -120,8 +122,9 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<span data-settings-dialog="layout-viewer" />
<List>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.preferSystemTitlebar">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.preferSystemTitlebar',
@@ -140,7 +143,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.trafficGraph">
<ListItemText
primary={t('settings.components.verge.layout.fields.trafficGraph')}
/>
@@ -156,7 +159,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.memoryUsage">
<ListItemText
primary={t('settings.components.verge.layout.fields.memoryUsage')}
/>
@@ -172,7 +175,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.proxyGroupIcon">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.proxyGroupIcon',
@@ -190,7 +193,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.pauseRenderTrafficStatsOnBlur',
@@ -212,7 +215,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.toastPosition">
<ListItemText
primary={t('settings.components.verge.layout.fields.toastPosition')}
/>
@@ -248,7 +251,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.hoverNavigator">
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
@@ -276,7 +279,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.hoverNavigatorDelay">
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
@@ -335,7 +338,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.navIcon">
<ListItemText
primary={t('settings.components.verge.layout.fields.navIcon')}
/>
@@ -360,7 +363,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.collapseNavBar">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.collapseNavBar',
@@ -379,7 +382,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
{OS === 'macos' && (
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.trayIcon">
<ListItemText
primary={t('settings.components.verge.layout.fields.trayIcon')}
/>
@@ -407,7 +410,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Item>
)}
{OS === 'macos' && (
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.enableTraySpeed">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.enableTraySpeed',
@@ -445,7 +448,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
)} */}
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.proxyGroupsDisplayMode">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.proxyGroupsDisplayMode',
@@ -481,7 +484,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</Select>
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.showOutboundModesInline">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.showOutboundModesInline',
@@ -499,7 +502,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.commonTrayIcon">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.commonTrayIcon',
@@ -512,6 +515,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
onGuard={(e) => patchVerge({ common_tray_icon: e })}
>
<Button
data-setting-target="settings.components.verge.basic.actions.browse"
variant="outlined"
size="small"
startIcon={
@@ -554,7 +558,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.systemProxyTrayIcon">
<ListItemText
primary={t(
'settings.components.verge.layout.fields.systemProxyTrayIcon',
@@ -609,7 +613,7 @@ export const LayoutViewer = forwardRef<DialogRef>((_, ref) => {
</GuardState>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.layout.fields.tunTrayIcon">
<ListItemText
primary={t('settings.components.verge.layout.fields.tunTrayIcon')}
/>
@@ -8,7 +8,7 @@ import {
} from '@mui/material'
import { useLockFn } from 'ahooks'
import type { Ref } from 'react'
import { useImperativeHandle, useState } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
@@ -17,6 +17,8 @@ import { entry_lightweight_mode } from '@/services/cmds'
import { mutate } from '@/services/mutate'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation()
const { verge, patchVerge } = useVerge()
@@ -27,7 +29,7 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
autoEnterLiteModeDelay: 10, // 默认10分钟
})
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'lite-mode-viewer', open, () => ({
open: () => {
setOpen(true)
setValues({
@@ -61,8 +63,12 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<span data-settings-dialog="lite-mode-viewer" />
<List>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.liteMode.actions.enterNow"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.liteMode.actions.enterNow')}
/>
@@ -84,7 +90,10 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
</Typography>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.liteMode.toggles.autoEnter"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.liteMode.toggles.autoEnter')}
sx={{ maxWidth: 'fit-content' }}
@@ -105,7 +114,10 @@ export function LiteModeViewer({ ref }: { ref?: Ref<DialogRef> }) {
{values.autoEnterLiteMode && (
<>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.liteMode.fields.delay"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.liteMode.fields.delay')}
/>
+53 -14
View File
@@ -8,13 +8,15 @@ import {
TextField,
} from '@mui/material'
import { useLockFn } from 'ahooks'
import { forwardRef, useImperativeHandle, useState } from 'react'
import { forwardRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
import { useVerge } from '@/hooks/use-verge'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
const buildInitialValues = (verge?: IVergeConfig | null) => ({
appLogLevel: verge?.app_log_level ?? 'warn',
appLogMaxSize: verge?.app_log_max_size ?? 128,
@@ -38,7 +40,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
const [open, setOpen] = useState(false)
const [values, setValues] = useState(() => buildInitialValues(null))
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'misc-viewer', open, () => ({
open: () => {
setOpen(true)
setValues(buildInitialValues(verge))
@@ -80,8 +82,12 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<span data-settings-dialog="misc-viewer" />
<List>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.appLogLevel"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.appLogLevel')}
/>
@@ -104,7 +110,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</Select>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.appLogMaxSize"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.appLogMaxSize')}
sx={{ maxWidth: 'fit-content' }}
@@ -136,7 +145,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.appLogMaxCount"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.appLogMaxCount')}
sx={{ maxWidth: 'fit-content' }}
@@ -168,7 +180,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.autoCloseConnections"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.autoCloseConnections')}
sx={{ maxWidth: 'fit-content' }}
@@ -187,7 +202,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.autoCheckUpdate"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.autoCheckUpdate')}
/>
@@ -200,7 +218,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.enableBuiltinEnhanced"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.enableBuiltinEnhanced')}
sx={{ maxWidth: 'fit-content' }}
@@ -219,7 +240,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.proxyLayoutColumns"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.proxyLayoutColumns')}
/>
@@ -245,7 +269,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</Select>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.autoLogClean"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.autoLogClean')}
/>
@@ -298,7 +325,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
</Select>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.autoDelayDetection"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.autoDelayDetection')}
sx={{ maxWidth: 'fit-content' }}
@@ -317,7 +347,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.autoDelayDetectionInterval"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t(
'settings.modals.misc.fields.autoDelayDetectionInterval',
@@ -355,7 +388,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.defaultLatencyTest"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.defaultLatencyTest')}
sx={{ maxWidth: 'fit-content' }}
@@ -379,7 +415,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.misc.fields.defaultLatencyTimeout"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.misc.fields.defaultLatencyTimeout')}
/>
@@ -2,19 +2,21 @@ import { ContentCopyRounded } from '@mui/icons-material'
import { alpha, Box, Button, CircularProgress, IconButton } from '@mui/material'
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
import type { Ref } from 'react'
import { useImperativeHandle, useState } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, BaseEmpty, DialogRef } from '@/components/base'
import { useNetworkInterfaces } from '@/hooks/use-network'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const [isV4, setIsV4] = useState(true)
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'network-interface-viewer', open, () => ({
open: () => {
setOpen(true)
},
@@ -51,41 +53,49 @@ export function NetworkInterfaceViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
{loading && isEmpty ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress size={24} />
</Box>
) : isEmpty ? (
<Box sx={{ minHeight: 160 }}>
<BaseEmpty />
</Box>
) : (
networkInterfaces.map((item) => (
<Box key={item.name}>
<h4>{item.name}</h4>
<Box>
{item.addr.map((address) => {
const ip = getAddressIp(address)
return (
ip && (
<AddressDisplay
key={ip}
label={t(
'settings.modals.networkInterface.fields.ipAddress',
)}
content={ip}
/>
)
)
})}
<AddressDisplay
label={t('settings.modals.networkInterface.fields.macAddress')}
content={item.mac_addr ?? ''}
/>
</Box>
<div
data-settings-dialog="network-interface-viewer"
data-setting-target="network-interface-viewer"
tabIndex={-1}
>
{loading && isEmpty ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress size={24} />
</Box>
))
)}
) : isEmpty ? (
<Box sx={{ minHeight: 160 }}>
<BaseEmpty />
</Box>
) : (
networkInterfaces.map((item) => (
<Box key={item.name}>
<h4>{item.name}</h4>
<Box>
{item.addr.map((address) => {
const ip = getAddressIp(address)
return (
ip && (
<AddressDisplay
key={ip}
label={t(
'settings.modals.networkInterface.fields.ipAddress',
)}
content={ip}
/>
)
)
})}
<AddressDisplay
label={t(
'settings.modals.networkInterface.fields.macAddress',
)}
content={item.mac_addr ?? ''}
/>
</Box>
</Box>
))
)}
</div>
</BaseDialog>
)
}
+4 -2
View File
@@ -13,6 +13,7 @@ import React, { ReactNode, useState } from 'react'
import isAsyncFunction from '@/utils/is-async-function'
interface ItemProps {
'data-setting-target'?: string
label: ReactNode
extra?: ReactNode
children?: ReactNode
@@ -21,6 +22,7 @@ interface ItemProps {
}
export const SettingItem: React.FC<ItemProps> = ({
'data-setting-target': searchTarget,
label,
extra,
children,
@@ -49,7 +51,7 @@ export const SettingItem: React.FC<ItemProps> = ({
}
return clickable ? (
<ListItem disablePadding>
<ListItem data-setting-target={searchTarget} disablePadding>
<ListItemButton onClick={handleClick} disabled={isLoading}>
<ListItemText primary={primary} secondary={secondary} />
{isLoading ? (
@@ -60,7 +62,7 @@ export const SettingItem: React.FC<ItemProps> = ({
</ListItemButton>
</ListItem>
) : (
<ListItem sx={{ pt: '5px', pb: '5px' }}>
<ListItem data-setting-target={searchTarget} sx={{ pt: '5px', pb: '5px' }}>
<ListItemText primary={primary} secondary={secondary} />
{children}
</ListItem>
+69 -37
View File
@@ -13,7 +13,7 @@ import {
Typography,
} from '@mui/material'
import { useLockFn } from 'ahooks'
import { forwardRef, useImperativeHandle, useMemo, useState } from 'react'
import { forwardRef, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -39,6 +39,8 @@ import { useQuery } from '@/services/query-client'
import { debugLog } from '@/utils/debug'
import getSystem from '@/utils/get-system'
import { useSettingDialog } from '../settings-search-context'
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;";
}`
@@ -183,7 +185,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
setPacEditorSavedValue(nextPac)
})
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'sysproxy-viewer', open, () => ({
open: () => {
setOpen(true)
setValue(buildInitialValues(verge))
@@ -355,41 +357,49 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
loading={saving}
disableOk={saving}
>
<span data-settings-dialog="sysproxy-viewer" />
<List>
<BaseFieldset
label={t('settings.modals.sysproxy.fieldsets.currentStatus')}
padding="15px 10px"
>
<FlexBox>
<Typography className="label">
{t('settings.modals.sysproxy.fields.enableStatus')}
</Typography>
<Typography className="value">
{isProxyReallyEnabled
? t('shared.statuses.enabled')
: t('shared.statuses.disabled')}
</Typography>
</FlexBox>
{!value.pac && (
<Box data-setting-target="sysproxy-status" tabIndex={-1}>
<BaseFieldset
label={t('settings.modals.sysproxy.fieldsets.currentStatus')}
padding="15px 10px"
>
<FlexBox>
<Typography className="label">
{t('settings.modals.sysproxy.fields.serverAddr')}
</Typography>
<Typography className="value">{getSystemProxyAddress}</Typography>
</FlexBox>
)}
{value.pac && (
<FlexBox>
<Typography className="label">
{t('settings.modals.sysproxy.fields.pacUrl')}
{t('settings.modals.sysproxy.fields.enableStatus')}
</Typography>
<Typography className="value">
{getCurrentPacUrl || '-'}
{isProxyReallyEnabled
? t('shared.statuses.enabled')
: t('shared.statuses.disabled')}
</Typography>
</FlexBox>
)}
</BaseFieldset>
<ListItem sx={{ padding: '5px 2px' }}>
{!value.pac && (
<FlexBox>
<Typography className="label">
{t('settings.modals.sysproxy.fields.serverAddr')}
</Typography>
<Typography className="value">
{getSystemProxyAddress}
</Typography>
</FlexBox>
)}
{value.pac && (
<FlexBox>
<Typography className="label">
{t('settings.modals.sysproxy.fields.pacUrl')}
</Typography>
<Typography className="value">
{getCurrentPacUrl || '-'}
</Typography>
</FlexBox>
)}
</BaseFieldset>
</Box>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.proxyHost"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.sysproxy.fields.proxyHost')}
/>
@@ -416,7 +426,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
}}
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.usePacMode"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.sysproxy.fields.usePacMode')}
/>
@@ -428,7 +441,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.proxyGuard"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.sysproxy.fields.proxyGuard')}
sx={{ maxWidth: 'fit-content' }}
@@ -446,7 +462,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.guardDuration"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.sysproxy.fields.guardDuration')}
/>
@@ -469,7 +488,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
/>
</ListItem>
{!value.pac && (
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.alwaysUseDefaultBypass"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t(
'settings.modals.sysproxy.fields.alwaysUseDefaultBypass',
@@ -496,7 +518,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
)}
{!value.pac && (
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.enableBypassCheck"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.sysproxy.fields.enableBypassCheck')}
/>
@@ -528,7 +553,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
setValue((v) => ({ ...v, bypass: nextValue }))
}}
renderHeader={(modeToggle) => (
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.proxyBypass"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.sysproxy.fields.proxyBypass')}
/>
@@ -556,12 +584,16 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
)}
{value.pac && (
<ListItem sx={{ padding: '5px 2px', alignItems: 'start' }}>
<ListItem
data-setting-target="settings.modals.sysproxy.fields.pacScriptContent"
sx={{ padding: '5px 2px', alignItems: 'start' }}
>
<ListItemText
primary={t('settings.modals.sysproxy.fields.pacScriptContent')}
sx={{ padding: '3px 0' }}
/>
<Button
data-setting-target="settings.modals.sysproxy.actions.editPac"
startIcon={<EditRounded />}
variant="outlined"
onClick={openPacEditor}
+9 -11
View File
@@ -9,13 +9,7 @@ import {
useTheme,
} from '@mui/material'
import { useLockFn } from 'ahooks'
import {
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, DialogRef } from '@/components/base'
@@ -24,6 +18,8 @@ import { useVerge } from '@/hooks/use-verge'
import { defaultDarkTheme, defaultTheme } from '@/pages/_theme'
import { showNotice } from '@/services/notice-service'
import { useSettingDialog } from '../settings-search-context'
export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
const { ref } = props
const { t } = useTranslation()
@@ -41,7 +37,7 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
themeRef.current = theme
}, [theme])
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'theme-viewer', open, () => ({
open: () => {
setOpen(true)
setTheme({ ...theme_setting })
@@ -128,7 +124,7 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
const renderItem = (labelKey: string, key: ThemeKey) => {
const label = t(labelKey)
return (
<Item key={key}>
<Item key={key} data-setting-target={labelKey}>
<ListItemText primary={label} />
<Round sx={{ background: theme[key] || dt[key] }} />
<TextField
@@ -153,10 +149,11 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<span data-settings-dialog="theme-viewer" />
<List sx={{ pt: 0 }}>
{fieldDefinitions.map((field) => renderItem(field.labelKey, field.key))}
<Item>
<Item data-setting-target="settings.components.verge.theme.fields.fontFamily">
<ListItemText
primary={t('settings.components.verge.theme.fields.fontFamily')}
/>
@@ -167,11 +164,12 @@ export function ThemeViewer(props: { ref?: React.Ref<DialogRef> }) {
onKeyDown={(e) => e.key === 'Enter' && onSave()}
/>
</Item>
<Item>
<Item data-setting-target="settings.components.verge.theme.fields.cssInjection">
<ListItemText
primary={t('settings.components.verge.theme.fields.cssInjection')}
/>
<Button
data-setting-target="settings.components.verge.theme.actions.editCss"
startIcon={<EditRounded />}
variant="outlined"
onClick={openCssEditor}
+46 -12
View File
@@ -9,7 +9,7 @@ import {
} from '@mui/material'
import { useLockFn } from 'ahooks'
import type { Ref } from 'react'
import { useImperativeHandle, useState } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -26,6 +26,8 @@ import { showNotice } from '@/services/notice-service'
import getSystem from '@/utils/get-system'
import { areValidIpCidrs } from '@/utils/network'
import { useSettingDialog } from '../settings-search-context'
import { DnsModeSwitch } from './dns-mode-switch'
import { StackModeSwitch } from './stack-mode-switch'
@@ -67,7 +69,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
? t('settings.modals.tun.messages.invalidRouteExcludeAddress')
: t('settings.modals.tun.messages.routeExcludeAddressHint')
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'tun-viewer', open, () => ({
open: () => {
setOpen(true)
const nextAutoRoute = clash?.tun['auto-route'] ?? true
@@ -152,6 +154,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 1 }}>
<Typography variant="h6">{t('settings.modals.tun.title')}</Typography>
<Button
data-setting-target="tun-viewer-reset"
variant="outlined"
size="small"
onClick={async () => {
@@ -204,8 +207,12 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
onCancel={() => setOpen(false)}
onOk={onSave}
>
<span data-settings-dialog="tun-viewer" />
<List>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.stack"
sx={{ padding: '5px 2px' }}
>
<ListItemText primary={t('settings.modals.tun.fields.stack')} />
<StackModeSwitch
value={values.stack}
@@ -218,7 +225,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.dnsMode"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.tun.fields.dnsMode')}
sx={{ maxWidth: 'fit-content' }}
@@ -237,7 +247,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</Box>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.device"
sx={{ padding: '5px 2px' }}
>
<ListItemText primary={t('settings.modals.tun.fields.device')} />
<TextField
autoComplete="new-password"
@@ -254,7 +267,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.autoRoute"
sx={{ padding: '5px 2px' }}
>
<ListItemText primary={t('settings.modals.tun.fields.autoRoute')} />
<Switch
edge="end"
@@ -270,7 +286,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
{OS === 'linux' && (
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.autoRedirect"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.tun.fields.autoRedirect')}
sx={{ maxWidth: 'fit-content' }}
@@ -294,7 +313,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
</ListItem>
)}
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.strictRoute"
sx={{ padding: '5px 2px' }}
>
<ListItemText primary={t('settings.modals.tun.fields.strictRoute')} />
<Switch
edge="end"
@@ -303,7 +325,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.autoDetectInterface"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.tun.fields.autoDetectInterface')}
/>
@@ -316,7 +341,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.dnsHijack"
sx={{ padding: '5px 2px' }}
>
<ListItemText primary={t('settings.modals.tun.fields.dnsHijack')} />
<TextField
autoComplete="new-password"
@@ -333,7 +361,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
/>
</ListItem>
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.mtu"
sx={{ padding: '5px 2px' }}
>
<ListItemText primary={t('settings.modals.tun.fields.mtu')} />
<TextField
autoComplete="new-password"
@@ -365,7 +396,10 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
setValues((v) => ({ ...v, routeExcludeAddress: nextValue }))
}
renderHeader={(modeToggle) => (
<ListItem sx={{ padding: '5px 2px' }}>
<ListItem
data-setting-target="settings.modals.tun.fields.routeExcludeAddress"
sx={{ padding: '5px 2px' }}
>
<ListItemText
primary={t('settings.modals.tun.fields.routeExcludeAddress')}
/>
+40 -16
View File
@@ -11,13 +11,7 @@ import {
Select,
MenuItem,
} from '@mui/material'
import {
forwardRef,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react'
import { forwardRef, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog } from '@/components/base'
@@ -40,6 +34,8 @@ import {
normalizeListenHost,
} from '@/utils/network'
import { useSettingDialog } from '../settings-search-context'
interface TunnelsViewerRef {
open: () => void
close: () => void
@@ -93,7 +89,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
const [values, setValues] = useState<TunnelFormValues>(buildInitialValues)
const [draftTunnels, setDraftTunnels] = useState<TunnelEntry[]>([])
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'tunnels-viewer', open, () => ({
open: () => {
setValues(buildInitialValues())
setDraftTunnels(() => clash?.tunnels ?? [])
@@ -301,10 +297,14 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
}}
onOk={handleSave}
>
<span data-settings-dialog="tunnels-viewer" />
<List>
{draftTunnels.length > 0 && (
<>
<ListItem sx={{ padding: '4px 0', opacity: 0.6 }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.existing"
sx={{ padding: '4px 0', opacity: 0.6 }}
>
<ListItemText
primary={t(
'settings.sections.clash.form.fields.tunnels.existing',
@@ -314,6 +314,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
<List component="nav">
{tunnelEntries.map((item) => (
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.default"
key={`${item.key}`}
sx={{ padding: '4px 0' }}
secondaryAction={
@@ -342,6 +343,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
)}
<ListItemButton
sx={{ padding: '4px 0', opacity: 0.8 }}
data-setting-target="tunnels-expand"
onClick={() => setExpanded((v) => !v)}
>
<ListItemText
@@ -352,7 +354,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
{expanded ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
{expanded && (
<ListItem sx={{ padding: '8px 0' }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.protocols"
sx={{ padding: '8px 0' }}
>
<div style={{ width: '100%' }}>
{/* 输入框区域 */}
{/* 协议 */}
@@ -380,7 +385,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
</ListItem>
{/* 本地监听地址 */}
<ListItem sx={{ padding: '6px 2px' }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.localAddr"
sx={{ padding: '6px 2px' }}
>
<ListItemText
primary={t(
'settings.sections.clash.form.fields.tunnels.localAddr',
@@ -399,7 +407,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
</ListItem>
{/* 本地监听端口 */}
<ListItem sx={{ padding: '6px 2px' }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.localPort"
sx={{ padding: '6px 2px' }}
>
<ListItemText
primary={t(
'settings.sections.clash.form.fields.tunnels.localPort',
@@ -419,7 +430,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
</ListItem>
{/* 目标服务器地址 */}
<ListItem sx={{ padding: '6px 2px' }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.targetAddr"
sx={{ padding: '6px 2px' }}
>
<ListItemText
primary={t(
'settings.sections.clash.form.fields.tunnels.targetAddr',
@@ -438,7 +452,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
</ListItem>
{/* 目标服务器端口 */}
<ListItem sx={{ padding: '6px 2px' }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.targetPort"
sx={{ padding: '6px 2px' }}
>
<ListItemText
primary={t(
'settings.sections.clash.form.fields.tunnels.targetPort',
@@ -458,7 +475,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
</ListItem>
{/* 代理组 */}
<ListItem sx={{ padding: '6px 2px' }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.proxyGroup"
sx={{ padding: '6px 2px' }}
>
<ListItemText
primary={
<>
@@ -520,7 +540,10 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
</ListItem>
{/* 代理节点 */}
<ListItem sx={{ padding: '6px 2px' }}>
<ListItem
data-setting-target="settings.sections.clash.form.fields.tunnels.proxyNode"
sx={{ padding: '6px 2px' }}
>
<ListItemText
primary={
<>
@@ -581,6 +604,7 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
{/* 添加按钮 */}
<Button
data-setting-target="settings.sections.clash.form.fields.tunnels.actions.add"
variant="contained"
size="small"
sx={{
@@ -131,6 +131,7 @@ export const WebUIItem = (props: Props) => {
</Typography>
<IconButton
size="small"
data-setting-target="web-ui-open"
title={t('settings.modals.webUI.actions.openUrl')}
color="inherit"
onClick={() => onOpenUrl?.(value)}
@@ -139,6 +140,7 @@ export const WebUIItem = (props: Props) => {
</IconButton>
<IconButton
size="small"
data-setting-target="web-ui-edit"
title={t('shared.actions.edit')}
color="inherit"
onClick={() => {
@@ -150,6 +152,7 @@ export const WebUIItem = (props: Props) => {
</IconButton>
<IconButton
size="small"
data-setting-target="web-ui-delete"
title={t('shared.actions.delete')}
color="inherit"
onClick={onDelete}
+40 -31
View File
@@ -1,7 +1,7 @@
import { Box, Button, Typography } from '@mui/material'
import { useLockFn } from 'ahooks'
import type { Ref } from 'react'
import { useImperativeHandle, useMemo, useState } from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseDialog, BaseEmpty, DialogRef } from '@/components/base'
@@ -10,6 +10,8 @@ import { useVerge } from '@/hooks/use-verge'
import { showNotice } from '@/services/notice-service'
import { openExternalUrl } from '@/utils/open-external-url'
import { useSettingDialog } from '../settings-search-context'
import { WebUIItem } from './web-ui-item'
const DEFAULT_WEB_UI_LIST = [
@@ -27,7 +29,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
const [open, setOpen] = useState(false)
const [editing, setEditing] = useState(false)
useImperativeHandle(ref, () => ({
useSettingDialog(ref, 'web-ui-viewer', open, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}))
@@ -114,6 +116,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
variant="contained"
size="small"
disabled={editing}
data-setting-target="web-ui-new"
onClick={() => setEditing(true)}
>
{t('shared.actions.new')}
@@ -132,36 +135,42 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
{!editing && webUIList.length === 0 && (
<BaseEmpty
extra={
<Typography sx={{ mt: 2, fontSize: '12px' }}>
{t('settings.modals.webUI.messages.placeholderInstruction')}
</Typography>
}
/>
)}
<div
data-settings-dialog="web-ui-viewer"
data-setting-target="web-ui-viewer"
tabIndex={-1}
>
{!editing && webUIList.length === 0 && (
<BaseEmpty
extra={
<Typography sx={{ mt: 2, fontSize: '12px' }}>
{t('settings.modals.webUI.messages.placeholderInstruction')}
</Typography>
}
/>
)}
{webUIEntries.map(({ item, index, key }) => (
<WebUIItem
key={key}
value={item}
onChange={(v) => handleChange(index, v)}
onDelete={() => handleDelete(index)}
onOpenUrl={handleOpenUrl}
/>
))}
{editing && (
<WebUIItem
value=""
onlyEdit
onChange={(v) => {
setEditing(false)
handleAdd(v || '')
}}
onCancel={() => setEditing(false)}
/>
)}
{webUIEntries.map(({ item, index, key }) => (
<WebUIItem
key={key}
value={item}
onChange={(v) => handleChange(index, v)}
onDelete={() => handleDelete(index)}
onOpenUrl={handleOpenUrl}
/>
))}
{editing && (
<WebUIItem
value=""
onlyEdit
onChange={(v) => {
setEditing(false)
handleAdd(v || '')
}}
onCancel={() => setEditing(false)}
/>
)}
</div>
</BaseDialog>
)
}