feat: sync the created view after duplicating (#5674)

* feat: sync the created view after duplicating

* chore: revert launch.json

* chore: refacotor code
This commit is contained in:
Lucas.Xu 2024-07-02 13:02:15 +08:00 committed by GitHub
parent a7b850e752
commit 8c1520b273
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 124 additions and 43 deletions

View File

@ -625,6 +625,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
await ViewBackendService.duplicate(
view: view,
openAfterDuplicate: true,
syncAfterDuplicate: true,
includeChildren: true,
parentViewId: newSpace.id,
suffix: '',

View File

@ -157,6 +157,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
final result = await ViewBackendService.duplicate(
view: view,
openAfterDuplicate: true,
syncAfterDuplicate: true,
includeChildren: true,
);
emit(

View File

@ -143,11 +143,13 @@ class ViewBackendService {
required bool includeChildren,
String? parentViewId,
String? suffix,
required bool syncAfterDuplicate,
}) {
final payload = DuplicateViewPayloadPB.create()
..viewId = view.id
..openAfterDuplicate = openAfterDuplicate
..includeChildren = includeChildren;
..includeChildren = includeChildren
..syncAfterCreate = syncAfterDuplicate;
if (parentViewId != null) {
payload.parentViewId = parentViewId;

View File

@ -242,15 +242,12 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
leftIcon: inner.leftIcon,
rightIcon: inner.rightIcon,
iconPadding: 10.0,
text: SizedBox(
height: 18.0,
child: FlowyText.regular(
text: FlowyText.regular(
inner.name,
color: inner == ViewMoreActionType.delete
? Theme.of(context).colorScheme.error
: null,
),
),
onTap: onTap,
),
);

View File

@ -140,7 +140,7 @@ impl EventIntegrationTest {
self
.appflowy_core
.folder_manager
.create_view_with_params(params)
.create_view_with_params(params, true)
.await
.unwrap();
}

View File

@ -185,16 +185,16 @@ impl FolderOperationHandler for DocumentFolderOperation {
&self,
user_id: i64,
params: CreateViewParams,
) -> FutureResult<(), FlowyError> {
) -> FutureResult<Option<EncodedCollab>, FlowyError> {
debug_assert_eq!(params.layout, ViewLayoutPB::Document);
let view_id = params.view_id.to_string();
let manager = self.0.clone();
FutureResult::new(async move {
let data = DocumentDataPB::try_from(Bytes::from(params.initial_data))?;
manager
let encoded_collab = manager
.create_document(user_id, &view_id, Some(data.into()))
.await?;
Ok(())
Ok(Some(encoded_collab))
})
}
@ -301,16 +301,16 @@ impl FolderOperationHandler for DatabaseFolderOperation {
&self,
_user_id: i64,
params: CreateViewParams,
) -> FutureResult<(), FlowyError> {
) -> FutureResult<Option<EncodedCollab>, FlowyError> {
match CreateDatabaseExtParams::from_map(params.meta.clone()) {
None => {
let database_manager = self.0.clone();
let view_id = params.view_id.to_string();
FutureResult::new(async move {
database_manager
let encoded_collab = database_manager
.create_database_with_database_data(&view_id, params.initial_data)
.await?;
Ok(())
Ok(Some(encoded_collab))
})
},
Some(database_params) => {
@ -338,7 +338,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
database_parent_view_id,
)
.await?;
Ok(())
Ok(None)
})
},
}
@ -505,7 +505,7 @@ impl FolderOperationHandler for ChatFolderOperation {
&self,
_user_id: i64,
_params: CreateViewParams,
) -> FutureResult<(), FlowyError> {
) -> FutureResult<Option<EncodedCollab>, FlowyError> {
FutureResult::new(async move { Err(FlowyError::not_support()) })
}

View File

@ -10,7 +10,7 @@ use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLay
use collab_database::workspace_database::{
CollabDocStateByOid, CollabFuture, DatabaseCollabService, DatabaseMeta, WorkspaceDatabase,
};
use collab_entity::CollabType;
use collab_entity::{CollabType, EncodedCollab};
use collab_plugins::local_storage::kv::KVTransactionDB;
use tokio::sync::{Mutex, RwLock};
use tracing::{event, instrument, trace};
@ -289,7 +289,7 @@ impl DatabaseManager {
&self,
view_id: &str,
data: Vec<u8>,
) -> FlowyResult<()> {
) -> FlowyResult<EncodedCollab> {
let database_data = DatabaseData::from_json_bytes(data)?;
let mut create_database_params = CreateDatabaseParams::from_database_data(database_data);
@ -305,8 +305,13 @@ impl DatabaseManager {
}
let wdb = self.get_database_indexer().await?;
let _ = wdb.create_database(create_database_params)?;
Ok(())
let database = wdb.create_database(create_database_params)?;
let encoded_collab = database
.lock()
.get_collab()
.lock()
.encode_collab_v1(|collab| CollabType::Database.validate_require_data(collab))?;
Ok(encoded_collab)
}
pub async fn create_database_with_params(

View File

@ -599,6 +599,9 @@ pub struct DuplicateViewPayloadPB {
// If the suffix is None, the duplicated view will have the same name with (copy) suffix.
#[pb(index = 5, one_of)]
pub suffix: Option<String>,
#[pb(index = 6)]
pub sync_after_create: bool,
}
#[derive(Debug)]
@ -612,6 +615,8 @@ pub struct DuplicateViewParams {
pub parent_view_id: Option<String>,
pub suffix: Option<String>,
pub sync_after_create: bool,
}
impl TryInto<DuplicateViewParams> for DuplicateViewPayloadPB {
@ -625,6 +630,7 @@ impl TryInto<DuplicateViewParams> for DuplicateViewPayloadPB {
include_children: self.include_children,
parent_view_id: self.parent_view_id,
suffix: self.suffix,
sync_after_create: self.sync_after_create,
})
}
}

