From bf560625c9d6ea8383ae3f201706b46bd9c0b9cc Mon Sep 17 00:00:00 2001 From: Zhang Minghan Date: Wed, 25 Oct 2023 11:05:57 +0800 Subject: [PATCH] add user container in sidebar --- app/src/assets/home.less | 38 ++ app/src/assets/main.less | 2 + app/src/assets/ui.less | 4 +- app/src/components/SelectGroup.tsx | 7 +- app/src/components/app/MenuBar.tsx | 86 ++++ app/src/components/app/NavBar.tsx | 87 +--- app/src/components/home/ModelSelector.tsx | 13 +- app/src/components/home/SideBar.tsx | 555 ++++++++++++---------- app/src/conf.ts | 2 +- 9 files changed, 456 insertions(+), 338 deletions(-) create mode 100644 app/src/components/app/MenuBar.tsx diff --git a/app/src/assets/home.less b/app/src/assets/home.less index 4ffc3ce5..ab8ef1d6 100644 --- a/app/src/assets/home.less +++ b/app/src/assets/home.less @@ -53,6 +53,44 @@ opacity: 1; } + .sidebar-menu { + height: max-content; + width: 100%; + + .sidebar-wrapper { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + height: max-content; + width: calc(100% - 0.5rem); + margin: 0.25rem; + + img { + width: 2.5rem; + height: 2.5rem; + padding: 0.2rem; + border-radius: .5rem; + transform: translateY(0.05rem); + flex-shrink: 0; + } + + .username { + margin: 0 auto 0 8px; + color: hsl(var(--text)); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 14px; + font-family: var(--font-family-normal); + } + + svg { + color: hsl(var(--text-secondary)); + } + } + } + .conversation-list { position: relative; display: flex; diff --git a/app/src/assets/main.less b/app/src/assets/main.less index 4ac19bb9..96de0ac4 100644 --- a/app/src/assets/main.less +++ b/app/src/assets/main.less @@ -1,5 +1,6 @@ @import "ui"; @font-family: Andika,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; +@font-family-normal: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; @line-height: 1.5; @font-weight: 400; @@ -35,6 +36,7 @@ html, body { -moz-osx-font-smoothing: @-moz-osx-font-smoothing; -webkit-text-size-adjust: @-webkit-text-size-adjust; --font-family: @font-family; + --font-family-normal: @font-family-normal; } * { diff --git a/app/src/assets/ui.less b/app/src/assets/ui.less index 59c7aa4e..ea4ea1af 100644 --- a/app/src/assets/ui.less +++ b/app/src/assets/ui.less @@ -65,8 +65,8 @@ } &.badge-gold { - color: rgb(146, 114, 1) !important; - background: rgb(250, 230, 158) !important; + color: rgb(164, 128, 0) !important; + background: rgb(255, 231, 145) !important; } } } diff --git a/app/src/components/SelectGroup.tsx b/app/src/components/SelectGroup.tsx index ebcd432a..bf5a6e2b 100644 --- a/app/src/components/SelectGroup.tsx +++ b/app/src/components/SelectGroup.tsx @@ -12,7 +12,7 @@ import { Badge } from "./ui/badge.tsx"; export type SelectItemBadgeProps = { variant: "default" | "gold"; name: string; -} +}; export type SelectItemProps = { name: string; @@ -32,12 +32,11 @@ function GroupSelectItem(props: SelectItemProps) { return ( <> {props.value} - { - props.badge && + {props.badge && ( {props.badge.name} - } + )} ); } diff --git a/app/src/components/app/MenuBar.tsx b/app/src/components/app/MenuBar.tsx new file mode 100644 index 00000000..dbf43196 --- /dev/null +++ b/app/src/components/app/MenuBar.tsx @@ -0,0 +1,86 @@ +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { logout, selectUsername } from "../../store/auth.ts"; +import { + openDialog as openQuotaDialog, + quotaSelector, +} from "../../store/quota.ts"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../ui/dropdown-menu.tsx"; +import { Button } from "../ui/button.tsx"; +import { + BadgeCent, + Boxes, + CalendarPlus, + Cloud, + ListStart, + Plug, +} from "lucide-react"; +import { openDialog as openSub } from "../../store/subscription.ts"; +import { openDialog as openPackageDialog } from "../../store/package.ts"; +import { openDialog as openSharingDialog } from "../../store/sharing.ts"; +import { openDialog as openApiDialog } from "../../store/api.ts"; + +type MenuBarProps = { + children: React.ReactNode; + className?: string; +}; + +function MenuBar({ children, className }: MenuBarProps) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const username = useSelector(selectUsername); + const quota = useSelector(quotaSelector); + + return ( + + {children} + + {username} + + dispatch(openQuotaDialog())}> + + {quota} + + dispatch(openQuotaDialog())}> + + {t("quota")} + + dispatch(openSub())}> + + {t("sub.title")} + + dispatch(openPackageDialog())}> + + {t("pkg.title")} + + dispatch(openSharingDialog())}> + + {t("share.manage")} + + dispatch(openApiDialog())}> + + {t("api.title")} + + + + + + + + ); +} + +export default MenuBar; diff --git a/app/src/components/app/NavBar.tsx b/app/src/components/app/NavBar.tsx index 6e04c679..a817bfae 100644 --- a/app/src/components/app/NavBar.tsx +++ b/app/src/components/app/NavBar.tsx @@ -2,37 +2,12 @@ import "@/assets/navbar.less"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { - logout, selectAuthenticated, selectUsername, validateToken, } from "../../store/auth.ts"; -import { - openDialog as openQuotaDialog, - quotaSelector, -} from "../../store/quota.ts"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "../ui/dropdown-menu.tsx"; import { Button } from "../ui/button.tsx"; -import { - BadgeCent, - Boxes, - CalendarPlus, - Cloud, - ListStart, - Menu, - Plug, -} from "lucide-react"; -import { openDialog as openSub } from "../../store/subscription.ts"; -import { openDialog as openPackageDialog } from "../../store/package.ts"; -import { openDialog as openSharingDialog } from "../../store/sharing.ts"; -import { openDialog as openApiDialog } from "../../store/api.ts"; +import { Menu } from "lucide-react"; import { useEffect } from "react"; import { login, tokenField } from "../../conf.ts"; import { toggleMenu } from "../../store/menu.ts"; @@ -40,62 +15,18 @@ import ProjectLink from "../ProjectLink.tsx"; import ModeToggle from "../ThemeProvider.tsx"; import I18nProvider from "../I18nProvider.tsx"; import router from "../../router.tsx"; +import MenuBar from "./MenuBar.tsx"; -function MenuBar() { - const { t } = useTranslation(); - const dispatch = useDispatch(); +function NavMenu() { const username = useSelector(selectUsername); - const quota = useSelector(quotaSelector); return (
- - - - - - - {username} - - - dispatch(openQuotaDialog())}> - - {quota} - - dispatch(openQuotaDialog())}> - - {t("quota")} - - dispatch(openSub())}> - - {t("sub.title")} - - dispatch(openPackageDialog())}> - - {t("pkg.title")} - - dispatch(openSharingDialog())}> - - {t("share.manage")} - - dispatch(openApiDialog())}> - - {t("api.title")} - - - - - - - + + +
); } @@ -129,7 +60,7 @@ function NavBar() { {auth ? ( - + ) : ( +
+ + + + + + + + {t("conversation.remove-all-title")} + + + {t("conversation.remove-all-description")} + + + + {t("conversation.cancel")} + { + e.preventDefault(); + e.stopPropagation(); + + if (await deleteAllConversations(dispatch)) + toast({ + title: t("conversation.delete-success"), + description: t("conversation.delete-success-prompt"), + }); + else + toast({ + title: t("conversation.delete-failed"), + description: t("conversation.delete-failed-prompt"), + }); + + await updateConversationList(dispatch); + setOperateConversation({ target: null, type: "" }); + setRemoveAll(false); }} > - - -
- - - - - - - - {t("conversation.remove-all-title")} - - - {t("conversation.remove-all-description")} - - - - - {t("conversation.cancel")} - - { - e.preventDefault(); - e.stopPropagation(); + {t("conversation.delete")} + + + + + +
+ ); +} - if (await deleteAllConversations(dispatch)) - toast({ - title: t("conversation.delete-success"), - description: t("conversation.delete-success-prompt"), - }); - else - toast({ - title: t("conversation.delete-failed"), - description: t("conversation.delete-failed-prompt"), - }); - setOperateConversation({ target: null, type: "" }); - setRemoveAll(false); - }} - > - {t("conversation.delete")} -
-
-
-
- -
-
- {history.length ? ( - history.map((conversation, i) => ( - - )) - ) : ( -
{t("conversation.empty")}
- )} -
- { - if (!open) setOperateConversation({ target: null, type: "" }); - }} - > - - - - {t("conversation.remove-title")} - - - {t("conversation.remove-description")} - - {extractMessage( - filterMessage(operateConversation?.target?.name || ""), - )} - - {t("end")} - - - - - {t("conversation.cancel")} - - { - e.preventDefault(); - e.stopPropagation(); + {t("conversation.delete")} + + + + - if ( - await deleteConversation( - dispatch, - operateConversation?.target?.id || -1, - ) - ) - toast({ - title: t("conversation.delete-success"), - description: t("conversation.delete-success-prompt"), - }); - else - toast({ - title: t("conversation.delete-failed"), - description: t("conversation.delete-failed-prompt"), - }); - setOperateConversation({ target: null, type: "" }); - }} - > - {t("conversation.delete")} - - - - + { + if (!open) setOperateConversation({ target: null, type: "" }); + }} + > + + + {t("share.title")} + + {t("share.description")} + + {extractMessage( + filterMessage(operateConversation?.target?.name || ""), + )} + + {t("end")} + + + + {t("conversation.cancel")} + { + e.preventDefault(); + e.stopPropagation(); - { - if (!open) setOperateConversation({ target: null, type: "" }); - }} - > - - - {t("share.title")} - - {t("share.description")} - - {extractMessage( - filterMessage(operateConversation?.target?.name || ""), - )} - - {t("end")} - - - - - {t("conversation.cancel")} - - { - e.preventDefault(); - e.stopPropagation(); + const resp = await shareConversation( + operateConversation?.target?.id || -1, + ); + if (resp.status) setShared(getSharedLink(resp.data)); + else + toast({ + title: t("share.failed"), + description: resp.message, + }); - const resp = await shareConversation( - operateConversation?.target?.id || -1, - ); - if (resp.status) setShared(getSharedLink(resp.data)); - else - toast({ - title: t("share.failed"), - description: resp.message, - }); + setOperateConversation({ target: null, type: "" }); + }} + > + {t("share.title")} + + + + - setOperateConversation({ target: null, type: "" }); + 0} + onOpenChange={(open) => { + if (!open) { + setShared(""); + setOperateConversation({ target: null, type: "" }); + } + }} + > + + + {t("share.success")} + +
+ + +
+
+
+ + {t("close")} + { + e.preventDefault(); + e.stopPropagation(); + window.open(shared, "_blank"); + }} + > + {t("share.view")} + + +
+
+ + ); +} - 0} - onOpenChange={(open) => { - if (!open) { - setShared(""); - setOperateConversation({ target: null, type: "" }); - } - }} - > - - - {t("share.success")} - -
- - -
-
-
- - {t("close")} - { - e.preventDefault(); - e.stopPropagation(); - window.open(shared, "_blank"); - }} - > - {t("share.view")} - - -
-
+function SidebarMenu() { + const username = useSelector(selectUsername); + return ( +
+ + + + +
+ ); +} +function SideBar() { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const open = useSelector((state: RootState) => state.menu.open); + const auth = useSelector(selectAuthenticated); + const [operateConversation, setOperateConversation] = useState({ + target: null, + type: "", + }); + useEffectAsync(async () => { + await updateConversationList(dispatch); + }, []); + + return ( +
+ {auth ? ( +
+ + +
) : (