Files
clash-verge-rev/src/components/setting/mods/clash-core-viewer.tsx
T

202 lines
5.5 KiB
TypeScript

import {
RestartAltRounded,
SwitchAccessShortcutRounded,
} from '@mui/icons-material'
import {
Box,
Button,
Chip,
CircularProgress,
List,
ListItemButton,
ListItemText,
} from '@mui/material'
import { useLockFn } from 'ahooks'
import type { Ref } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { closeAllConnections } from 'tauri-plugin-mihomo-api'
import { BaseDialog, DialogRef } from '@/components/base'
import { useClash, useClashInfo } from '@/hooks/use-clash'
import { useVerge } from '@/hooks/use-verge'
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',
core: 'verge-mihomo',
chipKey: 'settings.modals.clashCore.variants.release',
},
{
name: 'Mihomo Alpha',
core: 'verge-mihomo-alpha',
chipKey: 'settings.modals.clashCore.variants.alpha',
},
]
export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation()
const { verge, mutateVerge } = useVerge()
const { mutateVersion } = useClash()
const { invalidateClashConfig } = useClashInfo()
const [open, setOpen] = useState(false)
const [upgrading, setUpgrading] = useState(false)
const [restarting, setRestarting] = useState(false)
const [changingCore, setChangingCore] = useState<string | null>(null)
useSettingDialog(ref, 'clash-core-viewer', open, () => ({
open: () => setOpen(true),
close: () => setOpen(false),
}))
const { clash_core = 'verge-mihomo' } = verge ?? {}
const onCoreChange = useLockFn(async (core: string) => {
if (core === clash_core) return
try {
setChangingCore(core)
void mutate(() => closeAllConnections(), {
id: 'close-all-connections',
errorNotice: false,
})
const result = await mutate(() => changeClashCore(core), {
id: 'change-clash-core',
errorNotice: false,
})
if (!result.ok) return
const errorMsg = result.value
if (errorMsg) {
showNotice.error(errorMsg)
setChangingCore(null)
return
}
mutateVerge()
await new Promise((resolve) => setTimeout(resolve, 500))
invalidateClashConfig()
mutateVersion()
} catch (err) {
showNotice.error(err)
} finally {
setChangingCore(null)
}
})
const onRestart = useLockFn(async () => {
try {
setRestarting(true)
await mutate(() => restartCore(), {
id: 'restart-core',
successNotice: t(
'settings.feedback.notifications.clash.restartSuccess',
),
})
setRestarting(false)
} catch {
setRestarting(false)
}
})
const onUpgrade = useLockFn(async () => {
try {
setUpgrading(true)
await mutate(() => upgradeClashCore(), {
id: 'upgrade-clash-core',
onFulfilled: (report) => {
showNotice.success(
report.upgraded
? t('settings.feedback.notifications.clash.versionUpdated')
: t('settings.feedback.notifications.clash.alreadyLatestVersion'),
)
},
})
} catch {
// the funnel already notified
} finally {
setUpgrading(false)
}
})
return (
<BaseDialog
open={open}
title={
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
{t('settings.sections.clash.form.fields.clashCore')}
<Box>
<Button
variant="contained"
size="small"
startIcon={<SwitchAccessShortcutRounded />}
loadingPosition="start"
loading={upgrading}
disabled={restarting || changingCore !== null}
sx={{ marginRight: '8px' }}
data-setting-target="core-upgrade"
onClick={onUpgrade}
>
{t('shared.actions.upgrade')}
</Button>
<Button
variant="contained"
size="small"
startIcon={<RestartAltRounded />}
loadingPosition="start"
loading={restarting}
disabled={upgrading}
data-setting-target="core-restart"
onClick={onRestart}
>
{t('shared.actions.restart')}
</Button>
</Box>
</Box>
}
contentSx={{
pb: 0,
width: 400,
height: 180,
overflowY: 'auto',
userSelect: 'text',
marginTop: '-8px',
}}
disableOk
cancelBtn={t('shared.actions.close')}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<List
component="nav"
data-settings-dialog="clash-core-viewer"
data-setting-target="core-variants"
tabIndex={-1}
>
{VALID_CORE.map((each) => (
<ListItemButton
key={each.core}
selected={each.core === clash_core}
onClick={() => onCoreChange(each.core)}
disabled={changingCore !== null || restarting || upgrading}
>
<ListItemText primary={each.name} secondary={`/${each.core}`} />
{changingCore === each.core ? (
<CircularProgress size={20} sx={{ mr: 1 }} />
) : (
<Chip label={t(each.chipKey)} size="small" />
)}
</ListItemButton>
))}
</List>
</BaseDialog>
)
}