support for sorting profile items

This commit is contained in:
pompurin404 2024-08-11 17:55:03 +08:00
parent 5c7f3f48f7
commit d434352bc3
No known key found for this signature in database
8 changed files with 110 additions and 30 deletions

View File

@ -5,6 +5,7 @@ export {
getCurrentProfileItem,
getProfileItem,
getProfileConfig,
setProfileConfig,
addProfileItem,
removeProfileItem,
createProfile,

View File

@ -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: '空白订阅' }

View File

@ -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))

View File

@ -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()

View File

@ -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>
)
}

View File

@ -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,

View File

@ -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>
)
}

View File

@ -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')
}