From d434352bc30ce55cb6d1082ccdbcd39010bce0ed Mon Sep 17 00:00:00 2001 From: pompurin404 Date: Sun, 11 Aug 2024 17:55:03 +0800 Subject: [PATCH] support for sorting profile items --- src/main/config/index.ts | 1 + src/main/config/profile.ts | 6 ++ src/main/utils/ipc.ts | 4 +- src/renderer/src/App.tsx | 7 +- .../src/components/profiles/profile-item.tsx | 36 ++++++++- src/renderer/src/hooks/use-profile-config.tsx | 8 ++ src/renderer/src/pages/profiles.tsx | 74 ++++++++++++++----- src/renderer/src/utils/ipc.ts | 4 + 8 files changed, 110 insertions(+), 30 deletions(-) diff --git a/src/main/config/index.ts b/src/main/config/index.ts index e74b4c2..fd5d173 100644 --- a/src/main/config/index.ts +++ b/src/main/config/index.ts @@ -5,6 +5,7 @@ export { getCurrentProfileItem, getProfileItem, getProfileConfig, + setProfileConfig, addProfileItem, removeProfileItem, createProfile, diff --git a/src/main/config/profile.ts b/src/main/config/profile.ts index 4ff7955..d00c19f 100644 --- a/src/main/config/profile.ts +++ b/src/main/config/profile.ts @@ -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: '空白订阅' } diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index ac4055d..974ec19 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -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)) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 0d48a57..e1fda3c 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -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() diff --git a/src/renderer/src/components/profiles/profile-item.tsx b/src/renderer/src/components/profiles/profile-item.tsx index f819fb2..da8f6d5 100644 --- a/src/renderer/src/components/profiles/profile-item.tsx +++ b/src/renderer/src/components/profiles/profile-item.tsx @@ -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) => { 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) => { } } + useEffect(() => { + if (isDragging) { + setTimeout(() => { + setDisableSelect(true) + }, 200) + } else { + setTimeout(() => { + setDisableSelect(false) + }, 200) + } + }, [isDragging]) + return ( - <> +
{openFile && setOpenFile(false)} />} {openInfo && ( = (props) => { fullWidth isPressable onPress={() => { + if (disableSelect) return setSelecting(true) onClick().finally(() => { setSelecting(false) @@ -135,6 +162,9 @@ const ProfileItem: React.FC = (props) => {

{info?.name} @@ -219,7 +249,7 @@ const ProfileItem: React.FC = (props) => { )} - +

) } diff --git a/src/renderer/src/hooks/use-profile-config.tsx b/src/renderer/src/hooks/use-profile-config.tsx index ce47f54..62b851a 100644 --- a/src/renderer/src/hooks/use-profile-config.tsx +++ b/src/renderer/src/hooks/use-profile-config.tsx @@ -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 mutateProfileConfig: () => void addProfileItem: (item: Partial) => Promise updateProfileItem: (item: IProfileItem) => Promise @@ -22,6 +24,11 @@ export const useProfileConfig = (): RetuenType => { getProfileConfig() ) + const setProfileConfig = async (config: IProfileConfig): Promise => { + await set(config) + mutateProfileConfig() + } + const addProfileItem = async (item: Partial): Promise => { await add(item) mutateProfileConfig() @@ -53,6 +60,7 @@ export const useProfileConfig = (): RetuenType => { return { profileConfig, + setProfileConfig, mutateProfileConfig, addProfileItem, removeProfileItem, diff --git a/src/renderer/src/pages/profiles.tsx b/src/renderer/src/pages/profiles.tsx index b9c76c5..c175817 100644 --- a/src/renderer/src/pages/profiles.tsx +++ b/src/renderer/src/pages/profiles.tsx @@ -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 => { setImporting(true) try { @@ -30,6 +41,21 @@ const Profiles: React.FC = () => { } const pageRef = useRef(null) + const onDragEnd = async (event: DragEndEvent): Promise => { + 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 = () => { 打开
-
- {items?.map((item) => ( - { - await changeCurrentProfile(item.id) - }} - /> - ))} -
+ +
+ { + return item.id + })} + > + {sortedItems.map((item) => ( + { + await changeCurrentProfile(item.id) + }} + /> + ))} + +
+
) } diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index 532d81c..4485836 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -102,6 +102,10 @@ export async function getProfileConfig(force = false): Promise { return await window.electron.ipcRenderer.invoke('getProfileConfig', force) } +export async function setProfileConfig(config: IProfileConfig): Promise { + return await window.electron.ipcRenderer.invoke('setProfileConfig', config) +} + export async function getCurrentProfileItem(): Promise { return await window.electron.ipcRenderer.invoke('getCurrentProfileItem') }