View File

@ -105,7 +105,7 @@ pub(crate) async fn create_view_handler(
let folder = upgrade_folder(folder)?;
let params: CreateViewParams = data.into_inner().try_into()?;
let set_as_current = params.set_as_current;
let view = folder.create_view_with_params(params).await?;
let (view, _) = folder.create_view_with_params(params, true).await?;
if set_as_current {
let _ = folder.set_current_view(&view.id).await;
}

View File

@ -371,11 +371,21 @@ impl FolderManager {
}
}
pub async fn create_view_with_params(&self, params: CreateViewParams) -> FlowyResult<View> {
/// Asynchronously creates a view with provided parameters and notifies the workspace if update is needed.
///
/// Commonly, the notify_workspace_update parameter is set to true when the view is created in the workspace.
/// If you're handling multiple views in the same hierarchy and want to notify the workspace only after the last view is created,
/// you can set notify_workspace_update to false to avoid multiple notifications.
pub async fn create_view_with_params(
&self,
params: CreateViewParams,
notify_workspace_update: bool,
) -> FlowyResult<(View, Option<EncodedCollab>)> {
let workspace_id = self.user.workspace_id()?;
let view_layout: ViewLayout = params.layout.clone().into();
let handler = self.get_handler(&view_layout)?;
let user_id = self.user.user_id()?;
let mut encoded_collab: Option<EncodedCollab> = None;
if params.meta.is_empty() && params.initial_data.is_empty() {
tracing::trace!("Create view with build-in data");
@ -384,7 +394,7 @@ impl FolderManager {
.await?;
} else {
tracing::trace!("Create view with view data");
handler
encoded_collab = handler
.create_view_with_view_data(user_id, params.clone())
.await?;
}
@ -403,12 +413,14 @@ impl FolderManager {
},
);
if notify_workspace_update {
let folder = &self.mutex_folder.read();
if let Some(folder) = folder.as_ref() {
notify_did_update_workspace(&workspace_id, folder);
}
}
Ok(view)
Ok((view, encoded_collab))
}
/// The orphan view is meant to be a view that is not attached to any parent view. By default, this
@ -752,6 +764,7 @@ impl FolderManager {
params.open_after_duplicate,
params.include_children,
params.suffix,
params.sync_after_create,
)
.await
}
@ -767,6 +780,7 @@ impl FolderManager {
open_after_duplicated: bool,
include_children: bool,
suffix: Option<String>,
sync_after_create: bool,
) -> Result<(), FlowyError> {
if view_id == parent_view_id {
return Err(FlowyError::new(
@ -775,6 +789,7 @@ impl FolderManager {
));
}
// filter the view ids that in the trash or private section
let filtered_view_ids = self.with_folder(Vec::new, |folder| {
self.get_view_ids_should_be_filtered(folder)
});
@ -783,6 +798,7 @@ impl FolderManager {
let mut is_source_view = true;
// use a stack to duplicate the view and its children
let mut stack = vec![(view_id.to_string(), parent_view_id.to_string())];
let mut objects = vec![];
let suffix = suffix.unwrap_or(" (copy)".to_string());
while let Some((current_view_id, current_parent_id)) = stack.pop() {
@ -823,6 +839,7 @@ impl FolderManager {
} else {
view.name.clone()
};
let duplicate_params = CreateViewParams {
parent_view_id: current_parent_id.clone(),
name,
@ -838,7 +855,30 @@ impl FolderManager {
icon: view.icon.clone(),
};
let duplicated_view = self.create_view_with_params(duplicate_params).await?;
// set the notify_workspace_update to false to avoid multiple notifications
let (duplicated_view, encoded_collab) = self
.create_view_with_params(duplicate_params, false)
.await?;
if sync_after_create {
if let Some(encoded_collab) = encoded_collab {
let object_id = duplicated_view.id.clone();
let collab_type = match duplicated_view.layout {
ViewLayout::Document => CollabType::Document,
ViewLayout::Board | ViewLayout::Grid | ViewLayout::Calendar => CollabType::Database,
ViewLayout::Chat => CollabType::Unknown,
};
// don't block the whole import process if the view can't be encoded
if collab_type != CollabType::Unknown {
match self.get_folder_collab_params(object_id, collab_type, encoded_collab) {
Ok(params) => objects.push(params),
Err(e) => {
error!("duplicate error {}", e);
},
}
}
}
}
if include_children {
let child_views = self.get_views_belong_to(&current_view_id).await?;
@ -854,6 +894,23 @@ impl FolderManager {
is_source_view = false
}
let workspace_id = &self.user.workspace_id()?;
// Sync the view to the cloud
if sync_after_create {
self
.cloud_service
.batch_create_folder_collab_objects(workspace_id, objects)
.await?;
}
// notify the update here
notify_parent_view_did_change(
workspace_id,
self.mutex_folder.clone(),
vec![parent_view_id.to_string()],
);
Ok(())
}
@ -1128,18 +1185,11 @@ impl FolderManager {
if sync_after_create {
if let Some(encoded_collab) = encoded_collab {
// Try to encode the collaboration data to bytes
let encode_collab_v1 = encoded_collab.encode_to_bytes().map_err(internal_error);
// If the view can't be encoded, skip it and don't block the whole import process
match encode_collab_v1 {
Ok(encode_collab_v1) => objects.push(FolderCollabParams {
object_id,
encoded_collab_v1: encode_collab_v1,
collab_type,
}),
// don't block the whole import process if the view can't be encoded
match self.get_folder_collab_params(object_id, collab_type, encoded_collab) {
Ok(params) => objects.push(params),
Err(e) => {
error!("import error {}", e)
error!("import error {}", e);
},
}
}
@ -1214,6 +1264,22 @@ impl FolderManager {
}
}
fn get_folder_collab_params(
&self,
object_id: String,
collab_type: CollabType,
encoded_collab: EncodedCollab,
) -> FlowyResult<FolderCollabParams> {
// Try to encode the collaboration data to bytes
let encoded_collab_v1: Result<Vec<u8>, FlowyError> =
encoded_collab.encode_to_bytes().map_err(internal_error);
encoded_collab_v1.map(|encoded_collab_v1| FolderCollabParams {
object_id,
encoded_collab_v1,
collab_type,
})
}
/// Returns the relation of the view. The relation is a tuple of (is_workspace, parent_view_id,
/// child_view_ids). If the view is a workspace, then the parent_view_id is the workspace id.
/// Otherwise, the parent_view_id is the parent view id of the view. The child_view_ids is the

View File

@ -51,7 +51,7 @@ impl FolderManager {
icon: None,
extra: None,
};
self.create_view_with_params(params).await.unwrap();
self.create_view_with_params(params, true).await.unwrap();
view_id
}
}

View File

@ -61,11 +61,14 @@ pub trait FolderOperationHandler {
/// * `layout`: the layout of the view
/// * `meta`: use to carry extra information. For example, the database view will use this
/// to carry the reference database id.
///
/// The return value is the [Option<EncodedCollab>] that can be used to create the view.
/// It can be used in syncing the view data to cloud.
fn create_view_with_view_data(
&self,
user_id: i64,
params: CreateViewParams,
) -> FutureResult<(), FlowyError>;
) -> FutureResult<Option<EncodedCollab>, FlowyError>;
/// Create a view with the pre-defined data.
/// For example, the initial data of the grid/calendar/kanban board when