From ee52bf4b0ed0c91799cbc4362e4738a7312dc4bc Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 3 Jun 2023 13:55:43 +0800 Subject: [PATCH] fix: create build in document (#2687) * feat: support create multiple level views * refactor: rm document data wrapper * chore: add docs --------- Co-authored-by: nathan --- .../src/deps_resolve/folder2_deps.rs | 45 ++- .../flowy-document2/src/document_data.rs | 98 +++---- .../flowy-document2/src/event_handler.rs | 18 +- .../rust-lib/flowy-document2/src/manager.rs | 10 +- .../tests/document/document_insert_test.rs | 9 +- .../tests/document/document_test.rs | 27 +- .../flowy-folder2/src/user_default.rs | 118 ++++---- .../flowy-folder2/src/view_operation.rs | 277 +++++++++++++++++- 8 files changed, 447 insertions(+), 155 deletions(-) diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs index fd5303a1af..a69c9135d5 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs +++ b/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); impl FolderOperationHandler for DocumentFolderOperation { + fn create_workspace_view( + &self, + workspace_view_builder: Arc>, + ) -> 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(()) }) } diff --git a/frontend/rust-lib/flowy-document2/src/document_data.rs b/frontend/rust-lib/flowy-document2/src/document_data.rs index a98e6b4767..dfe169912d 100644 --- a/frontend/rust-lib/flowy-document2/src/document_data.rs +++ b/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 for DocumentDataPB { - fn from(data: DocumentDataWrapper) -> Self { +impl From 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 for DocumentDataPB { } } -impl From for DocumentDataWrapper { +impl From for DocumentData { fn from(data: DocumentDataPB) -> Self { let blocks = data .blocks @@ -52,60 +47,59 @@ impl From 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(); +/// 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 = HashMap::new(); - let mut meta: HashMap> = HashMap::new(); + let mut blocks: HashMap = HashMap::new(); + let mut meta: HashMap> = 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); + // 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); + // 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![]); + // 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 }, - }) + DocumentData { + page_id, + blocks, + meta: DocumentMeta { children_map: meta }, } } diff --git a/frontend/rust-lib/flowy-document2/src/event_handler.rs b/frontend/rust-lib/flowy-document2/src/event_handler.rs index 24afc9dc1d..7317907101 100644 --- a/frontend/rust-lib/flowy-document2/src/event_handler.rs +++ b/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, manager: AFPluginState>, ) -> 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 diff --git a/frontend/rust-lib/flowy-document2/src/manager.rs b/frontend/rust-lib/flowy-document2/src/manager.rs index 925c3a38d5..9cd3e1274e 100644 --- a/frontend/rust-lib/flowy-document2/src/manager.rs +++ b/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> { + pub fn create_document(&self, doc_id: String, data: DocumentData) -> FlowyResult> { 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) } diff --git a/frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs b/frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs index 5f4bd0bec5..e47ebd8439 100644 --- a/frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs +++ b/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, 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, String) let document = manager.open_document(doc_id).unwrap(); - (manager, document, data.0.page_id) + (manager, document, data.page_id) } diff --git a/frontend/rust-lib/flowy-document2/tests/document/document_test.rs b/frontend/rust-lib/flowy-document2/tests/document/document_test.rs index e0978e424e..55e50d88f5 100644 --- a/frontend/rust-lib/flowy-document2/tests/document/document_test.rs +++ b/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); diff --git a/frontend/rust-lib/flowy-folder2/src/user_default.rs b/frontend/rust-lib/flowy-folder2/src/user_default.rs index 689be6a626..61a2f5e106 100644 --- a/frontend/rust-lib/flowy-folder2/src/user_default.rs +++ b/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(), - }; + 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(); - // 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 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::>(); 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::>(); + + 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(), + ) } } diff --git a/frontend/rust-lib/flowy-folder2/src/view_operation.rs b/frontend/rust-lib/flowy-folder2/src/view_operation.rs index c338fd8b45..ca37523ba6 100644 --- a/frontend/rust-lib/flowy-folder2/src/view_operation.rs +++ b/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, +} + +impl WorkspaceViewBuilder { + pub fn new(workspace_id: String) -> Self { + Self { + workspace_id, + views: vec![], + } + } + + pub async fn with_view_builder(&mut self, view_builder: F) + where + F: Fn(ViewBuilder) -> O, + O: Future, + { + let builder = ViewBuilder::new(self.workspace_id.clone()); + self.views.push(view_builder(builder).await); + } + + pub fn build(&mut self) -> Vec { + 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, +} + +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(mut self, child_view_builder: F) -> Self + where + F: Fn(ViewBuilder) -> O, + O: Future, + { + 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, +} + +pub struct FlattedViews; + +impl FlattedViews { + pub fn flatten_views(views: Vec) -> Vec { + 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>, + ) -> 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); + } +}