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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 147 additions and 7 deletions

View File

@ -62,6 +62,19 @@ class FieldBackendService {
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
static Future<FlowyResult<void, FlowyError>> duplicateField({
required String viewId,

View File

@ -104,6 +104,8 @@ class _FieldEditorState extends State<FieldEditor> {
VSpace(GridSize.typeOptionSeparatorHeight),
_actionCell(FieldAction.duplicate),
VSpace(GridSize.typeOptionSeparatorHeight),
_actionCell(FieldAction.clearData),
VSpace(GridSize.typeOptionSeparatorHeight),
_actionCell(FieldAction.delete),
],
).padding(all: 8.0),
@ -195,6 +197,7 @@ enum FieldAction {
insertRight,
toggleVisibility,
duplicate,
clearData,
delete;
Widget icon(FieldInfo fieldInfo, Color? color) {
@ -213,6 +216,8 @@ enum FieldAction {
}
case FieldAction.duplicate:
svgData = FlowySvgs.copy_s;
case FieldAction.clearData:
svgData = FlowySvgs.reload_s;
case FieldAction.delete:
svgData = FlowySvgs.delete_s;
}
@ -241,6 +246,8 @@ enum FieldAction {
}
case FieldAction.duplicate:
return LocaleKeys.grid_field_duplicate.tr();
case FieldAction.clearData:
return LocaleKeys.grid_field_clear.tr();
case FieldAction.delete:
return LocaleKeys.grid_field_delete.tr();
}
@ -273,6 +280,22 @@ enum FieldAction {
fieldId: fieldInfo.id,
);
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:
NavigatorAlertDialog(
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/startup/tasks/app_widget.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/dialog/styled_dialogs.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
@ -114,12 +115,14 @@ class NavigatorAlertDialog extends StatefulWidget {
this.cancel,
this.confirm,
this.hideCancelButton = false,
this.constraints,
});
final String title;
final void Function()? cancel;
final void Function()? confirm;
final bool hideCancelButton;
final BoxConstraints? constraints;
@override
State<NavigatorAlertDialog> createState() => _CreateFlowyAlertDialog();
@ -140,10 +143,11 @@ class _CreateFlowyAlertDialog extends State<NavigatorAlertDialog> {
children: <Widget>[
...[
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400,
maxHeight: 260,
),
constraints: widget.constraints ??
const BoxConstraints(
maxWidth: 400,
maxHeight: 260,
),
child: FlowyText.medium(
widget.title,
fontSize: FontSizes.s16,

View File

@ -620,6 +620,7 @@
"insertRight": "Insert Right",
"duplicate": "Duplicate",
"delete": "Delete",
"clear": "Clear cells",
"textFieldName": "Text",
"checkboxFieldName": "Checkbox",
"dateFieldName": "Date",
@ -660,6 +661,7 @@
"editProperty": "Edit property",
"newProperty": "New property",
"deleteFieldPromptMessage": "Are you sure? This property will be deleted",
"clearFieldPromptMessage": "Are you sure? All cells in this column will be emptied",
"newColumn": "New Column",
"format": "Format",
"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_error::ErrorCode;
use validator::Validate;
use crate::entities::parser::NotEmptyStr;
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)]
pub struct DeleteFieldPayloadPB {
#[pb(index = 1)]

View File

@ -257,6 +257,20 @@ pub(crate) async fn delete_field_handler(
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)]
pub(crate) async fn switch_to_field_handler(
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::UpdateFieldTypeOption, update_field_type_option_handler)
.event(DatabaseEvent::DeleteField, delete_field_handler)
.event(DatabaseEvent::ClearField, clear_field_handler)
.event(DatabaseEvent::UpdateFieldType, switch_to_field_handler)
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
.event(DatabaseEvent::MoveField, move_field_handler)
@ -161,6 +162,11 @@ pub enum DatabaseEvent {
#[event(input = "DeleteFieldPayloadPB")]
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.
/// 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.

View File

@ -361,6 +361,30 @@ impl DatabaseEditor {
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.
/// Do nothing if the [TypeOptionData] is empty.
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);
if let Some(new_row_detail) = option_row {
for view in self.database_views.editors().await {
@ -821,8 +876,6 @@ impl DatabaseEditor {
self
.notify_update_row(view_id, row_id, vec![changeset])
.await;
Ok(())
}
pub fn get_auto_updated_fields_changesets(