Support displaying and editing room descriptions (#12)

This commit is contained in:
Ulyssa 2023-01-05 18:12:25 -08:00
parent 8ed037afca
commit 38f4795886
No known key found for this signature in database
GPG key ID: 1B3965A3D18B9B64
13 changed files with 286 additions and 55 deletions

4
Cargo.lock generated
View file

@ -1600,9 +1600,9 @@ dependencies = [
[[package]] [[package]]
name = "modalkit" name = "modalkit"
version = "0.0.8" version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4e400066e546471efee517b7e5e3ca5af2c04014e76289aecc7af621011bba" checksum = "28a676fc7ab6a9fd329ff82d9d291370aafcf904ac3ff9f72397f64529cb1b2d"
dependencies = [ dependencies = [
"anymap2", "anymap2",
"bitflags", "bitflags",

View file

@ -19,7 +19,7 @@ dirs = "4.0.0"
futures = "0.3.21" futures = "0.3.21"
gethostname = "0.4.1" gethostname = "0.4.1"
matrix-sdk = {version = "0.6", default-features = false, features = ["e2e-encryption", "sled", "rustls-tls"]} matrix-sdk = {version = "0.6", default-features = false, features = ["e2e-encryption", "sled", "rustls-tls"]}
modalkit = "0.0.8" modalkit = "0.0.9"
regex = "^1.5" regex = "^1.5"
rpassword = "^7.2" rpassword = "^7.2"
serde = "^1.0" serde = "^1.0"

View file

@ -43,8 +43,8 @@ two other TUI clients and Element Web:
| Room tag editing | :x: ([#15]) | :heavy_check_mark: | :x: | :heavy_check_mark: | | Room tag editing | :x: ([#15]) | :heavy_check_mark: | :x: | :heavy_check_mark: |
| Search joined rooms | :x: ([#16]) | :heavy_check_mark: | :x: | :heavy_check_mark: | | Search joined rooms | :x: ([#16]) | :heavy_check_mark: | :x: | :heavy_check_mark: |
| Room user list | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Room user list | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Display Room Description | :x: ([#12]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Display Room Description | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Edit Room Description | :x: ([#12]) | :x: | :heavy_check_mark: | :heavy_check_mark: | | Edit Room Description | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: |
| Highlights | :x: ([#8]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Highlights | :x: ([#8]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Pushrules | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | | Pushrules | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: |
| Send read markers | :x: ([#11]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Send read markers | :x: ([#11]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |

View file

@ -59,9 +59,22 @@ pub enum VerifyAction {
Mismatch, Mismatch,
} }
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SetRoomField {
Name(String),
Topic(String),
}
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum RoomAction { pub enum RoomAction {
Members(Box<CommandContext<ProgramContext>>), Members(Box<CommandContext<ProgramContext>>),
Set(SetRoomField),
}
impl From<SetRoomField> for RoomAction {
fn from(act: SetRoomField) -> Self {
RoomAction::Set(act)
}
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -73,6 +86,12 @@ pub enum IambAction {
ToggleScrollbackFocus, ToggleScrollbackFocus,
} }
impl From<RoomAction> for IambAction {
fn from(act: RoomAction) -> Self {
IambAction::Room(act)
}
}
impl ApplicationAction for IambAction { impl ApplicationAction for IambAction {
fn is_edit_sequence<C: EditContext>(&self, _: &C) -> SequenceStatus { fn is_edit_sequence<C: EditContext>(&self, _: &C) -> SequenceStatus {
match self { match self {

View file

@ -12,6 +12,7 @@ use crate::base::{
ProgramCommands, ProgramCommands,
ProgramContext, ProgramContext,
RoomAction, RoomAction,
SetRoomField,
VerifyAction, VerifyAction,
}; };
@ -125,11 +126,35 @@ fn iamb_join(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
return Ok(step); return Ok(step);
} }
fn iamb_set(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
let mut args = desc.arg.strings()?;
if args.len() != 2 {
return Result::Err(CommandError::InvalidArgument);
}
let field = args.remove(0);
let value = 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);
},
};
let step = CommandStep::Continue(act.into(), ctx.context.take());
return Ok(step);
}
fn add_iamb_commands(cmds: &mut ProgramCommands) { fn add_iamb_commands(cmds: &mut ProgramCommands) {
cmds.add_command(ProgramCommand { names: vec!["dms".into()], f: iamb_dms }); cmds.add_command(ProgramCommand { names: vec!["dms".into()], f: iamb_dms });
cmds.add_command(ProgramCommand { names: vec!["join".into()], f: iamb_join }); cmds.add_command(ProgramCommand { names: vec!["join".into()], f: iamb_join });
cmds.add_command(ProgramCommand { names: vec!["members".into()], f: iamb_members }); cmds.add_command(ProgramCommand { names: vec!["members".into()], f: iamb_members });
cmds.add_command(ProgramCommand { names: vec!["rooms".into()], f: iamb_rooms }); 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!["spaces".into()], f: iamb_spaces }); cmds.add_command(ProgramCommand { names: vec!["spaces".into()], f: iamb_spaces });
cmds.add_command(ProgramCommand { names: vec!["verify".into()], f: iamb_verify }); cmds.add_command(ProgramCommand { names: vec!["verify".into()], f: iamb_verify });
cmds.add_command(ProgramCommand { names: vec!["welcome".into()], f: iamb_welcome }); cmds.add_command(ProgramCommand { names: vec!["welcome".into()], f: iamb_welcome });
@ -214,4 +239,48 @@ mod tests {
let res = cmds.input_cmd("join foo bar", ctx.clone()); let res = cmds.input_cmd("join foo bar", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument)); assert_eq!(res, Err(CommandError::InvalidArgument));
} }
#[test]
fn test_cmd_set() {
let mut cmds = setup_commands();
let ctx = ProgramContext::default();
let res = cmds
.input_cmd("set room.topic \"Lots of fun discussion!\"", ctx.clone())
.unwrap();
let act = IambAction::Room(SetRoomField::Topic("Lots of fun discussion!".into()).into());
assert_eq!(res, vec![(act.into(), ctx.clone())]);
let res = cmds
.input_cmd("set room.topic The\\ Discussion\\ Room", ctx.clone())
.unwrap();
let act = IambAction::Room(SetRoomField::Topic("The Discussion Room".into()).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());
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());
assert_eq!(res, vec![(act.into(), ctx.clone())]);
let res = cmds
.input_cmd("set room.name \"Application Development\"", ctx.clone())
.unwrap();
let act = IambAction::Room(SetRoomField::Name("Application Development".into()).into());
assert_eq!(res, vec![(act.into(), ctx.clone())]);
let res = cmds.input_cmd("set", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
let res = cmds.input_cmd("set room.name", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
let res = cmds.input_cmd("set room.topic", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
let res = cmds.input_cmd("set room.topic A B C", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
}
} }

View file

@ -100,25 +100,44 @@ impl Tunables {
#[derive(Clone)] #[derive(Clone)]
pub struct DirectoryValues { pub struct DirectoryValues {
pub cache: PathBuf, pub cache: PathBuf,
pub logs: PathBuf,
pub downloads: PathBuf,
} }
#[derive(Clone, Default, Deserialize)] #[derive(Clone, Default, Deserialize)]
pub struct Directories { pub struct Directories {
pub cache: Option<PathBuf>, pub cache: Option<PathBuf>,
pub logs: Option<PathBuf>,
pub downloads: Option<PathBuf>,
} }
impl Directories { impl Directories {
fn merge(self, other: Self) -> Self { fn merge(self, other: Self) -> Self {
Directories { cache: self.cache.or(other.cache) } Directories {
cache: self.cache.or(other.cache),
logs: self.logs.or(other.logs),
downloads: self.downloads.or(other.downloads),
}
} }
fn values(self) -> DirectoryValues { fn values(self) -> DirectoryValues {
DirectoryValues { let cache = self
cache: self
.cache .cache
.or_else(dirs::cache_dir) .or_else(dirs::cache_dir)
.expect("no dirs.cache value configured!"), .expect("no dirs.cache value configured!");
}
let logs = self.logs.unwrap_or_else(|| {
let mut dir = cache.clone();
dir.push("logs");
dir
});
let downloads = self
.downloads
.or_else(dirs::download_dir)
.expect("no dirs.download value configured!");
DirectoryValues { cache, logs, downloads }
} }
} }

View file

@ -162,7 +162,7 @@ impl Application {
term.draw(|f| { term.draw(|f| {
let area = f.size(); let area = f.size();
let screen = Screen::new(store).showmode(modestr); let screen = Screen::new(store).showmode(modestr).borders(true);
f.render_stateful_widget(screen, area, sstate); f.render_stateful_widget(screen, area, sstate);
if let Some((cx, cy)) = sstate.get_term_cursor() { if let Some((cx, cy)) = sstate.get_term_cursor() {
@ -457,11 +457,10 @@ async fn main() -> IambResult<()> {
// Set up the tracing subscriber so we can log client messages. // Set up the tracing subscriber so we can log client messages.
let log_prefix = format!("iamb-log-{}", settings.profile_name); let log_prefix = format!("iamb-log-{}", settings.profile_name);
let mut log_dir = settings.dirs.cache.clone(); let log_dir = settings.dirs.logs.as_path();
log_dir.push("logs");
create_dir_all(settings.matrix_dir.as_path())?; create_dir_all(settings.matrix_dir.as_path())?;
create_dir_all(log_dir.as_path())?; create_dir_all(log_dir)?;
let appender = tracing_appender::rolling::daily(log_dir, log_prefix); let appender = tracing_appender::rolling::daily(log_dir, log_prefix);
let (appender, _) = tracing_appender::non_blocking(appender); let (appender, _) = tracing_appender::non_blocking(appender);

View file

@ -110,6 +110,14 @@ pub fn mock_room() -> RoomInfo {
} }
} }
pub fn mock_dirs() -> DirectoryValues {
DirectoryValues {
cache: PathBuf::new(),
logs: PathBuf::new(),
downloads: PathBuf::new(),
}
}
pub fn mock_settings() -> ApplicationSettings { pub fn mock_settings() -> ApplicationSettings {
ApplicationSettings { ApplicationSettings {
matrix_dir: PathBuf::new(), matrix_dir: PathBuf::new(),
@ -122,7 +130,7 @@ pub fn mock_settings() -> ApplicationSettings {
dirs: None, dirs: None,
}, },
tunables: TunableValues { typing_notice: true, typing_notice_display: true }, tunables: TunableValues { typing_notice: true, typing_notice_display: true },
dirs: DirectoryValues { cache: PathBuf::new() }, dirs: mock_dirs(),
} }
} }

View file

@ -13,7 +13,7 @@ use modalkit::tui::{
layout::{Alignment, Rect}, layout::{Alignment, Rect},
style::{Modifier as StyleModifier, Style}, style::{Modifier as StyleModifier, Style},
text::{Span, Spans, Text}, text::{Span, Spans, Text},
widgets::{Block, Borders, StatefulWidget, Widget}, widgets::StatefulWidget,
}; };
use modalkit::{ use modalkit::{
@ -71,6 +71,21 @@ use self::{room::RoomState, welcome::WelcomeState};
pub mod room; pub mod room;
pub mod welcome; pub mod welcome;
#[inline]
fn bold_style() -> Style {
Style::default().add_modifier(StyleModifier::BOLD)
}
#[inline]
fn bold_span(s: &str) -> Span {
Span::styled(s, bold_style())
}
#[inline]
fn bold_spans(s: &str) -> Spans {
bold_span(s).into()
}
#[inline] #[inline]
fn selected_style(selected: bool) -> Style { fn selected_style(selected: bool) -> Style {
if selected { if selected {
@ -168,22 +183,6 @@ impl IambWindow {
return Err(err); return Err(err);
} }
} }
pub fn get_title(&self, store: &mut ProgramStore) -> String {
match self {
IambWindow::DirectList(_) => "Direct Messages".to_string(),
IambWindow::RoomList(_) => "Rooms".to_string(),
IambWindow::SpaceList(_) => "Spaces".to_string(),
IambWindow::VerifyList(_) => "Verifications".to_string(),
IambWindow::Welcome(_) => "Welcome to iamb".to_string(),
IambWindow::Room(w) => w.get_title(store),
IambWindow::MemberList(_, room_id) => {
let title = store.application.get_room_title(room_id.as_ref());
format!("Room Members: {}", title)
},
}
}
} }
pub type DirectListState = ListState<DirectItem, IambInfo>; pub type DirectListState = ListState<DirectItem, IambInfo>;
@ -281,13 +280,8 @@ impl TerminalCursor for IambWindow {
impl WindowOps<IambInfo> for IambWindow { impl WindowOps<IambInfo> for IambWindow {
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) { fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
let title = self.get_title(store);
let block = Block::default().title(title.as_str()).borders(Borders::ALL);
let inner = block.inner(area);
block.render(area, buf);
match self { match self {
IambWindow::Room(state) => state.draw(inner, buf, focused, store), IambWindow::Room(state) => state.draw(area, buf, focused, store),
IambWindow::DirectList(state) => { IambWindow::DirectList(state) => {
let dms = store.application.worker.direct_messages(); let dms = store.application.worker.direct_messages();
let items = dms.into_iter().map(|(id, name)| DirectItem::new(id, name, store)); let items = dms.into_iter().map(|(id, name)| DirectItem::new(id, name, store));
@ -297,7 +291,7 @@ impl WindowOps<IambInfo> for IambWindow {
.empty_message("No direct messages yet!") .empty_message("No direct messages yet!")
.empty_alignment(Alignment::Center) .empty_alignment(Alignment::Center)
.focus(focused) .focus(focused)
.render(inner, buf, state); .render(area, buf, state);
}, },
IambWindow::MemberList(state, room_id) => { IambWindow::MemberList(state, room_id) => {
if let Ok(mems) = store.application.worker.members(room_id.clone()) { if let Ok(mems) = store.application.worker.members(room_id.clone()) {
@ -309,7 +303,7 @@ impl WindowOps<IambInfo> for IambWindow {
.empty_message("No users here yet!") .empty_message("No users here yet!")
.empty_alignment(Alignment::Center) .empty_alignment(Alignment::Center)
.focus(focused) .focus(focused)
.render(inner, buf, state); .render(area, buf, state);
}, },
IambWindow::RoomList(state) => { IambWindow::RoomList(state) => {
let joined = store.application.worker.joined_rooms(); let joined = store.application.worker.joined_rooms();
@ -320,20 +314,20 @@ impl WindowOps<IambInfo> for IambWindow {
.empty_message("You haven't joined any rooms yet") .empty_message("You haven't joined any rooms yet")
.empty_alignment(Alignment::Center) .empty_alignment(Alignment::Center)
.focus(focused) .focus(focused)
.render(inner, buf, state); .render(area, buf, state);
}, },
IambWindow::SpaceList(state) => { IambWindow::SpaceList(state) => {
let spaces = store.application.worker.spaces(); let spaces = store.application.worker.spaces();
let items = let items =
spaces.into_iter().map(|(room, name)| SpaceItem::new(room, name, store)); spaces.into_iter().map(|(room, name)| SpaceItem::new(room, name, store));
state.set(items.collect()); state.set(items.collect());
state.draw(inner, buf, focused, store); state.draw(area, buf, focused, store);
List::new(store) List::new(store)
.empty_message("You haven't joined any spaces yet") .empty_message("You haven't joined any spaces yet")
.empty_alignment(Alignment::Center) .empty_alignment(Alignment::Center)
.focus(focused) .focus(focused)
.render(inner, buf, state); .render(area, buf, state);
}, },
IambWindow::VerifyList(state) => { IambWindow::VerifyList(state) => {
let verifications = &store.application.verifications; let verifications = &store.application.verifications;
@ -348,9 +342,9 @@ impl WindowOps<IambInfo> for IambWindow {
.empty_message("No in-progress verifications") .empty_message("No in-progress verifications")
.empty_alignment(Alignment::Center) .empty_alignment(Alignment::Center)
.focus(focused) .focus(focused)
.render(inner, buf, state); .render(area, buf, state);
}, },
IambWindow::Welcome(state) => state.draw(inner, buf, focused, store), IambWindow::Welcome(state) => state.draw(area, buf, focused, store),
} }
} }
@ -394,6 +388,44 @@ impl Window<IambInfo> for IambWindow {
} }
} }
fn get_tab_title(&self, store: &mut ProgramStore) -> Spans {
match self {
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
IambWindow::RoomList(_) => bold_spans("Rooms"),
IambWindow::SpaceList(_) => bold_spans("Spaces"),
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::Room(w) => {
let title = store.application.get_room_title(w.id());
Spans::from(title)
},
IambWindow::MemberList(_, room_id) => {
let title = store.application.get_room_title(room_id.as_ref());
Spans(vec![bold_span("Room Members: "), title.into()])
},
}
}
fn get_win_title(&self, store: &mut ProgramStore) -> Spans {
match self {
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
IambWindow::RoomList(_) => bold_spans("Rooms"),
IambWindow::SpaceList(_) => bold_spans("Spaces"),
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::Room(w) => w.get_title(store),
IambWindow::MemberList(_, room_id) => {
let title = store.application.get_room_title(room_id.as_ref());
Spans(vec![bold_span("Room Members: "), title.into()])
},
}
}
fn open(id: IambId, store: &mut ProgramStore) -> IambResult<Self> { fn open(id: IambId, store: &mut ProgramStore) -> IambResult<Self> {
match id { match id {
IambId::Room(room_id) => { IambId::Room(room_id) => {
@ -464,6 +496,10 @@ impl Window<IambInfo> for IambWindow {
Err(err) Err(err)
} }
fn unnamed(store: &mut ProgramStore) -> IambResult<Self> {
Self::open(IambId::RoomList, store)
}
} }
#[derive(Clone)] #[derive(Clone)]

View file

@ -44,6 +44,7 @@ use super::scrollback::{Scrollback, ScrollbackState};
pub struct ChatState { pub struct ChatState {
room_id: OwnedRoomId, room_id: OwnedRoomId,
room: MatrixRoom,
tbox: TextBoxState<IambInfo>, tbox: TextBoxState<IambInfo>,
sent: HistoryList<EditRope>, sent: HistoryList<EditRope>,
@ -63,6 +64,7 @@ impl ChatState {
ChatState { ChatState {
room_id, room_id,
room,
tbox, tbox,
sent: HistoryList::new(EditRope::from(""), 100), sent: HistoryList::new(EditRope::from(""), 100),
@ -80,6 +82,10 @@ impl ChatState {
}; };
} }
pub fn room(&self) -> &MatrixRoom {
&self.room
}
pub fn id(&self) -> &RoomId { pub fn id(&self) -> &RoomId {
&self.room_id &self.room_id
} }
@ -133,6 +139,7 @@ impl WindowOps<IambInfo> for ChatState {
ChatState { ChatState {
room_id: self.room_id.clone(), room_id: self.room_id.clone(),
room: self.room.clone(),
tbox, tbox,
sent: self.sent.clone(), sent: self.sent.clone(),

View file

@ -2,7 +2,13 @@ use matrix_sdk::room::Room as MatrixRoom;
use matrix_sdk::ruma::RoomId; use matrix_sdk::ruma::RoomId;
use matrix_sdk::DisplayName; use matrix_sdk::DisplayName;
use modalkit::tui::{buffer::Buffer, layout::Rect, widgets::StatefulWidget}; use modalkit::tui::{
buffer::Buffer,
layout::Rect,
style::{Modifier as StyleModifier, Style},
text::{Span, Spans},
widgets::StatefulWidget,
};
use modalkit::{ use modalkit::{
editing::action::{ editing::action::{
@ -90,7 +96,7 @@ impl RoomState {
&mut self, &mut self,
act: RoomAction, act: RoomAction,
_: ProgramContext, _: ProgramContext,
_: &mut ProgramStore, store: &mut ProgramStore,
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> { ) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
match act { match act {
RoomAction::Members(mut cmd) => { RoomAction::Members(mut cmd) => {
@ -103,11 +109,29 @@ impl RoomState {
Ok(vec![(act, cmd.context.take())]) Ok(vec![(act, cmd.context.take())])
}, },
RoomAction::Set(field) => {
store.application.worker.set_room(self.id().to_owned(), field)?;
Ok(vec![])
},
} }
} }
pub fn get_title(&self, store: &mut ProgramStore) -> String { pub fn get_title(&self, store: &mut ProgramStore) -> Spans {
store.application.get_room_title(self.id()) let title = store.application.get_room_title(self.id());
let style = Style::default().add_modifier(StyleModifier::BOLD);
let mut spans = vec![Span::styled(title, style)];
match self.room().topic() {
Some(desc) if !desc.is_empty() => {
spans.push(" (".into());
spans.push(desc.into());
spans.push(")".into());
},
_ => {},
}
Spans(spans)
} }
pub fn focus_toggle(&mut self) { pub fn focus_toggle(&mut self) {
@ -117,6 +141,13 @@ impl RoomState {
} }
} }
pub fn room(&self) -> &MatrixRoom {
match self {
RoomState::Chat(chat) => chat.room(),
RoomState::Space(space) => space.room(),
}
}
pub fn id(&self) -> &RoomId { pub fn id(&self) -> &RoomId {
match self { match self {
RoomState::Chat(chat) => chat.id(), RoomState::Chat(chat) => chat.id(),

View file

@ -18,6 +18,7 @@ use crate::windows::RoomItem;
pub struct SpaceState { pub struct SpaceState {
room_id: OwnedRoomId, room_id: OwnedRoomId,
room: MatrixRoom,
list: ListState<RoomItem, IambInfo>, list: ListState<RoomItem, IambInfo>,
} }
@ -27,7 +28,11 @@ impl SpaceState {
let content = IambBufferId::Room(room_id.clone(), RoomFocus::Scrollback); let content = IambBufferId::Room(room_id.clone(), RoomFocus::Scrollback);
let list = ListState::new(content, vec![]); let list = ListState::new(content, vec![]);
SpaceState { room_id, list } SpaceState { room_id, room, list }
}
pub fn room(&self) -> &MatrixRoom {
&self.room
} }
pub fn id(&self) -> &RoomId { pub fn id(&self) -> &RoomId {
@ -37,6 +42,7 @@ impl SpaceState {
pub fn dup(&self, store: &mut ProgramStore) -> Self { pub fn dup(&self, store: &mut ProgramStore) -> Self {
SpaceState { SpaceState {
room_id: self.room_id.clone(), room_id: self.room_id.clone(),
room: self.room.clone(),
list: self.list.dup(store), list: self.list.dup(store),
} }
} }

View file

@ -30,8 +30,11 @@ use matrix_sdk::{
start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent}, start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent},
VerificationMethod, VerificationMethod,
}, },
room::message::{MessageType, RoomMessageEventContent, TextMessageEventContent}, room::{
room::name::RoomNameEventContent, message::{MessageType, RoomMessageEventContent, TextMessageEventContent},
name::RoomNameEventContent,
topic::RoomTopicEventContent,
},
typing::SyncTypingEvent, typing::SyncTypingEvent,
AnyMessageLikeEvent, AnyMessageLikeEvent,
AnyTimelineEvent, AnyTimelineEvent,
@ -51,7 +54,7 @@ use matrix_sdk::{
use modalkit::editing::action::{EditInfo, InfoMessage, UIError}; use modalkit::editing::action::{EditInfo, InfoMessage, UIError};
use crate::{ use crate::{
base::{AsyncProgramStore, IambError, IambResult, VerifyAction}, base::{AsyncProgramStore, IambError, IambResult, SetRoomField, VerifyAction},
message::{Message, MessageFetchResult, MessageTimeStamp}, message::{Message, MessageFetchResult, MessageTimeStamp},
ApplicationSettings, ApplicationSettings,
}; };
@ -106,6 +109,7 @@ pub enum WorkerTask {
SpaceMembers(OwnedRoomId, ClientReply<IambResult<Vec<OwnedRoomId>>>), SpaceMembers(OwnedRoomId, ClientReply<IambResult<Vec<OwnedRoomId>>>),
Spaces(ClientReply<Vec<(MatrixRoom, DisplayName)>>), Spaces(ClientReply<Vec<(MatrixRoom, DisplayName)>>),
SendMessage(OwnedRoomId, String, ClientReply<IambResult<EchoPair>>), SendMessage(OwnedRoomId, String, ClientReply<IambResult<EchoPair>>),
SetRoom(OwnedRoomId, SetRoomField, ClientReply<IambResult<()>>),
TypingNotice(OwnedRoomId), TypingNotice(OwnedRoomId),
Verify(VerifyAction, SasVerification, ClientReply<IambResult<EditInfo>>), Verify(VerifyAction, SasVerification, ClientReply<IambResult<EditInfo>>),
VerifyRequest(OwnedUserId, ClientReply<IambResult<EditInfo>>), VerifyRequest(OwnedUserId, ClientReply<IambResult<EditInfo>>),
@ -204,6 +208,14 @@ impl Requester {
return response.recv(); 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)> { pub fn spaces(&self) -> Vec<(MatrixRoom, DisplayName)> {
let (reply, response) = oneshot(); let (reply, response) = oneshot();
@ -340,6 +352,10 @@ impl ClientWorker {
assert!(self.initialized); assert!(self.initialized);
reply.send(self.members(room_id).await); 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) => { WorkerTask::SpaceMembers(space, reply) => {
assert!(self.initialized); assert!(self.initialized);
reply.send(self.space_members(space).await); reply.send(self.space_members(space).await);
@ -765,6 +781,27 @@ 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<Vec<OwnedRoomId>> { async fn space_members(&mut self, space: OwnedRoomId) -> IambResult<Vec<OwnedRoomId>> {
let mut req = SpaceHierarchyRequest::new(&space); let mut req = SpaceHierarchyRequest::new(&space);
req.limit = Some(1000u32.into()); req.limit = Some(1000u32.into());