diff --git a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart index 673c159c8d..afb04e8f38 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -36,21 +36,21 @@ class BoardBloc extends Bloc { super(BoardState.initial(view.id)) { boardController = AppFlowyBoardController( onMoveGroup: ( - fromColumnId, + fromGroupId, fromIndex, - toColumnId, + toGroupId, toIndex, ) { - _moveGroup(fromColumnId, toColumnId); + _moveGroup(fromGroupId, toGroupId); }, onMoveGroupItem: ( - columnId, + groupId, fromIndex, toIndex, ) { - final fromRow = groupControllers[columnId]?.rowAtIndex(fromIndex); - final toRow = groupControllers[columnId]?.rowAtIndex(toIndex); - _moveRow(fromRow, columnId, toRow); + final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex); + final toRow = groupControllers[groupId]?.rowAtIndex(toIndex); + _moveRow(fromRow, groupId, toRow); }, onMoveGroupItemToGroup: ( fromGroupId, diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index 69020b5eed..dd5e619b46 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -69,7 +69,6 @@ class BoardContent extends StatefulWidget { class _BoardContentState extends State { late AppFlowyBoardScrollController scrollManager; - final Map cardKeysCache = {}; final config = AppFlowyBoardConfig( groupBackgroundColor: HexColor.fromHex('#F7F8FC'), @@ -139,7 +138,7 @@ class _BoardContentState extends State { .read() .add(BoardEvent.endEditRow(editingRow.row.id)); } else { - scrollManager.scrollToBottom(editingRow.columnId, () { + scrollManager.scrollToBottom(editingRow.columnId, (boardContext) { context .read() .add(BoardEvent.endEditRow(editingRow.row.id)); @@ -247,15 +246,8 @@ class _BoardContentState extends State { ); final groupItemId = columnItem.id + group.id; - ValueKey? key = cardKeysCache[groupItemId]; - if (key == null) { - final newKey = ValueKey(groupItemId); - cardKeysCache[groupItemId] = newKey; - key = newKey; - } - return AppFlowyGroupCard( - key: key, + key: ValueKey(groupItemId), margin: config.cardPadding, decoration: _makeBoxDecoration(context), child: BoardCard( diff --git a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md index 56115c698b..2a29982421 100644 --- a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md +++ b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md @@ -1,3 +1,5 @@ +# 0.0.8 +* Enable drag and drop group # 0.0.7 * Rename some classes * Add documentation @@ -7,7 +9,7 @@ # 0.0.5 * Optimize insert card animation -* Enable insert card at the end of the column +* Enable insert card at the end of the group * Fix some bugs # 0.0.4 @@ -24,6 +26,5 @@ # 0.0.1 -* Support drag and drop column -* Support drag and drop column items from one to another +* Support drag and drop group items from one to another diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/main.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/main.dart index c881370e03..9e42611e7e 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/main.dart @@ -34,7 +34,7 @@ class _MyAppState extends State { appBar: AppBar( title: const Text('AppFlowy Board'), ), - body: _examples[_currentIndex], + body: Container(color: Colors.white, child: _examples[_currentIndex]), bottomNavigationBar: BottomNavigationBar( fixedColor: _bottomNavigationColor, showSelectedLabels: true, diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart index 8bd7ae98e3..2a256a51c4 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart @@ -21,8 +21,11 @@ class _MultiBoardListExampleState extends State { }, ); + late AppFlowyBoardScrollController boardController; + @override void initState() { + boardController = AppFlowyBoardScrollController(); final group1 = AppFlowyGroupData(id: "To Do", name: "To Do", items: [ TextItem("Card 1"), TextItem("Card 2"), @@ -67,12 +70,16 @@ class _MultiBoardListExampleState extends State { child: _buildCard(groupItem), ); }, + boardScrollController: boardController, footerBuilder: (context, columnData) { return AppFlowyGroupFooter( icon: const Icon(Icons.add, size: 20), title: const Text('New'), height: 50, margin: config.groupItemPadding, + onAddButtonClick: () { + boardController.scrollToBottom(columnData.id, (p0) {}); + }, ); }, headerBuilder: (context, columnData) { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart index 4a71595ea6..8aaf94e6d6 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart @@ -13,10 +13,8 @@ import '../rendering/board_overlay.dart'; class AppFlowyBoardScrollController { AppFlowyBoardState? _groupState; - void scrollToBottom(String groupId, VoidCallback? completed) { - _groupState - ?.getReorderFlexState(groupId: groupId) - ?.scrollToBottom(completed); + void scrollToBottom(String groupId, void Function(BuildContext)? completed) { + _groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed); } } @@ -133,7 +131,7 @@ class AppFlowyBoard extends StatelessWidget { dataController: controller, scrollController: scrollController, scrollManager: boardScrollController, - columnsState: _groupState, + groupState: _groupState, background: background, delegate: _phantomController, groupConstraints: groupConstraints, @@ -158,7 +156,7 @@ class _AppFlowyBoardContent extends StatefulWidget { final ReorderFlexConfig reorderFlexConfig; final BoxConstraints groupConstraints; final AppFlowyBoardScrollController? scrollManager; - final AppFlowyBoardState columnsState; + final AppFlowyBoardState groupState; final AppFlowyBoardCardBuilder cardBuilder; final AppFlowyBoardHeaderBuilder? headerBuilder; final AppFlowyBoardFooterBuilder? footerBuilder; @@ -171,7 +169,7 @@ class _AppFlowyBoardContent extends StatefulWidget { required this.delegate, required this.dataController, required this.scrollManager, - required this.columnsState, + required this.groupState, this.scrollController, this.background, required this.groupConstraints, @@ -192,8 +190,6 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> { GlobalKey(debugLabel: '$_AppFlowyBoardContent overlay key'); late BoardOverlayEntry _overlayEntry; - final Map _reorderFlexKeys = {}; - @override void initState() { _overlayEntry = BoardOverlayEntry( @@ -202,7 +198,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> { reorderFlexId: widget.dataController.identifier, acceptedReorderFlexId: widget.dataController.groupIds, delegate: widget.delegate, - columnsState: widget.columnsState, + columnsState: widget.groupState, ); final reorderFlex = ReorderFlex( @@ -212,7 +208,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> { dataSource: widget.dataController, direction: Axis.horizontal, interceptor: interceptor, - reorderable: false, + reorderable: true, children: _buildColumns(), ); @@ -257,18 +253,16 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> { dataController: widget.dataController, ); - if (_reorderFlexKeys[columnData.id] == null) { - _reorderFlexKeys[columnData.id] = GlobalObjectKey(columnData.id); - } + final reorderFlexAction = ReorderFlexActionImpl(); + widget.groupState.reorderFlexActionMap[columnData.id] = + reorderFlexAction; - GlobalObjectKey reorderFlexKey = _reorderFlexKeys[columnData.id]!; return ChangeNotifierProvider.value( key: ValueKey(columnData.id), value: widget.dataController.getGroupController(columnData.id), child: Consumer( builder: (context, value, child) { final boardColumn = AppFlowyBoardGroup( - reorderFlexKey: reorderFlexKey, // key: PageStorageKey(columnData.id), margin: _marginFromIndex(columnIndex), itemMargin: widget.config.groupItemPadding, @@ -281,11 +275,11 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> { onReorder: widget.dataController.moveGroupItem, cornerRadius: widget.config.cornerRadius, backgroundColor: widget.config.groupBackgroundColor, - dragStateStorage: widget.columnsState, - dragTargetIndexKeyStorage: widget.columnsState, + dragStateStorage: widget.groupState, + dragTargetKeys: widget.groupState, + reorderFlexAction: reorderFlexAction, ); - widget.columnsState.addGroup(columnData.id, boardColumn); return ConstrainedBox( constraints: widget.groupConstraints, child: boardColumn, @@ -356,71 +350,61 @@ class AppFlowyGroupContext { } class AppFlowyBoardState extends DraggingStateStorage - with ReorderDragTargetIndexKeyStorage { + with ReorderDragTargeKeys { + final Map groupDragStates = {}; + final Map> groupDragTargetKeys = {}; + /// Quick access to the [AppFlowyBoardGroup], the [GlobalKey] is bind to the /// AppFlowyBoardGroup's [ReorderFlex] widget. - final Map groupReorderFlexKeys = {}; - final Map groupDragStates = {}; - final Map> groupDragDragTargets = {}; - - void addGroup(String groupId, AppFlowyBoardGroup groupWidget) { - groupReorderFlexKeys[groupId] = groupWidget.reorderFlexKey; - } - - ReorderFlexState? getReorderFlexState({required String groupId}) { - final flexGlobalKey = groupReorderFlexKeys[groupId]; - if (flexGlobalKey == null) return null; - if (flexGlobalKey.currentState is! ReorderFlexState) return null; - final state = flexGlobalKey.currentState as ReorderFlexState; - return state; - } - - ReorderFlex? getReorderFlex({required String groupId}) { - final flexGlobalKey = groupReorderFlexKeys[groupId]; - if (flexGlobalKey == null) return null; - if (flexGlobalKey.currentWidget is! ReorderFlex) return null; - final widget = flexGlobalKey.currentWidget as ReorderFlex; - return widget; - } + final Map reorderFlexActionMap = {}; @override - DraggingState? read(String reorderFlexId) { + DraggingState? readState(String reorderFlexId) { return groupDragStates[reorderFlexId]; } @override - void write(String reorderFlexId, DraggingState state) { + void insertState(String reorderFlexId, DraggingState state) { Log.trace('$reorderFlexId Write dragging state: $state'); groupDragStates[reorderFlexId] = state; } @override - void remove(String reorderFlexId) { + void removeState(String reorderFlexId) { groupDragStates.remove(reorderFlexId); } @override - void addKey( + void insertDragTarget( String reorderFlexId, String key, GlobalObjectKey> value, ) { - Map? group = groupDragDragTargets[reorderFlexId]; + Map? group = groupDragTargetKeys[reorderFlexId]; if (group == null) { group = {}; - groupDragDragTargets[reorderFlexId] = group; + groupDragTargetKeys[reorderFlexId] = group; } group[key] = value; } @override - GlobalObjectKey>? readKey( - String reorderFlexId, String key) { - Map? group = groupDragDragTargets[reorderFlexId]; + GlobalObjectKey>? getDragTarget( + String reorderFlexId, + String key, + ) { + Map? group = groupDragTargetKeys[reorderFlexId]; if (group != null) { return group[key]; } else { return null; } } + + @override + void removeDragTarget(String reorderFlexId) { + groupDragTargetKeys.remove(reorderFlexId); + } } + +class ReorderFlexActionImpl extends ReorderFlexAction {} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group.dart index 8c27def35d..880a81f666 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group.dart @@ -90,21 +90,21 @@ class AppFlowyBoardGroup extends StatefulWidget { final DraggingStateStorage? dragStateStorage; - final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage; + final ReorderDragTargeKeys? dragTargetKeys; - final GlobalObjectKey reorderFlexKey; + final ReorderFlexAction? reorderFlexAction; const AppFlowyBoardGroup({ Key? key, - required this.reorderFlexKey, this.headerBuilder, this.footerBuilder, required this.cardBuilder, required this.onReorder, required this.dataSource, required this.phantomController, + this.reorderFlexAction, this.dragStateStorage, - this.dragTargetIndexKeyStorage, + this.dragTargetKeys, this.scrollController, this.onDragStarted, this.onDragEnded, @@ -146,9 +146,9 @@ class _AppFlowyBoardGroupState extends State { ); Widget reorderFlex = ReorderFlex( - key: widget.reorderFlexKey, + key: ValueKey(widget.groupId), dragStateStorage: widget.dragStateStorage, - dragTargetIndexKeyStorage: widget.dragTargetIndexKeyStorage, + dragTargetKeys: widget.dragTargetKeys, scrollController: widget.scrollController, config: widget.config, onDragStarted: (index) { @@ -168,6 +168,7 @@ class _AppFlowyBoardGroupState extends State { }, dataSource: widget.dataSource, interceptor: interceptor, + reorderFlexAction: widget.reorderFlexAction, children: children, ); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group_data.dart index 0dba483715..5223e10e90 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group_data.dart @@ -41,7 +41,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { void updateGroupName(String newName) { if (groupData.headerData.groupName != newName) { groupData.headerData.groupName = newName; - notifyListeners(); + _notify(); } } @@ -56,7 +56,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { Log.debug('[$AppFlowyGroupController] $groupData remove item at $index'); final item = groupData._items.removeAt(index); if (notify) { - notifyListeners(); + _notify(); } return item; } @@ -81,7 +81,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { '[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex'); final item = groupData._items.removeAt(fromIndex); groupData._items.insert(toIndex, item); - notifyListeners(); + _notify(); return true; } @@ -102,7 +102,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { groupData._items.add(item); } - if (notify) notifyListeners(); + if (notify) _notify(); return true; } } @@ -112,7 +112,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { return false; } else { groupData._items.add(item); - if (notify) notifyListeners(); + if (notify) _notify(); return true; } } @@ -135,7 +135,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { '[$AppFlowyGroupController] $groupData replace $removedItem with $newItem at $index'); } - notifyListeners(); + _notify(); } void replaceOrInsertItem(AppFlowyGroupItem newItem) { @@ -143,10 +143,10 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { if (index != -1) { groupData._items.removeAt(index); groupData._items.insert(index, newItem); - notifyListeners(); + _notify(); } else { groupData._items.add(newItem); - notifyListeners(); + _notify(); } } @@ -154,6 +154,10 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { return groupData._items.indexWhere((element) => element.id == item.id) != -1; } + + void _notify() { + notifyListeners(); + } } /// [AppFlowyGroupData] represents the data of each group of the Board. diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart index 9d528bf9f1..8e1a61be1a 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart @@ -69,9 +69,9 @@ class FlexDragTargetData extends DragTargetData { } abstract class DraggingStateStorage { - void write(String reorderFlexId, DraggingState state); - void remove(String reorderFlexId); - DraggingState? read(String reorderFlexId); + void insertState(String reorderFlexId, DraggingState state); + void removeState(String reorderFlexId); + DraggingState? readState(String reorderFlexId); } class DraggingState { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart index 4199e07ec8..fde9c3470a 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart @@ -73,11 +73,15 @@ class ReorderDragTarget extends StatefulWidget { final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder; final AnimationController insertAnimationController; + final AnimationController deleteAnimationController; final bool useMoveAnimation; + final bool draggable; + final double draggingOpacity; + const ReorderDragTarget({ Key? key, required this.child, @@ -94,6 +98,7 @@ class ReorderDragTarget extends StatefulWidget { this.onAccept, this.onLeave, this.draggableTargetBuilder, + this.draggingOpacity = 0.3, }) : super(key: key); @override @@ -164,6 +169,7 @@ class _ReorderDragTargetState feedback: feedbackBuilder, childWhenDragging: IgnorePointerWidget( useIntrinsicSize: !widget.useMoveAnimation, + opacity: widget.draggingOpacity, child: widget.child, ), onDragStarted: () { @@ -195,7 +201,10 @@ class _ReorderDragTargetState } Widget _buildDraggableFeedback( - BuildContext context, BoxConstraints constraints, Widget child) { + BuildContext context, + BoxConstraints constraints, + Widget child, + ) { return Transform( transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, @@ -205,7 +214,7 @@ class _ReorderDragTargetState clipBehavior: Clip.hardEdge, child: ConstrainedBox( constraints: constraints, - child: Opacity(opacity: 0.3, child: child), + child: Opacity(opacity: widget.draggingOpacity, child: child), ), ), ); @@ -274,8 +283,11 @@ class DragTargetAnimation { class IgnorePointerWidget extends StatelessWidget { final Widget? child; final bool useIntrinsicSize; + final double opacity; + const IgnorePointerWidget({ required this.child, + required this.opacity, this.useIntrinsicSize = false, Key? key, }) : super(key: key); @@ -286,11 +298,10 @@ class IgnorePointerWidget extends StatelessWidget { ? child : SizedBox(width: 0.0, height: 0.0, child: child); - final opacity = useIntrinsicSize ? 0.3 : 0.0; return IgnorePointer( ignoring: true, child: Opacity( - opacity: opacity, + opacity: useIntrinsicSize ? opacity : 0.0, child: sizedChild, ), ); @@ -300,8 +311,10 @@ class IgnorePointerWidget extends StatelessWidget { class AbsorbPointerWidget extends StatelessWidget { final Widget? child; final bool useIntrinsicSize; + final double opacity; const AbsorbPointerWidget({ required this.child, + required this.opacity, this.useIntrinsicSize = false, Key? key, }) : super(key: key); @@ -312,10 +325,9 @@ class AbsorbPointerWidget extends StatelessWidget { ? child : SizedBox(width: 0.0, height: 0.0, child: child); - final opacity = useIntrinsicSize ? 0.3 : 0.0; return AbsorbPointer( child: Opacity( - opacity: opacity, + opacity: useIntrinsicSize ? opacity : 0.0, child: sizedChild, ), ); @@ -494,6 +506,7 @@ class _FakeDragTargetState sizeFactor: widget.deleteAnimationController, axis: Axis.vertical, child: AbsorbPointerWidget( + opacity: 0.3, child: widget.child, ), ); @@ -503,6 +516,7 @@ class _FakeDragTargetState axis: Axis.vertical, child: AbsorbPointerWidget( useIntrinsicSize: true, + opacity: 0.3, child: widget.child, ), ); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart index ecf91d6934..21a67a3c7d 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart @@ -81,7 +81,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor { delegate.cancel(); } else { // Ignore the event if the dragTarget overlaps with the other column's dragTargets. - final columnKeys = columnsState.groupDragDragTargets[dragTargetId]; + final columnKeys = columnsState.groupDragTargetKeys[dragTargetId]; if (columnKeys != null) { final keys = columnKeys.values.toList(); if (dragTargetData.isOverlapWithWidgets(keys)) { @@ -102,8 +102,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor { delegate.dragTargetDidMoveToReorderFlex( dragTargetId, dragTargetData, index); - columnsState - .getReorderFlexState(groupId: dragTargetId) + columnsState.reorderFlexActionMap[dragTargetId] ?.resetDragTargetIndex(index); } }); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart index 1317ad9ab8..69d763804d 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart @@ -31,9 +31,32 @@ abstract class ReoderFlexItem { String get id; } -abstract class ReorderDragTargetIndexKeyStorage { - void addKey(String reorderFlexId, String key, GlobalObjectKey value); - GlobalObjectKey? readKey(String reorderFlexId, String key); +/// Cache each dragTarget's key. +/// For the moment, the key is used to locate the render object that will +/// be passed in the [ScrollPosition]'s [ensureVisible] function. +/// +abstract class ReorderDragTargeKeys { + void insertDragTarget( + String reorderFlexId, + String key, + GlobalObjectKey value, + ); + + GlobalObjectKey? getDragTarget( + String reorderFlexId, + String key, + ); + + void removeDragTarget(String reorderFlexId); +} + +abstract class ReorderFlexAction { + void Function(void Function(BuildContext)?)? _scrollToBottom; + void Function(void Function(BuildContext)?) get scrollToBottom => + _scrollToBottom!; + + void Function(int)? _resetDragTargetIndex; + void Function(int) get resetDragTargetIndex => _resetDragTargetIndex!; } class ReorderFlexConfig { @@ -78,9 +101,12 @@ class ReorderFlex extends StatefulWidget { final DragTargetInterceptor? interceptor; + /// Save the [DraggingState] if the current [ReorderFlex] get reinitialize. final DraggingStateStorage? dragStateStorage; - final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage; + final ReorderDragTargeKeys? dragTargetKeys; + + final ReorderFlexAction? reorderFlexAction; final bool reorderable; @@ -93,10 +119,11 @@ class ReorderFlex extends StatefulWidget { required this.onReorder, this.reorderable = true, this.dragStateStorage, - this.dragTargetIndexKeyStorage, + this.dragTargetKeys, this.onDragStarted, this.onDragEnded, this.interceptor, + this.reorderFlexAction, this.direction = Axis.vertical, }) : assert(children.every((Widget w) => w.key != null), 'All child must have a key.'), @@ -109,7 +136,7 @@ class ReorderFlex extends StatefulWidget { } class ReorderFlexState extends State - with ReorderFlexMinxi, TickerProviderStateMixin { + with ReorderFlexMixin, TickerProviderStateMixin { /// Controls scrolls and measures scroll progress. late ScrollController _scrollController; @@ -131,11 +158,11 @@ class ReorderFlexState extends State void initState() { _notifier = ReorderFlexNotifier(); final flexId = widget.reorderFlexId; - dragState = widget.dragStateStorage?.read(flexId) ?? + dragState = widget.dragStateStorage?.readState(flexId) ?? DraggingState(widget.reorderFlexId); Log.trace('[DragTarget] init dragState: $dragState'); - widget.dragStateStorage?.remove(flexId); + widget.dragStateStorage?.removeState(flexId); _animation = DragTargetAnimation( reorderAnimationDuration: widget.config.reorderAnimationDuration, @@ -148,6 +175,14 @@ class ReorderFlexState extends State vsync: this, ); + widget.reorderFlexAction?._scrollToBottom = (fn) { + scrollToBottom(fn); + }; + + widget.reorderFlexAction?._resetDragTargetIndex = (index) { + resetDragTargetIndex(index); + }; + super.initState(); } @@ -184,7 +219,7 @@ class ReorderFlexState extends State final indexKey = GlobalObjectKey(child.key!); // Save the index key for quick access - widget.dragTargetIndexKeyStorage?.addKey( + widget.dragTargetKeys?.insertDragTarget( widget.reorderFlexId, item.id, indexKey, @@ -236,8 +271,12 @@ class ReorderFlexState extends State /// [childIndex]: the index of the child in a list Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) { return Builder(builder: (context) { - final ReorderDragTarget dragTarget = - _buildDragTarget(context, child, childIndex, indexKey); + final ReorderDragTarget dragTarget = _buildDragTarget( + context, + child, + childIndex, + indexKey, + ); int shiftedIndex = childIndex; if (dragState.isOverlapWithPhantom()) { @@ -342,6 +381,15 @@ class ReorderFlexState extends State }); } + static ReorderFlexState of(BuildContext context) { + if (context is StatefulElement && context.state is ReorderFlexState) { + return context.state as ReorderFlexState; + } + final ReorderFlexState? result = + context.findAncestorStateOfType(); + return result!; + } + ReorderDragTarget _buildDragTarget( BuildContext builderContext, Widget child, @@ -364,7 +412,7 @@ class ReorderFlexState extends State "[DragTarget] Group:[${widget.dataSource.identifier}] start dragging item at $draggingIndex"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); - widget.dragStateStorage?.remove(widget.reorderFlexId); + widget.dragStateStorage?.removeState(widget.reorderFlexId); }, onDragMoved: (dragTargetData, offset) { dragTargetData.dragTargetOffset = offset; @@ -435,6 +483,7 @@ class ReorderFlexState extends State draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, useMoveAnimation: widget.config.useMoveAnimation, draggable: widget.reorderable, + draggingOpacity: widget.config.draggingWidgetOpacity, child: child, ); } @@ -487,7 +536,7 @@ class ReorderFlexState extends State } dragState.setStartDraggingIndex(dragTargetIndex); - widget.dragStateStorage?.write( + widget.dragStateStorage?.insertState( widget.reorderFlexId, dragState, ); @@ -581,46 +630,46 @@ class ReorderFlexState extends State } } - void scrollToBottom(VoidCallback? completed) { + void scrollToBottom(void Function(BuildContext)? completed) { if (_scrolling) { - completed?.call(); + completed?.call(context); return; } if (widget.dataSource.items.isNotEmpty) { final item = widget.dataSource.items.last; - final indexKey = widget.dragTargetIndexKeyStorage?.readKey( + final dragTargetKey = widget.dragTargetKeys?.getDragTarget( widget.reorderFlexId, item.id, ); - if (indexKey == null) { - completed?.call(); + if (dragTargetKey == null) { + completed?.call(context); return; } - final indexContext = indexKey.currentContext; - if (indexContext == null || _scrollController.hasClients == false) { - completed?.call(); + final dragTargetContext = dragTargetKey.currentContext; + if (dragTargetContext == null || _scrollController.hasClients == false) { + completed?.call(context); return; } - final renderObject = indexContext.findRenderObject(); - if (renderObject != null) { + final dragTargetRenderObject = dragTargetContext.findRenderObject(); + if (dragTargetRenderObject != null) { _scrolling = true; _scrollController.position .ensureVisible( - renderObject, + dragTargetRenderObject, alignment: 0.5, duration: const Duration(milliseconds: 120), ) .then((value) { setState(() { _scrolling = false; - completed?.call(); + completed?.call(context); }); }); } else { - completed?.call(); + completed?.call(context); } } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart index accdaa866b..742d5dd7a6 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; import '../transitions.dart'; import 'drag_target.dart'; -mixin ReorderFlexMinxi { +mixin ReorderFlexMixin { @protected Widget makeAppearingWidget( Widget child, diff --git a/frontend/app_flowy/packages/appflowy_board/pubspec.yaml b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml index 90907edca5..35c7aa2b67 100644 --- a/frontend/app_flowy/packages/appflowy_board/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml @@ -1,6 +1,6 @@ name: appflowy_board description: AppFlowyBoard is a board-style widget that consists of multi-groups. It supports drag and drop between different groups. -version: 0.0.7 +version: 0.0.8 homepage: https://github.com/AppFlowy-IO/AppFlowy repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 66d576a455..4abc6f80ef 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -1444,9 +1444,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" @@ -1610,9 +1610,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown", diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 709359560b..b52bb4582d 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -34,7 +34,7 @@ rayon = "1.5.2" serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} serde_repr = "0.1" -indexmap = {version = "1.8.1", features = ["serde"]} +indexmap = {version = "1.9.1", features = ["serde"]} fancy-regex = "0.10.0" regex = "1.5.6" url = { version = "2"} diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs index 63b883d570..b6c9aafaff 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs @@ -98,6 +98,7 @@ pub struct MoveGroupPayloadPB { pub to_group_id: String, } +#[derive(Debug)] pub struct MoveGroupParams { pub view_id: String, pub from_group_id: String, diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 41b1ff076f..29f7b759b8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -368,6 +368,7 @@ impl GridRevisionEditor { Ok(row_pb) } + #[tracing::instrument(level = "trace", skip_all, err)] pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { let _ = self.view_manager.move_group(params).await?; Ok(()) diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs index c7736c6fdc..6316135f5a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs @@ -173,6 +173,7 @@ impl GridViewRevisionEditor { Ok(groups.into_iter().map(GroupPB::from).collect()) } + #[tracing::instrument(level = "trace", skip(self), err)] pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { let _ = self .group_controller @@ -180,7 +181,7 @@ impl GridViewRevisionEditor { .await .move_group(¶ms.from_group_id, ¶ms.to_group_id)?; match self.group_controller.read().await.get_group(¶ms.from_group_id) { - None => {} + None => tracing::warn!("Can not find the group with id: {}", params.from_group_id), Some((index, group)) => { let inserted_group = InsertedGroupPB { group: GroupPB::from(group), @@ -228,7 +229,11 @@ impl GridViewRevisionEditor { let _ = self .modify(|pad| { let configuration = default_group_configuration(&field_rev); - let changeset = pad.insert_group(¶ms.field_id, ¶ms.field_type_rev, configuration)?; + let changeset = pad.insert_or_update_group_configuration( + ¶ms.field_id, + ¶ms.field_type_rev, + configuration, + )?; Ok(changeset) }) .await?; @@ -496,10 +501,11 @@ impl GroupConfigurationWriter for GroupConfigurationWriterImpl { let field_id = field_id.to_owned(); wrap_future(async move { - let changeset = view_pad - .write() - .await - .insert_group(&field_id, &field_type, group_configuration)?; + let changeset = view_pad.write().await.insert_or_update_group_configuration( + &field_id, + &field_type, + group_configuration, + )?; if let Some(changeset) = changeset { let _ = apply_change(&user_id, rev_manager, changeset).await?; diff --git a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs index 07e5ba45d4..8f3df66727 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs @@ -1,5 +1,5 @@ use crate::entities::{GroupPB, GroupViewChangesetPB}; -use crate::services::group::{default_group_configuration, GeneratedGroup, Group}; +use crate::services::group::{default_group_configuration, make_default_group, GeneratedGroup, Group}; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{ FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision, @@ -29,10 +29,7 @@ impl std::fmt::Display for GroupContext { self.groups_map.iter().for_each(|(_, group)| { let _ = f.write_fmt(format_args!("Group:{} has {} rows \n", group.id, group.rows.len())); }); - let _ = f.write_fmt(format_args!( - "Default group has {} rows \n", - self.default_group.rows.len() - )); + Ok(()) } } @@ -44,7 +41,7 @@ pub struct GroupContext { field_rev: Arc, groups_map: IndexMap, /// default_group is used to store the rows that don't belong to any groups. - default_group: Group, + // default_group: Group, writer: Arc, } @@ -59,16 +56,6 @@ where reader: Arc, writer: Arc, ) -> FlowyResult { - let default_group_id = format!("{}_default_group", view_id); - let default_group = Group { - id: default_group_id, - field_id: field_rev.id.clone(), - name: format!("No {}", field_rev.name), - is_default: true, - is_visible: true, - rows: vec![], - filter_content: "".to_string(), - }; let configuration = match reader.get_configuration().await { None => { let default_configuration = default_group_configuration(&field_rev); @@ -80,24 +67,22 @@ where Some(configuration) => configuration, }; - // let configuration = C::from_configuration_content(&configuration_rev.content)?; Ok(Self { view_id, field_rev, groups_map: IndexMap::new(), - default_group, writer, configuration, configuration_content: PhantomData, }) } - pub(crate) fn get_default_group(&self) -> &Group { - &self.default_group + pub(crate) fn get_default_group(&self) -> Option<&Group> { + self.groups_map.get(&self.field_rev.id) } - pub(crate) fn get_mut_default_group(&mut self) -> &mut Group { - &mut self.default_group + pub(crate) fn get_mut_default_group(&mut self) -> Option<&mut Group> { + self.groups_map.get_mut(&self.field_rev.id) } /// Returns the groups without the default group @@ -122,8 +107,6 @@ where self.groups_map.iter_mut().for_each(|(_, group)| { each(group); }); - - each(&mut self.default_group); } pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> { @@ -131,18 +114,23 @@ where let to_index = self.groups_map.get_index_of(to_id); match (from_index, to_index) { (Some(from_index), Some(to_index)) => { - self.groups_map.swap_indices(from_index, to_index); + self.groups_map.move_index(from_index, to_index); + self.mut_configuration(|configuration| { let from_index = configuration.groups.iter().position(|group| group.id == from_id); let to_index = configuration.groups.iter().position(|group| group.id == to_id); - if let (Some(from), Some(to)) = (from_index, to_index) { - configuration.groups.swap(from, to); + tracing::info!("Configuration groups: {:?} ", configuration.groups); + if let (Some(from), Some(to)) = &(from_index, to_index) { + tracing::trace!("Move group from index:{:?} to index:{:?}", from_index, to_index); + let group = configuration.groups.remove(*from); + configuration.groups.insert(*to, group); } - true + + from_index.is_some() && to_index.is_some() })?; Ok(()) } - _ => Err(FlowyError::out_of_bounds()), + _ => Err(FlowyError::record_not_found().context("Moving group failed. Groups are not exist")), } } @@ -150,7 +138,6 @@ where pub(crate) fn init_groups( &mut self, generated_groups: Vec, - reset: bool, ) -> FlowyResult> { let mut new_groups = vec![]; let mut filter_content_map = HashMap::new(); @@ -159,16 +146,17 @@ where new_groups.push(generate_group.group_rev); }); + let mut old_groups = self.configuration.groups.clone(); + if !old_groups.iter().any(|group| group.id == self.field_rev.id) { + old_groups.push(make_default_group(&self.field_rev)); + } + let MergeGroupResult { mut all_group_revs, new_group_revs, updated_group_revs: _, deleted_group_revs, - } = if reset { - merge_groups(&[], new_groups) - } else { - merge_groups(&self.configuration.groups, new_groups) - }; + } = merge_groups(old_groups, new_groups); let deleted_group_ids = deleted_group_revs .into_iter() @@ -197,31 +185,23 @@ where Some(pos) => { let mut old_group = configuration.groups.remove(pos); group_rev.update_with_other(&old_group); + is_changed = is_group_changed(group_rev, &old_group); - // Take the GroupRevision if the name has changed - if is_group_changed(group_rev, &old_group) { - old_group.name = group_rev.name.clone(); - is_changed = true; - configuration.groups.insert(pos, old_group); - } + old_group.name = group_rev.name.clone(); + configuration.groups.insert(pos, old_group); } } } is_changed })?; - // The len of the filter_content_map should equal to the len of the all_group_revs - debug_assert_eq!(filter_content_map.len(), all_group_revs.len()); all_group_revs.into_iter().for_each(|group_rev| { - if let Some(filter_content) = filter_content_map.get(&group_rev.id) { - let group = Group::new( - group_rev.id, - self.field_rev.id.clone(), - group_rev.name, - filter_content.clone(), - ); - self.groups_map.insert(group.id.clone(), group); - } + let filter_content = filter_content_map + .get(&group_rev.id) + .cloned() + .unwrap_or_else(|| "".to_owned()); + let group = Group::new(group_rev.id, self.field_rev.id.clone(), group_rev.name, filter_content); + self.groups_map.insert(group.id.clone(), group); }); let new_groups = new_group_revs @@ -269,6 +249,7 @@ where Ok(()) } + #[tracing::instrument(level = "trace", skip_all, err)] pub fn save_configuration(&self) -> FlowyResult<()> { let configuration = (&*self.configuration).clone(); let writer = self.writer.clone(); @@ -311,13 +292,14 @@ where } } -fn merge_groups(old_groups: &[GroupRevision], new_groups: Vec) -> MergeGroupResult { +fn merge_groups(old_groups: Vec, new_groups: Vec) -> MergeGroupResult { let mut merge_result = MergeGroupResult::new(); - if old_groups.is_empty() { - merge_result.all_group_revs = new_groups.clone(); - merge_result.new_group_revs = new_groups; - return merge_result; - } + // if old_groups.is_empty() { + // merge_result.all_group_revs.extend(new_groups.clone()); + // merge_result.all_group_revs.push(default_group); + // merge_result.new_group_revs = new_groups; + // return merge_result; + // } // group_map is a helper map is used to filter out the new groups. let mut new_group_map: IndexMap = IndexMap::new(); @@ -329,19 +311,20 @@ fn merge_groups(old_groups: &[GroupRevision], new_groups: Vec) -> for old in old_groups { if let Some(new) = new_group_map.remove(&old.id) { merge_result.all_group_revs.push(new.clone()); - if is_group_changed(&new, old) { + if is_group_changed(&new, &old) { merge_result.updated_group_revs.push(new); } } else { - merge_result.deleted_group_revs.push(old.clone()); + merge_result.all_group_revs.push(old); } } // Find out the new groups + new_group_map.reverse(); let new_groups = new_group_map.into_values(); for (_, group) in new_groups.into_iter().enumerate() { - merge_result.all_group_revs.push(group.clone()); - merge_result.new_group_revs.push(group); + merge_result.all_group_revs.insert(0, group.clone()); + merge_result.new_group_revs.insert(0, group); } merge_result } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs index 80cfa8edec..1a7bc8d218 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs @@ -88,7 +88,7 @@ where pub async fn new(field_rev: &Arc, mut configuration: GroupContext) -> FlowyResult { let type_option = field_rev.get_type_option::(field_rev.ty); let groups = G::generate_groups(&field_rev.id, &configuration, &type_option); - let _ = configuration.init_groups(groups, true)?; + let _ = configuration.init_groups(groups)?; Ok(Self { field_id: field_rev.id.clone(), @@ -105,8 +105,8 @@ where &mut self, row_rev: &RowRevision, other_group_changesets: &[GroupChangesetPB], - ) -> GroupChangesetPB { - let default_group = self.group_ctx.get_mut_default_group(); + ) -> Option { + let default_group = self.group_ctx.get_mut_default_group()?; // [other_group_inserted_row] contains all the inserted rows except the default group. let other_group_inserted_row = other_group_changesets @@ -163,7 +163,7 @@ where } default_group.rows.retain(|row| !deleted_row_ids.contains(&row.id)); changeset.deleted_rows.extend(deleted_row_ids); - changeset + Some(changeset) } } @@ -182,11 +182,14 @@ where fn groups(&self) -> Vec { if self.use_default_group() { - let mut groups: Vec = self.group_ctx.groups().into_iter().cloned().collect(); - groups.push(self.group_ctx.get_default_group().clone()); - groups - } else { self.group_ctx.groups().into_iter().cloned().collect() + } else { + self.group_ctx + .groups() + .into_iter() + .filter(|group| group.id != self.field_id) + .cloned() + .collect::>() } } @@ -216,17 +219,18 @@ where } } - if grouped_rows.is_empty() { - self.group_ctx.get_mut_default_group().add_row(row_rev.into()); - } else { + if !grouped_rows.is_empty() { for group_row in grouped_rows { if let Some(group) = self.group_ctx.get_mut_group(&group_row.group_id) { group.add_row(group_row.row); } } + continue; } - } else { - self.group_ctx.get_mut_default_group().add_row(row_rev.into()); + } + match self.group_ctx.get_mut_default_group() { + None => {} + Some(default_group) => default_group.add_row(row_rev.into()), } } @@ -247,10 +251,11 @@ where let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; let cell_data = cell_bytes.parser::

()?; let mut changesets = self.add_row_if_match(row_rev, &cell_data); - let default_group_changeset = self.update_default_group(row_rev, &changesets); - tracing::trace!("default_group_changeset: {}", default_group_changeset); - if !default_group_changeset.is_empty() { - changesets.push(default_group_changeset); + if let Some(default_group_changeset) = self.update_default_group(row_rev, &changesets) { + tracing::trace!("default_group_changeset: {}", default_group_changeset); + if !default_group_changeset.is_empty() { + changesets.push(default_group_changeset); + } } Ok(changesets) } else { @@ -268,12 +273,13 @@ where let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; let cell_data = cell_bytes.parser::

()?; Ok(self.remove_row_if_match(row_rev, &cell_data)) - } else { - let group = self.group_ctx.get_default_group(); + } else if let Some(group) = self.group_ctx.get_default_group() { Ok(vec![GroupChangesetPB::delete( group.id.clone(), vec![row_rev.id.clone()], )]) + } else { + Ok(vec![]) } } @@ -297,7 +303,7 @@ where fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult> { let type_option = field_rev.get_type_option::(field_rev.ty); let groups = G::generate_groups(&field_rev.id, &self.group_ctx, &type_option); - let changeset = self.group_ctx.init_groups(groups, false)?; + let changeset = self.group_ctx.init_groups(groups)?; Ok(changeset) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/entities.rs b/frontend/rust-lib/flowy-grid/src/services/group/entities.rs index c4687859f3..baa4842402 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/entities.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/entities.rs @@ -9,16 +9,17 @@ pub struct Group { pub is_visible: bool, pub(crate) rows: Vec, - /// [content] is used to determine which group the cell belongs to. + /// [filter_content] is used to determine which group the cell belongs to. pub filter_content: String, } impl Group { pub fn new(id: String, field_id: String, name: String, filter_content: String) -> Self { + let is_default = id == field_id; Self { id, field_id, - is_default: false, + is_default, is_visible: true, name, rows: vec![], diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs index c15717e2a7..8a901fd869 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs @@ -8,8 +8,8 @@ use crate::services::group::{ use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{ CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision, - LayoutRevision, NumberGroupConfigurationRevision, RowRevision, SelectOptionGroupConfigurationRevision, - TextGroupConfigurationRevision, UrlGroupConfigurationRevision, + GroupRevision, LayoutRevision, NumberGroupConfigurationRevision, RowRevision, + SelectOptionGroupConfigurationRevision, TextGroupConfigurationRevision, UrlGroupConfigurationRevision, }; use std::sync::Arc; @@ -79,7 +79,7 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat let field_id = field_rev.id.clone(); let field_type_rev = field_rev.ty; let field_type: FieldType = field_rev.ty.into(); - match field_type { + let mut group_configuration_rev = match field_type { FieldType::RichText => { GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default()) .unwrap() @@ -112,5 +112,23 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat FieldType::URL => { GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap() } + }; + + // Append the no `status` group + let default_group_rev = GroupRevision { + id: field_rev.id.clone(), + name: format!("No {}", field_rev.name), + visible: true, + }; + + group_configuration_rev.groups.push(default_group_rev); + group_configuration_rev +} + +pub fn make_default_group(field_rev: &FieldRevision) -> GroupRevision { + GroupRevision { + id: field_rev.id.clone(), + name: format!("No {}", field_rev.name), + visible: true, } } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs b/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs index a205420498..42fdc6b61c 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs @@ -370,6 +370,28 @@ async fn group_move_group_test() { test.run_scripts(scripts).await; } +#[tokio::test] +async fn group_default_move_group_test() { + let mut test = GridGroupTest::new().await; + let group_0 = test.group_at_index(0).await; + let group_3 = test.group_at_index(3).await; + let scripts = vec![ + MoveGroup { + from_group_index: 3, + to_group_index: 0, + }, + AssertGroup { + group_index: 0, + expected_group: group_3, + }, + AssertGroup { + group_index: 1, + expected_group: group_0, + }, + ]; + test.run_scripts(scripts).await; +} + #[tokio::test] async fn group_insert_single_select_option_test() { let mut test = GridGroupTest::new().await; @@ -402,7 +424,7 @@ async fn group_group_by_other_field() { group_index: 1, row_count: 2, }, - AssertGroupCount(4), + AssertGroupCount(5), ]; test.run_scripts(scripts).await; } diff --git a/shared-lib/Cargo.lock b/shared-lib/Cargo.lock index 79357da09b..e519a829cd 100644 --- a/shared-lib/Cargo.lock +++ b/shared-lib/Cargo.lock @@ -650,9 +650,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" @@ -732,9 +732,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown", diff --git a/shared-lib/flowy-grid-data-model/Cargo.toml b/shared-lib/flowy-grid-data-model/Cargo.toml index 3e640ec1af..671fdbb4e6 100644 --- a/shared-lib/flowy-grid-data-model/Cargo.toml +++ b/shared-lib/flowy-grid-data-model/Cargo.toml @@ -12,7 +12,7 @@ serde_json = {version = "1.0"} serde_repr = "0.1" nanoid = "0.4.0" flowy-error-code = { path = "../flowy-error-code"} -indexmap = {version = "1.8.1", features = ["serde"]} +indexmap = {version = "1.9.1", features = ["serde"]} tracing = { version = "0.1", features = ["log"] } [build-dependencies] diff --git a/shared-lib/flowy-grid-data-model/src/revision/group_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/group_rev.rs index 0aded0d3c7..44a7a4e1f6 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/group_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/group_rev.rs @@ -128,14 +128,6 @@ impl GroupRevision { } } - pub fn default_group(id: String, group_name: String) -> Self { - Self { - id, - name: group_name, - visible: true, - } - } - pub fn update_with_other(&mut self, other: &GroupRevision) { self.visible = other.visible } diff --git a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs index 8613418c40..11e9d5a07e 100644 --- a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs @@ -66,7 +66,7 @@ impl GridViewRevisionPad { } #[tracing::instrument(level = "trace", skip_all, err)] - pub fn insert_group( + pub fn insert_or_update_group_configuration( &mut self, field_id: &str, field_type: &FieldTypeRevision,