mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
Initialization constant
This commit is contained in:
parent
ae5939d2aa
commit
279f8fe1d3
@ -51,9 +51,14 @@ export async function addProfileItem(item: Partial<IProfileItem>): Promise<void>
|
||||
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')
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta charset="UTF-8" lang="zh" />
|
||||
<title>Mihomo Party</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
|
||||
@ -1,39 +1,131 @@
|
||||
import { Button, Card, CardBody, CardFooter, Progress } from '@nextui-org/react'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownTrigger,
|
||||
Progress
|
||||
} from '@nextui-org/react'
|
||||
import { calcPercent, calcTraffic } from '@renderer/utils/calc'
|
||||
import React from 'react'
|
||||
import { IoMdRefresh } from 'react-icons/io'
|
||||
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||
import dayjs from 'dayjs'
|
||||
import React, { Key, useMemo } from 'react'
|
||||
|
||||
interface Props {
|
||||
info: IProfileItem
|
||||
isCurrent: boolean
|
||||
removeProfileItem: (id: string) => Promise<void>
|
||||
mutateProfileConfig: () => void
|
||||
onClick: () => Promise<void>
|
||||
}
|
||||
|
||||
interface MenuItem {
|
||||
key: string
|
||||
label: string
|
||||
showDivider: boolean
|
||||
color: 'default' | 'danger'
|
||||
className: string
|
||||
}
|
||||
const ProfileItem: React.FC<Props> = (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 (
|
||||
<Card fullWidth isPressable onPress={onClick} className={isCurrent ? 'bg-primary' : ''}>
|
||||
<CardBody>
|
||||
<CardBody className="pb-1">
|
||||
<div className="flex justify-between h-[32px]">
|
||||
<h3 className="select-none text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px]">
|
||||
{info?.name}
|
||||
</h3>
|
||||
<Button isIconOnly size="sm" variant="light" color="default">
|
||||
<IoMdRefresh color="default" className="text-[24px]" />
|
||||
</Button>
|
||||
<div className="flex">
|
||||
<Button isIconOnly size="sm" variant="light" color="default">
|
||||
<IoMdRefresh color="default" className="text-[24px]" />
|
||||
</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 className="mt-2 flex justify-between">
|
||||
<small>{extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined}</small>
|
||||
<small>{dayjs(info.updated).fromNow()}</small>
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="pt-1">
|
||||
<Progress
|
||||
classNames={{ indicator: 'bg-foreground', label: 'select-none' }}
|
||||
label={extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined}
|
||||
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
||||
className="max-w-md"
|
||||
/>
|
||||
<CardFooter className="pt-0">
|
||||
{extra && (
|
||||
<Progress
|
||||
className="w-full"
|
||||
classNames={{ indicator: 'bg-foreground', label: 'select-none' }}
|
||||
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
||||
/>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@ -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')}
|
||||
>
|
||||
<CardBody>
|
||||
<CardBody className="pb-1">
|
||||
<div className="flex justify-between h-[32px]">
|
||||
<h3 className="select-none text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px]">
|
||||
{info?.name}
|
||||
@ -36,14 +43,19 @@ const ProfileCard: React.FC = () => {
|
||||
<IoMdRefresh color="default" className="text-[24px]" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<small>{extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined}</small>
|
||||
<small>{dayjs(info.updated).fromNow()}</small>
|
||||
</div>
|
||||
</CardBody>
|
||||
<CardFooter className="pt-1">
|
||||
<Progress
|
||||
classNames={{ indicator: 'bg-foreground', label: 'select-none' }}
|
||||
label={extra ? `${calcTraffic(usage)}/${calcTraffic(total)}` : undefined}
|
||||
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
||||
className="max-w-md"
|
||||
/>
|
||||
<CardFooter className="pt-0">
|
||||
{extra && (
|
||||
<Progress
|
||||
className="w-full"
|
||||
classNames={{ indicator: 'bg-foreground', label: 'select-none' }}
|
||||
value={calcPercent(extra?.upload, extra?.download, extra?.total)}
|
||||
/>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@ -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<void> => {
|
||||
if (enable && (await platform()) !== 'win32') {
|
||||
if (enable && platform !== 'win32') {
|
||||
const encryptionAvailable = await isEncryptionAvailable()
|
||||
if (!appConfig?.encryptedPassword && encryptionAvailable) {
|
||||
setOpenPasswordModal(true)
|
||||
|
||||
@ -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(
|
||||
<React.StrictMode>
|
||||
<NextUIProvider>
|
||||
<NextThemesProvider attribute="class" defaultTheme="dark">
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
init().then(() => {
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<NextUIProvider>
|
||||
<NextThemesProvider attribute="class" defaultTheme="dark">
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
})
|
||||
|
||||
@ -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 = () => {
|
||||
<ProfileItem
|
||||
key={item.id}
|
||||
isCurrent={item.id === current}
|
||||
removeProfileItem={removeProfileItem}
|
||||
mutateProfileConfig={mutateProfileConfig}
|
||||
info={item}
|
||||
onClick={async () => {
|
||||
await changeCurrentProfile(item.id)
|
||||
|
||||
@ -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<void> {
|
||||
platform = await getPlatform()
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ export async function encryptString(str: string): Promise<Buffer> {
|
||||
return await window.electron.ipcRenderer.invoke('encryptString', str)
|
||||
}
|
||||
|
||||
export async function platform(): Promise<NodeJS.Platform> {
|
||||
export async function getPlatform(): Promise<NodeJS.Platform> {
|
||||
return await window.electron.ipcRenderer.invoke('platform')
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user