mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 13:10:30 +08:00
support profile override script
This commit is contained in:
parent
d434352bc3
commit
7785c2237e
@ -14,3 +14,14 @@ export {
|
|||||||
changeCurrentProfile,
|
changeCurrentProfile,
|
||||||
updateProfileItem
|
updateProfileItem
|
||||||
} from './profile'
|
} from './profile'
|
||||||
|
export {
|
||||||
|
getOverrideConfig,
|
||||||
|
setOverrideConfig,
|
||||||
|
getOverrideItem,
|
||||||
|
addOverrideItem,
|
||||||
|
removeOverrideItem,
|
||||||
|
createOverride,
|
||||||
|
getOverride,
|
||||||
|
setOverride,
|
||||||
|
updateOverrideItem
|
||||||
|
} from './override'
|
||||||
|
|||||||
108
src/main/config/override.ts
Normal file
108
src/main/config/override.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { overrideConfigPath, overridePath } from '../utils/dirs'
|
||||||
|
import yaml from 'yaml'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { dialog } from 'electron'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { getControledMihomoConfig } from './controledMihomo'
|
||||||
|
|
||||||
|
let overrideConfig: IOverrideConfig // override.yaml
|
||||||
|
|
||||||
|
export function getOverrideConfig(force = false): IOverrideConfig {
|
||||||
|
if (force || !overrideConfig) {
|
||||||
|
overrideConfig = yaml.parse(fs.readFileSync(overrideConfigPath(), 'utf-8'))
|
||||||
|
}
|
||||||
|
return overrideConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setOverrideConfig(config: IOverrideConfig): void {
|
||||||
|
overrideConfig = config
|
||||||
|
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOverrideItem(id: string): IOverrideItem | undefined {
|
||||||
|
return overrideConfig.items.find((item) => item.id === id)
|
||||||
|
}
|
||||||
|
export function updateOverrideItem(item: IOverrideItem): void {
|
||||||
|
const index = overrideConfig.items.findIndex((i) => i.id === item.id)
|
||||||
|
overrideConfig.items[index] = item
|
||||||
|
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<void> {
|
||||||
|
const newItem = await createOverride(item)
|
||||||
|
if (overrideConfig.items.find((i) => i.id === newItem.id)) {
|
||||||
|
updateOverrideItem(newItem)
|
||||||
|
} else {
|
||||||
|
overrideConfig.items.push(newItem)
|
||||||
|
}
|
||||||
|
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeOverrideItem(id: string): void {
|
||||||
|
overrideConfig.items = overrideConfig.items?.filter((item) => item.id !== id)
|
||||||
|
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||||
|
fs.rmSync(overridePath(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createOverride(item: Partial<IOverrideItem>): Promise<IOverrideItem> {
|
||||||
|
const id = item.id || new Date().getTime().toString(16)
|
||||||
|
const newItem = {
|
||||||
|
id,
|
||||||
|
name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
|
||||||
|
type: item.type,
|
||||||
|
url: item.url,
|
||||||
|
updated: new Date().getTime()
|
||||||
|
} as IOverrideItem
|
||||||
|
switch (newItem.type) {
|
||||||
|
case 'remote': {
|
||||||
|
if (!item.url) {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'URL is required for remote script',
|
||||||
|
'URL is required for remote script'
|
||||||
|
)
|
||||||
|
throw new Error('URL is required for remote script')
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await axios.get(item.url, {
|
||||||
|
proxy: {
|
||||||
|
protocol: 'http',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: getControledMihomoConfig()['mixed-port'] || 7890
|
||||||
|
},
|
||||||
|
responseType: 'text'
|
||||||
|
})
|
||||||
|
const data = res.data
|
||||||
|
setOverride(id, data)
|
||||||
|
} catch (e) {
|
||||||
|
dialog.showErrorBox('Failed to fetch remote script', `${e}\nurl: ${item.url}`)
|
||||||
|
throw new Error(`Failed to fetch remote script ${e}`)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'local': {
|
||||||
|
if (!item.file) {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'File is required for local script',
|
||||||
|
'File is required for local script'
|
||||||
|
)
|
||||||
|
throw new Error('File is required for local script')
|
||||||
|
}
|
||||||
|
const data = item.file
|
||||||
|
setOverride(id, data)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOverride(id: string): string {
|
||||||
|
if (!fs.existsSync(overridePath(id))) {
|
||||||
|
return `function main(config){ return config }`
|
||||||
|
}
|
||||||
|
return fs.readFileSync(overridePath(id), 'utf-8')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setOverride(id: string, content: string): void {
|
||||||
|
fs.writeFileSync(overridePath(id), content, 'utf-8')
|
||||||
|
}
|
||||||
@ -1,11 +1,17 @@
|
|||||||
import { getControledMihomoConfig, getProfileConfig, getProfile } from '../config'
|
import {
|
||||||
|
getControledMihomoConfig,
|
||||||
|
getProfileConfig,
|
||||||
|
getProfile,
|
||||||
|
getProfileItem,
|
||||||
|
getOverride
|
||||||
|
} from '../config'
|
||||||
import { mihomoWorkConfigPath } from '../utils/dirs'
|
import { mihomoWorkConfigPath } from '../utils/dirs'
|
||||||
import yaml from 'yaml'
|
import yaml from 'yaml'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
export function generateProfile(): void {
|
export function generateProfile(): void {
|
||||||
const current = getProfileConfig().current
|
const current = getProfileConfig().current
|
||||||
const currentProfile = getProfile(current)
|
const currentProfile = overrideProfile(current, getProfile(current))
|
||||||
const controledMihomoConfig = getControledMihomoConfig()
|
const controledMihomoConfig = getControledMihomoConfig()
|
||||||
const { tun: profileTun = {} } = currentProfile
|
const { tun: profileTun = {} } = currentProfile
|
||||||
const { tun: controledTun } = controledMihomoConfig
|
const { tun: controledTun } = controledMihomoConfig
|
||||||
@ -22,3 +28,25 @@ export function generateProfile(): void {
|
|||||||
profile.sniffer = sniffer
|
profile.sniffer = sniffer
|
||||||
fs.writeFileSync(mihomoWorkConfigPath(), yaml.stringify(profile))
|
fs.writeFileSync(mihomoWorkConfigPath(), yaml.stringify(profile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function overrideProfile(current: string | undefined, profile: IMihomoConfig): IMihomoConfig {
|
||||||
|
const overrideScriptList = getProfileItem(current).override || []
|
||||||
|
for (const override of overrideScriptList) {
|
||||||
|
const script = getOverride(override)
|
||||||
|
profile = runOverrideScript(profile, script)
|
||||||
|
}
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
|
||||||
|
function runOverrideScript(profile: IMihomoConfig, script: string): IMihomoConfig {
|
||||||
|
try {
|
||||||
|
const func = eval(`${script} main`)
|
||||||
|
const newProfile = func(profile)
|
||||||
|
if (typeof newProfile !== 'object') {
|
||||||
|
throw new Error('Override script must return an object')
|
||||||
|
}
|
||||||
|
return newProfile
|
||||||
|
} catch (e) {
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import {
|
|||||||
logDir,
|
logDir,
|
||||||
mihomoTestDir,
|
mihomoTestDir,
|
||||||
mihomoWorkDir,
|
mihomoWorkDir,
|
||||||
|
overrideConfigPath,
|
||||||
|
overrideDir,
|
||||||
profileConfigPath,
|
profileConfigPath,
|
||||||
profilePath,
|
profilePath,
|
||||||
profilesDir,
|
profilesDir,
|
||||||
@ -13,6 +15,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
defaultConfig,
|
defaultConfig,
|
||||||
defaultControledMihomoConfig,
|
defaultControledMihomoConfig,
|
||||||
|
defaultOverrideConfig,
|
||||||
defaultProfile,
|
defaultProfile,
|
||||||
defaultProfileConfig
|
defaultProfileConfig
|
||||||
} from '../utils/template'
|
} from '../utils/template'
|
||||||
@ -31,6 +34,9 @@ function initDirs(): void {
|
|||||||
if (!fs.existsSync(profilesDir())) {
|
if (!fs.existsSync(profilesDir())) {
|
||||||
fs.mkdirSync(profilesDir())
|
fs.mkdirSync(profilesDir())
|
||||||
}
|
}
|
||||||
|
if (!fs.existsSync(overrideDir())) {
|
||||||
|
fs.mkdirSync(overrideDir())
|
||||||
|
}
|
||||||
if (!fs.existsSync(mihomoWorkDir())) {
|
if (!fs.existsSync(mihomoWorkDir())) {
|
||||||
fs.mkdirSync(mihomoWorkDir())
|
fs.mkdirSync(mihomoWorkDir())
|
||||||
}
|
}
|
||||||
@ -49,6 +55,9 @@ function initConfig(): void {
|
|||||||
if (!fs.existsSync(profileConfigPath())) {
|
if (!fs.existsSync(profileConfigPath())) {
|
||||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(defaultProfileConfig))
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(defaultProfileConfig))
|
||||||
}
|
}
|
||||||
|
if (!fs.existsSync(overrideConfigPath())) {
|
||||||
|
fs.writeFileSync(overrideConfigPath(), yaml.stringify(defaultOverrideConfig))
|
||||||
|
}
|
||||||
if (!fs.existsSync(profilePath('default'))) {
|
if (!fs.existsSync(profilePath('default'))) {
|
||||||
fs.writeFileSync(profilePath('default'), yaml.stringify(defaultProfile))
|
fs.writeFileSync(profilePath('default'), yaml.stringify(defaultProfile))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,18 @@ export function profilePath(id: string): string {
|
|||||||
return path.join(profilesDir(), `${id}.yaml`)
|
return path.join(profilesDir(), `${id}.yaml`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function overrideDir(): string {
|
||||||
|
return path.join(dataDir, 'override')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function overrideConfigPath(): string {
|
||||||
|
return path.join(dataDir, 'override.yaml')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function overridePath(id: string): string {
|
||||||
|
return path.join(overrideDir(), `${id}.js`)
|
||||||
|
}
|
||||||
|
|
||||||
export function mihomoWorkDir(): string {
|
export function mihomoWorkDir(): string {
|
||||||
return path.join(dataDir, 'work')
|
return path.join(dataDir, 'work')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,15 @@ import {
|
|||||||
getProfileStr,
|
getProfileStr,
|
||||||
setProfileStr,
|
setProfileStr,
|
||||||
updateProfileItem,
|
updateProfileItem,
|
||||||
setProfileConfig
|
setProfileConfig,
|
||||||
|
getOverrideConfig,
|
||||||
|
setOverrideConfig,
|
||||||
|
getOverrideItem,
|
||||||
|
addOverrideItem,
|
||||||
|
removeOverrideItem,
|
||||||
|
getOverride,
|
||||||
|
setOverride,
|
||||||
|
updateOverrideItem
|
||||||
} from '../config'
|
} from '../config'
|
||||||
import { isEncryptionAvailable, restartCore } from '../core/manager'
|
import { isEncryptionAvailable, restartCore } from '../core/manager'
|
||||||
import { triggerSysProxy } from '../resolve/sysproxy'
|
import { triggerSysProxy } from '../resolve/sysproxy'
|
||||||
@ -81,11 +89,19 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('changeCurrentProfile', (_e, id) => changeCurrentProfile(id))
|
ipcMain.handle('changeCurrentProfile', (_e, id) => changeCurrentProfile(id))
|
||||||
ipcMain.handle('addProfileItem', (_e, item) => addProfileItem(item))
|
ipcMain.handle('addProfileItem', (_e, item) => addProfileItem(item))
|
||||||
ipcMain.handle('removeProfileItem', (_e, id) => removeProfileItem(id))
|
ipcMain.handle('removeProfileItem', (_e, id) => removeProfileItem(id))
|
||||||
|
ipcMain.handle('getOverrideConfig', (_e, force) => getOverrideConfig(force))
|
||||||
|
ipcMain.handle('setOverrideConfig', (_e, config) => setOverrideConfig(config))
|
||||||
|
ipcMain.handle('getOverrideItem', (_e, id) => getOverrideItem(id))
|
||||||
|
ipcMain.handle('addOverrideItem', (_e, item) => addOverrideItem(item))
|
||||||
|
ipcMain.handle('removeOverrideItem', (_e, id) => removeOverrideItem(id))
|
||||||
|
ipcMain.handle('updateOverrideItem', (_e, item) => updateOverrideItem(item))
|
||||||
|
ipcMain.handle('getOverride', (_e, id) => getOverride(id))
|
||||||
|
ipcMain.handle('setOverride', (_e, id, str) => setOverride(id, str))
|
||||||
ipcMain.handle('restartCore', restartCore)
|
ipcMain.handle('restartCore', restartCore)
|
||||||
ipcMain.handle('triggerSysProxy', (_e, enable) => triggerSysProxy(enable))
|
ipcMain.handle('triggerSysProxy', (_e, enable) => triggerSysProxy(enable))
|
||||||
ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable)
|
ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable)
|
||||||
ipcMain.handle('encryptString', (_e, str) => safeStorage.encryptString(str))
|
ipcMain.handle('encryptString', (_e, str) => safeStorage.encryptString(str))
|
||||||
ipcMain.handle('getFilePath', getFilePath)
|
ipcMain.handle('getFilePath', (_e, ext) => getFilePath(ext))
|
||||||
ipcMain.handle('readTextFile', (_e, filePath) => readTextFile(filePath))
|
ipcMain.handle('readTextFile', (_e, filePath) => readTextFile(filePath))
|
||||||
ipcMain.handle('getRuntimeConfigStr', getRuntimeConfigStr)
|
ipcMain.handle('getRuntimeConfigStr', getRuntimeConfigStr)
|
||||||
ipcMain.handle('getRuntimeConfig', getRuntimeConfig)
|
ipcMain.handle('getRuntimeConfig', getRuntimeConfig)
|
||||||
@ -97,10 +113,10 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('quitApp', () => app.quit())
|
ipcMain.handle('quitApp', () => app.quit())
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilePath(): string[] | undefined {
|
function getFilePath(ext: string[]): string[] | undefined {
|
||||||
return dialog.showOpenDialogSync({
|
return dialog.showOpenDialogSync({
|
||||||
title: '选择订阅文件',
|
title: '选择订阅文件',
|
||||||
filters: [{ name: 'Yaml Files', extensions: ['yml', 'yaml'] }],
|
filters: [{ name: 'Yaml Files', extensions: ext }],
|
||||||
properties: ['openFile']
|
properties: ['openFile']
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,6 +94,10 @@ export const defaultProfileConfig: IProfileConfig = {
|
|||||||
items: []
|
items: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const defaultOverrideConfig: IOverrideConfig = {
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
|
||||||
export const defaultProfile: Partial<IMihomoConfig> = {
|
export const defaultProfile: Partial<IMihomoConfig> = {
|
||||||
proxies: [],
|
proxies: [],
|
||||||
'proxy-groups': [],
|
'proxy-groups': [],
|
||||||
|
|||||||
58
src/renderer/src/components/override/edit-file-modal.tsx
Normal file
58
src/renderer/src/components/override/edit-file-modal.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { BaseEditor } from '../base/base-editor'
|
||||||
|
import { getOverride, setOverride } from '@renderer/utils/ipc'
|
||||||
|
interface Props {
|
||||||
|
id: string
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
const EditFileModal: React.FC<Props> = (props) => {
|
||||||
|
const { id, onClose } = props
|
||||||
|
const [currData, setCurrData] = useState('')
|
||||||
|
|
||||||
|
const getContent = async (): Promise<void> => {
|
||||||
|
setCurrData(await getOverride(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getContent()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
backdrop="blur"
|
||||||
|
size="5xl"
|
||||||
|
hideCloseButton
|
||||||
|
isOpen={true}
|
||||||
|
onOpenChange={onClose}
|
||||||
|
scrollBehavior="inside"
|
||||||
|
>
|
||||||
|
<ModalContent className="h-full w-[calc(100%-100px)]">
|
||||||
|
<ModalHeader className="flex">编辑覆写脚本</ModalHeader>
|
||||||
|
<ModalBody className="h-full">
|
||||||
|
<BaseEditor
|
||||||
|
language="javascript"
|
||||||
|
value={currData}
|
||||||
|
onChange={(value) => setCurrData(value)}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="light" onPress={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onPress={async () => {
|
||||||
|
await setOverride(id, currData)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditFileModal
|
||||||
73
src/renderer/src/components/override/edit-info-modal.tsx
Normal file
73
src/renderer/src/components/override/edit-info-modal.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Button,
|
||||||
|
Input
|
||||||
|
} from '@nextui-org/react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import SettingItem from '../base/base-setting-item'
|
||||||
|
interface Props {
|
||||||
|
item: IOverrideItem
|
||||||
|
updateOverrideItem: (item: IOverrideItem) => Promise<void>
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
const EditInfoModal: React.FC<Props> = (props) => {
|
||||||
|
const { item, updateOverrideItem, onClose } = props
|
||||||
|
const [values, setValues] = useState(item)
|
||||||
|
|
||||||
|
const onSave = async (): Promise<void> => {
|
||||||
|
await updateOverrideItem(values)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
backdrop="blur"
|
||||||
|
hideCloseButton
|
||||||
|
isOpen={true}
|
||||||
|
onOpenChange={onClose}
|
||||||
|
scrollBehavior="inside"
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader className="flex">编辑信息</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<SettingItem title="名称">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
className="w-[200px]"
|
||||||
|
value={values.name}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, name: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
{values.type === 'remote' && (
|
||||||
|
<SettingItem title="地址">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
className="w-[200px]"
|
||||||
|
value={values.url}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, url: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="light" onPress={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" onPress={onSave}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditInfoModal
|
||||||
197
src/renderer/src/components/override/override-item.tsx
Normal file
197
src/renderer/src/components/override/override-item.tsx
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
Chip,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownTrigger
|
||||||
|
} from '@nextui-org/react'
|
||||||
|
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import React, { Key, useEffect, useState } from 'react'
|
||||||
|
import EditFileModal from './edit-file-modal'
|
||||||
|
import EditInfoModal from './edit-info-modal'
|
||||||
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
info: IOverrideItem
|
||||||
|
addOverrideItem: (item: Partial<IOverrideItem>) => Promise<void>
|
||||||
|
updateOverrideItem: (item: IOverrideItem) => Promise<void>
|
||||||
|
removeOverrideItem: (id: string) => Promise<void>
|
||||||
|
mutateOverrideConfig: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
key: string
|
||||||
|
label: string
|
||||||
|
showDivider: boolean
|
||||||
|
color: 'default' | 'danger'
|
||||||
|
className: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
key: 'edit-info',
|
||||||
|
label: '编辑信息',
|
||||||
|
showDivider: false,
|
||||||
|
color: 'default',
|
||||||
|
className: ''
|
||||||
|
} as MenuItem,
|
||||||
|
{
|
||||||
|
key: 'edit-file',
|
||||||
|
label: '编辑文件',
|
||||||
|
showDivider: true,
|
||||||
|
color: 'default',
|
||||||
|
className: ''
|
||||||
|
} as MenuItem,
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
label: '删除',
|
||||||
|
showDivider: false,
|
||||||
|
color: 'danger',
|
||||||
|
className: 'text-danger'
|
||||||
|
} as MenuItem
|
||||||
|
]
|
||||||
|
|
||||||
|
const OverrideItem: React.FC<Props> = (props) => {
|
||||||
|
const { info, addOverrideItem, removeOverrideItem, mutateOverrideConfig, updateOverrideItem } =
|
||||||
|
props
|
||||||
|
const [updating, setUpdating] = useState(false)
|
||||||
|
const [openInfo, setOpenInfo] = useState(false)
|
||||||
|
const [openFile, setOpenFile] = useState(false)
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
|
id: info.id
|
||||||
|
})
|
||||||
|
const [disableOpen, setDisableOpen] = useState(false)
|
||||||
|
|
||||||
|
const onMenuAction = (key: Key): void => {
|
||||||
|
switch (key) {
|
||||||
|
case 'edit-info': {
|
||||||
|
setOpenInfo(true)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'edit-file': {
|
||||||
|
setOpenFile(true)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'delete': {
|
||||||
|
removeOverrideItem(info.id)
|
||||||
|
mutateOverrideConfig()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDragging) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setDisableOpen(true)
|
||||||
|
}, 200)
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
setDisableOpen(false)
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
}, [isDragging])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="grid col-span-1"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
zIndex: isDragging ? 'calc(infinity)' : undefined
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{openFile && <EditFileModal id={info.id} onClose={() => setOpenFile(false)} />}
|
||||||
|
{openInfo && (
|
||||||
|
<EditInfoModal
|
||||||
|
item={info}
|
||||||
|
onClose={() => setOpenInfo(false)}
|
||||||
|
updateOverrideItem={updateOverrideItem}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Card
|
||||||
|
fullWidth
|
||||||
|
isPressable
|
||||||
|
onPress={() => {
|
||||||
|
if (disableOpen) return
|
||||||
|
setOpenFile(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardBody>
|
||||||
|
<div className="flex justify-between h-[32px]">
|
||||||
|
<h3
|
||||||
|
ref={setNodeRef}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
className={`text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px] text-foreground`}
|
||||||
|
>
|
||||||
|
{info?.name}
|
||||||
|
</h3>
|
||||||
|
<div className="flex">
|
||||||
|
{info.type === 'remote' && (
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
color="default"
|
||||||
|
disabled={updating}
|
||||||
|
onPress={() => {
|
||||||
|
setUpdating(true)
|
||||||
|
addOverrideItem(info).finally(() => {
|
||||||
|
setUpdating(false)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IoMdRefresh
|
||||||
|
color="default"
|
||||||
|
className={`text-[24px] ${updating ? 'animate-spin' : ''}`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Dropdown>
|
||||||
|
<DropdownTrigger>
|
||||||
|
<Button isIconOnly size="sm" variant="light" color="default">
|
||||||
|
<IoMdMore color="default" className={`text-[24px]`} />
|
||||||
|
</Button>
|
||||||
|
</DropdownTrigger>
|
||||||
|
<DropdownMenu onAction={onMenuAction}>
|
||||||
|
{menuItems.map((item) => (
|
||||||
|
<DropdownItem
|
||||||
|
showDivider={item.showDivider}
|
||||||
|
key={item.key}
|
||||||
|
color={item.color}
|
||||||
|
className={item.className}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</DropdownItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{info.type === 'remote' && (
|
||||||
|
<div className={`mt-2 flex justify-end`}>
|
||||||
|
<small>{dayjs(info.updated).fromNow()}</small>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{info.type === 'local' && (
|
||||||
|
<div className={`mt-2 flex justify-between`}>
|
||||||
|
<Chip size="sm" variant="bordered">
|
||||||
|
本地
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OverrideItem
|
||||||
@ -5,11 +5,14 @@ import {
|
|||||||
ModalBody,
|
ModalBody,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
Button,
|
Button,
|
||||||
Input
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
} from '@nextui-org/react'
|
} from '@nextui-org/react'
|
||||||
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 dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { useOverrideConfig } from '@renderer/hooks/use-override-config'
|
||||||
interface Props {
|
interface Props {
|
||||||
item: IProfileItem
|
item: IProfileItem
|
||||||
updateProfileItem: (item: IProfileItem) => Promise<void>
|
updateProfileItem: (item: IProfileItem) => Promise<void>
|
||||||
@ -17,6 +20,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
const EditInfoModal: React.FC<Props> = (props) => {
|
const EditInfoModal: React.FC<Props> = (props) => {
|
||||||
const { item, updateProfileItem, onClose } = props
|
const { item, updateProfileItem, onClose } = props
|
||||||
|
const { overrideConfig } = useOverrideConfig()
|
||||||
|
const { items: overrideItems = [] } = overrideConfig || {}
|
||||||
const [values, setValues] = useState(item)
|
const [values, setValues] = useState(item)
|
||||||
|
|
||||||
const onSave = async (): Promise<void> => {
|
const onSave = async (): Promise<void> => {
|
||||||
@ -77,6 +82,21 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
)}
|
)}
|
||||||
|
<SettingItem title="覆写脚本">
|
||||||
|
<Select
|
||||||
|
className="w-[200px]"
|
||||||
|
size="sm"
|
||||||
|
selectionMode="multiple"
|
||||||
|
selectedKeys={new Set(values.override || [])}
|
||||||
|
onSelectionChange={(v) => {
|
||||||
|
setValues({ ...values, override: Array.from(v).map((i) => i.toString()) })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{overrideItems.map((i) => (
|
||||||
|
<SelectItem key={i.id}>{i.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</SettingItem>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button variant="light" onPress={onClose}>
|
<Button variant="light" onPress={onClose}>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
|
||||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
import React from 'react'
|
||||||
import React, { useState } from 'react'
|
|
||||||
import { MdFormatOverline } from 'react-icons/md'
|
import { MdFormatOverline } from 'react-icons/md'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
@ -10,7 +9,6 @@ const OverrideCard: React.FC = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const match = location.pathname.includes('/override')
|
const match = location.pathname.includes('/override')
|
||||||
const [enable, setEnable] = useState(false)
|
|
||||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
id: 'override'
|
id: 'override'
|
||||||
})
|
})
|
||||||
@ -43,11 +41,6 @@ const OverrideCard: React.FC = () => {
|
|||||||
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
|
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<BorderSwitch
|
|
||||||
isShowBorder={match && enable}
|
|
||||||
isSelected={enable}
|
|
||||||
onValueChange={setEnable}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter className="pt-1">
|
<CardFooter className="pt-1">
|
||||||
|
|||||||
52
src/renderer/src/hooks/use-override-config.tsx
Normal file
52
src/renderer/src/hooks/use-override-config.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import useSWR from 'swr'
|
||||||
|
import {
|
||||||
|
getOverrideConfig,
|
||||||
|
setOverrideConfig as set,
|
||||||
|
addOverrideItem as add,
|
||||||
|
removeOverrideItem as remove,
|
||||||
|
updateOverrideItem as update
|
||||||
|
} from '@renderer/utils/ipc'
|
||||||
|
|
||||||
|
interface RetuenType {
|
||||||
|
overrideConfig: IOverrideConfig | undefined
|
||||||
|
setOverrideConfig: (config: IOverrideConfig) => Promise<void>
|
||||||
|
mutateOverrideConfig: () => void
|
||||||
|
addOverrideItem: (item: Partial<IOverrideItem>) => Promise<void>
|
||||||
|
updateOverrideItem: (item: IOverrideItem) => Promise<void>
|
||||||
|
removeOverrideItem: (id: string) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useOverrideConfig = (): RetuenType => {
|
||||||
|
const { data: overrideConfig, mutate: mutateOverrideConfig } = useSWR('getOverrideConfig', () =>
|
||||||
|
getOverrideConfig()
|
||||||
|
)
|
||||||
|
|
||||||
|
const setOverrideConfig = async (config: IOverrideConfig): Promise<void> => {
|
||||||
|
await set(config)
|
||||||
|
mutateOverrideConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
const addOverrideItem = async (item: Partial<IOverrideItem>): Promise<void> => {
|
||||||
|
await add(item)
|
||||||
|
mutateOverrideConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeOverrideItem = async (id: string): Promise<void> => {
|
||||||
|
await remove(id)
|
||||||
|
mutateOverrideConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateOverrideItem = async (item: IOverrideItem): Promise<void> => {
|
||||||
|
await update(item)
|
||||||
|
mutateOverrideConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
overrideConfig,
|
||||||
|
setOverrideConfig,
|
||||||
|
mutateOverrideConfig,
|
||||||
|
addOverrideItem,
|
||||||
|
removeOverrideItem,
|
||||||
|
updateOverrideItem
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,174 @@
|
|||||||
|
import { Button, Input } from '@nextui-org/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
|
import { getFilePath, readTextFile } from '@renderer/utils/ipc'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { MdContentPaste } from 'react-icons/md'
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
closestCenter,
|
||||||
|
PointerSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
DragEndEvent
|
||||||
|
} from '@dnd-kit/core'
|
||||||
|
import { SortableContext } from '@dnd-kit/sortable'
|
||||||
|
import { useOverrideConfig } from '@renderer/hooks/use-override-config'
|
||||||
|
import OverrideItem from '@renderer/components/override/override-item'
|
||||||
|
|
||||||
const Override: React.FC = () => {
|
const Override: React.FC = () => {
|
||||||
return <BasePage title="覆写"></BasePage>
|
const {
|
||||||
|
overrideConfig,
|
||||||
|
setOverrideConfig,
|
||||||
|
addOverrideItem,
|
||||||
|
updateOverrideItem,
|
||||||
|
removeOverrideItem,
|
||||||
|
mutateOverrideConfig
|
||||||
|
} = useOverrideConfig()
|
||||||
|
const { items = [] } = overrideConfig || {}
|
||||||
|
const [sortedItems, setSortedItems] = useState(items)
|
||||||
|
const [importing, setImporting] = useState(false)
|
||||||
|
const [fileOver, setFileOver] = useState(false)
|
||||||
|
const [url, setUrl] = useState('')
|
||||||
|
const sensors = useSensors(useSensor(PointerSensor))
|
||||||
|
const handleImport = async (): Promise<void> => {
|
||||||
|
setImporting(true)
|
||||||
|
try {
|
||||||
|
await addOverrideItem({ name: '', type: 'remote', url })
|
||||||
|
} finally {
|
||||||
|
setImporting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const pageRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const onDragEnd = async (event: DragEndEvent): Promise<void> => {
|
||||||
|
const { active, over } = event
|
||||||
|
if (over) {
|
||||||
|
if (active.id !== over.id) {
|
||||||
|
const newOrder = sortedItems.slice()
|
||||||
|
const activeIndex = newOrder.findIndex((item) => item.id === active.id)
|
||||||
|
const overIndex = newOrder.findIndex((item) => item.id === over.id)
|
||||||
|
newOrder.splice(activeIndex, 1)
|
||||||
|
newOrder.splice(overIndex, 0, items[activeIndex])
|
||||||
|
setSortedItems(newOrder)
|
||||||
|
await setOverrideConfig({ items: newOrder })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
pageRef.current?.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
setFileOver(true)
|
||||||
|
})
|
||||||
|
pageRef.current?.addEventListener('dragleave', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
setFileOver(false)
|
||||||
|
})
|
||||||
|
pageRef.current?.addEventListener('drop', async (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
if (event.dataTransfer?.files) {
|
||||||
|
const file = event.dataTransfer.files[0]
|
||||||
|
if (file.name.endsWith('.js')) {
|
||||||
|
const content = await readTextFile(file.path)
|
||||||
|
try {
|
||||||
|
await addOverrideItem({ name: file.name, type: 'local', file: content })
|
||||||
|
} finally {
|
||||||
|
setFileOver(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('不支持的文件类型')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFileOver(false)
|
||||||
|
})
|
||||||
|
return (): void => {
|
||||||
|
pageRef.current?.removeEventListener('dragover', () => {})
|
||||||
|
pageRef.current?.removeEventListener('dragleave', () => {})
|
||||||
|
pageRef.current?.removeEventListener('drop', () => {})
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSortedItems(items)
|
||||||
|
}, [items])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasePage ref={pageRef} title="覆写脚本">
|
||||||
|
<div className="sticky top-[48px] z-40 backdrop-blur bg-background/40 flex p-2">
|
||||||
|
<Input
|
||||||
|
variant="bordered"
|
||||||
|
size="sm"
|
||||||
|
value={url}
|
||||||
|
onValueChange={setUrl}
|
||||||
|
endContent={
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
isIconOnly
|
||||||
|
variant="light"
|
||||||
|
onPress={() => {
|
||||||
|
navigator.clipboard.readText().then((text) => {
|
||||||
|
setUrl(text)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MdContentPaste className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
className="ml-2"
|
||||||
|
isDisabled={url === ''}
|
||||||
|
isLoading={importing}
|
||||||
|
onPress={handleImport}
|
||||||
|
>
|
||||||
|
导入
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
className="ml-2"
|
||||||
|
onPress={() => {
|
||||||
|
getFilePath(['js']).then(async (files) => {
|
||||||
|
if (files?.length) {
|
||||||
|
const content = await readTextFile(files[0])
|
||||||
|
const fileName = files[0].split('/').pop()?.split('\\').pop()
|
||||||
|
await addOverrideItem({ name: fileName, type: 'local', file: content })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
打开
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
||||||
|
<div
|
||||||
|
className={`${fileOver ? 'blur-sm' : ''} grid sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-2 mx-2`}
|
||||||
|
>
|
||||||
|
<SortableContext
|
||||||
|
items={sortedItems.map((item) => {
|
||||||
|
return item.id
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{sortedItems.map((item) => (
|
||||||
|
<OverrideItem
|
||||||
|
key={item.id}
|
||||||
|
addOverrideItem={addOverrideItem}
|
||||||
|
removeOverrideItem={removeOverrideItem}
|
||||||
|
mutateOverrideConfig={mutateOverrideConfig}
|
||||||
|
updateOverrideItem={updateOverrideItem}
|
||||||
|
info={item}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</div>
|
||||||
|
</DndContext>
|
||||||
|
</BasePage>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Override
|
export default Override
|
||||||
|
|||||||
@ -92,6 +92,10 @@ const Profiles: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSortedItems(items)
|
||||||
|
}, [items])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage ref={pageRef} title="订阅管理">
|
<BasePage ref={pageRef} title="订阅管理">
|
||||||
<div className="sticky top-[48px] z-40 backdrop-blur bg-background/40 flex p-2">
|
<div className="sticky top-[48px] z-40 backdrop-blur bg-background/40 flex p-2">
|
||||||
@ -130,7 +134,7 @@ const Profiles: React.FC = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
getFilePath().then(async (files) => {
|
getFilePath(['yml', 'yaml']).then(async (files) => {
|
||||||
if (files?.length) {
|
if (files?.length) {
|
||||||
const content = await readTextFile(files[0])
|
const content = await readTextFile(files[0])
|
||||||
const fileName = files[0].split('/').pop()?.split('\\').pop()
|
const fileName = files[0].split('/').pop()?.split('\\').pop()
|
||||||
|
|||||||
@ -138,6 +138,38 @@ export async function setProfileStr(id: string, str: string): Promise<void> {
|
|||||||
return await window.electron.ipcRenderer.invoke('setProfileStr', id, str)
|
return await window.electron.ipcRenderer.invoke('setProfileStr', id, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('getOverrideConfig', force)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setOverrideConfig(config: IOverrideConfig): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('setOverrideConfig', config)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOverrideItem(id: string): Promise<IOverrideItem | undefined> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('getOverrideItem', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('addOverrideItem', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeOverrideItem(id: string): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('removeOverrideItem', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateOverrideItem(item: IOverrideItem): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('updateOverrideItem', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOverride(id: string): Promise<string> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('getOverride', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setOverride(id: string, str: string): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('setOverride', id, str)
|
||||||
|
}
|
||||||
|
|
||||||
export async function restartCore(): Promise<void> {
|
export async function restartCore(): Promise<void> {
|
||||||
return await window.electron.ipcRenderer.invoke('restartCore')
|
return await window.electron.ipcRenderer.invoke('restartCore')
|
||||||
}
|
}
|
||||||
@ -154,8 +186,8 @@ export async function encryptString(str: string): Promise<Buffer> {
|
|||||||
return await window.electron.ipcRenderer.invoke('encryptString', str)
|
return await window.electron.ipcRenderer.invoke('encryptString', str)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFilePath(): Promise<string[] | undefined> {
|
export async function getFilePath(ext: string[]): Promise<string[] | undefined> {
|
||||||
return await window.electron.ipcRenderer.invoke('getFilePath')
|
return await window.electron.ipcRenderer.invoke('getFilePath', ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readTextFile(filePath: string): Promise<string> {
|
export async function readTextFile(filePath: string): Promise<string> {
|
||||||
|
|||||||
14
src/shared/types.d.ts
vendored
14
src/shared/types.d.ts
vendored
@ -311,6 +311,19 @@ interface IProfileConfig {
|
|||||||
items: IProfileItem[]
|
items: IProfileItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IOverrideItem {
|
||||||
|
id: string
|
||||||
|
type: 'remote' | 'local'
|
||||||
|
name: string
|
||||||
|
updated: number
|
||||||
|
url?: string
|
||||||
|
file?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IOverrideConfig {
|
||||||
|
items: IOverrideItem[]
|
||||||
|
}
|
||||||
|
|
||||||
interface ISubscriptionUserInfo {
|
interface ISubscriptionUserInfo {
|
||||||
upload: number
|
upload: number
|
||||||
download: number
|
download: number
|
||||||
@ -327,5 +340,6 @@ interface IProfileItem {
|
|||||||
interval?: number
|
interval?: number
|
||||||
home?: string
|
home?: string
|
||||||
updated?: number
|
updated?: number
|
||||||
|
override?: string[]
|
||||||
extra?: ISubscriptionUserInfo
|
extra?: ISubscriptionUserInfo
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user