Merge pull request #977 from AppFlowy-IO/feat/support_switch_field

Feat/support grouping by different field
This commit is contained in:
Nathan.fooo 2022-09-04 16:13:58 +08:00 committed by GitHub
commit 60c8ec13eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 1974 additions and 1241 deletions

View File

@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 2H13C13.5523 2 14 2.44772 14 3V6" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 2H3C2.44772 2 2 2.44772 2 3V6" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 14H3C2.44772 14 2 13.5523 2 13V10" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 14H13C13.5523 14 14 13.5523 14 13V10" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="6" y="6" width="4" height="4" rx="1" stroke="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 620 B

View File

@ -160,7 +160,8 @@
"settings": {
"filter": "Filter",
"sortBy": "Sort by",
"Properties": "Properties"
"Properties": "Properties",
"group": "Group"
},
"field": {
"hide": "Hide",

View File

@ -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<BoardEvent, BoardState> {
final MoveRowFFIService _rowService;
LinkedHashMap<String, GroupController> 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<BoardEvent, BoardState> {
emit(state.copyWith(noneOrError: some(error)));
},
didReceiveGroups: (List<GroupPB> 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<BoardEvent, BoardState> {
}
void initializeGroups(List<GroupPB> groups) {
for (var controller in groupControllers.values) {
controller.dispose();
}
groupControllers.clear();
boardController.clear();
//
List<AFBoardColumnData> 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<BoardEvent, BoardState> {
}
},
didLoadGroups: (groups) {
List<AFBoardColumnData> 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));
},
);
}

View File

