refactor: rename group (#3815)

* chore: add group operation interceptor

* refactor: impl interceptor trait

* chore: update type option when group change

* test: fix test
This commit is contained in:
Nathan.fooo
2023-10-28 11:48:28 +08:00
committed by GitHub
parent 09b4e19c9d
commit e28e5a0649
34 changed files with 980 additions and 743 deletions

View File

@ -454,7 +454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"hashbrown 0.13.2", "hashbrown 0.12.3",
] ]
[[package]] [[package]]
@ -854,7 +854,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -873,7 +873,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -903,7 +903,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -915,7 +915,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -935,7 +935,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -949,7 +949,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -991,7 +991,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bincode", "bincode",
@ -1012,7 +1012,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1039,7 +1039,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -2115,6 +2115,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"thiserror", "thiserror",
"tokio",
"tokio-postgres", "tokio-postgres",
"url", "url",
"validator", "validator",

View File

@ -48,14 +48,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87
# To switch to the local path, run: # To switch to the local path, run:
# scripts/tool/update_collab_source.sh # scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }

View File

@ -461,7 +461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"hashbrown 0.13.2", "hashbrown 0.12.3",
] ]
[[package]] [[package]]
@ -721,7 +721,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -740,7 +740,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -770,7 +770,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -782,7 +782,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -802,7 +802,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -816,7 +816,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -858,7 +858,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bincode", "bincode",
@ -879,7 +879,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -906,7 +906,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ff10abd5e#ff10abd5e41b9a7d118cbb79f8c2bf9ac27c0ded" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=8861d7a#8861d7a45a2bda493f307483561d92e31fffff4c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1938,6 +1938,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"thiserror", "thiserror",
"tokio",
"tokio-postgres", "tokio-postgres",
"url", "url",
"validator", "validator",

View File

@ -92,11 +92,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87
# To switch to the local path, run: # To switch to the local path, run:
# scripts/tool/update_collab_source.sh # scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ff10abd5e" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "8861d7a" }

View File

