feat: implement conversation tabs in header

This commit is contained in:
steven
2023-05-22 17:16:29 +08:00
parent 1af59eca28
commit cfff86cab3
3 changed files with 166 additions and 28 deletions

View File

@@ -203,6 +203,7 @@ const ConnectionSidebar = () => {
</div>
)
))}
{/* TODO(steven): remove this after we finish left sidebar */}
<ConversationList />
</div>
<div className="sticky bottom-0 w-full flex flex-col justify-center bg-gray-100 dark:bg-zinc-700 backdrop-blur bg-opacity-60 pb-4 py-2">

View File

@@ -0,0 +1,131 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useConnectionStore, useConversationStore, useLayoutStore } from "@/store";
import { Conversation } from "@/types";
import Icon from "./Icon";
import Dropdown, { DropdownItem } from "./kit/Dropdown";
import UpdateConversationModal from "./UpdateConversationModal";
interface ConversationTabProps {
conversation: Conversation;
selected: boolean;
onClick: (conversation: Conversation) => void;
onEdit: (conversation: Conversation) => void;
onDelete: (conversation: Conversation) => void;
}
const ConversationTab = (props: ConversationTabProps) => {
const { conversation, selected, onClick, onEdit, onDelete } = props;
const { t } = useTranslation();
return (
<div
className={`shrink-0 flex flex-row justify-center items-center cursor-pointer text-sm border px-2 py-1 rounded-md text-gray-600 dark:text-gray-400 hover:text-gray-700 hover:bg-gray-50 dark:hover:bg-zinc-700 dark:border-zinc-700 ${
selected && "!bg-indigo-600 !text-white !border-transparent shadow"
}`}
onClick={() => onClick(conversation)}
>
<span>{conversation.title}</span>
<Dropdown
tigger={
<button className="w-4 h-auto shrink-0 ml-0.5 flex justify-center items-center text-gray-300 hover:opacity-80">
<Icon.FiMoreVertical className="w-full h-auto" />
</button>
}
>
<div className="p-1 flex flex-col justify-start items-start bg-white dark:bg-zinc-900 shadow-lg rounded-lg">
<DropdownItem
className="w-full p-1 px-2 flex flex-row justify-start items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-800"
onClick={() => onEdit(conversation)}
>
<Icon.FiEdit3 className="w-4 h-auto mr-2 opacity-70" />
{t("common.edit")}
</DropdownItem>
<DropdownItem
className="w-full p-1 px-2 flex flex-row justify-start items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-800"
onClick={() => onDelete(conversation)}
>
<Icon.IoTrash className="w-4 h-auto mr-2 opacity-70" />
{t("common.delete")}
</DropdownItem>
</div>
</Dropdown>
</div>
);
};
const ConversationTabsView = () => {
const { t } = useTranslation();
const layoutStore = useLayoutStore();
const connectionStore = useConnectionStore();
const conversationStore = useConversationStore();
const [updateConversationModalContext, setUpdateConversationModalContext] = useState<Conversation | undefined>(undefined);
const currentConnectionCtx = connectionStore.currentConnectionCtx;
const conversationList = conversationStore.conversationList.filter(
(conversation) =>
conversation.connectionId === currentConnectionCtx?.connection.id &&
conversation.databaseName === currentConnectionCtx?.database?.name
);
const handleCreateConversation = () => {
if (!currentConnectionCtx) {
conversationStore.createConversation();
} else {
conversationStore.createConversation(currentConnectionCtx.connection.id, currentConnectionCtx.database?.name);
}
};
const handleConversationClick = (conversation: Conversation) => {
conversationStore.setCurrentConversationId(conversation.id);
if (layoutStore.isMobileView) {
layoutStore.toggleSidebar(false);
}
};
const handleEditConversation = (conversation: Conversation) => {
setUpdateConversationModalContext(conversation);
};
const handleDeleteConversation = (conversation: Conversation) => {
conversationStore.clearConversation((item) => item.id !== conversation.id);
if (conversationStore.currentConversationId === conversation.id) {
conversationStore.setCurrentConversationId(undefined);
}
};
return (
<>
<div className="w-full flex flex-row justify-start items-center flex-nowrap px-4 mt-1">
<div className="w-auto flex flex-row justify-start items-center overflow-x-auto py-1 gap-x-2">
{conversationList.map((conversation) => {
return (
<ConversationTab
key={conversation.id}
conversation={conversation}
selected={conversation.id === conversationStore.currentConversationId}
onClick={handleConversationClick}
onEdit={handleEditConversation}
onDelete={handleDeleteConversation}
/>
);
})}
</div>
<div className={`shrink-0 my-2 ${conversationList.length > 0 && "ml-2"}`}>
<button
className="border px-2 py-1.5 text-sm rounded-md flex flex-row justify-center items-center text-gray-500 dark:text-gray-400 hover:text-gray-700 hover:bg-gray-50 dark:hover:bg-zinc-700 dark:border-zinc-700"
onClick={handleCreateConversation}
>
<Icon.AiOutlinePlus className="w-4 h-auto" />
{conversationList.length === 0 && t("conversation.new-chat")}
</button>
</div>
</div>
{updateConversationModalContext && (
<UpdateConversationModal close={() => setUpdateConversationModalContext(undefined)} conversation={updateConversationModalContext} />
)}
</>
);
};
export default ConversationTabsView;

