From 161dc5f901b03af8d0b93acf4eeb9d5b5f1278d1 Mon Sep 17 00:00:00 2001
From: Timothy Carambat <rambat1010@gmail.com>
Date: Wed, 14 Feb 2024 12:31:43 -0800
Subject: [PATCH] Implement thread UI update (#722)

* Implement thread UI update

* remove comment
---
 .../ThreadContainer/ThreadItem/index.jsx      | 58 +++++++++++++------
 .../ThreadContainer/index.jsx                 | 43 +++++++++++---
 .../Sidebar/ActiveWorkspaces/index.jsx        |  8 +--
 3 files changed, 78 insertions(+), 31 deletions(-)

diff --git a/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx b/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx
index 29e4f67e0..5e9a66f40 100644
--- a/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx
+++ b/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx
@@ -7,13 +7,20 @@ import { useParams } from "react-router-dom";
 import truncate from "truncate";
 
 const THREAD_CALLOUT_DETAIL_WIDTH = 26;
-export default function ThreadItem({ workspace, thread, onRemove, hasNext }) {
+export default function ThreadItem({
+  idx,
+  activeIdx,
+  isActive,
+  workspace,
+  thread,
+  onRemove,
+  hasNext,
+}) {
   const optionsContainer = useRef(null);
-  const { slug, threadSlug = null } = useParams();
+  const { slug } = useParams();
   const [showOptions, setShowOptions] = useState(false);
   const [name, setName] = useState(thread.name);
 
-  const isActive = threadSlug === thread.slug;
   const linkTo = !thread.slug
     ? paths.workspace.chat(slug)
     : paths.workspace.thread(slug, thread.slug);
@@ -23,33 +30,48 @@ export default function ThreadItem({ workspace, thread, onRemove, hasNext }) {
       {/* Curved line Element and leader if required */}
       <div
         style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }}
-        className="border-l border-b border-slate-300 h-[50%] absolute top-0 left-2 rounded-bl-lg"
+        className={`${
+          isActive
+            ? "border-l-2 border-b-2 border-white"
+            : "border-l border-b border-slate-300"
+        } h-[50%] absolute top-0 left-2 rounded-bl-lg`}
       ></div>
+      {/* Downstroke border for next item */}
       {hasNext && (
         <div
           style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }}
-          className="border-l border-slate-300 h-[100%] absolute top-0 left-2"
+          className={`${
+            idx <= activeIdx && !isActive
+              ? "border-l-2 border-white"
+              : "border-l border-slate-300"
+          } h-[100%] absolute top-0 left-2`}
         ></div>
       )}
 
-      {/* Curved line inline placeholder for spacing */}
+      {/* Curved line inline placeholder for spacing - not visible */}
       <div
         style={{ width: THREAD_CALLOUT_DETAIL_WIDTH }}
         className="w-[26px] h-full"
       />
       <div className="flex w-full items-center justify-between pr-2 group relative">
