Forráskód Böngészése

feat(dnd): add drag and drop feature

xiaotianxt 1 éve
szülő
commit
301cbbfdfb
3 módosított fájl, 108 hozzáadás és 45 törlés
  1. 81 44
      app/components/chat-list.tsx
  2. 1 1
      app/components/home.module.scss
  3. 26 0
      app/store/app.ts

+ 81 - 44
app/components/chat-list.tsx

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

+ 1 - 1
app/components/home.module.scss

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

+ 26 - 0
app/store/app.ts

@@ -189,6 +189,7 @@ interface ChatStore {
   currentSessionIndex: number;
   clearSessions: () => void;
   removeSession: (index: number) => void;
+  moveSession: (from: number, to: number) => void;
   selectSession: (index: number) => void;
   newSession: () => void;
   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() {
         set((state) => ({
           currentSessionIndex: 0,