daydream

A small matrix web client written in rust
git clone git://archive.git.mtrnord.blog/daydream-mx/daydream.git
Log | Files | Refs | README | LICENSE

commit 447a0461183cc5a46823adf37d6f69c7b0140401
parent 6c92e489db2b9a75ed1668ae2d6e2f7ee3193c6a
Author: Marcel <mtrnord1@gmail.com>
Date:   Wed, 27 May 2020 00:59:09 +0200

Switch Css framework and add basics of rooms

Took 5 hours 7 minutes

Diffstat:
MCargo.toml | 5+++--
Mpackage.json | 2+-
Msrc/app/matrix.rs | 67++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/app/matrix/sync.rs | 25++++++++++++++++---------
Asrc/app/matrix/types.rs | 15+++++++++++++++
Msrc/app/views/login.rs | 80+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/app/views/main_view.rs | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/errors.rs | 4++--
Mstatic/index.html | 8+++-----
Mstatic/style.scss | 34++++++++++++++++++++++++----------
Mwebpack.config.js | 8++++++++
Myarn.lock | 10+++++-----
12 files changed, 246 insertions(+), 102 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -16,8 +16,8 @@ console_error_panic_hook = { version = "0.1", optional = true } log = "0.4" strum = "0.17" strum_macros = "0.17" -serde = "1" -serde_derive = "1" +serde = { version = "1.0", features = ["rc", "derive"] } +serde_json = "1.0.53" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4.12" wasm-logger = "0.2" @@ -29,6 +29,7 @@ matrix-sdk = { version = "0.1.0", git = "https://github.com/matrix-org/matrix-ru url = "2.1.1" yew_styles = "0.3.1" thiserror = "1.0" +futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false } [dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/package.json b/package.json @@ -19,6 +19,6 @@ "webpack-dev-server": "^3.11.0" }, "dependencies": { - "bootstrap": "^4.5.0" + "uikit": "^3.4.6" } } diff --git a/src/app/matrix.rs b/src/app/matrix.rs @@ -1,10 +1,11 @@ -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; use std::convert::TryFrom; use std::sync::{Arc, Mutex}; +use std::sync::RwLock as SyncRwLock; use log::*; -use matrix_sdk::{Client, ClientConfig, Session, SyncSettings}; -use serde_derive::{Deserialize, Serialize}; +use matrix_sdk::{Client, ClientConfig, Session, SyncSettings, Room, identifiers::RoomId}; +use serde::{Deserialize, Serialize}; use url::Url; use wasm_bindgen_futures::spawn_local; use yew::format::Json; @@ -13,8 +14,12 @@ use yew::worker::*; use crate::constants::AUTH_KEY; use crate::errors::MatrixError; +use futures_locks::RwLock; +use crate::app::matrix::types::{SmallRoom, MessageWrapper}; +use yew_styles::button::Size::Small; mod sync; +pub mod types; #[derive(Serialize, Deserialize, Default, Clone, Debug)] pub struct MatrixClient { @@ -25,10 +30,10 @@ pub struct MatrixClient { #[derive(Serialize, Deserialize, Default, Clone, Debug)] pub struct SessionStore { - access_token: String, - user_id: String, - device_id: String, - homeserver_url: String, + pub(crate) access_token: String, + pub(crate) user_id: String, + pub(crate) device_id: String, + pub(crate) homeserver_url: String, } #[derive(Clone, Debug)] @@ -50,6 +55,7 @@ pub enum Request { Login(), GetLoggedIn, StartSync, + GetJoinedRooms, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -57,7 +63,9 @@ pub enum Response { Error(MatrixError), LoggedIn(bool), // TODO properly handle sync events - Sync(String), + Sync(MessageWrapper), + FinishedFirstSync, + JoinedRoomList(HashMap<RoomId, SmallRoom>), } impl Agent for MatrixAgent { @@ -106,9 +114,9 @@ impl Agent for MatrixAgent { Request::Login() => { let login_client = self.login(); if login_client.is_none() { - let resp = Response::Error(MatrixError::MissingClient); for sub in self.subscribers.iter() { - self.link.respond(*sub, resp.clone()); + let resp = Response::Error(MatrixError::MissingClient); + self.link.respond(*sub, resp); } return; } @@ -129,7 +137,7 @@ impl Agent for MatrixAgent { user_id: matrix_sdk::identifiers::UserId::try_from( stored_session.user_id.as_str(), ) - .unwrap(), + .unwrap(), device_id: stored_session.device_id, }; client.restore_login(session).await; @@ -153,9 +161,9 @@ impl Agent for MatrixAgent { storage.store(AUTH_KEY, Json(&session_store)); } - let resp = Response::LoggedIn(true); for sub in subscribers.iter() { - agent.link.respond(*sub, resp.clone()); + let resp = Response::LoggedIn(true); + agent.link.respond(*sub, resp); } }); } @@ -163,9 +171,9 @@ impl Agent for MatrixAgent { let subscribers = self.subscribers.clone(); let login_client = self.login(); if login_client.is_none() { - let resp = Response::Error(MatrixError::MissingClient); for sub in self.subscribers.iter() { - self.link.respond(*sub, resp.clone()); + let resp = Response::Error(MatrixError::MissingClient); + self.link.respond(*sub, resp); } return; } @@ -179,9 +187,9 @@ impl Agent for MatrixAgent { if !logged_in && agent.session.is_some() { error!("Not logged in but got session"); } else { - let resp = Response::LoggedIn(logged_in.clone()); for sub in subscribers.iter() { - agent.link.respond(*sub, resp.clone()); + let resp = Response::LoggedIn(logged_in.clone()); + agent.link.respond(*sub, resp); } } }); @@ -193,6 +201,26 @@ impl Agent for MatrixAgent { agent.start_sync().await; }); } + Request::GetJoinedRooms => { + let agent = self.clone(); + let client = agent.matrix_client.clone().unwrap(); + spawn_local(async move { + for sub in agent.subscribers.iter() { + let rooms: Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> = client.clone().joined_rooms(); + let mut rooms_list_hack = HashMap::new(); + for (id, room) in rooms.read().await.iter() { + let small_room = SmallRoom { + name: room.read().await.display_name(), + id: id.clone() + }; + rooms_list_hack.insert(id.clone(),small_room); + } + + let resp = Response::JoinedRoomList(rooms_list_hack); + agent.link.respond(*sub, resp); + } + }); + } } } @@ -202,6 +230,7 @@ impl Agent for MatrixAgent { } unsafe impl Send for MatrixAgent {} + unsafe impl std::marker::Sync for MatrixAgent {} impl MatrixAgent { @@ -231,9 +260,9 @@ impl MatrixAgent { || self.matrix_client.is_some()) && self.session.is_none() { - let resp = Response::Error(MatrixError::MissingFields); for sub in self.subscribers.iter() { - self.link.respond(*sub, resp.clone()); + let resp = Response::Error(MatrixError::MissingFields); + self.link.respond(*sub, resp); } None } else if self.session.is_some() { diff --git a/src/app/matrix/sync.rs b/src/app/matrix/sync.rs @@ -1,12 +1,7 @@ use crate::app::matrix::Response; use log::*; -use matrix_sdk::{ - api::r0::sync::sync_events::Response as SyncResponse, - events::collections::all::RoomEvent, - events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, - identifiers::RoomId, - Client, SyncSettings, -}; +use matrix_sdk::{api::r0::sync::sync_events::Response as SyncResponse, events::collections::all::RoomEvent, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, identifiers::RoomId, Client, SyncSettings, Error}; +use crate::app::matrix::types::MessageWrapper; pub struct Sync<F> where @@ -22,7 +17,14 @@ where { pub async fn start_sync(&self) { let client = self.matrix_client.clone(); - client.clone().sync(SyncSettings::default()).await.unwrap(); + let resp = client.clone().sync(SyncSettings::default()).await; + match resp { + Ok(_) => { + let resp = Response::FinishedFirstSync; + (self.callback)(resp); + }, + _ => {}, + } let settings = SyncSettings::default().token(client.clone().sync_token().await.unwrap()); client @@ -54,7 +56,12 @@ where return; }; - let resp = Response::Sync(msg_body); + let wrapper = MessageWrapper { + room_id: room_id.clone(), + content: msg_body + }; + + let resp = Response::Sync(wrapper); (self.callback)(resp); } } diff --git a/src/app/matrix/types.rs b/src/app/matrix/types.rs @@ -0,0 +1,15 @@ +use matrix_sdk::identifiers::RoomId; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SmallRoom { + pub(crate) name: String, + pub(crate) id: RoomId +} + +#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] +pub struct MessageWrapper { + pub(crate) room_id: RoomId, + // TODO use ruma structs + pub(crate) content: String, +} diff --git a/src/app/views/login.rs b/src/app/views/login.rs @@ -70,46 +70,54 @@ impl Component for Login { fn view(&self) -> Html { html! { - <div class="container h-100"> - <div class="row align-items-center h-100"> - <h1>{"Login"}</h1> - <form class="col-6 mx-auto" onsubmit=self.link.callback(|e: Event| {e.prevent_default(); Msg::Login})> - <div class="form-group"> - <label for="homeserver">{"Homeserver URL"}</label> - <input - class="form-control" - type="url" - id="homeserver" - placeholder="Homeserver URL" - value=&self.homeserver - oninput=self.link.callback(|e: InputData| Msg::SetHomeserver(e.value)) - /> + <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})> + <div class="uk-margin"> + <label class="uk-form-label">{"Homeserver URL"}</label> + <div class="uk-form-controls"> + <input + class="uk-input" + type="url" + id="homeserver" + placeholder="Homeserver URL" + value=&self.homeserver + oninput=self.link.callback(|e: InputData| Msg::SetHomeserver(e.value)) + /> + </div> </div> - <div class="form-group"> - <label for="username">{"MXID/Username"}</label> - <input - class="form-control" - id="username" - placeholder="MXID/Username" - value=&self.username - oninput=self.link.callback(|e: InputData| Msg::SetUsername(e.value)) - /> + <div class="uk-margin"> + <label class="uk-form-label">{"MXID/Username"}</label> + <div class="uk-form-controls"> + <input + class="uk-input" + id="username" + placeholder="MXID/Username" + value=&self.username + oninput=self.link.callback(|e: InputData| Msg::SetUsername(e.value)) + /> + </div> </div> - <div class="form-group"> - <label for="password">{"Password"}</label> - <input - class="form-control" - type="password" - id="password" - placeholder="Password" - value=&self.password - oninput=self.link.callback(|e: InputData| Msg::SetPassword(e.value)) - /> + <div class="uk-margin"> + <label class="uk-form-label">{"Password"}</label> + <div class="uk-form-controls"> + <input + class="uk-input" + type="password" + id="password" + placeholder="Password" + value=&self.password + oninput=self.link.callback(|e: InputData| Msg::SetPassword(e.value)) + /> + </div> </div> - <button type="submit" class="btn btn-primary"> - { "Login" } - </button> + <div class="uk-margin"> + <div class="uk-form-controls"> + <button class="uk-button uk-button-primary">{ "Login" }</button> + </div> + </div> </form> </div> </div> diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs @@ -1,10 +1,16 @@ use log::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use yew::prelude::*; use yew::ComponentLink; use crate::app::matrix::{MatrixAgent, Request, Response}; -use wasm_bindgen::__rt::std::collections::{HashMap, HashSet}; +use std::collections::{HashSet, HashMap}; +use matrix_sdk::{Client, ClientConfig, Session}; +use url::Url; +use std::convert::TryFrom; +use wasm_bindgen_futures::spawn_local; +use crate::app::matrix::types::{SmallRoom, MessageWrapper}; +use matrix_sdk::identifiers::RoomId; pub struct MainView { link: ComponentLink<Self>, @@ -14,12 +20,15 @@ pub struct MainView { pub enum Msg { NewMessage(Response), + ChangeRoom(String) } #[derive(Serialize, Deserialize, Default)] pub struct State { // TODO handle all events - pub events: HashSet<String>, + pub events: HashSet<MessageWrapper>, + pub rooms: HashMap<RoomId, SmallRoom>, + pub current_room: Option<RoomId> } impl Component for MainView { @@ -32,6 +41,8 @@ impl Component for MainView { matrix_agent.send(Request::StartSync); let state = State { events: Default::default(), + rooms: Default::default(), + current_room: None }; MainView { @@ -45,13 +56,23 @@ impl Component for MainView { match msg { Msg::NewMessage(response) => { match response { + 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 + } _ => {} } } + Msg::ChangeRoom(room) => { + self.state.current_room = Some(RoomId::try_from(room).unwrap()); + + } } true } @@ -61,26 +82,69 @@ impl Component for MainView { } fn view(&self) -> Html { - html! { - <div class="container-fluid h-100 non-scrollable-container"> - <div class="row h-100"> - <div class="col-md-2 scrollable h-100"> - <p>{"BLUB"}</p> + if !self.state.rooms.is_empty() { + if self.state.current_room.is_none() { + return html! { + <div class="uk-flex h-100 non-scrollable-container"> + <div class="container h-100 uk-width-1-6"> + <ul class="scrollable h-100 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 h-100 uk-width-5-6 uk-padding"> + <div class="scrollable h-100"> + // TODO add some content to the empty page + </div> + </div> </div> - <div class="col scrollable h-100"> - { self.state.events.iter().map(|event| self.get_event(event)).collect::<Html>() } + } + } else { + return html! { + <div class="uk-flex h-100 non-scrollable-container"> + <div class="container h-100 uk-width-1-6"> + <ul class="scrollable h-100 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 h-100 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 h-100"> + { self.state.events.iter().filter(|x| x.room_id == self.state.current_room.clone().unwrap()).map(|event| self.get_event(event.content.clone())).collect::<Html>() } + </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> - </div> + } } } } impl MainView { - fn get_event(&self, event: &String) -> Html { + fn get_event(&self, event: String) -> Html { + html! { + <p>{event}</p> + } + } + + fn get_room(&self, room: SmallRoom) -> Html { + // TODO better linking than onlclick (yew limitation?) + + let room_id = room.clone().id.to_string(); html! { - <><p>{event}</p><br/></> + <li><a href="#" onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room_id.clone()))>{room.name.clone()}</a></li> } } } diff --git a/src/errors.rs b/src/errors.rs @@ -1,7 +1,7 @@ -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use thiserror::Error; -#[derive(Error, Debug, Clone, Serialize, Deserialize)] +#[derive(Error, Debug, Copy, Clone, Serialize, Deserialize)] pub enum MatrixError { #[error("No Matrix Client is available yet")] MissingClient, diff --git a/static/index.html b/static/index.html @@ -8,9 +8,7 @@ </head> <body> <script src="/daydream.js"></script> - - <script> - require('bootstrap'); - </script> - </body> + <script src="js/uikit.min.js"></script> + <script src="js/uikit-icons.min.js"></script> + </body> </html> diff --git a/static/style.scss b/static/style.scss @@ -1,18 +1,32 @@ -html { - height: 100%; +.scrollable { + overflow-y: auto !important; + overflow-x: hidden !important; + height: 100% !important; } -body { - height: 100%; +.non-scrollable-container { + overflow: hidden; } -.scrollable { - overflow-y: auto; - overflow-x: hidden; +.h-100 { + height: 100% !important; } -.non-scrollable-container { - overflow: hidden; +html { + overflow: hidden !important; + height: 100% !important; +} + +body { + overflow: hidden !important; + height: 100% !important; +} + +.menu { + width: 20rem; + padding: 1.5rem; } -@import "../node_modules/bootstrap/scss/bootstrap"; +@import "../node_modules/uikit/src/scss/variables-theme.scss"; +@import "../node_modules/uikit/src/scss/mixins-theme.scss"; +@import "../node_modules/uikit/src/scss/uikit-theme.scss"; diff --git a/webpack.config.js b/webpack.config.js @@ -34,6 +34,14 @@ module.exports = (env, argv) => { patterns: [ { from: './static', to: distPath + }, + { + from: './node_modules/uikit/dist/js/uikit.min.js', + to: path.join(distPath, '/js/uikit.min.js') + }, + { + from: './node_modules/uikit/dist/js/uikit-icons.min.js', + to: path.join(distPath, '/js/uikit-icons.min.js') } ] }), diff --git a/yarn.lock b/yarn.lock @@ -508,11 +508,6 @@ bonjour@^3.5.0: multicast-dns "^6.0.1" multicast-dns-service-types "^1.1.0" -bootstrap@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.0.tgz#97d9dbcb5a8972f8722c9962483543b907d9b9ec" - integrity sha512-Z93QoXvodoVslA+PWNdk23Hze4RBYIkpb5h8I2HY2Tu2h7A0LpAgLcyrhrSUyo2/Oxm2l1fRZPs1e5hnxnliXA== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4123,6 +4118,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +uikit@^3.4.6: + version "3.4.6" + resolved "https://registry.yarnpkg.com/uikit/-/uikit-3.4.6.tgz#39d59620aeb42d53ba905a67e99e2015d4aaaf9c" + integrity sha512-Se8DXGJ69NCxm8AQTok6I9aXxvklaBsdkr3REfbUorxeIQA6g96sPvzHCXrubqknoqEhH6npD14xtzxJGCvS8A== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"