add connection status, version comparing, not hardlocking until config being sent (fixes #27) (related #23)
This commit is contained in:
parent
eb73597667
commit
c907d52027
|
@ -12674,6 +12674,26 @@
|
|||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-toastify": {
|
||||
"version": "10.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
|
||||
"integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-toastify/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-zoom-pan-pinch": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.4.2.tgz",
|
||||
|
@ -16005,6 +16025,7 @@
|
|||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-toastify": "^10.0.5",
|
||||
"react-zoom-pan-pinch": "^3.4.1",
|
||||
"socket.io-client": "^4.7.4"
|
||||
},
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-toastify": "^10.0.5",
|
||||
"react-zoom-pan-pinch": "^3.4.1",
|
||||
"socket.io-client": "^4.7.4"
|
||||
},
|
||||
|
|
|
@ -8,14 +8,37 @@ import { ToolbarWrapper } from "./Toolbar/ToolbarWrapper";
|
|||
import React, { lazy, useEffect } from "react";
|
||||
import { ChatContext } from "../contexts/ChatContext";
|
||||
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
const Chat = lazy(() => import("./Chat/Chat"));
|
||||
|
||||
console.log("Client init with version " + __COMMIT_HASH__);
|
||||
|
||||
const DynamicallyLoadChat = () => {
|
||||
const { loadChat } = useAppContext();
|
||||
|
||||
return <React.Suspense>{loadChat && <Chat />}</React.Suspense>;
|
||||
};
|
||||
|
||||
// get access to context data
|
||||
const AppInner = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<CanvasWrapper />
|
||||
<ToolbarWrapper />
|
||||
|
||||
{/* <DynamicallyLoadChat /> */}
|
||||
|
||||
<DebugModal />
|
||||
<SettingsSidebar />
|
||||
|
||||
<ToastContainer position="top-left" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
useEffect(() => {
|
||||
// detect auth callback for chat, regardless of it being loaded
|
||||
|
@ -113,14 +136,7 @@ const App = () => {
|
|||
<AppContext>
|
||||
<ChatContext>
|
||||
<TemplateContext>
|
||||
<Header />
|
||||
<CanvasWrapper />
|
||||
<ToolbarWrapper />
|
||||
|
||||
{/* <DynamicallyLoadChat /> */}
|
||||
|
||||
<DebugModal />
|
||||
<SettingsSidebar />
|
||||
<AppInner />
|
||||
</TemplateContext>
|
||||
</ChatContext>
|
||||
</AppContext>
|
||||
|
|
|
@ -10,11 +10,13 @@ import { Template } from "./Template";
|
|||
import { IRouterData, Router } from "../lib/router";
|
||||
|
||||
export const CanvasWrapper = () => {
|
||||
const { config } = useAppContext();
|
||||
// to prevent safari from blurring things, use the zoom css property
|
||||
|
||||
return (
|
||||
<main>
|
||||
<PanZoomWrapper>
|
||||
<Template />
|
||||
{config && <Template />}
|
||||
<CanvasInner />
|
||||
</PanZoomWrapper>
|
||||
</main>
|
||||
|
@ -31,7 +33,7 @@ const CanvasInner = () => {
|
|||
}, [PanZoom]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!config.canvas || !canvasRef.current) return;
|
||||
if (!config?.canvas || !canvasRef.current) return;
|
||||
const canvas = canvasRef.current!;
|
||||
const canvasInstance = new Canvas(config, canvas, PanZoom);
|
||||
const initAt = Date.now();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Button } from "@nextui-org/react";
|
||||
import { Button, Card, CardBody } from "@nextui-org/react";
|
||||
import { useAppContext } from "../contexts/AppContext";
|
||||
import { User } from "./Header/User";
|
||||
import { Debug } from "@sc07-canvas/lib/src/debug";
|
||||
|
@ -13,12 +13,20 @@ const DynamicChat = () => {
|
|||
};
|
||||
|
||||
export const Header = () => {
|
||||
const { setSettingsSidebar } = useAppContext();
|
||||
const { setSettingsSidebar, connected } = useAppContext();
|
||||
|
||||
return (
|
||||
<header id="main-header">
|
||||
<div></div>
|
||||
<div className="spacer"></div>
|
||||
{!connected && (
|
||||
<div>
|
||||
<Card>
|
||||
<CardBody>Disconnected</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
<div className="spacer"></div>
|
||||
<div className="box">
|
||||
<User />
|
||||
<Button onClick={() => setSettingsSidebar(true)}>Settings</Button>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useAppContext } from "../../contexts/AppContext";
|
||||
import { CanvasMeta } from "./CanvasMeta";
|
||||
import { Palette } from "./Palette";
|
||||
import { UndoButton } from "./UndoButton";
|
||||
|
@ -6,6 +7,10 @@ import { UndoButton } from "./UndoButton";
|
|||
* Wrapper for everything aligned at the bottom of the screen
|
||||
*/
|
||||
export const ToolbarWrapper = () => {
|
||||
const { config } = useAppContext();
|
||||
|
||||
if (!config) return <></>;
|
||||
|
||||
return (
|
||||
<div id="toolbar">
|
||||
<CanvasMeta />
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
IPosition,
|
||||
} from "@sc07-canvas/lib/src/net";
|
||||
import Network from "../lib/network";
|
||||
import { Spinner } from "@nextui-org/react";
|
||||
|
||||
const appContext = createContext<IAppContext>({} as any);
|
||||
|
||||
|
@ -23,6 +24,7 @@ export const AppContext = ({ children }: PropsWithChildren) => {
|
|||
const [auth, setAuth] = useState<AuthSession>();
|
||||
const [canvasPosition, setCanvasPosition] = useState<ICanvasPosition>();
|
||||
const [cursorPosition, setCursorPosition] = useState<IPosition>();
|
||||
const [connected, setConnected] = useState(false);
|
||||
|
||||
// --- settings ---
|
||||
const [loadChat, _setLoadChat] = useState(false);
|
||||
|
@ -43,7 +45,6 @@ export const AppContext = ({ children }: PropsWithChildren) => {
|
|||
}
|
||||
|
||||
function handleConfig(config: ClientConfig) {
|
||||
console.info("Server sent config", config);
|
||||
setConfig(config);
|
||||
}
|
||||
|
||||
|
@ -65,12 +66,23 @@ export const AppContext = ({ children }: PropsWithChildren) => {
|
|||
}
|
||||
}
|
||||
|
||||
function handleConnect() {
|
||||
setConnected(true);
|
||||
}
|
||||
|
||||
function handleDisconnect() {
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
Network.on("user", handleUser);
|
||||
Network.on("config", handleConfig);
|
||||
Network.waitFor("pixels").then(([data]) => handlePixels(data));
|
||||
Network.on("pixels", handlePixels);
|
||||
Network.on("undo", handleUndo);
|
||||
|
||||
Network.on("connected", handleConnect);
|
||||
Network.on("disconnected", handleDisconnect);
|
||||
|
||||
Network.socket.connect();
|
||||
|
||||
loadSettings();
|
||||
|
@ -79,6 +91,9 @@ export const AppContext = ({ children }: PropsWithChildren) => {
|
|||
Network.off("user", handleUser);
|
||||
Network.off("config", handleConfig);
|
||||
Network.off("pixels", handlePixels);
|
||||
Network.off("undo", handleUndo);
|
||||
Network.off("connected", handleConnect);
|
||||
Network.off("disconnected", handleDisconnect);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -102,9 +117,15 @@ export const AppContext = ({ children }: PropsWithChildren) => {
|
|||
undo,
|
||||
loadChat,
|
||||
setLoadChat,
|
||||
connected,
|
||||
}}
|
||||
>
|
||||
{config ? children : "Loading..."}
|
||||
{!config && (
|
||||
<div className="fixed top-0 left-0 w-full h-full z-[9999] backdrop-blur-sm bg-black/30 text-white flex items-center justify-center">
|
||||
<Spinner label="Loading..." />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</appContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,8 +7,12 @@ import {
|
|||
Pixel,
|
||||
ServerToClientEvents,
|
||||
} from "@sc07-canvas/lib/src/net";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export interface INetworkEvents {
|
||||
connected: () => void;
|
||||
disconnected: () => void;
|
||||
|
||||
user: (user: AuthSession) => void;
|
||||
config: (user: ClientConfig) => void;
|
||||
canvas: (pixels: string[]) => void;
|
||||
|
@ -31,6 +35,7 @@ class Network extends EventEmitter<INetworkEvents> {
|
|||
{
|
||||
autoConnect: false,
|
||||
withCredentials: true,
|
||||
transports: ["polling"],
|
||||
}
|
||||
);
|
||||
private online_count = 0;
|
||||
|
@ -41,11 +46,40 @@ class Network extends EventEmitter<INetworkEvents> {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.socket.on("connect", () => {
|
||||
console.log("Connected to server");
|
||||
toast.success("Connected to server");
|
||||
this.emit("connected");
|
||||
});
|
||||
|
||||
this.socket.on("connect_error", (err) => {
|
||||
// TODO: proper error handling
|
||||
console.error("Failed to connect to server", err);
|
||||
toast.error("Failed to connect: " + (err.message || err.name));
|
||||
});
|
||||
|
||||
this.socket.on("disconnect", (reason, desc) => {
|
||||
console.log("Disconnected from server", reason, desc);
|
||||
toast.warn("Disconnected from server");
|
||||
this.emit("disconnected");
|
||||
});
|
||||
|
||||
this.socket.on("user", (user: AuthSession) => {
|
||||
this.emit("user", user);
|
||||
});
|
||||
|
||||
this.socket.on("config", (config) => {
|
||||
console.info("Server sent config", config);
|
||||
|
||||
if (config.version !== __COMMIT_HASH__) {
|
||||
toast.info("Client version does not match server, reloading...");
|
||||
console.warn("Client version does not match server, reloading...", {
|
||||
clientVersion: __COMMIT_HASH__,
|
||||
serverVersion: config.version,
|
||||
});
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
this.emit("config", config);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,3 +8,5 @@ interface ImportMetaEnv {
|
|||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
||||
declare const __COMMIT_HASH__: string;
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import * as child from "child_process";
|
||||
|
||||
const commitHash = child
|
||||
.execSync("git rev-parse --short HEAD")
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
export default defineConfig({
|
||||
root: "src",
|
||||
|
@ -13,4 +19,7 @@ export default defineConfig({
|
|||
include: "**/*.{jsx,tsx}",
|
||||
}),
|
||||
],
|
||||
define: {
|
||||
__COMMIT_HASH__: JSON.stringify(commitHash),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -42,6 +42,7 @@ export interface IAppContext {
|
|||
undo?: { available: true; expireAt: number };
|
||||
loadChat: boolean;
|
||||
setLoadChat: (v: boolean) => void;
|
||||
connected: boolean;
|
||||
}
|
||||
|
||||
export interface IPalleteContext {
|
||||
|
@ -93,6 +94,10 @@ export type CanvasConfig = {
|
|||
};
|
||||
|
||||
export type ClientConfig = {
|
||||
/**
|
||||
* Monolith git hash, if it doesn't match, client will reload
|
||||
*/
|
||||
version: string;
|
||||
pallete: {
|
||||
colors: PalleteColor[];
|
||||
pixel_cooldown: number;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import http from "node:http";
|
||||
import * as child from "node:child_process";
|
||||
import {
|
||||
ClientConfig,
|
||||
ClientToServerEvents,
|
||||
|
@ -15,6 +16,12 @@ import { Logger } from "./Logger";
|
|||
import { Redis } from "./redis";
|
||||
import { User } from "../models/User";
|
||||
|
||||
// maybe move to a constants file?
|
||||
const commitHash = child
|
||||
.execSync("git rev-parse --short HEAD")
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
/**
|
||||
* get socket.io server config, generated from environment vars
|
||||
*/
|
||||
|
@ -55,6 +62,7 @@ prisma.paletteColor
|
|||
|
||||
const getClientConfig = (): ClientConfig => {
|
||||
return {
|
||||
version: commitHash,
|
||||
pallete: {
|
||||
colors: PALLETE,
|
||||
pixel_cooldown: PIXEL_TIMEOUT_MS,
|
||||
|
|
Loading…
Reference in New Issue