feat(dnd): add drag and drop feature

This commit is contained in:
xiaotianxt 2023-04-06 01:34:46 +08:00
parent 3490c294dc
commit 301cbbfdfb
No known key found for this signature in database
GPG Key ID: 66F5B0F68944334A
3 changed files with 108 additions and 45 deletions

View File

@ -1,14 +1,13 @@
import { useState, useRef, useEffect, useLayoutEffect } from "react";
import DeleteIcon from "../icons/delete.svg"; import DeleteIcon from "../icons/delete.svg";
import styles from "./home.module.scss"; import styles from "./home.module.scss";
import { import {
Message, DragDropContext,
SubmitKey, Droppable,
useChatStore, Draggable,
ChatSession, OnDragEndResponder,
BOT_HELLO, } from "@hello-pangea/dnd";
} from "../store";
import { useChatStore } from "../store";
import Locale from "../locales"; import Locale from "../locales";
import { isMobileScreen } from "../utils"; import { isMobileScreen } from "../utils";
@ -20,54 +19,92 @@ export function ChatItem(props: {
count: number; count: number;
time: string; time: string;
selected: boolean; selected: boolean;
id: number;
index: number;
}) { }) {
return ( return (
<div <Draggable draggableId={`${props.id}`} index={props.index}>
className={`${styles["chat-item"]} ${ {(provided) => (
props.selected && styles["chat-item-selected"] <div
}`} className={`${styles["chat-item"]} ${
onClick={props.onClick} props.selected && styles["chat-item-selected"]
> }`}
<div className={styles["chat-item-title"]}>{props.title}</div> onClick={props.onClick}
<div className={styles["chat-item-info"]}> ref={provided.innerRef}
<div className={styles["chat-item-count"]}> {...provided.draggableProps}
{Locale.ChatItem.ChatItemCount(props.count)} {...provided.dragHandleProps}
>
<div className={styles["chat-item-title"]}>{props.title}</div>
<div className={styles["chat-item-info"]}>
<div className={styles["chat-item-count"]}>
{Locale.ChatItem.ChatItemCount(props.count)}
</div>
<div className={styles["chat-item-date"]}>{props.time}</div>
</div>
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
<DeleteIcon />
</div>
</div> </div>
<div className={styles["chat-item-date"]}>{props.time}</div> )}
</div> </Draggable>
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
<DeleteIcon />
</div>
</div>
); );
} }
export function ChatList() { export function ChatList() {
const [sessions, selectedIndex, selectSession, removeSession] = useChatStore( const [sessions, selectedIndex, selectSession, removeSession, moveSession] =
(state) => [ useChatStore((state) => [
state.sessions, state.sessions,
state.currentSessionIndex, state.currentSessionIndex,
state.selectSession, state.selectSession,
state.removeSession, state.removeSession,
], state.moveSession,
); ]);
const onDragEnd: OnDragEndResponder = (result) => {
const { destination, source } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
moveSession(source.index, destination.index);
};
return ( return (
<div className={styles["chat-list"]}> <DragDropContext onDragEnd={onDragEnd}>
{sessions.map((item, i) => ( <Droppable droppableId="chat-list">
<ChatItem {(provided) => (
title={item.topic} <div
time={item.lastUpdate} className={styles["chat-list"]}
count={item.messages.length} ref={provided.innerRef}
key={i} {...provided.droppableProps}
selected={i === selectedIndex} >
onClick={() => selectSession(i)} {sessions.map((item, i) => (
onDelete={() => <ChatItem
(!isMobileScreen() || confirm(Locale.Home.DeleteChat)) && title={item.topic}
removeSession(i) time={item.lastUpdate}
} count={item.messages.length}
/> key={item.id}
))} id={item.id}
</div> index={i}
selected={i === selectedIndex}
onClick={() => selectSession(i)}
onDelete={() =>
(!isMobileScreen() || confirm(Locale.Home.DeleteChat)) &&
removeSession(i)
}
/>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
); );
} }

View File

@ -125,7 +125,7 @@
border-radius: 10px; border-radius: 10px;
margin-bottom: 10px; margin-bottom: 10px;
box-shadow: var(--card-shadow); box-shadow: var(--card-shadow);
transition: all 0.3s ease; transition: background-color 0.3s ease;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
border: 2px solid transparent; border: 2px solid transparent;

View File

@ -189,6 +189,7 @@ interface ChatStore {
currentSessionIndex: number; currentSessionIndex: number;
clearSessions: () => void; clearSessions: () => void;
removeSession: (index: number) => void; removeSession: (index: number) => void;
moveSession: (from: number, to: number) => void;
selectSession: (index: number) => void; selectSession: (index: number) => void;
newSession: () => void; newSession: () => void;
currentSession: () => ChatSession; currentSession: () => ChatSession;
@ -278,6 +279,31 @@ export const useChatStore = create<ChatStore>()(
}); });
}, },
moveSession(from: number, to: number) {
set((state) => {
const { sessions, currentSessionIndex: oldIndex } = state;
// move the session
const newSessions = [...sessions];
const session = newSessions[from];
newSessions.splice(from, 1);
newSessions.splice(to, 0, session);
// modify current session id
let newIndex = oldIndex === from ? to : oldIndex;
if (oldIndex > from && oldIndex <= to) {
newIndex -= 1;
} else if (oldIndex < from && oldIndex >= to) {
newIndex += 1;
}
return {
currentSessionIndex: newIndex,
sessions: newSessions,
};
});
},
newSession() { newSession() {
set((state) => ({ set((state) => ({
currentSessionIndex: 0, currentSessionIndex: 0,