mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: delete kanban board groups (#3925)
* feat: hide/unhide ui * chore: implement collapsible side bar and adjust group header (#2) * refactor: hidden columns into own file * chore: adjust new group button position * fix: flowy icon buton secondary color bleed * chore: some UI adjustments * fix: some regressions * chore: proper group is_visible fetching * chore: use a bloc to manage hidden groups * fix: hiding groups not working * chore: implement hidden group popups * chore: proper ungrouped item column management * chore: remove ungrouped items button * chore: flowy hover build * fix: clean up code * test: integration tests * fix: not null promise on null value * fix: hide and unhide multiple groups * chore: i18n and code review * chore: missed review * fix: rust-lib-test * fix: dont completely remove flowyiconhovercolor * chore: apply suggest * fix: number of rows inside hidden groups not updating properly * fix: hidden groups disappearing after collapse * fix: hidden group title alignment * fix: insert newly unhidden groups into the correct position * chore: adjust padding all around * feat: reorder hidden groups * chore: adjust padding * chore: collapse hidden groups section persist * chore: no status group at beginning * fix: hiding groups when grouping with other types * chore: disable rename groups that arent supported * chore: update appflowy board ref * chore: better naming * feat: delete kanban groups * chore: forgot to save * chore: fix build and small ui adjustments * chore: add a confirm dialog when deleting a column * fix: flutter lint * test: add integration test * chore: fix some design review issues * chore: apply suggestions from Nathan * fix: write lock on group controller --------- Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
This commit is contained in:
parent
3595de5e12
commit
9d61ca0278
@ -3,15 +3,17 @@ import 'package:appflowy/plugins/database_view/board/presentation/widgets/board_
|
|||||||
import 'package:appflowy/plugins/database_view/board/presentation/widgets/board_hidden_groups.dart';
|
import 'package:appflowy/plugins/database_view/board/presentation/widgets/board_hidden_groups.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import '../util/database_test_op.dart';
|
||||||
import '../util/util.dart';
|
import '../util/util.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('board hide groups test', () {
|
group('board group options:', () {
|
||||||
testWidgets('expand/collapse hidden groups', (tester) async {
|
testWidgets('expand/collapse hidden groups', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
@ -76,6 +78,47 @@ void main() {
|
|||||||
expect(shownGroups, 4);
|
expect(shownGroups, 4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('delete a group', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Board);
|
||||||
|
|
||||||
|
expect(tester.widgetList(find.byType(BoardColumnHeader)).length, 4);
|
||||||
|
|
||||||
|
// tap group option button for the first group. Delete shouldn't show up
|
||||||
|
await tester.tapButton(
|
||||||
|
find
|
||||||
|
.descendant(
|
||||||
|
of: find.byType(BoardColumnHeader),
|
||||||
|
matching: find.byFlowySvg(FlowySvgs.details_horizontal_s),
|
||||||
|
)
|
||||||
|
.first,
|
||||||
|
);
|
||||||
|
expect(find.byFlowySvg(FlowySvgs.delete_s), findsNothing);
|
||||||
|
|
||||||
|
// dismiss the popup
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// tap group option button for the first group. Delete should show up
|
||||||
|
await tester.tapButton(
|
||||||
|
find
|
||||||
|
.descendant(
|
||||||
|
of: find.byType(BoardColumnHeader),
|
||||||
|
matching: find.byFlowySvg(FlowySvgs.details_horizontal_s),
|
||||||
|
)
|
||||||
|
.at(1),
|
||||||
|
);
|
||||||
|
expect(find.byFlowySvg(FlowySvgs.delete_s), findsOneWidget);
|
||||||
|
|
||||||
|
// Tap the delete button and confirm
|
||||||
|
await tester.tapButton(find.byFlowySvg(FlowySvgs.delete_s));
|
||||||
|
await tester.tapDialogOkButton();
|
||||||
|
|
||||||
|
// Expect number of groups to decrease by one
|
||||||
|
expect(tester.widgetList(find.byType(BoardColumnHeader)).length, 3);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FlowySvgFinder on CommonFinders {
|
extension FlowySvgFinder on CommonFinders {
|
||||||
|
@ -48,4 +48,14 @@ class GroupBackendService {
|
|||||||
|
|
||||||
return DatabaseEventCreateGroup(payload).send();
|
return DatabaseEventCreateGroup(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Either<Unit, FlowyError>> deleteGroup({
|
||||||
|
required String groupId,
|
||||||
|
}) {
|
||||||
|
final payload = DeleteGroupPayloadPB.create()
|
||||||
|
..viewId = viewId
|
||||||
|
..groupId = groupId;
|
||||||
|
|
||||||
|
return DatabaseEventDeleteGroup(payload).send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,10 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
final result = await groupBackendSvc.createGroup(name: name);
|
final result = await groupBackendSvc.createGroup(name: name);
|
||||||
result.fold((_) {}, (err) => Log.error(err));
|
result.fold((_) {}, (err) => Log.error(err));
|
||||||
},
|
},
|
||||||
|
deleteGroup: (groupId) async {
|
||||||
|
final result = await groupBackendSvc.deleteGroup(groupId: groupId);
|
||||||
|
result.fold((_) {}, (err) => Log.error(err));
|
||||||
|
},
|
||||||
didCreateRow: (group, row, int? index) {
|
didCreateRow: (group, row, int? index) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -496,6 +500,7 @@ class BoardEvent with _$BoardEvent {
|
|||||||
) = _ToggleGroupVisibility;
|
) = _ToggleGroupVisibility;
|
||||||
const factory BoardEvent.toggleHiddenSectionVisibility(bool isVisible) =
|
const factory BoardEvent.toggleHiddenSectionVisibility(bool isVisible) =
|
||||||
_ToggleHiddenSectionVisibility;
|
_ToggleHiddenSectionVisibility;
|
||||||
|
const factory BoardEvent.deleteGroup(String groupId) = _DeleteGroup;
|
||||||
const factory BoardEvent.reorderGroup(String fromGroupId, String toGroupId) =
|
const factory BoardEvent.reorderGroup(String fromGroupId, String toGroupId) =
|
||||||
_ReorderGroup;
|
_ReorderGroup;
|
||||||
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||||
|
@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/board/application/board_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/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/dialogs.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
@ -73,7 +74,6 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
|
|||||||
Widget title = Expanded(
|
Widget title = Expanded(
|
||||||
child: FlowyText.medium(
|
child: FlowyText.medium(
|
||||||
widget.groupData.headerData.groupName,
|
widget.groupData.headerData.groupName,
|
||||||
fontSize: 14,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -92,7 +92,6 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
|
|||||||
.add(BoardEvent.startEditingHeader(widget.groupData.id)),
|
.add(BoardEvent.startEditingHeader(widget.groupData.id)),
|
||||||
child: FlowyText.medium(
|
child: FlowyText.medium(
|
||||||
widget.groupData.headerData.groupName,
|
widget.groupData.headerData.groupName,
|
||||||
fontSize: 14,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -119,6 +118,7 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
|
|||||||
const HSpace(4),
|
const HSpace(4),
|
||||||
FlowyTooltip(
|
FlowyTooltip(
|
||||||
message: LocaleKeys.board_column_addToColumnTopTooltip.tr(),
|
message: LocaleKeys.board_column_addToColumnTopTooltip.tr(),
|
||||||
|
preferBelow: false,
|
||||||
child: FlowyIconButton(
|
child: FlowyIconButton(
|
||||||
width: 20,
|
width: 20,
|
||||||
icon: const FlowySvg(FlowySvgs.add_s),
|
icon: const FlowySvg(FlowySvgs.add_s),
|
||||||
@ -199,7 +199,7 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
|
|||||||
Widget _groupOptionsButton(BuildContext context) {
|
Widget _groupOptionsButton(BuildContext context) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
clickHandler: PopoverClickHandler.gestureDetector,
|
clickHandler: PopoverClickHandler.gestureDetector,
|
||||||
margin: const EdgeInsets.fromLTRB(8, 8, 8, 4),
|
margin: const EdgeInsets.all(8),
|
||||||
constraints: BoxConstraints.loose(const Size(168, 300)),
|
constraints: BoxConstraints.loose(const Size(168, 300)),
|
||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
child: FlowyIconButton(
|
child: FlowyIconButton(
|
||||||
@ -209,29 +209,31 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
|
|||||||
),
|
),
|
||||||
popupBuilder: (popoverContext) {
|
popupBuilder: (popoverContext) {
|
||||||
final customGroupData = widget.groupData.customData as GroupData;
|
final customGroupData = widget.groupData.customData as GroupData;
|
||||||
|
final isDefault = customGroupData.group.isDefault;
|
||||||
final menuItems = GroupOptions.values.toList();
|
final menuItems = GroupOptions.values.toList();
|
||||||
if (!customGroupData.fieldType.canEditHeader) {
|
if (!customGroupData.fieldType.canEditHeader || isDefault) {
|
||||||
menuItems.remove(GroupOptions.rename);
|
menuItems.remove(GroupOptions.rename);
|
||||||
}
|
}
|
||||||
return Column(
|
if (!customGroupData.fieldType.canDeleteGroup || isDefault) {
|
||||||
|
menuItems.remove(GroupOptions.delete);
|
||||||
|
}
|
||||||
|
return SeparatedColumn(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
separatorBuilder: () => const VSpace(4),
|
||||||
children: [
|
children: [
|
||||||
...menuItems.map(
|
...menuItems.map(
|
||||||
(action) => SizedBox(
|
(action) => SizedBox(
|
||||||
height: GridSize.popoverItemHeight,
|
height: GridSize.popoverItemHeight,
|
||||||
child: Padding(
|
child: FlowyButton(
|
||||||
padding: const EdgeInsets.only(bottom: 4.0),
|
leftIcon: FlowySvg(action.icon),
|
||||||
child: FlowyButton(
|
text: FlowyText.medium(
|
||||||
leftIcon: FlowySvg(action.icon),
|
action.text,
|
||||||
text: FlowyText.medium(
|
overflow: TextOverflow.ellipsis,
|
||||||
action.text,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
action.call(context, customGroupData.group);
|
|
||||||
PopoverContainer.of(popoverContext).close();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
action.call(context, customGroupData.group);
|
||||||
|
PopoverContainer.of(popoverContext).close();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -244,7 +246,8 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
|
|||||||
|
|
||||||
enum GroupOptions {
|
enum GroupOptions {
|
||||||
rename,
|
rename,
|
||||||
hide;
|
hide,
|
||||||
|
delete;
|
||||||
|
|
||||||
void call(BuildContext context, GroupPB group) {
|
void call(BuildContext context, GroupPB group) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
@ -258,16 +261,28 @@ enum GroupOptions {
|
|||||||
.read<BoardBloc>()
|
.read<BoardBloc>()
|
||||||
.add(BoardEvent.toggleGroupVisibility(group, false));
|
.add(BoardEvent.toggleGroupVisibility(group, false));
|
||||||
break;
|
break;
|
||||||
|
case delete:
|
||||||
|
NavigatorAlertDialog(
|
||||||
|
title: LocaleKeys.board_column_deleteColumnConfirmation.tr(),
|
||||||
|
confirm: () {
|
||||||
|
context
|
||||||
|
.read<BoardBloc>()
|
||||||
|
.add(BoardEvent.deleteGroup(group.groupId));
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FlowySvgData get icon => switch (this) {
|
FlowySvgData get icon => switch (this) {
|
||||||
rename => FlowySvgs.edit_s,
|
rename => FlowySvgs.edit_s,
|
||||||
hide => FlowySvgs.hide_s,
|
hide => FlowySvgs.hide_s,
|
||||||
|
delete => FlowySvgs.delete_s,
|
||||||
};
|
};
|
||||||
|
|
||||||
String get text => switch (this) {
|
String get text => switch (this) {
|
||||||
rename => LocaleKeys.board_column_renameColumn.tr(),
|
rename => LocaleKeys.board_column_renameColumn.tr(),
|
||||||
hide => LocaleKeys.board_column_hideColumn.tr(),
|
hide => LocaleKeys.board_column_hideColumn.tr(),
|
||||||
|
delete => LocaleKeys.board_column_deleteColumn.tr(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,6 @@ class HiddenGroupsColumn extends StatelessWidget {
|
|||||||
LocaleKeys
|
LocaleKeys
|
||||||
.board_hiddenGroupSection_sectionTitle
|
.board_hiddenGroupSection_sectionTitle
|
||||||
.tr(),
|
.tr(),
|
||||||
fontSize: 14,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
|
@ -65,4 +65,13 @@ extension FieldTypeListExtension on FieldType {
|
|||||||
FieldType.SingleSelect => true,
|
FieldType.SingleSelect => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool get canDeleteGroup => switch (this) {
|
||||||
|
FieldType.URL ||
|
||||||
|
FieldType.SingleSelect ||
|
||||||
|
FieldType.MultiSelect ||
|
||||||
|
FieldType.DateTime =>
|
||||||
|
true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -139,9 +139,13 @@ class _TextCellState extends State<TextCardCell> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: CardSizes.cardCellPadding,
|
padding: CardSizes.cardCellPadding,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (widget.showNotes) ...[
|
if (widget.showNotes) ...[
|
||||||
const FlowySvg(FlowySvgs.notes_s),
|
FlowySvg(
|
||||||
|
FlowySvgs.notes_s,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
const HSpace(4),
|
const HSpace(4),
|
||||||
],
|
],
|
||||||
Expanded(child: child),
|
Expanded(child: child),
|
||||||
|
@ -133,6 +133,7 @@ class _CreateFlowyAlertDialog extends State<NavigatorAlertDialog> {
|
|||||||
child: FlowyText.medium(
|
child: FlowyText.medium(
|
||||||
widget.title,
|
widget.title,
|
||||||
fontSize: FontSizes.s16,
|
fontSize: FontSizes.s16,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
color: Theme.of(context).colorScheme.tertiary,
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
),
|
),
|
||||||
|
@ -810,7 +810,9 @@
|
|||||||
"createNewColumn": "Add a new group",
|
"createNewColumn": "Add a new group",
|
||||||
"addToColumnTopTooltip": "Add a new card at the top",
|
"addToColumnTopTooltip": "Add a new card at the top",
|
||||||
"renameColumn": "Rename",
|
"renameColumn": "Rename",
|
||||||
"hideColumn": "Hide"
|
"hideColumn": "Hide",
|
||||||
|
"deleteColumn": "Delete",
|
||||||
|
"deleteColumnConfirmation": "This will delete this group and all the cards in it.\nAre you sure you want to continue?"
|
||||||
},
|
},
|
||||||
"hiddenGroupSection": {
|
"hiddenGroupSection": {
|
||||||
"sectionTitle": "Hidden Groups",
|
"sectionTitle": "Hidden Groups",
|
||||||
@ -1118,4 +1120,4 @@
|
|||||||
"font": "Font",
|
"font": "Font",
|
||||||
"actions": "Actions"
|
"actions": "Actions"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -426,6 +426,18 @@ impl EventIntegrationTest {
|
|||||||
.error()
|
.error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn delete_group(&self, view_id: &str, group_id: &str) -> Option<FlowyError> {
|
||||||
|
EventBuilder::new(self.clone())
|
||||||
|
.event(DatabaseEvent::DeleteGroup)
|
||||||
|
.payload(DeleteGroupPayloadPB {
|
||||||
|
view_id: view_id.to_string(),
|
||||||
|
group_id: group_id.to_string(),
|
||||||
|
})
|
||||||
|
.async_send()
|
||||||
|
.await
|
||||||
|
.error()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update_setting(&self, changeset: DatabaseSettingChangesetPB) -> Option<FlowyError> {
|
pub async fn update_setting(&self, changeset: DatabaseSettingChangesetPB) -> Option<FlowyError> {
|
||||||
EventBuilder::new(self.clone())
|
EventBuilder::new(self.clone())
|
||||||
.event(DatabaseEvent::UpdateDatabaseSetting)
|
.event(DatabaseEvent::UpdateDatabaseSetting)
|
||||||
|
@ -1,5 +1,139 @@
|
|||||||
use event_integration::EventIntegrationTest;
|
use event_integration::EventIntegrationTest;
|
||||||
|
|
||||||
|
// The number of groups should be 0 if there is no group by field in grid
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_groups_event_with_grid_test() {
|
||||||
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
|
let current_workspace = test.get_current_workspace().await;
|
||||||
|
let grid_view = test
|
||||||
|
.create_grid(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let groups = test.get_groups(&grid_view.id).await;
|
||||||
|
assert_eq!(groups.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_groups_event_test() {
|
||||||
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
|
let current_workspace = test.get_current_workspace().await;
|
||||||
|
let board_view = test
|
||||||
|
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups.len(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn move_group_event_test() {
|
||||||
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
|
let current_workspace = test.get_current_workspace().await;
|
||||||
|
let board_view = test
|
||||||
|
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups.len(), 4);
|
||||||
|
let group_1 = groups[0].group_id.clone();
|
||||||
|
let group_2 = groups[1].group_id.clone();
|
||||||
|
let group_3 = groups[2].group_id.clone();
|
||||||
|
let group_4 = groups[3].group_id.clone();
|
||||||
|
|
||||||
|
let error = test.move_group(&board_view.id, &group_2, &group_3).await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups[0].group_id, group_1);
|
||||||
|
assert_eq!(groups[1].group_id, group_3);
|
||||||
|
assert_eq!(groups[2].group_id, group_2);
|
||||||
|
assert_eq!(groups[3].group_id, group_4);
|
||||||
|
|
||||||
|
let error = test.move_group(&board_view.id, &group_1, &group_4).await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups[0].group_id, group_3);
|
||||||
|
assert_eq!(groups[1].group_id, group_2);
|
||||||
|
assert_eq!(groups[2].group_id, group_4);
|
||||||
|
assert_eq!(groups[3].group_id, group_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn move_group_event_with_invalid_id_test() {
|
||||||
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
|
let current_workspace = test.get_current_workspace().await;
|
||||||
|
let board_view = test
|
||||||
|
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Empty to group id
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
let error = test
|
||||||
|
.move_group(&board_view.id, &groups[0].group_id, "")
|
||||||
|
.await;
|
||||||
|
assert!(error.is_some());
|
||||||
|
|
||||||
|
// empty from group id
|
||||||
|
let error = test
|
||||||
|
.move_group(&board_view.id, "", &groups[1].group_id)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn rename_group_event_test() {
|
||||||
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
|
let current_workspace = test.get_current_workspace().await;
|
||||||
|
let board_view = test
|
||||||
|
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Empty to group id
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
let error = test
|
||||||
|
.update_group(
|
||||||
|
&board_view.id,
|
||||||
|
&groups[1].group_id,
|
||||||
|
&groups[1].field_id,
|
||||||
|
Some("new name".to_owned()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups[1].group_name, "new name".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn hide_group_event_test() {
|
||||||
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
|
let current_workspace = test.get_current_workspace().await;
|
||||||
|
let board_view = test
|
||||||
|
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Empty to group id
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups.len(), 4);
|
||||||
|
|
||||||
|
let error = test
|
||||||
|
.update_group(
|
||||||
|
&board_view.id,
|
||||||
|
&groups[0].group_id,
|
||||||
|
&groups[0].field_id,
|
||||||
|
None,
|
||||||
|
Some(false),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups.len(), 4);
|
||||||
|
assert_eq!(groups[0].is_visible, false);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn update_group_name_test() {
|
async fn update_group_name_test() {
|
||||||
let test = EventIntegrationTest::new_with_guest_user().await;
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
@ -29,3 +163,25 @@ async fn update_group_name_test() {
|
|||||||
assert_eq!(groups[1].group_name, "To Do?");
|
assert_eq!(groups[1].group_name, "To Do?");
|
||||||
assert_eq!(groups[2].group_name, "Doing");
|
assert_eq!(groups[2].group_name, "Doing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn delete_group_test() {
|
||||||
|
let test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
|
let current_workspace = test.get_current_workspace().await;
|
||||||
|
let board_view = test
|
||||||
|
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups.len(), 4);
|
||||||
|
assert_eq!(groups[1].group_name, "To Do");
|
||||||
|
assert_eq!(groups[2].group_name, "Doing");
|
||||||
|
assert_eq!(groups[3].group_name, "Done");
|
||||||
|
|
||||||
|
test.delete_group(&board_view.id, &groups[1].group_id).await;
|
||||||
|
|
||||||
|
let groups = test.get_groups(&board_view.id).await;
|
||||||
|
assert_eq!(groups.len(), 3);
|
||||||
|
assert_eq!(groups[1].group_name, "Doing");
|
||||||
|
assert_eq!(groups[2].group_name, "Done");
|
||||||
|
}
|
||||||
|
@ -656,140 +656,6 @@ async fn update_checklist_cell_test() {
|
|||||||
assert_eq!(cell.percentage, 0.67);
|
assert_eq!(cell.percentage, 0.67);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The number of groups should be 0 if there is no group by field in grid
|
|
||||||
#[tokio::test]
|
|
||||||
async fn get_groups_event_with_grid_test() {
|
|
||||||
let test = EventIntegrationTest::new_with_guest_user().await;
|
|
||||||
let current_workspace = test.get_current_workspace().await;
|
|
||||||
let grid_view = test
|
|
||||||
.create_grid(¤t_workspace.id, "my board view".to_owned(), vec![])
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let groups = test.get_groups(&grid_view.id).await;
|
|
||||||
assert_eq!(groups.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn get_groups_event_test() {
|
|
||||||
let test = EventIntegrationTest::new_with_guest_user().await;
|
|
||||||
let current_workspace = test.get_current_workspace().await;
|
|
||||||
let board_view = test
|
|
||||||
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
assert_eq!(groups.len(), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn move_group_event_test() {
|
|
||||||
let test = EventIntegrationTest::new_with_guest_user().await;
|
|
||||||
let current_workspace = test.get_current_workspace().await;
|
|
||||||
let board_view = test
|
|
||||||
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
assert_eq!(groups.len(), 4);
|
|
||||||
let group_1 = groups[0].group_id.clone();
|
|
||||||
let group_2 = groups[1].group_id.clone();
|
|
||||||
let group_3 = groups[2].group_id.clone();
|
|
||||||
let group_4 = groups[3].group_id.clone();
|
|
||||||
|
|
||||||
let error = test.move_group(&board_view.id, &group_2, &group_3).await;
|
|
||||||
assert!(error.is_none());
|
|
||||||
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
assert_eq!(groups[0].group_id, group_1);
|
|
||||||
assert_eq!(groups[1].group_id, group_3);
|
|
||||||
assert_eq!(groups[2].group_id, group_2);
|
|
||||||
assert_eq!(groups[3].group_id, group_4);
|
|
||||||
|
|
||||||
let error = test.move_group(&board_view.id, &group_1, &group_4).await;
|
|
||||||
assert!(error.is_none());
|
|
||||||
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
assert_eq!(groups[0].group_id, group_3);
|
|
||||||
assert_eq!(groups[1].group_id, group_2);
|
|
||||||
assert_eq!(groups[2].group_id, group_4);
|
|
||||||
assert_eq!(groups[3].group_id, group_1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn move_group_event_with_invalid_id_test() {
|
|
||||||
let test = EventIntegrationTest::new_with_guest_user().await;
|
|
||||||
let current_workspace = test.get_current_workspace().await;
|
|
||||||
let board_view = test
|
|
||||||
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Empty to group id
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
let error = test
|
|
||||||
.move_group(&board_view.id, &groups[0].group_id, "")
|
|
||||||
.await;
|
|
||||||
assert!(error.is_some());
|
|
||||||
|
|
||||||
// empty from group id
|
|
||||||
let error = test
|
|
||||||
.move_group(&board_view.id, "", &groups[1].group_id)
|
|
||||||
.await;
|
|
||||||
assert!(error.is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn rename_group_event_test() {
|
|
||||||
let test = EventIntegrationTest::new_with_guest_user().await;
|
|
||||||
let current_workspace = test.get_current_workspace().await;
|
|
||||||
let board_view = test
|
|
||||||
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Empty to group id
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
let error = test
|
|
||||||
.update_group(
|
|
||||||
&board_view.id,
|
|
||||||
&groups[1].group_id,
|
|
||||||
&groups[1].field_id,
|
|
||||||
Some("new name".to_owned()),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(error.is_none());
|
|
||||||
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
assert_eq!(groups[1].group_name, "new name".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn hide_group_event_test() {
|
|
||||||
let test = EventIntegrationTest::new_with_guest_user().await;
|
|
||||||
let current_workspace = test.get_current_workspace().await;
|
|
||||||
let board_view = test
|
|
||||||
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Empty to group id
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
assert_eq!(groups.len(), 4);
|
|
||||||
|
|
||||||
let error = test
|
|
||||||
.update_group(
|
|
||||||
&board_view.id,
|
|
||||||
&groups[0].group_id,
|
|
||||||
&groups[0].field_id,
|
|
||||||
None,
|
|
||||||
Some(false),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(error.is_none());
|
|
||||||
|
|
||||||
let groups = test.get_groups(&board_view.id).await;
|
|
||||||
assert_eq!(groups.len(), 4);
|
|
||||||
assert!(!groups[0].is_visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the database layout type from grid to board
|
// Update the database layout type from grid to board
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn update_database_layout_event_test() {
|
async fn update_database_layout_event_test() {
|
||||||
|
@ -4,7 +4,7 @@ use flowy_derive::ProtoBuf;
|
|||||||
use flowy_error::ErrorCode;
|
use flowy_error::ErrorCode;
|
||||||
|
|
||||||
use crate::entities::parser::NotEmptyStr;
|
use crate::entities::parser::NotEmptyStr;
|
||||||
use crate::entities::{FieldType, RowMetaPB};
|
use crate::entities::RowMetaPB;
|
||||||
use crate::services::group::{GroupChangeset, GroupData, GroupSetting};
|
use crate::services::group::{GroupChangeset, GroupData, GroupSetting};
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||||
@ -130,13 +130,6 @@ pub struct GroupByFieldParams {
|
|||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DeleteGroupParams {
|
|
||||||
pub view_id: String,
|
|
||||||
pub field_id: String,
|
|
||||||
pub group_id: String,
|
|
||||||
pub field_type: FieldType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||||
pub struct UpdateGroupPB {
|
pub struct UpdateGroupPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
@ -230,3 +223,32 @@ impl TryFrom<CreateGroupPayloadPB> for CreateGroupParams {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, ProtoBuf)]
|
||||||
|
pub struct DeleteGroupPayloadPB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub view_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub group_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeleteGroupParams {
|
||||||
|
pub view_id: String,
|
||||||
|
pub group_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DeleteGroupPayloadPB> for DeleteGroupParams {
|
||||||
|
type Error = ErrorCode;
|
||||||
|
|
||||||
|
fn try_from(value: DeleteGroupPayloadPB) -> Result<Self, Self::Error> {
|
||||||
|
let view_id = NotEmptyStr::parse(value.view_id)
|
||||||
|
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
||||||
|
.0;
|
||||||
|
let group_id = NotEmptyStr::parse(value.group_id)
|
||||||
|
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
|
||||||
|
.0;
|
||||||
|
|
||||||
|
Ok(Self { view_id, group_id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -57,6 +57,10 @@ impl RowsChangePB {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.deleted_rows.is_empty() && self.inserted_rows.is_empty() && self.updated_rows.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, ProtoBuf)]
|
#[derive(Debug, Default, ProtoBuf)]
|
||||||
|
@ -755,6 +755,18 @@ pub(crate) async fn create_group_handler(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||||
|
pub(crate) async fn delete_group_handler(
|
||||||
|
data: AFPluginData<DeleteGroupPayloadPB>,
|
||||||
|
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||||
|
) -> FlowyResult<()> {
|
||||||
|
let manager = upgrade_manager(manager)?;
|
||||||
|
let params: DeleteGroupParams = data.into_inner().try_into()?;
|
||||||
|
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||||
|
database_editor.delete_group(params).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(manager), err)]
|
#[tracing::instrument(level = "debug", skip(manager), err)]
|
||||||
pub(crate) async fn get_databases_handler(
|
pub(crate) async fn get_databases_handler(
|
||||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||||
|
@ -61,6 +61,7 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
|||||||
.event(DatabaseEvent::GetGroup, get_group_handler)
|
.event(DatabaseEvent::GetGroup, get_group_handler)
|
||||||
.event(DatabaseEvent::UpdateGroup, update_group_handler)
|
.event(DatabaseEvent::UpdateGroup, update_group_handler)
|
||||||
.event(DatabaseEvent::CreateGroup, create_group_handler)
|
.event(DatabaseEvent::CreateGroup, create_group_handler)
|
||||||
|
.event(DatabaseEvent::DeleteGroup, delete_group_handler)
|
||||||
// Database
|
// Database
|
||||||
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
||||||
// Calendar
|
// Calendar
|
||||||
@ -288,6 +289,9 @@ pub enum DatabaseEvent {
|
|||||||
#[event(input = "CreateGroupPayloadPB")]
|
#[event(input = "CreateGroupPayloadPB")]
|
||||||
CreateGroup = 114,
|
CreateGroup = 114,
|
||||||
|
|
||||||
|
#[event(input = "DeleteGroupPayloadPB")]
|
||||||
|
DeleteGroup = 115,
|
||||||
|
|
||||||
/// Returns all the databases
|
/// Returns all the databases
|
||||||
#[event(output = "RepeatedDatabaseDescriptionPB")]
|
#[event(output = "RepeatedDatabaseDescriptionPB")]
|
||||||
GetDatabases = 120,
|
GetDatabases = 120,
|
||||||
|
@ -174,12 +174,16 @@ impl DatabaseEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||||
self
|
|
||||||
.database
|
|
||||||
.lock()
|
|
||||||
.delete_group_setting(¶ms.view_id, ¶ms.group_id);
|
|
||||||
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?;
|
||||||
view_editor.v_delete_group(params).await?;
|
let changes = view_editor.v_delete_group(¶ms.group_id).await?;
|
||||||
|
|
||||||
|
if !changes.is_empty() {
|
||||||
|
for view in self.database_views.editors().await {
|
||||||
|
send_notification(&view.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||||
|
.payload(changes.clone())
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -819,7 +823,7 @@ impl DatabaseEditor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for option in options {
|
for option in options {
|
||||||
type_option.delete_option(option.into());
|
type_option.delete_option(&option.id);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
.database
|
.database
|
||||||
@ -1317,6 +1321,10 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_row(&self, row_id: &RowId) -> Option<Row> {
|
||||||
|
self.database.lock().remove_row(row_id)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>> {
|
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>> {
|
||||||
let cells = self.database.lock().get_cells_for_field(view_id, field_id);
|
let cells = self.database.lock().get_cells_for_field(view_id, field_id);
|
||||||
to_fut(async move { cells.into_iter().map(Arc::new).collect() })
|
to_fut(async move { cells.into_iter().map(Arc::new).collect() })
|
||||||
|
@ -14,8 +14,8 @@ use lib_dispatch::prelude::af_spawn;
|
|||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
|
CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams,
|
||||||
DeleteGroupParams, DeleteSortParams, FieldType, FieldVisibility, GroupChangesPB, GroupPB,
|
DeleteSortParams, FieldType, FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB,
|
||||||
InsertedRowPB, LayoutSettingChangeset, LayoutSettingParams, RowMetaPB, RowsChangePB,
|
LayoutSettingChangeset, LayoutSettingParams, RowMetaPB, RowsChangePB,
|
||||||
SortChangesetNotificationPB, SortPB, UpdateFilterParams, UpdateSortParams,
|
SortChangesetNotificationPB, SortPB, UpdateFilterParams, UpdateSortParams,
|
||||||
};
|
};
|
||||||
use crate::notification::{send_notification, DatabaseNotification};
|
use crate::notification::{send_notification, DatabaseNotification};
|
||||||
@ -391,8 +391,43 @@ impl DatabaseViewEditor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn v_delete_group(&self, _params: DeleteGroupParams) -> FlowyResult<()> {
|
pub async fn v_delete_group(&self, group_id: &str) -> FlowyResult<RowsChangePB> {
|
||||||
Ok(())
|
let mut group_controller = self.group_controller.write().await;
|
||||||
|
let controller = match group_controller.as_mut() {
|
||||||
|
Some(controller) => controller,
|
||||||
|
None => return Ok(RowsChangePB::default()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let old_field = self.delegate.get_field(controller.field_id());
|
||||||
|
let (row_ids, type_option_data) = controller.delete_group(group_id)?;
|
||||||
|
|
||||||
|
drop(group_controller);
|
||||||
|
|
||||||
|
let mut changes = RowsChangePB::default();
|
||||||
|
|
||||||
|
if let Some(field) = old_field {
|
||||||
|
let deleted_rows = row_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|row_id| self.delegate.remove_row(row_id))
|
||||||
|
.map(|row| row.id.into_inner());
|
||||||
|
|
||||||
|
changes.deleted_rows.extend(deleted_rows);
|
||||||
|
|
||||||
|
if let Some(type_option) = type_option_data {
|
||||||
|
self
|
||||||
|
.delegate
|
||||||
|
.update_field(&self.view_id, type_option, field)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
let notification = GroupChangesPB {
|
||||||
|
view_id: self.view_id.clone(),
|
||||||
|
deleted_groups: vec![group_id.to_string()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
notify_did_update_num_of_groups(&self.view_id, notification).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn v_update_group(&self, changeset: GroupChangesets) -> FlowyResult<()> {
|
pub async fn v_update_group(&self, changeset: GroupChangesets) -> FlowyResult<()> {
|
||||||
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use collab_database::database::MutexDatabase;
|
use collab_database::database::MutexDatabase;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{RowCell, RowDetail, RowId};
|
use collab_database::rows::{Row, RowCell, RowDetail, RowId};
|
||||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
@ -57,6 +57,8 @@ pub trait DatabaseViewOperation: Send + Sync + 'static {
|
|||||||
/// Returns all the rows in the view
|
/// Returns all the rows in the view
|
||||||
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
||||||
|
|
||||||
|
fn remove_row(&self, row_id: &RowId) -> Option<Row>;
|
||||||
|
|
||||||
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
|
fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut<Vec<Arc<RowCell>>>;
|
||||||
|
|
||||||
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>>;
|
fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut<Arc<RowCell>>;
|
||||||
|
@ -49,12 +49,9 @@ pub trait SelectTypeOptionSharedAction: Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_option(&mut self, delete_option: SelectOption) {
|
fn delete_option(&mut self, option_id: &str) {
|
||||||
let options = self.mut_options();
|
let options = self.mut_options();
|
||||||
if let Some(index) = options
|
if let Some(index) = options.iter().position(|option| option.id == option_id) {
|
||||||
.iter()
|
|
||||||
.position(|option| option.id == delete_option.id)
|
|
||||||
{
|
|
||||||
options.remove(index);
|
options.remove(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cell, Row, RowDetail};
|
use collab_database::rows::{Cell, Row, RowDetail, RowId};
|
||||||
|
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
|
|
||||||
@ -78,6 +78,8 @@ pub trait GroupCustomize: Send + Sync {
|
|||||||
) -> FlowyResult<(Option<TypeOptionData>, Option<InsertedGroupPB>)> {
|
) -> FlowyResult<(Option<TypeOptionData>, Option<InsertedGroupPB>)> {
|
||||||
Ok((None, None))
|
Ok((None, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines the shared actions any group controller can perform.
|
/// Defines the shared actions any group controller can perform.
|
||||||
@ -159,6 +161,14 @@ pub trait GroupControllerOperation: Send + Sync {
|
|||||||
/// * `field`: new changeset
|
/// * `field`: new changeset
|
||||||
fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>;
|
fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>;
|
||||||
|
|
||||||
|
/// Delete a group from the group configuration.
|
||||||
|
///
|
||||||
|
/// Return a list of deleted row ids and/or a new `TypeOptionData` if
|
||||||
|
/// successful.
|
||||||
|
///
|
||||||
|
/// * `group_id`: the id of the group to be deleted
|
||||||
|
fn delete_group(&mut self, group_id: &str) -> FlowyResult<(Vec<RowId>, Option<TypeOptionData>)>;
|
||||||
|
|
||||||
/// Updates the name and/or visibility of groups.
|
/// Updates the name and/or visibility of groups.
|
||||||
///
|
///
|
||||||
/// Returns a non-empty `TypeOptionData` when the changes require a change
|
/// Returns a non-empty `TypeOptionData` when the changes require a change
|
||||||
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cells, Row, RowDetail};
|
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -396,6 +396,27 @@ where
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group(&mut self, group_id: &str) -> FlowyResult<(Vec<RowId>, Option<TypeOptionData>)> {
|
||||||
|
let group = if group_id != self.field_id() {
|
||||||
|
self.get_group(group_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match group {
|
||||||
|
Some((_index, group_data)) => {
|
||||||
|
let row_ids = group_data
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.map(|row| row.row.id.clone())
|
||||||
|
.collect();
|
||||||
|
let type_option_data = self.delete_group_custom(group_id)?;
|
||||||
|
Ok((row_ids, type_option_data))
|
||||||
|
},
|
||||||
|
None => Ok((vec![], None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn apply_group_changeset(
|
async fn apply_group_changeset(
|
||||||
&mut self,
|
&mut self,
|
||||||
changeset: &GroupChangesets,
|
changeset: &GroupChangesets,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
|
use flowy_error::FlowyResult;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::entities::{FieldType, GroupPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
|
use crate::entities::{FieldType, GroupPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
|
||||||
@ -138,6 +139,10 @@ impl GroupCustomize for CheckboxGroupController {
|
|||||||
});
|
});
|
||||||
group_changeset
|
group_changeset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group_custom(&mut self, _group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupController for CheckboxGroupController {
|
impl GroupController for CheckboxGroupController {
|
||||||
|
@ -7,7 +7,7 @@ use chrono::{
|
|||||||
};
|
};
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use collab_database::database::timestamp;
|
use collab_database::database::timestamp;
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
@ -248,6 +248,11 @@ impl GroupCustomize for DateGroupController {
|
|||||||
}
|
}
|
||||||
deleted_group
|
deleted_group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||||
|
self.context.delete_group(group_id)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupController for DateGroupController {
|
impl GroupController for DateGroupController {
|
||||||
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cells, Row, RowDetail};
|
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||||
|
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
|
|
||||||
@ -129,6 +129,10 @@ impl GroupControllerOperation for DefaultGroupController {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group(&mut self, _group_id: &str) -> FlowyResult<(Vec<RowId>, Option<TypeOptionData>)> {
|
||||||
|
Ok((vec![], None))
|
||||||
|
}
|
||||||
|
|
||||||
async fn apply_group_changeset(
|
async fn apply_group_changeset(
|
||||||
&mut self,
|
&mut self,
|
||||||
_changeset: &GroupChangesets,
|
_changeset: &GroupChangesets,
|
||||||
|
@ -107,6 +107,22 @@ impl GroupCustomize for MultiSelectGroupController {
|
|||||||
|
|
||||||
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||||
|
if let Some(option_index) = self
|
||||||
|
.type_option
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.position(|option| option.id == group_id)
|
||||||
|
{
|
||||||
|
// Remove the option if the group is found
|
||||||
|
let mut new_type_option = self.type_option.clone();
|
||||||
|
new_type_option.options.remove(option_index);
|
||||||
|
Ok(Some(new_type_option.into()))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupController for MultiSelectGroupController {
|
impl GroupController for MultiSelectGroupController {
|
||||||
|
@ -111,6 +111,23 @@ impl GroupCustomize for SingleSelectGroupController {
|
|||||||
|
|
||||||
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||||
|
if let Some(option_index) = self
|
||||||
|
.type_option
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.position(|option| option.id == group_id)
|
||||||
|
{
|
||||||
|
// Remove the option if the group is found
|
||||||
|
let mut new_type_option = self.type_option.clone();
|
||||||
|
new_type_option.options.remove(option_index);
|
||||||
|
Ok(Some(new_type_option.into()))
|
||||||
|
} else {
|
||||||
|
// Return None if no matching group is found
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupController for SingleSelectGroupController {
|
impl GroupController for SingleSelectGroupController {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -186,6 +186,11 @@ impl GroupCustomize for URLGroupController {
|
|||||||
}
|
}
|
||||||
deleted_group
|
deleted_group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||||
|
self.context.delete_group(group_id)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupController for URLGroupController {
|
impl GroupController for URLGroupController {
|
||||||
|
Loading…
Reference in New Issue
Block a user