mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: delete kanban board groups (#3925)
* feat: hide/unhide ui * chore: implement collapsible side bar and adjust group header (#2) * refactor: hidden columns into own file * chore: adjust new group button position * fix: flowy icon buton secondary color bleed * chore: some UI adjustments * fix: some regressions * chore: proper group is_visible fetching * chore: use a bloc to manage hidden groups * fix: hiding groups not working * chore: implement hidden group popups * chore: proper ungrouped item column management * chore: remove ungrouped items button * chore: flowy hover build * fix: clean up code * test: integration tests * fix: not null promise on null value * fix: hide and unhide multiple groups * chore: i18n and code review * chore: missed review * fix: rust-lib-test * fix: dont completely remove flowyiconhovercolor * chore: apply suggest * fix: number of rows inside hidden groups not updating properly * fix: hidden groups disappearing after collapse * fix: hidden group title alignment * fix: insert newly unhidden groups into the correct position * chore: adjust padding all around * feat: reorder hidden groups * chore: adjust padding * chore: collapse hidden groups section persist * chore: no status group at beginning * fix: hiding groups when grouping with other types * chore: disable rename groups that arent supported * chore: update appflowy board ref * chore: better naming * feat: delete kanban groups * chore: forgot to save * chore: fix build and small ui adjustments * chore: add a confirm dialog when deleting a column * fix: flutter lint * test: add integration test * chore: fix some design review issues * chore: apply suggestions from Nathan * fix: write lock on group controller --------- Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
This commit is contained in:
@ -4,7 +4,7 @@ use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{FieldType, RowMetaPB};
|
||||
use crate::entities::RowMetaPB;
|
||||
use crate::services::group::{GroupChangeset, GroupData, GroupSetting};
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
@ -130,13 +130,6 @@ pub struct GroupByFieldParams {
|
||||
pub view_id: String,
|
||||
}
|
||||
|
||||
pub struct DeleteGroupParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub group_id: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct UpdateGroupPB {
|
||||
#[pb(index = 1)]
|
||||
@ -230,3 +223,32 @@ impl TryFrom<CreateGroupPayloadPB> for CreateGroupParams {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct DeleteGroupPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
pub struct DeleteGroupParams {
|
||||
pub view_id: String,
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<DeleteGroupPayloadPB> for DeleteGroupParams {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: DeleteGroupPayloadPB) -> Result<Self, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(value.view_id)
|
||||
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
||||
.0;
|
||||
let group_id = NotEmptyStr::parse(value.group_id)
|
||||
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
|
||||
.0;
|
||||
|
||||
Ok(Self { view_id, group_id })
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,10 @@ impl RowsChangePB {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.deleted_rows.is_empty() && self.inserted_rows.is_empty() && self.updated_rows.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
|
@ -755,6 +755,18 @@ pub(crate) async fn create_group_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn delete_group_handler(
|
||||
data: AFPluginData<DeleteGroupPayloadPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> FlowyResult<()> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: DeleteGroupParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
database_editor.delete_group(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(manager), err)]
|
||||
pub(crate) async fn get_databases_handler(
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
|
@ -61,6 +61,7 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::GetGroup, get_group_handler)
|
||||
.event(DatabaseEvent::UpdateGroup, update_group_handler)
|
||||
.event(DatabaseEvent::CreateGroup, create_group_handler)
|
||||
.event(DatabaseEvent::DeleteGroup, delete_group_handler)
|
||||
// Database
|
||||
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
||||
// Calendar
|
||||
@ -288,6 +289,9 @@ pub enum DatabaseEvent {
|
||||
#[event(input = "CreateGroupPayloadPB")]
|
||||
CreateGroup = 114,
|
||||
|
||||
#[event(input = "DeleteGroupPayloadPB")]
|
||||
DeleteGroup = 115,
|
||||
|
||||
/// Returns all the databases
|
||||
#[event(output = "RepeatedDatabaseDescriptionPB")]
|
||||
GetDatabases = 120,
|
||||
|
@ -174,12 +174,16 @@ impl DatabaseEditor {
|
||||
}
|
||||
|
||||
pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
self
|
||||
.database
|
||||
.lock()
|
||||
.delete_group_setting(¶ms.view_id, ¶ms.group_id);
|
||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||
view_editor.v_delete_group(params).await?;
|
||||
let changes = view_editor.v_delete_group(¶ms.group_id).await?;
|
||||
|
||||
if !changes.is_empty() {
|
||||
for view in self.database_views.editors().await {
|
||||
send_notification(&view.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||
.payload(changes.clone())
|
||||
.send();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -819,7 +823,7 @@ impl DatabaseEditor {
|
||||
};
|
||||
|
||||
for option in options {
|
||||
type_option.delete_option(option.into());
|
||||
type_option.delete_option(&option.id);
|
||||
}
|
||||
self
|
||||
.database
|
||||
@ -1317,6 +1321,10 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_row(&self, row_id: &RowId) -> Option<Row> {
|
||||
self.database.lock().remove_row(row_id)
|
||||
}
|
||||
|
||||
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>> {
|
||||
let cells = self.database.lock().get_cells_for_field(view_id, field_id);
|
||||
to_fut(async move { cells.into_iter().map(Arc::new).collect() })
|
||||
|
@ -14,8 +14,8 @@ use lib_dispatch::prelude::af_spawn;
|
||||
|
||||
use crate::entities::{
|
||||
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
|
||||
DeleteGroupParams, DeleteSortParams, FieldType, FieldVisibility, GroupChangesPB, GroupPB,
|
||||
InsertedRowPB, LayoutSettingChangeset, LayoutSettingParams, RowMetaPB, RowsChangePB,
|
||||
DeleteSortParams, FieldType, FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB,
|
||||
LayoutSettingChangeset, LayoutSettingParams, RowMetaPB, RowsChangePB,
|
||||
SortChangesetNotificationPB, SortPB, UpdateFilterParams, UpdateSortParams,
|
||||
};
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
@ -391,8 +391,43 @@ impl DatabaseViewEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn v_delete_group(&self, _params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
pub async fn v_delete_group(&self, group_id: &str) -> FlowyResult<RowsChangePB> {
|
||||
let mut group_controller = self.group_controller.write().await;
|
||||
let controller = match group_controller.as_mut() {
|
||||
Some(controller) => controller,
|
||||
None => return Ok(RowsChangePB::default()),
|
||||
};
|
||||
|
||||
let old_field = self.delegate.get_field(controller.field_id());
|
||||
let (row_ids, type_option_data) = controller.delete_group(group_id)?;
|
||||
|
||||
drop(group_controller);
|
||||
|
||||
let mut changes = RowsChangePB::default();
|
||||
|
||||
if let Some(field) = old_field {
|
||||
let deleted_rows = row_ids
|
||||
.iter()
|
||||
.filter_map(|row_id| self.delegate.remove_row(row_id))
|
||||
.map(|row| row.id.into_inner());
|
||||
|
||||
changes.deleted_rows.extend(deleted_rows);
|
||||
|
||||
if let Some(type_option) = type_option_data {
|
||||
self
|
||||
.delegate
|
||||
.update_field(&self.view_id, type_option, field)
|
||||
.await?;
|
||||
}
|
||||
let notification = GroupChangesPB {
|
||||
view_id: self.view_id.clone(),
|
||||
deleted_groups: vec![group_id.to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
notify_did_update_num_of_groups(&self.view_id, notification).await;
|
||||
}
|
||||
|
||||
Ok(changes)
|
||||
}
|
||||
|
||||
pub async fn v_update_group(&self, changeset: GroupChangesets) -> FlowyResult<()> {
|
||||
|
@ -3,7 +3,7 @@ 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::rows::{Row, RowCell, RowDetail, RowId};
|
||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@ -57,6 +57,8 @@ pub trait DatabaseViewOperation: Send + Sync + 'static {
|
||||
/// Returns all the rows in the view
|
||||
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
||||
|
||||
fn remove_row(&self, row_id: &RowId) -> Option<Row>;
|
||||
|
||||
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>>;
|
||||
|
@ -49,12 +49,9 @@ pub trait SelectTypeOptionSharedAction: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_option(&mut self, delete_option: SelectOption) {
|
||||
fn delete_option(&mut self, option_id: &str) {
|
||||
let options = self.mut_options();
|
||||
if let Some(index) = options
|
||||
.iter()
|
||||
.position(|option| option.id == delete_option.id)
|
||||
{
|
||||
if let Some(index) = options.iter().position(|option| option.id == option_id) {
|
||||
options.remove(index);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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, RowId};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
@ -78,6 +78,8 @@ pub trait GroupCustomize: Send + Sync {
|
||||
) -> FlowyResult<(Option<TypeOptionData>, Option<InsertedGroupPB>)> {
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>>;
|
||||
}
|
||||
|
||||
/// Defines the shared actions any group controller can perform.
|
||||
@ -159,6 +161,14 @@ pub trait GroupControllerOperation: Send + Sync {
|
||||
/// * `field`: new changeset
|
||||
fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>;
|
||||
|
||||
/// Delete a group from the group configuration.
|
||||
///
|
||||
/// Return a list of deleted row ids and/or a new `TypeOptionData` if
|
||||
/// successful.
|
||||
///
|
||||
/// * `group_id`: the id of the group to be deleted
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<(Vec<RowId>, Option<TypeOptionData>)>;
|
||||
|
||||
/// Updates the name and/or visibility of groups.
|
||||
///
|
||||
/// Returns a non-empty `TypeOptionData` when the changes require a change
|
||||
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
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, RowId};
|
||||
use futures::executor::block_on;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
@ -396,6 +396,27 @@ where
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<(Vec<RowId>, Option<TypeOptionData>)> {
|
||||
let group = if group_id != self.field_id() {
|
||||
self.get_group(group_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match group {
|
||||
Some((_index, group_data)) => {
|
||||
let row_ids = group_data
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| row.row.id.clone())
|
||||
.collect();
|
||||
let type_option_data = self.delete_group_custom(group_id)?;
|
||||
Ok((row_ids, type_option_data))
|
||||
},
|
||||
None => Ok((vec![], None)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_group_changeset(
|
||||
&mut self,
|
||||
changeset: &GroupChangesets,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
use flowy_error::FlowyResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::entities::{FieldType, GroupPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
|
||||
@ -138,6 +139,10 @@ impl GroupCustomize for CheckboxGroupController {
|
||||
});
|
||||
group_changeset
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, _group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for CheckboxGroupController {
|
||||
|
@ -7,7 +7,7 @@ use chrono::{
|
||||
};
|
||||
use chrono_tz::Tz;
|
||||
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 serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
@ -248,6 +248,11 @@ impl GroupCustomize for DateGroupController {
|
||||
}
|
||||
deleted_group
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
self.context.delete_group(group_id)?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for DateGroupController {
|
||||
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
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, RowId};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
@ -129,6 +129,10 @@ impl GroupControllerOperation for DefaultGroupController {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn delete_group(&mut self, _group_id: &str) -> FlowyResult<(Vec<RowId>, Option<TypeOptionData>)> {
|
||||
Ok((vec![], None))
|
||||
}
|
||||
|
||||
async fn apply_group_changeset(
|
||||
&mut self,
|
||||
_changeset: &GroupChangesets,
|
||||
|
@ -107,6 +107,22 @@ impl GroupCustomize for MultiSelectGroupController {
|
||||
|
||||
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
if let Some(option_index) = self
|
||||
.type_option
|
||||
.options
|
||||
.iter()
|
||||
.position(|option| option.id == group_id)
|
||||
{
|
||||
// Remove the option if the group is found
|
||||
let mut new_type_option = self.type_option.clone();
|
||||
new_type_option.options.remove(option_index);
|
||||
Ok(Some(new_type_option.into()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for MultiSelectGroupController {
|
||||
|
@ -111,6 +111,23 @@ impl GroupCustomize for SingleSelectGroupController {
|
||||
|
||||
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
if let Some(option_index) = self
|
||||
.type_option
|
||||
.options
|
||||
.iter()
|
||||
.position(|option| option.id == group_id)
|
||||
{
|
||||
// Remove the option if the group is found
|
||||
let mut new_type_option = self.type_option.clone();
|
||||
new_type_option.options.remove(option_index);
|
||||
Ok(Some(new_type_option.into()))
|
||||
} else {
|
||||
// Return None if no matching group is found
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for SingleSelectGroupController {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -186,6 +186,11 @@ impl GroupCustomize for URLGroupController {
|
||||
}
|
||||
deleted_group
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
self.context.delete_group(group_id)?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for URLGroupController {
|
||||
|
Reference in New Issue
Block a user