Skip to content

Commit c6a25e9

Browse files
committed
docs(spaces): add documentation, comments and examples
1 parent 571daa7 commit c6a25e9

File tree

6 files changed

+142
-1
lines changed

6 files changed

+142
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/matrix-sdk-ui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ assert-json-diff.workspace = true
6767
assert_matches.workspace = true
6868
assert_matches2.workspace = true
6969
eyeball-im-util.workspace = true
70+
futures-executor.workspace = true
7071
matrix-sdk = { workspace = true, features = ["testing", "sqlite"] }
7172
matrix-sdk-test.workspace = true
7273
matrix-sdk-test-utils.workspace = true

crates/matrix-sdk-ui/src/spaces/graph.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ impl SpaceGraphNode {
3030
}
3131

3232
#[derive(Debug)]
33+
/// A graph structure representing a space hierarchy. Contains functionality
34+
/// for mapping parent-child relationships between rooms, removing cycles and
35+
/// retrieving top-level parents/roots.
3336
pub struct SpaceGraph {
3437
nodes: BTreeMap<OwnedRoomId, SpaceGraphNode>,
3538
}
@@ -45,18 +48,25 @@ impl SpaceGraph {
4548
Self { nodes: BTreeMap::new() }
4649
}
4750

51+
/// Returns the root nodes of the graph, which are nodes without any
52+
/// parents.
4853
pub fn root_nodes(&self) -> Vec<&OwnedRoomId> {
4954
self.nodes.values().filter(|node| node.parents.is_empty()).map(|node| &node.id).collect()
5055
}
5156

57+
/// Returns the children of a given node. If the node does not exist, it
58+
/// returns an empty vector.
5259
pub fn children_of(&self, node_id: &OwnedRoomId) -> Vec<&OwnedRoomId> {
5360
self.nodes.get(node_id).map_or(vec![], |node| node.children.iter().collect())
5461
}
5562

63+
/// Adds a node to the graph. If the node already exists, it does nothing.
5664
pub fn add_node(&mut self, node_id: OwnedRoomId) {
5765
self.nodes.entry(node_id.clone()).or_insert(SpaceGraphNode::new(node_id));
5866
}
5967

