mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: clear all cells (#4856)
* feat: clear all cells * fix: smaller dialog width * fix: clippy warning
This commit is contained in:
parent
c1006c18c3
commit
e2e38f72bb
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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)]
|
||||
|
@ -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(¶ms.view_id).await?;
|
||||
database_editor
|
||||
.clear_field(¶ms.view_id, ¶ms.field_id)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn switch_to_field_handler(
|
||||
data: AFPluginData<UpdateFieldTypePayloadPB>,
|
||||
|
@ -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.
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user