Set up a tabletop view for both the GM and the player #260
|
@ -71,9 +71,16 @@ pub struct RegisterResponse {
|
||||||
pub async fn handle_register_client(core: Core, request: RegisterRequest) -> impl Reply {
|
pub async fn handle_register_client(core: Core, request: RegisterRequest) -> impl Reply {
|
||||||
let client_id = core.register_client();
|
let client_id = core.register_client();
|
||||||
|
|
||||||
warp::reply::json(&RegisterResponse {
|
Response::builder()
|
||||||
|
.header("Access-Control-Allow-Origin", "*")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(
|
||||||
|
serde_json::to_string(&RegisterResponse {
|
||||||
url: format!("ws://127.0.0.1:8001/ws/{}", client_id),
|
url: format!("ws://127.0.0.1:8001/ws/{}", client_id),
|
||||||
})
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_unregister_client(core: Core, client_id: String) -> impl Reply {
|
pub async fn handle_unregister_client(core: Core, client_id: String) -> impl Reply {
|
||||||
|
@ -95,14 +102,16 @@ pub async fn handle_connect_websocket(
|
||||||
let mut receiver = core.connect_client(client_id);
|
let mut receiver = core.connect_client(client_id);
|
||||||
|
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
let mut i = 0;
|
|
||||||
ws_sender.send(Message::text(serde_json::to_string(&crate::types::Message::Count(0)).unwrap())).await;
|
|
||||||
while let Some(msg) = receiver.recv().await {
|
|
||||||
let _ = ws_sender
|
let _ = ws_sender
|
||||||
.send(Message::text(
|
.send(Message::text(
|
||||||
serde_json::to_string(&msg).unwrap(),
|
serde_json::to_string(&crate::types::Message::Count(0)).unwrap(),
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
|
while let Some(msg) = receiver.recv().await {
|
||||||
|
println!("Relaying message: {:?}", msg);
|
||||||
|
let _ = ws_sender
|
||||||
|
.send(Message::text(serde_json::to_string(&msg).unwrap()))
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,7 @@ pub async fn main() {
|
||||||
.map({
|
.map({
|
||||||
let core = core.clone();
|
let core = core.clone();
|
||||||
move |body| {
|
move |body| {
|
||||||
|
println!("route_publish: {:?}", body);
|
||||||
core.publish(body);
|
core.publish(body);
|
||||||
warp::reply()
|
warp::reply()
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,9 +10,14 @@
|
||||||
"@types/node": "^16.18.119",
|
"@types/node": "^16.18.119",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"@types/react-router": "^5.1.20",
|
||||||
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router": "^6.28.0",
|
||||||
|
"react-router-dom": "^6.28.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"react-use-websocket": "^4.11.1",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,14 +1,34 @@
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import logo from './logo.svg';
|
import logo from './logo.svg';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { PlayingFieldComponent } from './components/PlayingField/PlayingField';
|
import { WebsocketPlayingFieldComponent } from './components/PlayingField/PlayingField';
|
||||||
import { Client } from './client';
|
import { Client } from './client';
|
||||||
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||||
|
import { GmPlayingFieldComponent } from './components/GmPlayingField/GmPlayingField';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
let client = new Client();
|
console.log("rendering app");
|
||||||
|
const client = new Client();
|
||||||
|
const [websocketUrl, setWebsocketUrl] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
client.registerWebsocket().then((url) => setWebsocketUrl(url));
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
|
let router =
|
||||||
|
createBrowserRouter([
|
||||||
|
{
|
||||||
|
path: "/gm",
|
||||||
|
element: <GmPlayingFieldComponent client={client} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: websocketUrl ? <WebsocketPlayingFieldComponent websocketUrl={websocketUrl} /> : <div> </div>
|
||||||
|
}
|
||||||
|
]);
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<PlayingFieldComponent client={client} />
|
<RouterProvider router={router} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,20 @@ export class Client {
|
||||||
this.base = new URL("http://localhost:8001");
|
this.base = new URL("http://localhost:8001");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerWebsocket() {
|
||||||
|
const url = new URL(this.base);
|
||||||
|
url.pathname = `api/v1/client`;
|
||||||
|
return fetch(url, { method: 'POST' }).then((response) => response.json()).then((ws) => ws.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
unregisterWebsocket() {
|
||||||
|
const url = new URL(this.base);
|
||||||
|
url.pathname = `api/v1/client`;
|
||||||
|
return fetch(url, { method: 'POST' }).then((response => response.json()));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
imageUrl(imageId: string) {
|
imageUrl(imageId: string) {
|
||||||
const url = new URL(this.base);
|
const url = new URL(this.base);
|
||||||
url.pathname = `/api/v1/image/${imageId}`;
|
url.pathname = `/api/v1/image/${imageId}`;
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Client, PlayingField } from '../../client';
|
||||||
|
import { ThumbnailComponent } from '../Thumbnail/Thumbnail';
|
||||||
|
import './GmPlayingField.css';
|
||||||
|
|
||||||
|
interface GmPlayingFieldProps {
|
||||||
|
client: Client
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GmPlayingFieldComponent = ({ client }: GmPlayingFieldProps) => {
|
||||||
|
const [field, setField] = useState<PlayingField | undefined>(undefined);
|
||||||
|
useEffect(() => {
|
||||||
|
client.playingField().then((field) => setField(field));
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
|
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>)
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { Client, PlayingField } from '../../client';
|
import { Client, PlayingField } from '../../client';
|
||||||
import { ThumbnailComponent } from '../Thumbnail';
|
import { ThumbnailComponent } from '../Thumbnail/Thumbnail';
|
||||||
import './PlayingField.css';
|
import './PlayingField.css';
|
||||||
|
import useWebSocket, { ReadyState } from 'react-use-websocket';
|
||||||
|
|
||||||
interface PlayingFieldProps {
|
/*
|
||||||
client: Client
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PlayingFieldComponent = ({ client }: PlayingFieldProps) => {
|
export const PlayingFieldComponent = ({ client }: PlayingFieldProps) => {
|
||||||
|
const [socketUrl, setSocketUrl] = useState<string | undefined>(undefined);
|
||||||
const [field, setField] = useState<PlayingField | undefined>(undefined);
|
const [field, setField] = useState<PlayingField | undefined>(undefined);
|
||||||
useEffect(() => {
|
|
||||||
client.playingField().then((field) => setField(field));
|
|
||||||
}, [client]);
|
|
||||||
|
|
||||||
const [images, setImages] = useState<string[]>([]);
|
const [images, setImages] = useState<string[]>([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -27,3 +23,48 @@ export const PlayingFieldComponent = ({ client }: PlayingFieldProps) => {
|
||||||
<div> Right Panel </div>
|
<div> Right Panel </div>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface WebsocketPlayingFieldProps {
|
||||||
|
websocketUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WebsocketPlayingFieldComponent = ({ websocketUrl }: WebsocketPlayingFieldProps) => {
|
||||||
|
const { lastMessage, readyState } = useWebSocket(websocketUrl);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastMessage !== null) {
|
||||||
|
console.log("last message: ", lastMessage);
|
||||||
|
}
|
||||||
|
}, [lastMessage]);
|
||||||
|
|
||||||
|
const connectionStatus = {
|
||||||
|
[ReadyState.CONNECTING]: 'Connecting',
|
||||||
|
[ReadyState.OPEN]: 'Open',
|
||||||
|
[ReadyState.CLOSING]: 'Closing',
|
||||||
|
[ReadyState.CLOSED]: 'Closed',
|
||||||
|
[ReadyState.UNINSTANTIATED]: 'Uninstantiated',
|
||||||
|
}[readyState];
|
||||||
|
|
||||||
|
return <PlayingFieldComponent backgroundUrl={undefined} connectionStatus={connectionStatus} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlayingFieldProps {
|
||||||
|
backgroundUrl: string | undefined;
|
||||||
|
connectionStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlayingFieldComponent = ({ backgroundUrl, connectionStatus }: PlayingFieldProps) => {
|
||||||
|
if (backgroundUrl) {
|
||||||
|
return (<div className="playing-field">
|
||||||
|
<div className="playing-field__background"> {backgroundUrl && <img src={backgroundUrl.toString()} alt="playing field" />} </div>
|
||||||
|
<div> {connectionStatus} </div>
|
||||||
|
</div>)
|
||||||
|
} else {
|
||||||
|
return (<div className="playing-field">
|
||||||
|
<div> </div>
|
||||||
|
<div> {connectionStatus} </div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Client } from '../client';
|
import { Client } from '../../client';
|
||||||
import './Thumbnail.css';
|
import './Thumbnail.css';
|
||||||
|
|
||||||
interface ThumbnailProps {
|
interface ThumbnailProps {
|
Loading…
Reference in New Issue