mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: update board column name
This commit is contained in:
parent
f5f3f51cca
commit
82b44c2c98
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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)]
|
||||
|
@ -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 {
|
||||
|
@ -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(¶ms.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(¶ms.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(¶ms.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) => {
|
||||
|
@ -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>>,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user