mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-10 19:50:28 +08:00
fix: resolve all eslint errors and warnings
This commit is contained in:
parent
e70ca694b9
commit
a5d2114363
@ -69,5 +69,12 @@ module.exports = [
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
files: ['**/logger.ts'],
|
||||
rules: {
|
||||
'no-console': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -41,7 +41,7 @@ export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
||||
export async function updateProfileConfig(
|
||||
updater: (config: IProfileConfig) => IProfileConfig | Promise<IProfileConfig>
|
||||
): Promise<IProfileConfig> {
|
||||
let result: IProfileConfig
|
||||
let result: IProfileConfig | undefined
|
||||
profileConfigWriteQueue = profileConfigWriteQueue.then(async () => {
|
||||
const data = await readFile(profileConfigPath(), 'utf-8')
|
||||
profileConfig = parse(data) || { items: [] }
|
||||
@ -51,7 +51,7 @@ export async function updateProfileConfig(
|
||||
await writeFile(profileConfigPath(), stringify(profileConfig), 'utf-8')
|
||||
})
|
||||
await profileConfigWriteQueue
|
||||
return structuredClone(result!)
|
||||
return structuredClone(result ?? profileConfig)
|
||||
}
|
||||
|
||||
export async function getProfileItem(id: string | undefined): Promise<IProfileItem | undefined> {
|
||||
|
||||
@ -41,8 +41,6 @@ import {
|
||||
getAxios
|
||||
} from './mihomoApi'
|
||||
import { generateProfile } from './factory'
|
||||
|
||||
// 拆分模块
|
||||
import { getSessionAdminStatus } from './permissions'
|
||||
import {
|
||||
cleanupSocketFile,
|
||||
|
||||
@ -11,7 +11,7 @@ import { getMihomoIpcPath } from './manager'
|
||||
|
||||
const mihomoApiLogger = createLogger('MihomoApi')
|
||||
|
||||
let axiosIns: AxiosInstance = null!
|
||||
let axiosIns: AxiosInstance | null = null
|
||||
let currentIpcPath: string = ''
|
||||
let mihomoTrafficWs: WebSocket | null = null
|
||||
let trafficRetry = 10
|
||||
@ -27,7 +27,6 @@ const MAX_RETRY = 10
|
||||
export const getAxios = async (force: boolean = false): Promise<AxiosInstance> => {
|
||||
const dynamicIpcPath = getMihomoIpcPath()
|
||||
|
||||
// 如路径改变 强制重新创建实例
|
||||
if (axiosIns && !force && currentIpcPath === dynamicIpcPath) {
|
||||
return axiosIns
|
||||
}
|
||||
|
||||
@ -20,8 +20,6 @@ import { startMonitor } from './resolve/trafficMonitor'
|
||||
import { showFloatingWindow } from './resolve/floatingWindow'
|
||||
import { logger, createLogger } from './utils/logger'
|
||||
import { initWebdavBackupScheduler } from './resolve/backup'
|
||||
|
||||
const mainLogger = createLogger('Main')
|
||||
import {
|
||||
createWindow,
|
||||
mainWindow,
|
||||
@ -37,6 +35,8 @@ import {
|
||||
getSystemLanguage
|
||||
} from './lifecycle'
|
||||
|
||||
const mainLogger = createLogger('Main')
|
||||
|
||||
export { mainWindow, showMainWindow, triggerMainWindow, closeMainWindow }
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
|
||||
@ -49,7 +49,9 @@ async function createFloatingWindow(): Promise<void> {
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
windowOptions.hasShadow = !safeMode
|
||||
windowOptions.webPreferences!.offscreen = false
|
||||
if (windowOptions.webPreferences) {
|
||||
windowOptions.webPreferences.offscreen = false
|
||||
}
|
||||
}
|
||||
|
||||
floatingWindow = new BrowserWindow(windowOptions)
|
||||
@ -68,7 +70,9 @@ async function createFloatingWindow(): Promise<void> {
|
||||
})
|
||||
|
||||
floatingWindow.on('moved', () => {
|
||||
floatingWindow && floatingWindowState.saveState(floatingWindow)
|
||||
if (floatingWindow) {
|
||||
floatingWindowState.saveState(floatingWindow)
|
||||
}
|
||||
})
|
||||
|
||||
// IPC 监听器
|
||||
|
||||
@ -59,9 +59,8 @@ export async function getGitHubTags(
|
||||
|
||||
// 检查缓存
|
||||
if (!forceRefresh && versionCache.has(cacheKey)) {
|
||||
const cache = versionCache.get(cacheKey)!
|
||||
// 检查缓存是否过期
|
||||
if (Date.now() - cache.timestamp < CACHE_EXPIRY) {
|
||||
const cache = versionCache.get(cacheKey)
|
||||
if (cache && Date.now() - cache.timestamp < CACHE_EXPIRY) {
|
||||
log.debug(`Returning cached tags for ${owner}/${repo}`)
|
||||
return cache.data
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ const electronAPI = {
|
||||
if (!listenerMap.has(channel)) {
|
||||
listenerMap.set(channel, new Set())
|
||||
}
|
||||
listenerMap.get(channel)!.add(listener)
|
||||
listenerMap.get(channel)?.add(listener)
|
||||
ipcRenderer.on(channel, listener)
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { NavigateFunction, useLocation, useNavigate, useRoutes } from 'react-router-dom'
|
||||
import OutboundModeSwitcher from '@renderer/components/sider/outbound-mode-switcher'
|
||||
import SysproxySwitcher from '@renderer/components/sider/sysproxy-switcher'
|
||||
@ -32,10 +32,10 @@ import { applyTheme, setNativeTheme, setTitleBarOverlay } from '@renderer/utils/
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { TitleBarOverlayOptions } from 'electron'
|
||||
import SubStoreCard from '@renderer/components/sider/substore-card'
|
||||
import MihomoIcon from './components/base/mihomo-icon'
|
||||
import { createTourDriver, getDriver, startTourIfNeeded } from '@renderer/utils/tour'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MihomoIcon from './components/base/mihomo-icon'
|
||||
|
||||
let navigate: NavigateFunction
|
||||
|
||||
@ -78,7 +78,7 @@ const App: React.FC = () => {
|
||||
const location = useLocation()
|
||||
const page = useRoutes(routes)
|
||||
|
||||
const setTitlebar = (): void => {
|
||||
const setTitlebar = useCallback((): void => {
|
||||
if (!useWindowFrame && platform !== 'darwin') {
|
||||
const options = { height: 48 } as TitleBarOverlayOptions
|
||||
try {
|
||||
@ -89,7 +89,7 @@ const App: React.FC = () => {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [useWindowFrame])
|
||||
|
||||
useEffect(() => {
|
||||
setOrder(siderOrder)
|
||||
@ -101,6 +101,13 @@ const App: React.FC = () => {
|
||||
resizingRef.current = resizing
|
||||
}, [siderWidthValue, resizing])
|
||||
|
||||
const onResizeEnd = useCallback((): void => {
|
||||
if (resizingRef.current) {
|
||||
setResizing(false)
|
||||
patchAppConfig({ siderWidth: siderWidthValueRef.current })
|
||||
}
|
||||
}, [patchAppConfig])
|
||||
|
||||
useEffect(() => {
|
||||
if (!tourInitialized.current) {
|
||||
tourInitialized.current = true
|
||||
@ -113,25 +120,18 @@ const App: React.FC = () => {
|
||||
setNativeTheme(appTheme)
|
||||
setTheme(appTheme)
|
||||
setTitlebar()
|
||||
}, [appTheme, systemTheme])
|
||||
}, [appTheme, systemTheme, setTheme, setTitlebar])
|
||||
|
||||
useEffect(() => {
|
||||
applyTheme(customTheme || 'default.css').then(() => {
|
||||
setTitlebar()
|
||||
})
|
||||
}, [customTheme])
|
||||
}, [customTheme, setTitlebar])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('mouseup', onResizeEnd)
|
||||
return (): void => window.removeEventListener('mouseup', onResizeEnd)
|
||||
}, [])
|
||||
|
||||
const onResizeEnd = (): void => {
|
||||
if (resizingRef.current) {
|
||||
setResizing(false)
|
||||
patchAppConfig({ siderWidth: siderWidthValueRef.current })
|
||||
}
|
||||
}
|
||||
}, [onResizeEnd])
|
||||
|
||||
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
|
||||
const { active, over } = event
|
||||
|
||||
@ -36,7 +36,7 @@ const BasePage = forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [useWindowFrame])
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
useImperativeHandle(ref, () => {
|
||||
|
||||
@ -15,13 +15,12 @@ const EditFileModal: React.FC<Props> = (props) => {
|
||||
const [currData, setCurrData] = useState('')
|
||||
const { t } = useTranslation()
|
||||
|
||||
const getContent = async (): Promise<void> => {
|
||||
useEffect(() => {
|
||||
const loadContent = async (): Promise<void> => {
|
||||
setCurrData(await getOverride(id, language === 'javascript' ? 'js' : 'yaml'))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getContent()
|
||||
}, [])
|
||||
loadContent()
|
||||
}, [id, language])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@ -20,13 +20,12 @@ const ExecLogModal: React.FC<Props> = (props) => {
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const { t } = useTranslation()
|
||||
|
||||
const getLog = async (): Promise<void> => {
|
||||
useEffect(() => {
|
||||
const loadLog = async (): Promise<void> => {
|
||||
setLogs((await getOverride(id, 'log')).split('\n').filter(Boolean))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getLog()
|
||||
}, [])
|
||||
loadLog()
|
||||
}, [id])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@ -16,13 +16,12 @@ const EditFileModal: React.FC<Props> = (props) => {
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const getContent = async (): Promise<void> => {
|
||||
useEffect(() => {
|
||||
const loadContent = async (): Promise<void> => {
|
||||
setCurrData(await getProfileStr(id))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getContent()
|
||||
}, [])
|
||||
loadContent()
|
||||
}, [id])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@ -558,7 +558,109 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
|
||||
const deferredFilteredRules = useDeferredValue(filteredRules)
|
||||
|
||||
const getContent = async (): Promise<void> => {
|
||||
// 解析规则字符串
|
||||
const parseRuleString = useCallback((ruleStr: string): RuleItem => {
|
||||
const parts = ruleStr.split(',')
|
||||
const firstPartIsNumber =
|
||||
!isNaN(Number(parts[0])) && parts[0].trim() !== '' && parts.length >= 3
|
||||
|
||||
let offset = 0
|
||||
let ruleParts = parts
|
||||
|
||||
if (firstPartIsNumber) {
|
||||
offset = parseInt(parts[0])
|
||||
ruleParts = parts.slice(1)
|
||||
}
|
||||
|
||||
if (ruleParts[0] === 'MATCH') {
|
||||
return {
|
||||
type: 'MATCH',
|
||||
payload: '',
|
||||
proxy: ruleParts[1],
|
||||
offset: offset > 0 ? offset : undefined
|
||||
}
|
||||
} else {
|
||||
const additionalParams = ruleParts.slice(3).filter((param) => param.trim() !== '') || []
|
||||
return {
|
||||
type: ruleParts[0],
|
||||
payload: ruleParts[1],
|
||||
proxy: ruleParts[2],
|
||||
additionalParams,
|
||||
offset: offset > 0 ? offset : undefined
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 处理前置规则位置
|
||||
const processRulesWithPositions = useCallback(
|
||||
(
|
||||
rulesToProcess: RuleItem[],
|
||||
allRules: RuleItem[],
|
||||
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
||||
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
||||
const updatedRules = [...allRules]
|
||||
const ruleIndices = new Set<number>()
|
||||
|
||||
rulesToProcess.forEach((rule) => {
|
||||
const targetPosition = positionCalculator(rule, updatedRules)
|
||||
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
||||
updatedRules.splice(actualPosition, 0, rule)
|
||||
|
||||
const newRuleIndices = new Set<number>()
|
||||
ruleIndices.forEach((idx) => {
|
||||
if (idx >= actualPosition) {
|
||||
newRuleIndices.add(idx + 1)
|
||||
} else {
|
||||
newRuleIndices.add(idx)
|
||||
}
|
||||
})
|
||||
newRuleIndices.add(actualPosition)
|
||||
|
||||
ruleIndices.clear()
|
||||
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
||||
})
|
||||
|
||||
return { updatedRules, ruleIndices }
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
// 处理后置规则位置
|
||||
const processAppendRulesWithPositions = useCallback(
|
||||
(
|
||||
rulesToProcess: RuleItem[],
|
||||
allRules: RuleItem[],
|
||||
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
||||
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
||||
const updatedRules = [...allRules]
|
||||
const ruleIndices = new Set<number>()
|
||||
|
||||
rulesToProcess.forEach((rule) => {
|
||||
const targetPosition = positionCalculator(rule, updatedRules)
|
||||
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
||||
updatedRules.splice(actualPosition, 0, rule)
|
||||
|
||||
const newRuleIndices = new Set<number>()
|
||||
ruleIndices.forEach((idx) => {
|
||||
if (idx >= actualPosition) {
|
||||
newRuleIndices.add(idx + 1)
|
||||
} else {
|
||||
newRuleIndices.add(idx)
|
||||
}
|
||||
})
|
||||
newRuleIndices.add(actualPosition)
|
||||
|
||||
ruleIndices.clear()
|
||||
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
||||
})
|
||||
|
||||
return { updatedRules, ruleIndices }
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const loadContent = async (): Promise<void> => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const content = await getProfileStr(id)
|
||||
@ -588,11 +690,9 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 提取代理组
|
||||
if (parsed) {
|
||||
const groups: string[] = []
|
||||
|
||||
// 添加代理组和代理名称
|
||||
if (Array.isArray(parsed['proxy-groups'])) {
|
||||
groups.push(
|
||||
...((parsed['proxy-groups'] as Array<Record<string, unknown>>)
|
||||
@ -613,14 +713,10 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
// 预置出站 https://wiki.metacubex.one/config/proxies/built-in/
|
||||
groups.push('DIRECT', 'REJECT', 'REJECT-DROP', 'PASS', 'COMPATIBLE')
|
||||
|
||||
// 去重
|
||||
setProxyGroups([...new Set(groups)])
|
||||
}
|
||||
|
||||
// 读取规则文件
|
||||
try {
|
||||
const ruleContent = await getRuleStr(id)
|
||||
const ruleData = yaml.load(ruleContent) as {
|
||||
@ -635,16 +731,14 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
const newAppendRules = new Set<number>()
|
||||
const newDeletedRules = new Set<number>()
|
||||
|
||||
// 处理前置规则
|
||||
if (ruleData.prepend && Array.isArray(ruleData.prepend)) {
|
||||
const prependRules: RuleItem[] = []
|
||||
const prependRuleItems: RuleItem[] = []
|
||||
ruleData.prepend.forEach((ruleStr: string) => {
|
||||
prependRules.push(parseRuleString(ruleStr))
|
||||
prependRuleItems.push(parseRuleString(ruleStr))
|
||||
})
|
||||
|
||||
// 插入前置规则
|
||||
const { updatedRules, ruleIndices } = processRulesWithPositions(
|
||||
prependRules,
|
||||
prependRuleItems,
|
||||
allRules,
|
||||
(rule, currentRules) => {
|
||||
if (rule.offset !== undefined && rule.offset < currentRules.length) {
|
||||
@ -658,16 +752,14 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
ruleIndices.forEach((index) => newPrependRules.add(index))
|
||||
}
|
||||
|
||||
// 处理后置规则
|
||||
if (ruleData.append && Array.isArray(ruleData.append)) {
|
||||
const appendRules: RuleItem[] = []
|
||||
const appendRuleItems: RuleItem[] = []
|
||||
ruleData.append.forEach((ruleStr: string) => {
|
||||
appendRules.push(parseRuleString(ruleStr))
|
||||
appendRuleItems.push(parseRuleString(ruleStr))
|
||||
})
|
||||
|
||||
// 插入后置规则
|
||||
const { updatedRules, ruleIndices } = processAppendRulesWithPositions(
|
||||
appendRules,
|
||||
appendRuleItems,
|
||||
allRules,
|
||||
(rule, currentRules) => {
|
||||
if (rule.offset !== undefined) {
|
||||
@ -678,18 +770,14 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
)
|
||||
|
||||
allRules = updatedRules
|
||||
|
||||
// 标记后置规则
|
||||
ruleIndices.forEach((index) => newAppendRules.add(index))
|
||||
}
|
||||
|
||||
// 处理删除规则
|
||||
if (ruleData.delete && Array.isArray(ruleData.delete)) {
|
||||
const deleteRules = ruleData.delete.map((ruleStr: string) => {
|
||||
return parseRuleString(ruleStr)
|
||||
})
|
||||
|
||||
// 匹配并标记删除规则
|
||||
deleteRules.forEach((deleteRule) => {
|
||||
const matchedIndex = allRules.findIndex(
|
||||
(rule) =>
|
||||
@ -706,40 +794,30 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
setPrependRules(newPrependRules)
|
||||
setAppendRules(newAppendRules)
|
||||
setDeletedRules(newDeletedRules)
|
||||
|
||||
// 设置规则列表
|
||||
setRules(allRules)
|
||||
} else {
|
||||
// 使用初始规则
|
||||
setRules(initialRules)
|
||||
// 清空规则标记
|
||||
setPrependRules(new Set())
|
||||
setAppendRules(new Set())
|
||||
setDeletedRules(new Set())
|
||||
}
|
||||
} catch (ruleError) {
|
||||
// 规则文件读取失败
|
||||
console.debug('规则文件读取失败:', ruleError)
|
||||
} catch {
|
||||
setRules(initialRules)
|
||||
// 清空规则标记
|
||||
setPrependRules(new Set())
|
||||
setAppendRules(new Set())
|
||||
setDeletedRules(new Set())
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse profile content', e)
|
||||
} catch {
|
||||
// 解析配置文件失败,静默处理
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getContent()
|
||||
}, [])
|
||||
loadContent()
|
||||
}, [id, parseRuleString, processRulesWithPositions, processAppendRulesWithPositions])
|
||||
|
||||
const validateRulePayload = useCallback((ruleType: string, payload: string): boolean => {
|
||||
if (ruleType === 'MATCH') {
|
||||
@ -843,6 +921,58 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 计算插入位置的索引
|
||||
const getUpdatedIndexForInsertion = (index: number, insertPosition: number): number => {
|
||||
if (index >= insertPosition) {
|
||||
return index + 1
|
||||
} else {
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
// 插入规则后更新所有索引
|
||||
const updateAllRuleIndicesAfterInsertion = useCallback(
|
||||
(
|
||||
currentPrependRules: Set<number>,
|
||||
currentAppendRules: Set<number>,
|
||||
currentDeletedRules: Set<number>,
|
||||
insertPosition: number,
|
||||
isNewPrependRule: boolean = false,
|
||||
isNewAppendRule: boolean = false
|
||||
): {
|
||||
newPrependRules: Set<number>
|
||||
newAppendRules: Set<number>
|
||||
newDeletedRules: Set<number>
|
||||
} => {
|
||||
const newPrependRules = new Set<number>()
|
||||
const newAppendRules = new Set<number>()
|
||||
const newDeletedRules = new Set<number>()
|
||||
|
||||
currentPrependRules.forEach((idx) => {
|
||||
newPrependRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||
})
|
||||
|
||||
currentAppendRules.forEach((idx) => {
|
||||
newAppendRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||
})
|
||||
|
||||
currentDeletedRules.forEach((idx) => {
|
||||
newDeletedRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||
})
|
||||
|
||||
if (isNewPrependRule) {
|
||||
newPrependRules.add(insertPosition)
|
||||
}
|
||||
|
||||
if (isNewAppendRule) {
|
||||
newAppendRules.add(insertPosition)
|
||||
}
|
||||
|
||||
return { newPrependRules, newAppendRules, newDeletedRules }
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const handleAddRule = useCallback(
|
||||
(position: 'prepend' | 'append' = 'append'): void => {
|
||||
if (!(newRule.type === 'MATCH' || newRule.payload.trim() !== '')) {
|
||||
@ -917,7 +1047,16 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
})
|
||||
setNewRule({ type: 'DOMAIN', payload: '', proxy: 'DIRECT', additionalParams: [] })
|
||||
},
|
||||
[newRule, rules, prependRules, appendRules, deletedRules, validateRulePayload, t]
|
||||
[
|
||||
newRule,
|
||||
rules,
|
||||
prependRules,
|
||||
appendRules,
|
||||
deletedRules,
|
||||
validateRulePayload,
|
||||
t,
|
||||
updateAllRuleIndicesAfterInsertion
|
||||
]
|
||||
)
|
||||
|
||||
const handleRemoveRule = useCallback((index: number): void => {
|
||||
@ -1002,126 +1141,6 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
[rules, prependRules, appendRules]
|
||||
)
|
||||
|
||||
// 解析规则字符串
|
||||
const parseRuleString = (ruleStr: string): RuleItem => {
|
||||
const parts = ruleStr.split(',')
|
||||
const firstPartIsNumber =
|
||||
!isNaN(Number(parts[0])) && parts[0].trim() !== '' && parts.length >= 3
|
||||
|
||||
let offset = 0
|
||||
let ruleParts = parts
|
||||
|
||||
if (firstPartIsNumber) {
|
||||
offset = parseInt(parts[0])
|
||||
ruleParts = parts.slice(1)
|
||||
}
|
||||
|
||||
if (ruleParts[0] === 'MATCH') {
|
||||
return {
|
||||
type: 'MATCH',
|
||||
payload: '',
|
||||
proxy: ruleParts[1],
|
||||
offset: offset > 0 ? offset : undefined
|
||||
}
|
||||
} else {
|
||||
const additionalParams = ruleParts.slice(3).filter((param) => param.trim() !== '') || []
|
||||
return {
|
||||
type: ruleParts[0],
|
||||
payload: ruleParts[1],
|
||||
proxy: ruleParts[2],
|
||||
additionalParams,
|
||||
offset: offset > 0 ? offset : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 规则转字符串
|
||||
const convertRuleToString = (rule: RuleItem): string => {
|
||||
const parts = [rule.type]
|
||||
if (rule.payload) parts.push(rule.payload)
|
||||
if (rule.proxy) parts.push(rule.proxy)
|
||||
if (rule.additionalParams && rule.additionalParams.length > 0) {
|
||||
parts.push(...rule.additionalParams)
|
||||
}
|
||||
|
||||
// 添加偏移量
|
||||
if (rule.offset !== undefined && rule.offset > 0) {
|
||||
parts.unshift(rule.offset.toString())
|
||||
}
|
||||
|
||||
return parts.join(',')
|
||||
}
|
||||
|
||||
// 处理前置规则位置
|
||||
const processRulesWithPositions = (
|
||||
rules: RuleItem[],
|
||||
allRules: RuleItem[],
|
||||
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
||||
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
||||
const updatedRules = [...allRules]
|
||||
const ruleIndices = new Set<number>()
|
||||
|
||||
// 按顺序处理规则
|
||||
rules.forEach((rule) => {
|
||||
const targetPosition = positionCalculator(rule, updatedRules)
|
||||
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
||||
updatedRules.splice(actualPosition, 0, rule)
|
||||
|
||||
// 更新索引
|
||||
const newRuleIndices = new Set<number>()
|
||||
ruleIndices.forEach((idx) => {
|
||||
if (idx >= actualPosition) {
|
||||
newRuleIndices.add(idx + 1)
|
||||
} else {
|
||||
newRuleIndices.add(idx)
|
||||
}
|
||||
})
|
||||
// 添加当前规则索引
|
||||
newRuleIndices.add(actualPosition)
|
||||
|
||||
// 更新索引集合
|
||||
ruleIndices.clear()
|
||||
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
||||
})
|
||||
|
||||
return { updatedRules, ruleIndices }
|
||||
}
|
||||
|
||||
// 处理后置规则位置
|
||||
const processAppendRulesWithPositions = (
|
||||
rules: RuleItem[],
|
||||
allRules: RuleItem[],
|
||||
positionCalculator: (rule: RuleItem, currentRules: RuleItem[]) => number
|
||||
): { updatedRules: RuleItem[]; ruleIndices: Set<number> } => {
|
||||
const updatedRules = [...allRules]
|
||||
const ruleIndices = new Set<number>()
|
||||
|
||||
// 按顺序处理规则
|
||||
rules.forEach((rule) => {
|
||||
const targetPosition = positionCalculator(rule, updatedRules)
|
||||
const actualPosition = Math.min(targetPosition, updatedRules.length)
|
||||
updatedRules.splice(actualPosition, 0, rule)
|
||||
|
||||
// 更新索引
|
||||
const newRuleIndices = new Set<number>()
|
||||
ruleIndices.forEach((idx) => {
|
||||
if (idx >= actualPosition) {
|
||||
newRuleIndices.add(idx + 1)
|
||||
} else {
|
||||
newRuleIndices.add(idx)
|
||||
}
|
||||
})
|
||||
// 添加当前规则索引
|
||||
newRuleIndices.add(actualPosition)
|
||||
|
||||
// 更新索引集合
|
||||
ruleIndices.clear()
|
||||
newRuleIndices.forEach((idx) => ruleIndices.add(idx))
|
||||
})
|
||||
|
||||
return { updatedRules, ruleIndices }
|
||||
}
|
||||
|
||||
// 更新规则索引
|
||||
const updateRuleIndices = (prev: Set<number>, index1: number, index2: number): Set<number> => {
|
||||
const newSet = new Set<number>()
|
||||
@ -1137,57 +1156,20 @@ const EditRulesModal: React.FC<Props> = (props) => {
|
||||
return newSet
|
||||
}
|
||||
|
||||
// 计算插入位置的索引
|
||||
const getUpdatedIndexForInsertion = (index: number, insertPosition: number): number => {
|
||||
if (index >= insertPosition) {
|
||||
return index + 1
|
||||
} else {
|
||||
return index
|
||||
}
|
||||
// 规则转字符串
|
||||
const convertRuleToString = (rule: RuleItem): string => {
|
||||
const parts = [rule.type]
|
||||
if (rule.payload) parts.push(rule.payload)
|
||||
if (rule.proxy) parts.push(rule.proxy)
|
||||
if (rule.additionalParams && rule.additionalParams.length > 0) {
|
||||
parts.push(...rule.additionalParams)
|
||||
}
|
||||
|
||||
// 插入规则后更新所有索引
|
||||
const updateAllRuleIndicesAfterInsertion = (
|
||||
prependRules: Set<number>,
|
||||
appendRules: Set<number>,
|
||||
deletedRules: Set<number>,
|
||||
insertPosition: number,
|
||||
isNewPrependRule: boolean = false,
|
||||
isNewAppendRule: boolean = false
|
||||
): {
|
||||
newPrependRules: Set<number>
|
||||
newAppendRules: Set<number>
|
||||
newDeletedRules: Set<number>
|
||||
} => {
|
||||
const newPrependRules = new Set<number>()
|
||||
const newAppendRules = new Set<number>()
|
||||
const newDeletedRules = new Set<number>()
|
||||
|
||||
// 更新前置规则索引
|
||||
prependRules.forEach((idx) => {
|
||||
newPrependRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||
})
|
||||
|
||||
// 更新后置规则索引
|
||||
appendRules.forEach((idx) => {
|
||||
newAppendRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||
})
|
||||
|
||||
// 更新删除规则索引
|
||||
deletedRules.forEach((idx) => {
|
||||
newDeletedRules.add(getUpdatedIndexForInsertion(idx, insertPosition))
|
||||
})
|
||||
|
||||
// 标记新规则
|
||||
if (isNewPrependRule) {
|
||||
newPrependRules.add(insertPosition)
|
||||
if (rule.offset !== undefined && rule.offset > 0) {
|
||||
parts.unshift(rule.offset.toString())
|
||||
}
|
||||
|
||||
if (isNewAppendRule) {
|
||||
newAppendRules.add(insertPosition)
|
||||
}
|
||||
|
||||
return { newPrependRules, newAppendRules, newDeletedRules }
|
||||
return parts.join(',')
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import { getFileStr, setFileStr, convertMrsRuleset, getRuntimeConfig } from '@renderer/utils/ipc'
|
||||
import yaml from 'js-yaml'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -20,15 +20,21 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
const { type, path, title, format, privderType, behavior, onClose } = props
|
||||
const [currData, setCurrData] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
let language: Language = !format || format === 'YamlRule' ? 'yaml' : 'text'
|
||||
|
||||
const getContent = async (): Promise<void> => {
|
||||
const language: Language = useMemo(() => {
|
||||
if (format === 'MrsRule') return 'text'
|
||||
if (type === 'Inline') return 'yaml'
|
||||
if (!format || format === 'YamlRule') return 'yaml'
|
||||
return 'text'
|
||||
}, [format, type])
|
||||
|
||||
useEffect(() => {
|
||||
const loadContent = async (): Promise<void> => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
let fileContent: React.SetStateAction<string>
|
||||
|
||||
if (format === 'MrsRule') {
|
||||
language = 'text'
|
||||
let ruleBehavior: string = behavior || 'domain'
|
||||
if (!behavior) {
|
||||
try {
|
||||
@ -47,7 +53,6 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
|
||||
if (type === 'Inline') {
|
||||
fileContent = await getFileStr('config.yaml')
|
||||
language = 'yaml'
|
||||
} else {
|
||||
fileContent = await getFileStr(path)
|
||||
}
|
||||
@ -73,10 +78,8 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getContent()
|
||||
}, [])
|
||||
loadContent()
|
||||
}, [path, type, title, format, privderType, behavior])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@ -51,7 +51,7 @@ const MihomoCoreCard: React.FC<Props> = (props) => {
|
||||
PubSub.unsubscribe(token)
|
||||
window.electron.ipcRenderer.removeAllListeners('mihomoMemory')
|
||||
}
|
||||
}, [])
|
||||
}, [mutate])
|
||||
|
||||
if (iconOnly) {
|
||||
return (
|
||||
|
||||
@ -39,7 +39,7 @@ export const ControledMihomoConfigProvider: React.FC<{ children: ReactNode }> =
|
||||
return (): void => {
|
||||
window.electron.ipcRenderer.removeListener('controledMihomoConfigUpdated', handler)
|
||||
}
|
||||
}, [])
|
||||
}, [mutateControledMihomoConfig])
|
||||
|
||||
return (
|
||||
<ControledMihomoConfigContext.Provider
|
||||
|
||||
@ -27,7 +27,7 @@ export const GroupsProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
return (): void => {
|
||||
window.electron.ipcRenderer.removeListener('groupsUpdated', handler)
|
||||
}
|
||||
}, [])
|
||||
}, [mutate])
|
||||
|
||||
return <GroupsContext.Provider value={{ groups, mutate }}>{children}</GroupsContext.Provider>
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export const RulesProvider: React.FC<{ children: ReactNode }> = ({ children }) =
|
||||
return (): void => {
|
||||
window.electron.ipcRenderer.removeListener('rulesUpdated', handler)
|
||||
}
|
||||
}, [])
|
||||
}, [mutate])
|
||||
|
||||
return <RulesContext.Provider value={{ rules, mutate }}>{children}</RulesContext.Provider>
|
||||
}
|
||||
|
||||
@ -52,6 +52,34 @@ const CoreMap = {
|
||||
'mihomo-specific': 'mihomo.specificVersion'
|
||||
}
|
||||
|
||||
interface WebUIPanel {
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
const defaultWebUIPanels: WebUIPanel[] = [
|
||||
{
|
||||
id: 'metacubexd',
|
||||
name: 'MetaCubeXD',
|
||||
url: 'https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
id: 'yacd',
|
||||
name: 'YACD',
|
||||
url: 'https://yacd.metacubex.one/?hostname=%host&port=%port&secret=%secret',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
id: 'zashboard',
|
||||
name: 'Zashboard',
|
||||
url: 'https://board.zash.run.place/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
||||
isDefault: true
|
||||
}
|
||||
]
|
||||
|
||||
const Mihomo: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
@ -79,13 +107,6 @@ const Mihomo: React.FC = () => {
|
||||
} = appConfig || {}
|
||||
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
||||
|
||||
interface WebUIPanel {
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
ipv6,
|
||||
'external-controller': externalController = '',
|
||||
@ -153,28 +174,6 @@ const Mihomo: React.FC = () => {
|
||||
// 生成随机端口 (范围 1024-65535)
|
||||
const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
|
||||
|
||||
// 默认 WebUI 面板选项
|
||||
const defaultWebUIPanels: WebUIPanel[] = [
|
||||
{
|
||||
id: 'metacubexd',
|
||||
name: 'MetaCubeXD',
|
||||
url: 'https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
id: 'yacd',
|
||||
name: 'YACD',
|
||||
url: 'https://yacd.metacubex.one/?hostname=%host&port=%port&secret=%secret',
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
id: 'zashboard',
|
||||
name: 'Zashboard',
|
||||
url: 'https://board.zash.run.place/#/setup?http=true&hostname=%host&port=%port&secret=%secret',
|
||||
isDefault: true
|
||||
}
|
||||
]
|
||||
|
||||
// 初始化面板列表
|
||||
useEffect(() => {
|
||||
const savedPanels = localStorage.getItem('webui-panels')
|
||||
|
||||
@ -126,7 +126,7 @@ const Profiles: React.FC = () => {
|
||||
})
|
||||
}
|
||||
return items
|
||||
}, [subs, collections])
|
||||
}, [subs, collections, t])
|
||||
const handleImport = async (): Promise<void> => {
|
||||
setImporting(true)
|
||||
await addProfileItem({
|
||||
|
||||
@ -85,7 +85,7 @@ const useProxyState = (
|
||||
return prev.slice(0, groups.length)
|
||||
})
|
||||
}
|
||||
}, [groups.length])
|
||||
}, [groups.length, isOpen.length, setIsOpen])
|
||||
|
||||
// 保存展开状态
|
||||
useEffect(() => {
|
||||
@ -133,7 +133,7 @@ const Proxies: React.FC = () => {
|
||||
if (groups.length !== searchValue.length) {
|
||||
setSearchValue(Array(groups.length).fill(''))
|
||||
}
|
||||
}, [groups.length])
|
||||
}, [groups.length, searchValue.length])
|
||||
|
||||
// 代理列表排序
|
||||
const sortProxies = useCallback((proxies: (IMihomoProxy | IMihomoGroup)[], order: string) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user