Set up a tabletop view for both the GM and the player #260

Merged
savanni merged 15 commits from visions-playfield into main 2024-11-20 04:06:13 +00:00
4 changed files with 62 additions and 71 deletions
Showing only changes of commit e5deaa51d9 - Show all commits

View File

@ -99,7 +99,7 @@ pub async fn handle_connect_websocket(
let core = core.clone(); let core = core.clone();
async move { async move {
let (mut ws_sender, _) = socket.split(); let (mut ws_sender, _) = socket.split();
let mut receiver = core.connect_client(client_id); let mut receiver = core.connect_client(client_id.clone());
tokio::task::spawn(async move { tokio::task::spawn(async move {
let background_image = core.0.read().unwrap().playfield_background.clone(); let background_image = core.0.read().unwrap().playfield_background.clone();
@ -114,6 +114,7 @@ pub async fn handle_connect_websocket(
.send(Message::text(serde_json::to_string(&msg).unwrap())) .send(Message::text(serde_json::to_string(&msg).unwrap()))
.await; .await;
} }
println!("process ended for id {}", client_id);
}); });
} }
}) })

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css'; import './App.css';
import { WebsocketPlayingFieldComponent } from './components/PlayingField/PlayingField'; import { PlayingFieldComponent} from './components/PlayingField/PlayingField';
import { Client } from './client'; import { Client } from './client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { GmPlayingFieldComponent } from './components/GmPlayingField/GmPlayingField'; import { GmPlayingFieldComponent } from './components/GmPlayingField/GmPlayingField';
import { WebsocketProvider } from './components/WebsocketProvider';
interface AppProps { interface AppProps {
client: Client; client: Client;
@ -26,7 +26,7 @@ const App = ({ client }: AppProps) => {
}, },
{ {
path: "/", path: "/",
element: websocketUrl ? <WebsocketPlayingFieldComponent client={client} websocketUrl={websocketUrl} /> : <div> </div> element: websocketUrl ? <WebsocketProvider websocketUrl={websocketUrl}> <PlayingFieldComponent client={client} /> </WebsocketProvider> : <div> </div>
} }
]); ]);
return ( return (

View File

@ -1,80 +1,22 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useContext } from 'react';
import { Client, PlayingField } from '../../client';
import { ThumbnailComponent } from '../Thumbnail/Thumbnail';
import './PlayingField.css'; import './PlayingField.css';
import useWebSocket, { ReadyState } from 'react-use-websocket'; import { WebsocketContext } from '../WebsocketProvider';
import { Client } from '../../client';
/* interface PlayingFieldComponentProps {
export const PlayingFieldComponent = ({ client }: PlayingFieldProps) => {
const [socketUrl, setSocketUrl] = useState<string | undefined>(undefined);
const [field, setField] = useState<PlayingField | undefined>(undefined);
const [images, setImages] = useState<string[]>([]);
useEffect(() => {
client.availableImages().then((images) => setImages(images));
}, [client]);
const backgroundUrl = field && client.imageUrl(field.backgroundImage);
return (<div className="playing-field">
<div>
{images.map((imageName) => <ThumbnailComponent client={client} imageId={imageName} />)}
</div>
<div className="playing-field__background"> {backgroundUrl && <img src={backgroundUrl.toString()} alt="playing field" />} </div>
<div> Right Panel </div>
</div>)
}
*/
interface WebsocketPlayingFieldProps {
client: Client; client: Client;
websocketUrl: string;
} }
type Message = | export const PlayingFieldComponent = ({ client }: PlayingFieldComponentProps) => {
{ const { backgroundImage } = useContext(WebsocketContext);
type: "PlayArea"; console.log("backgroundImage", backgroundImage);
background_image: string; if (backgroundImage) {
}
export const WebsocketPlayingFieldComponent = ({ client, websocketUrl }: WebsocketPlayingFieldProps) => {
const { lastMessage, readyState } = useWebSocket(websocketUrl);
const [backgroundUrl, setBackgroundUrl] = useState<URL | undefined>(undefined);
useEffect(() => {
if (lastMessage !== null) {
const message: Message = JSON.parse(lastMessage.data);
console.log("playing area: ", message);
console.log("playing area: ", message.background_image);
setBackgroundUrl(client.imageUrl(message.background_image));
}
}, [lastMessage]);
const connectionStatus = {
[ReadyState.CONNECTING]: 'Connecting',
[ReadyState.OPEN]: 'Open',
[ReadyState.CLOSING]: 'Closing',
[ReadyState.CLOSED]: 'Closed',
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
}[readyState];
return <PlayingFieldComponent backgroundUrl={backgroundUrl} connectionStatus={connectionStatus} />;
}
interface PlayingFieldProps {
backgroundUrl: URL | undefined;
connectionStatus: string;
}
export const PlayingFieldComponent = ({ backgroundUrl, connectionStatus }: PlayingFieldProps) => {
if (backgroundUrl) {
return (<div className="playing-field"> return (<div className="playing-field">
<div className="playing-field__background"> {backgroundUrl && <img src={backgroundUrl.toString()} alt="playing field" />} </div> <div className="playing-field__background"> {backgroundImage && <img src={client.imageUrl(backgroundImage).toString()} alt="playing field" />} </div>
<div> {connectionStatus} </div>
</div>) </div>)
} else { } else {
return (<div className="playing-field"> return (<div className="playing-field">
<div> </div> <div> </div>
<div> {connectionStatus} </div>
</div> </div>
); );
} }

View File

@ -0,0 +1,48 @@
import React, { createContext, PropsWithChildren, useEffect, useReducer } from "react";
import useWebSocket from "react-use-websocket";
type Message = |
{
type: "PlayArea";
background_image: string;
}
type TabletopState = {
backgroundImage: string | undefined;
}
const initialState = () => ({ backgroundImage: undefined });
export const WebsocketContext = createContext<TabletopState>(initialState());
interface WebsocketProviderProps {
websocketUrl: string;
}
export const WebsocketProvider = ({ websocketUrl, children }: PropsWithChildren<WebsocketProviderProps>) => {
const { lastMessage } = useWebSocket(websocketUrl);
const [state, dispatch] = useReducer(handleMessage, initialState());
useEffect(() => {
if (lastMessage !== null) {
const message: Message = JSON.parse(lastMessage.data);
dispatch(message);
}
}, [lastMessage]);
return (<WebsocketContext.Provider value={state}>
{children}
</WebsocketContext.Provider>);
}
const handleMessage = (state: TabletopState, message: Message): TabletopState => {
switch (message.type) {
case "PlayArea": {
return {
...state,
backgroundImage: message.background_image,
}
}
}
}