diff --git a/frontend/app_flowy/assets/images/grid/setting/group.svg b/frontend/app_flowy/assets/images/grid/setting/group.svg new file mode 100644 index 0000000000..f0a6dff4f9 --- /dev/null +++ b/frontend/app_flowy/assets/images/grid/setting/group.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 953e5b02bb..3990a5acc4 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -160,7 +160,8 @@ "settings": { "filter": "Filter", "sortBy": "Sort by", - "Properties": "Properties" + "Properties": "Properties", + "group": "Group" }, "field": { "hide": "Hide", 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 6913b07023..26dd15038a 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -1,6 +1,6 @@ import 'dart:async'; 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/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; import 'package:app_flowy/plugins/grid/application/row/row_service.dart'; import 'package:appflowy_board/appflowy_board.dart'; @@ -25,7 +25,8 @@ class BoardBloc extends Bloc { final MoveRowFFIService _rowService; LinkedHashMap groupControllers = LinkedHashMap(); - GridFieldCache get fieldCache => _gridDataController.fieldCache; + GridFieldController get fieldController => + _gridDataController.fieldController; String get gridId => _gridDataController.gridId; BoardBloc({required ViewPB view}) @@ -110,9 +111,11 @@ class BoardBloc extends Bloc { emit(state.copyWith(noneOrError: some(error))); }, didReceiveGroups: (List groups) { - emit(state.copyWith( - groupIds: groups.map((group) => group.groupId).toList(), - )); + emit( + state.copyWith( + groupIds: groups.map((group) => group.groupId).toList(), + ), + ); }, ); }, @@ -154,6 +157,23 @@ class BoardBloc extends Bloc { } void initializeGroups(List groups) { + for (var controller in groupControllers.values) { + controller.dispose(); + } + groupControllers.clear(); + boardController.clear(); + + // + List columns = groups.map((group) { + return AFBoardColumnData( + id: group.groupId, + name: group.desc, + items: _buildRows(group), + customData: group, + ); + }).toList(); + boardController.addColumns(columns); + for (final group in groups) { final delegate = GroupControllerDelegateImpl( controller: boardController, @@ -184,38 +204,35 @@ class BoardBloc extends Bloc { } }, didLoadGroups: (groups) { - List columns = groups.map((group) { - return AFBoardColumnData( - id: group.groupId, - name: group.desc, - items: _buildRows(group), - customData: group, - ); - }).toList(); - - boardController.addColumns(columns); + if (isClosed) return; initializeGroups(groups); add(BoardEvent.didReceiveGroups(groups)); }, onDeletedGroup: (groupIds) { + if (isClosed) return; // }, onInsertedGroup: (insertedGroups) { + if (isClosed) return; // }, onUpdatedGroup: (updatedGroups) { - // + if (isClosed) return; for (final group in updatedGroups) { final columnController = boardController.getColumnController(group.groupId); - if (columnController != null) { - columnController.updateColumnName(group.desc); - } + columnController?.updateColumnName(group.desc); } }, onError: (err) { Log.error(err); }, + onResetGroups: (groups) { + if (isClosed) return; + + initializeGroups(groups); + add(BoardEvent.didReceiveGroups(groups)); + }, ); } 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 4ab45c64b7..923bb4ef8c 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 @@ -1,7 +1,7 @@ import 'dart:collection'; 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/field/field_controller.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/protobuf/flowy-error/errors.pb.dart'; @@ -12,12 +12,13 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; import 'board_listener.dart'; -typedef OnFieldsChanged = void Function(UnmodifiableListView); +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 OnResetGroups = void Function(List); typedef OnRowsChanged = void Function( List, @@ -28,7 +29,7 @@ typedef OnError = void Function(FlowyError); class BoardDataController { final String gridId; final GridFFIService _gridFFIService; - final GridFieldCache fieldCache; + final GridFieldController fieldController; final BoardListener _listener; // key: the block id @@ -55,7 +56,7 @@ class BoardDataController { // ignore: prefer_collection_literals _blocks = LinkedHashMap(), _gridFFIService = GridFFIService(gridId: view.id), - fieldCache = GridFieldCache(gridId: view.id); + fieldController = GridFieldController(gridId: view.id); void addListener({ required OnGridChanged onGridChanged, @@ -65,6 +66,7 @@ class BoardDataController { required OnUpdatedGroup onUpdatedGroup, required OnDeletedGroup onDeletedGroup, required OnInsertedGroup onInsertedGroup, + required OnResetGroups onResetGroups, required OnError? onError, }) { _onGridChanged = onGridChanged; @@ -73,28 +75,36 @@ class BoardDataController { _onRowsChanged = onRowsChanged; _onError = onError; - fieldCache.addListener(onFields: (fields) { + fieldController.addListener(onFields: (fields) { _onFieldsChanged?.call(UnmodifiableListView(fields)); }); - _listener.start(onBoardChanged: (result) { - result.fold( - (changeset) { - if (changeset.updateGroups.isNotEmpty) { - onUpdatedGroup.call(changeset.updateGroups); - } + _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.insertedGroups.isNotEmpty) { + onInsertedGroup.call(changeset.insertedGroups); + } - if (changeset.deletedGroups.isNotEmpty) { - onDeletedGroup.call(changeset.deletedGroups); - } - }, - (e) => _onError?.call(e), - ); - }); + if (changeset.deletedGroups.isNotEmpty) { + onDeletedGroup.call(changeset.deletedGroups); + } + }, + (e) => _onError?.call(e), + ); + }, + onGroupByNewField: (result) { + result.fold( + (groups) => onResetGroups(groups), + (e) => _onError?.call(e), + ); + }, + ); } Future> loadData() async { @@ -103,16 +113,15 @@ class BoardDataController { () => result.fold( (grid) async { _onGridChanged?.call(grid); - - return await _loadFields(grid).then((result) { - return result.fold( - (l) { - _loadGroups(grid.blocks); - return left(l); - }, - (err) => right(err), - ); - }); + return await fieldController.loadFields(fieldIds: grid.fields).then( + (result) => result.fold( + (l) { + _loadGroups(grid.blocks); + return left(l); + }, + (err) => right(err), + ), + ); }, (err) => right(err), ), @@ -126,33 +135,19 @@ class BoardDataController { Future dispose() async { await _gridFFIService.closeGrid(); - await fieldCache.dispose(); + await fieldController.dispose(); for (final blockCache in _blocks.values) { blockCache.dispose(); } } - Future> _loadFields(GridPB grid) async { - final result = await _gridFFIService.getFields(fieldIds: grid.fields); - return Future( - () => result.fold( - (fields) { - fieldCache.fields = fields.items; - _onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields)); - return left(unit); - }, - (err) => right(err), - ), - ); - } - Future _loadGroups(List blocks) async { for (final block in blocks) { final cache = GridBlockCache( gridId: gridId, block: block, - fieldCache: fieldCache, + fieldController: fieldController, ); cache.addListener(onRowsChanged: (reason) { diff --git a/frontend/app_flowy/lib/plugins/board/application/board_listener.dart b/frontend/app_flowy/lib/plugins/board/application/board_listener.dart index a953a993cc..9f8662fc5b 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_listener.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_listener.dart @@ -5,20 +5,26 @@ 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.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart'; -typedef UpdateBoardNotifiedValue = Either; +typedef GroupUpdateValue = Either; +typedef GroupByNewFieldValue = Either, FlowyError>; class BoardListener { final String viewId; - PublishNotifier? _groupNotifier = PublishNotifier(); + PublishNotifier? _groupUpdateNotifier = PublishNotifier(); + PublishNotifier? _groupByNewFieldNotifier = + PublishNotifier(); GridNotificationListener? _listener; BoardListener(this.viewId); void start({ - required void Function(UpdateBoardNotifiedValue) onBoardChanged, + required void Function(GroupUpdateValue) onBoardChanged, + required void Function(GroupByNewFieldValue) onGroupByNewField, }) { - _groupNotifier?.addPublishListener(onBoardChanged); + _groupUpdateNotifier?.addPublishListener(onBoardChanged); + _groupByNewFieldNotifier?.addPublishListener(onGroupByNewField); _listener = GridNotificationListener( objectId: viewId, handler: _handler, @@ -32,9 +38,16 @@ class BoardListener { switch (ty) { case GridNotification.DidUpdateGroupView: result.fold( - (payload) => _groupNotifier?.value = + (payload) => _groupUpdateNotifier?.value = left(GroupViewChangesetPB.fromBuffer(payload)), - (error) => _groupNotifier?.value = right(error), + (error) => _groupUpdateNotifier?.value = right(error), + ); + break; + case GridNotification.DidGroupByNewField: + result.fold( + (payload) => _groupByNewFieldNotifier?.value = + left(GroupViewChangesetPB.fromBuffer(payload).newGroups), + (error) => _groupByNewFieldNotifier?.value = right(error), ); break; default: @@ -44,7 +57,10 @@ class BoardListener { Future stop() async { await _listener?.stop(); - _groupNotifier?.dispose(); - _groupNotifier = null; + _groupUpdateNotifier?.dispose(); + _groupUpdateNotifier = null; + + _groupByNewFieldNotifier?.dispose(); + _groupByNewFieldNotifier = null; } } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart index b1110f45cc..a19d7b64a8 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart @@ -1,6 +1,6 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -20,8 +20,6 @@ class BoardDateCellBloc extends Bloc { emit(state.copyWith( data: cellData, dateStr: _dateStrFromCellData(cellData))); }, - didReceiveFieldUpdate: (FieldPB value) => - emit(state.copyWith(field: value)), ); }, ); @@ -53,8 +51,6 @@ class BoardDateCellEvent with _$BoardDateCellEvent { const factory BoardDateCellEvent.initial() = _InitialCell; const factory BoardDateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate; - const factory BoardDateCellEvent.didReceiveFieldUpdate(FieldPB field) = - _DidReceiveFieldUpdate; } @freezed @@ -62,14 +58,14 @@ class BoardDateCellState with _$BoardDateCellState { const factory BoardDateCellState({ required DateCellDataPB? data, required String dateStr, - required FieldPB field, + required GridFieldContext fieldContext, }) = _BoardDateCellState; factory BoardDateCellState.initial(GridDateCellController context) { final cellData = context.getCellData(); return BoardDateCellState( - field: context.field, + fieldContext: context.fieldContext, data: cellData, dateStr: _dateStrFromCellData(cellData), ); 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 ad30d2b250..e4dccbf0df 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 @@ -59,7 +59,7 @@ class BoardCardBloc extends Bloc { return RowInfo( gridId: _rowService.gridId, fields: UnmodifiableListView( - state.cells.map((cell) => cell.identifier.field).toList(), + state.cells.map((cell) => cell.identifier.fieldContext).toList(), ), rowPB: state.rowPB, ); @@ -120,9 +120,9 @@ class BoardCellEquatable extends Equatable { @override List get props => [ - identifier.field.id, - identifier.field.fieldType, - identifier.field.visibility, - identifier.field.width, + identifier.fieldContext.id, + identifier.fieldContext.fieldType, + identifier.fieldContext.visibility, + identifier.fieldContext.width, ]; } 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 f362fdf0e6..0e7cf6c153 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 @@ -1,7 +1,7 @@ import 'package:app_flowy/plugins/board/presentation/card/card_cell_builder.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart'; -import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; 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'; @@ -10,15 +10,15 @@ typedef OnCardChanged = void Function(GridCellMap, RowsChangedReason); class CardDataController extends BoardCellBuilderDelegate { final RowPB rowPB; - final GridFieldCache _fieldCache; + final GridFieldController _fieldController; final GridRowCache _rowCache; final List _onCardChangedListeners = []; CardDataController({ required this.rowPB, - required GridFieldCache fieldCache, + required GridFieldController fieldController, required GridRowCache rowCache, - }) : _fieldCache = fieldCache, + }) : _fieldController = fieldController, _rowCache = rowCache; GridCellMap loadData() { @@ -41,7 +41,7 @@ class CardDataController extends BoardCellBuilderDelegate { @override GridCellFieldNotifier buildFieldNotifier() { return GridCellFieldNotifier( - notifier: GridCellFieldNotifierImpl(_fieldCache)); + notifier: GridCellFieldNotifierImpl(_fieldController)); } @override diff --git a/frontend/app_flowy/lib/plugins/board/application/toolbar/board_setting_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/toolbar/board_setting_bloc.dart index 480b3a4768..97185c5efe 100644 --- a/frontend/app_flowy/lib/plugins/board/application/toolbar/board_setting_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/toolbar/board_setting_bloc.dart @@ -43,4 +43,5 @@ class BoardSettingState with _$BoardSettingState { enum BoardSettingAction { properties, + groups, } 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 0b043a2ba7..d6c5dca609 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -5,7 +5,7 @@ import 'dart:collection'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; -import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart'; @@ -82,8 +82,7 @@ class _BoardContentState extends State { return BlocListener( listener: (context, state) => _handleEditState(state, context), child: BlocBuilder( - buildWhen: (previous, current) => - previous.groupIds.length != current.groupIds.length, + buildWhen: (previous, current) => previous.groupIds != current.groupIds, builder: (context, state) { final theme = context.read(); return Container( @@ -95,6 +94,7 @@ class _BoardContentState extends State { const _ToolbarBlocAdaptor(), Expanded( child: AFBoard( + key: UniqueKey(), scrollManager: scrollManager, scrollController: scrollController, dataController: context.read().boardController, @@ -222,10 +222,10 @@ class _BoardContentState extends State { /// Return placeholder widget if the rowCache is null. if (rowCache == null) return SizedBox(key: ObjectKey(columnItem)); - final fieldCache = context.read().fieldCache; + final fieldController = context.read().fieldController; final gridId = context.read().gridId; final cardController = CardDataController( - fieldCache: fieldCache, + fieldController: fieldController, rowCache: rowCache, rowPB: rowPB, ); @@ -252,7 +252,7 @@ class _BoardContentState extends State { dataController: cardController, openCard: (context) => _openCard( gridId, - fieldCache, + fieldController, rowPB, rowCache, context, @@ -271,17 +271,17 @@ class _BoardContentState extends State { ); } - void _openCard(String gridId, GridFieldCache fieldCache, RowPB rowPB, - GridRowCache rowCache, BuildContext context) { + void _openCard(String gridId, GridFieldController fieldController, + RowPB rowPB, GridRowCache rowCache, BuildContext context) { final rowInfo = RowInfo( gridId: gridId, - fields: UnmodifiableListView(fieldCache.fields), + fields: UnmodifiableListView(fieldController.fieldContexts), rowPB: rowPB, ); final dataController = GridRowDataController( rowInfo: rowInfo, - fieldCache: fieldCache, + fieldController: fieldController, rowCache: rowCache, ); @@ -302,7 +302,7 @@ class _ToolbarBlocAdaptor extends StatelessWidget { final bloc = context.read(); final toolbarContext = BoardToolbarContext( viewId: bloc.gridId, - fieldCache: bloc.fieldCache, + fieldController: bloc.fieldController, ); return BoardToolbar(toolbarContext: toolbarContext); diff --git a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart index 76ab265a90..86beac6830 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart @@ -1,7 +1,8 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/plugins/board/application/toolbar/board_setting_bloc.dart'; -import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart'; +import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_group.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; @@ -18,16 +19,16 @@ import 'board_toolbar.dart'; class BoardSettingContext { final String viewId; - final GridFieldCache fieldCache; + final GridFieldController fieldController; BoardSettingContext({ required this.viewId, - required this.fieldCache, + required this.fieldController, }); factory BoardSettingContext.from(BoardToolbarContext toolbarContext) => BoardSettingContext( viewId: toolbarContext.viewId, - fieldCache: toolbarContext.fieldCache, + fieldController: toolbarContext.fieldController, ); } @@ -92,7 +93,13 @@ class BoardSettingList extends StatelessWidget { case BoardSettingAction.properties: GridPropertyList( gridId: settingContext.viewId, - fieldCache: settingContext.fieldCache) + fieldController: settingContext.fieldController) + .show(context); + break; + case BoardSettingAction.groups: + GridGroupList( + viewId: settingContext.viewId, + fieldController: settingContext.fieldController) .show(context); break; } @@ -156,6 +163,8 @@ extension _GridSettingExtension on BoardSettingAction { switch (this) { case BoardSettingAction.properties: return 'grid/setting/properties'; + case BoardSettingAction.groups: + return 'grid/setting/group'; } } @@ -163,6 +172,8 @@ extension _GridSettingExtension on BoardSettingAction { switch (this) { case BoardSettingAction.properties: return LocaleKeys.grid_settings_Properties.tr(); + case BoardSettingAction.groups: + return LocaleKeys.grid_settings_group.tr(); } } } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart index fae27851a9..8bc4dcb0c7 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart @@ -1,4 +1,4 @@ -import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -9,11 +9,11 @@ import 'board_setting.dart'; class BoardToolbarContext { final String viewId; - final GridFieldCache fieldCache; + final GridFieldController fieldController; BoardToolbarContext({ required this.viewId, - required this.fieldCache, + required this.fieldController, }); } 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 b639700b5f..b39226f0be 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 @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; -import '../field/field_cache.dart'; +import '../field/field_controller.dart'; import '../row/row_cache.dart'; import 'block_listener.dart'; @@ -19,12 +19,12 @@ class GridBlockCache { GridBlockCache({ required this.gridId, required this.block, - required GridFieldCache fieldCache, + required GridFieldController fieldController, }) { _rowCache = GridRowCache( gridId: gridId, block: block, - notifier: GridRowFieldNotifierImpl(fieldCache), + notifier: GridRowFieldNotifierImpl(fieldController), ); _listener = GridBlockListener(blockId: block.id); 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/cell_controller.dart similarity index 94% rename from frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart rename to frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart index d716133d05..29fb521976 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/cell_controller.dart @@ -148,10 +148,10 @@ class IGridCellController extends Equatable { _cellDataLoader = cellDataLoader, _cellDataPersistence = cellDataPersistence, _fieldNotifier = fieldNotifier, - _fieldService = - FieldService(gridId: cellId.gridId, fieldId: cellId.field.id), - _cacheKey = - GridCellCacheKey(rowId: cellId.rowId, fieldId: cellId.field.id); + _fieldService = FieldService( + gridId: cellId.gridId, fieldId: cellId.fieldContext.id), + _cacheKey = GridCellCacheKey( + rowId: cellId.rowId, fieldId: cellId.fieldContext.id); IGridCellController clone() { return IGridCellController( @@ -166,11 +166,11 @@ class IGridCellController extends Equatable { String get rowId => cellId.rowId; - String get fieldId => cellId.field.id; + String get fieldId => cellId.fieldContext.id; - FieldPB get field => cellId.field; + GridFieldContext get fieldContext => cellId.fieldContext; - FieldType get fieldType => cellId.field.fieldType; + FieldType get fieldType => cellId.fieldContext.fieldType; VoidCallback? startListening( {required void Function(T?) onCellChanged, @@ -182,7 +182,8 @@ class IGridCellController extends Equatable { isListening = true; _cellDataNotifier = ValueNotifier(_cellsCache.get(_cacheKey)); - _cellListener = CellListener(rowId: cellId.rowId, fieldId: cellId.field.id); + _cellListener = + CellListener(rowId: cellId.rowId, fieldId: cellId.fieldContext.id); /// 1.Listen on user edit event and load the new cell data if needed. /// For example: @@ -308,14 +309,14 @@ class IGridCellController extends Equatable { @override List get props => - [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id]; + [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldContext.id]; } class GridCellFieldNotifierImpl extends IGridCellFieldNotifier { - final GridFieldCache _cache; - FieldChangesetCallback? _onChangesetFn; + final GridFieldController _cache; + OnChangeset? _onChangesetFn; - GridCellFieldNotifierImpl(GridFieldCache cache) : _cache = cache; + GridCellFieldNotifierImpl(GridFieldController cache) : _cache = cache; @override void onCellDispose() { diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart index 48e82cc906..b0fbee15cd 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart @@ -16,12 +16,12 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_listener.dart'; import 'package:app_flowy/plugins/grid/application/field/field_service.dart'; import 'dart:convert' show utf8; -import '../../field/field_cache.dart'; +import '../../field/field_controller.dart'; import '../../field/type_option/type_option_context.dart'; import 'cell_field_notifier.dart'; part 'cell_service.freezed.dart'; part 'cell_data_loader.dart'; -part 'context_builder.dart'; +part 'cell_controller.dart'; part 'cell_cache.dart'; part 'cell_data_persistence.dart'; @@ -60,17 +60,17 @@ class GridCellIdentifier with _$GridCellIdentifier { const factory GridCellIdentifier({ required String gridId, required String rowId, - required FieldPB field, + required GridFieldContext fieldContext, }) = _GridCellIdentifier; // ignore: unused_element const GridCellIdentifier._(); - String get fieldId => field.id; + String get fieldId => fieldContext.id; - FieldType get fieldType => field.fieldType; + FieldType get fieldType => fieldContext.fieldType; ValueKey key() { - return ValueKey("$rowId$fieldId${field.fieldType}"); + return ValueKey("$rowId$fieldId${fieldContext.fieldType}"); } } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart index a7124b7a3d..6fb8e7e5b0 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart @@ -176,7 +176,7 @@ class DateCalBloc extends Bloc { final result = await FieldService.updateFieldTypeOption( gridId: cellController.gridId, - fieldId: cellController.field.id, + fieldId: cellController.fieldContext.id, typeOptionData: newDateTypeOption.writeToBuffer(), ); diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart index 4d453eca25..e44e15a2fa 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart @@ -1,5 +1,5 @@ +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -20,8 +20,6 @@ class DateCellBloc extends Bloc { emit(state.copyWith( data: cellData, dateStr: _dateStrFromCellData(cellData))); }, - didReceiveFieldUpdate: (FieldPB value) => - emit(state.copyWith(field: value)), ); }, ); @@ -53,8 +51,6 @@ class DateCellEvent with _$DateCellEvent { const factory DateCellEvent.initial() = _InitialCell; const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate; - const factory DateCellEvent.didReceiveFieldUpdate(FieldPB field) = - _DidReceiveFieldUpdate; } @freezed @@ -62,14 +58,14 @@ class DateCellState with _$DateCellState { const factory DateCellState({ required DateCellDataPB? data, required String dateStr, - required FieldPB field, + required GridFieldContext fieldContext, }) = _DateCellState; factory DateCellState.initial(GridDateCellController context) { final cellData = context.getCellData(); return DateCellState( - field: context.field, + fieldContext: context.fieldContext, data: cellData, dateStr: _dateStrFromCellData(cellData), ); diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart index 44d4bdd4be..7d5be30eda 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart @@ -11,7 +11,7 @@ class SelectOptionService { SelectOptionService({required this.cellId}); String get gridId => cellId.gridId; - String get fieldId => cellId.field.id; + String get fieldId => cellId.fieldContext.id; String get rowId => cellId.rowId; Future> create({required String name}) { diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_cache.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_cache.dart deleted file mode 100644 index e521f097ee..0000000000 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_cache.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'dart:collection'; - -import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart'; -import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -import 'package:flutter/foundation.dart'; - -import '../row/row_cache.dart'; - -class FieldsNotifier extends ChangeNotifier { - List _fields = []; - - set fields(List fields) { - _fields = fields; - notifyListeners(); - } - - List get fields => _fields; -} - -typedef FieldChangesetCallback = void Function(FieldChangesetPB); -typedef FieldsCallback = void Function(List); - -class GridFieldCache { - final String gridId; - final GridFieldsListener _fieldListener; - FieldsNotifier? _fieldNotifier = FieldsNotifier(); - final Map _fieldsCallbackMap = {}; - final Map - _changesetCallbackMap = {}; - - GridFieldCache({required this.gridId}) - : _fieldListener = GridFieldsListener(gridId: gridId) { - _fieldListener.start(onFieldsChanged: (result) { - result.fold( - (changeset) { - _deleteFields(changeset.deletedFields); - _insertFields(changeset.insertedFields); - _updateFields(changeset.updatedFields); - for (final listener in _changesetCallbackMap.values) { - listener(changeset); - } - }, - (err) => Log.error(err), - ); - }); - } - - Future dispose() async { - await _fieldListener.stop(); - _fieldNotifier?.dispose(); - _fieldNotifier = null; - } - - UnmodifiableListView get unmodifiableFields => - UnmodifiableListView(_fieldNotifier?.fields ?? []); - - List get fields => [..._fieldNotifier?.fields ?? []]; - - set fields(List fields) { - _fieldNotifier?.fields = [...fields]; - } - - void addListener({ - FieldsCallback? onFields, - FieldChangesetCallback? onChangeset, - bool Function()? listenWhen, - }) { - if (onChangeset != null) { - fn(c) { - if (listenWhen != null && listenWhen() == false) { - return; - } - onChangeset(c); - } - - _changesetCallbackMap[onChangeset] = fn; - } - - if (onFields != null) { - fn() { - if (listenWhen != null && listenWhen() == false) { - return; - } - onFields(fields); - } - - _fieldsCallbackMap[onFields] = fn; - _fieldNotifier?.addListener(fn); - } - } - - void removeListener({ - FieldsCallback? onFieldsListener, - FieldChangesetCallback? onChangesetListener, - }) { - if (onFieldsListener != null) { - final fn = _fieldsCallbackMap.remove(onFieldsListener); - if (fn != null) { - _fieldNotifier?.removeListener(fn); - } - } - - if (onChangesetListener != null) { - _changesetCallbackMap.remove(onChangesetListener); - } - } - - void _deleteFields(List deletedFields) { - if (deletedFields.isEmpty) { - return; - } - final List newFields = fields; - final Map deletedFieldMap = { - for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder - }; - - newFields.retainWhere((field) => (deletedFieldMap[field.id] == null)); - _fieldNotifier?.fields = newFields; - } - - void _insertFields(List insertedFields) { - if (insertedFields.isEmpty) { - return; - } - final List newFields = fields; - for (final indexField in insertedFields) { - if (newFields.length > indexField.index) { - newFields.insert(indexField.index, indexField.field_1); - } else { - newFields.add(indexField.field_1); - } - } - _fieldNotifier?.fields = newFields; - } - - void _updateFields(List updatedFields) { - if (updatedFields.isEmpty) { - return; - } - final List newFields = fields; - for (final updatedField in updatedFields) { - final index = - newFields.indexWhere((field) => field.id == updatedField.id); - if (index != -1) { - newFields.removeAt(index); - newFields.insert(index, updatedField); - } - } - _fieldNotifier?.fields = newFields; - } -} - -class GridRowFieldNotifierImpl extends IGridRowFieldNotifier { - final GridFieldCache _cache; - FieldChangesetCallback? _onChangesetFn; - FieldsCallback? _onFieldFn; - GridRowFieldNotifierImpl(GridFieldCache cache) : _cache = cache; - - @override - UnmodifiableListView get fields => _cache.unmodifiableFields; - - @override - void onRowFieldsChanged(VoidCallback callback) { - _onFieldFn = (_) => callback(); - _cache.addListener(onFields: _onFieldFn); - } - - @override - void onRowFieldChanged(void Function(FieldPB) callback) { - _onChangesetFn = (FieldChangesetPB changeset) { - for (final updatedField in changeset.updatedFields) { - callback(updatedField); - } - }; - - _cache.addListener(onChangeset: _onChangesetFn); - } - - @override - void onRowDispose() { - if (_onFieldFn != null) { - _cache.removeListener(onFieldsListener: _onFieldFn!); - _onFieldFn = null; - } - - if (_onChangesetFn != null) { - _cache.removeListener(onChangesetListener: _onChangesetFn!); - _onChangesetFn = null; - } - } -} diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart new file mode 100644 index 0000000000..e9bda8eef5 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart @@ -0,0 +1,281 @@ +import 'dart:collection'; +import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart'; +import 'package:app_flowy/plugins/grid/application/grid_service.dart'; +import 'package:app_flowy/plugins/grid/application/setting/setting_listener.dart'; +import 'package:app_flowy/plugins/grid/application/setting/setting_service.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart'; +import 'package:flutter/foundation.dart'; +import '../row/row_cache.dart'; + +class _GridFieldNotifier extends ChangeNotifier { + List _fieldContexts = []; + + set fieldContexts(List fieldContexts) { + _fieldContexts = fieldContexts; + notifyListeners(); + } + + void notify() { + notifyListeners(); + } + + List get fieldContexts => _fieldContexts; +} + +typedef OnChangeset = void Function(FieldChangesetPB); +typedef OnReceiveFields = void Function(List); + +class GridFieldController { + final String gridId; + final GridFieldsListener _fieldListener; + final SettingListener _settingListener; + final Map _fieldCallbackMap = {}; + final Map _changesetCallbackMap = {}; + + _GridFieldNotifier? _fieldNotifier = _GridFieldNotifier(); + List _groupFieldIds = []; + final GridFFIService _gridFFIService; + final SettingFFIService _settingFFIService; + + List get fieldContexts => + [..._fieldNotifier?.fieldContexts ?? []]; + + GridFieldController({required this.gridId}) + : _fieldListener = GridFieldsListener(gridId: gridId), + _gridFFIService = GridFFIService(gridId: gridId), + _settingFFIService = SettingFFIService(viewId: gridId), + _settingListener = SettingListener(gridId: gridId) { + //Listen on field's changes + _fieldListener.start(onFieldsChanged: (result) { + result.fold( + (changeset) { + _deleteFields(changeset.deletedFields); + _insertFields(changeset.insertedFields); + _updateFields(changeset.updatedFields); + for (final listener in _changesetCallbackMap.values) { + listener(changeset); + } + }, + (err) => Log.error(err), + ); + }); + + //Listen on setting changes + _settingListener.start(onSettingUpdated: (result) { + result.fold( + (setting) => _updateFieldsWhenSettingChanged(setting), + (r) => Log.error(r), + ); + }); + + _settingFFIService.getSetting().then((result) { + result.fold( + (setting) => _updateFieldsWhenSettingChanged(setting), + (err) => Log.error(err), + ); + }); + } + + void _updateFieldsWhenSettingChanged(GridSettingPB setting) { + _groupFieldIds = setting.groupConfigurations.items + .map((item) => item.groupFieldId) + .toList(); + + _updateFieldContexts(); + } + + void _updateFieldContexts() { + if (_fieldNotifier != null) { + for (var field in _fieldNotifier!.fieldContexts) { + if (_groupFieldIds.contains(field.id)) { + field._isGroupField = true; + } else { + field._isGroupField = false; + } + } + _fieldNotifier?.notify(); + } + } + + Future dispose() async { + await _fieldListener.stop(); + _fieldNotifier?.dispose(); + _fieldNotifier = null; + } + + Future> loadFields( + {required List fieldIds}) async { + final result = await _gridFFIService.getFields(fieldIds: fieldIds); + return Future( + () => result.fold( + (newFields) { + _fieldNotifier?.fieldContexts = newFields.items + .map((field) => GridFieldContext(field: field)) + .toList(); + _updateFieldContexts(); + return left(unit); + }, + (err) => right(err), + ), + ); + } + + void addListener({ + OnReceiveFields? onFields, + OnChangeset? onChangeset, + bool Function()? listenWhen, + }) { + if (onChangeset != null) { + callback(c) { + if (listenWhen != null && listenWhen() == false) { + return; + } + onChangeset(c); + } + + _changesetCallbackMap[onChangeset] = callback; + } + + if (onFields != null) { + callback() { + if (listenWhen != null && listenWhen() == false) { + return; + } + onFields(fieldContexts); + } + + _fieldCallbackMap[onFields] = callback; + _fieldNotifier?.addListener(callback); + } + } + + void removeListener({ + OnReceiveFields? onFieldsListener, + OnChangeset? onChangesetListener, + }) { + if (onFieldsListener != null) { + final callback = _fieldCallbackMap.remove(onFieldsListener); + if (callback != null) { + _fieldNotifier?.removeListener(callback); + } + } + + if (onChangesetListener != null) { + _changesetCallbackMap.remove(onChangesetListener); + } + } + + void _deleteFields(List deletedFields) { + if (deletedFields.isEmpty) { + return; + } + final List newFields = fieldContexts; + final Map deletedFieldMap = { + for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder + }; + + newFields.retainWhere((field) => (deletedFieldMap[field.id] == null)); + _fieldNotifier?.fieldContexts = newFields; + } + + void _insertFields(List insertedFields) { + if (insertedFields.isEmpty) { + return; + } + final List newFields = fieldContexts; + for (final indexField in insertedFields) { + final gridField = GridFieldContext(field: indexField.field_1); + if (newFields.length > indexField.index) { + newFields.insert(indexField.index, gridField); + } else { + newFields.add(gridField); + } + } + _fieldNotifier?.fieldContexts = newFields; + } + + void _updateFields(List updatedFields) { + if (updatedFields.isEmpty) { + return; + } + final List newFields = fieldContexts; + for (final updatedField in updatedFields) { + final index = + newFields.indexWhere((field) => field.id == updatedField.id); + if (index != -1) { + newFields.removeAt(index); + final gridField = GridFieldContext(field: updatedField); + newFields.insert(index, gridField); + } + } + _fieldNotifier?.fieldContexts = newFields; + } +} + +class GridRowFieldNotifierImpl extends IGridRowFieldNotifier { + final GridFieldController _cache; + OnChangeset? _onChangesetFn; + OnReceiveFields? _onFieldFn; + GridRowFieldNotifierImpl(GridFieldController cache) : _cache = cache; + + @override + UnmodifiableListView get fields => + UnmodifiableListView(_cache.fieldContexts); + + @override + void onRowFieldsChanged(VoidCallback callback) { + _onFieldFn = (_) => callback(); + _cache.addListener(onFields: _onFieldFn); + } + + @override + void onRowFieldChanged(void Function(FieldPB) callback) { + _onChangesetFn = (FieldChangesetPB changeset) { + for (final updatedField in changeset.updatedFields) { + callback(updatedField); + } + }; + + _cache.addListener(onChangeset: _onChangesetFn); + } + + @override + void onRowDispose() { + if (_onFieldFn != null) { + _cache.removeListener(onFieldsListener: _onFieldFn!); + _onFieldFn = null; + } + + if (_onChangesetFn != null) { + _cache.removeListener(onChangesetListener: _onChangesetFn!); + _onChangesetFn = null; + } + } +} + +class GridFieldContext { + final FieldPB _field; + bool _isGroupField = false; + + String get id => _field.id; + + FieldType get fieldType => _field.fieldType; + + bool get visibility => _field.visibility; + + double get width => _field.width.toDouble(); + + bool get isPrimary => _field.isPrimary; + + String get name => _field.name; + + FieldPB get field => _field; + + bool get isGroupField => _isGroupField; + + GridFieldContext({required FieldPB field}) : _field = field; +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart index 816f32fe16..28676d62c1 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart @@ -5,6 +5,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; + part 'field_service.freezed.dart'; /// FieldService consists of lots of event functions. We define the events in the backend(Rust), diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart index 66f4c35c20..65bd057ac5 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart @@ -1,3 +1,4 @@ +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:flowy_infra/notifier.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; @@ -17,12 +18,12 @@ class TypeOptionDataController { TypeOptionDataController({ required this.gridId, required this.loader, - FieldPB? field, + GridFieldContext? fieldContext, }) { - if (field != null) { + if (fieldContext != null) { _data = FieldTypeOptionDataPB.create() ..gridId = gridId - ..field_2 = field; + ..field_2 = fieldContext.field; } } 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 74f23a1b3e..0aceab8ccf 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart @@ -7,6 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'block/block_cache.dart'; +import 'field/field_controller.dart'; import 'grid_data_controller.dart'; import 'row/row_cache.dart'; import 'dart:collection'; @@ -101,7 +102,7 @@ class GridEvent with _$GridEvent { RowsChangedReason listState, ) = _DidReceiveRowUpdate; const factory GridEvent.didReceiveFieldUpdate( - UnmodifiableListView fields, + UnmodifiableListView fields, ) = _DidReceiveFieldUpdate; const factory GridEvent.didReceiveGridUpdate( @@ -138,9 +139,9 @@ class GridLoadingState with _$GridLoadingState { } class GridFieldEquatable extends Equatable { - final UnmodifiableListView _fields; + final UnmodifiableListView _fields; const GridFieldEquatable( - UnmodifiableListView fields, + UnmodifiableListView fields, ) : _fields = fields; @override @@ -157,5 +158,6 @@ class GridFieldEquatable extends Equatable { ]; } - UnmodifiableListView get value => UnmodifiableListView(_fields); + UnmodifiableListView get value => + UnmodifiableListView(_fields); } 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 55733b9b7b..a8e1a27c39 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 @@ -4,16 +4,15 @@ 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 'package:flowy_sdk/protobuf/flowy-grid/block_entities.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 'dart:async'; import 'package:dartz/dartz.dart'; import 'block/block_cache.dart'; -import 'field/field_cache.dart'; +import 'field/field_controller.dart'; import 'prelude.dart'; import 'row/row_cache.dart'; -typedef OnFieldsChanged = void Function(UnmodifiableListView); +typedef OnFieldsChanged = void Function(UnmodifiableListView); typedef OnGridChanged = void Function(GridPB); typedef OnRowsChanged = void Function( @@ -25,7 +24,7 @@ typedef ListenOnRowChangedCondition = bool Function(); class GridDataController { final String gridId; final GridFFIService _gridFFIService; - final GridFieldCache fieldCache; + final GridFieldController fieldController; // key: the block id final LinkedHashMap _blocks; @@ -49,7 +48,7 @@ class GridDataController { // ignore: prefer_collection_literals _blocks = LinkedHashMap(), _gridFFIService = GridFFIService(gridId: view.id), - fieldCache = GridFieldCache(gridId: view.id); + fieldController = GridFieldController(gridId: view.id); void addListener({ required OnGridChanged onGridChanged, @@ -60,7 +59,7 @@ class GridDataController { _onRowChanged = onRowsChanged; _onFieldsChanged = onFieldsChanged; - fieldCache.addListener(onFields: (fields) { + fieldController.addListener(onFields: (fields) { _onFieldsChanged?.call(UnmodifiableListView(fields)); }); } @@ -72,7 +71,7 @@ class GridDataController { (grid) async { _initialBlocks(grid.blocks); _onGridChanged?.call(grid); - return await _loadFields(grid); + return await fieldController.loadFields(fieldIds: grid.fields); }, (err) => right(err), ), @@ -85,7 +84,7 @@ class GridDataController { Future dispose() async { await _gridFFIService.closeGrid(); - await fieldCache.dispose(); + await fieldController.dispose(); for (final blockCache in _blocks.values) { blockCache.dispose(); @@ -102,7 +101,7 @@ class GridDataController { final cache = GridBlockCache( gridId: gridId, block: block, - fieldCache: fieldCache, + fieldController: fieldController, ); cache.addListener( @@ -114,18 +113,4 @@ class GridDataController { _blocks[block.id] = cache; } } - - Future> _loadFields(GridPB grid) async { - final result = await _gridFFIService.getFields(fieldIds: grid.fields); - return Future( - () => result.fold( - (fields) { - fieldCache.fields = fields.items; - _onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields)); - return left(unit); - }, - (err) => right(err), - ), - ); - } } diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart index 125bd8d652..c6c9b1fa5b 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart @@ -4,19 +4,18 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; - -import 'field/field_cache.dart'; +import 'field/field_controller.dart'; part 'grid_header_bloc.freezed.dart'; class GridHeaderBloc extends Bloc { - final GridFieldCache fieldCache; + final GridFieldController fieldController; final String gridId; GridHeaderBloc({ required this.gridId, - required this.fieldCache, - }) : super(GridHeaderState.initial(fieldCache.fields)) { + required this.fieldController, + }) : super(GridHeaderState.initial(fieldController.fieldContexts)) { on( (event, emit) async { await event.map( @@ -36,7 +35,7 @@ class GridHeaderBloc extends Bloc { Future _moveField( _MoveField value, Emitter emit) async { - final fields = List.from(state.fields); + final fields = List.from(state.fields); fields.insert(value.toIndex, fields.removeAt(value.fromIndex)); emit(state.copyWith(fields: fields)); @@ -49,7 +48,7 @@ class GridHeaderBloc extends Bloc { } Future _startListening() async { - fieldCache.addListener( + fieldController.addListener( onFields: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)), listenWhen: () => !isClosed, ); @@ -64,18 +63,18 @@ class GridHeaderBloc extends Bloc { @freezed class GridHeaderEvent with _$GridHeaderEvent { const factory GridHeaderEvent.initial() = _InitialHeader; - const factory GridHeaderEvent.didReceiveFieldUpdate(List fields) = - _DidReceiveFieldUpdate; + const factory GridHeaderEvent.didReceiveFieldUpdate( + List fields) = _DidReceiveFieldUpdate; const factory GridHeaderEvent.moveField( FieldPB field, int fromIndex, int toIndex) = _MoveField; } @freezed class GridHeaderState with _$GridHeaderState { - const factory GridHeaderState({required List fields}) = + const factory GridHeaderState({required List fields}) = _GridHeaderState; - factory GridHeaderState.initial(List fields) { + factory GridHeaderState.initial(List fields) { // final List newFields = List.from(fields); // newFields.retainWhere((field) => field.visibility); return GridHeaderState(fields: fields); 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 bea25d8008..5e14c2e445 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 @@ -1,7 +1,7 @@ import 'dart:collection'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:equatable/equatable.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -35,7 +35,7 @@ class RowBloc extends Bloc { }, didReceiveCells: (_DidReceiveCells value) async { final cells = value.gridCellMap.values - .map((e) => GridCellEquatable(e.field)) + .map((e) => GridCellEquatable(e.fieldContext)) .toList(); emit(state.copyWith( gridCellMap: value.gridCellMap, @@ -87,21 +87,23 @@ class RowState with _$RowState { rowInfo: rowInfo, gridCellMap: cellDataMap, cells: UnmodifiableListView( - cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(), + cellDataMap.values + .map((e) => GridCellEquatable(e.fieldContext)) + .toList(), ), ); } class GridCellEquatable extends Equatable { - final FieldPB _field; + final GridFieldContext _fieldContext; - const GridCellEquatable(FieldPB field) : _field = field; + const GridCellEquatable(GridFieldContext field) : _fieldContext = field; @override List get props => [ - _field.id, - _field.fieldType, - _field.visibility, - _field.width, + _fieldContext.id, + _fieldContext.fieldType, + _fieldContext.visibility, + _fieldContext.width, ]; } 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 618d73cbc1..6e25742974 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 @@ -1,5 +1,6 @@ import 'dart:collection'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; @@ -12,7 +13,7 @@ part 'row_cache.freezed.dart'; typedef RowUpdateCallback = void Function(); abstract class IGridRowFieldNotifier { - UnmodifiableListView get fields; + UnmodifiableListView get fields; void onRowFieldsChanged(VoidCallback callback); void onRowFieldChanged(void Function(FieldPB) callback); void onRowDispose(); @@ -217,7 +218,7 @@ class GridRowCache { cellDataMap[field.id] = GridCellIdentifier( rowId: rowId, gridId: gridId, - field: field, + fieldContext: field, ); } } @@ -284,7 +285,7 @@ class _RowChangesetNotifier extends ChangeNotifier { class RowInfo with _$RowInfo { const factory RowInfo({ required String gridId, - required UnmodifiableListView fields, + required UnmodifiableListView fields, required RowPB rowPB, }) = _RowInfo; } 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 b4618b397a..e5fc6161bb 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 @@ -2,7 +2,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_ import 'package:flutter/material.dart'; import '../../presentation/widgets/cell/cell_builder.dart'; import '../cell/cell_service/cell_service.dart'; -import '../field/field_cache.dart'; +import '../field/field_controller.dart'; import 'row_cache.dart'; typedef OnRowChanged = void Function(GridCellMap, RowsChangedReason); @@ -10,14 +10,14 @@ typedef OnRowChanged = void Function(GridCellMap, RowsChangedReason); class GridRowDataController extends GridCellBuilderDelegate { final RowInfo rowInfo; final List _onRowChangedListeners = []; - final GridFieldCache _fieldCache; + final GridFieldController _fieldController; final GridRowCache _rowCache; GridRowDataController({ required this.rowInfo, - required GridFieldCache fieldCache, + required GridFieldController fieldController, required GridRowCache rowCache, - }) : _fieldCache = fieldCache, + }) : _fieldController = fieldController, _rowCache = rowCache; GridCellMap loadData() { @@ -41,7 +41,7 @@ class GridRowDataController extends GridCellBuilderDelegate { @override GridCellFieldNotifier buildFieldNotifier() { return GridCellFieldNotifier( - notifier: GridCellFieldNotifierImpl(_fieldCache)); + notifier: GridCellFieldNotifierImpl(_fieldController)); } @override diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/group_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/group_bloc.dart new file mode 100644 index 0000000000..8b58d5ea0c --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/group_bloc.dart @@ -0,0 +1,84 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; + +import '../field/field_controller.dart'; +import 'setting_service.dart'; + +part 'group_bloc.freezed.dart'; + +class GridGroupBloc extends Bloc { + final GridFieldController _fieldController; + final SettingFFIService _settingFFIService; + Function(List)? _onFieldsFn; + + GridGroupBloc( + {required String viewId, required GridFieldController fieldController}) + : _fieldController = fieldController, + _settingFFIService = SettingFFIService(viewId: viewId), + super(GridGroupState.initial(viewId, fieldController.fieldContexts)) { + on( + (event, emit) async { + event.when( + initial: () { + _startListening(); + }, + didReceiveFieldUpdate: (fieldContexts) { + emit(state.copyWith(fieldContexts: fieldContexts)); + }, + setGroupByField: (String fieldId, FieldType fieldType) { + _settingFFIService.groupByField( + fieldId: fieldId, + fieldType: fieldType, + ); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_onFieldsFn != null) { + _fieldController.removeListener(onFieldsListener: _onFieldsFn!); + _onFieldsFn = null; + } + return super.close(); + } + + void _startListening() { + _onFieldsFn = (fieldContexts) => + add(GridGroupEvent.didReceiveFieldUpdate(fieldContexts)); + _fieldController.addListener( + onFields: _onFieldsFn, + listenWhen: () => !isClosed, + ); + } +} + +@freezed +class GridGroupEvent with _$GridGroupEvent { + const factory GridGroupEvent.initial() = _Initial; + const factory GridGroupEvent.setGroupByField( + String fieldId, + FieldType fieldType, + ) = _GroupByField; + const factory GridGroupEvent.didReceiveFieldUpdate( + List fields) = _DidReceiveFieldUpdate; +} + +@freezed +class GridGroupState with _$GridGroupState { + const factory GridGroupState({ + required String gridId, + required List fieldContexts, + }) = _GridGroupState; + + factory GridGroupState.initial( + String gridId, List fieldContexts) => + GridGroupState( + gridId: gridId, + fieldContexts: fieldContexts, + ); +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/property_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/property_bloc.dart index fc0c2b4b12..c54f16ae12 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/setting/property_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/property_bloc.dart @@ -1,21 +1,22 @@ import 'package:app_flowy/plugins/grid/application/field/field_service.dart'; import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; -import '../field/field_cache.dart'; +import '../field/field_controller.dart'; part 'property_bloc.freezed.dart'; class GridPropertyBloc extends Bloc { - final GridFieldCache _fieldCache; - Function(List)? _onFieldsFn; + final GridFieldController _fieldController; + Function(List)? _onFieldsFn; - GridPropertyBloc({required String gridId, required GridFieldCache fieldCache}) - : _fieldCache = fieldCache, - super(GridPropertyState.initial(gridId, fieldCache.fields)) { + GridPropertyBloc( + {required String gridId, required GridFieldController fieldController}) + : _fieldController = fieldController, + super( + GridPropertyState.initial(gridId, fieldController.fieldContexts)) { on( (event, emit) async { await event.map( @@ -33,7 +34,7 @@ class GridPropertyBloc extends Bloc { ); }, didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { - emit(state.copyWith(fields: value.fields)); + emit(state.copyWith(fieldContexts: value.fields)); }, moveField: (_MoveField value) { // @@ -46,7 +47,7 @@ class GridPropertyBloc extends Bloc { @override Future close() async { if (_onFieldsFn != null) { - _fieldCache.removeListener(onFieldsListener: _onFieldsFn!); + _fieldController.removeListener(onFieldsListener: _onFieldsFn!); _onFieldsFn = null; } return super.close(); @@ -55,7 +56,7 @@ class GridPropertyBloc extends Bloc { void _startListening() { _onFieldsFn = (fields) => add(GridPropertyEvent.didReceiveFieldUpdate(fields)); - _fieldCache.addListener( + _fieldController.addListener( onFields: _onFieldsFn, listenWhen: () => !isClosed, ); @@ -67,8 +68,8 @@ class GridPropertyEvent with _$GridPropertyEvent { const factory GridPropertyEvent.initial() = _Initial; const factory GridPropertyEvent.setFieldVisibility( String fieldId, bool visibility) = _SetFieldVisibility; - const factory GridPropertyEvent.didReceiveFieldUpdate(List fields) = - _DidReceiveFieldUpdate; + const factory GridPropertyEvent.didReceiveFieldUpdate( + List fields) = _DidReceiveFieldUpdate; const factory GridPropertyEvent.moveField(int fromIndex, int toIndex) = _MoveField; } @@ -77,12 +78,15 @@ class GridPropertyEvent with _$GridPropertyEvent { class GridPropertyState with _$GridPropertyState { const factory GridPropertyState({ required String gridId, - required List fields, + required List fieldContexts, }) = _GridPropertyState; - factory GridPropertyState.initial(String gridId, List fields) => + factory GridPropertyState.initial( + String gridId, + List fieldContexts, + ) => GridPropertyState( gridId: gridId, - fields: fields, + fieldContexts: fieldContexts, ); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/setting_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_controller.dart new file mode 100644 index 0000000000..795adc4622 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_controller.dart @@ -0,0 +1,59 @@ +import 'package:app_flowy/plugins/grid/application/setting/setting_service.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart'; +import 'setting_listener.dart'; + +typedef OnError = void Function(FlowyError); +typedef OnSettingUpdated = void Function(GridSettingPB); + +class SettingController { + final String viewId; + final SettingFFIService _ffiService; + final SettingListener _listener; + OnSettingUpdated? _onSettingUpdated; + OnError? _onError; + GridSettingPB? _setting; + GridSettingPB? get setting => _setting; + + SettingController({ + required this.viewId, + }) : _ffiService = SettingFFIService(viewId: viewId), + _listener = SettingListener(gridId: viewId) { + // Load setting + _ffiService.getSetting().then((result) { + result.fold( + (newSetting) => updateSetting(newSetting), + (err) => _onError?.call(err), + ); + }); + + // Listen on the seting changes + _listener.start(onSettingUpdated: (result) { + result.fold( + (newSetting) => updateSetting(newSetting), + (err) => _onError?.call(err), + ); + }); + } + + void startListeing({ + required OnSettingUpdated onSettingUpdated, + required OnError onError, + }) { + assert(_onSettingUpdated == null, 'Should call once'); + assert(_onError == null, 'Should call once'); + _onSettingUpdated = onSettingUpdated; + _onError = onError; + } + + void updateSetting(GridSettingPB newSetting) { + _setting = newSetting; + _onSettingUpdated?.call(newSetting); + } + + void dispose() { + _onSettingUpdated = null; + _onError = null; + _listener.stop(); + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/setting_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_listener.dart new file mode 100644 index 0000000000..00de48ca78 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_listener.dart @@ -0,0 +1,47 @@ +import 'dart:typed_data'; + +import 'package:app_flowy/core/grid_notification.dart'; +import 'package:dartz/dartz.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.pbserver.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart'; + +typedef UpdateSettingNotifiedValue = Either; + +class SettingListener { + final String gridId; + GridNotificationListener? _listener; + PublishNotifier? _updateSettingNotifier = + PublishNotifier(); + + SettingListener({required this.gridId}); + + void start({ + required void Function(UpdateSettingNotifiedValue) onSettingUpdated, + }) { + _updateSettingNotifier?.addPublishListener(onSettingUpdated); + _listener = GridNotificationListener(objectId: gridId, handler: _handler); + } + + void _handler(GridNotification ty, Either result) { + switch (ty) { + case GridNotification.DidUpdateGridSetting: + result.fold( + (payload) => _updateSettingNotifier?.value = left( + GridSettingPB.fromBuffer(payload), + ), + (error) => _updateSettingNotifier?.value = right(error), + ); + break; + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _updateSettingNotifier?.dispose(); + _updateSettingNotifier = null; + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/setting_service.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_service.dart new file mode 100644 index 0000000000..b050809bb5 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_service.dart @@ -0,0 +1,32 @@ +import 'package:dartz/dartz.dart'; +import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.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/setting_entities.pb.dart'; + +class SettingFFIService { + final String viewId; + + const SettingFFIService({required this.viewId}); + + Future> getSetting() { + final payload = GridIdPB.create()..value = viewId; + return GridEventGetGridSetting(payload).send(); + } + + Future> groupByField({ + required String fieldId, + required FieldType fieldType, + }) { + final insertGroupPayload = InsertGroupPayloadPB.create() + ..fieldId = fieldId + ..fieldType = fieldType; + final payload = GridSettingChangesetPayloadPB.create() + ..gridId = viewId + ..insertGroup = insertGroupPayload; + + return GridEventUpdateGridSetting(payload).send(); + } +} 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 2c6c1e2180..be9d2fe4e3 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart @@ -1,4 +1,4 @@ -import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/grid_bloc.dart'; @@ -157,10 +157,11 @@ class _FlowyGridState extends State { } Widget _gridHeader(BuildContext context, String gridId) { - final fieldCache = context.read().dataController.fieldCache; + final fieldController = + context.read().dataController.fieldController; return GridHeaderSliverAdaptor( gridId: gridId, - fieldCache: fieldCache, + fieldController: fieldController, anchorScrollController: headerScrollController, ); } @@ -173,10 +174,11 @@ class _GridToolbarAdaptor extends StatelessWidget { Widget build(BuildContext context) { return BlocSelector( selector: (state) { - final fieldCache = context.read().dataController.fieldCache; + final fieldController = + context.read().dataController.fieldController; return GridToolbarContext( gridId: state.gridId, - fieldCache: fieldCache, + fieldController: fieldController, ); }, builder: (context, toolbarContext) { @@ -247,10 +249,11 @@ class _GridRowsState extends State<_GridRows> { /// Return placeholder widget if the rowCache is null. if (rowCache == null) return const SizedBox(); - final fieldCache = context.read().dataController.fieldCache; + final fieldController = + context.read().dataController.fieldController; final dataController = GridRowDataController( rowInfo: rowInfo, - fieldCache: fieldCache, + fieldController: fieldController, rowCache: rowCache, ); @@ -264,7 +267,7 @@ class _GridRowsState extends State<_GridRows> { _openRowDetailPage( context, rowInfo, - fieldCache, + fieldController, rowCache, cellBuilder, ); @@ -277,13 +280,13 @@ class _GridRowsState extends State<_GridRows> { void _openRowDetailPage( BuildContext context, RowInfo rowInfo, - GridFieldCache fieldCache, + GridFieldController fieldController, GridRowCache rowCache, GridCellBuilder cellBuilder, ) { final dataController = GridRowDataController( rowInfo: rowInfo, - fieldCache: fieldCache, + fieldController: fieldController, rowCache: rowCache, ); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/layout/layout.dart b/frontend/app_flowy/lib/plugins/grid/presentation/layout/layout.dart index e47b47a267..72b1eb643d 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/layout/layout.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/layout/layout.dart @@ -1,8 +1,8 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'sizes.dart'; class GridLayout { - static double headerWidth(List fields) { + static double headerWidth(List fields) { if (fields.isEmpty) return 0; final fieldsWidth = fields diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart index c5bdb448c3..05c1faec08 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart @@ -1,5 +1,5 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; @@ -18,11 +18,11 @@ import 'field_cell.dart'; class GridHeaderSliverAdaptor extends StatefulWidget { final String gridId; - final GridFieldCache fieldCache; + final GridFieldController fieldController; final ScrollController anchorScrollController; const GridHeaderSliverAdaptor({ required this.gridId, - required this.fieldCache, + required this.fieldController, required this.anchorScrollController, Key? key, }) : super(key: key); @@ -38,7 +38,7 @@ class _GridHeaderSliverAdaptorState extends State { return BlocProvider( create: (context) { final bloc = getIt( - param1: widget.gridId, param2: widget.fieldCache); + param1: widget.gridId, param2: widget.fieldController); bloc.add(const GridHeaderEvent.initial()); return bloc; }, @@ -84,7 +84,7 @@ class _GridHeaderState extends State<_GridHeader> { final cells = state.fields .where((field) => field.visibility) .map((field) => - GridFieldCellContext(gridId: widget.gridId, field: field)) + GridFieldCellContext(gridId: widget.gridId, field: field.field)) .map((ctx) => GridFieldCell(ctx, key: ValueKey(ctx.field.id))) .toList(); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart index 2ea19eb1e8..cfa2f9291f 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart'; @@ -129,17 +130,18 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({ TypeOptionContext makeTypeOptionContext({ required String gridId, - required FieldPB field, + required GridFieldContext fieldContext, }) { - final loader = FieldTypeOptionLoader(gridId: gridId, field: field); + final loader = + FieldTypeOptionLoader(gridId: gridId, field: fieldContext.field); final dataController = TypeOptionDataController( gridId: gridId, loader: loader, - field: field, + fieldContext: fieldContext, ); return makeTypeOptionContextWithDataController( gridId: gridId, - fieldType: field.fieldType, + fieldType: fieldContext.fieldType, dataController: dataController, ); } 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 55ec7b9832..621f53cf1a 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 @@ -189,13 +189,13 @@ class RowContent extends StatelessWidget { final GridCellWidget child = builder.build(cellId); return CellContainer( - width: cellId.field.width.toDouble(), + width: cellId.fieldContext.width.toDouble(), rowStateNotifier: Provider.of(context, listen: false), accessoryBuilder: (buildContext) { final builder = child.accessoryBuilder; List accessories = []; - if (cellId.field.isPrimary) { + if (cellId.fieldContext.isPrimary) { accessories.add(PrimaryCellAccessory( onTapCallback: onExpand, isCellEditing: buildContext.isCellEditing, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart index 58e9b2808c..266be35a36 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart @@ -215,7 +215,7 @@ class _RowDetailCell extends StatelessWidget { SizedBox( width: 150, child: FieldCellButton( - field: cellId.field, + field: cellId.fieldContext.field, onTap: () => _showFieldEditor(context), ), ), @@ -230,10 +230,10 @@ class _RowDetailCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { FieldEditor( gridId: cellId.gridId, - fieldName: cellId.field.name, + fieldName: cellId.fieldContext.name, typeOptionLoader: FieldTypeOptionLoader( gridId: cellId.gridId, - field: cellId.field, + field: cellId.fieldContext.field, ), ).show(context); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart new file mode 100644 index 0000000000..0a82eef4bb --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart @@ -0,0 +1,110 @@ +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; +import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart'; +import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; +import 'package:app_flowy/plugins/grid/application/setting/group_bloc.dart'; + +import 'package:flutter_bloc/flutter_bloc.dart'; + +class GridGroupList extends StatelessWidget { + final String viewId; + final GridFieldController fieldController; + const GridGroupList({ + required this.viewId, + required this.fieldController, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => GridGroupBloc( + viewId: viewId, + fieldController: fieldController, + )..add(const GridGroupEvent.initial()), + child: BlocBuilder( + builder: (context, state) { + final cells = state.fieldContexts.map((fieldContext) { + return _GridGroupCell( + fieldContext: fieldContext, + key: ValueKey(fieldContext.id), + ); + }).toList(); + + return ListView.separated( + shrinkWrap: true, + itemCount: cells.length, + itemBuilder: (BuildContext context, int index) { + return cells[index]; + }, + separatorBuilder: (BuildContext context, int index) { + return VSpace(GridSize.typeOptionSeparatorHeight); + }, + ); + }, + ), + ); + } + + void show(BuildContext context) { + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + constraints: BoxConstraints.loose(const Size(260, 400)), + child: this, + ), + identifier: identifier(), + anchorContext: context, + anchorDirection: AnchorDirection.bottomRight, + style: FlowyOverlayStyle(blur: false), + ); + } + + static String identifier() { + return (GridGroupList).toString(); + } +} + +class _GridGroupCell extends StatelessWidget { + final GridFieldContext fieldContext; + const _GridGroupCell({required this.fieldContext, Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = context.read(); + + Widget? rightIcon; + if (fieldContext.isGroupField) { + rightIcon = Padding( + padding: const EdgeInsets.all(2.0), + child: svgWidget("grid/checkmark"), + ); + } + + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(fieldContext.name, fontSize: 12), + hoverColor: theme.hover, + leftIcon: svgWidget(fieldContext.fieldType.iconName(), + color: theme.iconColor), + rightIcon: rightIcon, + onTap: () { + context.read().add( + GridGroupEvent.setGroupByField( + fieldContext.id, + fieldContext.fieldType, + ), + ); + FlowyOverlay.of(context).remove(GridGroupList.identifier()); + }, + ), + ); + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart index 08ba3dcd08..f5acbcba2a 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart @@ -9,21 +9,20 @@ import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; -import '../../../application/field/field_cache.dart'; +import '../../../application/field/field_controller.dart'; import '../../layout/sizes.dart'; import '../header/field_editor.dart'; class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate { final String gridId; - final GridFieldCache fieldCache; + final GridFieldController fieldController; const GridPropertyList({ required this.gridId, - required this.fieldCache, + required this.fieldController, Key? key, }) : super(key: key); @@ -45,13 +44,13 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate { Widget build(BuildContext context) { return BlocProvider( create: (context) => - getIt(param1: gridId, param2: fieldCache) + getIt(param1: gridId, param2: fieldController) ..add(const GridPropertyEvent.initial()), child: BlocBuilder( builder: (context, state) { - final cells = state.fields.map((field) { + final cells = state.fieldContexts.map((field) { return _GridPropertyCell( - gridId: gridId, field: field, key: ValueKey(field.id)); + gridId: gridId, fieldContext: field, key: ValueKey(field.id)); }).toList(); return ListView.separated( @@ -78,16 +77,17 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate { } class _GridPropertyCell extends StatelessWidget { - final FieldPB field; + final GridFieldContext fieldContext; final String gridId; - const _GridPropertyCell({required this.gridId, required this.field, Key? key}) + const _GridPropertyCell( + {required this.gridId, required this.fieldContext, Key? key}) : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); - final checkmark = field.visibility + final checkmark = fieldContext.visibility ? svgWidget('home/show', color: theme.iconColor) : svgWidget('home/hide', color: theme.iconColor); @@ -105,7 +105,7 @@ class _GridPropertyCell extends StatelessWidget { onPressed: () { context.read().add( GridPropertyEvent.setFieldVisibility( - field.id, !field.visibility)); + fieldContext.id, !fieldContext.visibility)); }, icon: checkmark.padding(all: 6), ) @@ -115,14 +115,18 @@ class _GridPropertyCell extends StatelessWidget { FlowyButton _editFieldButton(AppTheme theme, BuildContext context) { return FlowyButton( - text: FlowyText.medium(field.name, fontSize: 12), + text: FlowyText.medium(fieldContext.name, fontSize: 12), hoverColor: theme.hover, - leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor), + leftIcon: + svgWidget(fieldContext.fieldType.iconName(), color: theme.iconColor), onTap: () { FieldEditor( gridId: gridId, - fieldName: field.name, - typeOptionLoader: FieldTypeOptionLoader(gridId: gridId, field: field), + fieldName: fieldContext.name, + typeOptionLoader: FieldTypeOptionLoader( + gridId: gridId, + field: fieldContext.field, + ), ).show(context, anchorDirection: AnchorDirection.bottomRight); }, ); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart index 289d84141f..c793d7eef1 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart @@ -11,17 +11,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; -import '../../../application/field/field_cache.dart'; +import '../../../application/field/field_controller.dart'; import '../../layout/sizes.dart'; import 'grid_property.dart'; class GridSettingContext { final String gridId; - final GridFieldCache fieldCache; + final GridFieldController fieldController; GridSettingContext({ required this.gridId, - required this.fieldCache, + required this.fieldController, }); } @@ -44,7 +44,7 @@ class GridSettingList extends StatelessWidget { case GridSettingAction.properties: GridPropertyList( gridId: settingContext.gridId, - fieldCache: settingContext.fieldCache) + fieldController: settingContext.fieldController) .show(context); break; } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart index 4ced06bf99..61ea099f56 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart @@ -5,16 +5,16 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../application/field/field_cache.dart'; +import '../../../application/field/field_controller.dart'; import '../../layout/sizes.dart'; import 'grid_setting.dart'; class GridToolbarContext { final String gridId; - final GridFieldCache fieldCache; + final GridFieldController fieldController; GridToolbarContext({ required this.gridId, - required this.fieldCache, + required this.fieldController, }); } @@ -26,7 +26,7 @@ class GridToolbar extends StatelessWidget { Widget build(BuildContext context) { final settingContext = GridSettingContext( gridId: toolbarContext.gridId, - fieldCache: toolbarContext.fieldCache, + fieldController: toolbarContext.fieldController, ); return SizedBox( height: 40, diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index be3c617943..cd3cc00ec6 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -21,7 +21,7 @@ import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get_it/get_it.dart'; -import '../plugins/grid/application/field/field_cache.dart'; +import '../plugins/grid/application/field/field_controller.dart'; class DependencyResolver { static Future resolve(GetIt getIt) async { @@ -154,10 +154,10 @@ void _resolveGridDeps(GetIt getIt) { (view, _) => GridBloc(view: view), ); - getIt.registerFactoryParam( - (gridId, fieldCache) => GridHeaderBloc( + getIt.registerFactoryParam( + (gridId, fieldController) => GridHeaderBloc( gridId: gridId, - fieldCache: fieldCache, + fieldController: fieldController, ), ); @@ -200,7 +200,7 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( - (gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache), + getIt.registerFactoryParam( + (gridId, cache) => GridPropertyBloc(gridId: gridId, fieldController: cache), ); } 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 75282d183c..47e74b37f5 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,6 +89,12 @@ class AFBoardDataController extends ChangeNotifier if (columnIds.isNotEmpty && notify) notifyListeners(); } + void clear() { + _columnDatas.clear(); + _columnControllers.clear(); + notifyListeners(); + } + AFBoardColumnDataController? getColumnController(String columnId) { final columnController = _columnControllers[columnId]; if (columnController == null) { diff --git a/frontend/rust-lib/flowy-grid/src/dart_notification.rs b/frontend/rust-lib/flowy-grid/src/dart_notification.rs index a0030c6773..8d7ec2ba36 100644 --- a/frontend/rust-lib/flowy-grid/src/dart_notification.rs +++ b/frontend/rust-lib/flowy-grid/src/dart_notification.rs @@ -13,6 +13,8 @@ pub enum GridNotification { DidUpdateField = 50, DidUpdateGroupView = 60, DidUpdateGroup = 61, + DidGroupByNewField = 62, + DidUpdateGridSetting = 70, } impl std::default::Default for GridNotification { diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs index 7dff00bf56..c6737e8e76 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs @@ -10,33 +10,33 @@ use std::convert::TryInto; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridFilterConfiguration { +pub struct GridFilterConfigurationPB { #[pb(index = 1)] pub id: String, } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct RepeatedGridConfigurationFilterPB { +pub struct RepeatedGridFilterConfigurationPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl std::convert::From<&FilterConfigurationRevision> for GridFilterConfiguration { +impl std::convert::From<&FilterConfigurationRevision> for GridFilterConfigurationPB { fn from(rev: &FilterConfigurationRevision) -> Self { Self { id: rev.id.clone() } } } -impl std::convert::From>> for RepeatedGridConfigurationFilterPB { +impl std::convert::From>> for RepeatedGridFilterConfigurationPB { fn from(revs: Vec>) -> Self { - RepeatedGridConfigurationFilterPB { + RepeatedGridFilterConfigurationPB { items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } } } -impl std::convert::From> for RepeatedGridConfigurationFilterPB { - fn from(items: Vec) -> Self { +impl std::convert::From> for RepeatedGridFilterConfigurationPB { + fn from(items: Vec) -> Self { Self { items } } } @@ -78,7 +78,7 @@ pub struct DeleteFilterParams { } #[derive(ProtoBuf, Debug, Default, Clone)] -pub struct CreateGridFilterPayloadPB { +pub struct InsertFilterPayloadPB { #[pb(index = 1)] pub field_id: String, @@ -92,7 +92,7 @@ pub struct CreateGridFilterPayloadPB { pub content: Option, } -impl CreateGridFilterPayloadPB { +impl InsertFilterPayloadPB { #[allow(dead_code)] pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self { Self { @@ -104,10 +104,10 @@ impl CreateGridFilterPayloadPB { } } -impl TryInto for CreateGridFilterPayloadPB { +impl TryInto for InsertFilterPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let field_id = NotEmptyStr::parse(self.field_id) .map_err(|_| ErrorCode::FieldIdIsEmpty)? .0; @@ -130,7 +130,7 @@ impl TryInto for CreateGridFilterPayloadPB { } } - Ok(CreateFilterParams { + Ok(InsertFilterParams { field_id, field_type_rev: self.field_type.into(), condition, @@ -139,7 +139,7 @@ impl TryInto for CreateGridFilterPayloadPB { } } -pub struct CreateFilterParams { +pub struct InsertFilterParams { pub field_id: String, pub field_type_rev: FieldTypeRevision, pub condition: u8, 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 002cb73c6d..de6f920341 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 @@ -91,6 +91,9 @@ pub struct GroupPB { #[pb(index = 5)] pub is_default: bool, + + #[pb(index = 6)] + pub is_visible: bool, } impl std::convert::From for GroupPB { @@ -101,6 +104,7 @@ impl std::convert::From for GroupPB { desc: group.name, rows: group.rows, is_default: group.is_default, + is_visible: group.is_visible, } } } @@ -126,7 +130,7 @@ impl std::convert::From>> for RepeatedGridGr } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct CreateGridGroupPayloadPB { +pub struct InsertGroupPayloadPB { #[pb(index = 1)] pub field_id: String, @@ -134,22 +138,22 @@ pub struct CreateGridGroupPayloadPB { pub field_type: FieldType, } -impl TryInto for CreateGridGroupPayloadPB { +impl TryInto for InsertGroupPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let field_id = NotEmptyStr::parse(self.field_id) .map_err(|_| ErrorCode::FieldIdIsEmpty)? .0; - Ok(CreatGroupParams { + Ok(InsertGroupParams { field_id, field_type_rev: self.field_type.into(), }) } } -pub struct CreatGroupParams { +pub struct InsertGroupParams { pub field_id: String, pub field_type_rev: FieldTypeRevision, } 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 21f39775f6..63b883d570 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 @@ -134,15 +134,21 @@ pub struct GroupViewChangesetPB { pub inserted_groups: Vec, #[pb(index = 3)] - pub deleted_groups: Vec, + pub new_groups: Vec, #[pb(index = 4)] + pub deleted_groups: Vec, + + #[pb(index = 5)] pub update_groups: Vec, } impl GroupViewChangesetPB { pub fn is_empty(&self) -> bool { - self.inserted_groups.is_empty() && self.deleted_groups.is_empty() && self.update_groups.is_empty() + self.new_groups.is_empty() + && self.inserted_groups.is_empty() + && self.deleted_groups.is_empty() + && self.update_groups.is_empty() } } diff --git a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs index 9c02a2c692..bbf5af831f 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs @@ -1,13 +1,12 @@ use crate::entities::{ - CreatGroupParams, CreateFilterParams, CreateGridFilterPayloadPB, CreateGridGroupPayloadPB, DeleteFilterParams, - DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, RepeatedGridConfigurationFilterPB, + DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, InsertFilterParams, + InsertFilterPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB, RepeatedGridGroupConfigurationPB, }; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::revision::LayoutRevision; -use std::collections::HashMap; use std::convert::TryInto; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -19,13 +18,13 @@ pub struct GridSettingPB { pub layouts: Vec, #[pb(index = 2)] - pub current_layout_type: GridLayout, + pub layout_type: GridLayout, #[pb(index = 3)] - pub filter_configuration_by_field_id: HashMap, + pub filter_configurations: RepeatedGridFilterConfigurationPB, #[pb(index = 4)] - pub group_configuration_by_field_id: HashMap, + pub group_configurations: RepeatedGridGroupConfigurationPB, } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] @@ -85,13 +84,13 @@ pub struct GridSettingChangesetPayloadPB { pub layout_type: GridLayout, #[pb(index = 3, one_of)] - pub insert_filter: Option, + pub insert_filter: Option, #[pb(index = 4, one_of)] pub delete_filter: Option, #[pb(index = 5, one_of)] - pub insert_group: Option, + pub insert_group: Option, #[pb(index = 6, one_of)] pub delete_group: Option, @@ -102,7 +101,7 @@ impl TryInto for GridSettingChangesetPayloadPB { fn try_into(self) -> Result { let view_id = NotEmptyStr::parse(self.grid_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .map_err(|_| ErrorCode::ViewIdInvalid)? .0; let insert_filter = match self.insert_filter { @@ -139,9 +138,9 @@ impl TryInto for GridSettingChangesetPayloadPB { pub struct GridSettingChangesetParams { pub grid_id: String, pub layout_type: LayoutRevision, - pub insert_filter: Option, + pub insert_filter: Option, pub delete_filter: Option, - pub insert_group: Option, + pub insert_group: Option, pub delete_group: Option, } diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 4b525c233f..c9b6cf4662 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -35,6 +35,32 @@ pub(crate) async fn get_grid_setting_handler( data_result(grid_setting) } +#[tracing::instrument(level = "trace", skip(data, manager), err)] +pub(crate) async fn update_grid_setting_handler( + data: Data, + manager: AppData>, +) -> Result<(), FlowyError> { + let params: GridSettingChangesetParams = data.into_inner().try_into()?; + + let editor = manager.get_grid_editor(¶ms.grid_id)?; + if let Some(insert_params) = params.insert_group { + let _ = editor.create_group(insert_params).await?; + } + + if let Some(delete_params) = params.delete_group { + let _ = editor.delete_group(delete_params).await?; + } + + if let Some(create_filter) = params.insert_filter { + let _ = editor.create_filter(create_filter).await?; + } + + if let Some(delete_filter) = params.delete_filter { + let _ = editor.delete_filter(delete_filter).await?; + } + Ok(()) +} + #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_grid_blocks_handler( data: Data, @@ -203,12 +229,14 @@ pub(crate) async fn move_field_handler( /// The FieldMeta contains multiple data, each of them belongs to a specific FieldType. async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType) -> FlowyResult> { - let s = field_rev - .get_type_option_str(field_type) - .unwrap_or_else(|| default_type_option_builder_from_type(field_type).entry().json_str()); + let s = field_rev.get_type_option_str(field_type).unwrap_or_else(|| { + default_type_option_builder_from_type(field_type) + .data_format() + .json_str() + }); let field_type: FieldType = field_rev.ty.into(); let builder = type_option_builder_from_json_str(&s, &field_type); - let type_option_data = builder.entry().protobuf_bytes().to_vec(); + let type_option_data = builder.data_format().protobuf_bytes().to_vec(); Ok(type_option_data) } @@ -337,7 +365,7 @@ pub(crate) async fn update_select_option_handler( type_option.delete_option(option); } - mut_field_rev.insert_type_option_entry(&*type_option); + mut_field_rev.insert_type_option(&*type_option); let _ = editor.replace_field(field_rev).await?; if let Some(cell_content_changeset) = cell_content_changeset { diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index a78bcb5ed3..e0a5ffcf1b 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -11,7 +11,7 @@ pub fn create(grid_manager: Arc) -> Module { .event(GridEvent::GetGrid, get_grid_handler) .event(GridEvent::GetGridBlocks, get_grid_blocks_handler) .event(GridEvent::GetGridSetting, get_grid_setting_handler) - // .event(GridEvent::UpdateGridSetting, update_grid_setting_handler) + .event(GridEvent::UpdateGridSetting, update_grid_setting_handler) // Field .event(GridEvent::GetFields, get_fields_handler) .event(GridEvent::UpdateField, update_field_handler) @@ -75,8 +75,8 @@ pub enum GridEvent { /// [UpdateGridSetting] event is used to update the grid's settings. /// - /// The event handler accepts [GridIdPB] and return errors if failed to modify the grid's settings. - #[event(input = "GridIdPB", input = "GridSettingChangesetPayloadPB")] + /// The event handler accepts [GridSettingChangesetPayloadPB] and return errors if failed to modify the grid's settings. + #[event(input = "GridSettingChangesetPayloadPB")] UpdateGridSetting = 3, /// [GetFields] event is used to get the grid's settings. @@ -225,4 +225,7 @@ pub enum GridEvent { #[event(input = "MoveGroupRowPayloadPB")] MoveGroupRow = 112, + + #[event(input = "MoveGroupRowPayloadPB")] + GroupByField = 113, } diff --git a/frontend/rust-lib/flowy-grid/src/macros.rs b/frontend/rust-lib/flowy-grid/src/macros.rs index aee8ca2b68..bee2e95e21 100644 --- a/frontend/rust-lib/flowy-grid/src/macros.rs +++ b/frontend/rust-lib/flowy-grid/src/macros.rs @@ -30,7 +30,7 @@ macro_rules! impl_type_option { ($target: ident, $field_type:expr) => { impl std::convert::From<&FieldRevision> for $target { fn from(field_rev: &FieldRevision) -> $target { - match field_rev.get_type_option_entry::<$target>($field_type.into()) { + match field_rev.get_type_option::<$target>($field_type.into()) { None => $target::default(), Some(target) => target, } @@ -39,7 +39,7 @@ macro_rules! impl_type_option { impl std::convert::From<&std::sync::Arc> for $target { fn from(field_rev: &std::sync::Arc) -> $target { - match field_rev.get_type_option_entry::<$target>($field_type.into()) { + match field_rev.get_type_option::<$target>($field_type.into()) { None => $target::default(), Some(target) => target, } @@ -52,7 +52,7 @@ macro_rules! impl_type_option { } } - impl TypeOptionDataEntry for $target { + impl TypeOptionDataFormat for $target { fn json_str(&self) -> String { match serde_json::to_string(&self) { Ok(s) => s, 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 ecff3b951b..17c52dd3a2 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 @@ -101,25 +101,25 @@ pub fn try_decode_cell_data( let field_type: FieldTypeRevision = t_field_type.into(); let data = match t_field_type { FieldType::RichText => field_rev - .get_type_option_entry::(field_type)? + .get_type_option::(field_type)? .decode_cell_data(cell_data.into(), s_field_type, field_rev), FieldType::Number => field_rev - .get_type_option_entry::(field_type)? + .get_type_option::(field_type)? .decode_cell_data(cell_data.into(), s_field_type, field_rev), FieldType::DateTime => field_rev - .get_type_option_entry::(field_type)? + .get_type_option::(field_type)? .decode_cell_data(cell_data.into(), s_field_type, field_rev), FieldType::SingleSelect => field_rev - .get_type_option_entry::(field_type)? + .get_type_option::(field_type)? .decode_cell_data(cell_data.into(), s_field_type, field_rev), FieldType::MultiSelect => field_rev - .get_type_option_entry::(field_type)? + .get_type_option::(field_type)? .decode_cell_data(cell_data.into(), s_field_type, field_rev), FieldType::Checkbox => field_rev - .get_type_option_entry::(field_type)? + .get_type_option::(field_type)? .decode_cell_data(cell_data.into(), s_field_type, field_rev), FieldType::URL => field_rev - .get_type_option_entry::(field_type)? + .get_type_option::(field_type)? .decode_cell_data(cell_data.into(), s_field_type, field_rev), }; Some(data) diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs index 0242a5ca43..4a1d57c28f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs @@ -1,7 +1,7 @@ use crate::entities::{FieldPB, FieldType}; use crate::services::field::type_options::*; use bytes::Bytes; -use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataFormat}; use indexmap::IndexMap; pub struct FieldBuilder { @@ -78,14 +78,14 @@ impl FieldBuilder { pub fn build(self) -> FieldRevision { let mut field_rev = self.field_rev; - field_rev.insert_type_option_entry(self.type_option_builder.entry()); + field_rev.insert_type_option(self.type_option_builder.data_format()); field_rev } } pub trait TypeOptionBuilder { fn field_type(&self) -> FieldType; - fn entry(&self) -> &dyn TypeOptionDataEntry; + fn data_format(&self) -> &dyn TypeOptionDataFormat; } pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs new file mode 100644 index 0000000000..3a2ce89c21 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs @@ -0,0 +1,45 @@ +use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; +use crate::services::grid_editor::GridRevisionEditor; +use flowy_error::FlowyResult; +use flowy_grid_data_model::revision::{TypeOptionDataDeserializer, TypeOptionDataFormat}; +use std::sync::Arc; + +pub async fn edit_field_type_option( + field_id: &str, + editor: Arc, + action: impl FnOnce(&mut T), +) -> FlowyResult<()> +where + T: TypeOptionDataDeserializer + TypeOptionDataFormat, +{ + let get_type_option = async { + let field_rev = editor.get_field_rev(field_id).await?; + field_rev.get_type_option::(field_rev.ty) + }; + + if let Some(mut type_option) = get_type_option.await { + action(&mut type_option); + let bytes = type_option.protobuf_bytes().to_vec(); + let _ = editor + .update_field_type_option(&editor.grid_id, field_id, bytes) + .await?; + } + + Ok(()) +} + +pub async fn edit_single_select_type_option( + field_id: &str, + editor: Arc, + action: impl FnOnce(&mut SingleSelectTypeOptionPB), +) -> FlowyResult<()> { + edit_field_type_option(field_id, editor, action).await +} + +pub async fn edit_multi_select_type_option( + field_id: &str, + editor: Arc, + action: impl FnOnce(&mut MultiSelectTypeOptionPB), +) -> FlowyResult<()> { + edit_field_type_option(field_id, editor, action).await +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/mod.rs index c1b689fbf4..5e8a461541 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/mod.rs @@ -1,5 +1,7 @@ mod field_builder; +mod field_operation; pub(crate) mod type_options; pub use field_builder::*; +pub use field_operation::*; pub use type_options::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs index cf668fcc1e..fbb1ba64e8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs @@ -5,7 +5,7 @@ use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionB use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -26,7 +26,7 @@ impl TypeOptionBuilder for CheckboxTypeOptionBuilder { FieldType::Checkbox } - fn entry(&self) -> &dyn TypeOptionDataEntry { + fn data_format(&self) -> &dyn TypeOptionDataFormat { &self.0 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs index 729ae1958a..e4ac22f299 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs @@ -9,7 +9,7 @@ use chrono::format::strftime::StrftimeItems; use chrono::{NaiveDateTime, Timelike}; use flowy_derive::ProtoBuf; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat}; use serde::{Deserialize, Serialize}; // Date @@ -189,7 +189,7 @@ impl TypeOptionBuilder for DateTypeOptionBuilder { FieldType::DateTime } - fn entry(&self) -> &dyn TypeOptionDataEntry { + fn data_format(&self) -> &dyn TypeOptionDataFormat { &self.0 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs index cdb1118385..fe4ddcf5fa 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs @@ -6,7 +6,7 @@ use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBui use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat}; use rust_decimal::Decimal; @@ -45,7 +45,7 @@ impl TypeOptionBuilder for NumberTypeOptionBuilder { FieldType::Number } - fn entry(&self) -> &dyn TypeOptionDataEntry { + fn data_format(&self) -> &dyn TypeOptionDataFormat { &self.0 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs index 75f654507d..967a65e60e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs @@ -9,7 +9,7 @@ use crate::services::field::{ use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat}; use serde::{Deserialize, Serialize}; // Multiple select @@ -108,7 +108,7 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder { FieldType::MultiSelect } - fn entry(&self) -> &dyn TypeOptionDataEntry { + fn data_format(&self) -> &dyn TypeOptionDataFormat { &self.0 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs index dcec7772a7..ff579f8172 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs @@ -5,7 +5,7 @@ use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::{internal_error, ErrorCode, FlowyResult}; use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataFormat}; use nanoid::nanoid; use serde::{Deserialize, Serialize}; @@ -75,7 +75,7 @@ pub fn make_selected_select_options( } } -pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync { +pub trait SelectOptionOperation: TypeOptionDataFormat + Send + Sync { fn insert_option(&mut self, new_option: SelectOptionPB) { let options = self.mut_options(); if let Some(index) = options diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs index 287d0c3217..23d3c02b69 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs @@ -9,7 +9,7 @@ use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat}; use serde::{Deserialize, Serialize}; // Single select @@ -91,7 +91,7 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder { FieldType::SingleSelect } - fn entry(&self) -> &dyn TypeOptionDataEntry { + fn data_format(&self) -> &dyn TypeOptionDataFormat { &self.0 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs index 6c50ce8da3..33c1f7eb9e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs @@ -8,7 +8,7 @@ use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -21,7 +21,7 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder { FieldType::RichText } - fn entry(&self) -> &dyn TypeOptionDataEntry { + fn data_format(&self) -> &dyn TypeOptionDataFormat { &self.0 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs index adbc91b4f0..d34dfc12d3 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs @@ -6,7 +6,7 @@ use bytes::Bytes; use fancy_regex::Regex; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataFormat}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -20,7 +20,7 @@ impl TypeOptionBuilder for URLTypeOptionBuilder { FieldType::URL } - fn entry(&self) -> &dyn TypeOptionDataEntry { + fn data_format(&self) -> &dyn TypeOptionDataFormat { &self.0 } } diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs index 8335a36fb2..173dc8ffea 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs @@ -188,7 +188,7 @@ fn filter_cell( FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| { Some( field_rev - .get_type_option_entry::(field_type_rev)? + .get_type_option::(field_type_rev)? .apply_filter(any_cell_data, filter.value()) .ok(), ) @@ -196,7 +196,7 @@ fn filter_cell( FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| { Some( field_rev - .get_type_option_entry::(field_type_rev)? + .get_type_option::(field_type_rev)? .apply_filter(any_cell_data, filter.value()) .ok(), ) @@ -204,7 +204,7 @@ fn filter_cell( FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| { Some( field_rev - .get_type_option_entry::(field_type_rev)? + .get_type_option::(field_type_rev)? .apply_filter(any_cell_data, filter.value()) .ok(), ) @@ -212,7 +212,7 @@ fn filter_cell( FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { Some( field_rev - .get_type_option_entry::(field_type_rev)? + .get_type_option::(field_type_rev)? .apply_filter(any_cell_data, filter.value()) .ok(), ) @@ -220,7 +220,7 @@ fn filter_cell( FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { Some( field_rev - .get_type_option_entry::(field_type_rev)? + .get_type_option::(field_type_rev)? .apply_filter(any_cell_data, filter.value()) .ok(), ) @@ -228,7 +228,7 @@ fn filter_cell( FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| { Some( field_rev - .get_type_option_entry::(field_type_rev)? + .get_type_option::(field_type_rev)? .apply_filter(any_cell_data, filter.value()) .ok(), ) @@ -236,7 +236,7 @@ fn filter_cell( FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| { Some( field_rev - .get_type_option_entry::(field_type_rev)? + .get_type_option::(field_type_rev)? .apply_filter(any_cell_data, filter.value()) .ok(), ) 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 9ae7918955..320d41cbff 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -179,6 +179,10 @@ impl GridRevisionEditor { None => Err(ErrorCode::FieldDoesNotExist.into()), Some(field_type) => { let _ = self.update_field_rev(params, field_type).await?; + match self.view_manager.did_update_field(&field_id).await { + Ok(_) => {} + Err(e) => tracing::error!("View manager update field failed: {:?}", e), + } let _ = self.notify_did_update_grid_field(&field_id).await?; Ok(()) } @@ -207,6 +211,11 @@ impl GridRevisionEditor { Ok(()) } + pub async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { + let _ = self.view_manager.group_by_field(field_id).await?; + Ok(()) + } + pub async fn switch_to_field_type(&self, field_id: &str, field_type: &FieldType) -> FlowyResult<()> { // let block_ids = self // .get_block_metas() @@ -221,7 +230,9 @@ impl GridRevisionEditor { let type_option_json_builder = |field_type: &FieldTypeRevision| -> String { let field_type: FieldType = field_type.into(); - return default_type_option_builder_from_type(&field_type).entry().json_str(); + return default_type_option_builder_from_type(&field_type) + .data_format() + .json_str(); }; let _ = self @@ -521,12 +532,20 @@ impl GridRevisionEditor { self.view_manager.get_setting().await } - pub async fn get_grid_filter(&self) -> FlowyResult> { + pub async fn get_grid_filter(&self) -> FlowyResult> { self.view_manager.get_filters().await } - pub async fn update_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { - let _ = self.view_manager.update_filter(params).await?; + pub async fn create_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + self.view_manager.insert_or_update_group(params).await + } + + pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + self.view_manager.delete_group(params).await + } + + pub async fn create_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { + let _ = self.view_manager.insert_or_update_filter(params).await?; Ok(()) } @@ -824,7 +843,7 @@ impl JsonDeserializer for TypeOptionJsonDeserializer { fn deserialize(&self, type_option_data: Vec) -> CollaborateResult { // The type_option_data sent from Dart is serialized by protobuf. let builder = type_option_builder_from_bytes(type_option_data, &self.0); - let json = builder.entry().json_str(); + let json = builder.data_format().json_str(); tracing::trace!("Deserialize type option data to: {}", json); Ok(json) } 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 688a844707..ab8e8bd41f 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,12 +1,16 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::entities::{ - CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridLayout, GridLayoutPB, - GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB, MoveGroupParams, - RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB, + CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridGroupConfigurationPB, + GridLayout, GridLayoutPB, GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertFilterParams, + InsertGroupParams, InsertedGroupPB, InsertedRowPB, MoveGroupParams, RepeatedGridFilterConfigurationPB, + RepeatedGridGroupConfigurationPB, RowPB, }; use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate}; -use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter, GroupService}; +use crate::services::group::{ + default_group_configuration, find_group_field, make_group_controller, GroupConfigurationReader, + GroupConfigurationWriter, GroupController, MoveGroupRowContext, +}; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{ gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision, @@ -16,9 +20,7 @@ use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilde use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad}; use flowy_sync::entities::revision::Revision; use lib_infra::future::{wrap_future, AFFuture, FutureResult}; -use std::collections::HashMap; - -use std::sync::atomic::{AtomicBool, Ordering}; +use std::future::Future; use std::sync::Arc; use tokio::sync::RwLock; @@ -30,11 +32,9 @@ pub struct GridViewRevisionEditor { rev_manager: Arc, field_delegate: Arc, row_delegate: Arc, - group_service: Arc>, + group_controller: Arc>>, scheduler: Arc, - did_load_group: AtomicBool, } - impl GridViewRevisionEditor { #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn new( @@ -52,16 +52,16 @@ impl GridViewRevisionEditor { let view_revision_pad = rev_manager.load::(Some(cloud)).await?; let pad = Arc::new(RwLock::new(view_revision_pad)); let rev_manager = Arc::new(rev_manager); - - let configuration_reader = GroupConfigurationReaderImpl(pad.clone()); - let configuration_writer = GroupConfigurationWriterImpl { - user_id: user_id.to_owned(), - rev_manager: rev_manager.clone(), - view_pad: pad.clone(), - }; - let group_service = GroupService::new(view_id.clone(), configuration_reader, configuration_writer).await; + let group_controller = new_group_controller( + user_id.to_owned(), + view_id.clone(), + pad.clone(), + rev_manager.clone(), + field_delegate.clone(), + row_delegate.clone(), + ) + .await?; let user_id = user_id.to_owned(); - let did_load_group = AtomicBool::new(false); Ok(Self { pad, user_id, @@ -70,24 +70,21 @@ impl GridViewRevisionEditor { scheduler, field_delegate, row_delegate, - group_service: Arc::new(RwLock::new(group_service)), - did_load_group, + group_controller: Arc::new(RwLock::new(group_controller)), }) } pub(crate) async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { - match params.group_id.as_ref() { - None => {} - Some(group_id) => { - self.group_service - .write() - .await - .will_create_row(row_rev, group_id, |field_id| { - self.field_delegate.get_field_rev(&field_id) - }) - .await; - } + if params.group_id.is_none() { + return; } + let group_id = params.group_id.as_ref().unwrap(); + let _ = self + .mut_group_controller(|group_controller, field_rev| { + group_controller.will_create_row(row_rev, &field_rev, group_id); + Ok(()) + }) + .await; } pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { @@ -112,13 +109,11 @@ impl GridViewRevisionEditor { pub(crate) async fn did_delete_row(&self, row_rev: &RowRevision) { // Send the group notification if the current view has groups; - if let Some(changesets) = self - .group_service - .write() - .await - .did_delete_row(row_rev, |field_id| self.field_delegate.get_field_rev(&field_id)) - .await - { + let changesets = self + .mut_group_controller(|group_controller, field_rev| group_controller.did_delete_row(row_rev, &field_rev)) + .await; + + if let Some(changesets) = changesets { for changeset in changesets { self.notify_did_update_group(changeset).await; } @@ -126,13 +121,11 @@ impl GridViewRevisionEditor { } pub(crate) async fn did_update_row(&self, row_rev: &RowRevision) { - if let Some(changesets) = self - .group_service - .write() - .await - .did_update_row(row_rev, |field_id| self.field_delegate.get_field_rev(&field_id)) - .await - { + let changesets = self + .mut_group_controller(|group_controller, field_rev| group_controller.did_update_row(row_rev, &field_rev)) + .await; + + if let Some(changesets) = changesets { for changeset in changesets { self.notify_did_update_group(changeset).await; } @@ -146,54 +139,38 @@ impl GridViewRevisionEditor { to_group_id: &str, to_row_id: Option, ) -> Vec { - match self - .group_service - .write() - .await - .move_group_row(row_rev, row_changeset, to_group_id, to_row_id, |field_id| { - self.field_delegate.get_field_rev(&field_id) + let changesets = self + .mut_group_controller(|group_controller, field_rev| { + let move_row_context = MoveGroupRowContext { + row_rev, + row_changeset, + field_rev: field_rev.as_ref(), + to_group_id, + to_row_id, + }; + + let changesets = group_controller.move_group_row(move_row_context)?; + Ok(changesets) }) - .await - { - None => vec![], - Some(changesets) => changesets, - } + .await; + + changesets.unwrap_or_default() } /// 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) { - self.did_load_group.store(true, Ordering::SeqCst); - let field_revs = self.field_delegate.get_field_revs().await; - let row_revs = self.row_delegate.gv_row_revs().await; - - match self - .group_service - .write() - .await - .load_groups(&field_revs, row_revs) - .await - { - None => vec![], - Some(groups) => groups, - } - } else { - self.group_service.read().await.groups().await - }; - + let groups = self.group_controller.read().await.groups(); tracing::trace!("Number of groups: {}", groups.len()); Ok(groups.into_iter().map(GroupPB::from).collect()) } pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { let _ = self - .group_service + .group_controller .write() .await - .move_group(¶ms.from_group_id, ¶ms.to_group_id) - .await?; - - match self.group_service.read().await.get_group(¶ms.from_group_id).await { + .move_group(¶ms.from_group_id, ¶ms.to_group_id)?; + match self.group_controller.read().await.get_group(¶ms.from_group_id) { None => {} Some((index, group)) => { let inserted_group = InsertedGroupPB { @@ -206,6 +183,7 @@ impl GridViewRevisionEditor { inserted_groups: vec![inserted_group], deleted_groups: vec![params.from_group_id.clone()], update_groups: vec![], + new_groups: vec![], }; self.notify_did_update_view(changeset).await; @@ -220,27 +198,52 @@ impl GridViewRevisionEditor { grid_setting } - pub(crate) async fn get_filters(&self) -> Vec { + pub(crate) async fn get_filters(&self) -> Vec { let field_revs = self.field_delegate.get_field_revs().await; match self.pad.read().await.get_all_filters(&field_revs) { None => vec![], Some(filters) => filters .into_values() .flatten() - .map(|filter| GridFilterConfiguration::from(filter.as_ref())) + .map(|filter| GridFilterConfigurationPB::from(filter.as_ref())) .collect(), } } - pub(crate) async fn insert_filter(&self, insert_filter: CreateFilterParams) -> FlowyResult<()> { + pub(crate) async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + if let Some(field_rev) = self.field_delegate.get_field_rev(¶ms.field_id).await { + let _ = self + .modify(|pad| { + let configuration = default_group_configuration(&field_rev); + let changeset = pad.insert_group(¶ms.field_id, ¶ms.field_type_rev, configuration)?; + Ok(changeset) + }) + .await?; + } + if self.group_controller.read().await.field_id() != params.field_id { + let _ = self.group_by_field(¶ms.field_id).await?; + self.notify_did_update_setting().await; + } + Ok(()) + } + + pub(crate) async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + self.modify(|pad| { + let changeset = pad.delete_filter(¶ms.field_id, ¶ms.field_type_rev, ¶ms.group_id)?; + Ok(changeset) + }) + .await + } + + pub(crate) async fn insert_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { self.modify(|pad| { let filter_rev = FilterConfigurationRevision { id: gen_grid_filter_id(), - field_id: insert_filter.field_id.clone(), - condition: insert_filter.condition, - content: insert_filter.content, + field_id: params.field_id.clone(), + condition: params.condition, + content: params.content, }; - let changeset = pad.insert_filter(&insert_filter.field_id, &insert_filter.field_type_rev, filter_rev)?; + let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?; Ok(changeset) }) .await @@ -260,7 +263,7 @@ impl GridViewRevisionEditor { #[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? { + match self.group_controller.write().await.did_update_field(&field_rev)? { None => {} Some(changeset) => { self.notify_did_update_view(changeset).await; @@ -270,6 +273,44 @@ impl GridViewRevisionEditor { Ok(()) } + pub(crate) async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { + if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await { + let new_group_controller = new_group_controller_with_field_rev( + self.user_id.clone(), + self.view_id.clone(), + self.pad.clone(), + self.rev_manager.clone(), + field_rev, + self.row_delegate.clone(), + ) + .await?; + + let new_groups = new_group_controller.groups().into_iter().map(GroupPB::from).collect(); + + *self.group_controller.write().await = new_group_controller; + let changeset = GroupViewChangesetPB { + view_id: self.view_id.clone(), + new_groups, + ..Default::default() + }; + + debug_assert!(!changeset.is_empty()); + if !changeset.is_empty() { + send_dart_notification(&changeset.view_id, GridNotification::DidGroupByNewField) + .payload(changeset) + .send(); + } + } + Ok(()) + } + + async fn notify_did_update_setting(&self) { + let setting = self.get_setting().await; + send_dart_notification(&self.view_id, GridNotification::DidUpdateGridSetting) + .payload(setting) + .send(); + } + pub async fn notify_did_update_group(&self, changeset: GroupChangesetPB) { send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup) .payload(changeset) @@ -295,6 +336,78 @@ impl GridViewRevisionEditor { } Ok(()) } + + async fn mut_group_controller(&self, f: F) -> Option + where + F: FnOnce(&mut Box, Arc) -> FlowyResult, + { + let group_field_id = self.group_controller.read().await.field_id().to_owned(); + match self.field_delegate.get_field_rev(&group_field_id).await { + None => None, + Some(field_rev) => { + let mut write_guard = self.group_controller.write().await; + f(&mut write_guard, field_rev).ok() + } + } + } + + #[allow(dead_code)] + async fn async_mut_group_controller(&self, f: F) -> Option + where + F: FnOnce(Arc>>, Arc) -> O, + O: Future> + Sync + 'static, + { + let group_field_id = self.group_controller.read().await.field_id().to_owned(); + match self.field_delegate.get_field_rev(&group_field_id).await { + None => None, + Some(field_rev) => { + let _write_guard = self.group_controller.write().await; + f(self.group_controller.clone(), field_rev).await.ok() + } + } + } +} +async fn new_group_controller( + user_id: String, + view_id: String, + pad: Arc>, + rev_manager: Arc, + field_delegate: Arc, + row_delegate: Arc, +) -> FlowyResult> { + let configuration_reader = GroupConfigurationReaderImpl(pad.clone()); + let field_revs = field_delegate.get_field_revs().await; + // Read the group field or find a new group field + let field_rev = configuration_reader + .get_configuration() + .await + .and_then(|configuration| { + field_revs + .iter() + .find(|field_rev| field_rev.id == configuration.field_id) + .cloned() + }) + .unwrap_or_else(|| find_group_field(&field_revs).unwrap()); + + new_group_controller_with_field_rev(user_id, view_id, pad, rev_manager, field_rev, row_delegate).await +} + +async fn new_group_controller_with_field_rev( + user_id: String, + view_id: String, + pad: Arc>, + rev_manager: Arc, + field_rev: Arc, + row_delegate: Arc, +) -> FlowyResult> { + let configuration_reader = GroupConfigurationReaderImpl(pad.clone()); + let configuration_writer = GroupConfigurationWriterImpl { + user_id, + rev_manager, + view_pad: pad, + }; + let row_revs = row_delegate.gv_row_revs().await; + make_group_controller(view_id, field_rev, row_revs, configuration_reader, configuration_writer).await } async fn apply_change( @@ -335,13 +448,10 @@ impl RevisionObjectBuilder for GridViewRevisionPadBuilder { struct GroupConfigurationReaderImpl(Arc>); impl GroupConfigurationReader for GroupConfigurationReaderImpl { - fn get_group_configuration( - &self, - field_rev: Arc, - ) -> AFFuture>> { + fn get_configuration(&self) -> AFFuture>> { let view_pad = self.0.clone(); wrap_future(async move { - let mut groups = view_pad.read().await.groups.get_objects(&field_rev.id, &field_rev.ty)?; + let mut groups = view_pad.read().await.get_all_groups(); if groups.is_empty() { None } else { @@ -359,7 +469,7 @@ struct GroupConfigurationWriterImpl { } impl GroupConfigurationWriter for GroupConfigurationWriterImpl { - fn save_group_configuration( + fn save_configuration( &self, field_id: &str, field_type: FieldTypeRevision, @@ -385,31 +495,40 @@ impl GroupConfigurationWriter for GroupConfigurationWriterImpl { } pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc]) -> GridSettingPB { - let current_layout_type: GridLayout = view_pad.layout.clone().into(); - let filters_by_field_id = view_pad + let layout_type: GridLayout = view_pad.layout.clone().into(); + let filter_configurations = view_pad .get_all_filters(field_revs) .map(|filters_by_field_id| { filters_by_field_id .into_iter() - .map(|(k, v)| (k, v.into())) - .collect::>() + .map(|(_, v)| { + let repeated_filter: RepeatedGridFilterConfigurationPB = v.into(); + repeated_filter.items + }) + .flatten() + .collect::>() }) .unwrap_or_default(); - let groups_by_field_id = view_pad - .get_all_groups(field_revs) + + let group_configurations = view_pad + .get_groups_by_field_revs(field_revs) .map(|groups_by_field_id| { groups_by_field_id .into_iter() - .map(|(k, v)| (k, v.into())) - .collect::>() + .map(|(_, v)| { + let repeated_group: RepeatedGridGroupConfigurationPB = v.into(); + repeated_group.items + }) + .flatten() + .collect::>() }) .unwrap_or_default(); GridSettingPB { layouts: GridLayoutPB::all(), - current_layout_type, - filter_configuration_by_field_id: filters_by_field_id, - group_configuration_by_field_id: groups_by_field_id, + layout_type, + filter_configurations: filter_configurations.into(), + group_configurations: group_configurations.into(), } } 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 0444c52763..b1041a818c 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 @@ -1,6 +1,6 @@ use crate::entities::{ - CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridSettingPB, MoveGroupParams, - RepeatedGridGroupPB, RowPB, + CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridSettingPB, + InsertFilterParams, InsertGroupParams, MoveGroupParams, RepeatedGridGroupPB, RowPB, }; use crate::manager::GridUser; use crate::services::grid_editor_task::GridServiceTaskScheduler; @@ -84,6 +84,12 @@ impl GridViewManager { } } + pub(crate) async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + let _ = view_editor.group_by_field(field_id).await?; + Ok(()) + } + pub(crate) async fn did_update_cell(&self, row_id: &str, _field_id: &str) { self.did_update_row(row_id).await } @@ -99,19 +105,19 @@ impl GridViewManager { Ok(view_editor.get_setting().await) } - pub(crate) async fn get_filters(&self) -> FlowyResult> { + pub(crate) async fn get_filters(&self) -> FlowyResult> { let view_editor = self.get_default_view_editor().await?; Ok(view_editor.get_filters().await) } - pub(crate) async fn update_filter(&self, insert_filter: CreateFilterParams) -> FlowyResult<()> { + pub(crate) async fn insert_or_update_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { let view_editor = self.get_default_view_editor().await?; - view_editor.insert_filter(insert_filter).await + view_editor.insert_filter(params).await } - pub(crate) async fn delete_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> { + pub(crate) async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { let view_editor = self.get_default_view_editor().await?; - view_editor.delete_filter(delete_filter).await + view_editor.delete_filter(params).await } pub(crate) async fn load_groups(&self) -> FlowyResult { @@ -120,6 +126,16 @@ impl GridViewManager { Ok(RepeatedGridGroupPB { items: groups }) } + pub(crate) async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.insert_group(params).await + } + + pub(crate) async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.delete_group(params).await + } + pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { let view_editor = self.get_default_view_editor().await?; let _ = view_editor.move_group(params).await?; diff --git a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs index 3fb5cefaa3..c110d01ad4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs @@ -1,24 +1,22 @@ -use crate::entities::{GroupPB, GroupViewChangesetPB, InsertedGroupPB}; -use crate::services::group::{default_group_configuration, Group}; +use crate::entities::{GroupPB, GroupViewChangesetPB}; +use crate::services::group::{default_group_configuration, GeneratedGroup, Group}; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{ FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision, }; use indexmap::IndexMap; use lib_infra::future::AFFuture; +use std::collections::HashMap; use std::fmt::Formatter; use std::marker::PhantomData; use std::sync::Arc; pub trait GroupConfigurationReader: Send + Sync + 'static { - fn get_group_configuration( - &self, - field_rev: Arc, - ) -> AFFuture>>; + fn get_configuration(&self) -> AFFuture>>; } pub trait GroupConfigurationWriter: Send + Sync + 'static { - fn save_group_configuration( + fn save_configuration( &self, field_id: &str, field_type: FieldTypeRevision, @@ -26,7 +24,7 @@ pub trait GroupConfigurationWriter: Send + Sync + 'static { ) -> AFFuture>; } -impl std::fmt::Display for GenericGroupConfiguration { +impl std::fmt::Display for GroupContext { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.groups_map.iter().for_each(|(_, group)| { let _ = f.write_fmt(format_args!("Group:{} has {} rows \n", group.id, group.rows.len())); @@ -39,9 +37,9 @@ impl std::fmt::Display for GenericGroupConfiguration { } } -pub struct GenericGroupConfiguration { - view_id: String, - pub configuration: Arc, +pub struct GroupContext { + pub view_id: String, + configuration: Arc, configuration_content: PhantomData, field_rev: Arc, groups_map: IndexMap, @@ -50,7 +48,7 @@ pub struct GenericGroupConfiguration { writer: Arc, } -impl GenericGroupConfiguration +impl GroupContext where C: GroupConfigurationContentSerde, { @@ -67,16 +65,17 @@ where field_id: field_rev.id.clone(), name: format!("No {}", field_rev.name), is_default: true, + is_visible: true, rows: vec![], - content: "".to_string(), + filter_content: "".to_string(), }; - let configuration = match reader.get_group_configuration(field_rev.clone()).await { + let configuration = match reader.get_configuration().await { None => { - let default_group_configuration = default_group_configuration(&field_rev); + let default_configuration = default_group_configuration(&field_rev); writer - .save_group_configuration(&field_rev.id, field_rev.ty, default_group_configuration.clone()) + .save_configuration(&field_rev.id, field_rev.ty, default_configuration.clone()) .await?; - Arc::new(default_group_configuration) + Arc::new(default_configuration) } Some(configuration) => configuration, }; @@ -134,47 +133,105 @@ where } } - pub(crate) fn merge_groups(&mut self, groups: Vec) -> FlowyResult> { + #[tracing::instrument(level = "debug", skip(self, generated_groups), err)] + pub(crate) fn init_groups( + &mut self, + generated_groups: Vec, + reset: bool, + ) -> FlowyResult> { + let mut new_groups = vec![]; + let mut filter_content_map = HashMap::new(); + generated_groups.into_iter().for_each(|generate_group| { + filter_content_map.insert(generate_group.group_rev.id.clone(), generate_group.filter_content); + new_groups.push(generate_group.group_rev); + }); + let MergeGroupResult { - groups, - inserted_groups, - updated_groups, - } = merge_groups(&self.configuration.groups, groups); + mut all_group_revs, + new_group_revs, + updated_group_revs: _, + deleted_group_revs, + } = if reset { + merge_groups(&[], new_groups) + } else { + merge_groups(&self.configuration.groups, new_groups) + }; - let group_revs = groups - .iter() - .map(|group| GroupRevision::new(group.id.clone(), group.name.clone())) - .collect::>(); + let deleted_group_ids = deleted_group_revs + .into_iter() + .map(|group_rev| group_rev.id) + .collect::>(); - self.mut_configuration(move |configuration| { + self.mut_configuration(|configuration| { let mut is_changed = false; - for new_group_rev in group_revs { + if !deleted_group_ids.is_empty() { + configuration + .groups + .retain(|group| !deleted_group_ids.contains(&group.id)); + is_changed = true; + } + + for group_rev in &mut all_group_revs { match configuration .groups .iter() - .position(|group_rev| group_rev.id == new_group_rev.id) + .position(|old_group_rev| old_group_rev.id == group_rev.id) { None => { - configuration.groups.push(new_group_rev); + configuration.groups.push(group_rev.clone()); is_changed = true; } Some(pos) => { - let removed_group = configuration.groups.remove(pos); - if removed_group != new_group_rev { + let mut old_group = configuration.groups.remove(pos); + group_rev.update_with_other(&old_group); + + // Take the GroupRevision if the name has changed + if is_group_changed(group_rev, &old_group) { + old_group.name = group_rev.name.clone(); is_changed = true; + configuration.groups.insert(pos, old_group); } - configuration.groups.insert(pos, new_group_rev); } } } is_changed })?; - groups.into_iter().for_each(|group| { - self.groups_map.insert(group.id.clone(), group); + // The len of the filter_content_map should equal to the len of the all_group_revs + debug_assert_eq!(filter_content_map.len(), all_group_revs.len()); + all_group_revs.into_iter().for_each(|group_rev| { + if let Some(filter_content) = filter_content_map.get(&group_rev.id) { + let group = Group::new( + group_rev.id, + self.field_rev.id.clone(), + group_rev.name, + filter_content.clone(), + ); + self.groups_map.insert(group.id.clone(), group); + } }); - let changeset = make_group_view_changeset(self.view_id.clone(), inserted_groups, updated_groups); + let new_groups = new_group_revs + .into_iter() + .flat_map(|group_rev| { + let filter_content = filter_content_map.get(&group_rev.id)?; + let group = Group::new( + group_rev.id, + self.field_rev.id.clone(), + group_rev.name, + filter_content.clone(), + ); + Some(GroupPB::from(group)) + }) + .collect(); + + let changeset = GroupViewChangesetPB { + view_id: self.view_id.clone(), + new_groups, + deleted_groups: deleted_group_ids, + update_groups: vec![], + inserted_groups: vec![], + }; tracing::trace!("Group changeset: {:?}", changeset); if changeset.is_empty() { Ok(None) @@ -221,10 +278,7 @@ where let field_id = self.field_rev.id.clone(); let field_type = self.field_rev.ty; tokio::spawn(async move { - match writer - .save_group_configuration(&field_id, field_type, configuration) - .await - { + match writer.save_configuration(&field_id, field_type, configuration).await { Ok(_) => {} Err(e) => { tracing::error!("Save group configuration failed: {}", e); @@ -260,82 +314,64 @@ where } } -fn merge_groups(old_groups: &[GroupRevision], groups: Vec) -> MergeGroupResult { +fn merge_groups(old_groups: &[GroupRevision], new_groups: Vec) -> MergeGroupResult { let mut merge_result = MergeGroupResult::new(); if old_groups.is_empty() { - merge_result.groups = groups; + merge_result.all_group_revs = new_groups.clone(); + merge_result.new_group_revs = new_groups; return merge_result; } // group_map is a helper map is used to filter out the new groups. - let mut group_map: IndexMap = IndexMap::new(); - groups.into_iter().for_each(|group| { - group_map.insert(group.id.clone(), group); + let mut new_group_map: IndexMap = IndexMap::new(); + new_groups.into_iter().for_each(|group_rev| { + new_group_map.insert(group_rev.id.clone(), group_rev); }); // 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.id) { - if group.name == group_rev.name { - merge_result.add_group(group); - } else { - merge_result.add_updated_group(group); + for old in old_groups { + if let Some(new) = new_group_map.remove(&old.id) { + merge_result.all_group_revs.push(new.clone()); + if is_group_changed(&new, old) { + merge_result.updated_group_revs.push(new); } + } else { + merge_result.deleted_group_revs.push(old.clone()); } } // Find out the new groups - group_map - .into_values() - .enumerate() - .for_each(|(index, group)| merge_result.add_insert_group(index, group)); - + let new_groups = new_group_map.into_values(); + for (_, group) in new_groups.into_iter().enumerate() { + merge_result.all_group_revs.push(group.clone()); + merge_result.new_group_revs.push(group); + } merge_result } +fn is_group_changed(new: &GroupRevision, old: &GroupRevision) -> bool { + if new.name != old.name { + return true; + } + + false +} + struct MergeGroupResult { - groups: Vec, - inserted_groups: Vec, - updated_groups: Vec, + // Contains the new groups and the updated groups + all_group_revs: Vec, + new_group_revs: Vec, + updated_group_revs: Vec, + deleted_group_revs: Vec, } impl MergeGroupResult { fn new() -> Self { Self { - groups: vec![], - inserted_groups: vec![], - updated_groups: vec![], + all_group_revs: vec![], + new_group_revs: vec![], + updated_group_revs: vec![], + deleted_group_revs: 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); - } - - 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 { - GroupViewChangesetPB { - view_id, - inserted_groups, - deleted_groups: vec![], - update_groups: updated_group.into_iter().map(GroupPB::from).collect(), - } } 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 62647d44b8..54d001d4a7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs @@ -1,11 +1,11 @@ use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, InsertedRowPB, RowPB}; use crate::services::cell::{decode_any_cell_data, CellBytesParser}; use crate::services::group::action::GroupAction; -use crate::services::group::configuration::GenericGroupConfiguration; +use crate::services::group::configuration::GroupContext; use crate::services::group::entities::Group; use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{ - FieldRevision, GroupConfigurationContentSerde, RowChangeset, RowRevision, TypeOptionDataDeserializer, + FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, RowRevision, TypeOptionDataDeserializer, }; use std::marker::PhantomData; @@ -19,14 +19,19 @@ pub trait GroupController: GroupControllerSharedOperation + Send + Sync { } pub trait GroupGenerator { - type ConfigurationType; + type Context; type TypeOptionType; fn generate_groups( field_id: &str, - configuration: &Self::ConfigurationType, + group_ctx: &Self::Context, type_option: &Option, - ) -> Vec; + ) -> Vec; +} + +pub struct GeneratedGroup { + pub group_rev: GroupRevision, + pub filter_content: String, } pub struct MoveGroupRowContext<'a> { @@ -43,7 +48,7 @@ pub trait GroupControllerSharedOperation: Send + Sync { fn field_id(&self) -> &str; fn groups(&self) -> Vec; fn get_group(&self, group_id: &str) -> Option<(usize, Group)>; - fn fill_groups(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult>; + fn fill_groups(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()>; fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>; fn did_update_row( &mut self, @@ -69,7 +74,7 @@ pub trait GroupControllerSharedOperation: Send + Sync { pub struct GenericGroupController { pub field_id: String, pub type_option: Option, - pub configuration: GenericGroupConfiguration, + pub group_ctx: GroupContext, group_action_phantom: PhantomData, cell_parser_phantom: PhantomData

, } @@ -78,21 +83,18 @@ impl GenericGroupController where C: GroupConfigurationContentSerde, T: TypeOptionDataDeserializer, - G: GroupGenerator, TypeOptionType = T>, + G: GroupGenerator, TypeOptionType = T>, { - pub async fn new( - field_rev: &Arc, - mut configuration: GenericGroupConfiguration, - ) -> FlowyResult { + pub async fn new(field_rev: &Arc, mut configuration: GroupContext) -> FlowyResult { let field_type_rev = field_rev.ty; - let type_option = field_rev.get_type_option_entry::(field_type_rev); + let type_option = field_rev.get_type_option::(field_type_rev); let groups = G::generate_groups(&field_rev.id, &configuration, &type_option); - let _ = configuration.merge_groups(groups)?; + let _ = configuration.init_groups(groups, true)?; Ok(Self { field_id: field_rev.id.clone(), type_option, - configuration, + group_ctx: configuration, group_action_phantom: PhantomData, cell_parser_phantom: PhantomData, }) @@ -105,7 +107,7 @@ where row_rev: &RowRevision, other_group_changesets: &[GroupChangesetPB], ) -> GroupChangesetPB { - let default_group = self.configuration.get_mut_default_group(); + let default_group = self.group_ctx.get_mut_default_group(); // [other_group_inserted_row] contains all the inserted rows except the default group. let other_group_inserted_row = other_group_changesets @@ -171,7 +173,7 @@ where P: CellBytesParser, C: GroupConfigurationContentSerde, T: TypeOptionDataDeserializer, - G: GroupGenerator, TypeOptionType = T>, + G: GroupGenerator, TypeOptionType = T>, Self: GroupAction, { @@ -180,23 +182,23 @@ where } fn groups(&self) -> Vec { - self.configuration.clone_groups() + self.group_ctx.clone_groups() } fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { - let group = self.configuration.get_group(group_id)?; + let group = self.group_ctx.get_group(group_id)?; Some((group.0, group.1.clone())) } #[tracing::instrument(level = "trace", skip_all, fields(row_count=%row_revs.len(), group_result))] - fn fill_groups(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult> { + fn fill_groups(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { for row_rev in row_revs { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { let mut grouped_rows: Vec = vec![]; let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_data = cell_bytes.parser::

()?; - for group in self.configuration.concrete_groups() { - if self.can_group(&group.content, &cell_data) { + for group in self.group_ctx.concrete_groups() { + if self.can_group(&group.filter_content, &cell_data) { grouped_rows.push(GroupedRow { row: row_rev.into(), group_id: group.id.clone(), @@ -205,25 +207,25 @@ where } if grouped_rows.is_empty() { - self.configuration.get_mut_default_group().add_row(row_rev.into()); + self.group_ctx.get_mut_default_group().add_row(row_rev.into()); } else { for group_row in grouped_rows { - if let Some(group) = self.configuration.get_mut_group(&group_row.group_id) { + if let Some(group) = self.group_ctx.get_mut_group(&group_row.group_id) { group.add_row(group_row.row); } } } } else { - self.configuration.get_mut_default_group().add_row(row_rev.into()); + self.group_ctx.get_mut_default_group().add_row(row_rev.into()); } } - tracing::Span::current().record("group_result", &format!("{},", self.configuration,).as_str()); - Ok(self.groups()) + tracing::Span::current().record("group_result", &format!("{},", self.group_ctx,).as_str()); + Ok(()) } fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> { - self.configuration.move_group(from_group_id, to_group_id) + self.group_ctx.move_group(from_group_id, to_group_id) } fn did_update_row( @@ -271,10 +273,9 @@ where } 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)?; + let type_option = field_rev.get_type_option::(field_rev.ty); + let groups = G::generate_groups(&field_rev.id, &self.group_ctx, &type_option); + let changeset = self.group_ctx.init_groups(groups, false)?; Ok(changeset) } } 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 4c06ba63ce..02ff4cb503 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,13 +1,13 @@ 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; +use crate::services::group::configuration::GroupContext; use crate::services::group::controller::{ GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, }; -use crate::services::group::entities::Group; -use flowy_grid_data_model::revision::{CheckboxGroupConfigurationRevision, FieldRevision, RowRevision}; +use crate::services::group::GeneratedGroup; +use flowy_grid_data_model::revision::{CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision}; pub type CheckboxGroupController = GenericGroupController< CheckboxGroupConfigurationRevision, @@ -16,7 +16,7 @@ pub type CheckboxGroupController = GenericGroupController< CheckboxCellDataParser, >; -pub type CheckboxGroupConfiguration = GenericGroupConfiguration; +pub type CheckboxGroupContext = GroupContext; impl GroupAction for CheckboxGroupController { type CellDataType = CheckboxCellData; @@ -49,26 +49,23 @@ impl GroupController for CheckboxGroupController { pub struct CheckboxGroupGenerator(); impl GroupGenerator for CheckboxGroupGenerator { - type ConfigurationType = CheckboxGroupConfiguration; + type Context = CheckboxGroupContext; type TypeOptionType = CheckboxTypeOptionPB; fn generate_groups( - field_id: &str, - _configuration: &Self::ConfigurationType, + _field_id: &str, + _group_ctx: &Self::Context, _type_option: &Option, - ) -> Vec { - let check_group = Group::new( - "true".to_string(), - field_id.to_owned(), - "".to_string(), - CHECK.to_string(), - ); - let uncheck_group = Group::new( - "false".to_string(), - field_id.to_owned(), - "".to_string(), - UNCHECK.to_string(), - ); + ) -> Vec { + let check_group = GeneratedGroup { + group_rev: GroupRevision::new("true".to_string(), CHECK.to_string()), + filter_content: "".to_string(), + }; + + let uncheck_group = GeneratedGroup { + group_rev: GroupRevision::new("false".to_string(), UNCHECK.to_string()), + filter_content: "".to_string(), + }; vec![check_group, uncheck_group] } } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs new file mode 100644 index 0000000000..938bdd127e --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs @@ -0,0 +1,80 @@ +use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB}; +use crate::services::group::{Group, GroupController, GroupControllerSharedOperation, MoveGroupRowContext}; +use flowy_error::FlowyResult; +use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use std::sync::Arc; + +pub struct DefaultGroupController { + pub field_id: String, + pub group: Group, +} + +const DEFAULT_GROUP_CONTROLLER: &str = "DefaultGroupController"; + +impl DefaultGroupController { + pub fn new(field_rev: &Arc) -> Self { + let group = Group::new( + DEFAULT_GROUP_CONTROLLER.to_owned(), + field_rev.id.clone(), + "".to_owned(), + "".to_owned(), + ); + Self { + field_id: field_rev.id.clone(), + group, + } + } +} + +impl GroupControllerSharedOperation for DefaultGroupController { + fn field_id(&self) -> &str { + &self.field_id + } + + fn groups(&self) -> Vec { + vec![self.group.clone()] + } + + fn get_group(&self, _group_id: &str) -> Option<(usize, Group)> { + Some((0, self.group.clone())) + } + + fn fill_groups(&mut self, row_revs: &[Arc], _field_rev: &FieldRevision) -> FlowyResult<()> { + row_revs.iter().for_each(|row_rev| { + self.group.add_row(RowPB::from(row_rev)); + }); + Ok(()) + } + + fn move_group(&mut self, _from_group_id: &str, _to_group_id: &str) -> FlowyResult<()> { + Ok(()) + } + + fn did_update_row( + &mut self, + _row_rev: &RowRevision, + _field_rev: &FieldRevision, + ) -> FlowyResult> { + todo!() + } + + fn did_delete_row( + &mut self, + _row_rev: &RowRevision, + _field_rev: &FieldRevision, + ) -> FlowyResult> { + todo!() + } + + fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult> { + todo!() + } + + fn did_update_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult> { + Ok(None) + } +} + +impl GroupController for DefaultGroupController { + fn will_create_row(&mut self, _row_rev: &mut RowRevision, _field_rev: &FieldRevision, _group_id: &str) {} +} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/mod.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/mod.rs index 974f311a48..512be761e9 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/mod.rs @@ -1,5 +1,7 @@ mod checkbox_controller; +mod default_controller; mod select_option_controller; pub use checkbox_controller::*; +pub use default_controller::*; pub use select_option_controller::*; 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 fe90e1b462..026843bbcc 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 @@ -7,7 +7,8 @@ use crate::services::group::controller::{ GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, }; use crate::services::group::controller_impls::select_option_controller::util::*; -use crate::services::group::entities::Group; + +use crate::services::group::GeneratedGroup; use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; // MultiSelect @@ -27,8 +28,8 @@ impl GroupAction for MultiSelectGroupController { fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.iter_mut_groups(|group| { - if let Some(changeset) = add_row(group, cell_data, row_rev) { + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = add_select_option_row(group, cell_data, row_rev) { changesets.push(changeset); } }); @@ -37,8 +38,8 @@ impl GroupAction for MultiSelectGroupController { fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.iter_mut_groups(|group| { - if let Some(changeset) = remove_row(group, cell_data, row_rev) { + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { changesets.push(changeset); } }); @@ -47,7 +48,7 @@ impl GroupAction for MultiSelectGroupController { fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec { let mut group_changeset = vec![]; - self.configuration.iter_mut_groups(|group| { + self.group_ctx.iter_mut_groups(|group| { if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) { group_changeset.push(changeset); } @@ -58,7 +59,7 @@ impl GroupAction for MultiSelectGroupController { impl GroupController for MultiSelectGroupController { fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { - match self.configuration.get_group(group_id) { + match self.group_ctx.get_group(group_id) { None => tracing::warn!("Can not find the group: {}", group_id), Some((_, group)) => { let cell_rev = insert_select_option_cell(group.id.clone(), field_rev); @@ -70,27 +71,16 @@ impl GroupController for MultiSelectGroupController { pub struct MultiSelectGroupGenerator(); impl GroupGenerator for MultiSelectGroupGenerator { - type ConfigurationType = SelectOptionGroupConfiguration; + type Context = SelectOptionGroupContext; type TypeOptionType = MultiSelectTypeOptionPB; fn generate_groups( field_id: &str, - _configuration: &Self::ConfigurationType, + group_ctx: &Self::Context, type_option: &Option, - ) -> Vec { + ) -> Vec { match type_option { None => vec![], - Some(type_option) => type_option - .options - .iter() - .map(|option| { - Group::new( - option.id.clone(), - field_id.to_owned(), - option.name.clone(), - option.id.clone(), - ) - }) - .collect(), + Some(type_option) => generate_select_option_groups(field_id, group_ctx, &type_option.options), } } } 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 d774ab083f..96b686efe3 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 @@ -9,6 +9,7 @@ use crate::services::group::controller::{ use crate::services::group::controller_impls::select_option_controller::util::*; use crate::services::group::entities::Group; +use crate::services::group::GeneratedGroup; use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; // SingleSelect @@ -27,8 +28,8 @@ impl GroupAction for SingleSelectGroupController { fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.iter_mut_groups(|group| { - if let Some(changeset) = add_row(group, cell_data, row_rev) { + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = add_select_option_row(group, cell_data, row_rev) { changesets.push(changeset); } }); @@ -37,8 +38,8 @@ impl GroupAction for SingleSelectGroupController { fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; - self.configuration.iter_mut_groups(|group| { - if let Some(changeset) = remove_row(group, cell_data, row_rev) { + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { changesets.push(changeset); } }); @@ -47,7 +48,7 @@ impl GroupAction for SingleSelectGroupController { fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec { let mut group_changeset = vec![]; - self.configuration.iter_mut_groups(|group| { + self.group_ctx.iter_mut_groups(|group| { if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) { group_changeset.push(changeset); } @@ -58,7 +59,7 @@ impl GroupAction for SingleSelectGroupController { impl GroupController for SingleSelectGroupController { fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { - let group: Option<&mut Group> = self.configuration.get_mut_group(group_id); + let group: Option<&mut Group> = self.group_ctx.get_mut_group(group_id); match group { None => {} Some(group) => { @@ -72,27 +73,16 @@ impl GroupController for SingleSelectGroupController { pub struct SingleSelectGroupGenerator(); impl GroupGenerator for SingleSelectGroupGenerator { - type ConfigurationType = SelectOptionGroupConfiguration; + type Context = SelectOptionGroupContext; type TypeOptionType = SingleSelectTypeOptionPB; fn generate_groups( field_id: &str, - _configuration: &Self::ConfigurationType, + group_ctx: &Self::Context, type_option: &Option, - ) -> Vec { + ) -> Vec { match type_option { None => vec![], - Some(type_option) => type_option - .options - .iter() - .map(|option| { - Group::new( - option.id.clone(), - field_id.to_owned(), - option.name.clone(), - option.id.clone(), - ) - }) - .collect(), + Some(type_option) => generate_select_option_groups(field_id, group_ctx, &type_option.options), } } } 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 39f581d97d..494cd41197 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,15 +1,15 @@ 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; -use crate::services::group::Group; +use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB}; +use crate::services::group::configuration::GroupContext; +use crate::services::group::{GeneratedGroup, Group}; use crate::services::group::controller::MoveGroupRowContext; -use flowy_grid_data_model::revision::{RowRevision, SelectOptionGroupConfigurationRevision}; +use flowy_grid_data_model::revision::{GroupRevision, RowRevision, SelectOptionGroupConfigurationRevision}; -pub type SelectOptionGroupConfiguration = GenericGroupConfiguration; +pub type SelectOptionGroupContext = GroupContext; -pub fn add_row( +pub fn add_select_option_row( group: &mut Group, cell_data: &SelectOptionCellDataPB, row_rev: &RowRevision, @@ -42,7 +42,7 @@ pub fn add_row( } } -pub fn remove_row( +pub fn remove_select_option_row( group: &mut Group, cell_data: &SelectOptionCellDataPB, row_rev: &RowRevision, @@ -125,3 +125,19 @@ pub fn move_select_option_row( Some(changeset) } } + +pub fn generate_select_option_groups( + _field_id: &str, + _group_ctx: &SelectOptionGroupContext, + options: &[SelectOptionPB], +) -> Vec { + let groups = options + .iter() + .map(|option| GeneratedGroup { + group_rev: GroupRevision::new(option.id.clone(), option.name.clone()), + filter_content: option.id.clone(), + }) + .collect(); + + groups +} 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 9afe7f54e9..c4687859f3 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/entities.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/entities.rs @@ -6,21 +6,23 @@ pub struct Group { pub field_id: String, pub name: String, pub is_default: bool, + pub is_visible: bool, pub(crate) rows: Vec, /// [content] is used to determine which group the cell belongs to. - pub content: String, + pub filter_content: String, } impl Group { - pub fn new(id: String, field_id: String, name: String, content: String) -> Self { + pub fn new(id: String, field_id: String, name: String, filter_content: String) -> Self { Self { id, field_id, is_default: false, + is_visible: true, name, rows: vec![], - content, + filter_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 deleted file mode 100644 index c7d67f65d6..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs +++ /dev/null @@ -1,293 +0,0 @@ -use crate::entities::{FieldType, GroupChangesetPB, GroupViewChangesetPB}; -use crate::services::group::configuration::GroupConfigurationReader; -use crate::services::group::controller::{GroupController, MoveGroupRowContext}; -use crate::services::group::{ - CheckboxGroupConfiguration, CheckboxGroupController, Group, GroupConfigurationWriter, MultiSelectGroupController, - SelectOptionGroupConfiguration, SingleSelectGroupController, -}; -use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{ - CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision, - NumberGroupConfigurationRevision, RowChangeset, RowRevision, SelectOptionGroupConfigurationRevision, - TextGroupConfigurationRevision, UrlGroupConfigurationRevision, -}; -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(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, - } - } - - pub(crate) async fn groups(&self) -> Vec { - self.group_controller - .as_ref() - .map(|group_controller| group_controller.groups()) - .unwrap_or_default() - } - - pub(crate) async fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { - self.group_controller - .as_ref() - .and_then(|group_controller| group_controller.get_group(group_id)) - } - - pub(crate) async fn load_groups( - &mut self, - field_revs: &[Arc], - row_revs: Vec>, - ) -> Option> { - let field_rev = find_group_field(field_revs)?; - let field_type: FieldType = field_rev.ty.into(); - - let mut group_controller = self.make_group_controller(&field_type, &field_rev).await.ok()??; - let groups = match group_controller.fill_groups(&row_revs, &field_rev) { - Ok(groups) => groups, - Err(e) => { - tracing::error!("Fill groups failed:{:?}", e); - vec![] - } - }; - self.group_controller = Some(group_controller); - Some(groups) - } - - pub(crate) async fn will_create_row(&mut self, row_rev: &mut RowRevision, group_id: &str, get_field_fn: F) - where - F: FnOnce(String) -> O, - O: Future>> + Send + Sync + 'static, - { - if let Some(group_controller) = self.group_controller.as_mut() { - let field_id = group_controller.field_id().to_owned(); - match get_field_fn(field_id).await { - None => {} - Some(field_rev) => { - group_controller.will_create_row(row_rev, &field_rev, group_id); - } - } - } - } - - pub(crate) async fn did_delete_row( - &mut self, - row_rev: &RowRevision, - get_field_fn: F, - ) -> Option> - where - F: FnOnce(String) -> O, - O: Future>> + Send + Sync + 'static, - { - let group_controller = self.group_controller.as_mut()?; - let field_id = group_controller.field_id().to_owned(); - let field_rev = get_field_fn(field_id).await?; - - match group_controller.did_delete_row(row_rev, &field_rev) { - Ok(changesets) => Some(changesets), - Err(e) => { - tracing::error!("Delete group data failed, {:?}", e); - None - } - } - } - - pub(crate) async fn move_group_row( - &mut self, - row_rev: &RowRevision, - row_changeset: &mut RowChangeset, - to_group_id: &str, - to_row_id: Option, - get_field_fn: F, - ) -> Option> - where - F: FnOnce(String) -> O, - O: Future>> + Send + Sync + 'static, - { - let group_controller = self.group_controller.as_mut()?; - let field_id = group_controller.field_id().to_owned(); - let field_rev = get_field_fn(field_id).await?; - let move_row_context = MoveGroupRowContext { - row_rev, - row_changeset, - field_rev: field_rev.as_ref(), - to_group_id, - to_row_id, - }; - - match group_controller.move_group_row(move_row_context) { - Ok(changesets) => Some(changesets), - Err(e) => { - tracing::error!("Move group data failed, {:?}", e); - None - } - } - } - - #[tracing::instrument(level = "trace", skip_all)] - pub(crate) async fn did_update_row( - &mut self, - row_rev: &RowRevision, - get_field_fn: F, - ) -> Option> - where - F: FnOnce(String) -> O, - O: Future>> + Send + Sync + 'static, - { - let group_controller = self.group_controller.as_mut()?; - let field_id = group_controller.field_id().to_owned(); - let field_rev = get_field_fn(field_id).await?; - - match group_controller.did_update_row(row_rev, &field_rev) { - Ok(changeset) => Some(changeset), - Err(e) => { - tracing::error!("Update group data failed, {:?}", e); - None - } - } - } - - #[tracing::instrument(level = "trace", skip_all)] - pub(crate) async fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> { - match self.group_controller.as_mut() { - None => Ok(()), - Some(group_controller) => { - let _ = group_controller.move_group(from_group_id, to_group_id)?; - Ok(()) - } - } - } - - #[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, - field_type: &FieldType, - field_rev: &Arc, - ) -> FlowyResult>> { - let mut group_controller: Option> = None; - match field_type { - FieldType::RichText => { - // let generator = GroupGenerator::::from_configuration(configuration); - } - FieldType::Number => { - // let generator = GroupGenerator::::from_configuration(configuration); - } - FieldType::DateTime => { - // let generator = GroupGenerator::::from_configuration(configuration); - } - FieldType::SingleSelect => { - let configuration = SelectOptionGroupConfiguration::new( - self.view_id.clone(), - field_rev.clone(), - self.configuration_reader.clone(), - self.configuration_writer.clone(), - ) - .await?; - let controller = SingleSelectGroupController::new(field_rev, configuration).await?; - group_controller = Some(Box::new(controller)); - } - FieldType::MultiSelect => { - let configuration = SelectOptionGroupConfiguration::new( - self.view_id.clone(), - field_rev.clone(), - self.configuration_reader.clone(), - self.configuration_writer.clone(), - ) - .await?; - let controller = MultiSelectGroupController::new(field_rev, configuration).await?; - group_controller = Some(Box::new(controller)); - } - FieldType::Checkbox => { - let configuration = CheckboxGroupConfiguration::new( - self.view_id.clone(), - field_rev.clone(), - self.configuration_reader.clone(), - self.configuration_writer.clone(), - ) - .await?; - let controller = CheckboxGroupController::new(field_rev, configuration).await?; - group_controller = Some(Box::new(controller)); - } - FieldType::URL => { - // let generator = GroupGenerator::::from_configuration(configuration); - } - } - Ok(group_controller) - } -} - -fn find_group_field(field_revs: &[Arc]) -> Option> { - let field_rev = field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.can_be_group() - }) - .cloned(); - field_rev -} - -pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision { - let field_id = field_rev.id.clone(); - let field_type_rev = field_rev.ty; - let field_type: FieldType = field_rev.ty.into(); - match field_type { - FieldType::RichText => { - GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default()) - .unwrap() - } - FieldType::Number => { - GroupConfigurationRevision::new(field_id, field_type_rev, NumberGroupConfigurationRevision::default()) - .unwrap() - } - FieldType::DateTime => { - GroupConfigurationRevision::new(field_id, field_type_rev, DateGroupConfigurationRevision::default()) - .unwrap() - } - - FieldType::SingleSelect => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::MultiSelect => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::Checkbox => { - GroupConfigurationRevision::new(field_id, field_type_rev, CheckboxGroupConfigurationRevision::default()) - .unwrap() - } - FieldType::URL => { - GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap() - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs new file mode 100644 index 0000000000..50d93396d7 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs @@ -0,0 +1,114 @@ +use crate::entities::FieldType; +use crate::services::group::configuration::GroupConfigurationReader; +use crate::services::group::controller::GroupController; +use crate::services::group::{ + CheckboxGroupContext, CheckboxGroupController, DefaultGroupController, GroupConfigurationWriter, + MultiSelectGroupController, SelectOptionGroupContext, SingleSelectGroupController, +}; +use flowy_error::FlowyResult; +use flowy_grid_data_model::revision::{ + CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision, + NumberGroupConfigurationRevision, RowRevision, SelectOptionGroupConfigurationRevision, + TextGroupConfigurationRevision, UrlGroupConfigurationRevision, +}; +use std::sync::Arc; + +#[tracing::instrument(level = "trace", skip_all, err)] +pub async fn make_group_controller( + view_id: String, + field_rev: Arc, + row_revs: Vec>, + configuration_reader: R, + configuration_writer: W, +) -> FlowyResult> +where + R: GroupConfigurationReader, + W: GroupConfigurationWriter, +{ + let field_type: FieldType = field_rev.ty.into(); + + let mut group_controller: Box; + let configuration_reader = Arc::new(configuration_reader); + let configuration_writer = Arc::new(configuration_writer); + + match field_type { + FieldType::SingleSelect => { + let configuration = + SelectOptionGroupContext::new(view_id, field_rev.clone(), configuration_reader, configuration_writer) + .await?; + let controller = SingleSelectGroupController::new(&field_rev, configuration).await?; + group_controller = Box::new(controller); + } + FieldType::MultiSelect => { + let configuration = + SelectOptionGroupContext::new(view_id, field_rev.clone(), configuration_reader, configuration_writer) + .await?; + let controller = MultiSelectGroupController::new(&field_rev, configuration).await?; + group_controller = Box::new(controller); + } + FieldType::Checkbox => { + let configuration = + CheckboxGroupContext::new(view_id, field_rev.clone(), configuration_reader, configuration_writer) + .await?; + let controller = CheckboxGroupController::new(&field_rev, configuration).await?; + group_controller = Box::new(controller); + } + _ => { + group_controller = Box::new(DefaultGroupController::new(&field_rev)); + } + } + + let _ = group_controller.fill_groups(&row_revs, &field_rev)?; + Ok(group_controller) +} + +pub fn find_group_field(field_revs: &[Arc]) -> Option> { + let field_rev = field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.can_be_group() + }) + .cloned(); + field_rev +} + +pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision { + let field_id = field_rev.id.clone(); + let field_type_rev = field_rev.ty; + let field_type: FieldType = field_rev.ty.into(); + match field_type { + FieldType::RichText => { + GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default()) + .unwrap() + } + FieldType::Number => { + GroupConfigurationRevision::new(field_id, field_type_rev, NumberGroupConfigurationRevision::default()) + .unwrap() + } + FieldType::DateTime => { + GroupConfigurationRevision::new(field_id, field_type_rev, DateGroupConfigurationRevision::default()) + .unwrap() + } + + FieldType::SingleSelect => GroupConfigurationRevision::new( + field_id, + field_type_rev, + SelectOptionGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::MultiSelect => GroupConfigurationRevision::new( + field_id, + field_type_rev, + SelectOptionGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::Checkbox => { + GroupConfigurationRevision::new(field_id, field_type_rev, CheckboxGroupConfigurationRevision::default()) + .unwrap() + } + FieldType::URL => { + GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap() + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/mod.rs b/frontend/rust-lib/flowy-grid/src/services/group/mod.rs index 2bc979c28d..b73ac511b6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/mod.rs @@ -3,9 +3,10 @@ mod configuration; mod controller; mod controller_impls; mod entities; -mod group_service; +mod group_util; pub(crate) use configuration::*; +pub(crate) use controller::*; pub(crate) use controller_impls::*; pub(crate) use entities::*; -pub(crate) use group_service::*; +pub(crate) use group_util::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs index 16ee630cc7..2bf8f1bbeb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs @@ -1,4 +1,4 @@ -use crate::entities::{CreateFilterParams, DeleteFilterParams, GridLayout, GridSettingChangesetParams}; +use crate::entities::{DeleteFilterParams, GridLayout, GridSettingChangesetParams, InsertFilterParams}; pub struct GridSettingChangesetBuilder { params: GridSettingChangesetParams, @@ -17,7 +17,7 @@ impl GridSettingChangesetBuilder { Self { params } } - pub fn insert_filter(mut self, params: CreateFilterParams) -> Self { + pub fn insert_filter(mut self, params: InsertFilterParams) -> Self { self.params.insert_filter = Some(params); self } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs index 3aa22cc488..df0c105e39 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs @@ -176,7 +176,7 @@ impl GridRowTest { FieldType::Number => { let field_rev = self.editor.get_field_rev(&cell_id.field_id).await.unwrap(); let number_type_option = field_rev - .get_type_option_entry::(FieldType::Number.into()) + .get_type_option::(FieldType::Number.into()) .unwrap(); let cell_data = self .editor diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs index 3e255d3662..7dfd76a1a4 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs @@ -4,7 +4,7 @@ use crate::grid::field_test::util::*; use flowy_grid::entities::FieldChangesetParams; use flowy_grid::services::field::selection_type_option::SelectOptionPB; use flowy_grid::services::field::SingleSelectTypeOptionPB; -use flowy_grid_data_model::revision::TypeOptionDataEntry; +use flowy_grid_data_model::revision::TypeOptionDataFormat; #[tokio::test] async fn grid_create_field() { @@ -86,7 +86,7 @@ async fn grid_update_field() { let mut expected_field_rev = single_select_field.clone(); expected_field_rev.frozen = true; expected_field_rev.width = 1000; - expected_field_rev.insert_type_option_entry(&single_select_type_option); + expected_field_rev.insert_type_option(&single_select_type_option); let scripts = vec![ CreateField { params }, diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs index fa2f003baa..e8cbf2b813 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs @@ -12,7 +12,7 @@ pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) { let cloned_field_rev = field_rev.clone(); let type_option_data = field_rev - .get_type_option_entry::(field_rev.ty) + .get_type_option::(field_rev.ty) .unwrap() .protobuf_bytes() .to_vec(); @@ -45,7 +45,7 @@ pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRev let field_rev = FieldBuilder::new(single_select).name("Name").visibility(true).build(); let cloned_field_rev = field_rev.clone(); let type_option_data = field_rev - .get_type_option_entry::(field_rev.ty) + .get_type_option::(field_rev.ty) .unwrap() .protobuf_bytes() .to_vec(); diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs index a1f0b8a0c6..0eff7edaa4 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs @@ -3,14 +3,14 @@ #![allow(dead_code)] #![allow(unused_imports)] -use flowy_grid::entities::{CreateFilterParams, CreateGridFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB}; +use flowy_grid::entities::{InsertFilterParams, InsertFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB}; use flowy_grid::services::setting::GridSettingChangesetBuilder; use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision}; use crate::grid::grid_editor::GridEditorTest; pub enum FilterScript { InsertGridTableFilter { - payload: CreateGridFilterPayloadPB, + payload: InsertFilterPayloadPB, }, AssertTableFilterCount { count: i32, @@ -47,8 +47,8 @@ impl GridFilterTest { match script { FilterScript::InsertGridTableFilter { payload } => { - let params: CreateFilterParams = payload.try_into().unwrap(); - let _ = self.editor.update_filter(params).await.unwrap(); + let params: InsertFilterParams = payload.try_into().unwrap(); + let _ = self.editor.create_filter(params).await.unwrap(); } FilterScript::AssertTableFilterCount { count } => { let filters = self.editor.get_grid_filter().await.unwrap(); diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs index 3e45a4053d..5868e16c2d 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs @@ -1,13 +1,13 @@ use crate::grid::filter_test::script::FilterScript::*; use crate::grid::filter_test::script::*; -use flowy_grid::entities::{CreateGridFilterPayloadPB, FieldType, TextFilterCondition}; +use flowy_grid::entities::{FieldType, InsertFilterPayloadPB, TextFilterCondition}; use flowy_grid_data_model::revision::FieldRevision; #[tokio::test] async fn grid_filter_create_test() { let mut test = GridFilterTest::new().await; let field_rev = test.get_field_rev(FieldType::RichText); - let payload = CreateGridFilterPayloadPB::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); + let payload = InsertFilterPayloadPB::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; test.run_scripts(scripts).await; } @@ -19,7 +19,7 @@ async fn grid_filter_invalid_condition_panic_test() { let field_rev = test.get_field_rev(FieldType::RichText).clone(); // 100 is not a valid condition, so this test should be panic. - let payload = CreateGridFilterPayloadPB::new(&field_rev, 100, Some("".to_owned())); + let payload = InsertFilterPayloadPB::new(&field_rev, 100, Some("".to_owned())); let scripts = vec![InsertGridTableFilter { payload }]; test.run_scripts(scripts).await; } @@ -46,6 +46,6 @@ async fn grid_filter_delete_test() { #[tokio::test] async fn grid_filter_get_rows_test() {} -fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> CreateGridFilterPayloadPB { - CreateGridFilterPayloadPB::new(field_rev, condition, Some(s.to_owned())) +fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> InsertFilterPayloadPB { + InsertFilterPayloadPB::new(field_rev, condition, Some(s.to_owned())) } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs index 78cfc17b26..2bbf5874aa 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs @@ -85,7 +85,7 @@ impl GridEditorTest { .row_revs } - pub async fn grid_filters(&self) -> Vec { + pub async fn grid_filters(&self) -> Vec { self.editor.get_grid_filter().await.unwrap() } @@ -195,6 +195,8 @@ fn make_test_grid() -> BuildGridContext { FieldType::SingleSelect => { row_builder.insert_single_select_cell(|mut options| options.remove(0)) } + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), _ => "".to_owned(), }; @@ -209,6 +211,8 @@ fn make_test_grid() -> BuildGridContext { FieldType::SingleSelect => { row_builder.insert_single_select_cell(|mut options| options.remove(0)) } + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), _ => "".to_owned(), }; @@ -223,6 +227,9 @@ fn make_test_grid() -> BuildGridContext { FieldType::SingleSelect => { row_builder.insert_single_select_cell(|mut options| options.remove(1)) } + FieldType::MultiSelect => { + row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)]) + } FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), _ => "".to_owned(), }; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index 11866b7d1c..42e4e860e0 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -8,7 +8,7 @@ use flowy_grid::services::field::{ use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder}; use flowy_grid_data_model::entities::{ CellChangeset, FieldChangesetParams, FieldType, GridBlockInfoChangeset, GridBlockMetaSnapshot, RowMetaChangeset, - TypeOptionDataEntry, + TypeOptionDataFormat, }; #[tokio::test] 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 6afeda6d4b..ce6044c39e 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,11 +1,13 @@ use crate::grid::grid_editor::GridEditorTest; use flowy_grid::entities::{ - CreateRowParams, FieldChangesetParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB, + CreateRowParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB, }; use flowy_grid::services::cell::{delete_select_option_cell, insert_select_option_cell}; -use flowy_grid_data_model::revision::RowChangeset; -use std::time::Duration; -use tokio::time::interval; +use flowy_grid::services::field::{ + edit_single_select_type_option, SelectOptionOperation, SelectOptionPB, SingleSelectTypeOptionPB, +}; +use flowy_grid_data_model::revision::{FieldRevision, RowChangeset}; +use std::sync::Arc; pub enum GroupScript { AssertGroupRowCount { @@ -44,8 +46,11 @@ pub enum GroupScript { from_group_index: usize, to_group_index: usize, }, - UpdateField { - changeset: FieldChangesetParams, + UpdateSingleSelectOption { + inserted_options: Vec, + }, + GroupByField { + field_id: String, }, } @@ -174,10 +179,16 @@ impl GridGroupTest { 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; + GroupScript::UpdateSingleSelectOption { inserted_options } => { + self.edit_single_select_type_option(|type_option| { + for inserted_option in inserted_options { + type_option.insert_option(inserted_option); + } + }) + .await; + } + GroupScript::GroupByField { field_id } => { + self.editor.group_by_field(&field_id).await.unwrap(); } } } @@ -191,6 +202,40 @@ impl GridGroupTest { let groups = self.group_at_index(group_index).await; groups.rows.get(row_index).unwrap().clone() } + + #[allow(dead_code)] + pub async fn get_multi_select_field(&self) -> Arc { + let field = self + .inner + .field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.is_multi_select() + }) + .unwrap() + .clone(); + field + } + + pub async fn get_single_select_field(&self) -> Arc { + self.inner + .field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.is_single_select() + }) + .unwrap() + .clone() + } + + pub async fn edit_single_select_type_option(&self, action: impl FnOnce(&mut SingleSelectTypeOptionPB)) { + let single_select = self.get_single_select_field().await; + edit_single_select_type_option(&single_select.id, self.editor.clone(), action) + .await + .unwrap(); + } } impl std::ops::Deref for GridGroupTest { 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 a52a088f51..5926f8c022 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,6 +1,7 @@ use crate::grid::group_test::script::GridGroupTest; use crate::grid::group_test::script::GroupScript::*; -use flowy_grid::entities::FieldChangesetParams; + +use flowy_grid::services::field::SelectOptionPB; #[tokio::test] async fn group_init_test() { @@ -370,23 +371,41 @@ async fn group_move_group_test() { } #[tokio::test] -async fn group_update_field_test() { +async fn group_insert_single_select_option_test() { let mut test = GridGroupTest::new().await; - let 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 new_option_name = "New option"; let scripts = vec![ - UpdateField { changeset }, - AssertGroup { - group_index: 0, - expected_group: group, + AssertGroupCount(4), + UpdateSingleSelectOption { + inserted_options: vec![SelectOptionPB::new(new_option_name)], }, + AssertGroupCount(5), + ]; + test.run_scripts(scripts).await; + + // the group at index 4 is the default_group, so the new insert group will be the + // index 3. + let group_3 = test.group_at_index(3).await; + assert_eq!(group_3.desc, new_option_name); +} + +#[tokio::test] +async fn group_group_by_other_field() { + let mut test = GridGroupTest::new().await; + let multi_select_field = test.get_multi_select_field().await; + let scripts = vec![ + GroupByField { + field_id: multi_select_field.id.clone(), + }, + AssertGroupRowCount { + group_index: 0, + row_count: 3, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + AssertGroupCount(4), ]; test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index c9ea583a14..eb65e2a7c8 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -5,7 +5,7 @@ use flowy_grid::services::row::CreateRowMetaPayload; use flowy_grid_data_model::entities::{ BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset, RowOrder, - TypeOptionDataEntry, + TypeOptionDataFormat, }; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; use flowy_sync::client_grid::GridBuilder; 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 fedac5c591..90187d9b7b 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 @@ -143,15 +143,15 @@ impl FieldRevision { } } - pub fn insert_type_option_entry(&mut self, entry: &T) + pub fn insert_type_option(&mut self, type_option: &T) where - T: TypeOptionDataEntry + ?Sized, + T: TypeOptionDataFormat + ?Sized, { let id = self.ty.to_string(); - self.type_options.insert(id, entry.json_str()); + self.type_options.insert(id, type_option.json_str()); } - pub fn get_type_option_entry(&self, field_type_rev: FieldTypeRevision) -> Option { + pub fn get_type_option(&self, field_type_rev: FieldTypeRevision) -> Option { let id = field_type_rev.to_string(); // TODO: cache the deserialized type option self.type_options.get(&id).map(|s| T::from_json_str(s)) @@ -171,7 +171,7 @@ impl FieldRevision { /// The macro [impl_type_option] will implement the [TypeOptionDataEntry] for the type that /// supports the serde trait and the TryInto trait. -pub trait TypeOptionDataEntry { +pub trait TypeOptionDataFormat { fn json_str(&self) -> String; fn protobuf_bytes(&self) -> Bytes; } diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs index 1f52ae93e2..996b5abcba 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs @@ -60,7 +60,7 @@ where .cloned() } - pub fn get_all_objects(&self, field_revs: &[Arc]) -> Option>>> { + pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> Option>>> { // Get the objects according to the FieldType, so we need iterate the field_revs. let objects_by_field_id = field_revs .iter() @@ -76,6 +76,10 @@ where Some(objects_by_field_id) } + pub fn get_all_objects(&self) -> Vec> { + self.inner.values().map(|map| map.all_objects()).flatten().collect() + } + /// add object to the end of the list pub fn add_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) { let object_rev_map = self @@ -111,6 +115,10 @@ where pub fn new() -> Self { ObjectIndexMap::default() } + + pub fn all_objects(&self) -> Vec> { + self.object_by_field_type.values().cloned().flatten().collect() + } } impl std::ops::Deref for ObjectIndexMap 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 04b571fd76..0aded0d3c7 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 @@ -4,9 +4,8 @@ use serde_json::Error; use serde_repr::*; pub trait GroupConfigurationContentSerde: Sized + Send + Sync { - fn from_configuration_content(s: &str) -> Result; - - fn to_configuration_content(&self) -> Result; + fn from_json(s: &str) -> Result; + fn to_json(&self) -> Result; } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -15,6 +14,7 @@ pub struct GroupConfigurationRevision { pub field_id: String, pub field_type_rev: FieldTypeRevision, pub groups: Vec, + // This content is serde in Json format pub content: String, } @@ -23,7 +23,7 @@ impl GroupConfigurationRevision { where T: GroupConfigurationContentSerde, { - let content = content.to_configuration_content()?; + let content = content.to_json()?; Ok(Self { id: gen_grid_group_id(), field_id, @@ -40,10 +40,10 @@ pub struct TextGroupConfigurationRevision { } impl GroupConfigurationContentSerde for TextGroupConfigurationRevision { - fn from_configuration_content(s: &str) -> Result { + fn from_json(s: &str) -> Result { serde_json::from_str(s) } - fn to_configuration_content(&self) -> Result { + fn to_json(&self) -> Result { serde_json::to_string(self) } } @@ -54,10 +54,10 @@ pub struct NumberGroupConfigurationRevision { } impl GroupConfigurationContentSerde for NumberGroupConfigurationRevision { - fn from_configuration_content(s: &str) -> Result { + fn from_json(s: &str) -> Result { serde_json::from_str(s) } - fn to_configuration_content(&self) -> Result { + fn to_json(&self) -> Result { serde_json::to_string(self) } } @@ -68,10 +68,10 @@ pub struct UrlGroupConfigurationRevision { } impl GroupConfigurationContentSerde for UrlGroupConfigurationRevision { - fn from_configuration_content(s: &str) -> Result { + fn from_json(s: &str) -> Result { serde_json::from_str(s) } - fn to_configuration_content(&self) -> Result { + fn to_json(&self) -> Result { serde_json::to_string(self) } } @@ -82,11 +82,11 @@ pub struct CheckboxGroupConfigurationRevision { } impl GroupConfigurationContentSerde for CheckboxGroupConfigurationRevision { - fn from_configuration_content(s: &str) -> Result { + fn from_json(s: &str) -> Result { serde_json::from_str(s) } - fn to_configuration_content(&self) -> Result { + fn to_json(&self) -> Result { serde_json::to_string(self) } } @@ -97,11 +97,11 @@ pub struct SelectOptionGroupConfigurationRevision { } impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision { - fn from_configuration_content(s: &str) -> Result { + fn from_json(s: &str) -> Result { serde_json::from_str(s) } - fn to_configuration_content(&self) -> Result { + fn to_json(&self) -> Result { serde_json::to_string(self) } } @@ -113,22 +113,17 @@ pub struct GroupRevision { #[serde(default)] pub name: String, - #[serde(skip, default = "IS_DEFAULT_GROUP")] - pub is_default: bool, - #[serde(default = "GROUP_REV_VISIBILITY")] pub visible: bool, } const GROUP_REV_VISIBILITY: fn() -> bool = || true; -const IS_DEFAULT_GROUP: fn() -> bool = || false; impl GroupRevision { pub fn new(id: String, group_name: String) -> Self { Self { id, name: group_name, - is_default: false, visible: true, } } @@ -137,10 +132,13 @@ impl GroupRevision { Self { id, name: group_name, - is_default: true, visible: true, } } + + pub fn update_with_other(&mut self, other: &GroupRevision) { + self.visible = other.visible + } } #[derive(Default, Serialize, Deserialize)] @@ -150,10 +148,10 @@ pub struct DateGroupConfigurationRevision { } impl GroupConfigurationContentSerde for DateGroupConfigurationRevision { - fn from_configuration_content(s: &str) -> Result { + fn from_json(s: &str) -> Result { serde_json::from_str(s) } - fn to_configuration_content(&self) -> Result { + fn to_json(&self) -> Result { serde_json::to_string(self) } } diff --git a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs index ddc9c6e05a..45989f5b1c 100644 --- a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs @@ -48,8 +48,12 @@ impl GridViewRevisionPad { Self::from_delta(delta) } - pub fn get_all_groups(&self, field_revs: &[Arc]) -> Option { - self.groups.get_all_objects(field_revs) + pub fn get_groups_by_field_revs(&self, field_revs: &[Arc]) -> Option { + self.groups.get_objects_by_field_revs(field_revs) + } + + pub fn get_all_groups(&self) -> Vec> { + self.groups.get_all_objects() } #[tracing::instrument(level = "trace", skip_all, err)] @@ -57,12 +61,12 @@ impl GridViewRevisionPad { &mut self, field_id: &str, field_type: &FieldTypeRevision, - group_rev: GroupConfigurationRevision, + group_configuration_rev: GroupConfigurationRevision, ) -> CollaborateResult> { self.modify(|view| { // Only save one group view.groups.clear(); - view.groups.add_object(field_id, field_type, group_rev); + view.groups.add_object(field_id, field_type, group_configuration_rev); Ok(Some(())) }) } @@ -111,7 +115,7 @@ impl GridViewRevisionPad { } pub fn get_all_filters(&self, field_revs: &[Arc]) -> Option { - self.filters.get_all_objects(field_revs) + self.filters.get_objects_by_field_revs(field_revs) } pub fn get_filters( diff --git a/shared-lib/lib-ot/src/core/document/document_operation.rs b/shared-lib/lib-ot/src/core/document/document_operation.rs index 4d9d3617eb..29bfdcc04f 100644 --- a/shared-lib/lib-ot/src/core/document/document_operation.rs +++ b/shared-lib/lib-ot/src/core/document/document_operation.rs @@ -178,7 +178,7 @@ mod tests { node_type: "text".into(), attributes: NodeAttributes::new(), delta: None, - children: vec![Box::new(NodeSubTree::new("text".into()))], + children: vec![Box::new(NodeSubTree::new("text"))], })], }; let result = serde_json::to_string(&insert).unwrap(); diff --git a/shared-lib/lib-ot/src/core/document/mod.rs b/shared-lib/lib-ot/src/core/document/mod.rs index b019cb0f71..2dde3ef3e9 100644 --- a/shared-lib/lib-ot/src/core/document/mod.rs +++ b/shared-lib/lib-ot/src/core/document/mod.rs @@ -1,3 +1,4 @@ +#![allow(clippy::module_inception)] mod attributes; mod document; mod document_operation; diff --git a/shared-lib/lib-ot/tests/main.rs b/shared-lib/lib-ot/tests/main.rs index 31e7748b3a..ae7080656e 100644 --- a/shared-lib/lib-ot/tests/main.rs +++ b/shared-lib/lib-ot/tests/main.rs @@ -13,7 +13,7 @@ fn test_documents() { let mut document = DocumentTree::new(); let transaction = { let mut tb = TransactionBuilder::new(&document); - tb.insert_nodes_at_path(&vec![0].into(), &vec![Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![0].into(), &[Box::new(NodeSubTree::new("text"))]); tb.finalize() }; document.apply(transaction).unwrap(); @@ -47,16 +47,16 @@ fn test_inserts_nodes() { let mut document = DocumentTree::new(); let transaction = { let mut tb = TransactionBuilder::new(&document); - tb.insert_nodes_at_path(&vec![0].into(), &vec![Box::new(NodeSubTree::new("text"))]); - tb.insert_nodes_at_path(&vec![1].into(), &vec![Box::new(NodeSubTree::new("text"))]); - tb.insert_nodes_at_path(&vec![2].into(), &vec![Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![0].into(), &[Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![1].into(), &[Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![2].into(), &[Box::new(NodeSubTree::new("text"))]); tb.finalize() }; document.apply(transaction).unwrap(); let transaction = { let mut tb = TransactionBuilder::new(&document); - tb.insert_nodes_at_path(&vec![1].into(), &vec![Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![1].into(), &[Box::new(NodeSubTree::new("text"))]); tb.finalize() }; document.apply(transaction).unwrap(); @@ -69,11 +69,11 @@ fn test_inserts_subtrees() { let mut tb = TransactionBuilder::new(&document); tb.insert_nodes_at_path( &vec![0].into(), - &vec![Box::new(NodeSubTree { + &[Box::new(NodeSubTree { node_type: "text".into(), attributes: NodeAttributes::new(), delta: None, - children: vec![Box::new(NodeSubTree::new("image".into()))], + children: vec![Box::new(NodeSubTree::new("image"))], })], ); tb.finalize() @@ -90,9 +90,9 @@ fn test_update_nodes() { let mut document = DocumentTree::new(); let transaction = { let mut tb = TransactionBuilder::new(&document); - tb.insert_nodes_at_path(&vec![0].into(), &vec![Box::new(NodeSubTree::new("text"))]); - tb.insert_nodes_at_path(&vec![1].into(), &vec![Box::new(NodeSubTree::new("text"))]); - tb.insert_nodes_at_path(&vec![2].into(), &vec![Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![0].into(), &[Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![1].into(), &[Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![2].into(), &[Box::new(NodeSubTree::new("text"))]); tb.finalize() }; document.apply(transaction).unwrap(); @@ -115,9 +115,9 @@ fn test_delete_nodes() { let mut document = DocumentTree::new(); let transaction = { let mut tb = TransactionBuilder::new(&document); - tb.insert_nodes_at_path(&vec![0].into(), &vec![Box::new(NodeSubTree::new("text"))]); - tb.insert_nodes_at_path(&vec![1].into(), &vec![Box::new(NodeSubTree::new("text"))]); - tb.insert_nodes_at_path(&vec![2].into(), &vec![Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![0].into(), &[Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![1].into(), &[Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![2].into(), &[Box::new(NodeSubTree::new("text"))]); tb.finalize() }; document.apply(transaction).unwrap(); @@ -138,8 +138,8 @@ fn test_errors() { let mut document = DocumentTree::new(); let transaction = { let mut tb = TransactionBuilder::new(&document); - tb.insert_nodes_at_path(&vec![0].into(), &vec![Box::new(NodeSubTree::new("text"))]); - tb.insert_nodes_at_path(&vec![100].into(), &vec![Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![0].into(), &[Box::new(NodeSubTree::new("text"))]); + tb.insert_nodes_at_path(&vec![100].into(), &[Box::new(NodeSubTree::new("text"))]); tb.finalize() }; let result = document.apply(transaction);