Set up a tabletop view for both the GM and the player #260
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue