mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: enable group condition (#5248)
* feat: enable group condition * style: added i18n for date field group conditions * fix: flutter analyze * fix: test use i18n * fix: more localization --------- Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
This commit is contained in:
parent
f1a4deb459
commit
4f4be7eac7
@ -137,6 +137,8 @@ class FieldController {
|
||||
List<FieldInfo> get fieldInfos => [..._fieldNotifier.fieldInfos];
|
||||
List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
|
||||
List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []];
|
||||
List<GroupSettingPB> get groupSettings =>
|
||||
_groupConfigurationByFieldId.entries.map((e) => e.value).toList();
|
||||
|
||||
FieldInfo? getField(String fieldId) {
|
||||
return _fieldNotifier.fieldInfos
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'field_info.freezed.dart';
|
||||
|
||||
@ -89,4 +90,13 @@ class FieldInfo with _$FieldInfo {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<ProtobufEnum> get groupConditions {
|
||||
switch (field.fieldType) {
|
||||
case FieldType.DateTime:
|
||||
return DateConditionPB.values;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database/domain/group_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@ -22,6 +23,7 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
|
||||
viewId,
|
||||
databaseController.fieldController.fieldInfos,
|
||||
databaseController.databaseLayoutSetting!.board,
|
||||
databaseController.fieldController.groupSettings,
|
||||
),
|
||||
) {
|
||||
_dispatch();
|
||||
@ -51,11 +53,22 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveFieldUpdate: (fieldInfos) {
|
||||
emit(state.copyWith(fieldInfos: fieldInfos));
|
||||
emit(
|
||||
state.copyWith(
|
||||
fieldInfos: fieldInfos,
|
||||
groupSettings:
|
||||
_databaseController.fieldController.groupSettings,
|
||||
),
|
||||
);
|
||||
},
|
||||
setGroupByField: (String fieldId, FieldType fieldType) async {
|
||||
setGroupByField: (
|
||||
String fieldId,
|
||||
FieldType fieldType, [
|
||||
List<int>? settingContent,
|
||||
]) async {
|
||||
final result = await _groupBackendSvc.groupByField(
|
||||
fieldId: fieldId,
|
||||
settingContent: settingContent ?? [],
|
||||
);
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
},
|
||||
@ -96,8 +109,9 @@ class DatabaseGroupEvent with _$DatabaseGroupEvent {
|
||||
const factory DatabaseGroupEvent.initial() = _Initial;
|
||||
const factory DatabaseGroupEvent.setGroupByField(
|
||||
String fieldId,
|
||||
FieldType fieldType,
|
||||
) = _DatabaseGroupEvent;
|
||||
FieldType fieldType, [
|
||||
@Default([]) List<int> settingContent,
|
||||
]) = _DatabaseGroupEvent;
|
||||
const factory DatabaseGroupEvent.didReceiveFieldUpdate(
|
||||
List<FieldInfo> fields,
|
||||
) = _DidReceiveFieldUpdate;
|
||||
@ -112,16 +126,19 @@ class DatabaseGroupState with _$DatabaseGroupState {
|
||||
required String viewId,
|
||||
required List<FieldInfo> fieldInfos,
|
||||
required BoardLayoutSettingPB layoutSettings,
|
||||
required List<GroupSettingPB> groupSettings,
|
||||
}) = _DatabaseGroupState;
|
||||
|
||||
factory DatabaseGroupState.initial(
|
||||
String viewId,
|
||||
List<FieldInfo> fieldInfos,
|
||||
BoardLayoutSettingPB layoutSettings,
|
||||
List<GroupSettingPB> groupSettings,
|
||||
) =>
|
||||
DatabaseGroupState(
|
||||
viewId: viewId,
|
||||
fieldInfos: fieldInfos,
|
||||
layoutSettings: layoutSettings,
|
||||
groupSettings: groupSettings,
|
||||
);
|
||||
}
|
||||
|
@ -16,8 +16,10 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
|
||||
import '../../application/database_controller.dart';
|
||||
import '../../application/field/field_controller.dart';
|
||||
@ -527,6 +529,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
return "No ${field.name}";
|
||||
}
|
||||
|
||||
final groupSettings = databaseController.fieldController.groupSettings
|
||||
.firstWhereOrNull((gs) => gs.fieldId == field.id);
|
||||
|
||||
switch (field.fieldType) {
|
||||
case FieldType.SingleSelect:
|
||||
final options =
|
||||
@ -547,33 +552,61 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
case FieldType.URL:
|
||||
return group.groupId;
|
||||
case FieldType.DateTime:
|
||||
// Assume DateCondition::Relative as there isn't an option for this
|
||||
// right now.
|
||||
final config = groupSettings?.content != null
|
||||
? DateGroupConfigurationPB.fromBuffer(groupSettings!.content)
|
||||
: DateGroupConfigurationPB();
|
||||
final dateFormat = DateFormat("y/MM/dd");
|
||||
try {
|
||||
final targetDateTime = dateFormat.parseLoose(group.groupId);
|
||||
final targetDateTimeDay = DateTime(
|
||||
targetDateTime.year,
|
||||
targetDateTime.month,
|
||||
targetDateTime.day,
|
||||
);
|
||||
final now = DateTime.now();
|
||||
final nowDay = DateTime(
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
);
|
||||
final diff = targetDateTimeDay.difference(nowDay).inDays;
|
||||
return switch (diff) {
|
||||
0 => "Today",
|
||||
-1 => "Yesterday",
|
||||
1 => "Tomorrow",
|
||||
-7 => "Last 7 days",
|
||||
2 => "Next 7 days",
|
||||
-30 => "Last 30 days",
|
||||
8 => "Next 30 days",
|
||||
_ => DateFormat("MMM y").format(targetDateTimeDay)
|
||||
};
|
||||
switch (config.condition) {
|
||||
case DateConditionPB.Day:
|
||||
return DateFormat("MMM dd, y").format(targetDateTime);
|
||||
case DateConditionPB.Week:
|
||||
final beginningOfWeek = targetDateTime
|
||||
.subtract(Duration(days: targetDateTime.weekday - 1));
|
||||
final endOfWeek = targetDateTime.add(
|
||||
Duration(days: DateTime.daysPerWeek - targetDateTime.weekday),
|
||||
);
|
||||
|
||||
final beginningOfWeekFormat =
|
||||
beginningOfWeek.year != endOfWeek.year
|
||||
? "MMM dd y"
|
||||
: "MMM dd";
|
||||
final endOfWeekFormat = beginningOfWeek.month != endOfWeek.month
|
||||
? "MMM dd y"
|
||||
: "dd y";
|
||||
|
||||
return LocaleKeys.board_dateCondition_weekOf.tr(
|
||||
args: [
|
||||
DateFormat(beginningOfWeekFormat).format(beginningOfWeek),
|
||||
DateFormat(endOfWeekFormat).format(endOfWeek),
|
||||
],
|
||||
);
|
||||
case DateConditionPB.Month:
|
||||
return DateFormat("MMM y").format(targetDateTime);
|
||||
case DateConditionPB.Year:
|
||||
return DateFormat("y").format(targetDateTime);
|
||||
case DateConditionPB.Relative:
|
||||
final targetDateTimeDay = DateTime(
|
||||
targetDateTime.year,
|
||||
targetDateTime.month,
|
||||
targetDateTime.day,
|
||||
);
|
||||
final nowDay = DateTime.now().withoutTime;
|
||||
final diff = targetDateTimeDay.difference(nowDay).inDays;
|
||||
return switch (diff) {
|
||||
0 => LocaleKeys.board_dateCondition_today.tr(),
|
||||
-1 => LocaleKeys.board_dateCondition_yesterday.tr(),
|
||||
1 => LocaleKeys.board_dateCondition_tomorrow.tr(),
|
||||
-7 => LocaleKeys.board_dateCondition_lastSevenDays.tr(),
|
||||
2 => LocaleKeys.board_dateCondition_nextSevenDays.tr(),
|
||||
-30 => LocaleKeys.board_dateCondition_lastThirtyDays.tr(),
|
||||
8 => LocaleKeys.board_dateCondition_nextThirtyDays.tr(),
|
||||
_ => DateFormat("MMM y").format(targetDateTimeDay)
|
||||
};
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
} on FormatException {
|
||||
return "";
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
@ -10,10 +10,12 @@ class GroupBackendService {
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> groupByField({
|
||||
required String fieldId,
|
||||
required List<int> settingContent,
|
||||
}) {
|
||||
final payload = GroupByFieldPayloadPB.create()
|
||||
..viewId = viewId
|
||||
..fieldId = fieldId;
|
||||
..fieldId = fieldId
|
||||
..settingContent = settingContent;
|
||||
|
||||
return DatabaseEventSetGroupByField(payload).send();
|
||||
}
|
||||
|
@ -8,8 +8,7 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@ -17,6 +16,7 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||
@ -42,12 +42,21 @@ class DatabaseGroupList extends StatelessWidget {
|
||||
)..add(const DatabaseGroupEvent.initial()),
|
||||
child: BlocBuilder<DatabaseGroupBloc, DatabaseGroupState>(
|
||||
builder: (context, state) {
|
||||
final showHideUngroupedToggle = state.fieldInfos.any(
|
||||
(field) =>
|
||||
field.canBeGroup &&
|
||||
field.isGroupField &&
|
||||
field.fieldType != FieldType.Checkbox,
|
||||
final field = state.fieldInfos.firstWhereOrNull(
|
||||
(field) => field.canBeGroup && field.isGroupField,
|
||||
);
|
||||
final showHideUngroupedToggle =
|
||||
field?.fieldType != FieldType.Checkbox;
|
||||
|
||||
DateGroupConfigurationPB? config;
|
||||
if (field != null) {
|
||||
final gs = state.groupSettings
|
||||
.firstWhereOrNull((gs) => gs.fieldId == field.id);
|
||||
config = gs != null
|
||||
? DateGroupConfigurationPB.fromBuffer(gs.content)
|
||||
: null;
|
||||
}
|
||||
|
||||
final children = [
|
||||
if (showHideUngroupedToggle) ...[
|
||||
SizedBox(
|
||||
@ -90,10 +99,37 @@ class DatabaseGroupList extends StatelessWidget {
|
||||
...state.fieldInfos.where((fieldInfo) => fieldInfo.canBeGroup).map(
|
||||
(fieldInfo) => _GridGroupCell(
|
||||
fieldInfo: fieldInfo,
|
||||
name: fieldInfo.name,
|
||||
icon: fieldInfo.fieldType.svgData,
|
||||
checked: fieldInfo.isGroupField,
|
||||
onSelected: onDismissed,
|
||||
key: ValueKey(fieldInfo.id),
|
||||
),
|
||||
),
|
||||
if (field?.groupConditions.isNotEmpty ?? false) ...[
|
||||
const TypeOptionSeparator(spacing: 0),
|
||||
SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.board_groupCondition.tr(),
|
||||
textAlign: TextAlign.left,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
...field!.groupConditions.map(
|
||||
(condition) => _GridGroupCell(
|
||||
fieldInfo: field,
|
||||
name: condition.name,
|
||||
condition: condition.value,
|
||||
onSelected: onDismissed,
|
||||
checked: config?.condition == condition,
|
||||
),
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
return ListView.separated(
|
||||
@ -128,15 +164,23 @@ class _GridGroupCell extends StatelessWidget {
|
||||
super.key,
|
||||
required this.fieldInfo,
|
||||
required this.onSelected,
|
||||
required this.checked,
|
||||
required this.name,
|
||||
this.condition = 0,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
final FieldInfo fieldInfo;
|
||||
final VoidCallback onSelected;
|
||||
final bool checked;
|
||||
final int condition;
|
||||
final String name;
|
||||
final FlowySvgData? icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget? rightIcon;
|
||||
if (fieldInfo.isGroupField) {
|
||||
if (checked) {
|
||||
rightIcon = const Padding(
|
||||
padding: EdgeInsets.all(2.0),
|
||||
child: FlowySvg(FlowySvgs.check_s),
|
||||
@ -150,19 +194,31 @@ class _GridGroupCell extends StatelessWidget {
|
||||
child: FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
fieldInfo.name,
|
||||
name,
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
leftIcon: FlowySvg(
|
||||
fieldInfo.fieldType.svgData,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
leftIcon: icon != null
|
||||
? FlowySvg(
|
||||
icon!,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
)
|
||||
: null,
|
||||
rightIcon: rightIcon,
|
||||
onTap: () {
|
||||
List<int> settingContent = [];
|
||||
switch (fieldInfo.fieldType) {
|
||||
case FieldType.DateTime:
|
||||
final config = DateGroupConfigurationPB()
|
||||
..condition = DateConditionPB.values[condition];
|
||||
settingContent = config.writeToBuffer();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
context.read<DatabaseGroupBloc>().add(
|
||||
DatabaseGroupEvent.setGroupByField(
|
||||
fieldInfo.id,
|
||||
fieldInfo.fieldType,
|
||||
settingContent,
|
||||
),
|
||||
);
|
||||
onSelected();
|
||||
|
@ -0,0 +1,115 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/setting/group_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
void main() {
|
||||
late AppFlowyBoardTest boardTest;
|
||||
|
||||
setUpAll(() async {
|
||||
boardTest = await AppFlowyBoardTest.ensureInitialized();
|
||||
});
|
||||
|
||||
test('group by date field test', () async {
|
||||
final context = await boardTest.createTestBoard();
|
||||
final boardBloc = BoardBloc(
|
||||
databaseController: DatabaseController(view: context.gridView),
|
||||
)..add(const BoardEvent.initial());
|
||||
await boardResponseFuture();
|
||||
|
||||
// assert the initial values
|
||||
assert(boardBloc.groupControllers.values.length == 4);
|
||||
assert(context.fieldContexts.length == 2);
|
||||
|
||||
await context.createField(FieldType.DateTime);
|
||||
await boardResponseFuture();
|
||||
assert(context.fieldContexts.length == 3);
|
||||
|
||||
final dateField = context.fieldContexts.last.field;
|
||||
final cellController = context.makeCellControllerFromFieldId(dateField.id)
|
||||
as DateCellController;
|
||||
final bloc = DateCellEditorBloc(
|
||||
cellController: cellController,
|
||||
reminderBloc: getIt<ReminderBloc>(),
|
||||
);
|
||||
await boardResponseFuture();
|
||||
|
||||
bloc.add(DateCellEditorEvent.selectDay(DateTime.now()));
|
||||
await boardResponseFuture();
|
||||
|
||||
final gridGroupBloc = DatabaseGroupBloc(
|
||||
viewId: context.gridView.id,
|
||||
databaseController: context.databaseController,
|
||||
)..add(const DatabaseGroupEvent.initial());
|
||||
gridGroupBloc.add(
|
||||
DatabaseGroupEvent.setGroupByField(
|
||||
dateField.id,
|
||||
dateField.fieldType,
|
||||
),
|
||||
);
|
||||
await boardResponseFuture();
|
||||
|
||||
assert(boardBloc.groupControllers.values.length == 2);
|
||||
assert(
|
||||
boardBloc.boardController.groupDatas.last.headerData.groupName ==
|
||||
LocaleKeys.board_dateCondition_today.tr(),
|
||||
);
|
||||
});
|
||||
|
||||
test('group by date field with condition', () async {
|
||||
final context = await boardTest.createTestBoard();
|
||||
final boardBloc = BoardBloc(
|
||||
databaseController: DatabaseController(view: context.gridView),
|
||||
)..add(const BoardEvent.initial());
|
||||
await boardResponseFuture();
|
||||
|
||||
// assert the initial values
|
||||
assert(boardBloc.groupControllers.values.length == 4);
|
||||
assert(context.fieldContexts.length == 2);
|
||||
|
||||
await context.createField(FieldType.DateTime);
|
||||
await boardResponseFuture();
|
||||
assert(context.fieldContexts.length == 3);
|
||||
|
||||
final dateField = context.fieldContexts.last.field;
|
||||
final cellController = context.makeCellControllerFromFieldId(dateField.id)
|
||||
as DateCellController;
|
||||
final bloc = DateCellEditorBloc(
|
||||
cellController: cellController,
|
||||
reminderBloc: getIt<ReminderBloc>(),
|
||||
);
|
||||
await boardResponseFuture();
|
||||
|
||||
bloc.add(DateCellEditorEvent.selectDay(DateTime.now()));
|
||||
await boardResponseFuture();
|
||||
|
||||
final gridGroupBloc = DatabaseGroupBloc(
|
||||
viewId: context.gridView.id,
|
||||
databaseController: context.databaseController,
|
||||
)..add(const DatabaseGroupEvent.initial());
|
||||
final settingContent = DateGroupConfigurationPB()
|
||||
..condition = DateConditionPB.Year;
|
||||
gridGroupBloc.add(
|
||||
DatabaseGroupEvent.setGroupByField(
|
||||
dateField.id,
|
||||
dateField.fieldType,
|
||||
settingContent.writeToBuffer(),
|
||||
),
|
||||
);
|
||||
await boardResponseFuture();
|
||||
|
||||
assert(boardBloc.groupControllers.values.length == 2);
|
||||
assert(
|
||||
boardBloc.boardController.groupDatas.last.headerData.groupName == "2024",
|
||||
);
|
||||
});
|
||||
}
|
@ -1436,6 +1436,7 @@
|
||||
"ungroupedButtonTooltip": "Contains cards that don't belong in any group",
|
||||
"ungroupedItemsTitle": "Click to add to the board",
|
||||
"groupBy": "Group by",
|
||||
"groupCondition": "Group condition",
|
||||
"referencedBoardPrefix": "View of",
|
||||
"notesTooltip": "Notes inside",
|
||||
"mobile": {
|
||||
@ -1444,6 +1445,16 @@
|
||||
"showGroupContent": "Are you sure you want to show this group on the board?",
|
||||
"failedToLoad": "Failed to load board view"
|
||||
},
|
||||
"dateCondition": {
|
||||
"weekOf": "Week of {} - {}",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"tomorrow": "Tomorrow",
|
||||
"lastSevenDays": "Last 7 days",
|
||||
"nextSevenDays": "Next 7 days",
|
||||
"lastThirtyDays": "Last 30 days",
|
||||
"nextThirtyDays": "Next 30 days"
|
||||
},
|
||||
"noGroup": "No group by property",
|
||||
"noGroupDesc": "Board views require a property to group by in order to display"
|
||||
},
|
||||
|
@ -444,12 +444,18 @@ impl EventIntegrationTest {
|
||||
.error()
|
||||
}
|
||||
|
||||
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
|
||||
pub async fn set_group_by_field(
|
||||
&self,
|
||||
view_id: &str,
|
||||
field_id: &str,
|
||||
setting_content: Vec<u8>,
|
||||
) -> Option<FlowyError> {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(DatabaseEvent::SetGroupByField)
|
||||
.payload(GroupByFieldPayloadPB {
|
||||
field_id: field_id.to_string(),
|
||||
view_id: view_id.to_string(),
|
||||
setting_content,
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
|
@ -697,7 +697,7 @@ async fn update_database_layout_event_test2() {
|
||||
.find(|field| field.field_type == FieldType::Checkbox)
|
||||
.unwrap();
|
||||
test
|
||||
.set_group_by_field(&grid_view.id, &checkbox_field.id)
|
||||
.set_group_by_field(&grid_view.id, &checkbox_field.id, vec![])
|
||||
.await;
|
||||
|
||||
let error = test
|
||||
@ -725,7 +725,7 @@ async fn set_group_by_checkbox_field_test() {
|
||||
|
||||
let checkbox_field = test.create_field(&board_view.id, FieldType::Checkbox).await;
|
||||
test
|
||||
.set_group_by_field(&board_view.id, &checkbox_field.id)
|
||||
.set_group_by_field(&board_view.id, &checkbox_field.id, vec![])
|
||||
.await;
|
||||
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
|
@ -1,5 +1,10 @@
|
||||
use crate::services::group::Group;
|
||||
use crate::{
|
||||
entities::FieldType,
|
||||
services::group::{DateCondition, DateGroupConfiguration, Group},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct URLGroupConfigurationPB {
|
||||
@ -46,16 +51,33 @@ pub struct NumberGroupConfigurationPB {
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct DateGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: DateCondition,
|
||||
pub condition: DateConditionPB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
hide_empty: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
impl From<DateGroupConfigurationPB> for DateGroupConfiguration {
|
||||
fn from(data: DateGroupConfigurationPB) -> Self {
|
||||
Self {
|
||||
condition: data.condition.into(),
|
||||
hide_empty: data.hide_empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateGroupConfiguration> for DateGroupConfigurationPB {
|
||||
fn from(data: DateGroupConfiguration) -> Self {
|
||||
Self {
|
||||
condition: data.condition.into(),
|
||||
hide_empty: data.hide_empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, Default)]
|
||||
#[repr(u8)]
|
||||
#[derive(Default)]
|
||||
pub enum DateCondition {
|
||||
pub enum DateConditionPB {
|
||||
#[default]
|
||||
Relative = 0,
|
||||
Day = 1,
|
||||
@ -64,8 +86,56 @@ pub enum DateCondition {
|
||||
Year = 4,
|
||||
}
|
||||
|
||||
impl From<DateConditionPB> for DateCondition {
|
||||
fn from(data: DateConditionPB) -> Self {
|
||||
match data {
|
||||
DateConditionPB::Relative => DateCondition::Relative,
|
||||
DateConditionPB::Day => DateCondition::Day,
|
||||
DateConditionPB::Week => DateCondition::Week,
|
||||
DateConditionPB::Month => DateCondition::Month,
|
||||
DateConditionPB::Year => DateCondition::Year,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateCondition> for DateConditionPB {
|
||||
fn from(data: DateCondition) -> Self {
|
||||
match data {
|
||||
DateCondition::Relative => DateConditionPB::Relative,
|
||||
DateCondition::Day => DateConditionPB::Day,
|
||||
DateCondition::Week => DateConditionPB::Week,
|
||||
DateCondition::Month => DateConditionPB::Month,
|
||||
DateCondition::Year => DateConditionPB::Year,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct CheckboxGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub(crate) hide_empty: bool,
|
||||
}
|
||||
|
||||
pub fn group_config_pb_to_json_str<T: Into<Bytes>>(
|
||||
bytes: T,
|
||||
field_type: &FieldType,
|
||||
) -> FlowyResult<String> {
|
||||
let bytes = bytes.into();
|
||||
match field_type {
|
||||
FieldType::DateTime => DateGroupConfigurationPB::try_from(bytes)
|
||||
.map(|pb| DateGroupConfiguration::from(pb).to_json())?,
|
||||
_ => Ok("".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group_config_json_to_pb(setting_content: String, field_type: &FieldType) -> Bytes {
|
||||
match field_type {
|
||||
FieldType::DateTime => {
|
||||
let date_group_config = DateGroupConfiguration::from_json(setting_content.as_ref()).unwrap();
|
||||
DateGroupConfigurationPB::from(date_group_config)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
},
|
||||
_ => Bytes::new(),
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ use flowy_error::ErrorCode;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::RowMetaPB;
|
||||
use crate::entities::{FieldType, RowMetaPB};
|
||||
use crate::services::group::{GroupChangeset, GroupData, GroupSetting};
|
||||
|
||||
use super::group_config_json_to_pb;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GroupSettingPB {
|
||||
#[pb(index = 1)]
|
||||
@ -15,13 +17,18 @@ pub struct GroupSettingPB {
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub content: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::convert::From<&GroupSetting> for GroupSettingPB {
|
||||
fn from(rev: &GroupSetting) -> Self {
|
||||
let field_type = FieldType::from(rev.field_type);
|
||||
GroupSettingPB {
|
||||
id: rev.id.clone(),
|
||||
field_id: rev.field_id.clone(),
|
||||
content: group_config_json_to_pb(rev.content.clone(), &field_type).to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,6 +112,9 @@ pub struct GroupByFieldPayloadPB {
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub setting_content: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB {
|
||||
@ -118,13 +128,18 @@ impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB {
|
||||
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
||||
.0;
|
||||
|
||||
Ok(GroupByFieldParams { field_id, view_id })
|
||||
Ok(GroupByFieldParams {
|
||||
field_id,
|
||||
view_id,
|
||||
setting_content: self.setting_content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GroupByFieldParams {
|
||||
pub field_id: String,
|
||||
pub view_id: String,
|
||||
pub setting_content: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone, Validate)]
|
||||
|
@ -656,7 +656,7 @@ pub(crate) async fn set_group_by_field_handler(
|
||||
let params: GroupByFieldParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
database_editor
|
||||
.set_group_by_field(¶ms.view_id, ¶ms.field_id)
|
||||
.set_group_by_field(¶ms.view_id, ¶ms.field_id, params.setting_content)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -136,20 +136,37 @@ impl DatabaseEditor {
|
||||
self.database.lock().fields.get_field(field_id)
|
||||
}
|
||||
|
||||
pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> {
|
||||
pub async fn set_group_by_field(
|
||||
&self,
|
||||
view_id: &str,
|
||||
field_id: &str,
|
||||
data: Vec<u8>,
|
||||
) -> FlowyResult<()> {
|
||||
let old_group_settings: Vec<GroupSetting>;
|
||||
let mut setting_content = "".to_string();
|
||||
{
|
||||
let database = self.database.lock();
|
||||
let field = database.fields.get_field(field_id);
|
||||
old_group_settings = database.get_all_group_setting(view_id);
|
||||
if let Some(field) = field {
|
||||
let group_setting = default_group_setting(&field);
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
setting_content = group_config_pb_to_json_str(data, &field_type)?;
|
||||
let mut group_setting = default_group_setting(&field);
|
||||
group_setting.content = setting_content.clone();
|
||||
database.views.update_database_view(view_id, |view| {
|
||||
view.set_groups(vec![group_setting.into()]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let old_group_setting = old_group_settings.iter().find(|g| g.field_id == field_id);
|
||||
let has_same_content =
|
||||
old_group_setting.is_some() && old_group_setting.unwrap().content == setting_content;
|
||||
|
||||
let view_editor = self.database_views.get_view_editor(view_id).await?;
|
||||
view_editor.v_initialize_new_group(field_id).await?;
|
||||
if !view_editor.is_grouping_field(field_id).await || !has_same_content {
|
||||
view_editor.v_initialize_new_group(field_id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -401,15 +401,12 @@ impl DatabaseViewEditor {
|
||||
|
||||
/// Called when the user changes the grouping field
|
||||
pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> {
|
||||
let is_grouping_field = self.is_grouping_field(field_id).await;
|
||||
if !is_grouping_field {
|
||||
self.v_group_by_field(field_id).await?;
|
||||
|
||||
if let Some(view) = self.delegate.get_view(&self.view_id).await {
|
||||
let setting = database_view_setting_pb_from_view(view);
|
||||
notify_did_update_setting(&self.view_id, setting).await;
|
||||
}
|
||||
if let Some(view) = self.delegate.get_view(&self.view_id).await {
|
||||
let setting = database_view_setting_pb_from_view(view);
|
||||
notify_did_update_setting(&self.view_id, setting).await;
|
||||
}
|
||||
|
||||
self.v_group_by_field(field_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_error::{internal_error, FlowyResult};
|
||||
|
||||
use crate::entities::{
|
||||
FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB,
|
||||
@ -27,13 +27,13 @@ pub struct DateGroupConfiguration {
|
||||
}
|
||||
|
||||
impl DateGroupConfiguration {
|
||||
fn from_json(s: &str) -> Result<Self, serde_json::Error> {
|
||||
pub fn from_json(s: &str) -> Result<Self, serde_json::Error> {
|
||||
serde_json::from_str(s)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn to_json(&self) -> Result<String, serde_json::Error> {
|
||||
serde_json::to_string(self)
|
||||
pub fn to_json(&self) -> FlowyResult<String> {
|
||||
serde_json::to_string(self).map_err(internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user