forked from XiaoMo/ChatGPT-Next-Web
feat(dnd): add drag and drop feature
This commit is contained in:
parent
3490c294dc
commit
301cbbfdfb
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user