From 0b8f9ed6244b3ac573be58a0ad91a1fe810cf7e6 Mon Sep 17 00:00:00 2001 From: Kuingsmile Date: Fri, 1 Dec 2023 16:31:43 -0800 Subject: [PATCH] feat: support drag profile item (#36) --- backend/tauri/src/cmds.rs | 5 ++ backend/tauri/src/config/profiles.rs | 24 ++++++++ backend/tauri/src/main.rs | 1 + package.json | 3 + pnpm-lock.yaml | 53 +++++++++++++++++- src/components/profile/profile-item.tsx | 46 ++++++++++++---- src/pages/profiles.tsx | 73 ++++++++++++++++++++----- src/services/cmds.ts | 7 +++ 8 files changed, 184 insertions(+), 28 deletions(-) diff --git a/backend/tauri/src/cmds.rs b/backend/tauri/src/cmds.rs index 46cddd6c20..bd549ad1ee 100644 --- a/backend/tauri/src/cmds.rs +++ b/backend/tauri/src/cmds.rs @@ -36,6 +36,11 @@ pub async fn create_profile(item: PrfItem, file_data: Option) -> CmdResu wrap_err!(Config::profiles().data().append_item(item)) } +#[tauri::command] +pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult { + wrap_err!(Config::profiles().data().reorder(active_id, over_id)) +} + #[tauri::command] pub async fn update_profile(index: String, option: Option) -> CmdResult { wrap_err!(feat::update_profile(index, option).await) diff --git a/backend/tauri/src/config/profiles.rs b/backend/tauri/src/config/profiles.rs index 74ffa814d8..417d608940 100644 --- a/backend/tauri/src/config/profiles.rs +++ b/backend/tauri/src/config/profiles.rs @@ -155,6 +155,30 @@ impl IProfiles { self.save_file() } + /// reorder items + pub fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> { + let mut items = self.items.take().unwrap_or(vec![]); + let mut old_index = None; + let mut new_index = None; + + for i in 0..items.len() { + if items[i].uid == Some(active_id.clone()) { + old_index = Some(i); + } + if items[i].uid == Some(over_id.clone()) { + new_index = Some(i); + } + } + + if old_index.is_none() || new_index.is_none() { + return Ok(()); + } + let item = items.remove(old_index.unwrap()); + items.insert(new_index.unwrap(), item); + self.items = Some(items); + self.save_file() + } + /// update the item value pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> { let mut items = self.items.take().unwrap_or(vec![]); diff --git a/backend/tauri/src/main.rs b/backend/tauri/src/main.rs index e78a8ae9b7..3a84b3d253 100644 --- a/backend/tauri/src/main.rs +++ b/backend/tauri/src/main.rs @@ -59,6 +59,7 @@ fn main() -> std::io::Result<()> { cmds::patch_profile, cmds::create_profile, cmds::import_profile, + cmds::reorder_profile, cmds::update_profile, cmds::delete_profile, cmds::read_profile_file, diff --git a/package.json b/package.json index 4d8aef6d7a..5af94d9da7 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,9 @@ "tabWidth": 2 }, "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@juggle/resize-observer": "^3.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b4aae2247..295e24702c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,15 @@ settings: excludeLinksFromLockfile: false dependencies: + '@dnd-kit/core': + specifier: ^6.1.0 + version: 6.1.0(react-dom@18.2.0)(react@18.2.0) + '@dnd-kit/sortable': + specifier: ^8.0.0 + version: 8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@18.2.0) '@emotion/react': specifier: ^11.11.1 version: 11.11.1(@types/react@18.2.37)(react@18.2.0) @@ -695,6 +704,49 @@ packages: postcss-selector-parser: 6.0.13 dev: true + /@dnd-kit/accessibility@3.1.0(react@18.2.0): + resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /@dnd-kit/core@6.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@dnd-kit/accessibility': 3.1.0(react@18.2.0) + '@dnd-kit/utilities': 3.2.2(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tslib: 2.6.2 + dev: false + + /@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0)(react@18.2.0): + resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==} + peerDependencies: + '@dnd-kit/core': ^6.1.0 + react: '>=16.8.0' + dependencies: + '@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0) + '@dnd-kit/utilities': 3.2.2(react@18.2.0) + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /@dnd-kit/utilities@3.2.2(react@18.2.0): + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + tslib: 2.6.2 + dev: false + /@emotion/babel-plugin@11.11.0: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: @@ -6038,7 +6090,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: true /tsx@4.5.0: resolution: {integrity: sha512-hgxdziy9KLaHh9KE+a6tIZFP6kb0MLq/1D0sJVifbGP4QVEYhy6+2FNn7MyCm1pMc63p9CW/L1OzdqTNPxs6rg==} diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index b2ad052f2a..39160d61e0 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from "react"; import { useLockFn } from "ahooks"; import { useRecoilState } from "recoil"; import { useTranslation } from "react-i18next"; +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; import { Box, Typography, @@ -14,7 +16,7 @@ import { Menu, CircularProgress, } from "@mui/material"; -import { RefreshRounded } from "@mui/icons-material"; +import { RefreshRounded, DragIndicator } from "@mui/icons-material"; import { atomLoadingCache } from "@/services/states"; import { updateProfile, deleteProfile, viewProfile } from "@/services/cmds"; import { EditorViewer } from "./editor-viewer"; @@ -28,6 +30,7 @@ const round = keyframes` `; interface Props { + id: string; selected: boolean; activating: boolean; itemData: IProfileItem; @@ -38,6 +41,9 @@ interface Props { export const ProfileItem = (props: Props) => { const { selected, activating, itemData, onSelect, onEdit } = props; + const { attributes, listeners, setNodeRef, transform, transition } = + useSortable({ id: props.id }); + const { t } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); const [position, setPosition] = useState({ left: 0, top: 0 }); @@ -185,7 +191,12 @@ export const ProfileItem = (props: Props) => { }; return ( - <> + onSelect(false)} @@ -216,15 +227,26 @@ export const ProfileItem = (props: Props) => { )} - - {name} - + + + + + + + {name} + + {/* only if has url can it be updated */} {hasUrl && ( @@ -326,7 +348,7 @@ export const ProfileItem = (props: Props) => { mode="yaml" onClose={() => setFileOpen(false)} /> - + ); }; diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index ddbcbca0cb..1f54dce1f8 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -3,6 +3,19 @@ import { useMemo, useRef, useState } from "react"; import { useLockFn } from "ahooks"; import { useSetRecoilState } from "recoil"; import { Box, Button, Grid, IconButton, Stack, TextField } from "@mui/material"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragEndEvent, +} from "@dnd-kit/core"; +import { + SortableContext, + sortableKeyboardCoordinates, +} from "@dnd-kit/sortable"; import { LoadingButton } from "@mui/lab"; import { ClearRounded, @@ -19,6 +32,7 @@ import { getRuntimeLogs, deleteProfile, updateProfile, + reorderProfile, } from "@/services/cmds"; import { atomLoadingCache } from "@/services/states"; import { closeAllConnections } from "@/services/api"; @@ -41,6 +55,12 @@ const ProfilePage = () => { const [disabled, setDisabled] = useState(false); const [activating, setActivating] = useState(""); const [loading, setLoading] = useState(false); + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); const { profiles = {}, @@ -107,6 +127,16 @@ const ProfilePage = () => { } }; + const onDragEnd = async (event: DragEndEvent) => { + const { active, over } = event; + if (over) { + if (active.id !== over.id) { + await reorderProfile(active.id.toString(), over.id.toString()); + mutateProfiles(); + } + } + }; + const onSelect = useLockFn(async (current: string, force: boolean) => { if (!force && current === profiles.current) return; // 避免大多数情况下loading态闪烁 @@ -295,21 +325,34 @@ const ProfilePage = () => { - - - {regularItems.map((item) => ( - - onSelect(item.uid, f)} - onEdit={() => viewerRef.current?.edit(item)} - /> - - ))} - - + + + + { + return x.uid; + })} + > + {regularItems.map((item) => ( + + onSelect(item.uid, f)} + onEdit={() => viewerRef.current?.edit(item)} + /> + + ))} + + + + {enhanceItems.length > 0 && ( diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 53d5d915e5..1e1222dd93 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -64,6 +64,13 @@ export async function importProfile(url: string) { }); } +export async function reorderProfile(activeId: string, overId: string) { + return invoke("reorder_profile", { + activeId, + overId, + }); +} + export async function updateProfile(index: string, option?: IProfileOption) { return invoke("update_profile", { index, option }); }