Skip to content

Commit c25bfc3

Browse files
authored
fix: current view should be updated on a per user basis instead of workspace (#363)
1 parent 27394a4 commit c25bfc3

File tree

2 files changed

+144
-5
lines changed

2 files changed

+144
-5
lines changed

collab-folder/src/folder.rs

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::section::{Section, SectionItem, SectionMap};
2020
use crate::view::view_from_map_ref;
2121
use crate::{
2222
impl_section_op, subscribe_folder_change, FolderData, ParentChildRelations, SectionChangeSender,
23-
TrashInfo, View, ViewUpdate, ViewsMap, Workspace,
23+
SpacePermission, TrashInfo, View, ViewUpdate, ViewsMap, Workspace,
2424
};
2525

2626
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
@@ -54,6 +54,7 @@ impl AsRef<str> for UserId {
5454
const VIEWS: &str = "views";
5555
const PARENT_CHILD_VIEW_RELATION: &str = "relation";
5656
const CURRENT_VIEW: &str = "current_view";
57+
const CURRENT_VIEW_FOR_USER: &str = "current_view_for_user";
5758

5859
pub(crate) const FAVORITES_V1: &str = "favorites";
5960
const SECTION: &str = "section";
@@ -546,7 +547,10 @@ impl FolderBody {
546547
}
547548

548549
meta.insert(&mut txn, FOLDER_WORKSPACE_ID, workspace_id);
549-
meta.insert(&mut txn, CURRENT_VIEW, folder_data.current_view);
550+
// For compatibility with older collab library which doesn't use CURRENT_VIEW_FOR_USER.
551+
meta.insert(&mut txn, CURRENT_VIEW, folder_data.current_view.clone());
552+
let current_view_for_user = meta.get_or_init_map(&mut txn, CURRENT_VIEW_FOR_USER);
553+
current_view_for_user.insert(&mut txn, uid.as_ref(), folder_data.current_view.clone());
550554

551555
if let Some(fav_section) = section.section_op(&txn, Section::Favorite) {
552556
for (uid, sections) in folder_data.favorites {
@@ -732,12 +736,64 @@ impl FolderBody {
732736
Some(view)
733737
}
734738

739+
pub fn get_child_of_first_public_view<T: ReadTxn>(&self, txn: &T) -> Option<String> {
740+
self
741+
.get_workspace_id(txn)
742+
.and_then(|workspace_id| self.views.get_view(txn, &workspace_id))
743+
.and_then(|root_view| {
744+
let first_public_space_view_id_with_child =
745+
root_view
746+
.children
747+
.iter()
748+
.find(|space_id| match self.views.get_view(txn, space_id) {
749+
Some(space_view) => {
750+
let is_public_space = space_view
751+
.space_info()
752+
.map(|info| info.space_permission == SpacePermission::PublicToAll)
753+
.unwrap_or(false);
754+
let has_children = !space_view.children.is_empty();
755+
is_public_space && has_children
756+
},
757+
None => false,
758+
});
759+
first_public_space_view_id_with_child.map(|v| v.id.clone())
760+
})
761+
.and_then(|first_public_space_view_id_with_child| {
762+
self
763+
.views
764+
.get_view(txn, &first_public_space_view_id_with_child)
765+
})
766+
.and_then(|first_public_space_view_with_child| {
767+
first_public_space_view_with_child
768+
.children
769+
.iter()
770+
.next()
771+
.map(|first_child| first_child.id.clone())
772+
})
773+
}
774+
735775
pub fn get_current_view<T: ReadTxn>(&self, txn: &T) -> Option<String> {
736-
self.meta.get_with_txn(txn, CURRENT_VIEW)
776+
// Fallback to CURRENT_VIEW if CURRENT_VIEW_FOR_USER is not present. This could happen for
777+
// workspace folder created by older version of the app before CURRENT_VIEW_FOR_USER is introduced.
778+
// If user cannot be found in CURRENT_VIEW_FOR_USER, use the first child of the first public space
779+
// which has children.
780+
let current_view_for_user_map = match self.meta.get(txn, CURRENT_VIEW_FOR_USER) {
781+
Some(YrsValue::YMap(map)) => Some(map),
782+
_ => None,
783+
};
784+
match current_view_for_user_map {
785+
Some(current_view_for_user) => {
786+
let view_for_user: Option<String> =
787+
current_view_for_user.get_with_txn(txn, self.uid.as_ref());
788+
view_for_user.or(self.get_child_of_first_public_view(txn))
789+
},
790+
None => self.meta.get_with_txn(txn, CURRENT_VIEW),
791+
}
737792
}
738793

739794
pub fn set_current_view(&self, txn: &mut TransactionMut, view: String) {
740-
self.meta.try_update(txn, CURRENT_VIEW, view);
795+
let current_view_for_user = self.meta.get_or_init_map(txn, CURRENT_VIEW_FOR_USER);
796+
current_view_for_user.try_update(txn, self.uid.0.clone(), view);
741797
}
742798
}
743799

@@ -761,3 +817,84 @@ pub fn default_folder_data(workspace_id: &str) -> FolderData {
761817
private: HashMap::new(),
762818
}
763819
}
820+
821+
#[cfg(test)]
822+
mod tests {
823+
use collab::{core::origin::CollabOrigin, preclude::Collab};
824+
825+
use crate::{
826+
Folder, FolderData, RepeatedViewIdentifier, SpaceInfo, UserId, View, ViewIdentifier, Workspace,
827+
};
828+
829+
#[test]
830+
pub fn test_set_and_get_current_view() {
831+
let current_time = chrono::Utc::now().timestamp();
832+
let workspace_id = "1234";
833+
let uid = 1;
834+
let collab = Collab::new_with_origin(CollabOrigin::Empty, workspace_id, vec![], false);
835+
let view_1 = View::new(
836+
"view_1".to_string(),
837+
workspace_id.to_string(),
838+
"View 1".to_string(),
839+
crate::ViewLayout::Document,
840+
Some(uid),
841+
);
842+
let view_1_id = view_1.id.clone();
843+
let view_2 = View::new(
844+
"view_2".to_string(),
845+
workspace_id.to_string(),
846+
"View 2".to_string(),
847+
crate::ViewLayout::Document,
848+
Some(uid),
849+
);
850+
let view_2_id = view_2.id.clone();
851+
let space_view = View {
852+
id: "space_1_id".to_string(),
853+
parent_view_id: workspace_id.to_string(),
854+
name: "Space 1".to_string(),
855+
children: RepeatedViewIdentifier::new(vec![
856+
ViewIdentifier::new(view_1_id.clone()),
857+
ViewIdentifier::new(view_2_id.clone()),
858+
]),
859+
created_at: current_time,
860+
is_favorite: false,
861+
layout: crate::ViewLayout::Document,
862+
icon: None,
863+
created_by: None,
864+
last_edited_time: current_time,
865+
last_edited_by: None,
866+
extra: Some(serde_json::to_string(&SpaceInfo::default()).unwrap()),
867+
};
868+
let space_view_id = space_view.id.clone();
869+
let workspace = Workspace {
870+
id: workspace_id.to_string(),
871+
name: "Workspace".to_string(),
872+
child_views: RepeatedViewIdentifier::new(vec![ViewIdentifier::new(space_view_id.clone())]),
873+
created_at: current_time,
874+
created_by: Some(uid),
875+
last_edited_time: current_time,
876+
last_edited_by: Some(uid),
877+
};
878+
let folder_data = FolderData {
879+
workspace,
880+
current_view: view_2.id.clone(),
881+
views: vec![space_view, view_1, view_2],
882+
favorites: Default::default(),
883+
recent: Default::default(),
884+
trash: Default::default(),
885+
private: Default::default(),
886+
};
887+
let mut folder = Folder::create(uid, collab, None, folder_data);
888+
889+
folder.set_current_view(view_2_id.clone());
890+
assert_eq!(folder.get_current_view(), Some(view_2_id.to_string()));
891+
// First visit from user 2, should return the first child of the first public space with children.
892+
folder.body.uid = UserId::from(2);
893+
assert_eq!(folder.get_current_view(), Some(view_1_id.to_string()));
894+
folder.set_current_view(view_1_id.to_string());
895+
folder.body.uid = UserId::from(1);
896+
assert_eq!(folder.get_current_view(), Some(view_2_id.to_string()));
897+
folder.body.uid = UserId::from(2);
898+
assert_eq!(folder.get_current_view(), Some(view_1_id.to_string()));
899+
}
900+
}

collab-folder/src/space_info.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ impl Default for SpaceInfo {
5858
}
5959
}
6060

61-
#[derive(Debug, Clone, Default, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)]
61+
#[derive(
62+
Debug, Clone, Default, serde_repr::Serialize_repr, serde_repr::Deserialize_repr, PartialEq, Eq,
63+
)]
6264
#[repr(u8)]
6365
pub enum SpacePermission {
6466
#[default]

0 commit comments

Comments
 (0)