mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
Support displaying and editing room descriptions (#12)
This commit is contained in:
parent
8ed037afca
commit
38f4795886
13 changed files with 286 additions and 55 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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: |
|
||||||
|
|
19
src/base.rs
19
src/base.rs
|
@ -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 {
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
10
src/tests.rs
10
src/tests.rs
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue