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 f84c2a2bd1..422f1dff2b 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -20,19 +20,19 @@ import 'group_controller.dart'; part 'board_bloc.freezed.dart'; class BoardBloc extends Bloc { - final BoardDataController _dataController; - late final AFBoardDataController afBoardDataController; + final BoardDataController _gridDataController; + late final AFBoardDataController boardController; final MoveRowFFIService _rowService; LinkedHashMap groupControllers = LinkedHashMap.new(); - GridFieldCache get fieldCache => _dataController.fieldCache; - String get gridId => _dataController.gridId; + GridFieldCache get fieldCache => _gridDataController.fieldCache; + String get gridId => _gridDataController.gridId; BoardBloc({required ViewPB view}) : _rowService = MoveRowFFIService(gridId: view.id), - _dataController = BoardDataController(view: view), + _gridDataController = BoardDataController(view: view), super(BoardState.initial(view.id)) { - afBoardDataController = AFBoardDataController( + boardController = AFBoardDataController( onMoveColumn: ( fromColumnId, fromIndex, @@ -70,7 +70,7 @@ class BoardBloc extends Bloc { await _loadGrid(emit); }, createRow: (groupId) async { - final result = await _dataController.createBoardCard(groupId); + final result = await _gridDataController.createBoardCard(groupId); result.fold( (rowPB) { emit(state.copyWith(editingRow: some(rowPB))); @@ -126,7 +126,7 @@ class BoardBloc extends Bloc { @override Future close() async { - await _dataController.dispose(); + await _gridDataController.dispose(); for (final controller in groupControllers.values) { controller.dispose(); } @@ -135,7 +135,7 @@ class BoardBloc extends Bloc { void initializeGroups(List groups) { for (final group in groups) { - final delegate = GroupControllerDelegateImpl(afBoardDataController); + final delegate = GroupControllerDelegateImpl(boardController); final controller = GroupController( gridId: state.gridId, group: group, @@ -147,12 +147,12 @@ class BoardBloc extends Bloc { } GridRowCache? getRowCache(String blockId) { - final GridBlockCache? blockCache = _dataController.blocks[blockId]; + final GridBlockCache? blockCache = _gridDataController.blocks[blockId]; return blockCache?.rowCache; } void _startListening() { - _dataController.addListener( + _gridDataController.addListener( onGridChanged: (grid) { if (!isClosed) { add(BoardEvent.didReceiveGridUpdate(grid)); @@ -162,18 +162,34 @@ class BoardBloc extends Bloc { List columns = groups.map((group) { return AFBoardColumnData( id: group.groupId, - desc: group.desc, + name: group.desc, items: _buildRows(group.rows), customData: group, ); }).toList(); - afBoardDataController.addColumns(columns); + boardController.addColumns(columns); initializeGroups(groups); }, onRowsChanged: (List rowInfos, RowsChangedReason reason) { add(BoardEvent.didReceiveRows(rowInfos)); }, + onDeletedGroup: (groupIds) { + // + }, + onInsertedGroup: (insertedGroups) { + // + }, + onUpdatedGroup: (updatedGroups) { + // + for (final group in updatedGroups) { + final columnController = + boardController.getColumnController(group.groupId); + if (columnController != null) { + columnController.updateColumnName(group.desc); + } + } + }, onError: (err) { Log.error(err); }, @@ -189,7 +205,7 @@ class BoardBloc extends Bloc { } Future _loadGrid(Emitter emit) async { - final result = await _dataController.loadData(); + final result = await _gridDataController.loadData(); result.fold( (grid) => emit( state.copyWith(loadingState: GridLoadingState.finish(left(unit))), @@ -301,6 +317,9 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate { @override void updateRow(String groupId, RowPB row) { - // + // workaround: fix the board card reload timing issue. + Future.delayed(const Duration(milliseconds: 300), () { + controller.updateColumnItem(groupId, BoardColumnItem(row: row)); + }); } } diff --git a/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart b/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart index 1d17431713..31b2594497 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart @@ -10,9 +10,15 @@ import 'dart:async'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; +import 'board_listener.dart'; + typedef OnFieldsChanged = void Function(UnmodifiableListView); typedef OnGridChanged = void Function(GridPB); typedef DidLoadGroups = void Function(List); +typedef OnUpdatedGroup = void Function(List); +typedef OnDeletedGroup = void Function(List); +typedef OnInsertedGroup = void Function(List); + typedef OnRowsChanged = void Function( List, RowsChangedReason, @@ -23,6 +29,7 @@ class BoardDataController { final String gridId; final GridFFIService _gridFFIService; final GridFieldCache fieldCache; + final BoardListener _listener; // key: the block id final LinkedHashMap _blocks; @@ -44,16 +51,20 @@ class BoardDataController { BoardDataController({required ViewPB view}) : gridId = view.id, + _listener = BoardListener(view.id), _blocks = LinkedHashMap.new(), _gridFFIService = GridFFIService(gridId: view.id), fieldCache = GridFieldCache(gridId: view.id); void addListener({ - OnGridChanged? onGridChanged, + required OnGridChanged onGridChanged, OnFieldsChanged? onFieldsChanged, - DidLoadGroups? didLoadGroups, - OnRowsChanged? onRowsChanged, - OnError? onError, + required DidLoadGroups didLoadGroups, + required OnRowsChanged onRowsChanged, + required OnUpdatedGroup onUpdatedGroup, + required OnDeletedGroup onDeletedGroup, + required OnInsertedGroup onInsertedGroup, + required OnError? onError, }) { _onGridChanged = onGridChanged; _onFieldsChanged = onFieldsChanged; @@ -64,6 +75,25 @@ class BoardDataController { fieldCache.addListener(onFields: (fields) { _onFieldsChanged?.call(UnmodifiableListView(fields)); }); + + _listener.start(onBoardChanged: (result) { + result.fold( + (changeset) { + if (changeset.updateGroups.isNotEmpty) { + onUpdatedGroup.call(changeset.updateGroups); + } + + if (changeset.insertedGroups.isNotEmpty) { + onInsertedGroup.call(changeset.insertedGroups); + } + + if (changeset.deletedGroups.isNotEmpty) { + onDeletedGroup.call(changeset.deletedGroups); + } + }, + (e) => _onError?.call(e), + ); + }); } Future> loadData() async { diff --git a/frontend/app_flowy/lib/plugins/board/application/board_listener.dart b/frontend/app_flowy/lib/plugins/board/application/board_listener.dart new file mode 100644 index 0000000000..a953a993cc --- /dev/null +++ b/frontend/app_flowy/lib/plugins/board/application/board_listener.dart @@ -0,0 +1,50 @@ +import 'dart:typed_data'; + +import 'package:app_flowy/core/grid_notification.dart'; +import 'package:flowy_infra/notifier.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart'; + +typedef UpdateBoardNotifiedValue = Either; + +class BoardListener { + final String viewId; + PublishNotifier? _groupNotifier = PublishNotifier(); + GridNotificationListener? _listener; + BoardListener(this.viewId); + + void start({ + required void Function(UpdateBoardNotifiedValue) onBoardChanged, + }) { + _groupNotifier?.addPublishListener(onBoardChanged); + _listener = GridNotificationListener( + objectId: viewId, + handler: _handler, + ); + } + + void _handler( + GridNotification ty, + Either result, + ) { + switch (ty) { + case GridNotification.DidUpdateGroupView: + result.fold( + (payload) => _groupNotifier?.value = + left(GroupViewChangesetPB.fromBuffer(payload)), + (error) => _groupNotifier?.value = right(error), + ); + break; + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _groupNotifier?.dispose(); + _groupNotifier = null; + } +} diff --git a/frontend/app_flowy/lib/plugins/board/application/group_controller.dart b/frontend/app_flowy/lib/plugins/board/application/group_controller.dart index 6fd68b1df8..b0a89baaa3 100644 --- a/frontend/app_flowy/lib/plugins/board/application/group_controller.dart +++ b/frontend/app_flowy/lib/plugins/board/application/group_controller.dart @@ -34,7 +34,12 @@ class GroupController { void startListening() { _listener.start(onGroupChanged: (result) { result.fold( - (GroupRowsChangesetPB changeset) { + (GroupChangesetPB changeset) { + for (final deletedRow in changeset.deletedRows) { + group.rows.removeWhere((rowPB) => rowPB.id == deletedRow); + delegate.removeRow(group.groupId, deletedRow); + } + for (final insertedRow in changeset.insertedRows) { final index = insertedRow.hasIndex() ? insertedRow.index : null; @@ -52,11 +57,6 @@ class GroupController { ); } - for (final deletedRow in changeset.deletedRows) { - group.rows.removeWhere((rowPB) => rowPB.id == deletedRow); - delegate.removeRow(group.groupId, deletedRow); - } - for (final updatedRow in changeset.updatedRows) { final index = group.rows.indexWhere( (rowPB) => rowPB.id == updatedRow.id, diff --git a/frontend/app_flowy/lib/plugins/board/application/group_listener.dart b/frontend/app_flowy/lib/plugins/board/application/group_listener.dart index 797177deca..e3b626af07 100644 --- a/frontend/app_flowy/lib/plugins/board/application/group_listener.dart +++ b/frontend/app_flowy/lib/plugins/board/application/group_listener.dart @@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart'; -typedef UpdateGroupNotifiedValue = Either; +typedef UpdateGroupNotifiedValue = Either; class GroupListener { final GroupPB group; @@ -34,7 +34,7 @@ class GroupListener { case GridNotification.DidUpdateGroup: result.fold( (payload) => _groupNotifier?.value = - left(GroupRowsChangesetPB.fromBuffer(payload)), + left(GroupChangesetPB.fromBuffer(payload)), (error) => _groupNotifier?.value = right(error), ); break; 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 e7202e0a6d..eb47e8c134 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -62,9 +62,8 @@ class BoardContent extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), child: AFBoard( - // key: UniqueKey(), scrollController: ScrollController(), - dataController: context.read().afBoardDataController, + dataController: context.read().boardController, headerBuilder: _buildHeader, footBuilder: _buildFooter, cardBuilder: (_, data) => _buildCard(context, data), @@ -79,10 +78,11 @@ class BoardContent extends StatelessWidget { ); } - Widget _buildHeader(BuildContext context, AFBoardColumnData columnData) { + Widget _buildHeader( + BuildContext context, AFBoardColumnHeaderData headerData) { return AppFlowyColumnHeader( icon: const Icon(Icons.lightbulb_circle), - title: Text(columnData.desc), + title: Text(headerData.columnName), addIcon: const Icon(Icons.add, size: 20), moreIcon: const Icon(Icons.more_horiz, size: 20), height: 50, diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart index 373bb3c850..a51a36f99f 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart @@ -35,19 +35,17 @@ class _BoardSelectOptionCellState extends State { child: BlocBuilder( builder: (context, state) { final children = state.selectedOptions - .map((option) => SelectOptionTag.fromOption( - context: context, - option: option, - )) + .map( + (option) => SelectOptionTag.fromOption( + context: context, + option: option, + ), + ) .toList(); return Align( alignment: Alignment.centerLeft, child: AbsorbPointer( - child: Wrap( - children: children, - spacing: 4, - runSpacing: 2, - ), + child: Wrap(children: children, spacing: 4, runSpacing: 2), ), ); }, diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart index a5c7b7ba2c..226a9e8241 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart @@ -42,7 +42,7 @@ class _BoardCardState extends State { _cardBloc = BoardCardBloc( gridId: widget.gridId, dataController: widget.dataController, - ); + )..add(const BoardCardEvent.initial()); super.initState(); } @@ -79,6 +79,12 @@ class _BoardCardState extends State { }, ).toList(); } + + @override + Future dispose() async { + _cardBloc.close(); + super.dispose(); + } } class _CardMoreOption extends StatelessWidget with CardAccessory { diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart index c4b3430199..a6a1ba43a9 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart @@ -24,18 +24,21 @@ class GridCellDataLoader { Future loadData() { final fut = service.getCell(cellId: cellId); return fut.then( - (result) => result.fold((GridCellPB cell) { - try { - return parser.parserData(cell.data); - } catch (e, s) { - Log.error('$parser parser cellData failed, $e'); - Log.error('Stack trace \n $s'); + (result) => result.fold( + (GridCellPB cell) { + try { + return parser.parserData(cell.data); + } catch (e, s) { + Log.error('$parser parser cellData failed, $e'); + Log.error('Stack trace \n $s'); + return null; + } + }, + (err) { + Log.error(err); return null; - } - }, (err) { - Log.error(err); - return null; - }), + }, + ), ); } } @@ -58,7 +61,8 @@ class DateCellDataParser implements IGridCellDataParser { } } -class SelectOptionCellDataParser implements IGridCellDataParser { +class SelectOptionCellDataParser + implements IGridCellDataParser { @override SelectOptionCellDataPB? parserData(List data) { if (data.isEmpty) { diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart index 1068cbf36b..f3ed73b449 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart @@ -279,8 +279,9 @@ class IGridCellController extends Equatable { _loadDataOperation?.cancel(); _loadDataOperation = Timer(const Duration(milliseconds: 10), () { _cellDataLoader.loadData().then((data) { - _cellDataNotifier?.value = data; + Log.debug('$fieldId CellData: Did Get cell data'); _cellsCache.insert(_cacheKey, GridCell(object: data)); + _cellDataNotifier?.value = data; }); }); } 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 218331d198..5df7ce54ff 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 @@ -34,13 +34,18 @@ class _MultiBoardListExampleState extends State { RichTextItem(title: "Card 8", subtitle: 'Aug 1, 2020 4:05 PM'), TextItem("Card 9"), ]; - final column1 = AFBoardColumnData(id: "To Do", items: a); - final column2 = AFBoardColumnData(id: "In Progress", items: [ - RichTextItem(title: "Card 10", subtitle: 'Aug 1, 2020 4:05 PM'), - TextItem("Card 11"), - ]); + final column1 = AFBoardColumnData(id: "To Do", name: "To Do", items: a); + final column2 = AFBoardColumnData( + id: "In Progress", + name: "In Progress", + items: [ + RichTextItem(title: "Card 10", subtitle: 'Aug 1, 2020 4:05 PM'), + TextItem("Card 11"), + ], + ); - final column3 = AFBoardColumnData(id: "Done", items: []); + final column3 = + AFBoardColumnData(id: "Done", name: "Done", items: []); boardDataController.addColumn(column1); boardDataController.addColumn(column2); @@ -68,10 +73,21 @@ class _MultiBoardListExampleState extends State { margin: config.columnItemPadding, ); }, - headerBuilder: (context, columnData) { + headerBuilder: (context, headerData) { return AppFlowyColumnHeader( icon: const Icon(Icons.lightbulb_circle), - title: Text(columnData.id), + title: SizedBox( + width: 60, + child: TextField( + controller: TextEditingController() + ..text = headerData.columnName, + onSubmitted: (val) { + boardDataController + .getColumnController(headerData.columnId)! + .updateColumnName(val); + }, + ), + ), addIcon: const Icon(Icons.add, size: 20), moreIcon: const Icon(Icons.more_horiz, size: 20), height: 50, diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart index 97e83df448..4dda616621 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart @@ -13,12 +13,16 @@ class _SingleBoardListExampleState extends State { @override void initState() { - final column = AFBoardColumnData(id: "1", items: [ - TextItem("a"), - TextItem("b"), - TextItem("c"), - TextItem("d"), - ]); + final column = AFBoardColumnData( + id: "1", + name: "1", + items: [ + TextItem("a"), + TextItem("b"), + TextItem("c"), + TextItem("d"), + ], + ); boardData.addColumn(column); super.initState(); 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 e07ee39d61..dbcf62671a 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 @@ -205,13 +205,13 @@ class _BoardContentState extends State { return ChangeNotifierProvider.value( key: ValueKey(columnData.id), - value: widget.dataController.columnController(columnData.id), + value: widget.dataController.getColumnController(columnData.id), child: Consumer( builder: (context, value, child) { final boardColumn = AFBoardColumnWidget( margin: _marginFromIndex(columnIndex), itemMargin: widget.config.columnItemPadding, - headerBuilder: widget.headerBuilder, + headerBuilder: _buildHeader, footBuilder: widget.footBuilder, cardBuilder: widget.cardBuilder, dataSource: dataSource, @@ -224,7 +224,6 @@ class _BoardContentState extends State { // columnKeys // .removeWhere((element) => element.columnId == columnData.id); - // columnKeys.add( // ColumnKey( // columnId: columnData.id, @@ -245,6 +244,19 @@ class _BoardContentState extends State { return children; } + Widget? _buildHeader( + BuildContext context, AFBoardColumnHeaderData headerData) { + if (widget.headerBuilder == null) { + return null; + } + return Selector( + selector: (context, controller) => controller.columnData.headerData, + builder: (context, headerData, _) { + return widget.headerBuilder!(context, headerData)!; + }, + ); + } + EdgeInsets _marginFromIndex(int index) { if (widget.dataController.columnDatas.isEmpty) { return widget.config.columnPadding; @@ -273,7 +285,7 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource { @override AFBoardColumnData get columnData => - dataController.columnController(columnId).columnData; + dataController.getColumnController(columnId)!.columnData; @override List get acceptedColumnIds => dataController.columnIds; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart index d4e5ff8800..a5e20055f6 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart @@ -27,9 +27,9 @@ typedef AFBoardColumnCardBuilder = Widget Function( AFColumnItem item, ); -typedef AFBoardColumnHeaderBuilder = Widget Function( +typedef AFBoardColumnHeaderBuilder = Widget? Function( BuildContext context, - AFBoardColumnData columnData, + AFBoardColumnHeaderData headerData, ); typedef AFBoardColumnFooterBuilder = Widget Function( @@ -125,8 +125,8 @@ class _AFBoardColumnWidgetState extends State { .map((item) => _buildWidget(context, item)) .toList(); - final header = - widget.headerBuilder?.call(context, widget.dataSource.columnData); + final header = widget.headerBuilder + ?.call(context, widget.dataSource.columnData.headerData); final footer = widget.footBuilder?.call(context, widget.dataSource.columnData); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart index f26bd16c50..0015ebd479 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart @@ -34,6 +34,13 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin { UnmodifiableListView get items => UnmodifiableListView(columnData.items); + void updateColumnName(String newName) { + if (columnData.headerData.columnName != newName) { + columnData.headerData.columnName = newName; + notifyListeners(); + } + } + /// Remove the item at [index]. /// * [index] the index of the item you want to remove /// * [notify] the default value of [notify] is true, it will notify the @@ -123,6 +130,18 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin { notifyListeners(); } + void replaceOrInsertItem(AFColumnItem newItem) { + final index = columnData._items.indexWhere((item) => item.id == newItem.id); + if (index != -1) { + columnData._items.removeAt(index); + columnData._items.insert(index, newItem); + notifyListeners(); + } else { + columnData._items.add(newItem); + notifyListeners(); + } + } + bool _containsItem(AFColumnItem item) { return columnData._items.indexWhere((element) => element.id == item.id) != -1; @@ -133,16 +152,20 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin { class AFBoardColumnData extends ReoderFlexItem with EquatableMixin { @override final String id; - final String desc; + AFBoardColumnHeaderData headerData; final List _items; final CustomData? customData; AFBoardColumnData({ this.customData, required this.id, - this.desc = "", + required String name, List items = const [], - }) : _items = items; + }) : _items = items, + headerData = AFBoardColumnHeaderData( + columnId: id, + columnName: name, + ); /// Returns the readonly List UnmodifiableListView get items => @@ -156,3 +179,10 @@ class AFBoardColumnData extends ReoderFlexItem with EquatableMixin { return 'Column:[$id]'; } } + +class AFBoardColumnHeaderData { + String columnId; + String columnName; + + AFBoardColumnHeaderData({required this.columnId, required this.columnName}); +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart index a08bba378f..2cc853d6a5 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart @@ -89,10 +89,6 @@ class AFBoardDataController extends ChangeNotifier if (columnIds.isNotEmpty && notify) notifyListeners(); } - AFBoardColumnDataController columnController(String columnId) { - return _columnControllers[columnId]!; - } - AFBoardColumnDataController? getColumnController(String columnId) { final columnController = _columnControllers[columnId]; if (columnController == null) { @@ -129,6 +125,10 @@ class AFBoardDataController extends ChangeNotifier getColumnController(columnId)?.removeWhere((item) => item.id == itemId); } + void updateColumnItem(String columnId, AFColumnItem item) { + getColumnController(columnId)?.replaceOrInsertItem(item); + } + @override @protected void swapColumnItem( @@ -137,15 +137,14 @@ class AFBoardDataController extends ChangeNotifier String toColumnId, int toColumnIndex, ) { - final item = columnController(fromColumnId).removeAt(fromColumnIndex); - - if (columnController(toColumnId).items.length > toColumnIndex) { - assert(columnController(toColumnId).items[toColumnIndex] - is PhantomColumnItem); + final fromColumnController = getColumnController(fromColumnId)!; + final toColumnController = getColumnController(toColumnId)!; + final item = fromColumnController.removeAt(fromColumnIndex); + if (toColumnController.items.length > toColumnIndex) { + assert(toColumnController.items[toColumnIndex] is PhantomColumnItem); } - columnController(toColumnId).replace(toColumnIndex, item); - + toColumnController.replace(toColumnIndex, item); onMoveColumnItemToColumn?.call( fromColumnId, fromColumnIndex, @@ -174,9 +173,12 @@ class AFBoardDataController extends ChangeNotifier @override @protected bool removePhantom(String columnId) { - final columnController = this.columnController(columnId); + final columnController = getColumnController(columnId); + if (columnController == null) { + Log.warn('Can not find the column controller with columnId: $columnId'); + return false; + } final index = columnController.items.indexWhere((item) => item.isPhantom); - final isExist = index != -1; if (isExist) { columnController.removeAt(index); @@ -190,7 +192,7 @@ class AFBoardDataController extends ChangeNotifier @override @protected void updatePhantom(String columnId, int newIndex) { - final columnDataController = columnController(columnId); + final columnDataController = getColumnController(columnId)!; final index = columnDataController.items.indexWhere((item) => item.isPhantom); @@ -208,6 +210,6 @@ class AFBoardDataController extends ChangeNotifier @override @protected void insertPhantom(String columnId, int index, PhantomColumnItem item) { - columnController(columnId).insert(index, item); + getColumnController(columnId)!.insert(index, item); } } diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock index 0f9421a563..b08897239c 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -28,7 +28,7 @@ packages: path: "packages/appflowy_board" relative: true source: path - version: "0.0.4" + version: "0.0.5" appflowy_editor: dependency: "direct main" description: diff --git a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs index bb7eec9032..e691ed1830 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs @@ -30,7 +30,7 @@ impl BlockPB { } /// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row. -#[derive(Debug, Default, Clone, ProtoBuf)] +#[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)] pub struct RowPB { #[pb(index = 1)] pub block_id: String, diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs index b769b18154..9cc138bc0f 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs @@ -1,4 +1,5 @@ use crate::entities::{CreateRowParams, FieldType, GridLayout, RowPB}; +use crate::services::group::Group; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; @@ -82,6 +83,17 @@ pub struct GroupPB { pub rows: Vec, } +impl std::convert::From for GroupPB { + fn from(group: Group) -> Self { + Self { + field_id: group.field_id, + group_id: group.id, + desc: group.name, + rows: group.rows, + } + } +} + #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct RepeatedGridGroupConfigurationPB { #[pb(index = 1)] 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 a3ebee9cb7..21f39775f6 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 @@ -5,21 +5,24 @@ use flowy_grid_data_model::parser::NotEmptyStr; use std::fmt::Formatter; #[derive(Debug, Default, ProtoBuf)] -pub struct GroupRowsChangesetPB { +pub struct GroupChangesetPB { #[pb(index = 1)] pub group_id: String, - #[pb(index = 2)] - pub inserted_rows: Vec, + #[pb(index = 2, one_of)] + pub group_name: Option, #[pb(index = 3)] - pub deleted_rows: Vec, + pub inserted_rows: Vec, #[pb(index = 4)] + pub deleted_rows: Vec, + + #[pb(index = 5)] pub updated_rows: Vec, } -impl std::fmt::Display for GroupRowsChangesetPB { +impl std::fmt::Display for GroupChangesetPB { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for inserted_row in &self.inserted_rows { let _ = f.write_fmt(format_args!( @@ -36,10 +39,29 @@ impl std::fmt::Display for GroupRowsChangesetPB { } } -impl GroupRowsChangesetPB { +impl GroupChangesetPB { pub fn is_empty(&self) -> bool { - self.inserted_rows.is_empty() && self.deleted_rows.is_empty() && self.updated_rows.is_empty() + self.group_name.is_none() + && self.inserted_rows.is_empty() + && self.deleted_rows.is_empty() + && self.updated_rows.is_empty() } + + pub fn new(group_id: String) -> Self { + Self { + group_id, + ..Default::default() + } + } + + pub fn name(group_id: String, name: &str) -> Self { + Self { + group_id, + group_name: Some(name.to_owned()), + ..Default::default() + } + } + pub fn insert(group_id: String, inserted_rows: Vec) -> Self { Self { group_id, @@ -113,9 +135,16 @@ pub struct GroupViewChangesetPB { #[pb(index = 3)] pub deleted_groups: Vec, + + #[pb(index = 4)] + pub update_groups: Vec, } -impl GroupViewChangesetPB {} +impl GroupViewChangesetPB { + pub fn is_empty(&self) -> bool { + self.inserted_groups.is_empty() && self.deleted_groups.is_empty() && self.update_groups.is_empty() + } +} #[derive(Debug, Default, ProtoBuf)] pub struct InsertedGroupPB { 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 485bf6930c..cdc6cf6e6b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -188,8 +188,13 @@ impl GridRevisionEditor { pub async fn replace_field(&self, field_rev: Arc) -> FlowyResult<()> { let field_id = field_rev.id.clone(); let _ = self - .modify(|grid_pad| Ok(grid_pad.replace_field_rev(field_rev)?)) + .modify(|grid_pad| Ok(grid_pad.replace_field_rev(field_rev.clone())?)) .await?; + + match self.view_manager.did_update_field(&field_rev.id).await { + Ok(_) => {} + Err(e) => tracing::error!("View manager update field failed: {:?}", e), + } let _ = self.notify_did_update_grid_field(&field_id).await?; Ok(()) } @@ -263,59 +268,65 @@ impl GridRevisionEditor { } async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> { - self.modify(|grid| { - let deserializer = TypeOptionJsonDeserializer(field_type); + let _ = self + .modify(|grid| { + let deserializer = TypeOptionJsonDeserializer(field_type); + let changeset = grid.modify_field(¶ms.field_id, |field| { + let mut is_changed = None; + if let Some(name) = params.name { + field.name = name; + is_changed = Some(()) + } - let changeset = grid.modify_field(¶ms.field_id, |field| { - let mut is_changed = None; - if let Some(name) = params.name { - field.name = name; - is_changed = Some(()) - } + if let Some(desc) = params.desc { + field.desc = desc; + is_changed = Some(()) + } - if let Some(desc) = params.desc { - field.desc = desc; - is_changed = Some(()) - } + if let Some(field_type) = params.field_type { + field.ty = field_type; + is_changed = Some(()) + } - if let Some(field_type) = params.field_type { - field.ty = field_type; - is_changed = Some(()) - } + if let Some(frozen) = params.frozen { + field.frozen = frozen; + is_changed = Some(()) + } - if let Some(frozen) = params.frozen { - field.frozen = frozen; - is_changed = Some(()) - } + if let Some(visibility) = params.visibility { + field.visibility = visibility; + is_changed = Some(()) + } - if let Some(visibility) = params.visibility { - field.visibility = visibility; - is_changed = Some(()) - } + if let Some(width) = params.width { + field.width = width; + is_changed = Some(()) + } - if let Some(width) = params.width { - field.width = width; - is_changed = Some(()) - } - - if let Some(type_option_data) = params.type_option_data { - match deserializer.deserialize(type_option_data) { - Ok(json_str) => { - let field_type = field.ty; - field.insert_type_option_str(&field_type, json_str); - is_changed = Some(()) - } - Err(err) => { - tracing::error!("Deserialize data to type option json failed: {}", err); + if let Some(type_option_data) = params.type_option_data { + match deserializer.deserialize(type_option_data) { + Ok(json_str) => { + let field_type = field.ty; + field.insert_type_option_str(&field_type, json_str); + is_changed = Some(()) + } + Err(err) => { + tracing::error!("Deserialize data to type option json failed: {}", err); + } } } - } - Ok(is_changed) - })?; - Ok(changeset) - }) - .await + Ok(is_changed) + })?; + Ok(changeset) + }) + .await?; + + match self.view_manager.did_update_field(¶ms.field_id).await { + Ok(_) => {} + Err(e) => tracing::error!("View manager update field failed: {:?}", e), + } + Ok(()) } pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> { @@ -571,7 +582,7 @@ impl GridRevisionEditor { pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> { let MoveGroupRowParams { - view_id: _, + view_id, from_row_id, to_group_id, to_row_id, @@ -585,10 +596,23 @@ impl GridRevisionEditor { .move_group_row(row_rev, to_group_id, to_row_id.clone()) .await { - match self.block_manager.update_row(row_changeset).await { - Ok(_) => {} - Err(e) => { - tracing::error!("Apply row changeset error:{:?}", e); + tracing::trace!("Move group row cause row data changed: {:?}", row_changeset); + + let cell_changesets = row_changeset + .cell_by_field_id + .into_iter() + .map(|(field_id, cell_rev)| CellChangesetPB { + grid_id: view_id.clone(), + row_id: row_changeset.row_id.clone(), + field_id, + content: cell_rev.data, + }) + .collect::>(); + + for cell_changeset in cell_changesets { + match self.block_manager.update_cell(cell_changeset).await { + Ok(_) => {} + Err(e) => tracing::error!("Apply cell changeset error:{:?}", e), } } } 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 4e23d9be19..6a04e18815 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 @@ -1,8 +1,8 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::entities::{ CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridLayout, GridLayoutPB, - GridSettingPB, GroupPB, GroupRowsChangesetPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB, - MoveGroupParams, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB, + GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB, MoveGroupParams, + RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB, }; use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate}; @@ -59,7 +59,7 @@ impl GridViewRevisionEditor { rev_manager: rev_manager.clone(), view_pad: pad.clone(), }; - let group_service = GroupService::new(configuration_reader, configuration_writer).await; + let group_service = GroupService::new(view_id.clone(), configuration_reader, configuration_writer).await; let user_id = user_id.to_owned(); let did_load_group = AtomicBool::new(false); Ok(Self { @@ -99,8 +99,8 @@ impl GridViewRevisionEditor { row: row_pb.clone(), index: None, }; - let changeset = GroupRowsChangesetPB::insert(group_id.clone(), vec![inserted_row]); - self.notify_did_update_group_rows(changeset).await; + let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]); + self.notify_did_update_group(changeset).await; } } } @@ -115,7 +115,7 @@ impl GridViewRevisionEditor { .await { for changeset in changesets { - self.notify_did_update_group_rows(changeset).await; + self.notify_did_update_group(changeset).await; } } } @@ -129,7 +129,7 @@ impl GridViewRevisionEditor { .await { for changeset in changesets { - self.notify_did_update_group_rows(changeset).await; + self.notify_did_update_group(changeset).await; } } } @@ -151,11 +151,11 @@ impl GridViewRevisionEditor { .await { for changeset in changesets { - self.notify_did_update_group_rows(changeset).await; + self.notify_did_update_group(changeset).await; } } } - + /// Only call once after grid view editor initialized #[tracing::instrument(level = "trace", skip(self))] pub(crate) async fn load_groups(&self) -> FlowyResult> { let groups = if !self.did_load_group.load(Ordering::SeqCst) { @@ -198,9 +198,10 @@ impl GridViewRevisionEditor { }; let changeset = GroupViewChangesetPB { - view_id: "".to_string(), + view_id: self.view_id.clone(), inserted_groups: vec![inserted_group], deleted_groups: vec![params.from_group_id.clone()], + update_groups: vec![], }; self.notify_did_update_view(changeset).await; @@ -252,8 +253,20 @@ impl GridViewRevisionEditor { }) .await } + #[tracing::instrument(level = "trace", skip_all, err)] + pub(crate) async fn did_update_field(&self, field_id: &str) -> FlowyResult<()> { + if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await { + match self.group_service.write().await.did_update_field(&field_rev).await? { + None => {} + Some(changeset) => { + self.notify_did_update_view(changeset).await; + } + } + } + Ok(()) + } - async fn notify_did_update_group_rows(&self, changeset: GroupRowsChangesetPB) { + async fn notify_did_update_group(&self, changeset: GroupChangesetPB) { send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup) .payload(changeset) .send(); @@ -265,7 +278,6 @@ impl GridViewRevisionEditor { .send(); } - #[allow(dead_code)] async fn modify(&self, f: F) -> FlowyResult<()> where F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult>, diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs index b0e578f804..657058b31f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs @@ -142,13 +142,19 @@ impl GridViewManager { .await; } - if row_changeset.has_changed() { - Some(row_changeset) - } else { + if row_changeset.is_empty() { None + } else { + Some(row_changeset) } } + pub(crate) async fn did_update_field(&self, field_id: &str) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + let _ = view_editor.did_update_field(field_id).await?; + Ok(()) + } + pub(crate) async fn get_view_editor(&self, view_id: &str) -> FlowyResult> { debug_assert!(!view_id.is_empty()); match self.view_editors.get(view_id) { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/action.rs b/frontend/rust-lib/flowy-grid/src/services/group/action.rs index 29dc51cc37..d19be8395e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/action.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/action.rs @@ -1,4 +1,4 @@ -use crate::entities::GroupRowsChangesetPB; +use crate::entities::GroupChangesetPB; use crate::services::group::controller::MoveGroupRowContext; use flowy_grid_data_model::revision::RowRevision; @@ -6,12 +6,8 @@ use flowy_grid_data_model::revision::RowRevision; pub trait GroupAction: Send + Sync { type CellDataType; fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool; - fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec; - fn remove_row_if_match( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellDataType, - ) -> Vec; + fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec; + fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec; - fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec; + fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec; } 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 a462278d2b..5126c9b2fc 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs @@ -1,12 +1,12 @@ +use crate::entities::{GroupPB, GroupViewChangesetPB, InsertedGroupPB}; use crate::services::group::{default_group_configuration, Group}; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{ FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRecordRevision, }; -use std::marker::PhantomData; - use indexmap::IndexMap; use lib_infra::future::AFFuture; +use std::marker::PhantomData; use std::sync::Arc; pub trait GroupConfigurationReader: Send + Sync + 'static { @@ -26,6 +26,7 @@ pub trait GroupConfigurationWriter: Send + Sync + 'static { } pub struct GenericGroupConfiguration { + view_id: String, pub configuration: Arc, configuration_content: PhantomData, field_rev: Arc, @@ -39,6 +40,7 @@ where { #[tracing::instrument(level = "trace", skip_all, err)] pub async fn new( + view_id: String, field_rev: Arc, reader: Arc, writer: Arc, @@ -56,6 +58,7 @@ where // let configuration = C::from_configuration_content(&configuration_rev.content)?; Ok(Self { + view_id, field_rev, groups_map: IndexMap::new(), writer, @@ -72,8 +75,18 @@ where self.groups_map.values().cloned().collect() } - pub(crate) async fn merge_groups(&mut self, groups: Vec) -> FlowyResult<()> { - let (group_revs, groups) = merge_groups(&self.configuration.groups, groups); + pub(crate) fn merge_groups(&mut self, groups: Vec) -> FlowyResult> { + let MergeGroupResult { + groups, + inserted_groups, + updated_groups, + } = merge_groups(&self.configuration.groups, groups); + + let group_revs = groups + .iter() + .map(|group| GroupRecordRevision::new(group.id.clone(), group.name.clone())) + .collect(); + self.mut_configuration(move |configuration| { configuration.groups = group_revs; true @@ -82,7 +95,14 @@ where groups.into_iter().for_each(|group| { self.groups_map.insert(group.id.clone(), group); }); - Ok(()) + + let changeset = make_group_view_changeset(self.view_id.clone(), inserted_groups, updated_groups); + tracing::trace!("Group changeset: {:?}", changeset); + if changeset.is_empty() { + Ok(None) + } else { + Ok(Some(changeset)) + } } #[allow(dead_code)] @@ -101,7 +121,7 @@ where Ok(()) } - pub(crate) fn with_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) { + pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) { self.groups_map.iter_mut().for_each(|(_, group)| { each(group); }) @@ -189,33 +209,82 @@ where } } -fn merge_groups(old_group_revs: &[GroupRecordRevision], groups: Vec) -> (Vec, Vec) { - if old_group_revs.is_empty() { - let new_groups = groups - .iter() - .map(|group| GroupRecordRevision::new(group.id.clone())) - .collect(); - return (new_groups, groups); +fn merge_groups(old_groups: &[GroupRecordRevision], groups: Vec) -> MergeGroupResult { + let mut merge_result = MergeGroupResult::new(); + if old_groups.is_empty() { + merge_result.groups = groups; + return merge_result; } + // group_map is a helper map is used to filter out the new groups. let mut group_map: IndexMap = IndexMap::new(); groups.into_iter().for_each(|group| { group_map.insert(group.id.clone(), group); }); - // Inert - let mut sorted_groups: Vec = vec![]; - for group_rev in old_group_revs { + // The group is ordered in old groups. Add them before adding the new groups + for group_rev in old_groups { if let Some(group) = group_map.remove(&group_rev.group_id) { - sorted_groups.push(group); + if group.name == group_rev.name { + merge_result.add_group(group); + } else { + merge_result.add_updated_group(group); + } } } - sorted_groups.extend(group_map.into_values().collect::>()); - let new_group_revs = sorted_groups - .iter() - .map(|group| GroupRecordRevision::new(group.id.clone())) - .collect::>(); - tracing::trace!("group revs: {}, groups: {}", new_group_revs.len(), sorted_groups.len()); - (new_group_revs, sorted_groups) + // Find out the new groups + let new_groups = group_map.into_values().collect::>(); + for (index, group) in new_groups.into_iter().enumerate() { + merge_result.add_insert_group(index, group); + } + merge_result +} + +struct MergeGroupResult { + groups: Vec, + inserted_groups: Vec, + updated_groups: Vec, +} + +impl MergeGroupResult { + fn new() -> Self { + Self { + groups: vec![], + inserted_groups: vec![], + updated_groups: vec![], + } + } + + fn add_updated_group(&mut self, group: Group) { + self.groups.push(group.clone()); + self.updated_groups.push(group); + } + + fn add_group(&mut self, group: Group) { + self.groups.push(group.clone()); + } + + fn add_insert_group(&mut self, index: usize, group: Group) { + self.groups.push(group.clone()); + let inserted_group = InsertedGroupPB { + group: GroupPB::from(group), + index: index as i32, + }; + self.inserted_groups.push(inserted_group); + } +} + +fn make_group_view_changeset( + view_id: String, + inserted_groups: Vec, + updated_group: Vec, +) -> GroupViewChangesetPB { + let changeset = GroupViewChangesetPB { + view_id, + inserted_groups, + deleted_groups: vec![], + update_groups: updated_group.into_iter().map(GroupPB::from).collect(), + }; + changeset } 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 1a5ee23694..1dae47c0db 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs @@ -1,4 +1,4 @@ -use crate::entities::{GroupRowsChangesetPB, RowPB}; +use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB}; use crate::services::cell::{decode_any_cell_data, CellBytesParser}; use crate::services::group::action::GroupAction; use crate::services::group::configuration::GenericGroupConfiguration; @@ -51,15 +51,17 @@ pub trait GroupControllerSharedOperation: Send + Sync { &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult>; + ) -> FlowyResult>; fn did_delete_row( &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult>; + ) -> FlowyResult>; - fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult>; + fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult>; + + fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult>; } /// C: represents the group configuration that impl [GroupConfigurationSerde] @@ -89,7 +91,7 @@ where let field_type_rev = field_rev.ty; let type_option = field_rev.get_type_option_entry::(field_type_rev); let groups = G::generate_groups(&field_rev.id, &configuration, &type_option); - let _ = configuration.merge_groups(groups).await?; + let _ = configuration.merge_groups(groups)?; let default_group = Group::new( DEFAULT_GROUP_ID.to_owned(), field_rev.id.clone(), @@ -112,6 +114,9 @@ impl GroupControllerSharedOperation for GenericGroupController, TypeOptionType = T>, + Self: GroupAction, { fn field_id(&self) -> &str { @@ -173,11 +178,12 @@ where &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult> { + ) -> FlowyResult> { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_data = cell_bytes.parser::

()?; - Ok(self.add_row_if_match(row_rev, &cell_data)) + let changesets = self.add_row_if_match(row_rev, &cell_data); + Ok(changesets) } else { Ok(vec![]) } @@ -187,7 +193,7 @@ where &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult> { + ) -> FlowyResult> { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_data = cell_bytes.parser::

()?; @@ -197,7 +203,7 @@ where } } - fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult> { + fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult> { if let Some(cell_rev) = context.row_rev.cells.get(&self.field_id) { let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), context.field_rev); let cell_data = cell_bytes.parser::

()?; @@ -206,6 +212,14 @@ where Ok(vec![]) } } + + fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult> { + let field_type_rev = field_rev.ty; + let type_option = field_rev.get_type_option_entry::(field_type_rev); + let groups = G::generate_groups(&field_rev.id, &self.configuration, &type_option); + let changeset = self.configuration.merge_groups(groups)?; + Ok(changeset) + } } struct GroupRow { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs index ffcbf117fe..4c06ba63ce 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs @@ -1,4 +1,4 @@ -use crate::entities::GroupRowsChangesetPB; +use crate::entities::GroupChangesetPB; use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; use crate::services::group::action::GroupAction; use crate::services::group::configuration::GenericGroupConfiguration; @@ -24,11 +24,7 @@ impl GroupAction for CheckboxGroupController { false } - fn add_row_if_match( - &mut self, - _row_rev: &RowRevision, - _cell_data: &Self::CellDataType, - ) -> Vec { + fn add_row_if_match(&mut self, _row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec { todo!() } @@ -36,15 +32,11 @@ impl GroupAction for CheckboxGroupController { &mut self, _row_rev: &RowRevision, _cell_data: &Self::CellDataType, - ) -> Vec { + ) -> Vec { todo!() } - fn move_row( - &mut self, - _cell_data: &Self::CellDataType, - _context: MoveGroupRowContext, - ) -> Vec { + fn move_row(&mut self, _cell_data: &Self::CellDataType, _context: MoveGroupRowContext) -> Vec { todo!() } } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs index cce2698158..fe90e1b462 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs @@ -1,4 +1,4 @@ -use crate::entities::GroupRowsChangesetPB; +use crate::entities::GroupChangesetPB; use crate::services::cell::insert_select_option_cell; use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser}; use crate::services::group::action::GroupAction; @@ -25,34 +25,32 @@ impl GroupAction for MultiSelectGroupController { cell_data.select_options.iter().any(|option| option.id == content) } - fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { + fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.with_mut_groups(|group| { - add_row(group, &mut changesets, cell_data, row_rev); + self.configuration.iter_mut_groups(|group| { + if let Some(changeset) = add_row(group, cell_data, row_rev) { + changesets.push(changeset); + } }); changesets } - fn remove_row_if_match( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellDataType, - ) -> Vec { + fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.with_mut_groups(|group| { - remove_row(group, &mut changesets, cell_data, row_rev); + self.configuration.iter_mut_groups(|group| { + if let Some(changeset) = remove_row(group, cell_data, row_rev) { + changesets.push(changeset); + } }); changesets } - fn move_row( - &mut self, - cell_data: &Self::CellDataType, - mut context: MoveGroupRowContext, - ) -> Vec { + fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec { let mut group_changeset = vec![]; - self.configuration.with_mut_groups(|group| { - move_select_option_row(group, &mut group_changeset, cell_data, &mut context); + self.configuration.iter_mut_groups(|group| { + if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) { + group_changeset.push(changeset); + } }); group_changeset } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs index d48cdd8ee7..d774ab083f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs @@ -1,4 +1,4 @@ -use crate::entities::{GroupRowsChangesetPB, RowPB}; +use crate::entities::{GroupChangesetPB, RowPB}; use crate::services::cell::insert_select_option_cell; use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB}; use crate::services::group::action::GroupAction; @@ -25,34 +25,32 @@ impl GroupAction for SingleSelectGroupController { cell_data.select_options.iter().any(|option| option.id == content) } - fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { + fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.with_mut_groups(|group| { - add_row(group, &mut changesets, cell_data, row_rev); + self.configuration.iter_mut_groups(|group| { + if let Some(changeset) = add_row(group, cell_data, row_rev) { + changesets.push(changeset); + } }); changesets } - fn remove_row_if_match( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellDataType, - ) -> Vec { + fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.with_mut_groups(|group| { - remove_row(group, &mut changesets, cell_data, row_rev); + self.configuration.iter_mut_groups(|group| { + if let Some(changeset) = remove_row(group, cell_data, row_rev) { + changesets.push(changeset); + } }); changesets } - fn move_row( - &mut self, - cell_data: &Self::CellDataType, - mut context: MoveGroupRowContext, - ) -> Vec { + fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec { let mut group_changeset = vec![]; - self.configuration.with_mut_groups(|group| { - move_select_option_row(group, &mut group_changeset, cell_data, &mut context); + self.configuration.iter_mut_groups(|group| { + if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) { + group_changeset.push(changeset); + } }); group_changeset } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs index bdca688531..349f7391a6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs @@ -1,4 +1,4 @@ -use crate::entities::{GroupRowsChangesetPB, InsertedRowPB, RowPB}; +use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB}; use crate::services::cell::insert_select_option_cell; use crate::services::field::SelectOptionCellDataPB; use crate::services::group::configuration::GenericGroupConfiguration; @@ -11,47 +11,56 @@ pub type SelectOptionGroupConfiguration = GenericGroupConfiguration, cell_data: &SelectOptionCellDataPB, row_rev: &RowRevision, -) { +) -> Option { + let mut changeset = GroupChangesetPB::new(group.id.clone()); cell_data.select_options.iter().for_each(|option| { if option.id == group.id { if !group.contains_row(&row_rev.id) { let row_pb = RowPB::from(row_rev); - changesets.push(GroupRowsChangesetPB::insert( - group.id.clone(), - vec![InsertedRowPB::new(row_pb.clone())], - )); + changeset.inserted_rows.push(InsertedRowPB::new(row_pb.clone())); group.add_row(row_pb); } } else if group.contains_row(&row_rev.id) { - changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); + changeset.deleted_rows.push(row_rev.id.clone()); group.remove_row(&row_rev.id); } }); + + if changeset.is_empty() { + None + } else { + Some(changeset) + } } pub fn remove_row( group: &mut Group, - changesets: &mut Vec, cell_data: &SelectOptionCellDataPB, row_rev: &RowRevision, -) { +) -> Option { + let mut changeset = GroupChangesetPB::new(group.id.clone()); cell_data.select_options.iter().for_each(|option| { if option.id == group.id && group.contains_row(&row_rev.id) { - changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); + changeset.deleted_rows.push(row_rev.id.clone()); group.remove_row(&row_rev.id); } }); + + if changeset.is_empty() { + None + } else { + Some(changeset) + } } pub fn move_select_option_row( group: &mut Group, - group_changeset: &mut Vec, _cell_data: &SelectOptionCellDataPB, context: &mut MoveGroupRowContext, -) { +) -> Option { + let mut changeset = GroupChangesetPB::new(group.id.clone()); let MoveGroupRowContext { row_rev, row_changeset, @@ -68,7 +77,7 @@ pub fn move_select_option_row( // Remove the row in which group contains it if from_index.is_some() { - group_changeset.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); + changeset.deleted_rows.push(row_rev.id.clone()); tracing::debug!("Group:{} remove row:{}", group.id, row_rev.id); group.remove_row(&row_rev.id); } @@ -78,7 +87,7 @@ pub fn move_select_option_row( let mut inserted_row = InsertedRowPB::new(row_pb.clone()); match to_index { None => { - group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row])); + changeset.inserted_rows.push(inserted_row); tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); group.add_row(row_pb); } @@ -91,7 +100,7 @@ pub fn move_select_option_row( tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); group.add_row(row_pb); } - group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row])); + changeset.inserted_rows.push(inserted_row); } } @@ -100,6 +109,12 @@ pub fn move_select_option_row( tracing::debug!("Mark row:{} belong to group:{}", row_rev.id, group.id); let cell_rev = insert_select_option_cell(group.id.clone(), field_rev); row_changeset.cell_by_field_id.insert(field_rev.id.clone(), cell_rev); + changeset.updated_rows.push(RowPB::from(*row_rev)); } } + if changeset.is_empty() { + None + } else { + Some(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 dd4171afb0..d8c4169eaf 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/entities.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/entities.rs @@ -1,33 +1,22 @@ -use crate::entities::{GroupPB, RowPB}; +use crate::entities::RowPB; -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct Group { pub id: String, pub field_id: String, - pub desc: String, - rows: Vec, + pub name: String, + pub(crate) rows: Vec, /// [content] is used to determine which group the cell belongs to. pub content: String, } -impl std::convert::From for GroupPB { - fn from(group: Group) -> Self { - Self { - field_id: group.field_id, - group_id: group.id, - desc: group.desc, - rows: group.rows, - } - } -} - impl Group { - pub fn new(id: String, field_id: String, desc: String, content: String) -> Self { + pub fn new(id: String, field_id: String, name: String, content: String) -> Self { Self { id, field_id, - desc, + name, rows: vec![], content, } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs index 6afec9dd82..c7d67f65d6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs @@ -1,4 +1,4 @@ -use crate::entities::{FieldType, GroupRowsChangesetPB}; +use crate::entities::{FieldType, GroupChangesetPB, GroupViewChangesetPB}; use crate::services::group::configuration::GroupConfigurationReader; use crate::services::group::controller::{GroupController, MoveGroupRowContext}; use crate::services::group::{ @@ -15,18 +15,20 @@ use std::future::Future; use std::sync::Arc; pub(crate) struct GroupService { + view_id: String, configuration_reader: Arc, configuration_writer: Arc, group_controller: Option>, } impl GroupService { - pub(crate) async fn new(configuration_reader: R, configuration_writer: W) -> Self + pub(crate) async fn new(view_id: String, configuration_reader: R, configuration_writer: W) -> Self where R: GroupConfigurationReader, W: GroupConfigurationWriter, { Self { + view_id, configuration_reader: Arc::new(configuration_reader), configuration_writer: Arc::new(configuration_writer), group_controller: None, @@ -36,8 +38,8 @@ impl GroupService { pub(crate) async fn groups(&self) -> Vec { self.group_controller .as_ref() - .and_then(|group_controller| Some(group_controller.groups())) - .unwrap_or(vec![]) + .map(|group_controller| group_controller.groups()) + .unwrap_or_default() } pub(crate) async fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { @@ -86,7 +88,7 @@ impl GroupService { &mut self, row_rev: &RowRevision, get_field_fn: F, - ) -> Option> + ) -> Option> where F: FnOnce(String) -> O, O: Future>> + Send + Sync + 'static, @@ -111,7 +113,7 @@ impl GroupService { to_group_id: &str, to_row_id: Option, get_field_fn: F, - ) -> Option> + ) -> Option> where F: FnOnce(String) -> O, O: Future>> + Send + Sync + 'static, @@ -141,7 +143,7 @@ impl GroupService { &mut self, row_rev: &RowRevision, get_field_fn: F, - ) -> Option> + ) -> Option> where F: FnOnce(String) -> O, O: Future>> + Send + Sync + 'static, @@ -170,6 +172,17 @@ impl GroupService { } } + #[tracing::instrument(level = "trace", name = "group_did_update_field", skip(self, field_rev), err)] + pub(crate) async fn did_update_field( + &mut self, + field_rev: &FieldRevision, + ) -> FlowyResult> { + match self.group_controller.as_mut() { + None => Ok(None), + Some(group_controller) => group_controller.did_update_field(field_rev), + } + } + #[tracing::instrument(level = "trace", skip(self, field_rev), err)] async fn make_group_controller( &self, @@ -189,6 +202,7 @@ impl GroupService { } FieldType::SingleSelect => { let configuration = SelectOptionGroupConfiguration::new( + self.view_id.clone(), field_rev.clone(), self.configuration_reader.clone(), self.configuration_writer.clone(), @@ -199,6 +213,7 @@ impl GroupService { } FieldType::MultiSelect => { let configuration = SelectOptionGroupConfiguration::new( + self.view_id.clone(), field_rev.clone(), self.configuration_reader.clone(), self.configuration_writer.clone(), @@ -209,6 +224,7 @@ impl GroupService { } FieldType::Checkbox => { let configuration = CheckboxGroupConfiguration::new( + self.view_id.clone(), field_rev.clone(), self.configuration_reader.clone(), self.configuration_writer.clone(), diff --git a/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs index 94ef4dff1b..3a5ae9455b 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs @@ -1,9 +1,11 @@ use crate::grid::grid_editor::GridEditorTest; use flowy_grid::entities::{ - CreateRowParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB, + CreateRowParams, FieldChangesetParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB, }; use flowy_grid::services::cell::insert_select_option_cell; use flowy_grid_data_model::revision::RowChangeset; +use std::time::Duration; +use tokio::time::interval; pub enum GroupScript { AssertGroupRowCount { @@ -42,6 +44,9 @@ pub enum GroupScript { from_group_index: usize, to_group_index: usize, }, + UpdateField { + changeset: FieldChangesetParams, + }, } pub struct GridGroupTest { @@ -156,6 +161,12 @@ impl GridGroupTest { } => { let group = self.group_at_index(group_index).await; assert_eq!(group.group_id, group_pb.group_id); + assert_eq!(group.desc, group_pb.desc); + } + GroupScript::UpdateField { changeset } => { + self.editor.update_field(changeset).await.unwrap(); + let mut interval = interval(Duration::from_millis(130)); + interval.tick().await; } } } 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 bebf499773..d6a9839f77 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 @@ -1,5 +1,6 @@ use crate::grid::group_test::script::GridGroupTest; use crate::grid::group_test::script::GroupScript::*; +use flowy_grid::entities::FieldChangesetParams; #[tokio::test] async fn group_init_test() { @@ -314,3 +315,25 @@ async fn group_move_group_test() { ]; test.run_scripts(scripts).await; } + +#[tokio::test] +async fn group_update_field_test() { + let mut test = GridGroupTest::new().await; + let mut group = test.group_at_index(0).await; + let changeset = FieldChangesetParams { + field_id: group.field_id.clone(), + grid_id: test.grid_id.clone(), + name: Some("ABC".to_string()), + ..Default::default() + }; + + // group.desc = "ABC".to_string(); + let scripts = vec![ + UpdateField { changeset }, + AssertGroup { + group_index: 0, + expected_group: group, + }, + ]; + test.run_scripts(scripts).await; +} diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_block.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_block.rs index 5464d83877..ba113810f6 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_block.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_block.rs @@ -59,8 +59,8 @@ impl RowChangeset { } } - pub fn has_changed(&self) -> bool { - self.height.is_some() || self.visibility.is_some() || !self.cell_by_field_id.is_empty() + pub fn is_empty(&self) -> bool { + self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty() } } 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 97d45295fe..ce378c0ffb 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 @@ -110,6 +110,9 @@ impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision { pub struct GroupRecordRevision { pub group_id: String, + #[serde(default)] + pub name: String, + #[serde(default = "DEFAULT_GROUP_RECORD_VISIBILITY")] pub visible: bool, } @@ -117,9 +120,10 @@ pub struct GroupRecordRevision { const DEFAULT_GROUP_RECORD_VISIBILITY: fn() -> bool = || true; impl GroupRecordRevision { - pub fn new(group_id: String) -> Self { + pub fn new(group_id: String, group_name: String) -> Self { Self { group_id, + name: group_name, visible: true, } }