Skip to content

Commit

Permalink
feat: support drag profile item (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kuingsmile authored Dec 2, 2023
1 parent b60f0e5 commit 0b8f9ed
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 28 deletions.
5 changes: 5 additions & 0 deletions backend/tauri/src/cmds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> 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<PrfOption>) -> CmdResult {
wrap_err!(feat::update_profile(index, option).await)
Expand Down
24 changes: 24 additions & 0 deletions backend/tauri/src/config/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![]);
Expand Down
1 change: 1 addition & 0 deletions backend/tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
53 changes: 52 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 34 additions & 12 deletions src/components/profile/profile-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand All @@ -28,6 +30,7 @@ const round = keyframes`
`;

interface Props {
id: string;
selected: boolean;
activating: boolean;
itemData: IProfileItem;
Expand All @@ -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<any>(null);
const [position, setPosition] = useState({ left: 0, top: 0 });
Expand Down Expand Up @@ -185,7 +191,12 @@ export const ProfileItem = (props: Props) => {
};

return (
<>
<Box
sx={{
transform: CSS.Transform.toString(transform),
transition,
}}
>
<ProfileBox
aria-selected={selected}
onClick={() => onSelect(false)}
Expand Down Expand Up @@ -216,15 +227,26 @@ export const ProfileItem = (props: Props) => {
)}

<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% - 40px)"
variant="h6"
component="h2"
noWrap
title={name}
>
{name}
</Typography>
</Box>

{/* only if has url can it be updated */}
{hasUrl && (
Expand Down Expand Up @@ -326,7 +348,7 @@ export const ProfileItem = (props: Props) => {
mode="yaml"
onClose={() => setFileOpen(false)}
/>
</>
</Box>
);
};

Expand Down
73 changes: 58 additions & 15 deletions src/pages/profiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -19,6 +32,7 @@ import {
getRuntimeLogs,
deleteProfile,
updateProfile,
reorderProfile,
} from "@/services/cmds";
import { atomLoadingCache } from "@/services/states";
import { closeAllConnections } from "@/services/api";
Expand All @@ -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 = {},
Expand Down Expand Up @@ -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态闪烁
Expand Down Expand Up @@ -295,21 +325,34 @@ const ProfilePage = () => {
</Button>
</Stack>

<Box sx={{ mb: 4.5 }}>
<Grid container spacing={{ xs: 2, lg: 3 }}>
{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: 3 }}>
Expand Down
7 changes: 7 additions & 0 deletions src/services/cmds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
Expand Down

0 comments on commit 0b8f9ed

Please sign in to comment.