mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 11:02:08 +08:00
refactor(backend): specta ipc bindings (#8195)
* refactor(ipc): generate bindings with Specta * fix(profile): project IPC inputs to consumed fields * fix(events): remove unused refresh payloads * fix(webdav): return only consumed backup metadata * fix(network): trim unused interface metadata * fix(sysinfo): return only displayed system fields * chore(knip): check generated runtime exports * fix(proxy): confirm runtime chain updates before UI changes * fix(clipboard): propagate proxy environment copy failures * refactor(dns): remove ignored runtime patch * refactor(core): trim unused upgrade response fields * refactor(ipc): avoid redundant response copies * refactor(proxy): clarify automatic proxy query naming * chore(tests): remove added IPC regression tests * refactor(ipc): enforce typed command boundaries * chore(contract): check generated bindings before push * refactor(ipc): remove unused proxy liveness payloads
This commit is contained in:
@@ -423,7 +423,7 @@ interface PersistentProxySelectProps
|
||||
value: string
|
||||
selectedName: string
|
||||
groupName: string
|
||||
fixed?: string
|
||||
fixed?: string | null
|
||||
selectedDelay: number
|
||||
options: ProxyOption[]
|
||||
onChange: (value: string) => void
|
||||
@@ -785,8 +785,8 @@ export const CurrentProxyCard = () => {
|
||||
// The profile selection is the durable source across core restarts and run modes.
|
||||
handleSelectChange(
|
||||
selectedGroup.name,
|
||||
previousProxy,
|
||||
selectedGroup.fixed,
|
||||
previousProxy!,
|
||||
selectedGroup.fixed!,
|
||||
)({
|
||||
target: { value: nextName },
|
||||
})
|
||||
@@ -940,7 +940,7 @@ export const CurrentProxyCard = () => {
|
||||
unsortedProxyOptions,
|
||||
sortType,
|
||||
delays.of,
|
||||
defaultLatencyTimeout,
|
||||
defaultLatencyTimeout!,
|
||||
),
|
||||
[
|
||||
delays,
|
||||
|
||||
@@ -56,26 +56,7 @@ const openProfileHome = (url: string) => {
|
||||
void openExternalUrl(url).catch(showNotice.error)
|
||||
}
|
||||
|
||||
// 使用类型定义,而不是导入
|
||||
interface ProfileExtra {
|
||||
upload: number
|
||||
download: number
|
||||
total: number
|
||||
expire: number
|
||||
}
|
||||
|
||||
interface ProfileItem {
|
||||
uid: string
|
||||
type?: 'local' | 'remote' | 'merge' | 'script'
|
||||
name?: string
|
||||
desc?: string
|
||||
file?: string
|
||||
url?: string
|
||||
updated?: number
|
||||
extra?: ProfileExtra
|
||||
home?: string
|
||||
option?: any
|
||||
}
|
||||
type ProfileItem = IProfileItem
|
||||
|
||||
interface HomeProfileCardProps {
|
||||
current: ProfileItem | null | undefined
|
||||
@@ -298,10 +279,13 @@ export const HomeProfileCard = ({
|
||||
|
||||
setUpdating(true)
|
||||
try {
|
||||
await mutate(() => updateProfile(current.uid, current.option), {
|
||||
id: `update-profile:${current.uid}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
await mutate(
|
||||
() => updateProfile(current.uid!, current.option ?? undefined),
|
||||
{
|
||||
id: `update-profile:${current.uid}`,
|
||||
errorNotice: false,
|
||||
},
|
||||
)
|
||||
onProfileUpdated?.()
|
||||
|
||||
// 刷新首页数据
|
||||
@@ -345,7 +329,7 @@ export const HomeProfileCard = ({
|
||||
flex: 1,
|
||||
},
|
||||
}}
|
||||
title={current.name}
|
||||
title={current.name!}
|
||||
>
|
||||
<span>{current.name}</span>
|
||||
<LaunchOutlined
|
||||
|
||||
@@ -201,7 +201,7 @@ export const ProxyTunCard: FC = () => {
|
||||
onClick={() => handleTabChange('tun')}
|
||||
icon={TroubleshootRounded}
|
||||
label={t('settings.sections.system.toggles.tunMode')}
|
||||
hasIndicator={enable_tun_mode && isTunModeAvailable}
|
||||
hasIndicator={enable_tun_mode! && isTunModeAvailable}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ export const TestCard = () => {
|
||||
}}
|
||||
>
|
||||
{testList.map((item, index) => (
|
||||
<SortableItem key={item.uid} id={item.uid} index={index}>
|
||||
<SortableItem key={item.uid} id={item.uid!} index={index}>
|
||||
<TestItem
|
||||
itemData={item}
|
||||
onEdit={() => viewerRef.current?.edit(item)}
|
||||
|
||||
@@ -120,7 +120,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
)
|
||||
const setLoading = useCallback(
|
||||
(loading: boolean) => {
|
||||
setProfileLoading([itemData.uid], loading)
|
||||
setProfileLoading([itemData.uid!], loading)
|
||||
},
|
||||
[itemData.uid, setProfileLoading],
|
||||
)
|
||||
@@ -140,7 +140,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
debugLog(`强制刷新定时器任务`)
|
||||
}
|
||||
|
||||
const nextUpdate = await getNextUpdateTime(itemData.uid)
|
||||
const nextUpdate = await getNextUpdateTime(itemData.uid!)
|
||||
debugLog(`获取到下次更新时间结果:`, nextUpdate)
|
||||
|
||||
if (nextUpdate) {
|
||||
@@ -245,7 +245,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
const hasHome = !!itemData.home // only subscription url has home page
|
||||
|
||||
const { upload = 0, download = 0, total = 0 } = extra ?? {}
|
||||
const from = parseUrl(itemData.url)
|
||||
const from = parseUrl(itemData.url!)
|
||||
const description = itemData.desc
|
||||
const expire = parseExpire(extra?.expire)
|
||||
const progress = Math.min(
|
||||
@@ -253,7 +253,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
100,
|
||||
)
|
||||
|
||||
const loading = loadingCache.has(itemData.uid)
|
||||
const loading = loadingCache.has(itemData.uid!)
|
||||
|
||||
const [, forceRefresh] = useReducer((value: number) => value + 1, 0)
|
||||
useEffect(() => {
|
||||
@@ -263,7 +263,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
|
||||
const handler = () => {
|
||||
const now = Date.now()
|
||||
const lastUpdate = updated * 1000
|
||||
const lastUpdate = updated! * 1000
|
||||
if (now - lastUpdate >= 24 * 36e5) return
|
||||
|
||||
const wait = now - lastUpdate >= 36e5 ? 30e5 : 5e4
|
||||
@@ -293,7 +293,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
const [confirmOpen, setConfirmOpen] = useState(false)
|
||||
const [qrOpen, setQrOpen] = useState(false)
|
||||
|
||||
const loadProfileDocument = useCallback(() => readProfileFile(uid), [uid])
|
||||
const loadProfileDocument = useCallback(() => readProfileFile(uid!), [uid])
|
||||
const loadMergeDocument = useCallback(
|
||||
() => readProfileFile(option?.merge ?? ''),
|
||||
[option?.merge],
|
||||
@@ -370,7 +370,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
const onOpenFile = useLockFn(async () => {
|
||||
setAnchorEl(null)
|
||||
try {
|
||||
await mutate(() => viewProfile(itemData.uid), {
|
||||
await mutate(() => viewProfile(itemData.uid!), {
|
||||
id: 'view-profile',
|
||||
errorNotice: false,
|
||||
})
|
||||
@@ -402,7 +402,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
|
||||
try {
|
||||
const payload = Object.keys(option).length > 0 ? option : undefined
|
||||
await mutate(() => updateProfile(itemData.uid, payload), {
|
||||
await mutate(() => updateProfile(itemData.uid!, payload), {
|
||||
id: `update-profile:${itemData.uid}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
@@ -593,7 +593,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
|
||||
const handleSaveProfileDocument = useLockFn(async () => {
|
||||
const currentValue = profileDocument.value
|
||||
const result = await mutate(() => saveProfileFile(uid, currentValue), {
|
||||
const result = await mutate(() => saveProfileFile(uid!, currentValue), {
|
||||
id: `save-profile-file:${uid}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
@@ -728,7 +728,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
variant="h6"
|
||||
component="h2"
|
||||
noWrap
|
||||
title={name}
|
||||
title={name!}
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
@@ -794,7 +794,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
title={
|
||||
showNextUpdate
|
||||
? t('profiles.components.profileItem.tooltips.showLast')
|
||||
: `${t('shared.labels.updateTime')}: ${parseExpire(updated)}\n${t('profiles.components.profileItem.tooltips.showNext')}`
|
||||
: `${t('shared.labels.updateTime')}: ${parseExpire(updated!)}\n${t('profiles.components.profileItem.tooltips.showNext')}`
|
||||
}
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
@@ -812,8 +812,8 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
>
|
||||
{showNextUpdate
|
||||
? nextUpdateTime
|
||||
: updated > 0
|
||||
? dayjs(updated * 1000).fromNow()
|
||||
: updated! > 0
|
||||
? dayjs(updated! * 1000).fromNow()
|
||||
: ''}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -831,7 +831,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
) : (
|
||||
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: 'flex-end' }}>
|
||||
<span title={t('shared.labels.updateTime')}>
|
||||
{parseExpire(updated)}
|
||||
{parseExpire(updated!)}
|
||||
</span>
|
||||
</Box>
|
||||
)}
|
||||
@@ -896,7 +896,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
<RulesEditorViewer
|
||||
groupsUid={option?.groups ?? ''}
|
||||
mergeUid={option?.merge ?? ''}
|
||||
profileUid={uid}
|
||||
profileUid={uid!}
|
||||
property={option?.rules ?? ''}
|
||||
open={true}
|
||||
onSave={onSave}
|
||||
@@ -905,7 +905,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
)}
|
||||
{proxiesOpen && (
|
||||
<ProxiesEditorViewer
|
||||
profileUid={uid}
|
||||
profileUid={uid!}
|
||||
property={option?.proxies ?? ''}
|
||||
open={true}
|
||||
onSave={onSave}
|
||||
@@ -916,7 +916,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
<GroupsEditorViewer
|
||||
mergeUid={option?.merge ?? ''}
|
||||
proxiesUid={option?.proxies ?? ''}
|
||||
profileUid={uid}
|
||||
profileUid={uid!}
|
||||
property={option?.groups ?? ''}
|
||||
open={true}
|
||||
onSave={onSave}
|
||||
@@ -976,7 +976,7 @@ const ProfileItemBase = (props: ProfileItemProps) => {
|
||||
{qrOpen && itemData.url && (
|
||||
<QrViewer
|
||||
open={true}
|
||||
value={`${itemData.url}${itemData.url.includes('?') ? '&' : '?'}name=${encodeURIComponent(name)}`}
|
||||
value={`${itemData.url}${itemData.url.includes('?') ? '&' : '?'}name=${encodeURIComponent(name!)}`}
|
||||
onClose={() => setQrOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -115,7 +115,13 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
||||
}
|
||||
|
||||
const name = form.name || `${form.type} file`
|
||||
const item = { ...form, name, option }
|
||||
const item = {
|
||||
type: form.type,
|
||||
name,
|
||||
desc: form.desc,
|
||||
option,
|
||||
...(form.type === 'remote' ? { url: form.url } : {}),
|
||||
}
|
||||
const isRemote = form.type === 'remote'
|
||||
const isUpdate = openType === 'edit'
|
||||
|
||||
@@ -139,7 +145,7 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
||||
t('profiles.modals.profileForm.errors.uidMissing'),
|
||||
)
|
||||
}
|
||||
await mutate(() => patchProfile(form.uid, item), {
|
||||
await mutate(() => patchProfile(form.uid!, item), {
|
||||
id: 'patch-profile',
|
||||
errorNotice: false,
|
||||
})
|
||||
@@ -157,7 +163,7 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
||||
t('profiles.modals.profileForm.errors.uidMissing'),
|
||||
)
|
||||
}
|
||||
await mutate(() => patchProfile(form.uid, item), {
|
||||
await mutate(() => patchProfile(form.uid!, item), {
|
||||
id: 'patch-profile',
|
||||
errorNotice: false,
|
||||
})
|
||||
@@ -190,13 +196,13 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
||||
t('profiles.modals.profileForm.errors.uidMissing'),
|
||||
)
|
||||
}
|
||||
await mutate(() => patchProfile(form.uid, retryItem), {
|
||||
await mutate(() => patchProfile(form.uid!, retryItem), {
|
||||
id: 'patch-profile',
|
||||
errorNotice: false,
|
||||
})
|
||||
|
||||
await mutate(
|
||||
() => patchProfile(form.uid, { option: originalOptions }),
|
||||
() => patchProfile(form.uid!, { option: originalOptions }),
|
||||
{
|
||||
id: 'patch-profile',
|
||||
errorNotice: false,
|
||||
@@ -413,7 +419,7 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
||||
<InputLabel>
|
||||
{t('profiles.modals.profileForm.fields.useSystemProxy')}
|
||||
</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
<Switch checked={field.value!} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
@@ -426,7 +432,7 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
||||
<InputLabel>
|
||||
{t('profiles.modals.profileForm.fields.useClashProxy')}
|
||||
</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
<Switch checked={field.value!} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
@@ -439,7 +445,7 @@ export function ProfileViewer({ onChange, ref }: ProfileViewerProps) {
|
||||
<InputLabel>
|
||||
{t('profiles.modals.profileForm.fields.acceptInvalidCerts')}
|
||||
</InputLabel>
|
||||
<Switch checked={field.value} {...field} color="primary" />
|
||||
<Switch checked={field.value!} {...field} color="primary" />
|
||||
</StyledBox>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -445,10 +445,14 @@ export const ProxyChain = ({
|
||||
if (isConnected) {
|
||||
setIsConnecting(true)
|
||||
try {
|
||||
await mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
const result = await mutate(
|
||||
() => updateProxyChainConfigInRuntime(null),
|
||||
{
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
},
|
||||
)
|
||||
if (!result.ok) return
|
||||
|
||||
const targetGroup =
|
||||
mode === 'global'
|
||||
@@ -461,7 +465,7 @@ export const ProxyChain = ({
|
||||
id: `select-proxy-node:${targetGroup}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
recordSelection(targetGroup, 'DIRECT')
|
||||
await recordSelection(targetGroup, 'DIRECT')
|
||||
} catch {
|
||||
if (currentProxyChain.length >= 1) {
|
||||
try {
|
||||
@@ -473,7 +477,7 @@ export const ProxyChain = ({
|
||||
errorNotice: false,
|
||||
},
|
||||
)
|
||||
recordSelection(targetGroup, currentProxyChain[0].name)
|
||||
await recordSelection(targetGroup, currentProxyChain[0].name)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -519,10 +523,14 @@ export const ProxyChain = ({
|
||||
// 第一步:保存链式代理配置
|
||||
const chainProxies = currentProxyChain.map((node) => node.name)
|
||||
debugLog('Saving chain config:', chainProxies)
|
||||
await mutate(() => updateProxyChainConfigInRuntime(chainProxies), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
const result = await mutate(
|
||||
() => updateProxyChainConfigInRuntime(chainProxies),
|
||||
{
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
},
|
||||
)
|
||||
if (!result.ok) return
|
||||
debugLog('Chain configuration saved successfully')
|
||||
|
||||
// 第二步:连接到代理链的最后一个节点
|
||||
@@ -545,12 +553,12 @@ export const ProxyChain = ({
|
||||
)
|
||||
// The chain moves the group like any other selection, so the profile has to learn about
|
||||
// it: what the profile holds is what gets re-applied the next time the core starts.
|
||||
recordSelection(targetGroup || 'GLOBAL', lastNode.name)
|
||||
await recordSelection(targetGroup || 'GLOBAL', lastNode.name)
|
||||
localStorage.setItem('proxy-chain-group', targetGroup || 'GLOBAL')
|
||||
localStorage.setItem('proxy-chain-exit-node', lastNode.name)
|
||||
|
||||
// 刷新代理信息以更新连接状态
|
||||
refreshProxy()
|
||||
await refreshProxy()
|
||||
debugLog('Successfully connected to proxy chain')
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to proxy chain:', error)
|
||||
@@ -619,14 +627,15 @@ export const ProxyChain = ({
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
void mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
localStorage.removeItem('proxy-chain-group')
|
||||
localStorage.removeItem('proxy-chain-exit-node')
|
||||
localStorage.removeItem('proxy-chain-items')
|
||||
onUpdateChain([])
|
||||
onFulfilled: () => {
|
||||
localStorage.removeItem('proxy-chain-group')
|
||||
localStorage.removeItem('proxy-chain-exit-node')
|
||||
localStorage.removeItem('proxy-chain-items')
|
||||
onUpdateChain([])
|
||||
},
|
||||
}).catch(() => {})
|
||||
}}
|
||||
sx={{
|
||||
color: theme.palette.error.main,
|
||||
|
||||
@@ -5,11 +5,11 @@ export type ProxyEmptyStateReason =
|
||||
| 'no-proxy-info'
|
||||
|
||||
type ProfileSummary = {
|
||||
current?: string
|
||||
current?: string | null
|
||||
items?: Array<{
|
||||
uid?: string
|
||||
type?: string
|
||||
}>
|
||||
uid?: string | null
|
||||
type?: string | null
|
||||
}> | null
|
||||
}
|
||||
|
||||
interface ProxyListStateInput {
|
||||
@@ -20,7 +20,7 @@ interface ProxyListStateInput {
|
||||
isRunningModePending: boolean
|
||||
}
|
||||
|
||||
const isSubscriptionProfile = (item: { type?: string }): boolean =>
|
||||
const isSubscriptionProfile = (item: { type?: string | null }): boolean =>
|
||||
item.type === 'remote' || item.type === 'local'
|
||||
|
||||
/** Decides only pre-render states; the built list owns whether it has renderable content. */
|
||||
|
||||
@@ -400,20 +400,26 @@ export function ProxyGroupsChain(props: ProxyGroupsChainProps) {
|
||||
setRuleMenuAnchor(null)
|
||||
}
|
||||
|
||||
const handleGroupSelect = (groupName: string) => {
|
||||
onGroupSelect(groupName)
|
||||
handleGroupMenuClose()
|
||||
|
||||
const handleGroupSelect = async (groupName: string) => {
|
||||
if (mode === 'rule') {
|
||||
mutate(() => updateProxyChainConfigInRuntime(null), {
|
||||
id: 'update-proxy-chain-runtime',
|
||||
errorNotice: false,
|
||||
})
|
||||
try {
|
||||
const result = await mutate(
|
||||
() => updateProxyChainConfigInRuntime(null),
|
||||
{
|
||||
id: 'update-proxy-chain-runtime',
|
||||
},
|
||||
)
|
||||
if (!result.ok) return
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
localStorage.removeItem('proxy-chain-group')
|
||||
localStorage.removeItem('proxy-chain-exit-node')
|
||||
localStorage.removeItem('proxy-chain-items')
|
||||
setProxyChain([])
|
||||
}
|
||||
onGroupSelect(groupName)
|
||||
handleGroupMenuClose()
|
||||
}
|
||||
|
||||
const handleCloseDuplicateWarning = useCallback(() => {
|
||||
|
||||
@@ -198,7 +198,7 @@ export const ProxyRender = memo(function ProxyRender(props: RenderProps) {
|
||||
{toolsOnLeft && proxyCount}
|
||||
<ProxyGroupTools
|
||||
side={toolsOnLeft ? 'left' : 'right'}
|
||||
url={group.testUrl}
|
||||
url={group.testUrl!}
|
||||
groupName={group.name}
|
||||
headState={headState!}
|
||||
onLocation={() => onLocation(group)}
|
||||
@@ -247,7 +247,7 @@ export const ProxyRender = memo(function ProxyRender(props: RenderProps) {
|
||||
return (
|
||||
<ProxyHead
|
||||
sx={{ pl: 2, pr: 3, mt: 0.5, mb: 1 }}
|
||||
url={group.testUrl}
|
||||
url={group.testUrl!}
|
||||
groupName={group.name}
|
||||
headState={headState!}
|
||||
onLocation={() => onLocation(group)}
|
||||
|
||||
@@ -17,7 +17,6 @@ const node = (memberIndex: number, delay: number, name = `${memberIndex}`) =>
|
||||
history: [{ delay }],
|
||||
extra: {
|
||||
'http://cp.cloudflare.com/generate_204': {
|
||||
alive: true,
|
||||
history: [{ time: new Date(0).toISOString(), delay }],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -110,13 +110,13 @@ const CHAIN_DELAY_GROUP = 'chain-mode'
|
||||
const virtualGroup = (members: ProxyGroupView['members']): ProxyGroupView => ({
|
||||
name: CHAIN_DELAY_GROUP,
|
||||
type: 'Selector',
|
||||
alive: true,
|
||||
udp: false,
|
||||
xudp: false,
|
||||
tfo: false,
|
||||
mptcp: false,
|
||||
smux: false,
|
||||
history: [],
|
||||
extra: {},
|
||||
members,
|
||||
})
|
||||
|
||||
@@ -237,7 +237,7 @@ export const useRenderList = (
|
||||
selected?.name ?? CHAIN_DELAY_GROUP,
|
||||
'',
|
||||
0,
|
||||
latencyTimeout,
|
||||
latencyTimeout!,
|
||||
)
|
||||
if (col > 1) {
|
||||
return groupOccurrences(occurrences, col).map((memberCol) => ({
|
||||
@@ -289,8 +289,8 @@ export const useRenderList = (
|
||||
key: group.name,
|
||||
group,
|
||||
headState,
|
||||
icon: group.icon,
|
||||
testUrl: group.testUrl,
|
||||
icon: group.icon!,
|
||||
testUrl: group.testUrl!,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -300,7 +300,7 @@ export const useRenderList = (
|
||||
group.name,
|
||||
headState.filterText,
|
||||
headState.sortType,
|
||||
latencyTimeout,
|
||||
latencyTimeout!,
|
||||
{
|
||||
matchCase: headState.filterMatchCase,
|
||||
matchWholeWord: headState.filterMatchWholeWord,
|
||||
@@ -337,11 +337,11 @@ export const useRenderList = (
|
||||
}
|
||||
|
||||
cache.set(group.name, {
|
||||
now: group.now,
|
||||
now: group.now!,
|
||||
members: group.members,
|
||||
headState,
|
||||
col,
|
||||
latencyTimeout,
|
||||
latencyTimeout: latencyTimeout!,
|
||||
delays: groupDelays.get(group.name),
|
||||
items: ret,
|
||||
})
|
||||
|
||||
@@ -50,9 +50,9 @@ export const BackupConfigViewer = memo(
|
||||
|
||||
const { register, handleSubmit, watch } = useForm<IWebDavConfig>({
|
||||
defaultValues: {
|
||||
url: webdav_url,
|
||||
username: webdav_username,
|
||||
password: webdav_password,
|
||||
url: webdav_url!,
|
||||
username: webdav_username!,
|
||||
password: webdav_password!,
|
||||
},
|
||||
})
|
||||
const url = watch('url')
|
||||
|
||||
@@ -196,7 +196,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
const { verge } = useVerge()
|
||||
const { current: currentProfile } = useProfiles()
|
||||
const dnsEnabled = currentProfile
|
||||
? (verge?.profile_dns_settings?.[currentProfile.uid]?.enabled ??
|
||||
? (verge?.profile_dns_settings?.[currentProfile.uid!]?.enabled ??
|
||||
verge?.enable_dns_settings ??
|
||||
false)
|
||||
: false
|
||||
|
||||
@@ -438,7 +438,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
<Switch
|
||||
edge="end"
|
||||
disabled={!enabled}
|
||||
checked={value.pac}
|
||||
checked={value.pac!}
|
||||
onChange={(_, e) => setValue((v) => ({ ...v, pac: e }))}
|
||||
/>
|
||||
</ListItem>
|
||||
@@ -455,7 +455,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
<Switch
|
||||
edge="end"
|
||||
disabled={!enabled}
|
||||
checked={value.guard}
|
||||
checked={value.guard!}
|
||||
onChange={(_, e) => setValue((v) => ({ ...v, guard: e }))}
|
||||
sx={{ marginLeft: 'auto' }}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { alpha, Box, Button, LinearProgress } from '@mui/material'
|
||||
import type { DownloadEvent } from '@tauri-apps/plugin-updater'
|
||||
import { useLockFn } from 'ahooks'
|
||||
import type { Ref } from 'react'
|
||||
import {
|
||||
@@ -15,6 +14,7 @@ import type { Options as ReactMarkdownOptions } from 'react-markdown'
|
||||
|
||||
import { BaseDialog, DialogRef } from '@/components/base'
|
||||
import { useUpdate } from '@/hooks/use-update'
|
||||
import type { DownloadEvent } from '@/services/bindings'
|
||||
import {
|
||||
cancelUpdateDownload,
|
||||
installUpdate,
|
||||
|
||||
@@ -40,7 +40,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
const { verge, mutateVerge } = useVerge()
|
||||
const { current: currentProfile } = useProfiles()
|
||||
const dnsEnabled = currentProfile
|
||||
? (verge?.profile_dns_settings?.[currentProfile.uid]?.enabled ??
|
||||
? (verge?.profile_dns_settings?.[currentProfile.uid!]?.enabled ??
|
||||
verge?.enable_dns_settings ??
|
||||
false)
|
||||
: false
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useVerge } from '@/hooks/use-verge'
|
||||
import { navigationItems } from '@/pages/_navigation-meta'
|
||||
import { copyClashEnv } from '@/services/cmds'
|
||||
import { supportedLanguages } from '@/services/i18n'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import getSystem from '@/utils/get-system'
|
||||
|
||||
@@ -75,8 +76,18 @@ const SettingVergeBasic = ({ onError }: Props) => {
|
||||
}
|
||||
|
||||
const onCopyClashEnv = useCallback(async () => {
|
||||
await copyClashEnv()
|
||||
showNotice.success('shared.feedback.notifications.common.copySuccess', 1000)
|
||||
try {
|
||||
await mutate(copyClashEnv, {
|
||||
id: 'copy-clash-env',
|
||||
onFulfilled: () =>
|
||||
showNotice.success(
|
||||
'shared.feedback.notifications.common.copySuccess',
|
||||
1000,
|
||||
),
|
||||
})
|
||||
} catch {
|
||||
// The shared mutation boundary reports the failure.
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,11 +25,11 @@ export const TestItem = ({ itemData, onEdit, onDelete: removeTest }: Props) => {
|
||||
const [position, setPosition] = useState({ left: 0, top: 0 })
|
||||
const [delay, setDelay] = useState(-1)
|
||||
const { uid, name, icon, url } = itemData
|
||||
const iconCachePath = useIconCache({ icon, cacheKey: uid })
|
||||
const iconCachePath = useIconCache({ icon: icon!, cacheKey: uid! })
|
||||
|
||||
const onDelay = useCallback(async () => {
|
||||
setDelay(-2)
|
||||
const result = await cmdTestDelay(url)
|
||||
const result = await cmdTestDelay(url!)
|
||||
setDelay(result)
|
||||
}, [url])
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TestItem = ({ itemData, onEdit, onDelete: removeTest }: Props) => {
|
||||
const onDelete = useLockFn(async () => {
|
||||
setAnchorEl(null)
|
||||
try {
|
||||
removeTest(uid)
|
||||
removeTest(uid!)
|
||||
} catch (err: any) {
|
||||
showNotice.error(err)
|
||||
}
|
||||
@@ -81,17 +81,17 @@ export const TestItem = ({ itemData, onEdit, onDelete: removeTest }: Props) => {
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
{icon.trim().startsWith('http') && (
|
||||
<img
|
||||
alt={name}
|
||||
alt={name!}
|
||||
src={iconCachePath === '' ? icon : iconCachePath}
|
||||
height="40px"
|
||||
/>
|
||||
)}
|
||||
{icon.trim().startsWith('data') && (
|
||||
<img alt={name} src={icon} height="40px" />
|
||||
<img alt={name!} src={icon!} height="40px" />
|
||||
)}
|
||||
{icon.trim().startsWith('<svg') && (
|
||||
<img
|
||||
alt={name}
|
||||
alt={name!}
|
||||
src={`data:image/svg+xml;charset=utf-8,${encodeURIComponent(icon)}`}
|
||||
height="40px"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user