68+
/// Adds a directed edge from `parent_id` to `child_id`, creating nodes if
69+
/// they do not already exist in the graph.
6070
pub fn add_edge(&mut self, parent_id: OwnedRoomId, child_id: OwnedRoomId) {
6171
self.nodes.entry(parent_id.clone()).or_insert(SpaceGraphNode::new(parent_id.clone()));
6272

@@ -66,6 +76,9 @@ impl SpaceGraph {
6676
self.nodes.get_mut(&child_id).unwrap().parents.insert(parent_id);
6777
}
6878

79+
/// Removes cycles in the graph by performing a depth-first search (DFS) and
80+
/// remembering the visited nodes. If a node is revisited while still in the
81+
/// current path (i.e. it's on the stack), it indicates a cycle.
6982
pub fn remove_cycles(&mut self) {
7083
let mut visited = BTreeSet::new();
7184
let mut stack = BTreeSet::new();

crates/matrix-sdk-ui/src/spaces/mod.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@
1414

1515
//! High level interfaces for working with Spaces
1616
//!
17-
//! See [`SpaceService`] for details.
17+
//! The `SpaceService` is an UI oriented, high-level interface for working with
18+
//! Matrix Spaces. It provides methods to retrieve joined spaces, subscribe
19+
//! to updates, and navigate space hierarchies.
20+
//!
21+
//! It consists of 3 main components:
22+
//! - `SpaceService`: The main service for managing spaces. It
23+
//! - `SpaceGraph`: An utility that maps the `m.space.parent` and
24+
//! `m.space.child` fields into a graph structure, removing cycles and
25+
//! providing access to top level parents.
26+
//! - `SpaceRoomList`: A component for retrieving a space's children rooms and
27+
//! their details.
1828
1929
use std::sync::Arc;
2030

@@ -39,6 +49,43 @@ pub mod graph;
3949
pub mod room;
4050
pub mod room_list;
4151

52+
/// The main entry point into the Spaces facilities.
53+
///
54+
/// The spaces service is responsible for retrieving one's joined rooms,
55+
/// building a graph out of their `m.space.parent` and `m.space.child` state
56+
/// events, and providing access to the top-level spaces and their children.
57+
///
58+
/// # Examples
59+
///
60+
/// ```no_run
61+
/// use futures_util::StreamExt;
62+
/// use matrix_sdk::Client;
63+
/// use matrix_sdk_ui::spaces::SpaceService;
64+
/// use ruma::owned_room_id;
65+
///
66+
/// # futures_executor::block_on(async {
67+
/// let client: Client = todo!();
68+
/// let space_service = SpaceService::new(client.clone());
69+
///
70+
/// // Get a list of all the joined spaces
71+
/// let joined_spaces = space_service.joined_spaces().await;
72+
///
73+
/// // And subscribe to changes on them
74+
/// // `initial_values` is equal to `joined_spaces` if nothing changed meanwhile
75+
/// let (initial_values, stream) = space_service.subscribe_to_joined_spaces();
76+
///
77+
/// while let Some(diffs) = stream.next().await {
78+
/// println!("Received joined spaces updates: {diffs:?}");
79+
/// }
80+
///
81+
/// // Get a list of all the rooms in a particular space
82+
/// let room_list = space_service
83+
/// .space_room_list(owned_room_id!("!some_space:example.org"));
84+
///
85+
/// // Which can be used to retrieve information about the children rooms
86+
/// let children = room_list.rooms();
87+
/// # })
88+
/// ```
4289
pub struct SpaceService {
4390
client: Client,
4491

@@ -64,6 +111,8 @@ impl SpaceService {
64111
}
65112
}
66113

114+
/// Subscribes to updates on the joined spaces list. If space rooms are
115+
/// joined or left, the stream will yield diffs that reflect the changes.
67116
pub fn subscribe_to_joined_spaces(
68117
&self,
69118
) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
@@ -96,6 +145,9 @@ impl SpaceService {
96145
self.joined_spaces.lock().subscribe().into_values_and_batched_stream()
97146
}
98147

148+
/// Returns a list of all the top-level joined spaces. It will eagerly
149+
/// compute the latest version and also notify subscribers if there were
150+
/// any changes.
99151
pub async fn joined_spaces(&self) -> Vec<SpaceRoom> {
100152
let spaces = Self::joined_spaces_for(&self.client).await;
101153

@@ -104,6 +156,7 @@ impl SpaceService {
104156
spaces
105157
}
106158

159+
/// Returns a `SpaceRoomList` for the given space ID.
107160
pub fn space_room_list(&self, space_id: OwnedRoomId) -> SpaceRoomList {
108161
SpaceRoomList::new(self.client.clone(), space_id)
109162
}
@@ -127,8 +180,11 @@ impl SpaceService {
127180
.filter_map(|room| room.is_space().then_some(room))
128181
.collect::<Vec<_>>();
129182

183+
// Build a graph to hold the parent-child relations
130184
let mut graph = SpaceGraph::new();
131185

186+
// Iterate over all joined spaces and populate the graph with edges based
187+
// on `m.space.parent` and `m.space.child` state events.
132188
for space in joined_spaces.iter() {
133189
graph.add_node(space.room_id().to_owned());
134190

@@ -167,6 +223,8 @@ impl SpaceService {
167223
}
168224
}
169225

226+
// Remove cycles from the graph. This is important because they are not
227+
// enforced backend side.
170228
graph.remove_cycles();
171229

172230
let root_notes = graph.root_nodes();

crates/matrix-sdk-ui/src/spaces/room.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use ruma::{
1919
room::{JoinRuleSummary, RoomSummary, RoomType},
2020
};
2121

22+
/// Structure representing a room in a space and aggregated information
23+
/// relevant to the UI layer.
2224
#[derive(Debug, Clone, PartialEq)]
2325
pub struct SpaceRoom {
2426
pub room_id: OwnedRoomId,

crates/matrix-sdk-ui/src/spaces/room_list.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,65 @@ pub enum SpaceRoomListPaginationState {
3333
Loading,
3434
}
3535

36+
/// The `SpaceRoomList`represents a paginated list of direct rooms
37+
/// that belong to a particular space.
38+
///
39+
/// It can be used to paginate through the list (and have live updates on the
40+
/// pagination state) as well as subscribe to changes as rooms are joined or
41+
/// left.
42+
///
43+
/// # Examples
44+
///
45+
/// ```no_run
46+
/// use futures_util::StreamExt;
47+
/// use matrix_sdk::Client;
48+
/// use matrix_sdk_ui::spaces::{
49+
/// SpaceService, room_list::SpaceRoomListPaginationState,
50+
/// };
51+
/// use ruma::owned_room_id;
52+
///
53+
/// # futures_executor::block_on(async {
54+
/// let client: Client = todo!();
55+
/// let space_service = SpaceService::new(client.clone());
56+
///
57+
/// // Get a list of all the rooms in a particular space
58+
/// let room_list = space_service
59+
/// .space_room_list(owned_room_id!("!some_space:example.org"));
60+
///
61+
/// // Start off with an empty and idle list
62+
/// room_list.rooms().is_empty();
63+
///
64+
/// assert_eq!(
65+
/// room_list.pagination_state(),
66+
/// SpaceRoomListPaginationState::Idle { end_reached: false }
67+
/// );
68+
///
69+
/// // Subscribe to pagination state updates
70+
/// let pagination_state_stream =
71+
/// room_list.subscribe_to_pagination_state_updates();
72+
///
73+
/// // And to room list updates
74+
/// let (_, room_stream) = room_list.subscribe_to_room_updates();
75+
///
76+
/// // spawn {
77+
/// while let Some(pagination_state) = pagination_state_stream.next().await {
78+
/// println!("Received pagination state update: {pagination_state:?}");
79+
/// };
80+
/// // }
81+
///
82+
/// // spawn {
83+
/// while let Some(diffs) = room_stream.next().await {
84+
/// println!("Received room list update: {diffs:?}");
85+
/// };
86+
/// // }
87+
///
88+
/// // Ask the room to load the next page
89+
/// room_list.paginate().await.unwrap();
90+
///
91+
/// // And, if successful, rooms are available
92+
/// let rooms = room_list.rooms();
93+
/// # })
94+
/// ```
3695
pub struct SpaceRoomList {
3796
client: Client,
3897

@@ -53,6 +112,7 @@ impl Drop for SpaceRoomList {
53112
}
54113
}
55114
impl SpaceRoomList {
115+
/// Creates a new `SpaceRoomList` for the given space identifier.
56116
pub fn new(client: Client, parent_space_id: OwnedRoomId) -> Self {
57117
let rooms = Arc::new(Mutex::new(ObservableVector::<SpaceRoom>::new()));
58118

@@ -105,26 +165,32 @@ impl SpaceRoomList {
105165
}
106166
}
107167

168+
/// Returns the room list is currently paginating or not.
108169
pub fn pagination_state(&self) -> SpaceRoomListPaginationState {
109170
self.pagination_state.get()
110171
}
111172

173+
/// Subscribe to pagination updates.
112174
pub fn subscribe_to_pagination_state_updates(
113175
&self,
114176
) -> Subscriber<SpaceRoomListPaginationState> {
115177
self.pagination_state.subscribe()
116178
}
117179

180+
/// Return the current list of rooms.
118181
pub fn rooms(&self) -> Vec<SpaceRoom> {
119182
self.rooms.lock().iter().cloned().collect_vec()
120183
}
121184

185+
/// Subscribes to room list updates.
122186
pub fn subscribe_to_room_updates(
123187
&self,
124188
) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
125189
self.rooms.lock().subscribe().into_values_and_batched_stream()
126190
}
127191

192+
/// As the list to retrieve the next page if the end hasn't been reached
193+
/// yet. Otherwise it no-ops.
128194
pub async fn paginate(&self) -> Result<(), Error> {
129195
match *self.pagination_state.read() {
130196
SpaceRoomListPaginationState::Idle { end_reached } if end_reached => {

0 commit comments

Comments
 (0)