|
@@ -1,21 +1,158 @@
|
|
|
use crate::entities::{CreateViewParams, ViewLayoutPB};
|
|
|
use bytes::Bytes;
|
|
|
-use collab_folder::core::ViewLayout;
|
|
|
+use collab_folder::core::{RepeatedView, ViewIdentifier, ViewLayout};
|
|
|
use flowy_error::FlowyError;
|
|
|
use lib_infra::future::FutureResult;
|
|
|
use lib_infra::util::timestamp;
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
+use std::future::Future;
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
pub type ViewData = Bytes;
|
|
|
pub use collab_folder::core::View;
|
|
|
+use tokio::sync::RwLock;
|
|
|
+
|
|
|
+/// A builder for creating a view for a workspace.
|
|
|
+/// The views created by this builder will be the first level views of the workspace.
|
|
|
+pub struct WorkspaceViewBuilder {
|
|
|
+ pub workspace_id: String,
|
|
|
+ pub views: Vec<ParentChildViews>,
|
|
|
+}
|
|
|
+
|
|
|
+impl WorkspaceViewBuilder {
|
|
|
+ pub fn new(workspace_id: String) -> Self {
|
|
|
+ Self {
|
|
|
+ workspace_id,
|
|
|
+ views: vec![],
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub async fn with_view_builder<F, O>(&mut self, view_builder: F)
|
|
|
+ where
|
|
|
+ F: Fn(ViewBuilder) -> O,
|
|
|
+ O: Future<Output = ParentChildViews>,
|
|
|
+ {
|
|
|
+ let builder = ViewBuilder::new(self.workspace_id.clone());
|
|
|
+ self.views.push(view_builder(builder).await);
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn build(&mut self) -> Vec<ParentChildViews> {
|
|
|
+ std::mem::take(&mut self.views)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// A builder for creating a view.
|
|
|
+/// The default layout of the view is [ViewLayout::Document]
|
|
|
+pub struct ViewBuilder {
|
|
|
+ parent_view_id: String,
|
|
|
+ view_id: String,
|
|
|
+ name: String,
|
|
|
+ desc: String,
|
|
|
+ layout: ViewLayout,
|
|
|
+ child_views: Vec<ParentChildViews>,
|
|
|
+}
|
|
|
+
|
|
|
+impl ViewBuilder {
|
|
|
+ pub fn new(parent_view_id: String) -> Self {
|
|
|
+ Self {
|
|
|
+ parent_view_id,
|
|
|
+ view_id: gen_view_id(),
|
|
|
+ name: Default::default(),
|
|
|
+ desc: Default::default(),
|
|
|
+ layout: ViewLayout::Document,
|
|
|
+ child_views: vec![],
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn view_id(&self) -> &str {
|
|
|
+ &self.view_id
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn with_layout(mut self, layout: ViewLayout) -> Self {
|
|
|
+ self.layout = layout;
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn with_name(mut self, name: &str) -> Self {
|
|
|
+ self.name = name.to_string();
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn with_desc(mut self, desc: &str) -> Self {
|
|
|
+ self.desc = desc.to_string();
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Create a child view for the current view.
|
|
|
+ /// The view created by this builder will be the next level view of the current view.
|
|
|
+ pub async fn with_child_view_builder<F, O>(mut self, child_view_builder: F) -> Self
|
|
|
+ where
|
|
|
+ F: Fn(ViewBuilder) -> O,
|
|
|
+ O: Future<Output = ParentChildViews>,
|
|
|
+ {
|
|
|
+ let builder = ViewBuilder::new(self.view_id.clone());
|
|
|
+ self.child_views.push(child_view_builder(builder).await);
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn build(self) -> ParentChildViews {
|
|
|
+ let view = View {
|
|
|
+ id: self.view_id,
|
|
|
+ parent_view_id: self.parent_view_id,
|
|
|
+ name: self.name,
|
|
|
+ desc: self.desc,
|
|
|
+ created_at: timestamp(),
|
|
|
+ layout: self.layout,
|
|
|
+ children: RepeatedView::new(
|
|
|
+ self
|
|
|
+ .child_views
|
|
|
+ .iter()
|
|
|
+ .map(|v| ViewIdentifier {
|
|
|
+ id: v.parent_view.id.clone(),
|
|
|
+ })
|
|
|
+ .collect(),
|
|
|
+ ),
|
|
|
+ };
|
|
|
+ ParentChildViews {
|
|
|
+ parent_view: view,
|
|
|
+ child_views: self.child_views,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub struct ParentChildViews {
|
|
|
+ pub parent_view: View,
|
|
|
+ pub child_views: Vec<ParentChildViews>,
|
|
|
+}
|
|
|
+
|
|
|
+pub struct FlattedViews;
|
|
|
+
|
|
|
+impl FlattedViews {
|
|
|
+ pub fn flatten_views(views: Vec<ParentChildViews>) -> Vec<View> {
|
|
|
+ let mut result = vec![];
|
|
|
+ for view in views {
|
|
|
+ result.push(view.parent_view);
|
|
|
+ result.append(&mut Self::flatten_views(view.child_views));
|
|
|
+ }
|
|
|
+ result
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
/// The handler will be used to handler the folder operation for a specific
|
|
|
/// view layout. Each [ViewLayout] will have a handler. So when creating a new
|
|
|
/// view, the [ViewLayout] will be used to get the handler.
|
|
|
///
|
|
|
pub trait FolderOperationHandler {
|
|
|
+ /// Create the view for the workspace of new user.
|
|
|
+ /// Only called once when the user is created.
|
|
|
+ fn create_workspace_view(
|
|
|
+ &self,
|
|
|
+ _workspace_view_builder: Arc<RwLock<WorkspaceViewBuilder>>,
|
|
|
+ ) -> FutureResult<(), FlowyError> {
|
|
|
+ FutureResult::new(async { Ok(()) })
|
|
|
+ }
|
|
|
+
|
|
|
/// Closes the view and releases the resources that this view has in
|
|
|
/// the backend
|
|
|
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
|
@@ -104,6 +241,144 @@ pub(crate) fn create_view(params: CreateViewParams, layout: ViewLayout) -> View
|
|
|
layout,
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
pub fn gen_view_id() -> String {
|
|
|
uuid::Uuid::new_v4().to_string()
|
|
|
}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use crate::view_operation::{FlattedViews, WorkspaceViewBuilder};
|
|
|
+
|
|
|
+ #[tokio::test]
|
|
|
+ async fn create_first_level_views_test() {
|
|
|
+ let workspace_id = "w1".to_string();
|
|
|
+ let mut builder = WorkspaceViewBuilder::new(workspace_id);
|
|
|
+ builder
|
|
|
+ .with_view_builder(|view_builder| async { view_builder.with_name("1").build() })
|
|
|
+ .await;
|
|
|
+ builder
|
|
|
+ .with_view_builder(|view_builder| async { view_builder.with_name("2").build() })
|
|
|
+ .await;
|
|
|
+ builder
|
|
|
+ .with_view_builder(|view_builder| async { view_builder.with_name("3").build() })
|
|
|
+ .await;
|
|
|
+ let workspace_views = builder.build();
|
|
|
+ assert_eq!(workspace_views.len(), 3);
|
|
|
+
|
|
|
+ let views = FlattedViews::flatten_views(workspace_views);
|
|
|
+ assert_eq!(views.len(), 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[tokio::test]
|
|
|
+ async fn create_view_with_child_views_test() {
|
|
|
+ let workspace_id = "w1".to_string();
|
|
|
+ let mut builder = WorkspaceViewBuilder::new(workspace_id);
|
|
|
+ builder
|
|
|
+ .with_view_builder(|view_builder| async {
|
|
|
+ view_builder
|
|
|
+ .with_name("1")
|
|
|
+ .with_child_view_builder(|child_view_builder| async {
|
|
|
+ child_view_builder.with_name("1_1").build()
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .with_child_view_builder(|child_view_builder| async {
|
|
|
+ child_view_builder.with_name("1_2").build()
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .build()
|
|
|
+ })
|
|
|
+ .await;
|
|
|
+ builder
|
|
|
+ .with_view_builder(|view_builder| async {
|
|
|
+ view_builder
|
|
|
+ .with_name("2")
|
|
|
+ .with_child_view_builder(|child_view_builder| async {
|
|
|
+ child_view_builder.with_name("2_1").build()
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .build()
|
|
|
+ })
|
|
|
+ .await;
|
|
|
+ let workspace_views = builder.build();
|
|
|
+ assert_eq!(workspace_views.len(), 2);
|
|
|
+
|
|
|
+ assert_eq!(workspace_views[0].parent_view.name, "1");
|
|
|
+ assert_eq!(workspace_views[0].child_views.len(), 2);
|
|
|
+ assert_eq!(workspace_views[0].child_views[0].parent_view.name, "1_1");
|
|
|
+ assert_eq!(workspace_views[0].child_views[1].parent_view.name, "1_2");
|
|
|
+ assert_eq!(workspace_views[1].child_views.len(), 1);
|
|
|
+ assert_eq!(workspace_views[1].child_views[0].parent_view.name, "2_1");
|
|
|
+
|
|
|
+ let views = FlattedViews::flatten_views(workspace_views);
|
|
|
+ assert_eq!(views.len(), 5);
|
|
|
+ }
|
|
|
+ #[tokio::test]
|
|
|
+ async fn create_three_level_view_test() {
|
|
|
+ let workspace_id = "w1".to_string();
|
|
|
+ let mut builder = WorkspaceViewBuilder::new(workspace_id);
|
|
|
+ builder
|
|
|
+ .with_view_builder(|view_builder| async {
|
|
|
+ view_builder
|
|
|
+ .with_name("1")
|
|
|
+ .with_child_view_builder(|child_view_builder| async {
|
|
|
+ child_view_builder
|
|
|
+ .with_name("1_1")
|
|
|
+ .with_child_view_builder(|b| async { b.with_name("1_1_1").build() })
|
|
|
+ .await
|
|
|
+ .with_child_view_builder(|b| async { b.with_name("1_1_2").build() })
|
|
|
+ .await
|
|
|
+ .build()
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .with_child_view_builder(|child_view_builder| async {
|
|
|
+ child_view_builder
|
|
|
+ .with_name("1_2")
|
|
|
+ .with_child_view_builder(|b| async { b.with_name("1_2_1").build() })
|
|
|
+ .await
|
|
|
+ .with_child_view_builder(|b| async { b.with_name("1_2_2").build() })
|
|
|
+ .await
|
|
|
+ .build()
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .build()
|
|
|
+ })
|
|
|
+ .await;
|
|
|
+ let workspace_views = builder.build();
|
|
|
+ assert_eq!(workspace_views.len(), 1);
|
|
|
+
|
|
|
+ assert_eq!(workspace_views[0].parent_view.name, "1");
|
|
|
+ assert_eq!(workspace_views[0].child_views.len(), 2);
|
|
|
+ assert_eq!(workspace_views[0].child_views[0].parent_view.name, "1_1");
|
|
|
+ assert_eq!(workspace_views[0].child_views[1].parent_view.name, "1_2");
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ workspace_views[0].child_views[0].child_views[0]
|
|
|
+ .parent_view
|
|
|
+ .name,
|
|
|
+ "1_1_1"
|
|
|
+ );
|
|
|
+ assert_eq!(
|
|
|
+ workspace_views[0].child_views[0].child_views[1]
|
|
|
+ .parent_view
|
|
|
+ .name,
|
|
|
+ "1_1_2"
|
|
|
+ );
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ workspace_views[0].child_views[1].child_views[0]
|
|
|
+ .parent_view
|
|
|
+ .name,
|
|
|
+ "1_2_1"
|
|
|
+ );
|
|
|
+ assert_eq!(
|
|
|
+ workspace_views[0].child_views[1].child_views[1]
|
|
|
+ .parent_view
|
|
|
+ .name,
|
|
|
+ "1_2_2"
|
|
|
+ );
|
|
|
+
|
|
|
+ let views = FlattedViews::flatten_views(workspace_views);
|
|
|
+ assert_eq!(views.len(), 7);
|
|
|
+ }
|
|
|
+}
|