@ -761,7 +761,7 @@ async fn rename_group_event_test() {
} }
#[tokio::test] #[tokio::test]
async fn hide_group_event_test2() { async fn hide_group_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await; let test = EventIntegrationTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace; let current_workspace = test.get_current_workspace().await.workspace;
let board_view = test let board_view = test

View File

@ -2,6 +2,7 @@ use std::sync::{Arc, Weak};
use collab_database::database::gen_row_id; use collab_database::database::gen_row_id;
use collab_database::rows::RowId; use collab_database::rows::RowId;
use tokio::sync::oneshot;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
@ -15,7 +16,7 @@ use crate::services::field::{
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset, type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
}; };
use crate::services::field_settings::FieldSettingsChangesetParams; use crate::services::field_settings::FieldSettingsChangesetParams;
use crate::services::group::{GroupChangeset, GroupChangesets}; use crate::services::group::GroupChangeset;
use crate::services::share::csv::CSVFormat; use crate::services::share::csv::CSVFormat;
fn upgrade_manager( fn upgrade_manager(
@ -725,18 +726,15 @@ pub(crate) async fn update_group_handler(
let view_id = params.view_id.clone(); let view_id = params.view_id.clone();
let database_editor = manager.get_database_with_view_id(&view_id).await?; let database_editor = manager.get_database_with_view_id(&view_id).await?;
let group_changeset = GroupChangeset::from(params); let group_changeset = GroupChangeset::from(params);
database_editor let (tx, rx) = oneshot::channel();
.update_group(&view_id, group_changeset.clone()) tokio::spawn(async move {
.await?; let result = database_editor
database_editor .update_group(&view_id, vec![group_changeset].into())
.update_group_setting( .await;
&view_id, let _ = tx.send(result);
GroupChangesets { });
update_groups: vec![group_changeset],
},
)
.await?;
let _ = rx.await?;
Ok(()) Ok(())
} }

View File

@ -8,10 +8,11 @@ use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetai
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting}; use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
use futures::StreamExt; use futures::StreamExt;
use tokio::sync::{broadcast, RwLock}; use tokio::sync::{broadcast, RwLock};
use tracing::{event, warn};
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
use flowy_task::TaskDispatcher; use flowy_task::TaskDispatcher;
use lib_infra::future::{to_fut, Fut}; use lib_infra::future::{to_fut, Fut, FutureResult};
use crate::entities::*; use crate::entities::*;
use crate::notification::{send_notification, DatabaseNotification}; use crate::notification::{send_notification, DatabaseNotification};
@ -20,7 +21,9 @@ use crate::services::cell::{
}; };
use crate::services::database::util::database_view_setting_pb_from_view; use crate::services::database::util::database_view_setting_pb_from_view;
use crate::services::database::UpdatedRow; use crate::services::database::UpdatedRow;
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews}; use crate::services::database_view::{
DatabaseViewChanged, DatabaseViewEditor, DatabaseViewOperation, DatabaseViews, EditorByViewId,
};
use crate::services::field::checklist_type_option::ChecklistCellChangeset; use crate::services::field::checklist_type_option::ChecklistCellChangeset;
use crate::services::field::{ use crate::services::field::{
default_type_option_data_from_type, select_type_option_from_field, transform_type_option, default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
@ -32,8 +35,7 @@ use crate::services::field_settings::{
}; };
use crate::services::filter::Filter; use crate::services::filter::Filter;
use crate::services::group::{ use crate::services::group::{
default_group_setting, GroupChangeset, GroupChangesets, GroupSetting, GroupSettingChangeset, default_group_setting, GroupChangesets, GroupSetting, GroupSettingChangeset, RowChangeset,
RowChangeset,
}; };
use crate::services::share::csv::{CSVExport, CSVFormat}; use crate::services::share::csv::{CSVExport, CSVFormat};
use crate::services::sort::Sort; use crate::services::sort::Sort;
@ -51,12 +53,6 @@ impl DatabaseEditor {
task_scheduler: Arc<RwLock<TaskDispatcher>>, task_scheduler: Arc<RwLock<TaskDispatcher>>,
) -> FlowyResult<Self> { ) -> FlowyResult<Self> {
let cell_cache = AnyTypeCache::<u64>::new(); let cell_cache = AnyTypeCache::<u64>::new();
let database_view_data = Arc::new(DatabaseViewDataImpl {
database: database.clone(),
task_scheduler: task_scheduler.clone(),
cell_cache: cell_cache.clone(),
});
let database_id = database.lock().get_database_id(); let database_id = database.lock().get_database_id();
// Receive database sync state and send to frontend via the notification // Receive database sync state and send to frontend via the notification
@ -93,8 +89,24 @@ impl DatabaseEditor {
} }
}); });
let database_views = // Used to cache the view of the database for fast access.
Arc::new(DatabaseViews::new(database.clone(), cell_cache.clone(), database_view_data).await?); let editor_by_view_id = Arc::new(RwLock::new(EditorByViewId::default()));
let view_operation = Arc::new(DatabaseViewOperationImpl {
database: database.clone(),
task_scheduler: task_scheduler.clone(),
cell_cache: cell_cache.clone(),
editor_by_view_id: editor_by_view_id.clone(),
});
let database_views = Arc::new(
DatabaseViews::new(
database.clone(),
cell_cache.clone(),
view_operation,
editor_by_view_id,
)
.await?,
);
Ok(Self { Ok(Self {
database, database,
cell_cache, cell_cache,
@ -177,40 +189,9 @@ impl DatabaseEditor {
Ok(self.database.lock().delete_view(view_id)) Ok(self.database.lock().delete_view(view_id))
} }
pub async fn update_group_setting( pub async fn update_group(&self, view_id: &str, changesets: GroupChangesets) -> FlowyResult<()> {
&self,
view_id: &str,
group_setting_changeset: GroupChangesets,
) -> FlowyResult<()> {
let view_editor = self.database_views.get_view_editor(view_id).await?; let view_editor = self.database_views.get_view_editor(view_id).await?;
view_editor view_editor.v_update_group(changesets).await?;
.v_update_group_setting(group_setting_changeset)
.await?;
Ok(())
}
pub async fn update_group(
&self,
view_id: &str,
group_changeset: GroupChangeset,
) -> FlowyResult<()> {
let view_editor = self.database_views.get_view_editor(view_id).await?;
let type_option = view_editor.update_group(group_changeset.clone()).await?;
if let Some(type_option_data) = type_option {
let field = self.get_field(&group_changeset.field_id);
if field.is_some() {
let _ = self
.update_field_type_option(
view_id,
&group_changeset.field_id,
type_option_data,
field.unwrap(),
)
.await;
}
}
Ok(()) Ok(())
} }
@ -295,9 +276,7 @@ impl DatabaseEditor {
.set_width_at_if_not_none(params.width.map(|value| value as i64)) .set_width_at_if_not_none(params.width.map(|value| value as i64))
.set_visibility_if_not_none(params.visibility); .set_visibility_if_not_none(params.visibility);
}); });
self notify_did_update_database_field(&self.database, &params.field_id)?;
.notify_did_update_database_field(&params.field_id)
.await?;
Ok(()) Ok(())
} }
@ -331,30 +310,13 @@ impl DatabaseEditor {
pub async fn update_field_type_option( pub async fn update_field_type_option(
&self, &self,
view_id: &str, view_id: &str,
field_id: &str, _field_id: &str,
type_option_data: TypeOptionData, type_option_data: TypeOptionData,
old_field: Field, old_field: Field,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
let field_type = FieldType::from(old_field.field_type); let view_editor = self.database_views.get_view_editor(view_id).await?;
self update_field_type_option_fn(&self.database, &view_editor, type_option_data, old_field).await?;
.database
.lock()
.fields
.update_field(field_id, |update| {
if old_field.is_primary {
tracing::warn!("Cannot update primary field type");
} else {
update.update_type_options(|type_options_update| {
type_options_update.insert(&field_type.to_string(), type_option_data);
});
}
});
self
.database_views
.did_update_field_type_option(view_id, field_id, &old_field)
.await?;
let _ = self.notify_did_update_database_field(field_id).await;
Ok(()) Ok(())
} }
@ -398,7 +360,7 @@ impl DatabaseEditor {
}, },
} }
self.notify_did_update_database_field(field_id).await?; notify_did_update_database_field(&self.database, field_id)?;
Ok(()) Ok(())
} }
@ -435,7 +397,7 @@ impl DatabaseEditor {
let params = self.database.lock().duplicate_row(row_id); let params = self.database.lock().duplicate_row(row_id);
match params { match params {
None => { None => {
tracing::warn!("Failed to duplicate row: {}", row_id); warn!("Failed to duplicate row: {}", row_id);
}, },
Some(params) => { Some(params) => {
let _ = self.create_row(view_id, group_id, params).await; let _ = self.create_row(view_id, group_id, params).await;
@ -585,7 +547,7 @@ impl DatabaseEditor {
cover: row_meta.cover_url, cover: row_meta.cover_url,
}) })
} else { } else {
tracing::warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id); warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
None None
} }
} }
@ -594,7 +556,7 @@ impl DatabaseEditor {
if self.database.lock().views.is_row_exist(view_id, row_id) { if self.database.lock().views.is_row_exist(view_id, row_id) {
self.database.lock().get_row_detail(row_id) self.database.lock().get_row_detail(row_id)
} else { } else {
tracing::warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id); warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
None None
} }
} }
@ -984,7 +946,7 @@ impl DatabaseEditor {
let row_detail = self.get_row_detail(view_id, &from_row); let row_detail = self.get_row_detail(view_id, &from_row);
match row_detail { match row_detail {
None => { None => {
tracing::warn!( warn!(
"Move row between group failed, can not find the row:{}", "Move row between group failed, can not find the row:{}",
from_row from_row
) )
@ -1054,7 +1016,7 @@ impl DatabaseEditor {
match self.database_views.get_view_editor(view_id).await { match self.database_views.get_view_editor(view_id).await {
Ok(view) => view.v_get_all_calendar_events().await.unwrap_or_default(), Ok(view) => view.v_get_all_calendar_events().await.unwrap_or_default(),
Err(_) => { Err(_) => {
tracing::warn!("Can not find the view: {}", view_id); warn!("Can not find the view: {}", view_id);
vec![] vec![]
}, },
} }
@ -1066,7 +1028,7 @@ impl DatabaseEditor {
view_id: &str, view_id: &str,
) -> FlowyResult<Vec<NoDateCalendarEventPB>> { ) -> FlowyResult<Vec<NoDateCalendarEventPB>> {
let _database_view = self.database_views.get_view_editor(view_id).await?; let _database_view = self.database_views.get_view_editor(view_id).await?;
todo!() Ok(vec![])
} }
#[tracing::instrument(level = "trace", skip_all)] #[tracing::instrument(level = "trace", skip_all)]
@ -1084,28 +1046,6 @@ impl DatabaseEditor {
Ok(()) Ok(())
} }
#[tracing::instrument(level = "trace", skip_all, err)]
async fn notify_did_update_database_field(&self, field_id: &str) -> FlowyResult<()> {
let (database_id, field) = {
let database = self.database.lock();
let database_id = database.get_database_id();
let field = database.fields.get_field(field_id);
(database_id, field)
};
if let Some(field) = field {
let updated_field = FieldPB::from(field);
let notified_changeset =
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);
self.notify_did_update_database(notified_changeset).await?;
send_notification(field_id, DatabaseNotification::DidUpdateField)
.payload(updated_field)
.send();
}
Ok(())
}
async fn notify_did_update_database( async fn notify_did_update_database(
&self, &self,
changeset: DatabaseFieldChangesetPB, changeset: DatabaseFieldChangesetPB,
@ -1134,7 +1074,7 @@ impl DatabaseEditor {
pub async fn get_database_data(&self, view_id: &str) -> FlowyResult<DatabasePB> { pub async fn get_database_data(&self, view_id: &str) -> FlowyResult<DatabasePB> {
let database_view = self.database_views.get_view_editor(view_id).await?; let database_view = self.database_views.get_view_editor(view_id).await?;
let view = database_view let view = database_view
.get_view() .v_get_view()
.await .await
.ok_or_else(FlowyError::record_not_found)?; .ok_or_else(FlowyError::record_not_found)?;
let rows = database_view.v_get_rows().await; let rows = database_view.v_get_rows().await;
@ -1284,13 +1224,14 @@ fn cell_changesets_from_cell_by_field_id(
.collect() .collect()
} }
struct DatabaseViewDataImpl { struct DatabaseViewOperationImpl {
database: Arc<MutexDatabase>, database: Arc<MutexDatabase>,
task_scheduler: Arc<RwLock<TaskDispatcher>>, task_scheduler: Arc<RwLock<TaskDispatcher>>,
cell_cache: CellCache, cell_cache: CellCache,
editor_by_view_id: Arc<RwLock<EditorByViewId>>,
} }
impl DatabaseViewData for DatabaseViewDataImpl { impl DatabaseViewOperation for DatabaseViewOperationImpl {
fn get_database(&self) -> Arc<MutexDatabase> { fn get_database(&self) -> Arc<MutexDatabase> {
self.database.clone() self.database.clone()
} }
@ -1305,14 +1246,8 @@ impl DatabaseViewData for DatabaseViewDataImpl {
to_fut(async move { fields.into_iter().map(Arc::new).collect() }) to_fut(async move { fields.into_iter().map(Arc::new).collect() })
} }
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>> { fn get_field(&self, field_id: &str) -> Option<Field> {
let field = self self.database.lock().fields.get_field(field_id)
.database
.lock()
.fields
.get_field(field_id)
.map(Arc::new);
to_fut(async move { field })
} }
fn create_field( fn create_field(
@ -1336,6 +1271,29 @@ impl DatabaseViewData for DatabaseViewDataImpl {
to_fut(async move { field }) to_fut(async move { field })
} }
fn update_field(
&self,
view_id: &str,
type_option_data: TypeOptionData,
old_field: Field,
) -> FutureResult<(), FlowyError> {
let view_id = view_id.to_string();
let weak_editor_by_view_id = Arc::downgrade(&self.editor_by_view_id);
let weak_database = Arc::downgrade(&self.database);
FutureResult::new(async move {
if let (Some(database), Some(editor_by_view_id)) =
(weak_database.upgrade(), weak_editor_by_view_id.upgrade())
{
let view_editor = editor_by_view_id.read().await.get(&view_id).cloned();
if let Some(view_editor) = view_editor {
let _ =
update_field_type_option_fn(&database, &view_editor, type_option_data, old_field).await;
}
}
Ok(())
})
}
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>> { fn get_primary_field(&self) -> Fut<Option<Arc<Field>>> {
let field = self let field = self
.database .database
@ -1559,3 +1517,73 @@ impl DatabaseViewData for DatabaseViewDataImpl {
.send() .send()
} }
} }
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn update_field_type_option_fn(
database: &Arc<MutexDatabase>,
view_editor: &Arc<DatabaseViewEditor>,
type_option_data: TypeOptionData,
old_field: Field,
) -> FlowyResult<()> {
if type_option_data.is_empty() {
warn!("Update type option with empty data");
return Ok(());
}
let field_type = FieldType::from(old_field.field_type);
database
.lock()
.fields
.update_field(&old_field.id, |update| {
if old_field.is_primary {
warn!("Cannot update primary field type");
} else {
update.update_type_options(|type_options_update| {
event!(
tracing::Level::TRACE,
"insert type option to field type: {:?}",
field_type
);
type_options_update.insert(&field_type.to_string(), type_option_data);
});
}
});
let _ = notify_did_update_database_field(database, &old_field.id);
view_editor
.v_did_update_field_type_option(&old_field)
.await?;
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)]
fn notify_did_update_database_field(
database: &Arc<MutexDatabase>,
field_id: &str,
) -> FlowyResult<()> {
let (database_id, field, views) = {
let database = database
.try_lock()
.ok_or(FlowyError::internal().with_context("fail to acquire the lock of database"))?;
let database_id = database.get_database_id();
let field = database.fields.get_field(field_id);
let views = database.get_all_views_description();
(database_id, field, views)
};
if let Some(field) = field {
let updated_field = FieldPB::from(field);
let notified_changeset =
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);
for view in views {
send_notification(&view.id, DatabaseNotification::DidUpdateFields)
.payload(notified_changeset.clone())
.send();
}
send_notification(field_id, DatabaseNotification::DidUpdateField)
.payload(updated_field)
.send();
}
Ok(())
}

View File

@ -1,6 +1,7 @@
pub use layout_deps::*; pub use layout_deps::*;
pub use notifier::*; pub use notifier::*;
pub use view_editor::*; pub use view_editor::*;
pub use view_operation::*;
pub use views::*; pub use views::*;
mod layout_deps; mod layout_deps;
@ -8,6 +9,7 @@ mod notifier;
mod view_editor; mod view_editor;
mod view_filter; mod view_filter;
mod view_group; mod view_group;
mod view_operation;
mod view_sort; mod view_sort;
mod views; mod views;
// mod trait_impl; // mod trait_impl;

View File

@ -2,15 +2,13 @@ use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use collab_database::database::{gen_database_filter_id, gen_database_sort_id, MutexDatabase}; use collab_database::database::{gen_database_filter_id, gen_database_sort_id};
use collab_database::fields::{Field, TypeOptionData}; use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cells, Row, RowCell, RowDetail, RowId}; use collab_database::rows::{Cells, Row, RowDetail, RowId};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting}; use collab_database::views::{DatabaseLayout, DatabaseView};
use tokio::sync::{broadcast, RwLock}; use tokio::sync::{broadcast, RwLock};
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_task::TaskDispatcher;
use lib_infra::future::Fut;
use crate::entities::{ use crate::entities::{
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams, CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
@ -25,124 +23,27 @@ use crate::services::database_view::view_filter::make_filter_controller;
use crate::services::database_view::view_group::{ use crate::services::database_view::view_group::{
get_cell_for_row, get_cells_for_field, new_group_controller, new_group_controller_with_field, get_cell_for_row, get_cells_for_field, new_group_controller, new_group_controller_with_field,
}; };
use crate::services::database_view::view_operation::DatabaseViewOperation;
use crate::services::database_view::view_sort::make_sort_controller; use crate::services::database_view::view_sort::make_sort_controller;
use crate::services::database_view::{ use crate::services::database_view::{
notify_did_update_filter, notify_did_update_group_rows, notify_did_update_num_of_groups, notify_did_update_filter, notify_did_update_group_rows, notify_did_update_num_of_groups,
notify_did_update_setting, notify_did_update_sort, DatabaseLayoutDepsResolver, notify_did_update_setting, notify_did_update_sort, DatabaseLayoutDepsResolver,
DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner, DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner,
}; };
use crate::services::field::TypeOptionCellDataHandler;
use crate::services::field_settings::FieldSettings; use crate::services::field_settings::FieldSettings;
use crate::services::filter::{ use crate::services::filter::{
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType, Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
}; };
use crate::services::group::{ use crate::services::group::{
GroupChangeset, GroupChangesets, GroupController, GroupSetting, GroupSettingChangeset, GroupChangesets, GroupController, GroupSetting, GroupSettingChangeset, MoveGroupRowContext,
MoveGroupRowContext, RowChangeset, RowChangeset,
}; };
use crate::services::setting::CalendarLayoutSetting; use crate::services::setting::CalendarLayoutSetting;
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType}; use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
pub trait DatabaseViewData: Send + Sync + 'static {
fn get_database(&self) -> Arc<MutexDatabase>;
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
/// If the field_ids is None, then it will return all the field revisions
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
/// Returns the field with the field_id
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>>;
fn create_field(
&self,
view_id: &str,
name: &str,
field_type: FieldType,
type_option_data: TypeOptionData,
) -> Fut<Field>;
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>>;
/// Returns the index of the row with row_id
fn index_of_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<usize>>;
/// Returns the `index` and `RowRevision` with row_id
fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;
/// Returns all the rows in the view
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>>;
/// Return the database layout type for the view with given view_id
/// The default layout type is [DatabaseLayout::Grid]
fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout;
fn get_group_setting(&self, view_id: &str) -> Vec<GroupSetting>;
fn insert_group_setting(&self, view_id: &str, setting: GroupSetting);
fn get_sort(&self, view_id: &str, sort_id: &str) -> Option<Sort>;
fn insert_sort(&self, view_id: &str, sort: Sort);
fn remove_sort(&self, view_id: &str, sort_id: &str);
fn get_all_sorts(&self, view_id: &str) -> Vec<Sort>;
fn remove_all_sorts(&self, view_id: &str);
fn get_all_filters(&self, view_id: &str) -> Vec<Arc<Filter>>;
fn delete_filter(&self, view_id: &str, filter_id: &str);
fn insert_filter(&self, view_id: &str, filter: Filter);
fn get_filter(&self, view_id: &str, filter_id: &str) -> Option<Filter>;
fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option<Filter>;
fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option<LayoutSetting>;
fn insert_layout_setting(
&self,
view_id: &str,
layout_ty: &DatabaseLayout,
layout_setting: LayoutSetting,
);
fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout);
/// Returns a `TaskDispatcher` used to poll a `Task`
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>>;
fn get_type_option_cell_handler(
&self,
field: &Field,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
fn get_field_settings(
&self,
view_id: &str,
field_ids: &[String],
) -> HashMap<String, FieldSettings>;
fn get_all_field_settings(&self, view_id: &str) -> HashMap<String, FieldSettings>;
fn update_field_settings(
&self,
view_id: &str,
field_id: &str,
visibility: Option<FieldVisibility>,
);
}
pub struct DatabaseViewEditor { pub struct DatabaseViewEditor {
pub view_id: String, pub view_id: String,
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
group_controller: Arc<RwLock<Option<Box<dyn GroupController>>>>, group_controller: Arc<RwLock<Option<Box<dyn GroupController>>>>,
filter_controller: Arc<FilterController>, filter_controller: Arc<FilterController>,
sort_controller: Arc<RwLock<SortController>>, sort_controller: Arc<RwLock<SortController>>,
@ -158,14 +59,17 @@ impl Drop for DatabaseViewEditor {
impl DatabaseViewEditor { impl DatabaseViewEditor {
pub async fn new( pub async fn new(
view_id: String, view_id: String,
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
cell_cache: CellCache, cell_cache: CellCache,
) -> FlowyResult<Self> { ) -> FlowyResult<Self> {
let (notifier, _) = broadcast::channel(100); let (notifier, _) = broadcast::channel(100);
tokio::spawn(DatabaseViewChangedReceiverRunner(Some(notifier.subscribe())).run()); tokio::spawn(DatabaseViewChangedReceiverRunner(Some(notifier.subscribe())).run());
let group_controller = new_group_controller(view_id.clone(), delegate.clone()).await?; // Group
let group_controller = Arc::new(RwLock::new(group_controller)); let group_controller = Arc::new(RwLock::new(
new_group_controller(view_id.clone(), delegate.clone()).await?,
));
// Filter
let filter_controller = make_filter_controller( let filter_controller = make_filter_controller(
&view_id, &view_id,
delegate.clone(), delegate.clone(),
@ -174,6 +78,7 @@ impl DatabaseViewEditor {
) )
.await; .await;
// Sort
let sort_controller = make_sort_controller( let sort_controller = make_sort_controller(
&view_id, &view_id,
delegate.clone(), delegate.clone(),
@ -198,7 +103,7 @@ impl DatabaseViewEditor {
self.filter_controller.close().await; self.filter_controller.close().await;
} }
pub async fn get_view(&self) -> Option<DatabaseView> { pub async fn v_get_view(&self) -> Option<DatabaseView> {
self.delegate.get_view(&self.view_id).await self.delegate.get_view(&self.view_id).await
} }
@ -383,7 +288,7 @@ impl DatabaseViewEditor {
let move_row_context = MoveGroupRowContext { let move_row_context = MoveGroupRowContext {
row_detail, row_detail,
row_changeset, row_changeset,
field: field.as_ref(), field: &field,
to_group_id, to_group_id,
to_row_id, to_row_id,
}; };
@ -485,40 +390,31 @@ impl DatabaseViewEditor {
Ok(result.flatten()) Ok(result.flatten())
} }
pub async fn v_update_group_setting(&self, changeset: GroupChangesets) -> FlowyResult<()> { pub async fn v_update_group(&self, changeset: GroupChangesets) -> FlowyResult<()> {
self let mut type_option_data = TypeOptionData::new();
.mut_group_controller(|group_controller, _| { let old_field = if let Some(controller) = self.group_controller.write().await.as_mut() {
group_controller.apply_group_setting_changeset(changeset) let old_field = self.delegate.get_field(controller.field_id());
}) type_option_data.extend(controller.apply_group_changeset(&changeset).await?);
.await; old_field
} else {
None
};
if let Some(old_field) = old_field {
if !type_option_data.is_empty() {
self
.delegate
.update_field(&self.view_id, type_option_data, old_field)
.await?;
}
}
Ok(()) Ok(())
} }
pub async fn v_get_group_configuration_settings(&self) -> Vec<GroupSetting> { pub async fn v_get_group_configuration_settings(&self) -> Vec<GroupSetting> {
self.delegate.get_group_setting(&self.view_id) self.delegate.get_group_setting(&self.view_id)
} }
pub async fn update_group(
&self,
changeset: GroupChangeset,
) -> FlowyResult<Option<TypeOptionData>> {
match changeset.name {
Some(group_name) => {
let result = self
.mut_group_controller(|controller, _| {
Ok(controller.update_group_name(&changeset.group_id, &group_name))
})
.await;
match result {
Some(r) => Ok(r),
None => Ok(None),
}
},
None => Ok(None),
}
}
pub async fn v_get_all_sorts(&self) -> Vec<Sort> { pub async fn v_get_all_sorts(&self) -> Vec<Sort> {
self.delegate.get_all_sorts(&self.view_id) self.delegate.get_all_sorts(&self.view_id)
} }
@ -659,7 +555,7 @@ impl DatabaseViewEditor {
if let Some(value) = self.delegate.get_layout_setting(&self.view_id, layout_ty) { if let Some(value) = self.delegate.get_layout_setting(&self.view_id, layout_ty) {
let calendar_setting = CalendarLayoutSetting::from(value); let calendar_setting = CalendarLayoutSetting::from(value);
// Check the field exist or not // Check the field exist or not
if let Some(field) = self.delegate.get_field(&calendar_setting.field_id).await { if let Some(field) = self.delegate.get_field(&calendar_setting.field_id) {
let field_type = FieldType::from(field.field_type); let field_type = FieldType::from(field.field_type);
// Check the type of field is Datetime or not // Check the type of field is Datetime or not
@ -682,11 +578,7 @@ impl DatabaseViewEditor {
pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> { pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> {
// Maybe it needs no send notification to refresh the UI // Maybe it needs no send notification to refresh the UI
if let Some(new_calendar_setting) = params.calendar { if let Some(new_calendar_setting) = params.calendar {
if let Some(field) = self if let Some(field) = self.delegate.get_field(&new_calendar_setting.field_id) {
.delegate
.get_field(&new_calendar_setting.field_id)
.await
{
let field_type = FieldType::from(field.field_type); let field_type = FieldType::from(field.field_type);
if field_type != FieldType::DateTime { if field_type != FieldType::DateTime {
return Err(FlowyError::unexpect_calendar_field_type()); return Err(FlowyError::unexpect_calendar_field_type());
@ -729,13 +621,19 @@ impl DatabaseViewEditor {
Ok(()) Ok(())
} }
/// Notifies the view's field type-option data is changed
/// For the moment, only the groups will be generated after the type-option data changed. A
/// [Field] has a property named type_options contains a list of type-option data.
#[tracing::instrument(level = "trace", skip_all, err)] #[tracing::instrument(level = "trace", skip_all, err)]
pub async fn v_did_update_field_type_option( pub async fn v_did_update_field_type_option(&self, old_field: &Field) -> FlowyResult<()> {
&self, let field_id = &old_field.id;
field_id: &str, // If the id of the grouping field is equal to the updated field's id, then we need to
old_field: &Field, // update the group setting
) -> FlowyResult<()> { if self.is_grouping_field(field_id).await {
if let Some(field) = self.delegate.get_field(field_id).await { self.v_grouping_by_field(field_id).await?;
}
if let Some(field) = self.delegate.get_field(field_id) {
self self
.sort_controller .sort_controller
.read() .read()
@ -776,9 +674,13 @@ impl DatabaseViewEditor {
/// Called when a grouping field is updated. /// Called when a grouping field is updated.
#[tracing::instrument(level = "debug", skip_all, err)] #[tracing::instrument(level = "debug", skip_all, err)]
pub async fn v_grouping_by_field(&self, field_id: &str) -> FlowyResult<()> { pub async fn v_grouping_by_field(&self, field_id: &str) -> FlowyResult<()> {
if let Some(field) = self.delegate.get_field(field_id).await { if let Some(field) = self.delegate.get_field(field_id) {
let new_group_controller = let new_group_controller = new_group_controller_with_field(
new_group_controller_with_field(self.view_id.clone(), self.delegate.clone(), field).await?; self.view_id.clone(),
self.delegate.clone(),
Arc::new(field),
)
.await?;
let new_groups = new_group_controller let new_groups = new_group_controller
.groups() .groups()
@ -812,7 +714,7 @@ impl DatabaseViewEditor {
let text_cell = get_cell_for_row(self.delegate.clone(), &primary_field.id, &row_id).await?; let text_cell = get_cell_for_row(self.delegate.clone(), &primary_field.id, &row_id).await?;
// Date // Date
let date_field = self.delegate.get_field(&calendar_setting.field_id).await?; let date_field = self.delegate.get_field(&calendar_setting.field_id)?;
let date_cell = get_cell_for_row(self.delegate.clone(), &date_field.id, &row_id).await?; let date_cell = get_cell_for_row(self.delegate.clone(), &date_field.id, &row_id).await?;
let title = text_cell let title = text_cell
@ -973,7 +875,7 @@ impl DatabaseViewEditor {
async fn mut_group_controller<F, T>(&self, f: F) -> Option<T> async fn mut_group_controller<F, T>(&self, f: F) -> Option<T>
where where
F: FnOnce(&mut Box<dyn GroupController>, Arc<Field>) -> FlowyResult<T>, F: FnOnce(&mut Box<dyn GroupController>, Field) -> FlowyResult<T>,
{ {
let group_field_id = self let group_field_id = self
.group_controller .group_controller
@ -981,8 +883,7 @@ impl DatabaseViewEditor {
.await .await
.as_ref() .as_ref()
.map(|group| group.field_id().to_owned())?; .map(|group| group.field_id().to_owned())?;
let field = self.delegate.get_field(&group_field_id).await?; let field = self.delegate.get_field(&group_field_id)?;
let mut write_guard = self.group_controller.write().await; let mut write_guard = self.group_controller.write().await;
if let Some(group_controller) = &mut *write_guard { if let Some(group_controller) = &mut *write_guard {
f(group_controller, field).ok() f(group_controller, field).ok()

View File

@ -7,13 +7,13 @@ use lib_infra::future::{to_fut, Fut};
use crate::services::cell::CellCache; use crate::services::cell::CellCache;
use crate::services::database_view::{ use crate::services::database_view::{
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData, gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewOperation,
}; };
use crate::services::filter::{Filter, FilterController, FilterDelegate, FilterTaskHandler}; use crate::services::filter::{Filter, FilterController, FilterDelegate, FilterTaskHandler};
pub async fn make_filter_controller( pub async fn make_filter_controller(
view_id: &str, view_id: &str,
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
notifier: DatabaseViewChangedNotifier, notifier: DatabaseViewChangedNotifier,
cell_cache: CellCache, cell_cache: CellCache,
) -> Arc<FilterController> { ) -> Arc<FilterController> {
@ -43,7 +43,7 @@ pub async fn make_filter_controller(
filter_controller filter_controller
} }
struct DatabaseViewFilterDelegateImpl(Arc<dyn DatabaseViewData>); struct DatabaseViewFilterDelegateImpl(Arc<dyn DatabaseViewOperation>);
impl FilterDelegate for DatabaseViewFilterDelegateImpl { impl FilterDelegate for DatabaseViewFilterDelegateImpl {
fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>> { fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>> {
@ -51,7 +51,7 @@ impl FilterDelegate for DatabaseViewFilterDelegateImpl {
to_fut(async move { filter }) to_fut(async move { filter })
} }
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>> { fn get_field(&self, field_id: &str) -> Option<Field> {
self.0.get_field(field_id) self.0.get_field(field_id)
} }

View File

@ -1,40 +1,43 @@
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::Field; use collab_database::fields::Field;
use collab_database::rows::RowId; use collab_database::rows::{Cell, RowId};
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use lib_infra::future::{to_fut, Fut}; use lib_infra::future::{to_fut, Fut};
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::services::database_view::DatabaseViewData; use crate::services::database_view::DatabaseViewOperation;
use crate::services::field::RowSingleCellData; use crate::services::field::RowSingleCellData;
use crate::services::group::{ use crate::services::group::{
find_new_grouping_field, make_group_controller, GroupController, GroupSetting, find_new_grouping_field, make_group_controller, GroupController, GroupSetting,
GroupSettingReader, GroupSettingWriter, GroupSettingReader, GroupSettingWriter, GroupTypeOptionCellOperation,
}; };
pub async fn new_group_controller_with_field( pub async fn new_group_controller_with_field(
view_id: String, view_id: String,
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
grouping_field: Arc<Field>, grouping_field: Arc<Field>,
) -> FlowyResult<Box<dyn GroupController>> { ) -> FlowyResult<Box<dyn GroupController>> {
let setting_reader = GroupSettingReaderImpl(delegate.clone()); let setting_reader = GroupSettingReaderImpl(delegate.clone());
let rows = delegate.get_rows(&view_id).await; let rows = delegate.get_rows(&view_id).await;
let setting_writer = GroupSettingWriterImpl(delegate.clone()); let setting_writer = GroupSettingWriterImpl(delegate.clone());
let type_option_writer = GroupTypeOptionCellWriterImpl(delegate.clone());
make_group_controller( make_group_controller(
view_id, view_id,
grouping_field, grouping_field,
rows, rows,
setting_reader, setting_reader,
setting_writer, setting_writer,
type_option_writer,
) )
.await .await
} }
pub async fn new_group_controller( pub async fn new_group_controller(
view_id: String, view_id: String,
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
) -> FlowyResult<Option<Box<dyn GroupController>>> { ) -> FlowyResult<Option<Box<dyn GroupController>>> {
let fields = delegate.get_fields(&view_id, None).await; let fields = delegate.get_fields(&view_id, None).await;
let setting_reader = GroupSettingReaderImpl(delegate.clone()); let setting_reader = GroupSettingReaderImpl(delegate.clone());
@ -59,6 +62,7 @@ pub async fn new_group_controller(
if let Some(grouping_field) = grouping_field { if let Some(grouping_field) = grouping_field {
let rows = delegate.get_rows(&view_id).await; let rows = delegate.get_rows(&view_id).await;
let setting_writer = GroupSettingWriterImpl(delegate.clone()); let setting_writer = GroupSettingWriterImpl(delegate.clone());
let type_option_writer = GroupTypeOptionCellWriterImpl(delegate.clone());
Ok(Some( Ok(Some(
make_group_controller( make_group_controller(
view_id, view_id,
@ -66,6 +70,7 @@ pub async fn new_group_controller(
rows, rows,
setting_reader, setting_reader,
setting_writer, setting_writer,
type_option_writer,
) )
.await?, .await?,
)) ))
@ -74,7 +79,7 @@ pub async fn new_group_controller(
} }
} }
pub(crate) struct GroupSettingReaderImpl(pub Arc<dyn DatabaseViewData>); pub(crate) struct GroupSettingReaderImpl(pub Arc<dyn DatabaseViewOperation>);
impl GroupSettingReader for GroupSettingReaderImpl { impl GroupSettingReader for GroupSettingReaderImpl {
fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>> { fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>> {
@ -97,11 +102,11 @@ impl GroupSettingReader for GroupSettingReaderImpl {
} }
pub(crate) async fn get_cell_for_row( pub(crate) async fn get_cell_for_row(
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
field_id: &str, field_id: &str,
row_id: &RowId, row_id: &RowId,
) -> Option<RowSingleCellData> { ) -> Option<RowSingleCellData> {
let field = delegate.get_field(field_id).await?; let field = delegate.get_field(field_id)?;
let row_cell = delegate.get_cell_in_row(field_id, row_id).await; let row_cell = delegate.get_cell_in_row(field_id, row_id).await;
let field_type = FieldType::from(field.field_type); let field_type = FieldType::from(field.field_type);
let handler = delegate.get_type_option_cell_handler(&field, &field_type)?; let handler = delegate.get_type_option_cell_handler(&field, &field_type)?;
@ -120,11 +125,11 @@ pub(crate) async fn get_cell_for_row(
// Returns the list of cells corresponding to the given field. // Returns the list of cells corresponding to the given field.
pub(crate) async fn get_cells_for_field( pub(crate) async fn get_cells_for_field(
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
view_id: &str, view_id: &str,
field_id: &str, field_id: &str,
) -> Vec<RowSingleCellData> { ) -> Vec<RowSingleCellData> {
if let Some(field) = delegate.get_field(field_id).await { if let Some(field) = delegate.get_field(field_id) {
let field_type = FieldType::from(field.field_type); let field_type = FieldType::from(field.field_type);
if let Some(handler) = delegate.get_type_option_cell_handler(&field, &field_type) { if let Some(handler) = delegate.get_type_option_cell_handler(&field, &field_type) {
let cells = delegate.get_cells_for_field(view_id, field_id).await; let cells = delegate.get_cells_for_field(view_id, field_id).await;
@ -149,11 +154,30 @@ pub(crate) async fn get_cells_for_field(
vec![] vec![]
} }
struct GroupSettingWriterImpl(Arc<dyn DatabaseViewData>); struct GroupSettingWriterImpl(Arc<dyn DatabaseViewOperation>);
impl GroupSettingWriter for GroupSettingWriterImpl { impl GroupSettingWriter for GroupSettingWriterImpl {
fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>> { fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>> {
self.0.insert_group_setting(view_id, group_setting); self.0.insert_group_setting(view_id, group_setting);
to_fut(async move { Ok(()) }) to_fut(async move { Ok(()) })
} }
} }
struct GroupTypeOptionCellWriterImpl(Arc<dyn DatabaseViewOperation>);
#[async_trait]
impl GroupTypeOptionCellOperation for GroupTypeOptionCellWriterImpl {
async fn get_cell(&self, _row_id: &RowId, _field_id: &str) -> FlowyResult<Option<Cell>> {
todo!()
}
#[tracing::instrument(level = "trace", skip_all, err)]
async fn update_cell(
&self,
_view_id: &str,
_row_id: &RowId,
_field_id: &str,
_cell: Cell,
) -> FlowyResult<()> {
todo!()
}
}

View File

@ -0,0 +1,126 @@
use std::collections::HashMap;
use std::sync::Arc;
use collab_database::database::MutexDatabase;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{RowCell, RowDetail, RowId};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
use tokio::sync::RwLock;
use flowy_error::FlowyError;
use flowy_task::TaskDispatcher;
use lib_infra::future::{Fut, FutureResult};
use crate::entities::{FieldType, FieldVisibility};
use crate::services::field::TypeOptionCellDataHandler;
use crate::services::field_settings::FieldSettings;
use crate::services::filter::Filter;
use crate::services::group::GroupSetting;
use crate::services::sort::Sort;
/// Defines the operation that can be performed on a database view
pub trait DatabaseViewOperation: Send + Sync + 'static {
/// Get the database that the view belongs to
fn get_database(&self) -> Arc<MutexDatabase>;
/// Get the view of the database with the view_id
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
/// If the field_ids is None, then it will return all the field revisions
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
/// Returns the field with the field_id
fn get_field(&self, field_id: &str) -> Option<Field>;
fn create_field(
&self,
view_id: &str,
name: &str,
field_type: FieldType,
type_option_data: TypeOptionData,
) -> Fut<Field>;
fn update_field(
&self,
view_id: &str,
type_option_data: TypeOptionData,
old_field: Field,
) -> FutureResult<(), FlowyError>;
fn get_primary_field(&self) -> Fut<Option<Arc<Field>>>;
/// Returns the index of the row with row_id
fn index_of_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<usize>>;
/// Returns the `index` and `RowRevision` with row_id
fn get_row(&self, view_id: &str, row_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;
/// Returns all the rows in the view
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>>;
/// Return the database layout type for the view with given view_id
/// The default layout type is [DatabaseLayout::Grid]
fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout;
fn get_group_setting(&self, view_id: &str) -> Vec<GroupSetting>;
fn insert_group_setting(&self, view_id: &str, setting: GroupSetting);
fn get_sort(&self, view_id: &str, sort_id: &str) -> Option<Sort>;
fn insert_sort(&self, view_id: &str, sort: Sort);
fn remove_sort(&self, view_id: &str, sort_id: &str);
fn get_all_sorts(&self, view_id: &str) -> Vec<Sort>;
fn remove_all_sorts(&self, view_id: &str);
fn get_all_filters(&self, view_id: &str) -> Vec<Arc<Filter>>;
fn delete_filter(&self, view_id: &str, filter_id: &str);
fn insert_filter(&self, view_id: &str, filter: Filter);
fn get_filter(&self, view_id: &str, filter_id: &str) -> Option<Filter>;
fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option<Filter>;
fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option<LayoutSetting>;
fn insert_layout_setting(
&self,
view_id: &str,
layout_ty: &DatabaseLayout,
layout_setting: LayoutSetting,
);
fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout);
/// Returns a `TaskDispatcher` used to poll a `Task`
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>>;
fn get_type_option_cell_handler(
&self,
field: &Field,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
fn get_field_settings(
&self,
view_id: &str,
field_ids: &[String],
) -> HashMap<String, FieldSettings>;
fn get_all_field_settings(&self, view_id: &str) -> HashMap<String, FieldSettings>;
fn update_field_settings(
&self,
view_id: &str,
field_id: &str,
visibility: Option<FieldVisibility>,
);
}

View File

@ -8,14 +8,14 @@ use lib_infra::future::{to_fut, Fut};
use crate::services::cell::CellCache; use crate::services::cell::CellCache;
use crate::services::database_view::{ use crate::services::database_view::{
gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData, gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewOperation,
}; };
use crate::services::filter::FilterController; use crate::services::filter::FilterController;
use crate::services::sort::{Sort, SortController, SortDelegate, SortTaskHandler}; use crate::services::sort::{Sort, SortController, SortDelegate, SortTaskHandler};
pub(crate) async fn make_sort_controller( pub(crate) async fn make_sort_controller(
view_id: &str, view_id: &str,
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
notifier: DatabaseViewChangedNotifier, notifier: DatabaseViewChangedNotifier,
filter_controller: Arc<FilterController>, filter_controller: Arc<FilterController>,
cell_cache: CellCache, cell_cache: CellCache,
@ -49,7 +49,7 @@ pub(crate) async fn make_sort_controller(
} }
struct DatabaseViewSortDelegateImpl { struct DatabaseViewSortDelegateImpl {
delegate: Arc<dyn DatabaseViewData>, delegate: Arc<dyn DatabaseViewOperation>,
filter_controller: Arc<FilterController>, filter_controller: Arc<FilterController>,
} }
@ -70,7 +70,7 @@ impl SortDelegate for DatabaseViewSortDelegateImpl {
}) })
} }
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>> { fn get_field(&self, field_id: &str) -> Option<Field> {
self.delegate.get_field(field_id) self.delegate.get_field(field_id)
} }

View File

@ -2,47 +2,46 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use collab_database::database::MutexDatabase; use collab_database::database::MutexDatabase;
use collab_database::fields::Field;
use collab_database::rows::{RowDetail, RowId}; use collab_database::rows::{RowDetail, RowId};
use nanoid::nanoid; use nanoid::nanoid;
use tokio::sync::{broadcast, RwLock}; use tokio::sync::{broadcast, RwLock};
use flowy_error::FlowyResult; use flowy_error::{FlowyError, FlowyResult};
use lib_infra::future::Fut; use lib_infra::future::Fut;
use crate::services::cell::CellCache; use crate::services::cell::CellCache;
use crate::services::database::DatabaseRowEvent; use crate::services::database::DatabaseRowEvent;
use crate::services::database_view::{DatabaseViewData, DatabaseViewEditor}; use crate::services::database_view::{DatabaseViewEditor, DatabaseViewOperation};
use crate::services::group::RowChangeset; use crate::services::group::RowChangeset;
pub type RowEventSender = broadcast::Sender<DatabaseRowEvent>; pub type RowEventSender = broadcast::Sender<DatabaseRowEvent>;
pub type RowEventReceiver = broadcast::Receiver<DatabaseRowEvent>; pub type RowEventReceiver = broadcast::Receiver<DatabaseRowEvent>;
pub type EditorByViewId = HashMap<String, Arc<DatabaseViewEditor>>;
pub struct DatabaseViews { pub struct DatabaseViews {
#[allow(dead_code)] #[allow(dead_code)]
database: Arc<MutexDatabase>, database: Arc<MutexDatabase>,
cell_cache: CellCache, cell_cache: CellCache,
database_view_data: Arc<dyn DatabaseViewData>, view_operation: Arc<dyn DatabaseViewOperation>,
editor_map: Arc<RwLock<HashMap<String, Arc<DatabaseViewEditor>>>>, editor_by_view_id: Arc<RwLock<EditorByViewId>>,
} }
impl DatabaseViews { impl DatabaseViews {
pub async fn new( pub async fn new(
database: Arc<MutexDatabase>, database: Arc<MutexDatabase>,
cell_cache: CellCache, cell_cache: CellCache,
database_view_data: Arc<dyn DatabaseViewData>, view_operation: Arc<dyn DatabaseViewOperation>,
editor_by_view_id: Arc<RwLock<EditorByViewId>>,
) -> FlowyResult<Self> { ) -> FlowyResult<Self> {
let editor_map = Arc::new(RwLock::new(HashMap::default()));
Ok(Self { Ok(Self {
database, database,
database_view_data, view_operation,
cell_cache, cell_cache,
editor_map, editor_by_view_id,
}) })
} }
pub async fn close_view(&self, view_id: &str) -> bool { pub async fn close_view(&self, view_id: &str) -> bool {
let mut editor_map = self.editor_map.write().await; let mut editor_map = self.editor_by_view_id.write().await;
if let Some(view) = editor_map.remove(view_id) { if let Some(view) = editor_map.remove(view_id) {
view.close().await; view.close().await;
} }
@ -50,7 +49,13 @@ impl DatabaseViews {
} }
pub async fn editors(&self) -> Vec<Arc<DatabaseViewEditor>> { pub async fn editors(&self) -> Vec<Arc<DatabaseViewEditor>> {
self.editor_map.read().await.values().cloned().collect() self
.editor_by_view_id
.read()
.await
.values()
.cloned()
.collect()
} }
/// It may generate a RowChangeset when the Row was moved from one group to another. /// It may generate a RowChangeset when the Row was moved from one group to another.
@ -77,43 +82,22 @@ impl DatabaseViews {
Ok(()) Ok(())
} }
/// Notifies the view's field type-option data is changed
/// For the moment, only the groups will be generated after the type-option data changed. A
/// [Field] has a property named type_options contains a list of type-option data.
/// # Arguments
///
/// * `field_id`: the id of the field in current view
///
#[tracing::instrument(level = "debug", skip(self, old_field), err)]
pub async fn did_update_field_type_option(
&self,
view_id: &str,
field_id: &str,
old_field: &Field,
) -> FlowyResult<()> {
let view_editor = self.get_view_editor(view_id).await?;
// If the id of the grouping field is equal to the updated field's id, then we need to
// update the group setting
if view_editor.is_grouping_field(field_id).await {
view_editor.v_grouping_by_field(field_id).await?;
}
view_editor
.v_did_update_field_type_option(field_id, old_field)
.await?;
Ok(())
}
pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult<Arc<DatabaseViewEditor>> { pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult<Arc<DatabaseViewEditor>> {
debug_assert!(!view_id.is_empty()); debug_assert!(!view_id.is_empty());
if let Some(editor) = self.editor_map.read().await.get(view_id) { if let Some(editor) = self.editor_by_view_id.read().await.get(view_id) {
return Ok(editor.clone()); return Ok(editor.clone());
} }
let mut editor_map = self.editor_map.write().await; let mut editor_map = self.editor_by_view_id.try_write().map_err(|err| {
FlowyError::internal().with_context(format!(
"fail to acquire the lock of editor_by_view_id: {}",
err
))
})?;
let editor = Arc::new( let editor = Arc::new(
DatabaseViewEditor::new( DatabaseViewEditor::new(
view_id.to_owned(), view_id.to_owned(),
self.database_view_data.clone(), self.view_operation.clone(),
self.cell_cache.clone(), self.cell_cache.clone(),
) )
.await?, .await?,

View File

@ -24,7 +24,6 @@ impl SelectOptionIds {
pub fn into_inner(self) -> Vec<String> { pub fn into_inner(self) -> Vec<String> {
self.0 self.0
} }
pub fn to_cell_data(&self, field_type: FieldType) -> Cell { pub fn to_cell_data(&self, field_type: FieldType) -> Cell {
new_cell_builder(field_type) new_cell_builder(field_type)
.insert_str_value(CELL_DATA, self.to_string()) .insert_str_value(CELL_DATA, self.to_string())

View File

@ -33,7 +33,15 @@ pub trait TypeOption {
/// ///
/// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`. /// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
/// ///
type CellData: TypeOptionCellData + ToString + Default + Send + Sync + Clone + Debug + 'static; type CellData: for<'a> From<&'a Cell>
+ TypeOptionCellData
+ ToString
+ Default
+ Send
+ Sync
+ Clone
+ Debug
+ 'static;
/// Represents as the corresponding field type cell changeset. /// Represents as the corresponding field type cell changeset.
/// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait. /// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait.

View File

@ -113,16 +113,21 @@ where
+ Sync + Sync
+ 'static, + 'static,
{ {
pub fn into_boxed(self) -> Box<dyn TypeOptionCellDataHandler> {
Box::new(self) as Box<dyn TypeOptionCellDataHandler>
}
pub fn new_with_boxed( pub fn new_with_boxed(
inner: T, inner: T,
cell_filter_cache: Option<CellFilterCache>, cell_filter_cache: Option<CellFilterCache>,
cell_data_cache: Option<CellCache>, cell_data_cache: Option<CellCache>,
) -> Box<dyn TypeOptionCellDataHandler> { ) -> Box<dyn TypeOptionCellDataHandler> {
Box::new(Self { Self {
inner, inner,
cell_data_cache, cell_data_cache,
cell_filter_cache, cell_filter_cache,
}) as Box<dyn TypeOptionCellDataHandler> }
.into_boxed()
} }
} }

View File

@ -21,7 +21,7 @@ use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResul
pub trait FilterDelegate: Send + Sync + 'static { pub trait FilterDelegate: Send + Sync + 'static {
fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>>; fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut<Option<Arc<Filter>>>;
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>>; fn get_field(&self, field_id: &str) -> Option<Field>;
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>; fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>; fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>; fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut<Option<(usize, Arc<RowDetail>)>>;

View File

@ -1,20 +1,22 @@
use collab_database::fields::Field; use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cell, Row, RowDetail}; use collab_database::rows::{Cell, Row, RowDetail};
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB}; use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
use crate::services::cell::DecodedCellData; use crate::services::field::TypeOption;
use crate::services::group::controller::MoveGroupRowContext;
use crate::services::group::entities::GroupSetting; use crate::services::group::entities::GroupSetting;
use crate::services::group::{GroupChangesets, GroupData, GroupSettingChangeset}; use crate::services::group::{
GroupChangesets, GroupData, GroupSettingChangeset, MoveGroupRowContext,
};
/// Using polymorphism to provides the customs action for different group controller. /// Using polymorphism to provides the customs action for different group controller.
/// ///
/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior. /// For example, the `CheckboxGroupController` implements this trait to provide custom behavior.
/// ///
pub trait GroupCustomize: Send + Sync { pub trait GroupCustomize: Send + Sync {
type CellData: DecodedCellData; type GroupTypeOption: TypeOption;
/// Returns the a value of the cell if the cell data is not exist. /// Returns the a value of the cell if the cell data is not exist.
/// The default value is `None` /// The default value is `None`
/// ///
@ -26,13 +28,17 @@ pub trait GroupCustomize: Send + Sync {
} }
/// Returns a bool value to determine whether the group should contain this cell or not. /// Returns a bool value to determine whether the group should contain this cell or not.
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool; fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool;
fn create_or_delete_group_when_cell_changed( fn create_or_delete_group_when_cell_changed(
&mut self, &mut self,
_row_detail: &RowDetail, _row_detail: &RowDetail,
_old_cell_data: Option<&Self::CellData>, _old_cell_data: Option<&<Self::GroupTypeOption as TypeOption>::CellProtobufType>,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> { ) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
Ok((None, None)) Ok((None, None))
} }
@ -43,16 +49,20 @@ pub trait GroupCustomize: Send + Sync {
fn add_or_remove_row_when_cell_changed( fn add_or_remove_row_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, row_detail: &RowDetail,
cell_data: &Self::CellData, cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB>; ) -> Vec<GroupRowsNotificationPB>;
/// Deletes the row from the group /// Deletes the row from the group
fn delete_row(&mut self, row: &Row, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB>; fn delete_row(
&mut self,
row: &Row,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB>;
/// Move row from one group to another /// Move row from one group to another
fn move_row( fn move_row(
&mut self, &mut self,
cell_data: &Self::CellData, cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
context: MoveGroupRowContext, context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB>; ) -> Vec<GroupRowsNotificationPB>;
@ -60,13 +70,14 @@ pub trait GroupCustomize: Send + Sync {
fn delete_group_when_move_row( fn delete_group_when_move_row(
&mut self, &mut self,
_row: &Row, _row: &Row,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Option<GroupPB> { ) -> Option<GroupPB> {
None None
} }
} }
/// Defines the shared actions any group controller can perform. /// Defines the shared actions any group controller can perform.
#[async_trait]
pub trait GroupControllerOperation: Send + Sync { pub trait GroupControllerOperation: Send + Sync {
/// The field that is used for grouping the rows /// The field that is used for grouping the rows
fn field_id(&self) -> &str; fn field_id(&self) -> &str;
@ -104,7 +115,10 @@ pub trait GroupControllerOperation: Send + Sync {
/// Update the group if the corresponding field is changed /// Update the group if the corresponding field is changed
fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>; fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>;
fn apply_group_setting_changeset(&mut self, changeset: GroupChangesets) -> FlowyResult<()>; async fn apply_group_changeset(
&mut self,
changeset: &GroupChangesets,
) -> FlowyResult<TypeOptionData>;
fn apply_group_configuration_setting_changeset( fn apply_group_configuration_setting_changeset(
&mut self, &mut self,

View File

@ -3,10 +3,13 @@ use std::fmt::Formatter;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::Field; use collab_database::fields::Field;
use collab_database::rows::{Cell, RowId};
use indexmap::IndexMap; use indexmap::IndexMap;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use tracing::event;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use lib_infra::future::Fut; use lib_infra::future::Fut;
@ -27,6 +30,18 @@ pub trait GroupSettingWriter: Send + Sync + 'static {
fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>>; fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>>;
} }
#[async_trait]
pub trait GroupTypeOptionCellOperation: Send + Sync + 'static {
async fn get_cell(&self, row_id: &RowId, field_id: &str) -> FlowyResult<Option<Cell>>;
async fn update_cell(
&self,
view_id: &str,
row_id: &RowId,
field_id: &str,
cell: Cell,
) -> FlowyResult<()>;
}
impl<T> std::fmt::Display for GroupContext<T> { impl<T> std::fmt::Display for GroupContext<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.group_by_id.iter().for_each(|(_, group)| { self.group_by_id.iter().for_each(|(_, group)| {
@ -64,7 +79,6 @@ pub struct GroupContext<C> {
/// A reader that implement the [GroupSettingReader] trait /// A reader that implement the [GroupSettingReader] trait
/// ///
#[allow(dead_code)]
reader: Arc<dyn GroupSettingReader>, reader: Arc<dyn GroupSettingReader>,
/// A writer that implement the [GroupSettingWriter] trait is used to save the /// A writer that implement the [GroupSettingWriter] trait is used to save the
@ -84,6 +98,7 @@ where
reader: Arc<dyn GroupSettingReader>, reader: Arc<dyn GroupSettingReader>,
writer: Arc<dyn GroupSettingWriter>, writer: Arc<dyn GroupSettingWriter>,
) -> FlowyResult<Self> { ) -> FlowyResult<Self> {
event!(tracing::Level::TRACE, "GroupContext::new");
let setting = match reader.get_group_setting(&view_id).await { let setting = match reader.get_group_setting(&view_id).await {
None => { None => {
let default_configuration = default_group_setting(&field); let default_configuration = default_group_setting(&field);
@ -356,7 +371,7 @@ where
} }
} }
pub(crate) fn update_group(&mut self, group_changeset: GroupChangeset) -> FlowyResult<()> { pub(crate) fn update_group(&mut self, group_changeset: &GroupChangeset) -> FlowyResult<()> {
let update_group = self.mut_group(&group_changeset.group_id, |group| { let update_group = self.mut_group(&group_changeset.group_id, |group| {
if let Some(visible) = group_changeset.visible { if let Some(visible) = group_changeset.visible {
group.visible = visible; group.visible = visible;

View File

@ -1,9 +1,10 @@
use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData}; use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cell, Cells, Row, RowDetail, RowId}; use collab_database::rows::{Cells, Row, RowDetail};
use futures::executor::block_on;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
@ -12,13 +13,16 @@ use flowy_error::FlowyResult;
use crate::entities::{ use crate::entities::{
FieldType, GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB, FieldType, GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB,
}; };
use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser, DecodedCellData}; use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser};
use crate::services::field::{default_type_option_data_from_type, TypeOption, TypeOptionCellData};
use crate::services::group::action::{ use crate::services::group::action::{
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize, DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize,
}; };
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
use crate::services::group::entities::{GroupData, GroupSetting}; use crate::services::group::entities::{GroupData, GroupSetting};
use crate::services::group::{Group, GroupChangesets, GroupSettingChangeset}; use crate::services::group::{
GroupChangeset, GroupChangesets, GroupSettingChangeset, GroupsBuilder, MoveGroupRowContext,
};
// use collab_database::views::Group; // use collab_database::views::Group;
@ -32,108 +36,67 @@ use crate::services::group::{Group, GroupChangesets, GroupSettingChangeset};
/// ///
pub trait GroupController: GroupControllerOperation + Send + Sync { pub trait GroupController: GroupControllerOperation + Send + Sync {
/// Called when the type option of the [Field] was updated. /// Called when the type option of the [Field] was updated.
fn did_update_field_type_option(&mut self, field: &Arc<Field>); fn did_update_field_type_option(&mut self, field: &Field);
/// Called before the row was created. /// Called before the row was created.
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str); fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str);
/// Called after the row was created. /// Called after the row was created.
fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str); fn did_create_row(&mut self, row_detail: &RowDetail, group_id: &str);
/// Update group name handler
fn update_group_name(&mut self, _group_id: &str, _group_name: &str) -> Option<TypeOptionData> {
None
}
} }
/// The [GroupsBuilder] trait is used to generate the groups for different [FieldType] #[async_trait]
pub trait GroupsBuilder { pub trait GroupOperationInterceptor {
type Context; type GroupTypeOption: TypeOption;
type TypeOptionType; async fn type_option_from_group_changeset(
&self,
fn build( changeset: &GroupChangeset,
field: &Field, type_option: &Self::GroupTypeOption,
context: &Self::Context, view_id: &str,
type_option: &Option<Self::TypeOptionType>, ) -> Option<TypeOptionData>;
) -> GeneratedGroups;
}
pub struct GeneratedGroups {
pub no_status_group: Option<Group>,
pub group_configs: Vec<GeneratedGroupConfig>,
}
pub struct GeneratedGroupConfig {
pub group: Group,
pub filter_content: String,
}
pub struct MoveGroupRowContext<'a> {
pub row_detail: &'a RowDetail,
pub row_changeset: &'a mut RowChangeset,
pub field: &'a Field,
pub to_group_id: &'a str,
pub to_row_id: Option<RowId>,
}
#[derive(Debug, Clone)]
pub struct RowChangeset {
pub row_id: RowId,
pub height: Option<i32>,
pub visibility: Option<bool>,
// Contains the key/value changes represents as the update of the cells. For example,
// if there is one cell was changed, then the `cell_by_field_id` will only have one key/value.
pub cell_by_field_id: HashMap<String, Cell>,
}
impl RowChangeset {
pub fn new(row_id: RowId) -> Self {
Self {
row_id,
height: None,
visibility: None,
cell_by_field_id: Default::default(),
}
}
pub fn is_empty(&self) -> bool {
self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty()
}
} }
/// C: represents the group configuration that impl [GroupConfigurationSerde] /// C: represents the group configuration that impl [GroupConfigurationSerde]
/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer] /// T: the type-option data deserializer that impl [TypeOptionDataDeserializer]
/// G: the group generator, [GroupsBuilder] /// G: the group generator, [GroupsBuilder]
/// P: the parser that impl [CellProtobufBlobParser] for the CellBytes /// P: the parser that impl [CellProtobufBlobParser] for the CellBytes
pub struct BaseGroupController<C, T, G, P> { pub struct BaseGroupController<C, T, G, P, I> {
pub grouping_field_id: String, pub grouping_field_id: String,
pub type_option: Option<T>, pub type_option: T,
pub context: GroupContext<C>, pub context: GroupContext<C>,
group_action_phantom: PhantomData<G>, group_builder_phantom: PhantomData<G>,
cell_parser_phantom: PhantomData<P>, cell_parser_phantom: PhantomData<P>,
pub operation_interceptor: I,
} }
impl<C, T, G, P> BaseGroupController<C, T, G, P> impl<C, T, G, P, I> BaseGroupController<C, T, G, P, I>
where where
C: Serialize + DeserializeOwned, C: Serialize + DeserializeOwned,
T: From<TypeOptionData>, T: TypeOption + From<TypeOptionData> + Send + Sync,
G: GroupsBuilder<Context = GroupContext<C>, TypeOptionType = T>, G: GroupsBuilder<Context = GroupContext<C>, GroupTypeOption = T>,
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
{ {
pub async fn new( pub async fn new(
grouping_field: &Arc<Field>, grouping_field: &Arc<Field>,
mut configuration: GroupContext<C>, mut configuration: GroupContext<C>,
operation_interceptor: I,
) -> FlowyResult<Self> { ) -> FlowyResult<Self> {
let field_type = FieldType::from(grouping_field.field_type); let field_type = FieldType::from(grouping_field.field_type);
let type_option = grouping_field.get_type_option::<T>(field_type); let type_option = grouping_field
let generated_groups = G::build(grouping_field, &configuration, &type_option); .get_type_option::<T>(&field_type)
.unwrap_or_else(|| T::from(default_type_option_data_from_type(&field_type)));
// TODO(nathan): remove block_on
let generated_groups = block_on(G::build(grouping_field, &configuration, &type_option));
let _ = configuration.init_groups(generated_groups)?; let _ = configuration.init_groups(generated_groups)?;
Ok(Self { Ok(Self {
grouping_field_id: grouping_field.id.clone(), grouping_field_id: grouping_field.id.clone(),
type_option, type_option,
context: configuration, context: configuration,
group_action_phantom: PhantomData, group_builder_phantom: PhantomData,
cell_parser_phantom: PhantomData, cell_parser_phantom: PhantomData,
operation_interceptor,
}) })
} }
@ -209,14 +172,15 @@ where
} }
} }
impl<C, T, G, P> GroupControllerOperation for BaseGroupController<C, T, G, P> #[async_trait]
impl<C, T, G, P, I> GroupControllerOperation for BaseGroupController<C, T, G, P, I>
where where
P: CellProtobufBlobParser, P: CellProtobufBlobParser<Object = <T as TypeOption>::CellProtobufType>,
C: Serialize + DeserializeOwned, C: Serialize + DeserializeOwned + Sync + Send,
T: From<TypeOptionData>, T: TypeOption + From<TypeOptionData> + Send + Sync,
G: GroupsBuilder<Context = GroupContext<C>, TypeOptionType = T>, G: GroupsBuilder<Context = GroupContext<C>, GroupTypeOption = T>,
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
Self: GroupCustomize<CellData = P::Object>, Self: GroupCustomize<GroupTypeOption = T>,
{ {
fn field_id(&self) -> &str { fn field_id(&self) -> &str {
&self.grouping_field_id &self.grouping_field_id
@ -232,7 +196,7 @@ where
} }
#[tracing::instrument(level = "trace", skip_all, fields(row_count=%rows.len(), group_result))] #[tracing::instrument(level = "trace", skip_all, fields(row_count=%rows.len(), group_result))]
fn fill_groups(&mut self, rows: &[&RowDetail], field: &Field) -> FlowyResult<()> { fn fill_groups(&mut self, rows: &[&RowDetail], _field: &Field) -> FlowyResult<()> {
for row_detail in rows { for row_detail in rows {
let cell = match row_detail.row.cells.get(&self.grouping_field_id) { let cell = match row_detail.row.cells.get(&self.grouping_field_id) {
None => self.placeholder_cell(), None => self.placeholder_cell(),
@ -241,8 +205,7 @@ where
if let Some(cell) = cell { if let Some(cell) = cell {
let mut grouped_rows: Vec<GroupedRow> = vec![]; let mut grouped_rows: Vec<GroupedRow> = vec![];
let cell_bytes = get_cell_protobuf(&cell, field, None); let cell_data = <T as TypeOption>::CellData::from(&cell);
let cell_data = cell_bytes.parser::<P>()?;
for group in self.context.groups() { for group in self.context.groups() {
if self.can_group(&group.filter_content, &cell_data) { if self.can_group(&group.filter_content, &cell_data) {
grouped_rows.push(GroupedRow { grouped_rows.push(GroupedRow {
@ -320,7 +283,7 @@ where
fn did_delete_delete_row( fn did_delete_delete_row(
&mut self, &mut self,
row: &Row, row: &Row,
field: &Field, _field: &Field,
) -> FlowyResult<DidMoveGroupRowResult> { ) -> FlowyResult<DidMoveGroupRowResult> {
// if the cell_rev is none, then the row must in the default group. // if the cell_rev is none, then the row must in the default group.
let mut result = DidMoveGroupRowResult { let mut result = DidMoveGroupRowResult {
@ -328,9 +291,8 @@ where
row_changesets: vec![], row_changesets: vec![],
}; };
if let Some(cell) = row.cells.get(&self.grouping_field_id) { if let Some(cell) = row.cells.get(&self.grouping_field_id) {
let cell_bytes = get_cell_protobuf(cell, field, None); let cell_data = <T as TypeOption>::CellData::from(cell);
let cell_data = cell_bytes.parser::<P>()?; if !cell_data.is_cell_empty() {
if !cell_data.is_empty() {
tracing::error!("did_delete_delete_row {:?}", cell); tracing::error!("did_delete_delete_row {:?}", cell);
result.row_changesets = self.delete_row(row, &cell_data); result.row_changesets = self.delete_row(row, &cell_data);
return Ok(result); return Ok(result);
@ -380,13 +342,24 @@ where
Ok(None) Ok(None)
} }
fn apply_group_setting_changeset(&mut self, changeset: GroupChangesets) -> FlowyResult<()> { async fn apply_group_changeset(
for group_changeset in changeset.update_groups { &mut self,
if let Err(e) = self.context.update_group(group_changeset) { changeset: &GroupChangesets,
tracing::error!("Failed to update group: {:?}", e); ) -> FlowyResult<TypeOptionData> {
for group_changeset in changeset.changesets.iter() {
self.context.update_group(group_changeset)?;
}
let mut type_option_data = TypeOptionData::new();
for group_changeset in changeset.changesets.iter() {
if let Some(new_type_option_data) = self
.operation_interceptor
.type_option_from_group_changeset(group_changeset, &self.type_option, &self.context.view_id)
.await
{
type_option_data.extend(new_type_option_data);
} }
} }
Ok(()) Ok(type_option_data)
} }
fn apply_group_configuration_setting_changeset( fn apply_group_configuration_setting_changeset(

View File

@ -1,20 +1,20 @@
use std::sync::Arc; use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::fields::Field;
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail}; use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB}; use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
use crate::services::cell::insert_checkbox_cell; use crate::services::cell::insert_checkbox_cell;
use crate::services::field::{ use crate::services::field::{
CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOption, CHECK, UNCHECK, CheckboxCellDataParser, CheckboxTypeOption, TypeOption, CHECK, UNCHECK,
}; };
use crate::services::group::action::GroupCustomize; use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{ use crate::services::group::controller::{BaseGroupController, GroupController};
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext, use crate::services::group::{
move_group_row, GeneratedGroupConfig, GeneratedGroups, Group, GroupChangeset,
GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
}; };
use crate::services::group::{move_group_row, GeneratedGroupConfig, GeneratedGroups, Group};
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
pub struct CheckboxGroupConfiguration { pub struct CheckboxGroupConfiguration {
@ -24,14 +24,15 @@ pub struct CheckboxGroupConfiguration {
pub type CheckboxGroupController = BaseGroupController< pub type CheckboxGroupController = BaseGroupController<
CheckboxGroupConfiguration, CheckboxGroupConfiguration,
CheckboxTypeOption, CheckboxTypeOption,
CheckboxGroupGenerator, CheckboxGroupBuilder,
CheckboxCellDataParser, CheckboxCellDataParser,
CheckboxGroupOperationInterceptorImpl,
>; >;
pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfiguration>; pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfiguration>;
impl GroupCustomize for CheckboxGroupController { impl GroupCustomize for CheckboxGroupController {
type CellData = CheckboxCellData; type GroupTypeOption = CheckboxTypeOption;
fn placeholder_cell(&self) -> Option<Cell> { fn placeholder_cell(&self) -> Option<Cell> {
Some( Some(
new_cell_builder(FieldType::Checkbox) new_cell_builder(FieldType::Checkbox)
@ -40,7 +41,11 @@ impl GroupCustomize for CheckboxGroupController {
) )
} }
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
if cell_data.is_check() { if cell_data.is_check() {
content == CHECK content == CHECK
} else { } else {
@ -51,7 +56,7 @@ impl GroupCustomize for CheckboxGroupController {
fn add_or_remove_row_when_cell_changed( fn add_or_remove_row_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, row_detail: &RowDetail,
cell_data: &Self::CellData, cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| { self.context.iter_mut_status_groups(|group| {
@ -100,7 +105,11 @@ impl GroupCustomize for CheckboxGroupController {
changesets changesets
} }
fn delete_row(&mut self, row: &Row, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> { fn delete_row(
&mut self,
row: &Row,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_groups(|group| { self.context.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -118,7 +127,7 @@ impl GroupCustomize for CheckboxGroupController {
fn move_row( fn move_row(
&mut self, &mut self,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext, mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
@ -132,7 +141,7 @@ impl GroupCustomize for CheckboxGroupController {
} }
impl GroupController for CheckboxGroupController { impl GroupController for CheckboxGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) { fn did_update_field_type_option(&mut self, _field: &Field) {
// Do nothing // Do nothing
} }
@ -154,15 +163,16 @@ impl GroupController for CheckboxGroupController {
} }
} }
pub struct CheckboxGroupGenerator(); pub struct CheckboxGroupBuilder();
impl GroupsBuilder for CheckboxGroupGenerator { #[async_trait]
impl GroupsBuilder for CheckboxGroupBuilder {
type Context = CheckboxGroupContext; type Context = CheckboxGroupContext;
type TypeOptionType = CheckboxTypeOption; type GroupTypeOption = CheckboxTypeOption;
fn build( async fn build(
_field: &Field, _field: &Field,
_context: &Self::Context, _context: &Self::Context,
_type_option: &Option<Self::TypeOptionType>, _type_option: &Self::GroupTypeOption,
) -> GeneratedGroups { ) -> GeneratedGroups {
let check_group = GeneratedGroupConfig { let check_group = GeneratedGroupConfig {
group: Group::new(CHECK.to_string(), "".to_string()), group: Group::new(CHECK.to_string(), "".to_string()),
@ -180,3 +190,18 @@ impl GroupsBuilder for CheckboxGroupGenerator {
} }
} }
} }
pub struct CheckboxGroupOperationInterceptorImpl {}
#[async_trait]
impl GroupOperationInterceptor for CheckboxGroupOperationInterceptorImpl {
type GroupTypeOption = CheckboxTypeOption;
async fn type_option_from_group_changeset(
&self,
_changeset: &GroupChangeset,
_type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
todo!()
}
}

View File

@ -1,13 +1,13 @@
use std::format; use std::format;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use async_trait::async_trait;
use chrono::{ use chrono::{
DateTime, Datelike, Days, Duration, Local, NaiveDate, NaiveDateTime, Offset, TimeZone, DateTime, Datelike, Days, Duration, Local, NaiveDate, NaiveDateTime, Offset, TimeZone,
}; };
use chrono_tz::Tz; use chrono_tz::Tz;
use collab_database::database::timestamp; use collab_database::database::timestamp;
use collab_database::fields::Field; use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail}; use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
@ -15,18 +15,16 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use crate::entities::{ use crate::entities::{
DateCellDataPB, FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB,
RowMetaPB,
}; };
use crate::services::cell::insert_date_cell; use crate::services::cell::insert_date_cell;
use crate::services::field::{DateCellData, DateCellDataParser, DateTypeOption}; use crate::services::field::{DateCellData, DateCellDataParser, DateTypeOption, TypeOption};
use crate::services::group::action::GroupCustomize; use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{ use crate::services::group::controller::{BaseGroupController, GroupController};
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::{ use crate::services::group::{
make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group, make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group,
GroupChangeset, GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
}; };
pub trait GroupConfigurationContentSerde: Sized + Send + Sync { pub trait GroupConfigurationContentSerde: Sized + Send + Sync {
@ -63,14 +61,15 @@ pub enum DateCondition {
pub type DateGroupController = BaseGroupController< pub type DateGroupController = BaseGroupController<
DateGroupConfiguration, DateGroupConfiguration,
DateTypeOption, DateTypeOption,
DateGroupGenerator, DateGroupBuilder,
DateCellDataParser, DateCellDataParser,
DateGroupOperationInterceptorImpl,
>; >;
pub type DateGroupContext = GroupContext<DateGroupConfiguration>; pub type DateGroupContext = GroupContext<DateGroupConfiguration>;
impl GroupCustomize for DateGroupController { impl GroupCustomize for DateGroupController {
type CellData = DateCellDataPB; type GroupTypeOption = DateTypeOption;
fn placeholder_cell(&self) -> Option<Cell> { fn placeholder_cell(&self) -> Option<Cell> {
Some( Some(
@ -80,47 +79,48 @@ impl GroupCustomize for DateGroupController {
) )
} }
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { fn can_group(
&self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
content content
== group_id( == group_id(
&cell_data.into(), cell_data,
self.type_option.as_ref(), &self.type_option,
&self.context.get_setting_content(), &self.context.get_setting_content(),
) )
} }
fn create_or_delete_group_when_cell_changed( fn create_or_delete_group_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, _row_detail: &RowDetail,
old_cell_data: Option<&Self::CellData>, _old_cell_data: Option<&<Self::GroupTypeOption as TypeOption>::CellProtobufType>,
cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> { ) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
let setting_content = self.context.get_setting_content(); let setting_content = self.context.get_setting_content();
let mut inserted_group = None; let mut inserted_group = None;
if self if self
.context .context
.get_group(&group_id( .get_group(&group_id(
&cell_data.into(), &_cell_data.into(),
self.type_option.as_ref(), &self.type_option,
&setting_content, &setting_content,
)) ))
.is_none() .is_none()
{ {
let group = make_group_from_date_cell( let group =
&cell_data.into(), make_group_from_date_cell(&_cell_data.into(), &self.type_option, &setting_content);
self.type_option.as_ref(),
&setting_content,
);
let mut new_group = self.context.add_new_group(group)?; let mut new_group = self.context.add_new_group(group)?;
new_group.group.rows.push(RowMetaPB::from(row_detail)); new_group.group.rows.push(RowMetaPB::from(_row_detail));
inserted_group = Some(new_group); inserted_group = Some(new_group);
} }
// Delete the old group if there are no rows in that group // Delete the old group if there are no rows in that group
let deleted_group = match old_cell_data.and_then(|old_cell_data| { let deleted_group = match _old_cell_data.and_then(|old_cell_data| {
self.context.get_group(&group_id( self.context.get_group(&group_id(
&old_cell_data.into(), &old_cell_data.into(),
self.type_option.as_ref(), &self.type_option,
&setting_content, &setting_content,
)) ))
}) { }) {
@ -148,19 +148,13 @@ impl GroupCustomize for DateGroupController {
fn add_or_remove_row_when_cell_changed( fn add_or_remove_row_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, row_detail: &RowDetail,
cell_data: &Self::CellData, cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
let setting_content = self.context.get_setting_content(); let setting_content = self.context.get_setting_content();
self.context.iter_mut_status_groups(|group| { self.context.iter_mut_status_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
if group.id if group.id == group_id(&cell_data.into(), &self.type_option, &setting_content) {
== group_id(
&cell_data.into(),
self.type_option.as_ref(),
&setting_content,
)
{
if !group.contains_row(&row_detail.row.id) { if !group.contains_row(&row_detail.row.id) {
changeset changeset
.inserted_rows .inserted_rows
@ -181,7 +175,11 @@ impl GroupCustomize for DateGroupController {
changesets changesets
} }
fn delete_row(&mut self, row: &Row, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> { fn delete_row(
&mut self,
row: &Row,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_groups(|group| { self.context.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -199,7 +197,7 @@ impl GroupCustomize for DateGroupController {
fn move_row( fn move_row(
&mut self, &mut self,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext, mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
@ -214,13 +212,13 @@ impl GroupCustomize for DateGroupController {
fn delete_group_when_move_row( fn delete_group_when_move_row(
&mut self, &mut self,
_row: &Row, _row: &Row,
cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Option<GroupPB> { ) -> Option<GroupPB> {
let mut deleted_group = None; let mut deleted_group = None;
let setting_content = self.context.get_setting_content(); let setting_content = self.context.get_setting_content();
if let Some((_, group)) = self.context.get_group(&group_id( if let Some((_, group)) = self.context.get_group(&group_id(
&cell_data.into(), &_cell_data.into(),
self.type_option.as_ref(), &self.type_option,
&setting_content, &setting_content,
)) { )) {
if group.rows.len() == 1 { if group.rows.len() == 1 {
@ -237,7 +235,7 @@ impl GroupCustomize for DateGroupController {
} }
impl GroupController for DateGroupController { impl GroupController for DateGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {} fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) { fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
match self.context.get_group(group_id) { match self.context.get_group(group_id) {
@ -257,18 +255,19 @@ impl GroupController for DateGroupController {
} }
} }
pub struct DateGroupGenerator(); pub struct DateGroupBuilder();
impl GroupsBuilder for DateGroupGenerator { #[async_trait]
impl GroupsBuilder for DateGroupBuilder {
type Context = DateGroupContext; type Context = DateGroupContext;
type TypeOptionType = DateTypeOption; type GroupTypeOption = DateTypeOption;
fn build( async fn build(
field: &Field, field: &Field,
context: &Self::Context, context: &Self::Context,
type_option: &Option<Self::TypeOptionType>, type_option: &Self::GroupTypeOption,
) -> GeneratedGroups { ) -> GeneratedGroups {
// Read all the cells for the grouping field // Read all the cells for the grouping field
let cells = futures::executor::block_on(context.get_all_cells()); let cells = context.get_all_cells().await;
// Generate the groups // Generate the groups
let mut group_configs: Vec<GeneratedGroupConfig> = cells let mut group_configs: Vec<GeneratedGroupConfig> = cells
@ -276,8 +275,7 @@ impl GroupsBuilder for DateGroupGenerator {
.flat_map(|value| value.into_date_field_cell_data()) .flat_map(|value| value.into_date_field_cell_data())
.filter(|cell| cell.timestamp.is_some()) .filter(|cell| cell.timestamp.is_some())
.map(|cell| { .map(|cell| {
let group = let group = make_group_from_date_cell(&cell, type_option, &context.get_setting_content());
make_group_from_date_cell(&cell, type_option.as_ref(), &context.get_setting_content());
GeneratedGroupConfig { GeneratedGroupConfig {
filter_content: group.id.clone(), filter_content: group.id.clone(),
group, group,
@ -296,7 +294,7 @@ impl GroupsBuilder for DateGroupGenerator {
fn make_group_from_date_cell( fn make_group_from_date_cell(
cell_data: &DateCellData, cell_data: &DateCellData,
type_option: Option<&DateTypeOption>, type_option: &DateTypeOption,
setting_content: &str, setting_content: &str,
) -> Group { ) -> Group {
let group_id = group_id(cell_data, type_option, setting_content); let group_id = group_id(cell_data, type_option, setting_content);
@ -310,11 +308,9 @@ const GROUP_ID_DATE_FORMAT: &str = "%Y/%m/%d";
fn group_id( fn group_id(
cell_data: &DateCellData, cell_data: &DateCellData,
type_option: Option<&DateTypeOption>, type_option: &DateTypeOption,
setting_content: &str, setting_content: &str,
) -> String { ) -> String {
let binding = DateTypeOption::default();
let type_option = type_option.unwrap_or(&binding);
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default(); let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
let date_time = date_time_from_timestamp(cell_data.timestamp, &type_option.timezone_id); let date_time = date_time_from_timestamp(cell_data.timestamp, &type_option.timezone_id);
@ -373,11 +369,9 @@ fn group_id(
fn group_name_from_id( fn group_name_from_id(
group_id: &str, group_id: &str,
type_option: Option<&DateTypeOption>, type_option: &DateTypeOption,
setting_content: &str, setting_content: &str,
) -> String { ) -> String {
let binding = DateTypeOption::default();
let type_option = type_option.unwrap_or(&binding);
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default(); let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
let date = NaiveDate::parse_from_str(group_id, GROUP_ID_DATE_FORMAT).unwrap(); let date = NaiveDate::parse_from_str(group_id, GROUP_ID_DATE_FORMAT).unwrap();
@ -449,6 +443,21 @@ fn date_time_from_timestamp(timestamp: Option<i64>, timezone_id: &str) -> DateTi
} }
} }
pub struct DateGroupOperationInterceptorImpl {}
#[async_trait]
impl GroupOperationInterceptor for DateGroupOperationInterceptorImpl {
type GroupTypeOption = DateTypeOption;
async fn type_option_from_group_changeset(
&self,
_changeset: &GroupChangeset,
_type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
todo!()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::vec; use std::vec;
@ -582,16 +591,11 @@ mod tests {
]; ];
for (i, test) in tests.iter().enumerate() { for (i, test) in tests.iter().enumerate() {
let group_id = group_id( let group_id = group_id(&test.cell_data, test.type_option, &test.setting_content);
&test.cell_data,
Some(test.type_option),
&test.setting_content,
);
assert_eq!(test.exp_group_id, group_id, "test {}", i); assert_eq!(test.exp_group_id, group_id, "test {}", i);
if !test.exp_group_name.is_empty() { if !test.exp_group_name.is_empty() {
let group_name = let group_name = group_name_from_id(&group_id, test.type_option, &test.setting_content);
group_name_from_id(&group_id, Some(test.type_option), &test.setting_content);
assert_eq!(test.exp_group_name, group_name, "test {}", i); assert_eq!(test.exp_group_name, group_name, "test {}", i);
} }
} }

View File

@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use collab_database::fields::Field; use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cells, Row, RowDetail}; use collab_database::rows::{Cells, Row, RowDetail};
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
@ -40,6 +41,7 @@ impl DefaultGroupController {
} }
} }
#[async_trait]
impl GroupControllerOperation for DefaultGroupController { impl GroupControllerOperation for DefaultGroupController {
fn field_id(&self) -> &str { fn field_id(&self) -> &str {
&self.field_id &self.field_id
@ -102,8 +104,11 @@ impl GroupControllerOperation for DefaultGroupController {
Ok(None) Ok(None)
} }
fn apply_group_setting_changeset(&mut self, _changeset: GroupChangesets) -> FlowyResult<()> { async fn apply_group_changeset(
Ok(()) &mut self,
_changeset: &GroupChangesets,
) -> FlowyResult<TypeOptionData> {
Ok(TypeOptionData::default())
} }
fn apply_group_configuration_setting_changeset( fn apply_group_configuration_setting_changeset(
@ -115,7 +120,7 @@ impl GroupControllerOperation for DefaultGroupController {
} }
impl GroupController for DefaultGroupController { impl GroupController for DefaultGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) { fn did_update_field_type_option(&mut self, _field: &Field) {
// Do nothing // Do nothing
} }

View File

@ -1,21 +1,20 @@
use std::sync::Arc; use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData}; use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail}; use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB}; use crate::entities::{FieldType, GroupRowsNotificationPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{ use crate::services::field::{
MultiSelectTypeOption, SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction, MultiSelectTypeOption, SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction,
TypeOption,
}; };
use crate::services::group::action::GroupCustomize; use crate::services::group::action::GroupCustomize;
use crate::services::group::controller::{ use crate::services::group::controller::{BaseGroupController, GroupController};
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::{ use crate::services::group::{
add_or_remove_select_option_row, generate_select_option_groups, make_no_status_group, add_or_remove_select_option_row, generate_select_option_groups, make_no_status_group,
move_group_row, remove_select_option_row, GeneratedGroups, GroupContext, move_group_row, remove_select_option_row, GeneratedGroups, GroupChangeset, GroupContext,
GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
}; };
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
@ -28,18 +27,20 @@ pub type MultiSelectOptionGroupContext = GroupContext<MultiSelectGroupConfigurat
pub type MultiSelectGroupController = BaseGroupController< pub type MultiSelectGroupController = BaseGroupController<
MultiSelectGroupConfiguration, MultiSelectGroupConfiguration,
MultiSelectTypeOption, MultiSelectTypeOption,
MultiSelectGroupGenerator, MultiSelectGroupBuilder,
SelectOptionCellDataParser, SelectOptionCellDataParser,
MultiSelectGroupOperationInterceptorImpl,
>; >;
impl GroupCustomize for MultiSelectGroupController { impl GroupCustomize for MultiSelectGroupController {
type CellData = SelectOptionCellDataPB; type GroupTypeOption = MultiSelectTypeOption;
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { fn can_group(
cell_data &self,
.select_options content: &str,
.iter() cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
.any(|option| option.id == content) ) -> bool {
cell_data.iter().any(|option_id| option_id == content)
} }
fn placeholder_cell(&self) -> Option<Cell> { fn placeholder_cell(&self) -> Option<Cell> {
@ -53,7 +54,7 @@ impl GroupCustomize for MultiSelectGroupController {
fn add_or_remove_row_when_cell_changed( fn add_or_remove_row_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, row_detail: &RowDetail,
cell_data: &Self::CellData, cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| { self.context.iter_mut_status_groups(|group| {
@ -64,7 +65,11 @@ impl GroupCustomize for MultiSelectGroupController {
changesets changesets
} }
fn delete_row(&mut self, row: &Row, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> { fn delete_row(
&mut self,
row: &Row,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| { self.context.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row) { if let Some(changeset) = remove_select_option_row(group, cell_data, row) {
@ -76,7 +81,7 @@ impl GroupCustomize for MultiSelectGroupController {
fn move_row( fn move_row(
&mut self, &mut self,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext, mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
@ -90,7 +95,7 @@ impl GroupCustomize for MultiSelectGroupController {
} }
impl GroupController for MultiSelectGroupController { impl GroupController for MultiSelectGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {} fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) { fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
match self.context.get_group(group_id) { match self.context.get_group(group_id) {
@ -107,49 +112,56 @@ impl GroupController for MultiSelectGroupController {
group.add_row(row_detail.clone()) group.add_row(row_detail.clone())
} }
} }
fn update_group_name(&mut self, group_id: &str, group_name: &str) -> Option<TypeOptionData> {
match &self.type_option {
Some(type_option) => {
let select_option = type_option
.options
.iter()
.find(|option| option.id == group_id)
.unwrap();
let new_select_option = SelectOption {
name: group_name.to_owned(),
..select_option.to_owned()
};
let mut new_type_option = type_option.clone();
new_type_option.insert_option(new_select_option);
Some(new_type_option.to_type_option_data())
},
None => None,
}
}
} }
pub struct MultiSelectGroupGenerator; pub struct MultiSelectGroupBuilder;
impl GroupsBuilder for MultiSelectGroupGenerator { #[async_trait]
impl GroupsBuilder for MultiSelectGroupBuilder {
type Context = MultiSelectOptionGroupContext; type Context = MultiSelectOptionGroupContext;
type TypeOptionType = MultiSelectTypeOption; type GroupTypeOption = MultiSelectTypeOption;
fn build( async fn build(
field: &Field, field: &Field,
_context: &Self::Context, _context: &Self::Context,
type_option: &Option<Self::TypeOptionType>, type_option: &Self::GroupTypeOption,
) -> GeneratedGroups { ) -> GeneratedGroups {
let group_configs = match type_option { let group_configs = generate_select_option_groups(&field.id, &type_option.options);
None => vec![],
Some(type_option) => generate_select_option_groups(&field.id, &type_option.options),
};
GeneratedGroups { GeneratedGroups {
no_status_group: Some(make_no_status_group(field)), no_status_group: Some(make_no_status_group(field)),
group_configs, group_configs,
} }
} }
} }
pub struct MultiSelectGroupOperationInterceptorImpl;
#[async_trait]
impl GroupOperationInterceptor for MultiSelectGroupOperationInterceptorImpl {
type GroupTypeOption = MultiSelectTypeOption;
#[tracing::instrument(level = "trace", skip_all)]
async fn type_option_from_group_changeset(
&self,
changeset: &GroupChangeset,
type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
if let Some(name) = &changeset.name {
let mut new_type_option = type_option.clone();
let select_option = type_option
.options
.iter()
.find(|option| option.id == changeset.group_id)
.unwrap();
let new_select_option = SelectOption {
name: name.to_owned(),
..select_option.to_owned()
};
new_type_option.insert_option(new_select_option);
return Some(new_type_option.into());
}
None
}
}

View File

@ -1,21 +1,22 @@
use std::sync::Arc; use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData}; use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail}; use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB}; use crate::entities::{FieldType, GroupRowsNotificationPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{ use crate::services::field::{
SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction, SingleSelectTypeOption, SelectOption, SelectOptionCellDataParser, SelectTypeOptionSharedAction, SingleSelectTypeOption,
TypeOption,
}; };
use crate::services::group::action::GroupCustomize; use crate::services::group::action::GroupCustomize;
use crate::services::group::controller::{ use crate::services::group::controller::{BaseGroupController, GroupController};
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::controller_impls::select_option_controller::util::*; use crate::services::group::controller_impls::select_option_controller::util::*;
use crate::services::group::entities::GroupData; use crate::services::group::entities::GroupData;
use crate::services::group::{make_no_status_group, GeneratedGroups, GroupContext}; use crate::services::group::{
make_no_status_group, GeneratedGroups, GroupChangeset, GroupContext, GroupOperationInterceptor,
GroupsBuilder, MoveGroupRowContext,
};
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
pub struct SingleSelectGroupConfiguration { pub struct SingleSelectGroupConfiguration {
@ -28,17 +29,19 @@ pub type SingleSelectOptionGroupContext = GroupContext<SingleSelectGroupConfigur
pub type SingleSelectGroupController = BaseGroupController< pub type SingleSelectGroupController = BaseGroupController<
SingleSelectGroupConfiguration, SingleSelectGroupConfiguration,
SingleSelectTypeOption, SingleSelectTypeOption,
SingleSelectGroupGenerator, SingleSelectGroupBuilder,
SelectOptionCellDataParser, SelectOptionCellDataParser,
SingleSelectGroupOperationInterceptorImpl,
>; >;
impl GroupCustomize for SingleSelectGroupController { impl GroupCustomize for SingleSelectGroupController {
type CellData = SelectOptionCellDataPB; type GroupTypeOption = SingleSelectTypeOption;
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { fn can_group(
cell_data &self,
.select_options content: &str,
.iter() cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
.any(|option| option.id == content) ) -> bool {
cell_data.iter().any(|option_id| option_id == content)
} }
fn placeholder_cell(&self) -> Option<Cell> { fn placeholder_cell(&self) -> Option<Cell> {
@ -52,7 +55,7 @@ impl GroupCustomize for SingleSelectGroupController {
fn add_or_remove_row_when_cell_changed( fn add_or_remove_row_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, row_detail: &RowDetail,
cell_data: &Self::CellData, cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| { self.context.iter_mut_status_groups(|group| {
@ -63,7 +66,11 @@ impl GroupCustomize for SingleSelectGroupController {
changesets changesets
} }
fn delete_row(&mut self, row: &Row, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> { fn delete_row(
&mut self,
row: &Row,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| { self.context.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row) { if let Some(changeset) = remove_select_option_row(group, cell_data, row) {
@ -75,7 +82,7 @@ impl GroupCustomize for SingleSelectGroupController {
fn move_row( fn move_row(
&mut self, &mut self,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext, mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
@ -89,7 +96,7 @@ impl GroupCustomize for SingleSelectGroupController {
} }
impl GroupController for SingleSelectGroupController { impl GroupController for SingleSelectGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {} fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) { fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
let group: Option<&mut GroupData> = self.context.get_mut_group(group_id); let group: Option<&mut GroupData> = self.context.get_mut_group(group_id);
@ -107,44 +114,19 @@ impl GroupController for SingleSelectGroupController {
group.add_row(row_detail.clone()) group.add_row(row_detail.clone())
} }
} }
fn update_group_name(&mut self, group_id: &str, group_name: &str) -> Option<TypeOptionData> {
match &self.type_option {
Some(type_option) => {
let select_option = type_option
.options
.iter()
.find(|option| option.id == group_id)
.unwrap();
let new_select_option = SelectOption {
name: group_name.to_owned(),
..select_option.to_owned()
};
let mut new_type_option = type_option.clone();
new_type_option.insert_option(new_select_option);
Some(new_type_option.to_type_option_data())
},
None => None,
}
}
} }
pub struct SingleSelectGroupGenerator(); pub struct SingleSelectGroupBuilder();
impl GroupsBuilder for SingleSelectGroupGenerator { #[async_trait]
impl GroupsBuilder for SingleSelectGroupBuilder {
type Context = SingleSelectOptionGroupContext; type Context = SingleSelectOptionGroupContext;
type TypeOptionType = SingleSelectTypeOption; type GroupTypeOption = SingleSelectTypeOption;
fn build( async fn build(
field: &Field, field: &Field,
_context: &Self::Context, _context: &Self::Context,
type_option: &Option<Self::TypeOptionType>, type_option: &Self::GroupTypeOption,
) -> GeneratedGroups { ) -> GeneratedGroups {
let group_configs = match type_option { let group_configs = generate_select_option_groups(&field.id, &type_option.options);
None => vec![],
Some(type_option) => generate_select_option_groups(&field.id, &type_option.options),
};
GeneratedGroups { GeneratedGroups {
no_status_group: Some(make_no_status_group(field)), no_status_group: Some(make_no_status_group(field)),
@ -152,3 +134,36 @@ impl GroupsBuilder for SingleSelectGroupGenerator {
} }
} }
} }
pub struct SingleSelectGroupOperationInterceptorImpl;
#[async_trait]
impl GroupOperationInterceptor for SingleSelectGroupOperationInterceptorImpl {
type GroupTypeOption = SingleSelectTypeOption;
#[tracing::instrument(level = "trace", skip_all)]
async fn type_option_from_group_changeset(
&self,
changeset: &GroupChangeset,
type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
if let Some(name) = &changeset.name {
let mut new_type_option = type_option.clone();
let select_option = type_option
.options
.iter()
.find(|option| option.id == changeset.group_id)
.unwrap();
let new_select_option = SelectOption {
name: name.to_owned(),
..select_option.to_owned()
};
new_type_option.insert_option(new_select_option);
return Some(new_type_option.into());
}
None
}
}

View File

@ -8,9 +8,8 @@ use crate::entities::{
use crate::services::cell::{ use crate::services::cell::{
insert_checkbox_cell, insert_date_cell, insert_select_option_cell, insert_url_cell, insert_checkbox_cell, insert_date_cell, insert_select_option_cell, insert_url_cell,
}; };
use crate::services::field::{SelectOption, CHECK}; use crate::services::field::{SelectOption, SelectOptionIds, CHECK};
use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::{GeneratedGroupConfig, Group, GroupData, MoveGroupRowContext};
use crate::services::group::{GeneratedGroupConfig, Group, GroupData};
pub fn add_or_remove_select_option_row( pub fn add_or_remove_select_option_row(
group: &mut GroupData, group: &mut GroupData,
@ -52,12 +51,12 @@ pub fn add_or_remove_select_option_row(
pub fn remove_select_option_row( pub fn remove_select_option_row(
group: &mut GroupData, group: &mut GroupData,
cell_data: &SelectOptionCellDataPB, cell_data: &SelectOptionIds,
row: &Row, row: &Row,
) -> Option<GroupRowsNotificationPB> { ) -> Option<GroupRowsNotificationPB> {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
cell_data.select_options.iter().for_each(|option| { cell_data.iter().for_each(|option_id| {
if option.id == group.id && group.contains_row(&row.id) { if option_id == &group.id && group.contains_row(&row.id) {
group.remove_row(&row.id); group.remove_row(&row.id);
changeset.deleted_rows.push(row.id.clone().into_inner()); changeset.deleted_rows.push(row.id.clone().into_inner());
} }

View File

@ -1,6 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use collab_database::fields::Field; use async_trait::async_trait;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail}; use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,17 +9,16 @@ use flowy_error::FlowyResult;
use crate::entities::{ use crate::entities::{
FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB, FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB,
URLCellDataPB,
}; };
use crate::services::cell::insert_url_cell; use crate::services::cell::insert_url_cell;
use crate::services::field::{URLCellData, URLCellDataParser, URLTypeOption}; use crate::services::field::{TypeOption, URLCellData, URLCellDataParser, URLTypeOption};
use crate::services::group::action::GroupCustomize; use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{ use crate::services::group::controller::{BaseGroupController, GroupController};
BaseGroupController, GroupController, GroupsBuilder, MoveGroupRowContext,
};
use crate::services::group::{ use crate::services::group::{
make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group, make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group,
GroupChangeset, GroupOperationInterceptor, GroupTypeOptionCellOperation, GroupsBuilder,
MoveGroupRowContext,
}; };
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
@ -26,13 +26,18 @@ pub struct URLGroupConfiguration {
pub hide_empty: bool, pub hide_empty: bool,
} }
pub type URLGroupController = pub type URLGroupController = BaseGroupController<
BaseGroupController<URLGroupConfiguration, URLTypeOption, URLGroupGenerator, URLCellDataParser>; URLGroupConfiguration,
URLTypeOption,
URLGroupGenerator,
URLCellDataParser,
URLGroupOperationInterceptorImpl,
>;
pub type URLGroupContext = GroupContext<URLGroupConfiguration>; pub type URLGroupContext = GroupContext<URLGroupConfiguration>;
impl GroupCustomize for URLGroupController { impl GroupCustomize for URLGroupController {
type CellData = URLCellDataPB; type GroupTypeOption = URLTypeOption;
fn placeholder_cell(&self) -> Option<Cell> { fn placeholder_cell(&self) -> Option<Cell> {
Some( Some(
@ -42,15 +47,19 @@ impl GroupCustomize for URLGroupController {
) )
} }
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { fn can_group(
cell_data.content == content &self,
content: &str,
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> bool {
cell_data.data == content
} }
fn create_or_delete_group_when_cell_changed( fn create_or_delete_group_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, _row_detail: &RowDetail,
_old_cell_data: Option<&Self::CellData>, _old_cell_data: Option<&<Self::GroupTypeOption as TypeOption>::CellProtobufType>,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> { ) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
// Just return if the group with this url already exists // Just return if the group with this url already exists
let mut inserted_group = None; let mut inserted_group = None;
@ -58,7 +67,7 @@ impl GroupCustomize for URLGroupController {
let cell_data: URLCellData = _cell_data.clone().into(); let cell_data: URLCellData = _cell_data.clone().into();
let group = make_group_from_url_cell(&cell_data); let group = make_group_from_url_cell(&cell_data);
let mut new_group = self.context.add_new_group(group)?; let mut new_group = self.context.add_new_group(group)?;
new_group.group.rows.push(RowMetaPB::from(row_detail)); new_group.group.rows.push(RowMetaPB::from(_row_detail));
inserted_group = Some(new_group); inserted_group = Some(new_group);
} }
@ -90,7 +99,7 @@ impl GroupCustomize for URLGroupController {
fn add_or_remove_row_when_cell_changed( fn add_or_remove_row_when_cell_changed(
&mut self, &mut self,
row_detail: &RowDetail, row_detail: &RowDetail,
cell_data: &Self::CellData, cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_status_groups(|group| { self.context.iter_mut_status_groups(|group| {
@ -116,7 +125,11 @@ impl GroupCustomize for URLGroupController {
changesets changesets
} }
fn delete_row(&mut self, row: &Row, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> { fn delete_row(
&mut self,
row: &Row,
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.context.iter_mut_groups(|group| { self.context.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -134,7 +147,7 @@ impl GroupCustomize for URLGroupController {
fn move_row( fn move_row(
&mut self, &mut self,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
mut context: MoveGroupRowContext, mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
@ -145,11 +158,10 @@ impl GroupCustomize for URLGroupController {
}); });
group_changeset group_changeset
} }
fn delete_group_when_move_row( fn delete_group_when_move_row(
&mut self, &mut self,
_row: &Row, _row: &Row,
_cell_data: &Self::CellData, _cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
) -> Option<GroupPB> { ) -> Option<GroupPB> {
let mut deleted_group = None; let mut deleted_group = None;
if let Some((_, group)) = self.context.get_group(&_cell_data.content) { if let Some((_, group)) = self.context.get_group(&_cell_data.content) {
@ -167,7 +179,7 @@ impl GroupCustomize for URLGroupController {
} }
impl GroupController for URLGroupController { impl GroupController for URLGroupController {
fn did_update_field_type_option(&mut self, _field: &Arc<Field>) {} fn did_update_field_type_option(&mut self, _field: &Field) {}
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) { fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
match self.context.get_group(group_id) { match self.context.get_group(group_id) {
@ -187,17 +199,18 @@ impl GroupController for URLGroupController {
} }
pub struct URLGroupGenerator(); pub struct URLGroupGenerator();
#[async_trait]
impl GroupsBuilder for URLGroupGenerator { impl GroupsBuilder for URLGroupGenerator {
type Context = URLGroupContext; type Context = URLGroupContext;
type TypeOptionType = URLTypeOption; type GroupTypeOption = URLTypeOption;
fn build( async fn build(
field: &Field, field: &Field,
context: &Self::Context, context: &Self::Context,
_type_option: &Option<Self::TypeOptionType>, _type_option: &Self::GroupTypeOption,
) -> GeneratedGroups { ) -> GeneratedGroups {
// Read all the cells for the grouping field // Read all the cells for the grouping field
let cells = futures::executor::block_on(context.get_all_cells()); let cells = context.get_all_cells().await;
// Generate the groups // Generate the groups
let group_configs = cells let group_configs = cells
@ -223,3 +236,21 @@ fn make_group_from_url_cell(cell: &URLCellData) -> Group {
let group_name = cell.data.clone(); let group_name = cell.data.clone();
Group::new(group_id, group_name) Group::new(group_id, group_name)
} }
pub struct URLGroupOperationInterceptorImpl {
#[allow(dead_code)]
pub(crate) cell_writer: Arc<dyn GroupTypeOptionCellOperation>,
}
#[async_trait::async_trait]
impl GroupOperationInterceptor for URLGroupOperationInterceptorImpl {
type GroupTypeOption = URLTypeOption;
async fn type_option_from_group_changeset(
&self,
_changeset: &GroupChangeset,
_type_option: &Self::GroupTypeOption,
_view_id: &str,
) -> Option<TypeOptionData> {
todo!()
}
}

View File

@ -20,7 +20,13 @@ pub struct GroupSettingChangeset {
} }
pub struct GroupChangesets { pub struct GroupChangesets {
pub update_groups: Vec<GroupChangeset>, pub changesets: Vec<GroupChangeset>,
}
impl From<Vec<GroupChangeset>> for GroupChangesets {
fn from(changesets: Vec<GroupChangeset>) -> Self {
Self { changesets }
}
} }
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]

View File

@ -1,19 +1,82 @@
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait;
use collab_database::fields::Field; use collab_database::fields::Field;
use collab_database::rows::RowDetail; use collab_database::rows::{Cell, RowDetail, RowId};
use collab_database::views::DatabaseLayout; use collab_database::views::DatabaseLayout;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use crate::entities::FieldType; use crate::entities::FieldType;
use crate::services::field::TypeOption;
use crate::services::group::{ use crate::services::group::{
CheckboxGroupContext, CheckboxGroupController, DateGroupContext, DateGroupController, CheckboxGroupContext, CheckboxGroupController, CheckboxGroupOperationInterceptorImpl,
DefaultGroupController, Group, GroupController, GroupSetting, GroupSettingReader, DateGroupContext, DateGroupController, DateGroupOperationInterceptorImpl, DefaultGroupController,
GroupSettingWriter, MultiSelectGroupController, MultiSelectOptionGroupContext, Group, GroupController, GroupSetting, GroupSettingReader, GroupSettingWriter,
SingleSelectGroupController, SingleSelectOptionGroupContext, URLGroupContext, URLGroupController, GroupTypeOptionCellOperation, MultiSelectGroupController,
MultiSelectGroupOperationInterceptorImpl, MultiSelectOptionGroupContext,
SingleSelectGroupController, SingleSelectGroupOperationInterceptorImpl,
SingleSelectOptionGroupContext, URLGroupContext, URLGroupController,
URLGroupOperationInterceptorImpl,
}; };
/// The [GroupsBuilder] trait is used to generate the groups for different [FieldType]
#[async_trait]
pub trait GroupsBuilder: Send + Sync + 'static {
type Context;
type GroupTypeOption: TypeOption;
async fn build(
field: &Field,
context: &Self::Context,
type_option: &Self::GroupTypeOption,
) -> GeneratedGroups;
}
pub struct GeneratedGroups {
pub no_status_group: Option<Group>,
pub group_configs: Vec<GeneratedGroupConfig>,
}
pub struct GeneratedGroupConfig {
pub group: Group,
pub filter_content: String,
}
pub struct MoveGroupRowContext<'a> {
pub row_detail: &'a RowDetail,
pub row_changeset: &'a mut RowChangeset,
pub field: &'a Field,
pub to_group_id: &'a str,
pub to_row_id: Option<RowId>,
}
#[derive(Debug, Clone)]
pub struct RowChangeset {
pub row_id: RowId,
pub height: Option<i32>,
pub visibility: Option<bool>,
// Contains the key/value changes represents as the update of the cells. For example,
// if there is one cell was changed, then the `cell_by_field_id` will only have one key/value.
pub cell_by_field_id: HashMap<String, Cell>,
}
impl RowChangeset {
pub fn new(row_id: RowId) -> Self {
Self {
row_id,
height: None,
visibility: None,
cell_by_field_id: Default::default(),
}
}
pub fn is_empty(&self) -> bool {
self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty()
}
}
/// Returns a group controller. /// Returns a group controller.
/// ///
/// Each view can be grouped by one field, each field has its own group controller. /// Each view can be grouped by one field, each field has its own group controller.
@ -31,16 +94,18 @@ use crate::services::group::{
fields(grouping_field_id=%grouping_field.id, grouping_field_type) fields(grouping_field_id=%grouping_field.id, grouping_field_type)
err err
)] )]
pub async fn make_group_controller<R, W>( pub async fn make_group_controller<R, W, TW>(
view_id: String, view_id: String,
grouping_field: Arc<Field>, grouping_field: Arc<Field>,
row_details: Vec<Arc<RowDetail>>, row_details: Vec<Arc<RowDetail>>,
setting_reader: R, setting_reader: R,
setting_writer: W, setting_writer: W,
type_option_cell_writer: TW,
) -> FlowyResult<Box<dyn GroupController>> ) -> FlowyResult<Box<dyn GroupController>>
where where
R: GroupSettingReader, R: GroupSettingReader,
W: GroupSettingWriter, W: GroupSettingWriter,
TW: GroupTypeOptionCellOperation,
{ {
let grouping_field_type = FieldType::from(grouping_field.field_type); let grouping_field_type = FieldType::from(grouping_field.field_type);
tracing::Span::current().record("grouping_field", &grouping_field_type.default_name()); tracing::Span::current().record("grouping_field", &grouping_field_type.default_name());
@ -48,6 +113,7 @@ where
let mut group_controller: Box<dyn GroupController>; let mut group_controller: Box<dyn GroupController>;
let configuration_reader = Arc::new(setting_reader); let configuration_reader = Arc::new(setting_reader);
let configuration_writer = Arc::new(setting_writer); let configuration_writer = Arc::new(setting_writer);
let type_option_cell_writer = Arc::new(type_option_cell_writer);
match grouping_field_type { match grouping_field_type {
FieldType::SingleSelect => { FieldType::SingleSelect => {
@ -58,7 +124,10 @@ where
configuration_writer, configuration_writer,
) )
.await?; .await?;
let controller = SingleSelectGroupController::new(&grouping_field, configuration).await?; let operation_interceptor = SingleSelectGroupOperationInterceptorImpl;
let controller =
SingleSelectGroupController::new(&grouping_field, configuration, operation_interceptor)
.await?;
group_controller = Box::new(controller); group_controller = Box::new(controller);
}, },
FieldType::MultiSelect => { FieldType::MultiSelect => {
@ -69,7 +138,10 @@ where
configuration_writer, configuration_writer,
) )
.await?; .await?;
let controller = MultiSelectGroupController::new(&grouping_field, configuration).await?; let operation_interceptor = MultiSelectGroupOperationInterceptorImpl;
let controller =
MultiSelectGroupController::new(&grouping_field, configuration, operation_interceptor)
.await?;
group_controller = Box::new(controller); group_controller = Box::new(controller);
}, },
FieldType::Checkbox => { FieldType::Checkbox => {
@ -80,7 +152,9 @@ where
configuration_writer, configuration_writer,
) )
.await?; .await?;
let controller = CheckboxGroupController::new(&grouping_field, configuration).await?; let operation_interceptor = CheckboxGroupOperationInterceptorImpl {};
let controller =
CheckboxGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
group_controller = Box::new(controller); group_controller = Box::new(controller);
}, },
FieldType::URL => { FieldType::URL => {
@ -91,7 +165,11 @@ where
configuration_writer, configuration_writer,
) )
.await?; .await?;
let controller = URLGroupController::new(&grouping_field, configuration).await?; let operation_interceptor = URLGroupOperationInterceptorImpl {
cell_writer: type_option_cell_writer,
};
let controller =
URLGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
group_controller = Box::new(controller); group_controller = Box::new(controller);
}, },
FieldType::DateTime => { FieldType::DateTime => {
@ -102,7 +180,9 @@ where
configuration_writer, configuration_writer,
) )
.await?; .await?;
let controller = DateGroupController::new(&grouping_field, configuration).await?; let operation_interceptor = DateGroupOperationInterceptorImpl {};
let controller =
DateGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
group_controller = Box::new(controller); group_controller = Box::new(controller);
}, },
_ => { _ => {

View File

@ -26,7 +26,7 @@ pub trait SortDelegate: Send + Sync {
fn get_sort(&self, view_id: &str, sort_id: &str) -> Fut<Option<Arc<Sort>>>; fn get_sort(&self, view_id: &str, sort_id: &str) -> Fut<Option<Arc<Sort>>>;
/// Returns all the rows after applying grid's filter /// Returns all the rows after applying grid's filter
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>; fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
fn get_field(&self, field_id: &str) -> Fut<Option<Arc<Field>>>; fn get_field(&self, field_id: &str) -> Option<Field>;
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>; fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
} }

View File

@ -10,7 +10,6 @@ use flowy_database2::services::field::{
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction, edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
SingleSelectTypeOption, SingleSelectTypeOption,
}; };
use flowy_database2::services::group::GroupSettingChangeset;
use lib_infra::util::timestamp; use lib_infra::util::timestamp;
use crate::database::database_editor::DatabaseEditorTest; use crate::database::database_editor::DatabaseEditorTest;
@ -68,12 +67,6 @@ pub enum GroupScript {
group_id: String, group_id: String,
group_name: String, group_name: String,
}, },
AssertGroupConfiguration {
hide_ungrouped: bool,
},
UpdateGroupConfiguration {
hide_ungrouped: Option<bool>,
},
} }
pub struct DatabaseGroupTest { pub struct DatabaseGroupTest {
@ -276,25 +269,6 @@ impl DatabaseGroupTest {
assert_eq!(group_id, group.group_id, "group index: {}", group_index); assert_eq!(group_id, group.group_id, "group index: {}", group_index);
assert_eq!(group_name, group.group_name, "group index: {}", group_index); assert_eq!(group_name, group.group_name, "group index: {}", group_index);
}, },
GroupScript::AssertGroupConfiguration { hide_ungrouped } => {
let group_configuration = self
.editor
.get_group_configuration_settings(&self.view_id)
.await
.unwrap();
let group_configuration = group_configuration.get(0).unwrap();
assert_eq!(group_configuration.hide_ungrouped, hide_ungrouped);
},
GroupScript::UpdateGroupConfiguration { hide_ungrouped } => {
self
.editor
.update_group_configuration_setting(
&self.view_id,
GroupSettingChangeset { hide_ungrouped },
)
.await
.unwrap();
},
} }
} }

View File

@ -12,6 +12,7 @@ bytes = "1.4"
anyhow = "1.0" anyhow = "1.0"
thiserror = "1.0" thiserror = "1.0"
validator = "0.16.0" validator = "0.16.0"
tokio = { version = "1.0", features = ["sync"]}
fancy-regex = { version = "0.11.0" } fancy-regex = { version = "0.11.0" }
lib-dispatch = { workspace = true, optional = true } lib-dispatch = { workspace = true, optional = true }

View File

@ -157,3 +157,9 @@ impl From<fancy_regex::Error> for FlowyError {
FlowyError::internal().with_context(e) FlowyError::internal().with_context(e)
} }
} }
impl From<tokio::sync::oneshot::error::RecvError> for FlowyError {
fn from(e: tokio::sync::oneshot::error::RecvError) -> Self {
FlowyError::internal().with_context(e)
}
}