From 693dcf12d6c6ddd610b12bbc85ebab0474e46256 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 21 Apr 2023 01:12:39 +0800 Subject: [PATCH] refactor: close #643 use react router --- app/components/chat-list.tsx | 8 +- app/components/chat.tsx | 14 +-- app/components/home.tsx | 212 +++++++++-------------------------- app/components/settings.tsx | 10 +- app/components/sidebar.tsx | 135 ++++++++++++++++++++++ app/constant.ts | 6 + app/utils.ts | 2 +- package.json | 1 + yarn.lock | 20 ++++ 9 files changed, 234 insertions(+), 174 deletions(-) create mode 100644 app/components/sidebar.tsx diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx index f013920..fb0f740 100644 --- a/app/components/chat-list.tsx +++ b/app/components/chat-list.tsx @@ -10,6 +10,8 @@ import { import { useChatStore } from "../store"; import Locale from "../locales"; +import { Link, useNavigate } from "react-router-dom"; +import { Path } from "../constant"; export function ChatItem(props: { onClick?: () => void; @@ -59,6 +61,7 @@ export function ChatList() { state.moveSession, ]); const chatStore = useChatStore(); + const navigate = useNavigate(); const onDragEnd: OnDragEndResponder = (result) => { const { destination, source } = result; @@ -94,7 +97,10 @@ export function ChatList() { id={item.id} index={i} selected={i === selectedIndex} - onClick={() => selectSession(i)} + onClick={() => { + navigate(Path.Chat); + selectSession(i); + }} onDelete={() => chatStore.deleteSession(i)} /> ))} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index c6bc61e..bab4229 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -54,6 +54,8 @@ import styles from "./home.module.scss"; import chatStyle from "./chat.module.scss"; import { Input, Modal, showModal } from "./ui-lib"; +import { useNavigate } from "react-router-dom"; +import { Path } from "../constant"; const Markdown = dynamic( async () => memo((await import("./markdown")).Markdown), @@ -418,10 +420,7 @@ export function ChatActions(props: { ); } -export function Chat(props: { - showSideBar?: () => void; - sideBarShowing?: boolean; -}) { +export function Chat() { type RenderMessage = Message & { preview?: boolean }; const chatStore = useChatStore(); @@ -439,6 +438,7 @@ export function Chat(props: { const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom(); const [hitBottom, setHitBottom] = useState(false); const isMobileScreen = useMobileScreen(); + const navigate = useNavigate(); const onChatBodyScroll = (e: HTMLElement) => { const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; @@ -641,7 +641,7 @@ export function Chat(props: { // Auto focus useEffect(() => { - if (props.sideBarShowing && isMobileScreen) return; + if (isMobileScreen) return; inputRef.current?.focus(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -666,7 +666,7 @@ export function Chat(props: { icon={} bordered title={Locale.Chat.Actions.ChatList} - onClick={props?.showSideBar} + onClick={() => navigate(Path.Home)} />
@@ -830,7 +830,7 @@ export function Chat(props: { setAutoScroll(false); setTimeout(() => setPromptHints([]), 500); }} - autoFocus={!props?.sideBarShowing} + autoFocus rows={inputRows} /> +
{!props.noLogo && }
@@ -38,7 +38,7 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, { loading: () => , }); -const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { +const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, { loading: () => , }); @@ -73,50 +73,6 @@ function useSwitchTheme() { }, [config.theme]); } -function useDragSideBar() { - const limit = (x: number) => Math.min(500, Math.max(220, x)); - - const chatStore = useChatStore(); - const startX = useRef(0); - const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300); - const lastUpdateTime = useRef(Date.now()); - - const handleMouseMove = useRef((e: MouseEvent) => { - if (Date.now() < lastUpdateTime.current + 100) { - return; - } - lastUpdateTime.current = Date.now(); - const d = e.clientX - startX.current; - const nextWidth = limit(startDragWidth.current + d); - chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth)); - }); - - const handleMouseUp = useRef(() => { - startDragWidth.current = chatStore.config.sidebarWidth ?? 300; - window.removeEventListener("mousemove", handleMouseMove.current); - window.removeEventListener("mouseup", handleMouseUp.current); - }); - - const onDragMouseDown = (e: MouseEvent) => { - startX.current = e.clientX; - - window.addEventListener("mousemove", handleMouseMove.current); - window.addEventListener("mouseup", handleMouseUp.current); - }; - const isMobileScreen = useMobileScreen(); - - useEffect(() => { - const sideBarWidth = isMobileScreen - ? "100vw" - : `${limit(chatStore.config.sidebarWidth ?? 300)}px`; - document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); - }, [chatStore.config.sidebarWidth, isMobileScreen]); - - return { - onDragMouseDown, - }; -} - const useHasHydrated = () => { const [hasHydrated, setHasHydrated] = useState(false); @@ -127,130 +83,64 @@ const useHasHydrated = () => { return hasHydrated; }; -function _Home() { - const [createNewSession, currentIndex, removeSession] = useChatStore( - (state) => [ - state.newSession, - state.currentSessionIndex, - state.removeSession, - ], - ); - const chatStore = useChatStore(); - const loading = !useHasHydrated(); - const [showSideBar, setShowSideBar] = useState(true); - +function WideScreen() { // setting - const [openSettings, setOpenSettings] = useState(false); const config = useChatStore((state) => state.config); - // drag side bar - const { onDragMouseDown } = useDragSideBar(); - const isMobileScreen = useMobileScreen(); - - useSwitchTheme(); - - if (loading) { - return ; - } - return (
-
-
-
ChatGPT Next
-
- Build your own AI assistant. -
-
- -
-
- -
{ - setOpenSettings(false); - setShowSideBar(false); - }} - > - -
- -
-
-
- } - onClick={chatStore.deleteSession} - /> -
-
- } - onClick={() => { - setOpenSettings(true); - setShowSideBar(false); - }} - shadow - /> -
- -
-
- } - text={Locale.Home.NewChat} - onClick={() => { - createNewSession(); - setShowSideBar(false); - }} - shadow - /> -
-
- -
onDragMouseDown(e as any)} - >
+
+
- {openSettings ? ( - { - setOpenSettings(false); - setShowSideBar(true); - }} - /> - ) : ( - setShowSideBar(true)} - sideBarShowing={showSideBar} - /> - )} + + } /> + } /> + } /> + +
+
+ ); +} + +function MobileScreen() { + const location = useLocation(); + const isHome = location.pathname === Path.Home; + + return ( +
+
+ +
+ +
+ + + } /> + } /> +
); } export function Home() { + useSwitchTheme(); + + const isMobileScreen = useMobileScreen(); + + if (!useHasHydrated()) { + return ; + } + return ( - <_Home> + {isMobileScreen ? : } ); } diff --git a/app/components/settings.tsx b/app/components/settings.tsx index d81b5b3..0214849 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -29,10 +29,11 @@ import { Avatar } from "./chat"; import Locale, { AllLangs, changeLang, getLang } from "../locales"; import { copyToClipboard, getEmojiUrl } from "../utils"; import Link from "next/link"; -import { UPDATE_URL } from "../constant"; +import { Path, UPDATE_URL } from "../constant"; import { Prompt, SearchService, usePromptStore } from "../store/prompt"; import { ErrorBoundary } from "./error"; import { InputRange } from "./input-range"; +import { useNavigate } from "react-router-dom"; function UserPromptModal(props: { onClose?: () => void }) { const promptStore = usePromptStore(); @@ -176,7 +177,8 @@ function PasswordInput(props: HTMLProps) { ); } -export function Settings(props: { closeSettings: () => void }) { +export function Settings() { + const navigate = useNavigate(); const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [config, updateConfig, resetConfig, clearAllData, clearSessions] = useChatStore((state) => [ @@ -235,7 +237,7 @@ export function Settings(props: { closeSettings: () => void }) { useEffect(() => { const keydownEvent = (e: KeyboardEvent) => { if (e.key === "Escape") { - props.closeSettings(); + navigate(Path.Home); } }; document.addEventListener("keydown", keydownEvent); @@ -290,7 +292,7 @@ export function Settings(props: { closeSettings: () => void }) {
} - onClick={props.closeSettings} + onClick={() => navigate(Path.Home)} bordered title={Locale.Settings.Actions.Close} /> diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx new file mode 100644 index 0000000..338dec1 --- /dev/null +++ b/app/components/sidebar.tsx @@ -0,0 +1,135 @@ +import { useState, useEffect, useRef } from "react"; + +import styles from "./home.module.scss"; + +import { IconButton } from "./button"; +import SettingsIcon from "../icons/settings.svg"; +import GithubIcon from "../icons/github.svg"; +import ChatGptIcon from "../icons/chatgpt.svg"; +import AddIcon from "../icons/add.svg"; +import CloseIcon from "../icons/close.svg"; +import Locale from "../locales"; + +import { useChatStore } from "../store"; + +import { Path, REPO_URL } from "../constant"; + +import { HashRouter as Router, Link, useNavigate } from "react-router-dom"; +import { useMobileScreen } from "../utils"; +import { ChatList } from "./chat-list"; + +function useDragSideBar() { + const limit = (x: number) => Math.min(500, Math.max(220, x)); + + const chatStore = useChatStore(); + const startX = useRef(0); + const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300); + const lastUpdateTime = useRef(Date.now()); + + const handleMouseMove = useRef((e: MouseEvent) => { + if (Date.now() < lastUpdateTime.current + 100) { + return; + } + lastUpdateTime.current = Date.now(); + const d = e.clientX - startX.current; + const nextWidth = limit(startDragWidth.current + d); + chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth)); + }); + + const handleMouseUp = useRef(() => { + startDragWidth.current = chatStore.config.sidebarWidth ?? 300; + window.removeEventListener("mousemove", handleMouseMove.current); + window.removeEventListener("mouseup", handleMouseUp.current); + }); + + const onDragMouseDown = (e: MouseEvent) => { + startX.current = e.clientX; + + window.addEventListener("mousemove", handleMouseMove.current); + window.addEventListener("mouseup", handleMouseUp.current); + }; + const isMobileScreen = useMobileScreen(); + + useEffect(() => { + const sideBarWidth = isMobileScreen + ? "100vw" + : `${limit(chatStore.config.sidebarWidth ?? 300)}px`; + document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); + }, [chatStore.config.sidebarWidth, isMobileScreen]); + + return { + onDragMouseDown, + }; +} + +export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) { + const chatStore = useChatStore(); + + // drag side bar + const { onDragMouseDown } = useDragSideBar(); + const navigate = useNavigate(); + const isMobileScreen = useMobileScreen(); + + return ( + <> +
+
ChatGPT Next
+
+ Build your own AI assistant. +
+
+ +
+
+ +
{ + if (e.target === e.currentTarget) { + navigate(Path.Home); + } + props.setShowSideBar?.(false); + }} + > + +
+ +
+
+
+ } + onClick={chatStore.deleteSession} + /> +
+
+ + } shadow /> + +
+ +
+
+ } + text={Locale.Home.NewChat} + onClick={() => { + chatStore.newSession(); + props.setShowSideBar?.(false); + }} + shadow + /> +
+
+ +
onDragMouseDown(e as any)} + >
+ + ); +} diff --git a/app/constant.ts b/app/constant.ts index 6f08ad7..6874454 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -6,3 +6,9 @@ export const UPDATE_URL = `${REPO_URL}#keep-updated`; export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`; export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`; export const RUNTIME_CONFIG_DOM = "danger-runtime-config"; + +export enum Path { + Home = "/", + Chat = "/chat", + Settings = "/settings", +} diff --git a/app/utils.ts b/app/utils.ts index 9d6e906..0af9fef 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -49,7 +49,7 @@ export function isIOS() { } export function useMobileScreen() { - const [isMobileScreen_, setIsMobileScreen] = useState(false); + const [isMobileScreen_, setIsMobileScreen] = useState(isMobileScreen()); useEffect(() => { const onResize = () => { setIsMobileScreen(isMobileScreen()); diff --git a/package.json b/package.json index 19047ad..7850055 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", + "react-router-dom": "^6.10.0", "rehype-highlight": "^6.0.0", "rehype-katex": "^6.0.2", "remark-breaks": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index 342ea4a..b7d9f83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1189,6 +1189,11 @@ tiny-glob "^0.2.9" tslib "^2.4.0" +"@remix-run/router@1.5.0": + version "1.5.0" + resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc" + integrity sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg== + "@rushstack/eslint-patch@^1.1.3": version "1.2.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" @@ -4296,6 +4301,21 @@ react-redux@^8.0.4: react-is "^18.0.0" use-sync-external-store "^1.0.0" +react-router-dom@^6.10.0: + version "6.10.0" + resolved "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.10.0.tgz#090ddc5c84dc41b583ce08468c4007c84245f61f" + integrity sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg== + dependencies: + "@remix-run/router" "1.5.0" + react-router "6.10.0" + +react-router@6.10.0: + version "6.10.0" + resolved "https://registry.npmmirror.com/react-router/-/react-router-6.10.0.tgz#230f824fde9dd0270781b5cb497912de32c0a971" + integrity sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ== + dependencies: + "@remix-run/router" "1.5.0" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"