mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
Support custom sorting for room and user lists (#170)
This commit is contained in:
parent
443ad241b4
commit
8943909f06
7 changed files with 500 additions and 119 deletions
|
@ -1,6 +1,6 @@
|
||||||
# iamb
|
# iamb
|
||||||
|
|
||||||
[](https://github.com/ulyssa/iamb/actions?query=workflow%3ACI+)
|
[](https://github.com/ulyssa/iamb/actions?query=workflow%3ACI+)
|
||||||
[](https://crates.io/crates/iamb)
|
[](https://crates.io/crates/iamb)
|
||||||
[](https://matrix.to/#/#iamb:0x.badd.cafe)
|
[](https://matrix.to/#/#iamb:0x.badd.cafe)
|
||||||
[](https://crates.io/crates/iamb)
|
[](https://crates.io/crates/iamb)
|
||||||
|
|
131
src/base.rs
131
src/base.rs
|
@ -202,6 +202,135 @@ bitflags::bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fields that rooms and spaces can be sorted by.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum SortFieldRoom {
|
||||||
|
Favorite,
|
||||||
|
LowPriority,
|
||||||
|
Name,
|
||||||
|
Alias,
|
||||||
|
RoomId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fields that users can be sorted by.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum SortFieldUser {
|
||||||
|
PowerLevel,
|
||||||
|
UserId,
|
||||||
|
LocalPart,
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether to use the default sort direction for a field, or to reverse it.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum SortOrder {
|
||||||
|
Ascending,
|
||||||
|
Descending,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One of the columns to sort on.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct SortColumn<T>(pub T, pub SortOrder);
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for SortColumn<SortFieldRoom> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(SortRoomVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [serde] visitor for deserializing [SortColumn] for rooms and spaces.
|
||||||
|
struct SortRoomVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for SortRoomVisitor {
|
||||||
|
type Value = SortColumn<SortFieldRoom>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a valid field for sorting rooms")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, mut value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
if value.is_empty() {
|
||||||
|
return Err(E::custom("Invalid sort field"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let order = if value.starts_with('~') {
|
||||||
|
value = &value[1..];
|
||||||
|
SortOrder::Descending
|
||||||
|
} else {
|
||||||
|
SortOrder::Ascending
|
||||||
|
};
|
||||||
|
|
||||||
|
let field = match value {
|
||||||
|
"favorite" => SortFieldRoom::Favorite,
|
||||||
|
"lowpriority" => SortFieldRoom::LowPriority,
|
||||||
|
"name" => SortFieldRoom::Name,
|
||||||
|
"alias" => SortFieldRoom::Alias,
|
||||||
|
"id" => SortFieldRoom::RoomId,
|
||||||
|
_ => {
|
||||||
|
let msg = format!("Unknown sort field: {value:?}");
|
||||||
|
return Err(E::custom(msg));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(SortColumn(field, order))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for SortColumn<SortFieldUser> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(SortUserVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [serde] visitor for deserializing [SortColumn] for users.
|
||||||
|
struct SortUserVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for SortUserVisitor {
|
||||||
|
type Value = SortColumn<SortFieldUser>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a valid field for sorting rooms")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, mut value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: SerdeError,
|
||||||
|
{
|
||||||
|
if value.is_empty() {
|
||||||
|
return Err(E::custom("Invalid field for sorting users"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let order = if value.starts_with('~') {
|
||||||
|
value = &value[1..];
|
||||||
|
SortOrder::Descending
|
||||||
|
} else {
|
||||||
|
SortOrder::Ascending
|
||||||
|
};
|
||||||
|
|
||||||
|
let field = match value {
|
||||||
|
"id" => SortFieldUser::UserId,
|
||||||
|
"localpart" => SortFieldUser::LocalPart,
|
||||||
|
"server" => SortFieldUser::Server,
|
||||||
|
"power" => SortFieldUser::PowerLevel,
|
||||||
|
_ => {
|
||||||
|
let msg = format!("Unknown sort field: {value:?}");
|
||||||
|
return Err(E::custom(msg));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(SortColumn(field, order))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A room property.
|
/// A room property.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum RoomField {
|
pub enum RoomField {
|
||||||
|
@ -811,7 +940,7 @@ fn emoji_map() -> CompletionMap<String, &'static Emoji> {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SyncInfo {
|
pub struct SyncInfo {
|
||||||
/// Spaces that the user is a member of.
|
/// Spaces that the user is a member of.
|
||||||
pub spaces: Vec<MatrixRoom>,
|
pub spaces: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
|
||||||
|
|
||||||
/// Rooms that the user is a member of.
|
/// Rooms that the user is a member of.
|
||||||
pub rooms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
|
pub rooms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
|
||||||
|
|
|
@ -20,7 +20,7 @@ use modalkit::tui::{
|
||||||
text::Span,
|
text::Span,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::base::{IambId, RoomInfo};
|
use super::base::{IambId, RoomInfo, SortColumn, SortFieldRoom, SortFieldUser, SortOrder};
|
||||||
|
|
||||||
macro_rules! usage {
|
macro_rules! usage {
|
||||||
( $($args: tt)* ) => {
|
( $($args: tt)* ) => {
|
||||||
|
@ -29,6 +29,17 @@ macro_rules! usage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_MEMBERS_SORT: [SortColumn<SortFieldUser>; 2] = [
|
||||||
|
SortColumn(SortFieldUser::PowerLevel, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldUser::UserId, SortOrder::Ascending),
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_ROOM_SORT: [SortColumn<SortFieldRoom>; 3] = [
|
||||||
|
SortColumn(SortFieldRoom::Favorite, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldRoom::LowPriority, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
|
||||||
|
];
|
||||||
|
|
||||||
const DEFAULT_REQ_TIMEOUT: u64 = 120;
|
const DEFAULT_REQ_TIMEOUT: u64 = 120;
|
||||||
|
|
||||||
const COLORS: [Color; 13] = [
|
const COLORS: [Color; 13] = [
|
||||||
|
@ -213,6 +224,15 @@ pub struct UserDisplayTunables {
|
||||||
|
|
||||||
pub type UserOverrides = HashMap<OwnedUserId, UserDisplayTunables>;
|
pub type UserOverrides = HashMap<OwnedUserId, UserDisplayTunables>;
|
||||||
|
|
||||||
|
fn merge_sorts(a: SortOverrides, b: SortOverrides) -> SortOverrides {
|
||||||
|
SortOverrides {
|
||||||
|
dms: b.dms.or(a.dms),
|
||||||
|
rooms: b.rooms.or(a.rooms),
|
||||||
|
spaces: b.spaces.or(a.spaces),
|
||||||
|
members: b.members.or(a.members),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn merge_users(a: Option<UserOverrides>, b: Option<UserOverrides>) -> Option<UserOverrides> {
|
fn merge_users(a: Option<UserOverrides>, b: Option<UserOverrides>) -> Option<UserOverrides> {
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Some(a), None) => Some(a),
|
(Some(a), None) => Some(a),
|
||||||
|
@ -246,6 +266,33 @@ pub enum UserDisplayStyle {
|
||||||
DisplayName,
|
DisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SortValues {
|
||||||
|
pub dms: Vec<SortColumn<SortFieldRoom>>,
|
||||||
|
pub rooms: Vec<SortColumn<SortFieldRoom>>,
|
||||||
|
pub spaces: Vec<SortColumn<SortFieldRoom>>,
|
||||||
|
pub members: Vec<SortColumn<SortFieldUser>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Deserialize)]
|
||||||
|
pub struct SortOverrides {
|
||||||
|
pub dms: Option<Vec<SortColumn<SortFieldRoom>>>,
|
||||||
|
pub rooms: Option<Vec<SortColumn<SortFieldRoom>>>,
|
||||||
|
pub spaces: Option<Vec<SortColumn<SortFieldRoom>>>,
|
||||||
|
pub members: Option<Vec<SortColumn<SortFieldUser>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SortOverrides {
|
||||||
|
pub fn values(self) -> SortValues {
|
||||||
|
let rooms = self.rooms.unwrap_or_else(|| Vec::from(DEFAULT_ROOM_SORT));
|
||||||
|
let dms = self.dms.unwrap_or_else(|| rooms.clone());
|
||||||
|
let spaces = self.spaces.unwrap_or_else(|| rooms.clone());
|
||||||
|
let members = self.members.unwrap_or_else(|| Vec::from(DEFAULT_MEMBERS_SORT));
|
||||||
|
|
||||||
|
SortValues { rooms, members, dms, spaces }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TunableValues {
|
pub struct TunableValues {
|
||||||
pub log_level: Level,
|
pub log_level: Level,
|
||||||
|
@ -254,6 +301,7 @@ pub struct TunableValues {
|
||||||
pub read_receipt_send: bool,
|
pub read_receipt_send: bool,
|
||||||
pub read_receipt_display: bool,
|
pub read_receipt_display: bool,
|
||||||
pub request_timeout: u64,
|
pub request_timeout: u64,
|
||||||
|
pub sort: SortValues,
|
||||||
pub typing_notice_send: bool,
|
pub typing_notice_send: bool,
|
||||||
pub typing_notice_display: bool,
|
pub typing_notice_display: bool,
|
||||||
pub users: UserOverrides,
|
pub users: UserOverrides,
|
||||||
|
@ -270,6 +318,8 @@ pub struct Tunables {
|
||||||
pub read_receipt_send: Option<bool>,
|
pub read_receipt_send: Option<bool>,
|
||||||
pub read_receipt_display: Option<bool>,
|
pub read_receipt_display: Option<bool>,
|
||||||
pub request_timeout: Option<u64>,
|
pub request_timeout: Option<u64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub sort: SortOverrides,
|
||||||
pub typing_notice_send: Option<bool>,
|
pub typing_notice_send: Option<bool>,
|
||||||
pub typing_notice_display: Option<bool>,
|
pub typing_notice_display: Option<bool>,
|
||||||
pub users: Option<UserOverrides>,
|
pub users: Option<UserOverrides>,
|
||||||
|
@ -289,6 +339,7 @@ impl Tunables {
|
||||||
read_receipt_send: self.read_receipt_send.or(other.read_receipt_send),
|
read_receipt_send: self.read_receipt_send.or(other.read_receipt_send),
|
||||||
read_receipt_display: self.read_receipt_display.or(other.read_receipt_display),
|
read_receipt_display: self.read_receipt_display.or(other.read_receipt_display),
|
||||||
request_timeout: self.request_timeout.or(other.request_timeout),
|
request_timeout: self.request_timeout.or(other.request_timeout),
|
||||||
|
sort: merge_sorts(self.sort, other.sort),
|
||||||
typing_notice_send: self.typing_notice_send.or(other.typing_notice_send),
|
typing_notice_send: self.typing_notice_send.or(other.typing_notice_send),
|
||||||
typing_notice_display: self.typing_notice_display.or(other.typing_notice_display),
|
typing_notice_display: self.typing_notice_display.or(other.typing_notice_display),
|
||||||
users: merge_users(self.users, other.users),
|
users: merge_users(self.users, other.users),
|
||||||
|
@ -306,6 +357,7 @@ impl Tunables {
|
||||||
read_receipt_send: self.read_receipt_send.unwrap_or(true),
|
read_receipt_send: self.read_receipt_send.unwrap_or(true),
|
||||||
read_receipt_display: self.read_receipt_display.unwrap_or(true),
|
read_receipt_display: self.read_receipt_display.unwrap_or(true),
|
||||||
request_timeout: self.request_timeout.unwrap_or(DEFAULT_REQ_TIMEOUT),
|
request_timeout: self.request_timeout.unwrap_or(DEFAULT_REQ_TIMEOUT),
|
||||||
|
sort: self.sort.values(),
|
||||||
typing_notice_send: self.typing_notice_send.unwrap_or(true),
|
typing_notice_send: self.typing_notice_send.unwrap_or(true),
|
||||||
typing_notice_display: self.typing_notice_display.unwrap_or(true),
|
typing_notice_display: self.typing_notice_display.unwrap_or(true),
|
||||||
users: self.users.unwrap_or_default(),
|
users: self.users.unwrap_or_default(),
|
||||||
|
@ -701,6 +753,43 @@ mod tests {
|
||||||
assert_eq!(res.username_display, Some(UserDisplayStyle::DisplayName));
|
assert_eq!(res.username_display, Some(UserDisplayStyle::DisplayName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_tunables_sort() {
|
||||||
|
let res: Tunables = serde_json::from_str(
|
||||||
|
r#"{"sort": {"members": ["server","~localpart"],"spaces":["~favorite", "alias"]}}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
res.sort.members,
|
||||||
|
Some(vec![
|
||||||
|
SortColumn(SortFieldUser::Server, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldUser::LocalPart, SortOrder::Descending),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res.sort.spaces,
|
||||||
|
Some(vec![
|
||||||
|
SortColumn(SortFieldRoom::Favorite, SortOrder::Descending),
|
||||||
|
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
assert_eq!(res.sort.rooms, None);
|
||||||
|
assert_eq!(res.sort.dms, None);
|
||||||
|
|
||||||
|
// Check that we get the right default "rooms" and "dms" values.
|
||||||
|
let res = res.values();
|
||||||
|
assert_eq!(res.sort.members, vec![
|
||||||
|
SortColumn(SortFieldUser::Server, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldUser::LocalPart, SortOrder::Descending),
|
||||||
|
]);
|
||||||
|
assert_eq!(res.sort.spaces, vec![
|
||||||
|
SortColumn(SortFieldRoom::Favorite, SortOrder::Descending),
|
||||||
|
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
|
||||||
|
]);
|
||||||
|
assert_eq!(res.sort.rooms, Vec::from(DEFAULT_ROOM_SORT));
|
||||||
|
assert_eq!(res.sort.dms, Vec::from(DEFAULT_ROOM_SORT));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_layout() {
|
fn test_parse_layout() {
|
||||||
let user = WindowPath::UserId(user_id!("@user:example.com").to_owned());
|
let user = WindowPath::UserId(user_id!("@user:example.com").to_owned());
|
||||||
|
|
|
@ -28,6 +28,7 @@ use crate::{
|
||||||
ApplicationSettings,
|
ApplicationSettings,
|
||||||
DirectoryValues,
|
DirectoryValues,
|
||||||
ProfileConfig,
|
ProfileConfig,
|
||||||
|
SortOverrides,
|
||||||
TunableValues,
|
TunableValues,
|
||||||
UserColor,
|
UserColor,
|
||||||
UserDisplayStyle,
|
UserDisplayStyle,
|
||||||
|
@ -183,6 +184,7 @@ pub fn mock_tunables() -> TunableValues {
|
||||||
read_receipt_send: true,
|
read_receipt_send: true,
|
||||||
read_receipt_display: true,
|
read_receipt_display: true,
|
||||||
request_timeout: 120,
|
request_timeout: 120,
|
||||||
|
sort: SortOverrides::default().values(),
|
||||||
typing_notice_send: true,
|
typing_notice_send: true,
|
||||||
typing_notice_display: true,
|
typing_notice_display: true,
|
||||||
users: vec![(TEST_USER5.clone(), UserDisplayTunables {
|
users: vec![(TEST_USER5.clone(), UserDisplayTunables {
|
||||||
|
|
|
@ -17,7 +17,9 @@ use matrix_sdk::{
|
||||||
ruma::{
|
ruma::{
|
||||||
events::room::member::MembershipState,
|
events::room::member::MembershipState,
|
||||||
events::tag::{TagName, Tags},
|
events::tag::{TagName, Tags},
|
||||||
|
OwnedRoomAliasId,
|
||||||
OwnedRoomId,
|
OwnedRoomId,
|
||||||
|
RoomAliasId,
|
||||||
RoomId,
|
RoomId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -80,6 +82,10 @@ use crate::base::{
|
||||||
ProgramStore,
|
ProgramStore,
|
||||||
RoomAction,
|
RoomAction,
|
||||||
SendAction,
|
SendAction,
|
||||||
|
SortColumn,
|
||||||
|
SortFieldRoom,
|
||||||
|
SortFieldUser,
|
||||||
|
SortOrder,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{room::RoomState, welcome::WelcomeState};
|
use self::{room::RoomState, welcome::WelcomeState};
|
||||||
|
@ -125,42 +131,87 @@ fn selected_text(s: &str, selected: bool) -> Text {
|
||||||
Text::from(selected_span(s, selected))
|
Text::from(selected_span(s, selected))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn room_cmp(a: &MatrixRoom, b: &MatrixRoom) -> Ordering {
|
/// Sort `Some` to be less than `None` so that list items with values come before those without.
|
||||||
let ca1 = a.canonical_alias();
|
#[inline]
|
||||||
let ca2 = b.canonical_alias();
|
fn some_cmp<T: Ord>(a: Option<T>, b: Option<T>) -> Ordering {
|
||||||
|
match (a, b) {
|
||||||
let ord = match (ca1, ca2) {
|
(Some(a), Some(b)) => a.cmp(&b),
|
||||||
(None, None) => Ordering::Equal,
|
(None, None) => Ordering::Equal,
|
||||||
(None, Some(_)) => Ordering::Greater,
|
(None, Some(_)) => Ordering::Greater,
|
||||||
(Some(_), None) => Ordering::Less,
|
(Some(_), None) => Ordering::Less,
|
||||||
(Some(ca1), Some(ca2)) => ca1.cmp(&ca2),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
ord.then_with(|| a.room_id().cmp(b.room_id()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tag_cmp(a: &Option<Tags>, b: &Option<Tags>) -> Ordering {
|
fn user_cmp(a: &MemberItem, b: &MemberItem, field: &SortFieldUser) -> Ordering {
|
||||||
let (fava, lowa) = a
|
let a_id = a.member.user_id();
|
||||||
.as_ref()
|
let b_id = b.member.user_id();
|
||||||
.map(|tags| {
|
|
||||||
(tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority))
|
|
||||||
})
|
|
||||||
.unwrap_or((false, false));
|
|
||||||
|
|
||||||
let (favb, lowb) = b
|
match field {
|
||||||
.as_ref()
|
SortFieldUser::UserId => a_id.cmp(b_id),
|
||||||
.map(|tags| {
|
SortFieldUser::LocalPart => a_id.localpart().cmp(b_id.localpart()),
|
||||||
(tags.contains_key(&TagName::Favorite), tags.contains_key(&TagName::LowPriority))
|
SortFieldUser::Server => a_id.server_name().cmp(b_id.server_name()),
|
||||||
})
|
SortFieldUser::PowerLevel => {
|
||||||
.unwrap_or((false, false));
|
// Sort higher power levels towards the top of the list.
|
||||||
|
b.member.power_level().cmp(&a.member.power_level())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn room_cmp<T: RoomLikeItem>(a: &T, b: &T, field: &SortFieldRoom) -> Ordering {
|
||||||
|
match field {
|
||||||
|
SortFieldRoom::Favorite => {
|
||||||
|
let fava = a.has_tag(TagName::Favorite);
|
||||||
|
let favb = b.has_tag(TagName::Favorite);
|
||||||
|
|
||||||
// If a has Favorite and b doesn't, it should sort earlier in room list.
|
// If a has Favorite and b doesn't, it should sort earlier in room list.
|
||||||
let cmpf = favb.cmp(&fava);
|
favb.cmp(&fava)
|
||||||
|
},
|
||||||
|
SortFieldRoom::LowPriority => {
|
||||||
|
let lowa = a.has_tag(TagName::LowPriority);
|
||||||
|
let lowb = b.has_tag(TagName::LowPriority);
|
||||||
|
|
||||||
// If a has LowPriority and b doesn't, it should sort later in room list.
|
// If a has LowPriority and b doesn't, it should sort later in room list.
|
||||||
let cmpl = lowa.cmp(&lowb);
|
lowa.cmp(&lowb)
|
||||||
|
},
|
||||||
|
SortFieldRoom::Name => a.name().cmp(b.name()),
|
||||||
|
SortFieldRoom::Alias => some_cmp(a.alias(), b.alias()),
|
||||||
|
SortFieldRoom::RoomId => a.room_id().cmp(b.room_id()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmpl.then(cmpf)
|
/// Compare two rooms according the configured sort criteria.
|
||||||
|
fn room_fields_cmp<T: RoomLikeItem>(
|
||||||
|
a: &T,
|
||||||
|
b: &T,
|
||||||
|
fields: &[SortColumn<SortFieldRoom>],
|
||||||
|
) -> Ordering {
|
||||||
|
for SortColumn(field, order) in fields {
|
||||||
|
match (room_cmp(a, b, field), order) {
|
||||||
|
(Ordering::Equal, _) => continue,
|
||||||
|
(o, SortOrder::Ascending) => return o,
|
||||||
|
(o, SortOrder::Descending) => return o.reverse(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break ties on ascending room id.
|
||||||
|
room_cmp(a, b, &SortFieldRoom::RoomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_fields_cmp(
|
||||||
|
a: &MemberItem,
|
||||||
|
b: &MemberItem,
|
||||||
|
fields: &[SortColumn<SortFieldUser>],
|
||||||
|
) -> Ordering {
|
||||||
|
for SortColumn(field, order) in fields {
|
||||||
|
match (user_cmp(a, b, field), order) {
|
||||||
|
(Ordering::Equal, _) => continue,
|
||||||
|
(o, SortOrder::Ascending) => return o,
|
||||||
|
(o, SortOrder::Descending) => return o.reverse(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break ties on ascending user id.
|
||||||
|
user_cmp(a, b, &SortFieldUser::UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_tags<'a>(tags: &'a Tags, spans: &mut Vec<Span<'a>>, style: Style) {
|
fn append_tags<'a>(tags: &'a Tags, spans: &mut Vec<Span<'a>>, style: Style) {
|
||||||
|
@ -190,6 +241,13 @@ fn append_tags<'a>(tags: &'a Tags, spans: &mut Vec<Span<'a>>, style: Style) {
|
||||||
spans.push(Span::styled(")", style));
|
spans.push(Span::styled(")", style));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait RoomLikeItem {
|
||||||
|
fn room_id(&self) -> &RoomId;
|
||||||
|
fn has_tag(&self, tag: TagName) -> bool;
|
||||||
|
fn alias(&self) -> Option<&RoomAliasId>;
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn room_prompt(
|
fn room_prompt(
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
|
@ -399,7 +457,8 @@ impl WindowOps<IambInfo> for IambWindow {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|room_info| DirectItem::new(room_info, store))
|
.map(|room_info| DirectItem::new(room_info, store))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
items.sort();
|
let fields = &store.application.settings.tunables.sort.dms;
|
||||||
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
|
||||||
state.set(items);
|
state.set(items);
|
||||||
|
|
||||||
|
@ -417,8 +476,13 @@ impl WindowOps<IambInfo> for IambWindow {
|
||||||
|
|
||||||
if need_fetch {
|
if need_fetch {
|
||||||
if let Ok(mems) = store.application.worker.members(room_id.clone()) {
|
if let Ok(mems) = store.application.worker.members(room_id.clone()) {
|
||||||
let items = mems.into_iter().map(|m| MemberItem::new(m, room_id.clone()));
|
let mut items = mems
|
||||||
state.set(items.collect());
|
.into_iter()
|
||||||
|
.map(|m| MemberItem::new(m, room_id.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let fields = &store.application.settings.tunables.sort.members;
|
||||||
|
items.sort_by(|a, b| user_fields_cmp(a, b, fields));
|
||||||
|
state.set(items);
|
||||||
*last_fetch = Some(Instant::now());
|
*last_fetch = Some(Instant::now());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,7 +502,8 @@ impl WindowOps<IambInfo> for IambWindow {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|room_info| RoomItem::new(room_info, store))
|
.map(|room_info| RoomItem::new(room_info, store))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
items.sort();
|
let fields = &store.application.settings.tunables.sort.rooms;
|
||||||
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
|
||||||
state.set(items);
|
state.set(items);
|
||||||
|
|
||||||
|
@ -449,15 +514,18 @@ impl WindowOps<IambInfo> for IambWindow {
|
||||||
.render(area, buf, state);
|
.render(area, buf, state);
|
||||||
},
|
},
|
||||||
IambWindow::SpaceList(state) => {
|
IambWindow::SpaceList(state) => {
|
||||||
let items = store
|
let mut items = store
|
||||||
.application
|
.application
|
||||||
.sync_info
|
.sync_info
|
||||||
.spaces
|
.spaces
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|room| SpaceItem::new(room, store));
|
.map(|room| SpaceItem::new(room, store))
|
||||||
state.set(items.collect());
|
.collect::<Vec<_>>();
|
||||||
state.draw(area, buf, focused, store);
|
let fields = &store.application.settings.tunables.sort.spaces;
|
||||||
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
|
||||||
|
state.set(items);
|
||||||
|
|
||||||
List::new(store)
|
List::new(store)
|
||||||
.empty_message("You haven't joined any spaces yet")
|
.empty_message("You haven't joined any spaces yet")
|
||||||
|
@ -662,6 +730,7 @@ impl Window<IambInfo> for IambWindow {
|
||||||
pub struct RoomItem {
|
pub struct RoomItem {
|
||||||
room_info: MatrixRoomInfo,
|
room_info: MatrixRoomInfo,
|
||||||
name: String,
|
name: String,
|
||||||
|
alias: Option<OwnedRoomAliasId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoomItem {
|
impl RoomItem {
|
||||||
|
@ -671,13 +740,14 @@ impl RoomItem {
|
||||||
|
|
||||||
let info = store.application.get_room_info(room_id.to_owned());
|
let info = store.application.get_room_info(room_id.to_owned());
|
||||||
let name = info.name.clone().unwrap_or_default();
|
let name = info.name.clone().unwrap_or_default();
|
||||||
|
let alias = room.canonical_alias();
|
||||||
info.tags = room_info.deref().1.clone();
|
info.tags = room_info.deref().1.clone();
|
||||||
|
|
||||||
if let Some(alias) = room.canonical_alias() {
|
if let Some(alias) = &alias {
|
||||||
store.application.names.insert(alias.to_string(), room_id.to_owned());
|
store.application.names.insert(alias.to_string(), room_id.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomItem { room_info, name }
|
RoomItem { room_info, name, alias }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -685,34 +755,31 @@ impl RoomItem {
|
||||||
&self.room_info.deref().0
|
&self.room_info.deref().0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn room_id(&self) -> &RoomId {
|
|
||||||
self.room().room_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn tags(&self) -> &Option<Tags> {
|
fn tags(&self) -> &Option<Tags> {
|
||||||
&self.room_info.deref().1
|
&self.room_info.deref().1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for RoomItem {
|
impl RoomLikeItem for RoomItem {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn name(&self) -> &str {
|
||||||
self.room_id() == other.room_id()
|
self.name.as_str()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for RoomItem {}
|
fn alias(&self) -> Option<&RoomAliasId> {
|
||||||
|
self.alias.as_deref()
|
||||||
impl Ord for RoomItem {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
tag_cmp(self.tags(), other.tags()).then_with(|| room_cmp(self.room(), other.room()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for RoomItem {
|
fn room_id(&self) -> &RoomId {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
self.room().room_id()
|
||||||
self.cmp(other).into()
|
}
|
||||||
|
|
||||||
|
fn has_tag(&self, tag: TagName) -> bool {
|
||||||
|
if let Some(tags) = &self.room_info.deref().1 {
|
||||||
|
tags.contains_key(&tag)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -756,14 +823,16 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for RoomItem {
|
||||||
pub struct DirectItem {
|
pub struct DirectItem {
|
||||||
room_info: MatrixRoomInfo,
|
room_info: MatrixRoomInfo,
|
||||||
name: String,
|
name: String,
|
||||||
|
alias: Option<OwnedRoomAliasId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectItem {
|
impl DirectItem {
|
||||||
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self {
|
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self {
|
||||||
let room_id = room_info.deref().0.room_id().to_owned();
|
let room_id = room_info.0.room_id().to_owned();
|
||||||
let name = store.application.get_room_info(room_id).name.clone().unwrap_or_default();
|
let name = store.application.get_room_info(room_id).name.clone().unwrap_or_default();
|
||||||
|
let alias = room_info.0.canonical_alias();
|
||||||
|
|
||||||
DirectItem { room_info, name }
|
DirectItem { room_info, name, alias }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -771,17 +840,34 @@ impl DirectItem {
|
||||||
&self.room_info.deref().0
|
&self.room_info.deref().0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn room_id(&self) -> &RoomId {
|
|
||||||
self.room().room_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn tags(&self) -> &Option<Tags> {
|
fn tags(&self) -> &Option<Tags> {
|
||||||
&self.room_info.deref().1
|
&self.room_info.deref().1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RoomLikeItem for DirectItem {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alias(&self) -> Option<&RoomAliasId> {
|
||||||
|
self.alias.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_tag(&self, tag: TagName) -> bool {
|
||||||
|
if let Some(tags) = &self.room_info.deref().1 {
|
||||||
|
tags.contains_key(&tag)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn room_id(&self) -> &RoomId {
|
||||||
|
self.room().room_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToString for DirectItem {
|
impl ToString for DirectItem {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
return self.name.clone();
|
return self.name.clone();
|
||||||
|
@ -807,26 +893,6 @@ impl ListItem<IambInfo> for DirectItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for DirectItem {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.room_id() == other.room_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for DirectItem {}
|
|
||||||
|
|
||||||
impl Ord for DirectItem {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
tag_cmp(self.tags(), other.tags()).then_with(|| room_cmp(self.room(), other.room()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for DirectItem {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
self.cmp(other).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Promptable<ProgramContext, ProgramStore, IambInfo> for DirectItem {
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for DirectItem {
|
||||||
fn prompt(
|
fn prompt(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -840,51 +906,58 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for DirectItem {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SpaceItem {
|
pub struct SpaceItem {
|
||||||
room: MatrixRoom,
|
room_info: MatrixRoomInfo,
|
||||||
name: String,
|
name: String,
|
||||||
|
alias: Option<OwnedRoomAliasId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpaceItem {
|
impl SpaceItem {
|
||||||
fn new(room: MatrixRoom, store: &mut ProgramStore) -> Self {
|
fn new(room_info: MatrixRoomInfo, store: &mut ProgramStore) -> Self {
|
||||||
let room_id = room.room_id();
|
let room_id = room_info.0.room_id();
|
||||||
let name = store
|
let name = store
|
||||||
.application
|
.application
|
||||||
.get_room_info(room_id.to_owned())
|
.get_room_info(room_id.to_owned())
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let alias = room_info.0.canonical_alias();
|
||||||
|
|
||||||
if let Some(alias) = room.canonical_alias() {
|
if let Some(alias) = &alias {
|
||||||
store.application.names.insert(alias.to_string(), room_id.to_owned());
|
store.application.names.insert(alias.to_string(), room_id.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
SpaceItem { room, name }
|
SpaceItem { room_info, name, alias }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn room(&self) -> &MatrixRoom {
|
||||||
|
&self.room_info.deref().0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for SpaceItem {
|
impl RoomLikeItem for SpaceItem {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn name(&self) -> &str {
|
||||||
self.room.room_id() == other.room.room_id()
|
self.name.as_str()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for SpaceItem {}
|
fn room_id(&self) -> &RoomId {
|
||||||
|
self.room().room_id()
|
||||||
impl Ord for SpaceItem {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
room_cmp(&self.room, &other.room)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for SpaceItem {
|
fn alias(&self) -> Option<&RoomAliasId> {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
self.alias.as_deref()
|
||||||
self.cmp(other).into()
|
}
|
||||||
|
|
||||||
|
fn has_tag(&self, _: TagName) -> bool {
|
||||||
|
// I think that spaces can technically have tags, but afaik no client
|
||||||
|
// exposes them, so we'll just always return false here for now.
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for SpaceItem {
|
impl ToString for SpaceItem {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
return self.room.room_id().to_string();
|
return self.room_id().to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,7 +967,7 @@ impl ListItem<IambInfo> for SpaceItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_word(&self) -> Option<String> {
|
fn get_word(&self) -> Option<String> {
|
||||||
self.room.room_id().to_string().into()
|
self.room_id().to_string().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -905,7 +978,7 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for SpaceItem {
|
||||||
ctx: &ProgramContext,
|
ctx: &ProgramContext,
|
||||||
_: &mut ProgramStore,
|
_: &mut ProgramStore,
|
||||||
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
) -> EditResult<Vec<(ProgramAction, ProgramContext)>, IambInfo> {
|
||||||
room_prompt(self.room.room_id(), act, ctx)
|
room_prompt(self.room_id(), act, ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1200,3 +1273,93 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for MemberItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use matrix_sdk::ruma::{room_alias_id, server_name};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
struct TestRoomItem {
|
||||||
|
room_id: OwnedRoomId,
|
||||||
|
tags: Vec<TagName>,
|
||||||
|
alias: Option<OwnedRoomAliasId>,
|
||||||
|
name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoomLikeItem for &TestRoomItem {
|
||||||
|
fn room_id(&self) -> &RoomId {
|
||||||
|
self.room_id.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_tag(&self, tag: TagName) -> bool {
|
||||||
|
self.tags.contains(&tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alias(&self) -> Option<&RoomAliasId> {
|
||||||
|
self.alias.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort_rooms() {
|
||||||
|
let server = server_name!("example.com");
|
||||||
|
|
||||||
|
let room1 = TestRoomItem {
|
||||||
|
room_id: RoomId::new(server).to_owned(),
|
||||||
|
tags: vec![TagName::Favorite],
|
||||||
|
alias: Some(room_alias_id!("#room1:example.com").to_owned()),
|
||||||
|
name: "Z",
|
||||||
|
};
|
||||||
|
|
||||||
|
let room2 = TestRoomItem {
|
||||||
|
room_id: RoomId::new(server).to_owned(),
|
||||||
|
tags: vec![],
|
||||||
|
alias: Some(room_alias_id!("#a:example.com").to_owned()),
|
||||||
|
name: "Unnamed Room",
|
||||||
|
};
|
||||||
|
|
||||||
|
let room3 = TestRoomItem {
|
||||||
|
room_id: RoomId::new(server).to_owned(),
|
||||||
|
tags: vec![],
|
||||||
|
alias: None,
|
||||||
|
name: "Cool Room",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sort by Name ascending.
|
||||||
|
let mut rooms = vec![&room1, &room2, &room3];
|
||||||
|
let fields = &[SortColumn(SortFieldRoom::Name, SortOrder::Ascending)];
|
||||||
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
assert_eq!(rooms, vec![&room3, &room2, &room1]);
|
||||||
|
|
||||||
|
// Sort by Name descending.
|
||||||
|
let mut rooms = vec![&room1, &room2, &room3];
|
||||||
|
let fields = &[SortColumn(SortFieldRoom::Name, SortOrder::Descending)];
|
||||||
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
assert_eq!(rooms, vec![&room1, &room2, &room3]);
|
||||||
|
|
||||||
|
// Sort by Favorite and Alias before Name to show order matters.
|
||||||
|
let mut rooms = vec![&room1, &room2, &room3];
|
||||||
|
let fields = &[
|
||||||
|
SortColumn(SortFieldRoom::Favorite, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
|
||||||
|
];
|
||||||
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
assert_eq!(rooms, vec![&room1, &room2, &room3]);
|
||||||
|
|
||||||
|
// Now flip order of Favorite with Descending
|
||||||
|
let mut rooms = vec![&room1, &room2, &room3];
|
||||||
|
let fields = &[
|
||||||
|
SortColumn(SortFieldRoom::Favorite, SortOrder::Descending),
|
||||||
|
SortColumn(SortFieldRoom::Alias, SortOrder::Ascending),
|
||||||
|
SortColumn(SortFieldRoom::Name, SortOrder::Ascending),
|
||||||
|
];
|
||||||
|
rooms.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
assert_eq!(rooms, vec![&room2, &room3, &room1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use modalkit::{
|
||||||
|
|
||||||
use crate::base::{IambBufferId, IambInfo, ProgramStore, RoomFocus};
|
use crate::base::{IambBufferId, IambInfo, ProgramStore, RoomFocus};
|
||||||
|
|
||||||
use crate::windows::RoomItem;
|
use crate::windows::{room_fields_cmp, RoomItem};
|
||||||
|
|
||||||
const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(5);
|
const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ impl<'a> StatefulWidget for Space<'a> {
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(members) => {
|
Ok(members) => {
|
||||||
let items = members
|
let mut items = members
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|id| {
|
.filter_map(|id| {
|
||||||
let (room, _, tags) =
|
let (room, _, tags) =
|
||||||
|
@ -133,7 +133,9 @@ impl<'a> StatefulWidget for Space<'a> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
|
let fields = &self.store.application.settings.tunables.sort.rooms;
|
||||||
|
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
|
||||||
|
|
||||||
state.list.set(items);
|
state.list.set(items);
|
||||||
state.last_fetch = Some(Instant::now());
|
state.last_fetch = Some(Instant::now());
|
||||||
|
|
|
@ -330,34 +330,30 @@ async fn refresh_rooms(client: &Client, store: &AsyncProgramStore) {
|
||||||
|
|
||||||
for room in client.invited_rooms().into_iter() {
|
for room in client.invited_rooms().into_iter() {
|
||||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty).to_string();
|
let name = room.display_name().await.unwrap_or(DisplayName::Empty).to_string();
|
||||||
|
let tags = room.tags().await.unwrap_or_default();
|
||||||
|
|
||||||
names.push((room.room_id().to_owned(), name));
|
names.push((room.room_id().to_owned(), name));
|
||||||
|
|
||||||
if room.is_direct() {
|
if room.is_direct() {
|
||||||
let tags = room.tags().await.unwrap_or_default();
|
|
||||||
|
|
||||||
dms.push(Arc::new((room.into(), tags)));
|
dms.push(Arc::new((room.into(), tags)));
|
||||||
} else if room.is_space() {
|
} else if room.is_space() {
|
||||||
spaces.push(room.into());
|
spaces.push(Arc::new((room.into(), tags)));
|
||||||
} else {
|
} else {
|
||||||
let tags = room.tags().await.unwrap_or_default();
|
|
||||||
|
|
||||||
rooms.push(Arc::new((room.into(), tags)));
|
rooms.push(Arc::new((room.into(), tags)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for room in client.joined_rooms().into_iter() {
|
for room in client.joined_rooms().into_iter() {
|
||||||
let name = room.display_name().await.unwrap_or(DisplayName::Empty).to_string();
|
let name = room.display_name().await.unwrap_or(DisplayName::Empty).to_string();
|
||||||
|
let tags = room.tags().await.unwrap_or_default();
|
||||||
|
|
||||||
names.push((room.room_id().to_owned(), name));
|
names.push((room.room_id().to_owned(), name));
|
||||||
|
|
||||||
if room.is_direct() {
|
if room.is_direct() {
|
||||||
let tags = room.tags().await.unwrap_or_default();
|
|
||||||
|
|
||||||
dms.push(Arc::new((room.into(), tags)));
|
dms.push(Arc::new((room.into(), tags)));
|
||||||
} else if room.is_space() {
|
} else if room.is_space() {
|
||||||
spaces.push(room.into());
|
spaces.push(Arc::new((room.into(), tags)));
|
||||||
} else {
|
} else {
|
||||||
let tags = room.tags().await.unwrap_or_default();
|
|
||||||
|
|
||||||
rooms.push(Arc::new((room.into(), tags)));
|
rooms.push(Arc::new((room.into(), tags)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue