Set up the user interface state model and set up the admin user onboarding #283

Merged
savanni merged 12 commits from visions-admin into main 2024-12-18 14:18:16 +00:00
6 changed files with 63 additions and 19 deletions
Showing only changes of commit 7ca1581b55 - Show all commits

View File

@ -0,0 +1,48 @@
import React, { createContext, PropsWithChildren, useEffect, useReducer } from "react";
import { Tabletop } from "visions-types";
import { assertNever } from "../plugins/Candela";
type AuthState = { type: "NoAdmin" } | { type: "Unauthed" } | { type: "Authed", username: string };
type TabletopState = {
auth: AuthState;
tabletop: Tabletop;
}
type StateAction = { type: "SetAuthState", state: AuthState }
| { type: "HandleMessage" };
const initialState = (): TabletopState => (
{
auth: { type: "Unauthed" },
tabletop: { backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundImage: undefined }
}
);
export const AppContext = createContext<TabletopState>(initialState());
interface StateProviderProps { }
export const StateProvider = ({ children }: PropsWithChildren<StateProviderProps>) => {
const [state, dispatch] = useReducer(stateReducer, initialState());
return <AppContext.Provider value={initialState()}>
{children}
</AppContext.Provider>;
}
const stateReducer = (state: TabletopState, action: StateAction): TabletopState => {
switch (action.type) {
case "SetAuthState": {
return { ...state, auth: action.state };
}
case "HandleMessage": {
return state;
}
default: {
assertNever(action);
return state;
}
}
}

View File

@ -2,19 +2,17 @@ import React, { createContext, PropsWithChildren, useEffect, useReducer } from "
import useWebSocket from "react-use-websocket"; import useWebSocket from "react-use-websocket";
import { Message, Tabletop } from "visions-types"; import { Message, Tabletop } from "visions-types";
type TabletopState = { type WebsocketState = { }
tabletop: Tabletop;
}
const initialState = (): TabletopState => ({ tabletop: { backgroundColor: { red: 0, green: 0, blue: 0 }, backgroundImage: undefined } }); export const WebsocketContext = createContext<WebsocketState>({});
export const WebsocketContext = createContext<TabletopState>(initialState());
interface WebsocketProviderProps { interface WebsocketProviderProps {
websocketUrl: string; websocketUrl: string;
} }
export const WebsocketProvider = ({ websocketUrl, children }: PropsWithChildren<WebsocketProviderProps>) => { export const WebsocketProvider = ({ websocketUrl, children }: PropsWithChildren<WebsocketProviderProps>) => {
return <div> {children} </div>;
/*
const { lastMessage } = useWebSocket(websocketUrl); const { lastMessage } = useWebSocket(websocketUrl);
const [state, dispatch] = useReducer(handleMessage, initialState()); const [state, dispatch] = useReducer(handleMessage, initialState());
@ -29,8 +27,10 @@ export const WebsocketProvider = ({ websocketUrl, children }: PropsWithChildren<
return (<WebsocketContext.Provider value={state}> return (<WebsocketContext.Provider value={state}>
{children} {children}
</WebsocketContext.Provider>); </WebsocketContext.Provider>);
*/
} }
/*
const handleMessage = (state: TabletopState, message: Message): TabletopState => { const handleMessage = (state: TabletopState, message: Message): TabletopState => {
console.log(message); console.log(message);
switch (message.type) { switch (message.type) {
@ -42,3 +42,4 @@ const handleMessage = (state: TabletopState, message: Message): TabletopState =>
} }
} }
} }
*/

View File

@ -8,12 +8,6 @@
height: 100vh; height: 100vh;
} }
.auth > div {
border: var(--border-standard);
border-radius: var(--border-radius-standard);
padding: var(--padding-m);
}
.auth__input-line { .auth__input-line {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@ -1,5 +1,6 @@
import React, { PropsWithChildren, ReactNode, useContext, useEffect, useState } from 'react'; import React, { PropsWithChildren, ReactNode, useContext, useEffect, useState } from 'react';
import { Client } from '../../client'; import { Client } from '../../client';
import { AppContext } from '../../components/StateProvider';
import { assertNever } from '../../plugins/Candela'; import { assertNever } from '../../plugins/Candela';
import './Authentication.css'; import './Authentication.css';
@ -7,16 +8,14 @@ interface AuthenticationProps {
client: Client; client: Client;
} }
type AuthState = "NoAdmin" | "Unauthed" | "Authed";
export const Authentication = ({ client, children }: PropsWithChildren<AuthenticationProps>) => { export const Authentication = ({ client, children }: PropsWithChildren<AuthenticationProps>) => {
// No admin password set: prompt for the admin password // No admin password set: prompt for the admin password
// Password set, nobody logged in: prompt for login // Password set, nobody logged in: prompt for login
// User logged in: show the children // User logged in: show the children
let [state, setState] = useState<AuthState>("Unauthed"); let { auth } = useContext(AppContext);
switch (state) { switch (auth.type) {
case "NoAdmin": { case "NoAdmin": {
return <div className="auth"> return <div className="auth">
<div className="card"> <div className="card">
@ -43,7 +42,7 @@ export const Authentication = ({ client, children }: PropsWithChildren<Authentic
return <div> {children} </div>; return <div> {children} </div>;
} }
default: { default: {
assertNever(state); assertNever(auth);
return <div></div>; return <div></div>;
} }
} }

View File

@ -1,5 +1,6 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { Client, PlayingField } from '../../client'; import { Client, PlayingField } from '../../client';
import { AppContext } from '../../components/StateProvider';
import { TabletopElement } from '../../components/Tabletop/Tabletop'; import { TabletopElement } from '../../components/Tabletop/Tabletop';
import { ThumbnailElement } from '../../components/Thumbnail/Thumbnail'; import { ThumbnailElement } from '../../components/Thumbnail/Thumbnail';
import { WebsocketContext } from '../../components/WebsocketProvider'; import { WebsocketContext } from '../../components/WebsocketProvider';
@ -10,7 +11,7 @@ interface GmViewProps {
} }
export const GmView = ({ client }: GmViewProps) => { export const GmView = ({ client }: GmViewProps) => {
const { tabletop } = useContext(WebsocketContext); const { tabletop } = useContext(AppContext);
const [images, setImages] = useState<string[]>([]); const [images, setImages] = useState<string[]>([]);
useEffect(() => { useEffect(() => {

View File

@ -4,6 +4,7 @@ import { WebsocketContext } from '../../components/WebsocketProvider';
import { Client } from '../../client'; import { Client } from '../../client';
import { TabletopElement } from '../../components/Tabletop/Tabletop'; import { TabletopElement } from '../../components/Tabletop/Tabletop';
import Candela from '../../plugins/Candela'; import Candela from '../../plugins/Candela';
import { AppContext } from '../../components/StateProvider';
const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803"; const TEST_CHARSHEET_UUID = "12df9c09-1f2f-4147-8eda-a97bd2a7a803";
@ -12,7 +13,7 @@ interface PlayerViewProps {
} }
export const PlayerView = ({ client }: PlayerViewProps) => { export const PlayerView = ({ client }: PlayerViewProps) => {
const { tabletop } = useContext(WebsocketContext); const { tabletop } = useContext(AppContext);
const [charsheet, setCharsheet] = useState(undefined); const [charsheet, setCharsheet] = useState(undefined);