mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
update profile
This commit is contained in:
parent
279f8fe1d3
commit
179a87d4bf
@ -7,5 +7,9 @@ export {
|
|||||||
getProfileConfig,
|
getProfileConfig,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
createProfile
|
createProfile,
|
||||||
|
getProfileStr,
|
||||||
|
setProfileStr,
|
||||||
|
changeCurrentProfile,
|
||||||
|
updateProfileItem
|
||||||
} from './profile'
|
} from './profile'
|
||||||
|
|||||||
@ -38,9 +38,21 @@ export async function changeCurrentProfile(id: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateProfileItem(item: IProfileItem): Promise<void> {
|
||||||
|
const index = profileConfig.items.findIndex((i) => i.id === item.id)
|
||||||
|
profileConfig.items[index] = item
|
||||||
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||||
|
window?.webContents.send('profileConfigUpdated')
|
||||||
|
}
|
||||||
|
|
||||||
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
|
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
|
||||||
const newItem = await createProfile(item)
|
const newItem = await createProfile(item)
|
||||||
profileConfig.items.push(newItem)
|
if (profileConfig.items.find((i) => i.id === newItem.id)) {
|
||||||
|
updateProfileItem(newItem)
|
||||||
|
} else {
|
||||||
|
profileConfig.items.push(newItem)
|
||||||
|
}
|
||||||
|
|
||||||
if (!getProfileConfig().current) {
|
if (!getProfileConfig().current) {
|
||||||
changeCurrentProfile(newItem.id)
|
changeCurrentProfile(newItem.id)
|
||||||
}
|
}
|
||||||
@ -134,7 +146,7 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
|||||||
if (headers['subscription-userinfo']) {
|
if (headers['subscription-userinfo']) {
|
||||||
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
||||||
}
|
}
|
||||||
fs.writeFileSync(profilePath(id), data, 'utf-8')
|
setProfileStr(id, data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.showErrorBox('Failed to fetch remote profile', `${e}\nurl: ${item.url}`)
|
dialog.showErrorBox('Failed to fetch remote profile', `${e}\nurl: ${item.url}`)
|
||||||
throw new Error(`Failed to fetch remote profile ${e}`)
|
throw new Error(`Failed to fetch remote profile ${e}`)
|
||||||
@ -150,7 +162,7 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
|||||||
throw new Error('File is required for local profile')
|
throw new Error('File is required for local profile')
|
||||||
}
|
}
|
||||||
const data = item.file
|
const data = item.file
|
||||||
fs.writeFileSync(profilePath(id), yaml.stringify(data))
|
setProfileStr(id, data)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,13 +170,25 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
|||||||
return newItem
|
return newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProfileStr(id: string): string {
|
||||||
|
return fs.readFileSync(profilePath(id), 'utf-8')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setProfileStr(id: string, content: string): void {
|
||||||
|
fs.writeFileSync(profilePath(id), content, 'utf-8')
|
||||||
|
if (id === getProfileConfig().current) {
|
||||||
|
getCurrentProfile(true)
|
||||||
|
restartCore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getCurrentProfile(force = false): Partial<IMihomoConfig> {
|
export function getCurrentProfile(force = false): Partial<IMihomoConfig> {
|
||||||
if (force || !currentProfile) {
|
if (force || !currentProfile) {
|
||||||
const current = getProfileConfig().current
|
const current = getProfileConfig().current
|
||||||
if (current) {
|
if (current) {
|
||||||
currentProfile = yaml.parse(fs.readFileSync(profilePath(current), 'utf-8'))
|
currentProfile = yaml.parse(getProfileStr(current))
|
||||||
} else {
|
} else {
|
||||||
currentProfile = yaml.parse(fs.readFileSync(profilePath('default'), 'utf-8'))
|
currentProfile = yaml.parse(getProfileStr('default'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return currentProfile
|
return currentProfile
|
||||||
|
|||||||
@ -23,11 +23,14 @@ import {
|
|||||||
getCurrentProfileItem,
|
getCurrentProfileItem,
|
||||||
getProfileItem,
|
getProfileItem,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
removeProfileItem
|
removeProfileItem,
|
||||||
|
changeCurrentProfile,
|
||||||
|
getProfileStr,
|
||||||
|
setProfileStr,
|
||||||
|
updateProfileItem
|
||||||
} 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'
|
||||||
import { changeCurrentProfile } from '../config/profile'
|
|
||||||
|
|
||||||
export function registerIpcMainHandlers(): void {
|
export function registerIpcMainHandlers(): void {
|
||||||
ipcMain.handle('mihomoVersion', mihomoVersion)
|
ipcMain.handle('mihomoVersion', mihomoVersion)
|
||||||
@ -52,6 +55,9 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('getProfileConfig', (_e, force) => getProfileConfig(force))
|
ipcMain.handle('getProfileConfig', (_e, force) => getProfileConfig(force))
|
||||||
ipcMain.handle('getCurrentProfileItem', getCurrentProfileItem)
|
ipcMain.handle('getCurrentProfileItem', getCurrentProfileItem)
|
||||||
ipcMain.handle('getProfileItem', (_e, id) => getProfileItem(id))
|
ipcMain.handle('getProfileItem', (_e, id) => getProfileItem(id))
|
||||||
|
ipcMain.handle('getProfileStr', (_e, id) => getProfileStr(id))
|
||||||
|
ipcMain.handle('setProfileStr', (_e, id, str) => setProfileStr(id, str))
|
||||||
|
ipcMain.handle('updateProfileItem', (_e, item) => updateProfileItem(item))
|
||||||
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))
|
||||||
|
|||||||
77
src/renderer/src/components/profiles/edit-file-modal.tsx
Normal file
77
src/renderer/src/components/profiles/edit-file-modal.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import MonacoEditor, { monaco } from 'react-monaco-editor'
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
|
import { getProfileStr, setProfileStr } 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 { theme } = useTheme()
|
||||||
|
|
||||||
|
const editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor): void => {
|
||||||
|
window.electron.ipcRenderer.on('resize', () => {
|
||||||
|
editor.layout()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorWillUnmount = (editor: monaco.editor.IStandaloneCodeEditor): void => {
|
||||||
|
window.electron.ipcRenderer.removeAllListeners('resize')
|
||||||
|
editor.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getContent = async (): Promise<void> => {
|
||||||
|
setCurrData(await getProfileStr(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getContent()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal size="5xl" hideCloseButton isOpen={true} scrollBehavior="inside">
|
||||||
|
<ModalContent className="h-full w-[calc(100%-100px)]">
|
||||||
|
<ModalHeader className="flex">编辑订阅</ModalHeader>
|
||||||
|
<ModalBody className="h-full">
|
||||||
|
<MonacoEditor
|
||||||
|
height="100%"
|
||||||
|
language="yaml"
|
||||||
|
value={currData}
|
||||||
|
theme={theme === 'dark' ? 'vs-dark' : 'vs'}
|
||||||
|
options={{
|
||||||
|
minimap: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
mouseWheelZoom: true,
|
||||||
|
fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"`,
|
||||||
|
fontLigatures: true, // 连字符
|
||||||
|
smoothScrolling: true // 平滑滚动
|
||||||
|
}}
|
||||||
|
editorDidMount={editorDidMount}
|
||||||
|
editorWillUnmount={editorWillUnmount}
|
||||||
|
onChange={(value) => setCurrData(value)}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="light" onPress={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onPress={async () => {
|
||||||
|
await setProfileStr(id, currData)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditFileModal
|
||||||
79
src/renderer/src/components/profiles/edit-info-modal.tsx
Normal file
79
src/renderer/src/components/profiles/edit-info-modal.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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: IProfileItem
|
||||||
|
updateProfileItem: (item: IProfileItem) => Promise<void>
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
const EditInfoModal: React.FC<Props> = (props) => {
|
||||||
|
const { item, updateProfileItem, onClose } = props
|
||||||
|
const [values, setValues] = useState(item)
|
||||||
|
|
||||||
|
const onSave = async (): Promise<void> => {
|
||||||
|
await updateProfileItem(values)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal hideCloseButton isOpen={true} 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.url && (
|
||||||
|
<SettingItem title="订阅地址">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
className="w-[200px]"
|
||||||
|
value={values.url}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, url: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SettingItem title="更新间隔(分钟)">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
type="number"
|
||||||
|
className="w-[200px]"
|
||||||
|
value={values.interval?.toString() ?? ''}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, interval: parseInt(v) })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant="light" onPress={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" onPress={onSave}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditInfoModal
|
||||||
@ -12,11 +12,15 @@ import {
|
|||||||
import { calcPercent, calcTraffic } from '@renderer/utils/calc'
|
import { calcPercent, calcTraffic } from '@renderer/utils/calc'
|
||||||
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import React, { Key, useMemo } from 'react'
|
import React, { Key, useMemo, useState } from 'react'
|
||||||
|
import EditFileModal from './edit-file-modal'
|
||||||
|
import EditInfoModal from './edit-info-modal'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
info: IProfileItem
|
info: IProfileItem
|
||||||
isCurrent: boolean
|
isCurrent: boolean
|
||||||
|
addProfileItem: (item: Partial<IProfileItem>) => Promise<void>
|
||||||
|
updateProfileItem: (item: IProfileItem) => Promise<void>
|
||||||
removeProfileItem: (id: string) => Promise<void>
|
removeProfileItem: (id: string) => Promise<void>
|
||||||
mutateProfileConfig: () => void
|
mutateProfileConfig: () => void
|
||||||
onClick: () => Promise<void>
|
onClick: () => Promise<void>
|
||||||
@ -30,15 +34,33 @@ interface MenuItem {
|
|||||||
className: string
|
className: string
|
||||||
}
|
}
|
||||||
const ProfileItem: React.FC<Props> = (props) => {
|
const ProfileItem: React.FC<Props> = (props) => {
|
||||||
const { info, removeProfileItem, mutateProfileConfig, onClick, isCurrent } = props
|
const {
|
||||||
|
info,
|
||||||
|
addProfileItem,
|
||||||
|
removeProfileItem,
|
||||||
|
mutateProfileConfig,
|
||||||
|
updateProfileItem,
|
||||||
|
onClick,
|
||||||
|
isCurrent
|
||||||
|
} = props
|
||||||
const extra = info?.extra
|
const extra = info?.extra
|
||||||
const usage = (extra?.upload ?? 0) + (extra?.download ?? 0)
|
const usage = (extra?.upload ?? 0) + (extra?.download ?? 0)
|
||||||
const total = extra?.total ?? 0
|
const total = extra?.total ?? 0
|
||||||
|
const [updating, setUpdating] = useState(false)
|
||||||
|
const [openInfo, setOpenInfo] = useState(false)
|
||||||
|
const [openFile, setOpenFile] = useState(false)
|
||||||
|
|
||||||
const menuItems: MenuItem[] = useMemo(() => {
|
const menuItems: MenuItem[] = useMemo(() => {
|
||||||
const list = [
|
const list = [
|
||||||
{
|
{
|
||||||
key: 'edit',
|
key: 'edit-info',
|
||||||
|
label: '编辑信息',
|
||||||
|
showDivider: false,
|
||||||
|
color: 'default',
|
||||||
|
className: ''
|
||||||
|
} as MenuItem,
|
||||||
|
{
|
||||||
|
key: 'edit-file',
|
||||||
label: '编辑文件',
|
label: '编辑文件',
|
||||||
showDivider: true,
|
showDivider: true,
|
||||||
color: 'default',
|
color: 'default',
|
||||||
@ -66,8 +88,14 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const onMenuAction = (key: Key): void => {
|
const onMenuAction = (key: Key): void => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'edit':
|
case 'edit-info': {
|
||||||
|
setOpenInfo(true)
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
case 'edit-file': {
|
||||||
|
setOpenFile(true)
|
||||||
|
break
|
||||||
|
}
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
removeProfileItem(info.id)
|
removeProfileItem(info.id)
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
@ -82,52 +110,77 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card fullWidth isPressable onPress={onClick} className={isCurrent ? 'bg-primary' : ''}>
|
<>
|
||||||
<CardBody className="pb-1">
|
{openFile && <EditFileModal id={info.id} onClose={() => setOpenFile(false)} />}
|
||||||
<div className="flex justify-between h-[32px]">
|
{openInfo && (
|
||||||
<h3 className="select-none text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px]">
|
<EditInfoModal
|
||||||
{info?.name}
|
item={info}
|
||||||
</h3>
|
onClose={() => setOpenInfo(false)}
|
||||||
<div className="flex">
|
updateProfileItem={updateProfileItem}
|
||||||
<Button isIconOnly size="sm" variant="light" color="default">
|
/>
|
||||||
<IoMdRefresh color="default" className="text-[24px]" />
|
)}
|
||||||
</Button>
|
<Card fullWidth isPressable onPress={onClick} className={isCurrent ? 'bg-primary' : ''}>
|
||||||
<Dropdown>
|
<CardBody className="pb-1">
|
||||||
<DropdownTrigger>
|
<div className="flex justify-between h-[32px]">
|
||||||
<Button isIconOnly size="sm" variant="light" color="default">
|
<h3 className="select-none text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px]">
|
||||||
<IoMdMore color="default" className="text-[24px]" />
|
{info?.name}
|
||||||
</Button>
|
</h3>
|
||||||
</DropdownTrigger>
|
<div className="flex">
|
||||||
<DropdownMenu onAction={onMenuAction}>
|
<Button
|
||||||
{menuItems.map((item) => (
|
isIconOnly
|
||||||
<DropdownItem
|
size="sm"
|
||||||
showDivider={item.showDivider}
|
variant="light"
|
||||||
key={item.key}
|
color="default"
|
||||||
color={item.color}
|
disabled={updating}
|
||||||
className={item.className}
|
onPress={() => {
|
||||||
>
|
setUpdating(true)
|
||||||
{item.label}
|
addProfileItem(info).finally(() => {
|
||||||
</DropdownItem>
|
setUpdating(false)
|
||||||
))}
|
})
|
||||||
</DropdownMenu>
|
}}
|
||||||
</Dropdown>
|
>
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
<div className="mt-2 flex justify-between">
|
||||||
<div className="mt-2 flex justify-between">
|
<small>{extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined}</small>
|
||||||
<small>{extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined}</small>
|
<small>{dayjs(info.updated).fromNow()}</small>
|
||||||
<small>{dayjs(info.updated).fromNow()}</small>
|
</div>
|
||||||
</div>
|
</CardBody>
|
||||||
</CardBody>
|
<CardFooter className="pt-0">
|
||||||
<CardFooter className="pt-0">
|
{extra && (
|
||||||
{extra && (
|
<Progress
|
||||||
<Progress
|
className="w-full"
|
||||||
className="w-full"
|
classNames={{ indicator: 'bg-foreground', label: 'select-none' }}
|
||||||
classNames={{ indicator: 'bg-foreground', label: 'select-none' }}
|
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
||||||
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
</CardFooter>
|
||||||
</CardFooter>
|
</Card>
|
||||||
</Card>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { IoMdRefresh } from 'react-icons/io'
|
|||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import 'dayjs/locale/zh-cn'
|
import 'dayjs/locale/zh-cn'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
dayjs.locale('zh-cn')
|
dayjs.locale('zh-cn')
|
||||||
@ -14,8 +15,8 @@ const ProfileCard: React.FC = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const match = location.pathname.includes('/profiles')
|
const match = location.pathname.includes('/profiles')
|
||||||
|
const [updating, setUpdating] = useState(false)
|
||||||
const { profileConfig } = useProfileConfig()
|
const { profileConfig, addProfileItem } = useProfileConfig()
|
||||||
const { current, items } = profileConfig ?? {}
|
const { current, items } = profileConfig ?? {}
|
||||||
const info = items?.find((item) => item.id === current) ?? {
|
const info = items?.find((item) => item.id === current) ?? {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
@ -39,8 +40,23 @@ const ProfileCard: React.FC = () => {
|
|||||||
<h3 className="select-none text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px]">
|
<h3 className="select-none text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px]">
|
||||||
{info?.name}
|
{info?.name}
|
||||||
</h3>
|
</h3>
|
||||||
<Button isIconOnly size="sm" variant="light" color="default">
|
<Button
|
||||||
<IoMdRefresh color="default" className="text-[24px]" />
|
isIconOnly
|
||||||
|
size="sm"
|
||||||
|
disabled={updating}
|
||||||
|
variant="light"
|
||||||
|
color="default"
|
||||||
|
onPress={() => {
|
||||||
|
setUpdating(true)
|
||||||
|
addProfileItem(info).finally(() => {
|
||||||
|
setUpdating(false)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IoMdRefresh
|
||||||
|
color="default"
|
||||||
|
className={`text-[24px] ${updating ? 'animate-spin' : ''}`}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex justify-between">
|
<div className="mt-2 flex justify-between">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
getProfileConfig,
|
getProfileConfig,
|
||||||
addProfileItem as add,
|
addProfileItem as add,
|
||||||
removeProfileItem as remove,
|
removeProfileItem as remove,
|
||||||
|
updateProfileItem as update,
|
||||||
changeCurrentProfile as change
|
changeCurrentProfile as change
|
||||||
} from '@renderer/utils/ipc'
|
} from '@renderer/utils/ipc'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
@ -11,6 +12,7 @@ interface RetuenType {
|
|||||||
profileConfig: IProfileConfig | undefined
|
profileConfig: IProfileConfig | undefined
|
||||||
mutateProfileConfig: () => void
|
mutateProfileConfig: () => void
|
||||||
addProfileItem: (item: Partial<IProfileItem>) => Promise<void>
|
addProfileItem: (item: Partial<IProfileItem>) => Promise<void>
|
||||||
|
updateProfileItem: (item: IProfileItem) => Promise<void>
|
||||||
removeProfileItem: (id: string) => Promise<void>
|
removeProfileItem: (id: string) => Promise<void>
|
||||||
changeCurrentProfile: (id: string) => Promise<void>
|
changeCurrentProfile: (id: string) => Promise<void>
|
||||||
}
|
}
|
||||||
@ -30,6 +32,11 @@ export const useProfileConfig = (): RetuenType => {
|
|||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateProfileItem = async (item: IProfileItem): Promise<void> => {
|
||||||
|
await update(item)
|
||||||
|
mutateProfileConfig()
|
||||||
|
}
|
||||||
|
|
||||||
const changeCurrentProfile = async (id: string): Promise<void> => {
|
const changeCurrentProfile = async (id: string): Promise<void> => {
|
||||||
await change(id)
|
await change(id)
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
@ -49,6 +56,7 @@ export const useProfileConfig = (): RetuenType => {
|
|||||||
mutateProfileConfig,
|
mutateProfileConfig,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
|
updateProfileItem,
|
||||||
changeCurrentProfile
|
changeCurrentProfile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ const Profiles: React.FC = () => {
|
|||||||
const {
|
const {
|
||||||
profileConfig,
|
profileConfig,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
|
updateProfileItem,
|
||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
changeCurrentProfile,
|
changeCurrentProfile,
|
||||||
mutateProfileConfig
|
mutateProfileConfig
|
||||||
@ -61,8 +62,10 @@ const Profiles: React.FC = () => {
|
|||||||
<ProfileItem
|
<ProfileItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
isCurrent={item.id === current}
|
isCurrent={item.id === current}
|
||||||
|
addProfileItem={addProfileItem}
|
||||||
removeProfileItem={removeProfileItem}
|
removeProfileItem={removeProfileItem}
|
||||||
mutateProfileConfig={mutateProfileConfig}
|
mutateProfileConfig={mutateProfileConfig}
|
||||||
|
updateProfileItem={updateProfileItem}
|
||||||
info={item}
|
info={item}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await changeCurrentProfile(item.id)
|
await changeCurrentProfile(item.id)
|
||||||
|
|||||||
@ -99,6 +99,18 @@ export async function removeProfileItem(id: string): Promise<void> {
|
|||||||
return await window.electron.ipcRenderer.invoke('removeProfileItem', id)
|
return await window.electron.ipcRenderer.invoke('removeProfileItem', id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateProfileItem(item: IProfileItem): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('updateProfileItem', item)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProfileStr(id: string): Promise<string> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('getProfileStr', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setProfileStr(id: string, str: string): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('setProfileStr', 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')
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user