feat: clear all cells (#4856)

* feat: clear all cells

* fix: smaller dialog width

* fix: clippy warning
This commit is contained in:
Mathias Mogensen
2024-03-21 17:40:23 +01:00
committed by GitHub
parent c1006c18c3
commit e2e38f72bb
8 changed files with 147 additions and 7 deletions

View File

@ -62,6 +62,19 @@ class FieldBackendService {
return DatabaseEventDeleteField(payload).send(); return DatabaseEventDeleteField(payload).send();
} }
// Clear all data of all cells in a Field
static Future<FlowyResult<void, FlowyError>> clearField({
required String viewId,
required String fieldId,
}) {
final payload = ClearFieldPayloadPB(
viewId: viewId,
fieldId: fieldId,
);
return DatabaseEventClearField(payload).send();
}
/// Duplicate a field /// Duplicate a field
static Future<FlowyResult<void, FlowyError>> duplicateField({ static Future<FlowyResult<void, FlowyError>> duplicateField({
required String viewId, required String viewId,

View File

@ -104,6 +104,8 @@ class _FieldEditorState extends State<FieldEditor> {
VSpace(GridSize.typeOptionSeparatorHeight), VSpace(GridSize.typeOptionSeparatorHeight),
_actionCell(FieldAction.duplicate), _actionCell(FieldAction.duplicate),
VSpace(GridSize.typeOptionSeparatorHeight), VSpace(GridSize.typeOptionSeparatorHeight),
_actionCell(FieldAction.clearData),
VSpace(GridSize.typeOptionSeparatorHeight),
_actionCell(FieldAction.delete), _actionCell(FieldAction.delete),
], ],
).padding(all: 8.0), ).padding(all: 8.0),
@ -195,6 +197,7 @@ enum FieldAction {
insertRight, insertRight,
toggleVisibility, toggleVisibility,
duplicate, duplicate,
clearData,
delete; delete;
Widget icon(FieldInfo fieldInfo, Color? color) { Widget icon(FieldInfo fieldInfo, Color? color) {
@ -213,6 +216,8 @@ enum FieldAction {
} }
case FieldAction.duplicate: case FieldAction.duplicate:
svgData = FlowySvgs.copy_s; svgData = FlowySvgs.copy_s;
case FieldAction.clearData:
svgData = FlowySvgs.reload_s;
case FieldAction.delete: case FieldAction.delete:
svgData = FlowySvgs.delete_s; svgData = FlowySvgs.delete_s;
} }
@ -241,6 +246,8 @@ enum FieldAction {
} }
case FieldAction.duplicate: case FieldAction.duplicate:
return LocaleKeys.grid_field_duplicate.tr(); return LocaleKeys.grid_field_duplicate.tr();
case FieldAction.clearData:
return LocaleKeys.grid_field_clear.tr();
case FieldAction.delete: case FieldAction.delete:
return LocaleKeys.grid_field_delete.tr(); return LocaleKeys.grid_field_delete.tr();
} }
@ -273,6 +280,22 @@ enum FieldAction {
fieldId: fieldInfo.id, fieldId: fieldInfo.id,
); );
break; break;
case FieldAction.clearData:
NavigatorAlertDialog(
constraints: const BoxConstraints(
maxWidth: 250,
maxHeight: 260,
),
title: LocaleKeys.grid_field_clearFieldPromptMessage.tr(),
confirm: () {
FieldBackendService.clearField(
viewId: viewId,
fieldId: fieldInfo.id,
);
},
).show(context);
PopoverContainer.of(context).close();
break;
case FieldAction.delete: case FieldAction.delete:
NavigatorAlertDialog( NavigatorAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/startup/tasks/app_widget.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -8,7 +10,6 @@ import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
@ -114,12 +115,14 @@ class NavigatorAlertDialog extends StatefulWidget {
this.cancel, this.cancel,
this.confirm, this.confirm,
this.hideCancelButton = false, this.hideCancelButton = false,
this.constraints,
}); });
final String title; final String title;
final void Function()? cancel; final void Function()? cancel;
final void Function()? confirm; final void Function()? confirm;
final bool hideCancelButton; final bool hideCancelButton;
final BoxConstraints? constraints;
@override @override
State<NavigatorAlertDialog> createState() => _CreateFlowyAlertDialog(); State<NavigatorAlertDialog> createState() => _CreateFlowyAlertDialog();
@ -140,10 +143,11 @@ class _CreateFlowyAlertDialog extends State<NavigatorAlertDialog> {
children: <Widget>[ children: <Widget>[
...[ ...[
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints( constraints: widget.constraints ??
maxWidth: 400, const BoxConstraints(
maxHeight: 260, maxWidth: 400,
), maxHeight: 260,
),
child: FlowyText.medium( child: FlowyText.medium(
widget.title, widget.title,
fontSize: FontSizes.s16, fontSize: FontSizes.s16,

View File

@ -620,6 +620,7 @@
"insertRight": "Insert Right", "insertRight": "Insert Right",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"delete": "Delete", "delete": "Delete",
"clear": "Clear cells",
"textFieldName": "Text", "textFieldName": "Text",
"checkboxFieldName": "Checkbox", "checkboxFieldName": "Checkbox",
"dateFieldName": "Date", "dateFieldName": "Date",
@ -660,6 +661,7 @@
"editProperty": "Edit property", "editProperty": "Edit property",
"newProperty": "New property", "newProperty": "New property",
"deleteFieldPromptMessage": "Are you sure? This property will be deleted", "deleteFieldPromptMessage": "Are you sure? This property will be deleted",
"clearFieldPromptMessage": "Are you sure? All cells in this column will be emptied",
"newColumn": "New Column", "newColumn": "New Column",
"format": "Format", "format": "Format",
"reminderOnDateTooltip": "This cell has a scheduled reminder", "reminderOnDateTooltip": "This cell has a scheduled reminder",

View File

@ -10,6 +10,7 @@ use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use validator::Validate;
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::position_entities::OrderObjectPositionPB; use crate::entities::position_entities::OrderObjectPositionPB;
@ -620,6 +621,30 @@ impl TryInto<FieldIdParams> for DuplicateFieldPayloadPB {
} }
} }
#[derive(Debug, Clone, Default, ProtoBuf, Validate)]
pub struct ClearFieldPayloadPB {
#[pb(index = 1)]
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
pub field_id: String,
#[pb(index = 2)]
#[validate(custom = "lib_infra::validator_fn::required_not_empty_str")]
pub view_id: String,
}
impl TryInto<FieldIdParams> for ClearFieldPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<FieldIdParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
Ok(FieldIdParams {
view_id: view_id.0,
field_id: field_id.0,
})
}
}
#[derive(Debug, Clone, Default, ProtoBuf)] #[derive(Debug, Clone, Default, ProtoBuf)]
pub struct DeleteFieldPayloadPB { pub struct DeleteFieldPayloadPB {
#[pb(index = 1)] #[pb(index = 1)]

View File

@ -257,6 +257,20 @@ pub(crate) async fn delete_field_handler(
Ok(()) Ok(())
} }
#[tracing::instrument(level = "trace", skip(data, manager), err)]
pub(crate) async fn clear_field_handler(
data: AFPluginData<ClearFieldPayloadPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?;
let params: FieldIdParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor
.clear_field(&params.view_id, &params.field_id)
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn switch_to_field_handler( pub(crate) async fn switch_to_field_handler(
data: AFPluginData<UpdateFieldTypePayloadPB>, data: AFPluginData<UpdateFieldTypePayloadPB>,

View File

@ -27,6 +27,7 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::UpdateField, update_field_handler) .event(DatabaseEvent::UpdateField, update_field_handler)
.event(DatabaseEvent::UpdateFieldTypeOption, update_field_type_option_handler) .event(DatabaseEvent::UpdateFieldTypeOption, update_field_type_option_handler)
.event(DatabaseEvent::DeleteField, delete_field_handler) .event(DatabaseEvent::DeleteField, delete_field_handler)
.event(DatabaseEvent::ClearField, clear_field_handler)
.event(DatabaseEvent::UpdateFieldType, switch_to_field_handler) .event(DatabaseEvent::UpdateFieldType, switch_to_field_handler)
.event(DatabaseEvent::DuplicateField, duplicate_field_handler) .event(DatabaseEvent::DuplicateField, duplicate_field_handler)
.event(DatabaseEvent::MoveField, move_field_handler) .event(DatabaseEvent::MoveField, move_field_handler)
@ -161,6 +162,11 @@ pub enum DatabaseEvent {
#[event(input = "DeleteFieldPayloadPB")] #[event(input = "DeleteFieldPayloadPB")]
DeleteField = 14, DeleteField = 14,
/// [ClearField] event is used to clear all Cells in a Field. [ClearFieldPayloadPB] is the context that
/// is used to clear the field from the Database.
#[event(input = "ClearFieldPayloadPB")]
ClearField = 15,
/// [UpdateFieldType] event is used to update the current Field's type. /// [UpdateFieldType] event is used to update the current Field's type.
/// It will insert a new FieldTypeOptionData if the new FieldType doesn't exist before, otherwise /// It will insert a new FieldTypeOptionData if the new FieldType doesn't exist before, otherwise
/// reuse the existing FieldTypeOptionData. You could check the [DatabaseRevisionPad] for more details. /// reuse the existing FieldTypeOptionData. You could check the [DatabaseRevisionPad] for more details.

View File

@ -361,6 +361,30 @@ impl DatabaseEditor {
Ok(()) Ok(())
} }
pub async fn clear_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> {
let field_type: FieldType = self
.get_field(field_id)
.map(|field| field.field_type.into())
.unwrap_or_default();
if matches!(
field_type,
FieldType::LastEditedTime | FieldType::CreatedTime
) {
return Err(FlowyError::new(
ErrorCode::Internal,
"Can not clear the field type of Last Edited Time or Created Time.",
));
}
let cells: Vec<RowCell> = self.get_cells_for_field(view_id, field_id).await;
for row_cell in cells {
self.clear_cell(view_id, row_cell.row_id, field_id).await?;
}
Ok(())
}
/// Update the field type option data. /// Update the field type option data.
/// Do nothing if the [TypeOptionData] is empty. /// Do nothing if the [TypeOptionData] is empty.
pub async fn update_field_type_option( pub async fn update_field_type_option(
@ -804,6 +828,37 @@ impl DatabaseEditor {
}); });
}); });
self
.did_update_row(view_id, row_id, field_id, old_row)
.await;
Ok(())
}
pub async fn clear_cell(&self, view_id: &str, row_id: RowId, field_id: &str) -> FlowyResult<()> {
// Get the old row before updating the cell. It would be better to get the old cell
let old_row = { self.get_row_detail(view_id, &row_id) };
self.database.lock().update_row(&row_id, |row_update| {
row_update.update_cells(|cell_update| {
cell_update.clear(field_id);
});
});
self
.did_update_row(view_id, row_id, field_id, old_row)
.await;
Ok(())
}
async fn did_update_row(
&self,
view_id: &str,
row_id: RowId,
field_id: &str,
old_row: Option<RowDetail>,
) {
let option_row = self.get_row_detail(view_id, &row_id); let option_row = self.get_row_detail(view_id, &row_id);
if let Some(new_row_detail) = option_row { if let Some(new_row_detail) = option_row {
for view in self.database_views.editors().await { for view in self.database_views.editors().await {
@ -821,8 +876,6 @@ impl DatabaseEditor {
self self
.notify_update_row(view_id, row_id, vec![changeset]) .notify_update_row(view_id, row_id, vec![changeset])
.await; .await;
Ok(())
} }
pub fn get_auto_updated_fields_changesets( pub fn get_auto_updated_fields_changesets(