From a6888bbc93216a111a4afc8e95d0291b17abf3dd Mon Sep 17 00:00:00 2001 From: Ulyssa Date: Wed, 25 Jan 2023 17:54:16 -0800 Subject: [PATCH] Support displaying and editing room tags (#15) --- README.md | 2 +- src/base.rs | 29 ++-- src/commands.rs | 282 ++++++++++++++++++++++++++++++++++---- src/tests.rs | 1 + src/windows/mod.rs | 89 ++++++++++-- src/windows/room/mod.rs | 64 ++++++++- src/windows/room/space.rs | 4 +- src/worker.rs | 81 ++++------- 8 files changed, 443 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index ebc9623..6a03fbe 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ two other TUI clients and Element Web: | --------------------------------------- | :---------- | :------: | :--------------: | :-----------------: | | Room directory | ❌ ([#14]) | ❌ | ✔️ | ✔️ | | Room tag showing | ❌ ([#15]) | ✔️ | ❌ | ✔️ | -| Room tag editing | ❌ ([#15]) | ✔️ | ❌ | ✔️ | +| Room tag editing | ✔️ | ✔️ | ❌ | ✔️ | | Search joined rooms | ❌ ([#16]) | ✔️ | ❌ | ✔️ | | Room user list | ✔️ | ✔️ | ✔️ | ✔️ | | Display Room Description | ✔️ | ✔️ | ✔️ | ✔️ | diff --git a/src/base.rs b/src/base.rs index f30683e..59f6ec2 100644 --- a/src/base.rs +++ b/src/base.rs @@ -8,6 +8,7 @@ use tracing::warn; use matrix_sdk::{ encryption::verification::SasVerification, + room::Joined, ruma::{ events::room::message::{ OriginalRoomMessageEvent, @@ -16,6 +17,7 @@ use matrix_sdk::{ RoomMessageEvent, RoomMessageEventContent, }, + events::tag::{TagName, Tags}, EventId, OwnedEventId, OwnedRoomId, @@ -94,9 +96,10 @@ pub enum MessageAction { } #[derive(Clone, Debug, Eq, PartialEq)] -pub enum SetRoomField { - Name(String), - Topic(String), +pub enum RoomField { + Name, + Tag(TagName), + Topic, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -105,13 +108,8 @@ pub enum RoomAction { InviteReject, InviteSend(OwnedUserId), Members(Box>), - Set(SetRoomField), -} - -impl From for RoomAction { - fn from(act: SetRoomField) -> Self { - RoomAction::Set(act) - } + Set(RoomField, String), + Unset(RoomField), } #[derive(Clone, Debug, Eq, PartialEq)] @@ -194,6 +192,12 @@ impl ApplicationAction for IambAction { } } +impl From for ProgramAction { + fn from(act: RoomAction) -> Self { + IambAction::from(act).into() + } +} + impl From for ProgramAction { fn from(act: IambAction) -> Self { Action::Application(act) @@ -277,6 +281,7 @@ pub enum RoomFetchStatus { #[derive(Default)] pub struct RoomInfo { pub name: Option, + pub tags: Option, pub keys: HashMap, pub messages: Messages, @@ -437,6 +442,10 @@ impl ChatStore { } } + pub fn get_joined_room(&self, room_id: &RoomId) -> Option { + self.worker.client.get_joined_room(room_id) + } + pub fn get_room_title(&self, room_id: &RoomId) -> String { self.rooms .get(room_id) diff --git a/src/commands.rs b/src/commands.rs index fde8f7f..f0373cc 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; -use matrix_sdk::ruma::OwnedUserId; +use matrix_sdk::ruma::{events::tag::TagName, OwnedUserId}; use modalkit::{ editing::base::OpenTarget, @@ -17,14 +17,38 @@ use crate::base::{ ProgramCommands, ProgramContext, RoomAction, + RoomField, SendAction, - SetRoomField, VerifyAction, }; type ProgContext = CommandContext; type ProgResult = CommandResult; +/// Convert strings the user types into a tag name. +fn tag_name(name: String) -> Result { + let tag = match name.as_str() { + "fav" | "favorite" | "favourite" | "m.favourite" => TagName::Favorite, + "low" | "lowpriority" | "low_priority" | "low-priority" | "m.lowpriority" => { + TagName::LowPriority + }, + "servernotice" | "server_notice" | "server-notice" | "m.server_notice" => { + TagName::ServerNotice + }, + _ => { + if let Ok(tag) = name.parse() { + TagName::User(tag) + } else { + let msg = format!("Invalid user tag name: {}", name); + + return Err(CommandError::Error(msg)); + } + }, + }; + + Ok(tag) +} + fn iamb_invite(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { let args = desc.arg.strings()?; @@ -225,22 +249,46 @@ fn iamb_join(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { return Ok(step); } -fn iamb_set(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { +fn iamb_room(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { let mut args = desc.arg.strings()?; - if args.len() != 2 { + if args.len() < 2 { return Result::Err(CommandError::InvalidArgument); } let field = args.remove(0); - let value = args.remove(0); + let action = args.remove(0); - let act: IambAction = match field.as_str() { - "room.name" => RoomAction::Set(SetRoomField::Name(value)).into(), - "room.topic" => RoomAction::Set(SetRoomField::Topic(value)).into(), - _ => { - return Result::Err(CommandError::InvalidArgument); - }, + if args.len() > 1 { + return Result::Err(CommandError::InvalidArgument); + } + + let act: IambAction = match (field.as_str(), action.as_str(), args.pop()) { + // :room name set + ("name", "set", Some(s)) => RoomAction::Set(RoomField::Name, s).into(), + ("name", "set", None) => return Result::Err(CommandError::InvalidArgument), + + // :room name unset + ("name", "unset", None) => RoomAction::Unset(RoomField::Name).into(), + ("name", "unset", Some(_)) => return Result::Err(CommandError::InvalidArgument), + + // :room topic set + ("topic", "set", Some(s)) => RoomAction::Set(RoomField::Topic, s).into(), + ("topic", "set", None) => return Result::Err(CommandError::InvalidArgument), + + // :room topic unset + ("topic", "unset", None) => RoomAction::Unset(RoomField::Topic).into(), + ("topic", "unset", Some(_)) => return Result::Err(CommandError::InvalidArgument), + + // :room tag set + ("tag", "set", Some(s)) => RoomAction::Set(RoomField::Tag(tag_name(s)?), "".into()).into(), + ("tag", "set", None) => return Result::Err(CommandError::InvalidArgument), + + // :room tag unset + ("tag", "unset", Some(s)) => RoomAction::Unset(RoomField::Tag(tag_name(s)?)).into(), + ("tag", "unset", None) => return Result::Err(CommandError::InvalidArgument), + + _ => return Result::Err(CommandError::InvalidArgument), }; let step = CommandStep::Continue(act.into(), ctx.context.take()); @@ -287,7 +335,7 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) { cmds.add_command(ProgramCommand { names: vec!["redact".into()], f: iamb_redact }); cmds.add_command(ProgramCommand { names: vec!["reply".into()], f: iamb_reply }); cmds.add_command(ProgramCommand { names: vec!["rooms".into()], f: iamb_rooms }); - cmds.add_command(ProgramCommand { names: vec!["set".into()], f: iamb_set }); + cmds.add_command(ProgramCommand { names: vec!["room".into()], f: iamb_room }); cmds.add_command(ProgramCommand { names: vec!["spaces".into()], f: iamb_spaces }); cmds.add_command(ProgramCommand { names: vec!["upload".into()], f: iamb_upload }); cmds.add_command(ProgramCommand { names: vec!["verify".into()], f: iamb_verify }); @@ -376,47 +424,227 @@ mod tests { } #[test] - fn test_cmd_set() { + fn test_cmd_room_invalid() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds.input_cmd("room", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("room foo", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("room set topic", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + } + + #[test] + fn test_cmd_room_topic_set() { let mut cmds = setup_commands(); let ctx = ProgramContext::default(); let res = cmds - .input_cmd("set room.topic \"Lots of fun discussion!\"", ctx.clone()) + .input_cmd("room topic set \"Lots of fun discussion!\"", ctx.clone()) .unwrap(); - let act = IambAction::Room(SetRoomField::Topic("Lots of fun discussion!".into()).into()); + let act = RoomAction::Set(RoomField::Topic, "Lots of fun discussion!".into()); assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds - .input_cmd("set room.topic The\\ Discussion\\ Room", ctx.clone()) + .input_cmd("room topic set The\\ Discussion\\ Room", ctx.clone()) .unwrap(); - let act = IambAction::Room(SetRoomField::Topic("The Discussion Room".into()).into()); + let act = RoomAction::Set(RoomField::Topic, "The Discussion Room".into()); assert_eq!(res, vec![(act.into(), ctx.clone())]); - let res = cmds.input_cmd("set room.topic Development", ctx.clone()).unwrap(); - let act = IambAction::Room(SetRoomField::Topic("Development".into()).into()); + let res = cmds.input_cmd("room topic set Development", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Topic, "Development".into()); assert_eq!(res, vec![(act.into(), ctx.clone())]); - let res = cmds.input_cmd("set room.name Development", ctx.clone()).unwrap(); - let act = IambAction::Room(SetRoomField::Name("Development".into()).into()); + let res = cmds.input_cmd("room topic", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("room topic set", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("room topic set A B C", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + } + + #[test] + fn test_cmd_room_name_invalid() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds.input_cmd("room name", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("room name foo", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + } + + #[test] + fn test_cmd_room_name_set() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds.input_cmd("room name set Development", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Name, "Development".into()); assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds - .input_cmd("set room.name \"Application Development\"", ctx.clone()) + .input_cmd("room name set \"Application Development\"", ctx.clone()) .unwrap(); - let act = IambAction::Room(SetRoomField::Name("Application Development".into()).into()); + let act = RoomAction::Set(RoomField::Name, "Application Development".into()); assert_eq!(res, vec![(act.into(), ctx.clone())]); - let res = cmds.input_cmd("set", ctx.clone()); + let res = cmds.input_cmd("room name set", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + } + + #[test] + fn test_cmd_room_name_unset() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds.input_cmd("room name unset", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Name); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room name unset foo", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + } + + #[test] + fn test_cmd_room_tag_set() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds.input_cmd("room tag set favourite", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::Favorite), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set favorite", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::Favorite), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set fav", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::Favorite), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set low_priority", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::LowPriority), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set low-priority", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::LowPriority), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set low", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::LowPriority), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set servernotice", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::ServerNotice), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set server_notice", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::ServerNotice), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set server_notice", ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::Tag(TagName::ServerNotice), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set u.custom-tag", ctx.clone()).unwrap(); + let act = RoomAction::Set( + RoomField::Tag(TagName::User("u.custom-tag".parse().unwrap())), + "".into(), + ); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag set u.irc", ctx.clone()).unwrap(); + let act = + RoomAction::Set(RoomField::Tag(TagName::User("u.irc".parse().unwrap())), "".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag", ctx.clone()); assert_eq!(res, Err(CommandError::InvalidArgument)); - let res = cmds.input_cmd("set room.name", ctx.clone()); + let res = cmds.input_cmd("room tag set", ctx.clone()); assert_eq!(res, Err(CommandError::InvalidArgument)); - let res = cmds.input_cmd("set room.topic", ctx.clone()); + let res = cmds.input_cmd("room tag set unknown", ctx.clone()); + assert_eq!(res, Err(CommandError::Error("Invalid user tag name: unknown".into()))); + + let res = cmds.input_cmd("room tag set needs-leading-u-dot", ctx.clone()); + assert_eq!( + res, + Err(CommandError::Error("Invalid user tag name: needs-leading-u-dot".into())) + ); + } + + #[test] + fn test_cmd_room_tag_unset() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds.input_cmd("room tag unset favourite", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::Favorite)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset favorite", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::Favorite)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset fav", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::Favorite)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset low_priority", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::LowPriority)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset low-priority", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::LowPriority)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset low", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::LowPriority)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset servernotice", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::ServerNotice)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset server_notice", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::ServerNotice)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset server_notice", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::ServerNotice)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset u.custom-tag", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::User("u.custom-tag".parse().unwrap()))); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag unset u.irc", ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::Tag(TagName::User("u.irc".parse().unwrap()))); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("room tag", ctx.clone()); assert_eq!(res, Err(CommandError::InvalidArgument)); - let res = cmds.input_cmd("set room.topic A B C", ctx.clone()); + let res = cmds.input_cmd("room tag set", ctx.clone()); assert_eq!(res, Err(CommandError::InvalidArgument)); + + let res = cmds.input_cmd("room tag unset unknown", ctx.clone()); + assert_eq!(res, Err(CommandError::Error("Invalid user tag name: unknown".into()))); + + let res = cmds.input_cmd("room tag unset needs-leading-u-dot", ctx.clone()); + assert_eq!( + res, + Err(CommandError::Error("Invalid user tag name: needs-leading-u-dot".into())) + ); } #[test] diff --git a/src/tests.rs b/src/tests.rs index ecacbc5..7ae7e31 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -141,6 +141,7 @@ pub fn mock_room() -> RoomInfo { keys: mock_keys(), messages: mock_messages(), + tags: None, fetch_id: RoomFetchStatus::NotStarted, fetch_last: None, diff --git a/src/windows/mod.rs b/src/windows/mod.rs index c0c79c4..b2f78f5 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -4,7 +4,12 @@ use std::collections::hash_map::Entry; use matrix_sdk::{ encryption::verification::{format_emojis, SasVerification}, room::{Room as MatrixRoom, RoomMember}, - ruma::{events::room::member::MembershipState, OwnedRoomId, RoomId}, + ruma::{ + events::room::member::MembershipState, + events::tag::{TagName, Tags}, + OwnedRoomId, + RoomId, + }, DisplayName, }; @@ -346,7 +351,7 @@ impl WindowOps for IambWindow { let joined = store.application.worker.active_rooms(); let mut items = joined .into_iter() - .map(|(room, name)| RoomItem::new(room, name, store)) + .map(|(room, name, tags)| RoomItem::new(room, name, tags, store)) .collect::>(); items.sort(); @@ -471,8 +476,8 @@ impl Window for IambWindow { fn open(id: IambId, store: &mut ProgramStore) -> IambResult { match id { IambId::Room(room_id) => { - let (room, name) = store.application.worker.get_room(room_id)?; - let room = RoomState::new(room, name, store); + let (room, name, tags) = store.application.worker.get_room(room_id)?; + let room = RoomState::new(room, name, tags, store); return Ok(room.into()); }, @@ -519,8 +524,8 @@ impl Window for IambWindow { let room_id = worker.join_room(v.key().to_string())?; v.insert(room_id.clone()); - let (room, name) = store.application.worker.get_room(room_id)?; - let room = RoomState::new(room, name, store); + let (room, name, tags) = store.application.worker.get_room(room_id)?; + let room = RoomState::new(room, name, tags, store); Ok(room.into()) }, @@ -547,16 +552,24 @@ impl Window for IambWindow { #[derive(Clone)] pub struct RoomItem { room: MatrixRoom, + tags: Option, name: String, } impl RoomItem { - fn new(room: MatrixRoom, name: DisplayName, store: &mut ProgramStore) -> Self { + fn new( + room: MatrixRoom, + name: DisplayName, + tags: Option, + store: &mut ProgramStore, + ) -> Self { let name = name.to_string(); - store.application.set_room_name(room.room_id(), name.as_str()); + let info = store.application.get_room_info(room.room_id().to_owned()); + info.name = name.clone().into(); + info.tags = tags.clone(); - RoomItem { room, name } + RoomItem { room, tags, name } } } @@ -570,7 +583,29 @@ impl Eq for RoomItem {} impl Ord for RoomItem { fn cmp(&self, other: &Self) -> Ordering { - room_cmp(&self.room, &other.room) + let (fava, lowa) = self + .tags + .as_ref() + .map(|tags| { + (tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority)) + }) + .unwrap_or((false, false)); + + let (favb, lowb) = other + .tags + .as_ref() + .map(|tags| { + (tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority)) + }) + .unwrap_or((false, false)); + + // If self has Favorite and other doesn't, it should sort earlier in room list. + let cmpf = favb.cmp(&fava); + + // If self has LowPriority and other doesn't, it should sort later in room list. + let cmpl = lowa.cmp(&lowb); + + cmpl.then(cmpf).then_with(|| room_cmp(&self.room, &other.room)) } } @@ -588,7 +623,39 @@ impl ToString for RoomItem { impl ListItem for RoomItem { fn show(&self, selected: bool, _: &ViewportContext, _: &mut ProgramStore) -> Text { - selected_text(self.name.as_str(), selected) + if let Some(tags) = &self.tags { + let style = selected_style(selected); + let mut spans = vec![Span::styled(self.name.as_str(), style)]; + + if tags.is_empty() { + return Text::from(Spans(spans)); + } + + spans.push(Span::styled(" (", style)); + + for (i, tag) in tags.keys().enumerate() { + if i > 0 { + spans.push(Span::styled(", ", style)); + } + + match tag { + TagName::Favorite => spans.push(Span::styled("Favorite", style)), + TagName::LowPriority => spans.push(Span::styled("Low Priority", style)), + TagName::ServerNotice => spans.push(Span::styled("Server Notice", style)), + TagName::User(tag) => { + spans.push(Span::styled("User Tag: ", style)); + spans.push(Span::styled(tag.as_ref(), style)); + }, + tag => spans.push(Span::styled(format!("{:?}", tag), style)), + } + } + + spans.push(Span::styled(")", style)); + + Text::from(Spans(spans)) + } else { + selected_text(self.name.as_str(), selected) + } } fn get_word(&self) -> Option { diff --git a/src/windows/room/mod.rs b/src/windows/room/mod.rs index 2188085..6becfab 100644 --- a/src/windows/room/mod.rs +++ b/src/windows/room/mod.rs @@ -1,6 +1,12 @@ use matrix_sdk::{ room::{Invited, Room as MatrixRoom}, - ruma::RoomId, + ruma::{ + events::{ + room::{name::RoomNameEventContent, topic::RoomTopicEventContent}, + tag::{TagInfo, Tags}, + }, + RoomId, + }, DisplayName, }; @@ -23,6 +29,7 @@ use modalkit::{ PromptAction, Promptable, Scrollable, + UIError, }, editing::base::{ Axis, @@ -48,6 +55,7 @@ use crate::base::{ ProgramContext, ProgramStore, RoomAction, + RoomField, SendAction, }; @@ -85,10 +93,16 @@ impl From for RoomState { } impl RoomState { - pub fn new(room: MatrixRoom, name: DisplayName, store: &mut ProgramStore) -> Self { + pub fn new( + room: MatrixRoom, + name: DisplayName, + tags: Option, + store: &mut ProgramStore, + ) -> Self { let room_id = room.room_id().to_owned(); let info = store.application.get_room_info(room_id); info.name = name.to_string().into(); + info.tags = tags; if room.is_space() { SpaceState::new(room).into() @@ -207,8 +221,50 @@ impl RoomState { Ok(vec![(act, cmd.context.take())]) }, - RoomAction::Set(field) => { - store.application.worker.set_room(self.id().to_owned(), field)?; + RoomAction::Set(field, value) => { + let room = store + .application + .get_joined_room(self.id()) + .ok_or(UIError::Application(IambError::NotJoined))?; + + match field { + RoomField::Name => { + let ev = RoomNameEventContent::new(value.into()); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + }, + RoomField::Tag(tag) => { + let mut info = TagInfo::new(); + info.order = Some(1.0); + + let _ = room.set_tag(tag, info).await.map_err(IambError::from)?; + }, + RoomField::Topic => { + let ev = RoomTopicEventContent::new(value); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + }, + } + + Ok(vec![]) + }, + RoomAction::Unset(field) => { + let room = store + .application + .get_joined_room(self.id()) + .ok_or(UIError::Application(IambError::NotJoined))?; + + match field { + RoomField::Name => { + let ev = RoomNameEventContent::new(None); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + }, + RoomField::Tag(tag) => { + let _ = room.remove_tag(tag).await.map_err(IambError::from)?; + }, + RoomField::Topic => { + let ev = RoomTopicEventContent::new("".into()); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + }, + } Ok(vec![]) }, diff --git a/src/windows/room/space.rs b/src/windows/room/space.rs index d910c76..a02a8ef 100644 --- a/src/windows/room/space.rs +++ b/src/windows/room/space.rs @@ -104,10 +104,10 @@ impl<'a> StatefulWidget for Space<'a> { let items = members .into_iter() .filter_map(|id| { - let (room, name) = self.store.application.worker.get_room(id.clone()).ok()?; + let (room, name, tags) = self.store.application.worker.get_room(id.clone()).ok()?; if id != state.room_id { - Some(RoomItem::new(room, name, self.store)) + Some(RoomItem::new(room, name, tags, self.store)) } else { None } diff --git a/src/worker.rs b/src/worker.rs index 8eccbdc..1fc0931 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -36,8 +36,8 @@ use matrix_sdk::{ message::{MessageType, RoomMessageEventContent}, name::RoomNameEventContent, redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent}, - topic::RoomTopicEventContent, }, + tag::Tags, typing::SyncTypingEvent, AnyMessageLikeEvent, AnyTimelineEvent, @@ -57,7 +57,7 @@ use matrix_sdk::{ use modalkit::editing::action::{EditInfo, InfoMessage, UIError}; use crate::{ - base::{AsyncProgramStore, IambError, IambResult, SetRoomField, VerifyAction}, + base::{AsyncProgramStore, IambError, IambResult, VerifyAction}, message::MessageFetchResult, ApplicationSettings, }; @@ -100,18 +100,17 @@ fn oneshot() -> (ClientReply, ClientResponse) { } pub enum WorkerTask { - ActiveRooms(ClientReply>), + ActiveRooms(ClientReply)>>), DirectMessages(ClientReply>), Init(AsyncProgramStore, ClientReply<()>), LoadOlder(OwnedRoomId, Option, u32, ClientReply), Login(LoginStyle, ClientReply>), GetInviter(Invited, ClientReply>>), - GetRoom(OwnedRoomId, ClientReply>), + GetRoom(OwnedRoomId, ClientReply)>>), JoinRoom(String, ClientReply>), Members(OwnedRoomId, ClientReply>>), SpaceMembers(OwnedRoomId, ClientReply>>), Spaces(ClientReply>), - SetRoom(OwnedRoomId, SetRoomField, ClientReply>), TypingNotice(OwnedRoomId), Verify(VerifyAction, SasVerification, ClientReply>), VerifyRequest(OwnedUserId, ClientReply>), @@ -178,13 +177,6 @@ impl Debug for WorkerTask { WorkerTask::Spaces(_) => { f.debug_tuple("WorkerTask::Spaces").field(&format_args!("_")).finish() }, - WorkerTask::SetRoom(room_id, field, _) => { - f.debug_tuple("WorkerTask::SetRoom") - .field(room_id) - .field(field) - .field(&format_args!("_")) - .finish() - }, WorkerTask::TypingNotice(room_id) => { f.debug_tuple("WorkerTask::TypingNotice").field(room_id).finish() }, @@ -259,7 +251,10 @@ impl Requester { return response.recv(); } - pub fn get_room(&self, room_id: OwnedRoomId) -> IambResult<(MatrixRoom, DisplayName)> { + pub fn get_room( + &self, + room_id: OwnedRoomId, + ) -> IambResult<(MatrixRoom, DisplayName, Option)> { let (reply, response) = oneshot(); self.tx.send(WorkerTask::GetRoom(room_id, reply)).unwrap(); @@ -275,7 +270,7 @@ impl Requester { return response.recv(); } - pub fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName)> { + pub fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName, Option)> { let (reply, response) = oneshot(); self.tx.send(WorkerTask::ActiveRooms(reply)).unwrap(); @@ -299,14 +294,6 @@ impl Requester { return response.recv(); } - pub fn set_room(&self, room_id: OwnedRoomId, ev: SetRoomField) -> IambResult<()> { - let (reply, response) = oneshot(); - - self.tx.send(WorkerTask::SetRoom(room_id, ev, reply)).unwrap(); - - return response.recv(); - } - pub fn spaces(&self) -> Vec<(MatrixRoom, DisplayName)> { let (reply, response) = oneshot(); @@ -444,10 +431,6 @@ impl ClientWorker { assert!(self.initialized); reply.send(self.members(room_id).await); }, - WorkerTask::SetRoom(room_id, field, reply) => { - assert!(self.initialized); - reply.send(self.set_room(room_id, field).await); - }, WorkerTask::SpaceMembers(space, reply) => { assert!(self.initialized); reply.send(self.space_members(space).await); @@ -721,10 +704,15 @@ impl ClientWorker { Ok(Some(InfoMessage::from("Successfully logged in!"))) } - async fn direct_message(&mut self, user: OwnedUserId) -> IambResult<(MatrixRoom, DisplayName)> { + async fn direct_message( + &mut self, + user: OwnedUserId, + ) -> IambResult<(MatrixRoom, DisplayName, Option)> { for (room, name) in self.direct_messages().await { if room.get_member(user.as_ref()).await.map_err(IambError::from)?.is_some() { - return Ok((room, name)); + let tags = room.tags().await.map_err(IambError::from)?; + + return Ok((room, name, tags)); } } @@ -758,11 +746,15 @@ impl ClientWorker { Ok(details.inviter) } - async fn get_room(&mut self, room_id: OwnedRoomId) -> IambResult<(MatrixRoom, DisplayName)> { + async fn get_room( + &mut self, + room_id: OwnedRoomId, + ) -> IambResult<(MatrixRoom, DisplayName, Option)> { if let Some(room) = self.client.get_room(&room_id) { let name = room.display_name().await.map_err(IambError::from)?; + let tags = room.tags().await.map_err(IambError::from)?; - Ok((room, name)) + Ok((room, name, tags)) } else { Err(IambError::UnknownRoom(room_id).into()) } @@ -817,7 +809,7 @@ impl ClientWorker { return rooms; } - async fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName)> { + async fn active_rooms(&self) -> Vec<(MatrixRoom, DisplayName, Option)> { let mut rooms = vec![]; for room in self.client.invited_rooms().into_iter() { @@ -826,8 +818,9 @@ impl ClientWorker { } let name = room.display_name().await.unwrap_or(DisplayName::Empty); + let tags = room.tags().await.unwrap_or_default(); - rooms.push((room.into(), name)); + rooms.push((room.into(), name, tags)); } for room in self.client.joined_rooms().into_iter() { @@ -836,8 +829,9 @@ impl ClientWorker { } let name = room.display_name().await.unwrap_or(DisplayName::Empty); + let tags = room.tags().await.unwrap_or_default(); - rooms.push((room.into(), name)); + rooms.push((room.into(), name, tags)); } return rooms; @@ -886,27 +880,6 @@ impl ClientWorker { } } - async fn set_room(&mut self, room_id: OwnedRoomId, field: SetRoomField) -> IambResult<()> { - let room = if let Some(r) = self.client.get_joined_room(&room_id) { - r - } else { - return Err(IambError::UnknownRoom(room_id).into()); - }; - - match field { - SetRoomField::Name(name) => { - let ev = RoomNameEventContent::new(name.into()); - let _ = room.send_state_event(ev).await.map_err(IambError::from)?; - }, - SetRoomField::Topic(topic) => { - let ev = RoomTopicEventContent::new(topic); - let _ = room.send_state_event(ev).await.map_err(IambError::from)?; - }, - } - - Ok(()) - } - async fn space_members(&mut self, space: OwnedRoomId) -> IambResult> { let mut req = SpaceHierarchyRequest::new(&space); req.limit = Some(1000u32.into());