Loading packages/client/src/components/App.tsx +115 −9 Original line number Diff line number Diff line import { Header } from "./Header"; import { AppContext } from "../contexts/AppContext"; import { AppContext, useAppContext } from "../contexts/AppContext"; import { CanvasWrapper } from "./CanvasWrapper"; import { TemplateContext } from "../contexts/TemplateContext"; import { SettingsSidebar } from "./Settings/SettingsSidebar"; import { DebugModal } from "./Debug/DebugModal"; import { ToolbarWrapper } from "./Toolbar/ToolbarWrapper"; import React, { lazy, useEffect } from "react"; import { ChatContext } from "../contexts/ChatContext"; const Chat = lazy(() => import("./Chat/Chat")); const DynamicallyLoadChat = () => { const { loadChat } = useAppContext(); return <React.Suspense>{loadChat && <Chat />}</React.Suspense>; }; const App = () => { useEffect(() => { // detect auth callback for chat, regardless of it being loaded // callback token expires quickly, so we should exchange it as quick as possible (async () => { const params = new URLSearchParams(window.location.search); if (params.has("loginToken")) { // login button opens a new tab that redirects here // if we're that tab, we should try to close this tab when we're done // should work because this tab is opened by JS const shouldCloseWindow = window.location.pathname.startsWith("/chat_callback"); // token provided by matrix's /sso/redirect const token = params.get("loginToken")!; // immediately remove from url to prevent reloading window.history.replaceState({}, "", "/"); const loginReq = await fetch( `https://${import.meta.env.VITE_MATRIX_HOST}/_matrix/client/v3/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ type: "m.login.token", token, }), } ); const loginRes = await loginReq.json(); console.log("[Chat] Matrix login", loginReq.status); switch (loginReq.status) { case 200: { // success console.log("[Chat] Logged in successfully", loginRes); localStorage.setItem( "matrix.access_token", loginRes.access_token + "" ); localStorage.setItem("matrix.device_id", loginRes.device_id + ""); localStorage.setItem("matrix.user_id", loginRes.user_id + ""); if (shouldCloseWindow) { console.log( "[Chat] Path matches autoclose, attempting to close window..." ); window.close(); alert("You can close this window and return to the other tab :)"); } else { console.log( "[Chat] Path doesn't match autoclose, not doing anything" ); } break; } case 400: case 403: console.log("[Chat] Matrix login", loginRes); alert( "[Chat] Failed to login\n" + loginRes.errcode + " " + loginRes.error ); break; case 429: alert( "[Chat] Failed to login, ratelimited.\nTry again in " + Math.floor(loginRes.retry_after_ms / 1000) + "s\n" + loginRes.errcode + " " + loginRes.error ); break; default: alert( "Error " + loginReq.status + " returned when trying to login to chat" ); } } })(); }, []); return ( <AppContext> <ChatContext> <TemplateContext> <Header /> <CanvasWrapper /> <ToolbarWrapper /> {/* <DynamicallyLoadChat /> */} <DebugModal /> <SettingsSidebar /> </TemplateContext> </ChatContext> </AppContext> ); }; Loading packages/client/src/components/Chat/Chat.tsx 0 → 100644 +13 −0 Original line number Diff line number Diff line import { useEffect, useRef, useState } from "react"; const Chat = () => { const ref = useRef<HTMLDivElement | null>(null); return ( <div ref={ref} style={{ position: "fixed", top: 0, left: 0, zIndex: 999 }}> chat </div> ); }; export default Chat; packages/client/src/components/Chat/InnerChatSettings.tsx 0 → 100644 +22 −0 Original line number Diff line number Diff line import { Button } from "@nextui-org/react"; import { useChatContext } from "../../contexts/ChatContext"; const InnerChatSettings = () => { const { user, doLogin, doLogout } = useChatContext(); return ( <> {!user && <Button onClick={doLogin}>Login</Button>} {user && ( <> <div className="flex gap-1"> <div className="flex-grow">{user.userId}</div> <Button onClick={doLogout}>Logout</Button> </div> </> )} </> ); }; export default InnerChatSettings; packages/client/src/components/Chat/OpenChatButton.tsx 0 → 100644 +19 −0 Original line number Diff line number Diff line import { Badge, Button } from "@nextui-org/react"; import { useChatContext } from "../../contexts/ChatContext"; const OpenChatButton = () => { const { notificationCount } = useChatContext(); return ( <Badge content={notificationCount} isInvisible={notificationCount === 0} color="danger" size="sm" > <Button>Chat</Button> </Badge> ); }; export default OpenChatButton; packages/client/src/components/Header.tsx +10 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,15 @@ import { Button } from "@nextui-org/react"; import { useAppContext } from "../contexts/AppContext"; import { User } from "./Header/User"; import { Debug } from "@sc07-canvas/lib/src/debug"; import React, { lazy } from "react"; const OpenChatButton = lazy(() => import("./Chat/OpenChatButton")); const DynamicChat = () => { const { loadChat } = useAppContext(); return <React.Suspense>{loadChat && <OpenChatButton />}</React.Suspense>; }; export const Header = () => { const { setSettingsSidebar } = useAppContext(); Loading @@ -14,6 +23,7 @@ export const Header = () => { <User /> <Button onClick={() => setSettingsSidebar(true)}>Settings</Button> <Button onClick={() => Debug.openDebugTools()}>debug</Button> <DynamicChat /> </div> </header> ); Loading Loading
packages/client/src/components/App.tsx +115 −9 Original line number Diff line number Diff line import { Header } from "./Header"; import { AppContext } from "../contexts/AppContext"; import { AppContext, useAppContext } from "../contexts/AppContext"; import { CanvasWrapper } from "./CanvasWrapper"; import { TemplateContext } from "../contexts/TemplateContext"; import { SettingsSidebar } from "./Settings/SettingsSidebar"; import { DebugModal } from "./Debug/DebugModal"; import { ToolbarWrapper } from "./Toolbar/ToolbarWrapper"; import React, { lazy, useEffect } from "react"; import { ChatContext } from "../contexts/ChatContext"; const Chat = lazy(() => import("./Chat/Chat")); const DynamicallyLoadChat = () => { const { loadChat } = useAppContext(); return <React.Suspense>{loadChat && <Chat />}</React.Suspense>; }; const App = () => { useEffect(() => { // detect auth callback for chat, regardless of it being loaded // callback token expires quickly, so we should exchange it as quick as possible (async () => { const params = new URLSearchParams(window.location.search); if (params.has("loginToken")) { // login button opens a new tab that redirects here // if we're that tab, we should try to close this tab when we're done // should work because this tab is opened by JS const shouldCloseWindow = window.location.pathname.startsWith("/chat_callback"); // token provided by matrix's /sso/redirect const token = params.get("loginToken")!; // immediately remove from url to prevent reloading window.history.replaceState({}, "", "/"); const loginReq = await fetch( `https://${import.meta.env.VITE_MATRIX_HOST}/_matrix/client/v3/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ type: "m.login.token", token, }), } ); const loginRes = await loginReq.json(); console.log("[Chat] Matrix login", loginReq.status); switch (loginReq.status) { case 200: { // success console.log("[Chat] Logged in successfully", loginRes); localStorage.setItem( "matrix.access_token", loginRes.access_token + "" ); localStorage.setItem("matrix.device_id", loginRes.device_id + ""); localStorage.setItem("matrix.user_id", loginRes.user_id + ""); if (shouldCloseWindow) { console.log( "[Chat] Path matches autoclose, attempting to close window..." ); window.close(); alert("You can close this window and return to the other tab :)"); } else { console.log( "[Chat] Path doesn't match autoclose, not doing anything" ); } break; } case 400: case 403: console.log("[Chat] Matrix login", loginRes); alert( "[Chat] Failed to login\n" + loginRes.errcode + " " + loginRes.error ); break; case 429: alert( "[Chat] Failed to login, ratelimited.\nTry again in " + Math.floor(loginRes.retry_after_ms / 1000) + "s\n" + loginRes.errcode + " " + loginRes.error ); break; default: alert( "Error " + loginReq.status + " returned when trying to login to chat" ); } } })(); }, []); return ( <AppContext> <ChatContext> <TemplateContext> <Header /> <CanvasWrapper /> <ToolbarWrapper /> {/* <DynamicallyLoadChat /> */} <DebugModal /> <SettingsSidebar /> </TemplateContext> </ChatContext> </AppContext> ); }; Loading
packages/client/src/components/Chat/Chat.tsx 0 → 100644 +13 −0 Original line number Diff line number Diff line import { useEffect, useRef, useState } from "react"; const Chat = () => { const ref = useRef<HTMLDivElement | null>(null); return ( <div ref={ref} style={{ position: "fixed", top: 0, left: 0, zIndex: 999 }}> chat </div> ); }; export default Chat;
packages/client/src/components/Chat/InnerChatSettings.tsx 0 → 100644 +22 −0 Original line number Diff line number Diff line import { Button } from "@nextui-org/react"; import { useChatContext } from "../../contexts/ChatContext"; const InnerChatSettings = () => { const { user, doLogin, doLogout } = useChatContext(); return ( <> {!user && <Button onClick={doLogin}>Login</Button>} {user && ( <> <div className="flex gap-1"> <div className="flex-grow">{user.userId}</div> <Button onClick={doLogout}>Logout</Button> </div> </> )} </> ); }; export default InnerChatSettings;
packages/client/src/components/Chat/OpenChatButton.tsx 0 → 100644 +19 −0 Original line number Diff line number Diff line import { Badge, Button } from "@nextui-org/react"; import { useChatContext } from "../../contexts/ChatContext"; const OpenChatButton = () => { const { notificationCount } = useChatContext(); return ( <Badge content={notificationCount} isInvisible={notificationCount === 0} color="danger" size="sm" > <Button>Chat</Button> </Badge> ); }; export default OpenChatButton;
packages/client/src/components/Header.tsx +10 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,15 @@ import { Button } from "@nextui-org/react"; import { useAppContext } from "../contexts/AppContext"; import { User } from "./Header/User"; import { Debug } from "@sc07-canvas/lib/src/debug"; import React, { lazy } from "react"; const OpenChatButton = lazy(() => import("./Chat/OpenChatButton")); const DynamicChat = () => { const { loadChat } = useAppContext(); return <React.Suspense>{loadChat && <OpenChatButton />}</React.Suspense>; }; export const Header = () => { const { setSettingsSidebar } = useAppContext(); Loading @@ -14,6 +23,7 @@ export const Header = () => { <User /> <Button onClick={() => setSettingsSidebar(true)}>Settings</Button> <Button onClick={() => Debug.openDebugTools()}>debug</Button> <DynamicChat /> </div> </header> ); Loading