refactoring + add pub
This commit is contained in:
parent
7ca48a2448
commit
e235fcc749
27
pub/index.html
Normal file
27
pub/index.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>LJ Sketch</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="main">
|
||||||
|
<div id="toolbox">
|
||||||
|
<div id="colors"></div>
|
||||||
|
<input type="color" id="selectedColor"
|
||||||
|
class="button2x" value="#ffffff"></input>
|
||||||
|
<input type="button" id="clearButton"
|
||||||
|
class="button2x" value="♻"></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<canvas id="canvas" width="1024" height="1024"></canvas>
|
||||||
|
<span class="footer">LJ Sketch</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="main.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
130
pub/main.js
Normal file
130
pub/main.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
const colors = [
|
||||||
|
"#000000",
|
||||||
|
"#1D2B53",
|
||||||
|
"#7E2553",
|
||||||
|
"#008751",
|
||||||
|
"#AB5236",
|
||||||
|
"#5F574F",
|
||||||
|
"#C2C3C7",
|
||||||
|
"#FFF1E8",
|
||||||
|
"#FF004D",
|
||||||
|
"#FFA300",
|
||||||
|
"#FFEC27",
|
||||||
|
"#00E436",
|
||||||
|
"#29ADFF",
|
||||||
|
"#83769C",
|
||||||
|
"#FF77A8",
|
||||||
|
"#FFCCAA"
|
||||||
|
];
|
||||||
|
|
||||||
|
const initUI = (colorsDiv, selectedColor) => {
|
||||||
|
for (const color of colors) {
|
||||||
|
(color => {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.setAttribute("class", "color");
|
||||||
|
div.setAttribute("style", "background: "+color+";");
|
||||||
|
div.onclick = evt => { selectedColor.value = color; };
|
||||||
|
colorsDiv.appendChild(div);
|
||||||
|
})(color);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Get Mouse Position
|
||||||
|
const getMousePoint = (canvas, evt, color) => {
|
||||||
|
var rect = canvas.getBoundingClientRect();
|
||||||
|
const x = Math.floor(canvas.width * (evt.clientX - rect.left) / rect.width);
|
||||||
|
const y = Math.floor(canvas.height * (evt.clientY - rect.top) / rect.width);
|
||||||
|
return { x, y, color };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLine = (ctx, line) => {
|
||||||
|
ctx.strokeStyle = line[0].color;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(line[0].x, line[0].y);
|
||||||
|
for(let i=1; i < line.length; ++i) {
|
||||||
|
ctx.lineTo(line[i].x, line[i].y);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCanvas = (canvas, ctx, lines) => {
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
for(let line of lines) {
|
||||||
|
updateLine(ctx, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initDrawing = (canvas, clearButton, selectedColor, ws) => {
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
let lines = [];
|
||||||
|
let currentLine = null;
|
||||||
|
|
||||||
|
const resetCanvas = () => {
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
};
|
||||||
|
resetCanvas();
|
||||||
|
|
||||||
|
clearButton.onclick = evt => {
|
||||||
|
lines = [];
|
||||||
|
resetCanvas();
|
||||||
|
ws.send(JSON.stringify({ t: "clear" }));
|
||||||
|
};
|
||||||
|
|
||||||
|
canvas.addEventListener("mousedown", evt => {
|
||||||
|
const pt = getMousePoint(canvas, evt, selectedColor.value);
|
||||||
|
currentLine = [ pt ];
|
||||||
|
ws.send(JSON.stringify({ ...pt, t: "moveTo" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener("mousemove", evt => {
|
||||||
|
if(evt.buttons === 1) {
|
||||||
|
const pt = getMousePoint(canvas, evt, selectedColor.value);
|
||||||
|
currentLine.push(pt);
|
||||||
|
updateLine(ctx, currentLine);
|
||||||
|
ws.send(JSON.stringify({ ...pt, t: "lineTo" }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener("mouseup", evt => {
|
||||||
|
console.log(currentLine);
|
||||||
|
currentLine = null;
|
||||||
|
ws.send(JSON.stringify({ t: "stroke"}));
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('message', function (event) {
|
||||||
|
let j = JSON.parse(event.data);
|
||||||
|
console.log(j);
|
||||||
|
if (j.t === "line") {
|
||||||
|
let line = j.line.map(a => ({ x: a[0], y: a[1], color: a[2] }));
|
||||||
|
lines.push(line);
|
||||||
|
updateCanvas(canvas, ctx, lines);
|
||||||
|
} else if (j.t == "clear") {
|
||||||
|
lines = [];
|
||||||
|
resetCanvas();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const initWs = () => {
|
||||||
|
const socket = new WebSocket('ws://localhost:3000/ws');
|
||||||
|
|
||||||
|
socket.addEventListener('open', function (event) {
|
||||||
|
});
|
||||||
|
|
||||||
|
return socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
const colorsDiv = document.querySelector("#colors");
|
||||||
|
const selectedColor = document.querySelector("#selectedColor");
|
||||||
|
const clearButton = document.querySelector("#clearButton");
|
||||||
|
const canvas = document.querySelector("#canvas");
|
||||||
|
initUI(colorsDiv, selectedColor);
|
||||||
|
let ws = initWs();
|
||||||
|
initDrawing(canvas, clearButton, selectedColor, ws);
|
||||||
|
|
||||||
|
};
|
66
pub/style.css
Normal file
66
pub/style.css
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
background: #222;
|
||||||
|
color: #ddd;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
grid-auto-rows: minmax(100px, auto);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
margin: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas:hover {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbox {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 128px;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button2x {
|
||||||
|
width: 128px !important;
|
||||||
|
height: 128px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbox > input {
|
||||||
|
display: inline-block;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
font-size: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#colors {
|
||||||
|
}
|
||||||
|
|
||||||
|
.color {
|
||||||
|
display: inline-block;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,25 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tokio::sync::mpsc:: {Sender, Receiver};
|
use crate::line::Line;
|
||||||
use crate::ws_client::Line;
|
use tokio::sync::mpsc::{self, Sender, Receiver};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum GSMsg {
|
pub enum GSMsg {
|
||||||
NewClient((SocketAddr, Sender<GSMsg>)),
|
NewClient((SocketAddr, Sender<GSMsg>)),
|
||||||
NewLine(Line),
|
|
||||||
DeleteClient(SocketAddr),
|
DeleteClient(SocketAddr),
|
||||||
|
NewLine(Line),
|
||||||
Clear
|
Clear
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub fn spawn() -> Sender<GSMsg> {
|
||||||
pub gs_tx: Sender<GSMsg>
|
let (tx, rx) = mpsc::channel(32);
|
||||||
|
tokio::spawn(gen_server(rx));
|
||||||
|
tx
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn gen_server(mut rx: Receiver<GSMsg>) {
|
async fn gen_server(mut rx: Receiver<GSMsg>) {
|
||||||
let mut clients : HashMap<SocketAddr, Sender<GSMsg>> =
|
let mut clients: HashMap<SocketAddr, Sender<GSMsg>> = HashMap::new();
|
||||||
HashMap::new();
|
let mut lines: Vec<Line> = vec![];
|
||||||
|
|
||||||
let mut lines : Vec<Line> = vec![];
|
|
||||||
|
|
||||||
while let Some(msg) = rx.recv().await {
|
while let Some(msg) = rx.recv().await {
|
||||||
match msg {
|
match msg {
|
||||||
@ -36,8 +36,8 @@ pub async fn gen_server(mut rx: Receiver<GSMsg>) {
|
|||||||
lines.push(line);
|
lines.push(line);
|
||||||
},
|
},
|
||||||
GSMsg::DeleteClient(addr) => {
|
GSMsg::DeleteClient(addr) => {
|
||||||
tracing::info!("Client {addr} removed");
|
|
||||||
clients.remove(&addr);
|
clients.remove(&addr);
|
||||||
|
tracing::info!("Client {addr} removed");
|
||||||
},
|
},
|
||||||
GSMsg::Clear => {
|
GSMsg::Clear => {
|
||||||
send_all(&mut clients, &GSMsg::Clear).await;
|
send_all(&mut clients, &GSMsg::Clear).await;
|
||||||
@ -52,8 +52,8 @@ async fn send_all(
|
|||||||
msg: &GSMsg
|
msg: &GSMsg
|
||||||
) {
|
) {
|
||||||
let mut to_remove : Vec<SocketAddr> = vec![];
|
let mut to_remove : Vec<SocketAddr> = vec![];
|
||||||
|
|
||||||
for (addr, ref mut tx) in &mut *clients {
|
for (addr, ref mut tx) in clients.iter() {
|
||||||
let ret = tx
|
let ret = tx
|
||||||
.send(msg.clone())
|
.send(msg.clone())
|
||||||
.await;
|
.await;
|
||||||
|
19
src/line.rs
Normal file
19
src/line.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use geo::Simplify;
|
||||||
|
|
||||||
|
pub type Line = Vec<(f32,f32,u32)>;
|
||||||
|
|
||||||
|
pub fn simplify_line(line: &Line) -> Line {
|
||||||
|
if line.len() < 2 {
|
||||||
|
return line.to_vec();
|
||||||
|
}
|
||||||
|
let color = line[0].2;
|
||||||
|
let linestring : geo::LineString =
|
||||||
|
line.iter()
|
||||||
|
.map(| (x, y, _) | (*x as f64, *y as f64 ))
|
||||||
|
.collect();
|
||||||
|
let linestring = linestring.simplify(&4.0);
|
||||||
|
linestring.0.iter()
|
||||||
|
.map(| c | (c.x as f32, c.y as f32, color))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
41
src/main.rs
41
src/main.rs
@ -1,5 +1,6 @@
|
|||||||
mod gen_server;
|
mod gen_server;
|
||||||
mod ws_client;
|
mod ws_client;
|
||||||
|
mod line;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{
|
extract::{
|
||||||
@ -11,20 +12,15 @@ use axum::{
|
|||||||
Router,
|
Router,
|
||||||
Extension
|
Extension
|
||||||
};
|
};
|
||||||
|
use axum::extract::connect_info::ConnectInfo;
|
||||||
use std::{net::SocketAddr, path::PathBuf};
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
services::ServeDir,
|
services::ServeDir,
|
||||||
trace::{DefaultMakeSpan, TraceLayer},
|
trace::{DefaultMakeSpan, TraceLayer},
|
||||||
};
|
};
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
use axum::extract::connect_info::ConnectInfo;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
use gen_server::GSMsg;
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::{
|
|
||||||
mpsc:: { self, Sender, Receiver },
|
|
||||||
Mutex
|
|
||||||
};
|
|
||||||
use gen_server::{State,GSMsg,gen_server};
|
|
||||||
|
|
||||||
const LISTEN_ON : &str = "0.0.0.0:3000";
|
const LISTEN_ON : &str = "0.0.0.0:3000";
|
||||||
|
|
||||||
@ -38,25 +34,19 @@ async fn main() {
|
|||||||
)
|
)
|
||||||
.with(tracing_subscriber::fmt::layer())
|
.with(tracing_subscriber::fmt::layer())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets");
|
|
||||||
|
|
||||||
let (gs_tx, gs_rx) : (Sender<GSMsg>, Receiver<GSMsg>) = mpsc::channel(32);
|
let gs_tx = gen_server::spawn();
|
||||||
|
|
||||||
let state = Arc::new(Mutex::new(State { gs_tx }));
|
let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("pub");
|
||||||
|
|
||||||
tokio::spawn(gen_server(gs_rx));
|
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.fallback_service(ServeDir::new(assets_dir)
|
.fallback_service(ServeDir::new(assets_dir)
|
||||||
.append_index_html_on_directories(true))
|
.append_index_html_on_directories(true))
|
||||||
.route("/ws", get(ws_handler))
|
.route("/ws", get(ws_handler))
|
||||||
.layer(Extension(state))
|
.layer(Extension(gs_tx))
|
||||||
.layer(
|
.layer(TraceLayer::new_for_http()
|
||||||
TraceLayer::new_for_http()
|
.make_span_with(DefaultMakeSpan::default()
|
||||||
.make_span_with(DefaultMakeSpan::default()
|
.include_headers(false)));
|
||||||
.include_headers(false)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let addr : SocketAddr = LISTEN_ON.parse().unwrap();
|
let addr : SocketAddr = LISTEN_ON.parse().unwrap();
|
||||||
|
|
||||||
@ -69,7 +59,7 @@ async fn main() {
|
|||||||
|
|
||||||
async fn ws_handler(
|
async fn ws_handler(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
Extension(state): Extension<Arc<Mutex<State>>>,
|
Extension(gs_tx): Extension<Sender<GSMsg>>,
|
||||||
user_agent: Option<TypedHeader<axum::headers::UserAgent>>,
|
user_agent: Option<TypedHeader<axum::headers::UserAgent>>,
|
||||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
@ -78,9 +68,6 @@ async fn ws_handler(
|
|||||||
} else {
|
} else {
|
||||||
String::from("Unknown browser")
|
String::from("Unknown browser")
|
||||||
};
|
};
|
||||||
tracing::info!("`{user_agent}` at {addr} connected.");
|
tracing::info!("{addr} connected [{user_agent}].");
|
||||||
// finalize the upgrade process by returning upgrade callback.
|
ws.on_upgrade(move |socket| ws_client::handle_socket(socket, addr, gs_tx))
|
||||||
// we can customize the callback by sending additional info such as address.
|
|
||||||
ws.on_upgrade(move |socket| ws_client::handle_socket(socket, addr, state))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
174
src/ws_client.rs
174
src/ws_client.rs
@ -1,20 +1,14 @@
|
|||||||
use crate::gen_server::{State,GSMsg};
|
|
||||||
|
|
||||||
use axum::extract::ws::{ Message, Message::Text, Message::Close, WebSocket };
|
use axum::extract::ws::{ Message, Message::Text, Message::Close, WebSocket };
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use tokio::sync::mpsc::{self, Sender};
|
||||||
use tokio::sync::{
|
|
||||||
mpsc:: { self, Sender, Receiver },
|
|
||||||
Mutex
|
|
||||||
};
|
|
||||||
use serde::{Serialize,Deserialize};
|
use serde::{Serialize,Deserialize};
|
||||||
use geo::Simplify;
|
|
||||||
|
|
||||||
use core::ops::ControlFlow;
|
use core::ops::ControlFlow;
|
||||||
|
use crate::gen_server::GSMsg;
|
||||||
|
use crate::line::{Line,simplify_line};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(tag = "t")]
|
#[serde(tag = "t")]
|
||||||
pub enum JMsg {
|
enum JMsg {
|
||||||
#[serde(rename = "clear")]
|
#[serde(rename = "clear")]
|
||||||
Clear,
|
Clear,
|
||||||
#[serde(rename = "moveTo")]
|
#[serde(rename = "moveTo")]
|
||||||
@ -27,126 +21,118 @@ pub enum JMsg {
|
|||||||
Line { line: Vec<(f32,f32,String)> }
|
Line { line: Vec<(f32,f32,String)> }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Line = Vec<(f32,f32,u32)>;
|
|
||||||
|
|
||||||
pub async fn handle_socket(
|
pub async fn handle_socket(
|
||||||
mut socket: WebSocket,
|
mut socket: WebSocket,
|
||||||
who: SocketAddr,
|
who: SocketAddr,
|
||||||
state: Arc<Mutex<State>>
|
gs_tx: Sender<GSMsg>
|
||||||
) {
|
) {
|
||||||
|
let (c_tx, mut c_rx) = mpsc::channel(32);
|
||||||
let (c_tx, mut c_rx) : (Sender<GSMsg>, Receiver<GSMsg>) = mpsc::channel(32);
|
gs_tx.send(GSMsg::NewClient((who, c_tx))).await.unwrap();
|
||||||
|
|
||||||
{
|
|
||||||
state.lock()
|
|
||||||
.await
|
|
||||||
.gs_tx.send(GSMsg::NewClient((who, c_tx)))
|
|
||||||
.await.unwrap();
|
|
||||||
}
|
|
||||||
let mut line : Line = vec![];
|
let mut line : Line = vec![];
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
Some(msg) = socket.recv() => {
|
Some(msg) = socket.recv() => {
|
||||||
match process_ws_msg(&state, &who, &mut line, msg).await {
|
let Ok(msg) = msg else {
|
||||||
ControlFlow::Break(()) => { return; },
|
tracing::warn!("{who}: Error receiving packet: {msg:?}");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
match process_ws_msg(&gs_tx, &who, &mut line, msg).await {
|
||||||
|
ControlFlow::Break(()) => break,
|
||||||
ControlFlow::Continue(()) => {}
|
ControlFlow::Continue(()) => {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(msg) = c_rx.recv() => {
|
Some(msg) = c_rx.recv() => {
|
||||||
match msg {
|
process_gs_msg(&mut socket, &who, msg).await
|
||||||
GSMsg::NewLine(line) => {
|
|
||||||
socket.send(Message::Text(line_to_json(&line)))
|
|
||||||
.await.unwrap();
|
|
||||||
},
|
|
||||||
GSMsg::Clear => {
|
|
||||||
let msg = serde_json::to_string(&JMsg::Clear).unwrap();
|
|
||||||
socket.send(Message::Text(msg))
|
|
||||||
.await.unwrap();
|
|
||||||
},
|
|
||||||
msg => {
|
|
||||||
tracing::info!("{who} should not get this: {:?}", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
tracing::warn!("{who}: Connection lost unexpectedly.");
|
tracing::warn!("{who}: Connection lost unexpectedly.");
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn process_gs_msg(socket: &mut WebSocket, who: &SocketAddr, msg: GSMsg) {
|
||||||
|
match msg {
|
||||||
|
GSMsg::NewLine(line) => {
|
||||||
|
socket.send(Message::Text(line_to_json(&line))).await.unwrap();
|
||||||
|
},
|
||||||
|
GSMsg::Clear => {
|
||||||
|
let msg = serde_json::to_string(&JMsg::Clear).unwrap();
|
||||||
|
socket.send(Message::Text(msg)).await.unwrap();
|
||||||
|
},
|
||||||
|
msg => {
|
||||||
|
tracing::info!("{who} should not get this: {:?}", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn process_ws_msg(
|
async fn process_ws_msg(
|
||||||
state: &Arc<Mutex<State>>,
|
gs_tx: &Sender<GSMsg>,
|
||||||
who: &SocketAddr,
|
who: &SocketAddr,
|
||||||
line: &mut Line,
|
line: &mut Line,
|
||||||
msg: Result<Message,axum::Error>
|
msg: Message
|
||||||
) -> ControlFlow<(),()> {
|
) -> ControlFlow<(),()> {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(Text(msg)) => {
|
Text(text) => {
|
||||||
let Ok(msg) : Result<JMsg,_> = serde_json::from_str(&msg) else {
|
match serde_json::from_str(&text) {
|
||||||
tracing::warn!("{who}: Can't parse JSON: {:?}", msg);
|
Ok(json) => {
|
||||||
return ControlFlow::Continue(());
|
tracing::debug!("{who}: '{:?}'", json);
|
||||||
};
|
match handle_ws_msg(line, json) {
|
||||||
tracing::debug!("{who}: '{:?}'", msg);
|
Ok(Some(req)) => gs_tx.send(req).await.unwrap(),
|
||||||
match msg {
|
Ok(None) => {},
|
||||||
JMsg::Clear => {
|
Err(err) => {
|
||||||
state.lock()
|
tracing::warn!("{who}: message error: {err}");
|
||||||
.await
|
}
|
||||||
.gs_tx.send(GSMsg::Clear)
|
|
||||||
.await.unwrap();
|
|
||||||
line.clear();
|
|
||||||
},
|
|
||||||
JMsg::MoveTo { x, y, color } => {
|
|
||||||
*line = vec![ (x, y, parse_color(color)) ];
|
|
||||||
},
|
|
||||||
JMsg::LineTo { x, y, color } => {
|
|
||||||
line.push( (x, y, parse_color(color)) );
|
|
||||||
},
|
|
||||||
JMsg::Stroke => {
|
|
||||||
if line.len() > 1 {
|
|
||||||
state.lock()
|
|
||||||
.await
|
|
||||||
.gs_tx.send(GSMsg::NewLine(simplify_line(line)))
|
|
||||||
.await.unwrap();
|
|
||||||
}
|
}
|
||||||
*line = vec![];
|
|
||||||
},
|
},
|
||||||
JMsg::Line{..} => { panic!("recieved a line message :/"); }
|
Err(err) => {
|
||||||
|
tracing::warn!("{who}: can't parse JSON: {err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Ok(Close(close)) => {
|
Close(close) => {
|
||||||
tracing::info!("{who}: closing: {:?}", close);
|
tracing::info!("{who}: closing: {:?}", close);
|
||||||
state.lock()
|
gs_tx.send(GSMsg::DeleteClient(*who)).await.unwrap();
|
||||||
.await
|
|
||||||
.gs_tx.send(GSMsg::DeleteClient(*who))
|
|
||||||
.await.unwrap();
|
|
||||||
return ControlFlow::Break(());
|
return ControlFlow::Break(());
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("{who}: Can't handle message: {:?}", msg);
|
tracing::warn!("{who}: can't handle message: {:?}", msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlFlow::Continue(())
|
ControlFlow::Continue(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simplify_line(line: &Line) -> Line {
|
fn handle_ws_msg(
|
||||||
if line.len() < 2 {
|
line: &mut Line,
|
||||||
return line.to_vec();
|
msg: JMsg
|
||||||
}
|
) -> Result<Option<GSMsg>, &'static str> {
|
||||||
let color = line[0].2;
|
match msg {
|
||||||
let linestring : geo::LineString =
|
JMsg::Clear => {
|
||||||
line.iter()
|
line.clear();
|
||||||
.map(| (x, y, _) | (*x as f64, *y as f64 ))
|
return Ok(Some(GSMsg::Clear));
|
||||||
.collect();
|
},
|
||||||
let linestring = linestring.simplify(&4.0);
|
JMsg::MoveTo { x, y, color } => {
|
||||||
linestring.0.iter()
|
*line = vec![ (x, y, parse_color(color)?) ];
|
||||||
.map(| c | (c.x as f32, c.y as f32, color))
|
},
|
||||||
.collect()
|
JMsg::LineTo { x, y, color } => {
|
||||||
|
line.push( (x, y, parse_color(color)?) );
|
||||||
|
},
|
||||||
|
JMsg::Stroke => {
|
||||||
|
if line.len() > 1 {
|
||||||
|
let line2 = simplify_line(line);
|
||||||
|
*line = vec![];
|
||||||
|
return Ok(Some(GSMsg::NewLine(line2)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
JMsg::Line{..} => {
|
||||||
|
tracing::warn!("recieved a line message O_o");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn line_to_json(line: &Line) -> String {
|
fn line_to_json(line: &Line) -> String {
|
||||||
let line = line.iter()
|
let line = line.iter()
|
||||||
.map(| (x, y, c) | {
|
.map(| (x, y, c) | {
|
||||||
@ -156,6 +142,10 @@ fn line_to_json(line: &Line) -> String {
|
|||||||
serde_json::to_string(&JMsg::Line{ line }).unwrap()
|
serde_json::to_string(&JMsg::Line{ line }).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_color(s: String) -> u32 {
|
fn parse_color(s: String) -> Result<u32, &'static str> {
|
||||||
u32::from_str_radix(&s[1..], 16).unwrap()
|
if s.len() != 7 || &s[0..1] != "#" {
|
||||||
|
Err("badly formated color.")
|
||||||
|
} else {
|
||||||
|
u32::from_str_radix(&s[1..], 16).map_err(|_| "unable to parse color")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user