chore: update board column name

This commit is contained in:
appflowy 2022-08-24 16:57:53 +08:00
parent f5f3f51cca
commit 82b44c2c98
30 changed files with 562 additions and 215 deletions

View File

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

View File

@ -10,9 +10,15 @@ import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
import 'board_listener.dart';
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
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 OnRowsChanged = void Function(
List<RowInfo>,
RowsChangedReason,
@ -23,6 +29,7 @@ class BoardDataController {
final String gridId;
final GridFFIService _gridFFIService;
final GridFieldCache fieldCache;
final BoardListener _listener;
// key: the block id
final LinkedHashMap<String, GridBlockCache> _blocks;
@ -44,16 +51,20 @@ class BoardDataController {
BoardDataController({required ViewPB view})
: gridId = view.id,
_listener = BoardListener(view.id),
_blocks = LinkedHashMap.new(),
_gridFFIService = GridFFIService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id);
void addListener({
OnGridChanged? onGridChanged,
required OnGridChanged onGridChanged,
OnFieldsChanged? onFieldsChanged,
DidLoadGroups? didLoadGroups,
OnRowsChanged? onRowsChanged,
OnError? onError,
required DidLoadGroups didLoadGroups,
required OnRowsChanged onRowsChanged,
required OnUpdatedGroup onUpdatedGroup,
required OnDeletedGroup onDeletedGroup,
required OnInsertedGroup onInsertedGroup,
required OnError? onError,
}) {
_onGridChanged = onGridChanged;
_onFieldsChanged = onFieldsChanged;
@ -64,6 +75,25 @@ class BoardDataController {
fieldCache.addListener(onFields: (fields) {
_onFieldsChanged?.call(UnmodifiableListView(fields));
});
_listener.start(onBoardChanged: (result) {
result.fold(
(changeset) {
if (changeset.updateGroups.isNotEmpty) {
onUpdatedGroup.call(changeset.updateGroups);
}
if (changeset.insertedGroups.isNotEmpty) {
onInsertedGroup.call(changeset.insertedGroups);
}
if (changeset.deletedGroups.isNotEmpty) {
onDeletedGroup.call(changeset.deletedGroups);
}
},
(e) => _onError?.call(e),
);
});
}
Future<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() {
_listener.start(onGroupChanged: (result) {
result.fold(
(GroupRowsChangesetPB changeset) {
(GroupChangesetPB changeset) {
for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group.groupId, deletedRow);
}
for (final insertedRow in changeset.insertedRows) {
final index = insertedRow.hasIndex() ? insertedRow.index : null;
@ -52,11 +57,6 @@ class GroupController {
);
}
for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group.groupId, deletedRow);
}
for (final updatedRow in changeset.updatedRows) {
final index = group.rows.indexWhere(
(rowPB) => rowPB.id == updatedRow.id,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,6 +34,13 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
UnmodifiableListView<AFColumnItem> get items =>
UnmodifiableListView(columnData.items);
void updateColumnName(String newName) {
if (columnData.headerData.columnName != newName) {
columnData.headerData.columnName = newName;
notifyListeners();
}
}
/// Remove the item at [index].
/// * [index] the index of the item you want to remove
/// * [notify] the default value of [notify] is true, it will notify the
@ -123,6 +130,20 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
notifyListeners();
}
void replaceOrInsertItem(AFColumnItem newItem) {
final index = columnData._items.indexWhere((item) => item.id == newItem.id);
if (index != -1) {
removeAt(index);
columnData._items.removeAt(index);
columnData._items.insert(index, newItem);
notifyListeners();
} else {
columnData._items.add(newItem);
notifyListeners();
}
}
bool _containsItem(AFColumnItem item) {
return columnData._items.indexWhere((element) => element.id == item.id) !=
-1;
@ -133,16 +154,20 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
@override
final String id;
final String desc;
AFBoardColumnHeaderData headerData;
final List<AFColumnItem> _items;
final CustomData? customData;
AFBoardColumnData({
this.customData,
required this.id,
this.desc = "",
required String name,
List<AFColumnItem> items = const [],
}) : _items = items;
}) : _items = items,
headerData = AFBoardColumnHeaderData(
columnId: id,
columnName: name,
);
/// Returns the readonly List<ColumnItem>
UnmodifiableListView<AFColumnItem> get items =>
@ -156,3 +181,10 @@ class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
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();
}
AFBoardColumnDataController columnController(String columnId) {
return _columnControllers[columnId]!;
}
AFBoardColumnDataController? getColumnController(String columnId) {
final columnController = _columnControllers[columnId];
if (columnController == null) {
@ -129,6 +125,10 @@ class AFBoardDataController extends ChangeNotifier
getColumnController(columnId)?.removeWhere((item) => item.id == itemId);
}
void updateColumnItem(String columnId, AFColumnItem item) {
getColumnController(columnId)?.replaceOrInsertItem(item);
}
@override
@protected
void swapColumnItem(
@ -137,15 +137,14 @@ class AFBoardDataController extends ChangeNotifier
String toColumnId,
int toColumnIndex,
) {
final item = columnController(fromColumnId).removeAt(fromColumnIndex);
if (columnController(toColumnId).items.length > toColumnIndex) {
assert(columnController(toColumnId).items[toColumnIndex]
is PhantomColumnItem);
final fromColumnController = getColumnController(fromColumnId)!;
final toColumnController = getColumnController(toColumnId)!;
final item = fromColumnController.removeAt(fromColumnIndex);
if (toColumnController.items.length > toColumnIndex) {
assert(toColumnController.items[toColumnIndex] is PhantomColumnItem);
}
columnController(toColumnId).replace(toColumnIndex, item);
toColumnController.replace(toColumnIndex, item);
onMoveColumnItemToColumn?.call(
fromColumnId,
fromColumnIndex,
@ -174,9 +173,12 @@ class AFBoardDataController extends ChangeNotifier
@override
@protected
bool removePhantom(String columnId) {
final columnController = this.columnController(columnId);
final columnController = getColumnController(columnId);
if (columnController == null) {
Log.warn('Can not find the column controller with columnId: $columnId');
return false;
}
final index = columnController.items.indexWhere((item) => item.isPhantom);
final isExist = index != -1;
if (isExist) {
columnController.removeAt(index);
@ -190,7 +192,7 @@ class AFBoardDataController extends ChangeNotifier
@override
@protected
void updatePhantom(String columnId, int newIndex) {
final columnDataController = columnController(columnId);
final columnDataController = getColumnController(columnId)!;
final index =
columnDataController.items.indexWhere((item) => item.isPhantom);
@ -208,6 +210,6 @@ class AFBoardDataController extends ChangeNotifier
@override
@protected
void insertPhantom(String columnId, int index, PhantomColumnItem item) {
columnController(columnId).insert(index, item);
getColumnController(columnId)!.insert(index, item);
}
}

View File

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

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.
#[derive(Debug, Default, Clone, ProtoBuf)]
#[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)]
pub struct RowPB {
#[pb(index = 1)]
pub block_id: String,

View File

@ -1,4 +1,5 @@
use crate::entities::{CreateRowParams, FieldType, GridLayout, RowPB};
use crate::services::group::Group;
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
@ -82,6 +83,17 @@ pub struct GroupPB {
pub rows: Vec<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)]
pub struct RepeatedGridGroupConfigurationPB {
#[pb(index = 1)]

View File

@ -1,5 +1,4 @@
use crate::entities::{GroupPB, InsertedRowPB, RowPB};
use diesel::insertable::ColumnInsertValue::Default;
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
@ -42,7 +41,17 @@ impl std::fmt::Display for GroupChangesetPB {
impl GroupChangesetPB {
pub fn is_empty(&self) -> bool {
self.inserted_rows.is_empty() && self.deleted_rows.is_empty() && self.updated_rows.is_empty()
self.group_name.is_none()
&& self.inserted_rows.is_empty()
&& self.deleted_rows.is_empty()
&& self.updated_rows.is_empty()
}
pub fn new(group_id: String) -> Self {
Self {
group_id,
..Default::default()
}
}
pub fn name(group_id: String, name: &str) -> Self {
@ -126,9 +135,16 @@ pub struct GroupViewChangesetPB {
#[pb(index = 3)]
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)]
pub struct InsertedGroupPB {

View File

@ -188,8 +188,13 @@ impl GridRevisionEditor {
pub async fn replace_field(&self, field_rev: Arc<FieldRevision>) -> FlowyResult<()> {
let field_id = field_rev.id.clone();
let _ = self
.modify(|grid_pad| Ok(grid_pad.replace_field_rev(field_rev)?))
.modify(|grid_pad| Ok(grid_pad.replace_field_rev(field_rev.clone())?))
.await?;
match self.view_manager.did_update_field(&field_rev.id).await {
Ok(_) => {}
Err(e) => tracing::error!("View manager update field failed: {:?}", e),
}
let _ = self.notify_did_update_grid_field(&field_id).await?;
Ok(())
}
@ -263,59 +268,65 @@ impl GridRevisionEditor {
}
async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> {
self.modify(|grid| {
let deserializer = TypeOptionJsonDeserializer(field_type);
let _ = self
.modify(|grid| {
let deserializer = TypeOptionJsonDeserializer(field_type);
let changeset = grid.modify_field(&params.field_id, |field| {
let mut is_changed = None;
if let Some(name) = params.name {
field.name = name;
is_changed = Some(())
}
let changeset = grid.modify_field(&params.field_id, |field| {
let mut is_changed = None;
if let Some(name) = params.name {
field.name = name;
is_changed = Some(())
}
if let Some(desc) = params.desc {
field.desc = desc;
is_changed = Some(())
}
if let Some(desc) = params.desc {
field.desc = desc;
is_changed = Some(())
}
if let Some(field_type) = params.field_type {
field.ty = field_type;
is_changed = Some(())
}
if let Some(field_type) = params.field_type {
field.ty = field_type;
is_changed = Some(())
}
if let Some(frozen) = params.frozen {
field.frozen = frozen;
is_changed = Some(())
}
if let Some(frozen) = params.frozen {
field.frozen = frozen;
is_changed = Some(())
}
if let Some(visibility) = params.visibility {
field.visibility = visibility;
is_changed = Some(())
}
if let Some(visibility) = params.visibility {
field.visibility = visibility;
is_changed = Some(())
}
if let Some(width) = params.width {
field.width = width;
is_changed = Some(())
}
if let Some(width) = params.width {
field.width = width;
is_changed = Some(())
}
if let Some(type_option_data) = params.type_option_data {
match deserializer.deserialize(type_option_data) {
Ok(json_str) => {
let field_type = field.ty;
field.insert_type_option_str(&field_type, json_str);
is_changed = Some(())
}
Err(err) => {
tracing::error!("Deserialize data to type option json failed: {}", err);
if let Some(type_option_data) = params.type_option_data {
match deserializer.deserialize(type_option_data) {
Ok(json_str) => {
let field_type = field.ty;
field.insert_type_option_str(&field_type, json_str);
is_changed = Some(())
}
Err(err) => {
tracing::error!("Deserialize data to type option json failed: {}", err);
}
}
}
}
Ok(is_changed)
})?;
Ok(changeset)
})
.await
Ok(is_changed)
})?;
Ok(changeset)
})
.await?;
match self.view_manager.did_update_field(&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<()> {
@ -585,6 +596,7 @@ impl GridRevisionEditor {
.move_group_row(row_rev, to_group_id, to_row_id.clone())
.await
{
tracing::trace!("Move group row cause row data changed: {:?}", row_changeset);
match self.block_manager.update_row(row_changeset).await {
Ok(_) => {}
Err(e) => {

View File

@ -59,7 +59,7 @@ impl GridViewRevisionEditor {
rev_manager: rev_manager.clone(),
view_pad: pad.clone(),
};
let group_service = GroupService::new(configuration_reader, configuration_writer).await;
let group_service = GroupService::new(view_id.clone(), configuration_reader, configuration_writer).await;
let user_id = user_id.to_owned();
let did_load_group = AtomicBool::new(false);
Ok(Self {
@ -155,7 +155,7 @@ impl GridViewRevisionEditor {
}
}
}
/// 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) {
@ -198,9 +198,10 @@ impl GridViewRevisionEditor {
};
let changeset = GroupViewChangesetPB {
view_id: "".to_string(),
view_id: self.view_id.clone(),
inserted_groups: vec![inserted_group],
deleted_groups: vec![params.from_group_id.clone()],
update_groups: vec![],
};
self.notify_did_update_view(changeset).await;
@ -252,10 +253,15 @@ impl GridViewRevisionEditor {
})
.await
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn did_update_field(&self, field_id: &str) -> FlowyResult<()> {
if let Some(field_rev) = self.field_delegate.get_field_rev(&field_id).await {
let _ = self.group_service.write().await.did_update_field(&field_rev).await?;
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(())
}
@ -272,7 +278,6 @@ impl GridViewRevisionEditor {
.send();
}
#[allow(dead_code)]
async fn modify<F>(&self, f: F) -> FlowyResult<()>
where
F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult<Option<GridViewRevisionChangeset>>,

View File

@ -142,10 +142,10 @@ impl GridViewManager {
.await;
}
if row_changeset.has_changed() {
Some(row_changeset)
} else {
if row_changeset.is_empty() {
None
} else {
Some(row_changeset)
}
}

View File

@ -1,12 +1,12 @@
use crate::entities::{GroupPB, GroupViewChangesetPB, InsertedGroupPB};
use crate::services::group::{default_group_configuration, Group};
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{
FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRecordRevision,
};
use std::marker::PhantomData;
use indexmap::IndexMap;
use lib_infra::future::AFFuture;
use std::marker::PhantomData;
use std::sync::Arc;
pub trait GroupConfigurationReader: Send + Sync + 'static {
@ -26,6 +26,7 @@ pub trait GroupConfigurationWriter: Send + Sync + 'static {
}
pub struct GenericGroupConfiguration<C> {
view_id: String,
pub configuration: Arc<GroupConfigurationRevision>,
configuration_content: PhantomData<C>,
field_rev: Arc<FieldRevision>,
@ -39,6 +40,7 @@ where
{
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn new(
view_id: String,
field_rev: Arc<FieldRevision>,
reader: Arc<dyn GroupConfigurationReader>,
writer: Arc<dyn GroupConfigurationWriter>,
@ -56,6 +58,7 @@ where
// let configuration = C::from_configuration_content(&configuration_rev.content)?;
Ok(Self {
view_id,
field_rev,
groups_map: IndexMap::new(),
writer,
@ -72,8 +75,18 @@ where
self.groups_map.values().cloned().collect()
}
pub(crate) async fn merge_groups(&mut self, groups: Vec<Group>) -> FlowyResult<()> {
let (group_revs, groups) = merge_groups(&self.configuration.groups, groups);
pub(crate) fn merge_groups(&mut self, groups: Vec<Group>) -> FlowyResult<Option<GroupViewChangesetPB>> {
let MergeGroupResult {
groups,
inserted_groups,
updated_groups,
} = merge_groups(&self.configuration.groups, groups);
let group_revs = groups
.iter()
.map(|group| GroupRecordRevision::new(group.id.clone(), group.name.clone()))
.collect();
self.mut_configuration(move |configuration| {
configuration.groups = group_revs;
true
@ -82,7 +95,14 @@ where
groups.into_iter().for_each(|group| {
self.groups_map.insert(group.id.clone(), group);
});
Ok(())
let changeset = make_group_view_changeset(self.view_id.clone(), inserted_groups, updated_groups);
tracing::trace!("Group changeset: {:?}", changeset);
if changeset.is_empty() {
Ok(None)
} else {
Ok(Some(changeset))
}
}
#[allow(dead_code)]
@ -101,7 +121,7 @@ where
Ok(())
}
pub(crate) fn with_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
self.groups_map.iter_mut().for_each(|(_, group)| {
each(group);
})
@ -189,33 +209,82 @@ where
}
}
fn merge_groups(old_group_revs: &[GroupRecordRevision], groups: Vec<Group>) -> (Vec<GroupRecordRevision>, Vec<Group>) {
if old_group_revs.is_empty() {
let new_groups = groups
.iter()
.map(|group| GroupRecordRevision::new(group.id.clone()))
.collect();
return (new_groups, groups);
fn merge_groups(old_groups: &[GroupRecordRevision], groups: Vec<Group>) -> MergeGroupResult {
let mut merge_result = MergeGroupResult::new();
if old_groups.is_empty() {
merge_result.groups = groups;
return merge_result;
}
// group_map is a helper map is used to filter out the new groups.
let mut group_map: IndexMap<String, Group> = IndexMap::new();
groups.into_iter().for_each(|group| {
group_map.insert(group.id.clone(), group);
});
// Inert
let mut sorted_groups: Vec<Group> = vec![];
for group_rev in old_group_revs {
// The group is ordered in old groups. Add them before adding the new groups
for group_rev in old_groups {
if let Some(group) = group_map.remove(&group_rev.group_id) {
sorted_groups.push(group);
if group.name == group_rev.name {
merge_result.add_group(group);
} else {
merge_result.add_updated_group(group);
}
}
}
sorted_groups.extend(group_map.into_values().collect::<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());
(new_group_revs, sorted_groups)
// Find out the new 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::{GroupChangesetPB, RowPB};
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB};
use crate::services::cell::{decode_any_cell_data, CellBytesParser};
use crate::services::group::action::GroupAction;
use crate::services::group::configuration::GenericGroupConfiguration;
@ -61,7 +61,7 @@ pub trait GroupControllerSharedOperation: Send + Sync {
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>>;
fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<()>;
fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;
}
/// C: represents the group configuration that impl [GroupConfigurationSerde]
@ -91,7 +91,7 @@ where
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, &configuration, &type_option);
let _ = configuration.merge_groups(groups).await?;
let _ = configuration.merge_groups(groups)?;
let default_group = Group::new(
DEFAULT_GROUP_ID.to_owned(),
field_rev.id.clone(),
@ -114,6 +114,9 @@ impl<C, T, G, P> GroupControllerSharedOperation for GenericGroupController<C, T,
where
P: CellBytesParser,
C: GroupConfigurationContentSerde,
T: TypeOptionDataDeserializer,
G: GroupGenerator<ConfigurationType = GenericGroupConfiguration<C>, TypeOptionType = T>,
Self: GroupAction<CellDataType = P::Object>,
{
fn field_id(&self) -> &str {
@ -179,7 +182,8 @@ where
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<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 {
Ok(vec![])
}
@ -209,8 +213,12 @@ where
}
}
fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<()> {
todo!()
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)
}
}

View File

@ -27,24 +27,30 @@ 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.with_mut_groups(|group| {
add_row(group, &mut changesets, cell_data, row_rev);
self.configuration.iter_mut_groups(|group| {
if let Some(changeset) = add_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
});
changesets
}
fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
let mut changesets = vec![];
self.configuration.with_mut_groups(|group| {
remove_row(group, &mut changesets, cell_data, row_rev);
self.configuration.iter_mut_groups(|group| {
if let Some(changeset) = remove_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
});
changesets
}
fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
let mut group_changeset = vec![];
self.configuration.with_mut_groups(|group| {
move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
self.configuration.iter_mut_groups(|group| {
if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) {
group_changeset.push(changeset);
}
});
group_changeset
}

View File

@ -27,24 +27,30 @@ 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.with_mut_groups(|group| {
add_row(group, &mut changesets, cell_data, row_rev);
self.configuration.iter_mut_groups(|group| {
if let Some(changeset) = add_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
});
changesets
}
fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
let mut changesets = vec![];
self.configuration.with_mut_groups(|group| {
remove_row(group, &mut changesets, cell_data, row_rev);
self.configuration.iter_mut_groups(|group| {
if let Some(changeset) = remove_row(group, cell_data, row_rev) {
changesets.push(changeset);
}
});
changesets
}
fn move_row(&mut self, cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
let mut group_changeset = vec![];
self.configuration.with_mut_groups(|group| {
move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
self.configuration.iter_mut_groups(|group| {
if let Some(changeset) = move_select_option_row(group, cell_data, &mut context) {
group_changeset.push(changeset);
}
});
group_changeset
}

View File

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

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 id: String,
pub field_id: String,
pub desc: String,
rows: Vec<RowPB>,
pub name: String,
pub(crate) rows: Vec<RowPB>,
/// [content] is used to determine which group the cell belongs to.
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 {
pub fn new(id: String, field_id: String, desc: String, content: String) -> Self {
pub fn new(id: String, field_id: String, name: String, content: String) -> Self {
Self {
id,
field_id,
desc,
name,
rows: vec![],
content,
}

View File

@ -1,4 +1,4 @@
use crate::entities::{FieldType, GroupChangesetPB};
use crate::entities::{FieldType, GroupChangesetPB, GroupViewChangesetPB};
use crate::services::group::configuration::GroupConfigurationReader;
use crate::services::group::controller::{GroupController, MoveGroupRowContext};
use crate::services::group::{
@ -15,18 +15,20 @@ use std::future::Future;
use std::sync::Arc;
pub(crate) struct GroupService {
view_id: String,
configuration_reader: Arc<dyn GroupConfigurationReader>,
configuration_writer: Arc<dyn GroupConfigurationWriter>,
group_controller: Option<Box<dyn GroupController>>,
}
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
R: GroupConfigurationReader,
W: GroupConfigurationWriter,
{
Self {
view_id,
configuration_reader: Arc::new(configuration_reader),
configuration_writer: Arc::new(configuration_writer),
group_controller: None,
@ -36,8 +38,8 @@ impl GroupService {
pub(crate) async fn groups(&self) -> Vec<Group> {
self.group_controller
.as_ref()
.and_then(|group_controller| Some(group_controller.groups()))
.unwrap_or(vec![])
.map(|group_controller| group_controller.groups())
.unwrap_or_default()
}
pub(crate) async fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
@ -170,9 +172,13 @@ impl GroupService {
}
}
pub(crate) async fn did_update_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<()> {
#[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 => Ok(None),
Some(group_controller) => group_controller.did_update_field(field_rev),
}
}
@ -196,6 +202,7 @@ impl GroupService {
}
FieldType::SingleSelect => {
let configuration = SelectOptionGroupConfiguration::new(
self.view_id.clone(),
field_rev.clone(),
self.configuration_reader.clone(),
self.configuration_writer.clone(),
@ -206,6 +213,7 @@ impl GroupService {
}
FieldType::MultiSelect => {
let configuration = SelectOptionGroupConfiguration::new(
self.view_id.clone(),
field_rev.clone(),
self.configuration_reader.clone(),
self.configuration_writer.clone(),
@ -216,6 +224,7 @@ impl GroupService {
}
FieldType::Checkbox => {
let configuration = CheckboxGroupConfiguration::new(
self.view_id.clone(),
field_rev.clone(),
self.configuration_reader.clone(),
self.configuration_writer.clone(),

View File

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

View File

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

View File

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

View File

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