mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: create build in document (#2687)
* feat: support create multiple level views * refactor: rm document data wrapper * chore: add docs --------- Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
4fa39f298c
commit
ee52bf4b0e
@ -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(())
|
||||
})
|
||||
}
|
||||
|
@ -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();
|
||||
/// 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();
|
||||
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);
|
||||
// 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 },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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::<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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user