mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 13:10:30 +08:00
support for sorting profile items
This commit is contained in:
parent
5c7f3f48f7
commit
d434352bc3
@ -5,6 +5,7 @@ export {
|
|||||||
getCurrentProfileItem,
|
getCurrentProfileItem,
|
||||||
getProfileItem,
|
getProfileItem,
|
||||||
getProfileConfig,
|
getProfileConfig,
|
||||||
|
setProfileConfig,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
createProfile,
|
createProfile,
|
||||||
|
|||||||
@ -18,6 +18,12 @@ export function getProfileConfig(force = false): IProfileConfig {
|
|||||||
return profileConfig
|
return profileConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setProfileConfig(config: IProfileConfig): void {
|
||||||
|
profileConfig = config
|
||||||
|
window?.webContents.send('profileConfigUpdated')
|
||||||
|
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||||
|
}
|
||||||
|
|
||||||
export function getProfileItem(id: string | undefined): IProfileItem {
|
export function getProfileItem(id: string | undefined): IProfileItem {
|
||||||
const items = getProfileConfig().items
|
const items = getProfileConfig().items
|
||||||
return items?.find((item) => item.id === id) || { id: 'default', type: 'local', name: '空白订阅' }
|
return items?.find((item) => item.id === id) || { id: 'default', type: 'local', name: '空白订阅' }
|
||||||
|
|||||||
@ -33,7 +33,8 @@ import {
|
|||||||
changeCurrentProfile,
|
changeCurrentProfile,
|
||||||
getProfileStr,
|
getProfileStr,
|
||||||
setProfileStr,
|
setProfileStr,
|
||||||
updateProfileItem
|
updateProfileItem,
|
||||||
|
setProfileConfig
|
||||||
} 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'
|
||||||
@ -71,6 +72,7 @@ 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('setProfileConfig', (_e, config) => setProfileConfig(config))
|
||||||
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('getProfileStr', (_e, id) => getProfileStr(id))
|
||||||
|
|||||||
@ -49,12 +49,7 @@ const App: React.FC = () => {
|
|||||||
]
|
]
|
||||||
} = appConfig || {}
|
} = appConfig || {}
|
||||||
const [order, setOrder] = useState(siderOrder)
|
const [order, setOrder] = useState(siderOrder)
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(useSensor(PointerSensor))
|
||||||
useSensor(PointerSensor)
|
|
||||||
// useSensor(KeyboardSensor, {
|
|
||||||
// coordinateGetter: sortableKeyboardCoordinates
|
|
||||||
// })
|
|
||||||
)
|
|
||||||
const { setTheme } = useTheme()
|
const { setTheme } = useTheme()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|||||||
@ -13,9 +13,11 @@ 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, useState } from 'react'
|
import React, { Key, useEffect, useMemo, useState } from 'react'
|
||||||
import EditFileModal from './edit-file-modal'
|
import EditFileModal from './edit-file-modal'
|
||||||
import EditInfoModal from './edit-info-modal'
|
import EditInfoModal from './edit-info-modal'
|
||||||
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
info: IProfileItem
|
info: IProfileItem
|
||||||
@ -51,6 +53,10 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
const [selecting, setSelecting] = useState(false)
|
const [selecting, setSelecting] = useState(false)
|
||||||
const [openInfo, setOpenInfo] = useState(false)
|
const [openInfo, setOpenInfo] = useState(false)
|
||||||
const [openFile, setOpenFile] = useState(false)
|
const [openFile, setOpenFile] = useState(false)
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||||
|
id: info.id
|
||||||
|
})
|
||||||
|
const [disableSelect, setDisableSelect] = useState(false)
|
||||||
|
|
||||||
const menuItems: MenuItem[] = useMemo(() => {
|
const menuItems: MenuItem[] = useMemo(() => {
|
||||||
const list = [
|
const list = [
|
||||||
@ -111,8 +117,28 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDragging) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setDisableSelect(true)
|
||||||
|
}, 200)
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
setDisableSelect(false)
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
}, [isDragging])
|
||||||
|
|
||||||
return (
|
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)} />}
|
{openFile && <EditFileModal id={info.id} onClose={() => setOpenFile(false)} />}
|
||||||
{openInfo && (
|
{openInfo && (
|
||||||
<EditInfoModal
|
<EditInfoModal
|
||||||
@ -125,6 +151,7 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
isPressable
|
isPressable
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
if (disableSelect) return
|
||||||
setSelecting(true)
|
setSelecting(true)
|
||||||
onClick().finally(() => {
|
onClick().finally(() => {
|
||||||
setSelecting(false)
|
setSelecting(false)
|
||||||
@ -135,6 +162,9 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
<CardBody className="pb-1">
|
<CardBody className="pb-1">
|
||||||
<div className="flex justify-between h-[32px]">
|
<div className="flex justify-between h-[32px]">
|
||||||
<h3
|
<h3
|
||||||
|
ref={setNodeRef}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
className={`text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px] ${isCurrent ? 'text-white' : 'text-foreground'}`}
|
className={`text-ellipsis whitespace-nowrap overflow-hidden text-md font-bold leading-[32px] ${isCurrent ? 'text-white' : 'text-foreground'}`}
|
||||||
>
|
>
|
||||||
{info?.name}
|
{info?.name}
|
||||||
@ -219,7 +249,7 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import {
|
import {
|
||||||
getProfileConfig,
|
getProfileConfig,
|
||||||
|
setProfileConfig as set,
|
||||||
addProfileItem as add,
|
addProfileItem as add,
|
||||||
removeProfileItem as remove,
|
removeProfileItem as remove,
|
||||||
updateProfileItem as update,
|
updateProfileItem as update,
|
||||||
@ -10,6 +11,7 @@ import { useEffect } from 'react'
|
|||||||
|
|
||||||
interface RetuenType {
|
interface RetuenType {
|
||||||
profileConfig: IProfileConfig | undefined
|
profileConfig: IProfileConfig | undefined
|
||||||
|
setProfileConfig: (config: IProfileConfig) => Promise<void>
|
||||||
mutateProfileConfig: () => void
|
mutateProfileConfig: () => void
|
||||||
addProfileItem: (item: Partial<IProfileItem>) => Promise<void>
|
addProfileItem: (item: Partial<IProfileItem>) => Promise<void>
|
||||||
updateProfileItem: (item: IProfileItem) => Promise<void>
|
updateProfileItem: (item: IProfileItem) => Promise<void>
|
||||||
@ -22,6 +24,11 @@ export const useProfileConfig = (): RetuenType => {
|
|||||||
getProfileConfig()
|
getProfileConfig()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const setProfileConfig = async (config: IProfileConfig): Promise<void> => {
|
||||||
|
await set(config)
|
||||||
|
mutateProfileConfig()
|
||||||
|
}
|
||||||
|
|
||||||
const addProfileItem = async (item: Partial<IProfileItem>): Promise<void> => {
|
const addProfileItem = async (item: Partial<IProfileItem>): Promise<void> => {
|
||||||
await add(item)
|
await add(item)
|
||||||
mutateProfileConfig()
|
mutateProfileConfig()
|
||||||
@ -53,6 +60,7 @@ export const useProfileConfig = (): RetuenType => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
profileConfig,
|
profileConfig,
|
||||||
|
setProfileConfig,
|
||||||
mutateProfileConfig,
|
mutateProfileConfig,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
|
|||||||
@ -5,21 +5,32 @@ import { useProfileConfig } from '@renderer/hooks/use-profile-config'
|
|||||||
import { getFilePath, readTextFile } from '@renderer/utils/ipc'
|
import { getFilePath, readTextFile } from '@renderer/utils/ipc'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { MdContentPaste } from 'react-icons/md'
|
import { MdContentPaste } from 'react-icons/md'
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
closestCenter,
|
||||||
|
PointerSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
DragEndEvent
|
||||||
|
} from '@dnd-kit/core'
|
||||||
|
import { SortableContext } from '@dnd-kit/sortable'
|
||||||
|
|
||||||
const Profiles: React.FC = () => {
|
const Profiles: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
profileConfig,
|
profileConfig,
|
||||||
|
setProfileConfig,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
updateProfileItem,
|
updateProfileItem,
|
||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
changeCurrentProfile,
|
changeCurrentProfile,
|
||||||
mutateProfileConfig
|
mutateProfileConfig
|
||||||
} = useProfileConfig()
|
} = useProfileConfig()
|
||||||
const { current, items } = profileConfig || {}
|
const { current, items = [] } = profileConfig || {}
|
||||||
|
const [sortedItems, setSortedItems] = useState(items)
|
||||||
const [importing, setImporting] = useState(false)
|
const [importing, setImporting] = useState(false)
|
||||||
const [fileOver, setFileOver] = useState(false)
|
const [fileOver, setFileOver] = useState(false)
|
||||||
const [url, setUrl] = useState('')
|
const [url, setUrl] = useState('')
|
||||||
|
const sensors = useSensors(useSensor(PointerSensor))
|
||||||
const handleImport = async (): Promise<void> => {
|
const handleImport = async (): Promise<void> => {
|
||||||
setImporting(true)
|
setImporting(true)
|
||||||
try {
|
try {
|
||||||
@ -30,6 +41,21 @@ const Profiles: React.FC = () => {
|
|||||||
}
|
}
|
||||||
const pageRef = useRef<HTMLDivElement>(null)
|
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 setProfileConfig({ current, items: newOrder })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pageRef.current?.addEventListener('dragover', (e) => {
|
pageRef.current?.addEventListener('dragover', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -116,10 +142,16 @@ const Profiles: React.FC = () => {
|
|||||||
打开
|
打开
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
||||||
<div
|
<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`}
|
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`}
|
||||||
>
|
>
|
||||||
{items?.map((item) => (
|
<SortableContext
|
||||||
|
items={sortedItems.map((item) => {
|
||||||
|
return item.id
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{sortedItems.map((item) => (
|
||||||
<ProfileItem
|
<ProfileItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
isCurrent={item.id === current}
|
isCurrent={item.id === current}
|
||||||
@ -133,7 +165,9 @@ const Profiles: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
</SortableContext>
|
||||||
</div>
|
</div>
|
||||||
|
</DndContext>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,6 +102,10 @@ export async function getProfileConfig(force = false): Promise<IProfileConfig> {
|
|||||||
return await window.electron.ipcRenderer.invoke('getProfileConfig', force)
|
return await window.electron.ipcRenderer.invoke('getProfileConfig', force)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
||||||
|
return await window.electron.ipcRenderer.invoke('setProfileConfig', config)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getCurrentProfileItem(): Promise<IProfileItem> {
|
export async function getCurrentProfileItem(): Promise<IProfileItem> {
|
||||||
return await window.electron.ipcRenderer.invoke('getCurrentProfileItem')
|
return await window.electron.ipcRenderer.invoke('getCurrentProfileItem')
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user