@ -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<FieldPB>);
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldContext>);
typedef OnGridChanged = void Function(GridPB);
typedef DidLoadGroups = void Function(List<GroupPB>);
typedef OnUpdatedGroup = void Function(List<GroupPB>);
typedef OnDeletedGroup = void Function(List<String>);
typedef OnInsertedGroup = void Function(List<InsertedGroupPB>);
typedef OnResetGroups = void Function(List<GroupPB>);
typedef OnRowsChanged = void Function(
List<RowInfo>,
@ -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<Either<Unit, FlowyError>> 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<void> dispose() async {
await _gridFFIService.closeGrid();
await fieldCache.dispose();
await fieldController.dispose();
for (final blockCache in _blocks.values) {
blockCache.dispose();
}
}
Future<Either<Unit, FlowyError>> _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<void> _loadGroups(List<BlockPB> blocks) async {
for (final block in blocks) {
final cache = GridBlockCache(
gridId: gridId,
block: block,
fieldCache: fieldCache,
fieldController: fieldController,
);
cache.addListener(onRowsChanged: (reason) {

View File

@ -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<GroupViewChangesetPB, FlowyError>;
typedef GroupUpdateValue = Either<GroupViewChangesetPB, FlowyError>;
typedef GroupByNewFieldValue = Either<List<GroupPB>, FlowyError>;
class BoardListener {
final String viewId;
PublishNotifier<UpdateBoardNotifiedValue>? _groupNotifier = PublishNotifier();
PublishNotifier<GroupUpdateValue>? _groupUpdateNotifier = PublishNotifier();
PublishNotifier<GroupByNewFieldValue>? _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<void> stop() async {
await _listener?.stop();
_groupNotifier?.dispose();
_groupNotifier = null;
_groupUpdateNotifier?.dispose();
_groupUpdateNotifier = null;
_groupByNewFieldNotifier?.dispose();
_groupByNewFieldNotifier = null;
}
}

View File

@ -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<BoardDateCellEvent, BoardDateCellState> {
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),
);

View File

@ -59,7 +59,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
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<Object?> 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,
];
}

View File

@ -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<VoidCallback> _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

View File

@ -43,4 +43,5 @@ class BoardSettingState with _$BoardSettingState {
enum BoardSettingAction {
properties,
groups,
}

View File

@ -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<BoardContent> {
return BlocListener<BoardBloc, BoardState>(
listener: (context, state) => _handleEditState(state, context),
child: BlocBuilder<BoardBloc, BoardState>(
buildWhen: (previous, current) =>
previous.groupIds.length != current.groupIds.length,
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
builder: (context, state) {
final theme = context.read<AppTheme>();
return Container(
@ -95,6 +94,7 @@ class _BoardContentState extends State<BoardContent> {
const _ToolbarBlocAdaptor(),
Expanded(
child: AFBoard(
key: UniqueKey(),
scrollManager: scrollManager,
scrollController: scrollController,
dataController: context.read<BoardBloc>().boardController,
@ -222,10 +222,10 @@ class _BoardContentState extends State<BoardContent> {
/// Return placeholder widget if the rowCache is null.
if (rowCache == null) return SizedBox(key: ObjectKey(columnItem));
final fieldCache = context.read<BoardBloc>().fieldCache;
final fieldController = context.read<BoardBloc>().fieldController;
final gridId = context.read<BoardBloc>().gridId;
final cardController = CardDataController(
fieldCache: fieldCache,
fieldController: fieldController,
rowCache: rowCache,
rowPB: rowPB,
);
@ -252,7 +252,7 @@ class _BoardContentState extends State<BoardContent> {
dataController: cardController,
openCard: (context) => _openCard(
gridId,
fieldCache,
fieldController,
rowPB,
rowCache,
context,
@ -271,17 +271,17 @@ class _BoardContentState extends State<BoardContent> {
);
}
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<BoardBloc>();
final toolbarContext = BoardToolbarContext(
viewId: bloc.gridId,
fieldCache: bloc.fieldCache,
fieldController: bloc.fieldController,
);
return BoardToolbar(toolbarContext: toolbarContext);

View File

@ -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();
}
}
}

View File

@ -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,
});
}

View File

@ -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);

View File

@ -148,10 +148,10 @@ class IGridCellController<T, D> 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<T, D> clone() {
return IGridCellController(
@ -166,11 +166,11 @@ class IGridCellController<T, D> 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<T, D> 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<T, D> extends Equatable {
@override
List<Object> 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() {

View File

@ -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}");
}
}

View File

@ -176,7 +176,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
final result = await FieldService.updateFieldTypeOption(
gridId: cellController.gridId,
fieldId: cellController.field.id,
fieldId: cellController.fieldContext.id,
typeOptionData: newDateTypeOption.writeToBuffer(),
);

View File

@ -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<DateCellEvent, DateCellState> {
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),
);

View File

@ -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<Either<Unit, FlowyError>> create({required String name}) {

View File

@ -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<FieldPB> _fields = [];
set fields(List<FieldPB> fields) {
_fields = fields;
notifyListeners();
}
List<FieldPB> get fields => _fields;
}
typedef FieldChangesetCallback = void Function(FieldChangesetPB);
typedef FieldsCallback = void Function(List<FieldPB>);
class GridFieldCache {
final String gridId;
final GridFieldsListener _fieldListener;
FieldsNotifier? _fieldNotifier = FieldsNotifier();
final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
final Map<FieldChangesetCallback, FieldChangesetCallback>
_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<void> dispose() async {
await _fieldListener.stop();
_fieldNotifier?.dispose();
_fieldNotifier = null;
}
UnmodifiableListView<FieldPB> get unmodifiableFields =>
UnmodifiableListView(_fieldNotifier?.fields ?? []);
List<FieldPB> get fields => [..._fieldNotifier?.fields ?? []];
set fields(List<FieldPB> 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<FieldIdPB> deletedFields) {
if (deletedFields.isEmpty) {
return;
}
final List<FieldPB> newFields = fields;
final Map<String, FieldIdPB> deletedFieldMap = {
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
};
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
_fieldNotifier?.fields = newFields;
}
void _insertFields(List<IndexFieldPB> insertedFields) {
if (insertedFields.isEmpty) {
return;
}
final List<FieldPB> 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<FieldPB> updatedFields) {
if (updatedFields.isEmpty) {
return;
}
final List<FieldPB> 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<FieldPB> 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;
}
}
}

View File

@ -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<GridFieldContext> _fieldContexts = [];
set fieldContexts(List<GridFieldContext> fieldContexts) {
_fieldContexts = fieldContexts;
notifyListeners();
}
void notify() {
notifyListeners();
}
List<GridFieldContext> get fieldContexts => _fieldContexts;
}
typedef OnChangeset = void Function(FieldChangesetPB);
typedef OnReceiveFields = void Function(List<GridFieldContext>);
class GridFieldController {
final String gridId;
final GridFieldsListener _fieldListener;
final SettingListener _settingListener;
final Map<OnReceiveFields, VoidCallback> _fieldCallbackMap = {};
final Map<OnChangeset, OnChangeset> _changesetCallbackMap = {};
_GridFieldNotifier? _fieldNotifier = _GridFieldNotifier();
List<String> _groupFieldIds = [];
final GridFFIService _gridFFIService;
final SettingFFIService _settingFFIService;
List<GridFieldContext> 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<void> dispose() async {
await _fieldListener.stop();
_fieldNotifier?.dispose();
_fieldNotifier = null;
}
Future<Either<Unit, FlowyError>> loadFields(
{required List<FieldIdPB> 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<FieldIdPB> deletedFields) {
if (deletedFields.isEmpty) {
return;
}
final List<GridFieldContext> newFields = fieldContexts;
final Map<String, FieldIdPB> deletedFieldMap = {
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
};
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
_fieldNotifier?.fieldContexts = newFields;
}
void _insertFields(List<IndexFieldPB> insertedFields) {
if (insertedFields.isEmpty) {
return;
}
final List<GridFieldContext> 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<FieldPB> updatedFields) {
if (updatedFields.isEmpty) {
return;
}
final List<GridFieldContext> 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<GridFieldContext> 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;
}

View File

@ -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),

View File

@ -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;
}
}

View File

@ -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<FieldPB> fields,
UnmodifiableListView<GridFieldContext> fields,
) = _DidReceiveFieldUpdate;
const factory GridEvent.didReceiveGridUpdate(
@ -138,9 +139,9 @@ class GridLoadingState with _$GridLoadingState {
}
class GridFieldEquatable extends Equatable {
final UnmodifiableListView<FieldPB> _fields;
final UnmodifiableListView<GridFieldContext> _fields;
const GridFieldEquatable(
UnmodifiableListView<FieldPB> fields,
UnmodifiableListView<GridFieldContext> fields,
) : _fields = fields;
@override
@ -157,5 +158,6 @@ class GridFieldEquatable extends Equatable {
];
}
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
UnmodifiableListView<GridFieldContext> get value =>
UnmodifiableListView(_fields);
}

View File

@ -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<FieldPB>);
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldContext>);
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<String, GridBlockCache> _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<void> 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<Either<Unit, FlowyError>> _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),
),
);
}
}

View File