View File

@@ -5,6 +5,7 @@ import { hasFeature } from "@/utils";
import Icon from "../Icon";
import GitHubStarBadge from "../GitHubStarBadge";
import SchemaDrawer from "../SchemaDrawer";
import ConversationTabsView from "../ConversationTabsView";
interface Props {
className?: string;
@@ -25,36 +26,41 @@ const Header = (props: Props) => {
return (
<>
<div
className={`${
className || ""
} w-full flex flex-row justify-between items-center lg:grid lg:grid-cols-3 py-1 border-b dark:border-zinc-700 z-1`}
>
<div className="ml-2 flex justify-start items-center">
<button
className="w-8 h-8 p-1 mr-1 block lg:hidden rounded-md cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-700"
onClick={() => layoutStore.toggleSidebar()}
>
<Icon.IoIosMenu className="text-gray-600 w-full h-auto" />
</button>
<span className="w-auto text-left block lg:hidden">{title}</span>
<GitHubStarBadge className="hidden lg:flex ml-2" />
</div>
<span className="w-auto text-center h-8 p-1 hidden lg:block">{title}</span>
<div className="mr-2 sm:mr-3 relative flex flex-row justify-end items-center">
<a
href="https://www.bytebase.com?source=sqlchat"
className="hidden sm:flex flex-row justify-center items-center h-10 px-3 py-1 rounded-md whitespace-nowrap hover:bg-gray-100 dark:hover:bg-zinc-700"
target="_blank"
>
<img className="h-5 sm:h-6 w-auto" src={isDarkMode ? "/craft-by-bytebase-dark-mode.webp" : "/craft-by-bytebase.webp"} alt="" />
</a>
{hasFeature("debug") && (
<button className="p-2 rounded cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-700">
<Icon.FiSettings className="w-5 h-auto" onClick={() => setShowSchemaDrawer(true)} />
<div className={`${className || ""} w-full border-b dark:border-zinc-700 z-1`}>
<div className="mt-2 w-full flex flex-row justify-between items-center lg:grid lg:grid-cols-3">
<div className="ml-2 flex justify-start items-center">
<button
className="w-8 h-8 p-1 mr-1 block lg:hidden rounded-md cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-700"
onClick={() => layoutStore.toggleSidebar()}
>
<Icon.IoIosMenu className="text-gray-600 w-full h-auto" />
</button>
)}
<span className="w-auto text-left block lg:hidden">{title}</span>
<GitHubStarBadge className="hidden lg:flex ml-2" />
</div>
<span className="w-auto text-center hidden lg:flex justify-center items-center">
<a
href="https://www.bytebase.com?source=sqlchat"
className="hidden sm:block w-auto h-auto py-1 px-2 rounded-md hover:bg-gray-100 dark:hover:bg-zinc-700"
target="_blank"
>
<img
className="h-5 sm:h-6 w-auto"
src={isDarkMode ? "/craft-by-bytebase-dark-mode.webp" : "/craft-by-bytebase.webp"}
alt=""
/>
</a>
</span>
<div className="mr-2 relative flex flex-row justify-end items-center">
{hasFeature("debug") && (
<button className="p-2 rounded cursor-pointer hover:bg-gray-100 dark:hover:bg-zinc-700">
<Icon.FiSettings className="w-4 h-auto" onClick={() => setShowSchemaDrawer(true)} />
</button>
)}
</div>
</div>
{hasFeature("debug") && <ConversationTabsView />}
</div>
{hasFeature("debug") && showSchemaDrawer && <SchemaDrawer close={() => setShowSchemaDrawer(false)} />}