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