-        <a href={isActive ? "#" : linkTo} className="w-full">
-          <p
-            className={`text-left text-sm ${
-              isActive
-                ? "font-semibold text-slate-300"
-                : "text-slate-400 italic"
-            }`}
-          >
-            {truncate(name, 25)}
-          </p>
-        </a>
-        {!!thread.slug && (
+        {thread.deleted ? (
+          <a className="w-full">
+            <p className={`text-left text-sm text-slate-400/50 italic`}>
+              deleted thread
+            </p>
+          </a>
+        ) : (
+          <a href={isActive ? "#" : linkTo} className="w-full">
+            <p
+              className={`text-left text-sm ${
+                isActive ? "font-bold text-white" : "text-slate-400"
+              }`}
+            >
+              {truncate(name, 25)}
+            </p>
+          </a>
+        )}
+        {!!thread.slug && !thread.deleted && (
           <div ref={optionsContainer}>
             <div className="flex items-center w-fit group-hover:visible md:invisible gap-x-1">
               <button
diff --git a/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/index.jsx b/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/index.jsx
index 5667d639e..50205ef9e 100644
--- a/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/index.jsx
+++ b/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/index.jsx
@@ -4,8 +4,10 @@ import showToast from "@/utils/toast";
 import { Plus, CircleNotch } from "@phosphor-icons/react";
 import { useEffect, useState } from "react";
 import ThreadItem from "./ThreadItem";
+import { useParams } from "react-router-dom";
 
 export default function ThreadContainer({ workspace }) {
+  const { threadSlug = null } = useParams();
   const [threads, setThreads] = useState([]);
   const [loading, setLoading] = useState(true);
 
@@ -20,7 +22,12 @@ export default function ThreadContainer({ workspace }) {
   }, [workspace.slug]);
 
   function removeThread(threadId) {
-    setThreads((prev) => prev.filter((thread) => thread.id !== threadId));
+    setThreads((prev) =>
+      prev.map((_t) => {
+        if (_t.id !== threadId) return _t;
+        return { ..._t, deleted: true };
+      })
+    );
   }
 
   if (loading) {
@@ -33,15 +40,26 @@ export default function ThreadContainer({ workspace }) {
     );
   }
 
+  const activeThreadIdx = !!threads.find(
+    (thread) => thread?.slug === threadSlug
+  )
+    ? threads.findIndex((thread) => thread?.slug === threadSlug) + 1
+    : 0;
   return (
     <div className="flex flex-col">
       <ThreadItem
+        idx={0}
+        activeIdx={activeThreadIdx}
+        isActive={activeThreadIdx === 0}
         thread={{ slug: null, name: "default" }}
         hasNext={threads.length > 0}
       />
       {threads.map((thread, i) => (
         <ThreadItem
           key={thread.slug}
+          idx={i + 1}
+          activeIdx={activeThreadIdx}
+          isActive={activeThreadIdx === i + 1}
           workspace={workspace}
           onRemove={removeThread}
           thread={thread}
@@ -54,7 +72,7 @@ export default function ThreadContainer({ workspace }) {
 }
 
 function NewThreadButton({ workspace }) {
-  const [loading, setLoading] = useState();
+  const [loading, setLoading] = useState(false);
   const onClick = async () => {
     setLoading(true);
     const { thread, error } = await Workspace.threads.new(workspace.slug);
@@ -74,15 +92,22 @@ function NewThreadButton({ workspace }) {
       className="w-full relative flex h-[40px] items-center border-none hover:bg-slate-600/20 rounded-lg"
     >
       <div className="flex w-full gap-x-2 items-center pl-4">
+        <div className="bg-zinc-600 p-2 rounded-lg h-[24px] w-[24px] flex items-center justify-center">
+          {loading ? (
+            <CircleNotch
+              weight="bold"
+              size={14}
+              className="shrink-0 animate-spin text-slate-100"
+            />
+          ) : (
+            <Plus weight="bold" size={14} className="shrink-0 text-slate-100" />
+          )}
+        </div>
+
         {loading ? (
-          <CircleNotch className="animate-spin text-slate-300" />
+          <p className="text-left text-slate-100 text-sm">Starting Thread...</p>
         ) : (
-          <Plus className="text-slate-300" />
-        )}
-        {loading ? (
-          <p className="text-left text-slate-300 text-sm">starting thread...</p>
-        ) : (
-          <p className="text-left text-slate-300 text-sm">new thread</p>
+          <p className="text-left text-slate-100 text-sm">New Thread</p>
         )}
       </div>
     </button>
diff --git a/frontend/src/components/Sidebar/ActiveWorkspaces/index.jsx b/frontend/src/components/Sidebar/ActiveWorkspaces/index.jsx
index 4603009bd..54580a2af 100644
--- a/frontend/src/components/Sidebar/ActiveWorkspaces/index.jsx
+++ b/frontend/src/components/Sidebar/ActiveWorkspaces/index.jsx
@@ -80,12 +80,12 @@ export default function ActiveWorkspaces() {
                 href={isActive ? null : paths.workspace.chat(workspace.slug)}
                 className={`
               transition-all duration-[200ms]
-                flex flex-grow w-[75%] gap-x-2 py-[6px] px-[12px] rounded-lg text-slate-200 justify-start items-center border
-                hover:bg-workspace-item-selected-gradient hover:border-slate-100 hover:border-opacity-50
+                flex flex-grow w-[75%] gap-x-2 py-[6px] px-[12px] rounded-lg text-slate-200 justify-start items-center 
+                hover:bg-workspace-item-selected-gradient 
                 ${
                   isActive
-                    ? "bg-workspace-item-selected-gradient border-slate-100 border-opacity-50"
-                    : "bg-workspace-item-gradient bg-opacity-60 border-transparent"
+                    ? "border-2 bg-workspace-item-selected-gradient border-white"
+                    : "border bg-workspace-item-gradient bg-opacity-60 border-transparent hover:border-slate-100 hover:border-opacity-50"
                 }`}
               >
                 <div className="flex flex-row justify-between w-full">