commit 024552abb7fad7766ac403152d69dde59921f6e8
parent 3667dd7b2fce482f599fb81502e364ae45ab81c0
Author: Marcel <mtrnord1@gmail.com>
Date: Fri, 3 Jul 2020 16:49:03 +0200
Add initial redesign stuff (not finished), move sync to worker (not finished)
Took 5 hours 59 minutes
Diffstat:
27 files changed, 923 insertions(+), 411 deletions(-)
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
@@ -8,10 +8,10 @@ jobs:
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
- toolchain: stable
+ toolchain: nightly
- name: Run fmt
run: cargo fmt -- --check
- name: Run clippy
run: cargo clippy -- --deny=warnings
- name: Run check
- run: cargo check
-\ No newline at end of file
+ run: cargo check
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
@@ -13,9 +13,11 @@ jobs:
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
- toolchain: stable
+ toolchain: nightly
target: wasm32-unknown-unknown
+ - run: rustup default nightly
+
- name: Setup Node
uses: actions/setup-node@v1
with:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
@@ -9,6 +9,7 @@ jobs:
containers: [1, 2, 3]
steps:
- uses: actions/checkout@v1
+ - run: rustup default nightly
- uses: cypress-io/github-action@v1
with:
browser: chrome
diff --git a/Cargo.toml b/Cargo.toml
@@ -15,29 +15,30 @@ console_error_panic_hook = { version = "0.1" }
log = "0.4"
tracing = {version = "0.1", features = ["log-always"] }
serde = { version = "1.0", features = ["rc", "derive"] }
-serde_json = "1.0.53"
+serde_json = "1.0"
wasm-bindgen = "0.2"
-wasm-bindgen-futures = "0.4.12"
+wasm-bindgen-futures = "0.4"
wasm-logger = "0.2"
-wee_alloc = "0.4"
+#wee_alloc = "0.4"
+lazy_static = "1.4.0"
# Yew
-yew = { git = "https://github.com/yewstack/yew" }
-yew-router = { git = "https://github.com/yewstack/yew" }
-yewtil = { git = "https://github.com/yewstack/yew" }
+yew = { git = "https://github.com/daydream-mx/yew.git", branch = "fix-webpack-worker", features = ["webpack"] }
+yew-router = { git = "https://github.com/daydream-mx/yew.git", branch = "fix-webpack-worker" }
+yewtil = { git = "https://github.com/daydream-mx/yew.git", branch = "fix-webpack-worker" }
# Matrix
-matrix-sdk = { version = "0.1.0", git = "https://github.com/MTRNord/matrix-rust-sdk", branch = "daydream", default-features = false}# features = ["encryption"]}
+matrix-sdk = { version = "0.1.0", git = "https://github.com/MTRNord/matrix-rust-sdk", branch = "daydream", default-features = false, features = ["messages"]}# features = ["encryption"]}
url = "2.1.1"
thiserror = "1.0"
futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false }
# Markdown
-pulldown-cmark = "0.7.1"
+pulldown-cmark = "0.7.2"
# Translations
tr = { version = "0.1.3", default-features = false, features = ["gettext"]}
-i18n-embed = { version = "0.4", features = ["web-sys-requester"] }
+i18n-embed = { version = "0.6", features = ["web-sys-requester"] }
rust-embed = { version = "5", features = ["debug-embed", "compression"]}
# Make links links again!
diff --git a/package.json b/package.json
@@ -6,30 +6,31 @@
"build:dev": "webpack --mode development",
"build": "webpack --mode production",
"start:dev": "webpack-dev-server --mode development",
- "start": "yarn run start:dev",
+ "start": "webpack-dev-server --mode production",
"start:prod": "webpack-dev-server --mode production",
"cy:open": "cypress open",
"cy:run:ci": "cypress run --browser chrome --record --key 99194b81-775d-4e98-9bac-8f2cc12bc62e --ci-build-id \"${GITHUB_SHA}-${GITHUB_WORKFLOW}-${GITHUB_EVENT_NAME}\" --group github-action-e2e --parallel"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "^1.3.1",
- "copy-webpack-plugin": "^6.0.1",
+ "copy-webpack-plugin": "^6.0.3",
"cross-env": "^7.0.2",
- "css-loader": "^3.5.3",
- "cypress": "^4.8.0",
+ "css-loader": "^3.6.0",
+ "cypress": "^4.9.0",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"mini-css-extract-plugin": "^0.9.0",
- "sass": "^1.26.5",
- "sass-loader": "^8.0.2",
+ "sass": "^1.26.9",
+ "sass-loader": "^9.0.0",
"start-server-and-test": "^1.11.0",
"style-loader": "^1.2.1",
+ "terser-webpack-plugin": "^3.0.6",
"wasm-pack": "^0.9.1",
"webpack": "^4.43.0",
- "webpack-cli": "^3.3.11",
+ "webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
- "uikit": "^3.4.6"
+ "uikit": "^3.5.4"
}
}
diff --git a/src/app/components/event_list.rs b/src/app/components/event_list.rs
@@ -70,27 +70,34 @@ impl Component for EventList {
match msg {
Msg::NewMessage(response) => {
match response {
- Response::Sync((room_id, msg)) => {
+ Response::Sync((room_id, raw_msg)) => {
// TODO handle all events
- if self.state.events.contains_key(&room_id) {
- if !(self.state.events[&room_id]
- .iter()
- .map(|x| x.event_id.clone())
- .any(|x| x == msg.event_id))
- {
- self.state.events.get_mut(&room_id).unwrap().push(msg);
- room_id == self.props.current_room.clone().unwrap().room_id
+ if let Ok(msg) = raw_msg.deserialize() {
+ if self.state.events.contains_key(&room_id) {
+ if !(self.state.events[&room_id]
+ .iter()
+ .any(|x| x.event_id == msg.event_id))
+ {
+ self.state.events.get_mut(&room_id).unwrap().push(msg);
+ room_id == self.props.current_room.clone().unwrap().room_id
+ } else {
+ false
+ }
} else {
- false
+ let mut msgs = vec![msg];
+ self.state.events.insert(room_id.clone(), msgs);
+ room_id == self.props.current_room.clone().unwrap().room_id
}
} else {
- let mut msgs = Vec::new();
- msgs.push(msg);
- self.state.events.insert(room_id.clone(), msgs);
- room_id == self.props.current_room.clone().unwrap().room_id
+ false
}
}
Response::OldMessages((room_id, mut messages)) => {
+ let mut deserialized_messages: Vec<MessageEvent> = messages
+ .iter()
+ .map(|x| x.deserialize())
+ .filter_map(Result::ok)
+ .collect();
// This is a clippy false positive
#[allow(clippy::map_entry)]
if self.state.events.contains_key(&room_id) {
@@ -98,10 +105,10 @@ impl Component for EventList {
.events
.get_mut(&room_id)
.unwrap()
- .append(messages.as_mut());
+ .append(deserialized_messages.as_mut());
true
} else {
- self.state.events.insert(room_id, messages);
+ self.state.events.insert(room_id, deserialized_messages);
true
}
}
@@ -139,30 +146,30 @@ impl Component for EventList {
fn view(&self) -> Html {
return html! {
- <div class="event-list container uk-flex uk-flex-column uk-width-5-6">
- <div class="room-title"><div><h1>{ self.props.current_room.as_ref().unwrap().display_name() }</h1></div></div>
- <div class="scrollable" style="height: 100%">
- {
- if self.state.events.contains_key(&self.props.current_room.as_ref().unwrap().room_id) {
- let events = self.state.events[&self.props.current_room.as_ref().unwrap().room_id].clone();
- let mut elements: Vec<Html> = Vec::new();
- for (pos, event) in self.state.events[&self.props.current_room.as_ref().unwrap().room_id].iter().enumerate() {
- if pos == 0 {
- elements.push(self.get_event(None, event));
- } else {
- elements.push(self.get_event(Some(events[pos - 1].clone()), event));
+ <div class="event-list">
+ <div class="room-title"><h1>{ self.props.current_room.as_ref().unwrap().display_name() }</h1></div>
+ <div class="scrollable" style="height: auto; flex-grow: 1;">
+ <div class="message-container">
+ {
+ if self.state.events.contains_key(&self.props.current_room.as_ref().unwrap().room_id) {
+ let events = self.state.events[&self.props.current_room.as_ref().unwrap().room_id].clone();
+ let mut elements: Vec<Html> = Vec::new();
+ for (pos, event) in self.state.events[&self.props.current_room.as_ref().unwrap().room_id].iter().enumerate() {
+ if pos == 0 {
+ elements.push(self.get_event(None, event));
+ } else {
+ elements.push(self.get_event(Some(events[pos - 1].clone()), event));
+ }
}
+ elements.into_iter().collect::<Html>()
+ } else {
+ html! {}
}
- elements.into_iter().collect::<Html>()
- } else {
- html! {}
}
- }
- <div id="anchor"></div>
- </div>
- <div class="uk-margin">
- <Input on_submit=&self.on_submit/>
+ <div id="anchor"></div>
+ </div>
</div>
+ <Input on_submit=&self.on_submit/>
</div>
};
}
diff --git a/src/app/components/events/notice.rs b/src/app/components/events/notice.rs
@@ -80,8 +80,7 @@ impl Component for Notice {
if new_user {
let full_html = format!(
"<p style=\"opacity: .6;\"><displayname>{}: </displayname>{}</p>",
- sender_displayname,
- content
+ sender_displayname, content
);
let js_text_event = {
let div = web_sys::window()
diff --git a/src/app/components/events/text.rs b/src/app/components/events/text.rs
@@ -113,8 +113,7 @@ impl Component for Text {
} else if new_user {
let full_html = format!(
"<p><displayname>{}: </displayname>{}</p>",
- sender_displayname,
- content
+ sender_displayname, content
);
let js_text_event = {
let div = web_sys::window()
diff --git a/src/app/components/room_list.rs b/src/app/components/room_list.rs
@@ -1,242 +0,0 @@
-use std::collections::HashMap;
-use std::include_str;
-
-use matrix_sdk::{identifiers::RoomId, js_int::UInt, Room};
-use serde::{Deserialize, Serialize};
-use wasm_bindgen::JsCast;
-use web_sys::HtmlElement;
-use yew::prelude::*;
-use yew::utils::document;
-use yew::{Bridge, Bridged, Component, ComponentLink, Html};
-use yewtil::NeqAssign;
-
-use tr::tr;
-
-use crate::app::components::raw_html::RawHTML;
-use crate::app::matrix::{MatrixAgent, Request, Response};
-
-pub struct RoomList {
- link: ComponentLink<Self>,
- state: State,
- matrix_agent: Box<dyn Bridge<MatrixAgent>>,
- props: Props,
-}
-
-#[allow(clippy::large_enum_variant)]
-pub enum Msg {
- NewMessage(Response),
- ChangeRoom(Room),
- SetFilter(String),
- ToggleTheme,
-}
-
-#[derive(Serialize, Deserialize, Default)]
-pub struct State {
- rooms: HashMap<RoomId, Room>,
- current_room: Option<Room>,
- loading: bool,
- search_query: Option<String>,
- dark_theme: bool,
- did_fetch: bool,
-}
-
-#[derive(Clone, PartialEq, Properties)]
-pub struct Props {
- #[prop_or_default]
- pub change_room_callback: Callback<Room>,
-}
-
-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,
- search_query: None,
- dark_theme: false,
- did_fetch: false,
- };
-
- 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;
- self.state.did_fetch = false;
- true
- }
- // Better Initial Sync Detection
- Response::SyncPing => {
- if self.state.rooms.is_empty() && !self.state.did_fetch {
- self.matrix_agent.send(Request::GetJoinedRooms);
- self.state.did_fetch = true;
- }
- false
- }
- // Handle new rooms from sync
- Response::Sync((room_id, _msg)) => {
- if !(self.state.rooms.contains_key(&room_id)) && !self.state.did_fetch {
- self.matrix_agent.send(Request::GetJoinedRoom(room_id));
- }
- false
- }
- Response::JoinedRoom((room_id, room)) => {
- self.state.rooms.insert(room_id, room);
- true
- }
- _ => false,
- },
- Msg::ChangeRoom(room) => {
- if self.state.current_room.is_some() && self.state.current_room.as_ref().unwrap() == &room {
- return false;
- }
- self.props.change_room_callback.emit(room.clone());
- self.state.current_room = Some(room);
- true
- }
- Msg::SetFilter(query) => {
- self.state.search_query = Some(query);
- true
- }
- Msg::ToggleTheme => {
- self.state.dark_theme = !self.state.dark_theme;
- let theme = if self.state.dark_theme {
- "dark"
- } else {
- "light"
- };
- document()
- .document_element()
- .unwrap()
- .dyn_into::<HtmlElement>()
- .unwrap()
- .dataset()
- .set("theme", theme)
- .unwrap();
- true
- }
- }
- }
-
- fn change(&mut self, props: Self::Properties) -> ShouldRender {
- self.props.neq_assign(props)
- }
-
- //noinspection RsTypeCheck
- fn view(&self) -> Html {
- if self.state.loading {
- html! {
- <div class="container">
- <div class="uk-position-center uk-padding">
- <RawHTML inner_html=include_str!("../svgs/loading_animation.svg")/>
- </div>
- </div>
- }
- } else {
- html! {
- <div class="container roomlist uk-flex uk-flex-column uk-width-1-6" style="height: 100%">
- <div class="uk-padding uk-padding-remove-bottom" style="height: 50px">
- <form class="uk-search uk-search-default">
- <span class="material-icons" id ="ma-icon">{"search"}</span>
- <input
- class="uk-search-input"
- type="search"
- placeholder={
- tr!(
- // Placeholder text for the roomlist filtering
- "Filter Rooms..."
- )
- }
- value=&self.state.search_query.as_ref().unwrap_or(&"".to_string())
- oninput=self.link.callback(|e: InputData| Msg::SetFilter(e.value)) />
- </form>
- </div>
- <ul class="scrollable uk-flex uk-flex-column uk-padding uk-nav-default uk-nav-parent-icon uk-padding-remove-bottom" uk-nav="">
- <li class="uk-nav-header">
- {
- tr!(
- // Header of the Roomlist
- "Rooms"
- )
- }
- </li>
- {
- if self.state.search_query.is_none() || (self.state.search_query.as_ref().unwrap_or(&"".to_string()) == &"".to_string()) {
- self.state.rooms.iter().map(|(_, room)| self.get_room(room)).collect::<Html>()
- } else {
- self.state.rooms.iter().filter(|(_, room)| room.display_name().to_lowercase().contains(&self.state.search_query.as_ref().unwrap().to_lowercase())).map(|(_, room)| self.get_room(room)).collect::<Html>()
- }
- }
- </ul>
- <div class="toggleWrapper uk-margin uk-flex uk-flex-column uk-flex-center" style="height: 60px;">
- <input type="checkbox" class="dn" id="dn" checked=self.state.dark_theme value=self.state.dark_theme onclick=self.link.callback(|e: MouseEvent| {Msg::ToggleTheme})/>
- <label for="dn" class="toggle">
- <span class="toggle__handler">
- <span class="crater crater--1"></span>
- <span class="crater crater--2"></span>
- <span class="crater crater--3"></span>
- </span>
- <span class="star star--1"></span>
- <span class="star star--2"></span>
- <span class="star star--3"></span>
- <span class="star star--4"></span>
- <span class="star star--5"></span>
- //<span class="star star--6"></span>
- </label>
- </div>
- </div>
- }
- }
- }
-}
-
-impl RoomList {
- fn get_room(&self, matrix_room: &Room) -> Html {
- let classes = if self.state.current_room.is_some() {
- if self.state.current_room.as_ref().unwrap().room_id == matrix_room.room_id {
- "uk-active"
- } else {
- ""
- }
- } else {
- ""
- };
-
- let room = matrix_room.clone();
- html! {
- <li class=classes>
- <a onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room.clone()))>
- {matrix_room.display_name()}
- {
- if matrix_room.unread_notifications.is_some() && matrix_room.unread_notifications.unwrap() != UInt::from(0u32) {
- html! { <span class="uk-badge uk-margin-small-left">{matrix_room.unread_notifications.unwrap()}</span> }
- } else {
- html! {}
- }
- }
- {
- if matrix_room.unread_highlight.is_some() && matrix_room.unread_highlight.unwrap() != UInt::from(0u32) {
- html! { <span class="uk-badge red uk-margin-small-left">{matrix_room.unread_highlight.unwrap()}</span> }
- } else {
- html! {}
- }
- }
- </a>
- </li>
- }
- }
-}
diff --git a/src/app/components/room_list/item.rs b/src/app/components/room_list/item.rs
@@ -0,0 +1,94 @@
+use matrix_sdk::{events::room::message::MessageEventContent, js_int::UInt, Room};
+use rand::random;
+use yew::prelude::*;
+
+use crate::app::components::events::{get_sender_avatar, get_sender_displayname, is_new_user};
+use crate::app::matrix::types::get_media_download_url;
+use url::Url;
+
+pub(crate) struct RoomItem {
+ props: Props,
+ link: ComponentLink<Self>,
+}
+
+pub enum Msg {
+ ChangeRoom(Room),
+}
+
+#[derive(Clone, Properties, Debug)]
+pub struct Props {
+ #[prop_or_default]
+ pub room: Option<Room>,
+
+ #[prop_or_default]
+ pub change_room_callback: Callback<Room>,
+}
+
+impl Component for RoomItem {
+ type Message = Msg;
+ type Properties = Props;
+
+ fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
+ RoomItem { props, link }
+ }
+
+ fn update(&mut self, msg: Self::Message) -> bool {
+ match msg {
+ Msg::ChangeRoom(room) => {
+ self.props.change_room_callback.emit(room);
+ }
+ }
+ false
+ }
+
+ fn change(&mut self, props: Self::Properties) -> bool {
+ // TODO fix the PartialEq hack
+ if format!("{:#?}", self.props) != format!("{:#?}", props) {
+ self.props = props;
+ true
+ } else {
+ false
+ }
+ }
+
+ //noinspection RsTypeCheck
+ fn view(&self) -> Html {
+ let room = self.props.room.clone().unwrap();
+
+ // TODO placeholder for encrypted rooms
+ let last_message = match self
+ .props
+ .room
+ .as_ref()
+ .unwrap()
+ .messages
+ .clone()
+ .into_iter()
+ .last()
+ {
+ None => "".to_string(),
+ Some(m) => {
+ let content = m.content.clone();
+ if let MessageEventContent::Text(text_event) = content {
+ text_event.body
+ } else {
+ "".to_string()
+ }
+ }
+ };
+ html! {
+ <div class="room-list-item">
+ <a onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room.clone()))>
+ <div class="content">
+ // TODO remove placeholder
+ <img class="avatar" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAARUlEQVRYhe3OMQ0AIADAMBKUowmBoIKMo0f/jrnX+dmoA4KCdUBQsA4ICtYBQcE6IChYBwQF64CgYB0QFKwDgoJ1QPC1C8gY0kSgNLTWAAAAAElFTkSuQmCC"/>
+ <div>
+ <h5 class="name">{self.props.room.as_ref().unwrap().display_name()}</h5>
+ <p class="latest-msg">{last_message}</p>
+ </div>
+ </div>
+ </a>
+ </div>
+ }
+ }
+}
diff --git a/src/app/components/room_list/mod.rs b/src/app/components/room_list/mod.rs
@@ -0,0 +1,228 @@
+use std::collections::HashMap;
+use std::include_str;
+
+use log::*;
+use matrix_sdk::{identifiers::RoomId, js_int::UInt, Room};
+use serde::{Deserialize, Serialize};
+use wasm_bindgen::JsCast;
+use web_sys::HtmlElement;
+use yew::prelude::*;
+use yew::utils::document;
+use yew::{Bridge, Bridged, Component, ComponentLink, Html};
+use yewtil::NeqAssign;
+
+use tr::tr;
+
+use crate::app::components::raw_html::RawHTML;
+use crate::app::components::room_list::item::RoomItem;
+use crate::app::matrix::{MatrixAgent, Request, Response};
+
+mod item;
+
+pub struct RoomList {
+ link: ComponentLink<Self>,
+ state: State,
+ matrix_agent: Box<dyn Bridge<MatrixAgent>>,
+ props: Props,
+}
+
+#[allow(clippy::large_enum_variant)]
+pub enum Msg {
+ NewMessage(Response),
+ ChangeRoom(Room),
+ SetFilter(String),
+ ToggleTheme,
+}
+
+#[derive(Serialize, Deserialize, Default)]
+pub struct State {
+ rooms: HashMap<RoomId, Room>,
+ current_room: Option<Room>,
+ loading: bool,
+ search_query: Option<String>,
+ dark_theme: bool,
+}
+
+#[derive(Clone, PartialEq, Properties)]
+pub struct Props {
+ #[prop_or_default]
+ pub change_room_callback: Callback<Room>,
+}
+
+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,
+ search_query: None,
+ dark_theme: false,
+ };
+
+ RoomList {
+ props,
+ link,
+ matrix_agent,
+ state,
+ }
+ }
+
+ fn update(&mut self, msg: Self::Message) -> bool {
+ match msg {
+ Msg::NewMessage(response) => match response {
+ // Handle new rooms from sync
+ Response::JoinedRoomSync(room_id) => {
+ info!("Got JoinedRoomSync");
+ if !(self.state.rooms.contains_key(&room_id)) {
+ self.matrix_agent.send(Request::GetJoinedRoom(room_id));
+ }
+ false
+ }
+ Response::JoinedRoom((room_id, room)) => {
+ self.state.rooms.insert(room_id, room);
+ if self.state.loading {
+ self.state.loading = false;
+ }
+ true
+ }
+ _ => false,
+ },
+ Msg::ChangeRoom(room) => {
+ if self.state.current_room.is_some()
+ && self.state.current_room.as_ref().unwrap() == &room
+ {
+ return false;
+ }
+ self.props.change_room_callback.emit(room.clone());
+ self.state.current_room = Some(room);
+ true
+ }
+ Msg::SetFilter(query) => {
+ self.state.search_query = Some(query);
+ true
+ }
+ Msg::ToggleTheme => {
+ self.state.dark_theme = !self.state.dark_theme;
+ let theme = if self.state.dark_theme {
+ "dark"
+ } else {
+ "light"
+ };
+ document()
+ .document_element()
+ .unwrap()
+ .dyn_into::<HtmlElement>()
+ .unwrap()
+ .dataset()
+ .set("theme", theme)
+ .unwrap();
+ true
+ }
+ }
+ }
+
+ fn change(&mut self, props: Self::Properties) -> ShouldRender {
+ self.props.neq_assign(props)
+ }
+
+ //noinspection RsTypeCheck
+ fn view(&self) -> Html {
+ if self.state.loading {
+ html! {
+ <div class="container">
+ <div class="uk-position-center uk-padding">
+ <RawHTML inner_html=include_str!("../../svgs/loading_animation.svg")/>
+ </div>
+ </div>
+ }
+ } else {
+ html! {
+ <div class="roomlist" style="height: 100%">
+ <div class="top-bar">
+ <div class="userdata">
+ </div>
+ <div class="search">
+ <div>
+ <span class="material-icons">{"search"}</span>
+ <input
+ class="search-input"
+ type="search"
+ placeholder={
+ tr!(
+ // Placeholder text for the roomlist filtering
+ "Filter Rooms..."
+ )
+ }
+ value=&self.state.search_query.as_ref().unwrap_or(&"".to_string())
+ oninput=self.link.callback(|e: InputData| Msg::SetFilter(e.value)) />
+ </div>
+ </div>
+ </div>
+
+ <div class="scrollable list">
+ {
+ if self.state.search_query.is_none() || (self.state.search_query.as_ref().unwrap_or(&"".to_string()) == &"".to_string()) {
+ self.state.rooms.iter().map(|(_, room)| self.get_room(room)).collect::<Html>()
+ } else {
+ self.state.rooms.iter().filter(|(_, room)| room.display_name().to_lowercase().contains(&self.state.search_query.as_ref().unwrap().to_lowercase())).map(|(_, room)| self.get_room(room)).collect::<Html>()
+ }
+ }
+ </div>
+ <div class="bottom-bar">
+ <div class="toggleWrapper">
+ <input type="checkbox" class="dn" id="dn" checked=self.state.dark_theme value=self.state.dark_theme onclick=self.link.callback(|e: MouseEvent| {Msg::ToggleTheme})/>
+ <label for="dn" class="toggle">
+ <span class="toggle__handler">
+ <span class="crater crater--1"></span>
+ <span class="crater crater--2"></span>
+ <span class="crater crater--3"></span>
+ </span>
+ <span class="star star--1"></span>
+ <span class="star star--2"></span>
+ <span class="star star--3"></span>
+ <span class="star star--4"></span>
+ <span class="star star--5"></span>
+ //<span class="star star--6"></span>
+ </label>
+ </div>
+ </div>
+ </div>
+ }
+ }
+ }
+}
+
+impl RoomList {
+ fn get_room(&self, matrix_room: &Room) -> Html {
+ let room = matrix_room.clone();
+ html! {
+ <RoomItem change_room_callback=self.link.callback(Msg::ChangeRoom) room=Some(room)/>
+ }
+ /*html! {
+ <li class=classes>
+ <a onclick=self.link.callback(move |e: MouseEvent| Msg::ChangeRoom(room.clone()))>
+ {matrix_room.display_name()}
+ {
+ if matrix_room.unread_notifications.is_some() && matrix_room.unread_notifications.unwrap() != UInt::from(0u32) {
+ html! { <span class="uk-badge uk-margin-small-left">{matrix_room.unread_notifications.unwrap()}</span> }
+ } else {
+ html! {}
+ }
+ }
+ {
+ if matrix_room.unread_highlight.is_some() && matrix_room.unread_highlight.unwrap() != UInt::from(0u32) {
+ html! { <span class="uk-badge red uk-margin-small-left">{matrix_room.unread_highlight.unwrap()}</span> }
+ } else {
+ html! {}
+ }
+ }
+ </a>
+ </li>
+ }*/
+ }
+}
diff --git a/src/app/matrix/mod.rs b/src/app/matrix/mod.rs
@@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
use url::Url;
use wasm_bindgen_futures::spawn_local;
use yew::format::Json;
-use yew::services::{storage::Area, StorageService};
+use yew::worker::*;
use yew::worker::*;
use crate::app::matrix::types::{get_media_download_url, get_video_media_download_url};
@@ -55,36 +55,35 @@ pub struct MatrixAgent {
matrix_client: Option<Client>,
// TODO make arc mutex :(
subscribers: HashSet<HandlerId>,
- storage: Arc<Mutex<StorageService>>,
session: Option<SessionStore>,
}
-#[derive(Debug)]
+#[derive(Serialize, Deserialize, Debug)]
pub enum Request {
SetHomeserver(String),
SetUsername(String),
SetPassword(String),
+ SetSession(SessionStore),
Login,
GetLoggedIn,
GetOldMessages((RoomId, Option<String>)),
StartSync,
- GetJoinedRooms,
GetJoinedRoom(RoomId),
SendMessage((RoomId, String)),
}
#[allow(clippy::large_enum_variant)]
-#[derive(Debug, Clone)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Response {
Error(MatrixError),
LoggedIn(bool),
// TODO properly handle sync events
- Sync((RoomId, MessageEvent)),
+ Sync((RoomId, EventJson<MessageEvent>)),
+ JoinedRoomSync(RoomId),
SyncPing,
- JoinedRoomList(HashMap<RoomId, Room>),
- Userdata(),
- OldMessages((RoomId, Vec<MessageEvent>)),
+ OldMessages((RoomId, Vec<EventJson<MessageEvent>>)),
JoinedRoom((RoomId, Room)),
+ SaveSession(SessionStore)
}
#[derive(Debug, Clone)]
@@ -93,29 +92,18 @@ pub enum Msg {
}
impl Agent for MatrixAgent {
- type Reach = Context<MatrixAgent>;
+ type Reach = Public<Self>;
type Message = Msg;
type Input = Request;
type Output = Response;
fn create(link: AgentLink<Self>) -> Self {
- let storage = Arc::new(Mutex::new(
- StorageService::new(Area::Local).expect("storage was disabled by the user"),
- ));
- let session: Option<SessionStore> = {
- if let Json(Ok(restored_model)) = storage.lock().unwrap().restore(AUTH_KEY) {
- Some(restored_model)
- } else {
- None
- }
- };
MatrixAgent {
link,
matrix_state: Default::default(),
matrix_client: None,
subscribers: HashSet::new(),
- storage,
- session,
+ session: Default::default(),
}
}
@@ -134,6 +122,10 @@ impl Agent for MatrixAgent {
}
fn handle_input(&mut self, msg: Self::Input, _: HandlerId) {
match msg {
+ Request::SetSession(session) => {
+ self.session = Some(session);
+ }
+
Request::SetHomeserver(homeserver) => {
self.matrix_state.homeserver = Some(homeserver);
}
@@ -184,10 +176,9 @@ impl Agent for MatrixAgent {
device_id: login_response.device_id,
homeserver_url: client.homeserver().to_string(),
};
- let mut storage = agent.storage.lock().unwrap();
- storage.store(AUTH_KEY, Json(&session_store));
-
for sub in subscribers.iter() {
+ let resp = Response::SaveSession(session_store.clone());
+ agent.link.respond(*sub, resp);
let resp = Response::LoggedIn(true);
agent.link.respond(*sub, resp);
}
@@ -265,24 +256,6 @@ impl Agent for MatrixAgent {
agent.start_sync().await;
});
}
- Request::GetJoinedRooms => {
- let agent = self.clone();
- spawn_local(async move {
- let rooms: Arc<RwLock<HashMap<RoomId, Arc<RwLock<Room>>>>> =
- agent.matrix_client.unwrap().joined_rooms();
-
- let readable_rooms = rooms.read().await;
- let mut rooms_unarced: HashMap<RoomId, Room> = HashMap::new();
- for (id, room) in readable_rooms.iter() {
- let unarced_room = (*room.write().await).clone();
- rooms_unarced.insert(id.clone(), unarced_room);
- }
- for sub in agent.subscribers.iter() {
- let resp = Response::JoinedRoomList(rooms_unarced.clone());
- agent.link.respond(*sub, resp);
- }
- });
- }
Request::GetOldMessages((room_id, from)) => {
let agent = self.clone();
spawn_local(async move {
@@ -320,7 +293,7 @@ impl Agent for MatrixAgent {
.unwrap();
// TODO save end point for future loading
- let mut wrapped_messages: Vec<MessageEvent> = Vec::new();
+ let mut wrapped_messages: Vec<EventJson<MessageEvent>> = Vec::new();
let chunk_iter: Vec<EventJson<RoomEvent>> = messsages.chunk;
let (oks, _): (Vec<_>, Vec<_>) = chunk_iter
.iter()
@@ -377,7 +350,9 @@ impl Agent for MatrixAgent {
}
event.content = MessageEventContent::Video(video_event);
}
- wrapped_messages.push(event.clone());
+
+ let serialized_event = EventJson::from(event.clone());
+ wrapped_messages.push(serialized_event);
}
}
@@ -439,6 +414,10 @@ impl Agent for MatrixAgent {
fn disconnected(&mut self, id: HandlerId) {
self.subscribers.remove(&id);
}
+
+ fn name_of_resource() -> &'static str {
+ "worker.js"
+ }
}
unsafe impl Send for MatrixAgent {}
diff --git a/src/app/matrix/sync.rs b/src/app/matrix/sync.rs
@@ -1,10 +1,15 @@
use std::sync::Arc;
+use std::sync::Mutex;
use std::time::Duration;
use log::*;
use matrix_sdk::{
api::r0::sync::sync_events::Response as SyncResponse,
- events::{collections::all::RoomEvent, room::message::MessageEventContent},
+ events::{
+ collections::all::{RoomEvent, StateEvent},
+ room::message::MessageEventContent,
+ EventJson,
+ },
identifiers::RoomId,
locks::RwLock,
Client, Room, SyncSettings,
@@ -12,10 +17,17 @@ use matrix_sdk::{
use wasm_bindgen_futures::spawn_local;
use yew::Callback;
+use lazy_static::lazy_static;
+
use crate::app::components::events::{get_sender_avatar, get_sender_displayname};
use crate::app::matrix::types::{get_media_download_url, get_video_media_download_url};
use crate::app::matrix::Response;
use crate::utils::notifications::Notifications;
+use serde::Serialize;
+
+lazy_static! {
+ static ref SYNC_NUMBER: Mutex<i32> = Mutex::new(0);
+}
pub struct Sync {
pub(crate) matrix_client: Client,
@@ -37,21 +49,45 @@ impl Sync {
async fn on_sync_response(&self, response: SyncResponse) {
debug!("got sync!");
+
// FIXME: Is there a smarter way?
let resp = Response::SyncPing;
self.callback.emit(resp);
for (room_id, room) in response.rooms.join {
+ for event in room.state.events {
+ if let Ok(event) = event.deserialize() {
+ self.on_state_event(&room_id, event).await
+ }
+ }
for event in room.timeline.events {
if let Ok(event) = event.deserialize() {
self.on_room_message(&room_id, event).await
}
}
}
+ let mut sync_number = SYNC_NUMBER.lock().unwrap();
+ if *sync_number == 0 {
+ *sync_number = 1;
+ }
+ }
+
+ async fn on_state_event(&self, room_id: &RoomId, event: StateEvent) {
+ if let StateEvent::RoomCreate(event) = event {
+ info!("Sent JoinedRoomSync State");
+ let resp = Response::JoinedRoomSync(room_id.clone());
+ self.callback.emit(resp);
+ }
}
async fn on_room_message(&self, room_id: &RoomId, event: RoomEvent) {
// TODO handle all messages...
+ if let RoomEvent::RoomCreate(_create_event) = event.clone() {
+ info!("Sent JoinedRoomSync Timeline");
+ let resp = Response::JoinedRoomSync(room_id.clone());
+ self.callback.emit(resp);
+ }
+
if let RoomEvent::RoomMessage(mut event) = event {
if let MessageEventContent::Text(text_event) = event.clone().content {
let homeserver_url = self.matrix_client.clone().homeserver().clone();
@@ -59,28 +95,41 @@ impl Sync {
let cloned_event = event.clone();
let client = self.matrix_client.clone();
let local_room_id = room_id.clone();
- spawn_local(async move {
- if Notifications::browser_support() {
- let room: Arc<RwLock<Room>> = client
- .clone()
- .get_joined_room(&local_room_id.clone())
- .await
- .unwrap();
- let read_clone = room.read().await;
- let clean_room = (*read_clone).clone();
- let avatar_url = get_sender_avatar(
- homeserver_url,
- clean_room.clone(),
- cloned_event.clone(),
- );
- let displayname =
- get_sender_displayname(clean_room, cloned_event.clone());
-
- let notification =
- Notifications::new(avatar_url, displayname, text_event.body.clone());
- notification.show();
- }
- });
+ let sync_number = SYNC_NUMBER.lock().unwrap();
+ if *sync_number == 1 {
+ spawn_local(async move {
+ if Notifications::browser_support() {
+ let room: Arc<RwLock<Room>> = client
+ .clone()
+ .get_joined_room(&local_room_id.clone())
+ .await
+ .unwrap();
+ let read_clone = room.read().await;
+ let clean_room = (*read_clone).clone();
+ let avatar_url = get_sender_avatar(
+ homeserver_url,
+ clean_room.clone(),
+ cloned_event.clone(),
+ );
+ let room_name = clean_room.display_name();
+ let displayname =
+ get_sender_displayname(clean_room, cloned_event.clone());
+
+ let mut title = "".to_string();
+ if displayname == room_name {
+ title = displayname;
+ } else {
+ title = format!("{} ({})", displayname, room_name);
+ }
+
+ /*
+ TODO fix by moving it out of the worker
+ let notification =
+ Notifications::new(avatar_url, title, text_event.body.clone());
+ notification.show();*/
+ }
+ });
+ }
}
if let MessageEventContent::Image(mut image_event) = event.clone().content {
if image_event.url.is_some() {
@@ -125,7 +174,8 @@ impl Sync {
event.content = MessageEventContent::Video(video_event);
}
- let resp = Response::Sync((room_id.clone(), event.clone()));
+ let serialized_event = EventJson::from(event.clone());
+ let resp = Response::Sync((room_id.clone(), serialized_event));
self.callback.emit(resp);
}
}
diff --git a/src/app/mod.rs b/src/app/mod.rs
@@ -2,12 +2,17 @@ use yew::{prelude::*, virtual_dom::VNode};
use yew_router::agent::RouteRequest::ChangeRoute;
use yew_router::{prelude::*, Switch};
-use crate::app::matrix::{MatrixAgent, Response};
+use crate::app::matrix::{MatrixAgent, Response, SessionStore};
use crate::app::views::{login::Login, main_view::MainView};
use log::*;
+use std::sync::{Arc, Mutex};
+use yew::services::StorageService;
+use yew::services::storage::Area;
+use crate::constants::AUTH_KEY;
+use yew::format::Json;
pub mod components;
-mod matrix;
+pub mod matrix;
mod views;
#[derive(Switch, Clone)]
@@ -30,6 +35,8 @@ pub struct App {
matrix_agent: Box<dyn Bridge<MatrixAgent>>,
route: Option<Route<()>>,
route_agent: Box<dyn Bridge<RouteAgent<()>>>,
+ session: Option<SessionStore>,
+ storage: Arc<Mutex<StorageService>>,
}
impl Component for App {
@@ -37,6 +44,17 @@ impl Component for App {
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
+ let storage = Arc::new(Mutex::new(
+ StorageService::new(Area::Local).expect("storage was disabled by the user"),
+ ));
+
+ let session: Option<SessionStore> = {
+ if let Json(Ok(restored_model)) = storage.lock().unwrap().restore(AUTH_KEY) {
+ Some(restored_model)
+ } else {
+ None
+ }
+ };
let route_agent = RouteAgent::bridge(link.callback(Msg::RouteChanged));
let mut matrix_agent = MatrixAgent::bridge(link.callback(Msg::NewMessage));
matrix_agent.send(matrix::Request::GetLoggedIn);
@@ -44,6 +62,8 @@ impl Component for App {
matrix_agent,
route_agent,
route: None,
+ session,
+ storage,
}
}
@@ -61,8 +81,17 @@ impl Component for App {
Msg::NewMessage(response) => {
//info!("NewMessage: {:#?}", response);
match response {
+ Response::SaveSession(session) => {
+ let mut storage = self.storage.lock().unwrap();
+ storage.store(AUTH_KEY, Json(&session));
+ }
Response::Error(_) => {}
Response::LoggedIn(logged_in) => {
+ if !logged_in && self.session.is_some() {
+ self.matrix_agent.send(matrix::Request::SetSession(self.session.clone().unwrap()));
+ self.matrix_agent.send(matrix::Request::GetLoggedIn);
+ return false;
+ }
let route: Route = if logged_in {
//self.state.logged_in = true;
diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs
@@ -71,8 +71,8 @@ impl Component for MainView {
html! {
<div class="uk-flex auto-scrollable-container" style="height: 100%">
<RoomList change_room_callback=self.link.callback(Msg::ChangeRoom)/>
- <div class="event-list container uk-flex uk-flex-column uk-width-5-6">
- <div class="room-title"><div><h1>{ self.state.current_room.as_ref().unwrap().display_name() }</h1></div></div>
+ <div class="event-list">
+ <div class="room-title"><h1>{ self.state.current_room.as_ref().unwrap().display_name() }</h1></div>
<h4>
{
tr!(
@@ -94,4 +94,3 @@ impl Component for MainView {
}
}
}
-
diff --git a/src/errors.rs b/src/errors.rs
@@ -20,7 +20,7 @@ impl fmt::Display for Field {
}
// TODO figure out a way to translate this
-#[derive(Error, Debug, Clone)]
+#[derive(Error, Debug, Clone, Serialize, Deserialize)]
pub enum MatrixError {
#[error("No Matrix Client is available yet")]
MissingClient,
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,6 +1,6 @@
#![recursion_limit = "512"]
extern crate console_error_panic_hook;
-extern crate wee_alloc;
+//extern crate wee_alloc;
use console_error_panic_hook::set_once as set_panic_hook;
use i18n_embed::{language_loader, I18nEmbed, WebLanguageRequester};
@@ -18,10 +18,11 @@ struct Translations;
language_loader!(DaydreamLanguageLoader);
-#[global_allocator]
-static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
+//#[global_allocator]
+//static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
-// This is the entry point for the web app
+
+//====== Running the primary frontend ======//
#[wasm_bindgen]
pub fn run_app() -> Result<(), JsValue> {
// If the `console_error_panic_hook` feature is enabled this will set a panic hook, otherwise
@@ -38,3 +39,21 @@ pub fn run_app() -> Result<(), JsValue> {
yew::start_app::<app::App>();
Ok(())
}
+
+//====== Running the worker ======///
+
+// We need to import the Threaded trait to register the worker
+use yew::agent::Threaded;
+
+/// This gets called by the worker.js entrypoint
+/// We need to wrap it in wasm_bindgen so the worker knows the spin the the yew worker instance
+#[wasm_bindgen]
+pub fn init_worker() {
+ // Spawning a yew component without StartApp requires initializing
+ yew::initialize();
+
+ // ... registering the worker
+ app::matrix::MatrixAgent::register();
+}
+
+// you would need a new launch function for each of the unique workers you want to register
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
@@ -1 +1,2 @@
pub mod notifications;
+pub mod string_utils;
diff --git a/src/utils/string_utils.rs b/src/utils/string_utils.rs
@@ -0,0 +1,51 @@
+use std::ops::{Bound, RangeBounds};
+
+pub trait StringUtils {
+ fn substring(&self, start: usize, len: usize) -> &str;
+ fn slice(&self, range: impl RangeBounds<usize>) -> &str;
+}
+
+impl StringUtils for str {
+ fn substring(&self, start: usize, len: usize) -> &str {
+ let mut char_pos = 0;
+ let mut byte_start = 0;
+ let mut it = self.chars();
+ loop {
+ if char_pos == start {
+ break;
+ }
+ if let Some(c) = it.next() {
+ char_pos += 1;
+ byte_start += c.len_utf8();
+ } else {
+ break;
+ }
+ }
+ char_pos = 0;
+ let mut byte_end = byte_start;
+ loop {
+ if char_pos == len {
+ break;
+ }
+ if let Some(c) = it.next() {
+ char_pos += 1;
+ byte_end += c.len_utf8();
+ } else {
+ break;
+ }
+ }
+ &self[byte_start..byte_end]
+ }
+ fn slice(&self, range: impl RangeBounds<usize>) -> &str {
+ let start = match range.start_bound() {
+ Bound::Included(bound) | Bound::Excluded(bound) => *bound,
+ Bound::Unbounded => 0,
+ };
+ let len = match range.end_bound() {
+ Bound::Included(bound) => *bound + 1,
+ Bound::Excluded(bound) => *bound,
+ Bound::Unbounded => self.len(),
+ } - start;
+ self.substring(start, len)
+ }
+}
diff --git a/startup_helper/worker/worker.js b/startup_helper/worker/worker.js
@@ -0,0 +1,4 @@
+import("../../pkg").then(wasm => {
+ // Call the library function we exported with wasm-bindgen in lib.rs
+ wasm.init_worker();
+});
diff --git a/static/custom-css.scss b/static/custom-css.scss
@@ -1,7 +1,7 @@
.scrollable {
overflow-y: auto !important;
overflow-x: hidden !important;
- height: 100vh;
+ height: 100%;
scrollbar-width: thin;
scrollbar-color: $scrollbar-color $scrollbar-bg-color;
}
@@ -78,7 +78,7 @@ em {
.error {
color: #992E2E;
- font-family: Roboto;
+ font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: 500;
font-size: 16px;
@@ -91,10 +91,6 @@ em {
margin: 0;
}
-.roomlist {
- min-width: 260px;
-}
-
.uk-search, .uk-inline {
position: relative;
}
@@ -141,7 +137,6 @@ li {
@import "sun-loading-animation.scss";
@import "lightbox.scss";
-@import "fonts.scss";
.daydream-title {
font-family: 'Dancing Script', cursive;
@@ -221,7 +216,7 @@ _::-webkit-full-page-media, _:future, :root .login-page-bg {
}
.login-title {
- font-family: Roboto;
+ font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: bold;
font-size: 48px;
@@ -252,7 +247,7 @@ _::-webkit-full-page-media, _:future, :root .login-page-bg {
transition: all 0.3s ease 0s;
border-radius: 45px;
- font-family: Roboto;
+ font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 24px;
@@ -295,7 +290,7 @@ input.login-input {
/* Subtitle 1 / Roboto Regular */
- font-family: Roboto;
+ font-family: 'Roboto', sans-serif;
font-style: normal;
font-weight: normal;
font-size: 16px;
@@ -344,25 +339,285 @@ body {
}
.room-title {
- margin-bottom: 1.5rem;
+ width: 100%;
+ height: 100%;
+ max-height: 5.875rem;
+
+ //h1 {
+ // margin: 0 !important;
+ // height: 5.875rem;
+ //}
+
+ &::before {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 5.875rem;
+ box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
+ -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
+ -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
+ }
+}
+
+.event-list {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
width: 100%;
- overflow: hidden;
- padding-bottom: 10px;
}
-.room-title > div {
+.message-container {
width: 100%;
- //height: 100px;
+ max-width: 67.0625rem;
- box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
- -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
- -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
+ margin: 0 auto;
}
-.event-list {
- padding: 40px;
- padding-bottom: 0 !important;
- padding-right: 0 !important;
- height: 100%;
+
+.roomlist {
+ width: 100%;
+ max-width: 31.5rem;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ z-index: 0;
+
+ .list {
+ width: 100%;
+ flex-grow: 1;
+ height: auto !important;
+ z-index: -1;
+ }
+
+ .top-bar {
+ width: 100%;
+
+ height: 138px;
+
+ .userdata {
+ width: 100%;
+ height: 5.875rem;
+
+ background-color: $accent-color;
+
+ padding-left: 1.5625rem;
+ padding-right: 1.1875rem;
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
+ box-sizing: border-box; /* Opera/IE 8+ */
+ }
+
+ .search {
+ width: 100%;
+
+ div {
+ height: 2.75rem;
+
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ padding-left: 1.5625rem;
+ padding-right: 1.1875rem;
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
+ box-sizing: border-box; /* Opera/IE 8+ */
+ z-index: 1;
+
+ width: 100%;
+ box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
+ -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
+ -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.2), 0 4px 5px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
+
+ .material-icons {
+ margin-top: 0.625rem;
+ margin-bottom: 0.625rem;
+ }
+
+ .search-input {
+ /*
+ * 1. Define consistent box sizing.
+ * 3. Remove `border-radius` in iOS.
+ * 4. Change font properties to `inherit` in all browsers
+ * 5. Show the overflow in Edge.
+ * 6. Remove default style in iOS.
+ * 7. Vertical alignment
+ * 8. Take the full container width
+ * 9. Style
+ */
+ /* 1 */
+ box-sizing: border-box;
+ /* 3 */
+ border-radius: 0;
+ /* 4 */
+ font: inherit;
+ /* 5 */
+ overflow: visible;
+ /* 6 */
+ -webkit-appearance: none;
+ /* 7 */
+ vertical-align: middle;
+ /* 8 */
+ width: 100%;
+ /* 9 */
+ border: none;
+
+
+ margin-top: 0.625rem;
+ margin-bottom: 0.625rem;
+ margin-left: 3.206875rem;
+
+ /* H6 / Roboto Medium */
+
+ font-family: 'Roboto', sans-serif;
+ font-style: normal;
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 23px;
+ display: flex;
+ align-items: center;
+ letter-spacing: 0.15px;
+
+ color: $search-grey;
+
+ &:focus {
+ // Chrome override
+ outline: none 0;
+ }
+
+
+ /*
+ * Remove the inner padding and cancel buttons in Chrome on OS X and Safari on OS X.
+ */
+
+ &::-webkit-search-cancel-button,
+ &::-webkit-search-decoration {
+ -webkit-appearance: none;
+ }
+
+ /*
+ * Removes placeholder transparency in Firefox.
+ */
+
+ &::-moz-placeholder {
+ opacity: 1;
+ }
+
+ }
+ }
+ }
+
+ }
+
+ .bottom-bar {
+ height: 4.375rem;
+ width: 100%;
+ position: relative;
+
+ &::after {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ z-index: -1;
+ box-shadow: -10px -1px 10px rgba(0, 0, 0, 0.2), -10px -4px 5px rgba(0, 0, 0, 0.12), -10px -2px 4px rgba(0, 0, 0, 0.14);
+ }
+
+ .toggleWrapper {
+ height: 100%;
+ // Following later becomes button stuff
+ margin: 9px 54px;
+ }
+ }
+}
+
+// TODO make room-list-item responsive
+.room-list-item {
+ &:hover {
+ background-color: #d9d9d9;
+ }
+
+ a {
+ &:hover {
+ text-decoration: none !important;
+ }
+
+ background-color: #F2F2F2;
+ height: 5rem;
+
+ .content {
+ margin-left: 35px;
+ height: 5rem;
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ max-width: 28.8125rem;
+
+ img.avatar {
+ border-radius: 50%;
+ box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2), 0 3px 4px rgba(0, 0, 0, 0.12), 0 3px 3px rgba(0, 0, 0, 0.14);
+ }
+
+ .avatar {
+ margin-right: 28px;
+ width: 48px;
+ height: 48px;
+ box-sizing: border-box;
+ margin-bottom: 16px;
+ margin-top: 16px;
+ }
+
+ div {
+ margin-bottom: 3px;
+
+ .name {
+ /* RoomList Names */
+
+ font-family: Roboto, sans-serif;
+ font-style: normal;
+ font-weight: 500;
+ font-size: 18px;
+ line-height: 23px;
+ margin: 0 !important;
+ margin-top: 18px !important;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+
+ /* Black */
+
+ color: #000000;
+
+ width: 100%;
+ max-width: 22rem;
+ }
+
+ p.latest-msg {
+ font-family: Roboto, sans-serif;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 28px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+
+ letter-spacing: 0.5px;
+ margin: 0 !important;
+
+ /* Search Grey */
+ color: #737373;
+
+ width: 100%;
+ max-width: 22rem;
+ display: inline-block;
+ }
+ }
+ }
+
+ }
}
+
diff --git a/static/dark_theme/_globals.scss b/static/dark_theme/_globals.scss
@@ -2,8 +2,10 @@
--global-background: #1c1c21;
--global-scrollbar-color: hsla(0,0%,100%,.2);
--global-scrollbar-bg-color: transparent;
+ --global-accent-color: #732222;
--global-emphasis-color: #fff;
--global-color: #fff;
--icon-link-active-color: darken(#fff, 5%);
--active-menu: #dddddd;
+ --search-grey: #737373;
}
diff --git a/static/fonts.scss b/static/fonts.scss
@@ -1 +0,0 @@
-@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@700&family=Roboto:wght@300;400;500;700&display=swap');
diff --git a/static/index.html b/static/index.html
@@ -5,6 +5,9 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="Description" content="A Matrix Client written in Rust using WebAssembly.">
+ <link rel="dns-prefetch" href="//fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=Dancing+Script:wght@700&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<title>Daydream</title>
</head>
diff --git a/static/light_theme/_globals.scss b/static/light_theme/_globals.scss
@@ -1,8 +1,10 @@
[data-theme="light"] {
--global-scrollbar-color: black;
--global-scrollbar-bg-color: transparent;
+ --global-accent-color: #732222;
--global-emphasis-color: #333;
--global-color: #333333;
--icon-link-active-color: darken(#666, 5%);
--active-menu: #666666;
+ --search-grey: #737373;
}
diff --git a/static/variable-ovewrites.scss b/static/variable-ovewrites.scss
@@ -1,6 +1,8 @@
// Custom component colors
$scrollbar-color: var(--global-scrollbar-color);
$scrollbar-bg-color: var(--global-scrollbar-bg-color);
+$accent-color: var(--global-accent-color);
+$search-grey: var(--search-grey);
// UIKit Colors
$global-background: var(--global-background);
diff --git a/webpack.config.js b/webpack.config.js
@@ -3,13 +3,14 @@ const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
const distPath = path.resolve(__dirname, "dist");
-module.exports = (env, argv) => {
+const appConfig = (env, argv) => {
return {
devServer: {
contentBase: distPath,
- compress: argv.mode === 'production',
+ compress: process.env.WEBPACK_MODE === 'production',
port: 8888,
historyApiFallback: true
},
@@ -19,6 +20,12 @@ module.exports = (env, argv) => {
filename: "daydream.js",
webassemblyModuleFilename: "daydream.wasm"
},
+ optimization: {
+ minimize: true,
+ minimizer: [new TerserPlugin({
+ parallel: true,
+ })],
+ },
module: {
rules: [
{
@@ -33,7 +40,7 @@ module.exports = (env, argv) => {
include: [path.resolve(__dirname, "static"), path.resolve(__dirname, "node_modules")],
use: [
// fallback to style-loader in development
- process.env.NODE_ENV !== 'production'
+ process.env.WEBPACK_MODE !== 'production'
? 'style-loader'
: MiniCssExtractPlugin.loader,
'css-loader',
@@ -69,7 +76,28 @@ module.exports = (env, argv) => {
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "static/index.html")
})
- ]
- //watch: argv.mode !== 'production'
+ ],
+ watch: process.env.WEBPACK_MODE !== 'production',
+ watchOptions: {
+ poll: true
+ }
};
};
+
+// This config actually generates both
+const workerConfig = {
+ entry: "./startup_helper/worker/worker.js",
+ target: "webworker",
+ plugins: [],
+ resolve: {
+ extensions: [".js", ".wasm"]
+ },
+ output: {
+ path: distPath,
+ filename: "worker.js"
+ }
+};
+
+module.exports = (env, argv) => {
+ return [appConfig(env, argv), workerConfig]
+};