mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 21:20:29 +08:00
setup profile
This commit is contained in:
parent
4d9fb1f457
commit
4f335bddd5
@ -10,9 +10,13 @@ import { checkAutoRun, disableAutoRun, enableAutoRun } from './autoRun'
|
|||||||
import {
|
import {
|
||||||
getAppConfig,
|
getAppConfig,
|
||||||
setAppConfig,
|
setAppConfig,
|
||||||
getProfileConfig,
|
|
||||||
getControledMihomoConfig,
|
getControledMihomoConfig,
|
||||||
setControledMihomoConfig
|
setControledMihomoConfig,
|
||||||
|
getProfileConfig,
|
||||||
|
getCurrentProfileItem,
|
||||||
|
getProfileItem,
|
||||||
|
addProfileItem,
|
||||||
|
removeProfileItem
|
||||||
} from './config'
|
} from './config'
|
||||||
import { restartCore } from './manager'
|
import { restartCore } from './manager'
|
||||||
|
|
||||||
@ -30,5 +34,9 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('getControledMihomoConfig', (_e, force) => getControledMihomoConfig(force))
|
ipcMain.handle('getControledMihomoConfig', (_e, force) => getControledMihomoConfig(force))
|
||||||
ipcMain.handle('setControledMihomoConfig', (_e, config) => setControledMihomoConfig(config))
|
ipcMain.handle('setControledMihomoConfig', (_e, config) => setControledMihomoConfig(config))
|
||||||
ipcMain.handle('getProfileConfig', (_e, force) => getProfileConfig(force))
|
ipcMain.handle('getProfileConfig', (_e, force) => getProfileConfig(force))
|
||||||
|
ipcMain.handle('getCurrentProfileItem', getCurrentProfileItem)
|
||||||
|
ipcMain.handle('getProfileItem', (_e, id) => getProfileItem(id))
|
||||||
|
ipcMain.handle('addProfileItem', (_e, item) => addProfileItem(item))
|
||||||
|
ipcMain.handle('removeProfileItem', (_e, id) => removeProfileItem(id))
|
||||||
ipcMain.handle('restartCore', () => restartCore())
|
ipcMain.handle('restartCore', () => restartCore())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,10 @@ import {
|
|||||||
} from './template'
|
} from './template'
|
||||||
import { appConfigPath, controledMihomoConfigPath, profileConfigPath, profilePath } from './dirs'
|
import { appConfigPath, controledMihomoConfigPath, profileConfigPath, profilePath } from './dirs'
|
||||||
|
|
||||||
export let appConfig: IAppConfig
|
export let appConfig: IAppConfig // config.yaml
|
||||||
export let profileConfig: IProfileConfig
|
export let profileConfig: IProfileConfig // profile.yaml
|
||||||
export let currentProfile: Partial<IMihomoConfig>
|
export let currentProfile: Partial<IMihomoConfig> // profiles/xxx.yaml
|
||||||
export let controledMihomoConfig: Partial<IMihomoConfig>
|
export let controledMihomoConfig: Partial<IMihomoConfig> // mihomo.yaml
|
||||||
|
|
||||||
export function initConfig(): void {
|
export function initConfig(): void {
|
||||||
if (!fs.existsSync(appConfigPath())) {
|
if (!fs.existsSync(appConfigPath())) {
|
||||||
@ -63,11 +63,30 @@ export function getProfileConfig(force = false): IProfileConfig {
|
|||||||
return profileConfig
|
return profileConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setProfileConfig(patch: Partial<IProfileConfig>): void {
|
export function getProfileItem(id: string | undefined): IProfileItem {
|
||||||
profileConfig = Object.assign(profileConfig, patch)
|
const items = profileConfig.items
|
||||||
|
return items?.find((item) => item.id === id) || { id: 'default', type: 'local', name: '空白订阅' }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addProfileItem(item: IProfileItem): void {
|
||||||
|
profileConfig.items.push(item)
|
||||||
|
if (!profileConfig.current) {
|
||||||
|
profileConfig.current = item.id
|
||||||
|
}
|
||||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeProfileItem(id: string): void {
|
||||||
|
profileConfig.items = profileConfig.items?.filter((item) => item.id !== id)
|
||||||
|
if (profileConfig.current === id) {
|
||||||
|
profileConfig.current = profileConfig.items[0]?.id
|
||||||
|
}
|
||||||
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentProfileItem(): IProfileItem {
|
||||||
|
return getProfileItem(profileConfig.current)
|
||||||
|
}
|
||||||
export function getCurrentProfile(force = false): Partial<IMihomoConfig> {
|
export function getCurrentProfile(force = false): Partial<IMihomoConfig> {
|
||||||
if (force || !currentProfile) {
|
if (force || !currentProfile) {
|
||||||
if (profileConfig.current) {
|
if (profileConfig.current) {
|
||||||
|
|||||||
@ -13,14 +13,7 @@ export const defaultControledMihomoConfig: Partial<IMihomoConfig> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultProfileConfig: IProfileConfig = {
|
export const defaultProfileConfig: IProfileConfig = {
|
||||||
current: 'default',
|
items: []
|
||||||
profiles: [
|
|
||||||
{
|
|
||||||
id: 'default',
|
|
||||||
type: 'local',
|
|
||||||
name: '默认'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultProfile: Partial<IMihomoConfig> = {
|
export const defaultProfile: Partial<IMihomoConfig> = {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { mihomoConnections } from '@renderer/utils/ipc'
|
|||||||
const ConnCard: React.FC = () => {
|
const ConnCard: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/connections')
|
||||||
|
|
||||||
const { data: connections } = useSWR<IMihomoConnectionsInfo>('/connections', mihomoConnections, {
|
const { data: connections } = useSWR<IMihomoConnectionsInfo>('/connections', mihomoConnections, {
|
||||||
refreshInterval: 5000
|
refreshInterval: 5000
|
||||||
@ -24,7 +25,7 @@ const ConnCard: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`w-[50%] mr-1 mb-2 ${location.pathname.includes('/connections') ? 'bg-primary' : ''}`}
|
className={`w-[50%] mr-1 mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/connections')}
|
onPress={() => navigate('/connections')}
|
||||||
>
|
>
|
||||||
@ -38,7 +39,22 @@ const ConnCard: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<IoLink color="default" className="text-[20px]" />
|
<IoLink color="default" className="text-[20px]" />
|
||||||
</Button>
|
</Button>
|
||||||
<Chip size="sm" color="secondary" variant="bordered" className="mr-3 mt-2">
|
<Chip
|
||||||
|
classNames={
|
||||||
|
match
|
||||||
|
? {
|
||||||
|
base: 'border-foreground',
|
||||||
|
content: 'text-foreground'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
base: 'border-primary',
|
||||||
|
content: 'text-primary'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
variant="bordered"
|
||||||
|
className="mr-3 mt-2"
|
||||||
|
>
|
||||||
{connections?.connections?.length ?? 0}
|
{connections?.connections?.length ?? 0}
|
||||||
</Chip>
|
</Chip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,10 +5,10 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
const LogCard: React.FC = () => {
|
const LogCard: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/logs')
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`w-[50%] ml-1 mb-2 ${location.pathname.includes('/logs') ? 'bg-primary' : ''}`}
|
className={`w-[50%] ml-1 mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/logs')}
|
onPress={() => navigate('/logs')}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -25,13 +25,13 @@ const MihomoCoreCard: React.FC = () => {
|
|||||||
const { core } = appConfig || {}
|
const { core } = appConfig || {}
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/mihomo')
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
fullWidth
|
fullWidth
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/mihomo')}
|
onPress={() => navigate('/mihomo')}
|
||||||
className={`mb-2 ${location.pathname.includes('/mihomo') ? 'bg-primary' : ''}`}
|
className={`mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
>
|
>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<div className="flex justify-between h-[32px]">
|
<div className="flex justify-between h-[32px]">
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/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'
|
||||||
|
|
||||||
const OverrideCard: React.FC = () => {
|
const OverrideCard: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/override')
|
||||||
|
const [enable, setEnable] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`w-[50%] ml-1 mb-2 ${location.pathname.includes('/override') ? 'bg-primary' : ''}`}
|
className={`w-[50%] ml-1 mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/override')}
|
onPress={() => navigate('/override')}
|
||||||
>
|
>
|
||||||
@ -22,7 +25,14 @@ const OverrideCard: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<MdFormatOverline color="default" className="text-[24px]" />
|
<MdFormatOverline color="default" className="text-[24px]" />
|
||||||
</Button>
|
</Button>
|
||||||
<Switch size="sm" />
|
<Switch
|
||||||
|
classNames={{
|
||||||
|
wrapper: `${match && enable ? 'border-2' : ''}`
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
isSelected={enable}
|
||||||
|
onValueChange={setEnable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter className="pt-1">
|
<CardFooter className="pt-1">
|
||||||
|
|||||||
@ -1,31 +1,51 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Slider } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter, Progress } from '@nextui-org/react'
|
||||||
|
import { getCurrentProfileItem } from '@renderer/utils/ipc'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import useSWR from 'swr'
|
||||||
const ProfileCard: React.FC = () => {
|
const ProfileCard: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/profiles')
|
||||||
|
|
||||||
|
const { data: info } = useSWR('getCurrentProfileItem', getCurrentProfileItem)
|
||||||
|
const extra = info?.extra
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
fullWidth
|
fullWidth
|
||||||
className={`mb-2 ${location.pathname.includes('/profiles') ? 'bg-primary' : ''}`}
|
className={`mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/profiles')}
|
onPress={() => navigate('/profiles')}
|
||||||
>
|
>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<div className="flex justify-between h-[32px]">
|
<div className="flex justify-between h-[32px]">
|
||||||
<h3 className="select-none text-md font-bold leading-[32px]">订阅名称</h3>
|
<h3 className="select-none text-md font-bold leading-[32px]">{info?.name}</h3>
|
||||||
<Button isIconOnly size="sm" variant="light" color="default">
|
<Button isIconOnly size="sm" variant="light" color="default">
|
||||||
<IoMdRefresh color="default" className="text-[24px]" />
|
<IoMdRefresh color="default" className="text-[24px]" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter className="pt-1">
|
<CardFooter className="pt-1">
|
||||||
<Slider className="pointer-events-none" color="foreground" value={20} hideThumb />
|
<Progress
|
||||||
|
classNames={{ indicator: 'bg-foreground' }}
|
||||||
|
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
||||||
|
className="max-w-md"
|
||||||
|
/>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ProfileCard
|
export default ProfileCard
|
||||||
|
|
||||||
|
function calcPercent(
|
||||||
|
upload: number | undefined,
|
||||||
|
download: number | undefined,
|
||||||
|
total: number | undefined
|
||||||
|
): number {
|
||||||
|
if (upload === undefined || download === undefined || total === undefined) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return Math.round(((upload + download) / total) * 100)
|
||||||
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
const ProxyCard: React.FC = () => {
|
const ProxyCard: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/proxies')
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
fullWidth
|
fullWidth
|
||||||
className={`mb-2 ${location.pathname.includes('/proxies') ? 'bg-primary' : ''}`}
|
className={`mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/proxies')}
|
onPress={() => navigate('/proxies')}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -7,14 +7,14 @@ import useSWR from 'swr'
|
|||||||
const RuleCard: React.FC = () => {
|
const RuleCard: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/rules')
|
||||||
const { data: rules } = useSWR<IMihomoRulesInfo>('/connections', mihomoRules, {
|
const { data: rules } = useSWR<IMihomoRulesInfo>('/connections', mihomoRules, {
|
||||||
refreshInterval: 5000
|
refreshInterval: 5000
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`w-[50%] mr-1 mb-2 ${location.pathname.includes('/rules') ? 'bg-primary' : ''}`}
|
className={`w-[50%] mr-1 mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/rules')}
|
onPress={() => navigate('/rules')}
|
||||||
>
|
>
|
||||||
@ -28,7 +28,22 @@ const RuleCard: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<IoGitNetwork color="default" className="text-[20px]" />
|
<IoGitNetwork color="default" className="text-[20px]" />
|
||||||
</Button>
|
</Button>
|
||||||
<Chip size="sm" color="secondary" variant="bordered" className="mr-3 mt-2">
|
<Chip
|
||||||
|
classNames={
|
||||||
|
match
|
||||||
|
? {
|
||||||
|
base: 'border-foreground',
|
||||||
|
content: 'text-foreground'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
base: 'border-primary',
|
||||||
|
content: 'text-primary'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
variant="bordered"
|
||||||
|
className="mr-3 mt-2"
|
||||||
|
>
|
||||||
{rules?.rules?.length ?? 0}
|
{rules?.rules?.length ?? 0}
|
||||||
</Chip>
|
</Chip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
import { AiOutlineGlobal } from 'react-icons/ai'
|
import { AiOutlineGlobal } from 'react-icons/ai'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const SysproxySwitcher: React.FC = () => {
|
const SysproxySwitcher: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/sysproxy')
|
||||||
|
const [enable, setEnable] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`w-[50%] mr-1 ${location.pathname.includes('/sysproxy') ? 'bg-primary' : ''}`}
|
className={`w-[50%] mr-1 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/sysproxy')}
|
onPress={() => navigate('/sysproxy')}
|
||||||
>
|
>
|
||||||
@ -22,7 +25,12 @@ const SysproxySwitcher: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<AiOutlineGlobal color="default" className="text-[24px]" />
|
<AiOutlineGlobal color="default" className="text-[24px]" />
|
||||||
</Button>
|
</Button>
|
||||||
<Switch size="sm" />
|
<Switch
|
||||||
|
classNames={{ wrapper: `${match && enable ? 'border-2' : ''}` }}
|
||||||
|
size="sm"
|
||||||
|
isSelected={enable}
|
||||||
|
onValueChange={setEnable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter className="pt-1">
|
<CardFooter className="pt-1">
|
||||||
|
|||||||
@ -1,23 +1,36 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
|
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const TunSwitcher: React.FC = () => {
|
const TunSwitcher: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/tun')
|
||||||
|
const [enable, setEnable] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`w-[50%] ml-1 ${location.pathname.includes('/tun') ? 'bg-primary' : ''}`}
|
className={`w-[50%] ml-1 ${match ? 'bg-primary' : ''}`}
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => navigate('/tun')}
|
onPress={() => navigate('/tun')}
|
||||||
>
|
>
|
||||||
<CardBody className="pb-1 pt-0 px-0">
|
<CardBody className="pb-1 pt-0 px-0">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Button isIconOnly className="bg-transparent" variant="flat" color="default">
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
className="bg-transparent pointer-events-none"
|
||||||
|
variant="flat"
|
||||||
|
color="default"
|
||||||
|
>
|
||||||
<TbDeviceIpadHorizontalBolt color="default" className="text-[24px] font-bold" />
|
<TbDeviceIpadHorizontalBolt color="default" className="text-[24px] font-bold" />
|
||||||
</Button>
|
</Button>
|
||||||
<Switch size="sm" />
|
<Switch
|
||||||
|
classNames={{ wrapper: `${match && enable ? 'border-2' : ''}` }}
|
||||||
|
size="sm"
|
||||||
|
isSelected={enable}
|
||||||
|
onValueChange={setEnable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter className="pt-1">
|
<CardFooter className="pt-1">
|
||||||
|
|||||||
0
src/renderer/src/hooks/use-profile.tsx
Normal file
0
src/renderer/src/hooks/use-profile.tsx
Normal file
@ -1,5 +1,44 @@
|
|||||||
|
import { Button, Input } from '@nextui-org/react'
|
||||||
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { MdContentPaste } from 'react-icons/md'
|
||||||
|
|
||||||
const Profiles: React.FC = () => {
|
const Profiles: React.FC = () => {
|
||||||
return <div>Profiles</div>
|
const [url, setUrl] = useState('')
|
||||||
|
|
||||||
|
const handleImport = async (): Promise<void> => {
|
||||||
|
console.log('import', url)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<BasePage title="订阅">
|
||||||
|
<div className="flex m-2">
|
||||||
|
<Input
|
||||||
|
variant="bordered"
|
||||||
|
className="mr-2"
|
||||||
|
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" onPress={handleImport}>
|
||||||
|
导入
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</BasePage>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Profiles
|
export default Profiles
|
||||||
|
|||||||
@ -46,6 +46,26 @@ export async function setControledMihomoConfig(patch: Partial<IMihomoConfig>): P
|
|||||||
await window.electron.ipcRenderer.invoke('setControledMihomoConfig', patch)
|
await window.electron.ipcRenderer.invoke('setControledMihomoConfig', patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getProfileConfig(force = false): Promise<IProfileConfig> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('getProfileConfig', force)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentProfileItem(): Promise<IProfileItem> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('getCurrentProfileItem')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProfileItem(id: string | undefined): Promise<IProfileItem> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('getProfileItem', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addProfileItem(item: IProfileItem): Promise<void> {
|
||||||
|
await window.electron.ipcRenderer.invoke('addProfileItem', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeProfileItem(id: string): Promise<void> {
|
||||||
|
await window.electron.ipcRenderer.invoke('removeProfileItem', id)
|
||||||
|
}
|
||||||
|
|
||||||
export async function restartCore(): Promise<void> {
|
export async function restartCore(): Promise<void> {
|
||||||
await window.electron.ipcRenderer.invoke('restartCore')
|
await window.electron.ipcRenderer.invoke('restartCore')
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/shared/types.d.ts
vendored
10
src/shared/types.d.ts
vendored
@ -85,11 +85,19 @@ interface IMihomoConfig {
|
|||||||
|
|
||||||
interface IProfileConfig {
|
interface IProfileConfig {
|
||||||
current?: string
|
current?: string
|
||||||
profiles?: IProfileItem[]
|
items: IProfileItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProfileItem {
|
interface IProfileItem {
|
||||||
id: string
|
id: string
|
||||||
type: 'remote' | 'local'
|
type: 'remote' | 'local'
|
||||||
name: string
|
name: string
|
||||||
|
url?: string
|
||||||
|
updated?: number
|
||||||
|
extra?: {
|
||||||
|
upload: number
|
||||||
|
download: number
|
||||||
|
total: number
|
||||||
|
expire: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user