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

mod.rs (7828B)


      1 use std::{collections::HashMap, rc::Rc};
      2 
      3 use log::*;
      4 use matrix_sdk::{identifiers::RoomId, Room};
      5 use serde::{Deserialize, Serialize};
      6 use wasm_bindgen::JsCast;
      7 use web_sys::HtmlElement;
      8 use yew::prelude::*;
      9 use yew::utils::document;
     10 use yew::{Bridge, Bridged, Component, ComponentLink, Html};
     11 use yewtil::NeqAssign;
     12 
     13 use tr::tr;
     14 
     15 use crate::app::components::raw_html::RawHTML;
     16 use crate::app::components::room_list::item::RoomItem;
     17 use crate::app::matrix::{MatrixAgent, Request, Response};
     18 
     19 mod item;
     20 
     21 pub struct RoomList {
     22     link: ComponentLink<Self>,
     23     state: State,
     24     matrix_agent: Box<dyn Bridge<MatrixAgent>>,
     25     props: Props,
     26 }
     27 
     28 #[allow(clippy::large_enum_variant)]
     29 pub enum Msg {
     30     NewMessage(Response),
     31     ChangeRoom(RoomId),
     32     SetFilter(String),
     33     ToggleTheme,
     34 }
     35 
     36 #[derive(Serialize, Deserialize, Default)]
     37 pub struct State {
     38     rooms: HashMap<RoomId, Rc<Room>>,
     39     current_room: Option<RoomId>,
     40     loading: bool,
     41     search_query: Option<String>,
     42     dark_theme: bool,
     43 }
     44 
     45 #[derive(Clone, PartialEq, Properties)]
     46 pub struct Props {
     47     #[prop_or_default]
     48     pub change_room_callback: Callback<Rc<Room>>,
     49 }
     50 
     51 impl Component for RoomList {
     52     type Message = Msg;
     53     type Properties = Props;
     54 
     55     fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
     56         let matrix_callback = link.callback(Msg::NewMessage);
     57         let matrix_agent = MatrixAgent::bridge(matrix_callback);
     58         let state = State {
     59             rooms: Default::default(),
     60             current_room: None,
     61             loading: true,
     62             search_query: None,
     63             dark_theme: false,
     64         };
     65 
     66         RoomList {
     67             props,
     68             link,
     69             matrix_agent,
     70             state,
     71         }
     72     }
     73 
     74     fn update(&mut self, msg: Self::Message) -> bool {
     75         match msg {
     76             Msg::NewMessage(response) => match response {
     77                 // Handle new rooms from sync
     78                 Response::JoinedRoomSync(room_id) => {
     79                     info!("Got JoinedRoomSync");
     80                     if !(self.state.rooms.contains_key(&room_id)) {
     81                         self.matrix_agent.send(Request::GetJoinedRoom(room_id));
     82                     }
     83                     false
     84                 }
     85                 Response::JoinedRoom((room_id, room)) => {
     86                     info!("Got JoinedRoom");
     87                     self.state.rooms.insert(room_id, Rc::new(room));
     88                     if self.state.loading {
     89                         self.state.loading = false;
     90                     }
     91                     true
     92                 }
     93                 _ => false,
     94             },
     95             Msg::ChangeRoom(room_id) => {
     96                 if self.state.current_room.is_some()
     97                     && self.state.current_room.as_ref().unwrap() == &room_id
     98                 {
     99                     return false;
    100                 }
    101 
    102                 let room = self.state.rooms[&room_id].clone();
    103                 self.props.change_room_callback.emit(room);
    104                 self.state.current_room = Some(room_id);
    105                 true
    106             }
    107             Msg::SetFilter(query) => {
    108                 self.state.search_query = Some(query);
    109                 true
    110             }
    111             Msg::ToggleTheme => {
    112                 self.state.dark_theme = !self.state.dark_theme;
    113                 let theme = if self.state.dark_theme {
    114                     "dark"
    115                 } else {
    116                     "light"
    117                 };
    118                 document()
    119                     .document_element()
    120                     .unwrap()
    121                     .dyn_into::<HtmlElement>()
    122                     .unwrap()
    123                     .dataset()
    124                     .set("theme", theme)
    125                     .unwrap();
    126                 true
    127             }
    128         }
    129     }
    130 
    131     fn change(&mut self, props: Self::Properties) -> ShouldRender {
    132         self.props.neq_assign(props)
    133     }
    134 
    135     //noinspection RsTypeCheck
    136     fn view(&self) -> Html {
    137         if self.state.loading {
    138             html! {
    139                 <div class="container">
    140                     <div class="uk-position-center uk-padding">
    141                         <RawHTML inner_html=include_str!("../../svgs/loading_animation.svg")/>
    142                     </div>
    143                 </div>
    144             }
    145         } else {
    146             let rooms: Html = match self.state.search_query.as_deref() {
    147                 None | Some("") => self
    148                     .state
    149                     .rooms
    150                     .iter()
    151                     .map(|(_, room)| self.get_room(room))
    152                     .collect(),
    153                 _ => self
    154                     .state
    155                     .rooms
    156                     .iter()
    157                     .filter(|(_, room)| {
    158                         room.display_name()
    159                             .to_lowercase()
    160                             .contains(&self.state.search_query.as_ref().unwrap().to_lowercase())
    161                     })
    162                     .map(|(_, room)| self.get_room(room))
    163                     .collect(),
    164             };
    165 
    166             html! {
    167                 <div class="roomlist" style="height: 100%">
    168                     <div class="top-bar">
    169                         <div class="userdata">
    170                         </div>
    171                         <div class="search">
    172                             <div>
    173                                 <span class="material-icons">{"search"}</span>
    174                                 <input
    175                                     class="search-input"
    176                                     type="search"
    177                                     placeholder={
    178                                         tr!(
    179                                             // Placeholder text for the roomlist filtering
    180                                             "Filter Rooms..."
    181                                         )
    182                                     }
    183                                     value=self.state.search_query.as_deref().unwrap_or("")
    184                                     oninput=self.link.callback(|e: InputData| Msg::SetFilter(e.value)) />
    185                             </div>
    186                         </div>
    187                     </div>
    188                     <div class="scrollable list">{rooms}</div>
    189                     <div class="bottom-bar">
    190                         <div class="toggleWrapper">
    191                             <div>
    192                                 <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})/>
    193                                 <label for="dn" class="toggle">
    194                                     <span class="toggle__handler">
    195                                         <span class="crater crater--1"></span>
    196                                         <span class="crater crater--2"></span>
    197                                         <span class="crater crater--3"></span>
    198                                     </span>
    199                                     <span class="star star--1"></span>
    200                                     <span class="star star--2"></span>
    201                                     <span class="star star--3"></span>
    202                                     <span class="star star--4"></span>
    203                                     <span class="star star--5"></span>
    204                                     //<span class="star star--6"></span>
    205                                 </label>
    206                             </div>
    207                         </div>
    208                     </div>
    209                 </div>
    210             }
    211         }
    212     }
    213 }
    214 
    215 impl RoomList {
    216     fn get_room(&self, matrix_room: &Rc<Room>) -> Html {
    217         let room = matrix_room.clone();
    218         html! {
    219             <RoomItem change_room_callback=self.link.callback(Msg::ChangeRoom) room=room.clone() />
    220         }
    221     }
    222 }