commit d8baa92040e5fbd0af22821fa39096668b7d7d47
parent 6c68df0a3b17e7e4b38b9543cacd6a91902f7ccd
Author: Marcel <mtrnord1@gmail.com>
Date: Wed, 27 May 2020 22:16:03 +0200
Refactor code, split components, recolor scrollbars, fix missing events at the bottom
Took 2 hours 43 minutes
Diffstat:
9 files changed, 324 insertions(+), 110 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -22,12 +22,12 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4.12"
wasm-logger = "0.2"
wee_alloc = "0.4"
-yew = "0.16"
-yew-router = "0.13"
+yew = { git = "https://github.com/yewstack/yew" }
+yew-router = { git = "https://github.com/yewstack/yew" }
+yewtil = { git = "https://github.com/yewstack/yew" }
futures = "0.3"
matrix-sdk = { version = "0.1.0", git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false}
url = "2.1.1"
-yew_styles = "0.3.1"
thiserror = "1.0"
futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false }
js_int = "0.1.5"
@@ -41,3 +41,7 @@ version = "0.3"
features = [
'KeyboardEvent',
]
+
+[profile.release]
+# Tell `rustc` to optimize for small code size.
+opt-level = "s"
diff --git a/src/app.rs b/src/app.rs
@@ -6,6 +6,7 @@ use crate::app::matrix::{MatrixAgent, Response};
use crate::app::views::{login::Login, main_view::MainView};
use yew_router::agent::RouteRequest::ChangeRoute;
+pub mod components;
mod matrix;
mod views;
@@ -63,7 +64,7 @@ impl Component for App {
self.route_agent.send(ChangeRoute(route));
}
Msg::NewMessage(response) => {
- info!("NewMessage: {:#?}", response);
+ //info!("NewMessage: {:#?}", response);
match response {
Response::Error(_) => {}
Response::LoggedIn(logged_in) => {
diff --git a/src/app/components/event_list.rs b/src/app/components/event_list.rs
@@ -0,0 +1,124 @@
+use linked_hash_set::LinkedHashSet;
+use log::*;
+use matrix_sdk::identifiers::RoomId;
+use serde::{Deserialize, Serialize};
+use yew::prelude::*;
+use yewtil::NeqAssign;
+
+use crate::app::matrix::{MatrixAgent, Request, Response};
+use crate::app::matrix::types::MessageWrapper;
+
+pub struct EventList {
+ link: ComponentLink<Self>,
+ state: State,
+ matrix_agent: Box<dyn Bridge<MatrixAgent>>,
+ props: Props,
+}
+
+#[derive(Serialize, Deserialize, Default)]
+pub struct State {
+ // TODO handle all events
+ pub events: LinkedHashSet<MessageWrapper>,
+}
+
+pub enum Msg {
+ NewMessage(Response),
+}
+
+#[derive(Clone, PartialEq, Properties)]
+pub struct Props {
+ #[prop_or_default]
+ pub current_room: Option<RoomId>,
+ #[prop_or_default]
+ pub displayname: String,
+}
+
+impl Component for EventList {
+ type Message = Msg;
+ type Properties = Props;
+
+ fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
+ let matrix_callback = link.callback(Msg::NewMessage);
+ let mut matrix_agent = MatrixAgent::bridge(matrix_callback);
+
+ let state = State {
+ events: Default::default(),
+ };
+
+ if props.clone().current_room.is_some() {
+ let room_id = props.clone().current_room.clone().unwrap();
+ if state
+ .events
+ .iter()
+ .filter(|x| x.room_id == room_id)
+ .collect::<LinkedHashSet<&MessageWrapper>>()
+ .is_empty()
+ {
+ matrix_agent.send(Request::GetOldMessages((room_id.clone(), None)));
+ }
+ }
+
+
+
+ EventList {
+ props,
+ link,
+ matrix_agent,
+ state,
+ }
+ }
+
+ fn update(&mut self, msg: Self::Message) -> bool {
+ match msg {
+ Msg::NewMessage(response) => {
+ match response {
+ Response::Sync(msg) => {
+ // TODO handle all events
+ self.state.events.insert(msg);
+ true
+ }
+ Response::OldMessages(messages) => {
+ // TODO this doesn't seem smart
+ let mut new_events_map = LinkedHashSet::new();
+ for event in messages.into_iter() {
+ new_events_map.insert(event);
+ }
+ for event in self.state.events.clone().into_iter() {
+ new_events_map.insert(event);
+ }
+ self.state.events = new_events_map;
+ true
+ }
+
+ _ => {
+ false
+ }
+ }
+ }
+ }
+ }
+
+ fn change(&mut self, props: Self::Properties) -> bool {
+ self.props.neq_assign(props)
+ }
+
+ fn view(&self) -> Html {
+ return html! {
+ <div class="container uk-flex uk-flex-column uk-width-5-6 uk-padding uk-padding-remove-bottom">
+ <h1>{ self.props.displayname.clone() }</h1>
+ <div class="scrollable">
+ { self.state.events.iter().filter(|x| x.room_id == self.props.current_room.clone().unwrap()).map(|event| self.get_event(event.clone())).collect::<Html>() }
+ </div>
+ </div>
+ };
+ }
+}
+
+
+impl EventList {
+ fn get_event(&self, event: MessageWrapper) -> Html {
+ html! {
+ <p>{event.sender_displayname.clone()}{": "}{event.content.clone()}</p>
+ }
+ }
+}
diff --git a/src/app/components/mod.rs b/src/app/components/mod.rs
@@ -0,0 +1,2 @@
+pub mod event_list;
+pub mod room_list;
diff --git a/src/app/components/room_list.rs b/src/app/components/room_list.rs
@@ -0,0 +1,137 @@
+use std::collections::HashMap;
+
+use js_int::UInt;
+use log::*;
+use matrix_sdk::identifiers::RoomId;
+use serde::{Deserialize, Serialize};
+use yew::prelude::*;
+use yew::{Bridge, Bridged, Component, ComponentLink, Html};
+use yewtil::NeqAssign;
+
+use crate::app::matrix::types::SmallRoom;
+use crate::app::matrix::{MatrixAgent, Response};
+
+pub struct RoomList {
+ link: ComponentLink<Self>,
+ state: State,
+ matrix_agent: Box<dyn Bridge<MatrixAgent>>,
+ props: Props,
+}
+
+pub enum Msg {
+ NewMessage(Response),
+ ChangeRoom(String),
+}
+
+#[derive(Serialize, Deserialize, Default)]
+pub struct State {
+ pub rooms: HashMap<RoomId, SmallRoom>,
+ pub current_room: Option<RoomId>,
+ loading: bool,
+}
+
+#[derive(Clone, PartialEq, Properties)]
+pub struct Props {
+ #[prop_or_default]
+ pub change_room_callback: Callback<(String, String)>,
+}
+
+impl Component for RoomList {
+ type Message = Msg;
+ type Properties = Props;
+
+ fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
+ let matrix_callback = link.callback(Msg::NewMessage);
+ let matrix_agent = MatrixAgent::bridge(matrix_callback);
+ let state = State {
+ rooms: Default::default(),
+ current_room: None,
+ loading: true,
+ };
+
+ RoomList {
+ props,
+ link,
+ matrix_agent,
+ state,
+ }
+ }
+
+ fn update(&mut self, msg: Self::Message) -> bool {
+ match msg {
+ Msg::NewMessage(response) => match response {
+ Response::JoinedRoomList(rooms) => {
+ self.state.rooms = rooms;
+ self.state.loading = false;
+ true
+ }
+ _ => false,
+ },
+ Msg::ChangeRoom(room) => {
+ let displayname = self.state.rooms.iter().filter(|(id, _)| **id == room).map(|(_, room)| room.name.clone()).collect::<String>();
+ self.props.change_room_callback.emit((displayname, room));
+ false
+ }
+ }
+ }
+
+ fn change(&mut self, props: Self::Properties) -> ShouldRender {
+ self.props.neq_assign(props)
+ }
+
+ fn view(&self) -> Html {
+ if self.state.loading {
+ return html! {
+ <div class="container">
+ <div class="uk-position-center uk-padding">
+ <span uk-spinner="ratio: 4.5"></span>
+ </div>
+ </div>
+ };
+ } else {
+ return html! {
+ <div class="container uk-flex uk-flex-column uk-width-1-6" style="height: 100%">
+ <div class="uk-padding uk-padding-remove-bottom">
+ <form class="uk-search uk-search-default">
+ <span uk-search-icon=""></span>
+ <input class="uk-search-input" type="search" placeholder="Filter Rooms..." />
+ </form>
+ </div>
+ <ul class="scrollable uk-flex uk-flex-column uk-padding uk-nav-default uk-nav-parent-icon" uk-nav="" style="height: 100%">
+ <li class="uk-nav-header">{"Rooms"}</li>
+ { self.state.rooms.iter().map(|(_, room)| self.get_room(room.clone())).collect::<Html>() }
+ </ul>
+ </div>
+ };
+ }
+ }
+}
+
+impl RoomList {
+ fn get_room(&self, room: SmallRoom) -> Html {
+ // TODO better linking than onlclick (yew limitation?)
+
+ let room_id = room.clone().id.to_string();
+ html! {
+ <li>
+ <a href="#" onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room_id.clone()))>
+ {room.name.clone()}
+ {
+ if room.unread_notifications.is_some() && room.unread_notifications.unwrap() != UInt::from(0u32) {
+ html! { <span class="uk-badge uk-margin-small-left">{room.unread_notifications.unwrap()}</span> }
+ } else {
+ html! {}
+ }
+ }
+ {
+ if room.unread_highlight.is_some() && room.unread_highlight.unwrap() != UInt::from(0u32) {
+ html! { <span class="uk-badge red uk-margin-small-left">{room.unread_highlight.unwrap()}</span> }
+ } else {
+ html! {}
+ }
+ }
+ </a>
+ </li>
+ }
+ }
+}
diff --git a/src/app/matrix.rs b/src/app/matrix.rs
@@ -79,7 +79,7 @@ pub enum Response {
}
impl Agent for MatrixAgent {
- type Reach = Context;
+ type Reach = Context<MatrixAgent>;
type Message = ();
type Input = Request;
type Output = Response;
@@ -284,12 +284,14 @@ impl Agent for MatrixAgent {
.await
.unwrap();
let room = room.read().await;
- let member = room.members.get(&sender).unwrap();
- member
- .display_name
- .as_ref()
- .map(ToString::to_string)
- .unwrap_or(sender.to_string())
+ match room.members.get(&sender) {
+ Some(member) => member
+ .display_name
+ .as_ref()
+ .map(ToString::to_string)
+ .unwrap_or(sender.to_string()),
+ None => sender.to_string(),
+ }
};
let wrapper = MessageWrapper {
diff --git a/src/app/views/login.rs b/src/app/views/login.rs
@@ -73,7 +73,7 @@ impl Component for Login {
<div class="container">
<div class="uk-position-center uk-padding">
<h1 class="title">{"Login"}</h1>
- <form class="uk-form-stacked uk-margin" onsubmit=self.link.callback(|e: Event| {e.prevent_default(); Msg::Login})>
+ <form class="uk-form-stacked uk-margin" onsubmit=self.link.callback(|e: FocusEvent| {e.prevent_default(); Msg::Login})>
<div class="uk-margin">
<label class="uk-form-label">{"Homeserver URL"}</label>
<div class="uk-form-controls">
diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs
@@ -1,16 +1,15 @@
-use std::collections::{HashMap};
use std::convert::TryFrom;
-use js_int::UInt;
+use linked_hash_set::LinkedHashSet;
use log::*;
use matrix_sdk::identifiers::RoomId;
use serde::{Deserialize, Serialize};
use yew::prelude::*;
use yew::ComponentLink;
-use crate::app::matrix::types::{MessageWrapper, SmallRoom};
+use crate::app::components::{room_list::RoomList, event_list::EventList};
+use crate::app::matrix::types::MessageWrapper;
use crate::app::matrix::{MatrixAgent, Request, Response};
-use linked_hash_set::LinkedHashSet;
pub struct MainView {
link: ComponentLink<Self>,
@@ -20,15 +19,15 @@ pub struct MainView {
pub enum Msg {
NewMessage(Response),
- ChangeRoom(String),
+ ChangeRoom((String, String)),
}
#[derive(Serialize, Deserialize, Default)]
pub struct State {
// TODO handle all events
pub events: LinkedHashSet<MessageWrapper>,
- pub rooms: HashMap<RoomId, SmallRoom>,
pub current_room: Option<RoomId>,
+ pub current_room_displayname: String,
}
impl Component for MainView {
@@ -41,8 +40,8 @@ impl Component for MainView {
matrix_agent.send(Request::StartSync);
let state = State {
events: Default::default(),
- rooms: Default::default(),
current_room: None,
+ current_room_displayname: Default::default()
};
MainView {
@@ -59,32 +58,24 @@ impl Component for MainView {
Response::FinishedFirstSync => {
self.matrix_agent.send(Request::GetJoinedRooms);
}
- Response::Sync(msg) => {
- // TODO handle all events
- self.state.events.insert(msg);
- }
- Response::JoinedRoomList(rooms) => self.state.rooms = rooms,
- Response::OldMessages(messages) => {
- // TODO this doesn't seem smart
- let mut new_events_map = LinkedHashSet::new();
- for event in messages.into_iter() {
- new_events_map.insert(event);
- }
- for event in self.state.events.clone().into_iter() {
- new_events_map.insert(event);
- }
- self.state.events = new_events_map;
- }
_ => {}
}
}
- Msg::ChangeRoom(room) => {
+ Msg::ChangeRoom((displayname, room)) => {
let room_id = RoomId::try_from(room).unwrap();
- if self.state.events.iter().filter(|x| x.room_id == room_id).collect::<LinkedHashSet<&MessageWrapper>>().is_empty() {
+ if self
+ .state
+ .events
+ .iter()
+ .filter(|x| x.room_id == room_id)
+ .collect::<LinkedHashSet<&MessageWrapper>>()
+ .is_empty()
+ {
self.matrix_agent
.send(Request::GetOldMessages((room_id.clone(), None)));
}
self.state.current_room = Some(room_id.clone());
+ self.state.current_room_displayname = displayname;
}
}
true
@@ -95,86 +86,25 @@ impl Component for MainView {
}
fn view(&self) -> Html {
- if !self.state.rooms.is_empty() {
- if self.state.current_room.is_none() {
- return html! {
- <div class="uk-flex uk-height-1-1 non-scrollable-container">
- <div class="container uk-height-1-1 uk-width-1-6">
- <ul class="scrollable uk-height-1-1 uk-padding uk-nav-default uk-nav-parent-icon" uk-nav="">
- <li class="uk-nav-header">{"Rooms"}</li>
- { self.state.rooms.iter().map(|(_, room)| self.get_room(room.clone())).collect::<Html>() }
- </ul>
- </div>
-
- <div class="container uk-height-1-1 uk-width-5-6 uk-padding">
- <div class="scrollable" uk-height-viewport="expand: true">
- // TODO add some content to the empty page
- </div>
- </div>
- </div>
- };
- } else {
- return html! {
- <div class="uk-flex uk-height-1-1 non-scrollable-container">
- <div class="container uk-height-1-1 uk-width-1-6">
- <ul class="scrollable uk-height-1-1 uk-padding uk-nav-default uk-nav-parent-icon" uk-nav="">
- <li class="uk-nav-header">{"Rooms"}</li>
- { self.state.rooms.iter().map(|(_, room)| self.get_room(room.clone())).collect::<Html>() }
- </ul>
- </div>
+ if self.state.current_room.is_none() {
+ return html! {
+ <div class="uk-flex auto-scrollable-container" style="height: 100%">
+ <RoomList change_room_callback=self.link.callback(Msg::ChangeRoom)/>
- <div class="container uk-height-1-1 uk-width-5-6 uk-padding">
- <h1>{ self.state.rooms.iter().filter(|(id, _)| **id == self.state.current_room.clone().unwrap()).map(|(_, room)| room.name.clone()).collect::<String>() }</h1>
- <div class="scrollable" uk-height-viewport="expand: true">
- { self.state.events.iter().filter(|x| x.room_id == self.state.current_room.clone().unwrap()).map(|event| self.get_event(event.clone())).collect::<Html>() }
- </div>
+ <div class="container uk-flex uk-width-5-6 uk-padding">
+ <div class="scrollable">
+ // TODO add some content to the empty page
</div>
</div>
- };
- }
+ </div>
+ };
} else {
return html! {
- <div class="container">
- <div class="uk-position-center uk-padding">
- <span uk-spinner="ratio: 4.5"></span>
- </div>
+ <div class="uk-flex auto-scrollable-container" style="height: 100%">
+ <RoomList change_room_callback=self.link.callback(Msg::ChangeRoom)/>
+ <EventList current_room=self.state.current_room.clone() displayname=self.state.current_room_displayname.clone() />
</div>
};
}
}
}
-
-impl MainView {
- fn get_event(&self, event: MessageWrapper) -> Html {
- html! {
- <p>{event.sender_displayname.clone()}{": "}{event.content.clone()}</p>
- }
- }
-
- fn get_room(&self, room: SmallRoom) -> Html {
- // TODO better linking than onlclick (yew limitation?)
-
- let room_id = room.clone().id.to_string();
- html! {
- <li>
- <a href="#" onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room_id.clone()))>
- {room.name.clone()}
- {
- if room.unread_notifications.is_some() && room.unread_notifications.unwrap() != UInt::from(0u32) {
- html! { <span class="uk-badge uk-margin-small-left">{room.unread_notifications.unwrap()}</span> }
- } else {
- html! {}
- }
- }
- {
- if room.unread_highlight.is_some() && room.unread_highlight.unwrap() != UInt::from(0u32) {
- html! { <span class="uk-badge red uk-margin-small-left">{room.unread_highlight.unwrap()}</span> }
- } else {
- html! {}
- }
- }
- </a>
- </li>
- }
- }
-}
diff --git a/static/style.scss b/static/style.scss
@@ -1,13 +1,27 @@
+$scrollbar-color: hsla(0,0%,100%,.2);
+$scrollbar-bg-color: #181b21;
+
.scrollable {
overflow-y: auto !important;
overflow-x: hidden !important;
height: 100% !important;
+ scrollbar-width: thin;
+ scrollbar-color: $scrollbar-bg-color $scrollbar-color;
+}
+
+.scrollable::-webkit-scrollbar {
+ width: 11px;
}
.non-scrollable-container {
overflow: hidden;
}
+
+.auto-scrollable-container {
+ overflow: auto;
+}
+
.uk-badge.red {
background: #f0506e;
}