use lazy loading and admin pages (in dev)

This commit is contained in:
Zhang Minghan 2023-11-06 12:09:03 +08:00
parent d5f917191e
commit 5c08d04b89
14 changed files with 180 additions and 19 deletions

View File

@ -0,0 +1,10 @@
@import "menu";
.admin-page {
position: relative;
display: flex;
flex-direction: row;
width: 100%;
min-height: calc(100vh - 56px);
height: max-content;
}

View File

@ -0,0 +1,59 @@
.admin-menu {
display: flex;
flex-shrink: 0;
flex-direction: column;
width: 0;
height: 100%;
padding: 0;
margin: 0;
background: var(--background-sidebar);
transition: 0.225s ease-in-out;
min-height: calc(100vh - 56px);
transition-property: width, background, box-shadow, opacity;
border-right: 0;
opacity: 0;
&.close {
display: none;
}
&.open {
width: 260px;
border-right: 1px solid hsl(var(--border));
opacity: 1;
}
.menu-item {
display: flex;
flex-direction: row;
width: calc(100% - 1.5rem);
height: max-content;
padding: 0.75rem 1rem;
align-items: center;
background: var(--conversation-card);
margin: 0.75rem 0.75rem -0.25rem;
user-select: none;
cursor: pointer;
transition: 0.2s ease-in-out;
border-radius: var(--radius);
font-size: 16px;
&:hover {
background: var(--conversation-card-hover);
}
&.active {
background: var(--conversation-card-hover);
}
& > * {
flex-shrink: 0;
}
& svg {
width: 1.5rem;
height: 1.5rem;
margin-right: 0.5rem;
}
}
}

View File

