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 }