feat: allow hiding ungrouped stack (#3752)

* feat: allow hiding ungrouped stack

* chore: add notifications and listeners

* chore: implement UI

* fix: field info update

* chore: more responsive notification

* chore: read the right configurations

* feat: add ungrouped button

* fix: new board not getting isGroupField

* feat: refresh the counter

* fix: item count update

* chore: apply code suggestions from Mathias

* chore: yolo through tests

* chore: UI fix

* chore: code cleanup

* chore: ungrouped item count fix

* chore: same as above
This commit is contained in:
Richard Shiue 2023-10-26 11:48:58 +08:00 committed by GitHub
parent 1883dd6d7c
commit 8c3984d21a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 895 additions and 118 deletions

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/view/view_cache.dart'; import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
@ -23,18 +24,21 @@ import 'row/row_cache.dart';
import 'group/group_listener.dart'; import 'group/group_listener.dart';
import 'row/row_service.dart'; import 'row/row_service.dart';
typedef OnGroupConfigurationChanged = void Function(List<GroupSettingPB>);
typedef OnGroupByField = void Function(List<GroupPB>); typedef OnGroupByField = void Function(List<GroupPB>);
typedef OnUpdateGroup = void Function(List<GroupPB>); typedef OnUpdateGroup = void Function(List<GroupPB>);
typedef OnDeleteGroup = void Function(List<String>); typedef OnDeleteGroup = void Function(List<String>);
typedef OnInsertGroup = void Function(InsertedGroupPB); typedef OnInsertGroup = void Function(InsertedGroupPB);
class GroupCallbacks { class GroupCallbacks {
final OnGroupConfigurationChanged? onGroupConfigurationChanged;
final OnGroupByField? onGroupByField; final OnGroupByField? onGroupByField;
final OnUpdateGroup? onUpdateGroup; final OnUpdateGroup? onUpdateGroup;
final OnDeleteGroup? onDeleteGroup; final OnDeleteGroup? onDeleteGroup;
final OnInsertGroup? onInsertGroup; final OnInsertGroup? onInsertGroup;
GroupCallbacks({ GroupCallbacks({
this.onGroupConfigurationChanged,
this.onGroupByField, this.onGroupByField,
this.onUpdateGroup, this.onUpdateGroup,
this.onDeleteGroup, this.onDeleteGroup,
@ -237,6 +241,15 @@ class DatabaseController {
}); });
} }
void updateGroupConfiguration(bool hideUngrouped) async {
final payload = GroupSettingChangesetPB(
viewId: viewId,
groupConfigurationId: "",
hideUngrouped: hideUngrouped,
);
DatabaseEventUpdateGroupConfiguration(payload).send();
}
Future<void> dispose() async { Future<void> dispose() async {
await _databaseViewBackendSvc.closeView(); await _databaseViewBackendSvc.closeView();
await fieldController.dispose(); await fieldController.dispose();
@ -248,16 +261,17 @@ class DatabaseController {
} }
Future<void> _loadGroups() async { Future<void> _loadGroups() async {
final result = await _databaseViewBackendSvc.loadGroups(); final configResult = await loadGroupConfigurations(viewId: viewId);
return Future( _handleGroupConfigurationChanged(configResult);
() => result.fold(
(groups) { final groupsResult = await _databaseViewBackendSvc.loadGroups();
for (final callback in _groupCallbacks) { groupsResult.fold(
callback.onGroupByField?.call(groups.items); (groups) {
} for (final callback in _groupCallbacks) {
}, callback.onGroupByField?.call(groups.items);
(err) => Log.error(err), }
), },
(err) => Log.error(err),
); );
} }
@ -325,6 +339,7 @@ class DatabaseController {
void _listenOnGroupChanged() { void _listenOnGroupChanged() {
_groupListener.start( _groupListener.start(
onGroupConfigurationChanged: _handleGroupConfigurationChanged,
onNumOfGroupsChanged: (result) { onNumOfGroupsChanged: (result) {
result.fold( result.fold(
(changeset) { (changeset) {
@ -379,6 +394,29 @@ class DatabaseController {
}, },
); );
} }
Future<Either<List<GroupSettingPB>, FlowyError>> loadGroupConfigurations({
required String viewId,
}) {
final payload = DatabaseViewIdPB(value: viewId);
return DatabaseEventGetGroupConfigurations(payload).send().then((result) {
return result.fold((l) => left(l.items), (r) => right(r));
});
}
void _handleGroupConfigurationChanged(
Either<List<GroupSettingPB>, FlowyError> result,
) {
result.fold(
(configurations) {
for (final callback in _groupCallbacks) {
callback.onGroupConfigurationChanged?.call(configurations);
}
},
(r) => Log.error(r),
);
}
} }
class RowDataBuilder { class RowDataBuilder {

View File

@ -22,6 +22,7 @@ import 'package:collection/collection.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../setting/setting_service.dart';
import 'field_info.dart'; import 'field_info.dart';
import 'field_listener.dart'; import 'field_listener.dart';
@ -558,6 +559,7 @@ class FieldController {
_loadFilters(), _loadFilters(),
_loadSorts(), _loadSorts(),
_loadAllFieldSettings(), _loadAllFieldSettings(),
_loadSettings(),
]); ]);
_updateFieldInfos(); _updateFieldInfos();
@ -608,6 +610,22 @@ class FieldController {
}); });
} }
Future<Either<Unit, FlowyError>> _loadSettings() async {
return SettingBackendService(viewId: viewId).getSetting().then(
(result) => result.fold(
(setting) {
_groupConfigurationByFieldId.clear();
for (final configuration in setting.groupSettings.items) {
_groupConfigurationByFieldId[configuration.fieldId] =
configuration;
}
return left(unit);
},
(err) => right(err),
),
);
}
/// Attach corresponding `FieldInfo`s to the `FilterPB`s /// Attach corresponding `FieldInfo`s to the `FilterPB`s
List<FilterInfo> _filterInfoListFromPBs(List<FilterPB> filterPBs) { List<FilterInfo> _filterInfoListFromPBs(List<FilterPB> filterPBs) {
FilterInfo? getFilterInfo(FilterPB filterPB) { FilterInfo? getFilterInfo(FilterPB filterPB) {

View File

@ -8,11 +8,15 @@ import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart';
typedef GroupConfigurationUpdateValue
= Either<List<GroupSettingPB>, FlowyError>;
typedef GroupUpdateValue = Either<GroupChangesPB, FlowyError>; typedef GroupUpdateValue = Either<GroupChangesPB, FlowyError>;
typedef GroupByNewFieldValue = Either<List<GroupPB>, FlowyError>; typedef GroupByNewFieldValue = Either<List<GroupPB>, FlowyError>;
class DatabaseGroupListener { class DatabaseGroupListener {
final String viewId; final String viewId;
PublishNotifier<GroupConfigurationUpdateValue>? _groupConfigurationNotifier =
PublishNotifier();
PublishNotifier<GroupUpdateValue>? _numOfGroupsNotifier = PublishNotifier(); PublishNotifier<GroupUpdateValue>? _numOfGroupsNotifier = PublishNotifier();
PublishNotifier<GroupByNewFieldValue>? _groupByFieldNotifier = PublishNotifier<GroupByNewFieldValue>? _groupByFieldNotifier =
PublishNotifier(); PublishNotifier();
@ -20,9 +24,13 @@ class DatabaseGroupListener {
DatabaseGroupListener(this.viewId); DatabaseGroupListener(this.viewId);
void start({ void start({
required void Function(GroupConfigurationUpdateValue)
onGroupConfigurationChanged,
required void Function(GroupUpdateValue) onNumOfGroupsChanged, required void Function(GroupUpdateValue) onNumOfGroupsChanged,
required void Function(GroupByNewFieldValue) onGroupByNewField, required void Function(GroupByNewFieldValue) onGroupByNewField,
}) { }) {
_groupConfigurationNotifier
?.addPublishListener(onGroupConfigurationChanged);
_numOfGroupsNotifier?.addPublishListener(onNumOfGroupsChanged); _numOfGroupsNotifier?.addPublishListener(onNumOfGroupsChanged);
_groupByFieldNotifier?.addPublishListener(onGroupByNewField); _groupByFieldNotifier?.addPublishListener(onGroupByNewField);
_listener = DatabaseNotificationListener( _listener = DatabaseNotificationListener(
@ -36,6 +44,13 @@ class DatabaseGroupListener {
Either<Uint8List, FlowyError> result, Either<Uint8List, FlowyError> result,
) { ) {
switch (ty) { switch (ty) {
case DatabaseNotification.DidUpdateGroupConfiguration:
result.fold(
(payload) => _groupConfigurationNotifier?.value =
left(RepeatedGroupSettingPB.fromBuffer(payload).items),
(error) => _groupConfigurationNotifier?.value = right(error),
);
break;
case DatabaseNotification.DidUpdateNumOfGroups: case DatabaseNotification.DidUpdateNumOfGroups:
result.fold( result.fold(
(payload) => _numOfGroupsNotifier?.value = (payload) => _numOfGroupsNotifier?.value =
@ -57,6 +72,9 @@ class DatabaseGroupListener {
Future<void> stop() async { Future<void> stop() async {
await _listener?.stop(); await _listener?.stop();
_groupConfigurationNotifier?.dispose();
_groupConfigurationNotifier = null;
_numOfGroupsNotifier?.dispose(); _numOfGroupsNotifier?.dispose();
_numOfGroupsNotifier = null; _numOfGroupsNotifier = null;

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
@ -11,20 +11,27 @@ import '../group/group_service.dart';
part 'group_bloc.freezed.dart'; part 'group_bloc.freezed.dart';
class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> { class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
final FieldController _fieldController; final DatabaseController _databaseController;
final GroupBackendService _groupBackendSvc; final GroupBackendService _groupBackendSvc;
Function(List<FieldInfo>)? _onFieldsFn; Function(List<FieldInfo>)? _onFieldsFn;
GroupCallbacks? _groupCallbacks;
DatabaseGroupBloc({ DatabaseGroupBloc({
required String viewId, required String viewId,
required FieldController fieldController, required DatabaseController databaseController,
}) : _fieldController = fieldController, }) : _databaseController = databaseController,
_groupBackendSvc = GroupBackendService(viewId), _groupBackendSvc = GroupBackendService(viewId),
super(DatabaseGroupState.initial(viewId, fieldController.fieldInfos)) { super(
DatabaseGroupState.initial(
viewId,
databaseController.fieldController.fieldInfos,
),
) {
on<DatabaseGroupEvent>( on<DatabaseGroupEvent>(
(event, emit) async { (event, emit) async {
event.when( event.when(
initial: () { initial: () {
_loadGroupConfigurations();
_startListening(); _startListening();
}, },
didReceiveFieldUpdate: (fieldInfos) { didReceiveFieldUpdate: (fieldInfos) {
@ -36,6 +43,9 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
); );
result.fold((l) => null, (err) => Log.error(err)); result.fold((l) => null, (err) => Log.error(err));
}, },
didUpdateHideUngrouped: (bool hideUngrouped) {
emit(state.copyWith(hideUngrouped: hideUngrouped));
},
); );
}, },
); );
@ -44,19 +54,49 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
@override @override
Future<void> close() async { Future<void> close() async {
if (_onFieldsFn != null) { if (_onFieldsFn != null) {
_fieldController.removeListener(onFieldsListener: _onFieldsFn!); _databaseController.fieldController
.removeListener(onFieldsListener: _onFieldsFn!);
_onFieldsFn = null; _onFieldsFn = null;
} }
_groupCallbacks = null;
return super.close(); return super.close();
} }
void _startListening() { void _startListening() {
_onFieldsFn = (fieldInfos) => _onFieldsFn = (fieldInfos) =>
add(DatabaseGroupEvent.didReceiveFieldUpdate(fieldInfos)); add(DatabaseGroupEvent.didReceiveFieldUpdate(fieldInfos));
_fieldController.addListener( _databaseController.fieldController.addListener(
onReceiveFields: _onFieldsFn, onReceiveFields: _onFieldsFn,
listenWhen: () => !isClosed, listenWhen: () => !isClosed,
); );
_groupCallbacks = GroupCallbacks(
onGroupConfigurationChanged: (configurations) {
if (isClosed) {
return;
}
final configuration = configurations.first;
add(
DatabaseGroupEvent.didUpdateHideUngrouped(
configuration.hideUngrouped,
),
);
},
);
_databaseController.addListener(onGroupChanged: _groupCallbacks);
}
void _loadGroupConfigurations() async {
final configResult = await _databaseController.loadGroupConfigurations(
viewId: _databaseController.viewId,
);
configResult.fold(
(configurations) {
final hideUngrouped = configurations.first.hideUngrouped;
add(DatabaseGroupEvent.didUpdateHideUngrouped(hideUngrouped));
},
(err) => Log.error(err),
);
} }
} }
@ -70,6 +110,8 @@ class DatabaseGroupEvent with _$DatabaseGroupEvent {
const factory DatabaseGroupEvent.didReceiveFieldUpdate( const factory DatabaseGroupEvent.didReceiveFieldUpdate(
List<FieldInfo> fields, List<FieldInfo> fields,
) = _DidReceiveFieldUpdate; ) = _DidReceiveFieldUpdate;
const factory DatabaseGroupEvent.didUpdateHideUngrouped(bool hideUngrouped) =
_DidUpdateHideUngrouped;
} }
@freezed @freezed
@ -77,6 +119,7 @@ class DatabaseGroupState with _$DatabaseGroupState {
const factory DatabaseGroupState({ const factory DatabaseGroupState({
required String viewId, required String viewId,
required List<FieldInfo> fieldInfos, required List<FieldInfo> fieldInfos,
required bool hideUngrouped,
}) = _DatabaseGroupState; }) = _DatabaseGroupState;
factory DatabaseGroupState.initial( factory DatabaseGroupState.initial(
@ -86,5 +129,6 @@ class DatabaseGroupState with _$DatabaseGroupState {
DatabaseGroupState( DatabaseGroupState(
viewId: viewId, viewId: viewId,
fieldInfos: fieldInfos, fieldInfos: fieldInfos,
hideUngrouped: true,
); );
} }

View File

@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_info.dart
import 'package:appflowy/plugins/database_view/application/group/group_service.dart'; import 'package:appflowy/plugins/database_view/application/group/group_service.dart';
import 'package:appflowy/plugins/database_view/application/row/row_service.dart'; import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
import 'package:appflowy_board/appflowy_board.dart'; import 'package:appflowy_board/appflowy_board.dart';
import 'package:collection/collection.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
@ -29,6 +30,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
late final AppFlowyBoardController boardController; late final AppFlowyBoardController boardController;
final LinkedHashMap<String, GroupController> groupControllers = final LinkedHashMap<String, GroupController> groupControllers =
LinkedHashMap(); LinkedHashMap();
GroupPB? ungroupedGroup;
FieldController get fieldController => databaseController.fieldController; FieldController get fieldController => databaseController.fieldController;
String get viewId => databaseController.viewId; String get viewId => databaseController.viewId;
@ -144,6 +146,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
), ),
); );
}, },
didUpdateHideUngrouped: (bool hideUngrouped) {
emit(state.copyWith(hideUngrouped: hideUngrouped));
},
startEditingHeader: (String groupId) { startEditingHeader: (String groupId) {
emit( emit(
state.copyWith(isEditingHeader: true, editingHeaderId: groupId), state.copyWith(isEditingHeader: true, editingHeaderId: groupId),
@ -188,6 +193,17 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
groupControllers.clear(); groupControllers.clear();
boardController.clear(); boardController.clear();
final ungroupedGroupIndex =
groups.indexWhere((group) => group.groupId == group.fieldId);
if (ungroupedGroupIndex != -1) {
ungroupedGroup = groups[ungroupedGroupIndex];
final group = groups.removeAt(ungroupedGroupIndex);
if (!state.hideUngrouped) {
groups.add(group);
}
}
boardController.addGroups( boardController.addGroups(
groups groups
.where((group) => fieldController.getField(group.fieldId) != null) .where((group) => fieldController.getField(group.fieldId) != null)
@ -214,8 +230,22 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
}, },
); );
final onGroupChanged = GroupCallbacks( final onGroupChanged = GroupCallbacks(
onGroupConfigurationChanged: (configurations) {
if (isClosed) return;
final config = configurations.first;
if (config.hideUngrouped) {
boardController.removeGroup(config.fieldId);
} else if (ungroupedGroup != null) {
final newGroup = initializeGroupData(ungroupedGroup!);
final controller = initializeGroupController(ungroupedGroup!);
groupControllers[controller.group.groupId] = (controller);
boardController.addGroup(newGroup);
}
add(BoardEvent.didUpdateHideUngrouped(config.hideUngrouped));
},
onGroupByField: (groups) { onGroupByField: (groups) {
if (isClosed) return; if (isClosed) return;
ungroupedGroup = null;
initializeGroups(groups); initializeGroups(groups);
add(BoardEvent.didReceiveGroups(groups)); add(BoardEvent.didReceiveGroups(groups));
}, },
@ -329,6 +359,8 @@ class BoardEvent with _$BoardEvent {
) = _DidReceiveGridUpdate; ) = _DidReceiveGridUpdate;
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) = const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
_DidReceiveGroups; _DidReceiveGroups;
const factory BoardEvent.didUpdateHideUngrouped(bool hideUngrouped) =
_DidUpdateHideUngrouped;
} }
@freezed @freezed
@ -343,6 +375,7 @@ class BoardState with _$BoardState {
BoardEditingRow? editingRow, BoardEditingRow? editingRow,
required LoadingState loadingState, required LoadingState loadingState,
required Option<FlowyError> noneOrError, required Option<FlowyError> noneOrError,
required bool hideUngrouped,
}) = _BoardState; }) = _BoardState;
factory BoardState.initial(String viewId) => BoardState( factory BoardState.initial(String viewId) => BoardState(
@ -353,6 +386,7 @@ class BoardState with _$BoardState {
isEditingRow: false, isEditingRow: false,
noneOrError: none(), noneOrError: none(),
loadingState: const LoadingState.loading(), loadingState: const LoadingState.loading(),
hideUngrouped: false,
); );
} }

View File

@ -0,0 +1,112 @@
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'group_controller.dart';
part 'ungrouped_items_bloc.freezed.dart';
class UngroupedItemsBloc
extends Bloc<UngroupedItemsEvent, UngroupedItemsState> {
UngroupedItemsListener? listener;
UngroupedItemsBloc({required GroupPB group})
: super(UngroupedItemsState(ungroupedItems: group.rows)) {
on<UngroupedItemsEvent>(
(event, emit) {
event.when(
initial: () {
listener = UngroupedItemsListener(
initialGroup: group,
onGroupChanged: (ungroupedItems) {
if (isClosed) return;
add(
UngroupedItemsEvent.updateGroup(
ungroupedItems: ungroupedItems,
),
);
},
)..startListening();
},
updateGroup: (newItems) =>
emit(UngroupedItemsState(ungroupedItems: newItems)),
);
},
);
}
}
@freezed
class UngroupedItemsEvent with _$UngroupedItemsEvent {
const factory UngroupedItemsEvent.initial() = _Initial;
const factory UngroupedItemsEvent.updateGroup({
required List<RowMetaPB> ungroupedItems,
}) = _UpdateGroup;
}
@freezed
class UngroupedItemsState with _$UngroupedItemsState {
const factory UngroupedItemsState({
required List<RowMetaPB> ungroupedItems,
}) = _UngroupedItemsState;
}
class UngroupedItemsListener {
List<RowMetaPB> _ungroupedItems;
final SingleGroupListener _listener;
final void Function(List<RowMetaPB> items) onGroupChanged;
UngroupedItemsListener({
required GroupPB initialGroup,
required this.onGroupChanged,
}) : _ungroupedItems = List<RowMetaPB>.from(initialGroup.rows),
_listener = SingleGroupListener(initialGroup);
void startListening() {
_listener.start(
onGroupChanged: (result) {
result.fold(
(GroupRowsNotificationPB changeset) {
final newItems = List<RowMetaPB>.from(_ungroupedItems);
for (final deletedRow in changeset.deletedRows) {
newItems.removeWhere((rowPB) => rowPB.id == deletedRow);
}
for (final insertedRow in changeset.insertedRows) {
final index = newItems.indexWhere(
(rowPB) => rowPB.id == insertedRow.rowMeta.id,
);
if (index != -1) {
continue;
}
if (insertedRow.hasIndex() &&
newItems.length > insertedRow.index) {
newItems.insert(insertedRow.index, insertedRow.rowMeta);
} else {
newItems.add(insertedRow.rowMeta);
}
}
for (final updatedRow in changeset.updatedRows) {
final index = newItems.indexWhere(
(rowPB) => rowPB.id == updatedRow.id,
);
if (index != -1) {
newItems[index] = updatedRow;
}
}
onGroupChanged.call(newItems);
_ungroupedItems = newItems;
},
(err) => Log.error(err),
);
},
);
}
Future<void> dispose() async {
_listener.stop();
}
}

View File

@ -20,6 +20,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart' hide Card; import 'package:flutter/material.dart' hide Card;
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -29,6 +30,7 @@ import '../../widgets/row/cell_builder.dart';
import '../application/board_bloc.dart'; import '../application/board_bloc.dart';
import '../../widgets/card/card.dart'; import '../../widgets/card/card.dart';
import 'toolbar/board_setting_bar.dart'; import 'toolbar/board_setting_bar.dart';
import 'ungrouped_items_button.dart';
class BoardPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder { class BoardPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
@override @override
@ -157,33 +159,42 @@ class _BoardContentState extends State<BoardContent> {
widget.onEditStateChanged?.call(); widget.onEditStateChanged?.call();
}, },
child: BlocBuilder<BoardBloc, BoardState>( child: BlocBuilder<BoardBloc, BoardState>(
// Only rebuild when groups are added/removed/rearranged
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
builder: (context, state) { builder: (context, state) {
return Padding( return Padding(
padding: GridSize.contentInsets, padding: GridSize.contentInsets,
child: AppFlowyBoard( child: Column(
boardScrollController: scrollManager, crossAxisAlignment: CrossAxisAlignment.start,
scrollController: ScrollController(), mainAxisSize: MainAxisSize.min,
controller: context.read<BoardBloc>().boardController, children: [
headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value( const VSpace(8.0),
value: context.read<BoardBloc>(), if (state.hideUngrouped) _buildBoardHeader(context),
child: BoardColumnHeader( Expanded(
groupData: groupData, child: AppFlowyBoard(
margin: config.headerPadding, boardScrollController: scrollManager,
), scrollController: ScrollController(),
), controller: context.read<BoardBloc>().boardController,
footerBuilder: _buildFooter, headerBuilder: (_, groupData) =>
cardBuilder: (_, column, columnItem) => _buildCard( BlocProvider<BoardBloc>.value(
context, value: context.read<BoardBloc>(),
column, child: BoardColumnHeader(
columnItem, groupData: groupData,
), margin: config.headerPadding,
groupConstraints: const BoxConstraints.tightFor(width: 300), ),
config: AppFlowyBoardConfig( ),
groupBackgroundColor: footerBuilder: _buildFooter,
Theme.of(context).colorScheme.surfaceVariant, cardBuilder: (_, column, columnItem) => _buildCard(
), context,
column,
columnItem,
),
groupConstraints: const BoxConstraints.tightFor(width: 300),
config: AppFlowyBoardConfig(
groupBackgroundColor:
Theme.of(context).colorScheme.surfaceVariant,
),
),
)
],
), ),
); );
}, },
@ -191,6 +202,19 @@ class _BoardContentState extends State<BoardContent> {
); );
} }
Widget _buildBoardHeader(BuildContext context) {
return const Padding(
padding: EdgeInsets.only(bottom: 8.0),
child: SizedBox(
height: 24,
child: Align(
alignment: AlignmentDirectional.centerEnd,
child: UngroupedItemsButton(),
),
),
);
}
void _handleEditStateChanged(BoardState state, BuildContext context) { void _handleEditStateChanged(BoardState state, BuildContext context) {
if (state.isEditingRow && state.editingRow != null) { if (state.isEditingRow && state.editingRow != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {

View File

@ -0,0 +1,230 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
import 'package:appflowy/plugins/database_view/board/application/ungrouped_items_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class UngroupedItemsButton extends StatefulWidget {
const UngroupedItemsButton({super.key});
@override
State<UngroupedItemsButton> createState() => _UnscheduledEventsButtonState();
}
class _UnscheduledEventsButtonState extends State<UngroupedItemsButton> {
late final PopoverController _popoverController;
@override
void initState() {
super.initState();
_popoverController = PopoverController();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<BoardBloc, BoardState>(
builder: (context, boardState) {
final ungroupedGroup = context.watch<BoardBloc>().ungroupedGroup;
final databaseController = context.read<BoardBloc>().databaseController;
final primaryField = databaseController.fieldController.fieldInfos
.firstWhereOrNull((element) => element.isPrimary)!;
if (ungroupedGroup == null) {
return const SizedBox.shrink();
}
return BlocProvider<UngroupedItemsBloc>(
create: (_) => UngroupedItemsBloc(group: ungroupedGroup)
..add(const UngroupedItemsEvent.initial()),
child: BlocBuilder<UngroupedItemsBloc, UngroupedItemsState>(
builder: (context, state) {
return AppFlowyPopover(
direction: PopoverDirection.bottomWithCenterAligned,
triggerActions: PopoverTriggerFlags.none,
controller: _popoverController,
offset: const Offset(0, 8),
constraints:
const BoxConstraints(maxWidth: 282, maxHeight: 600),
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
),
borderRadius: Corners.s6Border,
),
side: BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
),
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
visualDensity: VisualDensity.compact,
),
onPressed: () {
if (state.ungroupedItems.isNotEmpty) {
_popoverController.show();
}
},
child: FlowyText.regular(
"${LocaleKeys.board_ungroupedButtonText.tr()} (${state.ungroupedItems.length})",
fontSize: 10,
),
),
popupBuilder: (context) {
return UngroupedItemList(
viewId: databaseController.viewId,
primaryField: primaryField,
rowCache: databaseController.rowCache,
ungroupedItems: state.ungroupedItems,
);
},
);
},
),
);
},
);
}
}
class UngroupedItemList extends StatelessWidget {
final String viewId;
final FieldInfo primaryField;
final RowCache rowCache;
final List<RowMetaPB> ungroupedItems;
const UngroupedItemList({
required this.viewId,
required this.primaryField,
required this.ungroupedItems,
required this.rowCache,
super.key,
});
@override
Widget build(BuildContext context) {
final cells = <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
child: FlowyText.medium(
LocaleKeys.board_ungroupedItemsTitle.tr(),
fontSize: 10,
color: Theme.of(context).hintColor,
overflow: TextOverflow.ellipsis,
),
),
...ungroupedItems.map(
(item) {
final rowController = RowController(
rowMeta: item,
viewId: viewId,
rowCache: rowCache,
);
final renderHook = RowCardRenderHook<String>();
renderHook.addTextCellHook((cellData, _, __) {
return BlocBuilder<TextCellBloc, TextCellState>(
builder: (context, state) {
final text = cellData.isEmpty
? LocaleKeys.grid_row_titlePlaceholder.tr()
: cellData;
if (text.isEmpty) {
return const SizedBox.shrink();
}
return Align(
alignment: Alignment.centerLeft,
child: FlowyText.medium(
text,
textAlign: TextAlign.left,
fontSize: 11,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
},
);
});
return UngroupedItem(
cellContext: rowCache.loadCells(item)[primaryField.id]!,
primaryField: primaryField,
rowController: rowController,
cellBuilder: CardCellBuilder<String>(rowController.cellCache),
renderHook: renderHook,
onPressed: () {
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder:
GridCellBuilder(cellCache: rowController.cellCache),
rowController: rowController,
);
},
);
PopoverContainer.of(context).close();
},
);
},
)
];
return ListView.separated(
itemBuilder: (context, index) => cells[index],
itemCount: cells.length,
separatorBuilder: (context, index) =>
VSpace(GridSize.typeOptionSeparatorHeight),
shrinkWrap: true,
);
}
}
class UngroupedItem extends StatelessWidget {
final DatabaseCellContext cellContext;
final FieldInfo primaryField;
final RowController rowController;
final CardCellBuilder cellBuilder;
final RowCardRenderHook<String> renderHook;
final VoidCallback onPressed;
const UngroupedItem({
super.key,
required this.cellContext,
required this.onPressed,
required this.cellBuilder,
required this.rowController,
required this.primaryField,
required this.renderHook,
});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 26,
child: FlowyButton(
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
text: cellBuilder.buildCell(
cellContext: cellContext,
renderHook: renderHook,
),
onTap: onPressed,
),
);
}
}

View File

@ -231,7 +231,7 @@ class LayoutDateField extends StatelessWidget {
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
constraints: BoxConstraints.loose(const Size(300, 400)), constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex, mutex: popoverMutex,
offset: const Offset(-16, 0), offset: const Offset(-14, 0),
popupBuilder: (context) { popupBuilder: (context) {
return BlocProvider( return BlocProvider(
create: (context) => getIt<DatabasePropertyBloc>( create: (context) => getIt<DatabasePropertyBloc>(
@ -349,7 +349,7 @@ class FirstDayOfWeek extends StatelessWidget {
constraints: BoxConstraints.loose(const Size(300, 400)), constraints: BoxConstraints.loose(const Size(300, 400)),
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click, triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
mutex: popoverMutex, mutex: popoverMutex,
offset: const Offset(-16, 0), offset: const Offset(-14, 0),
popupBuilder: (context) { popupBuilder: (context) {
final symbols = final symbols =
DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols; DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;

View File

@ -1,9 +1,15 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart'; import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/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/field_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/button.dart';
@ -15,11 +21,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class DatabaseGroupList extends StatelessWidget { class DatabaseGroupList extends StatelessWidget {
final String viewId; final String viewId;
final FieldController fieldController; final DatabaseController databaseController;
final VoidCallback onDismissed; final VoidCallback onDismissed;
const DatabaseGroupList({ const DatabaseGroupList({
required this.viewId, required this.viewId,
required this.fieldController, required this.databaseController,
required this.onDismissed, required this.onDismissed,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -29,31 +35,71 @@ class DatabaseGroupList extends StatelessWidget {
return BlocProvider( return BlocProvider(
create: (context) => DatabaseGroupBloc( create: (context) => DatabaseGroupBloc(
viewId: viewId, viewId: viewId,
fieldController: fieldController, databaseController: databaseController,
)..add(const DatabaseGroupEvent.initial()), )..add(const DatabaseGroupEvent.initial()),
child: BlocBuilder<DatabaseGroupBloc, DatabaseGroupState>( child: BlocBuilder<DatabaseGroupBloc, DatabaseGroupState>(
buildWhen: (previous, current) => true,
builder: (context, state) { builder: (context, state) {
final cells = state.fieldInfos.map((fieldInfo) { final showHideUngroupedToggle = state.fieldInfos.any(
Widget cell = _GridGroupCell( (field) =>
fieldInfo: fieldInfo, field.canBeGroup &&
onSelected: () => onDismissed(), field.isGroupField &&
key: ValueKey(fieldInfo.id), field.fieldType != FieldType.Checkbox,
); );
final children = [
if (!fieldInfo.canBeGroup) { if (showHideUngroupedToggle) ...[
cell = IgnorePointer(child: Opacity(opacity: 0.3, child: cell)); SizedBox(
} height: GridSize.popoverItemHeight,
return cell; child: Padding(
}).toList(); padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: Row(
children: [
Expanded(
child: FlowyText.medium(
LocaleKeys.board_showUngrouped.tr(),
),
),
Toggle(
value: !state.hideUngrouped,
onChanged: (value) =>
databaseController.updateGroupConfiguration(value),
style: ToggleStyle.big,
padding: EdgeInsets.zero,
),
],
),
),
),
const TypeOptionSeparator(spacing: 0),
],
SizedBox(
height: GridSize.popoverItemHeight,
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: FlowyText.medium(
LocaleKeys.board_groupBy.tr(),
textAlign: TextAlign.left,
color: Theme.of(context).hintColor,
),
),
),
...state.fieldInfos.where((fieldInfo) => fieldInfo.canBeGroup).map(
(fieldInfo) => _GridGroupCell(
fieldInfo: fieldInfo,
onSelected: onDismissed,
key: ValueKey(fieldInfo.id),
),
),
];
return ListView.separated( return ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: cells.length, itemCount: children.length,
itemBuilder: (BuildContext context, int index) => cells[index], itemBuilder: (BuildContext context, int index) => children[index],
separatorBuilder: (BuildContext context, int index) => separatorBuilder: (BuildContext context, int index) =>
VSpace(GridSize.typeOptionSeparatorHeight), VSpace(GridSize.typeOptionSeparatorHeight),
padding: const EdgeInsets.all(6.0), padding: const EdgeInsets.symmetric(vertical: 6.0),
); );
}, },
), ),
@ -82,26 +128,29 @@ class _GridGroupCell extends StatelessWidget {
return SizedBox( return SizedBox(
height: GridSize.popoverItemHeight, height: GridSize.popoverItemHeight,
child: FlowyButton( child: Padding(
hoverColor: AFThemeExtension.of(context).lightGreyHover, padding: const EdgeInsets.symmetric(horizontal: 6.0),
text: FlowyText.medium( child: FlowyButton(
fieldInfo.name, hoverColor: AFThemeExtension.of(context).lightGreyHover,
color: AFThemeExtension.of(context).textColor, text: FlowyText.medium(
fieldInfo.name,
color: AFThemeExtension.of(context).textColor,
),
leftIcon: FlowySvg(
fieldInfo.fieldType.icon(),
color: Theme.of(context).iconTheme.color,
),
rightIcon: rightIcon,
onTap: () {
context.read<DatabaseGroupBloc>().add(
DatabaseGroupEvent.setGroupByField(
fieldInfo.id,
fieldInfo.fieldType,
),
);
onSelected();
},
), ),
leftIcon: FlowySvg(
fieldInfo.fieldType.icon(),
color: Theme.of(context).iconTheme.color,
),
rightIcon: rightIcon,
onTap: () {
context.read<DatabaseGroupBloc>().add(
DatabaseGroupEvent.setGroupByField(
fieldInfo.id,
fieldInfo.fieldType,
),
);
onSelected();
},
), ),
); );
} }

View File

@ -170,7 +170,7 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction {
), ),
DatabaseSettingAction.showGroup => DatabaseGroupList( DatabaseSettingAction.showGroup => DatabaseGroupList(
viewId: databaseController.viewId, viewId: databaseController.viewId,
fieldController: databaseController.fieldController, databaseController: databaseController,
onDismissed: () {}, onDismissed: () {},
), ),
DatabaseSettingAction.showProperties => DatabasePropertyList( DatabaseSettingAction.showProperties => DatabasePropertyList(
@ -191,7 +191,7 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction {
direction: PopoverDirection.leftWithTopAligned, direction: PopoverDirection.leftWithTopAligned,
mutex: popoverMutex, mutex: popoverMutex,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
offset: const Offset(-16, 0), offset: const Offset(-14, 0),
child: SizedBox( child: SizedBox(
height: GridSize.popoverItemHeight, height: GridSize.popoverItemHeight,
child: FlowyButton( child: FlowyButton(

View File

@ -35,7 +35,7 @@ void main() {
final checkboxField = context.fieldContexts.last.field; final checkboxField = context.fieldContexts.last.field;
final gridGroupBloc = DatabaseGroupBloc( final gridGroupBloc = DatabaseGroupBloc(
viewId: context.gridView.id, viewId: context.gridView.id,
fieldController: context.fieldController, databaseController: context.databaseController,
)..add(const DatabaseGroupEvent.initial()); )..add(const DatabaseGroupEvent.initial());
gridGroupBloc.add( gridGroupBloc.add(
DatabaseGroupEvent.setGroupByField( DatabaseGroupEvent.setGroupByField(

View File

@ -27,7 +27,7 @@ void main() {
// set grouped by the new multi-select field" // set grouped by the new multi-select field"
final gridGroupBloc = DatabaseGroupBloc( final gridGroupBloc = DatabaseGroupBloc(
viewId: context.gridView.id, viewId: context.gridView.id,
fieldController: context.fieldController, databaseController: context.databaseController,
)..add(const DatabaseGroupEvent.initial()); )..add(const DatabaseGroupEvent.initial());
await boardResponseFuture(); await boardResponseFuture();
@ -82,7 +82,7 @@ void main() {
// set grouped by the new multi-select field" // set grouped by the new multi-select field"
final gridGroupBloc = DatabaseGroupBloc( final gridGroupBloc = DatabaseGroupBloc(
viewId: context.gridView.id, viewId: context.gridView.id,
fieldController: context.fieldController, databaseController: context.databaseController,
)..add(const DatabaseGroupEvent.initial()); )..add(const DatabaseGroupEvent.initial());
await boardResponseFuture(); await boardResponseFuture();

View File

@ -78,6 +78,8 @@ class BoardTestContext {
return _boardDataController.fieldController; return _boardDataController.fieldController;
} }
DatabaseController get databaseController => _boardDataController;
FieldEditorBloc makeFieldEditor({ FieldEditorBloc makeFieldEditor({
required FieldInfo fieldInfo, required FieldInfo fieldInfo,
}) { }) {

View File

@ -748,6 +748,10 @@
"renameGroupTooltip": "Press to rename group" "renameGroupTooltip": "Press to rename group"
}, },
"menuName": "Board", "menuName": "Board",
"showUngrouped": "Show ungrouped items",
"ungroupedButtonText": "Ungrouped",
"ungroupedItemsTitle": "Click to add to the board",
"groupBy": "Group by",
"referencedBoardPrefix": "View of" "referencedBoardPrefix": "View of"
}, },
"calendar": { "calendar": {

View File

@ -5,7 +5,7 @@ use flowy_error::ErrorCode;
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::{FieldType, RowMetaPB}; use crate::entities::{FieldType, RowMetaPB};
use crate::services::group::{GroupChangeset, GroupData, GroupSetting}; use crate::services::group::{GroupChangeset, GroupData, GroupSetting, GroupSettingChangeset};
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GroupSettingPB { pub struct GroupSettingPB {
@ -14,6 +14,9 @@ pub struct GroupSettingPB {
#[pb(index = 2)] #[pb(index = 2)]
pub field_id: String, pub field_id: String,
#[pb(index = 3)]
pub hide_ungrouped: bool,
} }
impl std::convert::From<&GroupSetting> for GroupSettingPB { impl std::convert::From<&GroupSetting> for GroupSettingPB {
@ -21,6 +24,7 @@ impl std::convert::From<&GroupSetting> for GroupSettingPB {
GroupSettingPB { GroupSettingPB {
id: rev.id.clone(), id: rev.id.clone(),
field_id: rev.field_id.clone(), field_id: rev.field_id.clone(),
hide_ungrouped: rev.hide_ungrouped,
} }
} }
} }
@ -48,6 +52,26 @@ impl std::convert::From<Vec<GroupSetting>> for RepeatedGroupSettingPB {
} }
} }
#[derive(Debug, Default, ProtoBuf)]
pub struct GroupSettingChangesetPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub group_configuration_id: String,
#[pb(index = 3, one_of)]
pub hide_ungrouped: Option<bool>,
}
impl From<GroupSettingChangesetPB> for GroupSettingChangeset {
fn from(value: GroupSettingChangesetPB) -> Self {
Self {
hide_ungrouped: value.hide_ungrouped,
}
}
}
#[derive(ProtoBuf, Debug, Default, Clone)] #[derive(ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedGroupPB { pub struct RepeatedGroupPB {
#[pb(index = 1)] #[pb(index = 1)]

View File

@ -15,7 +15,7 @@ use crate::services::field::{
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset, type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
}; };
use crate::services::field_settings::FieldSettingsChangesetParams; use crate::services::field_settings::FieldSettingsChangesetParams;
use crate::services::group::{GroupChangeset, GroupSettingChangeset}; use crate::services::group::{GroupChangeset, GroupChangesets};
use crate::services::share::csv::CSVFormat; use crate::services::share::csv::CSVFormat;
fn upgrade_manager( fn upgrade_manager(
@ -645,6 +645,36 @@ pub(crate) async fn update_date_cell_handler(
Ok(()) Ok(())
} }
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn get_group_configurations_handler(
data: AFPluginData<DatabaseViewIdPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> DataResult<RepeatedGroupSettingPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let params = data.into_inner();
let database_editor = manager.get_database_with_view_id(params.as_ref()).await?;
let group_configs = database_editor
.get_group_configuration_settings(params.as_ref())
.await?;
data_result_ok(group_configs.into())
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn update_group_configuration_handler(
data: AFPluginData<GroupSettingChangesetPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?;
let params = data.into_inner();
let view_id = params.view_id.clone();
let database_editor = manager.get_database_with_view_id(&view_id).await?;
database_editor
.update_group_configuration_setting(&view_id, params.into())
.await?;
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)] #[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn get_groups_handler( pub(crate) async fn get_groups_handler(
data: AFPluginData<DatabaseViewIdPB>, data: AFPluginData<DatabaseViewIdPB>,
@ -701,7 +731,7 @@ pub(crate) async fn update_group_handler(
database_editor database_editor
.update_group_setting( .update_group_setting(
&view_id, &view_id,
GroupSettingChangeset { GroupChangesets {
update_groups: vec![group_changeset], update_groups: vec![group_changeset],
}, },
) )

View File

@ -54,11 +54,13 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
// Date // Date
.event(DatabaseEvent::UpdateDateCell, update_date_cell_handler) .event(DatabaseEvent::UpdateDateCell, update_date_cell_handler)
// Group // Group
.event(DatabaseEvent::GetGroupConfigurations, get_group_configurations_handler)
.event(DatabaseEvent::UpdateGroupConfiguration, update_group_configuration_handler)
.event(DatabaseEvent::SetGroupByField, set_group_by_field_handler)
.event(DatabaseEvent::MoveGroup, move_group_handler) .event(DatabaseEvent::MoveGroup, move_group_handler)
.event(DatabaseEvent::MoveGroupRow, move_group_row_handler) .event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
.event(DatabaseEvent::GetGroups, get_groups_handler) .event(DatabaseEvent::GetGroups, get_groups_handler)
.event(DatabaseEvent::GetGroup, get_group_handler) .event(DatabaseEvent::GetGroup, get_group_handler)
.event(DatabaseEvent::SetGroupByField, set_group_by_field_handler)
.event(DatabaseEvent::UpdateGroup, update_group_handler) .event(DatabaseEvent::UpdateGroup, update_group_handler)
// Database // Database
.event(DatabaseEvent::GetDatabases, get_databases_handler) .event(DatabaseEvent::GetDatabases, get_databases_handler)
@ -264,6 +266,15 @@ pub enum DatabaseEvent {
#[event(input = "DateChangesetPB")] #[event(input = "DateChangesetPB")]
UpdateDateCell = 80, UpdateDateCell = 80,
#[event(input = "DatabaseViewIdPB", output = "RepeatedGroupSettingPB")]
GetGroupConfigurations = 90,
#[event(input = "GroupSettingChangesetPB")]
UpdateGroupConfiguration = 91,
#[event(input = "GroupByFieldPayloadPB")]
SetGroupByField = 92,
#[event(input = "DatabaseViewIdPB", output = "RepeatedGroupPB")] #[event(input = "DatabaseViewIdPB", output = "RepeatedGroupPB")]
GetGroups = 100, GetGroups = 100,
@ -276,11 +287,8 @@ pub enum DatabaseEvent {
#[event(input = "MoveGroupRowPayloadPB")] #[event(input = "MoveGroupRowPayloadPB")]
MoveGroupRow = 112, MoveGroupRow = 112,
#[event(input = "GroupByFieldPayloadPB")]
SetGroupByField = 113,
#[event(input = "UpdateGroupPB")] #[event(input = "UpdateGroupPB")]
UpdateGroup = 114, UpdateGroup = 113,
/// Returns all the databases /// Returns all the databases
#[event(output = "RepeatedDatabaseDescriptionPB")] #[event(output = "RepeatedDatabaseDescriptionPB")]

View File

@ -20,6 +20,8 @@ pub enum DatabaseNotification {
DidUpdateCell = 40, DidUpdateCell = 40,
/// Trigger after editing a field properties including rename,update type option, etc /// Trigger after editing a field properties including rename,update type option, etc
DidUpdateField = 50, DidUpdateField = 50,
/// Trigger after the group configuration is changed
DidUpdateGroupConfiguration = 59,
/// Trigger after the number of groups is changed /// Trigger after the number of groups is changed
DidUpdateNumOfGroups = 60, DidUpdateNumOfGroups = 60,
/// Trigger after inserting/deleting/updating/moving a row /// Trigger after inserting/deleting/updating/moving a row
@ -69,6 +71,7 @@ impl std::convert::From<i32> for DatabaseNotification {
22 => DatabaseNotification::DidUpdateFields, 22 => DatabaseNotification::DidUpdateFields,
40 => DatabaseNotification::DidUpdateCell, 40 => DatabaseNotification::DidUpdateCell,
50 => DatabaseNotification::DidUpdateField, 50 => DatabaseNotification::DidUpdateField,
59 => DatabaseNotification::DidUpdateGroupConfiguration,
60 => DatabaseNotification::DidUpdateNumOfGroups, 60 => DatabaseNotification::DidUpdateNumOfGroups,
61 => DatabaseNotification::DidUpdateGroupRow, 61 => DatabaseNotification::DidUpdateGroupRow,
62 => DatabaseNotification::DidGroupByField, 62 => DatabaseNotification::DidGroupByField,

View File

@ -32,7 +32,8 @@ use crate::services::field_settings::{
}; };
use crate::services::filter::Filter; use crate::services::filter::Filter;
use crate::services::group::{ use crate::services::group::{
default_group_setting, GroupChangeset, GroupSetting, GroupSettingChangeset, RowChangeset, default_group_setting, GroupChangeset, GroupChangesets, GroupSetting, GroupSettingChangeset,
RowChangeset,
}; };
use crate::services::share::csv::{CSVExport, CSVFormat}; use crate::services::share::csv::{CSVExport, CSVFormat};
use crate::services::sort::Sort; use crate::services::sort::Sort;
@ -179,11 +180,11 @@ impl DatabaseEditor {
pub async fn update_group_setting( pub async fn update_group_setting(
&self, &self,
view_id: &str, view_id: &str,
group_setting_changeset: GroupSettingChangeset, group_setting_changeset: GroupChangesets,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
let view_editor = self.database_views.get_view_editor(view_id).await?; let view_editor = self.database_views.get_view_editor(view_id).await?;
view_editor view_editor
.update_group_setting(group_setting_changeset) .v_update_group_setting(group_setting_changeset)
.await?; .await?;
Ok(()) Ok(())
} }
@ -907,6 +908,40 @@ impl DatabaseEditor {
Ok(()) Ok(())
} }
pub async fn get_group_configuration_settings(
&self,
view_id: &str,
) -> FlowyResult<Vec<GroupSettingPB>> {
let view = self.database_views.get_view_editor(view_id).await?;
let group_settings = view
.v_get_group_configuration_settings()
.await
.into_iter()
.map(|value| GroupSettingPB::from(&value))
.collect::<Vec<GroupSettingPB>>();
Ok(group_settings)
}
pub async fn update_group_configuration_setting(
&self,
view_id: &str,
changeset: GroupSettingChangeset,
) -> FlowyResult<()> {
let view = self.database_views.get_view_editor(view_id).await?;
let group_configuration = view.v_update_group_configuration_setting(changeset).await?;
if let Some(configuration) = group_configuration {
let payload: RepeatedGroupSettingPB = vec![configuration].into();
send_notification(view_id, DatabaseNotification::DidUpdateGroupConfiguration)
.payload(payload)
.send();
}
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)] #[tracing::instrument(level = "trace", skip_all, err)]
pub async fn load_groups(&self, view_id: &str) -> FlowyResult<RepeatedGroupPB> { pub async fn load_groups(&self, view_id: &str) -> FlowyResult<RepeatedGroupPB> {
let view = self.database_views.get_view_editor(view_id).await?; let view = self.database_views.get_view_editor(view_id).await?;

View File

@ -37,8 +37,8 @@ use crate::services::filter::{
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType, Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
}; };
use crate::services::group::{ use crate::services::group::{
GroupChangeset, GroupController, GroupSetting, GroupSettingChangeset, MoveGroupRowContext, GroupChangeset, GroupChangesets, GroupController, GroupSetting, GroupSettingChangeset,
RowChangeset, MoveGroupRowContext, RowChangeset,
}; };
use crate::services::setting::CalendarLayoutSetting; use crate::services::setting::CalendarLayoutSetting;
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType}; use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
@ -407,6 +407,7 @@ impl DatabaseViewEditor {
} }
} }
} }
/// Only call once after database view editor initialized /// Only call once after database view editor initialized
#[tracing::instrument(level = "trace", skip(self))] #[tracing::instrument(level = "trace", skip(self))]
pub async fn v_load_groups(&self) -> Option<Vec<GroupPB>> { pub async fn v_load_groups(&self) -> Option<Vec<GroupPB>> {
@ -471,7 +472,20 @@ impl DatabaseViewEditor {
Ok(()) Ok(())
} }
pub async fn update_group_setting(&self, changeset: GroupSettingChangeset) -> FlowyResult<()> { pub async fn v_update_group_configuration_setting(
&self,
changeset: GroupSettingChangeset,
) -> FlowyResult<Option<GroupSetting>> {
let result = self
.mut_group_controller(|group_controller, _| {
group_controller.apply_group_configuration_setting_changeset(changeset)
})
.await;
Ok(result.flatten())
}
pub async fn v_update_group_setting(&self, changeset: GroupChangesets) -> FlowyResult<()> {
self self
.mut_group_controller(|group_controller, _| { .mut_group_controller(|group_controller, _| {
group_controller.apply_group_setting_changeset(changeset) group_controller.apply_group_setting_changeset(changeset)
@ -480,6 +494,10 @@ impl DatabaseViewEditor {
Ok(()) Ok(())
} }
pub async fn v_get_group_configuration_settings(&self) -> Vec<GroupSetting> {
self.delegate.get_group_setting(&self.view_id)
}
pub async fn update_group( pub async fn update_group(
&self, &self,
changeset: GroupChangeset, changeset: GroupChangeset,

View File

@ -6,7 +6,8 @@ use flowy_error::FlowyResult;
use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB}; use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
use crate::services::cell::DecodedCellData; use crate::services::cell::DecodedCellData;
use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::controller::MoveGroupRowContext;
use crate::services::group::{GroupData, GroupSettingChangeset}; use crate::services::group::entities::GroupSetting;
use crate::services::group::{GroupChangesets, GroupData, GroupSettingChangeset};
/// Using polymorphism to provides the customs action for different group controller. /// Using polymorphism to provides the customs action for different group controller.
/// ///
@ -103,7 +104,12 @@ pub trait GroupControllerOperation: Send + Sync {
/// Update the group if the corresponding field is changed /// Update the group if the corresponding field is changed
fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>; fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>;
fn apply_group_setting_changeset(&mut self, changeset: GroupSettingChangeset) -> FlowyResult<()>; fn apply_group_setting_changeset(&mut self, changeset: GroupChangesets) -> FlowyResult<()>;
fn apply_group_configuration_setting_changeset(
&mut self,
changeset: GroupSettingChangeset,
) -> FlowyResult<Option<GroupSetting>>;
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -15,6 +15,7 @@ use crate::entities::{GroupChangesPB, GroupPB, InsertedGroupPB};
use crate::services::field::RowSingleCellData; use crate::services::field::RowSingleCellData;
use crate::services::group::{ use crate::services::group::{
default_group_setting, GeneratedGroups, Group, GroupChangeset, GroupData, GroupSetting, default_group_setting, GeneratedGroups, Group, GroupChangeset, GroupData, GroupSetting,
GroupSettingChangeset,
}; };
pub trait GroupSettingReader: Send + Sync + 'static { pub trait GroupSettingReader: Send + Sync + 'static {
@ -374,6 +375,20 @@ where
Ok(()) Ok(())
} }
pub(crate) fn update_configuration(
&mut self,
changeset: GroupSettingChangeset,
) -> FlowyResult<Option<GroupSetting>> {
self.mut_configuration(|configuration| match changeset.hide_ungrouped {
Some(value) if value != configuration.hide_ungrouped => {
configuration.hide_ungrouped = value;
true
},
_ => false,
})?;
Ok(Some(GroupSetting::clone(&self.setting)))
}
pub(crate) async fn get_all_cells(&self) -> Vec<RowSingleCellData> { pub(crate) async fn get_all_cells(&self) -> Vec<RowSingleCellData> {
self self
.reader .reader
@ -402,7 +417,9 @@ where
let view_id = self.view_id.clone(); let view_id = self.view_id.clone();
tokio::spawn(async move { tokio::spawn(async move {
match writer.save_configuration(&view_id, configuration).await { match writer.save_configuration(&view_id, configuration).await {
Ok(_) => {}, Ok(_) => {
tracing::trace!("SUCCESSFULLY SAVED CONFIGURATION"); // TODO(richard): remove this
},
Err(e) => { Err(e) => {
tracing::error!("Save group configuration failed: {}", e); tracing::error!("Save group configuration failed: {}", e);
}, },

View File

@ -17,8 +17,8 @@ use crate::services::group::action::{
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize, DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize,
}; };
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
use crate::services::group::entities::GroupData; use crate::services::group::entities::{GroupData, GroupSetting};
use crate::services::group::{Group, GroupSettingChangeset}; use crate::services::group::{Group, GroupChangesets, GroupSettingChangeset};
// use collab_database::views::Group; // use collab_database::views::Group;
@ -137,8 +137,6 @@ where
}) })
} }
// https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
#[allow(clippy::needless_collect)]
fn update_no_status_group( fn update_no_status_group(
&mut self, &mut self,
row_detail: &RowDetail, row_detail: &RowDetail,
@ -382,7 +380,7 @@ where
Ok(None) Ok(None)
} }
fn apply_group_setting_changeset(&mut self, changeset: GroupSettingChangeset) -> FlowyResult<()> { fn apply_group_setting_changeset(&mut self, changeset: GroupChangesets) -> FlowyResult<()> {
for group_changeset in changeset.update_groups { for group_changeset in changeset.update_groups {
if let Err(e) = self.context.update_group(group_changeset) { if let Err(e) = self.context.update_group(group_changeset) {
tracing::error!("Failed to update group: {:?}", e); tracing::error!("Failed to update group: {:?}", e);
@ -390,6 +388,13 @@ where
} }
Ok(()) Ok(())
} }
fn apply_group_configuration_setting_changeset(
&mut self,
changeset: GroupSettingChangeset,
) -> FlowyResult<Option<GroupSetting>> {
self.context.update_configuration(changeset)
}
} }
struct GroupedRow { struct GroupedRow {

View File

@ -10,7 +10,8 @@ use crate::services::group::action::{
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation,
}; };
use crate::services::group::{ use crate::services::group::{
GroupController, GroupData, GroupSettingChangeset, MoveGroupRowContext, GroupChangesets, GroupController, GroupData, GroupSetting, GroupSettingChangeset,
MoveGroupRowContext,
}; };
/// A [DefaultGroupController] is used to handle the group actions for the [FieldType] that doesn't /// A [DefaultGroupController] is used to handle the group actions for the [FieldType] that doesn't
@ -101,11 +102,15 @@ impl GroupControllerOperation for DefaultGroupController {
Ok(None) Ok(None)
} }
fn apply_group_setting_changeset( fn apply_group_setting_changeset(&mut self, _changeset: GroupChangesets) -> FlowyResult<()> {
Ok(())
}
fn apply_group_configuration_setting_changeset(
&mut self, &mut self,
_changeset: GroupSettingChangeset, _changeset: GroupSettingChangeset,
) -> FlowyResult<()> { ) -> FlowyResult<Option<GroupSetting>> {
Ok(()) Ok(None)
} }
} }

View File

@ -12,9 +12,14 @@ pub struct GroupSetting {
pub field_type: i64, pub field_type: i64,
pub groups: Vec<Group>, pub groups: Vec<Group>,
pub content: String, pub content: String,
pub hide_ungrouped: bool,
} }
pub struct GroupSettingChangeset { pub struct GroupSettingChangeset {
pub hide_ungrouped: Option<bool>,
}
pub struct GroupChangesets {
pub update_groups: Vec<GroupChangeset>, pub update_groups: Vec<GroupChangeset>,
} }
@ -27,13 +32,14 @@ pub struct GroupChangeset {
} }
impl GroupSetting { impl GroupSetting {
pub fn new(field_id: String, field_type: i64, content: String) -> Self { pub fn new(field_id: String, field_type: i64, content: String, hide_ungrouped: bool) -> Self {
Self { Self {
id: gen_database_group_id(), id: gen_database_group_id(),
field_id, field_id,
field_type, field_type,
groups: vec![], groups: vec![],
content, content,
hide_ungrouped,
} }
} }
} }
@ -43,6 +49,7 @@ const FIELD_ID: &str = "field_id";
const FIELD_TYPE: &str = "ty"; const FIELD_TYPE: &str = "ty";
const GROUPS: &str = "groups"; const GROUPS: &str = "groups";
const CONTENT: &str = "content"; const CONTENT: &str = "content";
const HIDE_UNGROUPED: &str = "hide_ungrouped";
impl TryFrom<GroupSettingMap> for GroupSetting { impl TryFrom<GroupSettingMap> for GroupSetting {
type Error = anyhow::Error; type Error = anyhow::Error;
@ -52,8 +59,9 @@ impl TryFrom<GroupSettingMap> for GroupSetting {
value.get_str_value(GROUP_ID), value.get_str_value(GROUP_ID),
value.get_str_value(FIELD_ID), value.get_str_value(FIELD_ID),
value.get_i64_value(FIELD_TYPE), value.get_i64_value(FIELD_TYPE),
value.get_bool_value(HIDE_UNGROUPED),
) { ) {
(Some(id), Some(field_id), Some(field_type)) => { (Some(id), Some(field_id), Some(field_type), Some(hide_ungrouped)) => {
let content = value.get_str_value(CONTENT).unwrap_or_default(); let content = value.get_str_value(CONTENT).unwrap_or_default();
let groups = value.try_get_array(GROUPS); let groups = value.try_get_array(GROUPS);
Ok(Self { Ok(Self {
@ -62,6 +70,7 @@ impl TryFrom<GroupSettingMap> for GroupSetting {
field_type, field_type,
groups, groups,
content, content,
hide_ungrouped,
}) })
}, },
_ => { _ => {
@ -79,6 +88,7 @@ impl From<GroupSetting> for GroupSettingMap {
.insert_i64_value(FIELD_TYPE, setting.field_type) .insert_i64_value(FIELD_TYPE, setting.field_type)
.insert_maps(GROUPS, setting.groups) .insert_maps(GROUPS, setting.groups)
.insert_str_value(CONTENT, setting.content) .insert_str_value(CONTENT, setting.content)
.insert_bool_value(HIDE_UNGROUPED, setting.hide_ungrouped)
.build() .build()
} }
} }

View File

@ -154,7 +154,7 @@ pub fn find_new_grouping_field(
/// ///
pub fn default_group_setting(field: &Field) -> GroupSetting { pub fn default_group_setting(field: &Field) -> GroupSetting {
let field_id = field.id.clone(); let field_id = field.id.clone();
GroupSetting::new(field_id, field.field_type, "".to_owned()) GroupSetting::new(field_id, field.field_type, "".to_owned(), false)
} }
pub fn make_no_status_group(field: &Field) -> Group { pub fn make_no_status_group(field: &Field) -> Group {

View File

@ -8,5 +8,5 @@ mod group_builder;
pub(crate) use configuration::*; pub(crate) use configuration::*;
pub(crate) use controller::*; pub(crate) use controller::*;
pub(crate) use controller_impls::*; pub(crate) use controller_impls::*;
pub(crate) use entities::*; pub use entities::*;
pub(crate) use group_builder::*; pub(crate) use group_builder::*;

View File

@ -59,7 +59,7 @@ impl DatabaseEditorTest {
let _ = sdk.init_anon_user().await; let _ = sdk.init_anon_user().await;
let params = make_test_board(); let params = make_test_board();
let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await; let view_test = ViewTest::new_board_view(&sdk, params.to_json_bytes().unwrap()).await;
Self::new(sdk, view_test).await Self::new(sdk, view_test).await
} }

View File

@ -10,6 +10,7 @@ use flowy_database2::services::field::{
edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction, edit_single_select_type_option, SelectOption, SelectTypeOptionSharedAction,
SingleSelectTypeOption, SingleSelectTypeOption,
}; };
use flowy_database2::services::group::GroupSettingChangeset;
use lib_infra::util::timestamp; use lib_infra::util::timestamp;
use crate::database::database_editor::DatabaseEditorTest; use crate::database::database_editor::DatabaseEditorTest;
@ -67,6 +68,12 @@ pub enum GroupScript {
group_id: String, group_id: String,
group_name: String, group_name: String,
}, },
AssertGroupConfiguration {
hide_ungrouped: bool,
},
UpdateGroupConfiguration {
hide_ungrouped: Option<bool>,
},
} }
pub struct DatabaseGroupTest { pub struct DatabaseGroupTest {
@ -269,6 +276,25 @@ impl DatabaseGroupTest {
assert_eq!(group_id, group.group_id, "group index: {}", group_index); assert_eq!(group_id, group.group_id, "group index: {}", group_index);
assert_eq!(group_name, group.group_name, "group index: {}", group_index); assert_eq!(group_name, group.group_name, "group index: {}", group_index);
}, },
GroupScript::AssertGroupConfiguration { hide_ungrouped } => {
let group_configuration = self
.editor
.get_group_configuration_settings(&self.view_id)
.await
.unwrap();
let group_configuration = group_configuration.get(0).unwrap();
assert_eq!(group_configuration.hide_ungrouped, hide_ungrouped);
},
GroupScript::UpdateGroupConfiguration { hide_ungrouped } => {
self
.editor
.update_group_configuration_setting(
&self.view_id,
GroupSettingChangeset { hide_ungrouped },
)
.await
.unwrap();
},
} }
} }

View File

@ -28,6 +28,23 @@ async fn group_init_test() {
test.run_scripts(scripts).await; test.run_scripts(scripts).await;
} }
// #[tokio::test]
// async fn group_configuration_setting_test() {
// let mut test = DatabaseGroupTest::new().await;
// let scripts = vec![
// AssertGroupConfiguration {
// hide_ungrouped: false,
// },
// UpdateGroupConfiguration {
// hide_ungrouped: Some(true),
// },
// AssertGroupConfiguration {
// hide_ungrouped: true,
// },
// ];
// test.run_scripts(scripts).await;
// }
#[tokio::test] #[tokio::test]
async fn group_move_row_test() { async fn group_move_row_test() {
let mut test = DatabaseGroupTest::new().await; let mut test = DatabaseGroupTest::new().await;