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

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

View File

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

View File

@ -2,21 +2,21 @@ part of 'cell_service.dart';
typedef GridCellMap = LinkedHashMap<String, GridCell>;
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 {

View File

@ -19,10 +19,10 @@ import 'package:app_flowy/workspace/application/grid/cell/select_option_service.
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import '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

View File

@ -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));
});
});
}

View File

@ -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

View File

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

View File

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

View File

@ -10,7 +10,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
import 'package: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,32 +37,23 @@ class GridRowCache {
);
},
),
_rowsListener = GridRowListener(gridId: gridId),
_fieldDelegate = fieldDelegate {
//
fieldDelegate.onFieldChanged(() => _rowsNotifier.fieldDidChange());
// listen on the row update
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold(
(changesets) {
for (final changeset in changesets) {
_rowsNotifier.deleteRows(changeset.deletedRows);
_rowsNotifier.insertRows(changeset.insertedRows);
_rowsNotifier.updateRows(changeset.updatedRows);
}
},
(err) => Log.error(err),
);
});
_rowsListener.start();
}
Future<void> dispose() async {
await _rowsListener.stop();
_rowsNotifier.dispose();
}
void applyChangesets(List<GridRowsChangeset> changesets) {
for (final changeset in changesets) {
_rowsNotifier.deleteRows(changeset.deletedRows);
_rowsNotifier.insertRows(changeset.insertedRows);
_rowsNotifier.updateRows(changeset.updatedRows);
}
}
void addListener({
void Function(List<GridRow>, GridRowChangeReason)? onChanged,
bool Function()? listenWhen,
@ -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;

View File

@ -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",

View File

@ -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" }

View File

@ -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,

View File

@ -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(&params.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(&params.grid_id)?;
match editor.get_row(&params.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(&params.row_id).await?,
};
data_result(row)
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]

View File

@ -8,10 +8,10 @@ use strum_macros::Display;
pub fn create(grid_manager: Arc<GridManager>) -> Module {
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")]

View File

@ -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)
}

View File

@ -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)

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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))

View File

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

View File

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

View File

@ -1,10 +1,12 @@
use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::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(&params.field_id).await?;
let row_rev = self.block_manager.get_row_rev(&params.row_id).await.ok()??;
make_cell(&params.field_id, &field_rev, &row_rev)
let cell_rev = row_rev.cells.get(&params.field_id)?.clone();
let data = decode_cell_data(cell_rev.data, &field_rev).data;
Some(Cell::new(&params.field_id, data))
}
pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> {
@ -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()),
}

View File

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

View File

@ -3,6 +3,11 @@ mod util;
mod block_manager;
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;

View File

@ -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>,

View File

@ -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,
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
use crate::grid::script::EditorScript::*;
use crate::grid::script::*;
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 {

View File

@ -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;

View File

@ -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};

View File

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

View File

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

View File

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

View File

@ -1,23 +1,24 @@
use crate::grid::field_util::*;
use crate::grid::row_util::GridRowTestBuilder;
use crate::grid::script::EditorScript::*;
use crate::grid::script::*;
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;
}

View File

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

View File

@ -1,7 +1,9 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
use bytes::Bytes;
use 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 text_field(&self) -> &FieldRevision {
self.field_revs
.iter()
.filter(|field_rev| field_rev.field_type == FieldType::RichText)
.collect::<Vec<_>>()
.pop()
.unwrap()
}
}
pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {
let field_rev = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.build();
let cloned_field_rev = field_rev.clone();
let type_option_data = field_rev
.get_type_option_entry::<RichTextTypeOption>(&field_rev.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_rev.id,
name: field_rev.name,
desc: field_rev.desc,
field_type: field_rev.field_type,
frozen: field_rev.frozen,
visibility: field_rev.visibility,
width: field_rev.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_rev)
}
pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {
let single_select = SingleSelectTypeOptionBuilder::default()
.option(SelectOption::new("Done"))
.option(SelectOption::new("Progress"));
let field_rev = FieldBuilder::new(single_select).name("Name").visibility(true).build();
let cloned_field_rev = field_rev.clone();
let type_option_data = field_rev
.get_type_option_entry::<SingleSelectTypeOption>(&field_rev.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_rev.id,
name: field_rev.name,
desc: field_rev.desc,
field_type: field_rev.field_type,
frozen: field_rev.frozen,
visibility: field_rev.visibility,
width: field_rev.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_rev)
}
fn make_test_grid() -> BuildGridContext {
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()
}

View File

@ -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(())
})
}

View File

@ -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
}

View File

@ -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",

View File

@ -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" }

View File

@ -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 {

View File

@ -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"

View File

@ -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,
})
}
}

View File

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

View File

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

View File

@ -1,4 +1,9 @@
use crate::parser::{NotEmptyStr, ViewFilterParser, ViewGroupParser, ViewSortParser};
use crate::entities::{
CreateGridFilterParams, CreateGridFilterPayload, CreateGridGroupParams, CreateGridGroupPayload,
CreateGridSortParams, CreateGridSortPayload, RepeatedGridFilter, RepeatedGridGroup, RepeatedGridSort,
};
use crate::parser::NotEmptyStr;
use crate::revision::{GridLayoutRevision, GridSettingRevision};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_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>,
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSort {
#[pb(index = 1, one_of)]
pub 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(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,
})
}
}

View File

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

View File

@ -1,7 +1,13 @@
mod field;
mod 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::*;

View File

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

View File

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

View File

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

View File

@ -30,9 +30,9 @@ pub fn gen_field_id() -> String {
pub struct GridRevision {
pub 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 {

View File

@ -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 }
}
}

View File

@ -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 }
}
}

View File

@ -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();
}
}

View File

@ -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);
is_changed = Some(())
}
if let Some(delete_group_id) = changeset.delete_group {
match grid_rev.setting.groups.get_mut(&layout_rev) {
Some(groups) => groups.retain(|group| group.id != delete_group_id),
None => {
tracing::warn!("Can't find the group with {:?}", layout_rev);
}
}
}
if let Some(sort) = changeset.insert_sort {
let rev = GridSortRevision {
id: gen_grid_sort_id(),
field_id: sort.field_id,
};
grid_rev
.setting
.sorts
.entry(layout_rev.clone())
.or_insert_with(std::vec::Vec::new)
.push(rev);
is_changed = Some(())
}
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(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);
}
}
}
if let Some(sort) = changeset.sort {
grid_rev.setting.sort.insert(
layout_rev,
GridSortRevision {
field_id: sort.field_id,
},
);
is_changed = Some(())
}
Ok(is_changed)
})
}
@ -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)
}
},
)
}

View File

@ -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 {