@ -78,7 +78,7 @@ function Markdown({ children, className }: MarkdownProps) {
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || "");
const language = match ? match[1] : "";
if (language) return parseFile(children.toString());
if (language === "file") return parseFile(children.toString());
return !inline && match ? (
<div className={`markdown-syntax`}>
<div className={`markdown-syntax-header`}>

View File

@ -0,0 +1,50 @@
import {useSelector} from "react-redux";
import {selectMenu} from "@/store/menu.ts";
import React, {useEffect, useMemo, useState} from "react";
import {LayoutDashboard, Settings} from "lucide-react";
import router from "@/router.tsx";
import {useLocation} from "react-router-dom";
type MenuItemProps = {
title: string;
icon: React.ReactNode;
path: string;
}
function MenuItem({ title, icon, path }: MenuItemProps) {
const location = useLocation();
const active = useMemo(() => (
location.pathname === `/admin${path}` || (location.pathname + "/") === `/admin${path}`
), [location.pathname, path]);
return (
<div className={`menu-item ${active ? "active" : ""}`}
onClick={() => router.navigate(`/admin${path}`)}
>
<div className={`menu-item-icon`}>
{icon}
</div>
<div className={`menu-item-title`}>
{title}
</div>
</div>
)
}
function MenuBar() {
const open = useSelector(selectMenu);
const [close, setClose] = useState(false);
useEffect(() => {
if (open) setClose(false);
else setTimeout(() => setClose(true), 200);
}, [open]);
return (
<div className={`admin-menu ${open ? "open" : ""} ${close ? "close" : ""}`}>
<MenuItem title={"Dashboard"} icon={<LayoutDashboard />} path={"/"} />
<MenuItem title={"Dashboard"} icon={<Settings />} path={"/config"} />
</div>
)
}
export default MenuBar;

View File

@ -15,12 +15,12 @@ import { useToast } from "@/components/ui/use-toast.ts";
import { ToastAction } from "@/components/ui/toast.tsx";
import { alignSelector, contextSelector } from "@/store/settings.ts";
import { FileArray } from "@/conversation/file.ts";
import WebToggle from "@/components/home/components/WebToggle.tsx";
import WebToggle from "@/components/home/assemblies/WebToggle.tsx";
import ChatSpace from "@/components/home/ChatSpace.tsx";
import ChatFooter from "@/components/home/ChatFooter.tsx";
import SendButton from "@/components/home/components/SendButton.tsx";
import ChatInput from "@/components/home/components/ChatInput.tsx";
import ScrollAction from "@/components/home/components/ScrollAction.tsx";
import SendButton from "@/components/home/assemblies/SendButton.tsx";
import ChatInput from "@/components/home/assemblies/ChatInput.tsx";
import ScrollAction from "@/components/home/assemblies/ScrollAction.tsx";
function ChatWrapper() {
const { t } = useTranslation();
@ -127,7 +127,7 @@ function ChatWrapper() {
<FileProvider value={files} onChange={setFiles} />
<ChatInput
className={align ? "align" : ""}
ref={target}
target={target}
value={input}
onValueChange={setInput}
onEnterPressed={async () => await handleSend(auth, model, web)}

View File

@ -1,6 +1,5 @@
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "@/store";
import { selectAuthenticated, selectUsername } from "@/store/auth.ts";
import { selectCurrent, selectHistory } from "@/store/chat.ts";
import { useRef, useState } from "react";
@ -17,7 +16,7 @@ import {
updateConversationList,
} from "@/conversation/history.ts";
import { Button } from "@/components/ui/button.tsx";
import { setMenu } from "@/store/menu.ts";
import {selectMenu, setMenu} from "@/store/menu.ts";
import {
Copy,
Eraser,
@ -340,7 +339,7 @@ function SidebarMenu() {
function SideBar() {
const { t } = useTranslation();
const dispatch = useDispatch();
const open = useSelector((state: RootState) => state.menu.open);
const open = useSelector(selectMenu);
const auth = useSelector(selectAuthenticated);
const [operateConversation, setOperateConversation] = useState<Operation>({
target: null,

View File

@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
type ChatInputProps = {
className?: string;
ref?: React.RefObject<HTMLInputElement>;
target?: React.RefObject<HTMLInputElement>;
value: string;
onValueChange: (value: string) => void;
onEnterPressed: () => void;
@ -13,7 +13,7 @@ type ChatInputProps = {
function ChatInput({
className,
ref,
target,
value,
onValueChange,
onEnterPressed,
@ -24,7 +24,7 @@ function ChatInput({
<Input
id={`input`}
className={`input-box ${className || ""}`}
ref={ref}
ref={target}
value={value}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
onValueChange(e.target.value);

View File

@ -8,7 +8,7 @@ import {
} from "@/utils/env.ts";
import { getMemory } from "@/utils/memory.ts";
export const version = "3.6.13rc";
export const version = "3.6.13rc1";
export const dev: boolean = getDev();
export const deploy: boolean = true;
export let rest_api: string = getRestApi(deploy);

View File

@ -2,9 +2,12 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./routes/Home.tsx";
import NotFound from "./routes/NotFound.tsx";
import Auth from "./routes/Auth.tsx";
import Generation from "./routes/Generation.tsx";
import Sharing from "./routes/Sharing.tsx";
import Article from "@/routes/Article.tsx";
import { lazy, Suspense } from "react";
const Generation = lazy(() => import("@/routes/Generation.tsx"));
const Sharing = lazy(() => import("@/routes/Sharing.tsx"));
const Article = lazy(() => import("@/routes/Article.tsx"));
const Admin = lazy(() => import("@/routes/Admin.tsx"));
const router = createBrowserRouter([
{
@ -22,17 +25,43 @@ const router = createBrowserRouter([
{
id: "generation",
path: "/generate",
Component: Generation,
element: (
<Suspense>
<Generation />
</Suspense>
),
ErrorBoundary: NotFound,
},
{
id: "share",
path: "/share/:hash",
Component: Sharing,
element: (
<Suspense>
<Sharing />
</Suspense>
),
ErrorBoundary: NotFound,
},
{
id: "article",
path: "/article",
Component: Article,
element: (
<Suspense>
<Article />
</Suspense>
),
ErrorBoundary: NotFound,
},
{
id: "admin",
path: "/admin",
element: (
<Suspense>
<Admin />
</Suspense>
),
children: [],
ErrorBoundary: NotFound,
},
]);

12
app/src/routes/Admin.tsx Normal file
View File

@ -0,0 +1,12 @@
import "@/assets/admin/all.less";
import MenuBar from "@/components/admin/MenuBar.tsx";
function Admin() {
return (
<div className={`admin-page`}>
<MenuBar />
</div>
)
}
export default Admin;

View File

@ -24,3 +24,5 @@ export const menuSlice = createSlice({
export const { toggleMenu, closeMenu, openMenu, setMenu } = menuSlice.actions;
export default menuSlice.reducer;
export const selectMenu = (state: any) => state.menu.open;