@ -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<GridHeaderEvent, GridHeaderState> {
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<GridHeaderEvent>(
(event, emit) async {
await event.map(
@ -36,7 +35,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
Future<void> _moveField(
_MoveField value, Emitter<GridHeaderState> emit) async {
final fields = List<FieldPB>.from(state.fields);
final fields = List<GridFieldContext>.from(state.fields);
fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
emit(state.copyWith(fields: fields));
@ -49,7 +48,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
}
Future<void> _startListening() async {
fieldCache.addListener(
fieldController.addListener(
onFields: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed,
);
@ -64,18 +63,18 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
@freezed
class GridHeaderEvent with _$GridHeaderEvent {
const factory GridHeaderEvent.initial() = _InitialHeader;
const factory GridHeaderEvent.didReceiveFieldUpdate(List<FieldPB> fields) =
_DidReceiveFieldUpdate;
const factory GridHeaderEvent.didReceiveFieldUpdate(
List<GridFieldContext> fields) = _DidReceiveFieldUpdate;
const factory GridHeaderEvent.moveField(
FieldPB field, int fromIndex, int toIndex) = _MoveField;
}
@freezed
class GridHeaderState with _$GridHeaderState {
const factory GridHeaderState({required List<FieldPB> fields}) =
const factory GridHeaderState({required List<GridFieldContext> fields}) =
_GridHeaderState;
factory GridHeaderState.initial(List<FieldPB> fields) {
factory GridHeaderState.initial(List<GridFieldContext> fields) {
// final List<FieldPB> newFields = List.from(fields);
// newFields.retainWhere((field) => field.visibility);
return GridHeaderState(fields: fields);

View File

@ -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<RowEvent, RowState> {
},
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<Object?> get props => [
_field.id,
_field.fieldType,
_field.visibility,
_field.width,
_fieldContext.id,
_fieldContext.fieldType,
_fieldContext.visibility,
_fieldContext.width,
];
}

View File

@ -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<FieldPB> get fields;
UnmodifiableListView<GridFieldContext> 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<FieldPB> fields,
required UnmodifiableListView<GridFieldContext> fields,
required RowPB rowPB,
}) = _RowInfo;
}

View File

@ -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<VoidCallback> _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

View File

@ -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<GridGroupEvent, GridGroupState> {
final GridFieldController _fieldController;
final SettingFFIService _settingFFIService;
Function(List<GridFieldContext>)? _onFieldsFn;
GridGroupBloc(
{required String viewId, required GridFieldController fieldController})
: _fieldController = fieldController,
_settingFFIService = SettingFFIService(viewId: viewId),
super(GridGroupState.initial(viewId, fieldController.fieldContexts)) {
on<GridGroupEvent>(
(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<void> 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<GridFieldContext> fields) = _DidReceiveFieldUpdate;
}
@freezed
class GridGroupState with _$GridGroupState {
const factory GridGroupState({
required String gridId,
required List<GridFieldContext> fieldContexts,
}) = _GridGroupState;
factory GridGroupState.initial(
String gridId, List<GridFieldContext> fieldContexts) =>
GridGroupState(
gridId: gridId,
fieldContexts: fieldContexts,
);
}

View File

@ -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<GridPropertyEvent, GridPropertyState> {
final GridFieldCache _fieldCache;
Function(List<FieldPB>)? _onFieldsFn;
final GridFieldController _fieldController;
Function(List<GridFieldContext>)? _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<GridPropertyEvent>(
(event, emit) async {
await event.map(
@ -33,7 +34,7 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
);
},
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<GridPropertyEvent, GridPropertyState> {
@override
Future<void> 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<GridPropertyEvent, GridPropertyState> {
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<FieldPB> fields) =
_DidReceiveFieldUpdate;
const factory GridPropertyEvent.didReceiveFieldUpdate(
List<GridFieldContext> 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<FieldPB> fields,
required List<GridFieldContext> fieldContexts,
}) = _GridPropertyState;
factory GridPropertyState.initial(String gridId, List<FieldPB> fields) =>
factory GridPropertyState.initial(
String gridId,
List<GridFieldContext> fieldContexts,
) =>
GridPropertyState(
gridId: gridId,
fields: fields,
fieldContexts: fieldContexts,
);
}

View File

@ -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();
}
}

View File

@ -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<GridSettingPB, FlowyError>;
class SettingListener {
final String gridId;
GridNotificationListener? _listener;
PublishNotifier<UpdateSettingNotifiedValue>? _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<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridSetting:
result.fold(
(payload) => _updateSettingNotifier?.value = left(
GridSettingPB.fromBuffer(payload),
),
(error) => _updateSettingNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_updateSettingNotifier?.dispose();
_updateSettingNotifier = null;
}
}

View File

@ -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<Either<GridSettingPB, FlowyError>> getSetting() {
final payload = GridIdPB.create()..value = viewId;
return GridEventGetGridSetting(payload).send();
}
Future<Either<Unit, FlowyError>> 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();
}
}

View File

@ -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<FlowyGrid> {
}
Widget _gridHeader(BuildContext context, String gridId) {
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
final fieldController =
context.read<GridBloc>().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<GridBloc, GridState, GridToolbarContext>(
selector: (state) {
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
final fieldController =
context.read<GridBloc>().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<GridBloc>().dataController.fieldCache;
final fieldController =
context.read<GridBloc>().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,
);

View File

@ -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<FieldPB> fields) {
static double headerWidth(List<GridFieldContext> fields) {
if (fields.isEmpty) return 0;
final fieldsWidth = fields

View File

@ -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<GridHeaderSliverAdaptor> {
return BlocProvider(
create: (context) {
final bloc = getIt<GridHeaderBloc>(
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();

View File

@ -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<T> makeTypeOptionContext<T extends GeneratedMessage>({
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,
);
}

View File

@ -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<RegionStateNotifier>(context, listen: false),
accessoryBuilder: (buildContext) {
final builder = child.accessoryBuilder;
List<GridCellAccessory> accessories = [];
if (cellId.field.isPrimary) {
if (cellId.fieldContext.isPrimary) {
accessories.add(PrimaryCellAccessory(
onTapCallback: onExpand,
isCellEditing: buildContext.isCellEditing,

View File

@ -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);
}

View File

@ -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<GridGroupBloc, GridGroupState>(
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<AppTheme>();
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<GridGroupBloc>().add(
GridGroupEvent.setGroupByField(
fieldContext.id,
fieldContext.fieldType,
),
);
FlowyOverlay.of(context).remove(GridGroupList.identifier());
},
),
);
}
}

View File

@ -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<GridPropertyBloc>(param1: gridId, param2: fieldCache)
getIt<GridPropertyBloc>(param1: gridId, param2: fieldController)
..add(const GridPropertyEvent.initial()),
child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
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<AppTheme>();
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<GridPropertyBloc>().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);
},
);

View File

@ -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;
}

View File

@ -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,

View File

@ -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<void> resolve(GetIt getIt) async {
@ -154,10 +154,10 @@ void _resolveGridDeps(GetIt getIt) {
(view, _) => GridBloc(view: view),
);
getIt.registerFactoryParam<GridHeaderBloc, String, GridFieldCache>(
(gridId, fieldCache) => GridHeaderBloc(
getIt.registerFactoryParam<GridHeaderBloc, String, GridFieldController>(
(gridId, fieldController) => GridHeaderBloc(
gridId: gridId,
fieldCache: fieldCache,
fieldController: fieldController,
),
);
@ -200,7 +200,7 @@ void _resolveGridDeps(GetIt getIt) {
),
);
getIt.registerFactoryParam<GridPropertyBloc, String, GridFieldCache>(
(gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache),
getIt.registerFactoryParam<GridPropertyBloc, String, GridFieldController>(
(gridId, cache) => GridPropertyBloc(gridId: gridId, fieldController: cache),
);
}

View File

@ -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) {

View File

@ -13,6 +13,8 @@ pub enum GridNotification {
DidUpdateField = 50,
DidUpdateGroupView = 60,
DidUpdateGroup = 61,
DidGroupByNewField = 62,
DidUpdateGridSetting = 70,
}
impl std::default::Default for GridNotification {

View File

@ -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<GridFilterConfiguration>,
pub items: Vec<GridFilterConfigurationPB>,
}
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<Vec<Arc<FilterConfigurationRevision>>> for RepeatedGridConfigurationFilterPB {
impl std::convert::From<Vec<Arc<FilterConfigurationRevision>>> for RepeatedGridFilterConfigurationPB {
fn from(revs: Vec<Arc<FilterConfigurationRevision>>) -> Self {
RepeatedGridConfigurationFilterPB {
RepeatedGridFilterConfigurationPB {
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
}
}
}
impl std::convert::From<Vec<GridFilterConfiguration>> for RepeatedGridConfigurationFilterPB {
fn from(items: Vec<GridFilterConfiguration>) -> Self {
impl std::convert::From<Vec<GridFilterConfigurationPB>> for RepeatedGridFilterConfigurationPB {
fn from(items: Vec<GridFilterConfigurationPB>) -> 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<String>,
}
impl CreateGridFilterPayloadPB {
impl InsertFilterPayloadPB {
#[allow(dead_code)]
pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
Self {
@ -104,10 +104,10 @@ impl CreateGridFilterPayloadPB {
}
}
impl TryInto<CreateFilterParams> for CreateGridFilterPayloadPB {
impl TryInto<InsertFilterParams> for InsertFilterPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateFilterParams, Self::Error> {
fn try_into(self) -> Result<InsertFilterParams, Self::Error> {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
@ -130,7 +130,7 @@ impl TryInto<CreateFilterParams> for CreateGridFilterPayloadPB {
}
}
Ok(CreateFilterParams {
Ok(InsertFilterParams {
field_id,
field_type_rev: self.field_type.into(),
condition,
@ -139,7 +139,7 @@ impl TryInto<CreateFilterParams> for CreateGridFilterPayloadPB {
}
}
pub struct CreateFilterParams {
pub struct InsertFilterParams {
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub condition: u8,

View File

@ -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<Group> for GroupPB {
@ -101,6 +104,7 @@ impl std::convert::From<Group> 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<Vec<Arc<GroupConfigurationRevision>>> 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<CreatGroupParams> for CreateGridGroupPayloadPB {
impl TryInto<InsertGroupParams> for InsertGroupPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreatGroupParams, Self::Error> {
fn try_into(self) -> Result<InsertGroupParams, Self::Error> {
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,
}

View File

@ -134,15 +134,21 @@ pub struct GroupViewChangesetPB {
pub inserted_groups: Vec<InsertedGroupPB>,
#[pb(index = 3)]
pub deleted_groups: Vec<String>,
pub new_groups: Vec<GroupPB>,
#[pb(index = 4)]
pub deleted_groups: Vec<String>,
#[pb(index = 5)]
pub update_groups: Vec<GroupPB>,
}
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()
}
}

View File

@ -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<GridLayoutPB>,
#[pb(index = 2)]
pub current_layout_type: GridLayout,
pub layout_type: GridLayout,
#[pb(index = 3)]
pub filter_configuration_by_field_id: HashMap<String, RepeatedGridConfigurationFilterPB>,
pub filter_configurations: RepeatedGridFilterConfigurationPB,
#[pb(index = 4)]
pub group_configuration_by_field_id: HashMap<String, RepeatedGridGroupConfigurationPB>,
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<CreateGridFilterPayloadPB>,
pub insert_filter: Option<InsertFilterPayloadPB>,
#[pb(index = 4, one_of)]
pub delete_filter: Option<DeleteFilterPayloadPB>,
#[pb(index = 5, one_of)]
pub insert_group: Option<CreateGridGroupPayloadPB>,
pub insert_group: Option<InsertGroupPayloadPB>,
#[pb(index = 6, one_of)]
pub delete_group: Option<DeleteGroupPayloadPB>,
@ -102,7 +101,7 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
fn try_into(self) -> Result<GridSettingChangesetParams, Self::Error> {
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<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
pub struct GridSettingChangesetParams {
pub grid_id: String,
pub layout_type: LayoutRevision,
pub insert_filter: Option<CreateFilterParams>,
pub insert_filter: Option<InsertFilterParams>,
pub delete_filter: Option<DeleteFilterParams>,
pub insert_group: Option<CreatGroupParams>,
pub insert_group: Option<InsertGroupParams>,
pub delete_group: Option<DeleteGroupParams>,
}

View File

@ -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<GridSettingChangesetPayloadPB>,
manager: AppData<Arc<GridManager>>,
) -> Result<(), FlowyError> {
let params: GridSettingChangesetParams = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(&params.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<QueryBlocksPayloadPB>,
@ -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<Vec<u8>> {
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 {

View File

@ -11,7 +11,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> 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,
}

View File

@ -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<FieldRevision>> for $target {
fn from(field_rev: &std::sync::Arc<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,
}
@ -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,

View File

@ -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::<RichTextTypeOptionPB>(field_type)?
.get_type_option::<RichTextTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::Number => field_rev
.get_type_option_entry::<NumberTypeOptionPB>(field_type)?
.get_type_option::<NumberTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::DateTime => field_rev
.get_type_option_entry::<DateTypeOptionPB>(field_type)?
.get_type_option::<DateTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::SingleSelect => field_rev
.get_type_option_entry::<SingleSelectTypeOptionPB>(field_type)?
.get_type_option::<SingleSelectTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::MultiSelect => field_rev
.get_type_option_entry::<MultiSelectTypeOptionPB>(field_type)?
.get_type_option::<MultiSelectTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::Checkbox => field_rev
.get_type_option_entry::<CheckboxTypeOptionPB>(field_type)?
.get_type_option::<CheckboxTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
FieldType::URL => field_rev
.get_type_option_entry::<URLTypeOptionPB>(field_type)?
.get_type_option::<URLTypeOptionPB>(field_type)?
.decode_cell_data(cell_data.into(), s_field_type, field_rev),
};
Some(data)

View File

@ -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<dyn TypeOptionBuilder> {

View File

@ -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<T>(
field_id: &str,
editor: Arc<GridRevisionEditor>,
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::<T>(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<GridRevisionEditor>,
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<GridRevisionEditor>,
action: impl FnOnce(&mut MultiSelectTypeOptionPB),
) -> FlowyResult<()> {
edit_field_type_option(field_id, editor, action).await
}

View File

@ -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::*;

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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::<RichTextTypeOptionPB>(field_type_rev)?
.get_type_option::<RichTextTypeOptionPB>(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::<NumberTypeOptionPB>(field_type_rev)?
.get_type_option::<NumberTypeOptionPB>(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::<DateTypeOptionPB>(field_type_rev)?
.get_type_option::<DateTypeOptionPB>(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::<SingleSelectTypeOptionPB>(field_type_rev)?
.get_type_option::<SingleSelectTypeOptionPB>(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::<MultiSelectTypeOptionPB>(field_type_rev)?
.get_type_option::<MultiSelectTypeOptionPB>(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::<CheckboxTypeOptionPB>(field_type_rev)?
.get_type_option::<CheckboxTypeOptionPB>(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::<URLTypeOptionPB>(field_type_rev)?
.get_type_option::<URLTypeOptionPB>(field_type_rev)?
.apply_filter(any_cell_data, filter.value())
.ok(),
)

View File

@ -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<Vec<GridFilterConfiguration>> {
pub async fn get_grid_filter(&self) -> FlowyResult<Vec<GridFilterConfigurationPB>> {
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<u8>) -> CollaborateResult<String> {
// 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)
}

View File

@ -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<RevisionManager>,
field_delegate: Arc<dyn GridViewFieldDelegate>,
row_delegate: Arc<dyn GridViewRowDelegate>,
group_service: Arc<RwLock<GroupService>>,
group_controller: Arc<RwLock<Box<dyn GroupController>>>,
scheduler: Arc<dyn GridServiceTaskScheduler>,
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::<GridViewRevisionPadBuilder>(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<String>,
) -> Vec<GroupChangesetPB> {
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<Vec<GroupPB>> {
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(&params.from_group_id, &params.to_group_id)
.await?;
match self.group_service.read().await.get_group(&params.from_group_id).await {
.move_group(&params.from_group_id, &params.to_group_id)?;
match self.group_controller.read().await.get_group(&params.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<GridFilterConfiguration> {
pub(crate) async fn get_filters(&self) -> Vec<GridFilterConfigurationPB> {
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(&params.field_id).await {
let _ = self
.modify(|pad| {
let configuration = default_group_configuration(&field_rev);
let changeset = pad.insert_group(&params.field_id, &params.field_type_rev, configuration)?;
Ok(changeset)
})
.await?;
}
if self.group_controller.read().await.field_id() != params.field_id {
let _ = self.group_by_field(&params.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(&params.field_id, &params.field_type_rev, &params.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(&params.field_id, &params.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<F, T>(&self, f: F) -> Option<T>
where
F: FnOnce(&mut Box<dyn GroupController>, Arc<FieldRevision>) -> FlowyResult<T>,
{
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<F, O, T>(&self, f: F) -> Option<T>
where
F: FnOnce(Arc<RwLock<Box<dyn GroupController>>>, Arc<FieldRevision>) -> O,
O: Future<Output = FlowyResult<T>> + 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<RwLock<GridViewRevisionPad>>,
rev_manager: Arc<RevisionManager>,
field_delegate: Arc<dyn GridViewFieldDelegate>,
row_delegate: Arc<dyn GridViewRowDelegate>,
) -> FlowyResult<Box<dyn GroupController>> {
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<RwLock<GridViewRevisionPad>>,
rev_manager: Arc<RevisionManager>,
field_rev: Arc<FieldRevision>,
row_delegate: Arc<dyn GridViewRowDelegate>,
) -> FlowyResult<Box<dyn GroupController>> {
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<RwLock<GridViewRevisionPad>>);
impl GroupConfigurationReader for GroupConfigurationReaderImpl {
fn get_group_configuration(
&self,
field_rev: Arc<FieldRevision>,
) -> AFFuture<Option<Arc<GroupConfigurationRevision>>> {
fn get_configuration(&self) -> AFFuture<Option<Arc<GroupConfigurationRevision>>> {
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<FieldRevision>]) -> 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::<HashMap<String, RepeatedGridConfigurationFilterPB>>()
.map(|(_, v)| {
let repeated_filter: RepeatedGridFilterConfigurationPB = v.into();
repeated_filter.items
})
.flatten()
.collect::<Vec<GridFilterConfigurationPB>>()
})
.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::<HashMap<String, RepeatedGridGroupConfigurationPB>>()
.map(|(_, v)| {
let repeated_group: RepeatedGridGroupConfigurationPB = v.into();
repeated_group.items
})
.flatten()
.collect::<Vec<GridGroupConfigurationPB>>()
})
.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(),
}
}

View File

@ -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<Vec<GridFilterConfiguration>> {
pub(crate) async fn get_filters(&self) -> FlowyResult<Vec<GridFilterConfigurationPB>> {
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<RepeatedGridGroupPB> {
@ -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?;

View File

@ -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<FieldRevision>,
) -> AFFuture<Option<Arc<GroupConfigurationRevision>>>;
fn get_configuration(&self) -> AFFuture<Option<Arc<GroupConfigurationRevision>>>;
}
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<FlowyResult<()>>;
}
impl<T> std::fmt::Display for GenericGroupConfiguration<T> {
impl<T> std::fmt::Display for GroupContext<T> {
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<T> std::fmt::Display for GenericGroupConfiguration<T> {
}
}
pub struct GenericGroupConfiguration<C> {
view_id: String,
pub configuration: Arc<GroupConfigurationRevision>,
pub struct GroupContext<C> {
pub view_id: String,
configuration: Arc<GroupConfigurationRevision>,
configuration_content: PhantomData<C>,
field_rev: Arc<FieldRevision>,
groups_map: IndexMap<String, Group>,
@ -50,7 +48,7 @@ pub struct GenericGroupConfiguration<C> {
writer: Arc<dyn GroupConfigurationWriter>,
}
impl<C> GenericGroupConfiguration<C>
impl<C> GroupContext<C>
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<Group>) -> FlowyResult<Option<GroupViewChangesetPB>> {
#[tracing::instrument(level = "debug", skip(self, generated_groups), err)]
pub(crate) fn init_groups(
&mut self,
generated_groups: Vec<GeneratedGroup>,
reset: bool,
) -> FlowyResult<Option<GroupViewChangesetPB>> {
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::<Vec<GroupRevision>>();
let deleted_group_ids = deleted_group_revs
.into_iter()
.map(|group_rev| group_rev.id)
.collect::<Vec<String>>();
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<Group>) -> MergeGroupResult {
fn merge_groups(old_groups: &[GroupRevision], new_groups: Vec<GroupRevision>) -> 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<String, Group> = IndexMap::new();
groups.into_iter().for_each(|group| {
group_map.insert(group.id.clone(), group);
let mut new_group_map: IndexMap<String, GroupRevision> = 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<Group>,
inserted_groups: Vec<InsertedGroupPB>,
updated_groups: Vec<Group>,
// Contains the new groups and the updated groups
all_group_revs: Vec<GroupRevision>,
new_group_revs: Vec<GroupRevision>,
updated_group_revs: Vec<GroupRevision>,
deleted_group_revs: Vec<GroupRevision>,
}
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<InsertedGroupPB>,
updated_group: Vec<Group>,
) -> GroupViewChangesetPB {
GroupViewChangesetPB {
view_id,
inserted_groups,
deleted_groups: vec![],
update_groups: updated_group.into_iter().map(GroupPB::from).collect(),
}
}

View File

@ -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<Self::TypeOptionType>,
) -> Vec<Group>;
) -> Vec<GeneratedGroup>;
}
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<Group>;
fn get_group(&self, group_id: &str) -> Option<(usize, Group)>;
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<Vec<Group>>;
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], 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<C, T, G, P> {
pub field_id: String,
pub type_option: Option<T>,
pub configuration: GenericGroupConfiguration<C>,
pub group_ctx: GroupContext<C>,
group_action_phantom: PhantomData<G>,
cell_parser_phantom: PhantomData<P>,
}
@ -78,21 +83,18 @@ impl<C, T, G, P> GenericGroupController<C, T, G, P>
where
C: GroupConfigurationContentSerde,
T: TypeOptionDataDeserializer,
G: GroupGenerator<ConfigurationType = GenericGroupConfiguration<C>, TypeOptionType = T>,
G: GroupGenerator<Context = GroupContext<C>, TypeOptionType = T>,
{
pub async fn new(
field_rev: &Arc<FieldRevision>,
mut configuration: GenericGroupConfiguration<C>,
) -> FlowyResult<Self> {
pub async fn new(field_rev: &Arc<FieldRevision>, mut configuration: GroupContext<C>) -> FlowyResult<Self> {
let field_type_rev = field_rev.ty;
let type_option = field_rev.get_type_option_entry::<T>(field_type_rev);
let type_option = field_rev.get_type_option::<T>(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<ConfigurationType = GenericGroupConfiguration<C>, TypeOptionType = T>,
G: GroupGenerator<Context = GroupContext<C>, TypeOptionType = T>,
Self: GroupAction<CellDataType = P::Object>,
{
@ -180,23 +182,23 @@ where
}
fn groups(&self) -> Vec<Group> {
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<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<Vec<Group>> {
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], 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<GroupedRow> = vec![];
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
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<Option<GroupViewChangesetPB>> {
let field_type_rev = field_rev.ty;
let type_option = field_rev.get_type_option_entry::<T>(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::<T>(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)
}
}

View File

@ -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<CheckboxGroupConfigurationRevision>;
pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfigurationRevision>;
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<Self::TypeOptionType>,
) -> Vec<Group> {
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<GeneratedGroup> {
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]
}
}

View File

@ -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<FieldRevision>) -> 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<Group> {
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<RowRevision>], _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<Vec<GroupChangesetPB>> {
todo!()
}
fn did_delete_row(
&mut self,
_row_rev: &RowRevision,
_field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> {
todo!()
}
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> {
todo!()
}
fn did_update_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>> {
Ok(None)
}
}
impl GroupController for DefaultGroupController {
fn will_create_row(&mut self, _row_rev: &mut RowRevision, _field_rev: &FieldRevision, _group_id: &str) {}
}

View File

@ -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::*;

View File

@ -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<GroupChangesetPB> {
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<GroupChangesetPB> {
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<GroupChangesetPB> {
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<Self::TypeOptionType>,
) -> Vec<Group> {
) -> Vec<GeneratedGroup> {
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),
}
}
}

View File

@ -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<GroupChangesetPB> {
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<GroupChangesetPB> {
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<GroupChangesetPB> {
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<Self::TypeOptionType>,
) -> Vec<Group> {
) -> Vec<GeneratedGroup> {
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),
}
}
}

View File

@ -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<SelectOptionGroupConfigurationRevision>;
pub type SelectOptionGroupContext = GroupContext<SelectOptionGroupConfigurationRevision>;
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<GeneratedGroup> {
let groups = options
.iter()
.map(|option| GeneratedGroup {
group_rev: GroupRevision::new(option.id.clone(), option.name.clone()),
filter_content: option.id.clone(),
})
.collect();
groups
}

View File

@ -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<RowPB>,
/// [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,
}
}

View File

@ -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<dyn GroupConfigurationReader>,
configuration_writer: Arc<dyn GroupConfigurationWriter>,
group_controller: Option<Box<dyn GroupController>>,
}
impl GroupService {
pub(crate) async fn new<R, W>(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<Group> {
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<FieldRevision>],
row_revs: Vec<Arc<RowRevision>>,
) -> Option<Vec<Group>> {
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<F, O>(&mut self, row_rev: &mut RowRevision, group_id: &str, get_field_fn: F)
where
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + 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<F, O>(
&mut self,
row_rev: &RowRevision,
get_field_fn: F,
) -> Option<Vec<GroupChangesetPB>>
where
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + 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<F, O>(
&mut self,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
to_group_id: &str,
to_row_id: Option<String>,
get_field_fn: F,
) -> Option<Vec<GroupChangesetPB>>
where
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + 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<F, O>(
&mut self,
row_rev: &RowRevision,
get_field_fn: F,
) -> Option<Vec<GroupChangesetPB>>
where
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + 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<Option<GroupViewChangesetPB>> {
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<FieldRevision>,
) -> FlowyResult<Option<Box<dyn GroupController>>> {
let mut group_controller: Option<Box<dyn GroupController>> = None;
match field_type {
FieldType::RichText => {
// let generator = GroupGenerator::<TextGroupConfigurationPB>::from_configuration(configuration);
}
FieldType::Number => {
// let generator = GroupGenerator::<NumberGroupConfigurationPB>::from_configuration(configuration);
}
FieldType::DateTime => {
// let generator = GroupGenerator::<DateGroupConfigurationPB>::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::<UrlGroupConfigurationPB>::from_configuration(configuration);
}
}
Ok(group_controller)
}
}
fn find_group_field(field_revs: &[Arc<FieldRevision>]) -> Option<Arc<FieldRevision>> {
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()
}
}
}

View File

@ -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<R, W>(
view_id: String,
field_rev: Arc<FieldRevision>,
row_revs: Vec<Arc<RowRevision>>,
configuration_reader: R,
configuration_writer: W,
) -> FlowyResult<Box<dyn GroupController>>
where
R: GroupConfigurationReader,
W: GroupConfigurationWriter,
{
let field_type: FieldType = field_rev.ty.into();
let mut group_controller: Box<dyn GroupController>;
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<FieldRevision>]) -> Option<Arc<FieldRevision>> {
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()
}
}
}

View File

@ -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::*;

View File

@ -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
}

View File

@ -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::<NumberTypeOptionPB>(FieldType::Number.into())
.get_type_option::<NumberTypeOptionPB>(FieldType::Number.into())
.unwrap();
let cell_data = self
.editor

View File

@ -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 },

View File

@ -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::<RichTextTypeOptionPB>(field_rev.ty)
.get_type_option::<RichTextTypeOptionPB>(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::<SingleSelectTypeOptionPB>(field_rev.ty)
.get_type_option::<SingleSelectTypeOptionPB>(field_rev.ty)
.unwrap()
.protobuf_bytes()
.to_vec();

View File

@ -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();

View File

@ -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()))
}

View File

@ -85,7 +85,7 @@ impl GridEditorTest {
.row_revs
}
pub async fn grid_filters(&self) -> Vec<GridFilterConfiguration> {
pub async fn grid_filters(&self) -> Vec<GridFilterConfigurationPB> {
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(),
};

View File

@ -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]

View File

@ -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<SelectOptionPB>,
},
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<FieldRevision> {
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<FieldRevision> {
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 {

View File

@ -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;
}

View File

@ -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;

View File

@ -143,15 +143,15 @@ impl FieldRevision {
}
}
pub fn insert_type_option_entry<T>(&mut self, entry: &T)
pub fn insert_type_option<T>(&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<T: TypeOptionDataDeserializer>(&self, field_type_rev: FieldTypeRevision) -> Option<T> {
pub fn get_type_option<T: TypeOptionDataDeserializer>(&self, field_type_rev: FieldTypeRevision) -> Option<T> {
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<Bytes> trait.
pub trait TypeOptionDataEntry {
pub trait TypeOptionDataFormat {
fn json_str(&self) -> String;
fn protobuf_bytes(&self) -> Bytes;
}

View File

@ -60,7 +60,7 @@ where
.cloned()
}
pub fn get_all_objects(&self, field_revs: &[Arc<FieldRevision>]) -> Option<HashMap<String, Vec<Arc<T>>>> {
pub fn get_objects_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> Option<HashMap<String, Vec<Arc<T>>>> {
// 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<Arc<T>> {
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<Arc<T>> {
self.object_by_field_type.values().cloned().flatten().collect()
}
}
impl<T> std::ops::Deref for ObjectIndexMap<T>

View File

@ -4,9 +4,8 @@ use serde_json::Error;
use serde_repr::*;
pub trait GroupConfigurationContentSerde: Sized + Send + Sync {
fn from_configuration_content(s: &str) -> Result<Self, serde_json::Error>;
fn to_configuration_content(&self) -> Result<String, serde_json::Error>;
fn from_json(s: &str) -> Result<Self, serde_json::Error>;
fn to_json(&self) -> Result<String, serde_json::Error>;
}
#[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<GroupRevision>,
// 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<Self, Error> {
fn from_json(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
fn to_json(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
@ -54,10 +54,10 @@ pub struct NumberGroupConfigurationRevision {
}
impl GroupConfigurationContentSerde for NumberGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
fn from_json(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
fn to_json(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
@ -68,10 +68,10 @@ pub struct UrlGroupConfigurationRevision {
}
impl GroupConfigurationContentSerde for UrlGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
fn from_json(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
fn to_json(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
@ -82,11 +82,11 @@ pub struct CheckboxGroupConfigurationRevision {
}
impl GroupConfigurationContentSerde for CheckboxGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
fn from_json(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
fn to_json(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
@ -97,11 +97,11 @@ pub struct SelectOptionGroupConfigurationRevision {
}
impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
fn from_json(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
fn to_json(&self) -> Result<String, Error> {
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<Self, Error> {
fn from_json(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
fn to_json(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}

View File

@ -48,8 +48,12 @@ impl GridViewRevisionPad {
Self::from_delta(delta)
}
pub fn get_all_groups(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
self.groups.get_all_objects(field_revs)
pub fn get_groups_by_field_revs(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
self.groups.get_objects_by_field_revs(field_revs)
}
pub fn get_all_groups(&self) -> Vec<Arc<GroupConfigurationRevision>> {
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<Option<GridViewRevisionChangeset>> {
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<FieldRevision>]) -> Option<FilterConfigurationsByFieldId> {
self.filters.get_all_objects(field_revs)
self.filters.get_objects_by_field_revs(field_revs)
}
pub fn get_filters(

View File

@ -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();

View File

@ -1,3 +1,4 @@
#![allow(clippy::module_inception)]
mod attributes;
mod document;
mod document_operation;

View File

@ -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);