feat: Support Drag to Reorder the Profile (#29)

* feat: Support Drag to Reorder the Profile

* style: Remove unnecessary styles
This commit is contained in:
Pylogmon
2023-11-29 08:54:02 +08:00
committed by GitHub
parent 197f942b3f
commit 887f92babe
8 changed files with 208 additions and 40 deletions

View File

@@ -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 { Notice } from "@/components/base";
@@ -28,6 +30,7 @@ const round = keyframes`
`;
interface Props {
id: string;
selected: boolean;
activating: boolean;
itemData: IProfileItem;
@@ -37,6 +40,8 @@ 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<any>(null);
@@ -183,7 +188,12 @@ export const ProfileItem = (props: Props) => {
};
return (
<>
<Box
sx={{
transform: CSS.Transform.toString(transform),
transition,
}}
>
<ProfileBox
aria-selected={selected}
onClick={() => onSelect(false)}
@@ -212,17 +222,27 @@ export const ProfileItem = (props: Props) => {
<CircularProgress size={20} />
</Box>
)}
<Box position="relative">
<Typography
width="calc(100% - 36px)"
variant="h6"
component="h2"
noWrap
title={name}
>
{name}
</Typography>
<Box sx={{ display: "flex", justifyContent: "start" }}>
<Box
ref={setNodeRef}
sx={{ display: "flex", margin: "auto 0" }}
{...attributes}
{...listeners}
>
<DragIndicator sx={{ cursor: "grab" }} />
</Box>
<Typography
width="calc(100% - 36px)"
variant="h6"
component="h2"
noWrap
title={name}
>
{name}
</Typography>
</Box>
{/* only if has url can it be updated */}
{hasUrl && (
@@ -246,7 +266,6 @@ export const ProfileItem = (props: Props) => {
</IconButton>
)}
</Box>
{/* the second line show url's info or description */}
<Box sx={boxStyle}>
{hasUrl ? (
@@ -271,7 +290,6 @@ export const ProfileItem = (props: Props) => {
</Typography>
)}
</Box>
{/* the third line show extra info or last updated time */}
{hasExtra ? (
<Box sx={{ ...boxStyle, fontSize: 14 }}>
@@ -285,7 +303,6 @@ export const ProfileItem = (props: Props) => {
<span title="Updated Time">{parseExpire(updated)}</span>
</Box>
)}
<LinearProgress
variant="determinate"
value={progress}
@@ -324,7 +341,7 @@ export const ProfileItem = (props: Props) => {
mode="yaml"
onClose={() => setFileOpen(false)}
/>
</>
</Box>
);
};

View File

@@ -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";
@@ -40,7 +54,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 = {},
activateSelected,
@@ -106,6 +125,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态闪烁
@@ -293,22 +322,34 @@ const ProfilePage = () => {
{t("New")}
</Button>
</Stack>
<Box sx={{ mb: 4.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
{regularItems.map((item) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileItem
selected={profiles.current === item.uid}
activating={activating === item.uid}
itemData={item}
onSelect={(f) => onSelect(item.uid, f)}
onEdit={() => viewerRef.current?.edit(item)}
/>
</Grid>
))}
</Grid>
</Box>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={onDragEnd}
>
<Box sx={{ mb: 4.5 }}>
<Grid container spacing={{ xs: 1, lg: 1 }}>
<SortableContext
items={regularItems.map((x) => {
return x.uid;
})}
>
{regularItems.map((item) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
<ProfileItem
id={item.uid}
selected={profiles.current === item.uid}
activating={activating === item.uid}
itemData={item}
onSelect={(f) => onSelect(item.uid, f)}
onEdit={() => viewerRef.current?.edit(item)}
/>
</Grid>
))}
</SortableContext>
</Grid>
</Box>
</DndContext>
{enhanceItems.length > 0 && (
<Grid container spacing={{ xs: 2, lg: 2 }}>
@@ -330,7 +371,6 @@ const ProfilePage = () => {
))}
</Grid>
)}
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
<ConfigViewer ref={configRef} />
</BasePage>

View File

@@ -64,6 +64,13 @@ export async function importProfile(url: string) {
});
}
export async function reorderProfile(activeId: string, overId: string) {
return invoke<void>("reorder_profile", {
activeId,
overId,
});
}
export async function updateProfile(index: string, option?: IProfileOption) {
return invoke<void>("update_profile", { index, option });
}