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 09f85df732..a4a892a7fc 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -52,8 +52,21 @@ class BoardBloc extends Bloc { _startListening(); await _loadGrid(emit); }, - createRow: () { - _dataController.createRow(); + createRow: (groupId) async { + final result = await _dataController.createBoardCard(groupId); + result.fold( + (rowPB) { + emit(state.copyWith(editingRow: some(rowPB))); + }, + (err) => Log.error(err), + ); + }, + endEditRow: (rowId) { + assert(state.editingRow.isSome()); + state.editingRow.fold(() => null, (row) { + assert(row.id == rowId); + emit(state.copyWith(editingRow: none())); + }); }, didReceiveGridUpdate: (GridPB grid) { emit(state.copyWith(grid: Some(grid))); @@ -99,7 +112,7 @@ class BoardBloc extends Bloc { boardDataController.addColumns(columns); }, - onRowsChanged: (List rowInfos, RowChangeReason reason) { + onRowsChanged: (List rowInfos, RowsChangedReason reason) { add(BoardEvent.didReceiveRows(rowInfos)); }, onError: (err) { @@ -140,7 +153,8 @@ class BoardBloc extends Bloc { @freezed class BoardEvent with _$BoardEvent { const factory BoardEvent.initial() = InitialGrid; - const factory BoardEvent.createRow() = _CreateRow; + const factory BoardEvent.createRow(String groupId) = _CreateRow; + const factory BoardEvent.endEditRow(String rowId) = _EndEditRow; const factory BoardEvent.didReceiveGroups(List groups) = _DidReceiveGroup; const factory BoardEvent.didReceiveRows(List rowInfos) = @@ -156,6 +170,7 @@ class BoardState with _$BoardState { required String gridId, required Option grid, required List groups, + required Option editingRow, required List rowInfos, required GridLoadingState loadingState, }) = _BoardState; @@ -165,6 +180,7 @@ class BoardState with _$BoardState { groups: [], grid: none(), gridId: gridId, + editingRow: none(), loadingState: const _Loading(), ); } 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 da4cc54132..e4b4f90520 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 @@ -4,7 +4,6 @@ import 'package:app_flowy/plugins/grid/application/block/block_cache.dart'; import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; import 'package:app_flowy/plugins/grid/application/grid_service.dart'; import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; -import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'dart:async'; @@ -15,14 +14,14 @@ typedef OnFieldsChanged = void Function(UnmodifiableListView); typedef OnGridChanged = void Function(GridPB); typedef OnGroupChanged = void Function(List); typedef OnRowsChanged = void Function( - List rowInfos, - RowChangeReason, + List, + RowsChangedReason, ); typedef OnError = void Function(FlowyError); class BoardDataController { final String gridId; - final GridService _gridFFIService; + final GridFFIService _gridFFIService; final GridFieldCache fieldCache; // key: the block id @@ -46,7 +45,7 @@ class BoardDataController { BoardDataController({required ViewPB view}) : gridId = view.id, _blocks = LinkedHashMap.new(), - _gridFFIService = GridService(gridId: view.id), + _gridFFIService = GridFFIService(gridId: view.id), fieldCache = GridFieldCache(gridId: view.id); void addListener({ @@ -73,11 +72,11 @@ class BoardDataController { () => result.fold( (grid) async { _onGridChanged?.call(grid); - _initialBlocks(grid.blocks); + return await _loadFields(grid).then((result) { return result.fold( (l) { - _loadGroups(); + _loadGroups(grid.blocks); return left(l); }, (err) => right(err), @@ -89,8 +88,8 @@ class BoardDataController { ); } - Future> createRow() { - return _gridFFIService.createRow(); + Future> createBoardCard(String groupId) { + return _gridFFIService.createBoardCard(groupId); } Future dispose() async { @@ -102,29 +101,6 @@ class BoardDataController { } } - void _initialBlocks(List blocks) { - for (final block in blocks) { - if (_blocks[block.id] != null) { - Log.warn("Initial duplicate block's cache: ${block.id}"); - return; - } - - final cache = GridBlockCache( - gridId: gridId, - block: block, - fieldCache: fieldCache, - ); - - cache.addListener( - onChangeReason: (reason) { - _onRowsChanged?.call(rowInfos, reason); - }, - ); - - _blocks[block.id] = cache; - } - } - Future> _loadFields(GridPB grid) async { final result = await _gridFFIService.getFields(fieldIds: grid.fields); return Future( @@ -139,7 +115,20 @@ class BoardDataController { ); } - Future _loadGroups() async { + Future _loadGroups(List blocks) async { + for (final block in blocks) { + final cache = GridBlockCache( + gridId: gridId, + block: block, + fieldCache: fieldCache, + ); + + cache.addListener(onRowsChanged: (reason) { + _onRowsChanged?.call(rowInfos, reason); + }); + _blocks[block.id] = cache; + } + final result = await _gridFFIService.loadGroups(); return Future( () => result.fold( diff --git a/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart index 9ba66c2aab..1f66ebe335 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart @@ -74,7 +74,7 @@ class BoardCardEvent with _$BoardCardEvent { const factory BoardCardEvent.initial() = _InitialRow; const factory BoardCardEvent.createRow() = _CreateRow; const factory BoardCardEvent.didReceiveCells( - GridCellMap gridCellMap, RowChangeReason reason) = _DidReceiveCells; + GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells; } @freezed @@ -83,7 +83,7 @@ class BoardCardState with _$BoardCardState { required RowPB rowPB, required GridCellMap gridCellMap, required UnmodifiableListView cells, - RowChangeReason? changeReason, + RowsChangedReason? changeReason, }) = _BoardCardState; factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) => diff --git a/frontend/app_flowy/lib/plugins/board/application/card/card_data_controller.dart b/frontend/app_flowy/lib/plugins/board/application/card/card_data_controller.dart index d9ac41f10b..f362fdf0e6 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/card_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/card_data_controller.dart @@ -6,7 +6,7 @@ import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; import 'package:flutter/foundation.dart'; -typedef OnCardChanged = void Function(GridCellMap, RowChangeReason); +typedef OnCardChanged = void Function(GridCellMap, RowsChangedReason); class CardDataController extends BoardCellBuilderDelegate { final RowPB rowPB; 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 6dcfe8267c..faf7e193b0 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -53,7 +53,7 @@ class BoardContent extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), child: AFBoard( - key: UniqueKey(), + // key: UniqueKey(), scrollController: ScrollController(), dataController: context.read().boardDataController, headerBuilder: _buildHeader, @@ -83,11 +83,13 @@ class BoardContent extends StatelessWidget { Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) { return AppFlowyColumnFooter( - icon: const Icon(Icons.add, size: 20), - title: const Text('New'), - height: 50, - margin: config.columnItemPadding, - ); + icon: const Icon(Icons.add, size: 20), + title: const Text('New'), + height: 50, + margin: config.columnItemPadding, + onAddButtonClick: () { + context.read().add(BoardEvent.createRow(columnData.id)); + }); } Widget _buildCard(BuildContext context, AFColumnItem item) { @@ -106,13 +108,21 @@ class BoardContent extends StatelessWidget { ); final cellBuilder = BoardCellBuilder(cardController); + final isEditing = context.read().state.editingRow.fold( + () => false, + (editingRow) => editingRow.id == rowPB.id, + ); return AppFlowyColumnItemCard( key: ObjectKey(item), child: BoardCard( + gridId: gridId, + isEditing: isEditing, cellBuilder: cellBuilder, dataController: cardController, - gridId: gridId, + onEditEditing: (rowId) { + context.read().add(BoardEvent.endEditRow(rowId)); + }, ), ); } 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 dfb9ab42d7..2a4006e68c 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart @@ -9,15 +9,21 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'card_cell_builder.dart'; import 'card_container.dart'; +typedef OnEndEditing = void Function(String rowId); + class BoardCard extends StatefulWidget { final String gridId; + final bool isEditing; final CardDataController dataController; final BoardCellBuilder cellBuilder; + final OnEndEditing onEditEditing; const BoardCard({ required this.gridId, + required this.isEditing, required this.dataController, required this.cellBuilder, + required this.onEditEditing, Key? key, }) : super(key: key); @@ -60,7 +66,6 @@ class _BoardCardState extends State { return cellMap.values.map( (cellId) { final child = widget.cellBuilder.buildCell(cellId); - return Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), child: child, diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart index 13f3af2195..ce8df101a5 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart @@ -29,9 +29,13 @@ class BoardCardContainer extends StatelessWidget { ); } } + return Padding( padding: const EdgeInsets.all(8), - child: container, + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 30), + child: container, + ), ); }, ), diff --git a/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart b/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart index ecfea7e119..b639700b5f 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart @@ -42,7 +42,7 @@ class GridBlockCache { } void addListener({ - required void Function(RowChangeReason) onChangeReason, + required void Function(RowsChangedReason) onRowsChanged, bool Function()? listenWhen, }) { _rowCache.onRowsChanged((reason) { @@ -50,7 +50,7 @@ class GridBlockCache { return; } - onChangeReason(reason); + onRowsChanged(reason); }); } } diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart index 73d2079a6b..74f23a1b3e 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart @@ -98,7 +98,7 @@ class GridEvent with _$GridEvent { const factory GridEvent.createRow() = _CreateRow; const factory GridEvent.didReceiveRowUpdate( List rows, - RowChangeReason listState, + RowsChangedReason listState, ) = _DidReceiveRowUpdate; const factory GridEvent.didReceiveFieldUpdate( UnmodifiableListView fields, @@ -117,7 +117,7 @@ class GridState with _$GridState { required GridFieldEquatable fields, required List rowInfos, required GridLoadingState loadingState, - required RowChangeReason reason, + required RowsChangedReason reason, }) = _GridState; factory GridState.initial(String gridId) => GridState( diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart index aae6dc684e..f11db25167 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart @@ -18,13 +18,13 @@ typedef OnGridChanged = void Function(GridPB); typedef OnRowsChanged = void Function( List rowInfos, - RowChangeReason, + RowsChangedReason, ); typedef ListenOnRowChangedCondition = bool Function(); class GridDataController { final String gridId; - final GridService _gridFFIService; + final GridFFIService _gridFFIService; final GridFieldCache fieldCache; // key: the block id @@ -47,7 +47,7 @@ class GridDataController { GridDataController({required ViewPB view}) : gridId = view.id, _blocks = LinkedHashMap.new(), - _gridFFIService = GridService(gridId: view.id), + _gridFFIService = GridFFIService(gridId: view.id), fieldCache = GridFieldCache(gridId: view.id); void addListener({ @@ -105,7 +105,7 @@ class GridDataController { ); cache.addListener( - onChangeReason: (reason) { + onRowsChanged: (reason) { _onRowChanged?.call(rowInfos, reason); }, ); diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart index 093782d32f..4315fff38b 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart @@ -3,14 +3,15 @@ import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/board_card.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart'; -class GridService { +class GridFFIService { final String gridId; - GridService({ + GridFFIService({ required this.gridId, }); @@ -27,6 +28,13 @@ class GridService { return GridEventCreateRow(payload).send(); } + Future> createBoardCard(String groupId) { + CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create() + ..gridId = gridId + ..groupId = groupId; + return GridEventCreateBoardCard(payload).send(); + } + Future> getFields( {required List fieldIds}) { final payload = QueryFieldPayloadPB.create() diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart index 7e3e9a21bd..e586f65bd4 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart @@ -14,13 +14,13 @@ class RowActionSheetBloc extends Bloc { final RowFFIService _rowService; - RowActionSheetBloc({required RowInfo rowData}) + RowActionSheetBloc({required RowInfo rowInfo}) : _rowService = RowFFIService( - gridId: rowData.gridId, - blockId: rowData.blockId, - rowId: rowData.id, + gridId: rowInfo.gridId, + blockId: rowInfo.blockId, + rowId: rowInfo.rowPB.id, ), - super(RowActionSheetState.initial(rowData)) { + super(RowActionSheetState.initial(rowInfo)) { on( (event, emit) async { await event.map( diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart index 372287fd1f..6716967e5e 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart @@ -21,7 +21,7 @@ class RowBloc extends Bloc { }) : _rowService = RowFFIService( gridId: rowInfo.gridId, blockId: rowInfo.blockId, - rowId: rowInfo.id, + rowId: rowInfo.rowPB.id, ), _dataController = dataController, super(RowState.initial(rowInfo, dataController.loadData())) { @@ -71,7 +71,7 @@ class RowEvent with _$RowEvent { const factory RowEvent.initial() = _InitialRow; const factory RowEvent.createRow() = _CreateRow; const factory RowEvent.didReceiveCells( - GridCellMap gridCellMap, RowChangeReason reason) = _DidReceiveCells; + GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells; } @freezed @@ -80,7 +80,7 @@ class RowState with _$RowState { required RowInfo rowInfo, required GridCellMap gridCellMap, required UnmodifiableListView cells, - RowChangeReason? changeReason, + RowsChangedReason? changeReason, }) = _RowState; factory RowState.initial(RowInfo rowInfo, GridCellMap cellDataMap) => diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart index ec212148d1..74e11e409e 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart @@ -51,11 +51,9 @@ class GridRowCache { _fieldNotifier = notifier { // notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier - .receive(const RowChangeReason.fieldDidChange())); + .receive(const RowsChangedReason.fieldDidChange())); notifier.onRowFieldChanged((field) => _cellCache.remove(field.id)); - _rowInfos = block.rows - .map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble())) - .toList(); + _rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList(); } Future dispose() async { @@ -85,16 +83,16 @@ class GridRowCache { for (var rowId in deletedRows) rowId: rowId }; - _rowInfos.asMap().forEach((index, row) { - if (deletedRowByRowId[row.id] == null) { - newRows.add(row); + _rowInfos.asMap().forEach((index, RowInfo rowInfo) { + if (deletedRowByRowId[rowInfo.rowPB.id] == null) { + newRows.add(rowInfo); } else { - _rowByRowId.remove(row.id); - deletedIndex.add(DeletedIndex(index: index, row: row)); + _rowByRowId.remove(rowInfo.rowPB.id); + deletedIndex.add(DeletedIndex(index: index, row: rowInfo)); } }); _rowInfos = newRows; - _rowChangeReasonNotifier.receive(RowChangeReason.delete(deletedIndex)); + _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedIndex)); } void _insertRows(List insertRows) { @@ -103,39 +101,42 @@ class GridRowCache { } InsertedIndexs insertIndexs = []; - for (final insertRow in insertRows) { + for (final InsertedRowPB insertRow in insertRows) { final insertIndex = InsertedIndex( index: insertRow.index, - rowId: insertRow.rowId, + rowId: insertRow.row.id, ); insertIndexs.add(insertIndex); - _rowInfos.insert(insertRow.index, - (buildGridRow(insertRow.rowId, insertRow.height.toDouble()))); + _rowInfos.insert( + insertRow.index, + (buildGridRow(insertRow.row)), + ); } - _rowChangeReasonNotifier.receive(RowChangeReason.insert(insertIndexs)); + _rowChangeReasonNotifier.receive(RowsChangedReason.insert(insertIndexs)); } - void _updateRows(List updatedRows) { + void _updateRows(List updatedRows) { if (updatedRows.isEmpty) { return; } final UpdatedIndexs updatedIndexs = UpdatedIndexs(); - for (final updatedRow in updatedRows) { - final rowId = updatedRow.rowId; - final index = _rowInfos.indexWhere((row) => row.id == rowId); + for (final RowPB updatedRow in updatedRows) { + final rowId = updatedRow.id; + final index = _rowInfos.indexWhere( + (rowInfo) => rowInfo.rowPB.id == rowId, + ); if (index != -1) { - _rowByRowId[rowId] = updatedRow.row; + _rowByRowId[rowId] = updatedRow; _rowInfos.removeAt(index); - _rowInfos.insert( - index, buildGridRow(rowId, updatedRow.row.height.toDouble())); + _rowInfos.insert(index, buildGridRow(updatedRow)); updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId); } } - _rowChangeReasonNotifier.receive(RowChangeReason.update(updatedIndexs)); + _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs)); } void _hideRows(List hideRows) {} @@ -143,7 +144,7 @@ class GridRowCache { void _showRows(List visibleRows) {} void onRowsChanged( - void Function(RowChangeReason) onRowChanged, + void Function(RowsChangedReason) onRowChanged, ) { _rowChangeReasonNotifier.addListener(() { onRowChanged(_rowChangeReasonNotifier.reason); @@ -152,7 +153,7 @@ class GridRowCache { RowUpdateCallback addListener({ required String rowId, - void Function(GridCellMap, RowChangeReason)? onCellUpdated, + void Function(GridCellMap, RowsChangedReason)? onCellUpdated, bool Function()? listenWhen, }) { listenerHandler() async { @@ -230,40 +231,43 @@ class GridRowCache { _rowByRowId[updatedRow.id] = updatedRow; final index = - _rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id); + _rowInfos.indexWhere((rowInfo) => rowInfo.rowPB.id == updatedRow.id); if (index != -1) { // update the corresponding row in _rows if they are not the same - if (_rowInfos[index].rawRow != updatedRow) { - final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow); - _rowInfos.insert(index, row); + if (_rowInfos[index].rowPB != updatedRow) { + final rowInfo = _rowInfos.removeAt(index).copyWith(rowPB: updatedRow); + _rowInfos.insert(index, rowInfo); // Calculate the update index final UpdatedIndexs updatedIndexs = UpdatedIndexs(); - updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id); + updatedIndexs[rowInfo.rowPB.id] = UpdatedIndex( + index: index, + rowId: rowInfo.rowPB.id, + ); // - _rowChangeReasonNotifier.receive(RowChangeReason.update(updatedIndexs)); + _rowChangeReasonNotifier + .receive(RowsChangedReason.update(updatedIndexs)); } } } - RowInfo buildGridRow(String rowId, double rowHeight) { + RowInfo buildGridRow(RowPB rowPB) { return RowInfo( gridId: gridId, blockId: block.id, fields: _fieldNotifier.fields, - id: rowId, - height: rowHeight, + rowPB: rowPB, ); } } class _RowChangesetNotifier extends ChangeNotifier { - RowChangeReason reason = const InitialListState(); + RowsChangedReason reason = const InitialListState(); _RowChangesetNotifier(); - void receive(RowChangeReason newReason) { + void receive(RowsChangedReason newReason) { reason = newReason; reason.map( insert: (_) => notifyListeners(), @@ -280,10 +284,8 @@ class RowInfo with _$RowInfo { const factory RowInfo({ required String gridId, required String blockId, - required String id, required UnmodifiableListView fields, - required double height, - RowPB? rawRow, + required RowPB rowPB, }) = _RowInfo; } @@ -292,12 +294,12 @@ typedef DeletedIndexs = List; typedef UpdatedIndexs = LinkedHashMap; @freezed -class RowChangeReason with _$RowChangeReason { - const factory RowChangeReason.insert(InsertedIndexs items) = _Insert; - const factory RowChangeReason.delete(DeletedIndexs items) = _Delete; - const factory RowChangeReason.update(UpdatedIndexs indexs) = _Update; - const factory RowChangeReason.fieldDidChange() = _FieldDidChange; - const factory RowChangeReason.initial() = InitialListState; +class RowsChangedReason with _$RowsChangedReason { + const factory RowsChangedReason.insert(InsertedIndexs items) = _Insert; + const factory RowsChangedReason.delete(DeletedIndexs items) = _Delete; + const factory RowsChangedReason.update(UpdatedIndexs indexs) = _Update; + const factory RowsChangedReason.fieldDidChange() = _FieldDidChange; + const factory RowsChangedReason.initial() = InitialListState; } class InsertedIndex { diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart index 78783fc894..b4618b397a 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart @@ -5,7 +5,7 @@ import '../cell/cell_service/cell_service.dart'; import '../field/field_cache.dart'; import 'row_cache.dart'; -typedef OnRowChanged = void Function(GridCellMap, RowChangeReason); +typedef OnRowChanged = void Function(GridCellMap, RowsChangedReason); class GridRowDataController extends GridCellBuilderDelegate { final RowInfo rowInfo; @@ -21,12 +21,12 @@ class GridRowDataController extends GridCellBuilderDelegate { _rowCache = rowCache; GridCellMap loadData() { - return _rowCache.loadGridCells(rowInfo.id); + return _rowCache.loadGridCells(rowInfo.rowPB.id); } void addListener({OnRowChanged? onRowChanged}) { _onRowChangedListeners.add(_rowCache.addListener( - rowId: rowInfo.id, + rowId: rowInfo.rowPB.id, onCellUpdated: onRowChanged, )); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart index 8709b395b2..fe9b245567 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart @@ -240,7 +240,7 @@ class _GridRowsState extends State<_GridRows> { Animation animation, ) { final rowCache = - context.read().getRowCache(rowInfo.blockId, rowInfo.id); + context.read().getRowCache(rowInfo.blockId, rowInfo.rowPB.id); /// Return placeholder widget if the rowCache is null. if (rowCache == null) return const SizedBox(); @@ -267,7 +267,7 @@ class _GridRowsState extends State<_GridRows> { cellBuilder, ); }, - key: ValueKey(rowInfo.id), + key: ValueKey(rowInfo.rowPB.id), ), ); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart index c96b5e1526..a4bf813fe5 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart @@ -52,7 +52,7 @@ class _GridRowWidgetState extends State { value: _rowBloc, child: _RowEnterRegion( child: BlocBuilder( - buildWhen: (p, c) => p.rowInfo.height != c.rowInfo.height, + buildWhen: (p, c) => p.rowInfo.rowPB.height != c.rowInfo.rowPB.height, builder: (context, state) { final children = [ const _RowLeading(), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart index 720aac0dc0..8296add94e 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart @@ -21,7 +21,7 @@ class GridRowActionSheet extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => RowActionSheetBloc(rowData: rowData), + create: (context) => RowActionSheetBloc(rowInfo: rowData), child: BlocBuilder( builder: (context, state) { final cells = _RowAction.values diff --git a/frontend/rust-lib/flowy-grid/src/dart_notification.rs b/frontend/rust-lib/flowy-grid/src/dart_notification.rs index 202b12eb81..4108da1f11 100644 --- a/frontend/rust-lib/flowy-grid/src/dart_notification.rs +++ b/frontend/rust-lib/flowy-grid/src/dart_notification.rs @@ -11,6 +11,7 @@ pub enum GridNotification { DidUpdateRow = 30, DidUpdateCell = 40, DidUpdateField = 50, + DidUpdateBoard = 60, } impl std::default::Default for GridNotification { 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 ad89532b02..4d4fcc78c1 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs @@ -106,48 +106,15 @@ impl std::convert::From> for RepeatedBlockPB { #[derive(Debug, Clone, Default, ProtoBuf)] pub struct InsertedRowPB { #[pb(index = 1)] - pub block_id: String, + pub row: RowPB, - #[pb(index = 2)] - pub row_id: String, - - #[pb(index = 3)] - pub height: i32, - - #[pb(index = 4, one_of)] + #[pb(index = 2, one_of)] pub index: Option, } -#[derive(Debug, Default, ProtoBuf)] -pub struct UpdatedRowPB { - #[pb(index = 1)] - pub block_id: String, - - #[pb(index = 2)] - pub row_id: String, - - #[pb(index = 3)] - pub row: RowPB, -} - -impl UpdatedRowPB { - pub fn new(row_rev: &RowRevision, row: RowPB) -> Self { - Self { - row_id: row_rev.id.clone(), - block_id: row_rev.block_id.clone(), - row, - } - } -} - impl std::convert::From for InsertedRowPB { - fn from(row_info: RowPB) -> Self { - Self { - row_id: row_info.id, - block_id: row_info.block_id, - height: row_info.height, - index: None, - } + fn from(row: RowPB) -> Self { + Self { row, index: None } } } @@ -170,7 +137,7 @@ pub struct GridBlockChangesetPB { pub deleted_rows: Vec, #[pb(index = 4)] - pub updated_rows: Vec, + pub updated_rows: Vec, #[pb(index = 5)] pub visible_rows: Vec, @@ -179,9 +146,9 @@ pub struct GridBlockChangesetPB { pub hide_rows: Vec, } impl GridBlockChangesetPB { - pub fn insert(block_id: &str, inserted_rows: Vec) -> Self { + pub fn insert(block_id: String, inserted_rows: Vec) -> Self { Self { - block_id: block_id.to_owned(), + block_id, inserted_rows, ..Default::default() } @@ -195,7 +162,7 @@ impl GridBlockChangesetPB { } } - pub fn update(block_id: &str, updated_rows: Vec) -> Self { + pub fn update(block_id: &str, updated_rows: Vec) -> Self { Self { block_id: block_id.to_owned(), updated_rows, diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/board_card.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/board_card.rs new file mode 100644 index 0000000000..e2dac9069d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/board_card.rs @@ -0,0 +1,70 @@ +use crate::entities::RowPB; +use flowy_derive::ProtoBuf; +use flowy_error::ErrorCode; +use flowy_grid_data_model::parser::NotEmptyStr; + +#[derive(ProtoBuf, Debug, Default, Clone)] +pub struct CreateBoardCardPayloadPB { + #[pb(index = 1)] + pub grid_id: String, + + #[pb(index = 2)] + pub group_id: String, +} +pub struct CreateBoardCardParams { + pub grid_id: String, + pub group_id: String, +} + +impl TryInto for CreateBoardCardPayloadPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; + let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?; + Ok(CreateBoardCardParams { + grid_id: grid_id.0, + group_id: group_id.0, + }) + } +} + +#[derive(Debug, Default, ProtoBuf)] +pub struct BoardCardChangesetPB { + #[pb(index = 1)] + pub group_id: String, + + #[pb(index = 2)] + pub inserted_cards: Vec, + + #[pb(index = 3)] + pub deleted_cards: Vec, + + #[pb(index = 4)] + pub updated_cards: Vec, +} +impl BoardCardChangesetPB { + pub fn insert(group_id: String, inserted_cards: Vec) -> Self { + Self { + group_id, + inserted_cards, + ..Default::default() + } + } + + pub fn delete(group_id: String, deleted_cards: Vec) -> Self { + Self { + group_id, + deleted_cards, + ..Default::default() + } + } + + pub fn update(group_id: String, updated_cards: Vec) -> Self { + Self { + group_id, + updated_cards, + ..Default::default() + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs index c1834c1009..7aa3a1a43e 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs @@ -1,5 +1,7 @@ +mod board_card; mod configuration; mod group; +pub use board_card::*; pub use configuration::*; pub use group::*; diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 86b8e8892c..2c119c55dc 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -232,10 +232,7 @@ pub(crate) async fn get_row_handler( ) -> DataResult { let params: RowIdParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; - let row = editor - .get_row_rev(¶ms.row_id) - .await? - .and_then(make_row_from_row_rev); + let row = editor.get_row_rev(¶ms.row_id).await?.map(make_row_from_row_rev); data_result(OptionalRowPB { row }) } @@ -266,11 +263,11 @@ pub(crate) async fn duplicate_row_handler( pub(crate) async fn create_row_handler( data: Data, manager: AppData>, -) -> Result<(), FlowyError> { +) -> DataResult { let params: CreateRowParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(params.grid_id.as_ref())?; - let _ = editor.create_row(params.start_row_id).await?; - Ok(()) + let row = editor.create_row(params.start_row_id).await?; + data_result(row) } // #[tracing::instrument(level = "debug", skip_all, err)] @@ -416,3 +413,14 @@ pub(crate) async fn get_groups_handler( let group = editor.load_groups().await?; data_result(group) } + +#[tracing::instrument(level = "debug", skip(data, manager), err)] +pub(crate) async fn create_board_card_handler( + data: Data, + manager: AppData>, +) -> DataResult { + let params: CreateBoardCardParams = data.into_inner().try_into()?; + let editor = manager.get_grid_editor(params.grid_id.as_ref())?; + let row = editor.create_board_card(¶ms.group_id).await?; + data_result(row) +} diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index 9e0b2ef5dd..980087fee0 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -39,6 +39,7 @@ pub fn create(grid_manager: Arc) -> Module { // Date .event(GridEvent::UpdateDateCell, update_date_cell_handler) // Group + .event(GridEvent::CreateBoardCard, create_board_card_handler) .event(GridEvent::GetGroup, get_groups_handler); module @@ -209,4 +210,7 @@ pub enum GridEvent { #[event(input = "GridIdPB", output = "RepeatedGridGroupPB")] GetGroup = 100, + + #[event(input = "CreateBoardCardPayloadPB", output = "RowPB")] + CreateBoardCard = 110, } diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs index d7c01a6a09..b2b6ce63fc 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs @@ -1,9 +1,9 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::{CellChangesetPB, GridBlockChangesetPB, InsertedRowPB, RowPB, UpdatedRowPB}; +use crate::entities::{CellChangesetPB, GridBlockChangesetPB, InsertedRowPB, RowPB}; use crate::manager::GridUser; use crate::services::block_revision_editor::{GridBlockRevisionCompactor, GridBlockRevisionEditor}; use crate::services::persistence::block_index::BlockIndexCache; -use crate::services::row::{block_from_row_orders, GridBlockSnapshot}; +use crate::services::row::{block_from_row_orders, make_row_from_row_rev, GridBlockSnapshot}; use dashmap::DashMap; use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{ @@ -62,22 +62,16 @@ impl GridBlockManager { Ok(self.get_editor(&block_id).await?) } - pub(crate) async fn create_row( - &self, - block_id: &str, - row_rev: RowRevision, - start_row_id: Option, - ) -> FlowyResult { + pub(crate) async fn create_row(&self, row_rev: RowRevision, start_row_id: Option) -> FlowyResult { + let block_id = row_rev.block_id.clone(); let _ = self.persistence.insert(&row_rev.block_id, &row_rev.id)?; let editor = self.get_editor(&row_rev.block_id).await?; let mut index_row_order = InsertedRowPB::from(&row_rev); let (row_count, row_index) = editor.create_row(row_rev, start_row_id).await?; index_row_order.index = row_index; - - let _ = self - .notify_did_update_block(block_id, GridBlockChangesetPB::insert(block_id, vec![index_row_order])) - .await?; + let changeset = GridBlockChangesetPB::insert(block_id.clone(), vec![index_row_order]); + let _ = self.notify_did_update_block(&block_id, changeset).await?; Ok(row_count) } @@ -98,10 +92,16 @@ impl GridBlockManager { row_order.index = index; inserted_row_orders.push(row_order); } - changesets.push(GridBlockMetaRevisionChangeset::from_row_count(&block_id, row_count)); + changesets.push(GridBlockMetaRevisionChangeset::from_row_count( + block_id.clone(), + row_count, + )); let _ = self - .notify_did_update_block(&block_id, GridBlockChangesetPB::insert(&block_id, inserted_row_orders)) + .notify_did_update_block( + &block_id, + GridBlockChangesetPB::insert(block_id.clone(), inserted_row_orders), + ) .await?; } @@ -110,20 +110,18 @@ impl GridBlockManager { pub async fn update_row(&self, changeset: RowMetaChangeset, row_builder: F) -> FlowyResult<()> where - F: FnOnce(Arc) -> Option, + F: FnOnce(Arc) -> RowPB, { let editor = self.get_editor_from_row_id(&changeset.row_id).await?; let _ = editor.update_row(changeset.clone()).await?; match editor.get_row_rev(&changeset.row_id).await? { None => tracing::error!("Internal error: can't find the row with id: {}", changeset.row_id), Some(row_rev) => { - if let Some(row) = row_builder(row_rev.clone()) { - let row_order = UpdatedRowPB::new(&row_rev, row); - let block_order_changeset = GridBlockChangesetPB::update(&editor.block_id, vec![row_order]); - let _ = self - .notify_did_update_block(&editor.block_id, block_order_changeset) - .await?; - } + let block_order_changeset = + GridBlockChangesetPB::update(&editor.block_id, vec![row_builder(row_rev.clone())]); + let _ = self + .notify_did_update_block(&editor.block_id, block_order_changeset) + .await?; } } Ok(()) @@ -156,46 +154,40 @@ impl GridBlockManager { .map(|row_info| Cow::Owned(row_info.row_id().to_owned())) .collect::>>(); let row_count = editor.delete_rows(row_ids).await?; - let changeset = GridBlockMetaRevisionChangeset::from_row_count(&grid_block.id, row_count); + let changeset = GridBlockMetaRevisionChangeset::from_row_count(grid_block.id.clone(), row_count); changesets.push(changeset); } Ok(changesets) } - pub(crate) async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> { - let editor = self.get_editor_from_row_id(row_id).await?; - let _ = editor.move_row(row_id, from, to).await?; + pub(crate) async fn move_row(&self, row_rev: Arc, from: usize, to: usize) -> FlowyResult<()> { + let editor = self.get_editor_from_row_id(&row_rev.id).await?; + let _ = editor.move_row(&row_rev.id, from, to).await?; - match editor.get_row_revs(Some(vec![Cow::Borrowed(row_id)])).await?.pop() { - None => {} - Some(row_rev) => { - let insert_row = InsertedRowPB { - block_id: row_rev.block_id.clone(), - row_id: row_rev.id.clone(), - index: Some(to as i32), - height: row_rev.height, - }; + let delete_row_id = row_rev.id.clone(); + let insert_row = InsertedRowPB { + index: Some(to as i32), + row: make_row_from_row_rev(row_rev), + }; - let notified_changeset = GridBlockChangesetPB { - block_id: editor.block_id.clone(), - inserted_rows: vec![insert_row], - deleted_rows: vec![row_rev.id.clone()], - ..Default::default() - }; + let notified_changeset = GridBlockChangesetPB { + block_id: editor.block_id.clone(), + inserted_rows: vec![insert_row], + deleted_rows: vec![delete_row_id], + ..Default::default() + }; - let _ = self - .notify_did_update_block(&editor.block_id, notified_changeset) - .await?; - } - } + let _ = self + .notify_did_update_block(&editor.block_id, notified_changeset) + .await?; Ok(()) } pub async fn update_cell(&self, changeset: CellChangesetPB, row_builder: F) -> FlowyResult<()> where - F: FnOnce(Arc) -> Option, + F: FnOnce(Arc) -> RowPB, { let row_changeset: RowMetaChangeset = changeset.clone().into(); let _ = self.update_row(row_changeset, row_builder).await?; diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs index 4a12bfd613..cb84a4f8f4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs @@ -135,6 +135,47 @@ pub fn try_decode_cell_data( } } +pub fn insert_text_cell(s: String, field_rev: &FieldRevision) -> CellRevision { + let data = apply_cell_data_changeset(s, None, field_rev).unwrap(); + CellRevision::new(data) +} + +pub fn insert_number_cell(num: i64, field_rev: &FieldRevision) -> CellRevision { + let data = apply_cell_data_changeset(num, None, field_rev).unwrap(); + CellRevision::new(data) +} + +pub fn insert_url_cell(url: String, field_rev: &FieldRevision) -> CellRevision { + let data = apply_cell_data_changeset(url, None, field_rev).unwrap(); + CellRevision::new(data) +} + +pub fn insert_checkbox_cell(is_check: bool, field_rev: &FieldRevision) -> CellRevision { + let s = if is_check { + CHECK.to_string() + } else { + UNCHECK.to_string() + }; + let data = apply_cell_data_changeset(s, None, field_rev).unwrap(); + CellRevision::new(data) +} + +pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevision { + let cell_data = serde_json::to_string(&DateCellChangesetPB { + date: Some(timestamp.to_string()), + time: None, + }) + .unwrap(); + let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); + CellRevision::new(data) +} + +pub fn insert_select_option_cell(option_id: String, field_rev: &FieldRevision) -> CellRevision { + let cell_data = SelectOptionCellChangeset::from_insert(&option_id).to_str(); + let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); + CellRevision::new(data) +} + /// If the cell data is not String type, it should impl this trait. /// Deserialize the String into cell specific data type. pub trait FromCellString { 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 f2e28701df..0bdf92ec96 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -37,7 +37,7 @@ pub struct GridRevisionEditor { pub(crate) filter_service: Arc, #[allow(dead_code)] - pub(crate) group_service: Arc, + pub(crate) group_service: Arc>, } impl Drop for GridRevisionEditor { @@ -62,17 +62,17 @@ impl GridRevisionEditor { let block_meta_revs = grid_pad.read().await.get_block_meta_revs(); let block_manager = Arc::new(GridBlockManager::new(grid_id, &user, block_meta_revs, persistence).await?); let filter_service = - Arc::new(GridFilterService::new(grid_pad.clone(), block_manager.clone(), task_scheduler.clone()).await); + GridFilterService::new(grid_pad.clone(), block_manager.clone(), task_scheduler.clone()).await; let group_service = - Arc::new(GridGroupService::new(grid_pad.clone(), block_manager.clone(), task_scheduler.clone()).await); + GridGroupService::new(grid_pad.clone(), block_manager.clone(), task_scheduler.clone()).await; let editor = Arc::new(Self { grid_id: grid_id.to_owned(), user, grid_pad, rev_manager, block_manager, - filter_service, - group_service, + filter_service: Arc::new(filter_service), + group_service: Arc::new(RwLock::new(group_service)), }); Ok(editor) @@ -275,20 +275,8 @@ impl GridRevisionEditor { } pub async fn create_row(&self, start_row_id: Option) -> FlowyResult { - let field_revs = self.grid_pad.read().await.get_field_revs(None)?; - let block_id = self.block_id().await?; - - // insert empty row below the row whose id is upper_row_id - let row_rev = RowRevisionBuilder::new(&block_id, &field_revs).build(); - let row_order = RowPB::from(&row_rev); - - // insert the row - let row_count = self.block_manager.create_row(&block_id, row_rev, start_row_id).await?; - - // update block row count - let changeset = GridBlockMetaRevisionChangeset::from_row_count(&block_id, row_count); - let _ = self.update_block(changeset).await?; - Ok(row_order) + let row_rev = self.create_row_rev().await?; + self.create_row_pb(row_rev, start_row_id).await } pub async fn insert_rows(&self, row_revs: Vec) -> FlowyResult> { @@ -338,6 +326,7 @@ impl GridRevisionEditor { pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { let _ = self.block_manager.delete_row(row_id).await?; + self.group_service.read().await.did_delete_card(row_id.to_owned()).await; Ok(()) } @@ -529,10 +518,22 @@ impl GridRevisionEditor { } pub async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> { - let _ = self.block_manager.move_row(row_id, from as usize, to as usize).await?; + match self.block_manager.get_row_rev(row_id).await? { + None => tracing::warn!("Move row failed, can not find the row:{}", row_id), + Some(row_rev) => { + let _ = self + .block_manager + .move_row(row_rev.clone(), from as usize, to as usize) + .await?; + } + } Ok(()) } + pub async fn move_board_card(&self, group_id: &str, from: i32, to: i32) -> FlowyResult<()> { + self.group_service.write().await.move_card(group_id, from, to).await; + Ok(()) + } pub async fn delta_bytes(&self) -> Bytes { self.grid_pad.read().await.delta_bytes() } @@ -564,12 +565,48 @@ impl GridRevisionEditor { }) } + pub async fn create_board_card(&self, group_id: &str) -> FlowyResult { + let mut row_rev = self.create_row_rev().await?; + let _ = self + .group_service + .write() + .await + .update_board_card(&mut row_rev, group_id) + .await; + + let row_pb = self.create_row_pb(row_rev, None).await?; + self.group_service.read().await.did_create_card(group_id, &row_pb).await; + Ok(row_pb) + } + #[tracing::instrument(level = "trace", skip_all, err)] pub async fn load_groups(&self) -> FlowyResult { - let groups = self.group_service.load_groups().await.unwrap_or_default(); + let groups = self.group_service.write().await.load_groups().await.unwrap_or_default(); Ok(RepeatedGridGroupPB { items: groups }) } + async fn create_row_rev(&self) -> FlowyResult { + let field_revs = self.grid_pad.read().await.get_field_revs(None)?; + let block_id = self.block_id().await?; + + // insert empty row below the row whose id is upper_row_id + let row_rev = RowRevisionBuilder::new(&block_id, &field_revs).build(); + Ok(row_rev) + } + + async fn create_row_pb(&self, row_rev: RowRevision, start_row_id: Option) -> FlowyResult { + let row_pb = RowPB::from(&row_rev); + let block_id = row_rev.block_id.clone(); + + // insert the row + let row_count = self.block_manager.create_row(row_rev, start_row_id).await?; + + // update block row count + let changeset = GridBlockMetaRevisionChangeset::from_row_count(block_id, row_count); + let _ = self.update_block(changeset).await?; + Ok(row_pb) + } + async fn modify(&self, f: F) -> FlowyResult<()> where F: for<'a> FnOnce(&'a mut GridRevisionPad) -> FlowyResult>, diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs index 32b699d3a1..85c135d0ee 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs @@ -1,17 +1,48 @@ use crate::entities::CheckboxGroupConfigurationPB; +use flowy_error::FlowyResult; +use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use std::sync::Arc; use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; -use crate::services::group::{Group, GroupAction, GroupCellContentProvider, GroupController, GroupGenerator}; +use crate::services::group::{ + Group, GroupActionHandler, GroupCellContentProvider, GroupController, GroupGenerator, Groupable, +}; pub type CheckboxGroupController = GroupController; +impl Groupable for CheckboxGroupController { + type CellDataType = CheckboxCellData; + + fn can_group(&self, _content: &str, _cell_data: &Self::CellDataType) -> bool { + false + } +} + +impl GroupActionHandler for CheckboxGroupController { + fn field_id(&self) -> &str { + &self.field_id + } + + fn get_groups(&self) -> Vec { + self.make_groups() + } + + fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { + self.handle_rows(row_revs, field_rev) + } + + fn update_card(&self, _row_rev: &mut RowRevision, _field_rev: &FieldRevision, _group_id: &str) { + todo!() + } +} + pub struct CheckboxGroupGenerator(); impl GroupGenerator for CheckboxGroupGenerator { type ConfigurationType = CheckboxGroupConfigurationPB; type TypeOptionType = CheckboxTypeOptionPB; - fn gen_groups( + fn generate_groups( _configuration: &Option, _type_option: &Option, _cell_content_provider: &dyn GroupCellContentProvider, @@ -33,11 +64,3 @@ impl GroupGenerator for CheckboxGroupGenerator { vec![check_group, uncheck_group] } } - -impl GroupAction for CheckboxGroupController { - type CellDataType = CheckboxCellData; - - fn should_group(&self, _content: &str, _cell_data: &Self::CellDataType) -> bool { - false - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs index 4740e6e7bb..a351913546 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs @@ -5,16 +5,12 @@ use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{ FieldRevision, GroupConfigurationRevision, RowRevision, TypeOptionDataDeserializer, }; + use indexmap::IndexMap; + use std::marker::PhantomData; use std::sync::Arc; -pub trait GroupAction { - type CellDataType; - - fn should_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool; -} - pub trait GroupCellContentProvider { /// We need to group the rows base on the deduplication cell content when the field type is /// RichText. @@ -27,22 +23,46 @@ pub trait GroupGenerator { type ConfigurationType; type TypeOptionType; - fn gen_groups( + fn generate_groups( configuration: &Option, type_option: &Option, cell_content_provider: &dyn GroupCellContentProvider, ) -> Vec; } -pub struct GroupController { - pub field_rev: Arc, - pub groups: IndexMap, +pub trait Groupable { + type CellDataType; + fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool; +} + +pub trait GroupActionHandler: Send + Sync { + fn field_id(&self) -> &str; + fn get_groups(&self) -> Vec; + fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()>; + fn update_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); +} + +pub trait GroupActionHandler2: Send + Sync { + fn create_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); +} + +const DEFAULT_GROUP_ID: &str = "default_group"; + +/// C: represents the group configuration structure +/// T: the type option data deserializer that impl [TypeOptionDataDeserializer] +/// G: the group container generator +/// P: the parser that impl [CellBytesParser] for the CellBytes +pub struct GroupController { + pub field_id: String, + pub groups_map: IndexMap, + default_group: Group, pub type_option: Option, pub configuration: Option, group_action_phantom: PhantomData, - cell_parser_phantom: PhantomData, + cell_parser_phantom: PhantomData

, } +#[derive(Clone)] pub struct Group { pub id: String, pub desc: String, @@ -60,14 +80,14 @@ impl std::convert::From for GroupPB { } } -impl GroupController +impl GroupController where C: TryFrom, T: TypeOptionDataDeserializer, G: GroupGenerator, { pub fn new( - field_rev: Arc, + field_rev: &Arc, configuration: GroupConfigurationRevision, cell_content_provider: &dyn GroupCellContentProvider, ) -> FlowyResult { @@ -77,10 +97,19 @@ where }; let field_type_rev = field_rev.field_type_rev; let type_option = field_rev.get_type_option_entry::(field_type_rev); - let groups = G::gen_groups(&configuration, &type_option, cell_content_provider); + let groups = G::generate_groups(&configuration, &type_option, cell_content_provider); + + let default_group = Group { + id: DEFAULT_GROUP_ID.to_owned(), + desc: format!("No {}", field_rev.name), + rows: vec![], + content: "".to_string(), + }; + Ok(Self { - field_rev, - groups: groups.into_iter().map(|group| (group.id.clone(), group)).collect(), + field_id: field_rev.id.clone(), + groups_map: groups.into_iter().map(|group| (group.id.clone(), group)).collect(), + default_group, type_option, configuration, group_action_phantom: PhantomData, @@ -88,42 +117,52 @@ where }) } - pub fn take_groups(self) -> Vec { - self.groups.into_values().collect() + pub fn make_groups(&self) -> Vec { + let default_group = self.default_group.clone(); + let mut groups: Vec = self.groups_map.values().cloned().collect(); + if !default_group.rows.is_empty() { + groups.push(default_group); + } + groups } } -impl GroupController +impl GroupController where - CP: CellBytesParser, - Self: GroupAction, + P: CellBytesParser, + Self: Groupable, { - pub fn group_rows(&mut self, rows: &[Arc]) -> FlowyResult<()> { + pub fn handle_rows(&mut self, rows: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { + // The field_rev might be None if corresponding field_rev is deleted. if self.configuration.is_none() { return Ok(()); } for row in rows { - if let Some(cell_rev) = row.cells.get(&self.field_rev.id) { + if let Some(cell_rev) = row.cells.get(&self.field_id) { let mut records: Vec = vec![]; - - let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), &self.field_rev); - let cell_data = cell_bytes.parser::()?; - for group in self.groups.values() { - if self.should_group(&group.content, &cell_data) { + let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); + let cell_data = cell_bytes.parser::

()?; + for group in self.groups_map.values() { + if self.can_group(&group.content, &cell_data) { records.push(GroupRecord { row: row.into(), group_id: group.id.clone(), }); - break; } } - for record in records { - if let Some(group) = self.groups.get_mut(&record.group_id) { - group.rows.push(record.row); + if records.is_empty() { + self.default_group.rows.push(row.into()); + } else { + for record in records { + if let Some(group) = self.groups_map.get_mut(&record.group_id) { + group.rows.push(record.row); + } } } + } else { + self.default_group.rows.push(row.into()); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs index bc4f1c4709..50628875a8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs @@ -1,9 +1,16 @@ use crate::entities::SelectOptionGroupConfigurationPB; +use crate::services::cell::insert_select_option_cell; +use flowy_error::FlowyResult; +use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; + +use std::sync::Arc; use crate::services::field::{ MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB, }; -use crate::services::group::{Group, GroupAction, GroupCellContentProvider, GroupController, GroupGenerator}; +use crate::services::group::{ + Group, GroupActionHandler, GroupCellContentProvider, GroupController, GroupGenerator, Groupable, +}; // SingleSelect pub type SingleSelectGroupController = GroupController< @@ -13,11 +20,43 @@ pub type SingleSelectGroupController = GroupController< SelectOptionCellDataParser, >; +impl Groupable for SingleSelectGroupController { + type CellDataType = SelectOptionCellDataPB; + fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { + cell_data.select_options.iter().any(|option| option.id == content) + } +} + +impl GroupActionHandler for SingleSelectGroupController { + fn field_id(&self) -> &str { + &self.field_id + } + + fn get_groups(&self) -> Vec { + self.make_groups() + } + + fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { + self.handle_rows(row_revs, field_rev) + } + + fn update_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { + let group: Option<&Group> = self.groups_map.get(group_id); + match group { + None => {} + Some(group) => { + let cell_rev = insert_select_option_cell(group.id.clone(), field_rev); + row_rev.cells.insert(field_rev.id.clone(), cell_rev); + } + } + } +} + pub struct SingleSelectGroupGenerator(); impl GroupGenerator for SingleSelectGroupGenerator { type ConfigurationType = SelectOptionGroupConfigurationPB; type TypeOptionType = SingleSelectTypeOptionPB; - fn gen_groups( + fn generate_groups( _configuration: &Option, type_option: &Option, _cell_content_provider: &dyn GroupCellContentProvider, @@ -38,13 +77,6 @@ impl GroupGenerator for SingleSelectGroupGenerator { } } -impl GroupAction for SingleSelectGroupController { - type CellDataType = SelectOptionCellDataPB; - fn should_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { - cell_data.select_options.iter().any(|option| option.id == content) - } -} - // MultiSelect pub type MultiSelectGroupController = GroupController< SelectOptionGroupConfigurationPB, @@ -53,12 +85,44 @@ pub type MultiSelectGroupController = GroupController< SelectOptionCellDataParser, >; +impl Groupable for MultiSelectGroupController { + type CellDataType = SelectOptionCellDataPB; + fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { + cell_data.select_options.iter().any(|option| option.id == content) + } +} + +impl GroupActionHandler for MultiSelectGroupController { + fn field_id(&self) -> &str { + &self.field_id + } + + fn get_groups(&self) -> Vec { + self.make_groups() + } + + fn group_rows(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { + self.handle_rows(row_revs, field_rev) + } + + fn update_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { + let group: Option<&Group> = self.groups_map.get(group_id); + match group { + None => tracing::warn!("Can not find the group: {}", group_id), + Some(group) => { + let cell_rev = insert_select_option_cell(group.id.clone(), field_rev); + row_rev.cells.insert(field_rev.id.clone(), cell_rev); + } + } + } +} + pub struct MultiSelectGroupGenerator(); impl GroupGenerator for MultiSelectGroupGenerator { type ConfigurationType = SelectOptionGroupConfigurationPB; type TypeOptionType = MultiSelectTypeOptionPB; - fn gen_groups( + fn generate_groups( _configuration: &Option, type_option: &Option, _cell_content_provider: &dyn GroupCellContentProvider, @@ -78,10 +142,3 @@ impl GroupGenerator for MultiSelectGroupGenerator { } } } - -impl GroupAction for MultiSelectGroupController { - type CellDataType = SelectOptionCellDataPB; - fn should_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { - cell_data.select_options.iter().any(|option| option.id == 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 5be0141ca9..3fd84d014c 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,27 +1,30 @@ +use crate::dart_notification::{send_dart_notification, GridNotification}; +use crate::entities::{ + BoardCardChangesetPB, CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, GroupPB, + NumberGroupConfigurationPB, RowPB, SelectOptionGroupConfigurationPB, TextGroupConfigurationPB, + UrlGroupConfigurationPB, +}; use crate::services::block_manager::GridBlockManager; use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::group::{ - CheckboxGroupController, Group, GroupCellContentProvider, MultiSelectGroupController, SingleSelectGroupController, + CheckboxGroupController, GroupActionHandler, GroupCellContentProvider, MultiSelectGroupController, + SingleSelectGroupController, }; -use crate::entities::{ - CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, GroupPB, NumberGroupConfigurationPB, - SelectOptionGroupConfigurationPB, TextGroupConfigurationPB, UrlGroupConfigurationPB, -}; use bytes::Bytes; use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{gen_grid_group_id, FieldRevision, GroupConfigurationRevision, RowRevision}; use flowy_sync::client_grid::GridRevisionPad; + use std::sync::Arc; use tokio::sync::RwLock; pub(crate) struct GridGroupService { #[allow(dead_code)] scheduler: Arc, - #[allow(dead_code)] grid_pad: Arc>, - #[allow(dead_code)] block_manager: Arc, + group_action_handler: Option>>, } impl GridGroupService { @@ -35,14 +38,14 @@ impl GridGroupService { scheduler, grid_pad, block_manager, + group_action_handler: None, } } - pub(crate) async fn load_groups(&self) -> Option> { - let grid_pad = self.grid_pad.read().await; - let field_rev = find_group_field(grid_pad.fields()).unwrap(); + pub(crate) async fn load_groups(&mut self) -> Option> { + let field_rev = find_group_field(self.grid_pad.read().await.fields()).unwrap(); let field_type: FieldType = field_rev.field_type_rev.into(); - let configuration = self.get_group_configuration(field_rev).await; + let configuration = self.get_group_configuration(&field_rev).await; let blocks = self.block_manager.get_block_snapshots(None).await.unwrap(); let row_revs = blocks @@ -51,19 +54,39 @@ impl GridGroupService { .flatten() .collect::>>(); - match self.build_groups(&field_type, field_rev, row_revs, configuration) { + match self + .build_groups(&field_type, &field_rev, row_revs, configuration) + .await + { Ok(groups) => Some(groups), Err(_) => None, } } - async fn get_group_configuration(&self, field_rev: &FieldRevision) -> GroupConfigurationRevision { + #[tracing::instrument(level = "debug", skip(self, row_rev))] + pub(crate) async fn update_board_card(&self, row_rev: &mut RowRevision, group_id: &str) { + if let Some(group_action_handler) = self.group_action_handler.as_ref() { + let field_id = group_action_handler.read().await.field_id().to_owned(); + + match self.grid_pad.read().await.get_field_rev(&field_id) { + None => tracing::warn!("Fail to create card because the field does not exist"), + Some((_, field_rev)) => { + group_action_handler + .write() + .await + .update_card(row_rev, field_rev, group_id); + } + } + } + } + + pub(crate) async fn get_group_configuration(&self, field_rev: &FieldRevision) -> GroupConfigurationRevision { let grid_pad = self.grid_pad.read().await; let setting = grid_pad.get_setting_rev(); let layout = &setting.layout; let configurations = setting.get_groups(layout, &field_rev.id, &field_rev.field_type_rev); match configurations { - None => self.default_group_configuration(field_rev), + None => default_group_configuration(field_rev), Some(mut configurations) => { assert_eq!(configurations.len(), 1); (&*configurations.pop().unwrap()).clone() @@ -71,79 +94,112 @@ impl GridGroupService { } } - fn default_group_configuration(&self, field_rev: &FieldRevision) -> GroupConfigurationRevision { - let field_type: FieldType = field_rev.field_type_rev.clone().into(); - let bytes: Bytes = match field_type { - FieldType::RichText => TextGroupConfigurationPB::default().try_into().unwrap(), - FieldType::Number => NumberGroupConfigurationPB::default().try_into().unwrap(), - FieldType::DateTime => DateGroupConfigurationPB::default().try_into().unwrap(), - FieldType::SingleSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(), - FieldType::MultiSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(), - FieldType::Checkbox => CheckboxGroupConfigurationPB::default().try_into().unwrap(), - FieldType::URL => UrlGroupConfigurationPB::default().try_into().unwrap(), - }; - GroupConfigurationRevision { - id: gen_grid_group_id(), - field_id: field_rev.id.clone(), - field_type_rev: field_rev.field_type_rev.clone(), - content: Some(bytes.to_vec()), + pub async fn move_card(&self, _group_id: &str, _from: i32, _to: i32) { + // BoardCardChangesetPB { + // group_id: "".to_string(), + // inserted_cards: vec![], + // deleted_cards: vec![], + // updated_cards: vec![] + // } + // let row_pb = make_row_from_row_rev(row_rev); + todo!() + } + + pub async fn did_delete_card(&self, _row_id: String) { + // let changeset = BoardCardChangesetPB::delete(group_id.to_owned(), vec![row_id]); + // self.notify_did_update_board(changeset).await; + todo!() + } + + pub async fn did_create_card(&self, group_id: &str, row_pb: &RowPB) { + let changeset = BoardCardChangesetPB::insert(group_id.to_owned(), vec![row_pb.clone()]); + self.notify_did_update_board(changeset).await; + } + + pub async fn notify_did_update_board(&self, changeset: BoardCardChangesetPB) { + if self.group_action_handler.is_none() { + return; } + send_dart_notification(&changeset.group_id, GridNotification::DidUpdateBoard) + .payload(changeset) + .send(); } #[tracing::instrument(level = "trace", skip_all, err)] - fn build_groups( - &self, + async fn build_groups( + &mut self, field_type: &FieldType, field_rev: &Arc, row_revs: Vec>, configuration: GroupConfigurationRevision, ) -> FlowyResult> { - let groups: Vec = match field_type { + match field_type { FieldType::RichText => { // let generator = GroupGenerator::::from_configuration(configuration); - vec![] } FieldType::Number => { // let generator = GroupGenerator::::from_configuration(configuration); - vec![] } FieldType::DateTime => { // let generator = GroupGenerator::::from_configuration(configuration); - vec![] } FieldType::SingleSelect => { - let mut group_controller = - SingleSelectGroupController::new(field_rev.clone(), configuration, &self.grid_pad)?; - let _ = group_controller.group_rows(&row_revs)?; - group_controller.take_groups() + let controller = SingleSelectGroupController::new(field_rev, configuration, &self.grid_pad)?; + self.group_action_handler = Some(Arc::new(RwLock::new(controller))); } FieldType::MultiSelect => { - let mut group_controller = - MultiSelectGroupController::new(field_rev.clone(), configuration, &self.grid_pad)?; - let _ = group_controller.group_rows(&row_revs)?; - group_controller.take_groups() + let controller = MultiSelectGroupController::new(field_rev, configuration, &self.grid_pad)?; + self.group_action_handler = Some(Arc::new(RwLock::new(controller))); } FieldType::Checkbox => { - let mut group_controller = - CheckboxGroupController::new(field_rev.clone(), configuration, &self.grid_pad)?; - let _ = group_controller.group_rows(&row_revs)?; - group_controller.take_groups() + let controller = CheckboxGroupController::new(field_rev, configuration, &self.grid_pad)?; + self.group_action_handler = Some(Arc::new(RwLock::new(controller))); } FieldType::URL => { // let generator = GroupGenerator::::from_configuration(configuration); - vec![] } }; + let mut groups = vec![]; + if let Some(group_action_handler) = self.group_action_handler.as_ref() { + let mut write_guard = group_action_handler.write().await; + let _ = write_guard.group_rows(&row_revs, field_rev)?; + groups = write_guard.get_groups(); + drop(write_guard); + } + Ok(groups.into_iter().map(GroupPB::from).collect()) } } -fn find_group_field(field_revs: &[Arc]) -> Option<&Arc> { - field_revs.iter().find(|field_rev| { - let field_type: FieldType = field_rev.field_type_rev.into(); - field_type.can_be_group() - }) +fn find_group_field(field_revs: &[Arc]) -> Option> { + let field_rev = field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.field_type_rev.into(); + field_type.can_be_group() + }) + .cloned(); + field_rev } impl GroupCellContentProvider for Arc> {} + +fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision { + let field_type: FieldType = field_rev.field_type_rev.into(); + let bytes: Bytes = match field_type { + FieldType::RichText => TextGroupConfigurationPB::default().try_into().unwrap(), + FieldType::Number => NumberGroupConfigurationPB::default().try_into().unwrap(), + FieldType::DateTime => DateGroupConfigurationPB::default().try_into().unwrap(), + FieldType::SingleSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(), + FieldType::MultiSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(), + FieldType::Checkbox => CheckboxGroupConfigurationPB::default().try_into().unwrap(), + FieldType::URL => UrlGroupConfigurationPB::default().try_into().unwrap(), + }; + GroupConfigurationRevision { + id: gen_grid_group_id(), + field_id: field_rev.id.clone(), + field_type_rev: field_rev.field_type_rev, + content: Some(bytes.to_vec()), + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs index 3db4c0b550..ee586b3851 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs @@ -1,5 +1,8 @@ -use crate::services::cell::apply_cell_data_changeset; -use crate::services::field::{DateCellChangesetPB, SelectOptionCellChangeset}; +use crate::services::cell::{ + insert_checkbox_cell, insert_date_cell, insert_number_cell, insert_select_option_cell, insert_text_cell, + insert_url_cell, +}; + use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; use indexmap::IndexMap; use std::collections::HashMap; @@ -34,47 +37,68 @@ impl<'a> RowRevisionBuilder<'a> { } } - pub fn insert_cell(&mut self, field_id: &str, data: String) { + pub fn insert_text_cell(&mut self, field_id: &str, data: String) { match self.field_rev_map.get(&field_id.to_owned()) { - None => { - tracing::warn!("Can't find the field with id: {}", field_id); - } + None => tracing::warn!("Can't find the text field with id: {}", field_id), Some(field_rev) => { - let data = apply_cell_data_changeset(data, None, field_rev).unwrap(); - let cell = CellRevision::new(data); - self.payload.cell_by_field_id.insert(field_id.to_owned(), cell); + self.payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_text_cell(data, field_rev)); + } + } + } + + pub fn insert_url_cell(&mut self, field_id: &str, data: String) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the url field with id: {}", field_id), + Some(field_rev) => { + self.payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_url_cell(data, field_rev)); + } + } + } + + pub fn insert_number_cell(&mut self, field_id: &str, num: i64) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the number field with id: {}", field_id), + Some(field_rev) => { + self.payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_number_cell(num, field_rev)); + } + } + } + + pub fn insert_checkbox_cell(&mut self, field_id: &str, is_check: bool) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the checkbox field with id: {}", field_id), + Some(field_rev) => { + self.payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_checkbox_cell(is_check, field_rev)); } } } pub fn insert_date_cell(&mut self, field_id: &str, timestamp: i64) { match self.field_rev_map.get(&field_id.to_owned()) { - None => { - tracing::warn!("Invalid field_id: {}", field_id); - } + None => tracing::warn!("Can't find the date field with id: {}", field_id), Some(field_rev) => { - let cell_data = serde_json::to_string(&DateCellChangesetPB { - date: Some(timestamp.to_string()), - time: None, - }) - .unwrap(); - let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); - let cell = CellRevision::new(data); - self.payload.cell_by_field_id.insert(field_id.to_owned(), cell); + self.payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_date_cell(timestamp, field_rev)); } } } pub fn insert_select_option_cell(&mut self, field_id: &str, data: String) { match self.field_rev_map.get(&field_id.to_owned()) { - None => { - tracing::warn!("Invalid field_id: {}", field_id); - } + None => tracing::warn!("Can't find the select option field with id: {}", field_id), Some(field_rev) => { - let cell_data = SelectOptionCellChangeset::from_insert(&data).to_str(); - let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); - let cell = CellRevision::new(data); - self.payload.cell_by_field_id.insert(field_id.to_owned(), cell); + self.payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_select_option_cell(data, field_rev)); } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index 24c2cda68a..1a0d0eaff6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -39,8 +39,8 @@ pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc]) -> Ve row_revs.iter().map(RowPB::from).collect::>() } -pub(crate) fn make_row_from_row_rev(row_rev: Arc) -> Option { - make_rows_from_row_revs(&[row_rev]).pop() +pub(crate) fn make_row_from_row_rev(row_rev: Arc) -> RowPB { + make_rows_from_row_revs(&[row_rev]).pop().unwrap() } pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc]) -> Vec { diff --git a/frontend/rust-lib/flowy-grid/src/util.rs b/frontend/rust-lib/flowy-grid/src/util.rs index ca1d92a54c..90bf2f2a26 100644 --- a/frontend/rust-lib/flowy-grid/src/util.rs +++ b/frontend/rust-lib/flowy-grid/src/util.rs @@ -76,7 +76,7 @@ pub fn make_default_board() -> BuildGridContext { let multi_select_type_option = MultiSelectTypeOptionBuilder::default() .add_option(banana_option.clone()) .add_option(apple_option.clone()) - .add_option(pear_option.clone()); + .add_option(pear_option); let multi_select_field = FieldBuilder::new(multi_select_type_option) .name("Fruit") .visibility(true) @@ -114,20 +114,15 @@ pub fn make_default_board() -> BuildGridContext { row_builder.insert_select_option_cell(&multi_select_field_id, apple_option.id.clone()); row_builder.insert_select_option_cell(&multi_select_field_id, banana_option.id.clone()); // insert text - row_builder.insert_cell(&text_field_id, format!("Card {}", i)); + row_builder.insert_text_cell(&text_field_id, format!("Card {}", i)); // insert date row_builder.insert_date_cell(&date_field_id, timestamp); // number - row_builder.insert_cell(&number_field_id, format!("{}", i)); + row_builder.insert_number_cell(&number_field_id, i); // checkbox - let is_check = if i % 2 == 0 { - CHECK.to_string() - } else { - UNCHECK.to_string() - }; - row_builder.insert_cell(&checkbox_field_id, is_check); + row_builder.insert_checkbox_cell(&checkbox_field_id, i % 2 == 0); // url - row_builder.insert_cell(&url_field_id, "https://appflowy.io".to_string()); + row_builder.insert_url_cell(&url_field_id, "https://appflowy.io".to_string()); let row = row_builder.build(); grid_builder.add_row(row); diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs index a733926228..848b2876fa 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs @@ -26,14 +26,14 @@ impl<'a> GridRowTestBuilder<'a> { pub fn insert_text_cell(&mut self, data: &str) -> String { let text_field = self.field_rev_with_type(&FieldType::RichText); - self.inner_builder.insert_cell(&text_field.id, data.to_string()); + self.inner_builder.insert_text_cell(&text_field.id, data.to_string()); text_field.id.clone() } pub fn insert_number_cell(&mut self, data: &str) -> String { let number_field = self.field_rev_with_type(&FieldType::Number); - self.inner_builder.insert_cell(&number_field.id, data.to_string()); + self.inner_builder.insert_text_cell(&number_field.id, data.to_string()); number_field.id.clone() } @@ -44,20 +44,21 @@ impl<'a> GridRowTestBuilder<'a> { }) .unwrap(); let date_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.insert_cell(&date_field.id, value); + self.inner_builder.insert_text_cell(&date_field.id, value); date_field.id.clone() } pub fn insert_checkbox_cell(&mut self, data: &str) -> String { let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox); - self.inner_builder.insert_cell(&checkbox_field.id, data.to_string()); + self.inner_builder + .insert_text_cell(&checkbox_field.id, data.to_string()); checkbox_field.id.clone() } pub fn insert_url_cell(&mut self, data: &str) -> String { let url_field = self.field_rev_with_type(&FieldType::URL); - self.inner_builder.insert_cell(&url_field.id, data.to_string()); + self.inner_builder.insert_text_cell(&url_field.id, data.to_string()); url_field.id.clone() } diff --git a/shared-lib/flowy-error-code/src/code.rs b/shared-lib/flowy-error-code/src/code.rs index 352c4abbdb..5b676073da 100644 --- a/shared-lib/flowy-error-code/src/code.rs +++ b/shared-lib/flowy-error-code/src/code.rs @@ -111,6 +111,9 @@ pub enum ErrorCode { #[display(fmt = "Field's type option data should not be empty")] TypeOptionDataIsEmpty = 450, + #[display(fmt = "Group id is empty")] + GroupIdIsEmpty = 460, + #[display(fmt = "Invalid date time format")] InvalidDateTimeFormat = 500, diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs index b60baff811..184f5266ed 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs @@ -88,9 +88,9 @@ pub struct GridBlockMetaRevisionChangeset { } impl GridBlockMetaRevisionChangeset { - pub fn from_row_count(block_id: &str, row_count: i32) -> Self { + pub fn from_row_count(block_id: String, row_count: i32) -> Self { Self { - block_id: block_id.to_string(), + block_id, start_row_index: None, row_count: Some(row_count), } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs index 46b278a1af..e47cf1287d 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs @@ -44,7 +44,7 @@ impl GridRevisionPad { .blocks .iter() .map(|block| { - let mut duplicated_block = (&*block.clone()).clone(); + let mut duplicated_block = (&**block).clone(); duplicated_block.block_id = gen_block_id(); duplicated_block })