Merge pull request #898 from AppFlowy-IO/feat/board_update_when_field_change

Feat/board update when field change
This commit is contained in:
Nathan.fooo 2022-08-24 22:05:27 +08:00 committed by GitHub
commit 0d39afca2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 682 additions and 302 deletions

View File

@ -20,19 +20,19 @@ import 'group_controller.dart';
part 'board_bloc.freezed.dart'; part 'board_bloc.freezed.dart';
class BoardBloc extends Bloc<BoardEvent, BoardState> { class BoardBloc extends Bloc<BoardEvent, BoardState> {
final BoardDataController _dataController; final BoardDataController _gridDataController;
late final AFBoardDataController afBoardDataController; late final AFBoardDataController boardController;
final MoveRowFFIService _rowService; final MoveRowFFIService _rowService;
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap.new(); LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap.new();
GridFieldCache get fieldCache => _dataController.fieldCache; GridFieldCache get fieldCache => _gridDataController.fieldCache;
String get gridId => _dataController.gridId; String get gridId => _gridDataController.gridId;
BoardBloc({required ViewPB view}) BoardBloc({required ViewPB view})
: _rowService = MoveRowFFIService(gridId: view.id), : _rowService = MoveRowFFIService(gridId: view.id),
_dataController = BoardDataController(view: view), _gridDataController = BoardDataController(view: view),
super(BoardState.initial(view.id)) { super(BoardState.initial(view.id)) {
afBoardDataController = AFBoardDataController( boardController = AFBoardDataController(
onMoveColumn: ( onMoveColumn: (
fromColumnId, fromColumnId,
fromIndex, fromIndex,
@ -70,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
await _loadGrid(emit); await _loadGrid(emit);
}, },
createRow: (groupId) async { createRow: (groupId) async {
final result = await _dataController.createBoardCard(groupId); final result = await _gridDataController.createBoardCard(groupId);
result.fold( result.fold(
(rowPB) { (rowPB) {
emit(state.copyWith(editingRow: some(rowPB))); emit(state.copyWith(editingRow: some(rowPB)));
@ -126,7 +126,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
@override @override
Future<void> close() async { Future<void> close() async {
await _dataController.dispose(); await _gridDataController.dispose();
for (final controller in groupControllers.values) { for (final controller in groupControllers.values) {
controller.dispose(); controller.dispose();
} }
@ -135,7 +135,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
void initializeGroups(List<GroupPB> groups) { void initializeGroups(List<GroupPB> groups) {
for (final group in groups) { for (final group in groups) {
final delegate = GroupControllerDelegateImpl(afBoardDataController); final delegate = GroupControllerDelegateImpl(boardController);
final controller = GroupController( final controller = GroupController(
gridId: state.gridId, gridId: state.gridId,
group: group, group: group,
@ -147,12 +147,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
} }
GridRowCache? getRowCache(String blockId) { GridRowCache? getRowCache(String blockId) {
final GridBlockCache? blockCache = _dataController.blocks[blockId]; final GridBlockCache? blockCache = _gridDataController.blocks[blockId];
return blockCache?.rowCache; return blockCache?.rowCache;
} }
void _startListening() { void _startListening() {
_dataController.addListener( _gridDataController.addListener(
onGridChanged: (grid) { onGridChanged: (grid) {
if (!isClosed) { if (!isClosed) {
add(BoardEvent.didReceiveGridUpdate(grid)); add(BoardEvent.didReceiveGridUpdate(grid));
@ -162,18 +162,34 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
List<AFBoardColumnData> columns = groups.map((group) { List<AFBoardColumnData> columns = groups.map((group) {
return AFBoardColumnData( return AFBoardColumnData(
id: group.groupId, id: group.groupId,
desc: group.desc, name: group.desc,
items: _buildRows(group.rows), items: _buildRows(group.rows),
customData: group, customData: group,
); );
}).toList(); }).toList();
afBoardDataController.addColumns(columns); boardController.addColumns(columns);
initializeGroups(groups); initializeGroups(groups);
}, },
onRowsChanged: (List<RowInfo> rowInfos, RowsChangedReason reason) { onRowsChanged: (List<RowInfo> rowInfos, RowsChangedReason reason) {
add(BoardEvent.didReceiveRows(rowInfos)); add(BoardEvent.didReceiveRows(rowInfos));
}, },
onDeletedGroup: (groupIds) {
//
},
onInsertedGroup: (insertedGroups) {
//
},
onUpdatedGroup: (updatedGroups) {
//
for (final group in updatedGroups) {
final columnController =
boardController.getColumnController(group.groupId);
if (columnController != null) {
columnController.updateColumnName(group.desc);
}
}
},
onError: (err) { onError: (err) {
Log.error(err); Log.error(err);
}, },
@ -189,7 +205,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
} }
Future<void> _loadGrid(Emitter<BoardState> emit) async { Future<void> _loadGrid(Emitter<BoardState> emit) async {
final result = await _dataController.loadData(); final result = await _gridDataController.loadData();
result.fold( result.fold(
(grid) => emit( (grid) => emit(
state.copyWith(loadingState: GridLoadingState.finish(left(unit))), state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
@ -301,6 +317,9 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
@override @override
void updateRow(String groupId, RowPB row) { void updateRow(String groupId, RowPB row) {
// // workaround: fix the board card reload timing issue.
Future.delayed(const Duration(milliseconds: 300), () {
controller.updateColumnItem(groupId, BoardColumnItem(row: row));
});
} }
} }

View File

@ -10,9 +10,15 @@ import 'dart:async';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
import 'board_listener.dart';
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>); typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
typedef OnGridChanged = void Function(GridPB); typedef OnGridChanged = void Function(GridPB);
typedef DidLoadGroups = void Function(List<GroupPB>); 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 OnRowsChanged = void Function( typedef OnRowsChanged = void Function(
List<RowInfo>, List<RowInfo>,
RowsChangedReason, RowsChangedReason,
@ -23,6 +29,7 @@ class BoardDataController {
final String gridId; final String gridId;
final GridFFIService _gridFFIService; final GridFFIService _gridFFIService;
final GridFieldCache fieldCache; final GridFieldCache fieldCache;
final BoardListener _listener;
// key: the block id // key: the block id
final LinkedHashMap<String, GridBlockCache> _blocks; final LinkedHashMap<String, GridBlockCache> _blocks;
@ -44,16 +51,20 @@ class BoardDataController {
BoardDataController({required ViewPB view}) BoardDataController({required ViewPB view})
: gridId = view.id, : gridId = view.id,
_listener = BoardListener(view.id),
_blocks = LinkedHashMap.new(), _blocks = LinkedHashMap.new(),
_gridFFIService = GridFFIService(gridId: view.id), _gridFFIService = GridFFIService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id); fieldCache = GridFieldCache(gridId: view.id);
void addListener({ void addListener({
OnGridChanged? onGridChanged, required OnGridChanged onGridChanged,
OnFieldsChanged? onFieldsChanged, OnFieldsChanged? onFieldsChanged,
DidLoadGroups? didLoadGroups, required DidLoadGroups didLoadGroups,
OnRowsChanged? onRowsChanged, required OnRowsChanged onRowsChanged,
OnError? onError, required OnUpdatedGroup onUpdatedGroup,
required OnDeletedGroup onDeletedGroup,
required OnInsertedGroup onInsertedGroup,
required OnError? onError,
}) { }) {
_onGridChanged = onGridChanged; _onGridChanged = onGridChanged;
_onFieldsChanged = onFieldsChanged; _onFieldsChanged = onFieldsChanged;
@ -64,6 +75,25 @@ class BoardDataController {
fieldCache.addListener(onFields: (fields) { fieldCache.addListener(onFields: (fields) {
_onFieldsChanged?.call(UnmodifiableListView(fields)); _onFieldsChanged?.call(UnmodifiableListView(fields));
}); });
_listener.start(onBoardChanged: (result) {
result.fold(
(changeset) {
if (changeset.updateGroups.isNotEmpty) {
onUpdatedGroup.call(changeset.updateGroups);
}
if (changeset.insertedGroups.isNotEmpty) {
onInsertedGroup.call(changeset.insertedGroups);
}
if (changeset.deletedGroups.isNotEmpty) {
onDeletedGroup.call(changeset.deletedGroups);
}
},
(e) => _onError?.call(e),
);
});
} }
Future<Either<Unit, FlowyError>> loadData() async { Future<Either<Unit, FlowyError>> loadData() async {

View File

@ -0,0 +1,50 @@
import 'dart:typed_data';
import 'package:app_flowy/core/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
typedef UpdateBoardNotifiedValue = Either<GroupViewChangesetPB, FlowyError>;
class BoardListener {
final String viewId;
PublishNotifier<UpdateBoardNotifiedValue>? _groupNotifier = PublishNotifier();
GridNotificationListener? _listener;
BoardListener(this.viewId);
void start({
required void Function(UpdateBoardNotifiedValue) onBoardChanged,
}) {
_groupNotifier?.addPublishListener(onBoardChanged);
_listener = GridNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void _handler(
GridNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridNotification.DidUpdateGroupView:
result.fold(
(payload) => _groupNotifier?.value =
left(GroupViewChangesetPB.fromBuffer(payload)),
(error) => _groupNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_groupNotifier?.dispose();
_groupNotifier = null;
}
}

View File

@ -34,7 +34,12 @@ class GroupController {
void startListening() { void startListening() {
_listener.start(onGroupChanged: (result) { _listener.start(onGroupChanged: (result) {
result.fold( result.fold(
(GroupRowsChangesetPB changeset) { (GroupChangesetPB changeset) {
for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group.groupId, deletedRow);
}
for (final insertedRow in changeset.insertedRows) { for (final insertedRow in changeset.insertedRows) {
final index = insertedRow.hasIndex() ? insertedRow.index : null; final index = insertedRow.hasIndex() ? insertedRow.index : null;
@ -52,11 +57,6 @@ class GroupController {
); );
} }
for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group.groupId, deletedRow);
}
for (final updatedRow in changeset.updatedRows) { for (final updatedRow in changeset.updatedRows) {
final index = group.rows.indexWhere( final index = group.rows.indexWhere(
(rowPB) => rowPB.id == updatedRow.id, (rowPB) => rowPB.id == updatedRow.id,

View File

@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
typedef UpdateGroupNotifiedValue = Either<GroupRowsChangesetPB, FlowyError>; typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
class GroupListener { class GroupListener {
final GroupPB group; final GroupPB group;
@ -34,7 +34,7 @@ class GroupListener {
case GridNotification.DidUpdateGroup: case GridNotification.DidUpdateGroup:
result.fold( result.fold(
(payload) => _groupNotifier?.value = (payload) => _groupNotifier?.value =
left(GroupRowsChangesetPB.fromBuffer(payload)), left(GroupChangesetPB.fromBuffer(payload)),
(error) => _groupNotifier?.value = right(error), (error) => _groupNotifier?.value = right(error),
); );
break; break;

View File

@ -62,9 +62,8 @@ class BoardContent extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: AFBoard( child: AFBoard(
// key: UniqueKey(),
scrollController: ScrollController(), scrollController: ScrollController(),
dataController: context.read<BoardBloc>().afBoardDataController, dataController: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader, headerBuilder: _buildHeader,
footBuilder: _buildFooter, footBuilder: _buildFooter,
cardBuilder: (_, data) => _buildCard(context, data), cardBuilder: (_, data) => _buildCard(context, data),
@ -79,10 +78,11 @@ class BoardContent extends StatelessWidget {
); );
} }
Widget _buildHeader(BuildContext context, AFBoardColumnData columnData) { Widget _buildHeader(
BuildContext context, AFBoardColumnHeaderData headerData) {
return AppFlowyColumnHeader( return AppFlowyColumnHeader(
icon: const Icon(Icons.lightbulb_circle), icon: const Icon(Icons.lightbulb_circle),
title: Text(columnData.desc), title: Text(headerData.columnName),
addIcon: const Icon(Icons.add, size: 20), addIcon: const Icon(Icons.add, size: 20),
moreIcon: const Icon(Icons.more_horiz, size: 20), moreIcon: const Icon(Icons.more_horiz, size: 20),
height: 50, height: 50,

View File

@ -35,19 +35,17 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>( child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
builder: (context, state) { builder: (context, state) {
final children = state.selectedOptions final children = state.selectedOptions
.map((option) => SelectOptionTag.fromOption( .map(
(option) => SelectOptionTag.fromOption(
context: context, context: context,
option: option, option: option,
)) ),
)
.toList(); .toList();
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: AbsorbPointer( child: AbsorbPointer(
child: Wrap( child: Wrap(children: children, spacing: 4, runSpacing: 2),
children: children,
spacing: 4,
runSpacing: 2,
),
), ),
); );
}, },

View File

@ -42,7 +42,7 @@ class _BoardCardState extends State<BoardCard> {
_cardBloc = BoardCardBloc( _cardBloc = BoardCardBloc(
gridId: widget.gridId, gridId: widget.gridId,
dataController: widget.dataController, dataController: widget.dataController,
); )..add(const BoardCardEvent.initial());
super.initState(); super.initState();
} }
@ -79,6 +79,12 @@ class _BoardCardState extends State<BoardCard> {
}, },
).toList(); ).toList();
} }
@override
Future<void> dispose() async {
_cardBloc.close();
super.dispose();
}
} }
class _CardMoreOption extends StatelessWidget with CardAccessory { class _CardMoreOption extends StatelessWidget with CardAccessory {

View File

@ -24,7 +24,8 @@ class GridCellDataLoader<T> {
Future<T?> loadData() { Future<T?> loadData() {
final fut = service.getCell(cellId: cellId); final fut = service.getCell(cellId: cellId);
return fut.then( return fut.then(
(result) => result.fold((GridCellPB cell) { (result) => result.fold(
(GridCellPB cell) {
try { try {
return parser.parserData(cell.data); return parser.parserData(cell.data);
} catch (e, s) { } catch (e, s) {
@ -32,10 +33,12 @@ class GridCellDataLoader<T> {
Log.error('Stack trace \n $s'); Log.error('Stack trace \n $s');
return null; return null;
} }
}, (err) { },
(err) {
Log.error(err); Log.error(err);
return null; return null;
}), },
),
); );
} }
} }
@ -58,7 +61,8 @@ class DateCellDataParser implements IGridCellDataParser<DateCellDataPB> {
} }
} }
class SelectOptionCellDataParser implements IGridCellDataParser<SelectOptionCellDataPB> { class SelectOptionCellDataParser
implements IGridCellDataParser<SelectOptionCellDataPB> {
@override @override
SelectOptionCellDataPB? parserData(List<int> data) { SelectOptionCellDataPB? parserData(List<int> data) {
if (data.isEmpty) { if (data.isEmpty) {

View File

@ -279,8 +279,9 @@ class IGridCellController<T, D> extends Equatable {
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
_loadDataOperation = Timer(const Duration(milliseconds: 10), () { _loadDataOperation = Timer(const Duration(milliseconds: 10), () {
_cellDataLoader.loadData().then((data) { _cellDataLoader.loadData().then((data) {
_cellDataNotifier?.value = data; Log.debug('$fieldId CellData: Did Get cell data');
_cellsCache.insert(_cacheKey, GridCell(object: data)); _cellsCache.insert(_cacheKey, GridCell(object: data));
_cellDataNotifier?.value = data;
}); });
}); });
} }

View File

@ -34,13 +34,18 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
RichTextItem(title: "Card 8", subtitle: 'Aug 1, 2020 4:05 PM'), RichTextItem(title: "Card 8", subtitle: 'Aug 1, 2020 4:05 PM'),
TextItem("Card 9"), TextItem("Card 9"),
]; ];
final column1 = AFBoardColumnData(id: "To Do", items: a); final column1 = AFBoardColumnData(id: "To Do", name: "To Do", items: a);
final column2 = AFBoardColumnData(id: "In Progress", items: <AFColumnItem>[ final column2 = AFBoardColumnData(
id: "In Progress",
name: "In Progress",
items: <AFColumnItem>[
RichTextItem(title: "Card 10", subtitle: 'Aug 1, 2020 4:05 PM'), RichTextItem(title: "Card 10", subtitle: 'Aug 1, 2020 4:05 PM'),
TextItem("Card 11"), TextItem("Card 11"),
]); ],
);
final column3 = AFBoardColumnData(id: "Done", items: <AFColumnItem>[]); final column3 =
AFBoardColumnData(id: "Done", name: "Done", items: <AFColumnItem>[]);
boardDataController.addColumn(column1); boardDataController.addColumn(column1);
boardDataController.addColumn(column2); boardDataController.addColumn(column2);
@ -68,10 +73,21 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
margin: config.columnItemPadding, margin: config.columnItemPadding,
); );
}, },
headerBuilder: (context, columnData) { headerBuilder: (context, headerData) {
return AppFlowyColumnHeader( return AppFlowyColumnHeader(
icon: const Icon(Icons.lightbulb_circle), icon: const Icon(Icons.lightbulb_circle),
title: Text(columnData.id), title: SizedBox(
width: 60,
child: TextField(
controller: TextEditingController()
..text = headerData.columnName,
onSubmitted: (val) {
boardDataController
.getColumnController(headerData.columnId)!
.updateColumnName(val);
},
),
),
addIcon: const Icon(Icons.add, size: 20), addIcon: const Icon(Icons.add, size: 20),
moreIcon: const Icon(Icons.more_horiz, size: 20), moreIcon: const Icon(Icons.more_horiz, size: 20),
height: 50, height: 50,

View File

@ -13,12 +13,16 @@ class _SingleBoardListExampleState extends State<SingleBoardListExample> {
@override @override
void initState() { void initState() {
final column = AFBoardColumnData(id: "1", items: [ final column = AFBoardColumnData(
id: "1",
name: "1",
items: [
TextItem("a"), TextItem("a"),
TextItem("b"), TextItem("b"),
TextItem("c"), TextItem("c"),
TextItem("d"), TextItem("d"),
]); ],
);
boardData.addColumn(column); boardData.addColumn(column);
super.initState(); super.initState();

View File

@ -205,13 +205,13 @@ class _BoardContentState extends State<BoardContent> {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
key: ValueKey(columnData.id), key: ValueKey(columnData.id),
value: widget.dataController.columnController(columnData.id), value: widget.dataController.getColumnController(columnData.id),
child: Consumer<AFBoardColumnDataController>( child: Consumer<AFBoardColumnDataController>(
builder: (context, value, child) { builder: (context, value, child) {
final boardColumn = AFBoardColumnWidget( final boardColumn = AFBoardColumnWidget(
margin: _marginFromIndex(columnIndex), margin: _marginFromIndex(columnIndex),
itemMargin: widget.config.columnItemPadding, itemMargin: widget.config.columnItemPadding,
headerBuilder: widget.headerBuilder, headerBuilder: _buildHeader,
footBuilder: widget.footBuilder, footBuilder: widget.footBuilder,
cardBuilder: widget.cardBuilder, cardBuilder: widget.cardBuilder,
dataSource: dataSource, dataSource: dataSource,
@ -224,7 +224,6 @@ class _BoardContentState extends State<BoardContent> {
// columnKeys // columnKeys
// .removeWhere((element) => element.columnId == columnData.id); // .removeWhere((element) => element.columnId == columnData.id);
// columnKeys.add( // columnKeys.add(
// ColumnKey( // ColumnKey(
// columnId: columnData.id, // columnId: columnData.id,
@ -245,6 +244,19 @@ class _BoardContentState extends State<BoardContent> {
return children; return children;
} }
Widget? _buildHeader(
BuildContext context, AFBoardColumnHeaderData headerData) {
if (widget.headerBuilder == null) {
return null;
}
return Selector<AFBoardColumnDataController, AFBoardColumnHeaderData>(
selector: (context, controller) => controller.columnData.headerData,
builder: (context, headerData, _) {
return widget.headerBuilder!(context, headerData)!;
},
);
}
EdgeInsets _marginFromIndex(int index) { EdgeInsets _marginFromIndex(int index) {
if (widget.dataController.columnDatas.isEmpty) { if (widget.dataController.columnDatas.isEmpty) {
return widget.config.columnPadding; return widget.config.columnPadding;
@ -273,7 +285,7 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
@override @override
AFBoardColumnData get columnData => AFBoardColumnData get columnData =>
dataController.columnController(columnId).columnData; dataController.getColumnController(columnId)!.columnData;
@override @override
List<String> get acceptedColumnIds => dataController.columnIds; List<String> get acceptedColumnIds => dataController.columnIds;

View File

@ -27,9 +27,9 @@ typedef AFBoardColumnCardBuilder = Widget Function(
AFColumnItem item, AFColumnItem item,
); );
typedef AFBoardColumnHeaderBuilder = Widget Function( typedef AFBoardColumnHeaderBuilder = Widget? Function(
BuildContext context, BuildContext context,
AFBoardColumnData columnData, AFBoardColumnHeaderData headerData,
); );
typedef AFBoardColumnFooterBuilder = Widget Function( typedef AFBoardColumnFooterBuilder = Widget Function(
@ -125,8 +125,8 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
.map((item) => _buildWidget(context, item)) .map((item) => _buildWidget(context, item))
.toList(); .toList();
final header = final header = widget.headerBuilder
widget.headerBuilder?.call(context, widget.dataSource.columnData); ?.call(context, widget.dataSource.columnData.headerData);
final footer = final footer =
widget.footBuilder?.call(context, widget.dataSource.columnData); widget.footBuilder?.call(context, widget.dataSource.columnData);

View File

@ -34,6 +34,13 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
UnmodifiableListView<AFColumnItem> get items => UnmodifiableListView<AFColumnItem> get items =>
UnmodifiableListView(columnData.items); UnmodifiableListView(columnData.items);
void updateColumnName(String newName) {
if (columnData.headerData.columnName != newName) {
columnData.headerData.columnName = newName;
notifyListeners();
}
}
/// Remove the item at [index]. /// Remove the item at [index].
/// * [index] the index of the item you want to remove /// * [index] the index of the item you want to remove
/// * [notify] the default value of [notify] is true, it will notify the /// * [notify] the default value of [notify] is true, it will notify the
@ -123,6 +130,18 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
notifyListeners(); notifyListeners();
} }
void replaceOrInsertItem(AFColumnItem newItem) {
final index = columnData._items.indexWhere((item) => item.id == newItem.id);
if (index != -1) {
columnData._items.removeAt(index);
columnData._items.insert(index, newItem);
notifyListeners();
} else {
columnData._items.add(newItem);
notifyListeners();
}
}
bool _containsItem(AFColumnItem item) { bool _containsItem(AFColumnItem item) {
return columnData._items.indexWhere((element) => element.id == item.id) != return columnData._items.indexWhere((element) => element.id == item.id) !=
-1; -1;
@ -133,16 +152,20 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin { class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
@override @override
final String id; final String id;
final String desc; AFBoardColumnHeaderData headerData;
final List<AFColumnItem> _items; final List<AFColumnItem> _items;
final CustomData? customData; final CustomData? customData;
AFBoardColumnData({ AFBoardColumnData({
this.customData, this.customData,
required this.id, required this.id,
this.desc = "", required String name,
List<AFColumnItem> items = const [], List<AFColumnItem> items = const [],
}) : _items = items; }) : _items = items,
headerData = AFBoardColumnHeaderData(
columnId: id,
columnName: name,
);
/// Returns the readonly List<ColumnItem> /// Returns the readonly List<ColumnItem>
UnmodifiableListView<AFColumnItem> get items => UnmodifiableListView<AFColumnItem> get items =>
@ -156,3 +179,10 @@ class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
return 'Column:[$id]'; return 'Column:[$id]';
} }
} }
class AFBoardColumnHeaderData {
String columnId;
String columnName;
AFBoardColumnHeaderData({required this.columnId, required this.columnName});
}

View File

@ -89,10 +89,6 @@ class AFBoardDataController extends ChangeNotifier
if (columnIds.isNotEmpty && notify) notifyListeners(); if (columnIds.isNotEmpty && notify) notifyListeners();
} }
AFBoardColumnDataController columnController(String columnId) {
return _columnControllers[columnId]!;
}
AFBoardColumnDataController? getColumnController(String columnId) { AFBoardColumnDataController? getColumnController(String columnId) {
final columnController = _columnControllers[columnId]; final columnController = _columnControllers[columnId];
if (columnController == null) { if (columnController == null) {
@ -129,6 +125,10 @@ class AFBoardDataController extends ChangeNotifier
getColumnController(columnId)?.removeWhere((item) => item.id == itemId); getColumnController(columnId)?.removeWhere((item) => item.id == itemId);
} }
void updateColumnItem(String columnId, AFColumnItem item) {
getColumnController(columnId)?.replaceOrInsertItem(item);
}
@override @override
@protected @protected
void swapColumnItem( void swapColumnItem(
@ -137,15 +137,14 @@ class AFBoardDataController extends ChangeNotifier
String toColumnId, String toColumnId,
int toColumnIndex, int toColumnIndex,
) { ) {
final item = columnController(fromColumnId).removeAt(fromColumnIndex); final fromColumnController = getColumnController(fromColumnId)!;
final toColumnController = getColumnController(toColumnId)!;
if (columnController(toColumnId).items.length > toColumnIndex) { final item = fromColumnController.removeAt(fromColumnIndex);
assert(columnController(toColumnId).items[toColumnIndex] if (toColumnController.items.length > toColumnIndex) {
is PhantomColumnItem); assert(toColumnController.items[toColumnIndex] is PhantomColumnItem);
} }
columnController(toColumnId).replace(toColumnIndex, item); toColumnController.replace(toColumnIndex, item);
onMoveColumnItemToColumn?.call( onMoveColumnItemToColumn?.call(
fromColumnId, fromColumnId,
fromColumnIndex, fromColumnIndex,
@ -174,9 +173,12 @@ class AFBoardDataController extends ChangeNotifier
@override @override
@protected @protected
bool removePhantom(String columnId) { bool removePhantom(String columnId) {
final columnController = this.columnController(columnId); final columnController = getColumnController(columnId);
if (columnController == null) {
Log.warn('Can not find the column controller with columnId: $columnId');
return false;
}
final index = columnController.items.indexWhere((item) => item.isPhantom); final index = columnController.items.indexWhere((item) => item.isPhantom);
final isExist = index != -1; final isExist = index != -1;
if (isExist) { if (isExist) {
columnController.removeAt(index); columnController.removeAt(index);
@ -190,7 +192,7 @@ class AFBoardDataController extends ChangeNotifier
@override @override
@protected @protected
void updatePhantom(String columnId, int newIndex) { void updatePhantom(String columnId, int newIndex) {
final columnDataController = columnController(columnId); final columnDataController = getColumnController(columnId)!;
final index = final index =
columnDataController.items.indexWhere((item) => item.isPhantom); columnDataController.items.indexWhere((item) => item.isPhantom);
@ -208,6 +210,6 @@ class AFBoardDataController extends ChangeNotifier
@override @override
@protected @protected
void insertPhantom(String columnId, int index, PhantomColumnItem item) { void insertPhantom(String columnId, int index, PhantomColumnItem item) {
columnController(columnId).insert(index, item); getColumnController(columnId)!.insert(index, item);
} }
} }

View File

@ -28,7 +28,7 @@ packages:
path: "packages/appflowy_board" path: "packages/appflowy_board"
relative: true relative: true
source: path source: path
version: "0.0.4" version: "0.0.5"
appflowy_editor: appflowy_editor:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -30,7 +30,7 @@ impl BlockPB {
} }
/// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row. /// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row.
#[derive(Debug, Default, Clone, ProtoBuf)] #[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)]
pub struct RowPB { pub struct RowPB {
#[pb(index = 1)] #[pb(index = 1)]
pub block_id: String, pub block_id: String,

View File

@ -1,4 +1,5 @@
use crate::entities::{CreateRowParams, FieldType, GridLayout, RowPB}; use crate::entities::{CreateRowParams, FieldType, GridLayout, RowPB};
use crate::services::group::Group;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::parser::NotEmptyStr;
@ -82,6 +83,17 @@ pub struct GroupPB {
pub rows: Vec<RowPB>, pub rows: Vec<RowPB>,
} }
impl std::convert::From<Group> for GroupPB {
fn from(group: Group) -> Self {
Self {
field_id: group.field_id,
group_id: group.id,
desc: group.name,
rows: group.rows,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedGridGroupConfigurationPB { pub struct RepeatedGridGroupConfigurationPB {
#[pb(index = 1)] #[pb(index = 1)]

View File

@ -5,21 +5,24 @@ use flowy_grid_data_model::parser::NotEmptyStr;
use std::fmt::Formatter; use std::fmt::Formatter;
#[derive(Debug, Default, ProtoBuf)] #[derive(Debug, Default, ProtoBuf)]
pub struct GroupRowsChangesetPB { pub struct GroupChangesetPB {
#[pb(index = 1)] #[pb(index = 1)]
pub group_id: String, pub group_id: String,
#[pb(index = 2)] #[pb(index = 2, one_of)]
pub inserted_rows: Vec<InsertedRowPB>, pub group_name: Option<String>,
#[pb(index = 3)] #[pb(index = 3)]
pub deleted_rows: Vec<String>, pub inserted_rows: Vec<InsertedRowPB>,
#[pb(index = 4)] #[pb(index = 4)]
pub deleted_rows: Vec<String>,
#[pb(index = 5)]
pub updated_rows: Vec<RowPB>, pub updated_rows: Vec<RowPB>,
} }
impl std::fmt::Display for GroupRowsChangesetPB { impl std::fmt::Display for GroupChangesetPB {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for inserted_row in &self.inserted_rows { for inserted_row in &self.inserted_rows {
let _ = f.write_fmt(format_args!( let _ = f.write_fmt(format_args!(
@ -36,10 +39,29 @@ impl std::fmt::Display for GroupRowsChangesetPB {
} }
} }
impl GroupRowsChangesetPB { impl GroupChangesetPB {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.inserted_rows.is_empty() && self.deleted_rows.is_empty() && self.updated_rows.is_empty() self.group_name.is_none()
&& self.inserted_rows.is_empty()
&& self.deleted_rows.is_empty()
&& self.updated_rows.is_empty()
} }
pub fn new(group_id: String) -> Self {
Self {
group_id,
..Default::default()
}
}
pub fn name(group_id: String, name: &str) -> Self {
Self {
group_id,
group_name: Some(name.to_owned()),
..Default::default()
}
}
pub fn insert(group_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self { pub fn insert(group_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self {
Self { Self {
group_id, group_id,
@ -113,9 +135,16 @@ pub struct GroupViewChangesetPB {
#[pb(index = 3)] #[pb(index = 3)]
pub deleted_groups: Vec<String>, pub deleted_groups: Vec<String>,
#[pb(index = 4)]
pub update_groups: Vec<GroupPB>,
} }
impl GroupViewChangesetPB {} impl GroupViewChangesetPB {
pub fn is_empty(&self) -> bool {
self.inserted_groups.is_empty() && self.deleted_groups.is_empty() && self.update_groups.is_empty()
}
}
#[derive(Debug, Default, ProtoBuf)] #[derive(Debug, Default, ProtoBuf)]
pub struct InsertedGroupPB { pub struct InsertedGroupPB {

View File

@ -188,8 +188,13 @@ impl GridRevisionEditor {
pub async fn replace_field(&self, field_rev: Arc<FieldRevision>) -> FlowyResult<()> { pub async fn replace_field(&self, field_rev: Arc<FieldRevision>) -> FlowyResult<()> {
let field_id = field_rev.id.clone(); let field_id = field_rev.id.clone();
let _ = self let _ = self
.modify(|grid_pad| Ok(grid_pad.replace_field_rev(field_rev)?)) .modify(|grid_pad| Ok(grid_pad.replace_field_rev(field_rev.clone())?))
.await?; .await?;
match self.view_manager.did_update_field(&field_rev.id).await {
Ok(_) => {}
Err(e) => tracing::error!("View manager update field failed: {:?}", e),
}
let _ = self.notify_did_update_grid_field(&field_id).await?; let _ = self.notify_did_update_grid_field(&field_id).await?;
Ok(()) Ok(())
} }
@ -263,9 +268,9 @@ impl GridRevisionEditor {
} }
async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> { async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> {
self.modify(|grid| { let _ = self
.modify(|grid| {
let deserializer = TypeOptionJsonDeserializer(field_type); let deserializer = TypeOptionJsonDeserializer(field_type);
let changeset = grid.modify_field(&params.field_id, |field| { let changeset = grid.modify_field(&params.field_id, |field| {
let mut is_changed = None; let mut is_changed = None;
if let Some(name) = params.name { if let Some(name) = params.name {
@ -315,7 +320,13 @@ impl GridRevisionEditor {
})?; })?;
Ok(changeset) Ok(changeset)
}) })
.await .await?;
match self.view_manager.did_update_field(&params.field_id).await {
Ok(_) => {}
Err(e) => tracing::error!("View manager update field failed: {:?}", e),
}
Ok(())
} }
pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> { pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> {
@ -571,7 +582,7 @@ impl GridRevisionEditor {
pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> { pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> {
let MoveGroupRowParams { let MoveGroupRowParams {
view_id: _, view_id,
from_row_id, from_row_id,
to_group_id, to_group_id,
to_row_id, to_row_id,
@ -585,10 +596,23 @@ impl GridRevisionEditor {
.move_group_row(row_rev, to_group_id, to_row_id.clone()) .move_group_row(row_rev, to_group_id, to_row_id.clone())
.await .await
{ {
match self.block_manager.update_row(row_changeset).await { tracing::trace!("Move group row cause row data changed: {:?}", row_changeset);
let cell_changesets = row_changeset
.cell_by_field_id
.into_iter()
.map(|(field_id, cell_rev)| CellChangesetPB {
grid_id: view_id.clone(),
row_id: row_changeset.row_id.clone(),
field_id,
content: cell_rev.data,
})
.collect::<Vec<CellChangesetPB>>();
for cell_changeset in cell_changesets {
match self.block_manager.update_cell(cell_changeset).await {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => tracing::error!("Apply cell changeset error:{:?}", e),
tracing::error!("Apply row changeset error:{:?}", e);
} }
} }
} }

View File

@ -1,8 +1,8 @@
use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::entities::{ use crate::entities::{
CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridLayout, GridLayoutPB, CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridLayout, GridLayoutPB,
GridSettingPB, GroupPB, GroupRowsChangesetPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB, GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB, MoveGroupParams,
MoveGroupParams, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB,
}; };
use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::grid_editor_task::GridServiceTaskScheduler;
use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate}; use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate};
@ -59,7 +59,7 @@ impl GridViewRevisionEditor {
rev_manager: rev_manager.clone(), rev_manager: rev_manager.clone(),
view_pad: pad.clone(), view_pad: pad.clone(),
}; };
let group_service = GroupService::new(configuration_reader, configuration_writer).await; let group_service = GroupService::new(view_id.clone(), configuration_reader, configuration_writer).await;
let user_id = user_id.to_owned(); let user_id = user_id.to_owned();
let did_load_group = AtomicBool::new(false); let did_load_group = AtomicBool::new(false);
Ok(Self { Ok(Self {
@ -99,8 +99,8 @@ impl GridViewRevisionEditor {
row: row_pb.clone(), row: row_pb.clone(),
index: None, index: None,
}; };
let changeset = GroupRowsChangesetPB::insert(group_id.clone(), vec![inserted_row]); let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]);
self.notify_did_update_group_rows(changeset).await; self.notify_did_update_group(changeset).await;
} }
} }
} }
@ -115,7 +115,7 @@ impl GridViewRevisionEditor {
.await .await
{ {
for changeset in changesets { for changeset in changesets {
self.notify_did_update_group_rows(changeset).await; self.notify_did_update_group(changeset).await;
} }
} }
} }
@ -129,7 +129,7 @@ impl GridViewRevisionEditor {
.await .await
{ {
for changeset in changesets { for changeset in changesets {
self.notify_did_update_group_rows(changeset).await; self.notify_did_update_group(changeset).await;
} }
} }
} }
@ -151,11 +151,11 @@ impl GridViewRevisionEditor {
.await .await
{ {
for changeset in changesets { for changeset in changesets {
self.notify_did_update_group_rows(changeset).await; self.notify_did_update_group(changeset).await;
} }
} }
} }
/// Only call once after grid view editor initialized
#[tracing::instrument(level = "trace", skip(self))] #[tracing::instrument(level = "trace", skip(self))]
pub(crate) async fn load_groups(&self) -> FlowyResult<Vec<GroupPB>> { pub(crate) async fn load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
let groups = if !self.did_load_group.load(Ordering::SeqCst) { let groups = if !self.did_load_group.load(Ordering::SeqCst) {
@ -198,9 +198,10 @@ impl GridViewRevisionEditor {
}; };
let changeset = GroupViewChangesetPB { let changeset = GroupViewChangesetPB {
view_id: "".to_string(), view_id: self.view_id.clone(),
inserted_groups: vec![inserted_group], inserted_groups: vec![inserted_group],
deleted_groups: vec![params.from_group_id.clone()], deleted_groups: vec![params.from_group_id.clone()],
update_groups: vec![],
}; };
self.notify_did_update_view(changeset).await; self.notify_did_update_view(changeset).await;
@ -252,8 +253,20 @@ impl GridViewRevisionEditor {
}) })
.await .await
} }
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn did_update_field(&self, field_id: &str) -> FlowyResult<()> {
if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await {
match self.group_service.write().await.did_update_field(&field_rev).await? {
None => {}
Some(changeset) => {
self.notify_did_update_view(changeset).await;
}
}
}
Ok(())
}
async fn notify_did_update_group_rows(&self, changeset: GroupRowsChangesetPB) { async fn notify_did_update_group(&self, changeset: GroupChangesetPB) {
send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup) send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
.payload(changeset) .payload(changeset)
.send(); .send();
@ -265,7 +278,6 @@ impl GridViewRevisionEditor {
.send(); .send();
} }
#[allow(dead_code)]
async fn modify<F>(&self, f: F) -> FlowyResult<()> async fn modify<F>(&self, f: F) -> FlowyResult<()>
where where
F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult<Option<GridViewRevisionChangeset>>, F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult<Option<GridViewRevisionChangeset>>,

View File

@ -142,13 +142,19 @@ impl GridViewManager {
.await; .await;
} }
if row_changeset.has_changed() { if row_changeset.is_empty() {
Some(row_changeset)
} else {
None None
} else {
Some(row_changeset)
} }
} }
pub(crate) async fn did_update_field(&self, field_id: &str) -> FlowyResult<()> {
let view_editor = self.get_default_view_editor().await?;
let _ = view_editor.did_update_field(field_id).await?;
Ok(())
}
pub(crate) async fn get_view_editor(&self, view_id: &str) -> FlowyResult<Arc<GridViewRevisionEditor>> { pub(crate) async fn get_view_editor(&self, view_id: &str) -> FlowyResult<Arc<GridViewRevisionEditor>> {
debug_assert!(!view_id.is_empty()); debug_assert!(!view_id.is_empty());
match self.view_editors.get(view_id) { match self.view_editors.get(view_id) {

View File

@ -1,4 +1,4 @@
use crate::entities::GroupRowsChangesetPB; use crate::entities::GroupChangesetPB;
use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::controller::MoveGroupRowContext;
use flowy_grid_data_model::revision::RowRevision; use flowy_grid_data_model::revision::RowRevision;
@ -6,12 +6,8 @@ use flowy_grid_data_model::revision::RowRevision;
pub trait GroupAction: Send + Sync { pub trait GroupAction: Send + Sync {
type CellDataType; type CellDataType;
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool; fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB>; fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
fn remove_row_if_match( fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB>;
fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupRowsChangesetPB>; fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupChangesetPB>;
} }

View File

@ -1,12 +1,12 @@
use crate::entities::{GroupPB, GroupViewChangesetPB, InsertedGroupPB};
use crate::services::group::{default_group_configuration, Group}; use crate::services::group::{default_group_configuration, Group};
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{ use flowy_grid_data_model::revision::{
FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRecordRevision, FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRecordRevision,
}; };
use std::marker::PhantomData;
use indexmap::IndexMap; use indexmap::IndexMap;
use lib_infra::future::AFFuture; use lib_infra::future::AFFuture;
use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
pub trait GroupConfigurationReader: Send + Sync + 'static { pub trait GroupConfigurationReader: Send + Sync + 'static {
@ -26,6 +26,7 @@ pub trait GroupConfigurationWriter: Send + Sync + 'static {
} }
pub struct GenericGroupConfiguration<C> { pub struct GenericGroupConfiguration<C> {
view_id: String,
pub configuration: Arc<GroupConfigurationRevision>, pub configuration: Arc<GroupConfigurationRevision>,
configuration_content: PhantomData<C>, configuration_content: PhantomData<C>,
field_rev: Arc<FieldRevision>, field_rev: Arc<FieldRevision>,
@ -39,6 +40,7 @@ where
{ {
#[tracing::instrument(level = "trace", skip_all, err)] #[tracing::instrument(level = "trace", skip_all, err)]
pub async fn new( pub async fn new(
view_id: String,
field_rev: Arc<FieldRevision>, field_rev: Arc<FieldRevision>,
reader: Arc<dyn GroupConfigurationReader>, reader: Arc<dyn GroupConfigurationReader>,
writer: Arc<dyn GroupConfigurationWriter>, writer: Arc<dyn GroupConfigurationWriter>,
@ -56,6 +58,7 @@ where
// let configuration = C::from_configuration_content(&configuration_rev.content)?; // let configuration = C::from_configuration_content(&configuration_rev.content)?;
Ok(Self { Ok(Self {
view_id,
field_rev, field_rev,
groups_map: IndexMap::new(), groups_map: IndexMap::new(),
writer, writer,
@ -72,8 +75,18 @@ where
self.groups_map.values().cloned().collect() self.groups_map.values().cloned().collect()
} }
pub(crate) async fn merge_groups(&mut self, groups: Vec<Group>) -> FlowyResult<()> { pub(crate) fn merge_groups(&mut self, groups: Vec<Group>) -> FlowyResult<Option<GroupViewChangesetPB>> {
let (group_revs, groups) = merge_groups(&self.configuration.groups, groups); let MergeGroupResult {
groups,
inserted_groups,
updated_groups,
} = merge_groups(&self.configuration.groups, groups);
let group_revs = groups
.iter()
.map(|group| GroupRecordRevision::new(group.id.clone(), group.name.clone()))
.collect();
self.mut_configuration(move |configuration| { self.mut_configuration(move |configuration| {
configuration.groups = group_revs; configuration.groups = group_revs;
true true
@ -82,7 +95,14 @@ where
groups.into_iter().for_each(|group| { groups.into_iter().for_each(|group| {
self.groups_map.insert(group.id.clone(), group); self.groups_map.insert(group.id.clone(), group);
}); });
Ok(())
let changeset = make_group_view_changeset(self.view_id.clone(), inserted_groups, updated_groups);
tracing::trace!("Group changeset: {:?}", changeset);
if changeset.is_empty() {
Ok(None)
} else {
Ok(Some(changeset))
}
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -101,7 +121,7 @@ where
Ok(()) Ok(())
} }
pub(crate) fn with_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) { pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
self.groups_map.iter_mut().for_each(|(_, group)| { self.groups_map.iter_mut().for_each(|(_, group)| {
each(group); each(group);
}) })
@ -189,33 +209,82 @@ where
} }
} }
fn merge_groups(old_group_revs: &[GroupRecordRevision], groups: Vec<Group>) -> (Vec<GroupRecordRevision>, Vec<Group>) { fn merge_groups(old_groups: &[GroupRecordRevision], groups: Vec<Group>) -> MergeGroupResult {
if old_group_revs.is_empty() { let mut merge_result = MergeGroupResult::new();
let new_groups = groups if old_groups.is_empty() {
.iter() merge_result.groups = groups;
.map(|group| GroupRecordRevision::new(group.id.clone())) return merge_result;
.collect();
return (new_groups, groups);
} }
// group_map is a helper map is used to filter out the new groups.
let mut group_map: IndexMap<String, Group> = IndexMap::new(); let mut group_map: IndexMap<String, Group> = IndexMap::new();
groups.into_iter().for_each(|group| { groups.into_iter().for_each(|group| {
group_map.insert(group.id.clone(), group); group_map.insert(group.id.clone(), group);
}); });
// Inert // The group is ordered in old groups. Add them before adding the new groups
let mut sorted_groups: Vec<Group> = vec![]; for group_rev in old_groups {
for group_rev in old_group_revs {
if let Some(group) = group_map.remove(&group_rev.group_id) { if let Some(group) = group_map.remove(&group_rev.group_id) {
sorted_groups.push(group); if group.name == group_rev.name {
merge_result.add_group(group);
} else {
merge_result.add_updated_group(group);
}
} }
} }
sorted_groups.extend(group_map.into_values().collect::<Vec<Group>>());
let new_group_revs = sorted_groups
.iter()
.map(|group| GroupRecordRevision::new(group.id.clone()))
.collect::<Vec<GroupRecordRevision>>();
tracing::trace!("group revs: {}, groups: {}", new_group_revs.len(), sorted_groups.len()); // Find out the new groups
(new_group_revs, sorted_groups) let new_groups = group_map.into_values().collect::<Vec<Group>>();
for (index, group) in new_groups.into_iter().enumerate() {
merge_result.add_insert_group(index, group);
}
merge_result
}
struct MergeGroupResult {
groups: Vec<Group>,
inserted_groups: Vec<InsertedGroupPB>,
updated_groups: Vec<Group>,
}
impl MergeGroupResult {
fn new() -> Self {
Self {
groups: vec![],
inserted_groups: vec![],
updated_groups: vec![],
}
}
fn add_updated_group(&mut self, group: Group) {
self.groups.push(group.clone());
self.updated_groups.push(group);
}
fn add_group(&mut self, group: Group) {
self.groups.push(group.clone());
}
fn add_insert_group(&mut self, index: usize, group: Group) {
self.groups.push(group.clone());
let inserted_group = InsertedGroupPB {
group: GroupPB::from(group),
index: index as i32,
};
self.inserted_groups.push(inserted_group);
}
}
fn make_group_view_changeset(
view_id: String,
inserted_groups: Vec<InsertedGroupPB>,
updated_group: Vec<Group>,
) -> GroupViewChangesetPB {
let changeset = GroupViewChangesetPB {
view_id,
inserted_groups,
deleted_groups: vec![],
update_groups: updated_group.into_iter().map(GroupPB::from).collect(),
};
changeset
} }

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupRowsChangesetPB, RowPB}; use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB};
use crate::services::cell::{decode_any_cell_data, CellBytesParser}; use crate::services::cell::{decode_any_cell_data, CellBytesParser};
use crate::services::group::action::GroupAction; use crate::services::group::action::GroupAction;
use crate::services::group::configuration::GenericGroupConfiguration; use crate::services::group::configuration::GenericGroupConfiguration;
@ -51,15 +51,17 @@ pub trait GroupControllerSharedOperation: Send + Sync {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>>; ) -> FlowyResult<Vec<GroupChangesetPB>>;
fn did_delete_row( fn did_delete_row(
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>>; ) -> FlowyResult<Vec<GroupChangesetPB>>;
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsChangesetPB>>; fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>>;
fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;
} }
/// C: represents the group configuration that impl [GroupConfigurationSerde] /// C: represents the group configuration that impl [GroupConfigurationSerde]
@ -89,7 +91,7 @@ where
let field_type_rev = field_rev.ty; 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_entry::<T>(field_type_rev);
let groups = G::generate_groups(&field_rev.id, &configuration, &type_option); let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
let _ = configuration.merge_groups(groups).await?; let _ = configuration.merge_groups(groups)?;
let default_group = Group::new( let default_group = Group::new(
DEFAULT_GROUP_ID.to_owned(), DEFAULT_GROUP_ID.to_owned(),
field_rev.id.clone(), field_rev.id.clone(),
@ -112,6 +114,9 @@ impl<C, T, G, P> GroupControllerSharedOperation for GenericGroupController<C, T,
where where
P: CellBytesParser, P: CellBytesParser,
C: GroupConfigurationContentSerde, C: GroupConfigurationContentSerde,
T: TypeOptionDataDeserializer,
G: GroupGenerator<ConfigurationType = GenericGroupConfiguration<C>, TypeOptionType = T>,
Self: GroupAction<CellDataType = P::Object>, Self: GroupAction<CellDataType = P::Object>,
{ {
fn field_id(&self) -> &str { fn field_id(&self) -> &str {
@ -173,11 +178,12 @@ where
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>> { ) -> FlowyResult<Vec<GroupChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
Ok(self.add_row_if_match(row_rev, &cell_data)) let changesets = self.add_row_if_match(row_rev, &cell_data);
Ok(changesets)
} else { } else {
Ok(vec![]) Ok(vec![])
} }
@ -187,7 +193,7 @@ where
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>> { ) -> FlowyResult<Vec<GroupChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
@ -197,7 +203,7 @@ where
} }
} }
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsChangesetPB>> { fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> {
if let Some(cell_rev) = context.row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = context.row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), context.field_rev); let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), context.field_rev);
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
@ -206,6 +212,14 @@ where
Ok(vec![]) Ok(vec![])
} }
} }
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)?;
Ok(changeset)
}
} }
struct GroupRow { struct GroupRow {

View File

@ -1,4 +1,4 @@
use crate::entities::GroupRowsChangesetPB; use crate::entities::GroupChangesetPB;
use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
use crate::services::group::action::GroupAction; use crate::services::group::action::GroupAction;
use crate::services::group::configuration::GenericGroupConfiguration; use crate::services::group::configuration::GenericGroupConfiguration;
@ -24,11 +24,7 @@ impl GroupAction for CheckboxGroupController {
false false
} }
fn add_row_if_match( fn add_row_if_match(&mut self, _row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
&mut self,
_row_rev: &RowRevision,
_cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> {
todo!() todo!()
} }
@ -36,15 +32,11 @@ impl GroupAction for CheckboxGroupController {
&mut self, &mut self,
_row_rev: &RowRevision, _row_rev: &RowRevision,
_cell_data: &Self::CellDataType, _cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> { ) -> Vec<GroupChangesetPB> {
todo!() todo!()
} }
fn move_row( fn move_row(&mut self, _cell_data: &Self::CellDataType, _context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
&mut self,
_cell_data: &Self::CellDataType,
_context: MoveGroupRowContext,
) -> Vec<GroupRowsChangesetPB> {
todo!() todo!()
} }
} }

View File

@ -1,4 +1,4 @@
use crate::entities::GroupRowsChangesetPB; use crate::entities::GroupChangesetPB;
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser}; use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
use crate::services::group::action::GroupAction; use crate::services::group::action::GroupAction;
@ -25,34 +25,32 @@ impl GroupAction for MultiSelectGroupController {
cell_data.select_options.iter().any(|option| option.id == content) cell_data.select_options.iter().any(|option| option.id == content)
} }
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> { fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.configuration.with_mut_groups(|group| { self.configuration.iter_mut_groups(|group| {
add_row(group, &mut changesets, cell_data, row_rev); if let Some(changeset) = add_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
}); });
changesets changesets
} }
fn remove_row_if_match( fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.configuration.with_mut_groups(|group| { self.configuration.iter_mut_groups(|group| {
remove_row(group, &mut changesets, cell_data, row_rev); if let Some(changeset) = remove_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
}); });
changesets changesets
} }
fn move_row( fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
&mut self,
cell_data: &Self::CellDataType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsChangesetPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.configuration.with_mut_groups(|group| { self.configuration.iter_mut_groups(|group| {
move_select_option_row(group, &mut group_changeset, cell_data, &mut context); if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) {
group_changeset.push(changeset);
}
}); });
group_changeset group_changeset
} }

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupRowsChangesetPB, RowPB}; use crate::entities::{GroupChangesetPB, RowPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB}; use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
use crate::services::group::action::GroupAction; use crate::services::group::action::GroupAction;
@ -25,34 +25,32 @@ impl GroupAction for SingleSelectGroupController {
cell_data.select_options.iter().any(|option| option.id == content) cell_data.select_options.iter().any(|option| option.id == content)
} }
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> { fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.configuration.with_mut_groups(|group| { self.configuration.iter_mut_groups(|group| {
add_row(group, &mut changesets, cell_data, row_rev); if let Some(changeset) = add_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
}); });
changesets changesets
} }
fn remove_row_if_match( fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.configuration.with_mut_groups(|group| { self.configuration.iter_mut_groups(|group| {
remove_row(group, &mut changesets, cell_data, row_rev); if let Some(changeset) = remove_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
}); });
changesets changesets
} }
fn move_row( fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
&mut self,
cell_data: &Self::CellDataType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsChangesetPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.configuration.with_mut_groups(|group| { self.configuration.iter_mut_groups(|group| {
move_select_option_row(group, &mut group_changeset, cell_data, &mut context); if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) {
group_changeset.push(changeset);
}
}); });
group_changeset group_changeset
} }

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupRowsChangesetPB, InsertedRowPB, RowPB}; use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::SelectOptionCellDataPB; use crate::services::field::SelectOptionCellDataPB;
use crate::services::group::configuration::GenericGroupConfiguration; use crate::services::group::configuration::GenericGroupConfiguration;
@ -11,47 +11,56 @@ pub type SelectOptionGroupConfiguration = GenericGroupConfiguration<SelectOption
pub fn add_row( pub fn add_row(
group: &mut Group, group: &mut Group,
changesets: &mut Vec<GroupRowsChangesetPB>,
cell_data: &SelectOptionCellDataPB, cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision, row_rev: &RowRevision,
) { ) -> Option<GroupChangesetPB> {
let mut changeset = GroupChangesetPB::new(group.id.clone());
cell_data.select_options.iter().for_each(|option| { cell_data.select_options.iter().for_each(|option| {
if option.id == group.id { if option.id == group.id {
if !group.contains_row(&row_rev.id) { if !group.contains_row(&row_rev.id) {
let row_pb = RowPB::from(row_rev); let row_pb = RowPB::from(row_rev);
changesets.push(GroupRowsChangesetPB::insert( changeset.inserted_rows.push(InsertedRowPB::new(row_pb.clone()));
group.id.clone(),
vec![InsertedRowPB::new(row_pb.clone())],
));
group.add_row(row_pb); group.add_row(row_pb);
} }
} else if group.contains_row(&row_rev.id) { } else if group.contains_row(&row_rev.id) {
changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); changeset.deleted_rows.push(row_rev.id.clone());
group.remove_row(&row_rev.id); group.remove_row(&row_rev.id);
} }
}); });
if changeset.is_empty() {
None
} else {
Some(changeset)
}
} }
pub fn remove_row( pub fn remove_row(
group: &mut Group, group: &mut Group,
changesets: &mut Vec<GroupRowsChangesetPB>,
cell_data: &SelectOptionCellDataPB, cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision, row_rev: &RowRevision,
) { ) -> Option<GroupChangesetPB> {
let mut changeset = GroupChangesetPB::new(group.id.clone());
cell_data.select_options.iter().for_each(|option| { cell_data.select_options.iter().for_each(|option| {
if option.id == group.id && group.contains_row(&row_rev.id) { if option.id == group.id && group.contains_row(&row_rev.id) {
changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); changeset.deleted_rows.push(row_rev.id.clone());
group.remove_row(&row_rev.id); group.remove_row(&row_rev.id);
} }
}); });
if changeset.is_empty() {
None
} else {
Some(changeset)
}
} }
pub fn move_select_option_row( pub fn move_select_option_row(
group: &mut Group, group: &mut Group,
group_changeset: &mut Vec<GroupRowsChangesetPB>,
_cell_data: &SelectOptionCellDataPB, _cell_data: &SelectOptionCellDataPB,
context: &mut MoveGroupRowContext, context: &mut MoveGroupRowContext,
) { ) -> Option<GroupChangesetPB> {
let mut changeset = GroupChangesetPB::new(group.id.clone());
let MoveGroupRowContext { let MoveGroupRowContext {
row_rev, row_rev,
row_changeset, row_changeset,
@ -68,7 +77,7 @@ pub fn move_select_option_row(
// Remove the row in which group contains it // Remove the row in which group contains it
if from_index.is_some() { if from_index.is_some() {
group_changeset.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()])); changeset.deleted_rows.push(row_rev.id.clone());
tracing::debug!("Group:{} remove row:{}", group.id, row_rev.id); tracing::debug!("Group:{} remove row:{}", group.id, row_rev.id);
group.remove_row(&row_rev.id); group.remove_row(&row_rev.id);
} }
@ -78,7 +87,7 @@ pub fn move_select_option_row(
let mut inserted_row = InsertedRowPB::new(row_pb.clone()); let mut inserted_row = InsertedRowPB::new(row_pb.clone());
match to_index { match to_index {
None => { None => {
group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row])); changeset.inserted_rows.push(inserted_row);
tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
group.add_row(row_pb); group.add_row(row_pb);
} }
@ -91,7 +100,7 @@ pub fn move_select_option_row(
tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
group.add_row(row_pb); group.add_row(row_pb);
} }
group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row])); changeset.inserted_rows.push(inserted_row);
} }
} }
@ -100,6 +109,12 @@ pub fn move_select_option_row(
tracing::debug!("Mark row:{} belong to group:{}", row_rev.id, group.id); tracing::debug!("Mark row:{} belong to group:{}", row_rev.id, group.id);
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev); let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
row_changeset.cell_by_field_id.insert(field_rev.id.clone(), cell_rev); row_changeset.cell_by_field_id.insert(field_rev.id.clone(), cell_rev);
changeset.updated_rows.push(RowPB::from(*row_rev));
} }
} }
if changeset.is_empty() {
None
} else {
Some(changeset)
}
} }

View File

@ -1,33 +1,22 @@
use crate::entities::{GroupPB, RowPB}; use crate::entities::RowPB;
#[derive(Clone)] #[derive(Clone, PartialEq, Eq)]
pub struct Group { pub struct Group {
pub id: String, pub id: String,
pub field_id: String, pub field_id: String,
pub desc: String, pub name: String,
rows: Vec<RowPB>, pub(crate) rows: Vec<RowPB>,
/// [content] is used to determine which group the cell belongs to. /// [content] is used to determine which group the cell belongs to.
pub content: String, pub content: String,
} }
impl std::convert::From<Group> for GroupPB {
fn from(group: Group) -> Self {
Self {
field_id: group.field_id,
group_id: group.id,
desc: group.desc,
rows: group.rows,
}
}
}
impl Group { impl Group {
pub fn new(id: String, field_id: String, desc: String, content: String) -> Self { pub fn new(id: String, field_id: String, name: String, content: String) -> Self {
Self { Self {
id, id,
field_id, field_id,
desc, name,
rows: vec![], rows: vec![],
content, content,
} }

View File

@ -1,4 +1,4 @@
use crate::entities::{FieldType, GroupRowsChangesetPB}; use crate::entities::{FieldType, GroupChangesetPB, GroupViewChangesetPB};
use crate::services::group::configuration::GroupConfigurationReader; use crate::services::group::configuration::GroupConfigurationReader;
use crate::services::group::controller::{GroupController, MoveGroupRowContext}; use crate::services::group::controller::{GroupController, MoveGroupRowContext};
use crate::services::group::{ use crate::services::group::{
@ -15,18 +15,20 @@ use std::future::Future;
use std::sync::Arc; use std::sync::Arc;
pub(crate) struct GroupService { pub(crate) struct GroupService {
view_id: String,
configuration_reader: Arc<dyn GroupConfigurationReader>, configuration_reader: Arc<dyn GroupConfigurationReader>,
configuration_writer: Arc<dyn GroupConfigurationWriter>, configuration_writer: Arc<dyn GroupConfigurationWriter>,
group_controller: Option<Box<dyn GroupController>>, group_controller: Option<Box<dyn GroupController>>,
} }
impl GroupService { impl GroupService {
pub(crate) async fn new<R, W>(configuration_reader: R, configuration_writer: W) -> Self pub(crate) async fn new<R, W>(view_id: String, configuration_reader: R, configuration_writer: W) -> Self
where where
R: GroupConfigurationReader, R: GroupConfigurationReader,
W: GroupConfigurationWriter, W: GroupConfigurationWriter,
{ {
Self { Self {
view_id,
configuration_reader: Arc::new(configuration_reader), configuration_reader: Arc::new(configuration_reader),
configuration_writer: Arc::new(configuration_writer), configuration_writer: Arc::new(configuration_writer),
group_controller: None, group_controller: None,
@ -36,8 +38,8 @@ impl GroupService {
pub(crate) async fn groups(&self) -> Vec<Group> { pub(crate) async fn groups(&self) -> Vec<Group> {
self.group_controller self.group_controller
.as_ref() .as_ref()
.and_then(|group_controller| Some(group_controller.groups())) .map(|group_controller| group_controller.groups())
.unwrap_or(vec![]) .unwrap_or_default()
} }
pub(crate) async fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { pub(crate) async fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
@ -86,7 +88,7 @@ impl GroupService {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
get_field_fn: F, get_field_fn: F,
) -> Option<Vec<GroupRowsChangesetPB>> ) -> Option<Vec<GroupChangesetPB>>
where where
F: FnOnce(String) -> O, F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static, O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
@ -111,7 +113,7 @@ impl GroupService {
to_group_id: &str, to_group_id: &str,
to_row_id: Option<String>, to_row_id: Option<String>,
get_field_fn: F, get_field_fn: F,
) -> Option<Vec<GroupRowsChangesetPB>> ) -> Option<Vec<GroupChangesetPB>>
where where
F: FnOnce(String) -> O, F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static, O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
@ -141,7 +143,7 @@ impl GroupService {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
get_field_fn: F, get_field_fn: F,
) -> Option<Vec<GroupRowsChangesetPB>> ) -> Option<Vec<GroupChangesetPB>>
where where
F: FnOnce(String) -> O, F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static, O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
@ -170,6 +172,17 @@ impl GroupService {
} }
} }
#[tracing::instrument(level = "trace", name = "group_did_update_field", skip(self, field_rev), err)]
pub(crate) async fn did_update_field(
&mut self,
field_rev: &FieldRevision,
) -> FlowyResult<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)] #[tracing::instrument(level = "trace", skip(self, field_rev), err)]
async fn make_group_controller( async fn make_group_controller(
&self, &self,
@ -189,6 +202,7 @@ impl GroupService {
} }
FieldType::SingleSelect => { FieldType::SingleSelect => {
let configuration = SelectOptionGroupConfiguration::new( let configuration = SelectOptionGroupConfiguration::new(
self.view_id.clone(),
field_rev.clone(), field_rev.clone(),
self.configuration_reader.clone(), self.configuration_reader.clone(),
self.configuration_writer.clone(), self.configuration_writer.clone(),
@ -199,6 +213,7 @@ impl GroupService {
} }
FieldType::MultiSelect => { FieldType::MultiSelect => {
let configuration = SelectOptionGroupConfiguration::new( let configuration = SelectOptionGroupConfiguration::new(
self.view_id.clone(),
field_rev.clone(), field_rev.clone(),
self.configuration_reader.clone(), self.configuration_reader.clone(),
self.configuration_writer.clone(), self.configuration_writer.clone(),
@ -209,6 +224,7 @@ impl GroupService {
} }
FieldType::Checkbox => { FieldType::Checkbox => {
let configuration = CheckboxGroupConfiguration::new( let configuration = CheckboxGroupConfiguration::new(
self.view_id.clone(),
field_rev.clone(), field_rev.clone(),
self.configuration_reader.clone(), self.configuration_reader.clone(),
self.configuration_writer.clone(), self.configuration_writer.clone(),

View File

@ -1,9 +1,11 @@
use crate::grid::grid_editor::GridEditorTest; use crate::grid::grid_editor::GridEditorTest;
use flowy_grid::entities::{ use flowy_grid::entities::{
CreateRowParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB, CreateRowParams, FieldChangesetParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB,
}; };
use flowy_grid::services::cell::insert_select_option_cell; use flowy_grid::services::cell::insert_select_option_cell;
use flowy_grid_data_model::revision::RowChangeset; use flowy_grid_data_model::revision::RowChangeset;
use std::time::Duration;
use tokio::time::interval;
pub enum GroupScript { pub enum GroupScript {
AssertGroupRowCount { AssertGroupRowCount {
@ -42,6 +44,9 @@ pub enum GroupScript {
from_group_index: usize, from_group_index: usize,
to_group_index: usize, to_group_index: usize,
}, },
UpdateField {
changeset: FieldChangesetParams,
},
} }
pub struct GridGroupTest { pub struct GridGroupTest {
@ -156,6 +161,12 @@ impl GridGroupTest {
} => { } => {
let group = self.group_at_index(group_index).await; let group = self.group_at_index(group_index).await;
assert_eq!(group.group_id, group_pb.group_id); 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;
} }
} }
} }

View File

@ -1,5 +1,6 @@
use crate::grid::group_test::script::GridGroupTest; use crate::grid::group_test::script::GridGroupTest;
use crate::grid::group_test::script::GroupScript::*; use crate::grid::group_test::script::GroupScript::*;
use flowy_grid::entities::FieldChangesetParams;
#[tokio::test] #[tokio::test]
async fn group_init_test() { async fn group_init_test() {
@ -314,3 +315,25 @@ async fn group_move_group_test() {
]; ];
test.run_scripts(scripts).await; test.run_scripts(scripts).await;
} }
#[tokio::test]
async fn group_update_field_test() {
let mut test = GridGroupTest::new().await;
let mut group = test.group_at_index(0).await;
let changeset = FieldChangesetParams {
field_id: group.field_id.clone(),
grid_id: test.grid_id.clone(),
name: Some("ABC".to_string()),
..Default::default()
};
// group.desc = "ABC".to_string();
let scripts = vec![
UpdateField { changeset },
AssertGroup {
group_index: 0,
expected_group: group,
},
];
test.run_scripts(scripts).await;
}

View File

@ -59,8 +59,8 @@ impl RowChangeset {
} }
} }
pub fn has_changed(&self) -> bool { pub fn is_empty(&self) -> bool {
self.height.is_some() || self.visibility.is_some() || !self.cell_by_field_id.is_empty() self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty()
} }
} }

View File

@ -110,6 +110,9 @@ impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision {
pub struct GroupRecordRevision { pub struct GroupRecordRevision {
pub group_id: String, pub group_id: String,
#[serde(default)]
pub name: String,
#[serde(default = "DEFAULT_GROUP_RECORD_VISIBILITY")] #[serde(default = "DEFAULT_GROUP_RECORD_VISIBILITY")]
pub visible: bool, pub visible: bool,
} }
@ -117,9 +120,10 @@ pub struct GroupRecordRevision {
const DEFAULT_GROUP_RECORD_VISIBILITY: fn() -> bool = || true; const DEFAULT_GROUP_RECORD_VISIBILITY: fn() -> bool = || true;
impl GroupRecordRevision { impl GroupRecordRevision {
pub fn new(group_id: String) -> Self { pub fn new(group_id: String, group_name: String) -> Self {
Self { Self {
group_id, group_id,
name: group_name,
visible: true, visible: true,
} }
} }