diff --git a/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart new file mode 100644 index 0000000000..d4895a652f --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart @@ -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 _listeners = LinkedHashMap(); + GridBlockCache({required this.gridId}); + + void start(void Function(GridBlockUpdateNotifierValue) onBlockChanged) { + _onBlockChanged = onBlockChanged; + for (final listener in _listeners.values) { + listener.start(onBlockChanged); + } + } + + Future 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, FlowyError>; + +class _GridBlockListener { + final String blockId; + PublishNotifier? _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 result) { + switch (ty) { + case GridNotification.DidUpdateGridBlock: + result.fold( + (payload) => _rowsUpdateNotifier?.value = left([GridRowsChangeset.fromBuffer(payload)]), + (error) => _rowsUpdateNotifier?.value = right(error), + ); + break; + + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _rowsUpdateNotifier?.dispose(); + _rowsUpdateNotifier = null; + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart b/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart similarity index 77% rename from frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart rename to frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart index 5e09573506..fb7ce5734a 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_cache.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_cache.dart @@ -2,21 +2,21 @@ part of 'cell_service.dart'; typedef GridCellMap = LinkedHashMap; -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 item) { + void insert(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(GridCellCacheKey key) { + T? get(_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 { diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart similarity index 100% rename from frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart rename to frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart similarity index 100% rename from frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_persistence.dart rename to frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index 9283c0d22b..5425542b44 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -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 diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index 3041c563d9..6b97bc1456 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -100,7 +100,7 @@ class GridCellContextBuilder { class _GridCellContext extends Equatable { final GridCell gridCell; final GridCellCache cellCache; - final GridCellCacheKey _cacheKey; + final _GridCellCacheKey _cacheKey; final IGridCellDataLoader cellDataLoader; final _GridCellDataPersistence cellDataPersistence; final FieldService _fieldService; @@ -118,7 +118,7 @@ class _GridCellContext 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 clone() { return _GridCellContext( @@ -213,7 +213,7 @@ class _GridCellContext 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)); }); }); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart index 16b4beb041..bc61e5828a 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart @@ -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 { 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 { fieldDelegate: GridCellCacheDelegateImpl(fieldCache), ); + blockCache.start((result) { + result.fold( + (changesets) => rowCache.applyChangesets(changesets), + (err) => Log.error(err), + ); + }); + on( (event, emit) async { await event.when( @@ -60,6 +72,7 @@ class GridBloc extends Bloc { await cellCache.dispose(); await rowCache.dispose(); await fieldCache.dispose(); + await blockCache.dispose(); return super.close(); } @@ -79,7 +92,15 @@ class GridBloc extends Bloc { 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 { () => 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 _fields; - const GridFieldEquatable(List fields) : _fields = fields; @override diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_listener.dart deleted file mode 100644 index e6c7dc9de7..0000000000 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_listener.dart +++ /dev/null @@ -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, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null); - GridNotificationListener? _listener; - - GridRowListener({required this.gridId}); - - void start() { - _listener = GridNotificationListener( - objectId: gridId, - handler: _handler, - ); - } - - void _handler(GridNotification ty, Either result) { - switch (ty) { - case GridNotification.DidUpdateGridRow: - result.fold( - (payload) => rowsUpdateNotifier.value = left([GridRowsChangeset.fromBuffer(payload)]), - (error) => rowsUpdateNotifier.value = right(error), - ); - break; - - default: - break; - } - } - - Future stop() async { - await _listener?.stop(); - rowsUpdateNotifier.dispose(); - } -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart index 3380ad4349..984b50d0a2 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart @@ -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> createRow({Option? startRowId}) { diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart index 64fa164251..ed60c63c13 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart @@ -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 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 dispose() async { - await _rowsListener.stop(); _rowsNotifier.dispose(); } + void applyChangesets(List changesets) { + for (final changeset in changesets) { + _rowsNotifier.deleteRows(changeset.deletedRows); + _rowsNotifier.insertRows(changeset.insertedRows); + _rowsNotifier.updateRows(changeset.updatedRows); + } + } + void addListener({ void Function(List, GridRowChangeReason)? onChanged, bool Function()? listenWhen, @@ -130,9 +119,8 @@ class GridRowCache { return _makeGridCells(rowId, data); } - void resetRows(List blocks) { - final rowOrders = blocks.expand((block) => block.rowOrders).toList(); - _rowsNotifier.reset(rowOrders); + void initialRows(List rowOrders) { + _rowsNotifier.initialRows(rowOrders); } Future _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 rowOrders) { + List get clonedRows => [..._rows]; + + void initialRows(List 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 createdRows) { - if (createdRows.isEmpty) { + void insertRows(List insertRows) { + if (insertRows.isEmpty) { return; } InsertedIndexs insertIndexs = []; final List 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 get clonedRows => [..._rows]; } class RowService { @@ -310,7 +301,7 @@ class RowService { return GridEventMoveItem(payload).send(); } - Future> getRow() { + Future> getRow() { final payload = RowIdentifierPayload.create() ..gridId = gridId ..rowId = rowId; diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 68bc48d2d7..aa7cd8aef7 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -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", diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index 1cf38dbb3f..ba3702038e 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -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" } diff --git a/frontend/rust-lib/flowy-grid/src/dart_notification.rs b/frontend/rust-lib/flowy-grid/src/dart_notification.rs index 92749da519..202b12eb81 100644 --- a/frontend/rust-lib/flowy-grid/src/dart_notification.rs +++ b/frontend/rust-lib/flowy-grid/src/dart_notification.rs @@ -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, diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 79181732d7..9a121e58bd 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -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, manager: AppData>, ) -> DataResult { @@ -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 { let params: QueryGridBlocksParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; - let block_ids = params - .block_orders - .into_iter() - .map(|block| block.block_id) - .collect::>(); - 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, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: RowIdentifier = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; - match editor.get_row(¶ms.row_id).await? { - None => Err(FlowyError::record_not_found().context("Can not find the row")), - Some(row) => data_result(row), - } + let row = OptionalRow { + row: editor.get_row(¶ms.row_id).await?, + }; + data_result(row) } #[tracing::instrument(level = "debug", skip(data, manager), err)] diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index 166ac9eea3..f697f1e8d9 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -8,10 +8,10 @@ use strum_macros::Display; pub fn create(grid_manager: Arc) -> 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) -> 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")] diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index 1930417fc4..28fa7b0c9e 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -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; @@ -19,12 +21,15 @@ pub trait GridUser: Send + Sync { fn db_pool(&self) -> Result, FlowyError>; } +pub type GridTaskSchedulerRwLock = Arc>; + pub struct GridManager { - editor_map: Arc>>, + grid_editors: Arc>>, grid_user: Arc, block_index_cache: Arc, #[allow(dead_code)] kv_persistence: Arc, + 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>(&self, grid_id: T) -> FlowyResult<()> { + pub async fn close_grid>(&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>(&self, grid_id: T) -> FlowyResult<()> { + pub async fn delete_grid>(&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> { - 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> { - 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, 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, build_context: BuildGridContext, ) -> FlowyResult { - 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) } diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs index 462f25e041..a78f6fa37b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs @@ -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, persistence: Arc, @@ -29,10 +30,10 @@ impl GridBlockManager { pub(crate) async fn new( grid_id: &str, user: &Arc, - blocks: Vec, + block_meta_revs: Vec>, persistence: Arc, ) -> FlowyResult { - 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>, - ) -> FlowyResult> { + ) -> FlowyResult> { 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) -> FlowyResult> { + pub(crate) async fn delete_rows( + &self, + row_orders: Vec, + ) -> FlowyResult> { 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::>>(); 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, - blocks: Vec, + block_meta_revs: Vec>, ) -> FlowyResult>> { 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) diff --git a/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs index e7f4c14d32..6546b71b2e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs @@ -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, + prev_row_id: Option, ) -> FlowyResult<(i32, Option)> { 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() { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs index 22e73b2ad4..7558b0a010 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs @@ -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); } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs index ef353f5035..0fb2d4e48d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs @@ -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 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)) diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs new file mode 100644 index 0000000000..c22c154600 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs @@ -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>, + #[allow(dead_code)] + block_manager: Arc, +} +impl GridFilterService { + pub fn new( + grid_pad: Arc>, + block_manager: Arc, + 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 => {} + // }, + // }); + // } + // } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs new file mode 100644 index 0000000000..647885e527 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs @@ -0,0 +1,3 @@ +mod filter_service; + +pub(crate) use filter_service::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index b7392adb6e..97e36566b4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -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, grid_pad: Arc>, rev_manager: Arc, block_manager: Arc, + #[allow(dead_code)] + pub(crate) filter_service: Arc, } impl Drop for GridRevisionEditor { fn drop(&mut self) { - tracing::trace!("Drop GridMetaEditor"); + tracing::trace!("Drop GridRevisionEditor"); } } @@ -40,22 +44,30 @@ impl GridRevisionEditor { user: Arc, mut rev_manager: RevisionManager, persistence: Arc, + task_scheduler: GridTaskSchedulerRwLock, ) -> FlowyResult> { let token = user.token()?; let cloud = Arc::new(GridRevisionCloudService { token }); let grid_pad = rev_manager.load::(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 { let field_rev = self.get_field_rev(¶ms.field_id).await?; let row_rev = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??; - make_cell(¶ms.field_id, &field_rev, &row_rev) + + let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone(); + let data = decode_cell_data(cell_rev.data, &field_rev).data; + Some(Cell::new(¶ms.field_id, data)) } pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult> { @@ -405,9 +420,9 @@ impl GridRevisionEditor { make_grid_blocks(block_ids, block_snapshots) } - pub async fn get_block_metas(&self) -> FlowyResult> { - let grid_blocks = self.grid_pad.read().await.get_block_revs(); - Ok(grid_blocks) + pub async fn get_block_meta_revs(&self) -> FlowyResult>> { + 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) -> 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 { - 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 { + 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> { + 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::>()), + 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::>(), Some(block_ids) => block_ids, }; @@ -507,8 +536,8 @@ impl GridRevisionEditor { pub async fn duplicate_grid(&self) -> FlowyResult { 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 { - 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()), } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs new file mode 100644 index 0000000000..965cc0839e --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs @@ -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(()) + }) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index c627eba373..e051544839 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -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; diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs index b1bde07d89..aaa7300046 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs @@ -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>( Ok(TypeOptionCellData::new(s, field_rev.field_type.clone()).json()) } -pub fn decode_cell_data_from_type_option_cell_data>( - data: T, - field_rev: &FieldRevision, - field_type: &FieldType, -) -> DecodedCellData { +pub fn decode_cell_data>(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>( +pub fn try_decode_cell_data>( encoded_data: T, + field_rev: &FieldRevision, s_field_type: &FieldType, t_field_type: &FieldType, - field_rev: &FieldRevision, ) -> FlowyResult { 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, diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index 07bd3c1bf7..1c88442aac 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -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>, } -pub(crate) fn group_row_orders(row_orders: Vec) -> Vec { - let mut map: HashMap = HashMap::new(); +pub(crate) fn block_from_row_orders(row_orders: Vec) -> Vec { + let mut map: HashMap = 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::>() } - -#[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 { - 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]) -> Vec { row_revs.iter().map(RowOrder::from).collect::>() @@ -49,23 +42,22 @@ pub(crate) fn make_row_from_row_rev(fields: &[FieldRevision], row_rev: Arc]) -> Vec { - let field_rev_map = fields - .iter() - .map(|field_rev| (&field_rev.id, field_rev)) - .collect::>(); +pub(crate) fn make_rows_from_row_revs(_fields: &[FieldRevision], row_revs: &[Arc]) -> Vec { + // let field_rev_map = fields + // .iter() + // .map(|field_rev| (&field_rev.id, field_rev)) + // .collect::>(); let make_row = |row_rev: &Arc| { - 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::>(); + // 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::>(); Row { id: row_rev.id.clone(), - cell_by_field_id, height: row_rev.height, } }; diff --git a/frontend/rust-lib/flowy-grid/src/services/setting/mod.rs b/frontend/rust-lib/flowy-grid/src/services/setting/mod.rs new file mode 100644 index 0000000000..229b814e25 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/setting/mod.rs @@ -0,0 +1,3 @@ +mod setting_builder; + +pub use setting_builder::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs new file mode 100644 index 0000000000..7edb779d15 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs @@ -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 + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs b/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs new file mode 100644 index 0000000000..9c00ccc85f --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs @@ -0,0 +1,3 @@ +mod snapshot_service; + +pub use snapshot_service::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs b/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs new file mode 100644 index 0000000000..2a47b70f48 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs @@ -0,0 +1,7 @@ +// pub struct GridSnapshotService {} +// +// impl GridSnapshotService { +// pub fn new() -> Self { +// Self {} +// } +// } diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs new file mode 100644 index 0000000000..e91691cbd7 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs @@ -0,0 +1,9 @@ +mod queue; +mod runner; +mod scheduler; +mod store; +mod task; + +pub use queue::TaskHandlerId; +pub use scheduler::*; +pub use task::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs new file mode 100644 index 0000000000..2c86d6606c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs @@ -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>>, + queue: BinaryHeap>>, +} + +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(&mut self, mut f: F) -> Option + where + F: FnMut(&mut TaskList) -> Option, + { + 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, +} + +impl Deref for TaskList { + type Target = BinaryHeap; + + 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 { + Some(self.cmp(other)) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs new file mode 100644 index 0000000000..713fc15c86 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs @@ -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>, + debounce_duration: Duration, + notifier: Option>, +} + +impl GridTaskRunner { + pub fn new( + scheduler: Arc>, + 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); + } + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs new file mode 100644 index 0000000000..f4fb460eeb --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs @@ -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>, +} + +impl GridTaskScheduler { + pub fn new() -> Arc> { + 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(&mut self, handler: Arc) + where + T: GridTaskHandler, + { + let handler_id = handler.handler_id().to_owned(); + self.handlers.insert(handler_id, handler); + } + + pub fn unregister_handler>(&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(()); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs new file mode 100644 index 0000000000..7f2fc8d695 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs @@ -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, + 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 { + 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) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs new file mode 100644 index 0000000000..00a6b158cc --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs @@ -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 { + 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!() + } +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test.rs index 308cf62773..51094e2b3f 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test.rs @@ -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 { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs index 29fecf8f29..225389bd6c 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs @@ -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; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs index 5fe5d599d6..cc2ee2ca87 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs @@ -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}; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs new file mode 100644 index 0000000000..0217621acf --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs @@ -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::(&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::(&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() +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs new file mode 100644 index 0000000000..cd47911f19 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs @@ -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() {} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/mod.rs index 1ca8ad3983..4d746661eb 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/mod.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/mod.rs @@ -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; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs index 735ed91202..04cff420c5 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs @@ -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::() .unwrap() .date, "2022/03/16", ); - let scripts = vec![CreateRow { context }]; + let scripts = vec![CreateRow { payload: context }]; test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/row_util.rs b/frontend/rust-lib/flowy-grid/tests/grid/row_util.rs new file mode 100644 index 0000000000..7fd6f043ce --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/row_util.rs @@ -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() + } +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index eb00e88e2b..c2686974e8 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -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, pub field_revs: Vec, - pub grid_block_revs: Vec, + pub block_meta_revs: Vec>, pub row_revs: Vec>, 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::(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 { + 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::>() + .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::(&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::(&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() -} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/setting_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/setting_test.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/frontend/rust-lib/flowy-grid/tests/grid/setting_test.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs index 3cee5cf60a..f1257dc3ad 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs @@ -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(()) }) } diff --git a/frontend/rust-lib/flowy-text-block/src/editor.rs b/frontend/rust-lib/flowy-text-block/src/editor.rs index c1e50536a5..87b4416ef0 100644 --- a/frontend/rust-lib/flowy-text-block/src/editor.rs +++ b/frontend/rust-lib/flowy-text-block/src/editor.rs @@ -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 } diff --git a/frontend/scripts/flowy-tool/src/proto/template/proto_file/struct_template.rs b/frontend/scripts/flowy-tool/src/proto/template/proto_file/struct_template.rs index 681824eff0..7f6c7072d2 100644 --- a/frontend/scripts/flowy-tool/src/proto/template/proto_file/struct_template.rs +++ b/frontend/scripts/flowy-tool/src/proto/template/proto_file/struct_template.rs @@ -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", diff --git a/frontend/scripts/makefile/tool.toml b/frontend/scripts/makefile/tool.toml index dc840272d5..f18bb8faa4 100644 --- a/frontend/scripts/makefile/tool.toml +++ b/frontend/scripts/makefile/tool.toml @@ -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" } diff --git a/shared-lib/flowy-folder-data-model/src/revision/view_rev.rs b/shared-lib/flowy-folder-data-model/src/revision/view_rev.rs index 7d78e555f6..72ab0079b5 100644 --- a/shared-lib/flowy-folder-data-model/src/revision/view_rev.rs +++ b/shared-lib/flowy-folder-data-model/src/revision/view_rev.rs @@ -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 for View { fn from(view_serde: ViewRevision) -> Self { diff --git a/shared-lib/flowy-grid-data-model/Cargo.toml b/shared-lib/flowy-grid-data-model/Cargo.toml index 7998b6123d..12356183d0 100644 --- a/shared-lib/flowy-grid-data-model/Cargo.toml +++ b/shared-lib/flowy-grid-data-model/Cargo.toml @@ -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" diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index dd5cf40e3b..52a4be6e8f 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -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, #[pb(index = 3)] - pub block_orders: Vec, + pub blocks: Vec, } #[derive(Debug, Default, Clone, ProtoBuf)] @@ -36,12 +34,15 @@ pub struct Row { pub id: String, #[pb(index = 2)] - pub cell_by_field_id: HashMap, - - #[pb(index = 3)] pub height: i32, } +#[derive(Debug, Default, ProtoBuf)] +pub struct OptionalRow { + #[pb(index = 1, one_of)] + pub row: Option, +} + #[derive(Debug, Default, ProtoBuf)] pub struct RepeatedRow { #[pb(index = 1)] @@ -66,24 +67,6 @@ impl std::convert::From> for RepeatedGridBlock { } } -#[derive(Debug, Clone, Default, ProtoBuf)] -pub struct GridBlockOrder { - #[pb(index = 1)] - pub block_id: String, - - #[pb(index = 2)] - pub row_orders: Vec, -} - -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, + pub block_ids: Vec, } pub struct QueryGridBlocksParams { pub grid_id: String, - pub block_orders: Vec, + pub block_ids: Vec, } impl TryInto for QueryGridBlocksPayload { @@ -320,7 +303,7 @@ impl TryInto 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, }) } } diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid_filter.rs b/shared-lib/flowy-grid-data-model/src/entities/grid_filter.rs new file mode 100644 index 0000000000..b6eda47d1e --- /dev/null +++ b/shared-lib/flowy-grid-data-model/src/entities/grid_filter.rs @@ -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, +} + +impl std::convert::From<&GridFilterRevision> for GridFilter { + fn from(rev: &GridFilterRevision) -> Self { + Self { id: rev.id.clone() } + } +} + +impl std::convert::From<&Vec> for RepeatedGridFilter { + fn from(revs: &Vec) -> Self { + RepeatedGridFilter { + items: revs.iter().map(|rev| rev.into()).collect(), + } + } +} + +impl std::convert::From> for RepeatedGridFilter { + fn from(items: Vec) -> 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, +} + +impl CreateGridFilterPayload { + #[allow(dead_code)] + pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> 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, +} + +impl TryInto for CreateGridFilterPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + 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, +} + +#[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 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 for TextFilterCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + 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 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, +} + +#[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 for i32 { + fn from(value: NumberFilterCondition) -> Self { + value as i32 + } +} +impl std::convert::TryFrom for NumberFilterCondition { + type Error = ErrorCode; + + fn try_from(n: u8) -> Result { + 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 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, +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum SelectOptionCondition { + OptionIs = 0, + OptionIsNot = 1, + OptionIsEmpty = 2, + OptionIsNotEmpty = 3, +} + +impl std::convert::From 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 for SelectOptionCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + 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 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, +} + +#[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 for DateFilterCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + 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 for GridDateFilter { + fn from(rev: GridFilterRevision) -> Self { + GridDateFilter { + condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs), + content: rev.content, + } + } +} diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid_group.rs b/shared-lib/flowy-grid-data-model/src/entities/grid_group.rs new file mode 100644 index 0000000000..c12a0b6522 --- /dev/null +++ b/shared-lib/flowy-grid-data-model/src/entities/grid_group.rs @@ -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, + + #[pb(index = 3, one_of)] + pub sub_group_field_id: Option, +} + +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, +} + +impl std::convert::From> for RepeatedGridGroup { + fn from(items: Vec) -> Self { + Self { items } + } +} + +impl std::convert::From<&Vec> for RepeatedGridGroup { + fn from(revs: &Vec) -> 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, + + #[pb(index = 2, one_of)] + pub sub_field_id: Option, +} + +pub struct CreateGridGroupParams { + pub field_id: Option, + pub sub_field_id: Option, +} + +impl TryInto for CreateGridGroupPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + 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 }) + } +} diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid_setting.rs b/shared-lib/flowy-grid-data-model/src/entities/grid_setting.rs index ef96bf0714..130f267c98 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid_setting.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid_setting.rs @@ -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, + pub filters_by_layout_ty: HashMap, #[pb(index = 2)] - pub group: HashMap, + pub groups_by_layout_ty: HashMap, #[pb(index = 3)] - pub sort: HashMap, + pub sorts_by_layout_ty: HashMap, +} + +impl std::convert::From<&GridSettingRevision> for GridSetting { + fn from(rev: &GridSettingRevision) -> Self { + let filters_by_layout_ty: HashMap = rev + .filters + .iter() + .map(|(layout_rev, filter_revs)| (layout_rev.to_string(), filter_revs.into())) + .collect(); + + let groups_by_layout_ty: HashMap = rev + .groups + .iter() + .map(|(layout_rev, group_revs)| (layout_rev.to_string(), group_revs.into())) + .collect(); + + let sorts_by_layout_ty: HashMap = 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, +impl std::convert::From 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, - - #[pb(index = 2, one_of)] - pub sub_group_field_id: Option, -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridSort { - #[pb(index = 1, one_of)] - pub field_id: Option, +impl std::convert::From 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, + pub insert_filter: Option, #[pb(index = 4, one_of)] - pub group: Option, + pub delete_filter: Option, #[pb(index = 5, one_of)] - pub sort: Option, + pub insert_group: Option, + + #[pb(index = 6, one_of)] + pub delete_group: Option, + + #[pb(index = 7, one_of)] + pub insert_sort: Option, + + #[pb(index = 8, one_of)] + pub delete_sort: Option, } pub struct GridSettingChangesetParams { pub grid_id: String, pub layout_type: GridLayoutType, - pub filter: Option, - pub group: Option, - pub sort: Option, + pub insert_filter: Option, + pub delete_filter: Option, + pub insert_group: Option, + pub delete_group: Option, + pub insert_sort: Option, + pub delete_sort: Option, +} + +impl GridSettingChangesetParams { + pub fn is_filter_changed(&self) -> bool { + self.insert_filter.is_some() || self.delete_filter.is_some() + } } impl TryInto for GridSettingChangesetPayload { @@ -84,27 +132,45 @@ impl TryInto 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, }) } } diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid_sort.rs b/shared-lib/flowy-grid-data-model/src/entities/grid_sort.rs new file mode 100644 index 0000000000..4ebadb7036 --- /dev/null +++ b/shared-lib/flowy-grid-data-model/src/entities/grid_sort.rs @@ -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, +} + +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, +} + +impl std::convert::From<&Vec> for RepeatedGridSort { + fn from(revs: &Vec) -> Self { + RepeatedGridSort { + items: revs.iter().map(|rev| rev.into()).collect(), + } + } +} + +impl std::convert::From> for RepeatedGridSort { + fn from(items: Vec) -> Self { + Self { items } + } +} + +#[derive(ProtoBuf, Debug, Default, Clone)] +pub struct CreateGridSortPayload { + #[pb(index = 1, one_of)] + pub field_id: Option, +} + +pub struct CreateGridSortParams { + pub field_id: Option, +} + +impl TryInto for CreateGridSortPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + 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 }) + } +} diff --git a/shared-lib/flowy-grid-data-model/src/entities/mod.rs b/shared-lib/flowy-grid-data-model/src/entities/mod.rs index 535e2142d8..e6a16d8026 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/mod.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/mod.rs @@ -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::*; diff --git a/shared-lib/flowy-grid-data-model/src/parser/grid_info_parser.rs b/shared-lib/flowy-grid-data-model/src/parser/grid_info_parser.rs deleted file mode 100644 index 1d14582ac0..0000000000 --- a/shared-lib/flowy-grid-data-model/src/parser/grid_info_parser.rs +++ /dev/null @@ -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 { - 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 { - 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 { - 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 }) - } -} diff --git a/shared-lib/flowy-grid-data-model/src/parser/mod.rs b/shared-lib/flowy-grid-data-model/src/parser/mod.rs index 270d4ca043..8a9739e5b3 100644 --- a/shared-lib/flowy-grid-data-model/src/parser/mod.rs +++ b/shared-lib/flowy-grid-data-model/src/parser/mod.rs @@ -1,5 +1,2 @@ -mod grid_info_parser; mod str_parser; - -pub use grid_info_parser::*; pub use str_parser::*; diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_filter_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_filter_rev.rs new file mode 100644 index 0000000000..540a67b9ea --- /dev/null +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_filter_rev.rs @@ -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, +} + +#[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 { + 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 { + 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 { + let rev = serde_json::from_str(s)?; + Ok(rev) + } +} diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs index 310a28b843..f083e6fb49 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs @@ -30,9 +30,9 @@ pub fn gen_field_id() -> String { pub struct GridRevision { pub grid_id: String, pub fields: Vec, - pub blocks: Vec, + pub blocks: Vec>, - #[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, pub row_count: Option, } -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, + pub rows: Vec>, } #[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)] @@ -118,13 +127,11 @@ pub struct FieldRevision { #[serde(with = "indexmap::serde_seq")] pub type_options: IndexMap, - #[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, - pub blocks: Vec, - pub blocks_meta_data: Vec, + pub blocks: Vec, + pub blocks_meta_data: Vec, } impl BuildGridContext { diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs index fd251f3c04..146d15d069 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs @@ -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, + pub layout: GridLayoutRevision, #[serde(with = "indexmap::serde_seq")] - pub group: IndexMap, + pub filters: IndexMap>, - #[serde(with = "indexmap::serde_seq")] - pub sort: IndexMap, + #[serde(skip, with = "indexmap::serde_seq")] + pub groups: IndexMap>, + + #[serde(skip, with = "indexmap::serde_seq")] + pub sorts: IndexMap>, } #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)] @@ -36,81 +49,23 @@ impl std::default::Default for GridLayoutRevision { } } -impl std::convert::From for GridLayoutType { - fn from(rev: GridLayoutRevision) -> Self { - match rev { - GridLayoutRevision::Table => GridLayoutType::Table, - GridLayoutRevision::Board => GridLayoutType::Board, - } - } -} - -impl std::convert::From 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, + pub id: String, + pub field_id: String, + pub condition: u8, + pub content: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] pub struct GridGroupRevision { - pub group_field_id: Option, - pub sub_group_field_id: Option, + pub id: String, + pub field_id: Option, + pub sub_field_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] pub struct GridSortRevision { + pub id: String, pub field_id: Option, } - -impl std::convert::From for GridFilter { - fn from(rev: GridFilterRevision) -> Self { - GridFilter { field_id: rev.field_id } - } -} - -impl std::convert::From 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 for GridSort { - fn from(rev: GridSortRevision) -> Self { - GridSort { field_id: rev.field_id } - } -} - -impl std::convert::From for GridSetting { - fn from(rev: GridSettingRevision) -> Self { - let filter: HashMap = rev - .filter - .into_iter() - .map(|(layout_rev, filter_rev)| (layout_rev.to_string(), filter_rev.into())) - .collect(); - - let group: HashMap = rev - .group - .into_iter() - .map(|(layout_rev, group_rev)| (layout_rev.to_string(), group_rev.into())) - .collect(); - - let sort: HashMap = rev - .sort - .into_iter() - .map(|(layout_rev, sort_rev)| (layout_rev.to_string(), sort_rev.into())) - .collect(); - - GridSetting { filter, group, sort } - } -} diff --git a/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs index af187d5c37..8ee18309ad 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs @@ -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>, - - #[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::>(); - GridBlockRevisionData { + .collect::>>(); + GridBlockRevision { block_id: duplicated_block_id.to_string(), rows: duplicated_rows, } @@ -43,18 +47,12 @@ impl GridBlockRevisionPad { pub fn from_delta(delta: GridBlockRevisionDelta) -> CollaborateResult { 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::>>(); - Ok(Self { block_id, rows, delta }) + Ok(Self { block_revision, delta }) } pub fn from_revisions(_grid_id: &str, revisions: Vec) -> CollaborateResult { @@ -95,9 +93,10 @@ impl GridBlockRevisionPad { T: AsRef + 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 { - 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>) -> CollaborateResult>, { 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 { - 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::>(), - delta, - } + let delta = make_block_meta_delta(&block_revision); + GridBlockRevisionPad { block_revision, delta } } } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs index ade4ddfa53..d539335ee7 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs @@ -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(); } } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs index 986ce35045..2ff58582f4 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs @@ -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, Vec) { + pub async fn duplicate_grid_block_meta(&self) -> (Vec, Vec) { 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::>(); + .collect::>(); (fields, blocks) } @@ -280,14 +281,14 @@ impl GridRevisionPad { } } - pub fn create_block_rev(&mut self, block: GridBlockRevision) -> CollaborateResult> { + pub fn create_block_meta_rev(&mut self, block: GridBlockMetaRevision) -> CollaborateResult> { 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 { + pub fn get_block_meta_revs(&self) -> Vec> { self.grid_rev.blocks.clone() } pub fn update_block_rev( &mut self, - changeset: GridBlockRevisionChangeset, + changeset: GridBlockMetaRevisionChangeset, ) -> CollaborateResult> { 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> { + 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(&mut self, block_id: &str, f: F) -> CollaborateResult> where - F: FnOnce(&mut GridBlockRevision) -> CollaborateResult>, + F: FnOnce(&mut GridBlockMetaRevision) -> CollaborateResult>, { 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) + } }, ) } diff --git a/shared-lib/flowy-user-data-model/src/entities/user_setting.rs b/shared-lib/flowy-user-data-model/src/entities/user_setting.rs index f4543b6b5c..8f7beadb4a 100644 --- a/shared-lib/flowy-user-data-model/src/entities/user_setting.rs +++ b/shared-lib/flowy-user-data-model/src/entities/user_setting.rs @@ -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 {