Merge pull request #579 from AppFlowy-IO/feat/grid_task_runner

This commit is contained in:
Nathan.fooo 2022-06-29 18:28:06 +08:00 committed by GitHub
commit 6554b8354b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 2062 additions and 737 deletions

View File

@ -0,0 +1,90 @@
import 'dart:async';
import 'dart:collection';
import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
class GridBlockCache {
final String gridId;
void Function(GridBlockUpdateNotifierValue)? _onBlockChanged;
final LinkedHashMap<String, _GridBlockListener> _listeners = LinkedHashMap();
GridBlockCache({required this.gridId});
void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) {
_onBlockChanged = onBlockChanged;
for (final listener in _listeners.values) {
listener.start(onBlockChanged);
}
}
Future<void> dispose() async {
for (final listener in _listeners.values) {
await listener.stop();
}
}
void addBlockListener(String blockId) {
if (_onBlockChanged == null) {
Log.error("Should call start() first");
return;
}
if (_listeners.containsKey(blockId)) {
Log.error("Duplicate block listener");
return;
}
final listener = _GridBlockListener(blockId: blockId);
listener.start(_onBlockChanged!);
_listeners[blockId] = listener;
}
}
typedef GridBlockUpdateNotifierValue = Either<List<GridRowsChangeset>, FlowyError>;
class _GridBlockListener {
final String blockId;
PublishNotifier<GridBlockUpdateNotifierValue>? _rowsUpdateNotifier = PublishNotifier();
GridNotificationListener? _listener;
_GridBlockListener({required this.blockId});
void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) {
if (_listener != null) {
_listener?.stop();
}
_listener = GridNotificationListener(
objectId: blockId,
handler: _handler,
);
_rowsUpdateNotifier?.addPublishListener(onBlockChanged);
}
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridBlock:
result.fold(
(payload) => _rowsUpdateNotifier?.value = left([GridRowsChangeset.fromBuffer(payload)]),
(error) => _rowsUpdateNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_rowsUpdateNotifier?.dispose();
_rowsUpdateNotifier = null;
}
}

View File

