mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 02:52:07 +08:00
feat(settings): add searchable settings navigation
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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] ?? []}
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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}
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user