diff --git a/README.md b/README.md index b289d43..4afb542 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,14 @@ To build application with `release` profile make: cargo run release ``` -That runs a build script which creates `dock` directory with a ready to start `Dockerfile`. Build a container with: +That runs a build script which creates `dock` directory with a ready to start `Dockerfile`. Build and run a container with: ``` -docker build -t voki ./dock +docker-compose up -d --build ``` -## Run -todo!() +Visit [localhost](http://localhost/) to open the application. Currently only http support, so ensure the browser opens a page with `http://` prefix. + +To show the server log make: +``` +docker-compose logs +``` diff --git a/base/src/api.rs b/base/src/api.rs index b13252d..c157b43 100644 --- a/base/src/api.rs +++ b/base/src/api.rs @@ -37,13 +37,21 @@ pub struct Channel { pub id: u32, pub name: String, pub icon: Option, + pub history: Vec, } -#[derive(BorrowDecode, Encode)] -pub enum ServerMessage<'a> { +#[derive(Clone, Decode, Encode)] +pub struct Message { + pub from: u32, + pub chan: u32, + pub text: String, +} + +#[derive(Decode, Encode)] +pub enum ServerMessage { Closed, LoggedIn(Result), User(User), Channel(Channel), - Said { from: u32, chan: u32, text: &'a str }, + Message(Message), } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6d02323 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3.9" +services: + voki: + build: ./dock + container_name: voki + ports: + - 80:80 + - 4567:4567 diff --git a/http/Rocket.toml b/http/Rocket.toml index da12e35..efef495 100644 --- a/http/Rocket.toml +++ b/http/Rocket.toml @@ -1,3 +1,5 @@ -[default] +[global] address = "0.0.0.0" -port = 8000 +port = 80 +log_level = "debug" +cli_colors = false diff --git a/http/src/main.rs b/http/src/main.rs index 5cb56df..9462225 100644 --- a/http/src/main.rs +++ b/http/src/main.rs @@ -1,8 +1,11 @@ use http::index; use rocket::{fs::FileServer, launch, routes}; +use std::process::Command; #[launch] -fn rocket() -> _ { +async fn rocket() -> _ { + Command::new("./server").spawn().expect("run server"); + rocket::build() .mount("/", FileServer::from("./static")) .mount("/api", routes![index]) diff --git a/server/src/manage.rs b/server/src/manage.rs index f06e21f..eff5a3c 100644 --- a/server/src/manage.rs +++ b/server/src/manage.rs @@ -114,6 +114,7 @@ pub async fn manage(mut receiver: Receiver) -> ! { let mut users = Users::new(); let channels = Channels::new(); let mut clients: HashMap = HashMap::default(); + let mut history = vec![]; loop { let event = receiver.recv().await.expect("channel is open"); @@ -164,16 +165,19 @@ pub async fn manage(mut receiver: Receiver) -> ! { let name = &user.name; println!("{name} ({chan}): {text}"); - // Send this to all + let message = Message { + from: id, + chan, + text: text.into(), + }; + + // Send this to all clients for client in clients.values() { - let message = ServerMessage::Said { - from: id, - chan, - text, - }; + let message = ServerMessage::Message(message.clone()); send(&client.sender, message).await; } + history.push(message); continue; } None => ServerMessage::Closed, @@ -208,6 +212,11 @@ pub async fn manage(mut receiver: Receiver) -> ! { id: chan.id, name: chan.name, icon: chan.icon, + history: history + .iter() + .filter(|message| message.chan == chan.id) + .cloned() + .collect(), }); send(sender, message).await; } @@ -217,7 +226,7 @@ pub async fn manage(mut receiver: Receiver) -> ! { } } -async fn send(sender: &Sender>, message: api::ServerMessage<'_>) { +async fn send(sender: &Sender>, message: api::ServerMessage) { let mut buf = Vec::with_capacity(64); encode(&message, &mut buf).expect("encode"); let _ = sender.send(buf).await; diff --git a/src/info.rs b/src/info.rs index 3e467c2..82cb99f 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,31 +1,27 @@ macro_rules! info { - ($l:expr, $e:expr) => {{ - use termion::{color, style}; - + ($l:expr, $e:expr) => { println!( "{}{}{:>12}{}{} {}", - color::Fg(color::Blue), - style::Bold, + ::termion::color::Fg(::termion::color::Blue), + ::termion::style::Bold, $l, - color::Fg(color::Reset), - style::Reset, + ::termion::color::Fg(::termion::color::Reset), + ::termion::style::Reset, $e ); - }}; + }; } pub(crate) use info; macro_rules! error { ($e:expr) => { - use termion::{color, style}; - println!( "{}{}error{}{}: {}", - color::Fg(color::Red), - style::Bold, - color::Fg(color::Reset), - style::Reset, + ::termion::color::Fg(::termion::color::Red), + ::termion::style::Bold, + ::termion::color::Fg(::termion::color::Reset), + ::termion::style::Reset, $e ); }; diff --git a/web/src/lib.rs b/web/src/lib.rs index 6b85844..85fdef3 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -81,18 +81,28 @@ pub fn main() -> Result<(), JsValue> { view.update(); } ServerMessage::Channel(chan) => { - state - .borrow_mut() - .push_channel(Channel::new(&chan.name, chan.icon.as_deref())); + { + let mut state = state.borrow_mut(); + state.push_channel(chan.id, Channel::new(&chan.name, chan.icon.as_deref())); + for message in chan.history { + state.push_message( + message.chan, + Message { + from: message.from, + text: message.text.into(), + }, + ); + } + } view.update(); } - ServerMessage::Said { from, chan, text } => { + ServerMessage::Message(message) => { state.borrow_mut().push_message( - chan, + message.chan, Message { - from, - text: text.into(), + from: message.from, + text: message.text.into(), }, ); diff --git a/web/src/state.rs b/web/src/state.rs index 4d77735..e83a630 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -1,4 +1,4 @@ -use im::{HashMap, Vector}; +use im::{HashMap, OrdMap, Vector}; use std::{fmt, rc::Rc}; #[derive(Clone, PartialEq)] @@ -74,11 +74,9 @@ impl PartialEq for Channel { return false; } - if lhs.is_inline() { - lhs == rhs - } else { - lhs.ptr_eq(rhs) - } + lhs.is_inline() + .then(|| lhs == rhs) + .unwrap_or_else(|| lhs.ptr_eq(rhs)) } self.name == rhs.name && self.icon == rhs.icon && possibly_eq(&self.messages, &rhs.messages) @@ -102,23 +100,22 @@ impl Default for User { #[derive(Default, PartialEq)] pub struct State { - channels: Vector, + channels: OrdMap, users: HashMap, } impl State { pub fn channels(&self) -> impl Iterator { - self.channels.iter() + self.channels.values() } - pub fn messages(&self, channel: u32) -> Vector<(u32, Vector>)> { + pub fn messages(&self, chan: u32) -> Vector<(u32, Vector>)> { use itertools::Itertools; self.channels - .get(channel as usize) - .map(|channel| { - channel - .messages + .get(&chan) + .map(|chan| { + chan.messages .iter() .group_by(|message| message.from) .into_iter() @@ -137,12 +134,12 @@ impl State { self.users.get(&user) } - pub fn push_channel(&mut self, chan: Channel) { - self.channels.push_back(chan); + pub fn push_channel(&mut self, id: u32, chan: Channel) { + self.channels.insert(id, chan); } pub fn push_message(&mut self, chan: u32, message: Message) { - if let Some(chan) = self.channels.get_mut(chan as usize) { + if let Some(chan) = self.channels.get_mut(&chan) { chan.messages.push_back(message); } } diff --git a/web/src/view/chat.rs b/web/src/view/chat.rs index b4d1d31..6270e82 100644 --- a/web/src/view/chat.rs +++ b/web/src/view/chat.rs @@ -116,22 +116,16 @@ pub struct Props { pub onsend: Callback<(u32, Rc)>, } -pub struct Chat { - scroll_to_end: bool, -} +pub struct Chat; impl Chat { fn scroll_to_end(&mut self) { - if self.scroll_to_end { - let height = gloo::utils::document() - .body() - .expect_throw("body") - .scroll_height(); - - gloo::utils::window().scroll_by_with_x_and_y(0., height as f64); + let height = gloo::utils::document() + .body() + .expect_throw("body") + .scroll_height(); - self.scroll_to_end = false; - } + gloo::utils::window().scroll_by_with_x_and_y(0., height as f64); } } @@ -140,16 +134,13 @@ impl Component for Chat { type Properties = Props; fn create(_: &Context) -> Self { - Self { - scroll_to_end: true, - } + Self } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { match msg { Event::Send { channel, text } if !text.trim().is_empty() => { ctx.props().onsend.emit((channel, text)); - self.scroll_to_end = true; } _ => {} } diff --git a/web/static/index.html b/web/static/index.html index 3d2d9bd..ba61f5b 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -7,7 +7,7 @@ - Voki + Chat