@ -2,21 +2,21 @@ part of 'cell_service.dart';
typedef GridCellMap = LinkedHashMap<String, GridCell>; typedef GridCellMap = LinkedHashMap<String, GridCell>;
class GridCellCacheData { class _GridCellCacheObject {
GridCellCacheKey key; _GridCellCacheKey key;
dynamic object; dynamic object;
GridCellCacheData({ _GridCellCacheObject({
required this.key, required this.key,
required this.object, required this.object,
}); });
} }
class GridCellCacheKey { class _GridCellCacheKey {
final String fieldId; final String fieldId;
final String objectId; final String rowId;
GridCellCacheKey({ _GridCellCacheKey({
required this.fieldId, required this.fieldId,
required this.objectId, required this.rowId,
}); });
} }
@ -51,46 +51,46 @@ class GridCellCache {
}); });
} }
void addFieldListener(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) { void addFieldListener(_GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
var map = _fieldListenerByFieldId[cacheKey.fieldId]; var map = _fieldListenerByFieldId[cacheKey.fieldId];
if (map == null) { if (map == null) {
_fieldListenerByFieldId[cacheKey.fieldId] = {}; _fieldListenerByFieldId[cacheKey.fieldId] = {};
map = _fieldListenerByFieldId[cacheKey.fieldId]; map = _fieldListenerByFieldId[cacheKey.fieldId];
map![cacheKey.objectId] = [onFieldChanged]; map![cacheKey.rowId] = [onFieldChanged];
} else { } else {
var objects = map[cacheKey.objectId]; var objects = map[cacheKey.rowId];
if (objects == null) { if (objects == null) {
map[cacheKey.objectId] = [onFieldChanged]; map[cacheKey.rowId] = [onFieldChanged];
} else { } else {
objects.add(onFieldChanged); objects.add(onFieldChanged);
} }
} }
} }
void removeFieldListener(GridCellCacheKey cacheKey, VoidCallback fn) { void removeFieldListener(_GridCellCacheKey cacheKey, VoidCallback fn) {
var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId]; var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId];
final index = callbacks?.indexWhere((callback) => callback == fn); final index = callbacks?.indexWhere((callback) => callback == fn);
if (index != null && index != -1) { if (index != null && index != -1) {
callbacks?.removeAt(index); callbacks?.removeAt(index);
} }
} }
void insert<T extends GridCellCacheData>(T item) { void insert<T extends _GridCellCacheObject>(T item) {
var map = _cellDataByFieldId[item.key.fieldId]; var map = _cellDataByFieldId[item.key.fieldId];
if (map == null) { if (map == null) {
_cellDataByFieldId[item.key.fieldId] = {}; _cellDataByFieldId[item.key.fieldId] = {};
map = _cellDataByFieldId[item.key.fieldId]; map = _cellDataByFieldId[item.key.fieldId];
} }
map![item.key.objectId] = item.object; map![item.key.rowId] = item.object;
} }
T? get<T>(GridCellCacheKey key) { T? get<T>(_GridCellCacheKey key) {
final map = _cellDataByFieldId[key.fieldId]; final map = _cellDataByFieldId[key.fieldId];
if (map == null) { if (map == null) {
return null; return null;
} else { } else {
final object = map[key.objectId]; final object = map[key.rowId];
if (object is T) { if (object is T) {
return object; return object;
} else { } else {

View File

@ -19,10 +19,10 @@ import 'package:app_flowy/workspace/application/grid/cell/select_option_service.
import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'dart:convert' show utf8; import 'dart:convert' show utf8;
part 'cell_service.freezed.dart'; part 'cell_service.freezed.dart';
part 'data_loader.dart'; part 'cell_data_loader.dart';
part 'context_builder.dart'; part 'context_builder.dart';
part 'data_cache.dart'; part 'cell_data_cache.dart';
part 'data_persistence.dart'; part 'cell_data_persistence.dart';
// key: rowId // key: rowId
@ -62,7 +62,6 @@ class GridCell with _$GridCell {
required String gridId, required String gridId,
required String rowId, required String rowId,
required Field field, required Field field,
Cell? cell,
}) = _GridCell; }) = _GridCell;
// ignore: unused_element // ignore: unused_element

View File

@ -100,7 +100,7 @@ class GridCellContextBuilder {
class _GridCellContext<T, D> extends Equatable { class _GridCellContext<T, D> extends Equatable {
final GridCell gridCell; final GridCell gridCell;
final GridCellCache cellCache; final GridCellCache cellCache;
final GridCellCacheKey _cacheKey; final _GridCellCacheKey _cacheKey;
final IGridCellDataLoader<T> cellDataLoader; final IGridCellDataLoader<T> cellDataLoader;
final _GridCellDataPersistence<D> cellDataPersistence; final _GridCellDataPersistence<D> cellDataPersistence;
final FieldService _fieldService; final FieldService _fieldService;
@ -118,7 +118,7 @@ class _GridCellContext<T, D> extends Equatable {
required this.cellDataLoader, required this.cellDataLoader,
required this.cellDataPersistence, required this.cellDataPersistence,
}) : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id), }) : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
_cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id); _cacheKey = _GridCellCacheKey(rowId: gridCell.rowId, fieldId: gridCell.field.id);
_GridCellContext<T, D> clone() { _GridCellContext<T, D> clone() {
return _GridCellContext( return _GridCellContext(
@ -213,7 +213,7 @@ class _GridCellContext<T, D> extends Equatable {
_loadDataOperation = Timer(const Duration(milliseconds: 10), () { _loadDataOperation = Timer(const Duration(milliseconds: 10), () {
cellDataLoader.loadData().then((data) { cellDataLoader.loadData().then((data) {
_cellDataNotifier?.value = data; _cellDataNotifier?.value = data;
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data)); cellCache.insert(_GridCellCacheObject(key: _cacheKey, object: data));
}); });
}); });
} }

View File

@ -1,11 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'block/block_listener.dart';
import 'cell/cell_service/cell_service.dart'; import 'cell/cell_service/cell_service.dart';
import 'grid_service.dart'; import 'grid_service.dart';
import 'row/row_service.dart'; import 'row/row_service.dart';
@ -19,9 +21,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
late final GridRowCache rowCache; late final GridRowCache rowCache;
late final GridCellCache cellCache; late final GridCellCache cellCache;
final GridBlockCache blockCache;
GridBloc({required View view}) GridBloc({required View view})
: _gridService = GridService(gridId: view.id), : _gridService = GridService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id), fieldCache = GridFieldCache(gridId: view.id),
blockCache = GridBlockCache(gridId: view.id),
super(GridState.initial(view.id)) { super(GridState.initial(view.id)) {
rowCache = GridRowCache( rowCache = GridRowCache(
gridId: view.id, gridId: view.id,
@ -33,6 +38,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
fieldDelegate: GridCellCacheDelegateImpl(fieldCache), fieldDelegate: GridCellCacheDelegateImpl(fieldCache),
); );
blockCache.start((result) {
result.fold(
(changesets) => rowCache.applyChangesets(changesets),
(err) => Log.error(err),
);
});
on<GridEvent>( on<GridEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
@ -60,6 +72,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
await cellCache.dispose(); await cellCache.dispose();
await rowCache.dispose(); await rowCache.dispose();
await fieldCache.dispose(); await fieldCache.dispose();
await blockCache.dispose();
return super.close(); return super.close();
} }
@ -79,7 +92,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
final result = await _gridService.loadGrid(); final result = await _gridService.loadGrid();
return Future( return Future(
() => result.fold( () => result.fold(
(grid) async => await _loadFields(grid, emit), (grid) async {
for (final block in grid.blocks) {
blockCache.addBlockListener(block.id);
}
final rowOrders = grid.blocks.expand((block) => block.rowOrders).toList();
rowCache.initialRows(rowOrders);
await _loadFields(grid, emit);
},
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))), (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
), ),
); );
@ -91,7 +112,6 @@ class GridBloc extends Bloc<GridEvent, GridState> {
() => result.fold( () => result.fold(
(fields) { (fields) {
fieldCache.fields = fields.items; fieldCache.fields = fields.items;
rowCache.resetRows(grid.blockOrders);
emit(state.copyWith( emit(state.copyWith(
grid: Some(grid), grid: Some(grid),
@ -143,7 +163,6 @@ class GridLoadingState with _$GridLoadingState {
class GridFieldEquatable extends Equatable { class GridFieldEquatable extends Equatable {
final List<Field> _fields; final List<Field> _fields;
const GridFieldEquatable(List<Field> fields) : _fields = fields; const GridFieldEquatable(List<Field> fields) : _fields = fields;
@override @override

View File

@ -1,42 +0,0 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
import 'package:flowy_infra/notifier.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
class GridRowListener {
final String gridId;
PublishNotifier<Either<List<GridRowsChangeset>, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
GridNotificationListener? _listener;
GridRowListener({required this.gridId});
void start() {
_listener = GridNotificationListener(
objectId: gridId,
handler: _handler,
);
}
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridRow:
result.fold(
(payload) => rowsUpdateNotifier.value = left([GridRowsChangeset.fromBuffer(payload)]),
(error) => rowsUpdateNotifier.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
rowsUpdateNotifier.dispose();
}
}

View File

@ -22,7 +22,7 @@ class GridService {
await FolderEventSetLatestView(ViewId(value: gridId)).send(); await FolderEventSetLatestView(ViewId(value: gridId)).send();
final payload = GridId(value: gridId); final payload = GridId(value: gridId);
return GridEventGetGridData(payload).send(); return GridEventGetGrid(payload).send();
} }
Future<Either<Row, FlowyError>> createRow({Option<String>? startRowId}) { Future<Either<Row, FlowyError>> createRow({Option<String>? startRowId}) {

View File

@ -10,7 +10,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
part 'row_service.freezed.dart'; part 'row_service.freezed.dart';
typedef RowUpdateCallback = void Function(); typedef RowUpdateCallback = void Function();
@ -24,7 +23,6 @@ abstract class GridRowFieldDelegate {
class GridRowCache { class GridRowCache {
final String gridId; final String gridId;
final RowsNotifier _rowsNotifier; final RowsNotifier _rowsNotifier;
final GridRowListener _rowsListener;
final GridRowFieldDelegate _fieldDelegate; final GridRowFieldDelegate _fieldDelegate;
List<GridRow> get clonedRows => _rowsNotifier.clonedRows; List<GridRow> get clonedRows => _rowsNotifier.clonedRows;
@ -39,32 +37,23 @@ class GridRowCache {
); );
}, },
), ),
_rowsListener = GridRowListener(gridId: gridId),
_fieldDelegate = fieldDelegate { _fieldDelegate = fieldDelegate {
// //
fieldDelegate.onFieldChanged(() => _rowsNotifier.fieldDidChange()); fieldDelegate.onFieldChanged(() => _rowsNotifier.fieldDidChange());
// listen on the row update
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold(
(changesets) {
for (final changeset in changesets) {
_rowsNotifier.deleteRows(changeset.deletedRows);
_rowsNotifier.insertRows(changeset.insertedRows);
_rowsNotifier.updateRows(changeset.updatedRows);
}
},
(err) => Log.error(err),
);
});
_rowsListener.start();
} }
Future<void> dispose() async { Future<void> dispose() async {
await _rowsListener.stop();
_rowsNotifier.dispose(); _rowsNotifier.dispose();
} }
void applyChangesets(List<GridRowsChangeset> changesets) {
for (final changeset in changesets) {
_rowsNotifier.deleteRows(changeset.deletedRows);
_rowsNotifier.insertRows(changeset.insertedRows);
_rowsNotifier.updateRows(changeset.updatedRows);
}
}
void addListener({ void addListener({
void Function(List<GridRow>, GridRowChangeReason)? onChanged, void Function(List<GridRow>, GridRowChangeReason)? onChanged,
bool Function()? listenWhen, bool Function()? listenWhen,
@ -130,9 +119,8 @@ class GridRowCache {
return _makeGridCells(rowId, data); return _makeGridCells(rowId, data);
} }
void resetRows(List<GridBlockOrder> blocks) { void initialRows(List<RowOrder> rowOrders) {
final rowOrders = blocks.expand((block) => block.rowOrders).toList(); _rowsNotifier.initialRows(rowOrders);
_rowsNotifier.reset(rowOrders);
} }
Future<void> _loadRow(String rowId) async { Future<void> _loadRow(String rowId) async {
@ -142,7 +130,11 @@ class GridRowCache {
final result = await GridEventGetRow(payload).send(); final result = await GridEventGetRow(payload).send();
result.fold( result.fold(
(rowData) => _rowsNotifier.rowData = rowData, (rowData) {
if (rowData.hasRow()) {
_rowsNotifier.rowData = rowData.row;
}
},
(err) => Log.error(err), (err) => Log.error(err),
); );
} }
@ -154,7 +146,6 @@ class GridRowCache {
cellDataMap[field.id] = GridCell( cellDataMap[field.id] = GridCell(
rowId: rowId, rowId: rowId,
gridId: gridId, gridId: gridId,
cell: row?.cellByFieldId[field.id],
field: field, field: field,
); );
} }
@ -173,7 +164,9 @@ class RowsNotifier extends ChangeNotifier {
required this.rowBuilder, required this.rowBuilder,
}); });
void reset(List<RowOrder> rowOrders) { List<GridRow> get clonedRows => [..._rows];
void initialRows(List<RowOrder> rowOrders) {
_rowDataMap = HashMap(); _rowDataMap = HashMap();
final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList(); final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList();
_update(rows, const GridRowChangeReason.initial()); _update(rows, const GridRowChangeReason.initial());
@ -199,20 +192,20 @@ class RowsNotifier extends ChangeNotifier {
_update(newRows, GridRowChangeReason.delete(deletedIndex)); _update(newRows, GridRowChangeReason.delete(deletedIndex));
} }
void insertRows(List<IndexRowOrder> createdRows) { void insertRows(List<IndexRowOrder> insertRows) {
if (createdRows.isEmpty) { if (insertRows.isEmpty) {
return; return;
} }
InsertedIndexs insertIndexs = []; InsertedIndexs insertIndexs = [];
final List<GridRow> newRows = clonedRows; final List<GridRow> newRows = clonedRows;
for (final createdRow in createdRows) { for (final insertRow in insertRows) {
final insertIndex = InsertedIndex( final insertIndex = InsertedIndex(
index: createdRow.index, index: insertRow.index,
rowId: createdRow.rowOrder.rowId, rowId: insertRow.rowOrder.rowId,
); );
insertIndexs.add(insertIndex); insertIndexs.add(insertIndex);
newRows.insert(createdRow.index, (rowBuilder(createdRow.rowOrder))); newRows.insert(insertRow.index, (rowBuilder(insertRow.rowOrder)));
} }
_update(newRows, GridRowChangeReason.insert(insertIndexs)); _update(newRows, GridRowChangeReason.insert(insertIndexs));
} }
@ -281,8 +274,6 @@ class RowsNotifier extends ChangeNotifier {
Row? rowDataWithId(String rowId) { Row? rowDataWithId(String rowId) {
return _rowDataMap[rowId]; return _rowDataMap[rowId];
} }
List<GridRow> get clonedRows => [..._rows];
} }
class RowService { class RowService {
@ -310,7 +301,7 @@ class RowService {
return GridEventMoveItem(payload).send(); return GridEventMoveItem(payload).send();
} }
Future<Either<Row, FlowyError>> getRow() { Future<Either<OptionalRow, FlowyError>> getRow() {
final payload = RowIdentifierPayload.create() final payload = RowIdentifierPayload.create()
..gridId = gridId ..gridId = gridId
..rowId = rowId; ..rowId = rowId;

View File

@ -93,6 +93,12 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "atomic_refcell"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -925,6 +931,7 @@ dependencies = [
name = "flowy-grid" name = "flowy-grid"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"atomic_refcell",
"bytes", "bytes",
"chrono", "chrono",
"dart-notify", "dart-notify",

View File

@ -39,6 +39,7 @@ fancy-regex = "0.10.0"
regex = "1.5.6" regex = "1.5.6"
url = { version = "2"} url = { version = "2"}
futures = "0.3.15" futures = "0.3.15"
atomic_refcell = "0.1.8"
[dev-dependencies] [dev-dependencies]
flowy-test = { path = "../flowy-test" } flowy-test = { path = "../flowy-test" }

View File

@ -6,7 +6,7 @@ const OBSERVABLE_CATEGORY: &str = "Grid";
pub enum GridNotification { pub enum GridNotification {
Unknown = 0, Unknown = 0,
DidCreateBlock = 11, DidCreateBlock = 11,
DidUpdateGridRow = 20, DidUpdateGridBlock = 20,
DidUpdateGridField = 21, DidUpdateGridField = 21,
DidUpdateRow = 30, DidUpdateRow = 30,
DidUpdateCell = 40, DidUpdateCell = 40,

View File

@ -9,7 +9,7 @@ use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
use std::sync::Arc; use std::sync::Arc;
#[tracing::instrument(level = "trace", skip(data, manager), err)] #[tracing::instrument(level = "trace", skip(data, manager), err)]
pub(crate) async fn get_grid_data_handler( pub(crate) async fn get_grid_handler(
data: Data<GridId>, data: Data<GridId>,
manager: AppData<Arc<GridManager>>, manager: AppData<Arc<GridManager>>,
) -> DataResult<Grid, FlowyError> { ) -> DataResult<Grid, FlowyError> {
@ -27,7 +27,7 @@ pub(crate) async fn get_grid_setting_handler(
let grid_id: GridId = data.into_inner(); let grid_id: GridId = data.into_inner();
let editor = manager.open_grid(grid_id).await?; let editor = manager.open_grid(grid_id).await?;
let grid_setting = editor.get_grid_setting().await?; let grid_setting = editor.get_grid_setting().await?;
data_result(grid_setting.into()) data_result(grid_setting)
} }
#[tracing::instrument(level = "trace", skip(data, manager), err)] #[tracing::instrument(level = "trace", skip(data, manager), err)]
@ -48,12 +48,7 @@ pub(crate) async fn get_grid_blocks_handler(
) -> DataResult<RepeatedGridBlock, FlowyError> { ) -> DataResult<RepeatedGridBlock, FlowyError> {
let params: QueryGridBlocksParams = data.into_inner().try_into()?; let params: QueryGridBlocksParams = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(&params.grid_id)?; let editor = manager.get_grid_editor(&params.grid_id)?;
let block_ids = params let repeated_grid_block = editor.get_blocks(Some(params.block_ids)).await?;
.block_orders
.into_iter()
.map(|block| block.block_id)
.collect::<Vec<String>>();
let repeated_grid_block = editor.get_blocks(Some(block_ids)).await?;
data_result(repeated_grid_block) data_result(repeated_grid_block)
} }
@ -220,13 +215,13 @@ async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType)
pub(crate) async fn get_row_handler( pub(crate) async fn get_row_handler(
data: Data<RowIdentifierPayload>, data: Data<RowIdentifierPayload>,
manager: AppData<Arc<GridManager>>, manager: AppData<Arc<GridManager>>,
) -> DataResult<Row, FlowyError> { ) -> DataResult<OptionalRow, FlowyError> {
let params: RowIdentifier = data.into_inner().try_into()?; let params: RowIdentifier = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(&params.grid_id)?; let editor = manager.get_grid_editor(&params.grid_id)?;
match editor.get_row(&params.row_id).await? { let row = OptionalRow {
None => Err(FlowyError::record_not_found().context("Can not find the row")), row: editor.get_row(&params.row_id).await?,
Some(row) => data_result(row), };
} data_result(row)
} }
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]

View File

@ -8,10 +8,10 @@ use strum_macros::Display;
pub fn create(grid_manager: Arc<GridManager>) -> Module { pub fn create(grid_manager: Arc<GridManager>) -> Module {
let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(grid_manager); let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(grid_manager);
module = module module = module
.event(GridEvent::GetGridData, get_grid_data_handler) .event(GridEvent::GetGrid, get_grid_handler)
.event(GridEvent::GetGridBlocks, get_grid_blocks_handler) .event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
.event(GridEvent::GetGridSetting, get_grid_setting_handler) .event(GridEvent::GetGridSetting, get_grid_setting_handler)
.event(GridEvent::UpdateGridSetting, get_grid_setting_handler) .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
// Field // Field
.event(GridEvent::GetFields, get_fields_handler) .event(GridEvent::GetFields, get_fields_handler)
.event(GridEvent::UpdateField, update_field_handler) .event(GridEvent::UpdateField, update_field_handler)
@ -46,7 +46,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
#[event_err = "FlowyError"] #[event_err = "FlowyError"]
pub enum GridEvent { pub enum GridEvent {
#[event(input = "GridId", output = "Grid")] #[event(input = "GridId", output = "Grid")]
GetGridData = 0, GetGrid = 0,
#[event(input = "QueryGridBlocksPayload", output = "RepeatedGridBlock")] #[event(input = "QueryGridBlocksPayload", output = "RepeatedGridBlock")]
GetGridBlocks = 1, GetGridBlocks = 1,
@ -99,7 +99,7 @@ pub enum GridEvent {
#[event(input = "CreateRowPayload", output = "Row")] #[event(input = "CreateRowPayload", output = "Row")]
CreateRow = 50, CreateRow = 50,
#[event(input = "RowIdentifierPayload", output = "Row")] #[event(input = "RowIdentifierPayload", output = "OptionalRow")]
GetRow = 51, GetRow = 51,
#[event(input = "RowIdentifierPayload")] #[event(input = "RowIdentifierPayload")]

View File

@ -2,16 +2,18 @@ use crate::services::grid_editor::GridRevisionEditor;
use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::block_index::BlockIndexCache;
use crate::services::persistence::kv::GridKVPersistence; use crate::services::persistence::kv::GridKVPersistence;
use crate::services::persistence::GridDatabase; use crate::services::persistence::GridDatabase;
use crate::services::tasks::GridTaskScheduler;
use bytes::Bytes; use bytes::Bytes;
use dashmap::DashMap; use dashmap::DashMap;
use flowy_database::ConnectionPool; use flowy_database::ConnectionPool;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{BuildGridContext, GridRevision, GridSettingRevision}; use flowy_grid_data_model::revision::{BuildGridContext, GridRevision};
use flowy_revision::disk::{SQLiteGridBlockMetaRevisionPersistence, SQLiteGridRevisionPersistence}; use flowy_revision::disk::{SQLiteGridBlockMetaRevisionPersistence, SQLiteGridRevisionPersistence};
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket}; use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket};
use flowy_sync::client_grid::{make_block_meta_delta, make_grid_delta}; use flowy_sync::client_grid::{make_block_meta_delta, make_grid_delta};
use flowy_sync::entities::revision::{RepeatedRevision, Revision}; use flowy_sync::entities::revision::{RepeatedRevision, Revision};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock;
pub trait GridUser: Send + Sync { pub trait GridUser: Send + Sync {
fn user_id(&self) -> Result<String, FlowyError>; fn user_id(&self) -> Result<String, FlowyError>;
@ -19,12 +21,15 @@ pub trait GridUser: Send + Sync {
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>; fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
} }
pub type GridTaskSchedulerRwLock = Arc<RwLock<GridTaskScheduler>>;
pub struct GridManager { pub struct GridManager {
editor_map: Arc<DashMap<String, Arc<GridRevisionEditor>>>, grid_editors: Arc<DashMap<String, Arc<GridRevisionEditor>>>,
grid_user: Arc<dyn GridUser>, grid_user: Arc<dyn GridUser>,
block_index_cache: Arc<BlockIndexCache>, block_index_cache: Arc<BlockIndexCache>,
#[allow(dead_code)] #[allow(dead_code)]
kv_persistence: Arc<GridKVPersistence>, kv_persistence: Arc<GridKVPersistence>,
task_scheduler: GridTaskSchedulerRwLock,
} }
impl GridManager { impl GridManager {
@ -35,12 +40,14 @@ impl GridManager {
) -> Self { ) -> Self {
let grid_editors = Arc::new(DashMap::new()); let grid_editors = Arc::new(DashMap::new());
let kv_persistence = Arc::new(GridKVPersistence::new(database.clone())); let kv_persistence = Arc::new(GridKVPersistence::new(database.clone()));
let block_index_persistence = Arc::new(BlockIndexCache::new(database)); let block_index_cache = Arc::new(BlockIndexCache::new(database));
let task_scheduler = GridTaskScheduler::new();
Self { Self {
editor_map: grid_editors, grid_editors,
grid_user, grid_user,
kv_persistence, kv_persistence,
block_index_cache: block_index_persistence, block_index_cache,
task_scheduler,
} }
} }
@ -74,18 +81,20 @@ impl GridManager {
} }
#[tracing::instrument(level = "debug", skip_all, fields(grid_id), err)] #[tracing::instrument(level = "debug", skip_all, fields(grid_id), err)]
pub fn close_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<()> { pub async fn close_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<()> {
let grid_id = grid_id.as_ref(); let grid_id = grid_id.as_ref();
tracing::Span::current().record("grid_id", &grid_id); tracing::Span::current().record("grid_id", &grid_id);
self.editor_map.remove(grid_id); self.grid_editors.remove(grid_id);
self.task_scheduler.write().await.unregister_handler(grid_id);
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self, grid_id), fields(doc_id), err)] #[tracing::instrument(level = "debug", skip(self, grid_id), fields(doc_id), err)]
pub fn delete_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<()> { pub async fn delete_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<()> {
let grid_id = grid_id.as_ref(); let grid_id = grid_id.as_ref();
tracing::Span::current().record("grid_id", &grid_id); tracing::Span::current().record("grid_id", &grid_id);
self.editor_map.remove(grid_id); self.grid_editors.remove(grid_id);
self.task_scheduler.write().await.unregister_handler(grid_id);
Ok(()) Ok(())
} }
@ -93,23 +102,24 @@ impl GridManager {
// #[tracing::instrument(level = "debug", skip(self), err)] // #[tracing::instrument(level = "debug", skip(self), err)]
pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<GridRevisionEditor>> { pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<GridRevisionEditor>> {
match self.editor_map.get(grid_id) { match self.grid_editors.get(grid_id) {
None => Err(FlowyError::internal().context("Should call open_grid function first")), None => Err(FlowyError::internal().context("Should call open_grid function first")),
Some(editor) => Ok(editor.clone()), Some(editor) => Ok(editor.clone()),
} }
} }
async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<GridRevisionEditor>> { async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<GridRevisionEditor>> {
match self.editor_map.get(grid_id) { match self.grid_editors.get(grid_id) {
None => { None => {
tracing::trace!("Create grid editor with id: {}", grid_id); tracing::trace!("Create grid editor with id: {}", grid_id);
let db_pool = self.grid_user.db_pool()?; let db_pool = self.grid_user.db_pool()?;
let editor = self.make_grid_editor(grid_id, db_pool).await?; let editor = self.make_grid_editor(grid_id, db_pool).await?;
if self.editor_map.contains_key(grid_id) { if self.grid_editors.contains_key(grid_id) {
tracing::warn!("Grid:{} already exists in cache", grid_id); tracing::warn!("Grid:{} already exists in cache", grid_id);
} }
self.editor_map.insert(grid_id.to_string(), editor.clone()); self.grid_editors.insert(grid_id.to_string(), editor.clone());
self.task_scheduler.write().await.register_handler(editor.clone());
Ok(editor) Ok(editor)
} }
Some(editor) => Ok(editor.clone()), Some(editor) => Ok(editor.clone()),
@ -124,7 +134,14 @@ impl GridManager {
) -> Result<Arc<GridRevisionEditor>, FlowyError> { ) -> Result<Arc<GridRevisionEditor>, FlowyError> {
let user = self.grid_user.clone(); let user = self.grid_user.clone();
let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?; let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?;
let grid_editor = GridRevisionEditor::new(grid_id, user, rev_manager, self.block_index_cache.clone()).await?; let grid_editor = GridRevisionEditor::new(
grid_id,
user,
rev_manager,
self.block_index_cache.clone(),
self.task_scheduler.clone(),
)
.await?;
Ok(grid_editor) Ok(grid_editor)
} }
@ -156,12 +173,24 @@ pub async fn make_grid_view_data(
grid_manager: Arc<GridManager>, grid_manager: Arc<GridManager>,
build_context: BuildGridContext, build_context: BuildGridContext,
) -> FlowyResult<Bytes> { ) -> FlowyResult<Bytes> {
let grid_rev = GridRevision { for block_meta_data in &build_context.blocks_meta_data {
grid_id: view_id.to_string(), let block_id = &block_meta_data.block_id;
fields: build_context.field_revs, // Indexing the block's rows
blocks: build_context.blocks, block_meta_data.rows.iter().for_each(|row| {
setting: GridSettingRevision::default(), let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id);
}; });
// Create grid's block
let grid_block_meta_delta = make_block_meta_delta(block_meta_data);
let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes();
let repeated_revision: RepeatedRevision =
Revision::initial_revision(user_id, block_id, block_meta_delta_data).into();
let _ = grid_manager
.create_grid_block_meta(&block_id, repeated_revision)
.await?;
}
let grid_rev = GridRevision::from_build_context(view_id, build_context);
// Create grid // Create grid
let grid_meta_delta = make_grid_delta(&grid_rev); let grid_meta_delta = make_grid_delta(&grid_rev);
@ -170,23 +199,5 @@ pub async fn make_grid_view_data(
Revision::initial_revision(user_id, view_id, grid_delta_data.clone()).into(); Revision::initial_revision(user_id, view_id, grid_delta_data.clone()).into();
let _ = grid_manager.create_grid(view_id, repeated_revision).await?; let _ = grid_manager.create_grid(view_id, repeated_revision).await?;
for block_meta_data in build_context.blocks_meta_data {
let block_id = block_meta_data.block_id.clone();
// Indexing the block's rows
block_meta_data.rows.iter().for_each(|row| {
let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id);
});
// Create grid's block
let grid_block_meta_delta = make_block_meta_delta(&block_meta_data);
let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes();
let repeated_revision: RepeatedRevision =
Revision::initial_revision(user_id, &block_id, block_meta_delta_data).into();
let _ = grid_manager
.create_grid_block_meta(&block_id, repeated_revision)
.await?;
}
Ok(grid_delta_data) Ok(grid_delta_data)
} }

View File

@ -2,14 +2,14 @@ use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::manager::GridUser; use crate::manager::GridUser;
use crate::services::block_revision_editor::GridBlockRevisionEditor; use crate::services::block_revision_editor::GridBlockRevisionEditor;
use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::block_index::BlockIndexCache;
use crate::services::row::{group_row_orders, GridBlockSnapshot}; use crate::services::row::{block_from_row_orders, GridBlockSnapshot};
use dashmap::DashMap; use dashmap::DashMap;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use flowy_grid_data_model::entities::{ use flowy_grid_data_model::entities::{
CellChangeset, GridRowsChangeset, IndexRowOrder, Row, RowOrder, UpdatedRowOrder, CellChangeset, GridRowsChangeset, IndexRowOrder, Row, RowOrder, UpdatedRowOrder,
}; };
use flowy_grid_data_model::revision::{ use flowy_grid_data_model::revision::{
CellRevision, GridBlockRevision, GridBlockRevisionChangeset, RowMetaChangeset, RowRevision, CellRevision, GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision,
}; };
use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence; use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
use flowy_revision::{RevisionManager, RevisionPersistence}; use flowy_revision::{RevisionManager, RevisionPersistence};
@ -19,6 +19,7 @@ use std::sync::Arc;
type BlockId = String; type BlockId = String;
pub(crate) struct GridBlockManager { pub(crate) struct GridBlockManager {
#[allow(dead_code)]
grid_id: String, grid_id: String,
user: Arc<dyn GridUser>, user: Arc<dyn GridUser>,
persistence: Arc<BlockIndexCache>, persistence: Arc<BlockIndexCache>,
@ -29,10 +30,10 @@ impl GridBlockManager {
pub(crate) async fn new( pub(crate) async fn new(
grid_id: &str, grid_id: &str,
user: &Arc<dyn GridUser>, user: &Arc<dyn GridUser>,
blocks: Vec<GridBlockRevision>, block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
persistence: Arc<BlockIndexCache>, persistence: Arc<BlockIndexCache>,
) -> FlowyResult<Self> { ) -> FlowyResult<Self> {
let editor_map = make_block_meta_editor_map(user, blocks).await?; let editor_map = make_block_meta_editor_map(user, block_meta_revs).await?;
let user = user.clone(); let user = user.clone();
let grid_id = grid_id.to_owned(); let grid_id = grid_id.to_owned();
let manager = Self { let manager = Self {
@ -77,7 +78,7 @@ impl GridBlockManager {
index_row_order.index = row_index; index_row_order.index = row_index;
let _ = self let _ = self
.notify_did_update_block(GridRowsChangeset::insert(block_id, vec![index_row_order])) .notify_did_update_block(block_id, GridRowsChangeset::insert(block_id, vec![index_row_order]))
.await?; .await?;
Ok(row_count) Ok(row_count)
} }
@ -85,7 +86,7 @@ impl GridBlockManager {
pub(crate) async fn insert_row( pub(crate) async fn insert_row(
&self, &self,
rows_by_block_id: HashMap<String, Vec<RowRevision>>, rows_by_block_id: HashMap<String, Vec<RowRevision>>,
) -> FlowyResult<Vec<GridBlockRevisionChangeset>> { ) -> FlowyResult<Vec<GridBlockMetaRevisionChangeset>> {
let mut changesets = vec![]; let mut changesets = vec![];
for (block_id, row_revs) in rows_by_block_id { for (block_id, row_revs) in rows_by_block_id {
let mut inserted_row_orders = vec![]; let mut inserted_row_orders = vec![];
@ -99,10 +100,10 @@ impl GridBlockManager {
row_order.index = index; row_order.index = index;
inserted_row_orders.push(row_order); inserted_row_orders.push(row_order);
} }
changesets.push(GridBlockRevisionChangeset::from_row_count(&block_id, row_count)); changesets.push(GridBlockMetaRevisionChangeset::from_row_count(&block_id, row_count));
let _ = self let _ = self
.notify_did_update_block(GridRowsChangeset::insert(&block_id, inserted_row_orders)) .notify_did_update_block(&block_id, GridRowsChangeset::insert(&block_id, inserted_row_orders))
.await?; .await?;
} }
@ -121,7 +122,9 @@ impl GridBlockManager {
if let Some(row) = row_builder(row_rev.clone()) { if let Some(row) = row_builder(row_rev.clone()) {
let row_order = UpdatedRowOrder::new(&row_rev, row); let row_order = UpdatedRowOrder::new(&row_rev, row);
let block_order_changeset = GridRowsChangeset::update(&editor.block_id, vec![row_order]); let block_order_changeset = GridRowsChangeset::update(&editor.block_id, vec![row_order]);
let _ = self.notify_did_update_block(block_order_changeset).await?; let _ = self
.notify_did_update_block(&editor.block_id, block_order_changeset)
.await?;
} }
} }
} }
@ -137,7 +140,7 @@ impl GridBlockManager {
Some(row_order) => { Some(row_order) => {
let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?; let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?;
let _ = self let _ = self
.notify_did_update_block(GridRowsChangeset::delete(&block_id, vec![row_order])) .notify_did_update_block(&block_id, GridRowsChangeset::delete(&block_id, vec![row_order]))
.await?; .await?;
} }
} }
@ -145,17 +148,20 @@ impl GridBlockManager {
Ok(()) Ok(())
} }
pub(crate) async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<Vec<GridBlockRevisionChangeset>> { pub(crate) async fn delete_rows(
&self,
row_orders: Vec<RowOrder>,
) -> FlowyResult<Vec<GridBlockMetaRevisionChangeset>> {
let mut changesets = vec![]; let mut changesets = vec![];
for block_order in group_row_orders(row_orders) { for block_order in block_from_row_orders(row_orders) {
let editor = self.get_editor(&block_order.block_id).await?; let editor = self.get_editor(&block_order.id).await?;
let row_ids = block_order let row_ids = block_order
.row_orders .row_orders
.into_iter() .into_iter()
.map(|row_order| Cow::Owned(row_order.row_id)) .map(|row_order| Cow::Owned(row_order.row_id))
.collect::<Vec<Cow<String>>>(); .collect::<Vec<Cow<String>>>();
let row_count = editor.delete_rows(row_ids).await?; let row_count = editor.delete_rows(row_ids).await?;
let changeset = GridBlockRevisionChangeset::from_row_count(&block_order.block_id, row_count); let changeset = GridBlockMetaRevisionChangeset::from_row_count(&block_order.id, row_count);
changesets.push(changeset); changesets.push(changeset);
} }
@ -181,7 +187,9 @@ impl GridBlockManager {
updated_rows: vec![], updated_rows: vec![],
}; };
let _ = self.notify_did_update_block(notified_changeset).await?; let _ = self
.notify_did_update_block(&editor.block_id, notified_changeset)
.await?;
} }
} }
@ -241,8 +249,8 @@ impl GridBlockManager {
Ok(block_cell_revs) Ok(block_cell_revs)
} }
async fn notify_did_update_block(&self, changeset: GridRowsChangeset) -> FlowyResult<()> { async fn notify_did_update_block(&self, block_id: &str, changeset: GridRowsChangeset) -> FlowyResult<()> {
send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridRow) send_dart_notification(block_id, GridNotification::DidUpdateGridBlock)
.payload(changeset) .payload(changeset)
.send(); .send();
Ok(()) Ok(())
@ -257,12 +265,12 @@ impl GridBlockManager {
async fn make_block_meta_editor_map( async fn make_block_meta_editor_map(
user: &Arc<dyn GridUser>, user: &Arc<dyn GridUser>,
blocks: Vec<GridBlockRevision>, block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
) -> FlowyResult<DashMap<String, Arc<GridBlockRevisionEditor>>> { ) -> FlowyResult<DashMap<String, Arc<GridBlockRevisionEditor>>> {
let editor_map = DashMap::new(); let editor_map = DashMap::new();
for block in blocks { for block_meta_rev in block_meta_revs {
let editor = make_block_meta_editor(user, &block.block_id).await?; let editor = make_block_meta_editor(user, &block_meta_rev.block_id).await?;
editor_map.insert(block.block_id, Arc::new(editor)); editor_map.insert(block_meta_rev.block_id.clone(), Arc::new(editor));
} }
Ok(editor_map) Ok(editor_map)

View File

@ -1,7 +1,7 @@
use bytes::Bytes; use bytes::Bytes;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::entities::RowOrder; use flowy_grid_data_model::entities::RowOrder;
use flowy_grid_data_model::revision::{CellRevision, GridBlockRevisionData, RowMetaChangeset, RowRevision}; use flowy_grid_data_model::revision::{CellRevision, GridBlockRevision, RowMetaChangeset, RowRevision};
use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder}; use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
use flowy_sync::client_grid::{GridBlockMetaChange, GridBlockRevisionPad}; use flowy_sync::client_grid::{GridBlockMetaChange, GridBlockRevisionPad};
use flowy_sync::entities::revision::Revision; use flowy_sync::entities::revision::Revision;
@ -42,28 +42,28 @@ impl GridBlockRevisionEditor {
}) })
} }
pub async fn duplicate_block_meta_data(&self, duplicated_block_id: &str) -> GridBlockRevisionData { pub async fn duplicate_block(&self, duplicated_block_id: &str) -> GridBlockRevision {
self.pad.read().await.duplicate_data(duplicated_block_id).await self.pad.read().await.duplicate_data(duplicated_block_id).await
} }
/// return current number of rows and the inserted index. The inserted index will be None if the start_row_id is None /// Create a row after the the with prev_row_id. If prev_row_id is None, the row will be appended to the list
pub(crate) async fn create_row( pub(crate) async fn create_row(
&self, &self,
row: RowRevision, row: RowRevision,
start_row_id: Option<String>, prev_row_id: Option<String>,
) -> FlowyResult<(i32, Option<i32>)> { ) -> FlowyResult<(i32, Option<i32>)> {
let mut row_count = 0; let mut row_count = 0;
let mut row_index = None; let mut row_index = None;
let _ = self let _ = self
.modify(|block_pad| { .modify(|block_pad| {
if let Some(start_row_id) = start_row_id.as_ref() { if let Some(start_row_id) = prev_row_id.as_ref() {
match block_pad.index_of_row(start_row_id) { match block_pad.index_of_row(start_row_id) {
None => {} None => {}
Some(index) => row_index = Some(index + 1), Some(index) => row_index = Some(index + 1),
} }
} }
let change = block_pad.add_row_rev(row, start_row_id)?; let change = block_pad.add_row_rev(row, prev_row_id)?;
row_count = block_pad.number_of_rows(); row_count = block_pad.number_of_rows();
if row_index.is_none() { if row_index.is_none() {

View File

@ -93,7 +93,7 @@ mod tests {
use crate::services::field::type_options::checkbox_type_option::{NO, YES}; use crate::services::field::type_options::checkbox_type_option::{NO, YES};
use crate::services::field::FieldBuilder; use crate::services::field::FieldBuilder;
use crate::services::row::{apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data}; use crate::services::row::{apply_cell_data_changeset, decode_cell_data};
use flowy_grid_data_model::entities::FieldType; use flowy_grid_data_model::entities::FieldType;
@ -101,39 +101,21 @@ mod tests {
fn checkout_box_description_test() { fn checkout_box_description_test() {
let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build(); let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
let data = apply_cell_data_changeset("true", None, &field_rev).unwrap(); let data = apply_cell_data_changeset("true", None, &field_rev).unwrap();
assert_eq!( assert_eq!(decode_cell_data(data, &field_rev).to_string(), YES);
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
YES
);
let data = apply_cell_data_changeset("1", None, &field_rev).unwrap(); let data = apply_cell_data_changeset("1", None, &field_rev).unwrap();
assert_eq!( assert_eq!(decode_cell_data(data, &field_rev,).to_string(), YES);
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
YES
);
let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap(); let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap();
assert_eq!( assert_eq!(decode_cell_data(data, &field_rev,).to_string(), YES);
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
YES
);
let data = apply_cell_data_changeset("false", None, &field_rev).unwrap(); let data = apply_cell_data_changeset("false", None, &field_rev).unwrap();
assert_eq!( assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO);
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
NO
);
let data = apply_cell_data_changeset("no", None, &field_rev).unwrap(); let data = apply_cell_data_changeset("no", None, &field_rev).unwrap();
assert_eq!( assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO);
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
NO
);
let data = apply_cell_data_changeset("12", None, &field_rev).unwrap(); let data = apply_cell_data_changeset("12", None, &field_rev).unwrap();
assert_eq!( assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO);
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
NO
);
} }
} }

View File

@ -1,6 +1,6 @@
use crate::impl_type_option; use crate::impl_type_option;
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
use crate::services::row::{decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData}; use crate::services::row::{try_decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData};
use bytes::Bytes; use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
@ -45,7 +45,7 @@ impl CellDataOperation<String> for RichTextTypeOption {
|| decoded_field_type.is_multi_select() || decoded_field_type.is_multi_select()
|| decoded_field_type.is_number() || decoded_field_type.is_number()
{ {
decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_rev) try_decode_cell_data(encoded_data, field_rev, decoded_field_type, decoded_field_type)
} else { } else {
let cell_data = encoded_data.into(); let cell_data = encoded_data.into();
Ok(DecodedCellData::new(cell_data)) Ok(DecodedCellData::new(cell_data))

View File

@ -0,0 +1,58 @@
use crate::manager::GridTaskSchedulerRwLock;
use crate::services::block_manager::GridBlockManager;
use crate::services::tasks::Task;
use flowy_error::FlowyResult;
use flowy_sync::client_grid::GridRevisionPad;
use std::sync::Arc;
use tokio::sync::RwLock;
pub(crate) struct GridFilterService {
#[allow(dead_code)]
scheduler: GridTaskSchedulerRwLock,
#[allow(dead_code)]
grid_pad: Arc<RwLock<GridRevisionPad>>,
#[allow(dead_code)]
block_manager: Arc<GridBlockManager>,
}
impl GridFilterService {
pub fn new(
grid_pad: Arc<RwLock<GridRevisionPad>>,
block_manager: Arc<GridBlockManager>,
scheduler: GridTaskSchedulerRwLock,
) -> Self {
Self {
grid_pad,
block_manager,
scheduler,
}
}
pub async fn process_task(&self, _task: Task) -> FlowyResult<()> {
Ok(())
}
pub async fn notify_changed(&self) {
//
// let grid_pad = self.grid_pad.read().await;
// match grid_pad.get_filters(None) {
// None => {}
// Some(filter_revs) => {
// filter_revs
// .iter()
// .for_each(|filter_rev| match grid_pad.get_field_rev(&filter_rev.field_id) {
// None => {}
// Some((_, _field_rev)) => match field_rev.field_type {
// FieldType::RichText => {}
// FieldType::Number => {}
// FieldType::DateTime => {}
// FieldType::SingleSelect => {}
// FieldType::MultiSelect => {}
// FieldType::Checkbox => {}
// FieldType::URL => {}
// },
// });
// }
// }
}
}

View File

@ -0,0 +1,3 @@
mod filter_service;
pub(crate) use filter_service::*;

View File

@ -1,10 +1,12 @@
use crate::dart_notification::{send_dart_notification, GridNotification}; use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::entities::CellIdentifier; use crate::entities::CellIdentifier;
use crate::manager::GridUser; use crate::manager::{GridTaskSchedulerRwLock, GridUser};
use crate::services::block_manager::GridBlockManager; use crate::services::block_manager::GridBlockManager;
use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder}; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
use crate::services::filter::GridFilterService;
use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::block_index::BlockIndexCache;
use crate::services::row::*; use crate::services::row::*;
use bytes::Bytes; use bytes::Bytes;
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_grid_data_model::entities::*; use flowy_grid_data_model::entities::*;
@ -21,16 +23,18 @@ use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
pub struct GridRevisionEditor { pub struct GridRevisionEditor {
grid_id: String, pub(crate) grid_id: String,
user: Arc<dyn GridUser>, user: Arc<dyn GridUser>,
grid_pad: Arc<RwLock<GridRevisionPad>>, grid_pad: Arc<RwLock<GridRevisionPad>>,
rev_manager: Arc<RevisionManager>, rev_manager: Arc<RevisionManager>,
block_manager: Arc<GridBlockManager>, block_manager: Arc<GridBlockManager>,
#[allow(dead_code)]
pub(crate) filter_service: Arc<GridFilterService>,
} }
impl Drop for GridRevisionEditor { impl Drop for GridRevisionEditor {
fn drop(&mut self) { fn drop(&mut self) {
tracing::trace!("Drop GridMetaEditor"); tracing::trace!("Drop GridRevisionEditor");
} }
} }
@ -40,22 +44,30 @@ impl GridRevisionEditor {
user: Arc<dyn GridUser>, user: Arc<dyn GridUser>,
mut rev_manager: RevisionManager, mut rev_manager: RevisionManager,
persistence: Arc<BlockIndexCache>, persistence: Arc<BlockIndexCache>,
task_scheduler: GridTaskSchedulerRwLock,
) -> FlowyResult<Arc<Self>> { ) -> FlowyResult<Arc<Self>> {
let token = user.token()?; let token = user.token()?;
let cloud = Arc::new(GridRevisionCloudService { token }); let cloud = Arc::new(GridRevisionCloudService { token });
let grid_pad = rev_manager.load::<GridPadBuilder>(Some(cloud)).await?; let grid_pad = rev_manager.load::<GridPadBuilder>(Some(cloud)).await?;
let rev_manager = Arc::new(rev_manager); let rev_manager = Arc::new(rev_manager);
let grid_pad = Arc::new(RwLock::new(grid_pad)); let grid_pad = Arc::new(RwLock::new(grid_pad));
let blocks = grid_pad.read().await.get_block_revs(); let block_meta_revs = grid_pad.read().await.get_block_meta_revs();
let block_manager = Arc::new(GridBlockManager::new(grid_id, &user, block_meta_revs, persistence).await?);
let block_meta_manager = Arc::new(GridBlockManager::new(grid_id, &user, blocks, persistence).await?); let filter_service = Arc::new(GridFilterService::new(
Ok(Arc::new(Self { grid_pad.clone(),
block_manager.clone(),
task_scheduler.clone(),
));
let editor = Arc::new(Self {
grid_id: grid_id.to_owned(), grid_id: grid_id.to_owned(),
user, user,
grid_pad, grid_pad,
rev_manager, rev_manager,
block_manager: block_meta_manager, block_manager,
})) filter_service,
});
Ok(editor)
} }
pub async fn insert_field(&self, params: InsertFieldParams) -> FlowyResult<()> { pub async fn insert_field(&self, params: InsertFieldParams) -> FlowyResult<()> {
@ -243,14 +255,14 @@ impl GridRevisionEditor {
Ok(field_revs) Ok(field_revs)
} }
pub async fn create_block(&self, grid_block: GridBlockRevision) -> FlowyResult<()> { pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> {
let _ = self let _ = self
.modify(|grid_pad| Ok(grid_pad.create_block_rev(grid_block)?)) .modify(|grid_pad| Ok(grid_pad.create_block_meta_rev(block_meta_rev)?))
.await?; .await?;
Ok(()) Ok(())
} }
pub async fn update_block(&self, changeset: GridBlockRevisionChangeset) -> FlowyResult<()> { pub async fn update_block(&self, changeset: GridBlockMetaRevisionChangeset) -> FlowyResult<()> {
let _ = self let _ = self
.modify(|grid_pad| Ok(grid_pad.update_block_rev(changeset)?)) .modify(|grid_pad| Ok(grid_pad.update_block_rev(changeset)?))
.await?; .await?;
@ -270,7 +282,7 @@ impl GridRevisionEditor {
let row_count = self.block_manager.create_row(&block_id, row_rev, start_row_id).await?; let row_count = self.block_manager.create_row(&block_id, row_rev, start_row_id).await?;
// update block row count // update block row count
let changeset = GridBlockRevisionChangeset::from_row_count(&block_id, row_count); let changeset = GridBlockMetaRevisionChangeset::from_row_count(&block_id, row_count);
let _ = self.update_block(changeset).await?; let _ = self.update_block(changeset).await?;
Ok(row_order) Ok(row_order)
} }
@ -342,7 +354,10 @@ impl GridRevisionEditor {
pub async fn get_cell(&self, params: &CellIdentifier) -> Option<Cell> { pub async fn get_cell(&self, params: &CellIdentifier) -> Option<Cell> {
let field_rev = self.get_field_rev(&params.field_id).await?; let field_rev = self.get_field_rev(&params.field_id).await?;
let row_rev = self.block_manager.get_row_rev(&params.row_id).await.ok()??; let row_rev = self.block_manager.get_row_rev(&params.row_id).await.ok()??;
make_cell(&params.field_id, &field_rev, &row_rev)
let cell_rev = row_rev.cells.get(&params.field_id)?.clone();
let data = decode_cell_data(cell_rev.data, &field_rev).data;
Some(Cell::new(&params.field_id, data))
} }
pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> { pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> {
@ -405,9 +420,9 @@ impl GridRevisionEditor {
make_grid_blocks(block_ids, block_snapshots) make_grid_blocks(block_ids, block_snapshots)
} }
pub async fn get_block_metas(&self) -> FlowyResult<Vec<GridBlockRevision>> { pub async fn get_block_meta_revs(&self) -> FlowyResult<Vec<Arc<GridBlockMetaRevision>>> {
let grid_blocks = self.grid_pad.read().await.get_block_revs(); let block_meta_revs = self.grid_pad.read().await.get_block_meta_revs();
Ok(grid_blocks) Ok(block_meta_revs)
} }
pub async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<()> { pub async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<()> {
@ -422,10 +437,10 @@ impl GridRevisionEditor {
let pad_read_guard = self.grid_pad.read().await; let pad_read_guard = self.grid_pad.read().await;
let field_orders = pad_read_guard.get_field_orders(); let field_orders = pad_read_guard.get_field_orders();
let mut block_orders = vec![]; let mut block_orders = vec![];
for block_order in pad_read_guard.get_block_revs() { for block_rev in pad_read_guard.get_block_meta_revs() {
let row_orders = self.block_manager.get_row_orders(&block_order.block_id).await?; let row_orders = self.block_manager.get_row_orders(&block_rev.block_id).await?;
let block_order = GridBlockOrder { let block_order = GridBlock {
block_id: block_order.block_id, id: block_rev.block_id.clone(),
row_orders, row_orders,
}; };
block_orders.push(block_order); block_orders.push(block_order);
@ -434,20 +449,34 @@ impl GridRevisionEditor {
Ok(Grid { Ok(Grid {
id: self.grid_id.clone(), id: self.grid_id.clone(),
field_orders, field_orders,
block_orders, blocks: block_orders,
}) })
} }
pub async fn get_grid_setting(&self) -> FlowyResult<GridSettingRevision> { pub async fn get_grid_setting(&self) -> FlowyResult<GridSetting> {
let pad_read_guard = self.grid_pad.read().await; let read_guard = self.grid_pad.read().await;
let grid_setting_rev = pad_read_guard.get_grid_setting_rev(); let grid_setting_rev = read_guard.get_grid_setting_rev();
Ok(grid_setting_rev) Ok(grid_setting_rev.into())
}
pub async fn get_grid_filter(&self, layout_type: &GridLayoutType) -> FlowyResult<Vec<GridFilter>> {
let read_guard = self.grid_pad.read().await;
let layout_rev = layout_type.clone().into();
match read_guard.get_filters(Some(&layout_rev)) {
Some(filter_revs) => Ok(filter_revs.iter().map(GridFilter::from).collect::<Vec<GridFilter>>()),
None => Ok(vec![]),
}
} }
pub async fn update_grid_setting(&self, params: GridSettingChangesetParams) -> FlowyResult<()> { pub async fn update_grid_setting(&self, params: GridSettingChangesetParams) -> FlowyResult<()> {
let is_filter_changed = params.is_filter_changed();
let _ = self let _ = self
.modify(|grid_pad| Ok(grid_pad.update_grid_setting_rev(params)?)) .modify(|grid_pad| Ok(grid_pad.update_grid_setting_rev(params)?))
.await?; .await?;
if is_filter_changed {
self.filter_service.notify_changed().await;
}
Ok(()) Ok(())
} }
@ -457,9 +486,9 @@ impl GridRevisionEditor {
.grid_pad .grid_pad
.read() .read()
.await .await
.get_block_revs() .get_block_meta_revs()
.into_iter() .iter()
.map(|block_meta| block_meta.block_id) .map(|block_rev| block_rev.block_id.clone())
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
Some(block_ids) => block_ids, Some(block_ids) => block_ids,
}; };
@ -507,8 +536,8 @@ impl GridRevisionEditor {
pub async fn duplicate_grid(&self) -> FlowyResult<BuildGridContext> { pub async fn duplicate_grid(&self) -> FlowyResult<BuildGridContext> {
let grid_pad = self.grid_pad.read().await; let grid_pad = self.grid_pad.read().await;
let original_blocks = grid_pad.get_block_revs(); let original_blocks = grid_pad.get_block_meta_revs();
let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_meta().await; let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_block_meta().await;
let mut blocks_meta_data = vec![]; let mut blocks_meta_data = vec![];
if original_blocks.len() == duplicated_blocks.len() { if original_blocks.len() == duplicated_blocks.len() {
@ -517,9 +546,7 @@ impl GridRevisionEditor {
let duplicated_block_id = &duplicated_blocks[index].block_id; let duplicated_block_id = &duplicated_blocks[index].block_id;
tracing::trace!("Duplicate block:{} meta data", duplicated_block_id); tracing::trace!("Duplicate block:{} meta data", duplicated_block_id);
let duplicated_block_meta_data = grid_block_meta_editor let duplicated_block_meta_data = grid_block_meta_editor.duplicate_block(duplicated_block_id).await;
.duplicate_block_meta_data(duplicated_block_id)
.await;
blocks_meta_data.push(duplicated_block_meta_data); blocks_meta_data.push(duplicated_block_meta_data);
} }
} else { } else {
@ -566,7 +593,7 @@ impl GridRevisionEditor {
} }
async fn block_id(&self) -> FlowyResult<String> { async fn block_id(&self) -> FlowyResult<String> {
match self.grid_pad.read().await.get_block_revs().last() { match self.grid_pad.read().await.get_block_meta_revs().last() {
None => Err(FlowyError::internal().context("There is no grid block in this grid")), None => Err(FlowyError::internal().context("There is no grid block in this grid")),
Some(grid_block) => Ok(grid_block.block_id.clone()), Some(grid_block) => Ok(grid_block.block_id.clone()),
} }

View File

@ -0,0 +1,21 @@
use crate::services::grid_editor::GridRevisionEditor;
use crate::services::tasks::{GridTaskHandler, Task, TaskContent, TaskHandlerId};
use flowy_error::FlowyError;
use lib_infra::future::BoxResultFuture;
impl GridTaskHandler for GridRevisionEditor {
fn handler_id(&self) -> &TaskHandlerId {
&self.grid_id
}
fn process_task(&self, task: Task) -> BoxResultFuture<(), FlowyError> {
Box::pin(async move {
match &task.content {
TaskContent::Snapshot { .. } => {}
TaskContent::Filter => self.filter_service.process_task(task).await?,
}
Ok(())
})
}
}

View File

@ -3,6 +3,11 @@ mod util;
mod block_manager; mod block_manager;
pub mod block_revision_editor; pub mod block_revision_editor;
pub mod field; pub mod field;
mod filter;
pub mod grid_editor; pub mod grid_editor;
mod grid_editor_task;
pub mod persistence; pub mod persistence;
pub mod row; pub mod row;
pub mod setting;
mod snapshot;
pub mod tasks;

View File

@ -55,12 +55,6 @@ pub struct TypeOptionCellData {
pub field_type: FieldType, pub field_type: FieldType,
} }
impl TypeOptionCellData {
pub fn split(self) -> (String, FieldType) {
(self.data, self.field_type)
}
}
impl std::str::FromStr for TypeOptionCellData { impl std::str::FromStr for TypeOptionCellData {
type Err = FlowyError; type Err = FlowyError;
@ -139,14 +133,11 @@ pub fn apply_cell_data_changeset<T: Into<CellContentChangeset>>(
Ok(TypeOptionCellData::new(s, field_rev.field_type.clone()).json()) Ok(TypeOptionCellData::new(s, field_rev.field_type.clone()).json())
} }
pub fn decode_cell_data_from_type_option_cell_data<T: TryInto<TypeOptionCellData>>( pub fn decode_cell_data<T: TryInto<TypeOptionCellData>>(data: T, field_rev: &FieldRevision) -> DecodedCellData {
data: T,
field_rev: &FieldRevision,
field_type: &FieldType,
) -> DecodedCellData {
if let Ok(type_option_cell_data) = data.try_into() { if let Ok(type_option_cell_data) = data.try_into() {
let (encoded_data, s_field_type) = type_option_cell_data.split(); let TypeOptionCellData { data, field_type } = type_option_cell_data;
match decode_cell_data(encoded_data, &s_field_type, field_type, field_rev) { let to_field_type = &field_rev.field_type;
match try_decode_cell_data(data, field_rev, &field_type, to_field_type) {
Ok(cell_data) => cell_data, Ok(cell_data) => cell_data,
Err(e) => { Err(e) => {
tracing::error!("Decode cell data failed, {:?}", e); tracing::error!("Decode cell data failed, {:?}", e);
@ -159,11 +150,11 @@ pub fn decode_cell_data_from_type_option_cell_data<T: TryInto<TypeOptionCellData
} }
} }
pub fn decode_cell_data<T: Into<String>>( pub fn try_decode_cell_data<T: Into<String>>(
encoded_data: T, encoded_data: T,
field_rev: &FieldRevision,
s_field_type: &FieldType, s_field_type: &FieldType,
t_field_type: &FieldType, t_field_type: &FieldType,
field_rev: &FieldRevision,
) -> FlowyResult<DecodedCellData> { ) -> FlowyResult<DecodedCellData> {
let encoded_data = encoded_data.into(); let encoded_data = encoded_data.into();
let get_cell_data = || { let get_cell_data = || {
@ -229,6 +220,14 @@ where
} }
} }
/// The data is encoded by protobuf or utf8. You should choose the corresponding decode struct to parse it.
///
/// For example:
///
/// * Use DateCellData to parse the data when the FieldType is Date.
/// * Use URLCellData to parse the data when the FieldType is URL.
/// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox.
/// * Check out the implementation of CellDataOperation trait for more information.
#[derive(Default)] #[derive(Default)]
pub struct DecodedCellData { pub struct DecodedCellData {
pub data: Vec<u8>, pub data: Vec<u8>,

View File

@ -1,7 +1,6 @@
use crate::services::row::decode_cell_data_from_type_option_cell_data;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
use flowy_grid_data_model::entities::{Cell, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowOrder}; use flowy_grid_data_model::entities::{GridBlock, RepeatedGridBlock, Row, RowOrder};
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, RowRevision}; use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -10,36 +9,30 @@ pub struct GridBlockSnapshot {
pub row_revs: Vec<Arc<RowRevision>>, pub row_revs: Vec<Arc<RowRevision>>,
} }
pub(crate) fn group_row_orders(row_orders: Vec<RowOrder>) -> Vec<GridBlockOrder> { pub(crate) fn block_from_row_orders(row_orders: Vec<RowOrder>) -> Vec<GridBlock> {
let mut map: HashMap<String, GridBlockOrder> = HashMap::new(); let mut map: HashMap<String, GridBlock> = HashMap::new();
row_orders.into_iter().for_each(|row_order| { row_orders.into_iter().for_each(|row_order| {
// Memory Optimization: escape clone block_id // Memory Optimization: escape clone block_id
let block_id = row_order.block_id.clone(); let block_id = row_order.block_id.clone();
map.entry(block_id) map.entry(block_id)
.or_insert_with(|| GridBlockOrder::new(&row_order.block_id)) .or_insert_with(|| GridBlock::new(&row_order.block_id, vec![]))
.row_orders .row_orders
.push(row_order); .push(row_order);
}); });
map.into_values().collect::<Vec<_>>() map.into_values().collect::<Vec<_>>()
} }
//
#[inline(always)] // #[inline(always)]
pub fn make_cell_by_field_id( // fn make_cell_by_field_id(
field_map: &HashMap<&String, &FieldRevision>, // field_map: &HashMap<&String, &FieldRevision>,
field_id: String, // field_id: String,
cell_rev: CellRevision, // cell_rev: CellRevision,
) -> Option<(String, Cell)> { // ) -> Option<(String, Cell)> {
let field_rev = field_map.get(&field_id)?; // let field_rev = field_map.get(&field_id)?;
let data = decode_cell_data_from_type_option_cell_data(cell_rev.data, field_rev, &field_rev.field_type).data; // let data = decode_cell_data(cell_rev.data, field_rev).data;
let cell = Cell::new(&field_id, data); // let cell = Cell::new(&field_id, data);
Some((field_id, cell)) // Some((field_id, cell))
} // }
pub fn make_cell(field_id: &str, field_rev: &FieldRevision, row_rev: &RowRevision) -> Option<Cell> {
let cell_rev = row_rev.cells.get(field_id)?.clone();
let data = decode_cell_data_from_type_option_cell_data(cell_rev.data, field_rev, &field_rev.field_type).data;
Some(Cell::new(field_id, data))
}
pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<RowOrder> { pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<RowOrder> {
row_revs.iter().map(RowOrder::from).collect::<Vec<_>>() row_revs.iter().map(RowOrder::from).collect::<Vec<_>>()
@ -49,23 +42,22 @@ pub(crate) fn make_row_from_row_rev(fields: &[FieldRevision], row_rev: Arc<RowRe
make_rows_from_row_revs(fields, &[row_rev]).pop() make_rows_from_row_revs(fields, &[row_rev]).pop()
} }
pub(crate) fn make_rows_from_row_revs(fields: &[FieldRevision], row_revs: &[Arc<RowRevision>]) -> Vec<Row> { pub(crate) fn make_rows_from_row_revs(_fields: &[FieldRevision], row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
let field_rev_map = fields // let field_rev_map = fields
.iter() // .iter()
.map(|field_rev| (&field_rev.id, field_rev)) // .map(|field_rev| (&field_rev.id, field_rev))
.collect::<HashMap<&String, &FieldRevision>>(); // .collect::<HashMap<&String, &FieldRevision>>();
let make_row = |row_rev: &Arc<RowRevision>| { let make_row = |row_rev: &Arc<RowRevision>| {
let cell_by_field_id = row_rev // let cell_by_field_id = row_rev
.cells // .cells
.clone() // .clone()
.into_iter() // .into_iter()
.flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev)) // .flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev))
.collect::<HashMap<String, Cell>>(); // .collect::<HashMap<String, Cell>>();
Row { Row {
id: row_rev.id.clone(), id: row_rev.id.clone(),
cell_by_field_id,
height: row_rev.height, height: row_rev.height,
} }
}; };

View File

@ -0,0 +1,3 @@
mod setting_builder;
pub use setting_builder::*;

View File

@ -0,0 +1,35 @@
use flowy_grid_data_model::entities::{CreateGridFilterParams, GridLayoutType, GridSettingChangesetParams};
pub struct GridSettingChangesetBuilder {
params: GridSettingChangesetParams,
}
impl GridSettingChangesetBuilder {
pub fn new(grid_id: &str, layout_type: &GridLayoutType) -> Self {
let params = GridSettingChangesetParams {
grid_id: grid_id.to_string(),
layout_type: layout_type.clone(),
insert_filter: None,
delete_filter: None,
insert_group: None,
delete_group: None,
insert_sort: None,
delete_sort: None,
};
Self { params }
}
pub fn insert_filter(mut self, params: CreateGridFilterParams) -> Self {
self.params.insert_filter = Some(params);
self
}
pub fn delete_filter(mut self, filter_id: &str) -> Self {
self.params.delete_filter = Some(filter_id.to_string());
self
}
pub fn build(self) -> GridSettingChangesetParams {
self.params
}
}

View File

@ -0,0 +1,3 @@
mod snapshot_service;
pub use snapshot_service::*;

View File

@ -0,0 +1,7 @@
// pub struct GridSnapshotService {}
//
// impl GridSnapshotService {
// pub fn new() -> Self {
// Self {}
// }
// }

View File

@ -0,0 +1,9 @@
mod queue;
mod runner;
mod scheduler;
mod store;
mod task;
pub use queue::TaskHandlerId;
pub use scheduler::*;
pub use task::*;

View File

@ -0,0 +1,119 @@
use crate::services::tasks::task::{PendingTask, Task, TaskContent, TaskType};
use atomic_refcell::AtomicRefCell;
use std::cmp::Ordering;
use std::collections::hash_map::Entry;
use std::collections::{BinaryHeap, HashMap};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
#[derive(Default)]
pub(crate) struct GridTaskQueue {
// index_tasks for quick access
index_tasks: HashMap<TaskHandlerId, Arc<AtomicRefCell<TaskList>>>,
queue: BinaryHeap<Arc<AtomicRefCell<TaskList>>>,
}
impl GridTaskQueue {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn push(&mut self, task: &Task) {
let task_type = match task.content {
TaskContent::Snapshot { .. } => TaskType::Snapshot,
TaskContent::Filter => TaskType::Filter,
};
let pending_task = PendingTask {
ty: task_type,
id: task.id,
};
match self.index_tasks.entry("1".to_owned()) {
Entry::Occupied(entry) => {
let mut list = entry.get().borrow_mut();
assert!(list.peek().map(|old_id| pending_task.id >= old_id.id).unwrap_or(true));
list.push(pending_task);
}
Entry::Vacant(entry) => {
let mut task_list = TaskList::new(entry.key());
task_list.push(pending_task);
let task_list = Arc::new(AtomicRefCell::new(task_list));
entry.insert(task_list.clone());
self.queue.push(task_list);
}
}
}
pub(crate) fn mut_head<T, F>(&mut self, mut f: F) -> Option<T>
where
F: FnMut(&mut TaskList) -> Option<T>,
{
let head = self.queue.pop()?;
let result = {
let mut ref_head = head.borrow_mut();
f(&mut *ref_head)
};
if !head.borrow().tasks.is_empty() {
self.queue.push(head);
} else {
self.index_tasks.remove(&head.borrow().id);
}
result
}
}
pub type TaskHandlerId = String;
#[derive(Debug)]
pub(crate) struct TaskList {
pub(crate) id: TaskHandlerId,
tasks: BinaryHeap<PendingTask>,
}
impl Deref for TaskList {
type Target = BinaryHeap<PendingTask>;
fn deref(&self) -> &Self::Target {
&self.tasks
}
}
impl DerefMut for TaskList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.tasks
}
}
impl TaskList {
fn new(id: &str) -> Self {
Self {
id: id.to_owned(),
tasks: BinaryHeap::new(),
}
}
}
impl PartialEq for TaskList {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for TaskList {}
impl Ord for TaskList {
fn cmp(&self, other: &Self) -> Ordering {
match (self.peek(), other.peek()) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(lhs), Some(rhs)) => lhs.cmp(rhs),
}
}
}
impl PartialOrd for TaskList {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

View File

@ -0,0 +1,45 @@
use crate::services::tasks::scheduler::GridTaskScheduler;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{watch, RwLock};
use tokio::time::interval;
pub struct GridTaskRunner {
scheduler: Arc<RwLock<GridTaskScheduler>>,
debounce_duration: Duration,
notifier: Option<watch::Receiver<()>>,
}
impl GridTaskRunner {
pub fn new(
scheduler: Arc<RwLock<GridTaskScheduler>>,
notifier: watch::Receiver<()>,
debounce_duration: Duration,
) -> Self {
Self {
scheduler,
debounce_duration,
notifier: Some(notifier),
}
}
pub async fn run(mut self) {
let mut notifier = self
.notifier
.take()
.expect("The GridTaskRunner's notifier should only take once");
loop {
if notifier.changed().await.is_err() {
// The runner will be stopped if the corresponding Sender drop.
break;
}
let mut interval = interval(self.debounce_duration);
interval.tick().await;
if let Err(e) = self.scheduler.write().await.process_next_task().await {
tracing::error!("{:?}", e);
}
}
}
}

View File

@ -0,0 +1,87 @@
use crate::services::tasks::queue::{GridTaskQueue, TaskHandlerId};
use crate::services::tasks::runner::GridTaskRunner;
use crate::services::tasks::store::GridTaskStore;
use crate::services::tasks::task::Task;
use flowy_error::{FlowyError, FlowyResult};
use lib_infra::future::BoxResultFuture;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{watch, RwLock};
pub trait GridTaskHandler: Send + Sync + 'static {
fn handler_id(&self) -> &TaskHandlerId;
fn process_task(&self, task: Task) -> BoxResultFuture<(), FlowyError>;
}
pub struct GridTaskScheduler {
queue: GridTaskQueue,
store: GridTaskStore,
notifier: watch::Sender<()>,
handlers: HashMap<TaskHandlerId, Arc<dyn GridTaskHandler>>,
}
impl GridTaskScheduler {
pub fn new() -> Arc<RwLock<Self>> {
let (notifier, rx) = watch::channel(());
let scheduler = Self {
queue: GridTaskQueue::new(),
store: GridTaskStore::new(),
notifier,
handlers: HashMap::new(),
};
// The runner will receive the newest value after start running.
scheduler.notify();
let scheduler = Arc::new(RwLock::new(scheduler));
let debounce_duration = Duration::from_millis(300);
let runner = GridTaskRunner::new(scheduler.clone(), rx, debounce_duration);
tokio::spawn(runner.run());
scheduler
}
pub fn register_handler<T>(&mut self, handler: Arc<T>)
where
T: GridTaskHandler,
{
let handler_id = handler.handler_id().to_owned();
self.handlers.insert(handler_id, handler);
}
pub fn unregister_handler<T: AsRef<str>>(&mut self, handler_id: T) {
let _ = self.handlers.remove(handler_id.as_ref());
}
pub async fn process_next_task(&mut self) -> FlowyResult<()> {
let mut get_next_task = || {
let pending_task = self.queue.mut_head(|list| list.pop())?;
let task = self.store.remove_task(&pending_task.id)?;
Some(task)
};
if let Some(task) = get_next_task() {
match self.handlers.get(&task.hid) {
None => {}
Some(handler) => {
let _ = handler.process_task(task).await;
}
}
}
Ok(())
}
pub fn register_task(&mut self, task: Task) {
assert!(!task.is_finished());
self.queue.push(&task);
self.store.insert_task(task);
self.notify();
}
pub fn notify(&self) {
let _ = self.notifier.send(());
}
}

View File

@ -0,0 +1,32 @@
use crate::services::tasks::task::Task;
use crate::services::tasks::TaskId;
use std::collections::HashMap;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::SeqCst;
pub struct GridTaskStore {
tasks: HashMap<TaskId, Task>,
task_id_counter: AtomicU32,
}
impl GridTaskStore {
pub fn new() -> Self {
Self {
tasks: HashMap::new(),
task_id_counter: AtomicU32::new(0),
}
}
pub fn insert_task(&mut self, task: Task) {
self.tasks.insert(task.id, task);
}
pub fn remove_task(&mut self, task_id: &TaskId) -> Option<Task> {
self.tasks.remove(task_id)
}
#[allow(dead_code)]
pub fn next_task_id(&self) -> TaskId {
let _ = self.task_id_counter.fetch_add(1, SeqCst);
self.task_id_counter.load(SeqCst)
}
}

View File

@ -0,0 +1,69 @@
use crate::services::tasks::queue::TaskHandlerId;
use std::cmp::Ordering;
#[derive(Eq, Debug, Clone, Copy)]
pub enum TaskType {
/// Remove the row if it doesn't satisfy the filter.
Filter,
/// Generate snapshot for grid, unused by now.
Snapshot,
}
impl PartialEq for TaskType {
fn eq(&self, other: &Self) -> bool {
matches!(
(self, other),
(Self::Filter, Self::Filter) | (Self::Snapshot, Self::Snapshot)
)
}
}
pub type TaskId = u32;
#[derive(Eq, Debug, Clone, Copy)]
pub struct PendingTask {
pub ty: TaskType,
pub id: TaskId,
}
impl PartialEq for PendingTask {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id)
}
}
impl PartialOrd for PendingTask {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PendingTask {
fn cmp(&self, other: &Self) -> Ordering {
match (self.ty, other.ty) {
(TaskType::Snapshot, TaskType::Snapshot) => Ordering::Equal,
(TaskType::Snapshot, _) => Ordering::Greater,
(_, TaskType::Snapshot) => Ordering::Less,
(TaskType::Filter, TaskType::Filter) => self.id.cmp(&other.id),
}
}
}
pub type ContentId = String;
pub enum TaskContent {
Snapshot { content_id: ContentId },
Filter,
}
pub struct Task {
pub hid: TaskHandlerId,
pub id: TaskId,
pub content: TaskContent,
}
impl Task {
pub fn is_finished(&self) -> bool {
todo!()
}
}

View File

@ -1,13 +1,13 @@
use crate::grid::script::EditorScript::*; use crate::grid::script::EditorScript::*;
use crate::grid::script::*; use crate::grid::script::*;
use flowy_grid_data_model::revision::{GridBlockRevision, GridBlockRevisionChangeset}; use flowy_grid_data_model::revision::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset};
#[tokio::test] #[tokio::test]
async fn grid_create_block() { async fn grid_create_block() {
let grid_block = GridBlockRevision::new(); let block_meta_rev = GridBlockMetaRevision::new();
let scripts = vec![ let scripts = vec![
AssertBlockCount(1), AssertBlockCount(1),
CreateBlock { block: grid_block }, CreateBlock { block: block_meta_rev },
AssertBlockCount(2), AssertBlockCount(2),
]; ];
GridEditorTest::new().await.run_scripts(scripts).await; GridEditorTest::new().await.run_scripts(scripts).await;
@ -15,10 +15,10 @@ async fn grid_create_block() {
#[tokio::test] #[tokio::test]
async fn grid_update_block() { async fn grid_update_block() {
let grid_block = GridBlockRevision::new(); let block_meta_rev = GridBlockMetaRevision::new();
let mut cloned_grid_block = grid_block.clone(); let mut cloned_grid_block = block_meta_rev.clone();
let changeset = GridBlockRevisionChangeset { let changeset = GridBlockMetaRevisionChangeset {
block_id: grid_block.block_id.clone(), block_id: block_meta_rev.block_id.clone(),
start_row_index: Some(2), start_row_index: Some(2),
row_count: Some(10), row_count: Some(10),
}; };
@ -28,7 +28,7 @@ async fn grid_update_block() {
let scripts = vec![ let scripts = vec![
AssertBlockCount(1), AssertBlockCount(1),
CreateBlock { block: grid_block }, CreateBlock { block: block_meta_rev },
UpdateBlock { changeset }, UpdateBlock { changeset },
AssertBlockCount(2), AssertBlockCount(2),
AssertBlockEqual { AssertBlockEqual {

View File

@ -1,3 +1,4 @@
use crate::grid::field_util::make_date_cell_string;
use crate::grid::script::EditorScript::*; use crate::grid::script::EditorScript::*;
use crate::grid::script::*; use crate::grid::script::*;
use flowy_grid::services::field::{MultiSelectTypeOption, SelectOptionCellContentChangeset, SingleSelectTypeOption}; use flowy_grid::services::field::{MultiSelectTypeOption, SelectOptionCellContentChangeset, SingleSelectTypeOption};
@ -8,7 +9,7 @@ async fn grid_cell_update() {
let mut test = GridEditorTest::new().await; let mut test = GridEditorTest::new().await;
let field_revs = &test.field_revs; let field_revs = &test.field_revs;
let row_revs = &test.row_revs; let row_revs = &test.row_revs;
let grid_blocks = &test.grid_block_revs; let grid_blocks = &test.block_meta_revs;
// For the moment, We only have one block to store rows // For the moment, We only have one block to store rows
let block_id = &grid_blocks.first().unwrap().block_id; let block_id = &grid_blocks.first().unwrap().block_id;

View File

@ -1,3 +1,4 @@
use crate::grid::field_util::*;
use crate::grid::script::EditorScript::*; use crate::grid::script::EditorScript::*;
use crate::grid::script::*; use crate::grid::script::*;
use flowy_grid::services::field::{SelectOption, SingleSelectTypeOption}; use flowy_grid::services::field::{SelectOption, SingleSelectTypeOption};

View File

@ -0,0 +1,81 @@
use flowy_grid::services::field::*;
use flowy_grid_data_model::entities::*;
use flowy_grid_data_model::revision::*;
pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {
let field_rev = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.build();
let cloned_field_rev = field_rev.clone();
let type_option_data = field_rev
.get_type_option_entry::<RichTextTypeOption>(&field_rev.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_rev.id,
name: field_rev.name,
desc: field_rev.desc,
field_type: field_rev.field_type,
frozen: field_rev.frozen,
visibility: field_rev.visibility,
width: field_rev.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_rev)
}
pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {
let single_select = SingleSelectTypeOptionBuilder::default()
.option(SelectOption::new("Done"))
.option(SelectOption::new("Progress"));
let field_rev = FieldBuilder::new(single_select).name("Name").visibility(true).build();
let cloned_field_rev = field_rev.clone();
let type_option_data = field_rev
.get_type_option_entry::<SingleSelectTypeOption>(&field_rev.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_rev.id,
name: field_rev.name,
desc: field_rev.desc,
field_type: field_rev.field_type,
frozen: field_rev.frozen,
visibility: field_rev.visibility,
width: field_rev.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_rev)
}
// The grid will contains all existing field types and there are three empty rows in this grid.
pub fn make_date_cell_string(s: &str) -> String {
serde_json::to_string(&DateCellContentChangeset {
date: Some(s.to_string()),
time: None,
})
.unwrap()
}

View File

@ -0,0 +1,43 @@
use crate::grid::script::EditorScript::*;
use crate::grid::script::*;
use flowy_grid_data_model::entities::{CreateGridFilterPayload, TextFilterCondition};
#[tokio::test]
async fn grid_filter_create_test() {
let test = GridEditorTest::new().await;
let field_rev = test.text_field();
let payload = CreateGridFilterPayload::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned()));
let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
GridEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
#[should_panic]
async fn grid_filter_invalid_condition_panic_test() {
let test = GridEditorTest::new().await;
let field_rev = test.text_field();
// 100 is not a valid condition, so this test should be panic.
let payload = CreateGridFilterPayload::new(field_rev, 100, Some("abc".to_owned()));
let scripts = vec![InsertGridTableFilter { payload }];
GridEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_filter_delete_test() {
let mut test = GridEditorTest::new().await;
let field_rev = test.text_field();
let payload = CreateGridFilterPayload::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned()));
let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }];
test.run_scripts(scripts).await;
let filter = test.grid_filters().await.pop().unwrap();
test.run_scripts(vec![
DeleteGridTableFilter { filter_id: filter.id },
AssertTableFilterCount { count: 0 },
])
.await;
}
#[tokio::test]
async fn grid_filter_get_rows_test() {}

View File

@ -1,6 +1,8 @@
mod block_test; mod block_test;
mod cell_test; mod cell_test;
mod field_test; mod field_test;
mod field_util;
mod filter_test;
mod row_test; mod row_test;
mod row_util;
mod script; mod script;
mod setting_test;

View File

@ -1,23 +1,24 @@
use crate::grid::field_util::*;
use crate::grid::row_util::GridRowTestBuilder;
use crate::grid::script::EditorScript::*; use crate::grid::script::EditorScript::*;
use crate::grid::script::*; use crate::grid::script::*;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use flowy_grid::services::field::{ use flowy_grid::services::field::{
DateCellData, MultiSelectTypeOption, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, DateCellData, MultiSelectTypeOption, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
}; };
use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowRevisionBuilder}; use flowy_grid::services::row::{decode_cell_data, CreateRowRevisionBuilder};
use flowy_grid_data_model::entities::FieldType; use flowy_grid_data_model::entities::FieldType;
use flowy_grid_data_model::revision::RowMetaChangeset; use flowy_grid_data_model::revision::RowMetaChangeset;
#[tokio::test] #[tokio::test]
async fn grid_create_row_count_test() { async fn grid_create_row_count_test() {
let test = GridEditorTest::new().await; let test = GridEditorTest::new().await;
let create_row_context = CreateRowRevisionBuilder::new(&test.field_revs).build();
let scripts = vec![ let scripts = vec![
AssertRowCount(3), AssertRowCount(3),
CreateEmptyRow, CreateEmptyRow,
CreateEmptyRow, CreateEmptyRow,
CreateRow { CreateRow {
context: create_row_context, payload: GridRowTestBuilder::new(&test).build(),
}, },
AssertRowCount(6), AssertRowCount(6),
]; ];
@ -27,15 +28,15 @@ async fn grid_create_row_count_test() {
#[tokio::test] #[tokio::test]
async fn grid_update_row() { async fn grid_update_row() {
let mut test = GridEditorTest::new().await; let mut test = GridEditorTest::new().await;
let context = CreateRowRevisionBuilder::new(&test.field_revs).build(); let payload = GridRowTestBuilder::new(&test).build();
let changeset = RowMetaChangeset { let changeset = RowMetaChangeset {
row_id: context.row_id.clone(), row_id: payload.row_id.clone(),
height: None, height: None,
visibility: None, visibility: None,
cell_by_field_id: Default::default(), cell_by_field_id: Default::default(),
}; };
let scripts = vec![AssertRowCount(3), CreateRow { context }, UpdateRow { changeset }]; let scripts = vec![AssertRowCount(3), CreateRow { payload }, UpdateRow { changeset }];
test.run_scripts(scripts).await; test.run_scripts(scripts).await;
let expected_row = (&*test.row_revs.last().cloned().unwrap()).clone(); let expected_row = (&*test.row_revs.last().cloned().unwrap()).clone();
@ -46,13 +47,13 @@ async fn grid_update_row() {
#[tokio::test] #[tokio::test]
async fn grid_delete_row() { async fn grid_delete_row() {
let mut test = GridEditorTest::new().await; let mut test = GridEditorTest::new().await;
let context_1 = CreateRowRevisionBuilder::new(&test.field_revs).build(); let payload1 = GridRowTestBuilder::new(&test).build();
let context_2 = CreateRowRevisionBuilder::new(&test.field_revs).build(); let payload2 = GridRowTestBuilder::new(&test).build();
let row_ids = vec![context_1.row_id.clone(), context_2.row_id.clone()]; let row_ids = vec![payload1.row_id.clone(), payload2.row_id.clone()];
let scripts = vec![ let scripts = vec![
AssertRowCount(3), AssertRowCount(3),
CreateRow { context: context_1 }, CreateRow { payload: payload1 },
CreateRow { context: context_2 }, CreateRow { payload: payload2 },
AssertBlockCount(1), AssertBlockCount(1),
AssertBlock { AssertBlock {
block_index: 0, block_index: 0,
@ -110,7 +111,7 @@ async fn grid_row_add_cells_test() {
} }
} }
let context = builder.build(); let context = builder.build();
let scripts = vec![CreateRow { context }, AssertGridRevisionPad]; let scripts = vec![CreateRow { payload: context }, AssertGridRevisionPad];
test.run_scripts(scripts).await; test.run_scripts(scripts).await;
} }
@ -136,12 +137,12 @@ async fn grid_row_add_date_cell_test() {
let date_field = date_field.unwrap(); let date_field = date_field.unwrap();
let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
assert_eq!( assert_eq!(
decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) decode_cell_data(cell_data.data.clone(), &date_field)
.parse::<DateCellData>() .parse::<DateCellData>()
.unwrap() .unwrap()
.date, .date,
"2022/03/16", "2022/03/16",
); );
let scripts = vec![CreateRow { context }]; let scripts = vec![CreateRow { payload: context }];
test.run_scripts(scripts).await; test.run_scripts(scripts).await;
} }

View File

@ -0,0 +1,72 @@
use crate::grid::script::GridEditorTest;
use flowy_grid::services::field::DateCellContentChangeset;
use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload};
use flowy_grid_data_model::entities::FieldType;
use flowy_grid_data_model::revision::FieldRevision;
use strum::EnumCount;
pub struct GridRowTestBuilder<'a> {
test: &'a GridEditorTest,
inner_builder: CreateRowRevisionBuilder<'a>,
}
impl<'a> GridRowTestBuilder<'a> {
pub fn new(test: &'a GridEditorTest) -> Self {
assert_eq!(test.field_revs.len(), FieldType::COUNT);
let inner_builder = CreateRowRevisionBuilder::new(&test.field_revs);
Self { test, inner_builder }
}
#[allow(dead_code)]
pub fn update_text_cell(mut self, data: String) -> Self {
let text_field = self.field_rev_with_type(&FieldType::DateTime);
self.inner_builder.add_cell(&text_field.id, data).unwrap();
self
}
#[allow(dead_code)]
pub fn update_number_cell(mut self, data: String) -> Self {
let number_field = self.field_rev_with_type(&FieldType::DateTime);
self.inner_builder.add_cell(&number_field.id, data).unwrap();
self
}
#[allow(dead_code)]
pub fn update_date_cell(mut self, value: i64) -> Self {
let value = serde_json::to_string(&DateCellContentChangeset {
date: Some(value.to_string()),
time: None,
})
.unwrap();
let date_field = self.field_rev_with_type(&FieldType::DateTime);
self.inner_builder.add_cell(&date_field.id, value).unwrap();
self
}
#[allow(dead_code)]
pub fn update_checkbox_cell(mut self, data: bool) -> Self {
let number_field = self.field_rev_with_type(&FieldType::Checkbox);
self.inner_builder.add_cell(&number_field.id, data.to_string()).unwrap();
self
}
#[allow(dead_code)]
pub fn update_url_cell(mut self, data: String) -> Self {
let number_field = self.field_rev_with_type(&FieldType::Checkbox);
self.inner_builder.add_cell(&number_field.id, data).unwrap();
self
}
pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision {
self.test
.field_revs
.iter()
.find(|field_rev| &field_rev.field_type == field_type)
.unwrap()
.clone()
}
pub fn build(self) -> CreateRowRevisionPayload {
self.inner_builder.build()
}
}

View File

@ -1,7 +1,9 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
use bytes::Bytes; use bytes::Bytes;
use flowy_grid::services::field::*; use flowy_grid::services::field::*;
use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor}; use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor};
use flowy_grid::services::row::CreateRowRevisionPayload; use flowy_grid::services::row::CreateRowRevisionPayload;
use flowy_grid::services::setting::GridSettingChangesetBuilder;
use flowy_grid_data_model::entities::*; use flowy_grid_data_model::entities::*;
use flowy_grid_data_model::revision::*; use flowy_grid_data_model::revision::*;
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
@ -30,10 +32,10 @@ pub enum EditorScript {
field_rev: FieldRevision, field_rev: FieldRevision,
}, },
CreateBlock { CreateBlock {
block: GridBlockRevision, block: GridBlockMetaRevision,
}, },
UpdateBlock { UpdateBlock {
changeset: GridBlockRevisionChangeset, changeset: GridBlockMetaRevisionChangeset,
}, },
AssertBlockCount(usize), AssertBlockCount(usize),
AssertBlock { AssertBlock {
@ -43,11 +45,11 @@ pub enum EditorScript {
}, },
AssertBlockEqual { AssertBlockEqual {
block_index: usize, block_index: usize,
block: GridBlockRevision, block: GridBlockMetaRevision,
}, },
CreateEmptyRow, CreateEmptyRow,
CreateRow { CreateRow {
context: CreateRowRevisionPayload, payload: CreateRowRevisionPayload,
}, },
UpdateRow { UpdateRow {
changeset: RowMetaChangeset, changeset: RowMetaChangeset,
@ -63,11 +65,22 @@ pub enum EditorScript {
is_err: bool, is_err: bool,
}, },
AssertRowCount(usize), AssertRowCount(usize),
#[allow(dead_code)]
UpdateGridSetting { UpdateGridSetting {
params: GridSettingChangesetParams, params: GridSettingChangesetParams,
}, },
InsertGridTableFilter {
payload: CreateGridFilterPayload,
},
AssertTableFilterCount {
count: i32,
},
DeleteGridTableFilter {
filter_id: String,
},
#[allow(dead_code)]
AssertGridSetting { AssertGridSetting {
expected_setting: GridSettingRevision, expected_setting: GridSetting,
}, },
AssertGridRevisionPad, AssertGridRevisionPad,
} }
@ -77,7 +90,7 @@ pub struct GridEditorTest {
pub grid_id: String, pub grid_id: String,
pub editor: Arc<GridRevisionEditor>, pub editor: Arc<GridRevisionEditor>,
pub field_revs: Vec<FieldRevision>, pub field_revs: Vec<FieldRevision>,
pub grid_block_revs: Vec<GridBlockRevision>, pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
pub row_revs: Vec<Arc<RowRevision>>, pub row_revs: Vec<Arc<RowRevision>>,
pub field_count: usize, pub field_count: usize,
@ -88,15 +101,15 @@ impl GridEditorTest {
pub async fn new() -> Self { pub async fn new() -> Self {
let sdk = FlowySDKTest::default(); let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await; let _ = sdk.init_user().await;
let build_context = make_test_grid(); let build_context = make_all_field_test_grid();
let view_data: Bytes = build_context.into(); let view_data: Bytes = build_context.into();
let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await; let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap(); let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
let field_revs = editor.get_field_revs::<FieldOrder>(None).await.unwrap(); let field_revs = editor.get_field_revs::<FieldOrder>(None).await.unwrap();
let grid_blocks = editor.get_block_metas().await.unwrap(); let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs; let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs;
assert_eq!(row_revs.len(), 3); assert_eq!(row_revs.len(), 3);
assert_eq!(grid_blocks.len(), 1); assert_eq!(block_meta_revs.len(), 1);
// It seems like you should add the field in the make_test_grid() function. // It seems like you should add the field in the make_test_grid() function.
// Because we assert the initialize count of the fields is equal to FieldType::COUNT. // Because we assert the initialize count of the fields is equal to FieldType::COUNT.
@ -108,7 +121,7 @@ impl GridEditorTest {
grid_id, grid_id,
editor, editor,
field_revs, field_revs,
grid_block_revs: grid_blocks, block_meta_revs,
row_revs, row_revs,
field_count: FieldType::COUNT, field_count: FieldType::COUNT,
row_order_by_row_id: HashMap::default(), row_order_by_row_id: HashMap::default(),
@ -162,40 +175,40 @@ impl GridEditorTest {
} }
EditorScript::CreateBlock { block } => { EditorScript::CreateBlock { block } => {
self.editor.create_block(block).await.unwrap(); self.editor.create_block(block).await.unwrap();
self.grid_block_revs = self.editor.get_block_metas().await.unwrap(); self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
} }
EditorScript::UpdateBlock { changeset: change } => { EditorScript::UpdateBlock { changeset: change } => {
self.editor.update_block(change).await.unwrap(); self.editor.update_block(change).await.unwrap();
} }
EditorScript::AssertBlockCount(count) => { EditorScript::AssertBlockCount(count) => {
assert_eq!(self.editor.get_block_metas().await.unwrap().len(), count); assert_eq!(self.editor.get_block_meta_revs().await.unwrap().len(), count);
} }
EditorScript::AssertBlock { EditorScript::AssertBlock {
block_index, block_index,
row_count, row_count,
start_row_index, start_row_index,
} => { } => {
assert_eq!(self.grid_block_revs[block_index].row_count, row_count); assert_eq!(self.block_meta_revs[block_index].row_count, row_count);
assert_eq!(self.grid_block_revs[block_index].start_row_index, start_row_index); assert_eq!(self.block_meta_revs[block_index].start_row_index, start_row_index);
} }
EditorScript::AssertBlockEqual { block_index, block } => { EditorScript::AssertBlockEqual { block_index, block } => {
let blocks = self.editor.get_block_metas().await.unwrap(); let blocks = self.editor.get_block_meta_revs().await.unwrap();
let compared_block = blocks[block_index].clone(); let compared_block = blocks[block_index].clone();
assert_eq!(compared_block, block); assert_eq!(compared_block, Arc::new(block));
} }
EditorScript::CreateEmptyRow => { EditorScript::CreateEmptyRow => {
let row_order = self.editor.create_row(None).await.unwrap(); let row_order = self.editor.create_row(None).await.unwrap();
self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order); self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order);
self.row_revs = self.get_row_revs().await; self.row_revs = self.get_row_revs().await;
self.grid_block_revs = self.editor.get_block_metas().await.unwrap(); self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
} }
EditorScript::CreateRow { context } => { EditorScript::CreateRow { payload: context } => {
let row_orders = self.editor.insert_rows(vec![context]).await.unwrap(); let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
for row_order in row_orders { for row_order in row_orders {
self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order); self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order);
} }
self.row_revs = self.get_row_revs().await; self.row_revs = self.get_row_revs().await;
self.grid_block_revs = self.editor.get_block_metas().await.unwrap(); self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
} }
EditorScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(), EditorScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(),
EditorScript::DeleteRows { row_ids } => { EditorScript::DeleteRows { row_ids } => {
@ -206,7 +219,7 @@ impl GridEditorTest {
self.editor.delete_rows(row_orders).await.unwrap(); self.editor.delete_rows(row_orders).await.unwrap();
self.row_revs = self.get_row_revs().await; self.row_revs = self.get_row_revs().await;
self.grid_block_revs = self.editor.get_block_metas().await.unwrap(); self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
} }
EditorScript::AssertRow { expected_row } => { EditorScript::AssertRow { expected_row } => {
let row = &*self let row = &*self
@ -239,6 +252,26 @@ impl GridEditorTest {
EditorScript::UpdateGridSetting { params } => { EditorScript::UpdateGridSetting { params } => {
let _ = self.editor.update_grid_setting(params).await.unwrap(); let _ = self.editor.update_grid_setting(params).await.unwrap();
} }
EditorScript::InsertGridTableFilter { payload } => {
let params: CreateGridFilterParams = payload.try_into().unwrap();
let layout_type = GridLayoutType::Table;
let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
.insert_filter(params)
.build();
let _ = self.editor.update_grid_setting(params).await.unwrap();
}
EditorScript::AssertTableFilterCount { count } => {
let layout_type = GridLayoutType::Table;
let filters = self.editor.get_grid_filter(&layout_type).await.unwrap();
assert_eq!(count as usize, filters.len());
}
EditorScript::DeleteGridTableFilter { filter_id } => {
let layout_type = GridLayoutType::Table;
let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
.delete_filter(&filter_id)
.build();
let _ = self.editor.update_grid_setting(params).await.unwrap();
}
EditorScript::AssertGridSetting { expected_setting } => { EditorScript::AssertGridSetting { expected_setting } => {
let setting = self.editor.get_grid_setting().await.unwrap(); let setting = self.editor.get_grid_setting().await.unwrap();
assert_eq!(expected_setting, setting); assert_eq!(expected_setting, setting);
@ -261,76 +294,23 @@ impl GridEditorTest {
.unwrap() .unwrap()
.row_revs .row_revs
} }
pub async fn grid_filters(&self) -> Vec<GridFilter> {
let layout_type = GridLayoutType::Table;
self.editor.get_grid_filter(&layout_type).await.unwrap()
}
pub fn text_field(&self) -> &FieldRevision {
self.field_revs
.iter()
.filter(|field_rev| field_rev.field_type == FieldType::RichText)
.collect::<Vec<_>>()
.pop()
.unwrap()
}
} }
pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) { fn make_all_field_test_grid() -> BuildGridContext {
let field_rev = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.build();
let cloned_field_rev = field_rev.clone();
let type_option_data = field_rev
.get_type_option_entry::<RichTextTypeOption>(&field_rev.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_rev.id,
name: field_rev.name,
desc: field_rev.desc,
field_type: field_rev.field_type,
frozen: field_rev.frozen,
visibility: field_rev.visibility,
width: field_rev.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_rev)
}
pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {
let single_select = SingleSelectTypeOptionBuilder::default()
.option(SelectOption::new("Done"))
.option(SelectOption::new("Progress"));
let field_rev = FieldBuilder::new(single_select).name("Name").visibility(true).build();
let cloned_field_rev = field_rev.clone();
let type_option_data = field_rev
.get_type_option_entry::<SingleSelectTypeOption>(&field_rev.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_rev.id,
name: field_rev.name,
desc: field_rev.desc,
field_type: field_rev.field_type,
frozen: field_rev.frozen,
visibility: field_rev.visibility,
width: field_rev.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_rev)
}
fn make_test_grid() -> BuildGridContext {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name") .name("Name")
.visibility(true) .visibility(true)
@ -385,11 +365,3 @@ fn make_test_grid() -> BuildGridContext {
.add_empty_row() .add_empty_row()
.build() .build()
} }
pub fn make_date_cell_string(s: &str) -> String {
serde_json::to_string(&DateCellContentChangeset {
date: Some(s.to_string()),
time: None,
})
.unwrap()
}

View File

@ -228,7 +228,7 @@ impl ViewDataProcessor for GridViewDataProcessor {
let grid_manager = self.0.clone(); let grid_manager = self.0.clone();
let view_id = view_id.to_string(); let view_id = view_id.to_string();
FutureResult::new(async move { FutureResult::new(async move {
let _ = grid_manager.delete_grid(view_id)?; let _ = grid_manager.delete_grid(view_id).await?;
Ok(()) Ok(())
}) })
} }
@ -237,7 +237,7 @@ impl ViewDataProcessor for GridViewDataProcessor {
let grid_manager = self.0.clone(); let grid_manager = self.0.clone();
let view_id = view_id.to_string(); let view_id = view_id.to_string();
FutureResult::new(async move { FutureResult::new(async move {
let _ = grid_manager.close_grid(view_id)?; let _ = grid_manager.close_grid(view_id).await?;
Ok(()) Ok(())
}) })
} }

View File

@ -199,6 +199,15 @@ fn spawn_edit_queue(
) -> EditorCommandSender { ) -> EditorCommandSender {
let (sender, receiver) = mpsc::channel(1000); let (sender, receiver) = mpsc::channel(1000);
let edit_queue = EditBlockQueue::new(user, rev_manager, delta, receiver); let edit_queue = EditBlockQueue::new(user, rev_manager, delta, receiver);
// We can use tokio::task::spawn_local here by using tokio::spawn_blocking.
// https://github.com/tokio-rs/tokio/issues/2095
// tokio::task::spawn_blocking(move || {
// let rt = tokio::runtime::Handle::current();
// rt.block_on(async {
// let local = tokio::task::LocalSet::new();
// local.run_until(edit_queue.run()).await;
// });
// });
tokio::spawn(edit_queue.run()); tokio::spawn(edit_queue.run());
sender sender
} }

View File

@ -11,6 +11,7 @@ static RUST_TYPE_MAP: phf::Map<&'static str, &'static str> = phf_map! {
"i32" => "int32", "i32" => "int32",
"u64" => "uint64", "u64" => "uint64",
"u32" => "uint32", "u32" => "uint32",
"u8" => "uint8",
"Vec" => "repeated", "Vec" => "repeated",
"f64" => "double", "f64" => "double",
"HashMap" => "map", "HashMap" => "map",

View File

@ -36,7 +36,7 @@ run_task = { name = "remove_files_with_pattern" }
[tasks.rm_shared_lib_generated_protobuf_files] [tasks.rm_shared_lib_generated_protobuf_files]
private = true private = true
env = { "rm_proto_path" = "./shared-lib/**/resources/proto", "rm_protobuf_path" = "./shared-lib/**/protobuf" } env = { "rm_proto_path" = "../shared-lib/**/resources/proto", "rm_protobuf_path" = "../shared-lib/**/protobuf" }
run_task = { name = "remove_files_with_pattern" } run_task = { name = "remove_files_with_pattern" }

View File

@ -30,13 +30,10 @@ pub struct ViewRevision {
#[serde(default)] #[serde(default)]
pub thumbnail: String, pub thumbnail: String,
#[serde(default = "default_plugin_type")] #[serde(default = "DEFAULT_PLUGIN_TYPE")]
pub plugin_type: i32, pub plugin_type: i32,
} }
const DEFAULT_PLUGIN_TYPE: fn() -> i32 = || 0;
fn default_plugin_type() -> i32 {
0
}
impl std::convert::From<ViewRevision> for View { impl std::convert::From<ViewRevision> for View {
fn from(view_serde: ViewRevision) -> Self { fn from(view_serde: ViewRevision) -> Self {

View File

@ -11,7 +11,7 @@ protobuf = {version = "2.18.0"}
bytes = "1.0" bytes = "1.0"
strum = "0.21" strum = "0.21"
strum_macros = "0.21" strum_macros = "0.21"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = {version = "1.0"} serde_json = {version = "1.0"}
serde_repr = "0.1" serde_repr = "0.1"
nanoid = "0.4.0" nanoid = "0.4.0"

View File

@ -4,8 +4,6 @@ use crate::revision::RowRevision;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error_code::ErrorCode; use flowy_error_code::ErrorCode;
use std::collections::HashMap;
#[derive(Debug, Clone, Default, ProtoBuf)] #[derive(Debug, Clone, Default, ProtoBuf)]
pub struct Grid { pub struct Grid {
#[pb(index = 1)] #[pb(index = 1)]
@ -15,7 +13,7 @@ pub struct Grid {
pub field_orders: Vec<FieldOrder>, pub field_orders: Vec<FieldOrder>,
#[pb(index = 3)] #[pb(index = 3)]
pub block_orders: Vec<GridBlockOrder>, pub blocks: Vec<GridBlock>,
} }
#[derive(Debug, Default, Clone, ProtoBuf)] #[derive(Debug, Default, Clone, ProtoBuf)]
@ -36,12 +34,15 @@ pub struct Row {
pub id: String, pub id: String,
#[pb(index = 2)] #[pb(index = 2)]
pub cell_by_field_id: HashMap<String, Cell>,
#[pb(index = 3)]
pub height: i32, pub height: i32,
} }
#[derive(Debug, Default, ProtoBuf)]
pub struct OptionalRow {
#[pb(index = 1, one_of)]
pub row: Option<Row>,
}
#[derive(Debug, Default, ProtoBuf)] #[derive(Debug, Default, ProtoBuf)]
pub struct RepeatedRow { pub struct RepeatedRow {
#[pb(index = 1)] #[pb(index = 1)]
@ -66,24 +67,6 @@ impl std::convert::From<Vec<GridBlock>> for RepeatedGridBlock {
} }
} }
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct GridBlockOrder {
#[pb(index = 1)]
pub block_id: String,
#[pb(index = 2)]
pub row_orders: Vec<RowOrder>,
}
impl GridBlockOrder {
pub fn new(block_id: &str) -> Self {
GridBlockOrder {
block_id: block_id.to_owned(),
row_orders: vec![],
}
}
}
#[derive(Debug, Clone, Default, ProtoBuf)] #[derive(Debug, Clone, Default, ProtoBuf)]
pub struct IndexRowOrder { pub struct IndexRowOrder {
#[pb(index = 1)] #[pb(index = 1)]
@ -168,7 +151,7 @@ impl GridRowsChangeset {
} }
} }
#[derive(Debug, Default, ProtoBuf)] #[derive(Debug, Clone, Default, ProtoBuf)]
pub struct GridBlock { pub struct GridBlock {
#[pb(index = 1)] #[pb(index = 1)]
pub id: String, pub id: String,
@ -305,12 +288,12 @@ pub struct QueryGridBlocksPayload {
pub grid_id: String, pub grid_id: String,
#[pb(index = 2)] #[pb(index = 2)]
pub block_orders: Vec<GridBlockOrder>, pub block_ids: Vec<String>,
} }
pub struct QueryGridBlocksParams { pub struct QueryGridBlocksParams {
pub grid_id: String, pub grid_id: String,
pub block_orders: Vec<GridBlockOrder>, pub block_ids: Vec<String>,
} }
impl TryInto<QueryGridBlocksParams> for QueryGridBlocksPayload { impl TryInto<QueryGridBlocksParams> for QueryGridBlocksPayload {
@ -320,7 +303,7 @@ impl TryInto<QueryGridBlocksParams> for QueryGridBlocksPayload {
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
Ok(QueryGridBlocksParams { Ok(QueryGridBlocksParams {
grid_id: grid_id.0, grid_id: grid_id.0,
block_orders: self.block_orders, block_ids: self.block_ids,
}) })
} }
} }

View File

@ -0,0 +1,328 @@
use crate::parser::NotEmptyStr;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error_code::ErrorCode;
use crate::entities::FieldType;
use crate::revision::{FieldRevision, GridFilterRevision};
use std::convert::TryInto;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridFilter {
#[pb(index = 1)]
pub id: String,
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedGridFilter {
#[pb(index = 1)]
pub items: Vec<GridFilter>,
}
impl std::convert::From<&GridFilterRevision> for GridFilter {
fn from(rev: &GridFilterRevision) -> Self {
Self { id: rev.id.clone() }
}
}
impl std::convert::From<&Vec<GridFilterRevision>> for RepeatedGridFilter {
fn from(revs: &Vec<GridFilterRevision>) -> Self {
RepeatedGridFilter {
items: revs.iter().map(|rev| rev.into()).collect(),
}
}
}
impl std::convert::From<Vec<GridFilter>> for RepeatedGridFilter {
fn from(items: Vec<GridFilter>) -> Self {
Self { items }
}
}
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct CreateGridFilterPayload {
#[pb(index = 1)]
pub field_id: String,
#[pb(index = 2)]
pub field_type: FieldType,
#[pb(index = 3)]
pub condition: i32,
#[pb(index = 4, one_of)]
pub content: Option<String>,
}
impl CreateGridFilterPayload {
#[allow(dead_code)]
pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
Self {
field_id: field_rev.id.clone(),
field_type: field_rev.field_type.clone(),
condition: condition.into(),
content,
}
}
}
pub struct CreateGridFilterParams {
pub field_id: String,
pub field_type: FieldType,
pub condition: u8,
pub content: Option<String>,
}
impl TryInto<CreateGridFilterParams> for CreateGridFilterPayload {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateGridFilterParams, Self::Error> {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
let condition = self.condition as u8;
match self.field_type {
FieldType::RichText | FieldType::Checkbox | FieldType::URL => {
let _ = TextFilterCondition::try_from(condition)?;
}
FieldType::Number => {
let _ = NumberFilterCondition::try_from(condition)?;
}
FieldType::DateTime => {
let _ = DateFilterCondition::try_from(condition)?;
}
FieldType::SingleSelect | FieldType::MultiSelect => {
let _ = SelectOptionCondition::try_from(condition)?;
}
}
Ok(CreateGridFilterParams {
field_id,
field_type: self.field_type,
condition,
content: self.content,
})
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridTextFilter {
#[pb(index = 1)]
pub condition: TextFilterCondition,
#[pb(index = 2, one_of)]
pub content: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum TextFilterCondition {
Is = 0,
IsNot = 1,
Contains = 2,
DoesNotContain = 3,
StartsWith = 4,
EndsWith = 5,
TextIsEmpty = 6,
TextIsNotEmpty = 7,
}
impl std::convert::From<TextFilterCondition> for i32 {
fn from(value: TextFilterCondition) -> Self {
value as i32
}
}
impl std::default::Default for TextFilterCondition {
fn default() -> Self {
TextFilterCondition::Is
}
}
impl std::convert::TryFrom<u8> for TextFilterCondition {
type Error = ErrorCode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(TextFilterCondition::Is),
1 => Ok(TextFilterCondition::IsNot),
2 => Ok(TextFilterCondition::Contains),
3 => Ok(TextFilterCondition::DoesNotContain),
4 => Ok(TextFilterCondition::StartsWith),
5 => Ok(TextFilterCondition::EndsWith),
6 => Ok(TextFilterCondition::TextIsEmpty),
7 => Ok(TextFilterCondition::TextIsNotEmpty),
_ => Err(ErrorCode::InvalidData),
}
}
}
impl std::convert::From<GridFilterRevision> for GridTextFilter {
fn from(rev: GridFilterRevision) -> Self {
GridTextFilter {
condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
content: rev.content,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridNumberFilter {
#[pb(index = 1)]
pub condition: NumberFilterCondition,
#[pb(index = 2, one_of)]
pub content: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum NumberFilterCondition {
Equal = 0,
NotEqual = 1,
GreaterThan = 2,
LessThan = 3,
GreaterThanOrEqualTo = 4,
LessThanOrEqualTo = 5,
NumberIsEmpty = 6,
NumberIsNotEmpty = 7,
}
impl std::default::Default for NumberFilterCondition {
fn default() -> Self {
NumberFilterCondition::Equal
}
}
impl std::convert::From<NumberFilterCondition> for i32 {
fn from(value: NumberFilterCondition) -> Self {
value as i32
}
}
impl std::convert::TryFrom<u8> for NumberFilterCondition {
type Error = ErrorCode;
fn try_from(n: u8) -> Result<Self, Self::Error> {
match n {
0 => Ok(NumberFilterCondition::Equal),
1 => Ok(NumberFilterCondition::NotEqual),
2 => Ok(NumberFilterCondition::GreaterThan),
3 => Ok(NumberFilterCondition::LessThan),
4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo),
5 => Ok(NumberFilterCondition::LessThanOrEqualTo),
6 => Ok(NumberFilterCondition::NumberIsEmpty),
7 => Ok(NumberFilterCondition::NumberIsNotEmpty),
_ => Err(ErrorCode::InvalidData),
}
}
}
impl std::convert::From<GridFilterRevision> for GridNumberFilter {
fn from(rev: GridFilterRevision) -> Self {
GridNumberFilter {
condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
content: rev.content,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSelectOptionFilter {
#[pb(index = 1)]
pub condition: SelectOptionCondition,
#[pb(index = 2, one_of)]
pub content: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum SelectOptionCondition {
OptionIs = 0,
OptionIsNot = 1,
OptionIsEmpty = 2,
OptionIsNotEmpty = 3,
}
impl std::convert::From<SelectOptionCondition> for i32 {
fn from(value: SelectOptionCondition) -> Self {
value as i32
}
}
impl std::default::Default for SelectOptionCondition {
fn default() -> Self {
SelectOptionCondition::OptionIs
}
}
impl std::convert::TryFrom<u8> for SelectOptionCondition {
type Error = ErrorCode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(SelectOptionCondition::OptionIs),
1 => Ok(SelectOptionCondition::OptionIsNot),
2 => Ok(SelectOptionCondition::OptionIsEmpty),
3 => Ok(SelectOptionCondition::OptionIsNotEmpty),
_ => Err(ErrorCode::InvalidData),
}
}
}
impl std::convert::From<GridFilterRevision> for GridSelectOptionFilter {
fn from(rev: GridFilterRevision) -> Self {
GridSelectOptionFilter {
condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
content: rev.content,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridDateFilter {
#[pb(index = 1)]
pub condition: DateFilterCondition,
#[pb(index = 2, one_of)]
pub content: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
pub enum DateFilterCondition {
DateIs = 0,
DateBefore = 1,
DateAfter = 2,
DateOnOrBefore = 3,
DateOnOrAfter = 4,
DateWithIn = 5,
DateIsEmpty = 6,
}
impl std::default::Default for DateFilterCondition {
fn default() -> Self {
DateFilterCondition::DateIs
}
}
impl std::convert::TryFrom<u8> for DateFilterCondition {
type Error = ErrorCode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(DateFilterCondition::DateIs),
1 => Ok(DateFilterCondition::DateBefore),
2 => Ok(DateFilterCondition::DateAfter),
3 => Ok(DateFilterCondition::DateOnOrBefore),
4 => Ok(DateFilterCondition::DateOnOrAfter),
5 => Ok(DateFilterCondition::DateWithIn),
6 => Ok(DateFilterCondition::DateIsEmpty),
_ => Err(ErrorCode::InvalidData),
}
}
}
impl std::convert::From<GridFilterRevision> for GridDateFilter {
fn from(rev: GridFilterRevision) -> Self {
GridDateFilter {
condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs),
content: rev.content,
}
}
}

View File

@ -0,0 +1,80 @@
use crate::parser::NotEmptyStr;
use flowy_derive::ProtoBuf;
use flowy_error_code::ErrorCode;
use crate::revision::GridGroupRevision;
use std::convert::TryInto;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridGroup {
#[pb(index = 1)]
pub id: String,
#[pb(index = 2, one_of)]
pub group_field_id: Option<String>,
#[pb(index = 3, one_of)]
pub sub_group_field_id: Option<String>,
}
impl std::convert::From<&GridGroupRevision> for GridGroup {
fn from(rev: &GridGroupRevision) -> Self {
GridGroup {
id: rev.id.clone(),
group_field_id: rev.field_id.clone(),
sub_group_field_id: rev.sub_field_id.clone(),
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedGridGroup {
#[pb(index = 1)]
pub items: Vec<GridGroup>,
}
impl std::convert::From<Vec<GridGroup>> for RepeatedGridGroup {
fn from(items: Vec<GridGroup>) -> Self {
Self { items }
}
}
impl std::convert::From<&Vec<GridGroupRevision>> for RepeatedGridGroup {
fn from(revs: &Vec<GridGroupRevision>) -> Self {
RepeatedGridGroup {
items: revs.iter().map(|rev| rev.into()).collect(),
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct CreateGridGroupPayload {
#[pb(index = 1, one_of)]
pub field_id: Option<String>,
#[pb(index = 2, one_of)]
pub sub_field_id: Option<String>,
}
pub struct CreateGridGroupParams {
pub field_id: Option<String>,
pub sub_field_id: Option<String>,
}
impl TryInto<CreateGridGroupParams> for CreateGridGroupPayload {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateGridGroupParams, Self::Error> {
let field_id = match self.field_id {
None => None,
Some(field_id) => Some(NotEmptyStr::parse(field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
};
let sub_field_id = match self.sub_field_id {
None => None,
Some(field_id) => Some(NotEmptyStr::parse(field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
};
Ok(CreateGridGroupParams { field_id, sub_field_id })
}
}

View File

@ -1,4 +1,9 @@
use crate::parser::{NotEmptyStr, ViewFilterParser, ViewGroupParser, ViewSortParser}; use crate::entities::{
CreateGridFilterParams, CreateGridFilterPayload, CreateGridGroupParams, CreateGridGroupPayload,
CreateGridSortParams, CreateGridSortPayload, RepeatedGridFilter, RepeatedGridGroup, RepeatedGridSort,
};
use crate::parser::NotEmptyStr;
use crate::revision::{GridLayoutRevision, GridSettingRevision};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error_code::ErrorCode; use flowy_error_code::ErrorCode;
use std::collections::HashMap; use std::collections::HashMap;
@ -7,13 +12,41 @@ use std::convert::TryInto;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSetting { pub struct GridSetting {
#[pb(index = 1)] #[pb(index = 1)]
pub filter: HashMap<String, GridFilter>, pub filters_by_layout_ty: HashMap<String, RepeatedGridFilter>,
#[pb(index = 2)] #[pb(index = 2)]
pub group: HashMap<String, GridGroup>, pub groups_by_layout_ty: HashMap<String, RepeatedGridGroup>,
#[pb(index = 3)] #[pb(index = 3)]
pub sort: HashMap<String, GridSort>, pub sorts_by_layout_ty: HashMap<String, RepeatedGridSort>,
}
impl std::convert::From<&GridSettingRevision> for GridSetting {
fn from(rev: &GridSettingRevision) -> Self {
let filters_by_layout_ty: HashMap<String, RepeatedGridFilter> = rev
.filters
.iter()
.map(|(layout_rev, filter_revs)| (layout_rev.to_string(), filter_revs.into()))
.collect();
let groups_by_layout_ty: HashMap<String, RepeatedGridGroup> = rev
.groups
.iter()
.map(|(layout_rev, group_revs)| (layout_rev.to_string(), group_revs.into()))
.collect();
let sorts_by_layout_ty: HashMap<String, RepeatedGridSort> = rev
.sorts
.iter()
.map(|(layout_rev, sort_revs)| (layout_rev.to_string(), sort_revs.into()))
.collect();
GridSetting {
filters_by_layout_ty,
groups_by_layout_ty,
sorts_by_layout_ty,
}
}
} }
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
@ -29,25 +62,22 @@ impl std::default::Default for GridLayoutType {
} }
} }
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] impl std::convert::From<GridLayoutRevision> for GridLayoutType {
pub struct GridFilter { fn from(rev: GridLayoutRevision) -> Self {
#[pb(index = 1, one_of)] match rev {
pub field_id: Option<String>, GridLayoutRevision::Table => GridLayoutType::Table,
GridLayoutRevision::Board => GridLayoutType::Board,
}
}
} }
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] impl std::convert::From<GridLayoutType> for GridLayoutRevision {
pub struct GridGroup { fn from(layout: GridLayoutType) -> Self {
#[pb(index = 1, one_of)] match layout {
pub group_field_id: Option<String>, GridLayoutType::Table => GridLayoutRevision::Table,
GridLayoutType::Board => GridLayoutRevision::Board,
#[pb(index = 2, one_of)] }
pub sub_group_field_id: Option<String>, }
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSort {
#[pb(index = 1, one_of)]
pub field_id: Option<String>,
} }
#[derive(Default, ProtoBuf)] #[derive(Default, ProtoBuf)]
@ -59,21 +89,39 @@ pub struct GridSettingChangesetPayload {
pub layout_type: GridLayoutType, pub layout_type: GridLayoutType,
#[pb(index = 3, one_of)] #[pb(index = 3, one_of)]
pub filter: Option<GridFilter>, pub insert_filter: Option<CreateGridFilterPayload>,
#[pb(index = 4, one_of)] #[pb(index = 4, one_of)]
pub group: Option<GridGroup>, pub delete_filter: Option<String>,
#[pb(index = 5, one_of)] #[pb(index = 5, one_of)]
pub sort: Option<GridSort>, pub insert_group: Option<CreateGridGroupPayload>,
#[pb(index = 6, one_of)]
pub delete_group: Option<String>,
#[pb(index = 7, one_of)]
pub insert_sort: Option<CreateGridSortPayload>,
#[pb(index = 8, one_of)]
pub delete_sort: Option<String>,
} }
pub struct GridSettingChangesetParams { pub struct GridSettingChangesetParams {
pub grid_id: String, pub grid_id: String,
pub layout_type: GridLayoutType, pub layout_type: GridLayoutType,
pub filter: Option<GridFilter>, pub insert_filter: Option<CreateGridFilterParams>,
pub group: Option<GridGroup>, pub delete_filter: Option<String>,
pub sort: Option<GridSort>, pub insert_group: Option<CreateGridGroupParams>,
pub delete_group: Option<String>,
pub insert_sort: Option<CreateGridSortParams>,
pub delete_sort: Option<String>,
}
impl GridSettingChangesetParams {
pub fn is_filter_changed(&self) -> bool {
self.insert_filter.is_some() || self.delete_filter.is_some()
}
} }
impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayload { impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayload {
@ -84,27 +132,45 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayload {
.map_err(|_| ErrorCode::FieldIdIsEmpty)? .map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0; .0;
let filter = match self.filter { let insert_filter = match self.insert_filter {
None => None, None => None,
Some(filter) => Some(ViewFilterParser::parse(filter)?), Some(payload) => Some(payload.try_into()?),
}; };
let group = match self.group { let delete_filter = match self.delete_filter {
None => None, None => None,
Some(group) => Some(ViewGroupParser::parse(group)?), Some(filter_id) => Some(NotEmptyStr::parse(filter_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
}; };
let sort = match self.sort { let insert_group = match self.insert_group {
Some(payload) => Some(payload.try_into()?),
None => None, None => None,
Some(sort) => Some(ViewSortParser::parse(sort)?), };
let delete_group = match self.delete_group {
None => None,
Some(filter_id) => Some(NotEmptyStr::parse(filter_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
};
let insert_sort = match self.insert_sort {
None => None,
Some(payload) => Some(payload.try_into()?),
};
let delete_sort = match self.delete_sort {
None => None,
Some(filter_id) => Some(NotEmptyStr::parse(filter_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
}; };
Ok(GridSettingChangesetParams { Ok(GridSettingChangesetParams {
grid_id: view_id, grid_id: view_id,
layout_type: self.layout_type, layout_type: self.layout_type,
filter, insert_filter,
group, delete_filter,
sort, insert_group,
delete_group,
insert_sort,
delete_sort,
}) })
} }
} }

View File

@ -0,0 +1,68 @@
use crate::parser::NotEmptyStr;
use flowy_derive::ProtoBuf;
use flowy_error_code::ErrorCode;
use crate::revision::GridSortRevision;
use std::convert::TryInto;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSort {
#[pb(index = 1)]
pub id: String,
#[pb(index = 2, one_of)]
pub field_id: Option<String>,
}
impl std::convert::From<&GridSortRevision> for GridSort {
fn from(rev: &GridSortRevision) -> Self {
GridSort {
id: rev.id.clone(),
field_id: rev.field_id.clone(),
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedGridSort {
#[pb(index = 1)]
pub items: Vec<GridSort>,
}
impl std::convert::From<&Vec<GridSortRevision>> for RepeatedGridSort {
fn from(revs: &Vec<GridSortRevision>) -> Self {
RepeatedGridSort {
items: revs.iter().map(|rev| rev.into()).collect(),
}
}
}
impl std::convert::From<Vec<GridSort>> for RepeatedGridSort {
fn from(items: Vec<GridSort>) -> Self {
Self { items }
}
}
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct CreateGridSortPayload {
#[pb(index = 1, one_of)]
pub field_id: Option<String>,
}
pub struct CreateGridSortParams {
pub field_id: Option<String>,
}
impl TryInto<CreateGridSortParams> for CreateGridSortPayload {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateGridSortParams, Self::Error> {
let field_id = match self.field_id {
None => None,
Some(field_id) => Some(NotEmptyStr::parse(field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
};
Ok(CreateGridSortParams { field_id })
}
}

View File

@ -1,7 +1,13 @@
mod field; mod field;
mod grid; mod grid;
mod grid_filter;
mod grid_group;
mod grid_setting; mod grid_setting;
mod grid_sort;
pub use field::*; pub use field::*;
pub use grid::*; pub use grid::*;
pub use grid_filter::*;
pub use grid_group::*;
pub use grid_setting::*; pub use grid_setting::*;
pub use grid_sort::*;

View File

@ -1,58 +0,0 @@
use crate::entities::{GridFilter, GridGroup, GridSort};
use crate::parser::NotEmptyStr;
use flowy_error_code::ErrorCode;
pub struct ViewFilterParser(pub GridFilter);
impl ViewFilterParser {
pub fn parse(value: GridFilter) -> Result<GridFilter, ErrorCode> {
let field_id = match value.field_id {
None => None,
Some(field_id) => Some(NotEmptyStr::parse(field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
};
Ok(GridFilter { field_id })
}
}
pub struct ViewGroupParser(pub GridGroup);
impl ViewGroupParser {
pub fn parse(value: GridGroup) -> Result<GridGroup, ErrorCode> {
let group_field_id = match value.group_field_id {
None => None,
Some(group_field_id) => Some(
NotEmptyStr::parse(group_field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0,
),
};
let sub_group_field_id = match value.sub_group_field_id {
None => None,
Some(sub_group_field_id) => Some(
NotEmptyStr::parse(sub_group_field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0,
),
};
Ok(GridGroup {
group_field_id,
sub_group_field_id,
})
}
}
pub struct ViewSortParser(pub GridSort);
impl ViewSortParser {
pub fn parse(value: GridSort) -> Result<GridSort, ErrorCode> {
let field_id = match value.field_id {
None => None,
Some(field_id) => Some(NotEmptyStr::parse(field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
};
Ok(GridSort { field_id })
}
}

View File

@ -1,5 +1,2 @@
mod grid_info_parser;
mod str_parser; mod str_parser;
pub use grid_info_parser::*;
pub use str_parser::*; pub use str_parser::*;

View File

@ -0,0 +1,94 @@
use crate::entities::NumberFilterCondition;
use indexmap::IndexMap;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use serde_repr::*;
use std::str::FromStr;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct GridFilterRevision {
pub id: String,
pub field_id: String,
pub condition: u8,
pub content: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum TextFilterConditionRevision {
Is = 0,
IsNot = 1,
Contains = 2,
DoesNotContain = 3,
StartsWith = 4,
EndsWith = 5,
IsEmpty = 6,
IsNotEmpty = 7,
}
impl ToString for TextFilterConditionRevision {
fn to_string(&self) -> String {
(self.clone() as u8).to_string()
}
}
impl FromStr for TextFilterConditionRevision {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rev = serde_json::from_str(s)?;
Ok(rev)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum NumberFilterConditionRevision {
Equal = 0,
NotEqual = 1,
GreaterThan = 2,
LessThan = 3,
GreaterThanOrEqualTo = 4,
LessThanOrEqualTo = 5,
IsEmpty = 6,
IsNotEmpty = 7,
}
impl ToString for NumberFilterConditionRevision {
fn to_string(&self) -> String {
(self.clone() as u8).to_string()
}
}
impl FromStr for NumberFilterConditionRevision {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rev = serde_json::from_str(s)?;
Ok(rev)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum SelectOptionConditionRevision {
OptionIs = 0,
OptionIsNot = 1,
OptionIsEmpty = 2,
OptionIsNotEmpty = 3,
}
impl ToString for SelectOptionConditionRevision {
fn to_string(&self) -> String {
(self.clone() as u8).to_string()
}
}
impl FromStr for SelectOptionConditionRevision {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let rev = serde_json::from_str(s)?;
Ok(rev)
}
}

View File

@ -30,9 +30,9 @@ pub fn gen_field_id() -> String {
pub struct GridRevision { pub struct GridRevision {
pub grid_id: String, pub grid_id: String,
pub fields: Vec<FieldRevision>, pub fields: Vec<FieldRevision>,
pub blocks: Vec<GridBlockRevision>, pub blocks: Vec<Arc<GridBlockMetaRevision>>,
#[serde(skip)] #[serde(default, skip)]
pub setting: GridSettingRevision, pub setting: GridSettingRevision,
} }
@ -45,16 +45,25 @@ impl GridRevision {
setting: GridSettingRevision::default(), setting: GridSettingRevision::default(),
} }
} }
pub fn from_build_context(grid_id: &str, context: BuildGridContext) -> Self {
Self {
grid_id: grid_id.to_owned(),
fields: context.field_revs,
blocks: context.blocks.into_iter().map(Arc::new).collect(),
setting: Default::default(),
}
}
} }
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GridBlockRevision { pub struct GridBlockMetaRevision {
pub block_id: String, pub block_id: String,
pub start_row_index: i32, pub start_row_index: i32,
pub row_count: i32, pub row_count: i32,
} }
impl GridBlockRevision { impl GridBlockMetaRevision {
pub fn len(&self) -> i32 { pub fn len(&self) -> i32 {
self.row_count self.row_count
} }
@ -64,22 +73,22 @@ impl GridBlockRevision {
} }
} }
impl GridBlockRevision { impl GridBlockMetaRevision {
pub fn new() -> Self { pub fn new() -> Self {
GridBlockRevision { GridBlockMetaRevision {
block_id: gen_block_id(), block_id: gen_block_id(),
..Default::default() ..Default::default()
} }
} }
} }
pub struct GridBlockRevisionChangeset { pub struct GridBlockMetaRevisionChangeset {
pub block_id: String, pub block_id: String,
pub start_row_index: Option<i32>, pub start_row_index: Option<i32>,
pub row_count: Option<i32>, pub row_count: Option<i32>,
} }
impl GridBlockRevisionChangeset { impl GridBlockMetaRevisionChangeset {
pub fn from_row_count(block_id: &str, row_count: i32) -> Self { pub fn from_row_count(block_id: &str, row_count: i32) -> Self {
Self { Self {
block_id: block_id.to_string(), block_id: block_id.to_string(),
@ -90,9 +99,9 @@ impl GridBlockRevisionChangeset {
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GridBlockRevisionData { pub struct GridBlockRevision {
pub block_id: String, pub block_id: String,
pub rows: Vec<RowRevision>, pub rows: Vec<Arc<RowRevision>>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)] #[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
@ -118,13 +127,11 @@ pub struct FieldRevision {
#[serde(with = "indexmap::serde_seq")] #[serde(with = "indexmap::serde_seq")]
pub type_options: IndexMap<String, String>, pub type_options: IndexMap<String, String>,
#[serde(default = "default_is_primary")] #[serde(default = "DEFAULT_IS_PRIMARY")]
pub is_primary: bool, pub is_primary: bool,
} }
fn default_is_primary() -> bool { const DEFAULT_IS_PRIMARY: fn() -> bool = || false;
false
}
impl FieldRevision { impl FieldRevision {
pub fn new(name: &str, desc: &str, field_type: FieldType, is_primary: bool) -> Self { pub fn new(name: &str, desc: &str, field_type: FieldType, is_primary: bool) -> Self {
@ -283,8 +290,8 @@ impl CellRevision {
#[derive(Clone, Default, Deserialize, Serialize)] #[derive(Clone, Default, Deserialize, Serialize)]
pub struct BuildGridContext { pub struct BuildGridContext {
pub field_revs: Vec<FieldRevision>, pub field_revs: Vec<FieldRevision>,
pub blocks: Vec<GridBlockRevision>, pub blocks: Vec<GridBlockMetaRevision>,
pub blocks_meta_data: Vec<GridBlockRevisionData>, pub blocks_meta_data: Vec<GridBlockRevision>,
} }
impl BuildGridContext { impl BuildGridContext {

View File

@ -1,19 +1,32 @@
use crate::entities::{GridFilter, GridGroup, GridLayoutType, GridSetting, GridSort};
use indexmap::IndexMap; use indexmap::IndexMap;
use nanoid::nanoid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::*; use serde_repr::*;
use std::collections::HashMap;
pub fn gen_grid_filter_id() -> String {
nanoid!(6)
}
pub fn gen_grid_group_id() -> String {
nanoid!(6)
}
pub fn gen_grid_sort_id() -> String {
nanoid!(6)
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
pub struct GridSettingRevision { pub struct GridSettingRevision {
#[serde(with = "indexmap::serde_seq")] pub layout: GridLayoutRevision,
pub filter: IndexMap<GridLayoutRevision, GridFilterRevision>,
#[serde(with = "indexmap::serde_seq")] #[serde(with = "indexmap::serde_seq")]
pub group: IndexMap<GridLayoutRevision, GridGroupRevision>, pub filters: IndexMap<GridLayoutRevision, Vec<GridFilterRevision>>,
#[serde(with = "indexmap::serde_seq")] #[serde(skip, with = "indexmap::serde_seq")]
pub sort: IndexMap<GridLayoutRevision, GridSortRevision>, pub groups: IndexMap<GridLayoutRevision, Vec<GridGroupRevision>>,
#[serde(skip, with = "indexmap::serde_seq")]
pub sorts: IndexMap<GridLayoutRevision, Vec<GridSortRevision>>,
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
@ -36,81 +49,23 @@ impl std::default::Default for GridLayoutRevision {
} }
} }
impl std::convert::From<GridLayoutRevision> for GridLayoutType {
fn from(rev: GridLayoutRevision) -> Self {
match rev {
GridLayoutRevision::Table => GridLayoutType::Table,
GridLayoutRevision::Board => GridLayoutType::Board,
}
}
}
impl std::convert::From<GridLayoutType> for GridLayoutRevision {
fn from(layout: GridLayoutType) -> Self {
match layout {
GridLayoutType::Table => GridLayoutRevision::Table,
GridLayoutType::Board => GridLayoutRevision::Board,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct GridFilterRevision { pub struct GridFilterRevision {
pub field_id: Option<String>, pub id: String,
pub field_id: String,
pub condition: u8,
pub content: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct GridGroupRevision { pub struct GridGroupRevision {
pub group_field_id: Option<String>, pub id: String,
pub sub_group_field_id: Option<String>, pub field_id: Option<String>,
pub sub_field_id: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct GridSortRevision { pub struct GridSortRevision {
pub id: String,
pub field_id: Option<String>, pub field_id: Option<String>,
} }
impl std::convert::From<GridFilterRevision> for GridFilter {
fn from(rev: GridFilterRevision) -> Self {
GridFilter { field_id: rev.field_id }
}
}
impl std::convert::From<GridGroupRevision> for GridGroup {
fn from(rev: GridGroupRevision) -> Self {
GridGroup {
group_field_id: rev.group_field_id,
sub_group_field_id: rev.sub_group_field_id,
}
}
}
impl std::convert::From<GridSortRevision> for GridSort {
fn from(rev: GridSortRevision) -> Self {
GridSort { field_id: rev.field_id }
}
}
impl std::convert::From<GridSettingRevision> for GridSetting {
fn from(rev: GridSettingRevision) -> Self {
let filter: HashMap<String, GridFilter> = rev
.filter
.into_iter()
.map(|(layout_rev, filter_rev)| (layout_rev.to_string(), filter_rev.into()))
.collect();
let group: HashMap<String, GridGroup> = rev
.group
.into_iter()
.map(|(layout_rev, group_rev)| (layout_rev.to_string(), group_rev.into()))
.collect();
let sort: HashMap<String, GridSort> = rev
.sort
.into_iter()
.map(|(layout_rev, sort_rev)| (layout_rev.to_string(), sort_rev.into()))
.collect();
GridSetting { filter, group, sort }
}
}

View File

@ -2,40 +2,44 @@ use crate::entities::revision::{md5, RepeatedRevision, Revision};
use crate::errors::{CollaborateError, CollaborateResult}; use crate::errors::{CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_delta_from_revisions}; use crate::util::{cal_diff, make_delta_from_revisions};
use flowy_grid_data_model::revision::{ use flowy_grid_data_model::revision::{
gen_block_id, gen_row_id, CellRevision, GridBlockRevisionData, RowMetaChangeset, RowRevision, gen_block_id, gen_row_id, CellRevision, GridBlockRevision, RowMetaChangeset, RowRevision,
}; };
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder}; use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
use serde::{Deserialize, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
pub type GridBlockRevisionDelta = PlainTextDelta; pub type GridBlockRevisionDelta = PlainTextDelta;
pub type GridBlockRevisionDeltaBuilder = PlainTextDeltaBuilder; pub type GridBlockRevisionDeltaBuilder = PlainTextDeltaBuilder;
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Clone)]
pub struct GridBlockRevisionPad { pub struct GridBlockRevisionPad {
block_id: String, block_revision: GridBlockRevision,
rows: Vec<Arc<RowRevision>>,
#[serde(skip)]
pub(crate) delta: GridBlockRevisionDelta, pub(crate) delta: GridBlockRevisionDelta,
} }
impl std::ops::Deref for GridBlockRevisionPad {
type Target = GridBlockRevision;
fn deref(&self) -> &Self::Target {
&self.block_revision
}
}
impl GridBlockRevisionPad { impl GridBlockRevisionPad {
pub async fn duplicate_data(&self, duplicated_block_id: &str) -> GridBlockRevisionData { pub async fn duplicate_data(&self, duplicated_block_id: &str) -> GridBlockRevision {
let duplicated_rows = self let duplicated_rows = self
.block_revision
.rows .rows
.iter() .iter()
.map(|row| { .map(|row| {
let mut duplicated_row = row.as_ref().clone(); let mut duplicated_row = row.as_ref().clone();
duplicated_row.id = gen_row_id(); duplicated_row.id = gen_row_id();
duplicated_row.block_id = duplicated_block_id.to_string(); duplicated_row.block_id = duplicated_block_id.to_string();
duplicated_row Arc::new(duplicated_row)
}) })
.collect::<Vec<RowRevision>>(); .collect::<Vec<Arc<RowRevision>>>();
GridBlockRevisionData { GridBlockRevision {
block_id: duplicated_block_id.to_string(), block_id: duplicated_block_id.to_string(),
rows: duplicated_rows, rows: duplicated_rows,
} }
@ -43,18 +47,12 @@ impl GridBlockRevisionPad {
pub fn from_delta(delta: GridBlockRevisionDelta) -> CollaborateResult<Self> { pub fn from_delta(delta: GridBlockRevisionDelta) -> CollaborateResult<Self> {
let s = delta.to_str()?; let s = delta.to_str()?;
let meta_data: GridBlockRevisionData = serde_json::from_str(&s).map_err(|e| { let block_revision: GridBlockRevision = serde_json::from_str(&s).map_err(|e| {
let msg = format!("Deserialize delta to block meta failed: {}", e); let msg = format!("Deserialize delta to block meta failed: {}", e);
tracing::error!("{}", s); tracing::error!("{}", s);
CollaborateError::internal().context(msg) CollaborateError::internal().context(msg)
})?; })?;
let block_id = meta_data.block_id; Ok(Self { block_revision, delta })
let rows = meta_data
.rows
.into_iter()
.map(Arc::new)
.collect::<Vec<Arc<RowRevision>>>();
Ok(Self { block_id, rows, delta })
} }
pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> { pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
@ -95,9 +93,10 @@ impl GridBlockRevisionPad {
T: AsRef<str> + ToOwned + ?Sized, T: AsRef<str> + ToOwned + ?Sized,
{ {
match row_ids { match row_ids {
None => Ok(self.rows.to_vec()), None => Ok(self.block_revision.rows.clone()),
Some(row_ids) => { Some(row_ids) => {
let row_map = self let row_map = self
.block_revision
.rows .rows
.iter() .iter()
.map(|row| (row.id.as_str(), row.clone())) .map(|row| (row.id.as_str(), row.clone()))
@ -137,11 +136,12 @@ impl GridBlockRevisionPad {
} }
pub fn number_of_rows(&self) -> i32 { pub fn number_of_rows(&self) -> i32 {
self.rows.len() as i32 self.block_revision.rows.len() as i32
} }
pub fn index_of_row(&self, row_id: &str) -> Option<i32> { pub fn index_of_row(&self, row_id: &str) -> Option<i32> {
self.rows self.block_revision
.rows
.iter() .iter()
.position(|row| row.id == row_id) .position(|row| row.id == row_id)
.map(|index| index as i32) .map(|index| index as i32)
@ -190,7 +190,7 @@ impl GridBlockRevisionPad {
F: for<'a> FnOnce(&'a mut Vec<Arc<RowRevision>>) -> CollaborateResult<Option<()>>, F: for<'a> FnOnce(&'a mut Vec<Arc<RowRevision>>) -> CollaborateResult<Option<()>>,
{ {
let cloned_self = self.clone(); let cloned_self = self.clone();
match f(&mut self.rows)? { match f(&mut self.block_revision.rows)? {
None => Ok(None), None => Ok(None),
Some(_) => { Some(_) => {
let old = cloned_self.to_json()?; let old = cloned_self.to_json()?;
@ -226,7 +226,7 @@ impl GridBlockRevisionPad {
} }
pub fn to_json(&self) -> CollaborateResult<String> { pub fn to_json(&self) -> CollaborateResult<String> {
serde_json::to_string(self) serde_json::to_string(&self.block_revision)
.map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e))) .map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))
} }
@ -245,12 +245,12 @@ pub struct GridBlockMetaChange {
pub md5: String, pub md5: String,
} }
pub fn make_block_meta_delta(grid_block_meta_data: &GridBlockRevisionData) -> GridBlockRevisionDelta { pub fn make_block_meta_delta(block_rev: &GridBlockRevision) -> GridBlockRevisionDelta {
let json = serde_json::to_string(&grid_block_meta_data).unwrap(); let json = serde_json::to_string(&block_rev).unwrap();
PlainTextDeltaBuilder::new().insert(&json).build() PlainTextDeltaBuilder::new().insert(&json).build()
} }
pub fn make_block_meta_revisions(user_id: &str, grid_block_meta_data: &GridBlockRevisionData) -> RepeatedRevision { pub fn make_block_meta_revisions(user_id: &str, grid_block_meta_data: &GridBlockRevision) -> RepeatedRevision {
let delta = make_block_meta_delta(grid_block_meta_data); let delta = make_block_meta_delta(grid_block_meta_data);
let bytes = delta.to_delta_bytes(); let bytes = delta.to_delta_bytes();
let revision = Revision::initial_revision(user_id, &grid_block_meta_data.block_id, bytes); let revision = Revision::initial_revision(user_id, &grid_block_meta_data.block_id, bytes);
@ -259,17 +259,13 @@ pub fn make_block_meta_revisions(user_id: &str, grid_block_meta_data: &GridBlock
impl std::default::Default for GridBlockRevisionPad { impl std::default::Default for GridBlockRevisionPad {
fn default() -> Self { fn default() -> Self {
let block_meta_data = GridBlockRevisionData { let block_revision = GridBlockRevision {
block_id: gen_block_id(), block_id: gen_block_id(),
rows: vec![], rows: vec![],
}; };
let delta = make_block_meta_delta(&block_meta_data); let delta = make_block_meta_delta(&block_revision);
GridBlockRevisionPad { GridBlockRevisionPad { block_revision, delta }
block_id: block_meta_data.block_id,
rows: block_meta_data.rows.into_iter().map(Arc::new).collect::<Vec<_>>(),
delta,
}
} }
} }

View File

@ -1,7 +1,8 @@
use crate::errors::{CollaborateError, CollaborateResult}; use crate::errors::{CollaborateError, CollaborateResult};
use flowy_grid_data_model::revision::{ use flowy_grid_data_model::revision::{
BuildGridContext, FieldRevision, GridBlockRevision, GridBlockRevisionData, RowRevision, BuildGridContext, FieldRevision, GridBlockMetaRevision, GridBlockRevision, RowRevision,
}; };
use std::sync::Arc;
pub struct GridBuilder { pub struct GridBuilder {
build_context: BuildGridContext, build_context: BuildGridContext,
@ -11,8 +12,8 @@ impl std::default::Default for GridBuilder {
fn default() -> Self { fn default() -> Self {
let mut build_context = BuildGridContext::new(); let mut build_context = BuildGridContext::new();
let block_meta = GridBlockRevision::new(); let block_meta = GridBlockMetaRevision::new();
let block_meta_data = GridBlockRevisionData { let block_meta_data = GridBlockRevision {
block_id: block_meta.block_id.clone(), block_id: block_meta.block_id.clone(),
rows: vec![], rows: vec![],
}; };
@ -32,10 +33,10 @@ impl GridBuilder {
pub fn add_empty_row(mut self) -> Self { pub fn add_empty_row(mut self) -> Self {
let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id); let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id);
let block_meta = self.build_context.blocks.first_mut().unwrap(); let block_meta_rev = self.build_context.blocks.first_mut().unwrap();
let block_meta_data = self.build_context.blocks_meta_data.first_mut().unwrap(); let block_rev = self.build_context.blocks_meta_data.first_mut().unwrap();
block_meta_data.rows.push(row); block_rev.rows.push(Arc::new(row));
block_meta.row_count += 1; block_meta_rev.row_count += 1;
self self
} }
@ -59,10 +60,10 @@ fn check_rows(fields: &[FieldRevision], rows: &[RowRevision]) -> CollaborateResu
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::client_grid::{make_block_meta_delta, make_grid_delta, GridBuilder}; use crate::client_grid::{make_block_meta_delta, make_grid_delta, GridBuilder};
use flowy_grid_data_model::entities::FieldType; use flowy_grid_data_model::entities::FieldType;
use flowy_grid_data_model::revision::{FieldRevision, GridBlockRevisionData, GridRevision}; use flowy_grid_data_model::revision::{FieldRevision, GridBlockRevision, GridRevision};
use std::sync::Arc;
#[test] #[test]
fn create_default_grid_test() { fn create_default_grid_test() {
@ -78,7 +79,7 @@ mod tests {
let grid_rev = GridRevision { let grid_rev = GridRevision {
grid_id, grid_id,
fields: build_context.field_revs, fields: build_context.field_revs,
blocks: build_context.blocks, blocks: build_context.blocks.into_iter().map(Arc::new).collect(),
setting: Default::default(), setting: Default::default(),
}; };
@ -86,6 +87,6 @@ mod tests {
let _: GridRevision = serde_json::from_str(&grid_meta_delta.to_str().unwrap()).unwrap(); let _: GridRevision = serde_json::from_str(&grid_meta_delta.to_str().unwrap()).unwrap();
let grid_block_meta_delta = make_block_meta_delta(build_context.blocks_meta_data.first().unwrap()); let grid_block_meta_delta = make_block_meta_delta(build_context.blocks_meta_data.first().unwrap());
let _: GridBlockRevisionData = serde_json::from_str(&grid_block_meta_delta.to_str().unwrap()).unwrap(); let _: GridBlockRevision = serde_json::from_str(&grid_block_meta_delta.to_str().unwrap()).unwrap();
} }
} }

View File

@ -5,8 +5,9 @@ use bytes::Bytes;
use flowy_grid_data_model::entities::{FieldChangesetParams, FieldOrder}; use flowy_grid_data_model::entities::{FieldChangesetParams, FieldOrder};
use flowy_grid_data_model::entities::{FieldType, GridSettingChangesetParams}; use flowy_grid_data_model::entities::{FieldType, GridSettingChangesetParams};
use flowy_grid_data_model::revision::{ use flowy_grid_data_model::revision::{
gen_block_id, gen_grid_id, FieldRevision, GridBlockRevision, GridBlockRevisionChangeset, GridFilterRevision, gen_block_id, gen_grid_filter_id, gen_grid_group_id, gen_grid_id, gen_grid_sort_id, FieldRevision,
GridGroupRevision, GridLayoutRevision, GridRevision, GridSettingRevision, GridSortRevision, GridBlockMetaRevision, GridBlockMetaRevisionChangeset, GridFilterRevision, GridGroupRevision, GridLayoutRevision,
GridRevision, GridSettingRevision, GridSortRevision,
}; };
use lib_infra::util::move_vec_element; use lib_infra::util::move_vec_element;
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder}; use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
@ -26,7 +27,7 @@ pub trait JsonDeserializer {
} }
impl GridRevisionPad { impl GridRevisionPad {
pub async fn duplicate_grid_meta(&self) -> (Vec<FieldRevision>, Vec<GridBlockRevision>) { pub async fn duplicate_grid_block_meta(&self) -> (Vec<FieldRevision>, Vec<GridBlockMetaRevision>) {
let fields = self.grid_rev.fields.to_vec(); let fields = self.grid_rev.fields.to_vec();
let blocks = self let blocks = self
@ -34,11 +35,11 @@ impl GridRevisionPad {
.blocks .blocks
.iter() .iter()
.map(|block| { .map(|block| {
let mut duplicated_block = block.clone(); let mut duplicated_block = (&*block.clone()).clone();
duplicated_block.block_id = gen_block_id(); duplicated_block.block_id = gen_block_id();
duplicated_block duplicated_block
}) })
.collect::<Vec<GridBlockRevision>>(); .collect::<Vec<GridBlockMetaRevision>>();
(fields, blocks) (fields, blocks)
} }
@ -280,14 +281,14 @@ impl GridRevisionPad {
} }
} }
pub fn create_block_rev(&mut self, block: GridBlockRevision) -> CollaborateResult<Option<GridChangeset>> { pub fn create_block_meta_rev(&mut self, block: GridBlockMetaRevision) -> CollaborateResult<Option<GridChangeset>> {
self.modify_grid(|grid_meta| { self.modify_grid(|grid_meta| {
if grid_meta.blocks.iter().any(|b| b.block_id == block.block_id) { if grid_meta.blocks.iter().any(|b| b.block_id == block.block_id) {
tracing::warn!("Duplicate grid block"); tracing::warn!("Duplicate grid block");
Ok(None) Ok(None)
} else { } else {
match grid_meta.blocks.last() { match grid_meta.blocks.last() {
None => grid_meta.blocks.push(block), None => grid_meta.blocks.push(Arc::new(block)),
Some(last_block) => { Some(last_block) => {
if last_block.start_row_index > block.start_row_index if last_block.start_row_index > block.start_row_index
&& last_block.len() > block.start_row_index && last_block.len() > block.start_row_index
@ -295,7 +296,7 @@ impl GridRevisionPad {
let msg = "GridBlock's start_row_index should be greater than the last_block's start_row_index and its len".to_string(); let msg = "GridBlock's start_row_index should be greater than the last_block's start_row_index and its len".to_string();
return Err(CollaborateError::internal().context(msg)) return Err(CollaborateError::internal().context(msg))
} }
grid_meta.blocks.push(block); grid_meta.blocks.push(Arc::new(block));
} }
} }
Ok(Some(())) Ok(Some(()))
@ -303,13 +304,13 @@ impl GridRevisionPad {
}) })
} }
pub fn get_block_revs(&self) -> Vec<GridBlockRevision> { pub fn get_block_meta_revs(&self) -> Vec<Arc<GridBlockMetaRevision>> {
self.grid_rev.blocks.clone() self.grid_rev.blocks.clone()
} }
pub fn update_block_rev( pub fn update_block_rev(
&mut self, &mut self,
changeset: GridBlockRevisionChangeset, changeset: GridBlockMetaRevisionChangeset,
) -> CollaborateResult<Option<GridChangeset>> { ) -> CollaborateResult<Option<GridChangeset>> {
let block_id = changeset.block_id.clone(); let block_id = changeset.block_id.clone();
self.modify_block(&block_id, |block| { self.modify_block(&block_id, |block| {
@ -329,8 +330,13 @@ impl GridRevisionPad {
}) })
} }
pub fn get_grid_setting_rev(&self) -> GridSettingRevision { pub fn get_grid_setting_rev(&self) -> &GridSettingRevision {
self.grid_rev.setting.clone() &self.grid_rev.setting
}
pub fn get_filters(&self, layout: Option<&GridLayoutRevision>) -> Option<&Vec<GridFilterRevision>> {
let layout_ty = layout.unwrap_or(&self.grid_rev.setting.layout);
self.grid_rev.setting.filters.get(layout_ty)
} }
pub fn update_grid_setting_rev( pub fn update_grid_setting_rev(
@ -341,37 +347,78 @@ impl GridRevisionPad {
let mut is_changed = None; let mut is_changed = None;
let layout_rev: GridLayoutRevision = changeset.layout_type.into(); let layout_rev: GridLayoutRevision = changeset.layout_type.into();
if let Some(filter) = changeset.filter { if let Some(params) = changeset.insert_filter {
grid_rev.setting.filter.insert( let rev = GridFilterRevision {
layout_rev.clone(), id: gen_grid_filter_id(),
GridFilterRevision { field_id: params.field_id,
field_id: filter.field_id, condition: params.condition,
}, content: params.content,
); };
grid_rev
.setting
.filters
.entry(layout_rev.clone())
.or_insert_with(std::vec::Vec::new)
.push(rev);
is_changed = Some(())
}
if let Some(delete_filter_id) = changeset.delete_filter {
match grid_rev.setting.filters.get_mut(&layout_rev) {
Some(filters) => filters.retain(|filter| filter.id != delete_filter_id),
None => {
tracing::warn!("Can't find the filter with {:?}", layout_rev);
}
}
}
if let Some(params) = changeset.insert_group {
let rev = GridGroupRevision {
id: gen_grid_group_id(),
field_id: params.field_id,
sub_field_id: params.sub_field_id,
};
grid_rev
.setting
.groups
.entry(layout_rev.clone())
.or_insert_with(std::vec::Vec::new)
.push(rev);
is_changed = Some(())
}
if let Some(delete_group_id) = changeset.delete_group {
match grid_rev.setting.groups.get_mut(&layout_rev) {
Some(groups) => groups.retain(|group| group.id != delete_group_id),
None => {
tracing::warn!("Can't find the group with {:?}", layout_rev);
}
}
}
if let Some(sort) = changeset.insert_sort {
let rev = GridSortRevision {
id: gen_grid_sort_id(),
field_id: sort.field_id,
};
grid_rev
.setting
.sorts
.entry(layout_rev.clone())
.or_insert_with(std::vec::Vec::new)
.push(rev);
is_changed = Some(()) is_changed = Some(())
} }
if let Some(group) = changeset.group { if let Some(delete_sort_id) = changeset.delete_sort {
grid_rev.setting.group.insert( match grid_rev.setting.sorts.get_mut(&layout_rev) {
layout_rev.clone(), Some(sorts) => sorts.retain(|sort| sort.id != delete_sort_id),
GridGroupRevision { None => {
group_field_id: group.group_field_id, tracing::warn!("Can't find the sort with {:?}", layout_rev);
sub_group_field_id: group.sub_group_field_id, }
}, }
);
is_changed = Some(())
} }
if let Some(sort) = changeset.sort {
grid_rev.setting.sort.insert(
layout_rev,
GridSortRevision {
field_id: sort.field_id,
},
);
is_changed = Some(())
}
Ok(is_changed) Ok(is_changed)
}) })
} }
@ -415,7 +462,7 @@ impl GridRevisionPad {
fn modify_block<F>(&mut self, block_id: &str, f: F) -> CollaborateResult<Option<GridChangeset>> fn modify_block<F>(&mut self, block_id: &str, f: F) -> CollaborateResult<Option<GridChangeset>>
where where
F: FnOnce(&mut GridBlockRevision) -> CollaborateResult<Option<()>>, F: FnOnce(&mut GridBlockMetaRevision) -> CollaborateResult<Option<()>>,
{ {
self.modify_grid( self.modify_grid(
|grid_rev| match grid_rev.blocks.iter().position(|block| block.block_id == block_id) { |grid_rev| match grid_rev.blocks.iter().position(|block| block.block_id == block_id) {
@ -423,7 +470,10 @@ impl GridRevisionPad {
tracing::warn!("[GridMetaPad]: Can't find any block with id: {}", block_id); tracing::warn!("[GridMetaPad]: Can't find any block with id: {}", block_id);
Ok(None) Ok(None)
} }
Some(index) => f(&mut grid_rev.blocks[index]), Some(index) => {
let block_rev = Arc::make_mut(&mut grid_rev.blocks[index]);
f(block_rev)
}
}, },
) )
} }

View File

@ -20,10 +20,12 @@ pub struct AppearanceSettings {
pub locale: LocaleSettings, pub locale: LocaleSettings,
#[pb(index = 3)] #[pb(index = 3)]
#[serde(default = "reset_default_value")] #[serde(default = "DEFAULT_RESET_VALUE")]
pub reset_as_default: bool, pub reset_as_default: bool,
} }
const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT;
#[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)] #[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)]
pub struct LocaleSettings { pub struct LocaleSettings {
#[pb(index = 1)] #[pb(index = 1)]
@ -42,12 +44,8 @@ impl std::default::Default for LocaleSettings {
} }
} }
fn reset_default_value() -> bool {
APPEARANCE_RESET_AS_DEFAULT
}
pub const APPEARANCE_DEFAULT_THEME: &str = "light"; pub const APPEARANCE_DEFAULT_THEME: &str = "light";
pub const APPEARANCE_RESET_AS_DEFAULT: bool = true; const APPEARANCE_RESET_AS_DEFAULT: bool = true;
impl std::default::Default for AppearanceSettings { impl std::default::Default for AppearanceSettings {
fn default() -> Self { fn default() -> Self {