diff --git a/src/main/config/profile.ts b/src/main/config/profile.ts index 48c6a8b..a6f3b81 100644 --- a/src/main/config/profile.ts +++ b/src/main/config/profile.ts @@ -51,9 +51,14 @@ export async function addProfileItem(item: Partial): Promise 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 + if (profileConfig.items.length > 0) { + profileConfig.current = profileConfig.items[0]?.id + } else { + profileConfig.current = undefined + } } fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig)) + fs.rmSync(profilePath(id)) window?.webContents.send('profileConfigUpdated') } diff --git a/src/renderer/index.html b/src/renderer/index.html index a48de92..5927825 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -1,7 +1,7 @@ - + Mihomo Party Promise + mutateProfileConfig: () => void onClick: () => Promise } +interface MenuItem { + key: string + label: string + showDivider: boolean + color: 'default' | 'danger' + className: string +} const ProfileItem: React.FC = (props) => { - const { info, onClick, isCurrent } = props + const { info, removeProfileItem, mutateProfileConfig, onClick, isCurrent } = props const extra = info?.extra const usage = (extra?.upload ?? 0) + (extra?.download ?? 0) const total = extra?.total ?? 0 + const menuItems: MenuItem[] = useMemo(() => { + const list = [ + { + key: 'edit', + label: '编辑文件', + showDivider: true, + color: 'default', + className: '' + } as MenuItem, + { + key: 'delete', + label: '删除', + showDivider: false, + color: 'danger', + className: 'text-danger' + } as MenuItem + ] + if (info.home) { + list.unshift({ + key: 'home', + label: '主页', + showDivider: false, + color: 'default', + className: '' + } as MenuItem) + } + return list + }, [info]) + + const onMenuAction = (key: Key): void => { + switch (key) { + case 'edit': + break + case 'delete': { + removeProfileItem(info.id) + mutateProfileConfig() + break + } + + case 'home': { + open(info.home) + break + } + } + } + return ( - +

{info?.name}

- +
+ + + + + + + {menuItems.map((item) => ( + + {item.label} + + ))} + + +
+
+
+ {extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined} + {dayjs(info.updated).fromNow()}
- - + + {extra && ( + + )}
) diff --git a/src/renderer/src/components/sider/profile-card.tsx b/src/renderer/src/components/sider/profile-card.tsx index 9c2bf5f..6c528b3 100644 --- a/src/renderer/src/components/sider/profile-card.tsx +++ b/src/renderer/src/components/sider/profile-card.tsx @@ -3,6 +3,12 @@ import { useProfileConfig } from '@renderer/hooks/use-profile-config' import { useLocation, useNavigate } from 'react-router-dom' import { calcTraffic, calcPercent } from '@renderer/utils/calc' import { IoMdRefresh } from 'react-icons/io' +import relativeTime from 'dayjs/plugin/relativeTime' +import 'dayjs/locale/zh-cn' +import dayjs from 'dayjs' + +dayjs.extend(relativeTime) +dayjs.locale('zh-cn') const ProfileCard: React.FC = () => { const navigate = useNavigate() @@ -16,6 +22,7 @@ const ProfileCard: React.FC = () => { type: 'local', name: '空白订阅' } + const extra = info?.extra const usage = (extra?.upload ?? 0) + (extra?.download ?? 0) const total = extra?.total ?? 0 @@ -27,7 +34,7 @@ const ProfileCard: React.FC = () => { isPressable onPress={() => navigate('/profiles')} > - +

{info?.name} @@ -36,14 +43,19 @@ const ProfileCard: React.FC = () => {

+
+ {extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined} + {dayjs(info.updated).fromNow()} +
- - + + {extra && ( + + )} ) diff --git a/src/renderer/src/components/sider/tun-switcher.tsx b/src/renderer/src/components/sider/tun-switcher.tsx index c73c9c2..e008990 100644 --- a/src/renderer/src/components/sider/tun-switcher.tsx +++ b/src/renderer/src/components/sider/tun-switcher.tsx @@ -3,12 +3,8 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c import BorderSwitch from '@renderer/components/base/border-swtich' import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb' import { useLocation, useNavigate } from 'react-router-dom' -import { - platform, - encryptString, - patchMihomoConfig, - isEncryptionAvailable -} from '@renderer/utils/ipc' +import { encryptString, patchMihomoConfig, isEncryptionAvailable } from '@renderer/utils/ipc' +import { platform } from '@renderer/utils/init' import React, { useState } from 'react' import { useAppConfig } from '@renderer/hooks/use-app-config' import BasePasswordModal from '../base/base-password-modal' @@ -24,7 +20,7 @@ const TunSwitcher: React.FC = () => { const { enable } = tun || {} const onChange = async (enable: boolean): Promise => { - if (enable && (await platform()) !== 'win32') { + if (enable && platform !== 'win32') { const encryptionAvailable = await isEncryptionAvailable() if (!appConfig?.encryptedPassword && encryptionAvailable) { setOpenPasswordModal(true) diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 6b74a39..5f67e3f 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -3,18 +3,20 @@ import ReactDOM from 'react-dom/client' import { HashRouter } from 'react-router-dom' import { ThemeProvider as NextThemesProvider } from 'next-themes' import { NextUIProvider } from '@nextui-org/react' -import '@renderer/utils/init' +import { init } from '@renderer/utils/init' import '@renderer/assets/main.css' import App from '@renderer/App' -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - - - - - - - -) +init().then(() => { + ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + + + + + + + ) +}) diff --git a/src/renderer/src/pages/profiles.tsx b/src/renderer/src/pages/profiles.tsx index 67c0127..750057c 100644 --- a/src/renderer/src/pages/profiles.tsx +++ b/src/renderer/src/pages/profiles.tsx @@ -6,7 +6,13 @@ import { useState } from 'react' import { MdContentPaste } from 'react-icons/md' const Profiles: React.FC = () => { - const { profileConfig, addProfileItem, changeCurrentProfile } = useProfileConfig() + const { + profileConfig, + addProfileItem, + removeProfileItem, + changeCurrentProfile, + mutateProfileConfig + } = useProfileConfig() const { current, items } = profileConfig || {} const [importing, setImporting] = useState(false) const [url, setUrl] = useState('') @@ -55,6 +61,8 @@ const Profiles: React.FC = () => { { await changeCurrentProfile(item.id) diff --git a/src/renderer/src/utils/init.ts b/src/renderer/src/utils/init.ts index 63ed9d3..91f2144 100644 --- a/src/renderer/src/utils/init.ts +++ b/src/renderer/src/utils/init.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ + +import { getPlatform } from './ipc' const originError = console.error const originWarn = console.warn console.error = function (...args: any[]): void { @@ -13,3 +15,9 @@ console.warn = function (...args): void { } originWarn.call(console, args) } + +export let platform: NodeJS.Platform + +export async function init(): Promise { + platform = await getPlatform() +} diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index d8b0eea..3e027bb 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -115,7 +115,7 @@ export async function encryptString(str: string): Promise { return await window.electron.ipcRenderer.invoke('encryptString', str) } -export async function platform(): Promise { +export async function getPlatform(): Promise { return await window.electron.ipcRenderer.invoke('platform') }