mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #579 from AppFlowy-IO/feat/grid_task_runner
This commit is contained in:
commit
6554b8354b
@ -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;
|
||||
}
|
||||
}
|
@ -2,21 +2,21 @@ part of 'cell_service.dart';
|
||||
|
||||
typedef GridCellMap = LinkedHashMap<String, GridCell>;
|
||||
|
||||
class GridCellCacheData {
|
||||
GridCellCacheKey key;
|
||||
class _GridCellCacheObject {
|
||||
_GridCellCacheKey key;
|
||||
dynamic object;
|
||||
GridCellCacheData({
|
||||
_GridCellCacheObject({
|
||||
required this.key,
|
||||
required this.object,
|
||||
});
|
||||
}
|
||||
|
||||
class GridCellCacheKey {
|
||||
class _GridCellCacheKey {
|
||||
final String fieldId;
|
||||
final String objectId;
|
||||
GridCellCacheKey({
|
||||
final String rowId;
|
||||
_GridCellCacheKey({
|
||||
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];
|
||||
if (map == null) {
|
||||
_fieldListenerByFieldId[cacheKey.fieldId] = {};
|
||||
map = _fieldListenerByFieldId[cacheKey.fieldId];
|
||||
map![cacheKey.objectId] = [onFieldChanged];
|
||||
map![cacheKey.rowId] = [onFieldChanged];
|
||||
} else {
|
||||
var objects = map[cacheKey.objectId];
|
||||
var objects = map[cacheKey.rowId];
|
||||
if (objects == null) {
|
||||
map[cacheKey.objectId] = [onFieldChanged];
|
||||
map[cacheKey.rowId] = [onFieldChanged];
|
||||
} else {
|
||||
objects.add(onFieldChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeFieldListener(GridCellCacheKey cacheKey, VoidCallback fn) {
|
||||
var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
|
||||
void removeFieldListener(_GridCellCacheKey cacheKey, VoidCallback fn) {
|
||||
var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId];
|
||||
final index = callbacks?.indexWhere((callback) => callback == fn);
|
||||
if (index != null && index != -1) {
|
||||
callbacks?.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
void insert<T extends GridCellCacheData>(T item) {
|
||||
void insert<T extends _GridCellCacheObject>(T item) {
|
||||
var map = _cellDataByFieldId[item.key.fieldId];
|
||||
if (map == null) {
|
||||
_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];
|
||||
if (map == null) {
|
||||
return null;
|
||||
} else {
|
||||
final object = map[key.objectId];
|
||||
final object = map[key.rowId];
|
||||
if (object is T) {
|
||||
return object;
|
||||
} else {
|
@ -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 'dart:convert' show utf8;
|
||||
part 'cell_service.freezed.dart';
|
||||
part 'data_loader.dart';
|
||||
part 'cell_data_loader.dart';
|
||||
part 'context_builder.dart';
|
||||
part 'data_cache.dart';
|
||||
part 'data_persistence.dart';
|
||||
part 'cell_data_cache.dart';
|
||||
part 'cell_data_persistence.dart';
|
||||
|
||||
// key: rowId
|
||||
|
||||
@ -62,7 +62,6 @@ class GridCell with _$GridCell {
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required Field field,
|
||||
Cell? cell,
|
||||
}) = _GridCell;
|
||||
|
||||
// ignore: unused_element
|
||||
|
@ -100,7 +100,7 @@ class GridCellContextBuilder {
|
||||
class _GridCellContext<T, D> extends Equatable {
|
||||
final GridCell gridCell;
|
||||
final GridCellCache cellCache;
|
||||
final GridCellCacheKey _cacheKey;
|
||||
final _GridCellCacheKey _cacheKey;
|
||||
final IGridCellDataLoader<T> cellDataLoader;
|
||||
final _GridCellDataPersistence<D> cellDataPersistence;
|
||||
final FieldService _fieldService;
|
||||
@ -118,7 +118,7 @@ class _GridCellContext<T, D> extends Equatable {
|
||||
required this.cellDataLoader,
|
||||
required this.cellDataPersistence,
|
||||
}) : _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() {
|
||||
return _GridCellContext(
|
||||
@ -213,7 +213,7 @@ class _GridCellContext<T, D> extends Equatable {
|
||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
cellDataLoader.loadData().then((data) {
|
||||
_cellDataNotifier?.value = data;
|
||||
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
||||
cellCache.insert(_GridCellCacheObject(key: _cacheKey, object: data));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.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-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'block/block_listener.dart';
|
||||
import 'cell/cell_service/cell_service.dart';
|
||||
import 'grid_service.dart';
|
||||
import 'row/row_service.dart';
|
||||
@ -19,9 +21,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
late final GridRowCache rowCache;
|
||||
late final GridCellCache cellCache;
|
||||
|
||||
final GridBlockCache blockCache;
|
||||
|
||||
GridBloc({required View view})
|
||||
: _gridService = GridService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id),
|
||||
blockCache = GridBlockCache(gridId: view.id),
|
||||
super(GridState.initial(view.id)) {
|
||||
rowCache = GridRowCache(
|
||||
gridId: view.id,
|
||||
@ -33,6 +38,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
fieldDelegate: GridCellCacheDelegateImpl(fieldCache),
|
||||
);
|
||||
|
||||
blockCache.start((result) {
|
||||
result.fold(
|
||||
(changesets) => rowCache.applyChangesets(changesets),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
|
||||
on<GridEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -60,6 +72,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
await cellCache.dispose();
|
||||
await rowCache.dispose();
|
||||
await fieldCache.dispose();
|
||||
await blockCache.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@ -79,7 +92,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
final result = await _gridService.loadGrid();
|
||||
return Future(
|
||||
() => 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)))),
|
||||
),
|
||||
);
|
||||
@ -91,7 +112,6 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
() => result.fold(
|
||||
(fields) {
|
||||
fieldCache.fields = fields.items;
|
||||
rowCache.resetRows(grid.blockOrders);
|
||||
|
||||
emit(state.copyWith(
|
||||
grid: Some(grid),
|
||||
@ -143,7 +163,6 @@ class GridLoadingState with _$GridLoadingState {
|
||||
|
||||
class GridFieldEquatable extends Equatable {
|
||||
final List<Field> _fields;
|
||||
|
||||
const GridFieldEquatable(List<Field> fields) : _fields = fields;
|
||||
|
||||
@override
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ class GridService {
|
||||
await FolderEventSetLatestView(ViewId(value: gridId)).send();
|
||||
|
||||
final payload = GridId(value: gridId);
|
||||
return GridEventGetGridData(payload).send();
|
||||
return GridEventGetGrid(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Row, FlowyError>> createRow({Option<String>? startRowId}) {
|
||||
|
@ -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:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
|
||||
part 'row_service.freezed.dart';
|
||||
|
||||
typedef RowUpdateCallback = void Function();
|
||||
@ -24,7 +23,6 @@ abstract class GridRowFieldDelegate {
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final RowsNotifier _rowsNotifier;
|
||||
final GridRowListener _rowsListener;
|
||||
final GridRowFieldDelegate _fieldDelegate;
|
||||
List<GridRow> get clonedRows => _rowsNotifier.clonedRows;
|
||||
|
||||
@ -39,30 +37,21 @@ class GridRowCache {
|
||||
);
|
||||
},
|
||||
),
|
||||
_rowsListener = GridRowListener(gridId: gridId),
|
||||
_fieldDelegate = fieldDelegate {
|
||||
//
|
||||
fieldDelegate.onFieldChanged(() => _rowsNotifier.fieldDidChange());
|
||||
}
|
||||
|
||||
// listen on the row update
|
||||
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
|
||||
result.fold(
|
||||
(changesets) {
|
||||
Future<void> dispose() async {
|
||||
_rowsNotifier.dispose();
|
||||
}
|
||||
|
||||
void applyChangesets(List<GridRowsChangeset> 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 {
|
||||
await _rowsListener.stop();
|
||||
_rowsNotifier.dispose();
|
||||
}
|
||||
|
||||
void addListener({
|
||||
@ -130,9 +119,8 @@ class GridRowCache {
|
||||
return _makeGridCells(rowId, data);
|
||||
}
|
||||
|
||||
void resetRows(List<GridBlockOrder> blocks) {
|
||||
final rowOrders = blocks.expand((block) => block.rowOrders).toList();
|
||||
_rowsNotifier.reset(rowOrders);
|
||||
void initialRows(List<RowOrder> rowOrders) {
|
||||
_rowsNotifier.initialRows(rowOrders);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(String rowId) async {
|
||||
@ -142,7 +130,11 @@ class GridRowCache {
|
||||
|
||||
final result = await GridEventGetRow(payload).send();
|
||||
result.fold(
|
||||
(rowData) => _rowsNotifier.rowData = rowData,
|
||||
(rowData) {
|
||||
if (rowData.hasRow()) {
|
||||
_rowsNotifier.rowData = rowData.row;
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
@ -154,7 +146,6 @@ class GridRowCache {
|
||||
cellDataMap[field.id] = GridCell(
|
||||
rowId: rowId,
|
||||
gridId: gridId,
|
||||
cell: row?.cellByFieldId[field.id],
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
@ -173,7 +164,9 @@ class RowsNotifier extends ChangeNotifier {
|
||||
required this.rowBuilder,
|
||||
});
|
||||
|
||||
void reset(List<RowOrder> rowOrders) {
|
||||
List<GridRow> get clonedRows => [..._rows];
|
||||
|
||||
void initialRows(List<RowOrder> rowOrders) {
|
||||
_rowDataMap = HashMap();
|
||||
final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList();
|
||||
_update(rows, const GridRowChangeReason.initial());
|
||||
@ -199,20 +192,20 @@ class RowsNotifier extends ChangeNotifier {
|
||||
_update(newRows, GridRowChangeReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void insertRows(List<IndexRowOrder> createdRows) {
|
||||
if (createdRows.isEmpty) {
|
||||
void insertRows(List<IndexRowOrder> insertRows) {
|
||||
if (insertRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
final List<GridRow> newRows = clonedRows;
|
||||
for (final createdRow in createdRows) {
|
||||
for (final insertRow in insertRows) {
|
||||
final insertIndex = InsertedIndex(
|
||||
index: createdRow.index,
|
||||
rowId: createdRow.rowOrder.rowId,
|
||||
index: insertRow.index,
|
||||
rowId: insertRow.rowOrder.rowId,
|
||||
);
|
||||
insertIndexs.add(insertIndex);
|
||||
newRows.insert(createdRow.index, (rowBuilder(createdRow.rowOrder)));
|
||||
newRows.insert(insertRow.index, (rowBuilder(insertRow.rowOrder)));
|
||||
}
|
||||
_update(newRows, GridRowChangeReason.insert(insertIndexs));
|
||||
}
|
||||
@ -281,8 +274,6 @@ class RowsNotifier extends ChangeNotifier {
|
||||
Row? rowDataWithId(String rowId) {
|
||||
return _rowDataMap[rowId];
|
||||
}
|
||||
|
||||
List<GridRow> get clonedRows => [..._rows];
|
||||
}
|
||||
|
||||
class RowService {
|
||||
@ -310,7 +301,7 @@ class RowService {
|
||||
return GridEventMoveItem(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Row, FlowyError>> getRow() {
|
||||
Future<Either<OptionalRow, FlowyError>> getRow() {
|
||||
final payload = RowIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..rowId = rowId;
|
||||
|
7
frontend/rust-lib/Cargo.lock
generated
7
frontend/rust-lib/Cargo.lock
generated
@ -93,6 +93,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic_refcell"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@ -925,6 +931,7 @@ dependencies = [
|
||||
name = "flowy-grid"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"atomic_refcell",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"dart-notify",
|
||||
|
@ -39,6 +39,7 @@ fancy-regex = "0.10.0"
|
||||
regex = "1.5.6"
|
||||
url = { version = "2"}
|
||||
futures = "0.3.15"
|
||||
atomic_refcell = "0.1.8"
|
||||
|
||||
[dev-dependencies]
|
||||
flowy-test = { path = "../flowy-test" }
|
||||
|
@ -6,7 +6,7 @@ const OBSERVABLE_CATEGORY: &str = "Grid";
|
||||
pub enum GridNotification {
|
||||
Unknown = 0,
|
||||
DidCreateBlock = 11,
|
||||
DidUpdateGridRow = 20,
|
||||
DidUpdateGridBlock = 20,
|
||||
DidUpdateGridField = 21,
|
||||
DidUpdateRow = 30,
|
||||
DidUpdateCell = 40,
|
||||
|
@ -9,7 +9,7 @@ use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[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>,
|
||||
manager: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<Grid, FlowyError> {
|
||||
@ -27,7 +27,7 @@ pub(crate) async fn get_grid_setting_handler(
|
||||
let grid_id: GridId = data.into_inner();
|
||||
let editor = manager.open_grid(grid_id).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)]
|
||||
@ -48,12 +48,7 @@ pub(crate) async fn get_grid_blocks_handler(
|
||||
) -> DataResult<RepeatedGridBlock, FlowyError> {
|
||||
let params: QueryGridBlocksParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_grid_editor(¶ms.grid_id)?;
|
||||
let block_ids = params
|
||||
.block_orders
|
||||
.into_iter()
|
||||
.map(|block| block.block_id)
|
||||
.collect::<Vec<String>>();
|
||||
let repeated_grid_block = editor.get_blocks(Some(block_ids)).await?;
|
||||
let repeated_grid_block = editor.get_blocks(Some(params.block_ids)).await?;
|
||||
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(
|
||||
data: Data<RowIdentifierPayload>,
|
||||
manager: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<Row, FlowyError> {
|
||||
) -> DataResult<OptionalRow, FlowyError> {
|
||||
let params: RowIdentifier = data.into_inner().try_into()?;
|
||||
let editor = manager.get_grid_editor(¶ms.grid_id)?;
|
||||
match editor.get_row(¶ms.row_id).await? {
|
||||
None => Err(FlowyError::record_not_found().context("Can not find the row")),
|
||||
Some(row) => data_result(row),
|
||||
}
|
||||
let row = OptionalRow {
|
||||
row: editor.get_row(¶ms.row_id).await?,
|
||||
};
|
||||
data_result(row)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
|
@ -8,10 +8,10 @@ use strum_macros::Display;
|
||||
pub fn create(grid_manager: Arc<GridManager>) -> Module {
|
||||
let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(grid_manager);
|
||||
module = module
|
||||
.event(GridEvent::GetGridData, get_grid_data_handler)
|
||||
.event(GridEvent::GetGrid, get_grid_handler)
|
||||
.event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
|
||||
.event(GridEvent::GetGridSetting, get_grid_setting_handler)
|
||||
.event(GridEvent::UpdateGridSetting, get_grid_setting_handler)
|
||||
.event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
|
||||
// Field
|
||||
.event(GridEvent::GetFields, get_fields_handler)
|
||||
.event(GridEvent::UpdateField, update_field_handler)
|
||||
@ -46,7 +46,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
|
||||
#[event_err = "FlowyError"]
|
||||
pub enum GridEvent {
|
||||
#[event(input = "GridId", output = "Grid")]
|
||||
GetGridData = 0,
|
||||
GetGrid = 0,
|
||||
|
||||
#[event(input = "QueryGridBlocksPayload", output = "RepeatedGridBlock")]
|
||||
GetGridBlocks = 1,
|
||||
@ -99,7 +99,7 @@ pub enum GridEvent {
|
||||
#[event(input = "CreateRowPayload", output = "Row")]
|
||||
CreateRow = 50,
|
||||
|
||||
#[event(input = "RowIdentifierPayload", output = "Row")]
|
||||
#[event(input = "RowIdentifierPayload", output = "OptionalRow")]
|
||||
GetRow = 51,
|
||||
|
||||
#[event(input = "RowIdentifierPayload")]
|
||||
|
@ -2,16 +2,18 @@ use crate::services::grid_editor::GridRevisionEditor;
|
||||
use crate::services::persistence::block_index::BlockIndexCache;
|
||||
use crate::services::persistence::kv::GridKVPersistence;
|
||||
use crate::services::persistence::GridDatabase;
|
||||
use crate::services::tasks::GridTaskScheduler;
|
||||
use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use flowy_database::ConnectionPool;
|
||||
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::{RevisionManager, RevisionPersistence, RevisionWebSocket};
|
||||
use flowy_sync::client_grid::{make_block_meta_delta, make_grid_delta};
|
||||
use flowy_sync::entities::revision::{RepeatedRevision, Revision};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub trait GridUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<String, FlowyError>;
|
||||
@ -19,12 +21,15 @@ pub trait GridUser: Send + Sync {
|
||||
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
|
||||
}
|
||||
|
||||
pub type GridTaskSchedulerRwLock = Arc<RwLock<GridTaskScheduler>>;
|
||||
|
||||
pub struct GridManager {
|
||||
editor_map: Arc<DashMap<String, Arc<GridRevisionEditor>>>,
|
||||
grid_editors: Arc<DashMap<String, Arc<GridRevisionEditor>>>,
|
||||
grid_user: Arc<dyn GridUser>,
|
||||
block_index_cache: Arc<BlockIndexCache>,
|
||||
#[allow(dead_code)]
|
||||
kv_persistence: Arc<GridKVPersistence>,
|
||||
task_scheduler: GridTaskSchedulerRwLock,
|
||||
}
|
||||
|
||||
impl GridManager {
|
||||
@ -35,12 +40,14 @@ impl GridManager {
|
||||
) -> Self {
|
||||
let grid_editors = Arc::new(DashMap::new());
|
||||
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 {
|
||||
editor_map: grid_editors,
|
||||
grid_editors,
|
||||
grid_user,
|
||||
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)]
|
||||
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();
|
||||
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(())
|
||||
}
|
||||
|
||||
#[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();
|
||||
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(())
|
||||
}
|
||||
|
||||
@ -93,23 +102,24 @@ impl GridManager {
|
||||
|
||||
// #[tracing::instrument(level = "debug", skip(self), err)]
|
||||
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")),
|
||||
Some(editor) => Ok(editor.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
tracing::trace!("Create grid editor with id: {}", grid_id);
|
||||
let db_pool = self.grid_user.db_pool()?;
|
||||
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);
|
||||
}
|
||||
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)
|
||||
}
|
||||
Some(editor) => Ok(editor.clone()),
|
||||
@ -124,7 +134,14 @@ impl GridManager {
|
||||
) -> Result<Arc<GridRevisionEditor>, FlowyError> {
|
||||
let user = self.grid_user.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)
|
||||
}
|
||||
|
||||
@ -156,12 +173,24 @@ pub async fn make_grid_view_data(
|
||||
grid_manager: Arc<GridManager>,
|
||||
build_context: BuildGridContext,
|
||||
) -> FlowyResult<Bytes> {
|
||||
let grid_rev = GridRevision {
|
||||
grid_id: view_id.to_string(),
|
||||
fields: build_context.field_revs,
|
||||
blocks: build_context.blocks,
|
||||
setting: GridSettingRevision::default(),
|
||||
};
|
||||
for block_meta_data in &build_context.blocks_meta_data {
|
||||
let block_id = &block_meta_data.block_id;
|
||||
// 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?;
|
||||
}
|
||||
|
||||
let grid_rev = GridRevision::from_build_context(view_id, build_context);
|
||||
|
||||
// Create grid
|
||||
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();
|
||||
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)
|
||||
}
|
||||
|
@ -2,14 +2,14 @@ use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::manager::GridUser;
|
||||
use crate::services::block_revision_editor::GridBlockRevisionEditor;
|
||||
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 flowy_error::FlowyResult;
|
||||
use flowy_grid_data_model::entities::{
|
||||
CellChangeset, GridRowsChangeset, IndexRowOrder, Row, RowOrder, UpdatedRowOrder,
|
||||
};
|
||||
use flowy_grid_data_model::revision::{
|
||||
CellRevision, GridBlockRevision, GridBlockRevisionChangeset, RowMetaChangeset, RowRevision,
|
||||
CellRevision, GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision,
|
||||
};
|
||||
use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
|
||||
use flowy_revision::{RevisionManager, RevisionPersistence};
|
||||
@ -19,6 +19,7 @@ use std::sync::Arc;
|
||||
|
||||
type BlockId = String;
|
||||
pub(crate) struct GridBlockManager {
|
||||
#[allow(dead_code)]
|
||||
grid_id: String,
|
||||
user: Arc<dyn GridUser>,
|
||||
persistence: Arc<BlockIndexCache>,
|
||||
@ -29,10 +30,10 @@ impl GridBlockManager {
|
||||
pub(crate) async fn new(
|
||||
grid_id: &str,
|
||||
user: &Arc<dyn GridUser>,
|
||||
blocks: Vec<GridBlockRevision>,
|
||||
block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
|
||||
persistence: Arc<BlockIndexCache>,
|
||||
) -> 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 grid_id = grid_id.to_owned();
|
||||
let manager = Self {
|
||||
@ -77,7 +78,7 @@ impl GridBlockManager {
|
||||
index_row_order.index = row_index;
|
||||
|
||||
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?;
|
||||
Ok(row_count)
|
||||
}
|
||||
@ -85,7 +86,7 @@ impl GridBlockManager {
|
||||
pub(crate) async fn insert_row(
|
||||
&self,
|
||||
rows_by_block_id: HashMap<String, Vec<RowRevision>>,
|
||||
) -> FlowyResult<Vec<GridBlockRevisionChangeset>> {
|
||||
) -> FlowyResult<Vec<GridBlockMetaRevisionChangeset>> {
|
||||
let mut changesets = vec![];
|
||||
for (block_id, row_revs) in rows_by_block_id {
|
||||
let mut inserted_row_orders = vec![];
|
||||
@ -99,10 +100,10 @@ impl GridBlockManager {
|
||||
row_order.index = index;
|
||||
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
|
||||
.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?;
|
||||
}
|
||||
|
||||
@ -121,7 +122,9 @@ impl GridBlockManager {
|
||||
if let Some(row) = row_builder(row_rev.clone()) {
|
||||
let row_order = UpdatedRowOrder::new(&row_rev, row);
|
||||
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) => {
|
||||
let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?;
|
||||
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?;
|
||||
}
|
||||
}
|
||||
@ -145,17 +148,20 @@ impl GridBlockManager {
|
||||
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![];
|
||||
for block_order in group_row_orders(row_orders) {
|
||||
let editor = self.get_editor(&block_order.block_id).await?;
|
||||
for block_order in block_from_row_orders(row_orders) {
|
||||
let editor = self.get_editor(&block_order.id).await?;
|
||||
let row_ids = block_order
|
||||
.row_orders
|
||||
.into_iter()
|
||||
.map(|row_order| Cow::Owned(row_order.row_id))
|
||||
.collect::<Vec<Cow<String>>>();
|
||||
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);
|
||||
}
|
||||
|
||||
@ -181,7 +187,9 @@ impl GridBlockManager {
|
||||
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)
|
||||
}
|
||||
|
||||
async fn notify_did_update_block(&self, changeset: GridRowsChangeset) -> FlowyResult<()> {
|
||||
send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridRow)
|
||||
async fn notify_did_update_block(&self, block_id: &str, changeset: GridRowsChangeset) -> FlowyResult<()> {
|
||||
send_dart_notification(block_id, GridNotification::DidUpdateGridBlock)
|
||||
.payload(changeset)
|
||||
.send();
|
||||
Ok(())
|
||||
@ -257,12 +265,12 @@ impl GridBlockManager {
|
||||
|
||||
async fn make_block_meta_editor_map(
|
||||
user: &Arc<dyn GridUser>,
|
||||
blocks: Vec<GridBlockRevision>,
|
||||
block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
|
||||
) -> FlowyResult<DashMap<String, Arc<GridBlockRevisionEditor>>> {
|
||||
let editor_map = DashMap::new();
|
||||
for block in blocks {
|
||||
let editor = make_block_meta_editor(user, &block.block_id).await?;
|
||||
editor_map.insert(block.block_id, Arc::new(editor));
|
||||
for block_meta_rev in block_meta_revs {
|
||||
let editor = make_block_meta_editor(user, &block_meta_rev.block_id).await?;
|
||||
editor_map.insert(block_meta_rev.block_id.clone(), Arc::new(editor));
|
||||
}
|
||||
|
||||
Ok(editor_map)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
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_sync::client_grid::{GridBlockMetaChange, GridBlockRevisionPad};
|
||||
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
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&self,
|
||||
row: RowRevision,
|
||||
start_row_id: Option<String>,
|
||||
prev_row_id: Option<String>,
|
||||
) -> FlowyResult<(i32, Option<i32>)> {
|
||||
let mut row_count = 0;
|
||||
let mut row_index = None;
|
||||
let _ = self
|
||||
.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) {
|
||||
None => {}
|
||||
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();
|
||||
|
||||
if row_index.is_none() {
|
||||
|
@ -93,7 +93,7 @@ mod tests {
|
||||
use crate::services::field::type_options::checkbox_type_option::{NO, YES};
|
||||
|
||||
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;
|
||||
|
||||
@ -101,39 +101,21 @@ mod tests {
|
||||
fn checkout_box_description_test() {
|
||||
let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
|
||||
let data = apply_cell_data_changeset("true", None, &field_rev).unwrap();
|
||||
assert_eq!(
|
||||
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
|
||||
YES
|
||||
);
|
||||
assert_eq!(decode_cell_data(data, &field_rev).to_string(), YES);
|
||||
|
||||
let data = apply_cell_data_changeset("1", None, &field_rev).unwrap();
|
||||
assert_eq!(
|
||||
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
|
||||
YES
|
||||
);
|
||||
assert_eq!(decode_cell_data(data, &field_rev,).to_string(), YES);
|
||||
|
||||
let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap();
|
||||
assert_eq!(
|
||||
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
|
||||
YES
|
||||
);
|
||||
assert_eq!(decode_cell_data(data, &field_rev,).to_string(), YES);
|
||||
|
||||
let data = apply_cell_data_changeset("false", None, &field_rev).unwrap();
|
||||
assert_eq!(
|
||||
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
|
||||
NO
|
||||
);
|
||||
assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO);
|
||||
|
||||
let data = apply_cell_data_changeset("no", None, &field_rev).unwrap();
|
||||
assert_eq!(
|
||||
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
|
||||
NO
|
||||
);
|
||||
assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO);
|
||||
|
||||
let data = apply_cell_data_changeset("12", None, &field_rev).unwrap();
|
||||
assert_eq!(
|
||||
decode_cell_data_from_type_option_cell_data(data, &field_rev, &field_rev.field_type).to_string(),
|
||||
NO
|
||||
);
|
||||
assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::impl_type_option;
|
||||
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 flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
@ -45,7 +45,7 @@ impl CellDataOperation<String> for RichTextTypeOption {
|
||||
|| decoded_field_type.is_multi_select()
|
||||
|| 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 {
|
||||
let cell_data = encoded_data.into();
|
||||
Ok(DecodedCellData::new(cell_data))
|
||||
|
@ -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 => {}
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
3
frontend/rust-lib/flowy-grid/src/services/filter/mod.rs
Normal file
3
frontend/rust-lib/flowy-grid/src/services/filter/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod filter_service;
|
||||
|
||||
pub(crate) use filter_service::*;
|
@ -1,10 +1,12 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::entities::CellIdentifier;
|
||||
use crate::manager::GridUser;
|
||||
use crate::manager::{GridTaskSchedulerRwLock, GridUser};
|
||||
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::filter::GridFilterService;
|
||||
use crate::services::persistence::block_index::BlockIndexCache;
|
||||
use crate::services::row::*;
|
||||
|
||||
use bytes::Bytes;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::entities::*;
|
||||
@ -21,16 +23,18 @@ use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct GridRevisionEditor {
|
||||
grid_id: String,
|
||||
pub(crate) grid_id: String,
|
||||
user: Arc<dyn GridUser>,
|
||||
grid_pad: Arc<RwLock<GridRevisionPad>>,
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
block_manager: Arc<GridBlockManager>,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) filter_service: Arc<GridFilterService>,
|
||||
}
|
||||
|
||||
impl Drop for GridRevisionEditor {
|
||||
fn drop(&mut self) {
|
||||
tracing::trace!("Drop GridMetaEditor");
|
||||
tracing::trace!("Drop GridRevisionEditor");
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,22 +44,30 @@ impl GridRevisionEditor {
|
||||
user: Arc<dyn GridUser>,
|
||||
mut rev_manager: RevisionManager,
|
||||
persistence: Arc<BlockIndexCache>,
|
||||
task_scheduler: GridTaskSchedulerRwLock,
|
||||
) -> FlowyResult<Arc<Self>> {
|
||||
let token = user.token()?;
|
||||
let cloud = Arc::new(GridRevisionCloudService { token });
|
||||
let grid_pad = rev_manager.load::<GridPadBuilder>(Some(cloud)).await?;
|
||||
let rev_manager = Arc::new(rev_manager);
|
||||
let grid_pad = Arc::new(RwLock::new(grid_pad));
|
||||
let blocks = grid_pad.read().await.get_block_revs();
|
||||
|
||||
let block_meta_manager = Arc::new(GridBlockManager::new(grid_id, &user, blocks, persistence).await?);
|
||||
Ok(Arc::new(Self {
|
||||
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 filter_service = Arc::new(GridFilterService::new(
|
||||
grid_pad.clone(),
|
||||
block_manager.clone(),
|
||||
task_scheduler.clone(),
|
||||
));
|
||||
let editor = Arc::new(Self {
|
||||
grid_id: grid_id.to_owned(),
|
||||
user,
|
||||
grid_pad,
|
||||
rev_manager,
|
||||
block_manager: block_meta_manager,
|
||||
}))
|
||||
block_manager,
|
||||
filter_service,
|
||||
});
|
||||
|
||||
Ok(editor)
|
||||
}
|
||||
|
||||
pub async fn insert_field(&self, params: InsertFieldParams) -> FlowyResult<()> {
|
||||
@ -243,14 +255,14 @@ impl GridRevisionEditor {
|
||||
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
|
||||
.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_block(&self, changeset: GridBlockRevisionChangeset) -> FlowyResult<()> {
|
||||
pub async fn update_block(&self, changeset: GridBlockMetaRevisionChangeset) -> FlowyResult<()> {
|
||||
let _ = self
|
||||
.modify(|grid_pad| Ok(grid_pad.update_block_rev(changeset)?))
|
||||
.await?;
|
||||
@ -270,7 +282,7 @@ impl GridRevisionEditor {
|
||||
let row_count = self.block_manager.create_row(&block_id, row_rev, start_row_id).await?;
|
||||
|
||||
// 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?;
|
||||
Ok(row_order)
|
||||
}
|
||||
@ -342,7 +354,10 @@ impl GridRevisionEditor {
|
||||
pub async fn get_cell(&self, params: &CellIdentifier) -> Option<Cell> {
|
||||
let field_rev = self.get_field_rev(¶ms.field_id).await?;
|
||||
let row_rev = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??;
|
||||
make_cell(¶ms.field_id, &field_rev, &row_rev)
|
||||
|
||||
let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone();
|
||||
let data = decode_cell_data(cell_rev.data, &field_rev).data;
|
||||
Some(Cell::new(¶ms.field_id, data))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub async fn get_block_metas(&self) -> FlowyResult<Vec<GridBlockRevision>> {
|
||||
let grid_blocks = self.grid_pad.read().await.get_block_revs();
|
||||
Ok(grid_blocks)
|
||||
pub async fn get_block_meta_revs(&self) -> FlowyResult<Vec<Arc<GridBlockMetaRevision>>> {
|
||||
let block_meta_revs = self.grid_pad.read().await.get_block_meta_revs();
|
||||
Ok(block_meta_revs)
|
||||
}
|
||||
|
||||
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 field_orders = pad_read_guard.get_field_orders();
|
||||
let mut block_orders = vec![];
|
||||
for block_order in pad_read_guard.get_block_revs() {
|
||||
let row_orders = self.block_manager.get_row_orders(&block_order.block_id).await?;
|
||||
let block_order = GridBlockOrder {
|
||||
block_id: block_order.block_id,
|
||||
for block_rev in pad_read_guard.get_block_meta_revs() {
|
||||
let row_orders = self.block_manager.get_row_orders(&block_rev.block_id).await?;
|
||||
let block_order = GridBlock {
|
||||
id: block_rev.block_id.clone(),
|
||||
row_orders,
|
||||
};
|
||||
block_orders.push(block_order);
|
||||
@ -434,20 +449,34 @@ impl GridRevisionEditor {
|
||||
Ok(Grid {
|
||||
id: self.grid_id.clone(),
|
||||
field_orders,
|
||||
block_orders,
|
||||
blocks: block_orders,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_grid_setting(&self) -> FlowyResult<GridSettingRevision> {
|
||||
let pad_read_guard = self.grid_pad.read().await;
|
||||
let grid_setting_rev = pad_read_guard.get_grid_setting_rev();
|
||||
Ok(grid_setting_rev)
|
||||
pub async fn get_grid_setting(&self) -> FlowyResult<GridSetting> {
|
||||
let read_guard = self.grid_pad.read().await;
|
||||
let grid_setting_rev = read_guard.get_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<()> {
|
||||
let is_filter_changed = params.is_filter_changed();
|
||||
let _ = self
|
||||
.modify(|grid_pad| Ok(grid_pad.update_grid_setting_rev(params)?))
|
||||
.await?;
|
||||
|
||||
if is_filter_changed {
|
||||
self.filter_service.notify_changed().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -457,9 +486,9 @@ impl GridRevisionEditor {
|
||||
.grid_pad
|
||||
.read()
|
||||
.await
|
||||
.get_block_revs()
|
||||
.into_iter()
|
||||
.map(|block_meta| block_meta.block_id)
|
||||
.get_block_meta_revs()
|
||||
.iter()
|
||||
.map(|block_rev| block_rev.block_id.clone())
|
||||
.collect::<Vec<String>>(),
|
||||
Some(block_ids) => block_ids,
|
||||
};
|
||||
@ -507,8 +536,8 @@ impl GridRevisionEditor {
|
||||
|
||||
pub async fn duplicate_grid(&self) -> FlowyResult<BuildGridContext> {
|
||||
let grid_pad = self.grid_pad.read().await;
|
||||
let original_blocks = grid_pad.get_block_revs();
|
||||
let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_meta().await;
|
||||
let original_blocks = grid_pad.get_block_meta_revs();
|
||||
let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_block_meta().await;
|
||||
|
||||
let mut blocks_meta_data = vec![];
|
||||
if original_blocks.len() == duplicated_blocks.len() {
|
||||
@ -517,9 +546,7 @@ impl GridRevisionEditor {
|
||||
let duplicated_block_id = &duplicated_blocks[index].block_id;
|
||||
|
||||
tracing::trace!("Duplicate block:{} meta data", duplicated_block_id);
|
||||
let duplicated_block_meta_data = grid_block_meta_editor
|
||||
.duplicate_block_meta_data(duplicated_block_id)
|
||||
.await;
|
||||
let duplicated_block_meta_data = grid_block_meta_editor.duplicate_block(duplicated_block_id).await;
|
||||
blocks_meta_data.push(duplicated_block_meta_data);
|
||||
}
|
||||
} else {
|
||||
@ -566,7 +593,7 @@ impl GridRevisionEditor {
|
||||
}
|
||||
|
||||
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")),
|
||||
Some(grid_block) => Ok(grid_block.block_id.clone()),
|
||||
}
|
||||
|
@ -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(())
|
||||
})
|
||||
}
|
||||
}
|
@ -3,6 +3,11 @@ mod util;
|
||||
mod block_manager;
|
||||
pub mod block_revision_editor;
|
||||
pub mod field;
|
||||
mod filter;
|
||||
pub mod grid_editor;
|
||||
mod grid_editor_task;
|
||||
pub mod persistence;
|
||||
pub mod row;
|
||||
pub mod setting;
|
||||
mod snapshot;
|
||||
pub mod tasks;
|
||||
|
@ -55,12 +55,6 @@ pub struct TypeOptionCellData {
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl TypeOptionCellData {
|
||||
pub fn split(self) -> (String, FieldType) {
|
||||
(self.data, self.field_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for TypeOptionCellData {
|
||||
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())
|
||||
}
|
||||
|
||||
pub fn decode_cell_data_from_type_option_cell_data<T: TryInto<TypeOptionCellData>>(
|
||||
data: T,
|
||||
field_rev: &FieldRevision,
|
||||
field_type: &FieldType,
|
||||
) -> DecodedCellData {
|
||||
pub fn decode_cell_data<T: TryInto<TypeOptionCellData>>(data: T, field_rev: &FieldRevision) -> DecodedCellData {
|
||||
if let Ok(type_option_cell_data) = data.try_into() {
|
||||
let (encoded_data, s_field_type) = type_option_cell_data.split();
|
||||
match decode_cell_data(encoded_data, &s_field_type, field_type, field_rev) {
|
||||
let TypeOptionCellData { data, field_type } = type_option_cell_data;
|
||||
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,
|
||||
Err(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,
|
||||
field_rev: &FieldRevision,
|
||||
s_field_type: &FieldType,
|
||||
t_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
let encoded_data = encoded_data.into();
|
||||
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)]
|
||||
pub struct DecodedCellData {
|
||||
pub data: Vec<u8>,
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::services::row::decode_cell_data_from_type_option_cell_data;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_grid_data_model::entities::{Cell, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowOrder};
|
||||
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, RowRevision};
|
||||
use flowy_grid_data_model::entities::{GridBlock, RepeatedGridBlock, Row, RowOrder};
|
||||
use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -10,36 +9,30 @@ pub struct GridBlockSnapshot {
|
||||
pub row_revs: Vec<Arc<RowRevision>>,
|
||||
}
|
||||
|
||||
pub(crate) fn group_row_orders(row_orders: Vec<RowOrder>) -> Vec<GridBlockOrder> {
|
||||
let mut map: HashMap<String, GridBlockOrder> = HashMap::new();
|
||||
pub(crate) fn block_from_row_orders(row_orders: Vec<RowOrder>) -> Vec<GridBlock> {
|
||||
let mut map: HashMap<String, GridBlock> = HashMap::new();
|
||||
row_orders.into_iter().for_each(|row_order| {
|
||||
// Memory Optimization: escape clone block_id
|
||||
let block_id = row_order.block_id.clone();
|
||||
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
|
||||
.push(row_order);
|
||||
});
|
||||
map.into_values().collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn make_cell_by_field_id(
|
||||
field_map: &HashMap<&String, &FieldRevision>,
|
||||
field_id: String,
|
||||
cell_rev: CellRevision,
|
||||
) -> Option<(String, Cell)> {
|
||||
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 cell = Cell::new(&field_id, data);
|
||||
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))
|
||||
}
|
||||
//
|
||||
// #[inline(always)]
|
||||
// fn make_cell_by_field_id(
|
||||
// field_map: &HashMap<&String, &FieldRevision>,
|
||||
// field_id: String,
|
||||
// cell_rev: CellRevision,
|
||||
// ) -> Option<(String, Cell)> {
|
||||
// let field_rev = field_map.get(&field_id)?;
|
||||
// let data = decode_cell_data(cell_rev.data, field_rev).data;
|
||||
// let cell = Cell::new(&field_id, data);
|
||||
// Some((field_id, cell))
|
||||
// }
|
||||
|
||||
pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<RowOrder> {
|
||||
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()
|
||||
}
|
||||
|
||||
pub(crate) fn make_rows_from_row_revs(fields: &[FieldRevision], row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
|
||||
let field_rev_map = fields
|
||||
.iter()
|
||||
.map(|field_rev| (&field_rev.id, field_rev))
|
||||
.collect::<HashMap<&String, &FieldRevision>>();
|
||||
pub(crate) fn make_rows_from_row_revs(_fields: &[FieldRevision], row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
|
||||
// let field_rev_map = fields
|
||||
// .iter()
|
||||
// .map(|field_rev| (&field_rev.id, field_rev))
|
||||
// .collect::<HashMap<&String, &FieldRevision>>();
|
||||
|
||||
let make_row = |row_rev: &Arc<RowRevision>| {
|
||||
let cell_by_field_id = row_rev
|
||||
.cells
|
||||
.clone()
|
||||
.into_iter()
|
||||
.flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev))
|
||||
.collect::<HashMap<String, Cell>>();
|
||||
// let cell_by_field_id = row_rev
|
||||
// .cells
|
||||
// .clone()
|
||||
// .into_iter()
|
||||
// .flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev))
|
||||
// .collect::<HashMap<String, Cell>>();
|
||||
|
||||
Row {
|
||||
id: row_rev.id.clone(),
|
||||
cell_by_field_id,
|
||||
height: row_rev.height,
|
||||
}
|
||||
};
|
||||
|
3
frontend/rust-lib/flowy-grid/src/services/setting/mod.rs
Normal file
3
frontend/rust-lib/flowy-grid/src/services/setting/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod setting_builder;
|
||||
|
||||
pub use setting_builder::*;
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
mod snapshot_service;
|
||||
|
||||
pub use snapshot_service::*;
|
@ -0,0 +1,7 @@
|
||||
// pub struct GridSnapshotService {}
|
||||
//
|
||||
// impl GridSnapshotService {
|
||||
// pub fn new() -> Self {
|
||||
// Self {}
|
||||
// }
|
||||
// }
|
9
frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs
Normal file
9
frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs
Normal 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::*;
|
119
frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs
Normal file
119
frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs
Normal 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))
|
||||
}
|
||||
}
|
45
frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs
Normal file
45
frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs
Normal file
87
frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs
Normal 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(());
|
||||
}
|
||||
}
|
32
frontend/rust-lib/flowy-grid/src/services/tasks/store.rs
Normal file
32
frontend/rust-lib/flowy-grid/src/services/tasks/store.rs
Normal 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)
|
||||
}
|
||||
}
|
69
frontend/rust-lib/flowy-grid/src/services/tasks/task.rs
Normal file
69
frontend/rust-lib/flowy-grid/src/services/tasks/task.rs
Normal 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!()
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use flowy_grid_data_model::revision::{GridBlockRevision, GridBlockRevisionChangeset};
|
||||
use flowy_grid_data_model::revision::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset};
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_block() {
|
||||
let grid_block = GridBlockRevision::new();
|
||||
let block_meta_rev = GridBlockMetaRevision::new();
|
||||
let scripts = vec![
|
||||
AssertBlockCount(1),
|
||||
CreateBlock { block: grid_block },
|
||||
CreateBlock { block: block_meta_rev },
|
||||
AssertBlockCount(2),
|
||||
];
|
||||
GridEditorTest::new().await.run_scripts(scripts).await;
|
||||
@ -15,10 +15,10 @@ async fn grid_create_block() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_block() {
|
||||
let grid_block = GridBlockRevision::new();
|
||||
let mut cloned_grid_block = grid_block.clone();
|
||||
let changeset = GridBlockRevisionChangeset {
|
||||
block_id: grid_block.block_id.clone(),
|
||||
let block_meta_rev = GridBlockMetaRevision::new();
|
||||
let mut cloned_grid_block = block_meta_rev.clone();
|
||||
let changeset = GridBlockMetaRevisionChangeset {
|
||||
block_id: block_meta_rev.block_id.clone(),
|
||||
start_row_index: Some(2),
|
||||
row_count: Some(10),
|
||||
};
|
||||
@ -28,7 +28,7 @@ async fn grid_update_block() {
|
||||
|
||||
let scripts = vec![
|
||||
AssertBlockCount(1),
|
||||
CreateBlock { block: grid_block },
|
||||
CreateBlock { block: block_meta_rev },
|
||||
UpdateBlock { changeset },
|
||||
AssertBlockCount(2),
|
||||
AssertBlockEqual {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::grid::field_util::make_date_cell_string;
|
||||
use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use flowy_grid::services::field::{MultiSelectTypeOption, SelectOptionCellContentChangeset, SingleSelectTypeOption};
|
||||
@ -8,7 +9,7 @@ async fn grid_cell_update() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let field_revs = &test.field_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
|
||||
let block_id = &grid_blocks.first().unwrap().block_id;
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::grid::field_util::*;
|
||||
use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use flowy_grid::services::field::{SelectOption, SingleSelectTypeOption};
|
||||
|
81
frontend/rust-lib/flowy-grid/tests/grid/field_util.rs
Normal file
81
frontend/rust-lib/flowy-grid/tests/grid/field_util.rs
Normal 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()
|
||||
}
|
43
frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs
Normal file
43
frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs
Normal 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() {}
|
@ -1,6 +1,8 @@
|
||||
mod block_test;
|
||||
mod cell_test;
|
||||
mod field_test;
|
||||
mod field_util;
|
||||
mod filter_test;
|
||||
mod row_test;
|
||||
mod row_util;
|
||||
mod script;
|
||||
mod setting_test;
|
||||
|
@ -1,23 +1,24 @@
|
||||
use crate::grid::field_util::*;
|
||||
use crate::grid::row_util::GridRowTestBuilder;
|
||||
use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use chrono::NaiveDateTime;
|
||||
use flowy_grid::services::field::{
|
||||
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::revision::RowMetaChangeset;
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_row_count_test() {
|
||||
let test = GridEditorTest::new().await;
|
||||
let create_row_context = CreateRowRevisionBuilder::new(&test.field_revs).build();
|
||||
let scripts = vec![
|
||||
AssertRowCount(3),
|
||||
CreateEmptyRow,
|
||||
CreateEmptyRow,
|
||||
CreateRow {
|
||||
context: create_row_context,
|
||||
payload: GridRowTestBuilder::new(&test).build(),
|
||||
},
|
||||
AssertRowCount(6),
|
||||
];
|
||||
@ -27,15 +28,15 @@ async fn grid_create_row_count_test() {
|
||||
#[tokio::test]
|
||||
async fn grid_update_row() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let context = CreateRowRevisionBuilder::new(&test.field_revs).build();
|
||||
let payload = GridRowTestBuilder::new(&test).build();
|
||||
let changeset = RowMetaChangeset {
|
||||
row_id: context.row_id.clone(),
|
||||
row_id: payload.row_id.clone(),
|
||||
height: None,
|
||||
visibility: None,
|
||||
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;
|
||||
|
||||
let expected_row = (&*test.row_revs.last().cloned().unwrap()).clone();
|
||||
@ -46,13 +47,13 @@ async fn grid_update_row() {
|
||||
#[tokio::test]
|
||||
async fn grid_delete_row() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let context_1 = CreateRowRevisionBuilder::new(&test.field_revs).build();
|
||||
let context_2 = CreateRowRevisionBuilder::new(&test.field_revs).build();
|
||||
let row_ids = vec![context_1.row_id.clone(), context_2.row_id.clone()];
|
||||
let payload1 = GridRowTestBuilder::new(&test).build();
|
||||
let payload2 = GridRowTestBuilder::new(&test).build();
|
||||
let row_ids = vec![payload1.row_id.clone(), payload2.row_id.clone()];
|
||||
let scripts = vec![
|
||||
AssertRowCount(3),
|
||||
CreateRow { context: context_1 },
|
||||
CreateRow { context: context_2 },
|
||||
CreateRow { payload: payload1 },
|
||||
CreateRow { payload: payload2 },
|
||||
AssertBlockCount(1),
|
||||
AssertBlock {
|
||||
block_index: 0,
|
||||
@ -110,7 +111,7 @@ async fn grid_row_add_cells_test() {
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
let scripts = vec![CreateRow { context }, AssertGridRevisionPad];
|
||||
let scripts = vec![CreateRow { payload: context }, AssertGridRevisionPad];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
@ -136,12 +137,12 @@ async fn grid_row_add_date_cell_test() {
|
||||
let date_field = date_field.unwrap();
|
||||
let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
|
||||
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>()
|
||||
.unwrap()
|
||||
.date,
|
||||
"2022/03/16",
|
||||
);
|
||||
let scripts = vec![CreateRow { context }];
|
||||
let scripts = vec![CreateRow { payload: context }];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
72
frontend/rust-lib/flowy-grid/tests/grid/row_util.rs
Normal file
72
frontend/rust-lib/flowy-grid/tests/grid/row_util.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
use bytes::Bytes;
|
||||
use flowy_grid::services::field::*;
|
||||
use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor};
|
||||
use flowy_grid::services::row::CreateRowRevisionPayload;
|
||||
use flowy_grid::services::setting::GridSettingChangesetBuilder;
|
||||
use flowy_grid_data_model::entities::*;
|
||||
use flowy_grid_data_model::revision::*;
|
||||
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
|
||||
@ -30,10 +32,10 @@ pub enum EditorScript {
|
||||
field_rev: FieldRevision,
|
||||
},
|
||||
CreateBlock {
|
||||
block: GridBlockRevision,
|
||||
block: GridBlockMetaRevision,
|
||||
},
|
||||
UpdateBlock {
|
||||
changeset: GridBlockRevisionChangeset,
|
||||
changeset: GridBlockMetaRevisionChangeset,
|
||||
},
|
||||
AssertBlockCount(usize),
|
||||
AssertBlock {
|
||||
@ -43,11 +45,11 @@ pub enum EditorScript {
|
||||
},
|
||||
AssertBlockEqual {
|
||||
block_index: usize,
|
||||
block: GridBlockRevision,
|
||||
block: GridBlockMetaRevision,
|
||||
},
|
||||
CreateEmptyRow,
|
||||
CreateRow {
|
||||
context: CreateRowRevisionPayload,
|
||||
payload: CreateRowRevisionPayload,
|
||||
},
|
||||
UpdateRow {
|
||||
changeset: RowMetaChangeset,
|
||||
@ -63,11 +65,22 @@ pub enum EditorScript {
|
||||
is_err: bool,
|
||||
},
|
||||
AssertRowCount(usize),
|
||||
#[allow(dead_code)]
|
||||
UpdateGridSetting {
|
||||
params: GridSettingChangesetParams,
|
||||
},
|
||||
InsertGridTableFilter {
|
||||
payload: CreateGridFilterPayload,
|
||||
},
|
||||
AssertTableFilterCount {
|
||||
count: i32,
|
||||
},
|
||||
DeleteGridTableFilter {
|
||||
filter_id: String,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
AssertGridSetting {
|
||||
expected_setting: GridSettingRevision,
|
||||
expected_setting: GridSetting,
|
||||
},
|
||||
AssertGridRevisionPad,
|
||||
}
|
||||
@ -77,7 +90,7 @@ pub struct GridEditorTest {
|
||||
pub grid_id: String,
|
||||
pub editor: Arc<GridRevisionEditor>,
|
||||
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 field_count: usize,
|
||||
|
||||
@ -88,15 +101,15 @@ impl GridEditorTest {
|
||||
pub async fn new() -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
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 test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
|
||||
let editor = sdk.grid_manager.open_grid(&test.view.id).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;
|
||||
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.
|
||||
// Because we assert the initialize count of the fields is equal to FieldType::COUNT.
|
||||
@ -108,7 +121,7 @@ impl GridEditorTest {
|
||||
grid_id,
|
||||
editor,
|
||||
field_revs,
|
||||
grid_block_revs: grid_blocks,
|
||||
block_meta_revs,
|
||||
row_revs,
|
||||
field_count: FieldType::COUNT,
|
||||
row_order_by_row_id: HashMap::default(),
|
||||
@ -162,40 +175,40 @@ impl GridEditorTest {
|
||||
}
|
||||
EditorScript::CreateBlock { block } => {
|
||||
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 } => {
|
||||
self.editor.update_block(change).await.unwrap();
|
||||
}
|
||||
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 {
|
||||
block_index,
|
||||
row_count,
|
||||
start_row_index,
|
||||
} => {
|
||||
assert_eq!(self.grid_block_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].row_count, row_count);
|
||||
assert_eq!(self.block_meta_revs[block_index].start_row_index, start_row_index);
|
||||
}
|
||||
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();
|
||||
assert_eq!(compared_block, block);
|
||||
assert_eq!(compared_block, Arc::new(block));
|
||||
}
|
||||
EditorScript::CreateEmptyRow => {
|
||||
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_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();
|
||||
for row_order in row_orders {
|
||||
self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order);
|
||||
}
|
||||
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::DeleteRows { row_ids } => {
|
||||
@ -206,7 +219,7 @@ impl GridEditorTest {
|
||||
|
||||
self.editor.delete_rows(row_orders).await.unwrap();
|
||||
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 } => {
|
||||
let row = &*self
|
||||
@ -239,6 +252,26 @@ impl GridEditorTest {
|
||||
EditorScript::UpdateGridSetting { params } => {
|
||||
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 } => {
|
||||
let setting = self.editor.get_grid_setting().await.unwrap();
|
||||
assert_eq!(expected_setting, setting);
|
||||
@ -261,76 +294,23 @@ impl GridEditorTest {
|
||||
.unwrap()
|
||||
.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 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)
|
||||
pub fn text_field(&self) -> &FieldRevision {
|
||||
self.field_revs
|
||||
.iter()
|
||||
.filter(|field_rev| field_rev.field_type == FieldType::RichText)
|
||||
.collect::<Vec<_>>()
|
||||
.pop()
|
||||
.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 {
|
||||
fn make_all_field_test_grid() -> BuildGridContext {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
@ -385,11 +365,3 @@ fn make_test_grid() -> BuildGridContext {
|
||||
.add_empty_row()
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn make_date_cell_string(s: &str) -> String {
|
||||
serde_json::to_string(&DateCellContentChangeset {
|
||||
date: Some(s.to_string()),
|
||||
time: None,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -228,7 +228,7 @@ impl ViewDataProcessor for GridViewDataProcessor {
|
||||
let grid_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
let _ = grid_manager.delete_grid(view_id)?;
|
||||
let _ = grid_manager.delete_grid(view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@ -237,7 +237,7 @@ impl ViewDataProcessor for GridViewDataProcessor {
|
||||
let grid_manager = self.0.clone();
|
||||
let view_id = view_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
let _ = grid_manager.close_grid(view_id)?;
|
||||
let _ = grid_manager.close_grid(view_id).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
@ -199,6 +199,15 @@ fn spawn_edit_queue(
|
||||
) -> EditorCommandSender {
|
||||
let (sender, receiver) = mpsc::channel(1000);
|
||||
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());
|
||||
sender
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ static RUST_TYPE_MAP: phf::Map<&'static str, &'static str> = phf_map! {
|
||||
"i32" => "int32",
|
||||
"u64" => "uint64",
|
||||
"u32" => "uint32",
|
||||
"u8" => "uint8",
|
||||
"Vec" => "repeated",
|
||||
"f64" => "double",
|
||||
"HashMap" => "map",
|
||||
|
@ -36,7 +36,7 @@ run_task = { name = "remove_files_with_pattern" }
|
||||
|
||||
[tasks.rm_shared_lib_generated_protobuf_files]
|
||||
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" }
|
||||
|
||||
|
||||
|
@ -30,13 +30,10 @@ pub struct ViewRevision {
|
||||
#[serde(default)]
|
||||
pub thumbnail: String,
|
||||
|
||||
#[serde(default = "default_plugin_type")]
|
||||
#[serde(default = "DEFAULT_PLUGIN_TYPE")]
|
||||
pub plugin_type: i32,
|
||||
}
|
||||
|
||||
fn default_plugin_type() -> i32 {
|
||||
0
|
||||
}
|
||||
const DEFAULT_PLUGIN_TYPE: fn() -> i32 = || 0;
|
||||
|
||||
impl std::convert::From<ViewRevision> for View {
|
||||
fn from(view_serde: ViewRevision) -> Self {
|
||||
|
@ -11,7 +11,7 @@ protobuf = {version = "2.18.0"}
|
||||
bytes = "1.0"
|
||||
strum = "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_repr = "0.1"
|
||||
nanoid = "0.4.0"
|
||||
|
@ -4,8 +4,6 @@ use crate::revision::RowRevision;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error_code::ErrorCode;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct Grid {
|
||||
#[pb(index = 1)]
|
||||
@ -15,7 +13,7 @@ pub struct Grid {
|
||||
pub field_orders: Vec<FieldOrder>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub block_orders: Vec<GridBlockOrder>,
|
||||
pub blocks: Vec<GridBlock>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
@ -36,12 +34,15 @@ pub struct Row {
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub cell_by_field_id: HashMap<String, Cell>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct OptionalRow {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub row: Option<Row>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct RepeatedRow {
|
||||
#[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)]
|
||||
pub struct IndexRowOrder {
|
||||
#[pb(index = 1)]
|
||||
@ -168,7 +151,7 @@ impl GridRowsChangeset {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct GridBlock {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
@ -305,12 +288,12 @@ pub struct QueryGridBlocksPayload {
|
||||
pub grid_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub block_orders: Vec<GridBlockOrder>,
|
||||
pub block_ids: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct QueryGridBlocksParams {
|
||||
pub grid_id: String,
|
||||
pub block_orders: Vec<GridBlockOrder>,
|
||||
pub block_ids: Vec<String>,
|
||||
}
|
||||
|
||||
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)?;
|
||||
Ok(QueryGridBlocksParams {
|
||||
grid_id: grid_id.0,
|
||||
block_orders: self.block_orders,
|
||||
block_ids: self.block_ids,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
328
shared-lib/flowy-grid-data-model/src/entities/grid_filter.rs
Normal file
328
shared-lib/flowy-grid-data-model/src/entities/grid_filter.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
80
shared-lib/flowy-grid-data-model/src/entities/grid_group.rs
Normal file
80
shared-lib/flowy-grid-data-model/src/entities/grid_group.rs
Normal 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 })
|
||||
}
|
||||
}
|
@ -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_error_code::ErrorCode;
|
||||
use std::collections::HashMap;
|
||||
@ -7,13 +12,41 @@ use std::convert::TryInto;
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GridSetting {
|
||||
#[pb(index = 1)]
|
||||
pub filter: HashMap<String, GridFilter>,
|
||||
pub filters_by_layout_ty: HashMap<String, RepeatedGridFilter>,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub group: HashMap<String, GridGroup>,
|
||||
pub groups_by_layout_ty: HashMap<String, RepeatedGridGroup>,
|
||||
|
||||
#[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)]
|
||||
@ -29,25 +62,22 @@ impl std::default::Default for GridLayoutType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GridFilter {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub field_id: Option<String>,
|
||||
impl std::convert::From<GridLayoutRevision> for GridLayoutType {
|
||||
fn from(rev: GridLayoutRevision) -> Self {
|
||||
match rev {
|
||||
GridLayoutRevision::Table => GridLayoutType::Table,
|
||||
GridLayoutRevision::Board => GridLayoutType::Board,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GridGroup {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub group_field_id: Option<String>,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub sub_group_field_id: Option<String>,
|
||||
impl std::convert::From<GridLayoutType> for GridLayoutRevision {
|
||||
fn from(layout: GridLayoutType) -> Self {
|
||||
match layout {
|
||||
GridLayoutType::Table => GridLayoutRevision::Table,
|
||||
GridLayoutType::Board => GridLayoutRevision::Board,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GridSort {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub field_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
@ -59,21 +89,39 @@ pub struct GridSettingChangesetPayload {
|
||||
pub layout_type: GridLayoutType,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub filter: Option<GridFilter>,
|
||||
pub insert_filter: Option<CreateGridFilterPayload>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub group: Option<GridGroup>,
|
||||
pub delete_filter: Option<String>,
|
||||
|
||||
#[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 grid_id: String,
|
||||
pub layout_type: GridLayoutType,
|
||||
pub filter: Option<GridFilter>,
|
||||
pub group: Option<GridGroup>,
|
||||
pub sort: Option<GridSort>,
|
||||
pub insert_filter: Option<CreateGridFilterParams>,
|
||||
pub delete_filter: Option<String>,
|
||||
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 {
|
||||
@ -84,27 +132,45 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayload {
|
||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||
.0;
|
||||
|
||||
let filter = match self.filter {
|
||||
let insert_filter = match self.insert_filter {
|
||||
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,
|
||||
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,
|
||||
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 {
|
||||
grid_id: view_id,
|
||||
layout_type: self.layout_type,
|
||||
filter,
|
||||
group,
|
||||
sort,
|
||||
insert_filter,
|
||||
delete_filter,
|
||||
insert_group,
|
||||
delete_group,
|
||||
insert_sort,
|
||||
delete_sort,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
68
shared-lib/flowy-grid-data-model/src/entities/grid_sort.rs
Normal file
68
shared-lib/flowy-grid-data-model/src/entities/grid_sort.rs
Normal 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 })
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
mod field;
|
||||
mod grid;
|
||||
mod grid_filter;
|
||||
mod grid_group;
|
||||
mod grid_setting;
|
||||
mod grid_sort;
|
||||
|
||||
pub use field::*;
|
||||
pub use grid::*;
|
||||
pub use grid_filter::*;
|
||||
pub use grid_group::*;
|
||||
pub use grid_setting::*;
|
||||
pub use grid_sort::*;
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
@ -1,5 +1,2 @@
|
||||
mod grid_info_parser;
|
||||
mod str_parser;
|
||||
|
||||
pub use grid_info_parser::*;
|
||||
pub use str_parser::*;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -30,9 +30,9 @@ pub fn gen_field_id() -> String {
|
||||
pub struct GridRevision {
|
||||
pub grid_id: String,
|
||||
pub fields: Vec<FieldRevision>,
|
||||
pub blocks: Vec<GridBlockRevision>,
|
||||
pub blocks: Vec<Arc<GridBlockMetaRevision>>,
|
||||
|
||||
#[serde(skip)]
|
||||
#[serde(default, skip)]
|
||||
pub setting: GridSettingRevision,
|
||||
}
|
||||
|
||||
@ -45,16 +45,25 @@ impl GridRevision {
|
||||
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)]
|
||||
pub struct GridBlockRevision {
|
||||
pub struct GridBlockMetaRevision {
|
||||
pub block_id: String,
|
||||
pub start_row_index: i32,
|
||||
pub row_count: i32,
|
||||
}
|
||||
|
||||
impl GridBlockRevision {
|
||||
impl GridBlockMetaRevision {
|
||||
pub fn len(&self) -> i32 {
|
||||
self.row_count
|
||||
}
|
||||
@ -64,22 +73,22 @@ impl GridBlockRevision {
|
||||
}
|
||||
}
|
||||
|
||||
impl GridBlockRevision {
|
||||
impl GridBlockMetaRevision {
|
||||
pub fn new() -> Self {
|
||||
GridBlockRevision {
|
||||
GridBlockMetaRevision {
|
||||
block_id: gen_block_id(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GridBlockRevisionChangeset {
|
||||
pub struct GridBlockMetaRevisionChangeset {
|
||||
pub block_id: String,
|
||||
pub start_row_index: Option<i32>,
|
||||
pub row_count: Option<i32>,
|
||||
}
|
||||
|
||||
impl GridBlockRevisionChangeset {
|
||||
impl GridBlockMetaRevisionChangeset {
|
||||
pub fn from_row_count(block_id: &str, row_count: i32) -> Self {
|
||||
Self {
|
||||
block_id: block_id.to_string(),
|
||||
@ -90,9 +99,9 @@ impl GridBlockRevisionChangeset {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct GridBlockRevisionData {
|
||||
pub struct GridBlockRevision {
|
||||
pub block_id: String,
|
||||
pub rows: Vec<RowRevision>,
|
||||
pub rows: Vec<Arc<RowRevision>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
|
||||
@ -118,13 +127,11 @@ pub struct FieldRevision {
|
||||
#[serde(with = "indexmap::serde_seq")]
|
||||
pub type_options: IndexMap<String, String>,
|
||||
|
||||
#[serde(default = "default_is_primary")]
|
||||
#[serde(default = "DEFAULT_IS_PRIMARY")]
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
fn default_is_primary() -> bool {
|
||||
false
|
||||
}
|
||||
const DEFAULT_IS_PRIMARY: fn() -> bool = || false;
|
||||
|
||||
impl FieldRevision {
|
||||
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)]
|
||||
pub struct BuildGridContext {
|
||||
pub field_revs: Vec<FieldRevision>,
|
||||
pub blocks: Vec<GridBlockRevision>,
|
||||
pub blocks_meta_data: Vec<GridBlockRevisionData>,
|
||||
pub blocks: Vec<GridBlockMetaRevision>,
|
||||
pub blocks_meta_data: Vec<GridBlockRevision>,
|
||||
}
|
||||
|
||||
impl BuildGridContext {
|
||||
|
@ -1,19 +1,32 @@
|
||||
use crate::entities::{GridFilter, GridGroup, GridLayoutType, GridSetting, GridSort};
|
||||
use indexmap::IndexMap;
|
||||
use nanoid::nanoid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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)]
|
||||
pub struct GridSettingRevision {
|
||||
#[serde(with = "indexmap::serde_seq")]
|
||||
pub filter: IndexMap<GridLayoutRevision, GridFilterRevision>,
|
||||
pub layout: GridLayoutRevision,
|
||||
|
||||
#[serde(with = "indexmap::serde_seq")]
|
||||
pub group: IndexMap<GridLayoutRevision, GridGroupRevision>,
|
||||
pub filters: IndexMap<GridLayoutRevision, Vec<GridFilterRevision>>,
|
||||
|
||||
#[serde(with = "indexmap::serde_seq")]
|
||||
pub sort: IndexMap<GridLayoutRevision, GridSortRevision>,
|
||||
#[serde(skip, with = "indexmap::serde_seq")]
|
||||
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)]
|
||||
@ -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)]
|
||||
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)]
|
||||
pub struct GridGroupRevision {
|
||||
pub group_field_id: Option<String>,
|
||||
pub sub_group_field_id: Option<String>,
|
||||
pub id: String,
|
||||
pub field_id: Option<String>,
|
||||
pub sub_field_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||
pub struct GridSortRevision {
|
||||
pub id: 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 }
|
||||
}
|
||||
}
|
||||
|
@ -2,40 +2,44 @@ use crate::entities::revision::{md5, RepeatedRevision, Revision};
|
||||
use crate::errors::{CollaborateError, CollaborateResult};
|
||||
use crate::util::{cal_diff, make_delta_from_revisions};
|
||||
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 serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type GridBlockRevisionDelta = PlainTextDelta;
|
||||
pub type GridBlockRevisionDeltaBuilder = PlainTextDeltaBuilder;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GridBlockRevisionPad {
|
||||
block_id: String,
|
||||
rows: Vec<Arc<RowRevision>>,
|
||||
|
||||
#[serde(skip)]
|
||||
block_revision: GridBlockRevision,
|
||||
pub(crate) delta: GridBlockRevisionDelta,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for GridBlockRevisionPad {
|
||||
type Target = GridBlockRevision;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.block_revision
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
.block_revision
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let mut duplicated_row = row.as_ref().clone();
|
||||
duplicated_row.id = gen_row_id();
|
||||
duplicated_row.block_id = duplicated_block_id.to_string();
|
||||
duplicated_row
|
||||
Arc::new(duplicated_row)
|
||||
})
|
||||
.collect::<Vec<RowRevision>>();
|
||||
GridBlockRevisionData {
|
||||
.collect::<Vec<Arc<RowRevision>>>();
|
||||
GridBlockRevision {
|
||||
block_id: duplicated_block_id.to_string(),
|
||||
rows: duplicated_rows,
|
||||
}
|
||||
@ -43,18 +47,12 @@ impl GridBlockRevisionPad {
|
||||
|
||||
pub fn from_delta(delta: GridBlockRevisionDelta) -> CollaborateResult<Self> {
|
||||
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);
|
||||
tracing::error!("{}", s);
|
||||
CollaborateError::internal().context(msg)
|
||||
})?;
|
||||
let block_id = meta_data.block_id;
|
||||
let rows = meta_data
|
||||
.rows
|
||||
.into_iter()
|
||||
.map(Arc::new)
|
||||
.collect::<Vec<Arc<RowRevision>>>();
|
||||
Ok(Self { block_id, rows, delta })
|
||||
Ok(Self { block_revision, delta })
|
||||
}
|
||||
|
||||
pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
|
||||
@ -95,9 +93,10 @@ impl GridBlockRevisionPad {
|
||||
T: AsRef<str> + ToOwned + ?Sized,
|
||||
{
|
||||
match row_ids {
|
||||
None => Ok(self.rows.to_vec()),
|
||||
None => Ok(self.block_revision.rows.clone()),
|
||||
Some(row_ids) => {
|
||||
let row_map = self
|
||||
.block_revision
|
||||
.rows
|
||||
.iter()
|
||||
.map(|row| (row.id.as_str(), row.clone()))
|
||||
@ -137,11 +136,12 @@ impl GridBlockRevisionPad {
|
||||
}
|
||||
|
||||
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> {
|
||||
self.rows
|
||||
self.block_revision
|
||||
.rows
|
||||
.iter()
|
||||
.position(|row| row.id == row_id)
|
||||
.map(|index| index as i32)
|
||||
@ -190,7 +190,7 @@ impl GridBlockRevisionPad {
|
||||
F: for<'a> FnOnce(&'a mut Vec<Arc<RowRevision>>) -> CollaborateResult<Option<()>>,
|
||||
{
|
||||
let cloned_self = self.clone();
|
||||
match f(&mut self.rows)? {
|
||||
match f(&mut self.block_revision.rows)? {
|
||||
None => Ok(None),
|
||||
Some(_) => {
|
||||
let old = cloned_self.to_json()?;
|
||||
@ -226,7 +226,7 @@ impl GridBlockRevisionPad {
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
@ -245,12 +245,12 @@ pub struct GridBlockMetaChange {
|
||||
pub md5: String,
|
||||
}
|
||||
|
||||
pub fn make_block_meta_delta(grid_block_meta_data: &GridBlockRevisionData) -> GridBlockRevisionDelta {
|
||||
let json = serde_json::to_string(&grid_block_meta_data).unwrap();
|
||||
pub fn make_block_meta_delta(block_rev: &GridBlockRevision) -> GridBlockRevisionDelta {
|
||||
let json = serde_json::to_string(&block_rev).unwrap();
|
||||
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 bytes = delta.to_delta_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 {
|
||||
fn default() -> Self {
|
||||
let block_meta_data = GridBlockRevisionData {
|
||||
let block_revision = GridBlockRevision {
|
||||
block_id: gen_block_id(),
|
||||
rows: vec![],
|
||||
};
|
||||
|
||||
let delta = make_block_meta_delta(&block_meta_data);
|
||||
GridBlockRevisionPad {
|
||||
block_id: block_meta_data.block_id,
|
||||
rows: block_meta_data.rows.into_iter().map(Arc::new).collect::<Vec<_>>(),
|
||||
delta,
|
||||
}
|
||||
let delta = make_block_meta_delta(&block_revision);
|
||||
GridBlockRevisionPad { block_revision, delta }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::errors::{CollaborateError, CollaborateResult};
|
||||
use flowy_grid_data_model::revision::{
|
||||
BuildGridContext, FieldRevision, GridBlockRevision, GridBlockRevisionData, RowRevision,
|
||||
BuildGridContext, FieldRevision, GridBlockMetaRevision, GridBlockRevision, RowRevision,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct GridBuilder {
|
||||
build_context: BuildGridContext,
|
||||
@ -11,8 +12,8 @@ impl std::default::Default for GridBuilder {
|
||||
fn default() -> Self {
|
||||
let mut build_context = BuildGridContext::new();
|
||||
|
||||
let block_meta = GridBlockRevision::new();
|
||||
let block_meta_data = GridBlockRevisionData {
|
||||
let block_meta = GridBlockMetaRevision::new();
|
||||
let block_meta_data = GridBlockRevision {
|
||||
block_id: block_meta.block_id.clone(),
|
||||
rows: vec![],
|
||||
};
|
||||
@ -32,10 +33,10 @@ impl GridBuilder {
|
||||
|
||||
pub fn add_empty_row(mut self) -> Self {
|
||||
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_data = self.build_context.blocks_meta_data.first_mut().unwrap();
|
||||
block_meta_data.rows.push(row);
|
||||
block_meta.row_count += 1;
|
||||
let block_meta_rev = self.build_context.blocks.first_mut().unwrap();
|
||||
let block_rev = self.build_context.blocks_meta_data.first_mut().unwrap();
|
||||
block_rev.rows.push(Arc::new(row));
|
||||
block_meta_rev.row_count += 1;
|
||||
self
|
||||
}
|
||||
|
||||
@ -59,10 +60,10 @@ fn check_rows(fields: &[FieldRevision], rows: &[RowRevision]) -> CollaborateResu
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::client_grid::{make_block_meta_delta, make_grid_delta, GridBuilder};
|
||||
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]
|
||||
fn create_default_grid_test() {
|
||||
@ -78,7 +79,7 @@ mod tests {
|
||||
let grid_rev = GridRevision {
|
||||
grid_id,
|
||||
fields: build_context.field_revs,
|
||||
blocks: build_context.blocks,
|
||||
blocks: build_context.blocks.into_iter().map(Arc::new).collect(),
|
||||
setting: Default::default(),
|
||||
};
|
||||
|
||||
@ -86,6 +87,6 @@ mod tests {
|
||||
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 _: 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();
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,9 @@ use bytes::Bytes;
|
||||
use flowy_grid_data_model::entities::{FieldChangesetParams, FieldOrder};
|
||||
use flowy_grid_data_model::entities::{FieldType, GridSettingChangesetParams};
|
||||
use flowy_grid_data_model::revision::{
|
||||
gen_block_id, gen_grid_id, FieldRevision, GridBlockRevision, GridBlockRevisionChangeset, GridFilterRevision,
|
||||
GridGroupRevision, GridLayoutRevision, GridRevision, GridSettingRevision, GridSortRevision,
|
||||
gen_block_id, gen_grid_filter_id, gen_grid_group_id, gen_grid_id, gen_grid_sort_id, FieldRevision,
|
||||
GridBlockMetaRevision, GridBlockMetaRevisionChangeset, GridFilterRevision, GridGroupRevision, GridLayoutRevision,
|
||||
GridRevision, GridSettingRevision, GridSortRevision,
|
||||
};
|
||||
use lib_infra::util::move_vec_element;
|
||||
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
|
||||
@ -26,7 +27,7 @@ pub trait JsonDeserializer {
|
||||
}
|
||||
|
||||
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 blocks = self
|
||||
@ -34,11 +35,11 @@ impl GridRevisionPad {
|
||||
.blocks
|
||||
.iter()
|
||||
.map(|block| {
|
||||
let mut duplicated_block = block.clone();
|
||||
let mut duplicated_block = (&*block.clone()).clone();
|
||||
duplicated_block.block_id = gen_block_id();
|
||||
duplicated_block
|
||||
})
|
||||
.collect::<Vec<GridBlockRevision>>();
|
||||
.collect::<Vec<GridBlockMetaRevision>>();
|
||||
|
||||
(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| {
|
||||
if grid_meta.blocks.iter().any(|b| b.block_id == block.block_id) {
|
||||
tracing::warn!("Duplicate grid block");
|
||||
Ok(None)
|
||||
} else {
|
||||
match grid_meta.blocks.last() {
|
||||
None => grid_meta.blocks.push(block),
|
||||
None => grid_meta.blocks.push(Arc::new(block)),
|
||||
Some(last_block) => {
|
||||
if last_block.start_row_index > 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();
|
||||
return Err(CollaborateError::internal().context(msg))
|
||||
}
|
||||
grid_meta.blocks.push(block);
|
||||
grid_meta.blocks.push(Arc::new(block));
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn update_block_rev(
|
||||
&mut self,
|
||||
changeset: GridBlockRevisionChangeset,
|
||||
changeset: GridBlockMetaRevisionChangeset,
|
||||
) -> CollaborateResult<Option<GridChangeset>> {
|
||||
let block_id = changeset.block_id.clone();
|
||||
self.modify_block(&block_id, |block| {
|
||||
@ -329,8 +330,13 @@ impl GridRevisionPad {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_grid_setting_rev(&self) -> GridSettingRevision {
|
||||
self.grid_rev.setting.clone()
|
||||
pub fn get_grid_setting_rev(&self) -> &GridSettingRevision {
|
||||
&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(
|
||||
@ -341,37 +347,78 @@ impl GridRevisionPad {
|
||||
let mut is_changed = None;
|
||||
let layout_rev: GridLayoutRevision = changeset.layout_type.into();
|
||||
|
||||
if let Some(filter) = changeset.filter {
|
||||
grid_rev.setting.filter.insert(
|
||||
layout_rev.clone(),
|
||||
GridFilterRevision {
|
||||
field_id: filter.field_id,
|
||||
},
|
||||
);
|
||||
if let Some(params) = changeset.insert_filter {
|
||||
let rev = GridFilterRevision {
|
||||
id: gen_grid_filter_id(),
|
||||
field_id: params.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);
|
||||
|
||||
if let Some(group) = changeset.group {
|
||||
grid_rev.setting.group.insert(
|
||||
layout_rev.clone(),
|
||||
GridGroupRevision {
|
||||
group_field_id: group.group_field_id,
|
||||
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 {
|
||||
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(())
|
||||
}
|
||||
|
||||
if let Some(delete_sort_id) = changeset.delete_sort {
|
||||
match grid_rev.setting.sorts.get_mut(&layout_rev) {
|
||||
Some(sorts) => sorts.retain(|sort| sort.id != delete_sort_id),
|
||||
None => {
|
||||
tracing::warn!("Can't find the sort with {:?}", layout_rev);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(is_changed)
|
||||
})
|
||||
}
|
||||
@ -415,7 +462,7 @@ impl GridRevisionPad {
|
||||
|
||||
fn modify_block<F>(&mut self, block_id: &str, f: F) -> CollaborateResult<Option<GridChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut GridBlockRevision) -> CollaborateResult<Option<()>>,
|
||||
F: FnOnce(&mut GridBlockMetaRevision) -> CollaborateResult<Option<()>>,
|
||||
{
|
||||
self.modify_grid(
|
||||
|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);
|
||||
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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -20,10 +20,12 @@ pub struct AppearanceSettings {
|
||||
pub locale: LocaleSettings,
|
||||
|
||||
#[pb(index = 3)]
|
||||
#[serde(default = "reset_default_value")]
|
||||
#[serde(default = "DEFAULT_RESET_VALUE")]
|
||||
pub reset_as_default: bool,
|
||||
}
|
||||
|
||||
const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT;
|
||||
|
||||
#[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct LocaleSettings {
|
||||
#[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_RESET_AS_DEFAULT: bool = true;
|
||||
const APPEARANCE_RESET_AS_DEFAULT: bool = true;
|
||||
|
||||
impl std::default::Default for AppearanceSettings {
|
||||
fn default() -> Self {
|
||||
|
Loading…
Reference in New Issue
Block a user