Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,9 @@ pub enum IambId {
/// The `:spaces` window.
SpaceList,

/// The `:spaces!` window.
SpaceTree,

/// The `:verify` window.
VerifyList,

Expand Down Expand Up @@ -1602,6 +1605,7 @@ impl Display for IambId {
IambId::DirectList => f.write_str("iamb://dms"),
IambId::RoomList => f.write_str("iamb://rooms"),
IambId::SpaceList => f.write_str("iamb://spaces"),
IambId::SpaceTree => f.write_str("iamb://spacetree"),
IambId::VerifyList => f.write_str("iamb://verify"),
IambId::Welcome => f.write_str("iamb://welcome"),
IambId::ChatList => f.write_str("iamb://chats"),
Expand Down Expand Up @@ -1803,6 +1807,9 @@ pub enum IambBufferId {
/// The `:spaces` window.
SpaceList,

/// The `:spaces!` window.
SpaceTree,

/// The `:verify` window.
VerifyList,

Expand All @@ -1826,6 +1833,7 @@ impl IambBufferId {
IambBufferId::MemberList(room) => IambId::MemberList(room.clone()),
IambBufferId::RoomList => IambId::RoomList,
IambBufferId::SpaceList => IambId::SpaceList,
IambBufferId::SpaceTree => IambId::SpaceTree,
IambBufferId::VerifyList => IambId::VerifyList,
IambBufferId::Welcome => IambId::Welcome,
IambBufferId::ChatList => IambId::ChatList,
Expand Down Expand Up @@ -1861,6 +1869,7 @@ impl ApplicationInfo for IambInfo {
IambBufferId::MemberList(_) => vec![],
IambBufferId::RoomList => vec![],
IambBufferId::SpaceList => vec![],
IambBufferId::SpaceTree => vec![],
IambBufferId::VerifyList => vec![],
IambBufferId::Welcome => vec![],
IambBufferId::ChatList => vec![],
Expand Down
8 changes: 7 additions & 1 deletion src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,13 @@ fn iamb_spaces(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
return Result::Err(CommandError::InvalidArgument);
}

let open = ctx.switch(OpenTarget::Application(IambId::SpaceList));
let target = if desc.bang {
IambId::SpaceTree
} else {
IambId::SpaceList
};

let open = ctx.switch(OpenTarget::Application(target));
let step = CommandStep::Continue(open, ctx.context.clone());

return Ok(step);
Expand Down
21 changes: 20 additions & 1 deletion src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ use crate::base::{
UnreadInfo,
};

use self::{room::RoomState, welcome::WelcomeState};
use self::{room::RoomState, spacetree::SpaceTreeState, welcome::WelcomeState};
use crate::message::MessageTimeStamp;
use feruca::Collator;

pub mod room;
pub mod spacetree;
pub mod welcome;

type MatrixRoomInfo = Arc<(MatrixRoom, Option<Tags>)>;
Expand Down Expand Up @@ -326,6 +327,7 @@ macro_rules! delegate {
IambWindow::MemberList($id, _, _) => $e,
IambWindow::RoomList($id) => $e,
IambWindow::SpaceList($id) => $e,
IambWindow::SpaceTree($id) => $e,
IambWindow::VerifyList($id) => $e,
IambWindow::Welcome($id) => $e,
IambWindow::ChatList($id) => $e,
Expand All @@ -341,6 +343,7 @@ pub enum IambWindow {
VerifyList(VerifyListState),
RoomList(RoomListState),
SpaceList(SpaceListState),
SpaceTree(SpaceTreeState),
Welcome(WelcomeState),
ChatList(ChatListState),
UnreadList(UnreadListState),
Expand Down Expand Up @@ -452,6 +455,12 @@ impl From<SpaceListState> for IambWindow {
}
}

impl From<SpaceTreeState> for IambWindow {
fn from(tree: SpaceTreeState) -> Self {
IambWindow::SpaceTree(tree)
}
}

impl From<WelcomeState> for IambWindow {
fn from(win: WelcomeState) -> Self {
IambWindow::Welcome(win)
Expand Down Expand Up @@ -513,6 +522,7 @@ impl WindowOps<IambInfo> for IambWindow {
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
match self {
IambWindow::Room(state) => state.draw(area, buf, focused, store),
IambWindow::SpaceTree(state) => state.draw(area, buf, focused, store),
IambWindow::DirectList(state) => {
let mut items = store
.application
Expand Down Expand Up @@ -695,6 +705,7 @@ impl WindowOps<IambInfo> for IambWindow {
},
IambWindow::RoomList(w) => w.dup(store).into(),
IambWindow::SpaceList(w) => w.dup(store).into(),
IambWindow::SpaceTree(w) => w.dup(store).into(),
IambWindow::VerifyList(w) => w.dup(store).into(),
IambWindow::Welcome(w) => w.dup(store).into(),
IambWindow::ChatList(w) => w.dup(store).into(),
Expand Down Expand Up @@ -736,6 +747,7 @@ impl Window<IambInfo> for IambWindow {
IambWindow::MemberList(_, room_id, _) => IambId::MemberList(room_id.clone()),
IambWindow::RoomList(_) => IambId::RoomList,
IambWindow::SpaceList(_) => IambId::SpaceList,
IambWindow::SpaceTree(_) => IambId::SpaceTree,
IambWindow::VerifyList(_) => IambId::VerifyList,
IambWindow::Welcome(_) => IambId::Welcome,
IambWindow::ChatList(_) => IambId::ChatList,
Expand All @@ -748,6 +760,7 @@ impl Window<IambInfo> for IambWindow {
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
IambWindow::RoomList(_) => bold_spans("Rooms"),
IambWindow::SpaceList(_) => bold_spans("Spaces"),
IambWindow::SpaceTree(_) => bold_spans("Space Tree"),
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
Expand Down Expand Up @@ -776,6 +789,7 @@ impl Window<IambInfo> for IambWindow {
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
IambWindow::RoomList(_) => bold_spans("Rooms"),
IambWindow::SpaceList(_) => bold_spans("Spaces"),
IambWindow::SpaceTree(_) => bold_spans("Space Tree"),
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
Expand Down Expand Up @@ -826,6 +840,11 @@ impl Window<IambInfo> for IambWindow {

return Ok(list.into());
},
IambId::SpaceTree => {
let tree = SpaceTreeState::new();

return Ok(tree.into());
},
IambId::VerifyList => {
let list = VerifyListState::new(IambBufferId::VerifyList, vec![]);

Expand Down
215 changes: 215 additions & 0 deletions src/windows/spacetree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
use std::time::{Duration, Instant};

use modalkit::{
actions::{Editable, EditorAction, Jumpable, PromptAction, Promptable, Scrollable},
editing::completion::CompletionList,
errors::EditResult,
prelude::*,
};
use modalkit_ratatui::{
list::{List, ListState},
TermOffset,
TerminalCursor,
WindowOps,
};
use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Style},
text::{Line, Span, Text},
widgets::StatefulWidget,
};

use crate::base::{
IambBufferId,
IambInfo,
IambResult,
ProgramAction,
ProgramContext,
ProgramStore,
};

use crate::windows::RoomItem;

use super::room_fields_cmp;

const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(15);

/// [StatefulWidget] for Matrix space tree.
pub struct SpaceTree<'a> {
focused: bool,
store: &'a mut ProgramStore,
}

impl<'a> SpaceTree<'a> {
pub fn new(store: &'a mut ProgramStore) -> Self {
SpaceTree { focused: false, store }
}

pub fn focus(mut self, focused: bool) -> Self {
self.focused = focused;
self
}
}

impl StatefulWidget for SpaceTree<'_> {
type State = SpaceTreeState;

fn render(self, area: Rect, buffer: &mut Buffer, state: &mut Self::State) {
let mut empty_message = None;
let need_fetch = match state.last_fetch {
Some(i) => i.elapsed() >= SPACE_HIERARCHY_DEBOUNCE,
None => true,
};

if need_fetch {
let mut children = vec![];
let res = self.store.application.sync_info.spaces.iter().try_for_each(|room| {
let id = room.0.room_id();
let res = self.store.application.worker.space_members(id.to_owned());

res.map(|members| children.extend(members.into_iter().filter(|child| child != id)))
});

if let Err(e) = res {
let lines = vec![
Line::from("Unable to fetch space room hierarchy:"),
Span::styled(e.to_string(), Style::default().fg(Color::Red)).into(),
];

empty_message = Text::from(lines).into();
} else {
let mut items = self
.store
.application
.sync_info
.spaces
.clone()
.into_iter()
.filter(|space| !children.contains(&space.0.room_id().to_owned()))
.map(|room| RoomItem::new(room, self.store))
.collect::<Vec<_>>();
let fields = &self.store.application.settings.tunables.sort.spaces;
let collator = &mut self.store.application.collator;
items.sort_by(|a, b| room_fields_cmp(a, b, fields, collator));

state.list.set(items);
state.last_fetch = Some(Instant::now());
}
}

let mut list = List::new(self.store).focus(self.focused);

if let Some(text) = empty_message {
list = list.empty_message(text);
} else {
list = list.empty_message(Text::from("You haven't joined any spaces yet"));
}

list.render(area, buffer, &mut state.list)
}
}

/// State for the list of toplevel spaces
pub struct SpaceTreeState {
list: ListState<RoomItem, IambInfo>,
last_fetch: Option<Instant>,
}

impl SpaceTreeState {
pub fn new() -> Self {
let content = IambBufferId::SpaceTree;
let list = ListState::new(content, vec![]);

SpaceTreeState { list, last_fetch: None }
}
}

impl Editable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
fn editor_command(
&mut self,
act: &EditorAction,
ctx: &ProgramContext,
store: &mut ProgramStore,
) -> EditResult<EditInfo, IambInfo> {
self.list.editor_command(act, ctx, store)
}
}

impl Jumpable<ProgramContext, IambInfo> for SpaceTreeState {
fn jump(
&mut self,
list: PositionList,
dir: MoveDir1D,
count: usize,
ctx: &ProgramContext,
) -> IambResult<usize> {
self.list.jump(list, dir, count, ctx)
}
}

impl Scrollable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
fn scroll(
&mut self,
style: &ScrollStyle,
ctx: &ProgramContext,
store: &mut ProgramStore,
) -> EditResult<EditInfo, IambInfo> {
self.list.scroll(style, ctx, store)
}
}

impl Promptable<ProgramContext, ProgramStore, IambInfo> for SpaceTreeState {
fn prompt(
&mut self,
act: &PromptAction,
ctx: &ProgramContext,
store: &mut ProgramStore,
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
self.list.prompt(act, ctx, store)
}
}

impl TerminalCursor for SpaceTreeState {
fn get_term_cursor(&self) -> Option<TermOffset> {
self.list.get_term_cursor()
}
}

impl WindowOps<IambInfo> for SpaceTreeState {
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
SpaceTree::new(store).focus(focused).render(area, buf, self);
}

fn dup(&self, store: &mut ProgramStore) -> Self {
SpaceTreeState {
list: self.list.dup(store),
last_fetch: self.last_fetch,
}
}

fn close(&mut self, flags: CloseFlags, store: &mut ProgramStore) -> bool {
self.list.close(flags, store)
}

fn get_completions(&self) -> Option<CompletionList> {
self.list.get_completions()
}

fn get_cursor_word(&self, style: &WordStyle) -> Option<String> {
self.list.get_cursor_word(style)
}

fn get_selected_word(&self) -> Option<String> {
self.list.get_selected_word()
}

fn write(
&mut self,
path: Option<&str>,
flags: WriteFlags,
store: &mut ProgramStore,
) -> IambResult<EditInfo> {
self.list.write(path, flags, store)
}
}
Loading