2023-06-06 09:19:53 +00:00
|
|
|
use std::collections::HashSet;
|
2023-04-28 06:08:53 +00:00
|
|
|
use std::ops::Deref;
|
2023-05-31 09:42:14 +00:00
|
|
|
use std::sync::{Arc, Weak};
|
2023-04-28 06:08:53 +00:00
|
|
|
|
2023-05-15 14:16:05 +00:00
|
|
|
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
2023-06-07 06:33:12 +00:00
|
|
|
use appflowy_integrate::CollabPersistenceConfig;
|
2023-05-31 09:42:14 +00:00
|
|
|
use collab::core::collab_state::CollabState;
|
2023-04-28 06:08:53 +00:00
|
|
|
use collab_folder::core::{
|
2023-06-08 06:20:31 +00:00
|
|
|
Folder, FolderContext, TrashChange, TrashChangeReceiver, TrashInfo, View, ViewChange,
|
|
|
|
ViewChangeReceiver, ViewLayout, Workspace,
|
2023-04-28 06:08:53 +00:00
|
|
|
};
|
|
|
|
use parking_lot::Mutex;
|
2023-06-05 01:42:11 +00:00
|
|
|
use tokio_stream::wrappers::WatchStream;
|
|
|
|
use tokio_stream::StreamExt;
|
2023-04-28 06:08:53 +00:00
|
|
|
use tracing::{event, Level};
|
|
|
|
|
2023-05-31 06:08:54 +00:00
|
|
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
2023-04-28 06:08:53 +00:00
|
|
|
|
2023-06-05 01:42:11 +00:00
|
|
|
use crate::deps::{FolderCloudService, FolderUser};
|
2023-04-04 00:41:16 +00:00
|
|
|
use crate::entities::{
|
2023-06-20 15:48:34 +00:00
|
|
|
view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, CreateViewParams,
|
|
|
|
CreateWorkspaceParams, DeletedViewPB, RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB,
|
|
|
|
UpdateViewParams, ViewPB, WorkspacePB,
|
2023-04-04 00:41:16 +00:00
|
|
|
};
|
|
|
|
use crate::notification::{
|
|
|
|
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
|
|
|
FolderNotification,
|
|
|
|
};
|
2023-05-31 06:08:54 +00:00
|
|
|
use crate::share::ImportParams;
|
2023-05-23 15:55:21 +00:00
|
|
|
use crate::user_default::DefaultFolderBuilder;
|
2023-06-01 12:23:27 +00:00
|
|
|
use crate::view_operation::{
|
|
|
|
create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers,
|
|
|
|
};
|
2023-04-04 00:41:16 +00:00
|
|
|
|
|
|
|
pub struct Folder2Manager {
|
2023-05-31 09:42:14 +00:00
|
|
|
mutex_folder: Arc<MutexFolder>,
|
2023-05-15 14:16:05 +00:00
|
|
|
collab_builder: Arc<AppFlowyCollabBuilder>,
|
2023-04-04 00:41:16 +00:00
|
|
|
user: Arc<dyn FolderUser>,
|
2023-05-31 06:08:54 +00:00
|
|
|
operation_handlers: FolderOperationHandlers,
|
2023-05-23 15:55:21 +00:00
|
|
|
cloud_service: Arc<dyn FolderCloudService>,
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl Send for Folder2Manager {}
|
|
|
|
unsafe impl Sync for Folder2Manager {}
|
|
|
|
|
|
|
|
impl Folder2Manager {
|
|
|
|
pub async fn new(
|
|
|
|
user: Arc<dyn FolderUser>,
|
2023-05-15 14:16:05 +00:00
|
|
|
collab_builder: Arc<AppFlowyCollabBuilder>,
|
2023-05-31 06:08:54 +00:00
|
|
|
operation_handlers: FolderOperationHandlers,
|
2023-05-23 15:55:21 +00:00
|
|
|
cloud_service: Arc<dyn FolderCloudService>,
|
2023-04-04 00:41:16 +00:00
|
|
|
) -> FlowyResult<Self> {
|
2023-05-31 09:42:14 +00:00
|
|
|
let mutex_folder = Arc::new(MutexFolder::default());
|
2023-04-04 00:41:16 +00:00
|
|
|
let manager = Self {
|
|
|
|
user,
|
2023-05-31 09:42:14 +00:00
|
|
|
mutex_folder,
|
2023-05-15 14:16:05 +00:00
|
|
|
collab_builder,
|
2023-05-31 06:08:54 +00:00
|
|
|
operation_handlers,
|
2023-05-23 15:55:21 +00:00
|
|
|
cloud_service,
|
2023-04-04 00:41:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(manager)
|
|
|
|
}
|
|
|
|
|
2023-06-06 16:05:27 +00:00
|
|
|
pub async fn get_current_workspace(&self) -> FlowyResult<WorkspacePB> {
|
|
|
|
self.with_folder(Err(FlowyError::internal()), |folder| {
|
2023-06-07 14:27:13 +00:00
|
|
|
let workspace_pb_from_workspace = |workspace: Workspace, folder: &Folder| {
|
|
|
|
let views = get_workspace_view_pbs(&workspace.id, folder);
|
2023-06-08 04:20:18 +00:00
|
|
|
let workspace: WorkspacePB = (workspace, views).into();
|
2023-06-07 14:27:13 +00:00
|
|
|
Ok::<WorkspacePB, FlowyError>(workspace)
|
|
|
|
};
|
|
|
|
|
2023-06-06 16:05:27 +00:00
|
|
|
match folder.get_current_workspace() {
|
2023-06-07 14:27:13 +00:00
|
|
|
None => {
|
|
|
|
// The current workspace should always exist. If not, try to find the first workspace.
|
|
|
|
// from the folder. Otherwise, return an error.
|
|
|
|
let mut workspaces = folder.workspaces.get_all_workspaces();
|
|
|
|
if workspaces.is_empty() {
|
|
|
|
Err(FlowyError::record_not_found().context("Can not find the workspace"))
|
|
|
|
} else {
|
|
|
|
tracing::error!("Can't find the current workspace, use the first workspace");
|
|
|
|
let workspace = workspaces.remove(0);
|
|
|
|
folder.set_current_workspace(&workspace.id);
|
|
|
|
workspace_pb_from_workspace(workspace, folder)
|
|
|
|
}
|
2023-06-06 16:05:27 +00:00
|
|
|
},
|
2023-06-07 14:27:13 +00:00
|
|
|
Some(workspace) => workspace_pb_from_workspace(workspace, folder),
|
2023-06-06 16:05:27 +00:00
|
|
|
}
|
|
|
|
})
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
|
2023-06-05 05:10:14 +00:00
|
|
|
/// Return a list of views of the current workspace.
|
|
|
|
/// Only the first level of child views are included.
|
2023-04-04 06:08:50 +00:00
|
|
|
pub async fn get_current_workspace_views(&self) -> FlowyResult<Vec<ViewPB>> {
|
|
|
|
let workspace_id = self
|
2023-05-31 09:42:14 +00:00
|
|
|
.mutex_folder
|
2023-04-04 06:08:50 +00:00
|
|
|
.lock()
|
|
|
|
.as_ref()
|
|
|
|
.map(|folder| folder.get_current_workspace_id());
|
|
|
|
|
|
|
|
if let Some(Some(workspace_id)) = workspace_id {
|
|
|
|
self.get_workspace_views(&workspace_id).await
|
|
|
|
} else {
|
|
|
|
Ok(vec![])
|
|
|
|
}
|
|
|
|
}
|
2023-04-04 00:41:16 +00:00
|
|
|
|
2023-04-04 06:08:50 +00:00
|
|
|
pub async fn get_workspace_views(&self, workspace_id: &str) -> FlowyResult<Vec<ViewPB>> {
|
|
|
|
let views = self.with_folder(vec![], |folder| {
|
|
|
|
get_workspace_view_pbs(workspace_id, folder)
|
2023-04-04 00:41:16 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Ok(views)
|
|
|
|
}
|
|
|
|
|
2023-04-28 06:08:53 +00:00
|
|
|
/// Called immediately after the application launched fi the user already sign in/sign up.
|
2023-06-08 06:20:31 +00:00
|
|
|
#[tracing::instrument(level = "info", skip(self), err)]
|
2023-05-21 10:53:59 +00:00
|
|
|
pub async fn initialize(&self, uid: i64, workspace_id: &str) -> FlowyResult<()> {
|
2023-05-31 09:42:14 +00:00
|
|
|
let workspace_id = workspace_id.to_string();
|
2023-05-21 10:53:59 +00:00
|
|
|
if let Ok(collab_db) = self.user.collab_db() {
|
2023-06-07 06:33:12 +00:00
|
|
|
let collab = self.collab_builder.build_with_config(
|
|
|
|
uid,
|
|
|
|
&workspace_id,
|
|
|
|
"workspace",
|
|
|
|
collab_db,
|
|
|
|
&CollabPersistenceConfig::new()
|
|
|
|
.enable_snapshot(true)
|
|
|
|
.snapshot_per_update(5),
|
|
|
|
);
|
2023-05-21 10:53:59 +00:00
|
|
|
let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
|
|
|
|
let (trash_tx, trash_rx) = tokio::sync::broadcast::channel(100);
|
|
|
|
let folder_context = FolderContext {
|
2023-05-31 09:42:14 +00:00
|
|
|
view_change_tx: view_tx,
|
|
|
|
trash_change_tx: trash_tx,
|
2023-05-21 10:53:59 +00:00
|
|
|
};
|
2023-05-31 09:42:14 +00:00
|
|
|
let folder = Folder::get_or_create(collab, folder_context);
|
|
|
|
let folder_state_rx = folder.subscribe_state_change();
|
|
|
|
*self.mutex_folder.lock() = Some(folder);
|
|
|
|
|
|
|
|
let weak_mutex_folder = Arc::downgrade(&self.mutex_folder);
|
|
|
|
listen_on_folder_state_change(workspace_id, folder_state_rx, &weak_mutex_folder);
|
|
|
|
listen_on_trash_change(trash_rx, &weak_mutex_folder);
|
|
|
|
listen_on_view_change(view_rx, &weak_mutex_folder);
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-04-28 06:08:53 +00:00
|
|
|
/// Called after the user sign up / sign in
|
2023-05-21 10:53:59 +00:00
|
|
|
pub async fn initialize_with_new_user(
|
|
|
|
&self,
|
|
|
|
user_id: i64,
|
|
|
|
token: &str,
|
|
|
|
workspace_id: &str,
|
|
|
|
) -> FlowyResult<()> {
|
|
|
|
self.initialize(user_id, workspace_id).await?;
|
|
|
|
let (folder_data, workspace_pb) = DefaultFolderBuilder::build(
|
|
|
|
self.user.user_id()?,
|
|
|
|
workspace_id.to_string(),
|
2023-05-31 06:08:54 +00:00
|
|
|
&self.operation_handlers,
|
2023-05-21 10:53:59 +00:00
|
|
|
)
|
|
|
|
.await;
|
2023-04-04 00:41:16 +00:00
|
|
|
self.with_folder((), |folder| {
|
|
|
|
folder.create_with_data(folder_data);
|
|
|
|
});
|
|
|
|
|
|
|
|
send_notification(token, FolderNotification::DidCreateWorkspace)
|
|
|
|
.payload(RepeatedWorkspacePB {
|
|
|
|
items: vec![workspace_pb],
|
|
|
|
})
|
|
|
|
.send();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Called when the current user logout
|
|
|
|
///
|
2023-04-28 06:08:53 +00:00
|
|
|
pub async fn clear(&self, _user_id: i64) {}
|
2023-04-04 00:41:16 +00:00
|
|
|
|
2023-06-08 06:20:31 +00:00
|
|
|
#[tracing::instrument(level = "info", skip_all, err)]
|
2023-04-04 00:41:16 +00:00
|
|
|
pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> FlowyResult<Workspace> {
|
2023-05-23 15:55:21 +00:00
|
|
|
let workspace = self
|
|
|
|
.cloud_service
|
|
|
|
.create_workspace(self.user.user_id()?, ¶ms.name)
|
|
|
|
.await?;
|
2023-06-07 14:27:13 +00:00
|
|
|
|
2023-04-04 00:41:16 +00:00
|
|
|
self.with_folder((), |folder| {
|
|
|
|
folder.workspaces.create_workspace(workspace.clone());
|
|
|
|
folder.set_current_workspace(&workspace.id);
|
|
|
|
});
|
|
|
|
|
|
|
|
let repeated_workspace = RepeatedWorkspacePB {
|
|
|
|
items: vec![workspace.clone().into()],
|
|
|
|
};
|
|
|
|
send_workspace_notification(FolderNotification::DidCreateWorkspace, repeated_workspace);
|
|
|
|
Ok(workspace)
|
|
|
|
}
|
|
|
|
|
2023-06-08 06:20:31 +00:00
|
|
|
#[tracing::instrument(level = "info", skip_all, err)]
|
2023-04-04 00:41:16 +00:00
|
|
|
pub async fn open_workspace(&self, workspace_id: &str) -> FlowyResult<Workspace> {
|
|
|
|
self.with_folder(Err(FlowyError::internal()), |folder| {
|
|
|
|
let workspace = folder
|
|
|
|
.workspaces
|
|
|
|
.get_workspace(workspace_id)
|
|
|
|
.ok_or_else(|| {
|
|
|
|
FlowyError::record_not_found().context("Can't open not existing workspace")
|
|
|
|
})?;
|
2023-06-07 14:27:13 +00:00
|
|
|
folder.set_current_workspace(&workspace.id);
|
2023-04-04 00:41:16 +00:00
|
|
|
Ok::<Workspace, FlowyError>(workspace)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_workspace(&self, workspace_id: &str) -> Option<Workspace> {
|
|
|
|
self.with_folder(None, |folder| folder.workspaces.get_workspace(workspace_id))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn with_folder<F, Output>(&self, default_value: Output, f: F) -> Output
|
|
|
|
where
|
2023-05-31 09:42:14 +00:00
|
|
|
F: FnOnce(&Folder) -> Output,
|
2023-04-04 00:41:16 +00:00
|
|
|
{
|
2023-05-31 09:42:14 +00:00
|
|
|
let folder = self.mutex_folder.lock();
|
2023-04-04 00:41:16 +00:00
|
|
|
match &*folder {
|
|
|
|
None => default_value,
|
|
|
|
Some(folder) => f(folder),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_all_workspaces(&self) -> Vec<Workspace> {
|
|
|
|
self.with_folder(vec![], |folder| folder.workspaces.get_all_workspaces())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn create_view_with_params(&self, params: CreateViewParams) -> FlowyResult<View> {
|
|
|
|
let view_layout: ViewLayout = params.layout.clone().into();
|
2023-05-31 06:08:54 +00:00
|
|
|
let handler = self.get_handler(&view_layout)?;
|
2023-04-04 00:41:16 +00:00
|
|
|
let user_id = self.user.user_id()?;
|
2023-06-01 12:23:27 +00:00
|
|
|
let meta = params.meta.clone();
|
2023-06-06 09:19:53 +00:00
|
|
|
|
|
|
|
if meta.is_empty() && params.initial_data.is_empty() {
|
|
|
|
tracing::trace!("Create view with build-in data");
|
|
|
|
handler
|
|
|
|
.create_built_in_view(user_id, ¶ms.view_id, ¶ms.name, view_layout.clone())
|
|
|
|
.await?;
|
|
|
|
} else {
|
|
|
|
tracing::trace!("Create view with view data");
|
|
|
|
handler
|
|
|
|
.create_view_with_view_data(
|
|
|
|
user_id,
|
|
|
|
¶ms.view_id,
|
|
|
|
¶ms.name,
|
|
|
|
params.initial_data.clone(),
|
|
|
|
view_layout.clone(),
|
|
|
|
meta,
|
|
|
|
)
|
|
|
|
.await?;
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
2023-06-06 09:19:53 +00:00
|
|
|
|
2023-05-31 06:08:54 +00:00
|
|
|
let view = create_view(params, view_layout);
|
2023-04-04 00:41:16 +00:00
|
|
|
self.with_folder((), |folder| {
|
|
|
|
folder.insert_view(view.clone());
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(view)
|
|
|
|
}
|
|
|
|
|
2023-06-14 14:16:33 +00:00
|
|
|
/// The orphan view is meant to be a view that is not attached to any parent view. By default, this
|
|
|
|
/// view will not be shown in the view list unless it is attached to a parent view that is shown in
|
|
|
|
/// the view list.
|
|
|
|
pub async fn create_orphan_view_with_params(
|
|
|
|
&self,
|
|
|
|
params: CreateViewParams,
|
|
|
|
) -> FlowyResult<View> {
|
|
|
|
let view_layout: ViewLayout = params.layout.clone().into();
|
|
|
|
let handler = self.get_handler(&view_layout)?;
|
|
|
|
let user_id = self.user.user_id()?;
|
|
|
|
handler
|
|
|
|
.create_built_in_view(user_id, ¶ms.view_id, ¶ms.name, view_layout.clone())
|
|
|
|
.await?;
|
|
|
|
let view = create_view(params, view_layout);
|
|
|
|
self.with_folder((), |folder| {
|
|
|
|
folder.insert_view(view.clone());
|
|
|
|
});
|
|
|
|
Ok(view)
|
|
|
|
}
|
|
|
|
|
2023-04-04 00:41:16 +00:00
|
|
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
|
|
|
pub(crate) async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
|
|
|
let view = self
|
|
|
|
.with_folder(None, |folder| folder.views.get_view(view_id))
|
|
|
|
.ok_or_else(|| {
|
|
|
|
FlowyError::record_not_found().context("Can't find the view when closing the view")
|
|
|
|
})?;
|
2023-05-31 06:08:54 +00:00
|
|
|
let handler = self.get_handler(&view.layout)?;
|
|
|
|
handler.close_view(view_id).await?;
|
2023-04-04 00:41:16 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-06-03 05:40:12 +00:00
|
|
|
/// Returns the view with the given view id.
|
|
|
|
/// The child views of the view will only access the first. So if you want to get the child view's
|
|
|
|
/// child view, you need to call this method again.
|
2023-04-04 00:41:16 +00:00
|
|
|
#[tracing::instrument(level = "debug", skip(self, view_id), err)]
|
|
|
|
pub async fn get_view(&self, view_id: &str) -> FlowyResult<ViewPB> {
|
|
|
|
let view_id = view_id.to_string();
|
2023-05-31 09:42:14 +00:00
|
|
|
let folder = self.mutex_folder.lock();
|
2023-04-04 00:41:16 +00:00
|
|
|
let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
|
|
|
|
let trash_ids = folder
|
|
|
|
.get_all_trash()
|
|
|
|
.into_iter()
|
|
|
|
.map(|trash| trash.id)
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
|
|
|
if trash_ids.contains(&view_id) {
|
|
|
|
return Err(FlowyError::record_not_found());
|
|
|
|
}
|
|
|
|
|
|
|
|
match folder.views.get_view(&view_id) {
|
|
|
|
None => Err(FlowyError::record_not_found()),
|
2023-06-23 15:19:34 +00:00
|
|
|
Some(view) => {
|
2023-05-10 11:43:32 +00:00
|
|
|
let child_views = folder
|
2023-04-04 00:41:16 +00:00
|
|
|
.views
|
2023-05-10 11:43:32 +00:00
|
|
|
.get_views_belong_to(&view.id)
|
2023-04-04 00:41:16 +00:00
|
|
|
.into_iter()
|
|
|
|
.filter(|view| !trash_ids.contains(&view.id))
|
2023-06-23 15:19:34 +00:00
|
|
|
.collect::<Vec<_>>();
|
2023-05-10 11:43:32 +00:00
|
|
|
let view_pb = view_pb_with_child_views(view, child_views);
|
2023-04-04 00:41:16 +00:00
|
|
|
Ok(view_pb)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-03 05:40:12 +00:00
|
|
|
/// Move the view to trash. If the view is the current view, then set the current view to empty.
|
|
|
|
/// When the view is moved to trash, all the child views will be moved to trash as well.
|
2023-04-04 00:41:16 +00:00
|
|
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
|
|
|
pub async fn move_view_to_trash(&self, view_id: &str) -> FlowyResult<()> {
|
|
|
|
self.with_folder((), |folder| {
|
2023-06-20 15:48:34 +00:00
|
|
|
let view = folder.views.get_view(view_id);
|
2023-06-08 06:20:31 +00:00
|
|
|
folder.add_trash(vec![view_id.to_string()]);
|
2023-04-04 00:41:16 +00:00
|
|
|
|
2023-06-05 05:10:14 +00:00
|
|
|
// notify the parent view that the view is moved to trash
|
|
|
|
send_notification(view_id, FolderNotification::DidMoveViewToTrash)
|
|
|
|
.payload(DeletedViewPB {
|
|
|
|
view_id: view_id.to_string(),
|
|
|
|
index: None,
|
|
|
|
})
|
|
|
|
.send();
|
2023-06-20 15:48:34 +00:00
|
|
|
|
|
|
|
if let Some(view) = view {
|
|
|
|
notify_child_views_changed(
|
|
|
|
view_pb_without_child_views(view),
|
|
|
|
ChildViewChangeReason::DidDeleteView,
|
|
|
|
);
|
|
|
|
}
|
2023-04-04 00:41:16 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-06-06 16:05:27 +00:00
|
|
|
/// Move the view with given id from one position to another position.
|
|
|
|
/// The view will be moved to the new position in the same parent view.
|
|
|
|
/// The passed in index is the index of the view that displayed in the UI.
|
|
|
|
/// We need to convert the index to the real index of the view in the parent view.
|
|
|
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
2023-04-04 00:41:16 +00:00
|
|
|
pub async fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> {
|
2023-06-06 16:05:27 +00:00
|
|
|
if let Some((is_workspace, parent_view_id, child_views)) = self.get_view_relation(view_id).await
|
|
|
|
{
|
|
|
|
// The display parent view is the view that is displayed in the UI
|
|
|
|
let display_views = if is_workspace {
|
|
|
|
self
|
|
|
|
.get_current_workspace()
|
|
|
|
.await?
|
|
|
|
.views
|
|
|
|
.into_iter()
|
|
|
|
.map(|view| view.id)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
} else {
|
|
|
|
self
|
|
|
|
.get_view(&parent_view_id)
|
|
|
|
.await?
|
|
|
|
.child_views
|
|
|
|
.into_iter()
|
|
|
|
.map(|view| view.id)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
};
|
2023-04-04 00:41:16 +00:00
|
|
|
|
2023-06-06 16:05:27 +00:00
|
|
|
if display_views.len() > to {
|
|
|
|
let to_view_id = display_views[to].clone();
|
|
|
|
|
|
|
|
// Find the actual index of the view in the parent view
|
|
|
|
let actual_from_index = child_views.iter().position(|id| id == view_id);
|
|
|
|
let actual_to_index = child_views.iter().position(|id| id == &to_view_id);
|
|
|
|
if let (Some(actual_from_index), Some(actual_to_index)) =
|
|
|
|
(actual_from_index, actual_to_index)
|
|
|
|
{
|
|
|
|
self.with_folder((), |folder| {
|
|
|
|
folder.move_view(view_id, actual_from_index as u32, actual_to_index as u32);
|
|
|
|
});
|
|
|
|
notify_parent_view_did_change(self.mutex_folder.clone(), vec![parent_view_id]);
|
|
|
|
}
|
|
|
|
}
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-06-03 05:40:12 +00:00
|
|
|
/// Return a list of views that belong to the given parent view id.
|
|
|
|
#[tracing::instrument(level = "debug", skip(self, parent_view_id), err)]
|
2023-06-23 15:19:34 +00:00
|
|
|
pub async fn get_views_belong_to(&self, parent_view_id: &str) -> FlowyResult<Vec<Arc<View>>> {
|
2023-06-03 05:40:12 +00:00
|
|
|
let views = self.with_folder(vec![], |folder| {
|
|
|
|
folder.views.get_views_belong_to(parent_view_id)
|
|
|
|
});
|
2023-04-04 00:41:16 +00:00
|
|
|
Ok(views)
|
|
|
|
}
|
|
|
|
|
2023-06-03 05:40:12 +00:00
|
|
|
/// Update the view with the given params.
|
2023-04-04 00:41:16 +00:00
|
|
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
2023-05-10 11:43:32 +00:00
|
|
|
pub async fn update_view_with_params(&self, params: UpdateViewParams) -> FlowyResult<()> {
|
2023-06-01 12:23:27 +00:00
|
|
|
let value = self.with_folder(None, |folder| {
|
|
|
|
let old_view = folder.views.get_view(¶ms.view_id);
|
|
|
|
let new_view = folder.views.update_view(¶ms.view_id, |update| {
|
2023-04-04 00:41:16 +00:00
|
|
|
update
|
|
|
|
.set_name_if_not_none(params.name)
|
|
|
|
.set_desc_if_not_none(params.desc)
|
2023-06-01 12:23:27 +00:00
|
|
|
.set_layout_if_not_none(params.layout)
|
2023-06-14 14:16:33 +00:00
|
|
|
.set_icon_url_if_not_none(params.icon_url)
|
|
|
|
.set_cover_url_if_not_none(params.cover_url)
|
2023-04-04 00:41:16 +00:00
|
|
|
.done()
|
|
|
|
});
|
2023-06-20 15:48:34 +00:00
|
|
|
|
2023-06-01 12:23:27 +00:00
|
|
|
Some((old_view, new_view))
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some((Some(old_view), Some(new_view))) = value {
|
|
|
|
if let Ok(handler) = self.get_handler(&old_view.layout) {
|
|
|
|
handler.did_update_view(&old_view, &new_view).await?;
|
|
|
|
}
|
|
|
|
}
|
2023-04-04 00:41:16 +00:00
|
|
|
|
2023-05-10 11:43:32 +00:00
|
|
|
if let Ok(view_pb) = self.get_view(¶ms.view_id).await {
|
|
|
|
send_notification(&view_pb.id, FolderNotification::DidUpdateView)
|
|
|
|
.payload(view_pb)
|
|
|
|
.send();
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
2023-05-10 11:43:32 +00:00
|
|
|
Ok(())
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
|
2023-06-03 05:40:12 +00:00
|
|
|
/// Duplicate the view with the given view id.
|
2023-04-04 00:41:16 +00:00
|
|
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
|
|
|
pub(crate) async fn duplicate_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
|
|
|
let view = self
|
|
|
|
.with_folder(None, |folder| folder.views.get_view(view_id))
|
|
|
|
.ok_or_else(|| FlowyError::record_not_found().context("Can't duplicate the view"))?;
|
|
|
|
|
2023-05-31 06:08:54 +00:00
|
|
|
let handler = self.get_handler(&view.layout)?;
|
|
|
|
let view_data = handler.duplicate_view(&view.id).await?;
|
2023-04-04 00:41:16 +00:00
|
|
|
let duplicate_params = CreateViewParams {
|
2023-06-01 12:23:27 +00:00
|
|
|
parent_view_id: view.parent_view_id.clone(),
|
2023-04-04 00:41:16 +00:00
|
|
|
name: format!("{} (copy)", &view.name),
|
2023-06-23 15:19:34 +00:00
|
|
|
desc: view.desc.clone(),
|
|
|
|
layout: view.layout.clone().into(),
|
2023-04-04 00:41:16 +00:00
|
|
|
initial_data: view_data.to_vec(),
|
|
|
|
view_id: gen_view_id(),
|
2023-06-03 05:40:12 +00:00
|
|
|
meta: Default::default(),
|
|
|
|
set_as_current: true,
|
2023-04-04 00:41:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let _ = self.create_view_with_params(duplicate_params).await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
|
|
|
pub(crate) async fn set_current_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
2023-05-31 09:42:14 +00:00
|
|
|
let folder = self.mutex_folder.lock();
|
2023-04-04 00:41:16 +00:00
|
|
|
let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
|
|
|
|
folder.set_current_view(view_id);
|
|
|
|
|
|
|
|
let workspace = folder.get_current_workspace();
|
|
|
|
let view = folder
|
|
|
|
.get_current_view()
|
|
|
|
.and_then(|view_id| folder.views.get_view(&view_id));
|
|
|
|
send_workspace_setting_notification(workspace, view);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
|
|
pub(crate) async fn get_current_view(&self) -> Option<ViewPB> {
|
|
|
|
let view_id = self.with_folder(None, |folder| folder.get_current_view())?;
|
|
|
|
self.get_view(&view_id).await.ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
|
|
pub(crate) async fn get_all_trash(&self) -> Vec<TrashInfo> {
|
2023-06-08 06:20:31 +00:00
|
|
|
self.with_folder(vec![], |folder| folder.get_all_trash())
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
|
|
pub(crate) async fn restore_all_trash(&self) {
|
|
|
|
self.with_folder((), |folder| {
|
2023-06-08 06:20:31 +00:00
|
|
|
folder.remote_all_trash();
|
2023-04-04 00:41:16 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
send_notification("trash", FolderNotification::DidUpdateTrash)
|
|
|
|
.payload(RepeatedTrashPB { items: vec![] })
|
|
|
|
.send();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
|
|
pub(crate) async fn restore_trash(&self, trash_id: &str) {
|
|
|
|
self.with_folder((), |folder| {
|
2023-06-08 06:20:31 +00:00
|
|
|
folder.delete_trash(vec![trash_id.to_string()]);
|
2023-04-04 00:41:16 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-05 01:42:11 +00:00
|
|
|
/// Delete all the trash permanently.
|
2023-04-04 00:41:16 +00:00
|
|
|
#[tracing::instrument(level = "trace", skip(self))]
|
|
|
|
pub(crate) async fn delete_all_trash(&self) {
|
2023-06-08 06:20:31 +00:00
|
|
|
let deleted_trash = self.with_folder(vec![], |folder| folder.get_all_trash());
|
2023-06-05 01:42:11 +00:00
|
|
|
for trash in deleted_trash {
|
|
|
|
let _ = self.delete_trash(&trash.id).await;
|
|
|
|
}
|
2023-04-04 00:41:16 +00:00
|
|
|
send_notification("trash", FolderNotification::DidUpdateTrash)
|
|
|
|
.payload(RepeatedTrashPB { items: vec![] })
|
|
|
|
.send();
|
|
|
|
}
|
|
|
|
|
2023-06-05 01:42:11 +00:00
|
|
|
/// Delete the trash permanently.
|
|
|
|
/// Delete the view will delete all the resources that the view holds. For example, if the view
|
|
|
|
/// is a database view. Then the database will be deleted as well.
|
|
|
|
#[tracing::instrument(level = "debug", skip(self, view_id), err)]
|
|
|
|
pub async fn delete_trash(&self, view_id: &str) -> FlowyResult<()> {
|
|
|
|
let view = self.with_folder(None, |folder| folder.views.get_view(view_id));
|
|
|
|
self.with_folder((), |folder| {
|
2023-06-08 06:20:31 +00:00
|
|
|
folder.delete_trash(vec![view_id.to_string()]);
|
2023-06-05 01:42:11 +00:00
|
|
|
folder.views.delete_views(vec![view_id]);
|
|
|
|
});
|
|
|
|
if let Some(view) = view {
|
|
|
|
if let Ok(handler) = self.get_handler(&view.layout) {
|
|
|
|
handler.delete_view(view_id).await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-05-31 06:08:54 +00:00
|
|
|
pub(crate) async fn import(&self, import_data: ImportParams) -> FlowyResult<View> {
|
|
|
|
if import_data.data.is_none() && import_data.file_path.is_none() {
|
|
|
|
return Err(FlowyError::new(
|
|
|
|
ErrorCode::InvalidData,
|
|
|
|
"data or file_path is required",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
let handler = self.get_handler(&import_data.view_layout)?;
|
|
|
|
let view_id = gen_view_id();
|
|
|
|
if let Some(data) = import_data.data {
|
|
|
|
handler
|
2023-06-05 10:29:52 +00:00
|
|
|
.import_from_bytes(&view_id, &import_data.name, import_data.import_type, data)
|
2023-05-31 06:08:54 +00:00
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(file_path) = import_data.file_path {
|
|
|
|
handler
|
|
|
|
.import_from_file_path(&view_id, &import_data.name, file_path)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let params = CreateViewParams {
|
|
|
|
parent_view_id: import_data.parent_view_id,
|
|
|
|
name: import_data.name,
|
|
|
|
desc: "".to_string(),
|
|
|
|
layout: import_data.view_layout.clone().into(),
|
|
|
|
initial_data: vec![],
|
|
|
|
view_id,
|
|
|
|
meta: Default::default(),
|
2023-06-03 05:40:12 +00:00
|
|
|
set_as_current: false,
|
2023-05-31 06:08:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let view = create_view(params, import_data.view_layout);
|
|
|
|
self.with_folder((), |folder| {
|
|
|
|
folder.insert_view(view.clone());
|
|
|
|
});
|
2023-06-01 12:23:27 +00:00
|
|
|
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]);
|
2023-05-31 06:08:54 +00:00
|
|
|
Ok(view)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a handler that implements the [FolderOperationHandler] trait
|
|
|
|
fn get_handler(
|
2023-04-04 00:41:16 +00:00
|
|
|
&self,
|
|
|
|
view_layout: &ViewLayout,
|
2023-05-31 06:08:54 +00:00
|
|
|
) -> FlowyResult<Arc<dyn FolderOperationHandler + Send + Sync>> {
|
|
|
|
match self.operation_handlers.get(view_layout) {
|
2023-04-04 00:41:16 +00:00
|
|
|
None => Err(FlowyError::internal().context(format!(
|
|
|
|
"Get data processor failed. Unknown layout type: {:?}",
|
|
|
|
view_layout
|
|
|
|
))),
|
|
|
|
Some(processor) => Ok(processor.clone()),
|
|
|
|
}
|
|
|
|
}
|
2023-06-06 16:05:27 +00:00
|
|
|
|
|
|
|
/// 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
|
|
|
|
/// child view ids of the view.
|
|
|
|
async fn get_view_relation(&self, view_id: &str) -> Option<(bool, String, Vec<String>)> {
|
|
|
|
self.with_folder(None, |folder| {
|
|
|
|
let view = folder.views.get_view(view_id)?;
|
|
|
|
match folder.views.get_view(&view.parent_view_id) {
|
|
|
|
None => folder.get_current_workspace().map(|workspace| {
|
|
|
|
(
|
|
|
|
true,
|
|
|
|
workspace.id,
|
|
|
|
workspace
|
|
|
|
.child_views
|
|
|
|
.items
|
|
|
|
.into_iter()
|
|
|
|
.map(|view| view.id)
|
|
|
|
.collect::<Vec<String>>(),
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
Some(parent_view) => Some((
|
|
|
|
false,
|
2023-06-23 15:19:34 +00:00
|
|
|
parent_view.id.clone(),
|
2023-06-06 16:05:27 +00:00
|
|
|
parent_view
|
|
|
|
.children
|
|
|
|
.items
|
2023-06-23 15:19:34 +00:00
|
|
|
.clone()
|
2023-06-06 16:05:27 +00:00
|
|
|
.into_iter()
|
|
|
|
.map(|view| view.id)
|
|
|
|
.collect::<Vec<String>>(),
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Listen on the [ViewChange] after create/delete/update events happened
|
2023-05-31 09:42:14 +00:00
|
|
|
fn listen_on_view_change(mut rx: ViewChangeReceiver, weak_mutex_folder: &Weak<MutexFolder>) {
|
|
|
|
let weak_mutex_folder = weak_mutex_folder.clone();
|
2023-04-04 00:41:16 +00:00
|
|
|
tokio::spawn(async move {
|
|
|
|
while let Ok(value) = rx.recv().await {
|
2023-05-31 09:42:14 +00:00
|
|
|
if let Some(folder) = weak_mutex_folder.upgrade() {
|
|
|
|
tracing::trace!("Did receive view change: {:?}", value);
|
|
|
|
match value {
|
|
|
|
ViewChange::DidCreateView { view } => {
|
2023-06-20 15:48:34 +00:00
|
|
|
notify_child_views_changed(
|
2023-06-23 15:19:34 +00:00
|
|
|
view_pb_without_child_views(Arc::new(view.clone())),
|
2023-06-20 15:48:34 +00:00
|
|
|
ChildViewChangeReason::DidCreateView,
|
|
|
|
);
|
2023-06-01 12:23:27 +00:00
|
|
|
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
|
2023-05-31 09:42:14 +00:00
|
|
|
},
|
2023-06-20 15:48:34 +00:00
|
|
|
ViewChange::DidDeleteView { views } => {
|
|
|
|
for view in views {
|
|
|
|
notify_child_views_changed(
|
|
|
|
view_pb_without_child_views(view),
|
|
|
|
ChildViewChangeReason::DidDeleteView,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
2023-05-31 09:42:14 +00:00
|
|
|
ViewChange::DidUpdate { view } => {
|
2023-06-20 15:48:34 +00:00
|
|
|
notify_child_views_changed(
|
2023-06-23 15:19:34 +00:00
|
|
|
view_pb_without_child_views(Arc::new(view.clone())),
|
2023-06-20 15:48:34 +00:00
|
|
|
ChildViewChangeReason::DidUpdateView,
|
|
|
|
);
|
2023-06-01 12:23:27 +00:00
|
|
|
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
|
2023-05-31 09:42:14 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fn listen_on_folder_state_change(
|
|
|
|
workspace_id: String,
|
|
|
|
mut folder_state_rx: WatchStream<CollabState>,
|
|
|
|
weak_mutex_folder: &Weak<MutexFolder>,
|
|
|
|
) {
|
|
|
|
let weak_mutex_folder = weak_mutex_folder.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
while let Some(state) = folder_state_rx.next().await {
|
|
|
|
if state.is_root_changed() {
|
|
|
|
if let Some(mutex_folder) = weak_mutex_folder.upgrade() {
|
|
|
|
let folder = mutex_folder.lock().take();
|
|
|
|
if let Some(folder) = folder {
|
|
|
|
tracing::trace!("🔥Reload folder");
|
|
|
|
let reload_folder = folder.reload();
|
|
|
|
notify_did_update_workspace(&workspace_id, &reload_folder);
|
|
|
|
*mutex_folder.lock() = Some(reload_folder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Listen on the [TrashChange]s and notify the frontend some views were changed.
|
2023-05-31 09:42:14 +00:00
|
|
|
fn listen_on_trash_change(mut rx: TrashChangeReceiver, weak_mutex_folder: &Weak<MutexFolder>) {
|
|
|
|
let weak_mutex_folder = weak_mutex_folder.clone();
|
2023-04-04 00:41:16 +00:00
|
|
|
tokio::spawn(async move {
|
|
|
|
while let Ok(value) = rx.recv().await {
|
2023-05-31 09:42:14 +00:00
|
|
|
if let Some(folder) = weak_mutex_folder.upgrade() {
|
|
|
|
let mut unique_ids = HashSet::new();
|
|
|
|
tracing::trace!("Did receive trash change: {:?}", value);
|
|
|
|
let ids = match value {
|
|
|
|
TrashChange::DidCreateTrash { ids } => ids,
|
|
|
|
TrashChange::DidDeleteTrash { ids } => ids,
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(folder) = folder.lock().as_ref() {
|
|
|
|
let views = folder.views.get_views(&ids);
|
|
|
|
for view in views {
|
2023-06-23 15:19:34 +00:00
|
|
|
unique_ids.insert(view.parent_view_id.clone());
|
2023-05-31 09:42:14 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 06:20:31 +00:00
|
|
|
let repeated_trash: RepeatedTrashPB = folder.get_all_trash().into();
|
2023-05-31 09:42:14 +00:00
|
|
|
send_notification("trash", FolderNotification::DidUpdateTrash)
|
|
|
|
.payload(repeated_trash)
|
|
|
|
.send();
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
|
2023-05-31 09:42:14 +00:00
|
|
|
let parent_view_ids = unique_ids.into_iter().collect();
|
|
|
|
notify_parent_view_did_change(folder.clone(), parent_view_ids);
|
2023-04-04 00:41:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-06 16:05:27 +00:00
|
|
|
/// Return the views that belong to the workspace. The views are filtered by the trash.
|
2023-05-31 09:42:14 +00:00
|
|
|
fn get_workspace_view_pbs(workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
|
2023-04-04 06:08:50 +00:00
|
|
|
let trash_ids = folder
|
|
|
|
.get_all_trash()
|
|
|
|
.into_iter()
|
|
|
|
.map(|trash| trash.id)
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
|
|
|
let mut views = folder.get_workspace_views(workspace_id);
|
|
|
|
views.retain(|view| !trash_ids.contains(&view.id));
|
|
|
|
|
|
|
|
views
|
|
|
|
.into_iter()
|
|
|
|
.map(|view| {
|
|
|
|
// Get child views
|
2023-05-10 11:43:32 +00:00
|
|
|
let child_views = folder
|
2023-04-04 06:08:50 +00:00
|
|
|
.views
|
2023-05-10 11:43:32 +00:00
|
|
|
.get_views_belong_to(&view.id)
|
2023-04-04 06:08:50 +00:00
|
|
|
.into_iter()
|
|
|
|
.collect();
|
2023-05-10 11:43:32 +00:00
|
|
|
view_pb_with_child_views(view, child_views)
|
2023-04-04 06:08:50 +00:00
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2023-05-31 09:42:14 +00:00
|
|
|
fn notify_did_update_workspace(workspace_id: &str, folder: &Folder) {
|
|
|
|
let repeated_view: RepeatedViewPB = get_workspace_view_pbs(workspace_id, folder).into();
|
|
|
|
tracing::trace!("Did update workspace views: {:?}", repeated_view);
|
|
|
|
send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceViews)
|
|
|
|
.payload(repeated_view)
|
|
|
|
.send();
|
|
|
|
}
|
|
|
|
|
2023-05-24 00:57:58 +00:00
|
|
|
/// Notify the the list of parent view ids that its child views were changed.
|
2023-04-04 00:41:16 +00:00
|
|
|
#[tracing::instrument(level = "debug", skip(folder, parent_view_ids))]
|
|
|
|
fn notify_parent_view_did_change<T: AsRef<str>>(
|
2023-05-31 09:42:14 +00:00
|
|
|
folder: Arc<MutexFolder>,
|
2023-04-04 00:41:16 +00:00
|
|
|
parent_view_ids: Vec<T>,
|
|
|
|
) -> Option<()> {
|
|
|
|
let folder = folder.lock();
|
|
|
|
let folder = folder.as_ref()?;
|
|
|
|
let workspace_id = folder.get_current_workspace_id()?;
|
|
|
|
let trash_ids = folder
|
|
|
|
.get_all_trash()
|
|
|
|
.into_iter()
|
|
|
|
.map(|trash| trash.id)
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
|
|
|
for parent_view_id in parent_view_ids {
|
|
|
|
let parent_view_id = parent_view_id.as_ref();
|
|
|
|
|
2023-05-21 10:53:59 +00:00
|
|
|
// if the view's parent id equal to workspace id. Then it will fetch the current
|
2023-04-04 00:41:16 +00:00
|
|
|
// workspace views. Because the the workspace is not a view stored in the views map.
|
|
|
|
if parent_view_id == workspace_id {
|
2023-05-31 09:42:14 +00:00
|
|
|
notify_did_update_workspace(&workspace_id, folder)
|
2023-04-04 00:41:16 +00:00
|
|
|
} else {
|
|
|
|
// Parent view can contain a list of child views. Currently, only get the first level
|
|
|
|
// child views.
|
|
|
|
let parent_view = folder.views.get_view(parent_view_id)?;
|
|
|
|
let mut child_views = folder.views.get_views_belong_to(parent_view_id);
|
|
|
|
child_views.retain(|view| !trash_ids.contains(&view.id));
|
|
|
|
event!(Level::DEBUG, child_views_count = child_views.len());
|
|
|
|
|
|
|
|
// Post the notification
|
2023-05-10 11:43:32 +00:00
|
|
|
let parent_view_pb = view_pb_with_child_views(parent_view, child_views);
|
2023-06-20 15:48:34 +00:00
|
|
|
send_notification(parent_view_id, FolderNotification::DidUpdateView)
|
2023-04-04 00:41:16 +00:00
|
|
|
.payload(parent_view_pb)
|
|
|
|
.send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2023-06-20 15:48:34 +00:00
|
|
|
pub enum ChildViewChangeReason {
|
|
|
|
DidCreateView,
|
|
|
|
DidDeleteView,
|
|
|
|
DidUpdateView,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Notify the the list of parent view ids that its child views were changed.
|
|
|
|
#[tracing::instrument(level = "debug", skip_all)]
|
|
|
|
fn notify_child_views_changed(view_pb: ViewPB, reason: ChildViewChangeReason) {
|
|
|
|
let parent_view_id = view_pb.parent_view_id.clone();
|
|
|
|
let mut payload = ChildViewUpdatePB {
|
|
|
|
parent_view_id: view_pb.parent_view_id.clone(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
match reason {
|
|
|
|
ChildViewChangeReason::DidCreateView => {
|
|
|
|
payload.create_child_views.push(view_pb);
|
|
|
|
},
|
|
|
|
ChildViewChangeReason::DidDeleteView => {
|
|
|
|
payload.delete_child_views.push(view_pb.id);
|
|
|
|
},
|
|
|
|
ChildViewChangeReason::DidUpdateView => {
|
|
|
|
payload.update_child_views.push(view_pb);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
send_notification(&parent_view_id, FolderNotification::DidUpdateChildViews)
|
|
|
|
.payload(payload)
|
|
|
|
.send();
|
|
|
|
}
|
|
|
|
|
2023-04-04 00:41:16 +00:00
|
|
|
fn folder_not_init_error() -> FlowyError {
|
|
|
|
FlowyError::internal().context("Folder not initialized")
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Default)]
|
2023-05-31 09:42:14 +00:00
|
|
|
pub struct MutexFolder(Arc<Mutex<Option<Folder>>>);
|
|
|
|
impl Deref for MutexFolder {
|
|
|
|
type Target = Arc<Mutex<Option<Folder>>>;
|
2023-04-04 00:41:16 +00:00
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
2023-05-31 09:42:14 +00:00
|
|
|
unsafe impl Sync for MutexFolder {}
|
|
|
|
unsafe impl Send for MutexFolder {}
|