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 d9c8875497732c06ecc1f25d84ec760c742d266d
parent 651ad3ece8adfc3bebbe03d695f87b89cff169fe
Author: Marcel <mtrnord1@gmail.com>
Date:   Sat,  6 Jun 2020 20:01:00 +0200

Split all current events to own Components, Add Video Event, Prepare translations

Took 40 minutes

Diffstat:
MCargo.toml | 9++++++++-
MREADME.md | 4++++
Ai18n.toml | 11+++++++++++
Ai18n/po/de/daydream.po | 0
Ai18n/pot/daydream.pot | 0
Ai18n/pot/src/app/components/event_list.pot | 18++++++++++++++++++
Ai18n/pot/src/app/components/events/image.pot | 18++++++++++++++++++
Ai18n/pot/src/app/components/events/mod.pot | 18++++++++++++++++++
Ai18n/pot/src/app/components/events/notice.pot | 18++++++++++++++++++
Ai18n/pot/src/app/components/events/text.pot | 18++++++++++++++++++
Ai18n/pot/src/app/components/events/video.pot | 18++++++++++++++++++
Ai18n/pot/src/app/components/mod.pot | 18++++++++++++++++++
Ai18n/pot/src/app/components/room_list.pot | 18++++++++++++++++++
Ai18n/pot/src/app/matrix/mod.pot | 18++++++++++++++++++
Ai18n/pot/src/app/matrix/sync.pot | 18++++++++++++++++++
Ai18n/pot/src/app/matrix/types.pot | 18++++++++++++++++++
Ai18n/pot/src/app/mod.pot | 18++++++++++++++++++
Ai18n/pot/src/app/views/login.pot | 18++++++++++++++++++
Ai18n/pot/src/app/views/main_view.pot | 18++++++++++++++++++
Ai18n/pot/src/app/views/mod.pot | 18++++++++++++++++++
Ai18n/pot/src/constants.pot | 18++++++++++++++++++
Ai18n/pot/src/errors.pot | 18++++++++++++++++++
Ai18n/pot/src/lib.pot | 18++++++++++++++++++
Msrc/app/components/event_list.rs | 63+++++++++++++++++++++++++--------------------------------------
Msrc/app/components/events/image.rs | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/app/components/events/notice.rs | 51++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/app/components/events/video.rs | 58+++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Dsrc/app/matrix.rs | 445-------------------------------------------------------------------------------
Asrc/app/matrix/mod.rs | 466+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/app/matrix/sync.rs | 25+++++++++++++++++++++++--
Msrc/app/matrix/types.rs | 14++++++++++++++
Rsrc/app.rs -> src/app/mod.rs | 0
Msrc/app/views/main_view.rs | 23++++++++++++++++++-----
33 files changed, 1051 insertions(+), 500 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -22,13 +22,20 @@ wee_alloc = "0.4" yew = { git = "https://github.com/yewstack/yew" } yew-router = { git = "https://github.com/yewstack/yew" } yewtil = { git = "https://github.com/yewstack/yew" } + +# Matrix matrix-sdk = { version = "0.1.0", git = "https://github.com/MTRNord/matrix-rust-sdk", branch = "expose-send-rebased", default-features = false}# features = ["encryption"]} url = "2.1.1" thiserror = "1.0" futures-locks = { git = "https://github.com/asomers/futures-locks", default-features = false } -linked_hash_set = {version = "0.1.3", features = ["serde"] } + +# Markdown pulldown-cmark = "0.7.1" +# Translations +tr = { version = "0.1.3", default-features = false, features = ["gettext"]} +i18n-embed = { version = "0.4", features = ["web-sys-requester"]} + [dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/README.md b/README.md @@ -80,6 +80,10 @@ This is an example of how to list things you need to use the software and how to * Rust * Nodejs * Yarn +* gettext (See: https://github.com/kellpossible/cargo-i18n#system-requirements) +* When you want to recompile translations: + * https://github.com/kellpossible/cargo-i18n + * `cargo install xtr` ### Setting up a Development Environment diff --git a/i18n.toml b/i18n.toml @@ -0,0 +1,11 @@ +# (Required) The locale/language identifier of the language used in the source +# code. +src_locale = "en" + +# (Required) The locales that the software will be translated into. +target_locales = ["de"] + +[gettext] +# (Required) Path to the output directory, relative to `i18n.toml` of the crate +# being localized. +output_dir = "i18n" diff --git a/i18n/po/de/daydream.po b/i18n/po/de/daydream.po diff --git a/i18n/pot/daydream.pot b/i18n/pot/daydream.pot diff --git a/i18n/pot/src/app/components/event_list.pot b/i18n/pot/src/app/components/event_list.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/components/events/image.pot b/i18n/pot/src/app/components/events/image.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/components/events/mod.pot b/i18n/pot/src/app/components/events/mod.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/components/events/notice.pot b/i18n/pot/src/app/components/events/notice.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/components/events/text.pot b/i18n/pot/src/app/components/events/text.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/components/events/video.pot b/i18n/pot/src/app/components/events/video.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/components/mod.pot b/i18n/pot/src/app/components/mod.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/components/room_list.pot b/i18n/pot/src/app/components/room_list.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/matrix/mod.pot b/i18n/pot/src/app/matrix/mod.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/matrix/sync.pot b/i18n/pot/src/app/matrix/sync.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/matrix/types.pot b/i18n/pot/src/app/matrix/types.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/mod.pot b/i18n/pot/src/app/mod.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/views/login.pot b/i18n/pot/src/app/views/login.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/views/main_view.pot b/i18n/pot/src/app/views/main_view.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/app/views/mod.pot b/i18n/pot/src/app/views/mod.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/constants.pot b/i18n/pot/src/constants.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/errors.pot b/i18n/pot/src/errors.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/i18n/pot/src/lib.pot b/i18n/pot/src/lib.pot @@ -0,0 +1,18 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE daydream'S COPYRIGHT HOLDER +# This file is distributed under the same license as the daydream package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: daydream 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-06-06 17:49+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" diff --git a/src/app/components/event_list.rs b/src/app/components/event_list.rs @@ -8,7 +8,7 @@ use matrix_sdk::{ }; use yew::prelude::*; -use crate::app::components::events::text::Text; +use crate::app::components::events::{text::Text, notice::Notice, image::Image, video::Video}; use crate::app::matrix::{MatrixAgent, Request, Response}; pub struct EventList { @@ -224,46 +224,33 @@ impl EventList { } } MessageEventContent::Notice(notice_event) => { - if new_user { - html! { - <p style="opacity: .6;"><displayname>{sender_displayname}{": "}</displayname>{notice_event.body.clone()}</p> - } - } else { - html! { - <p style="opacity: .6;">{notice_event.body.clone()}</p> - } + html! { + <Notice + prev_event=prev_event.clone() + event=Some(event.clone()) + room=Some(self.props.current_room.clone().unwrap()) + notice_event=Some(notice_event.clone()) + /> } } MessageEventContent::Image(image_event) => { - let caption = format!("{}: {}", sender_displayname, image_event.body); - if image_event.url.clone().is_some() { - let image_url = image_event.url.clone().unwrap(); - let thumbnail = match image_event.info.clone().unwrap().thumbnail_url { - None => image_url.clone(), - Some(v) => v, - }; - if new_user { - html! { - <> - <p><displayname>{sender_displayname}{": "}</displayname></p> - <div uk-lightbox=""> - <a class="uk-inline" href=image_url data-caption=caption > - <img src=thumbnail alt=caption /> - </a> - </div> - </> - } - } else { - html! { - <div uk-lightbox=""> - <a class="uk-inline" href=image_url data-caption=caption > - <img src=thumbnail alt=caption /> - </a> - </div> - } - } - } else { - html! {} + html! { + <Image + prev_event=prev_event.clone() + event=Some(event.clone()) + room=Some(self.props.current_room.clone().unwrap()) + image_event=Some(image_event.clone()) + /> + } + } + MessageEventContent::Video(video_event) => { + html! { + <Video + prev_event=prev_event.clone() + event=Some(event.clone()) + room=Some(self.props.current_room.clone().unwrap()) + video_event=Some(video_event.clone()) + /> } } _ => { diff --git a/src/app/components/events/image.rs b/src/app/components/events/image.rs @@ -1,7 +1,11 @@ -use matrix_sdk::events::room::message::MessageEvent; +use matrix_sdk::{ + events::room::message::{MessageEvent, ImageMessageEventContent}, + Room, +}; use yew::prelude::*; +use crate::app::components::events::{is_new_user, get_sender_displayname}; -struct Image { +pub(crate) struct Image { props: Props, } @@ -11,6 +15,10 @@ pub struct Props { pub prev_event: Option<MessageEvent>, #[prop_or_default] pub event: Option<MessageEvent>, + #[prop_or_default] + pub image_event: Option<ImageMessageEventContent>, + #[prop_or_default] + pub room: Option<Room>, } impl Component for Image { @@ -35,7 +43,51 @@ impl Component for Image { } } + //noinspection RsTypeCheck fn view(&self) -> Html { - html! {} + let new_user = is_new_user( + self.props.prev_event.clone(), + self.props.event.clone().unwrap(), + ); + let sender_displayname = if new_user { + get_sender_displayname( + self.props.room.clone().unwrap(), + self.props.event.clone().unwrap(), + ) + } else { + "".to_string() + }; + + + let caption = format!("{}: {}", sender_displayname, self.props.image_event.as_ref().unwrap().body); + if self.props.image_event.as_ref().unwrap().url.clone().is_some() { + let image_url = self.props.image_event.as_ref().unwrap().url.clone().unwrap(); + let thumbnail = match self.props.image_event.as_ref().unwrap().info.clone().unwrap().thumbnail_url { + None => image_url.clone(), + Some(v) => v, + }; + if new_user { + html! { + <> + <p><displayname>{sender_displayname}{": "}</displayname></p> + <div uk-lightbox=""> + <a class="uk-inline" href=image_url data-caption=caption > + <img src=thumbnail alt=caption /> + </a> + </div> + </> + } + } else { + html! { + <div uk-lightbox=""> + <a class="uk-inline" href=image_url data-caption=caption > + <img src=thumbnail alt=caption /> + </a> + </div> + } + } + } else { + html! {} + } } } diff --git a/src/app/components/events/notice.rs b/src/app/components/events/notice.rs @@ -1,7 +1,12 @@ -use matrix_sdk::events::room::message::MessageEvent; +use matrix_sdk::{ + events::room::message::{MessageEvent, NoticeMessageEventContent}, + Room, +}; use yew::prelude::*; -struct Notice { +use crate::app::components::events::{get_sender_displayname, is_new_user}; + +pub(crate) struct Notice { props: Props, } @@ -11,6 +16,10 @@ pub struct Props { pub prev_event: Option<MessageEvent>, #[prop_or_default] pub event: Option<MessageEvent>, + #[prop_or_default] + pub notice_event: Option<NoticeMessageEventContent>, + #[prop_or_default] + pub room: Option<Room>, } impl Component for Notice { @@ -35,7 +44,43 @@ impl Component for Notice { } } + //noinspection RsTypeCheck fn view(&self) -> Html { - html! {} + let new_user = is_new_user( + self.props.prev_event.clone(), + self.props.event.clone().unwrap(), + ); + let sender_displayname = if new_user { + get_sender_displayname( + self.props.room.clone().unwrap(), + self.props.event.clone().unwrap(), + ) + } else { + "".to_string() + }; + + if new_user { + html! { + <p style="opacity: .6;"><displayname>{sender_displayname}{": "}</displayname> + { + self.props + .notice_event + .clone() + .unwrap().body.clone() + } + </p> + } + } else { + html! { + <p style="opacity: .6;"> + { + self.props + .notice_event + .clone() + .unwrap().body.clone() + } + </p> + } + } } } diff --git a/src/app/components/events/video.rs b/src/app/components/events/video.rs @@ -1,7 +1,11 @@ -use matrix_sdk::events::room::message::MessageEvent; +use matrix_sdk::{ + events::room::message::{MessageEvent, VideoMessageEventContent}, + Room, +}; use yew::prelude::*; +use crate::app::components::events::{is_new_user, get_sender_displayname}; -struct Video { +pub(crate) struct Video { props: Props, } @@ -11,6 +15,10 @@ pub struct Props { pub prev_event: Option<MessageEvent>, #[prop_or_default] pub event: Option<MessageEvent>, + #[prop_or_default] + pub video_event: Option<VideoMessageEventContent>, + #[prop_or_default] + pub room: Option<Room>, } impl Component for Video { @@ -35,7 +43,51 @@ impl Component for Video { } } + //noinspection RsTypeCheck fn view(&self) -> Html { - html! {} + let new_user = is_new_user( + self.props.prev_event.clone(), + self.props.event.clone().unwrap(), + ); + let sender_displayname = if new_user { + get_sender_displayname( + self.props.room.clone().unwrap(), + self.props.event.clone().unwrap(), + ) + } else { + "".to_string() + }; + + + let caption = format!("{}: {}", sender_displayname, self.props.video_event.as_ref().unwrap().body); + if self.props.video_event.as_ref().unwrap().url.clone().is_some() { + let video_url = self.props.video_event.as_ref().unwrap().url.clone().unwrap(); + let thumbnail = match self.props.video_event.as_ref().unwrap().info.clone().unwrap().thumbnail_url { + None => video_url.clone(), + Some(v) => v, + }; + if new_user { + html! { + <> + <p><displayname>{sender_displayname}{": "}</displayname></p> + <div uk-lightbox=""> + <a class="uk-inline" href=video_url data-caption=caption data-poster=thumbnail > + <img src=thumbnail alt=caption /> + </a> + </div> + </> + } + } else { + html! { + <div uk-lightbox=""> + <a class="uk-inline" href=video_url data-caption=caption data-poster=thumbnail > + <img src=thumbnail alt=caption /> + </a> + </div> + } + } + } else { + html! {} + } } } diff --git a/src/app/matrix.rs b/src/app/matrix.rs @@ -1,445 +0,0 @@ -use log::*; -use matrix_sdk::{ - api::r0::{filter::RoomEventFilter, message::get_message_events::Direction}, - events::{ - collections::all::RoomEvent, - room::message::{MessageEvent, MessageEventContent, TextMessageEventContent, FormattedBody, MessageFormat}, - EventJson, - }, - identifiers::RoomId, - js_int::UInt, - locks::RwLock, - Client, ClientConfig, MessagesRequestBuilder, Room, Session, -}; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use std::convert::TryFrom; -use std::sync::{Arc, Mutex}; -use url::Url; -use wasm_bindgen_futures::spawn_local; -use yew::format::Json; -use yew::services::{storage::Area, StorageService}; -use yew::worker::*; - -use crate::app::matrix::types::get_media_download_url; -use crate::constants::AUTH_KEY; -use crate::errors::MatrixError; -use pulldown_cmark::{html, Options, Parser}; - -mod sync; -pub mod types; - -#[derive(Serialize, Deserialize, Default, Clone, Debug)] -pub struct MatrixClient { - pub(crate) homeserver: Option<String>, - pub(crate) username: Option<String>, - pub(crate) password: Option<String>, -} - -#[derive(Serialize, Deserialize, Default, Clone, Debug)] -pub struct SessionStore { - pub(crate) access_token: String, - pub(crate) user_id: String, - pub(crate) device_id: String, - pub(crate) homeserver_url: String, -} - -#[derive(Clone, Debug)] -pub struct MatrixAgent { - link: AgentLink<MatrixAgent>, - matrix_state: MatrixClient, - matrix_client: Option<Client>, - // TODO make arc mutex :( - subscribers: HashSet<HandlerId>, - storage: Arc<Mutex<StorageService>>, - session: Option<SessionStore>, -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum Request { - SetHomeserver(String), - SetUsername(String), - SetPassword(String), - Login(), - GetLoggedIn, - GetUserdata, - GetOldMessages((RoomId, Option<String>)), - StartSync, - GetJoinedRooms, - GetJoinedRoom(RoomId), - SendMessage((RoomId, String)), -} - -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Clone)] -pub enum Response { - Error(MatrixError), - LoggedIn(bool), - // TODO properly handle sync events - Sync((RoomId, MessageEvent)), - SyncPing, - JoinedRoomList(HashMap<RoomId, Room>), - Userdata(), - OldMessages((RoomId, Vec<MessageEvent>)), - JoinedRoom((RoomId, Room)), -} - -#[derive(Debug, Clone)] -pub enum Msg { - OnSyncResponse(Response), -} - -impl Agent for MatrixAgent { - type Reach = Context<MatrixAgent>; - 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, - } - } - - fn update(&mut self, msg: Self::Message) { - match msg { - Msg::OnSyncResponse(resp) => { - for sub in self.subscribers.iter() { - self.link.respond(*sub, resp.clone()); - } - } - } - } - - fn connected(&mut self, id: HandlerId) { - self.subscribers.insert(id); - } - fn handle_input(&mut self, msg: Self::Input, _: HandlerId) { - match msg { - Request::SetHomeserver(homeserver) => { - self.matrix_state.homeserver = Some(homeserver); - } - Request::SetUsername(username) => { - self.matrix_state.username = Some(username); - } - Request::SetPassword(password) => { - self.matrix_state.password = Some(password); - } - Request::Login() => { - let login_client = self.login(); - if login_client.is_none() { - for sub in self.subscribers.iter() { - let resp = Response::Error(MatrixError::MissingClient); - self.link.respond(*sub, resp); - } - return; - } - let client = login_client.unwrap(); - let username = self.matrix_state.username.clone().unwrap(); - let password = self.matrix_state.password.clone().unwrap(); - let subscribers = self.subscribers.clone(); - let agent = self.clone(); - spawn_local(async move { - // TODO handle login error - if agent.session.is_some() { - let stored_session = agent.session.unwrap(); - let session = Session { - access_token: stored_session.access_token, - user_id: matrix_sdk::identifiers::UserId::try_from( - stored_session.user_id.as_str(), - ) - .unwrap(), - device_id: stored_session.device_id, - }; - client.restore_login(session).await; - } else { - // FIXME gracefully handle login errors - let login_response: matrix_sdk::api::r0::session::login::Response = client - .login(username, password, None, Some("Daydream".to_string())) - .await - .unwrap(); - let session_store = SessionStore { - access_token: login_response.access_token, - user_id: login_response.user_id.to_string(), - 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::LoggedIn(true); - agent.link.respond(*sub, resp); - } - }); - } - Request::GetLoggedIn => { - let login_client = self.login(); - if login_client.is_none() { - for sub in self.subscribers.iter() { - let resp = Response::Error(MatrixError::MissingClient); - self.link.respond(*sub, resp); - } - return; - } - - // Always clone agent after having tried to login! - let agent = self.clone(); - - spawn_local(async move { - let logged_in = agent.get_logged_in().await; - - if !logged_in && agent.session.is_some() { - error!("Not logged in but got session"); - } else { - for sub in agent.subscribers.iter() { - let resp = Response::LoggedIn(logged_in); - agent.link.respond(*sub, resp); - } - } - }); - } - Request::StartSync => { - // Always clone agent after having tried to login! - let agent = self.clone(); - spawn_local(async move { - 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::GetUserdata => { - // Noop - } - Request::GetOldMessages((room_id, from)) => { - let agent = self.clone(); - spawn_local(async move { - let mut builder = &mut MessagesRequestBuilder::new(); - builder = builder.room_id(room_id.clone()); - if let Some(from) = from { - builder = builder.from(from); - } else { - builder = builder.from( - agent - .matrix_client - .clone() - .unwrap() - .sync_token() - .await - .unwrap(), - ); - } - let filter = RoomEventFilter { - types: Some(vec!["m.room.message".to_string()]), - ..Default::default() - }; - builder = builder - .filter(filter) - .direction(Direction::Backward) - .limit(UInt::new(30).unwrap()); - - // TODO handle error gracefully - let messsages = agent - .matrix_client - .clone() - .unwrap() - .room_messages(builder.clone()) - .await - .unwrap(); - // TODO save end point for future loading - - let mut wrapped_messages: Vec<MessageEvent> = Vec::new(); - let chunk_iter: Vec<EventJson<RoomEvent>> = messsages.chunk; - let (oks, _): (Vec<_>, Vec<_>) = chunk_iter - .iter() - .map(|event| event.deserialize()) - .partition(Result::is_ok); - - let deserialized_events: Vec<RoomEvent> = - oks.into_iter().map(Result::unwrap).collect(); - - for event in deserialized_events.into_iter().rev() { - if let RoomEvent::RoomMessage(mut event) = event { - if let MessageEventContent::Image(mut image_event) = event.content { - if image_event.url.is_some() { - let new_url = Some(get_media_download_url( - agent.matrix_client.clone().unwrap(), - image_event.url.unwrap(), - )); - image_event.url = new_url; - } - if image_event.info.is_some() { - let mut info = image_event.info.unwrap(); - if info.thumbnail_url.is_some() { - let new_url = Some(get_media_download_url( - agent.matrix_client.clone().unwrap(), - info.thumbnail_url.unwrap(), - )); - info.thumbnail_url = new_url; - } - image_event.info = Some(info); - } - event.content = MessageEventContent::Image(image_event); - } - wrapped_messages.push(event.clone()); - } - } - - for sub in agent.subscribers.iter() { - let resp = - Response::OldMessages((room_id.clone(), wrapped_messages.clone())); - agent.link.respond(*sub, resp); - } - }); - } - Request::GetJoinedRoom(room_id) => { - let agent = self.clone(); - spawn_local(async move { - let room: Arc<RwLock<Room>> = agent - .matrix_client - .unwrap() - .get_joined_room(&room_id) - .await - .unwrap(); - let read_clone = room.read().await; - let clean_room = (*read_clone).clone(); - for sub in agent.subscribers.iter() { - let resp = Response::JoinedRoom((room_id.clone(), clean_room.clone())); - agent.link.respond(*sub, resp); - } - }); - } - Request::SendMessage((room_id, message)) => { - let client = self.matrix_client.clone().unwrap(); - spawn_local(async move { - let mut options = Options::empty(); - options.insert(Options::ENABLE_STRIKETHROUGH); - let parser = Parser::new_ext(message.as_str(), options); - - let mut formatted_message: String = - String::with_capacity(message.len() * 3 / 2); - html::push_html(&mut formatted_message, parser); - formatted_message = formatted_message.replace("<p>", "").replace("</p>", ""); - formatted_message.pop(); - - let content = if formatted_message == message { - MessageEventContent::Text(TextMessageEventContent::new_plain(message)) - } else { - MessageEventContent::Text(TextMessageEventContent { - body: message, - relates_to: None, - formatted: Some(FormattedBody{ - body: formatted_message, - format: MessageFormat::Html, - }) - }) - }; - client.room_send(&room_id, content, None).await; - }); - } - } - } - - fn disconnected(&mut self, id: HandlerId) { - self.subscribers.remove(&id); - } -} - -unsafe impl Send for MatrixAgent {} - -unsafe impl std::marker::Sync for MatrixAgent {} - -impl MatrixAgent { - async fn start_sync(&self) { - let sync = sync::Sync { - matrix_client: self.matrix_client.clone().unwrap(), - callback: self.link.callback(Msg::OnSyncResponse), - }; - sync.start_sync().await; - } - - async fn get_logged_in(&self) -> bool { - if self.matrix_client.is_none() { - return false; - } - self.matrix_client.as_ref().unwrap().logged_in().await - } - - fn login(&mut self) -> Option<Client> { - if (self.matrix_state.homeserver.is_none() - || self.matrix_state.username.is_none() - || self.matrix_state.password.is_none() - || self.matrix_client.is_some()) - && self.session.is_none() - { - for sub in self.subscribers.iter() { - let resp = Response::Error(MatrixError::MissingFields); - self.link.respond(*sub, resp); - } - None - } else if self.session.is_some() { - let homeserver = self.session.clone().unwrap().homeserver_url; - - let client_config = ClientConfig::new(); - let homeserver_url = Url::parse(&homeserver).unwrap(); - let client = Client::new_with_config(homeserver_url, client_config).unwrap(); - self.matrix_client = Some(client.clone()); - - // Also directly restore Login data - let stored_session = self.session.clone().unwrap(); - let session = Session { - access_token: stored_session.access_token, - user_id: matrix_sdk::identifiers::UserId::try_from(stored_session.user_id.as_str()) - .unwrap(), - device_id: stored_session.device_id, - }; - let client_clone = client.clone(); - spawn_local(async move { - client_clone.restore_login(session).await; - }); - - Some(client) - } else { - let homeserver = self.matrix_state.homeserver.clone().unwrap(); - - let client_config = ClientConfig::new(); - let homeserver_url = Url::parse(&homeserver).unwrap(); - let client = Client::new_with_config(homeserver_url, client_config).unwrap(); - self.matrix_client = Some(client.clone()); - - Some(client) - } - } -} diff --git a/src/app/matrix/mod.rs b/src/app/matrix/mod.rs @@ -0,0 +1,466 @@ +use log::*; +use matrix_sdk::{ + api::r0::{filter::RoomEventFilter, message::get_message_events::Direction}, + events::{ + collections::all::RoomEvent, + room::message::{MessageEvent, MessageEventContent, TextMessageEventContent, FormattedBody, MessageFormat}, + EventJson, + }, + identifiers::RoomId, + js_int::UInt, + locks::RwLock, + Client, ClientConfig, MessagesRequestBuilder, Room, Session, +}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use std::convert::TryFrom; +use std::sync::{Arc, Mutex}; +use url::Url; +use wasm_bindgen_futures::spawn_local; +use yew::format::Json; +use yew::services::{storage::Area, StorageService}; +use yew::worker::*; + +use crate::app::matrix::types::{get_media_download_url, get_video_media_download_url}; +use crate::constants::AUTH_KEY; +use crate::errors::MatrixError; +use pulldown_cmark::{html, Options, Parser}; + +mod sync; +pub mod types; + +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct MatrixClient { + pub(crate) homeserver: Option<String>, + pub(crate) username: Option<String>, + pub(crate) password: Option<String>, +} + +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct SessionStore { + pub(crate) access_token: String, + pub(crate) user_id: String, + pub(crate) device_id: String, + pub(crate) homeserver_url: String, +} + +#[derive(Clone, Debug)] +pub struct MatrixAgent { + link: AgentLink<MatrixAgent>, + matrix_state: MatrixClient, + matrix_client: Option<Client>, + // TODO make arc mutex :( + subscribers: HashSet<HandlerId>, + storage: Arc<Mutex<StorageService>>, + session: Option<SessionStore>, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Request { + SetHomeserver(String), + SetUsername(String), + SetPassword(String), + Login(), + GetLoggedIn, + GetUserdata, + GetOldMessages((RoomId, Option<String>)), + StartSync, + GetJoinedRooms, + GetJoinedRoom(RoomId), + SendMessage((RoomId, String)), +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone)] +pub enum Response { + Error(MatrixError), + LoggedIn(bool), + // TODO properly handle sync events + Sync((RoomId, MessageEvent)), + SyncPing, + JoinedRoomList(HashMap<RoomId, Room>), + Userdata(), + OldMessages((RoomId, Vec<MessageEvent>)), + JoinedRoom((RoomId, Room)), +} + +#[derive(Debug, Clone)] +pub enum Msg { + OnSyncResponse(Response), +} + +impl Agent for MatrixAgent { + type Reach = Context<MatrixAgent>; + 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, + } + } + + fn update(&mut self, msg: Self::Message) { + match msg { + Msg::OnSyncResponse(resp) => { + for sub in self.subscribers.iter() { + self.link.respond(*sub, resp.clone()); + } + } + } + } + + fn connected(&mut self, id: HandlerId) { + self.subscribers.insert(id); + } + fn handle_input(&mut self, msg: Self::Input, _: HandlerId) { + match msg { + Request::SetHomeserver(homeserver) => { + self.matrix_state.homeserver = Some(homeserver); + } + Request::SetUsername(username) => { + self.matrix_state.username = Some(username); + } + Request::SetPassword(password) => { + self.matrix_state.password = Some(password); + } + Request::Login() => { + let login_client = self.login(); + if login_client.is_none() { + for sub in self.subscribers.iter() { + let resp = Response::Error(MatrixError::MissingClient); + self.link.respond(*sub, resp); + } + return; + } + let client = login_client.unwrap(); + let username = self.matrix_state.username.clone().unwrap(); + let password = self.matrix_state.password.clone().unwrap(); + let subscribers = self.subscribers.clone(); + let agent = self.clone(); + spawn_local(async move { + // TODO handle login error + if agent.session.is_some() { + let stored_session = agent.session.unwrap(); + let session = Session { + access_token: stored_session.access_token, + user_id: matrix_sdk::identifiers::UserId::try_from( + stored_session.user_id.as_str(), + ) + .unwrap(), + device_id: stored_session.device_id, + }; + client.restore_login(session).await; + } else { + // FIXME gracefully handle login errors + let login_response: matrix_sdk::api::r0::session::login::Response = client + .login(username, password, None, Some("Daydream".to_string())) + .await + .unwrap(); + let session_store = SessionStore { + access_token: login_response.access_token, + user_id: login_response.user_id.to_string(), + 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::LoggedIn(true); + agent.link.respond(*sub, resp); + } + }); + } + Request::GetLoggedIn => { + let login_client = self.login(); + if login_client.is_none() { + for sub in self.subscribers.iter() { + let resp = Response::Error(MatrixError::MissingClient); + self.link.respond(*sub, resp); + } + return; + } + + // Always clone agent after having tried to login! + let agent = self.clone(); + + spawn_local(async move { + let logged_in = agent.get_logged_in().await; + + if !logged_in && agent.session.is_some() { + error!("Not logged in but got session"); + } else { + for sub in agent.subscribers.iter() { + let resp = Response::LoggedIn(logged_in); + agent.link.respond(*sub, resp); + } + } + }); + } + Request::StartSync => { + // Always clone agent after having tried to login! + let agent = self.clone(); + spawn_local(async move { + 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::GetUserdata => { + // Noop + } + Request::GetOldMessages((room_id, from)) => { + let agent = self.clone(); + spawn_local(async move { + let mut builder = &mut MessagesRequestBuilder::new(); + builder = builder.room_id(room_id.clone()); + if let Some(from) = from { + builder = builder.from(from); + } else { + builder = builder.from( + agent + .matrix_client + .clone() + .unwrap() + .sync_token() + .await + .unwrap(), + ); + } + let filter = RoomEventFilter { + types: Some(vec!["m.room.message".to_string()]), + ..Default::default() + }; + builder = builder + .filter(filter) + .direction(Direction::Backward) + .limit(UInt::new(30).unwrap()); + + // TODO handle error gracefully + let messsages = agent + .matrix_client + .clone() + .unwrap() + .room_messages(builder.clone()) + .await + .unwrap(); + // TODO save end point for future loading + + let mut wrapped_messages: Vec<MessageEvent> = Vec::new(); + let chunk_iter: Vec<EventJson<RoomEvent>> = messsages.chunk; + let (oks, _): (Vec<_>, Vec<_>) = chunk_iter + .iter() + .map(|event| event.deserialize()) + .partition(Result::is_ok); + + let deserialized_events: Vec<RoomEvent> = + oks.into_iter().map(Result::unwrap).collect(); + + for event in deserialized_events.into_iter().rev() { + if let RoomEvent::RoomMessage(mut event) = event { + if let MessageEventContent::Image(mut image_event) = event.clone().content { + if image_event.url.is_some() { + let new_url = Some(get_media_download_url( + agent.matrix_client.clone().unwrap(), + image_event.url.unwrap(), + )); + image_event.url = new_url; + } + if image_event.info.is_some() { + let mut info = image_event.info.unwrap(); + if info.thumbnail_url.is_some() { + let new_url = Some(get_media_download_url( + agent.matrix_client.clone().unwrap(), + info.thumbnail_url.unwrap(), + )); + info.thumbnail_url = new_url; + } + image_event.info = Some(info); + } + event.content = MessageEventContent::Image(image_event); + } + if let MessageEventContent::Video(mut video_event) = event.content { + if video_event.url.is_some() { + let new_url = Some(get_video_media_download_url( + agent.matrix_client.clone().unwrap(), + video_event.url.unwrap(), + )); + video_event.url = new_url; + } + if video_event.info.is_some() { + let mut info = video_event.info.unwrap(); + if info.thumbnail_url.is_some() { + let new_url = Some(get_media_download_url( + agent.matrix_client.clone().unwrap(), + info.thumbnail_url.unwrap(), + )); + info.thumbnail_url = new_url; + } + video_event.info = Some(info); + } + event.content = MessageEventContent::Video(video_event); + } + wrapped_messages.push(event.clone()); + } + } + + for sub in agent.subscribers.iter() { + let resp = + Response::OldMessages((room_id.clone(), wrapped_messages.clone())); + agent.link.respond(*sub, resp); + } + }); + } + Request::GetJoinedRoom(room_id) => { + let agent = self.clone(); + spawn_local(async move { + let room: Arc<RwLock<Room>> = agent + .matrix_client + .unwrap() + .get_joined_room(&room_id) + .await + .unwrap(); + let read_clone = room.read().await; + let clean_room = (*read_clone).clone(); + for sub in agent.subscribers.iter() { + let resp = Response::JoinedRoom((room_id.clone(), clean_room.clone())); + agent.link.respond(*sub, resp); + } + }); + } + Request::SendMessage((room_id, message)) => { + let client = self.matrix_client.clone().unwrap(); + spawn_local(async move { + let mut options = Options::empty(); + options.insert(Options::ENABLE_STRIKETHROUGH); + let parser = Parser::new_ext(message.as_str(), options); + + let mut formatted_message: String = + String::with_capacity(message.len() * 3 / 2); + html::push_html(&mut formatted_message, parser); + formatted_message = formatted_message.replace("<p>", "").replace("</p>", ""); + formatted_message.pop(); + + let content = if formatted_message == message { + MessageEventContent::Text(TextMessageEventContent::new_plain(message)) + } else { + MessageEventContent::Text(TextMessageEventContent { + body: message, + relates_to: None, + formatted: Some(FormattedBody{ + body: formatted_message, + format: MessageFormat::Html, + }) + }) + }; + client.room_send(&room_id, content, None).await; + }); + } + } + } + + fn disconnected(&mut self, id: HandlerId) { + self.subscribers.remove(&id); + } +} + +unsafe impl Send for MatrixAgent {} + +unsafe impl std::marker::Sync for MatrixAgent {} + +impl MatrixAgent { + async fn start_sync(&self) { + let sync = sync::Sync { + matrix_client: self.matrix_client.clone().unwrap(), + callback: self.link.callback(Msg::OnSyncResponse), + }; + sync.start_sync().await; + } + + async fn get_logged_in(&self) -> bool { + if self.matrix_client.is_none() { + return false; + } + self.matrix_client.as_ref().unwrap().logged_in().await + } + + fn login(&mut self) -> Option<Client> { + if (self.matrix_state.homeserver.is_none() + || self.matrix_state.username.is_none() + || self.matrix_state.password.is_none() + || self.matrix_client.is_some()) + && self.session.is_none() + { + for sub in self.subscribers.iter() { + let resp = Response::Error(MatrixError::MissingFields); + self.link.respond(*sub, resp); + } + None + } else if self.session.is_some() { + let homeserver = self.session.clone().unwrap().homeserver_url; + + let client_config = ClientConfig::new(); + let homeserver_url = Url::parse(&homeserver).unwrap(); + let client = Client::new_with_config(homeserver_url, client_config).unwrap(); + self.matrix_client = Some(client.clone()); + + // Also directly restore Login data + let stored_session = self.session.clone().unwrap(); + let session = Session { + access_token: stored_session.access_token, + user_id: matrix_sdk::identifiers::UserId::try_from(stored_session.user_id.as_str()) + .unwrap(), + device_id: stored_session.device_id, + }; + let client_clone = client.clone(); + spawn_local(async move { + client_clone.restore_login(session).await; + }); + + Some(client) + } else { + let homeserver = self.matrix_state.homeserver.clone().unwrap(); + + let client_config = ClientConfig::new(); + let homeserver_url = Url::parse(&homeserver).unwrap(); + let client = Client::new_with_config(homeserver_url, client_config).unwrap(); + self.matrix_client = Some(client.clone()); + + Some(client) + } + } +} diff --git a/src/app/matrix/sync.rs b/src/app/matrix/sync.rs @@ -8,7 +8,7 @@ use matrix_sdk::{ }; use yew::Callback; -use crate::app::matrix::types::get_media_download_url; +use crate::app::matrix::types::{get_media_download_url, get_video_media_download_url}; use crate::app::matrix::Response; pub struct Sync { @@ -45,7 +45,7 @@ impl Sync { // TODO handle all messages... if let RoomEvent::RoomMessage(mut event) = event { - if let MessageEventContent::Image(mut image_event) = event.content { + if let MessageEventContent::Image(mut image_event) = event.clone().content { if image_event.url.is_some() { let new_url = Some(get_media_download_url( self.matrix_client.clone(), @@ -66,6 +66,27 @@ impl Sync { } event.content = MessageEventContent::Image(image_event); } + if let MessageEventContent::Video(mut video_event) = event.content { + if video_event.url.is_some() { + let new_url = Some(get_video_media_download_url( + self.matrix_client.clone(), + video_event.url.unwrap(), + )); + video_event.url = new_url; + } + if video_event.info.is_some() { + let mut info = video_event.info.unwrap(); + if info.thumbnail_url.is_some() { + let new_url = Some(get_media_download_url( + self.matrix_client.clone(), + info.thumbnail_url.unwrap(), + )); + info.thumbnail_url = new_url; + } + video_event.info = Some(info); + } + event.content = MessageEventContent::Video(video_event); + } let resp = Response::Sync((room_id.clone(), event.clone())); self.callback.emit(resp); } diff --git a/src/app/matrix/types.rs b/src/app/matrix/types.rs @@ -13,3 +13,17 @@ pub fn get_media_download_url(client: Client, mxc_url: String) -> String { new_url.set_path(new_path.as_str()); new_url.to_string() } + +pub fn get_video_media_download_url(client: Client, mxc_url: String) -> String { + let url_parts_raw = mxc_url.replace("mxc://", ""); + let url_parts: Vec<&str> = url_parts_raw.split('/').collect(); + let server_name = (*url_parts.first().unwrap()).to_string(); + let media_id = (*url_parts.last().unwrap()).to_string(); + let new_path = format!( + "_matrix/media/r0/download/{}/{}/fix.mp4", + server_name, media_id, + ); + let mut new_url = client.homeserver().clone(); + new_url.set_path(new_path.as_str()); + new_url.to_string() +} diff --git a/src/app.rs b/src/app/mod.rs diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs @@ -64,12 +64,25 @@ impl Component for MainView { </div> } } else { - html! { - <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.as_ref().unwrap() /> - </div> + if self.state.current_room.as_ref().unwrap().is_encrypted() { + html! { + <div class="uk-flex auto-scrollable-container" style="height: 100%"> + <RoomList change_room_callback=self.link.callback(Msg::ChangeRoom)/> + <div class="container uk-flex uk-flex-column uk-width-5-6 uk-padding uk-padding-remove-bottom" style="height: 100%"> + <h1>{ self.state.current_room.as_ref().unwrap().display_name() }</h1> + <h4>{ "Daydream currently does not support encryption." }</h4> + </div> + </div> + } + } else { + html! { + <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.as_ref().unwrap() /> + </div> + } } + } } }