mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-28 05:30:29 +08:00
Compare commits
No commits in common. "ddd0077a616cfa827faa61f69bb6e51c45a0dbf9" and "7619b4d3e516407917e59327007a112eb214b102" have entirely different histories.
ddd0077a61
...
7619b4d3e5
@ -40,7 +40,7 @@ export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<voi
|
|||||||
const config = await getOverrideConfig()
|
const config = await getOverrideConfig()
|
||||||
const newItem = await createOverride(item)
|
const newItem = await createOverride(item)
|
||||||
if (await getOverrideItem(item.id)) {
|
if (await getOverrideItem(item.id)) {
|
||||||
await updateOverrideItem(newItem)
|
updateOverrideItem(newItem)
|
||||||
} else {
|
} else {
|
||||||
config.items.push(newItem)
|
config.items.push(newItem)
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
|
|||||||
}
|
}
|
||||||
case 'local': {
|
case 'local': {
|
||||||
const data = item.file || ''
|
const data = item.file || ''
|
||||||
await setOverride(id, newItem.ext, data)
|
setOverride(id, newItem.ext, data)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -339,6 +339,11 @@ export async function createSmartOverride(): Promise<void> {
|
|||||||
smartCollectorSize
|
smartCollectorSize
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 检查是否已存在 Smart 覆写配置
|
||||||
|
const existingOverride = await getOverrideItem(SMART_OVERRIDE_ID)
|
||||||
|
|
||||||
|
if (existingOverride) {
|
||||||
|
// 如果已存在,更新配置
|
||||||
await addOverrideItem({
|
await addOverrideItem({
|
||||||
id: SMART_OVERRIDE_ID,
|
id: SMART_OVERRIDE_ID,
|
||||||
name: 'Smart Core Override',
|
name: 'Smart Core Override',
|
||||||
@ -347,6 +352,17 @@ export async function createSmartOverride(): Promise<void> {
|
|||||||
global: true,
|
global: true,
|
||||||
file: template
|
file: template
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// 如果不存在,创建新的覆写配置
|
||||||
|
await addOverrideItem({
|
||||||
|
id: SMART_OVERRIDE_ID,
|
||||||
|
name: 'Smart Core Override',
|
||||||
|
type: 'local',
|
||||||
|
ext: 'js',
|
||||||
|
global: true,
|
||||||
|
file: template
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await overrideLogger.error('Failed to create Smart override', error)
|
await overrideLogger.error('Failed to create Smart override', error)
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@ -499,7 +499,24 @@ async function checkProfile(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function checkTunPermissions(): Promise<boolean> {
|
export async function checkTunPermissions(): Promise<boolean> {
|
||||||
return checkMihomoCorePermissions()
|
const { core = 'mihomo' } = await getAppConfig()
|
||||||
|
const corePath = mihomoCorePath(core)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return await checkAdminPrivileges()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
const { stat } = await import('fs/promises')
|
||||||
|
const stats = await stat(corePath)
|
||||||
|
return (stats.mode & 0o4000) !== 0 && stats.uid === 0
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function grantTunPermissions(): Promise<void> {
|
export async function grantTunPermissions(): Promise<void> {
|
||||||
|
|||||||
@ -278,7 +278,7 @@ const mihomoTraffic = async (): Promise<void> => {
|
|||||||
mihomoTrafficWs.onclose = (): void => {
|
mihomoTrafficWs.onclose = (): void => {
|
||||||
if (trafficRetry) {
|
if (trafficRetry) {
|
||||||
trafficRetry--
|
trafficRetry--
|
||||||
setTimeout(mihomoTraffic, 1000)
|
mihomoTraffic()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,7 +325,7 @@ const mihomoMemory = async (): Promise<void> => {
|
|||||||
mihomoMemoryWs.onclose = (): void => {
|
mihomoMemoryWs.onclose = (): void => {
|
||||||
if (memoryRetry) {
|
if (memoryRetry) {
|
||||||
memoryRetry--
|
memoryRetry--
|
||||||
setTimeout(mihomoMemory, 1000)
|
mihomoMemory()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +373,7 @@ const mihomoLogs = async (): Promise<void> => {
|
|||||||
mihomoLogsWs.onclose = (): void => {
|
mihomoLogsWs.onclose = (): void => {
|
||||||
if (logsRetry) {
|
if (logsRetry) {
|
||||||
logsRetry--
|
logsRetry--
|
||||||
setTimeout(mihomoLogs, 1000)
|
mihomoLogs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,7 +419,7 @@ const mihomoConnections = async (): Promise<void> => {
|
|||||||
mihomoConnectionsWs.onclose = (): void => {
|
mihomoConnectionsWs.onclose = (): void => {
|
||||||
if (connectionsRetry) {
|
if (connectionsRetry) {
|
||||||
connectionsRetry--
|
connectionsRetry--
|
||||||
setTimeout(mihomoConnections, 1000)
|
mihomoConnections()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { quitWithoutCore, startCore, stopCore, checkAdminRestartForTun, checkHig
|
|||||||
import { triggerSysProxy } from './sys/sysproxy'
|
import { triggerSysProxy } from './sys/sysproxy'
|
||||||
import icon from '../../resources/icon.png?asset'
|
import icon from '../../resources/icon.png?asset'
|
||||||
import { createTray, hideDockIcon, showDockIcon } from './resolve/tray'
|
import { createTray, hideDockIcon, showDockIcon } from './resolve/tray'
|
||||||
import { init, initBasic, safeShowErrorBox } from './utils/init'
|
import { init, initBasic } from './utils/init'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { initShortcut } from './resolve/shortcut'
|
import { initShortcut } from './resolve/shortcut'
|
||||||
import { spawn, exec } from 'child_process'
|
import { spawn, exec } from 'child_process'
|
||||||
@ -23,6 +23,24 @@ import i18next from 'i18next'
|
|||||||
import { logger } from './utils/logger'
|
import { logger } from './utils/logger'
|
||||||
import { initWebdavBackupScheduler } from './resolve/backup'
|
import { initWebdavBackupScheduler } from './resolve/backup'
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
function showSafeErrorBox(titleKey: string, message: string): void {
|
||||||
|
let title: string
|
||||||
|
try {
|
||||||
|
title = i18next.t(titleKey)
|
||||||
|
if (!title || title === titleKey) throw new Error('Translation not ready')
|
||||||
|
} catch {
|
||||||
|
const isZh = app.getLocale().startsWith('zh')
|
||||||
|
const fallbacks: Record<string, { zh: string; en: string }> = {
|
||||||
|
'common.error.initFailed': { zh: '应用初始化失败', en: 'Application initialization failed' },
|
||||||
|
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' },
|
||||||
|
'profiles.error.importFailed': { zh: '配置导入失败', en: 'Profile import failed' },
|
||||||
|
'common.error.adminRequired': { zh: '需要管理员权限', en: 'Administrator privileges required' }
|
||||||
|
}
|
||||||
|
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
|
||||||
|
}
|
||||||
|
dialog.showErrorBox(title, message)
|
||||||
|
}
|
||||||
|
|
||||||
async function fixUserDataPermissions(): Promise<void> {
|
async function fixUserDataPermissions(): Promise<void> {
|
||||||
if (process.platform !== 'darwin') return
|
if (process.platform !== 'darwin') return
|
||||||
@ -62,7 +80,7 @@ async function initApp(): Promise<void> {
|
|||||||
|
|
||||||
initApp()
|
initApp()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
safeShowErrorBox('common.error.initFailed', `${e}`)
|
showSafeErrorBox('common.error.initFailed', `${e}`)
|
||||||
app.quit()
|
app.quit()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -132,7 +150,7 @@ async function checkHighPrivilegeCoreEarly(): Promise<void> {
|
|||||||
await restartAsAdmin(false)
|
await restartAsAdmin(false)
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
safeShowErrorBox('common.error.adminRequired', `${error}`)
|
showSafeErrorBox('common.error.adminRequired', `${error}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -216,7 +234,7 @@ app.whenReady().then(async () => {
|
|||||||
}
|
}
|
||||||
await initI18n({ lng: appConfig.language })
|
await initI18n({ lng: appConfig.language })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
safeShowErrorBox('common.error.initFailed', `${e}`)
|
showSafeErrorBox('common.error.initFailed', `${e}`)
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +247,7 @@ app.whenReady().then(async () => {
|
|||||||
await checkAdminRestartForTun()
|
await checkAdminRestartForTun()
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await startMonitor()
|
await startMonitor()
|
||||||
@ -285,7 +303,7 @@ async function handleDeepLink(url: string): Promise<void> {
|
|||||||
new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show()
|
new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show()
|
||||||
break
|
break
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
safeShowErrorBox('profiles.error.importFailed', `${url}\n${e}`)
|
showSafeErrorBox('profiles.error.importFailed', `${url}\n${e}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,13 +21,7 @@ import i18next from 'i18next'
|
|||||||
|
|
||||||
let backupCronJob: Cron | null = null
|
let backupCronJob: Cron | null = null
|
||||||
|
|
||||||
interface WebDAVContext {
|
export async function webdavBackup(): Promise<boolean> {
|
||||||
client: ReturnType<Awaited<typeof import('webdav/dist/node/index.js')>['createClient']>
|
|
||||||
webdavDir: string
|
|
||||||
webdavMaxBackups: number
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getWebDAVClient(): Promise<WebDAVContext> {
|
|
||||||
const { createClient } = await import('webdav/dist/node/index.js')
|
const { createClient } = await import('webdav/dist/node/index.js')
|
||||||
const {
|
const {
|
||||||
webdavUrl = '',
|
webdavUrl = '',
|
||||||
@ -36,17 +30,6 @@ async function getWebDAVClient(): Promise<WebDAVContext> {
|
|||||||
webdavDir = 'clash-party',
|
webdavDir = 'clash-party',
|
||||||
webdavMaxBackups = 0
|
webdavMaxBackups = 0
|
||||||
} = await getAppConfig()
|
} = await getAppConfig()
|
||||||
|
|
||||||
const client = createClient(webdavUrl, {
|
|
||||||
username: webdavUsername,
|
|
||||||
password: webdavPassword
|
|
||||||
})
|
|
||||||
|
|
||||||
return { client, webdavDir, webdavMaxBackups }
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function webdavBackup(): Promise<boolean> {
|
|
||||||
const { client, webdavDir, webdavMaxBackups } = await getWebDAVClient()
|
|
||||||
const zip = new AdmZip()
|
const zip = new AdmZip()
|
||||||
|
|
||||||
zip.addLocalFile(appConfigPath())
|
zip.addLocalFile(appConfigPath())
|
||||||
@ -61,6 +44,10 @@ export async function webdavBackup(): Promise<boolean> {
|
|||||||
const date = new Date()
|
const date = new Date()
|
||||||
const zipFileName = `${process.platform}_${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip`
|
const zipFileName = `${process.platform}_${dayjs(date).format('YYYY-MM-DD_HH-mm-ss')}.zip`
|
||||||
|
|
||||||
|
const client = createClient(webdavUrl, {
|
||||||
|
username: webdavUsername,
|
||||||
|
password: webdavPassword
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
await client.createDirectory(webdavDir)
|
await client.createDirectory(webdavDir)
|
||||||
} catch {
|
} catch {
|
||||||
@ -105,14 +92,36 @@ export async function webdavBackup(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function webdavRestore(filename: string): Promise<void> {
|
export async function webdavRestore(filename: string): Promise<void> {
|
||||||
const { client, webdavDir } = await getWebDAVClient()
|
const { createClient } = await import('webdav/dist/node/index.js')
|
||||||
|
const {
|
||||||
|
webdavUrl = '',
|
||||||
|
webdavUsername = '',
|
||||||
|
webdavPassword = '',
|
||||||
|
webdavDir = 'clash-party'
|
||||||
|
} = await getAppConfig()
|
||||||
|
|
||||||
|
const client = createClient(webdavUrl, {
|
||||||
|
username: webdavUsername,
|
||||||
|
password: webdavPassword
|
||||||
|
})
|
||||||
const zipData = await client.getFileContents(`${webdavDir}/${filename}`)
|
const zipData = await client.getFileContents(`${webdavDir}/${filename}`)
|
||||||
const zip = new AdmZip(zipData as Buffer)
|
const zip = new AdmZip(zipData as Buffer)
|
||||||
zip.extractAllTo(dataDir(), true)
|
zip.extractAllTo(dataDir(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listWebdavBackups(): Promise<string[]> {
|
export async function listWebdavBackups(): Promise<string[]> {
|
||||||
const { client, webdavDir } = await getWebDAVClient()
|
const { createClient } = await import('webdav/dist/node/index.js')
|
||||||
|
const {
|
||||||
|
webdavUrl = '',
|
||||||
|
webdavUsername = '',
|
||||||
|
webdavPassword = '',
|
||||||
|
webdavDir = 'clash-party'
|
||||||
|
} = await getAppConfig()
|
||||||
|
|
||||||
|
const client = createClient(webdavUrl, {
|
||||||
|
username: webdavUsername,
|
||||||
|
password: webdavPassword
|
||||||
|
})
|
||||||
const files = await client.getDirectoryContents(webdavDir, { glob: '*.zip' })
|
const files = await client.getDirectoryContents(webdavDir, { glob: '*.zip' })
|
||||||
if (Array.isArray(files)) {
|
if (Array.isArray(files)) {
|
||||||
return files.map((file) => file.basename)
|
return files.map((file) => file.basename)
|
||||||
@ -122,7 +131,18 @@ export async function listWebdavBackups(): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function webdavDelete(filename: string): Promise<void> {
|
export async function webdavDelete(filename: string): Promise<void> {
|
||||||
const { client, webdavDir } = await getWebDAVClient()
|
const { createClient } = await import('webdav/dist/node/index.js')
|
||||||
|
const {
|
||||||
|
webdavUrl = '',
|
||||||
|
webdavUsername = '',
|
||||||
|
webdavPassword = '',
|
||||||
|
webdavDir = 'clash-party'
|
||||||
|
} = await getAppConfig()
|
||||||
|
|
||||||
|
const client = createClient(webdavUrl, {
|
||||||
|
username: webdavUsername,
|
||||||
|
password: webdavPassword
|
||||||
|
})
|
||||||
await client.deleteFile(`${webdavDir}/${filename}`)
|
await client.deleteFile(`${webdavDir}/${filename}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@ import {
|
|||||||
} from '../config'
|
} from '../config'
|
||||||
import { app, dialog } from 'electron'
|
import { app, dialog } from 'electron'
|
||||||
import { startSSIDCheck } from '../sys/ssid'
|
import { startSSIDCheck } from '../sys/ssid'
|
||||||
import i18next, { resources } from '../../shared/i18n'
|
import i18next from '../../shared/i18n'
|
||||||
import { initLogger } from './logger'
|
import { initLogger } from './logger'
|
||||||
|
|
||||||
let isInitBasicCompleted = false
|
let isInitBasicCompleted = false
|
||||||
@ -54,9 +54,11 @@ export function safeShowErrorBox(titleKey: string, message: string): void {
|
|||||||
title = i18next.t(titleKey)
|
title = i18next.t(titleKey)
|
||||||
if (!title || title === titleKey) throw new Error('Translation not ready')
|
if (!title || title === titleKey) throw new Error('Translation not ready')
|
||||||
} catch {
|
} catch {
|
||||||
const isZh = app.getLocale().startsWith('zh')
|
const isZh = process.env.LANG?.startsWith('zh') || process.env.LC_ALL?.startsWith('zh')
|
||||||
const lang = isZh ? resources['zh-CN'].translation : resources['en-US'].translation
|
const fallbacks: Record<string, { zh: string; en: string }> = {
|
||||||
title = lang[titleKey] || (isZh ? '错误' : 'Error')
|
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' }
|
||||||
|
}
|
||||||
|
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
|
||||||
}
|
}
|
||||||
dialog.showErrorBox(title, message)
|
dialog.showErrorBox(title, message)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ export const defaultConfig: IAppConfig = {
|
|||||||
appTheme: 'system',
|
appTheme: 'system',
|
||||||
useWindowFrame: false,
|
useWindowFrame: false,
|
||||||
proxyInTray: true,
|
proxyInTray: true,
|
||||||
showCurrentProxyInTray: false,
|
|
||||||
disableTrayIconColor: false,
|
disableTrayIconColor: false,
|
||||||
maxLogDays: 7,
|
maxLogDays: 7,
|
||||||
proxyCols: 'auto',
|
proxyCols: 'auto',
|
||||||
|
|||||||
@ -148,48 +148,3 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toast */
|
|
||||||
.toast-enter {
|
|
||||||
animation: toast-in 0.2s ease-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-exit {
|
|
||||||
animation: toast-out 0.15s ease-in forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes toast-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(20px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes toast-out {
|
|
||||||
from {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(20px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-progress {
|
|
||||||
animation: progress-shrink linear forwards;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes progress-shrink {
|
|
||||||
from {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: 0%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react'
|
|
||||||
import { createPortal } from 'react-dom'
|
|
||||||
import { IoCheckmark, IoClose, IoAlertSharp, IoInformationSharp } from 'react-icons/io5'
|
|
||||||
|
|
||||||
type ToastType = 'success' | 'error' | 'warning' | 'info'
|
|
||||||
|
|
||||||
interface ToastData {
|
|
||||||
id: string
|
|
||||||
type: ToastType
|
|
||||||
title?: string
|
|
||||||
message: string
|
|
||||||
duration?: number
|
|
||||||
exiting?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToastListener = (toasts: ToastData[]) => void
|
|
||||||
|
|
||||||
let toasts: ToastData[] = []
|
|
||||||
let listeners: ToastListener[] = []
|
|
||||||
|
|
||||||
const notifyListeners = (): void => {
|
|
||||||
listeners.forEach((listener) => listener([...toasts]))
|
|
||||||
}
|
|
||||||
|
|
||||||
const addToast = (type: ToastType, message: string, title?: string, duration = 1500): void => {
|
|
||||||
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
||||||
toasts = [...toasts.slice(-4), { id, type, message, title, duration }]
|
|
||||||
notifyListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
const markExiting = (id: string): void => {
|
|
||||||
toasts = toasts.map((t) => (t.id === id ? { ...t, exiting: true } : t))
|
|
||||||
notifyListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeToast = (id: string): void => {
|
|
||||||
toasts = toasts.filter((t) => t.id !== id)
|
|
||||||
notifyListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toast = {
|
|
||||||
success: (message: string, title?: string): void => addToast('success', message, title),
|
|
||||||
error: (message: string, title?: string): void => addToast('error', message, title, 1800),
|
|
||||||
warning: (message: string, title?: string): void => addToast('warning', message, title),
|
|
||||||
info: (message: string, title?: string): void => addToast('info', message, title)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ToastItem: React.FC<{
|
|
||||||
data: ToastData
|
|
||||||
onRemove: (id: string) => void
|
|
||||||
}> = ({ data, onRemove }) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const duration = data.duration || 3500
|
|
||||||
const exitTimer = setTimeout(() => markExiting(data.id), duration - 200)
|
|
||||||
const removeTimer = setTimeout(() => onRemove(data.id), duration)
|
|
||||||
return () => {
|
|
||||||
clearTimeout(exitTimer)
|
|
||||||
clearTimeout(removeTimer)
|
|
||||||
}
|
|
||||||
}, [data.id, data.duration, onRemove])
|
|
||||||
|
|
||||||
const theme: Record<ToastType, { icon: React.ReactNode; bg: string; iconBg: string }> = {
|
|
||||||
success: {
|
|
||||||
icon: <IoCheckmark className="text-white text-sm" />,
|
|
||||||
bg: 'bg-content1',
|
|
||||||
iconBg: 'bg-success'
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
icon: <IoClose className="text-white text-sm" />,
|
|
||||||
bg: 'bg-content1',
|
|
||||||
iconBg: 'bg-danger'
|
|
||||||
},
|
|
||||||
warning: {
|
|
||||||
icon: <IoAlertSharp className="text-white text-sm" />,
|
|
||||||
bg: 'bg-content1',
|
|
||||||
iconBg: 'bg-warning'
|
|
||||||
},
|
|
||||||
info: {
|
|
||||||
icon: <IoInformationSharp className="text-white text-sm" />,
|
|
||||||
bg: 'bg-content1',
|
|
||||||
iconBg: 'bg-primary'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { icon, iconBg } = theme[data.type]
|
|
||||||
const duration = data.duration || 3500
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
relative overflow-hidden
|
|
||||||
flex items-center gap-3 p-3
|
|
||||||
bg-content1/80 rounded-large
|
|
||||||
shadow-large border border-default-200/50
|
|
||||||
backdrop-blur-xl backdrop-saturate-150
|
|
||||||
${data.exiting ? 'toast-exit' : 'toast-enter'}
|
|
||||||
`}
|
|
||||||
style={{ width: 340 }}
|
|
||||||
>
|
|
||||||
<div className={`flex-shrink-0 w-7 h-7 ${iconBg} rounded-full flex items-center justify-center`}>
|
|
||||||
{icon}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
{data.title && (
|
|
||||||
<p className="text-sm font-medium text-foreground">{data.title}</p>
|
|
||||||
)}
|
|
||||||
<p className="text-sm text-foreground-500 break-words select-text">
|
|
||||||
{data.message}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
markExiting(data.id)
|
|
||||||
setTimeout(() => onRemove(data.id), 150)
|
|
||||||
}}
|
|
||||||
className="flex-shrink-0 p-1 rounded-full hover:bg-default-200/60 transition-colors"
|
|
||||||
>
|
|
||||||
<IoClose className="text-base text-foreground-400" />
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
className={`absolute bottom-0 left-0 h-[2px] ${iconBg} toast-progress`}
|
|
||||||
style={{ animationDuration: `${duration}ms` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
||||||
const [currentToasts, setCurrentToasts] = useState<ToastData[]>([])
|
|
||||||
|
|
||||||
const handleRemove = useCallback((id: string) => {
|
|
||||||
removeToast(id)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const listener: ToastListener = (newToasts) => setCurrentToasts(newToasts)
|
|
||||||
listeners.push(listener)
|
|
||||||
return () => {
|
|
||||||
listeners = listeners.filter((l) => l !== listener)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{children}
|
|
||||||
{currentToasts.length > 0 &&
|
|
||||||
createPortal(
|
|
||||||
<div className="fixed top-[60px] right-4 z-[9999] flex flex-col gap-2">
|
|
||||||
{currentToasts.map((t) => (
|
|
||||||
<ToastItem key={t.id} data={t} onRemove={handleRemove} />
|
|
||||||
))}
|
|
||||||
</div>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,19 +1,26 @@
|
|||||||
import { Button, Card, CardFooter, CardHeader, Chip } from '@heroui/react'
|
import { Button, Card, CardFooter, CardHeader, Chip } from '@heroui/react'
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
import dayjs from '@renderer/utils/dayjs'
|
||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { CgClose, CgTrash } from 'react-icons/cg'
|
import { CgClose, CgTrash } from 'react-icons/cg'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: number
|
index: number
|
||||||
info: IMihomoConnectionDetail
|
info: IMihomoConnectionDetail
|
||||||
|
selected: IMihomoConnectionDetail | undefined
|
||||||
setSelected: React.Dispatch<React.SetStateAction<IMihomoConnectionDetail | undefined>>
|
setSelected: React.Dispatch<React.SetStateAction<IMihomoConnectionDetail | undefined>>
|
||||||
setIsDetailModalOpen: React.Dispatch<React.SetStateAction<boolean>>
|
setIsDetailModalOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
close: (id: string) => void
|
close: (id: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectionItem: React.FC<Props> = (props) => {
|
const ConnectionItem: React.FC<Props> = (props) => {
|
||||||
const { index, info, close, setSelected, setIsDetailModalOpen } = props
|
const { index, info, close, selected, setSelected, setIsDetailModalOpen } = props
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selected?.id === info.id) {
|
||||||
|
setSelected(info)
|
||||||
|
}
|
||||||
|
}, [info])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`px-2 pb-2 ${index === 0 ? 'pt-2' : ''}`}>
|
<div className={`px-2 pb-2 ${index === 0 ? 'pt-2' : ''}`}>
|
||||||
@ -30,7 +37,7 @@ const ConnectionItem: React.FC<Props> = (props) => {
|
|||||||
<div className="w-full pr-12">
|
<div className="w-full pr-12">
|
||||||
<CardHeader className="pb-0 gap-1">
|
<CardHeader className="pb-0 gap-1">
|
||||||
<Chip
|
<Chip
|
||||||
color={info.isActive ? 'primary' : 'danger'}
|
color={`${info.isActive ? 'primary' : 'danger'}`}
|
||||||
size="sm"
|
size="sm"
|
||||||
radius="sm"
|
radius="sm"
|
||||||
variant="dot"
|
variant="dot"
|
||||||
@ -77,7 +84,7 @@ const ConnectionItem: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Button
|
<Button
|
||||||
color={info.isActive ? 'warning' : 'danger'}
|
color={`${info.isActive ? 'warning' : 'danger'}`}
|
||||||
variant="light"
|
variant="light"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2"
|
className="absolute right-2 top-1/2 -translate-y-1/2"
|
||||||
|
|||||||
@ -14,7 +14,7 @@ const LogItem: React.FC<IMihomoLogInfo & { index: number }> = (props) => {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-0 pt-1">
|
<CardHeader className="pb-0 pt-1">
|
||||||
<div className={`mr-2 text-lg font-bold text-${colorMap[type]}`}>
|
<div className={`mr-2 text-lg font-bold text-${colorMap[type]}`}>
|
||||||
{type.toUpperCase()}
|
{props.type.toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<small className="text-foreground-500">{time}</small>
|
<small className="text-foreground-500">{time}</small>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@ -24,4 +24,4 @@ const LogItem: React.FC<IMihomoLogInfo & { index: number }> = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(LogItem)
|
export default LogItem
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { BaseEditor } from '../base/base-editor'
|
import { BaseEditor } from '../base/base-editor'
|
||||||
import { getOverride, restartCore, setOverride } from '@renderer/utils/ipc'
|
import { getOverride, restartCore, setOverride } from '@renderer/utils/ipc'
|
||||||
@ -59,7 +58,7 @@ const EditFileModal: React.FC<Props> = (props) => {
|
|||||||
await restartCore()
|
await restartCore()
|
||||||
onClose()
|
onClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownTrigger
|
DropdownTrigger
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
import dayjs from '@renderer/utils/dayjs'
|
||||||
import React, { Key, useMemo, useState } from 'react'
|
import React, { Key, useMemo, useState } from 'react'
|
||||||
@ -198,7 +197,7 @@ const OverrideItem: React.FC<Props> = (props) => {
|
|||||||
await addOverrideItem(info)
|
await addOverrideItem(info)
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating(false)
|
setUpdating(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownItem
|
DropdownItem
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { useOverrideConfig } from '@renderer/hooks/use-override-config'
|
import { useOverrideConfig } from '@renderer/hooks/use-override-config'
|
||||||
@ -50,7 +49,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
|||||||
await restartCore()
|
await restartCore()
|
||||||
onClose()
|
onClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import yaml from 'js-yaml'
|
|||||||
import { IoMdTrash, IoMdArrowUp, IoMdArrowDown, IoMdUndo } from 'react-icons/io'
|
import { IoMdTrash, IoMdArrowUp, IoMdArrowDown, IoMdUndo } from 'react-icons/io'
|
||||||
import { MdVerticalAlignTop, MdVerticalAlignBottom } from 'react-icons/md'
|
import { MdVerticalAlignTop, MdVerticalAlignBottom } from 'react-icons/md'
|
||||||
import { platform } from '@renderer/utils/init'
|
import { platform } from '@renderer/utils/init'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string
|
id: string
|
||||||
@ -643,7 +642,7 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
|||||||
await setRuleStr(id, ruleYaml);
|
await setRuleStr(id, ruleYaml);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t('profiles.editRules.saveError') + ': ' + (e instanceof Error ? e.message : String(e)));
|
alert(t('profiles.editRules.saveError') + ': ' + (e instanceof Error ? e.message : String(e)));
|
||||||
}
|
}
|
||||||
}, [prependRules, deletedRules, rules, appendRules, id, onClose, t])
|
}, [prependRules, deletedRules, rules, appendRules, id, onClose, t])
|
||||||
|
|
||||||
@ -689,7 +688,7 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newRule.type !== 'MATCH' && newRule.payload.trim() !== '' && !validateRulePayload(newRule.type, newRule.payload)) {
|
if (newRule.type !== 'MATCH' && newRule.payload.trim() !== '' && !validateRulePayload(newRule.type, newRule.payload)) {
|
||||||
toast.error(t('profiles.editRules.invalidPayload') + ': ' + getRuleExample(newRule.type));
|
alert(t('profiles.editRules.invalidPayload') + ': ' + getRuleExample(newRule.type));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Button, Input, Switch, Tab, Tabs } from '@heroui/react'
|
import { Button, Input, Switch, Tab, Tabs } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
@ -124,7 +123,7 @@ const GeoData: React.FC = () => {
|
|||||||
await mihomoUpgradeGeo()
|
await mihomoUpgradeGeo()
|
||||||
new Notification(t('resources.geoData.updateSuccess'))
|
new Notification(t('resources.geoData.updateSuccess'))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating(false)
|
setUpdating(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import useSWR from 'swr'
|
|||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Chip } from '@heroui/react'
|
import { Button, Chip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
import { CgLoadbarDoc } from 'react-icons/cg'
|
import { CgLoadbarDoc } from 'react-icons/cg'
|
||||||
import { MdEditDocument } from 'react-icons/md'
|
import { MdEditDocument } from 'react-icons/md'
|
||||||
@ -68,7 +67,7 @@ const ProxyProvider: React.FC = () => {
|
|||||||
await mihomoUpdateProxyProviders(name)
|
await mihomoUpdateProxyProviders(name)
|
||||||
mutate()
|
mutate()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating((prev) => {
|
setUpdating((prev) => {
|
||||||
prev[index] = false
|
prev[index] = false
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import useSWR from 'swr'
|
|||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Chip } from '@heroui/react'
|
import { Button, Chip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
import { CgLoadbarDoc } from 'react-icons/cg'
|
import { CgLoadbarDoc } from 'react-icons/cg'
|
||||||
import { MdEditDocument } from 'react-icons/md'
|
import { MdEditDocument } from 'react-icons/md'
|
||||||
@ -72,7 +71,7 @@ const RuleProvider: React.FC = () => {
|
|||||||
await mihomoUpdateRuleProviders(name)
|
await mihomoUpdateRuleProviders(name)
|
||||||
mutate()
|
mutate()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating((prev) => {
|
setUpdating((prev) => {
|
||||||
prev[index] = false
|
prev[index] = false
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Tooltip } from '@heroui/react'
|
import { Button, Tooltip } from '@heroui/react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import {
|
import {
|
||||||
checkUpdate,
|
checkUpdate,
|
||||||
@ -70,7 +69,7 @@ const Actions: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setCheckingUpdate(false)
|
setCheckingUpdate(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Input, Select, SelectItem, Switch, Tab, Tabs, Tooltip } from '@heroui/react'
|
import { Button, Input, Select, SelectItem, Switch, Tab, Tabs, Tooltip } from '@heroui/react'
|
||||||
import { BiCopy, BiSolidFileImport } from 'react-icons/bi'
|
import { BiCopy, BiSolidFileImport } from 'react-icons/bi'
|
||||||
@ -106,7 +105,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
await patchAppConfig({ disableHardwareAcceleration: pendingHardwareAccelValue })
|
await patchAppConfig({ disableHardwareAcceleration: pendingHardwareAccelValue })
|
||||||
await relaunchApp()
|
await relaunchApp()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
setIsRelaunching(false)
|
setIsRelaunching(false)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -152,7 +151,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
await disableAutoRun()
|
await disableAutoRun()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateEnable()
|
mutateEnable()
|
||||||
}
|
}
|
||||||
@ -249,7 +248,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
envType: Array.from(v) as ('bash' | 'cmd' | 'powershell')[]
|
envType: Array.from(v) as ('bash' | 'cmd' | 'powershell')[]
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -414,7 +413,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
await patchAppConfig({ useWindowFrame: v })
|
await patchAppConfig({ useWindowFrame: v })
|
||||||
await relaunchApp()
|
await relaunchApp()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
setIsRelaunching(false)
|
setIsRelaunching(false)
|
||||||
}
|
}
|
||||||
}, 1000)}
|
}, 1000)}
|
||||||
@ -504,7 +503,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
await fetchThemes()
|
await fetchThemes()
|
||||||
setCustomThemes(await resolveThemes())
|
setCustomThemes(await resolveThemes())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setFetching(false)
|
setFetching(false)
|
||||||
}
|
}
|
||||||
@ -524,7 +523,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
await importThemes(files)
|
await importThemes(files)
|
||||||
setCustomThemes(await resolveThemes())
|
setCustomThemes(await resolveThemes())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -556,7 +555,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
await patchAppConfig({ customTheme: v.currentKey as string })
|
await patchAppConfig({ customTheme: v.currentKey as string })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, useDisclosure } from '@heroui/react'
|
import { Button, useDisclosure } from '@heroui/react'
|
||||||
import { exportLocalBackup, importLocalBackup } from '@renderer/utils/ipc'
|
import { exportLocalBackup, importLocalBackup } from '@renderer/utils/ipc'
|
||||||
@ -23,7 +22,7 @@ const LocalBackupConfig: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setExporting(false)
|
setExporting(false)
|
||||||
}
|
}
|
||||||
@ -39,7 +38,7 @@ const LocalBackupConfig: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t('common.error.importFailed', { error: e }))
|
alert(t('common.error.importFailed', { error: e }))
|
||||||
} finally {
|
} finally {
|
||||||
setImporting(false)
|
setImporting(false)
|
||||||
onClose()
|
onClose()
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
@ -123,7 +122,7 @@ const MihomoConfig: React.FC = () => {
|
|||||||
await navigator.clipboard.writeText(`${url}/raw/clash-party.yaml`)
|
await navigator.clipboard.writeText(`${url}/raw/clash-party.yaml`)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -177,7 +176,7 @@ const MihomoConfig: React.FC = () => {
|
|||||||
})
|
})
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -209,7 +208,7 @@ const MihomoConfig: React.FC = () => {
|
|||||||
await patchAppConfig({ diffWorkDir: v })
|
await patchAppConfig({ diffWorkDir: v })
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Input } from '@heroui/react'
|
import { Button, Input } from '@heroui/react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import React, { KeyboardEvent, useState } from 'react'
|
import React, { KeyboardEvent, useState } from 'react'
|
||||||
@ -214,10 +213,10 @@ const ShortcutInput: React.FC<{
|
|||||||
await patchAppConfig({ [action]: inputValue })
|
await patchAppConfig({ [action]: inputValue })
|
||||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||||
} else {
|
} else {
|
||||||
toast.error(t('common.error.shortcutRegistrationFailed'))
|
alert(t('common.error.shortcutRegistrationFailed'))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t('common.error.shortcutRegistrationFailedWithError', { error: e }))
|
alert(t('common.error.shortcutRegistrationFailedWithError', { error: e }))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import { Button, Input, Switch } from '@heroui/react'
|
import { Button, Input, Switch } from '@heroui/react'
|
||||||
import {
|
import {
|
||||||
@ -56,7 +55,7 @@ const SubStoreConfig: React.FC = () => {
|
|||||||
await stopSubStoreBackendServer()
|
await stopSubStoreBackendServer()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -77,7 +76,7 @@ const SubStoreConfig: React.FC = () => {
|
|||||||
await startSubStoreFrontendServer()
|
await startSubStoreFrontendServer()
|
||||||
await startSubStoreBackendServer()
|
await startSubStoreBackendServer()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -95,7 +94,7 @@ const SubStoreConfig: React.FC = () => {
|
|||||||
await startSubStoreBackendServer()
|
await startSubStoreBackendServer()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -124,7 +123,7 @@ const SubStoreConfig: React.FC = () => {
|
|||||||
await patchAppConfig({ useProxyInSubStore: v })
|
await patchAppConfig({ useProxyInSubStore: v })
|
||||||
await startSubStoreBackendServer()
|
await startSubStoreBackendServer()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -145,7 +144,7 @@ const SubStoreConfig: React.FC = () => {
|
|||||||
})
|
})
|
||||||
new Notification(t('common.notification.restartRequired'))
|
new Notification(t('common.notification.restartRequired'))
|
||||||
} else {
|
} else {
|
||||||
toast.warning(t('common.error.invalidCron'))
|
alert(t('common.error.invalidCron'))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -178,7 +177,7 @@ const SubStoreConfig: React.FC = () => {
|
|||||||
})
|
})
|
||||||
new Notification(t('common.notification.restartRequired'))
|
new Notification(t('common.notification.restartRequired'))
|
||||||
} else {
|
} else {
|
||||||
toast.warning(t('common.error.invalidCron'))
|
alert(t('common.error.invalidCron'))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -211,7 +210,7 @@ const SubStoreConfig: React.FC = () => {
|
|||||||
})
|
})
|
||||||
new Notification(t('common.notification.restartRequired'))
|
new Notification(t('common.notification.restartRequired'))
|
||||||
} else {
|
} else {
|
||||||
toast.warning(t('common.error.invalidCron'))
|
alert(t('common.error.invalidCron'))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Input, Select, SelectItem } from '@heroui/react'
|
import { Button, Input, Select, SelectItem } from '@heroui/react'
|
||||||
import { listWebdavBackups, webdavBackup, reinitWebdavBackupScheduler } from '@renderer/utils/ipc'
|
import { listWebdavBackups, webdavBackup, reinitWebdavBackupScheduler } from '@renderer/utils/ipc'
|
||||||
@ -48,7 +47,7 @@ const WebdavConfig: React.FC = () => {
|
|||||||
body: t('webdav.notification.backupSuccess.body')
|
body: t('webdav.notification.backupSuccess.body')
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setBackuping(false)
|
setBackuping(false)
|
||||||
}
|
}
|
||||||
@ -61,7 +60,7 @@ const WebdavConfig: React.FC = () => {
|
|||||||
setFilenames(filenames)
|
setFilenames(filenames)
|
||||||
setRestoreOpen(true)
|
setRestoreOpen(true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t('common.error.getBackupListFailed', { error: e }))
|
alert(t('common.error.getBackupListFailed', { error: e }))
|
||||||
} finally {
|
} finally {
|
||||||
setRestoring(false)
|
setRestoring(false)
|
||||||
}
|
}
|
||||||
@ -157,7 +156,7 @@ const WebdavConfig: React.FC = () => {
|
|||||||
new Notification(t('webdav.notification.cronUpdateFailed'))
|
new Notification(t('webdav.notification.cronUpdateFailed'))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.warning(t('common.error.invalidCron'))
|
alert(t('common.error.invalidCron'))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { relaunchApp, webdavDelete, webdavRestore } from '@renderer/utils/ipc'
|
import { relaunchApp, webdavDelete, webdavRestore } from '@renderer/utils/ipc'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { MdDeleteForever } from 'react-icons/md'
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
@ -44,7 +43,7 @@ const WebdavRestoreModal: React.FC<Props> = (props) => {
|
|||||||
await webdavRestore(filename)
|
await webdavRestore(filename)
|
||||||
await relaunchApp()
|
await relaunchApp()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t('common.error.restoreFailed', { error: e }))
|
alert(t('common.error.restoreFailed', { error: e }))
|
||||||
} finally {
|
} finally {
|
||||||
setRestoring(false)
|
setRestoring(false)
|
||||||
}
|
}
|
||||||
@ -62,7 +61,7 @@ const WebdavRestoreModal: React.FC<Props> = (props) => {
|
|||||||
await webdavDelete(filename)
|
await webdavDelete(filename)
|
||||||
setFilenames(filenames.filter((name) => name !== filename))
|
setFilenames(filenames.filter((name) => name !== filename))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t('common.error.deleteFailed', { error: e }))
|
alert(t('common.error.deleteFailed', { error: e }))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||||
import { LuServer } from 'react-icons/lu'
|
import { LuServer } from 'react-icons/lu'
|
||||||
@ -40,7 +39,7 @@ const DNSCard: React.FC<Props> = (props) => {
|
|||||||
await patchControledMihomoConfig({})
|
await patchControledMihomoConfig({})
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
import { mihomoVersion, restartCore } from '@renderer/utils/ipc'
|
import { mihomoVersion, restartCore } from '@renderer/utils/ipc'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
@ -113,7 +112,7 @@ const MihomoCoreCard: React.FC<Props> = (props) => {
|
|||||||
try {
|
try {
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutate()
|
mutate()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||||
import { RiScan2Fill } from 'react-icons/ri'
|
import { RiScan2Fill } from 'react-icons/ri'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
@ -40,7 +39,7 @@ const SniffCard: React.FC<Props> = (props) => {
|
|||||||
await patchControledMihomoConfig({})
|
await patchControledMihomoConfig({})
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
@ -55,7 +54,7 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
|
|||||||
await patchAppConfig({ sysProxy: { enable: previousState } })
|
await patchAppConfig({ sysProxy: { enable: previousState } })
|
||||||
// 回滚图标
|
// 回滚图标
|
||||||
updateTrayIconImmediate(previousState, tunEnabled)
|
updateTrayIconImmediate(previousState, tunEnabled)
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
import { Button, Code, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@heroui/react'
|
import {
|
||||||
import { toast } from '@renderer/components/base/toast'
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Button,
|
||||||
|
Code
|
||||||
|
} from '@heroui/react'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { downloadAndInstallUpdate } from '@renderer/utils/ipc'
|
import { downloadAndInstallUpdate } from '@renderer/utils/ipc'
|
||||||
@ -20,7 +27,7 @@ const UpdaterModal: React.FC<Props> = (props) => {
|
|||||||
try {
|
try {
|
||||||
await downloadAndInstallUpdate(version)
|
await downloadAndInstallUpdate(version)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +82,7 @@ const UpdaterModal: React.FC<Props> = (props) => {
|
|||||||
await onUpdate()
|
await onUpdate()
|
||||||
onClose()
|
onClose()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setDownloading(false)
|
setDownloading(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { createContext, useContext, ReactNode } from 'react'
|
import React, { createContext, useContext, ReactNode } from 'react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { getAppConfig, patchAppConfig as patch } from '@renderer/utils/ipc'
|
import { getAppConfig, patchAppConfig as patch } from '@renderer/utils/ipc'
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ export const AppConfigProvider: React.FC<{ children: ReactNode }> = ({ children
|
|||||||
try {
|
try {
|
||||||
await patch(value)
|
await patch(value)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateAppConfig()
|
mutateAppConfig()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { createContext, useContext, ReactNode } from 'react'
|
import React, { createContext, useContext, ReactNode } from 'react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { getControledMihomoConfig, patchControledMihomoConfig as patch } from '@renderer/utils/ipc'
|
import { getControledMihomoConfig, patchControledMihomoConfig as patch } from '@renderer/utils/ipc'
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ export const ControledMihomoConfigProvider: React.FC<{ children: ReactNode }> =
|
|||||||
try {
|
try {
|
||||||
await patch(value)
|
await patch(value)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateControledMihomoConfig()
|
mutateControledMihomoConfig()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { createContext, useContext, ReactNode } from 'react'
|
import React, { createContext, useContext, ReactNode } from 'react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import {
|
import {
|
||||||
getOverrideConfig,
|
getOverrideConfig,
|
||||||
@ -29,7 +28,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
|
|||||||
try {
|
try {
|
||||||
await set(config)
|
await set(config)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateOverrideConfig()
|
mutateOverrideConfig()
|
||||||
}
|
}
|
||||||
@ -39,7 +38,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
|
|||||||
try {
|
try {
|
||||||
await add(item)
|
await add(item)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateOverrideConfig()
|
mutateOverrideConfig()
|
||||||
}
|
}
|
||||||
@ -49,7 +48,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
|
|||||||
try {
|
try {
|
||||||
await remove(id)
|
await remove(id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateOverrideConfig()
|
mutateOverrideConfig()
|
||||||
}
|
}
|
||||||
@ -59,7 +58,7 @@ export const OverrideConfigProvider: React.FC<{ children: ReactNode }> = ({ chil
|
|||||||
try {
|
try {
|
||||||
await update(item)
|
await update(item)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateOverrideConfig()
|
mutateOverrideConfig()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React, { createContext, ReactNode, useContext } from 'react'
|
import React, { createContext, ReactNode, useContext } from 'react'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import {
|
import {
|
||||||
addProfileItem as add,
|
addProfileItem as add,
|
||||||
@ -33,7 +32,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
|
|||||||
try {
|
try {
|
||||||
await set(config)
|
await set(config)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||||
@ -44,7 +43,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
|
|||||||
try {
|
try {
|
||||||
await add(item)
|
await add(item)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||||
@ -55,7 +54,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
|
|||||||
try {
|
try {
|
||||||
await remove(id)
|
await remove(id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||||
@ -66,7 +65,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
|
|||||||
try {
|
try {
|
||||||
await update(item)
|
await update(item)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||||
@ -108,7 +107,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
|
|||||||
if (errorMsg.includes('reply was never sent')) {
|
if (errorMsg.includes('reply was never sent')) {
|
||||||
setTimeout(() => mutateProfileConfig(), 1000)
|
setTimeout(() => mutateProfileConfig(), 1000)
|
||||||
} else {
|
} else {
|
||||||
toast.error(errorMsg, '切换配置失败')
|
alert(`切换 Profile 失败: ${errorMsg}`)
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { OverrideConfigProvider } from './hooks/use-override-config'
|
|||||||
import { ProfileConfigProvider } from './hooks/use-profile-config'
|
import { ProfileConfigProvider } from './hooks/use-profile-config'
|
||||||
import { RulesProvider } from './hooks/use-rules'
|
import { RulesProvider } from './hooks/use-rules'
|
||||||
import { GroupsProvider } from './hooks/use-groups'
|
import { GroupsProvider } from './hooks/use-groups'
|
||||||
import { ToastProvider } from './components/base/toast'
|
|
||||||
import './i18n'
|
import './i18n'
|
||||||
|
|
||||||
let F12Count = 0
|
let F12Count = 0
|
||||||
@ -54,9 +53,7 @@ init().then(() => {
|
|||||||
<OverrideConfigProvider>
|
<OverrideConfigProvider>
|
||||||
<GroupsProvider>
|
<GroupsProvider>
|
||||||
<RulesProvider>
|
<RulesProvider>
|
||||||
<ToastProvider>
|
|
||||||
<App />
|
<App />
|
||||||
</ToastProvider>
|
|
||||||
</RulesProvider>
|
</RulesProvider>
|
||||||
</GroupsProvider>
|
</GroupsProvider>
|
||||||
</OverrideConfigProvider>
|
</OverrideConfigProvider>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc'
|
import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc'
|
||||||
import { Key, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { Key, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs } from '@heroui/react'
|
import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs } from '@heroui/react'
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
import ConnectionItem from '@renderer/components/connections/connection-item'
|
import ConnectionItem from '@renderer/components/connections/connection-item'
|
||||||
@ -48,22 +48,6 @@ const Connections: React.FC = () => {
|
|||||||
const [viewMode, setViewMode] = useState<'list' | 'table'>(connectionViewMode)
|
const [viewMode, setViewMode] = useState<'list' | 'table'>(connectionViewMode)
|
||||||
const [visibleColumns, setVisibleColumns] = useState<Set<string>>(new Set(connectionTableColumns))
|
const [visibleColumns, setVisibleColumns] = useState<Set<string>>(new Set(connectionTableColumns))
|
||||||
|
|
||||||
const activeConnectionsRef = useRef(activeConnections)
|
|
||||||
const allConnectionsRef = useRef(allConnections)
|
|
||||||
useEffect(() => {
|
|
||||||
activeConnectionsRef.current = activeConnections
|
|
||||||
allConnectionsRef.current = allConnections
|
|
||||||
}, [activeConnections, allConnections])
|
|
||||||
|
|
||||||
const selectedConnection = useMemo(() => {
|
|
||||||
if (!selected) return undefined
|
|
||||||
return (
|
|
||||||
activeConnections.find((c) => c.id === selected.id) ||
|
|
||||||
closedConnections.find((c) => c.id === selected.id) ||
|
|
||||||
selected
|
|
||||||
)
|
|
||||||
}, [selected, activeConnections, closedConnections])
|
|
||||||
|
|
||||||
const handleColumnWidthChange = useCallback(async (widths: Record<string, number>) => {
|
const handleColumnWidthChange = useCallback(async (widths: Record<string, number>) => {
|
||||||
await patchAppConfig({ connectionTableColumnWidths: widths })
|
await patchAppConfig({ connectionTableColumnWidths: widths })
|
||||||
}, [patchAppConfig])
|
}, [patchAppConfig])
|
||||||
@ -114,80 +98,71 @@ const Connections: React.FC = () => {
|
|||||||
|
|
||||||
const closeAllConnections = useCallback((): void => {
|
const closeAllConnections = useCallback((): void => {
|
||||||
tab === 'active' ? mihomoCloseAllConnections() : trashAllClosedConnection()
|
tab === 'active' ? mihomoCloseAllConnections() : trashAllClosedConnection()
|
||||||
}, [tab])
|
}, [tab, closedConnections])
|
||||||
|
|
||||||
const closeConnection = useCallback((id: string): void => {
|
const closeConnection = useCallback((id: string): void => {
|
||||||
tab === 'active' ? mihomoCloseConnection(id) : trashClosedConnection(id)
|
tab === 'active' ? mihomoCloseConnection(id) : trashClosedConnection(id)
|
||||||
}, [tab])
|
}, [tab])
|
||||||
|
|
||||||
const trashAllClosedConnection = (): void => {
|
const trashAllClosedConnection = (): void => {
|
||||||
setClosedConnections((closedConns) => {
|
const trashIds = closedConnections.map((conn) => conn.id)
|
||||||
const trashIds = new Set(closedConns.map((conn) => conn.id))
|
setAllConnections((allConns) => allConns.filter((conn) => !trashIds.includes(conn.id)))
|
||||||
setAllConnections((allConns) => {
|
setClosedConnections([])
|
||||||
const filtered = allConns.filter((conn) => !trashIds.has(conn.id))
|
|
||||||
cachedConnections = filtered
|
cachedConnections = allConnections
|
||||||
return filtered
|
|
||||||
})
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const trashClosedConnection = (id: string): void => {
|
const trashClosedConnection = (id: string): void => {
|
||||||
setAllConnections((allConns) => {
|
setAllConnections((allConns) => allConns.filter((conn) => conn.id != id))
|
||||||
const filtered = allConns.filter((conn) => conn.id !== id)
|
setClosedConnections((closedConns) => closedConns.filter((conn) => conn.id != id))
|
||||||
cachedConnections = filtered
|
|
||||||
return filtered
|
cachedConnections = allConnections
|
||||||
})
|
|
||||||
setClosedConnections((closedConns) => closedConns.filter((conn) => conn.id !== id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (_e: unknown, info: IMihomoConnectionsInfo): void => {
|
if (isPaused) return
|
||||||
|
window.electron.ipcRenderer.on('mihomoConnections', (_e, info: IMihomoConnectionsInfo) => {
|
||||||
setConnectionsInfo(info)
|
setConnectionsInfo(info)
|
||||||
|
|
||||||
if (!info.connections) return
|
if (!info.connections) return
|
||||||
const allConns = unionWith(
|
const allConns = unionWith(activeConnections, allConnections, (a, b) => a.id === b.id)
|
||||||
activeConnectionsRef.current,
|
|
||||||
allConnectionsRef.current,
|
|
||||||
(a, b) => a.id === b.id
|
|
||||||
)
|
|
||||||
|
|
||||||
const prevConnMap = new Map(activeConnectionsRef.current.map((c) => [c.id, c]))
|
|
||||||
const activeConns = info.connections.map((conn) => {
|
const activeConns = info.connections.map((conn) => {
|
||||||
const preConn = prevConnMap.get(conn.id)
|
const preConn = activeConnections.find((c) => c.id === conn.id)
|
||||||
|
const downloadSpeed = preConn ? conn.download - preConn.download : 0
|
||||||
|
const uploadSpeed = preConn ? conn.upload - preConn.upload : 0
|
||||||
return {
|
return {
|
||||||
...conn,
|
...conn,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
downloadSpeed: preConn ? conn.download - preConn.download : 0,
|
downloadSpeed: downloadSpeed,
|
||||||
uploadSpeed: preConn ? conn.upload - preConn.upload : 0
|
uploadSpeed: uploadSpeed
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const closedConns = differenceWith(allConns, activeConns, (a, b) => a.id === b.id).map(
|
const closedConns = differenceWith(allConns, activeConns, (a, b) => a.id === b.id).map(
|
||||||
(conn) => ({
|
(conn) => {
|
||||||
|
return {
|
||||||
...conn,
|
...conn,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
downloadSpeed: 0,
|
downloadSpeed: 0,
|
||||||
uploadSpeed: 0
|
uploadSpeed: 0
|
||||||
})
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setActiveConnections(activeConns)
|
setActiveConnections(activeConns)
|
||||||
setClosedConnections(closedConns)
|
setClosedConnections(closedConns)
|
||||||
setAllConnections(allConns.slice(-(activeConns.length + 200)))
|
setAllConnections(allConns.slice(-(activeConns.length + 200)))
|
||||||
cachedConnections = allConns
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPaused) {
|
cachedConnections = allConnections
|
||||||
window.electron.ipcRenderer.on('mihomoConnections', handler)
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return (): void => {
|
return (): void => {
|
||||||
window.electron.ipcRenderer.removeAllListeners('mihomoConnections')
|
window.electron.ipcRenderer.removeAllListeners('mihomoConnections')
|
||||||
}
|
}
|
||||||
}, [isPaused])
|
}, [allConnections, activeConnections, closedConnections, isPaused])
|
||||||
const togglePause = useCallback(() => {
|
const togglePause = () => {
|
||||||
setIsPaused((prev) => !prev)
|
setIsPaused(!isPaused)
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage
|
<BasePage
|
||||||
@ -207,7 +182,7 @@ const Connections: React.FC = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
showOutline={false}
|
showOutline={false}
|
||||||
content={filteredConnections.length}
|
content={`${filteredConnections.length}`}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="app-nodrag ml-1"
|
className="app-nodrag ml-1"
|
||||||
@ -255,14 +230,14 @@ const Connections: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isDetailModalOpen && selectedConnection && (
|
{isDetailModalOpen && selected && (
|
||||||
<ConnectionDetailModal onClose={() => setIsDetailModalOpen(false)} connection={selectedConnection} />
|
<ConnectionDetailModal onClose={() => setIsDetailModalOpen(false)} connection={selected} />
|
||||||
)}
|
)}
|
||||||
<div className="overflow-x-auto sticky top-0 z-40">
|
<div className="overflow-x-auto sticky top-0 z-40">
|
||||||
<div className="flex p-2 gap-2">
|
<div className="flex p-2 gap-2">
|
||||||
<Tabs
|
<Tabs
|
||||||
size="sm"
|
size="sm"
|
||||||
color={tab === 'active' ? 'primary' : 'danger'}
|
color={`${tab === 'active' ? 'primary' : 'danger'}`}
|
||||||
selectedKey={tab}
|
selectedKey={tab}
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
className="w-fit h-[32px]"
|
className="w-fit h-[32px]"
|
||||||
@ -274,7 +249,7 @@ const Connections: React.FC = () => {
|
|||||||
key="active"
|
key="active"
|
||||||
title={
|
title={
|
||||||
<Badge
|
<Badge
|
||||||
color={tab === 'active' ? 'primary' : 'default'}
|
color={`${tab === 'active' ? 'primary' : 'default'}`}
|
||||||
size="sm"
|
size="sm"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
@ -289,7 +264,7 @@ const Connections: React.FC = () => {
|
|||||||
key="closed"
|
key="closed"
|
||||||
title={
|
title={
|
||||||
<Badge
|
<Badge
|
||||||
color={tab === 'closed' ? 'danger' : 'default'}
|
color={`${tab === 'closed' ? 'danger' : 'default'}`}
|
||||||
size="sm"
|
size="sm"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
@ -364,7 +339,7 @@ const Connections: React.FC = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="w-[180px] min-w-[131px]"
|
className="w-[180px] min-w-[131px]"
|
||||||
aria-label={t('connections.orderBy')}
|
aria-label={t('connections.orderBy')}
|
||||||
selectedKeys={[connectionOrderBy]}
|
selectedKeys={new Set([connectionOrderBy])}
|
||||||
disallowEmptySelection={true}
|
disallowEmptySelection={true}
|
||||||
onSelectionChange={async (v) => {
|
onSelectionChange={async (v) => {
|
||||||
await patchAppConfig({
|
await patchAppConfig({
|
||||||
@ -387,7 +362,7 @@ const Connections: React.FC = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
className="bg-content2"
|
className="bg-content2"
|
||||||
onPress={() => {
|
onPress={async () => {
|
||||||
patchAppConfig({
|
patchAppConfig({
|
||||||
connectionDirection: connectionDirection === 'asc' ? 'desc' : 'asc'
|
connectionDirection: connectionDirection === 'asc' ? 'desc' : 'asc'
|
||||||
})
|
})
|
||||||
@ -412,6 +387,7 @@ const Connections: React.FC = () => {
|
|||||||
<ConnectionItem
|
<ConnectionItem
|
||||||
setSelected={setSelected}
|
setSelected={setSelected}
|
||||||
setIsDetailModalOpen={setIsDetailModalOpen}
|
setIsDetailModalOpen={setIsDetailModalOpen}
|
||||||
|
selected={selected}
|
||||||
close={closeConnection}
|
close={closeConnection}
|
||||||
index={i}
|
index={i}
|
||||||
key={connection.id}
|
key={connection.id}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Tab, Input, Switch, Tabs, Divider } from '@heroui/react'
|
import { Button, Tab, Input, Switch, Tabs, Divider } from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { MdDeleteForever } from 'react-icons/md'
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
@ -146,7 +145,7 @@ const DNS: React.FC = () => {
|
|||||||
await restartCore()
|
await restartCore()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -121,14 +121,17 @@ const Logs: React.FC = () => {
|
|||||||
<Virtuoso
|
<Virtuoso
|
||||||
ref={virtuosoRef}
|
ref={virtuosoRef}
|
||||||
data={filteredLogs}
|
data={filteredLogs}
|
||||||
itemContent={(i, log) => (
|
itemContent={(i, log) => {
|
||||||
|
return (
|
||||||
<LogItem
|
<LogItem
|
||||||
index={i}
|
index={i}
|
||||||
|
key={log.payload + i}
|
||||||
time={log.time}
|
time={log.time}
|
||||||
type={log.type}
|
type={log.type}
|
||||||
payload={log.payload}
|
payload={log.payload}
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure, Spinner, Chip } from '@heroui/react'
|
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure, Spinner, Chip } from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
@ -45,6 +44,10 @@ const Mihomo: React.FC = () => {
|
|||||||
smartCollectorSize = 100,
|
smartCollectorSize = 100,
|
||||||
maxLogDays = 7,
|
maxLogDays = 7,
|
||||||
sysProxy,
|
sysProxy,
|
||||||
|
disableLoopbackDetector,
|
||||||
|
disableEmbedCA,
|
||||||
|
disableSystemCA,
|
||||||
|
skipSafePathCheck,
|
||||||
showMixedPort,
|
showMixedPort,
|
||||||
enableMixedPort = true,
|
enableMixedPort = true,
|
||||||
showSocksPort,
|
showSocksPort,
|
||||||
@ -309,7 +312,7 @@ const Mihomo: React.FC = () => {
|
|||||||
if (errorMessage.includes('配置检查失败') || errorMessage.includes('Profile Check Failed')) {
|
if (errorMessage.includes('配置检查失败') || errorMessage.includes('Profile Check Failed')) {
|
||||||
await showDetailedError(t('mihomo.error.profileCheckFailed'), errorMessage)
|
await showDetailedError(t('mihomo.error.profileCheckFailed'), errorMessage)
|
||||||
} else {
|
} else {
|
||||||
toast.error(errorMessage)
|
alert(errorMessage)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
PubSub.publish('mihomo-core-changed')
|
PubSub.publish('mihomo-core-changed')
|
||||||
@ -325,7 +328,7 @@ const Mihomo: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch tags:', error)
|
console.error('Failed to fetch tags:', error)
|
||||||
setTags([])
|
setTags([])
|
||||||
toast.error(t('mihomo.error.fetchTagsFailed'))
|
alert(t('mihomo.error.fetchTagsFailed'))
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingTags(false)
|
setLoadingTags(false)
|
||||||
}
|
}
|
||||||
@ -356,7 +359,7 @@ const Mihomo: React.FC = () => {
|
|||||||
new Notification(t('mihomo.coreUpgradeSuccess'))
|
new Notification(t('mihomo.coreUpgradeSuccess'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to install specific core:', error)
|
console.error('Failed to install specific core:', error)
|
||||||
toast.error(t('mihomo.error.installCoreFailed'))
|
alert(t('mihomo.error.installCoreFailed'))
|
||||||
} finally {
|
} finally {
|
||||||
setInstalling(false)
|
setInstalling(false)
|
||||||
}
|
}
|
||||||
@ -494,7 +497,7 @@ const Mihomo: React.FC = () => {
|
|||||||
if (typeof e === 'string' && e.includes('already using latest version')) {
|
if (typeof e === 'string' && e.includes('already using latest version')) {
|
||||||
new Notification(t('mihomo.alreadyLatestVersion'))
|
new Notification(t('mihomo.alreadyLatestVersion'))
|
||||||
} else {
|
} else {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setUpgrading(false)
|
setUpgrading(false)
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import {
|
|||||||
Input
|
Input
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import { getFilePath, readTextFile } from '@renderer/utils/ipc'
|
import { getFilePath, readTextFile } from '@renderer/utils/ipc'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { MdContentPaste } from 'react-icons/md'
|
import { MdContentPaste } from 'react-icons/md'
|
||||||
@ -76,29 +75,18 @@ const Override: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addOverrideItemRef = useRef(addOverrideItem)
|
|
||||||
addOverrideItemRef.current = addOverrideItem
|
|
||||||
|
|
||||||
const tRef = useRef(t)
|
|
||||||
tRef.current = t
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = pageRef.current
|
pageRef.current?.addEventListener('dragover', (e) => {
|
||||||
if (!element) return
|
|
||||||
|
|
||||||
const handleDragOver = (e: DragEvent): void => {
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setFileOver(true)
|
setFileOver(true)
|
||||||
}
|
})
|
||||||
|
pageRef.current?.addEventListener('dragleave', (e) => {
|
||||||
const handleDragLeave = (e: DragEvent): void => {
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setFileOver(false)
|
setFileOver(false)
|
||||||
}
|
})
|
||||||
|
pageRef.current?.addEventListener('drop', async (event) => {
|
||||||
const handleDrop = async (event: DragEvent): Promise<void> => {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
if (event.dataTransfer?.files) {
|
if (event.dataTransfer?.files) {
|
||||||
@ -106,7 +94,7 @@ const Override: React.FC = () => {
|
|||||||
if (file.name.endsWith('.js') || file.name.endsWith('.yaml')) {
|
if (file.name.endsWith('.js') || file.name.endsWith('.yaml')) {
|
||||||
const content = await readTextFile((file as File & { path: string }).path)
|
const content = await readTextFile((file as File & { path: string }).path)
|
||||||
try {
|
try {
|
||||||
await addOverrideItemRef.current({
|
await addOverrideItem({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
type: 'local',
|
type: 'local',
|
||||||
file: content,
|
file: content,
|
||||||
@ -116,20 +104,15 @@ const Override: React.FC = () => {
|
|||||||
setFileOver(false)
|
setFileOver(false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.warning(tRef.current('override.unsupportedFileType'))
|
alert(t('override.unsupportedFileType'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setFileOver(false)
|
setFileOver(false)
|
||||||
}
|
})
|
||||||
|
|
||||||
element.addEventListener('dragover', handleDragOver)
|
|
||||||
element.addEventListener('dragleave', handleDragLeave)
|
|
||||||
element.addEventListener('drop', handleDrop)
|
|
||||||
|
|
||||||
return (): void => {
|
return (): void => {
|
||||||
element.removeEventListener('dragover', handleDragOver)
|
pageRef.current?.removeEventListener('dragover', () => {})
|
||||||
element.removeEventListener('dragleave', handleDragLeave)
|
pageRef.current?.removeEventListener('dragleave', () => {})
|
||||||
element.removeEventListener('drop', handleDrop)
|
pageRef.current?.removeEventListener('drop', () => {})
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -223,7 +206,7 @@ const Override: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
} else if (key === 'new-yaml') {
|
} else if (key === 'new-yaml') {
|
||||||
await addOverrideItem({
|
await addOverrideItem({
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import {
|
|||||||
Tooltip
|
Tooltip
|
||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import ProfileItem from '@renderer/components/profiles/profile-item'
|
import ProfileItem from '@renderer/components/profiles/profile-item'
|
||||||
import { useProfileConfig } from '@renderer/hooks/use-profile-config'
|
import { useProfileConfig } from '@renderer/hooks/use-profile-config'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
@ -157,37 +156,26 @@ const Profiles: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleImportRef = useRef(handleImport)
|
const handleInputKeyUp = useCallback(
|
||||||
handleImportRef.current = handleImport
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key !== 'Enter' || isUrlEmpty) return
|
||||||
const addProfileItemRef = useRef(addProfileItem)
|
handleImport()
|
||||||
addProfileItemRef.current = addProfileItem
|
},
|
||||||
|
[isUrlEmpty]
|
||||||
const tRef = useRef(t)
|
)
|
||||||
tRef.current = t
|
|
||||||
|
|
||||||
const handleInputKeyUp = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (e.key !== 'Enter' || e.currentTarget.value.trim() === '') return
|
|
||||||
handleImportRef.current()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = pageRef.current
|
pageRef.current?.addEventListener('dragover', (e) => {
|
||||||
if (!element) return
|
|
||||||
|
|
||||||
const handleDragOver = (e: DragEvent): void => {
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setFileOver(true)
|
setFileOver(true)
|
||||||
}
|
})
|
||||||
|
pageRef.current?.addEventListener('dragleave', (e) => {
|
||||||
const handleDragLeave = (e: DragEvent): void => {
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setFileOver(false)
|
setFileOver(false)
|
||||||
}
|
})
|
||||||
|
pageRef.current?.addEventListener('drop', async (event) => {
|
||||||
const handleDrop = async (event: DragEvent): Promise<void> => {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
if (event.dataTransfer?.files) {
|
if (event.dataTransfer?.files) {
|
||||||
@ -196,25 +184,20 @@ const Profiles: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const path = window.api.webUtils.getPathForFile(file)
|
const path = window.api.webUtils.getPathForFile(file)
|
||||||
const content = await readTextFile(path)
|
const content = await readTextFile(path)
|
||||||
await addProfileItemRef.current({ name: file.name, type: 'local', file: content })
|
await addProfileItem({ name: file.name, type: 'local', file: content })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.warning(tRef.current('profiles.error.unsupportedFileType'))
|
alert(t('profiles.error.unsupportedFileType'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setFileOver(false)
|
setFileOver(false)
|
||||||
}
|
})
|
||||||
|
|
||||||
element.addEventListener('dragover', handleDragOver)
|
|
||||||
element.addEventListener('dragleave', handleDragLeave)
|
|
||||||
element.addEventListener('drop', handleDrop)
|
|
||||||
|
|
||||||
return (): void => {
|
return (): void => {
|
||||||
element.removeEventListener('dragover', handleDragOver)
|
pageRef.current?.removeEventListener('dragover', () => {})
|
||||||
element.removeEventListener('dragleave', handleDragLeave)
|
pageRef.current?.removeEventListener('dragleave', () => {})
|
||||||
element.removeEventListener('drop', handleDrop)
|
pageRef.current?.removeEventListener('drop', () => {})
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -346,7 +329,7 @@ const Profiles: React.FC = () => {
|
|||||||
useProxy
|
useProxy
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setSubStoreImporting(false)
|
setSubStoreImporting(false)
|
||||||
}
|
}
|
||||||
@ -367,7 +350,7 @@ const Profiles: React.FC = () => {
|
|||||||
useProxy
|
useProxy
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setSubStoreImporting(false)
|
setSubStoreImporting(false)
|
||||||
}
|
}
|
||||||
@ -399,7 +382,7 @@ const Profiles: React.FC = () => {
|
|||||||
await addProfileItem({ name: fileName, type: 'local', file: content })
|
await addProfileItem({ name: fileName, type: 'local', file: content })
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
} else if (key === 'new') {
|
} else if (key === 'new') {
|
||||||
await addProfileItem({
|
await addProfileItem({
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Divider, Input, Switch } from '@heroui/react'
|
import { Button, Divider, Input, Switch } from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
@ -71,7 +70,7 @@ const Sniffer: React.FC = () => {
|
|||||||
await restartCore()
|
await restartCore()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Input, Tab, Tabs } from '@heroui/react'
|
import { Button, Input, Tab, Tabs } from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import PacEditorModal from '@renderer/components/sysproxy/pac-editor-modal'
|
import PacEditorModal from '@renderer/components/sysproxy/pac-editor-modal'
|
||||||
@ -106,7 +105,7 @@ const Sysproxy: React.FC = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setValues({ ...values, enable: previousState })
|
setValues({ ...values, enable: previousState })
|
||||||
setChanged(true)
|
setChanged(true)
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
|
|
||||||
await patchAppConfig({ sysProxy: { enable: false } })
|
await patchAppConfig({ sysProxy: { enable: false } })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Input, Switch, Tab, Tabs } from '@heroui/react'
|
import { Button, Input, Switch, Tab, Tabs } from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { toast } from '@renderer/components/base/toast'
|
|
||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
@ -113,7 +112,7 @@ const Tun: React.FC = () => {
|
|||||||
new Notification(t('tun.notifications.firewallResetSuccess'))
|
new Notification(t('tun.notifications.firewallResetSuccess'))
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@ -134,7 +133,7 @@ const Tun: React.FC = () => {
|
|||||||
new Notification(t('tun.notifications.coreAuthSuccess'))
|
new Notification(t('tun.notifications.coreAuthSuccess'))
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(String(e))
|
alert(e)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -556,3 +556,10 @@ export async function getRuleStr(id: string): Promise<string> {
|
|||||||
export async function setRuleStr(id: string, str: string): Promise<void> {
|
export async function setRuleStr(id: string, str: string): Promise<void> {
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setRuleStr', id, str))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setRuleStr', id, str))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function alert<T>(msg: T): Promise<void> {
|
||||||
|
const msgStr = typeof msg === 'string' ? msg : JSON.stringify(msg)
|
||||||
|
return await window.electron.ipcRenderer.invoke('alert', msgStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.alert = alert
|
||||||
Loading…
x
Reference in New Issue
Block a user