Set up the user interface state model and set up the admin user onboarding #283
|
@ -20,7 +20,6 @@
|
|||
"classnames": "^2.5.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^6.28.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scripts": "5.0.1",
|
||||
|
@ -3748,11 +3747,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||
|
@ -12926,28 +12920,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.25 || ^19",
|
||||
"react": "^18.0 || ^19",
|
||||
"redux": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||
|
@ -15285,14 +15257,6 @@
|
|||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
"classnames": "^2.5.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^6.28.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scripts": "5.0.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import React, { PropsWithChildren, useContext, useEffect, useState } from 'react';
|
||||
import './App.css';
|
||||
import { Client } from './client';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
|
@ -9,8 +9,7 @@ import { PlayerView } from './views/PlayerView/PlayerView';
|
|||
import { Admin } from './views/Admin/Admin';
|
||||
import Candela from './plugins/Candela';
|
||||
import { Authentication } from './views/Authentication/Authentication';
|
||||
import { StateProvider } from './components/StateProvider';
|
||||
import { AuthProvider } from './providers/AuthProvider/AuthProvider';
|
||||
import { StateContext, StateProvider } from './providers/StateProvider/StateProvider';
|
||||
|
||||
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803";
|
||||
|
||||
|
@ -33,13 +32,14 @@ interface AuthedViewProps {
|
|||
}
|
||||
|
||||
const AuthedView = ({ client, children }: PropsWithChildren<AuthedViewProps>) => {
|
||||
return (<AuthProvider client={client}>
|
||||
<StateProvider client={client}>
|
||||
<Authentication onAdminPassword={(password) => console.log(password)} onAuth={(username, password) => console.log(username, password)}>
|
||||
{children}
|
||||
</Authentication>
|
||||
</StateProvider>
|
||||
</AuthProvider >);
|
||||
const [state, dispatch] = useContext(StateContext);
|
||||
return (
|
||||
<Authentication onAdminPassword={(password) => {
|
||||
dispatch({type: "SetAdminPassword", password });
|
||||
}} onAuth={(username, password) => console.log(username, password)}>
|
||||
{children}
|
||||
</Authentication>
|
||||
);
|
||||
}
|
||||
|
||||
const App = ({ client }: AppProps) => {
|
||||
|
@ -54,8 +54,7 @@ const App = ({ client }: AppProps) => {
|
|||
createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <AuthedView client={client}> <PlayerView client={client} /> </AuthedView>
|
||||
|
||||
element: <StateProvider client={client}><AuthedView client={client}> <PlayerView client={client} /> </AuthedView> </StateProvider>
|
||||
},
|
||||
{
|
||||
path: "/gm",
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import React, { createContext, PropsWithChildren, useCallback, useEffect, useReducer } from "react";
|
||||
import { Status, Tabletop } from "visions-types";
|
||||
import { Client } from "../client";
|
||||
import { assertNever } from "../plugins/Candela";
|
||||
|
||||
type TabletopState = {
|
||||
tabletop: Tabletop;
|
||||
}
|
||||
|
||||
type Action = {};
|
||||
|
||||
const initialState = (): TabletopState => (
|
||||
{
|
||||
tabletop: { backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundImage: undefined }
|
||||
}
|
||||
);
|
||||
|
||||
export const AppContext = createContext<TabletopState>(initialState());
|
||||
|
||||
interface StateProviderProps { client: Client; }
|
||||
|
||||
export const StateProvider = ({ client, children }: PropsWithChildren<StateProviderProps>) => {
|
||||
console.log("StateProvider");
|
||||
const [state, dispatch] = useReducer(stateReducer, initialState());
|
||||
|
||||
return <AppContext.Provider value={state}>
|
||||
{children}
|
||||
</AppContext.Provider>;
|
||||
}
|
||||
|
||||
const stateReducer = (state: TabletopState, _action: Action): TabletopState => state;
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import React, { createContext, PropsWithChildren, useEffect, useReducer } from "react";
|
||||
import { Status } from "visions-types";
|
||||
import { Client } from "../../client";
|
||||
|
||||
type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", username: string };
|
||||
|
||||
type Action = { type: "SetAuthState", state: AuthState };
|
||||
|
||||
export const AuthContext = createContext<AuthState>({ type: "NoAdmin" });
|
||||
|
||||
interface AuthProviderProps {
|
||||
client: Client;
|
||||
}
|
||||
|
||||
export const AuthProvider = ({ client, children }: PropsWithChildren<AuthProviderProps>) => {
|
||||
const [authState, dispatch] = useReducer(stateReducer, { type: "NoAdmin" });
|
||||
|
||||
useEffect(() => {
|
||||
client.status().then((status: Status) => {
|
||||
if (status.admin_enabled) {
|
||||
dispatch({ type: "SetAuthState", state: { type: "Unauthed" } });
|
||||
} else {
|
||||
dispatch({ type: "SetAuthState", state: { type: "NoAdmin" } });
|
||||
}
|
||||
})
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
return (<AuthContext.Provider value={authState}>
|
||||
{children}
|
||||
</AuthContext.Provider>);
|
||||
}
|
||||
|
||||
const stateReducer = (_state: AuthState, action: Action) => action.state;
|
|
@ -0,0 +1,37 @@
|
|||
import React, { createContext, PropsWithChildren, useCallback, useEffect, useReducer } from "react";
|
||||
import { Status, Tabletop } from "visions-types";
|
||||
import { Client } from "../../client";
|
||||
import { assertNever } from "../../plugins/Candela";
|
||||
|
||||
type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", username: string };
|
||||
|
||||
type AppState = {
|
||||
auth: AuthState;
|
||||
tabletop: Tabletop;
|
||||
}
|
||||
|
||||
type Action = { type: "SetAdminPassword", password: string } | { type: "Auth", username: string, password: string };
|
||||
|
||||
const initialState = (): AppState => (
|
||||
{
|
||||
auth: { type: "NoAdmin" },
|
||||
tabletop: { backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundImage: undefined }
|
||||
}
|
||||
);
|
||||
|
||||
const stateReducer = (state: AppState, action: Action): AppState => {
|
||||
console.log("reducer: ", state, action);
|
||||
return state;
|
||||
}
|
||||
|
||||
export const StateContext = createContext<[AppState, React.Dispatch<any>]>([initialState(), () => { }]);
|
||||
|
||||
interface StateProviderProps { client: Client; }
|
||||
|
||||
export const StateProvider = ({ client, children }: PropsWithChildren<StateProviderProps>) => {
|
||||
const [state, dispatch] = useReducer(stateReducer, initialState());
|
||||
|
||||
return <StateContext.Provider value={[state, dispatch]}>
|
||||
{children}
|
||||
</StateContext.Provider>;
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import React, { PropsWithChildren, useContext, useState } from 'react';
|
||||
import { AppContext } from '../../components/StateProvider';
|
||||
import { PropsWithChildren, useContext, useState } from 'react';
|
||||
import { StateContext } from '../../providers/StateProvider/StateProvider';
|
||||
import { assertNever } from '../../plugins/Candela';
|
||||
import { AuthContext } from '../../providers/AuthProvider/AuthProvider';
|
||||
import './Authentication.css';
|
||||
|
||||
interface AuthenticationProps {
|
||||
|
@ -16,9 +15,9 @@ export const Authentication = ({ onAdminPassword, onAuth, children }: PropsWithC
|
|||
|
||||
let [userField, setUserField] = useState<string>("");
|
||||
let [pwField, setPwField] = useState<string>("");
|
||||
let auth = useContext(AuthContext);
|
||||
let [state, _] = useContext(StateContext);
|
||||
|
||||
switch (auth.type) {
|
||||
switch (state.auth.type) {
|
||||
case "NoAdmin": {
|
||||
return <div className="auth">
|
||||
<div className="card">
|
||||
|
@ -45,7 +44,7 @@ export const Authentication = ({ onAdminPassword, onAuth, children }: PropsWithC
|
|||
return <div> {children} </div>;
|
||||
}
|
||||
default: {
|
||||
assertNever(auth);
|
||||
assertNever(state.auth);
|
||||
return <div></div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Client, PlayingField } from '../../client';
|
||||
import { AppContext } from '../../components/StateProvider';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { Client } from '../../client';
|
||||
import { StateContext } from '../../providers/StateProvider/StateProvider';
|
||||
import { TabletopElement } from '../../components/Tabletop/Tabletop';
|
||||
import { ThumbnailElement } from '../../components/Thumbnail/Thumbnail';
|
||||
import { WebsocketContext } from '../../components/WebsocketProvider';
|
||||
|
@ -11,19 +11,19 @@ interface GmViewProps {
|
|||
}
|
||||
|
||||
export const GmView = ({ client }: GmViewProps) => {
|
||||
const { tabletop } = useContext(AppContext);
|
||||
const [state, dispatch] = useContext(StateContext);
|
||||
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
useEffect(() => {
|
||||
client.availableImages().then((images) => setImages(images));
|
||||
}, [client]);
|
||||
|
||||
const backgroundUrl = tabletop.backgroundImage ? client.imageUrl(tabletop.backgroundImage) : undefined;
|
||||
const backgroundUrl = state.tabletop.backgroundImage ? client.imageUrl(state.tabletop.backgroundImage) : undefined;
|
||||
return (<div className="gm-view">
|
||||
<div>
|
||||
{images.map((imageName) => <ThumbnailElement id={imageName} url={client.imageUrl(imageName)} onclick={() => { client.setBackgroundImage(imageName); }} />)}
|
||||
</div>
|
||||
<TabletopElement backgroundColor={tabletop.backgroundColor} backgroundUrl={backgroundUrl} />
|
||||
<TabletopElement backgroundColor={state.tabletop.backgroundColor} backgroundUrl={backgroundUrl} />
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { WebsocketContext } from '../../components/WebsocketProvider';
|
|||
import { Client } from '../../client';
|
||||
import { TabletopElement } from '../../components/Tabletop/Tabletop';
|
||||
import Candela from '../../plugins/Candela';
|
||||
import { AppContext } from '../../components/StateProvider';
|
||||
import { StateContext } from '../../providers/StateProvider/StateProvider';
|
||||
|
||||
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803";
|
||||
|
||||
|
@ -13,7 +13,7 @@ interface PlayerViewProps {
|
|||
}
|
||||
|
||||
export const PlayerView = ({ client }: PlayerViewProps) => {
|
||||
const { tabletop } = useContext(AppContext);
|
||||
const [state, dispatch] = useContext(StateContext);
|
||||
|
||||
const [charsheet, setCharsheet] = useState(undefined);
|
||||
|
||||
|
@ -26,9 +26,9 @@ export const PlayerView = ({ client }: PlayerViewProps) => {
|
|||
[client, setCharsheet]
|
||||
);
|
||||
|
||||
const backgroundColor = tabletop.backgroundColor;
|
||||
const backgroundColor = state.tabletop.backgroundColor;
|
||||
const tabletopColorStyle = `rgb(${backgroundColor.red}, ${backgroundColor.green}, ${backgroundColor.blue})`;
|
||||
const backgroundUrl = tabletop.backgroundImage ? client.imageUrl(tabletop.backgroundImage) : undefined;
|
||||
const backgroundUrl = state.tabletop.backgroundImage ? client.imageUrl(state.tabletop.backgroundImage) : undefined;
|
||||
|
||||
return (<div className="player-view" style={{ backgroundColor: tabletopColorStyle }}>
|
||||
<div className="player-view__middle-panel"> <TabletopElement backgroundColor={backgroundColor} backgroundUrl={backgroundUrl} /> </div>
|
||||
|
|
Loading…
Reference in New Issue