[fractal/fractal-next] room: Add PowerLevels GObject
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] room: Add PowerLevels GObject
- Date: Fri, 13 Aug 2021 18:46:42 +0000 (UTC)
commit aa649a89b0f5933258c98cd72ab0b0f909472c8b
Author: Kai A. Hiller <V02460 gmail com>
Date: Thu Aug 12 15:51:22 2021 +0200
room: Add PowerLevels GObject
src/meson.build | 1 +
src/session/room/mod.rs | 2 +
src/session/room/power_levels.rs | 141 +++++++++++++++++++++++++++++++++++++++
src/session/room/room.rs | 20 +++++-
src/utils.rs | 9 ++-
5 files changed, 171 insertions(+), 2 deletions(-)
---
diff --git a/src/meson.build b/src/meson.build
index 032fed6d..ee7f4d2a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -58,6 +58,7 @@ sources = files(
'session/room/item.rs',
'session/room/member.rs',
'session/room/mod.rs',
+ 'session/room/power_levels.rs',
'session/room/room.rs',
'session/room/room_type.rs',
'session/room_list.rs',
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index fa8d4f82..c6920c6d 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -2,6 +2,7 @@ mod event;
mod highlight_flags;
mod item;
mod member;
+mod power_levels;
mod room;
mod room_type;
mod timeline;
@@ -11,6 +12,7 @@ pub use self::highlight_flags::HighlightFlags;
pub use self::item::Item;
pub use self::item::ItemType;
pub use self::member::Member;
+pub use self::power_levels::{PowerLevels, RoomAction};
pub use self::room::Room;
pub use self::room_type::RoomType;
pub use self::timeline::Timeline;
diff --git a/src/session/room/power_levels.rs b/src/session/room/power_levels.rs
new file mode 100644
index 00000000..4bd3869d
--- /dev/null
+++ b/src/session/room/power_levels.rs
@@ -0,0 +1,141 @@
+use gtk::glib;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use matrix_sdk::ruma::events::room::power_levels::PowerLevelsEventContent;
+use matrix_sdk::ruma::events::{EventType, SyncStateEvent};
+
+use crate::session::room::Member;
+use crate::utils::prop_expr;
+
+#[derive(Clone, Debug, Default, glib::GBoxed)]
+#[gboxed(type_name = "BoxedPowerLevelsEventContent")]
+pub struct BoxedPowerLevelsEventContent(PowerLevelsEventContent);
+
+mod imp {
+ use super::*;
+ use once_cell::sync::Lazy;
+ use std::cell::RefCell;
+
+ #[derive(Debug, Default)]
+ pub struct PowerLevels {
+ pub content: RefCell<BoxedPowerLevelsEventContent>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for PowerLevels {
+ const NAME: &'static str = "PowerLevels";
+ type Type = super::PowerLevels;
+ type ParentType = glib::Object;
+ }
+
+ impl ObjectImpl for PowerLevels {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpec::new_boxed(
+ "power-levels",
+ "Power levels",
+ "Ruma struct containing all power level information of a room",
+ BoxedPowerLevelsEventContent::static_type(),
+ glib::ParamFlags::READABLE,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "power-levels" => obj.power_levels().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+}
+
+glib::wrapper! {
+ pub struct PowerLevels(ObjectSubclass<imp::PowerLevels>);
+}
+
+impl PowerLevels {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create PowerLevels")
+ }
+
+ pub fn power_levels(&self) -> BoxedPowerLevelsEventContent {
+ let priv_ = imp::PowerLevels::from_instance(self);
+ priv_.content.borrow().clone()
+ }
+
+ /// Returns the power level minimally required to perform the given action.
+ pub fn min_level_for_room_action(&self, room_action: &RoomAction) -> u32 {
+ let priv_ = imp::PowerLevels::from_instance(self);
+ let content = priv_.content.borrow();
+ min_level_for_room_action(&content.0, room_action)
+ }
+
+ /// Creates an expression that is true when the user is allowed the given action.
+ pub fn new_allowed_expr(&self, member: &Member, room_action: RoomAction) -> gtk::Expression {
+ gtk::ClosureExpression::new(
+ move |args| {
+ let power_level: u32 = args[1].get().unwrap();
+ let content = args[2].get::<BoxedPowerLevelsEventContent>().unwrap().0;
+ power_level >= min_level_for_room_action(&content, &room_action)
+ },
+ &[
+ prop_expr(member, "power-level"),
+ prop_expr(self, "power-levels"),
+ ],
+ )
+ .upcast()
+ }
+
+ /// Updates the power levels from the given event.
+ pub fn update_from_event(&self, event: SyncStateEvent<PowerLevelsEventContent>) {
+ let priv_ = imp::PowerLevels::from_instance(self);
+ let content = BoxedPowerLevelsEventContent(event.content);
+ priv_.content.replace(content);
+ self.notify("power-levels");
+ }
+}
+
+impl Default for PowerLevels {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Returns the power level minimally required to perform the given action.
+fn min_level_for_room_action(content: &PowerLevelsEventContent, room_action: &RoomAction) -> u32 {
+ let power_level = i64::from(match room_action {
+ RoomAction::Ban => content.ban,
+ RoomAction::Invite => content.invite,
+ RoomAction::Kick => content.kick,
+ RoomAction::Redact => content.redact,
+ RoomAction::RoomNotification => content.notifications.room,
+ RoomAction::StateEvent(event_type) => *content
+ .events
+ .get(event_type)
+ .unwrap_or(&content.state_default),
+ RoomAction::MessageEvent(event_type) => *content
+ .events
+ .get(event_type)
+ .unwrap_or(&content.events_default),
+ });
+
+ if (0..=100).contains(&power_level) {
+ power_level as u32
+ } else {
+ 0
+ }
+}
+
+/// Actions that require different power levels to perform them.
+pub enum RoomAction {
+ Ban,
+ Invite,
+ Kick,
+ Redact,
+ RoomNotification,
+ StateEvent(EventType),
+ MessageEvent(EventType),
+}
diff --git a/src/session/room/room.rs b/src/session/room/room.rs
index 3538987e..346db85b 100644
--- a/src/session/room/room.rs
+++ b/src/session/room/room.rs
@@ -31,7 +31,9 @@ use std::convert::TryFrom;
use crate::components::{LabelWithWidgets, Pill};
use crate::prelude::*;
-use crate::session::room::{Event, HighlightFlags, Member, RoomType, Timeline};
+use crate::session::room::{
+ Event, HighlightFlags, Member, PowerLevels, RoomAction, RoomType, Timeline,
+};
use crate::session::{Avatar, Session};
use crate::utils::do_async;
use crate::Error;
@@ -56,6 +58,7 @@ mod imp {
/// The user who sent the invite to this room. This is only set when this room is an invitiation.
pub inviter: RefCell<Option<Member>>,
pub members_loaded: Cell<bool>,
+ pub power_levels: RefCell<PowerLevels>,
}
#[glib::object_subclass]
@@ -486,6 +489,11 @@ impl Room {
.filter(|topic| !topic.is_empty() && topic.find(|c: char| !c.is_whitespace()).is_some())
}
+ pub fn power_levels(&self) -> PowerLevels {
+ let priv_ = imp::Room::from_instance(self);
+ priv_.power_levels.borrow().clone()
+ }
+
pub fn inviter(&self) -> Option<Member> {
let priv_ = imp::Room::from_instance(self);
priv_.inviter.borrow().clone()
@@ -563,6 +571,9 @@ impl Room {
AnySyncRoomEvent::State(AnySyncStateEvent::RoomTopic(_)) => {
self.notify("topic");
}
+ AnySyncRoomEvent::State(AnySyncStateEvent::RoomPowerLevels(event)) => {
+ self.power_levels().update_from_event(event);
+ }
_ => {}
}
}
@@ -695,6 +706,13 @@ impl Room {
}
}
+ /// Creates an expression that is true when the user is allowed the given action.
+ pub fn new_allowed_expr(&self, room_action: RoomAction) -> gtk::Expression {
+ let user_id = self.session().user().user_id();
+ let member = self.member_by_id(user_id);
+ self.power_levels().new_allowed_expr(&member, room_action)
+ }
+
pub async fn accept_invite(&self) -> Result<(), Error> {
let matrix_room = self.matrix_room();
diff --git a/src/utils.rs b/src/utils.rs
index 6642b57e..7b5e8d3b 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -33,7 +33,8 @@ macro_rules! event_from_sync_event {
}
use crate::RUNTIME;
-use gtk::glib;
+use gtk::gio::prelude::*;
+use gtk::glib::{self, Object};
use std::future::Future;
/// Execute a future on a tokio runtime and spawn a future on the local thread to handle the result
pub fn do_async<
@@ -54,3 +55,9 @@ pub fn do_async<
RUNTIME.spawn(async move { sender.send(tokio_fut.await) });
}
+
+/// Returns an expression looking up the given property on `object`.
+pub fn prop_expr<T: IsA<Object>>(object: &T, prop: &str) -> gtk::Expression {
+ let obj_expr = gtk::ConstantExpression::new(object).upcast();
+ gtk::PropertyExpression::new(T::static_type(), Some(&obj_expr), prop).upcast()
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]