mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #1197 from AppFlowy-IO/feat/support_drag_group
Feat/support drag group
This commit is contained in:
commit
f355ff01e4
@ -36,21 +36,21 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
super(BoardState.initial(view.id)) {
|
super(BoardState.initial(view.id)) {
|
||||||
boardController = AppFlowyBoardController(
|
boardController = AppFlowyBoardController(
|
||||||
onMoveGroup: (
|
onMoveGroup: (
|
||||||
fromColumnId,
|
fromGroupId,
|
||||||
fromIndex,
|
fromIndex,
|
||||||
toColumnId,
|
toGroupId,
|
||||||
toIndex,
|
toIndex,
|
||||||
) {
|
) {
|
||||||
_moveGroup(fromColumnId, toColumnId);
|
_moveGroup(fromGroupId, toGroupId);
|
||||||
},
|
},
|
||||||
onMoveGroupItem: (
|
onMoveGroupItem: (
|
||||||
columnId,
|
groupId,
|
||||||
fromIndex,
|
fromIndex,
|
||||||
toIndex,
|
toIndex,
|
||||||
) {
|
) {
|
||||||
final fromRow = groupControllers[columnId]?.rowAtIndex(fromIndex);
|
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
|
||||||
final toRow = groupControllers[columnId]?.rowAtIndex(toIndex);
|
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
|
||||||
_moveRow(fromRow, columnId, toRow);
|
_moveRow(fromRow, groupId, toRow);
|
||||||
},
|
},
|
||||||
onMoveGroupItemToGroup: (
|
onMoveGroupItemToGroup: (
|
||||||
fromGroupId,
|
fromGroupId,
|
||||||
|
@ -69,7 +69,6 @@ class BoardContent extends StatefulWidget {
|
|||||||
|
|
||||||
class _BoardContentState extends State<BoardContent> {
|
class _BoardContentState extends State<BoardContent> {
|
||||||
late AppFlowyBoardScrollController scrollManager;
|
late AppFlowyBoardScrollController scrollManager;
|
||||||
final Map<String, ValueKey> cardKeysCache = {};
|
|
||||||
|
|
||||||
final config = AppFlowyBoardConfig(
|
final config = AppFlowyBoardConfig(
|
||||||
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||||
@ -139,7 +138,7 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
.read<BoardBloc>()
|
.read<BoardBloc>()
|
||||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||||
} else {
|
} else {
|
||||||
scrollManager.scrollToBottom(editingRow.columnId, () {
|
scrollManager.scrollToBottom(editingRow.columnId, (boardContext) {
|
||||||
context
|
context
|
||||||
.read<BoardBloc>()
|
.read<BoardBloc>()
|
||||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||||
@ -247,15 +246,8 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final groupItemId = columnItem.id + group.id;
|
final groupItemId = columnItem.id + group.id;
|
||||||
ValueKey? key = cardKeysCache[groupItemId];
|
|
||||||
if (key == null) {
|
|
||||||
final newKey = ValueKey(groupItemId);
|
|
||||||
cardKeysCache[groupItemId] = newKey;
|
|
||||||
key = newKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AppFlowyGroupCard(
|
return AppFlowyGroupCard(
|
||||||
key: key,
|
key: ValueKey(groupItemId),
|
||||||
margin: config.cardPadding,
|
margin: config.cardPadding,
|
||||||
decoration: _makeBoxDecoration(context),
|
decoration: _makeBoxDecoration(context),
|
||||||
child: BoardCard(
|
child: BoardCard(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# 0.0.8
|
||||||
|
* Enable drag and drop group
|
||||||
# 0.0.7
|
# 0.0.7
|
||||||
* Rename some classes
|
* Rename some classes
|
||||||
* Add documentation
|
* Add documentation
|
||||||
@ -7,7 +9,7 @@
|
|||||||
|
|
||||||
# 0.0.5
|
# 0.0.5
|
||||||
* Optimize insert card animation
|
* 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
|
* Fix some bugs
|
||||||
|
|
||||||
# 0.0.4
|
# 0.0.4
|
||||||
@ -24,6 +26,5 @@
|
|||||||
|
|
||||||
# 0.0.1
|
# 0.0.1
|
||||||
|
|
||||||
* Support drag and drop column
|
* Support drag and drop group items from one to another
|
||||||
* Support drag and drop column items from one to another
|
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class _MyAppState extends State<MyApp> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('AppFlowy Board'),
|
title: const Text('AppFlowy Board'),
|
||||||
),
|
),
|
||||||
body: _examples[_currentIndex],
|
body: Container(color: Colors.white, child: _examples[_currentIndex]),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
fixedColor: _bottomNavigationColor,
|
fixedColor: _bottomNavigationColor,
|
||||||
showSelectedLabels: true,
|
showSelectedLabels: true,
|
||||||
|
@ -21,8 +21,11 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
late AppFlowyBoardScrollController boardController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
boardController = AppFlowyBoardScrollController();
|
||||||
final group1 = AppFlowyGroupData(id: "To Do", name: "To Do", items: [
|
final group1 = AppFlowyGroupData(id: "To Do", name: "To Do", items: [
|
||||||
TextItem("Card 1"),
|
TextItem("Card 1"),
|
||||||
TextItem("Card 2"),
|
TextItem("Card 2"),
|
||||||
@ -67,12 +70,16 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
|||||||
child: _buildCard(groupItem),
|
child: _buildCard(groupItem),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
boardScrollController: boardController,
|
||||||
footerBuilder: (context, columnData) {
|
footerBuilder: (context, columnData) {
|
||||||
return AppFlowyGroupFooter(
|
return AppFlowyGroupFooter(
|
||||||
icon: const Icon(Icons.add, size: 20),
|
icon: const Icon(Icons.add, size: 20),
|
||||||
title: const Text('New'),
|
title: const Text('New'),
|
||||||
height: 50,
|
height: 50,
|
||||||
margin: config.groupItemPadding,
|
margin: config.groupItemPadding,
|
||||||
|
onAddButtonClick: () {
|
||||||
|
boardController.scrollToBottom(columnData.id, (p0) {});
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
headerBuilder: (context, columnData) {
|
headerBuilder: (context, columnData) {
|
||||||
|
@ -13,10 +13,8 @@ import '../rendering/board_overlay.dart';
|
|||||||
class AppFlowyBoardScrollController {
|
class AppFlowyBoardScrollController {
|
||||||
AppFlowyBoardState? _groupState;
|
AppFlowyBoardState? _groupState;
|
||||||
|
|
||||||
void scrollToBottom(String groupId, VoidCallback? completed) {
|
void scrollToBottom(String groupId, void Function(BuildContext)? completed) {
|
||||||
_groupState
|
_groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
||||||
?.getReorderFlexState(groupId: groupId)
|
|
||||||
?.scrollToBottom(completed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +131,7 @@ class AppFlowyBoard extends StatelessWidget {
|
|||||||
dataController: controller,
|
dataController: controller,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
scrollManager: boardScrollController,
|
scrollManager: boardScrollController,
|
||||||
columnsState: _groupState,
|
groupState: _groupState,
|
||||||
background: background,
|
background: background,
|
||||||
delegate: _phantomController,
|
delegate: _phantomController,
|
||||||
groupConstraints: groupConstraints,
|
groupConstraints: groupConstraints,
|
||||||
@ -158,7 +156,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
|||||||
final ReorderFlexConfig reorderFlexConfig;
|
final ReorderFlexConfig reorderFlexConfig;
|
||||||
final BoxConstraints groupConstraints;
|
final BoxConstraints groupConstraints;
|
||||||
final AppFlowyBoardScrollController? scrollManager;
|
final AppFlowyBoardScrollController? scrollManager;
|
||||||
final AppFlowyBoardState columnsState;
|
final AppFlowyBoardState groupState;
|
||||||
final AppFlowyBoardCardBuilder cardBuilder;
|
final AppFlowyBoardCardBuilder cardBuilder;
|
||||||
final AppFlowyBoardHeaderBuilder? headerBuilder;
|
final AppFlowyBoardHeaderBuilder? headerBuilder;
|
||||||
final AppFlowyBoardFooterBuilder? footerBuilder;
|
final AppFlowyBoardFooterBuilder? footerBuilder;
|
||||||
@ -171,7 +169,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
|||||||
required this.delegate,
|
required this.delegate,
|
||||||
required this.dataController,
|
required this.dataController,
|
||||||
required this.scrollManager,
|
required this.scrollManager,
|
||||||
required this.columnsState,
|
required this.groupState,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.background,
|
this.background,
|
||||||
required this.groupConstraints,
|
required this.groupConstraints,
|
||||||
@ -192,8 +190,6 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
GlobalKey(debugLabel: '$_AppFlowyBoardContent overlay key');
|
GlobalKey(debugLabel: '$_AppFlowyBoardContent overlay key');
|
||||||
late BoardOverlayEntry _overlayEntry;
|
late BoardOverlayEntry _overlayEntry;
|
||||||
|
|
||||||
final Map<String, GlobalObjectKey> _reorderFlexKeys = {};
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_overlayEntry = BoardOverlayEntry(
|
_overlayEntry = BoardOverlayEntry(
|
||||||
@ -202,7 +198,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
reorderFlexId: widget.dataController.identifier,
|
reorderFlexId: widget.dataController.identifier,
|
||||||
acceptedReorderFlexId: widget.dataController.groupIds,
|
acceptedReorderFlexId: widget.dataController.groupIds,
|
||||||
delegate: widget.delegate,
|
delegate: widget.delegate,
|
||||||
columnsState: widget.columnsState,
|
columnsState: widget.groupState,
|
||||||
);
|
);
|
||||||
|
|
||||||
final reorderFlex = ReorderFlex(
|
final reorderFlex = ReorderFlex(
|
||||||
@ -212,7 +208,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
dataSource: widget.dataController,
|
dataSource: widget.dataController,
|
||||||
direction: Axis.horizontal,
|
direction: Axis.horizontal,
|
||||||
interceptor: interceptor,
|
interceptor: interceptor,
|
||||||
reorderable: false,
|
reorderable: true,
|
||||||
children: _buildColumns(),
|
children: _buildColumns(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -257,18 +253,16 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
dataController: widget.dataController,
|
dataController: widget.dataController,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_reorderFlexKeys[columnData.id] == null) {
|
final reorderFlexAction = ReorderFlexActionImpl();
|
||||||
_reorderFlexKeys[columnData.id] = GlobalObjectKey(columnData.id);
|
widget.groupState.reorderFlexActionMap[columnData.id] =
|
||||||
}
|
reorderFlexAction;
|
||||||
|
|
||||||
GlobalObjectKey reorderFlexKey = _reorderFlexKeys[columnData.id]!;
|
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
key: ValueKey(columnData.id),
|
key: ValueKey(columnData.id),
|
||||||
value: widget.dataController.getGroupController(columnData.id),
|
value: widget.dataController.getGroupController(columnData.id),
|
||||||
child: Consumer<AppFlowyGroupController>(
|
child: Consumer<AppFlowyGroupController>(
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
final boardColumn = AppFlowyBoardGroup(
|
final boardColumn = AppFlowyBoardGroup(
|
||||||
reorderFlexKey: reorderFlexKey,
|
|
||||||
// key: PageStorageKey<String>(columnData.id),
|
// key: PageStorageKey<String>(columnData.id),
|
||||||
margin: _marginFromIndex(columnIndex),
|
margin: _marginFromIndex(columnIndex),
|
||||||
itemMargin: widget.config.groupItemPadding,
|
itemMargin: widget.config.groupItemPadding,
|
||||||
@ -281,11 +275,11 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
onReorder: widget.dataController.moveGroupItem,
|
onReorder: widget.dataController.moveGroupItem,
|
||||||
cornerRadius: widget.config.cornerRadius,
|
cornerRadius: widget.config.cornerRadius,
|
||||||
backgroundColor: widget.config.groupBackgroundColor,
|
backgroundColor: widget.config.groupBackgroundColor,
|
||||||
dragStateStorage: widget.columnsState,
|
dragStateStorage: widget.groupState,
|
||||||
dragTargetIndexKeyStorage: widget.columnsState,
|
dragTargetKeys: widget.groupState,
|
||||||
|
reorderFlexAction: reorderFlexAction,
|
||||||
);
|
);
|
||||||
|
|
||||||
widget.columnsState.addGroup(columnData.id, boardColumn);
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: widget.groupConstraints,
|
constraints: widget.groupConstraints,
|
||||||
child: boardColumn,
|
child: boardColumn,
|
||||||
@ -356,71 +350,61 @@ class AppFlowyGroupContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AppFlowyBoardState extends DraggingStateStorage
|
class AppFlowyBoardState extends DraggingStateStorage
|
||||||
with ReorderDragTargetIndexKeyStorage {
|
with ReorderDragTargeKeys {
|
||||||
|
final Map<String, DraggingState> groupDragStates = {};
|
||||||
|
final Map<String, Map<String, GlobalObjectKey>> groupDragTargetKeys = {};
|
||||||
|
|
||||||
/// Quick access to the [AppFlowyBoardGroup], the [GlobalKey] is bind to the
|
/// Quick access to the [AppFlowyBoardGroup], the [GlobalKey] is bind to the
|
||||||
/// AppFlowyBoardGroup's [ReorderFlex] widget.
|
/// AppFlowyBoardGroup's [ReorderFlex] widget.
|
||||||
final Map<String, GlobalKey> groupReorderFlexKeys = {};
|
final Map<String, ReorderFlexActionImpl> reorderFlexActionMap = {};
|
||||||
final Map<String, DraggingState> groupDragStates = {};
|
|
||||||
final Map<String, Map<String, GlobalObjectKey>> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DraggingState? read(String reorderFlexId) {
|
DraggingState? readState(String reorderFlexId) {
|
||||||
return groupDragStates[reorderFlexId];
|
return groupDragStates[reorderFlexId];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(String reorderFlexId, DraggingState state) {
|
void insertState(String reorderFlexId, DraggingState state) {
|
||||||
Log.trace('$reorderFlexId Write dragging state: $state');
|
Log.trace('$reorderFlexId Write dragging state: $state');
|
||||||
groupDragStates[reorderFlexId] = state;
|
groupDragStates[reorderFlexId] = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void remove(String reorderFlexId) {
|
void removeState(String reorderFlexId) {
|
||||||
groupDragStates.remove(reorderFlexId);
|
groupDragStates.remove(reorderFlexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addKey(
|
void insertDragTarget(
|
||||||
String reorderFlexId,
|
String reorderFlexId,
|
||||||
String key,
|
String key,
|
||||||
GlobalObjectKey<State<StatefulWidget>> value,
|
GlobalObjectKey<State<StatefulWidget>> value,
|
||||||
) {
|
) {
|
||||||
Map<String, GlobalObjectKey>? group = groupDragDragTargets[reorderFlexId];
|
Map<String, GlobalObjectKey>? group = groupDragTargetKeys[reorderFlexId];
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
group = {};
|
group = {};
|
||||||
groupDragDragTargets[reorderFlexId] = group;
|
groupDragTargetKeys[reorderFlexId] = group;
|
||||||
}
|
}
|
||||||
group[key] = value;
|
group[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GlobalObjectKey<State<StatefulWidget>>? readKey(
|
GlobalObjectKey<State<StatefulWidget>>? getDragTarget(
|
||||||
String reorderFlexId, String key) {
|
String reorderFlexId,
|
||||||
Map<String, GlobalObjectKey>? group = groupDragDragTargets[reorderFlexId];
|
String key,
|
||||||
|
) {
|
||||||
|
Map<String, GlobalObjectKey>? group = groupDragTargetKeys[reorderFlexId];
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
return group[key];
|
return group[key];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeDragTarget(String reorderFlexId) {
|
||||||
|
groupDragTargetKeys.remove(reorderFlexId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReorderFlexActionImpl extends ReorderFlexAction {}
|
||||||
|
@ -90,21 +90,21 @@ class AppFlowyBoardGroup extends StatefulWidget {
|
|||||||
|
|
||||||
final DraggingStateStorage? dragStateStorage;
|
final DraggingStateStorage? dragStateStorage;
|
||||||
|
|
||||||
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
|
final ReorderDragTargeKeys? dragTargetKeys;
|
||||||
|
|
||||||
final GlobalObjectKey reorderFlexKey;
|
final ReorderFlexAction? reorderFlexAction;
|
||||||
|
|
||||||
const AppFlowyBoardGroup({
|
const AppFlowyBoardGroup({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.reorderFlexKey,
|
|
||||||
this.headerBuilder,
|
this.headerBuilder,
|
||||||
this.footerBuilder,
|
this.footerBuilder,
|
||||||
required this.cardBuilder,
|
required this.cardBuilder,
|
||||||
required this.onReorder,
|
required this.onReorder,
|
||||||
required this.dataSource,
|
required this.dataSource,
|
||||||
required this.phantomController,
|
required this.phantomController,
|
||||||
|
this.reorderFlexAction,
|
||||||
this.dragStateStorage,
|
this.dragStateStorage,
|
||||||
this.dragTargetIndexKeyStorage,
|
this.dragTargetKeys,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.onDragStarted,
|
this.onDragStarted,
|
||||||
this.onDragEnded,
|
this.onDragEnded,
|
||||||
@ -146,9 +146,9 @@ class _AppFlowyBoardGroupState extends State<AppFlowyBoardGroup> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Widget reorderFlex = ReorderFlex(
|
Widget reorderFlex = ReorderFlex(
|
||||||
key: widget.reorderFlexKey,
|
key: ValueKey(widget.groupId),
|
||||||
dragStateStorage: widget.dragStateStorage,
|
dragStateStorage: widget.dragStateStorage,
|
||||||
dragTargetIndexKeyStorage: widget.dragTargetIndexKeyStorage,
|
dragTargetKeys: widget.dragTargetKeys,
|
||||||
scrollController: widget.scrollController,
|
scrollController: widget.scrollController,
|
||||||
config: widget.config,
|
config: widget.config,
|
||||||
onDragStarted: (index) {
|
onDragStarted: (index) {
|
||||||
@ -168,6 +168,7 @@ class _AppFlowyBoardGroupState extends State<AppFlowyBoardGroup> {
|
|||||||
},
|
},
|
||||||
dataSource: widget.dataSource,
|
dataSource: widget.dataSource,
|
||||||
interceptor: interceptor,
|
interceptor: interceptor,
|
||||||
|
reorderFlexAction: widget.reorderFlexAction,
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
void updateGroupName(String newName) {
|
void updateGroupName(String newName) {
|
||||||
if (groupData.headerData.groupName != newName) {
|
if (groupData.headerData.groupName != newName) {
|
||||||
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');
|
Log.debug('[$AppFlowyGroupController] $groupData remove item at $index');
|
||||||
final item = groupData._items.removeAt(index);
|
final item = groupData._items.removeAt(index);
|
||||||
if (notify) {
|
if (notify) {
|
||||||
notifyListeners();
|
_notify();
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
'[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex');
|
'[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex');
|
||||||
final item = groupData._items.removeAt(fromIndex);
|
final item = groupData._items.removeAt(fromIndex);
|
||||||
groupData._items.insert(toIndex, item);
|
groupData._items.insert(toIndex, item);
|
||||||
notifyListeners();
|
_notify();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
groupData._items.add(item);
|
groupData._items.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notify) notifyListeners();
|
if (notify) _notify();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
groupData._items.add(item);
|
groupData._items.add(item);
|
||||||
if (notify) notifyListeners();
|
if (notify) _notify();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
'[$AppFlowyGroupController] $groupData replace $removedItem with $newItem at $index');
|
'[$AppFlowyGroupController] $groupData replace $removedItem with $newItem at $index');
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
_notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceOrInsertItem(AppFlowyGroupItem newItem) {
|
void replaceOrInsertItem(AppFlowyGroupItem newItem) {
|
||||||
@ -143,10 +143,10 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
groupData._items.removeAt(index);
|
groupData._items.removeAt(index);
|
||||||
groupData._items.insert(index, newItem);
|
groupData._items.insert(index, newItem);
|
||||||
notifyListeners();
|
_notify();
|
||||||
} else {
|
} else {
|
||||||
groupData._items.add(newItem);
|
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) !=
|
return groupData._items.indexWhere((element) => element.id == item.id) !=
|
||||||
-1;
|
-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _notify() {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [AppFlowyGroupData] represents the data of each group of the Board.
|
/// [AppFlowyGroupData] represents the data of each group of the Board.
|
||||||
|
@ -69,9 +69,9 @@ class FlexDragTargetData extends DragTargetData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class DraggingStateStorage {
|
abstract class DraggingStateStorage {
|
||||||
void write(String reorderFlexId, DraggingState state);
|
void insertState(String reorderFlexId, DraggingState state);
|
||||||
void remove(String reorderFlexId);
|
void removeState(String reorderFlexId);
|
||||||
DraggingState? read(String reorderFlexId);
|
DraggingState? readState(String reorderFlexId);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DraggingState {
|
class DraggingState {
|
||||||
|
@ -73,11 +73,15 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
|||||||
final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder;
|
final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder;
|
||||||
|
|
||||||
final AnimationController insertAnimationController;
|
final AnimationController insertAnimationController;
|
||||||
|
|
||||||
final AnimationController deleteAnimationController;
|
final AnimationController deleteAnimationController;
|
||||||
|
|
||||||
final bool useMoveAnimation;
|
final bool useMoveAnimation;
|
||||||
|
|
||||||
final bool draggable;
|
final bool draggable;
|
||||||
|
|
||||||
|
final double draggingOpacity;
|
||||||
|
|
||||||
const ReorderDragTarget({
|
const ReorderDragTarget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.child,
|
required this.child,
|
||||||
@ -94,6 +98,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
|||||||
this.onAccept,
|
this.onAccept,
|
||||||
this.onLeave,
|
this.onLeave,
|
||||||
this.draggableTargetBuilder,
|
this.draggableTargetBuilder,
|
||||||
|
this.draggingOpacity = 0.3,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -164,6 +169,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
|||||||
feedback: feedbackBuilder,
|
feedback: feedbackBuilder,
|
||||||
childWhenDragging: IgnorePointerWidget(
|
childWhenDragging: IgnorePointerWidget(
|
||||||
useIntrinsicSize: !widget.useMoveAnimation,
|
useIntrinsicSize: !widget.useMoveAnimation,
|
||||||
|
opacity: widget.draggingOpacity,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
onDragStarted: () {
|
onDragStarted: () {
|
||||||
@ -195,7 +201,10 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDraggableFeedback(
|
Widget _buildDraggableFeedback(
|
||||||
BuildContext context, BoxConstraints constraints, Widget child) {
|
BuildContext context,
|
||||||
|
BoxConstraints constraints,
|
||||||
|
Widget child,
|
||||||
|
) {
|
||||||
return Transform(
|
return Transform(
|
||||||
transform: Matrix4.rotationZ(0),
|
transform: Matrix4.rotationZ(0),
|
||||||
alignment: FractionalOffset.topLeft,
|
alignment: FractionalOffset.topLeft,
|
||||||
@ -205,7 +214,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: constraints,
|
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 {
|
class IgnorePointerWidget extends StatelessWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final bool useIntrinsicSize;
|
final bool useIntrinsicSize;
|
||||||
|
final double opacity;
|
||||||
|
|
||||||
const IgnorePointerWidget({
|
const IgnorePointerWidget({
|
||||||
required this.child,
|
required this.child,
|
||||||
|
required this.opacity,
|
||||||
this.useIntrinsicSize = false,
|
this.useIntrinsicSize = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -286,11 +298,10 @@ class IgnorePointerWidget extends StatelessWidget {
|
|||||||
? child
|
? child
|
||||||
: SizedBox(width: 0.0, height: 0.0, child: child);
|
: SizedBox(width: 0.0, height: 0.0, child: child);
|
||||||
|
|
||||||
final opacity = useIntrinsicSize ? 0.3 : 0.0;
|
|
||||||
return IgnorePointer(
|
return IgnorePointer(
|
||||||
ignoring: true,
|
ignoring: true,
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: opacity,
|
opacity: useIntrinsicSize ? opacity : 0.0,
|
||||||
child: sizedChild,
|
child: sizedChild,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -300,8 +311,10 @@ class IgnorePointerWidget extends StatelessWidget {
|
|||||||
class AbsorbPointerWidget extends StatelessWidget {
|
class AbsorbPointerWidget extends StatelessWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final bool useIntrinsicSize;
|
final bool useIntrinsicSize;
|
||||||
|
final double opacity;
|
||||||
const AbsorbPointerWidget({
|
const AbsorbPointerWidget({
|
||||||
required this.child,
|
required this.child,
|
||||||
|
required this.opacity,
|
||||||
this.useIntrinsicSize = false,
|
this.useIntrinsicSize = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -312,10 +325,9 @@ class AbsorbPointerWidget extends StatelessWidget {
|
|||||||
? child
|
? child
|
||||||
: SizedBox(width: 0.0, height: 0.0, child: child);
|
: SizedBox(width: 0.0, height: 0.0, child: child);
|
||||||
|
|
||||||
final opacity = useIntrinsicSize ? 0.3 : 0.0;
|
|
||||||
return AbsorbPointer(
|
return AbsorbPointer(
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: opacity,
|
opacity: useIntrinsicSize ? opacity : 0.0,
|
||||||
child: sizedChild,
|
child: sizedChild,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -494,6 +506,7 @@ class _FakeDragTargetState<T extends DragTargetData>
|
|||||||
sizeFactor: widget.deleteAnimationController,
|
sizeFactor: widget.deleteAnimationController,
|
||||||
axis: Axis.vertical,
|
axis: Axis.vertical,
|
||||||
child: AbsorbPointerWidget(
|
child: AbsorbPointerWidget(
|
||||||
|
opacity: 0.3,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -503,6 +516,7 @@ class _FakeDragTargetState<T extends DragTargetData>
|
|||||||
axis: Axis.vertical,
|
axis: Axis.vertical,
|
||||||
child: AbsorbPointerWidget(
|
child: AbsorbPointerWidget(
|
||||||
useIntrinsicSize: true,
|
useIntrinsicSize: true,
|
||||||
|
opacity: 0.3,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -81,7 +81,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
|
|||||||
delegate.cancel();
|
delegate.cancel();
|
||||||
} else {
|
} else {
|
||||||
// Ignore the event if the dragTarget overlaps with the other column's dragTargets.
|
// 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) {
|
if (columnKeys != null) {
|
||||||
final keys = columnKeys.values.toList();
|
final keys = columnKeys.values.toList();
|
||||||
if (dragTargetData.isOverlapWithWidgets(keys)) {
|
if (dragTargetData.isOverlapWithWidgets(keys)) {
|
||||||
@ -102,8 +102,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
|
|||||||
delegate.dragTargetDidMoveToReorderFlex(
|
delegate.dragTargetDidMoveToReorderFlex(
|
||||||
dragTargetId, dragTargetData, index);
|
dragTargetId, dragTargetData, index);
|
||||||
|
|
||||||
columnsState
|
columnsState.reorderFlexActionMap[dragTargetId]
|
||||||
.getReorderFlexState(groupId: dragTargetId)
|
|
||||||
?.resetDragTargetIndex(index);
|
?.resetDragTargetIndex(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -31,9 +31,32 @@ abstract class ReoderFlexItem {
|
|||||||
String get id;
|
String get id;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ReorderDragTargetIndexKeyStorage {
|
/// Cache each dragTarget's key.
|
||||||
void addKey(String reorderFlexId, String key, GlobalObjectKey value);
|
/// For the moment, the key is used to locate the render object that will
|
||||||
GlobalObjectKey? readKey(String reorderFlexId, String key);
|
/// 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 {
|
class ReorderFlexConfig {
|
||||||
@ -78,9 +101,12 @@ class ReorderFlex extends StatefulWidget {
|
|||||||
|
|
||||||
final DragTargetInterceptor? interceptor;
|
final DragTargetInterceptor? interceptor;
|
||||||
|
|
||||||
|
/// Save the [DraggingState] if the current [ReorderFlex] get reinitialize.
|
||||||
final DraggingStateStorage? dragStateStorage;
|
final DraggingStateStorage? dragStateStorage;
|
||||||
|
|
||||||
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
|
final ReorderDragTargeKeys? dragTargetKeys;
|
||||||
|
|
||||||
|
final ReorderFlexAction? reorderFlexAction;
|
||||||
|
|
||||||
final bool reorderable;
|
final bool reorderable;
|
||||||
|
|
||||||
@ -93,10 +119,11 @@ class ReorderFlex extends StatefulWidget {
|
|||||||
required this.onReorder,
|
required this.onReorder,
|
||||||
this.reorderable = true,
|
this.reorderable = true,
|
||||||
this.dragStateStorage,
|
this.dragStateStorage,
|
||||||
this.dragTargetIndexKeyStorage,
|
this.dragTargetKeys,
|
||||||
this.onDragStarted,
|
this.onDragStarted,
|
||||||
this.onDragEnded,
|
this.onDragEnded,
|
||||||
this.interceptor,
|
this.interceptor,
|
||||||
|
this.reorderFlexAction,
|
||||||
this.direction = Axis.vertical,
|
this.direction = Axis.vertical,
|
||||||
}) : assert(children.every((Widget w) => w.key != null),
|
}) : assert(children.every((Widget w) => w.key != null),
|
||||||
'All child must have a key.'),
|
'All child must have a key.'),
|
||||||
@ -109,7 +136,7 @@ class ReorderFlex extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ReorderFlexState extends State<ReorderFlex>
|
class ReorderFlexState extends State<ReorderFlex>
|
||||||
with ReorderFlexMinxi, TickerProviderStateMixin<ReorderFlex> {
|
with ReorderFlexMixin, TickerProviderStateMixin<ReorderFlex> {
|
||||||
/// Controls scrolls and measures scroll progress.
|
/// Controls scrolls and measures scroll progress.
|
||||||
late ScrollController _scrollController;
|
late ScrollController _scrollController;
|
||||||
|
|
||||||
@ -131,11 +158,11 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
void initState() {
|
void initState() {
|
||||||
_notifier = ReorderFlexNotifier();
|
_notifier = ReorderFlexNotifier();
|
||||||
final flexId = widget.reorderFlexId;
|
final flexId = widget.reorderFlexId;
|
||||||
dragState = widget.dragStateStorage?.read(flexId) ??
|
dragState = widget.dragStateStorage?.readState(flexId) ??
|
||||||
DraggingState(widget.reorderFlexId);
|
DraggingState(widget.reorderFlexId);
|
||||||
Log.trace('[DragTarget] init dragState: $dragState');
|
Log.trace('[DragTarget] init dragState: $dragState');
|
||||||
|
|
||||||
widget.dragStateStorage?.remove(flexId);
|
widget.dragStateStorage?.removeState(flexId);
|
||||||
|
|
||||||
_animation = DragTargetAnimation(
|
_animation = DragTargetAnimation(
|
||||||
reorderAnimationDuration: widget.config.reorderAnimationDuration,
|
reorderAnimationDuration: widget.config.reorderAnimationDuration,
|
||||||
@ -148,6 +175,14 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
widget.reorderFlexAction?._scrollToBottom = (fn) {
|
||||||
|
scrollToBottom(fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
widget.reorderFlexAction?._resetDragTargetIndex = (index) {
|
||||||
|
resetDragTargetIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +219,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
|
|
||||||
final indexKey = GlobalObjectKey(child.key!);
|
final indexKey = GlobalObjectKey(child.key!);
|
||||||
// Save the index key for quick access
|
// Save the index key for quick access
|
||||||
widget.dragTargetIndexKeyStorage?.addKey(
|
widget.dragTargetKeys?.insertDragTarget(
|
||||||
widget.reorderFlexId,
|
widget.reorderFlexId,
|
||||||
item.id,
|
item.id,
|
||||||
indexKey,
|
indexKey,
|
||||||
@ -236,8 +271,12 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
/// [childIndex]: the index of the child in a list
|
/// [childIndex]: the index of the child in a list
|
||||||
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) {
|
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) {
|
||||||
return Builder(builder: (context) {
|
return Builder(builder: (context) {
|
||||||
final ReorderDragTarget dragTarget =
|
final ReorderDragTarget dragTarget = _buildDragTarget(
|
||||||
_buildDragTarget(context, child, childIndex, indexKey);
|
context,
|
||||||
|
child,
|
||||||
|
childIndex,
|
||||||
|
indexKey,
|
||||||
|
);
|
||||||
int shiftedIndex = childIndex;
|
int shiftedIndex = childIndex;
|
||||||
|
|
||||||
if (dragState.isOverlapWithPhantom()) {
|
if (dragState.isOverlapWithPhantom()) {
|
||||||
@ -342,6 +381,15 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ReorderFlexState of(BuildContext context) {
|
||||||
|
if (context is StatefulElement && context.state is ReorderFlexState) {
|
||||||
|
return context.state as ReorderFlexState;
|
||||||
|
}
|
||||||
|
final ReorderFlexState? result =
|
||||||
|
context.findAncestorStateOfType<ReorderFlexState>();
|
||||||
|
return result!;
|
||||||
|
}
|
||||||
|
|
||||||
ReorderDragTarget _buildDragTarget(
|
ReorderDragTarget _buildDragTarget(
|
||||||
BuildContext builderContext,
|
BuildContext builderContext,
|
||||||
Widget child,
|
Widget child,
|
||||||
@ -364,7 +412,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
"[DragTarget] Group:[${widget.dataSource.identifier}] start dragging item at $draggingIndex");
|
"[DragTarget] Group:[${widget.dataSource.identifier}] start dragging item at $draggingIndex");
|
||||||
_startDragging(draggingWidget, draggingIndex, size);
|
_startDragging(draggingWidget, draggingIndex, size);
|
||||||
widget.onDragStarted?.call(draggingIndex);
|
widget.onDragStarted?.call(draggingIndex);
|
||||||
widget.dragStateStorage?.remove(widget.reorderFlexId);
|
widget.dragStateStorage?.removeState(widget.reorderFlexId);
|
||||||
},
|
},
|
||||||
onDragMoved: (dragTargetData, offset) {
|
onDragMoved: (dragTargetData, offset) {
|
||||||
dragTargetData.dragTargetOffset = offset;
|
dragTargetData.dragTargetOffset = offset;
|
||||||
@ -435,6 +483,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
||||||
useMoveAnimation: widget.config.useMoveAnimation,
|
useMoveAnimation: widget.config.useMoveAnimation,
|
||||||
draggable: widget.reorderable,
|
draggable: widget.reorderable,
|
||||||
|
draggingOpacity: widget.config.draggingWidgetOpacity,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -487,7 +536,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
}
|
}
|
||||||
|
|
||||||
dragState.setStartDraggingIndex(dragTargetIndex);
|
dragState.setStartDraggingIndex(dragTargetIndex);
|
||||||
widget.dragStateStorage?.write(
|
widget.dragStateStorage?.insertState(
|
||||||
widget.reorderFlexId,
|
widget.reorderFlexId,
|
||||||
dragState,
|
dragState,
|
||||||
);
|
);
|
||||||
@ -581,46 +630,46 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void scrollToBottom(VoidCallback? completed) {
|
void scrollToBottom(void Function(BuildContext)? completed) {
|
||||||
if (_scrolling) {
|
if (_scrolling) {
|
||||||
completed?.call();
|
completed?.call(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.dataSource.items.isNotEmpty) {
|
if (widget.dataSource.items.isNotEmpty) {
|
||||||
final item = widget.dataSource.items.last;
|
final item = widget.dataSource.items.last;
|
||||||
final indexKey = widget.dragTargetIndexKeyStorage?.readKey(
|
final dragTargetKey = widget.dragTargetKeys?.getDragTarget(
|
||||||
widget.reorderFlexId,
|
widget.reorderFlexId,
|
||||||
item.id,
|
item.id,
|
||||||
);
|
);
|
||||||
if (indexKey == null) {
|
if (dragTargetKey == null) {
|
||||||
completed?.call();
|
completed?.call(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final indexContext = indexKey.currentContext;
|
final dragTargetContext = dragTargetKey.currentContext;
|
||||||
if (indexContext == null || _scrollController.hasClients == false) {
|
if (dragTargetContext == null || _scrollController.hasClients == false) {
|
||||||
completed?.call();
|
completed?.call(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final renderObject = indexContext.findRenderObject();
|
final dragTargetRenderObject = dragTargetContext.findRenderObject();
|
||||||
if (renderObject != null) {
|
if (dragTargetRenderObject != null) {
|
||||||
_scrolling = true;
|
_scrolling = true;
|
||||||
_scrollController.position
|
_scrollController.position
|
||||||
.ensureVisible(
|
.ensureVisible(
|
||||||
renderObject,
|
dragTargetRenderObject,
|
||||||
alignment: 0.5,
|
alignment: 0.5,
|
||||||
duration: const Duration(milliseconds: 120),
|
duration: const Duration(milliseconds: 120),
|
||||||
)
|
)
|
||||||
.then((value) {
|
.then((value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_scrolling = false;
|
_scrolling = false;
|
||||||
completed?.call();
|
completed?.call(context);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
completed?.call();
|
completed?.call(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import '../transitions.dart';
|
import '../transitions.dart';
|
||||||
import 'drag_target.dart';
|
import 'drag_target.dart';
|
||||||
|
|
||||||
mixin ReorderFlexMinxi {
|
mixin ReorderFlexMixin {
|
||||||
@protected
|
@protected
|
||||||
Widget makeAppearingWidget(
|
Widget makeAppearingWidget(
|
||||||
Widget child,
|
Widget child,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: appflowy_board
|
name: appflowy_board
|
||||||
description: AppFlowyBoard is a board-style widget that consists of multi-groups. It supports drag and drop between different groups.
|
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
|
homepage: https://github.com/AppFlowy-IO/AppFlowy
|
||||||
repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board
|
repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board
|
||||||
|
|
||||||
|
8
frontend/rust-lib/Cargo.lock
generated
8
frontend/rust-lib/Cargo.lock
generated
@ -1444,9 +1444,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@ -1610,9 +1610,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.8.1"
|
version = "1.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
@ -34,7 +34,7 @@ rayon = "1.5.2"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = {version = "1.0"}
|
serde_json = {version = "1.0"}
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
indexmap = {version = "1.8.1", features = ["serde"]}
|
indexmap = {version = "1.9.1", features = ["serde"]}
|
||||||
fancy-regex = "0.10.0"
|
fancy-regex = "0.10.0"
|
||||||
regex = "1.5.6"
|
regex = "1.5.6"
|
||||||
url = { version = "2"}
|
url = { version = "2"}
|
||||||
|
@ -98,6 +98,7 @@ pub struct MoveGroupPayloadPB {
|
|||||||
pub to_group_id: String,
|
pub to_group_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct MoveGroupParams {
|
pub struct MoveGroupParams {
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
pub from_group_id: String,
|
pub from_group_id: String,
|
||||||
|
@ -368,6 +368,7 @@ impl GridRevisionEditor {
|
|||||||
Ok(row_pb)
|
Ok(row_pb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||||
let _ = self.view_manager.move_group(params).await?;
|
let _ = self.view_manager.move_group(params).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -173,6 +173,7 @@ impl GridViewRevisionEditor {
|
|||||||
Ok(groups.into_iter().map(GroupPB::from).collect())
|
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<()> {
|
pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||||
let _ = self
|
let _ = self
|
||||||
.group_controller
|
.group_controller
|
||||||
@ -180,7 +181,7 @@ impl GridViewRevisionEditor {
|
|||||||
.await
|
.await
|
||||||
.move_group(¶ms.from_group_id, ¶ms.to_group_id)?;
|
.move_group(¶ms.from_group_id, ¶ms.to_group_id)?;
|
||||||
match self.group_controller.read().await.get_group(¶ms.from_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)) => {
|
Some((index, group)) => {
|
||||||
let inserted_group = InsertedGroupPB {
|
let inserted_group = InsertedGroupPB {
|
||||||
group: GroupPB::from(group),
|
group: GroupPB::from(group),
|
||||||
@ -228,7 +229,11 @@ impl GridViewRevisionEditor {
|
|||||||
let _ = self
|
let _ = self
|
||||||
.modify(|pad| {
|
.modify(|pad| {
|
||||||
let configuration = default_group_configuration(&field_rev);
|
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)
|
Ok(changeset)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -496,10 +501,11 @@ impl GroupConfigurationWriter for GroupConfigurationWriterImpl {
|
|||||||
let field_id = field_id.to_owned();
|
let field_id = field_id.to_owned();
|
||||||
|
|
||||||
wrap_future(async move {
|
wrap_future(async move {
|
||||||
let changeset = view_pad
|
let changeset = view_pad.write().await.insert_or_update_group_configuration(
|
||||||
.write()
|
&field_id,
|
||||||
.await
|
&field_type,
|
||||||
.insert_group(&field_id, &field_type, group_configuration)?;
|
group_configuration,
|
||||||
|
)?;
|
||||||
|
|
||||||
if let Some(changeset) = changeset {
|
if let Some(changeset) = changeset {
|
||||||
let _ = apply_change(&user_id, rev_manager, changeset).await?;
|
let _ = apply_change(&user_id, rev_manager, changeset).await?;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::entities::{GroupPB, GroupViewChangesetPB};
|
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_error::{FlowyError, FlowyResult};
|
||||||
use flowy_grid_data_model::revision::{
|
use flowy_grid_data_model::revision::{
|
||||||
FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision,
|
FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision,
|
||||||
@ -29,10 +29,7 @@ impl<T> std::fmt::Display for GroupContext<T> {
|
|||||||
self.groups_map.iter().for_each(|(_, group)| {
|
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!("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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,7 +41,7 @@ pub struct GroupContext<C> {
|
|||||||
field_rev: Arc<FieldRevision>,
|
field_rev: Arc<FieldRevision>,
|
||||||
groups_map: IndexMap<String, Group>,
|
groups_map: IndexMap<String, Group>,
|
||||||
/// default_group is used to store the rows that don't belong to any groups.
|
/// default_group is used to store the rows that don't belong to any groups.
|
||||||
default_group: Group,
|
// default_group: Group,
|
||||||
writer: Arc<dyn GroupConfigurationWriter>,
|
writer: Arc<dyn GroupConfigurationWriter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,16 +56,6 @@ where
|
|||||||
reader: Arc<dyn GroupConfigurationReader>,
|
reader: Arc<dyn GroupConfigurationReader>,
|
||||||
writer: Arc<dyn GroupConfigurationWriter>,
|
writer: Arc<dyn GroupConfigurationWriter>,
|
||||||
) -> FlowyResult<Self> {
|
) -> FlowyResult<Self> {
|
||||||
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 {
|
let configuration = match reader.get_configuration().await {
|
||||||
None => {
|
None => {
|
||||||
let default_configuration = default_group_configuration(&field_rev);
|
let default_configuration = default_group_configuration(&field_rev);
|
||||||
@ -80,24 +67,22 @@ where
|
|||||||
Some(configuration) => configuration,
|
Some(configuration) => configuration,
|
||||||
};
|
};
|
||||||
|
|
||||||
// let configuration = C::from_configuration_content(&configuration_rev.content)?;
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
view_id,
|
view_id,
|
||||||
field_rev,
|
field_rev,
|
||||||
groups_map: IndexMap::new(),
|
groups_map: IndexMap::new(),
|
||||||
default_group,
|
|
||||||
writer,
|
writer,
|
||||||
configuration,
|
configuration,
|
||||||
configuration_content: PhantomData,
|
configuration_content: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_default_group(&self) -> &Group {
|
pub(crate) fn get_default_group(&self) -> Option<&Group> {
|
||||||
&self.default_group
|
self.groups_map.get(&self.field_rev.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_mut_default_group(&mut self) -> &mut Group {
|
pub(crate) fn get_mut_default_group(&mut self) -> Option<&mut Group> {
|
||||||
&mut self.default_group
|
self.groups_map.get_mut(&self.field_rev.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the groups without the default group
|
/// Returns the groups without the default group
|
||||||
@ -122,8 +107,6 @@ where
|
|||||||
self.groups_map.iter_mut().for_each(|(_, group)| {
|
self.groups_map.iter_mut().for_each(|(_, group)| {
|
||||||
each(group);
|
each(group);
|
||||||
});
|
});
|
||||||
|
|
||||||
each(&mut self.default_group);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> {
|
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);
|
let to_index = self.groups_map.get_index_of(to_id);
|
||||||
match (from_index, to_index) {
|
match (from_index, to_index) {
|
||||||
(Some(from_index), Some(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| {
|
self.mut_configuration(|configuration| {
|
||||||
let from_index = configuration.groups.iter().position(|group| group.id == from_id);
|
let from_index = configuration.groups.iter().position(|group| group.id == from_id);
|
||||||
let to_index = configuration.groups.iter().position(|group| group.id == to_id);
|
let to_index = configuration.groups.iter().position(|group| group.id == to_id);
|
||||||
if let (Some(from), Some(to)) = (from_index, to_index) {
|
tracing::info!("Configuration groups: {:?} ", configuration.groups);
|
||||||
configuration.groups.swap(from, to);
|
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(())
|
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(
|
pub(crate) fn init_groups(
|
||||||
&mut self,
|
&mut self,
|
||||||
generated_groups: Vec<GeneratedGroup>,
|
generated_groups: Vec<GeneratedGroup>,
|
||||||
reset: bool,
|
|
||||||
) -> FlowyResult<Option<GroupViewChangesetPB>> {
|
) -> FlowyResult<Option<GroupViewChangesetPB>> {
|
||||||
let mut new_groups = vec![];
|
let mut new_groups = vec![];
|
||||||
let mut filter_content_map = HashMap::new();
|
let mut filter_content_map = HashMap::new();
|
||||||
@ -159,16 +146,17 @@ where
|
|||||||
new_groups.push(generate_group.group_rev);
|
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 {
|
let MergeGroupResult {
|
||||||
mut all_group_revs,
|
mut all_group_revs,
|
||||||
new_group_revs,
|
new_group_revs,
|
||||||
updated_group_revs: _,
|
updated_group_revs: _,
|
||||||
deleted_group_revs,
|
deleted_group_revs,
|
||||||
} = if reset {
|
} = merge_groups(old_groups, new_groups);
|
||||||
merge_groups(&[], new_groups)
|
|
||||||
} else {
|
|
||||||
merge_groups(&self.configuration.groups, new_groups)
|
|
||||||
};
|
|
||||||
|
|
||||||
let deleted_group_ids = deleted_group_revs
|
let deleted_group_ids = deleted_group_revs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -197,31 +185,23 @@ where
|
|||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
let mut old_group = configuration.groups.remove(pos);
|
let mut old_group = configuration.groups.remove(pos);
|
||||||
group_rev.update_with_other(&old_group);
|
group_rev.update_with_other(&old_group);
|
||||||
|
is_changed = is_group_changed(group_rev, &old_group);
|
||||||
|
|
||||||
// Take the GroupRevision if the name has changed
|
old_group.name = group_rev.name.clone();
|
||||||
if is_group_changed(group_rev, &old_group) {
|
configuration.groups.insert(pos, old_group);
|
||||||
old_group.name = group_rev.name.clone();
|
|
||||||
is_changed = true;
|
|
||||||
configuration.groups.insert(pos, old_group);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is_changed
|
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| {
|
all_group_revs.into_iter().for_each(|group_rev| {
|
||||||
if let Some(filter_content) = filter_content_map.get(&group_rev.id) {
|
let filter_content = filter_content_map
|
||||||
let group = Group::new(
|
.get(&group_rev.id)
|
||||||
group_rev.id,
|
.cloned()
|
||||||
self.field_rev.id.clone(),
|
.unwrap_or_else(|| "".to_owned());
|
||||||
group_rev.name,
|
let group = Group::new(group_rev.id, self.field_rev.id.clone(), group_rev.name, filter_content);
|
||||||
filter_content.clone(),
|
self.groups_map.insert(group.id.clone(), group);
|
||||||
);
|
|
||||||
self.groups_map.insert(group.id.clone(), group);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let new_groups = new_group_revs
|
let new_groups = new_group_revs
|
||||||
@ -269,6 +249,7 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
pub fn save_configuration(&self) -> FlowyResult<()> {
|
pub fn save_configuration(&self) -> FlowyResult<()> {
|
||||||
let configuration = (&*self.configuration).clone();
|
let configuration = (&*self.configuration).clone();
|
||||||
let writer = self.writer.clone();
|
let writer = self.writer.clone();
|
||||||
@ -311,13 +292,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_groups(old_groups: &[GroupRevision], new_groups: Vec<GroupRevision>) -> MergeGroupResult {
|
fn merge_groups(old_groups: Vec<GroupRevision>, new_groups: Vec<GroupRevision>) -> MergeGroupResult {
|
||||||
let mut merge_result = MergeGroupResult::new();
|
let mut merge_result = MergeGroupResult::new();
|
||||||
if old_groups.is_empty() {
|
// if old_groups.is_empty() {
|
||||||
merge_result.all_group_revs = new_groups.clone();
|
// merge_result.all_group_revs.extend(new_groups.clone());
|
||||||
merge_result.new_group_revs = new_groups;
|
// merge_result.all_group_revs.push(default_group);
|
||||||
return merge_result;
|
// merge_result.new_group_revs = new_groups;
|
||||||
}
|
// return merge_result;
|
||||||
|
// }
|
||||||
|
|
||||||
// group_map is a helper map is used to filter out the new groups.
|
// group_map is a helper map is used to filter out the new groups.
|
||||||
let mut new_group_map: IndexMap<String, GroupRevision> = IndexMap::new();
|
let mut new_group_map: IndexMap<String, GroupRevision> = IndexMap::new();
|
||||||
@ -329,19 +311,20 @@ fn merge_groups(old_groups: &[GroupRevision], new_groups: Vec<GroupRevision>) ->
|
|||||||
for old in old_groups {
|
for old in old_groups {
|
||||||
if let Some(new) = new_group_map.remove(&old.id) {
|
if let Some(new) = new_group_map.remove(&old.id) {
|
||||||
merge_result.all_group_revs.push(new.clone());
|
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);
|
merge_result.updated_group_revs.push(new);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
merge_result.deleted_group_revs.push(old.clone());
|
merge_result.all_group_revs.push(old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find out the new groups
|
// Find out the new groups
|
||||||
|
new_group_map.reverse();
|
||||||
let new_groups = new_group_map.into_values();
|
let new_groups = new_group_map.into_values();
|
||||||
for (_, group) in new_groups.into_iter().enumerate() {
|
for (_, group) in new_groups.into_iter().enumerate() {
|
||||||
merge_result.all_group_revs.push(group.clone());
|
merge_result.all_group_revs.insert(0, group.clone());
|
||||||
merge_result.new_group_revs.push(group);
|
merge_result.new_group_revs.insert(0, group);
|
||||||
}
|
}
|
||||||
merge_result
|
merge_result
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ where
|
|||||||
pub async fn new(field_rev: &Arc<FieldRevision>, mut configuration: GroupContext<C>) -> FlowyResult<Self> {
|
pub async fn new(field_rev: &Arc<FieldRevision>, mut configuration: GroupContext<C>) -> FlowyResult<Self> {
|
||||||
let type_option = field_rev.get_type_option::<T>(field_rev.ty);
|
let type_option = field_rev.get_type_option::<T>(field_rev.ty);
|
||||||
let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
|
let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
|
||||||
let _ = configuration.init_groups(groups, true)?;
|
let _ = configuration.init_groups(groups)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
field_id: field_rev.id.clone(),
|
field_id: field_rev.id.clone(),
|
||||||
@ -105,8 +105,8 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
row_rev: &RowRevision,
|
row_rev: &RowRevision,
|
||||||
other_group_changesets: &[GroupChangesetPB],
|
other_group_changesets: &[GroupChangesetPB],
|
||||||
) -> GroupChangesetPB {
|
) -> Option<GroupChangesetPB> {
|
||||||
let default_group = self.group_ctx.get_mut_default_group();
|
let default_group = self.group_ctx.get_mut_default_group()?;
|
||||||
|
|
||||||
// [other_group_inserted_row] contains all the inserted rows except the default group.
|
// [other_group_inserted_row] contains all the inserted rows except the default group.
|
||||||
let other_group_inserted_row = other_group_changesets
|
let other_group_inserted_row = other_group_changesets
|
||||||
@ -163,7 +163,7 @@ where
|
|||||||
}
|
}
|
||||||
default_group.rows.retain(|row| !deleted_row_ids.contains(&row.id));
|
default_group.rows.retain(|row| !deleted_row_ids.contains(&row.id));
|
||||||
changeset.deleted_rows.extend(deleted_row_ids);
|
changeset.deleted_rows.extend(deleted_row_ids);
|
||||||
changeset
|
Some(changeset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,11 +182,14 @@ where
|
|||||||
|
|
||||||
fn groups(&self) -> Vec<Group> {
|
fn groups(&self) -> Vec<Group> {
|
||||||
if self.use_default_group() {
|
if self.use_default_group() {
|
||||||
let mut groups: Vec<Group> = 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()
|
self.group_ctx.groups().into_iter().cloned().collect()
|
||||||
|
} else {
|
||||||
|
self.group_ctx
|
||||||
|
.groups()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|group| group.id != self.field_id)
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,17 +219,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if grouped_rows.is_empty() {
|
if !grouped_rows.is_empty() {
|
||||||
self.group_ctx.get_mut_default_group().add_row(row_rev.into());
|
|
||||||
} else {
|
|
||||||
for group_row in grouped_rows {
|
for group_row in grouped_rows {
|
||||||
if let Some(group) = self.group_ctx.get_mut_group(&group_row.group_id) {
|
if let Some(group) = self.group_ctx.get_mut_group(&group_row.group_id) {
|
||||||
group.add_row(group_row.row);
|
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_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
|
||||||
let cell_data = cell_bytes.parser::<P>()?;
|
let cell_data = cell_bytes.parser::<P>()?;
|
||||||
let mut changesets = self.add_row_if_match(row_rev, &cell_data);
|
let mut changesets = self.add_row_if_match(row_rev, &cell_data);
|
||||||
let default_group_changeset = self.update_default_group(row_rev, &changesets);
|
if let Some(default_group_changeset) = self.update_default_group(row_rev, &changesets) {
|
||||||
tracing::trace!("default_group_changeset: {}", default_group_changeset);
|
tracing::trace!("default_group_changeset: {}", default_group_changeset);
|
||||||
if !default_group_changeset.is_empty() {
|
if !default_group_changeset.is_empty() {
|
||||||
changesets.push(default_group_changeset);
|
changesets.push(default_group_changeset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(changesets)
|
Ok(changesets)
|
||||||
} else {
|
} else {
|
||||||
@ -268,12 +273,13 @@ where
|
|||||||
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
|
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
|
||||||
let cell_data = cell_bytes.parser::<P>()?;
|
let cell_data = cell_bytes.parser::<P>()?;
|
||||||
Ok(self.remove_row_if_match(row_rev, &cell_data))
|
Ok(self.remove_row_if_match(row_rev, &cell_data))
|
||||||
} else {
|
} else if let Some(group) = self.group_ctx.get_default_group() {
|
||||||
let group = self.group_ctx.get_default_group();
|
|
||||||
Ok(vec![GroupChangesetPB::delete(
|
Ok(vec![GroupChangesetPB::delete(
|
||||||
group.id.clone(),
|
group.id.clone(),
|
||||||
vec![row_rev.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<Option<GroupViewChangesetPB>> {
|
fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>> {
|
||||||
let type_option = field_rev.get_type_option::<T>(field_rev.ty);
|
let type_option = field_rev.get_type_option::<T>(field_rev.ty);
|
||||||
let groups = G::generate_groups(&field_rev.id, &self.group_ctx, &type_option);
|
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)
|
Ok(changeset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,17 @@ pub struct Group {
|
|||||||
pub is_visible: bool,
|
pub is_visible: bool,
|
||||||
pub(crate) rows: Vec<RowPB>,
|
pub(crate) rows: Vec<RowPB>,
|
||||||
|
|
||||||
/// [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,
|
pub filter_content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Group {
|
impl Group {
|
||||||
pub fn new(id: String, field_id: String, name: String, filter_content: String) -> Self {
|
pub fn new(id: String, field_id: String, name: String, filter_content: String) -> Self {
|
||||||
|
let is_default = id == field_id;
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
field_id,
|
field_id,
|
||||||
is_default: false,
|
is_default,
|
||||||
is_visible: true,
|
is_visible: true,
|
||||||
name,
|
name,
|
||||||
rows: vec![],
|
rows: vec![],
|
||||||
|
@ -8,8 +8,8 @@ use crate::services::group::{
|
|||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
use flowy_grid_data_model::revision::{
|
use flowy_grid_data_model::revision::{
|
||||||
CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision,
|
CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision,
|
||||||
LayoutRevision, NumberGroupConfigurationRevision, RowRevision, SelectOptionGroupConfigurationRevision,
|
GroupRevision, LayoutRevision, NumberGroupConfigurationRevision, RowRevision,
|
||||||
TextGroupConfigurationRevision, UrlGroupConfigurationRevision,
|
SelectOptionGroupConfigurationRevision, TextGroupConfigurationRevision, UrlGroupConfigurationRevision,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
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_id = field_rev.id.clone();
|
||||||
let field_type_rev = field_rev.ty;
|
let field_type_rev = field_rev.ty;
|
||||||
let field_type: FieldType = field_rev.ty.into();
|
let field_type: FieldType = field_rev.ty.into();
|
||||||
match field_type {
|
let mut group_configuration_rev = match field_type {
|
||||||
FieldType::RichText => {
|
FieldType::RichText => {
|
||||||
GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default())
|
GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -112,5 +112,23 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat
|
|||||||
FieldType::URL => {
|
FieldType::URL => {
|
||||||
GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap()
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,6 +370,28 @@ async fn group_move_group_test() {
|
|||||||
test.run_scripts(scripts).await;
|
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]
|
#[tokio::test]
|
||||||
async fn group_insert_single_select_option_test() {
|
async fn group_insert_single_select_option_test() {
|
||||||
let mut test = GridGroupTest::new().await;
|
let mut test = GridGroupTest::new().await;
|
||||||
@ -402,7 +424,7 @@ async fn group_group_by_other_field() {
|
|||||||
group_index: 1,
|
group_index: 1,
|
||||||
row_count: 2,
|
row_count: 2,
|
||||||
},
|
},
|
||||||
AssertGroupCount(4),
|
AssertGroupCount(5),
|
||||||
];
|
];
|
||||||
test.run_scripts(scripts).await;
|
test.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
8
shared-lib/Cargo.lock
generated
8
shared-lib/Cargo.lock
generated
@ -650,9 +650,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@ -732,9 +732,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.8.1"
|
version = "1.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
@ -12,7 +12,7 @@ serde_json = {version = "1.0"}
|
|||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
flowy-error-code = { path = "../flowy-error-code"}
|
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"] }
|
tracing = { version = "0.1", features = ["log"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
@ -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) {
|
pub fn update_with_other(&mut self, other: &GroupRevision) {
|
||||||
self.visible = other.visible
|
self.visible = other.visible
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ impl GridViewRevisionPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||||
pub fn insert_group(
|
pub fn insert_or_update_group_configuration(
|
||||||
&mut self,
|
&mut self,
|
||||||
field_id: &str,
|
field_id: &str,
|
||||||
field_type: &FieldTypeRevision,
|
field_type: &FieldTypeRevision,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user