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
6 changed files with 38 additions and 15 deletions
Showing only changes of commit 45275be11b - Show all commits

View File

@ -8,7 +8,7 @@ use std::{
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use uuid::Uuid; use uuid::Uuid;
use crate::types::Message; use crate::types::{AppError, Message, PlayArea};
#[derive(Debug)] #[derive(Debug)]
struct WebsocketClient { struct WebsocketClient {
@ -19,6 +19,8 @@ struct WebsocketClient {
pub struct AppState { pub struct AppState {
pub image_base: PathBuf, pub image_base: PathBuf,
pub playfield_background: String,
pub clients: HashMap<String, WebsocketClient>, pub clients: HashMap<String, WebsocketClient>,
} }
@ -29,6 +31,7 @@ impl Core {
pub fn new() -> Self { pub fn new() -> Self {
Self(Arc::new(RwLock::new(AppState { Self(Arc::new(RwLock::new(AppState {
image_base: PathBuf::from("/home/savanni/Pictures"), image_base: PathBuf::from("/home/savanni/Pictures"),
playfield_background: "moon.jpg".to_owned(),
clients: HashMap::new(), clients: HashMap::new(),
}))) })))
} }
@ -95,6 +98,15 @@ impl Core {
.collect() .collect()
} }
pub fn set_playfield_background(&self, path: String) -> Result<(), AppError> {
{
let mut state = self.0.write().unwrap();
state.playfield_background = path.clone();
}
self.publish(Message::PlayArea(PlayArea{ background_image: path }));
Ok(())
}
pub fn publish(&self, message: Message) { pub fn publish(&self, message: Message) {
let state = self.0.read().unwrap(); let state = self.0.read().unwrap();

View File

@ -98,13 +98,14 @@ pub async fn handle_connect_websocket(
ws.on_upgrade(move |socket| { ws.on_upgrade(move |socket| {
let core = core.clone(); let core = core.clone();
async move { async move {
let (mut ws_sender, mut ws_recv) = socket.split(); let (mut ws_sender, _) = socket.split();
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 background_image = core.0.read().unwrap().playfield_background.clone();
let _ = ws_sender let _ = ws_sender
.send(Message::text( .send(Message::text(
serde_json::to_string(&crate::types::Message::Count(0)).unwrap(), serde_json::to_string(&crate::types::Message::PlayArea(PlayArea{ background_image })).unwrap(),
)) ))
.await; .await;
while let Some(msg) = receiver.recv().await { while let Some(msg) = receiver.recv().await {

View File

@ -134,14 +134,13 @@ pub async fn main() {
move |ws, client_id| handle_connect_websocket(core.clone(), ws, client_id) move |ws, client_id| handle_connect_websocket(core.clone(), ws, client_id)
}); });
let route_publish = warp::path!("api" / "v1" / "message") let route_set_playfield_bg = warp::path!("api" / "v1" / "playfield" / "bg")
.and(warp::post()) .and(warp::put())
.and(warp::body::json()) .and(warp::body::json())
.map({ .map({
let core = core.clone(); let core = core.clone();
move |body| { move |body| {
println!("route_publish: {:?}", body); core.set_playfield_background(body);
core.publish(body);
warp::reply() warp::reply()
} }
}); });
@ -152,7 +151,7 @@ pub async fn main() {
.or(route_playing_field) .or(route_playing_field)
.or(route_image) .or(route_image)
.or(route_available_images) .or(route_available_images)
.or(route_publish) .or(route_set_playfield_bg)
.recover(handle_rejection); .recover(handle_rejection);
let server = warp::serve(filter); let server = warp::serve(filter);

View File

@ -12,9 +12,9 @@ pub enum AppError {
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum Message { pub enum Message {
Count(u32), PlayArea(PlayArea),
// PlayArea(PlayArea),
} }

View File

@ -26,7 +26,7 @@ const App = ({ client }: AppProps) => {
}, },
{ {
path: "/", path: "/",
element: websocketUrl ? <WebsocketPlayingFieldComponent websocketUrl={websocketUrl} /> : <div> </div> element: websocketUrl ? <WebsocketPlayingFieldComponent client={client} websocketUrl={websocketUrl} /> : <div> </div>
} }
]); ]);
return ( return (

View File

@ -26,15 +26,26 @@ export const PlayingFieldComponent = ({ client }: PlayingFieldProps) => {
*/ */
interface WebsocketPlayingFieldProps { interface WebsocketPlayingFieldProps {
client: Client;
websocketUrl: string; websocketUrl: string;
} }
export const WebsocketPlayingFieldComponent = ({ websocketUrl }: WebsocketPlayingFieldProps) => { type Message = |
{
type: "PlayArea";
background_image: string;
}
export const WebsocketPlayingFieldComponent = ({ client, websocketUrl }: WebsocketPlayingFieldProps) => {
const { lastMessage, readyState } = useWebSocket(websocketUrl); const { lastMessage, readyState } = useWebSocket(websocketUrl);
const [backgroundUrl, setBackgroundUrl] = useState<URL | undefined>(undefined);
useEffect(() => { useEffect(() => {
if (lastMessage !== null) { if (lastMessage !== null) {
console.log("last message: ", lastMessage); 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]); }, [lastMessage]);
@ -46,11 +57,11 @@ export const WebsocketPlayingFieldComponent = ({ websocketUrl }: WebsocketPlayin
[ReadyState.UNINSTANTIATED]: 'Uninstantiated', [ReadyState.UNINSTANTIATED]: 'Uninstantiated',
}[readyState]; }[readyState];
return <PlayingFieldComponent backgroundUrl={undefined} connectionStatus={connectionStatus} />; return <PlayingFieldComponent backgroundUrl={backgroundUrl} connectionStatus={connectionStatus} />;
} }
interface PlayingFieldProps { interface PlayingFieldProps {
backgroundUrl: string | undefined; backgroundUrl: URL | undefined;
connectionStatus: string; connectionStatus: string;
} }