Browse Source

fix: create build in document (#2687)

* feat: support create multiple level views

* refactor: rm document data wrapper

* chore: add docs

---------

Co-authored-by: nathan <[email protected]>
Lucas.Xu 1 year ago
parent
commit
ee52bf4b0e

+ 38 - 7
frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs

@@ -10,7 +10,7 @@ use flowy_database2::entities::DatabaseLayoutPB;
 use flowy_database2::services::share::csv::CSVFormat;
 use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
 use flowy_database2::DatabaseManager2;
-use flowy_document2::document_data::DocumentDataWrapper;
+use flowy_document2::document_data::default_document_data;
 use flowy_document2::entities::DocumentDataPB;
 use flowy_document2::manager::DocumentManager;
 use flowy_document2::parser::json::parser::JsonToDocumentParser;
@@ -18,11 +18,14 @@ use flowy_error::FlowyError;
 use flowy_folder2::deps::{FolderCloudService, FolderUser};
 use flowy_folder2::entities::ViewLayoutPB;
 use flowy_folder2::manager::Folder2Manager;
-use flowy_folder2::view_operation::{FolderOperationHandler, FolderOperationHandlers, View};
+use flowy_folder2::view_operation::{
+  FolderOperationHandler, FolderOperationHandlers, View, WorkspaceViewBuilder,
+};
 use flowy_folder2::ViewLayout;
 use flowy_user::services::UserSession;
 use lib_dispatch::prelude::ToBytes;
 use lib_infra::future::FutureResult;
+use tokio::sync::RwLock;
 
 pub struct Folder2DepsResolver();
 impl Folder2DepsResolver {
@@ -83,6 +86,37 @@ impl FolderUser for FolderUserImpl {
 
 struct DocumentFolderOperation(Arc<DocumentManager>);
 impl FolderOperationHandler for DocumentFolderOperation {
+  fn create_workspace_view(
+    &self,
+    workspace_view_builder: Arc<RwLock<WorkspaceViewBuilder>>,
+  ) -> FutureResult<(), FlowyError> {
+    let manager = self.0.clone();
+    FutureResult::new(async move {
+      let mut write_guard = workspace_view_builder.write().await;
+
+      // Create a parent view named "⭐️ Getting started". and a child view named "Read me".
+      // Don't modify this code unless you know what you are doing.
+      write_guard
+        .with_view_builder(|view_builder| async {
+          view_builder
+            .with_name("⭐️ Getting started")
+            .with_child_view_builder(|child_view_builder| async {
+              let view = child_view_builder.with_name("Read me").build();
+              let json_str = include_str!("../../assets/read_me.json");
+              let document_pb = JsonToDocumentParser::json_str_to_document(json_str).unwrap();
+              manager
+                .create_document(view.parent_view.id.clone(), document_pb.into())
+                .unwrap();
+              view
+            })
+            .await
+            .build()
+        })
+        .await;
+      Ok(())
+    })
+  }
+
   /// Close the document view.
   fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> {
     let manager = self.0.clone();
@@ -98,7 +132,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
     let view_id = view_id.to_string();
     FutureResult::new(async move {
       let document = manager.get_document(view_id)?;
-      let data: DocumentDataPB = DocumentDataWrapper(document.lock().get_document()?).into();
+      let data: DocumentDataPB = document.lock().get_document()?.into();
       let data_bytes = data.into_bytes().map_err(|_| FlowyError::invalid_data())?;
       Ok(data_bytes)
     })
@@ -132,13 +166,10 @@ impl FolderOperationHandler for DocumentFolderOperation {
     layout: ViewLayout,
   ) -> FutureResult<(), FlowyError> {
     debug_assert_eq!(layout, ViewLayout::Document);
-
-    let json_str = include_str!("../../assets/read_me.json");
     let view_id = view_id.to_string();
     let manager = self.0.clone();
     FutureResult::new(async move {
-      let document_pb = JsonToDocumentParser::json_str_to_document(json_str)?;
-      manager.create_document(view_id, document_pb.into())?;
+      manager.create_document(view_id, default_document_data())?;
       Ok(())
     })
   }

+ 51 - 57
frontend/rust-lib/flowy-document2/src/document_data.rs

@@ -5,27 +5,22 @@ use nanoid::nanoid;
 
 use crate::entities::{BlockPB, ChildrenPB, DocumentDataPB, MetaPB};
 
-#[derive(Clone, Debug)]
-pub struct DocumentDataWrapper(pub DocumentData);
-
-impl From<DocumentDataWrapper> for DocumentDataPB {
-  fn from(data: DocumentDataWrapper) -> Self {
+impl From<DocumentData> for DocumentDataPB {
+  fn from(data: DocumentData) -> Self {
     let blocks = data
-      .0
       .blocks
       .into_iter()
       .map(|(id, block)| (id, block.into()))
       .collect();
 
     let children_map = data
-      .0
       .meta
       .children_map
       .into_iter()
       .map(|(id, children)| (id, children.into()))
       .collect();
 
-    let page_id = data.0.page_id;
+    let page_id = data.page_id;
 
     Self {
       page_id,
@@ -35,7 +30,7 @@ impl From<DocumentDataWrapper> for DocumentDataPB {
   }
 }
 
-impl From<DocumentDataPB> for DocumentDataWrapper {
+impl From<DocumentDataPB> for DocumentData {
   fn from(data: DocumentDataPB) -> Self {
     let blocks = data
       .blocks
@@ -52,60 +47,59 @@ impl From<DocumentDataPB> for DocumentDataWrapper {
 
     let page_id = data.page_id;
 
-    Self(DocumentData {
+    DocumentData {
       page_id,
       blocks,
       meta: DocumentMeta { children_map },
-    })
+    }
   }
 }
 
-// the default document data contains a page block and a text block
-impl Default for DocumentDataWrapper {
-  fn default() -> Self {
-    let page_type = "page".to_string();
-    let text_type = "text".to_string();
-
-    let mut blocks: HashMap<String, Block> = HashMap::new();
-    let mut meta: HashMap<String, Vec<String>> = HashMap::new();
-
-    // page block
-    let page_id = nanoid!(10);
-    let children_id = nanoid!(10);
-    let root = Block {
-      id: page_id.clone(),
-      ty: page_type,
-      parent: "".to_string(),
-      children: children_id.clone(),
-      external_id: None,
-      external_type: None,
-      data: HashMap::new(),
-    };
-    blocks.insert(page_id.clone(), root);
-
-    // text block
-    let text_block_id = nanoid!(10);
-    let text_block_children_id = nanoid!(10);
-    let text_block = Block {
-      id: text_block_id.clone(),
-      ty: text_type,
-      parent: page_id.clone(),
-      children: text_block_children_id.clone(),
-      external_id: None,
-      external_type: None,
-      data: HashMap::new(),
-    };
-    blocks.insert(text_block_id.clone(), text_block);
-
-    // meta
-    meta.insert(children_id, vec![text_block_id]);
-    meta.insert(text_block_children_id, vec![]);
-
-    Self(DocumentData {
-      page_id,
-      blocks,
-      meta: DocumentMeta { children_map: meta },
-    })
+/// The default document data.
+/// The default document data is a document with a page block and a text block.
+pub fn default_document_data() -> DocumentData {
+  let page_type = "page".to_string();
+  let text_type = "text".to_string();
+
+  let mut blocks: HashMap<String, Block> = HashMap::new();
+  let mut meta: HashMap<String, Vec<String>> = HashMap::new();
+
+  // page block
+  let page_id = nanoid!(10);
+  let children_id = nanoid!(10);
+  let root = Block {
+    id: page_id.clone(),
+    ty: page_type,
+    parent: "".to_string(),
+    children: children_id.clone(),
+    external_id: None,
+    external_type: None,
+    data: HashMap::new(),
+  };
+  blocks.insert(page_id.clone(), root);
+
+  // text block
+  let text_block_id = nanoid!(10);
+  let text_block_children_id = nanoid!(10);
+  let text_block = Block {
+    id: text_block_id.clone(),
+    ty: text_type,
+    parent: page_id.clone(),
+    children: text_block_children_id.clone(),
+    external_id: None,
+    external_type: None,
+    data: HashMap::new(),
+  };
+  blocks.insert(text_block_id.clone(), text_block);
+
+  // meta
+  meta.insert(children_id, vec![text_block_id]);
+  meta.insert(text_block_children_id, vec![]);
+
+  DocumentData {
+    page_id,
+    blocks,
+    meta: DocumentMeta { children_map: meta },
   }
 }
 

+ 9 - 9
frontend/rust-lib/flowy-document2/src/event_handler.rs

@@ -8,14 +8,14 @@ use std::sync::Arc;
 
 use collab_document::blocks::{
   json_str_to_hashmap, Block, BlockAction, BlockActionPayload, BlockActionType, BlockEvent,
-  BlockEventPayload, DeltaType,
+  BlockEventPayload, DeltaType, DocumentData,
 };
 
 use flowy_error::{FlowyError, FlowyResult};
 use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
 
+use crate::document_data::default_document_data;
 use crate::{
-  document_data::DocumentDataWrapper,
   entities::{
     ApplyActionPayloadPB, BlockActionPB, BlockActionPayloadPB, BlockActionTypePB, BlockEventPB,
     BlockEventPayloadPB, BlockPB, CloseDocumentPayloadPB, CreateDocumentPayloadPB, DeltaTypePB,
@@ -29,12 +29,12 @@ pub(crate) async fn create_document_handler(
   data: AFPluginData<CreateDocumentPayloadPB>,
   manager: AFPluginState<Arc<DocumentManager>>,
 ) -> FlowyResult<()> {
-  let context = data.into_inner();
-  let initial_data: DocumentDataWrapper = context
+  let data = data.into_inner();
+  let initial_data = data
     .initial_data
-    .map(|data| data.into())
-    .unwrap_or_default();
-  manager.create_document(context.document_id, initial_data)?;
+    .map(|data| DocumentData::from(data))
+    .unwrap_or_else(default_document_data);
+  manager.create_document(data.document_id, initial_data)?;
   Ok(())
 }
 
@@ -46,7 +46,7 @@ pub(crate) async fn open_document_handler(
   let context = data.into_inner();
   let document = manager.open_document(context.document_id)?;
   let document_data = document.lock().get_document()?;
-  data_result_ok(DocumentDataPB::from(DocumentDataWrapper(document_data)))
+  data_result_ok(DocumentDataPB::from(document_data))
 }
 
 pub(crate) async fn close_document_handler(
@@ -67,7 +67,7 @@ pub(crate) async fn get_document_data_handler(
   let context = data.into_inner();
   let document = manager.get_document(context.document_id)?;
   let document_data = document.lock().get_document()?;
-  data_result_ok(DocumentDataPB::from(DocumentDataWrapper(document_data)))
+  data_result_ok(DocumentDataPB::from(document_data))
 }
 
 // Handler for applying an action to a document

+ 3 - 7
frontend/rust-lib/flowy-document2/src/manager.rs

@@ -2,11 +2,11 @@ use std::{collections::HashMap, sync::Arc};
 
 use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
 use appflowy_integrate::RocksCollabDB;
+use collab_document::blocks::DocumentData;
 use parking_lot::RwLock;
 
 use flowy_error::{FlowyError, FlowyResult};
 
-use crate::document_data::DocumentDataWrapper;
 use crate::{
   document::Document,
   entities::DocEventPB,
@@ -34,16 +34,12 @@ impl DocumentManager {
     }
   }
 
-  pub fn create_document(
-    &self,
-    doc_id: String,
-    data: DocumentDataWrapper,
-  ) -> FlowyResult<Arc<Document>> {
+  pub fn create_document(&self, doc_id: String, data: DocumentData) -> FlowyResult<Arc<Document>> {
     tracing::debug!("create a document: {:?}", &doc_id);
     let uid = self.user.user_id()?;
     let db = self.user.collab_db()?;
     let collab = self.collab_builder.build(uid, &doc_id, "document", db);
-    let document = Arc::new(Document::create_with_data(collab, data.0)?);
+    let document = Arc::new(Document::create_with_data(collab, data)?);
     Ok(document)
   }
 

+ 4 - 5
frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs

@@ -2,9 +2,8 @@ use std::{collections::HashMap, sync::Arc, vec};
 
 use crate::document::util::default_collab_builder;
 use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
-use flowy_document2::{
-  document::Document, document_data::DocumentDataWrapper, manager::DocumentManager,
-};
+use flowy_document2::document_data::default_document_data;
+use flowy_document2::{document::Document, manager::DocumentManager};
 use nanoid::nanoid;
 
 use super::util::FakeUser;
@@ -44,7 +43,7 @@ fn create_and_open_empty_document() -> (DocumentManager, Arc<Document>, String)
   let manager = DocumentManager::new(Arc::new(user), default_collab_builder());
 
   let doc_id: String = nanoid!(10);
-  let data = DocumentDataWrapper::default();
+  let data = default_document_data();
 
   // create a document
   _ = manager
@@ -53,5 +52,5 @@ fn create_and_open_empty_document() -> (DocumentManager, Arc<Document>, String)
 
   let document = manager.open_document(doc_id).unwrap();
 
-  (manager, document, data.0.page_id)
+  (manager, document, data.page_id)
 }

+ 14 - 13
frontend/rust-lib/flowy-document2/tests/document/document_test.rs

@@ -5,7 +5,8 @@ use nanoid::nanoid;
 use serde_json::{json, to_value, Value};
 
 use crate::document::util::default_collab_builder;
-use flowy_document2::{document_data::DocumentDataWrapper, manager::DocumentManager};
+use flowy_document2::document_data::default_document_data;
+use flowy_document2::manager::DocumentManager;
 
 use super::util::FakeUser;
 
@@ -16,12 +17,12 @@ fn restore_document() {
 
   // create a document
   let doc_id: String = nanoid!(10);
-  let data = DocumentDataWrapper::default();
+  let data = default_document_data();
   let document_a = manager
     .create_document(doc_id.clone(), data.clone())
     .unwrap();
   let data_a = document_a.lock().get_document().unwrap();
-  assert_eq!(data_a, data.0);
+  assert_eq!(data_a, data);
 
   // open a document
   let data_b = manager
@@ -32,7 +33,7 @@ fn restore_document() {
     .unwrap();
   // close a document
   _ = manager.close_document(doc_id.clone());
-  assert_eq!(data_b, data.0);
+  assert_eq!(data_b, data);
 
   // restore
   _ = manager.create_document(doc_id.clone(), data.clone());
@@ -46,7 +47,7 @@ fn restore_document() {
   // close a document
   _ = manager.close_document(doc_id);
 
-  assert_eq!(data_b, data.0);
+  assert_eq!(data_b, data);
 }
 
 #[test]
@@ -55,14 +56,14 @@ fn document_apply_insert_action() {
   let manager = DocumentManager::new(Arc::new(user), default_collab_builder());
 
   let doc_id: String = nanoid!(10);
-  let data = DocumentDataWrapper::default();
+  let data = default_document_data();
 
   // create a document
   _ = manager.create_document(doc_id.clone(), data.clone());
 
   // open a document
   let document = manager.open_document(doc_id.clone()).unwrap();
-  let page_block = document.lock().get_block(&data.0.page_id).unwrap();
+  let page_block = document.lock().get_block(&data.page_id).unwrap();
 
   // insert a text block
   let text_block = Block {
@@ -106,14 +107,14 @@ fn document_apply_update_page_action() {
   let manager = DocumentManager::new(Arc::new(user), default_collab_builder());
 
   let doc_id: String = nanoid!(10);
-  let data = DocumentDataWrapper::default();
+  let data = default_document_data();
 
   // create a document
   _ = manager.create_document(doc_id.clone(), data.clone());
 
   // open a document
   let document = manager.open_document(doc_id.clone()).unwrap();
-  let page_block = document.lock().get_block(&data.0.page_id).unwrap();
+  let page_block = document.lock().get_block(&data.page_id).unwrap();
 
   let mut page_block_clone = page_block;
   page_block_clone.data = HashMap::new();
@@ -132,12 +133,12 @@ fn document_apply_update_page_action() {
   let actions = vec![action];
   tracing::trace!("{:?}", &actions);
   document.lock().apply_action(actions);
-  let page_block_old = document.lock().get_block(&data.0.page_id).unwrap();
+  let page_block_old = document.lock().get_block(&data.page_id).unwrap();
   _ = manager.close_document(doc_id.clone());
 
   // re-open the document
   let document = manager.open_document(doc_id).unwrap();
-  let page_block_new = document.lock().get_block(&data.0.page_id).unwrap();
+  let page_block_new = document.lock().get_block(&data.page_id).unwrap();
   assert_eq!(page_block_old, page_block_new);
   assert!(page_block_new.data.contains_key("delta"));
 }
@@ -148,14 +149,14 @@ fn document_apply_update_action() {
   let manager = DocumentManager::new(Arc::new(user), default_collab_builder());
 
   let doc_id: String = nanoid!(10);
-  let data = DocumentDataWrapper::default();
+  let data = default_document_data();
 
   // create a document
   _ = manager.create_document(doc_id.clone(), data.clone());
 
   // open a document
   let document = manager.open_document(doc_id.clone()).unwrap();
-  let page_block = document.lock().get_block(&data.0.page_id).unwrap();
+  let page_block = document.lock().get_block(&data.page_id).unwrap();
 
   // insert a text block
   let text_block_id = nanoid!(10);

+ 57 - 61
frontend/rust-lib/flowy-folder2/src/user_default.rs

@@ -1,75 +1,72 @@
-use chrono::Utc;
-use collab_folder::core::{FolderData, RepeatedView, View, ViewIdentifier, ViewLayout, Workspace};
+use collab_folder::core::{FolderData, RepeatedView, ViewIdentifier, Workspace};
+use lib_infra::util::timestamp;
 use nanoid::nanoid;
+use std::sync::Arc;
+use tokio::sync::RwLock;
 
-use crate::entities::{view_pb_with_child_views, WorkspacePB};
-use crate::view_operation::{gen_view_id, FolderOperationHandlers};
+use crate::entities::{view_pb_with_child_views, ViewPB, WorkspacePB};
+use crate::view_operation::{
+  FlattedViews, FolderOperationHandlers, ParentChildViews, WorkspaceViewBuilder,
+};
 
 pub struct DefaultFolderBuilder();
 impl DefaultFolderBuilder {
   pub async fn build(
-    uid: i64,
+    _uid: i64,
     workspace_id: String,
     handlers: &FolderOperationHandlers,
   ) -> (FolderData, WorkspacePB) {
-    let time = Utc::now().timestamp();
-    let view_id = gen_view_id();
-    let child_view_id = gen_view_id();
+    let workspace_view_builder =
+      Arc::new(RwLock::new(WorkspaceViewBuilder::new(workspace_id.clone())));
+    for handler in handlers.values() {
+      let _ = handler
+        .create_workspace_view(workspace_view_builder.clone())
+        .await;
+    }
 
-    let child_view_layout = ViewLayout::Document;
-    let child_view = View {
-      id: child_view_id.clone(),
-      parent_view_id: view_id.clone(),
-      name: "Read me".to_string(),
-      desc: "".to_string(),
-      created_at: time,
-      layout: child_view_layout.clone(),
-      children: Default::default(),
-    };
-
-    // create the document
-    // TODO: use the initial data from the view processor
-    // let data = initial_read_me().into_bytes();
-    let handler = handlers.get(&child_view_layout).unwrap();
-    handler
-      .create_built_in_view(
-        uid,
-        &child_view.id,
-        &child_view.name,
-        child_view_layout.clone(),
-      )
-      .await
-      .unwrap();
+    let views = workspace_view_builder.write().await.build();
+    // Safe to unwrap because we have at least one view. check out the DocumentFolderOperation.
+    let first_view = views
+      .first()
+      .unwrap()
+      .child_views
+      .first()
+      .unwrap()
+      .parent_view
+      .clone();
 
-    let view = View {
-      id: view_id,
-      parent_view_id: workspace_id.clone(),
-      name: "⭐️ Getting started".to_string(),
-      desc: "".to_string(),
-      children: RepeatedView::new(vec![ViewIdentifier {
-        id: child_view.id.clone(),
-      }]),
-      created_at: time,
-      layout: ViewLayout::Document,
-    };
+    let first_level_views = views
+      .iter()
+      .map(|value| ViewIdentifier {
+        id: value.parent_view.id.clone(),
+      })
+      .collect::<Vec<_>>();
 
     let workspace = Workspace {
       id: workspace_id,
       name: "Workspace".to_string(),
-      child_views: RepeatedView::new(vec![ViewIdentifier {
-        id: view.id.clone(),
-      }]),
-      created_at: time,
+      child_views: RepeatedView::new(first_level_views),
+      created_at: timestamp(),
     };
 
-    let workspace_pb = workspace_pb_from_workspace(&workspace, &view, &child_view);
+    let first_level_view_pbs = views
+      .iter()
+      .map(|value| ViewPB::from(value))
+      .collect::<Vec<_>>();
+
+    let workspace_pb = WorkspacePB {
+      id: workspace.id.clone(),
+      name: workspace.name.clone(),
+      views: first_level_view_pbs,
+      create_time: workspace.created_at,
+    };
 
     (
       FolderData {
         current_workspace: workspace.id.clone(),
-        current_view: child_view_id,
+        current_view: first_view.id,
         workspaces: vec![workspace],
-        views: vec![view, child_view],
+        views: FlattedViews::flatten_views(views),
       },
       workspace_pb,
     )
@@ -80,16 +77,15 @@ pub fn gen_workspace_id() -> String {
   nanoid!(10)
 }
 
-fn workspace_pb_from_workspace(
-  workspace: &Workspace,
-  view: &View,
-  child_view: &View,
-) -> WorkspacePB {
-  let view_pb = view_pb_with_child_views(view.clone(), vec![child_view.clone()]);
-  WorkspacePB {
-    id: workspace.id.clone(),
-    name: workspace.name.clone(),
-    views: vec![view_pb],
-    create_time: workspace.created_at,
+impl From<&ParentChildViews> for ViewPB {
+  fn from(value: &ParentChildViews) -> Self {
+    view_pb_with_child_views(
+      value.parent_view.clone(),
+      value
+        .child_views
+        .iter()
+        .map(|v| v.parent_view.clone())
+        .collect(),
+    )
   }
 }

+ 276 - 1
frontend/rust-lib/flowy-folder2/src/view_operation.rs

@@ -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);
+  }
+}