Merge pull request #882 from AppFlowy-IO/feat/board_configuration

Feat/board configuration
This commit is contained in:
Nathan.fooo 2022-08-22 18:14:02 +08:00 committed by GitHub
commit 5e6b9496fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 2219 additions and 1466 deletions

View File

@ -29,7 +29,7 @@
"program": "./lib/main.dart",
"type": "dart",
"env": {
"RUST_LOG": "trace"
"RUST_LOG": "debug"
},
"cwd": "${workspaceRoot}/app_flowy"
},

View File

@ -23,7 +23,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
final BoardDataController _dataController;
late final AFBoardDataController afBoardDataController;
final MoveRowFFIService _rowService;
Map<String, GroupController> groupControllers = {};
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap.new();
GridFieldCache get fieldCache => _dataController.fieldCache;
String get gridId => _dataController.gridId;
@ -34,9 +34,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
super(BoardState.initial(view.id)) {
afBoardDataController = AFBoardDataController(
onMoveColumn: (
fromColumnId,
fromIndex,
toColumnId,
toIndex,
) {},
) {
_moveGroup(fromColumnId, toColumnId);
},
onMoveColumnItem: (
columnId,
fromIndex,
@ -44,7 +48,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
) {
final fromRow = groupControllers[columnId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[columnId]?.rowAtIndex(toIndex);
_moveRow(fromRow, toRow);
_moveRow(fromRow, columnId, toRow);
},
onMoveColumnItemToColumn: (
fromColumnId,
@ -54,7 +58,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
) {
final fromRow = groupControllers[fromColumnId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[toColumnId]?.rowAtIndex(toIndex);
_moveRow(fromRow, toRow);
_moveRow(fromRow, toColumnId, toRow);
},
);
@ -95,12 +99,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
);
}
void _moveRow(RowPB? fromRow, RowPB? toRow) {
if (fromRow != null && toRow != null) {
void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
if (fromRow != null) {
_rowService
.moveRow(
.moveGroupRow(
fromRowId: fromRow.id,
toRowId: toRow.id,
toGroupId: columnId,
toRowId: toRow?.id,
)
.then((result) {
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
@ -108,6 +113,17 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
}
}
void _moveGroup(String fromColumnId, String toColumnId) {
_rowService
.moveGroup(
fromGroupId: fromColumnId,
toGroupId: toColumnId,
)
.then((result) {
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
});
}
@override
Future<void> close() async {
await _dataController.dispose();

View File

@ -37,6 +37,14 @@ class GroupController {
(GroupRowsChangesetPB changeset) {
for (final insertedRow in changeset.insertedRows) {
final index = insertedRow.hasIndex() ? insertedRow.index : null;
if (insertedRow.hasIndex() &&
group.rows.length > insertedRow.index) {
group.rows.insert(insertedRow.index, insertedRow.row);
} else {
group.rows.add(insertedRow.row);
}
delegate.insertRow(
group.groupId,
insertedRow.row,
@ -45,10 +53,19 @@ class GroupController {
}
for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group.groupId, deletedRow);
}
for (final updatedRow in changeset.updatedRows) {
final index = group.rows.indexWhere(
(rowPB) => rowPB.id == updatedRow.id,
);
if (index != -1) {
group.rows[index] = updatedRow;
}
delegate.updateRow(group.groupId, updatedRow);
}
},

View File

@ -3,7 +3,6 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/board_card.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';

View File

@ -3,6 +3,7 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
class RowFFIService {
@ -68,4 +69,33 @@ class MoveRowFFIService {
return GridEventMoveRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveGroupRow({
required String fromRowId,
required String toGroupId,
required String? toRowId,
}) {
var payload = MoveGroupRowPayloadPB.create()
..viewId = gridId
..fromRowId = fromRowId
..toGroupId = toGroupId;
if (toRowId != null) {
payload.toRowId = toRowId;
}
return GridEventMoveGroupRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveGroup({
required String fromGroupId,
required String toGroupId,
}) {
final payload = MoveGroupPayloadPB.create()
..viewId = gridId
..fromGroupId = fromGroupId
..toGroupId = toGroupId;
return GridEventMoveGroup(payload).send();
}
}

View File

@ -10,7 +10,7 @@ class MultiBoardListExample extends StatefulWidget {
class _MultiBoardListExampleState extends State<MultiBoardListExample> {
final AFBoardDataController boardDataController = AFBoardDataController(
onMoveColumn: (fromIndex, toIndex) {
onMoveColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
debugPrint('Move column from $fromIndex to $toIndex');
},
onMoveColumnItem: (columnId, fromIndex, toIndex) {

View File

@ -145,7 +145,8 @@ class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
}) : _items = items;
/// Returns the readonly List<ColumnItem>
UnmodifiableListView<AFColumnItem> get items => UnmodifiableListView(_items);
UnmodifiableListView<AFColumnItem> get items =>
UnmodifiableListView([..._items]);
@override
List<Object?> get props => [id, ..._items];

View File

@ -8,7 +8,12 @@ import 'reorder_flex/reorder_flex.dart';
import 'package:flutter/material.dart';
import 'reorder_phantom/phantom_controller.dart';
typedef OnMoveColumn = void Function(int fromIndex, int toIndex);
typedef OnMoveColumn = void Function(
String fromColumnId,
int fromIndex,
String toColumnId,
int toIndex,
);
typedef OnMoveColumnItem = void Function(
String columnId,
@ -98,9 +103,11 @@ class AFBoardDataController extends ChangeNotifier
}
void moveColumn(int fromIndex, int toIndex, {bool notify = true}) {
final columnData = _columnDatas.removeAt(fromIndex);
_columnDatas.insert(toIndex, columnData);
onMoveColumn?.call(fromIndex, toIndex);
final toColumnData = _columnDatas[toIndex];
final fromColumnData = _columnDatas.removeAt(fromIndex);
_columnDatas.insert(toIndex, fromColumnData);
onMoveColumn?.call(fromColumnData.id, fromIndex, toColumnData.id, toIndex);
if (notify) notifyListeners();
}

View File

@ -66,6 +66,7 @@ impl FlowyError {
static_flowy_error!(user_not_exist, ErrorCode::UserNotExist);
static_flowy_error!(text_too_long, ErrorCode::TextTooLong);
static_flowy_error!(invalid_data, ErrorCode::InvalidData);
static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds);
}
impl std::convert::From<ErrorCode> for FlowyError {

View File

@ -11,7 +11,8 @@ pub enum GridNotification {
DidUpdateRow = 30,
DidUpdateCell = 40,
DidUpdateField = 50,
DidUpdateGroup = 60,
DidUpdateGroupView = 60,
DidUpdateGroup = 61,
}
impl std::default::Default for GridNotification {

View File

@ -2,7 +2,6 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision};
use flowy_sync::entities::grid::FieldChangesetParams;
use serde_repr::*;
use std::sync::Arc;
@ -491,6 +490,27 @@ impl TryInto<FieldChangesetParams> for FieldChangesetPayloadPB {
}
}
#[derive(Debug, Clone, Default)]
pub struct FieldChangesetParams {
pub field_id: String,
pub grid_id: String,
pub name: Option<String>,
pub desc: Option<String>,
pub field_type: Option<FieldTypeRevision>,
pub frozen: Option<bool>,
pub visibility: Option<bool>,
pub width: Option<i32>,
pub type_option_data: Option<Vec<u8>>,
}
#[derive(
Debug,
Clone,

View File

@ -5,8 +5,7 @@ use crate::entities::{
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::{FieldRevision, FilterConfigurationRevision};
use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams};
use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision};
use std::convert::TryInto;
use std::sync::Arc;
@ -72,6 +71,12 @@ impl TryInto<DeleteFilterParams> for DeleteFilterPayloadPB {
}
}
pub struct DeleteFilterParams {
pub field_id: String,
pub filter_id: String,
pub field_type_rev: FieldTypeRevision,
}
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct CreateGridFilterPayloadPB {
#[pb(index = 1)]
@ -99,10 +104,10 @@ impl CreateGridFilterPayloadPB {
}
}
impl TryInto<CreateGridFilterParams> for CreateGridFilterPayloadPB {
impl TryInto<CreateFilterParams> for CreateGridFilterPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateGridFilterParams, Self::Error> {
fn try_into(self) -> Result<CreateFilterParams, Self::Error> {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
@ -125,7 +130,7 @@ impl TryInto<CreateGridFilterParams> for CreateGridFilterPayloadPB {
}
}
Ok(CreateGridFilterParams {
Ok(CreateFilterParams {
field_id,
field_type_rev: self.field_type.into(),
condition,
@ -133,3 +138,10 @@ impl TryInto<CreateGridFilterParams> for CreateGridFilterPayloadPB {
})
}
}
pub struct CreateFilterParams {
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub condition: u8,
pub content: Option<String>,
}

View File

@ -123,3 +123,46 @@ impl TryInto<MoveRowParams> for MoveRowPayloadPB {
})
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct MoveGroupRowPayloadPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub from_row_id: String,
#[pb(index = 3)]
pub to_group_id: String,
#[pb(index = 4, one_of)]
pub to_row_id: Option<String>,
}
pub struct MoveGroupRowParams {
pub view_id: String,
pub from_row_id: String,
pub to_group_id: String,
pub to_row_id: Option<String>,
}
impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<MoveGroupRowParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::GridViewIdIsEmpty)?;
let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
let to_group_id = NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
let to_row_id = match self.to_row_id {
None => None,
Some(to_row_id) => Some(NotEmptyStr::parse(to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?.0),
};
Ok(MoveGroupRowParams {
view_id: view_id.0,
from_row_id: from_row_id.0,
to_group_id: to_group_id.0,
to_row_id,
})
}
}

View File

@ -1,28 +0,0 @@
use crate::entities::{CreateRowParams, GridLayout};
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct CreateBoardCardPayloadPB {
#[pb(index = 1)]
pub grid_id: String,
#[pb(index = 2)]
pub group_id: String,
}
impl TryInto<CreateRowParams> for CreateBoardCardPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateRowParams, Self::Error> {
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
Ok(CreateRowParams {
grid_id: grid_id.0,
start_row_id: None,
group_id: Some(group_id.0),
layout: GridLayout::Board,
})
}
}

View File

@ -1,4 +1,5 @@
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_grid_data_model::revision::{GroupRecordRevision, SelectOptionGroupConfigurationRevision};
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct UrlGroupConfigurationPB {
@ -18,6 +19,32 @@ pub struct SelectOptionGroupConfigurationPB {
hide_empty: bool,
}
impl std::convert::From<SelectOptionGroupConfigurationRevision> for SelectOptionGroupConfigurationPB {
fn from(rev: SelectOptionGroupConfigurationRevision) -> Self {
Self {
hide_empty: rev.hide_empty,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GroupRecordPB {
#[pb(index = 1)]
group_id: String,
#[pb(index = 2)]
visible: bool,
}
impl std::convert::From<GroupRecordRevision> for GroupRecordPB {
fn from(rev: GroupRecordRevision) -> Self {
Self {
group_id: rev.group_id,
visible: rev.visible,
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct NumberGroupConfigurationPB {
#[pb(index = 1)]

View File

@ -1,12 +1,35 @@
use crate::entities::{FieldType, RowPB};
use crate::entities::{CreateRowParams, FieldType, GridLayout, RowPB};
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::GroupConfigurationRevision;
use flowy_sync::entities::grid::{CreateGridGroupParams, DeleteGroupParams};
use flowy_grid_data_model::revision::{FieldTypeRevision, GroupConfigurationRevision};
use std::convert::TryInto;
use std::sync::Arc;
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct CreateBoardCardPayloadPB {
#[pb(index = 1)]
pub grid_id: String,
#[pb(index = 2)]
pub group_id: String,
}
impl TryInto<CreateRowParams> for CreateBoardCardPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateRowParams, Self::Error> {
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
Ok(CreateRowParams {
grid_id: grid_id.0,
start_row_id: None,
group_id: Some(group_id.0),
layout: GridLayout::Board,
})
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridGroupConfigurationPB {
#[pb(index = 1)]
@ -86,27 +109,28 @@ pub struct CreateGridGroupPayloadPB {
#[pb(index = 2)]
pub field_type: FieldType,
#[pb(index = 3, one_of)]
pub content: Option<Vec<u8>>,
}
impl TryInto<CreateGridGroupParams> for CreateGridGroupPayloadPB {
impl TryInto<CreatGroupParams> for CreateGridGroupPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateGridGroupParams, Self::Error> {
fn try_into(self) -> Result<CreatGroupParams, Self::Error> {
let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0;
Ok(CreateGridGroupParams {
Ok(CreatGroupParams {
field_id,
field_type_rev: self.field_type.into(),
content: self.content,
})
}
}
pub struct CreatGroupParams {
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
}
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct DeleteGroupPayloadPB {
#[pb(index = 1)]
@ -137,3 +161,9 @@ impl TryInto<DeleteGroupParams> for DeleteGroupPayloadPB {
})
}
}
pub struct DeleteGroupParams {
pub field_id: String,
pub group_id: String,
pub field_type_rev: FieldTypeRevision,
}

View File

@ -1,5 +1,7 @@
use crate::entities::{InsertedRowPB, RowPB};
use crate::entities::{GroupPB, InsertedRowPB, RowPB};
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use std::fmt::Formatter;
#[derive(Debug, Default, ProtoBuf)]
@ -62,3 +64,64 @@ impl GroupRowsChangesetPB {
}
}
}
#[derive(Debug, Default, ProtoBuf)]
pub struct MoveGroupPayloadPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub from_group_id: String,
#[pb(index = 3)]
pub to_group_id: String,
}
pub struct MoveGroupParams {
pub view_id: String,
pub from_group_id: String,
pub to_group_id: String,
}
impl TryInto<MoveGroupParams> for MoveGroupPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<MoveGroupParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id)
.map_err(|_| ErrorCode::GridViewIdIsEmpty)?
.0;
let from_group_id = NotEmptyStr::parse(self.from_group_id)
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
.0;
let to_group_id = NotEmptyStr::parse(self.to_group_id)
.map_err(|_| ErrorCode::GroupIdIsEmpty)?
.0;
Ok(MoveGroupParams {
view_id,
from_group_id,
to_group_id,
})
}
}
#[derive(Debug, Default, ProtoBuf)]
pub struct GroupViewChangesetPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub inserted_groups: Vec<InsertedGroupPB>,
#[pb(index = 3)]
pub deleted_groups: Vec<String>,
}
impl GroupViewChangesetPB {}
#[derive(Debug, Default, ProtoBuf)]
pub struct InsertedGroupPB {
#[pb(index = 1)]
pub group: GroupPB,
#[pb(index = 2)]
pub index: i32,
}

View File

@ -1,9 +1,7 @@
mod board_card;
mod configuration;
mod group;
mod group_changeset;
pub use board_card::*;
pub use configuration::*;
pub use group::*;
pub use group_changeset::*;

View File

@ -6,7 +6,6 @@ mod grid_entities;
mod group_entities;
mod row_entities;
mod setting_entities;
mod sort_entities;
pub use block_entities::*;
pub use cell_entities::*;
@ -16,4 +15,3 @@ pub use grid_entities::*;
pub use group_entities::*;
pub use row_entities::*;
pub use setting_entities::*;
pub use sort_entities::*;

View File

@ -1,12 +1,12 @@
use crate::entities::{
CreateGridFilterPayloadPB, CreateGridGroupPayloadPB, CreateGridSortPayloadPB, DeleteFilterPayloadPB,
DeleteGroupPayloadPB, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RepeatedGridSortPB,
CreatGroupParams, CreateFilterParams, CreateGridFilterPayloadPB, CreateGridGroupPayloadPB, DeleteFilterParams,
DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, RepeatedGridConfigurationFilterPB,
RepeatedGridGroupConfigurationPB,
};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::LayoutRevision;
use flowy_sync::entities::grid::GridSettingChangesetParams;
use std::collections::HashMap;
use std::convert::TryInto;
use strum::IntoEnumIterator;
@ -26,9 +26,6 @@ pub struct GridSettingPB {
#[pb(index = 4)]
pub group_configuration_by_field_id: HashMap<String, RepeatedGridGroupConfigurationPB>,
#[pb(index = 5)]
pub sorts_by_field_id: HashMap<String, RepeatedGridSortPB>,
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
@ -98,12 +95,6 @@ pub struct GridSettingChangesetPayloadPB {
#[pb(index = 6, one_of)]
pub delete_group: Option<DeleteGroupPayloadPB>,
#[pb(index = 7, one_of)]
pub insert_sort: Option<CreateGridSortPayloadPB>,
#[pb(index = 8, one_of)]
pub delete_sort: Option<String>,
}
impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
@ -134,16 +125,6 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
None => None,
};
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.into(),
@ -151,8 +132,21 @@ impl TryInto<GridSettingChangesetParams> for GridSettingChangesetPayloadPB {
delete_filter,
insert_group,
delete_group,
insert_sort,
delete_sort,
})
}
}
pub struct GridSettingChangesetParams {
pub grid_id: String,
pub layout_type: LayoutRevision,
pub insert_filter: Option<CreateFilterParams>,
pub delete_filter: Option<DeleteFilterParams>,
pub insert_group: Option<CreatGroupParams>,
pub delete_group: Option<DeleteGroupParams>,
}
impl GridSettingChangesetParams {
pub fn is_filter_changed(&self) -> bool {
self.insert_filter.is_some() || self.delete_filter.is_some()
}
}

View File

@ -1,65 +0,0 @@
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use flowy_grid_data_model::parser::NotEmptyStr;
use flowy_grid_data_model::revision::SortConfigurationRevision;
use flowy_sync::entities::grid::CreateGridSortParams;
use std::convert::TryInto;
use std::sync::Arc;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct GridSort {
#[pb(index = 1)]
pub id: String,
#[pb(index = 2, one_of)]
pub field_id: Option<String>,
}
impl std::convert::From<&SortConfigurationRevision> for GridSort {
fn from(rev: &SortConfigurationRevision) -> Self {
GridSort {
id: rev.id.clone(),
field_id: rev.field_id.clone(),
}
}
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedGridSortPB {
#[pb(index = 1)]
pub items: Vec<GridSort>,
}
impl std::convert::From<Vec<Arc<SortConfigurationRevision>>> for RepeatedGridSortPB {
fn from(revs: Vec<Arc<SortConfigurationRevision>>) -> Self {
RepeatedGridSortPB {
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
}
}
}
impl std::convert::From<Vec<GridSort>> for RepeatedGridSortPB {
fn from(items: Vec<GridSort>) -> Self {
Self { items }
}
}
#[derive(ProtoBuf, Debug, Default, Clone)]
pub struct CreateGridSortPayloadPB {
#[pb(index = 1, one_of)]
pub field_id: Option<String>,
}
impl TryInto<CreateGridSortParams> for CreateGridSortPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateGridSortParams, Self::Error> {
let field_id = match self.field_id {
None => None,
Some(field_id) => Some(NotEmptyStr::parse(field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?.0),
};
Ok(CreateGridSortParams { field_id })
}
}

View File

@ -10,7 +10,6 @@ use crate::services::field::{
use crate::services::row::make_row_from_row_rev;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_grid_data_model::revision::FieldRevision;
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
use std::sync::Arc;
@ -36,17 +35,6 @@ pub(crate) async fn get_grid_setting_handler(
data_result(grid_setting)
}
#[tracing::instrument(level = "trace", skip(data, manager), err)]
pub(crate) async fn update_grid_setting_handler(
data: Data<GridSettingChangesetPayloadPB>,
manager: AppData<Arc<GridManager>>,
) -> Result<(), FlowyError> {
let params: GridSettingChangesetParams = data.into_inner().try_into()?;
let editor = manager.open_grid(&params.grid_id).await?;
let _ = editor.update_grid_setting(params).await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_grid_blocks_handler(
data: Data<QueryBlocksPayloadPB>,
@ -437,3 +425,25 @@ pub(crate) async fn create_board_card_handler(
let row = editor.create_row(params).await?;
data_result(row)
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn move_group_handler(
data: Data<MoveGroupPayloadPB>,
manager: AppData<Arc<GridManager>>,
) -> FlowyResult<()> {
let params: MoveGroupParams = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(params.view_id.as_ref())?;
let _ = editor.move_group(params).await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn move_group_row_handler(
data: Data<MoveGroupRowPayloadPB>,
manager: AppData<Arc<GridManager>>,
) -> FlowyResult<()> {
let params: MoveGroupRowParams = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(params.view_id.as_ref())?;
let _ = editor.move_group_row(params).await?;
Ok(())
}

View File

@ -11,7 +11,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
.event(GridEvent::GetGrid, get_grid_handler)
.event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
.event(GridEvent::GetGridSetting, get_grid_setting_handler)
.event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
// .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
// Field
.event(GridEvent::GetFields, get_fields_handler)
.event(GridEvent::UpdateField, update_field_handler)
@ -41,6 +41,8 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
.event(GridEvent::UpdateDateCell, update_date_cell_handler)
// Group
.event(GridEvent::CreateBoardCard, create_board_card_handler)
.event(GridEvent::MoveGroup, move_group_handler)
.event(GridEvent::MoveGroupRow, move_group_row_handler)
.event(GridEvent::GetGroup, get_groups_handler);
module
@ -217,4 +219,10 @@ pub enum GridEvent {
#[event(input = "CreateBoardCardPayloadPB", output = "RowPB")]
CreateBoardCard = 110,
#[event(input = "MoveGroupPayloadPB")]
MoveGroup = 111,
#[event(input = "MoveGroupRowPayloadPB")]
MoveGroupRow = 112,
}

View File

@ -131,7 +131,6 @@ impl GridManager {
async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult<Arc<GridRevisionEditor>> {
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_rev_editor(grid_id, db_pool).await?;

View File

@ -4,7 +4,7 @@
#![allow(unused_imports)]
#![allow(unused_results)]
use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::entities::{FieldType, GridBlockChangesetPB};
use crate::entities::{FieldType, GridBlockChangesetPB, GridSettingChangesetParams};
use crate::services::block_manager::GridBlockManager;
use crate::services::cell::{AnyCellData, CellFilterOperation};
use crate::services::field::{
@ -20,7 +20,6 @@ use crate::services::tasks::{FilterTaskContext, Task, TaskContent};
use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{CellRevision, FieldId, FieldRevision, RowRevision};
use flowy_sync::client_grid::GridRevisionPad;
use flowy_sync::entities::grid::GridSettingChangesetParams;
use rayon::prelude::*;
use std::collections::HashMap;
use std::sync::Arc;

View File

@ -15,7 +15,6 @@ use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_grid_data_model::revision::*;
use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
use flowy_sync::client_grid::{GridRevisionChangeset, GridRevisionPad, JsonDeserializer};
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
use flowy_sync::entities::revision::Revision;
use flowy_sync::errors::CollaborateResult;
use flowy_sync::util::make_text_delta_from_revisions;
@ -96,23 +95,19 @@ impl GridRevisionEditor {
} = params;
let field_id = field.id.clone();
if self.contain_field(&field_id).await {
let _ = self
.modify(|grid| {
let deserializer = TypeOptionJsonDeserializer(field.field_type.clone());
let changeset = FieldChangesetParams {
field_id: field.id,
grid_id,
name: Some(field.name),
desc: Some(field.desc),
field_type: Some(field.field_type.into()),
frozen: Some(field.frozen),
visibility: Some(field.visibility),
width: Some(field.width),
type_option_data: Some(type_option_data),
};
Ok(grid.update_field_rev(changeset, deserializer)?)
})
.await?;
let changeset = FieldChangesetParams {
field_id: field.id,
grid_id,
name: Some(field.name),
desc: Some(field.desc),
field_type: Some(field.field_type.clone().into()),
frozen: Some(field.frozen),
visibility: Some(field.visibility),
width: Some(field.width),
type_option_data: Some(type_option_data),
};
let _ = self.update_field_rev(changeset, field.field_type).await?;
let _ = self.notify_did_update_grid_field(&field_id).await?;
} else {
let _ = self
@ -140,19 +135,13 @@ impl GridRevisionEditor {
return Ok(());
}
let field_rev = result.unwrap();
let _ = self
.modify(|grid| {
let field_type = field_rev.ty.into();
let deserializer = TypeOptionJsonDeserializer(field_type);
let changeset = FieldChangesetParams {
field_id: field_id.to_owned(),
grid_id: grid_id.to_owned(),
type_option_data: Some(type_option_data),
..Default::default()
};
Ok(grid.update_field_rev(changeset, deserializer)?)
})
.await?;
let changeset = FieldChangesetParams {
field_id: field_id.to_owned(),
grid_id: grid_id.to_owned(),
type_option_data: Some(type_option_data),
..Default::default()
};
let _ = self.update_field_rev(changeset, field_rev.ty.into()).await?;
let _ = self.notify_did_update_grid_field(field_id).await?;
Ok(())
}
@ -179,17 +168,21 @@ impl GridRevisionEditor {
pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
let field_id = params.field_id.clone();
let json_deserializer = match self.grid_pad.read().await.get_field_rev(params.field_id.as_str()) {
None => return Err(ErrorCode::FieldDoesNotExist.into()),
Some((_, field_rev)) => TypeOptionJsonDeserializer(field_rev.ty.into()),
};
let field_type: Option<FieldType> = self
.grid_pad
.read()
.await
.get_field_rev(params.field_id.as_str())
.map(|(_, field_rev)| field_rev.ty.into());
let _ = self
.modify(|grid| Ok(grid.update_field_rev(params, json_deserializer)?))
.await?;
let _ = self.notify_did_update_grid_field(&field_id).await?;
Ok(())
match field_type {
None => Err(ErrorCode::FieldDoesNotExist.into()),
Some(field_type) => {
let _ = self.update_field_rev(params, field_type).await?;
let _ = self.notify_did_update_grid_field(&field_id).await?;
Ok(())
}
}
}
pub async fn replace_field(&self, field_rev: Arc<FieldRevision>) -> FlowyResult<()> {
@ -269,6 +262,62 @@ impl GridRevisionEditor {
Ok(field_revs)
}
async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> {
self.modify(|grid| {
let deserializer = TypeOptionJsonDeserializer(field_type);
let changeset = grid.modify_field(&params.field_id, |field| {
let mut is_changed = None;
if let Some(name) = params.name {
field.name = name;
is_changed = Some(())
}
if let Some(desc) = params.desc {
field.desc = desc;
is_changed = Some(())
}
if let Some(field_type) = params.field_type {
field.ty = field_type;
is_changed = Some(())
}
if let Some(frozen) = params.frozen {
field.frozen = frozen;
is_changed = Some(())
}
if let Some(visibility) = params.visibility {
field.visibility = visibility;
is_changed = Some(())
}
if let Some(width) = params.width {
field.width = width;
is_changed = Some(())
}
if let Some(type_option_data) = params.type_option_data {
match deserializer.deserialize(type_option_data) {
Ok(json_str) => {
let field_type = field.ty;
field.insert_type_option_str(&field_type, json_str);
is_changed = Some(())
}
Err(err) => {
tracing::error!("Deserialize data to type option json failed: {}", err);
}
}
}
Ok(is_changed)
})?;
Ok(changeset)
})
.await
}
pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> {
let _ = self
.modify(|grid_pad| Ok(grid_pad.create_block_meta_rev(block_meta_rev)?))
@ -294,6 +343,11 @@ impl GridRevisionEditor {
Ok(row_pb)
}
pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
let _ = self.view_manager.move_group(params).await?;
Ok(())
}
pub async fn insert_rows(&self, row_revs: Vec<RowRevision>) -> FlowyResult<Vec<RowPB>> {
let block_id = self.block_id().await?;
let mut rows_by_block_id: HashMap<String, Vec<RowRevision>> = HashMap::new();
@ -460,8 +514,13 @@ impl GridRevisionEditor {
self.view_manager.get_filters().await
}
pub async fn update_grid_setting(&self, params: GridSettingChangesetParams) -> FlowyResult<()> {
let _ = self.view_manager.update_setting(params).await?;
pub async fn update_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
let _ = self.view_manager.update_filter(params).await?;
Ok(())
}
pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
let _ = self.view_manager.delete_filter(params).await?;
Ok(())
}
@ -501,16 +560,6 @@ impl GridRevisionEditor {
.block_manager
.move_row(row_rev.clone(), from_index, to_index)
.await?;
if let Some(row_changeset) = self.view_manager.move_row(row_rev, to_row_id.clone()).await {
tracing::trace!("Receive row changeset after moving the row");
match self.block_manager.update_row(row_changeset).await {
Ok(_) => {}
Err(e) => {
tracing::error!("Apply row changeset error:{:?}", e);
}
}
}
}
(_, None) => tracing::warn!("Can not find the from row id: {}", from_row_id),
(None, _) => tracing::warn!("Can not find the to row id: {}", to_row_id),
@ -520,6 +569,35 @@ impl GridRevisionEditor {
Ok(())
}
pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> {
let MoveGroupRowParams {
view_id: _,
from_row_id,
to_group_id,
to_row_id,
} = params;
match self.block_manager.get_row_rev(&from_row_id).await? {
None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id),
Some(row_rev) => {
if let Some(row_changeset) = self
.view_manager
.move_group_row(row_rev, to_group_id, to_row_id.clone())
.await
{
match self.block_manager.update_row(row_changeset).await {
Ok(_) => {}
Err(e) => {
tracing::error!("Apply row changeset error:{:?}", e);
}
}
}
}
}
Ok(())
}
pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
let MoveFieldParams {
grid_id: _,

View File

@ -1,18 +1,23 @@
use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::entities::{
CreateRowParams, GridFilterConfiguration, GridSettingPB, GroupPB, GroupRowsChangesetPB, InsertedRowPB, RowPB,
CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridLayout, GridLayoutPB,
GridSettingPB, GroupPB, GroupRowsChangesetPB, GroupViewChangesetPB, InsertedGroupPB, InsertedRowPB,
MoveGroupParams, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB, RowPB,
};
use crate::services::grid_editor_task::GridServiceTaskScheduler;
use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate};
use crate::services::group::{default_group_configuration, GroupConfigurationDelegate, GroupService};
use crate::services::setting::make_grid_setting;
use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter, GroupService};
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{FieldRevision, GroupConfigurationRevision, RowChangeset, RowRevision};
use flowy_grid_data_model::revision::{
gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision,
RowChangeset, RowRevision,
};
use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilder};
use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad};
use flowy_sync::entities::grid::GridSettingChangesetParams;
use flowy_sync::entities::revision::Revision;
use lib_infra::future::{wrap_future, AFFuture, FutureResult};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
@ -31,6 +36,7 @@ pub struct GridViewRevisionEditor {
}
impl GridViewRevisionEditor {
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn new(
user_id: &str,
token: &str,
@ -46,7 +52,14 @@ impl GridViewRevisionEditor {
let view_revision_pad = rev_manager.load::<GridViewRevisionPadBuilder>(Some(cloud)).await?;
let pad = Arc::new(RwLock::new(view_revision_pad));
let rev_manager = Arc::new(rev_manager);
let group_service = GroupService::new(Box::new(pad.clone())).await;
let configuration_reader = GroupConfigurationReaderImpl(pad.clone());
let configuration_writer = GroupConfigurationWriterImpl {
user_id: user_id.to_owned(),
rev_manager: rev_manager.clone(),
view_pad: pad.clone(),
};
let group_service = GroupService::new(configuration_reader, configuration_writer).await;
let user_id = user_id.to_owned();
let did_load_group = AtomicBool::new(false);
Ok(Self {
@ -67,7 +80,7 @@ impl GridViewRevisionEditor {
None => {}
Some(group_id) => {
self.group_service
.read()
.write()
.await
.will_create_row(row_rev, group_id, |field_id| {
self.field_delegate.get_field_rev(&field_id)
@ -87,7 +100,7 @@ impl GridViewRevisionEditor {
index: None,
};
let changeset = GroupRowsChangesetPB::insert(group_id.clone(), vec![inserted_row]);
self.notify_did_update_group(changeset).await;
self.notify_did_update_group_rows(changeset).await;
}
}
}
@ -102,7 +115,7 @@ impl GridViewRevisionEditor {
.await
{
for changeset in changesets {
self.notify_did_update_group(changeset).await;
self.notify_did_update_group_rows(changeset).await;
}
}
}
@ -116,38 +129,40 @@ impl GridViewRevisionEditor {
.await
{
for changeset in changesets {
self.notify_did_update_group(changeset).await;
self.notify_did_update_group_rows(changeset).await;
}
}
}
pub(crate) async fn did_move_row(
pub(crate) async fn move_group_row(
&self,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
upper_row_id: &str,
to_group_id: &str,
to_row_id: Option<String>,
) {
if let Some(changesets) = self
.group_service
.write()
.await
.did_move_row(row_rev, row_changeset, upper_row_id, |field_id| {
.move_group_row(row_rev, row_changeset, to_group_id, to_row_id, |field_id| {
self.field_delegate.get_field_rev(&field_id)
})
.await
{
for changeset in changesets {
tracing::trace!("Group: {} changeset: {}", changeset.group_id, changeset);
self.notify_did_update_group(changeset).await;
self.notify_did_update_group_rows(changeset).await;
}
}
}
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) async fn load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
let groups = if !self.did_load_group.load(Ordering::SeqCst) {
self.did_load_group.store(true, Ordering::SeqCst);
let field_revs = self.field_delegate.get_field_revs().await;
let row_revs = self.row_delegate.gv_row_revs().await;
match self
.group_service
.write()
@ -162,23 +177,47 @@ impl GridViewRevisionEditor {
self.group_service.read().await.groups().await
};
tracing::trace!("Number of groups: {}", groups.len());
Ok(groups.into_iter().map(GroupPB::from).collect())
}
pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
let _ = self
.group_service
.write()
.await
.move_group(&params.from_group_id, &params.to_group_id)
.await?;
match self.group_service.read().await.get_group(&params.from_group_id).await {
None => {}
Some((index, group)) => {
let inserted_group = InsertedGroupPB {
group: GroupPB::from(group),
index: index as i32,
};
let changeset = GroupViewChangesetPB {
view_id: "".to_string(),
inserted_groups: vec![inserted_group],
deleted_groups: vec![params.from_group_id.clone()],
};
self.notify_did_update_view(changeset).await;
}
}
Ok(())
}
pub(crate) async fn get_setting(&self) -> GridSettingPB {
let field_revs = self.field_delegate.get_field_revs().await;
let grid_setting = make_grid_setting(self.pad.read().await.get_setting_rev(), &field_revs);
let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs);
grid_setting
}
pub(crate) async fn update_setting(&self, changeset: GridSettingChangesetParams) -> FlowyResult<()> {
let _ = self.modify(|pad| Ok(pad.update_setting(changeset)?)).await;
Ok(())
}
pub(crate) async fn get_filters(&self) -> Vec<GridFilterConfiguration> {
let field_revs = self.field_delegate.get_field_revs().await;
match self.pad.read().await.get_setting_rev().get_all_filters(&field_revs) {
match self.pad.read().await.get_all_filters(&field_revs) {
None => vec![],
Some(filters) => filters
.into_values()
@ -188,12 +227,45 @@ impl GridViewRevisionEditor {
}
}
async fn notify_did_update_group(&self, changeset: GroupRowsChangesetPB) {
pub(crate) async fn insert_filter(&self, insert_filter: CreateFilterParams) -> FlowyResult<()> {
self.modify(|pad| {
let filter_rev = FilterConfigurationRevision {
id: gen_grid_filter_id(),
field_id: insert_filter.field_id.clone(),
condition: insert_filter.condition,
content: insert_filter.content,
};
let changeset = pad.insert_filter(&insert_filter.field_id, &insert_filter.field_type_rev, filter_rev)?;
Ok(changeset)
})
.await
}
pub(crate) async fn delete_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> {
self.modify(|pad| {
let changeset = pad.delete_filter(
&delete_filter.field_id,
&delete_filter.field_type_rev,
&delete_filter.filter_id,
)?;
Ok(changeset)
})
.await
}
async fn notify_did_update_group_rows(&self, changeset: GroupRowsChangesetPB) {
send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
.payload(changeset)
.send();
}
async fn notify_did_update_view(&self, changeset: GroupViewChangesetPB) {
send_dart_notification(&self.view_id, GridNotification::DidUpdateGroupView)
.payload(changeset)
.send();
}
#[allow(dead_code)]
async fn modify<F>(&self, f: F) -> FlowyResult<()>
where
F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult<Option<GridViewRevisionChangeset>>,
@ -202,28 +274,24 @@ impl GridViewRevisionEditor {
match f(&mut *write_guard)? {
None => {}
Some(change) => {
let _ = self.apply_change(change).await?;
let _ = apply_change(&self.user_id, self.rev_manager.clone(), change).await?;
}
}
Ok(())
}
}
async fn apply_change(&self, change: GridViewRevisionChangeset) -> FlowyResult<()> {
let GridViewRevisionChangeset { delta, md5 } = change;
let user_id = self.user_id.clone();
let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair();
let delta_data = delta.json_bytes();
let revision = Revision::new(
&self.rev_manager.object_id,
base_rev_id,
rev_id,
delta_data,
&user_id,
md5,
);
let _ = self.rev_manager.add_local_revision(&revision).await?;
Ok(())
}
async fn apply_change(
user_id: &str,
rev_manager: Arc<RevisionManager>,
change: GridViewRevisionChangeset,
) -> FlowyResult<()> {
let GridViewRevisionChangeset { delta, md5 } = change;
let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair();
let delta_data = delta.json_bytes();
let revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, delta_data, user_id, md5);
let _ = rev_manager.add_local_revision(&revision).await?;
Ok(())
}
struct GridViewRevisionCloudService {
@ -248,19 +316,97 @@ impl RevisionObjectBuilder for GridViewRevisionPadBuilder {
}
}
impl GroupConfigurationDelegate for Arc<RwLock<GridViewRevisionPad>> {
fn get_group_configuration(&self, field_rev: Arc<FieldRevision>) -> AFFuture<GroupConfigurationRevision> {
let view_pad = self.clone();
struct GroupConfigurationReaderImpl(Arc<RwLock<GridViewRevisionPad>>);
impl GroupConfigurationReader for GroupConfigurationReaderImpl {
fn get_group_configuration(
&self,
field_rev: Arc<FieldRevision>,
) -> AFFuture<Option<Arc<GroupConfigurationRevision>>> {
let view_pad = self.0.clone();
wrap_future(async move {
let grid_pad = view_pad.read().await;
let configurations = grid_pad.get_groups(&field_rev.id, &field_rev.ty);
match configurations {
None => default_group_configuration(&field_rev),
Some(mut configurations) => {
assert_eq!(configurations.len(), 1);
(&*configurations.pop().unwrap()).clone()
}
let mut groups = view_pad.read().await.groups.get_objects(&field_rev.id, &field_rev.ty)?;
if groups.is_empty() {
None
} else {
debug_assert_eq!(groups.len(), 1);
Some(groups.pop().unwrap())
}
})
}
}
struct GroupConfigurationWriterImpl {
user_id: String,
rev_manager: Arc<RevisionManager>,
view_pad: Arc<RwLock<GridViewRevisionPad>>,
}
impl GroupConfigurationWriter for GroupConfigurationWriterImpl {
fn save_group_configuration(
&self,
field_id: &str,
field_type: FieldTypeRevision,
group_configuration: GroupConfigurationRevision,
) -> AFFuture<FlowyResult<()>> {
let user_id = self.user_id.clone();
let rev_manager = self.rev_manager.clone();
let view_pad = self.view_pad.clone();
let field_id = field_id.to_owned();
wrap_future(async move {
let changeset = view_pad
.write()
.await
.insert_group(&field_id, &field_type, group_configuration)?;
if let Some(changeset) = changeset {
let _ = apply_change(&user_id, rev_manager, changeset).await?;
}
Ok(())
})
}
}
pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc<FieldRevision>]) -> GridSettingPB {
let current_layout_type: GridLayout = view_pad.layout.clone().into();
let filters_by_field_id = view_pad
.get_all_filters(field_revs)
.map(|filters_by_field_id| {
filters_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridConfigurationFilterPB>>()
})
.unwrap_or_default();
let groups_by_field_id = view_pad
.get_all_groups(field_revs)
.map(|groups_by_field_id| {
groups_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridGroupConfigurationPB>>()
})
.unwrap_or_default();
GridSettingPB {
layouts: GridLayoutPB::all(),
current_layout_type,
filter_configuration_by_field_id: filters_by_field_id,
group_configuration_by_field_id: groups_by_field_id,
}
}
#[cfg(test)]
mod tests {
use lib_ot::core::TextDelta;
#[test]
fn test() {
let s1 = r#"[{"insert":"{\"view_id\":\"fTURELffPr\",\"grid_id\":\"fTURELffPr\",\"layout\":0,\"filters\":[],\"groups\":[]}"}]"#;
let _delta_1 = TextDelta::from_json(s1).unwrap();
let s2 = r#"[{"retain":195},{"insert":"{\\\"group_id\\\":\\\"wD9i\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"xZtv\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"tFV2\\\",\\\"visible\\\":true}"},{"retain":10}]"#;
let _delta_2 = TextDelta::from_json(s2).unwrap();
}
}

View File

@ -1,4 +1,7 @@
use crate::entities::{CreateRowParams, GridFilterConfiguration, GridSettingPB, RepeatedGridGroupPB, RowPB};
use crate::entities::{
CreateFilterParams, CreateRowParams, DeleteFilterParams, GridFilterConfiguration, GridSettingPB, MoveGroupParams,
RepeatedGridGroupPB, RowPB,
};
use crate::manager::GridUser;
use crate::services::grid_editor_task::GridServiceTaskScheduler;
use crate::services::grid_view_editor::GridViewRevisionEditor;
@ -8,7 +11,6 @@ use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision};
use flowy_revision::disk::SQLiteGridViewRevisionPersistence;
use flowy_revision::{RevisionCompactor, RevisionManager, RevisionPersistence, SQLiteRevisionSnapshotPersistence};
use flowy_sync::entities::grid::GridSettingChangesetParams;
use flowy_sync::entities::revision::Revision;
use flowy_sync::util::make_text_delta_from_revisions;
use lib_infra::future::AFFuture;
@ -97,30 +99,47 @@ impl GridViewManager {
Ok(view_editor.get_setting().await)
}
pub(crate) async fn update_setting(&self, params: GridSettingChangesetParams) -> FlowyResult<()> {
let view_editor = self.get_default_view_editor().await?;
let _ = view_editor.update_setting(params).await?;
Ok(())
}
pub(crate) async fn get_filters(&self) -> FlowyResult<Vec<GridFilterConfiguration>> {
let view_editor = self.get_default_view_editor().await?;
Ok(view_editor.get_filters().await)
}
pub(crate) async fn update_filter(&self, insert_filter: CreateFilterParams) -> FlowyResult<()> {
let view_editor = self.get_default_view_editor().await?;
view_editor.insert_filter(insert_filter).await
}
pub(crate) async fn delete_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> {
let view_editor = self.get_default_view_editor().await?;
view_editor.delete_filter(delete_filter).await
}
pub(crate) async fn load_groups(&self) -> FlowyResult<RepeatedGridGroupPB> {
let view_editor = self.get_default_view_editor().await?;
let groups = view_editor.load_groups().await?;
Ok(RepeatedGridGroupPB { items: groups })
}
pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
let view_editor = self.get_default_view_editor().await?;
let _ = view_editor.move_group(params).await?;
Ok(())
}
/// It may generate a RowChangeset when the Row was moved from one group to another.
/// The return value, [RowChangeset], contains the changes made by the groups.
///
pub(crate) async fn move_row(&self, row_rev: Arc<RowRevision>, to_row_id: String) -> Option<RowChangeset> {
pub(crate) async fn move_group_row(
&self,
row_rev: Arc<RowRevision>,
to_group_id: String,
to_row_id: Option<String>,
) -> Option<RowChangeset> {
let mut row_changeset = RowChangeset::new(row_rev.id.clone());
for view_editor in self.view_editors.iter() {
view_editor.did_move_row(&row_rev, &mut row_changeset, &to_row_id).await;
view_editor
.move_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone())
.await;
}
if row_changeset.has_changed() {
@ -163,12 +182,11 @@ async fn make_view_editor(
row_delegate: Arc<dyn GridViewRowDelegate>,
scheduler: Arc<dyn GridServiceTaskScheduler>,
) -> FlowyResult<GridViewRevisionEditor> {
tracing::trace!("Open view:{} editor", view_id);
let rev_manager = make_grid_view_rev_manager(user, view_id).await?;
let user_id = user.user_id()?;
let token = user.token()?;
let view_id = view_id.to_owned();
GridViewRevisionEditor::new(
&user_id,
&token,
@ -182,7 +200,6 @@ async fn make_view_editor(
}
pub async fn make_grid_view_rev_manager(user: &Arc<dyn GridUser>, view_id: &str) -> FlowyResult<RevisionManager> {
tracing::trace!("Open view:{} editor", view_id);
let user_id = user.user_id()?;
let pool = user.db_pool()?;

View File

@ -0,0 +1,17 @@
use crate::entities::GroupRowsChangesetPB;
use crate::services::group::controller::MoveGroupRowContext;
use flowy_grid_data_model::revision::RowRevision;
pub trait GroupAction: Send + Sync {
type CellDataType;
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB>;
fn remove_row_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB>;
fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupRowsChangesetPB>;
}

View File

@ -0,0 +1,221 @@
use crate::services::group::{default_group_configuration, Group};
use flowy_error::{FlowyError, FlowyResult};
use flowy_grid_data_model::revision::{
FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRecordRevision,
};
use std::marker::PhantomData;
use indexmap::IndexMap;
use lib_infra::future::AFFuture;
use std::sync::Arc;
pub trait GroupConfigurationReader: Send + Sync + 'static {
fn get_group_configuration(
&self,
field_rev: Arc<FieldRevision>,
) -> AFFuture<Option<Arc<GroupConfigurationRevision>>>;
}
pub trait GroupConfigurationWriter: Send + Sync + 'static {
fn save_group_configuration(
&self,
field_id: &str,
field_type: FieldTypeRevision,
group_configuration: GroupConfigurationRevision,
) -> AFFuture<FlowyResult<()>>;
}
pub struct GenericGroupConfiguration<C> {
pub configuration: Arc<GroupConfigurationRevision>,
configuration_content: PhantomData<C>,
field_rev: Arc<FieldRevision>,
groups_map: IndexMap<String, Group>,
writer: Arc<dyn GroupConfigurationWriter>,
}
impl<C> GenericGroupConfiguration<C>
where
C: GroupConfigurationContentSerde,
{
#[tracing::instrument(level = "trace", skip_all, err)]
pub async fn new(
field_rev: Arc<FieldRevision>,
reader: Arc<dyn GroupConfigurationReader>,
writer: Arc<dyn GroupConfigurationWriter>,
) -> FlowyResult<Self> {
let configuration = match reader.get_group_configuration(field_rev.clone()).await {
None => {
let default_group_configuration = default_group_configuration(&field_rev);
writer
.save_group_configuration(&field_rev.id, field_rev.ty, default_group_configuration.clone())
.await?;
Arc::new(default_group_configuration)
}
Some(configuration) => configuration,
};
// let configuration = C::from_configuration_content(&configuration_rev.content)?;
Ok(Self {
field_rev,
groups_map: IndexMap::new(),
writer,
configuration,
configuration_content: PhantomData,
})
}
pub(crate) fn groups(&self) -> Vec<&Group> {
self.groups_map.values().collect()
}
pub(crate) fn clone_groups(&self) -> Vec<Group> {
self.groups_map.values().cloned().collect()
}
pub(crate) async fn merge_groups(&mut self, groups: Vec<Group>) -> FlowyResult<()> {
let (group_revs, groups) = merge_groups(&self.configuration.groups, groups);
self.mut_configuration(move |configuration| {
configuration.groups = group_revs;
true
})?;
groups.into_iter().for_each(|group| {
self.groups_map.insert(group.id.clone(), group);
});
Ok(())
}
#[allow(dead_code)]
pub(crate) async fn hide_group(&mut self, group_id: &str) -> FlowyResult<()> {
self.mut_configuration_group(group_id, |group_rev| {
group_rev.visible = false;
})?;
Ok(())
}
#[allow(dead_code)]
pub(crate) async fn show_group(&mut self, group_id: &str) -> FlowyResult<()> {
self.mut_configuration_group(group_id, |group_rev| {
group_rev.visible = true;
})?;
Ok(())
}
pub(crate) fn with_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) {
self.groups_map.iter_mut().for_each(|(_, group)| {
each(group);
})
}
pub(crate) fn get_mut_group(&mut self, group_id: &str) -> Option<&mut Group> {
self.groups_map.get_mut(group_id)
}
pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> {
let from_index = self.groups_map.get_index_of(from_id);
let to_index = self.groups_map.get_index_of(to_id);
match (from_index, to_index) {
(Some(from_index), Some(to_index)) => {
self.groups_map.swap_indices(from_index, to_index);
self.mut_configuration(|configuration| {
let from_index = configuration.groups.iter().position(|group| group.group_id == from_id);
let to_index = configuration.groups.iter().position(|group| group.group_id == to_id);
if let (Some(from), Some(to)) = (from_index, to_index) {
configuration.groups.swap(from, to);
}
true
})?;
Ok(())
}
_ => Err(FlowyError::out_of_bounds()),
}
}
// Returns the index and group specified by the group_id
pub(crate) fn get_group(&self, group_id: &str) -> Option<(usize, &Group)> {
match (self.groups_map.get_index_of(group_id), self.groups_map.get(group_id)) {
(Some(index), Some(group)) => Some((index, group)),
_ => None,
}
}
pub fn save_configuration(&self) -> FlowyResult<()> {
let configuration = (&*self.configuration).clone();
let writer = self.writer.clone();
let field_id = self.field_rev.id.clone();
let field_type = self.field_rev.ty;
tokio::spawn(async move {
match writer
.save_group_configuration(&field_id, field_type, configuration)
.await
{
Ok(_) => {}
Err(e) => {
tracing::error!("Save group configuration failed: {}", e);
}
}
});
Ok(())
}
fn mut_configuration_group(
&mut self,
group_id: &str,
mut_groups_fn: impl Fn(&mut GroupRecordRevision),
) -> FlowyResult<()> {
self.mut_configuration(|configuration| {
match configuration.groups.iter_mut().find(|group| group.group_id == group_id) {
None => false,
Some(group_rev) => {
mut_groups_fn(group_rev);
true
}
}
})
}
fn mut_configuration(
&mut self,
mut_configuration_fn: impl FnOnce(&mut GroupConfigurationRevision) -> bool,
) -> FlowyResult<()> {
let configuration = Arc::make_mut(&mut self.configuration);
let is_changed = mut_configuration_fn(configuration);
if is_changed {
let _ = self.save_configuration()?;
}
Ok(())
}
}
fn merge_groups(old_group_revs: &[GroupRecordRevision], groups: Vec<Group>) -> (Vec<GroupRecordRevision>, Vec<Group>) {
if old_group_revs.is_empty() {
let new_groups = groups
.iter()
.map(|group| GroupRecordRevision::new(group.id.clone()))
.collect();
return (new_groups, groups);
}
let mut group_map: IndexMap<String, Group> = IndexMap::new();
groups.into_iter().for_each(|group| {
group_map.insert(group.id.clone(), group);
});
// Inert
let mut sorted_groups: Vec<Group> = vec![];
for group_rev in old_group_revs {
if let Some(group) = group_map.remove(&group_rev.group_id) {
sorted_groups.push(group);
}
}
sorted_groups.extend(group_map.into_values().collect::<Vec<Group>>());
let new_group_revs = sorted_groups
.iter()
.map(|group| GroupRecordRevision::new(group.id.clone()))
.collect::<Vec<GroupRecordRevision>>();
tracing::trace!("group revs: {}, groups: {}", new_group_revs.len(), sorted_groups.len());
(new_group_revs, sorted_groups)
}

View File

@ -0,0 +1,214 @@
use crate::entities::{GroupRowsChangesetPB, RowPB};
use crate::services::cell::{decode_any_cell_data, CellBytesParser};
use crate::services::group::action::GroupAction;
use crate::services::group::configuration::GenericGroupConfiguration;
use crate::services::group::entities::Group;
use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{
FieldRevision, GroupConfigurationContentSerde, RowChangeset, RowRevision, TypeOptionDataDeserializer,
};
use std::marker::PhantomData;
use std::sync::Arc;
const DEFAULT_GROUP_ID: &str = "default_group";
// Each kind of group must implement this trait to provide custom group
// operations. For example, insert cell data to the row_rev when creating
// a new row.
pub trait GroupController: GroupControllerSharedOperation + Send + Sync {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
}
pub trait GroupGenerator {
type ConfigurationType;
type TypeOptionType;
fn generate_groups(
field_id: &str,
configuration: &Self::ConfigurationType,
type_option: &Option<Self::TypeOptionType>,
) -> Vec<Group>;
}
pub struct MoveGroupRowContext<'a> {
pub row_rev: &'a RowRevision,
pub row_changeset: &'a mut RowChangeset,
pub field_rev: &'a FieldRevision,
pub to_group_id: &'a str,
pub to_row_id: Option<String>,
}
// Defines the shared actions each group controller can perform.
pub trait GroupControllerSharedOperation: Send + Sync {
// The field that is used for grouping the rows
fn field_id(&self) -> &str;
fn groups(&self) -> Vec<Group>;
fn get_group(&self, group_id: &str) -> Option<(usize, Group)>;
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<Vec<Group>>;
fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>;
fn did_update_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
fn did_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
}
/// C: represents the group configuration that impl [GroupConfigurationSerde]
/// T: the type option data deserializer that impl [TypeOptionDataDeserializer]
/// G: the group generator, [GroupGenerator]
/// P: the parser that impl [CellBytesParser] for the CellBytes
pub struct GenericGroupController<C, T, G, P> {
pub field_id: String,
pub type_option: Option<T>,
pub configuration: GenericGroupConfiguration<C>,
/// default_group is used to store the rows that don't belong to any groups.
default_group: Group,
group_action_phantom: PhantomData<G>,
cell_parser_phantom: PhantomData<P>,
}
impl<C, T, G, P> GenericGroupController<C, T, G, P>
where
C: GroupConfigurationContentSerde,
T: TypeOptionDataDeserializer,
G: GroupGenerator<ConfigurationType = GenericGroupConfiguration<C>, TypeOptionType = T>,
{
pub async fn new(
field_rev: &Arc<FieldRevision>,
mut configuration: GenericGroupConfiguration<C>,
) -> FlowyResult<Self> {
let field_type_rev = field_rev.ty;
let type_option = field_rev.get_type_option_entry::<T>(field_type_rev);
let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
let _ = configuration.merge_groups(groups).await?;
let default_group = Group::new(
DEFAULT_GROUP_ID.to_owned(),
field_rev.id.clone(),
format!("No {}", field_rev.name),
"".to_string(),
);
Ok(Self {
field_id: field_rev.id.clone(),
default_group,
type_option,
configuration,
group_action_phantom: PhantomData,
cell_parser_phantom: PhantomData,
})
}
}
impl<C, T, G, P> GroupControllerSharedOperation for GenericGroupController<C, T, G, P>
where
P: CellBytesParser,
C: GroupConfigurationContentSerde,
Self: GroupAction<CellDataType = P::Object>,
{
fn field_id(&self) -> &str {
&self.field_id
}
fn groups(&self) -> Vec<Group> {
self.configuration.clone_groups()
}
fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
let group = self.configuration.get_group(group_id)?;
Some((group.0, group.1.clone()))
}
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<Vec<Group>> {
for row_rev in row_revs {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let mut group_rows: Vec<GroupRow> = vec![];
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
for group in self.configuration.groups() {
if self.can_group(&group.content, &cell_data) {
group_rows.push(GroupRow {
row: row_rev.into(),
group_id: group.id.clone(),
});
}
}
if group_rows.is_empty() {
self.default_group.add_row(row_rev.into());
} else {
for group_row in group_rows {
if let Some(group) = self.configuration.get_mut_group(&group_row.group_id) {
group.add_row(group_row.row);
}
}
}
} else {
self.default_group.add_row(row_rev.into());
}
}
let default_group = self.default_group.clone();
let mut groups: Vec<Group> = self.configuration.clone_groups();
if !default_group.number_of_row() == 0 {
groups.push(default_group);
}
Ok(groups)
}
fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> {
self.configuration.move_group(from_group_id, to_group_id)
}
fn did_update_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
Ok(self.add_row_if_match(row_rev, &cell_data))
} else {
Ok(vec![])
}
}
fn did_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
Ok(self.remove_row_if_match(row_rev, &cell_data))
} else {
Ok(vec![])
}
}
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
if let Some(cell_rev) = context.row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), context.field_rev);
let cell_data = cell_bytes.parser::<P>()?;
Ok(self.move_row(&cell_data, context))
} else {
Ok(vec![])
}
}
}
struct GroupRow {
row: RowPB,
group_id: String,
}

View File

@ -1,20 +1,25 @@
use crate::entities::{CheckboxGroupConfigurationPB, GroupRowsChangesetPB};
use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision};
use crate::entities::GroupRowsChangesetPB;
use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
use crate::services::group::{GenericGroupController, Group, GroupController, GroupGenerator, Groupable};
use crate::services::group::action::GroupAction;
use crate::services::group::configuration::GenericGroupConfiguration;
use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
};
use crate::services::group::entities::Group;
use flowy_grid_data_model::revision::{CheckboxGroupConfigurationRevision, FieldRevision, RowRevision};
pub type CheckboxGroupController = GenericGroupController<
CheckboxGroupConfigurationPB,
CheckboxGroupConfigurationRevision,
CheckboxTypeOptionPB,
CheckboxGroupGenerator,
CheckboxCellDataParser,
>;
impl Groupable for CheckboxGroupController {
type CellDataType = CheckboxCellData;
pub type CheckboxGroupConfiguration = GenericGroupConfiguration<CheckboxGroupConfigurationRevision>;
impl GroupAction for CheckboxGroupController {
type CellDataType = CheckboxCellData;
fn can_group(&self, _content: &str, _cell_data: &Self::CellDataType) -> bool {
false
}
@ -35,13 +40,10 @@ impl Groupable for CheckboxGroupController {
todo!()
}
fn move_row_if_match(
fn move_row(
&mut self,
_field_rev: &FieldRevision,
_row_rev: &RowRevision,
_row_changeset: &mut RowChangeset,
_cell_data: &Self::CellDataType,
_to_row_id: &str,
_context: MoveGroupRowContext,
) -> Vec<GroupRowsChangesetPB> {
todo!()
}
@ -55,12 +57,12 @@ impl GroupController for CheckboxGroupController {
pub struct CheckboxGroupGenerator();
impl GroupGenerator for CheckboxGroupGenerator {
type ConfigurationType = CheckboxGroupConfigurationPB;
type ConfigurationType = CheckboxGroupConfiguration;
type TypeOptionType = CheckboxTypeOptionPB;
fn generate_groups(
field_id: &str,
_configuration: &Option<Self::ConfigurationType>,
_configuration: &Self::ConfigurationType,
_type_option: &Option<Self::TypeOptionType>,
) -> Vec<Group> {
let check_group = Group::new(

View File

@ -0,0 +1,5 @@
mod checkbox_controller;
mod select_option_controller;
pub use checkbox_controller::*;
pub use select_option_controller::*;

View File

@ -0,0 +1,7 @@
mod multi_select_controller;
mod single_select_controller;
mod util;
pub use multi_select_controller::*;
pub use single_select_controller::*;
pub use util::*;

View File

@ -0,0 +1,98 @@
use crate::entities::GroupRowsChangesetPB;
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
use crate::services::group::action::GroupAction;
use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
};
use crate::services::group::controller_impls::select_option_controller::util::*;
use crate::services::group::entities::Group;
use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
// MultiSelect
pub type MultiSelectGroupController = GenericGroupController<
SelectOptionGroupConfigurationRevision,
MultiSelectTypeOptionPB,
MultiSelectGroupGenerator,
SelectOptionCellDataParser,
>;
impl GroupAction for MultiSelectGroupController {
type CellDataType = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content)
}
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.configuration.with_mut_groups(|group| {
add_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn remove_row_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.configuration.with_mut_groups(|group| {
remove_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn move_row(
&mut self,
cell_data: &Self::CellDataType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsChangesetPB> {
let mut group_changeset = vec![];
self.configuration.with_mut_groups(|group| {
move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
});
group_changeset
}
}
impl GroupController for MultiSelectGroupController {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
match self.configuration.get_group(group_id) {
None => tracing::warn!("Can not find the group: {}", group_id),
Some((_, group)) => {
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
row_rev.cells.insert(field_rev.id.clone(), cell_rev);
}
}
}
}
pub struct MultiSelectGroupGenerator();
impl GroupGenerator for MultiSelectGroupGenerator {
type ConfigurationType = SelectOptionGroupConfiguration;
type TypeOptionType = MultiSelectTypeOptionPB;
fn generate_groups(
field_id: &str,
_configuration: &Self::ConfigurationType,
type_option: &Option<Self::TypeOptionType>,
) -> Vec<Group> {
match type_option {
None => vec![],
Some(type_option) => type_option
.options
.iter()
.map(|option| {
Group::new(
option.id.clone(),
field_id.to_owned(),
option.name.clone(),
option.id.clone(),
)
})
.collect(),
}
}
}

View File

@ -0,0 +1,100 @@
use crate::entities::{GroupRowsChangesetPB, RowPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
use crate::services::group::action::GroupAction;
use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
};
use crate::services::group::controller_impls::select_option_controller::util::*;
use crate::services::group::entities::Group;
use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision};
// SingleSelect
pub type SingleSelectGroupController = GenericGroupController<
SelectOptionGroupConfigurationRevision,
SingleSelectTypeOptionPB,
SingleSelectGroupGenerator,
SelectOptionCellDataParser,
>;
impl GroupAction for SingleSelectGroupController {
type CellDataType = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content)
}
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.configuration.with_mut_groups(|group| {
add_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn remove_row_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.configuration.with_mut_groups(|group| {
remove_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn move_row(
&mut self,
cell_data: &Self::CellDataType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsChangesetPB> {
let mut group_changeset = vec![];
self.configuration.with_mut_groups(|group| {
move_select_option_row(group, &mut group_changeset, cell_data, &mut context);
});
group_changeset
}
}
impl GroupController for SingleSelectGroupController {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
let group: Option<&mut Group> = self.configuration.get_mut_group(group_id);
match group {
None => {}
Some(group) => {
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
row_rev.cells.insert(field_rev.id.clone(), cell_rev);
group.add_row(RowPB::from(row_rev));
}
}
}
}
pub struct SingleSelectGroupGenerator();
impl GroupGenerator for SingleSelectGroupGenerator {
type ConfigurationType = SelectOptionGroupConfiguration;
type TypeOptionType = SingleSelectTypeOptionPB;
fn generate_groups(
field_id: &str,
_configuration: &Self::ConfigurationType,
type_option: &Option<Self::TypeOptionType>,
) -> Vec<Group> {
match type_option {
None => vec![],
Some(type_option) => type_option
.options
.iter()
.map(|option| {
Group::new(
option.id.clone(),
field_id.to_owned(),
option.name.clone(),
option.id.clone(),
)
})
.collect(),
}
}
}

View File

@ -0,0 +1,105 @@
use crate::entities::{GroupRowsChangesetPB, InsertedRowPB, RowPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::SelectOptionCellDataPB;
use crate::services::group::configuration::GenericGroupConfiguration;
use crate::services::group::Group;
use crate::services::group::controller::MoveGroupRowContext;
use flowy_grid_data_model::revision::{RowRevision, SelectOptionGroupConfigurationRevision};
pub type SelectOptionGroupConfiguration = GenericGroupConfiguration<SelectOptionGroupConfigurationRevision>;
pub fn add_row(
group: &mut Group,
changesets: &mut Vec<GroupRowsChangesetPB>,
cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision,
) {
cell_data.select_options.iter().for_each(|option| {
if option.id == group.id {
if !group.contains_row(&row_rev.id) {
let row_pb = RowPB::from(row_rev);
changesets.push(GroupRowsChangesetPB::insert(
group.id.clone(),
vec![InsertedRowPB::new(row_pb.clone())],
));
group.add_row(row_pb);
}
} else if group.contains_row(&row_rev.id) {
changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
group.remove_row(&row_rev.id);
}
});
}
pub fn remove_row(
group: &mut Group,
changesets: &mut Vec<GroupRowsChangesetPB>,
cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision,
) {
cell_data.select_options.iter().for_each(|option| {
if option.id == group.id && group.contains_row(&row_rev.id) {
changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
group.remove_row(&row_rev.id);
}
});
}
pub fn move_select_option_row(
group: &mut Group,
group_changeset: &mut Vec<GroupRowsChangesetPB>,
_cell_data: &SelectOptionCellDataPB,
context: &mut MoveGroupRowContext,
) {
let MoveGroupRowContext {
row_rev,
row_changeset,
field_rev,
to_group_id,
to_row_id,
} = context;
let from_index = group.index_of_row(&row_rev.id);
let to_index = match to_row_id {
None => None,
Some(to_row_id) => group.index_of_row(to_row_id),
};
// Remove the row in which group contains it
if from_index.is_some() {
group_changeset.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
tracing::debug!("Group:{} remove row:{}", group.id, row_rev.id);
group.remove_row(&row_rev.id);
}
if group.id == *to_group_id {
let row_pb = RowPB::from(*row_rev);
let mut inserted_row = InsertedRowPB::new(row_pb.clone());
match to_index {
None => {
group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row]));
tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
group.add_row(row_pb);
}
Some(to_index) => {
if to_index < group.number_of_row() {
tracing::debug!("Group:{} insert row:{} at {} ", group.id, row_rev.id, to_index);
inserted_row.index = Some(to_index as i32);
group.insert_row(to_index, row_pb);
} else {
tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
group.add_row(row_pb);
}
group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row]));
}
}
// Update the corresponding row's cell content.
if from_index.is_none() {
tracing::debug!("Mark row:{} belong to group:{}", row_rev.id, group.id);
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
row_changeset.cell_by_field_id.insert(field_rev.id.clone(), cell_rev);
}
}
}

View File

@ -0,0 +1,73 @@
use crate::entities::{GroupPB, RowPB};
#[derive(Clone)]
pub struct Group {
pub id: String,
pub field_id: String,
pub desc: String,
rows: Vec<RowPB>,
/// [content] is used to determine which group the cell belongs to.
pub content: String,
}
impl std::convert::From<Group> for GroupPB {
fn from(group: Group) -> Self {
Self {
field_id: group.field_id,
group_id: group.id,
desc: group.desc,
rows: group.rows,
}
}
}
impl Group {
pub fn new(id: String, field_id: String, desc: String, content: String) -> Self {
Self {
id,
field_id,
desc,
rows: vec![],
content,
}
}
pub fn contains_row(&self, row_id: &str) -> bool {
self.rows.iter().any(|row| row.id == row_id)
}
pub fn remove_row(&mut self, row_id: &str) {
match self.rows.iter().position(|row| row.id == row_id) {
None => {}
Some(pos) => {
self.rows.remove(pos);
}
}
}
pub fn add_row(&mut self, row_pb: RowPB) {
match self.rows.iter().find(|row| row.id == row_pb.id) {
None => {
self.rows.push(row_pb);
}
Some(_) => {}
}
}
pub fn insert_row(&mut self, index: usize, row_pb: RowPB) {
if index < self.rows.len() {
self.rows.insert(index, row_pb);
} else {
tracing::error!("Insert row index:{} beyond the bounds:{},", index, self.rows.len());
}
}
pub fn index_of_row(&self, row_id: &str) -> Option<usize> {
self.rows.iter().position(|row| row.id == row_id)
}
pub fn number_of_row(&self) -> usize {
self.rows.len()
}
}

View File

@ -1,296 +0,0 @@
use crate::entities::{GroupPB, GroupRowsChangesetPB, RowPB};
use crate::services::cell::{decode_any_cell_data, CellBytesParser};
use bytes::Bytes;
use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{
FieldRevision, GroupConfigurationRevision, RowChangeset, RowRevision, TypeOptionDataDeserializer,
};
use indexmap::IndexMap;
use std::marker::PhantomData;
use std::sync::Arc;
pub trait GroupGenerator {
type ConfigurationType;
type TypeOptionType;
fn generate_groups(
field_id: &str,
configuration: &Option<Self::ConfigurationType>,
type_option: &Option<Self::TypeOptionType>,
) -> Vec<Group>;
}
pub trait Groupable: Send + Sync {
type CellDataType;
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB>;
fn remove_row_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB>;
fn move_row_if_match(
&mut self,
field_rev: &FieldRevision,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
cell_data: &Self::CellDataType,
to_row_id: &str,
) -> Vec<GroupRowsChangesetPB>;
}
pub trait GroupController: GroupControllerSharedAction + Send + Sync {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
}
pub trait GroupControllerSharedAction: Send + Sync {
// The field that is used for grouping the rows
fn field_id(&self) -> &str;
fn groups(&self) -> Vec<Group>;
fn group_rows(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()>;
fn did_update_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
fn did_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
fn did_move_row(
&mut self,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
field_rev: &FieldRevision,
to_row_id: &str,
) -> FlowyResult<Vec<GroupRowsChangesetPB>>;
}
const DEFAULT_GROUP_ID: &str = "default_group";
/// C: represents the group configuration structure
/// T: the type option data deserializer that impl [TypeOptionDataDeserializer]
/// G: the group generator, [GroupGenerator]
/// P: the parser that impl [CellBytesParser] for the CellBytes
pub struct GenericGroupController<C, T, G, P> {
pub field_id: String,
pub groups_map: IndexMap<String, Group>,
default_group: Group,
pub type_option: Option<T>,
pub configuration: Option<C>,
group_action_phantom: PhantomData<G>,
cell_parser_phantom: PhantomData<P>,
}
#[derive(Clone)]
pub struct Group {
pub id: String,
pub field_id: String,
pub desc: String,
rows: Vec<RowPB>,
pub content: String,
}
impl std::convert::From<Group> for GroupPB {
fn from(group: Group) -> Self {
Self {
field_id: group.field_id,
group_id: group.id,
desc: group.desc,
rows: group.rows,
}
}
}
impl Group {
pub fn new(id: String, field_id: String, desc: String, content: String) -> Self {
Self {
id,
field_id,
desc,
rows: vec![],
content,
}
}
pub fn contains_row(&self, row_id: &str) -> bool {
self.rows.iter().any(|row| row.id == row_id)
}
pub fn remove_row(&mut self, row_id: &str) {
match self.rows.iter().position(|row| row.id == row_id) {
None => {}
Some(pos) => {
self.rows.remove(pos);
}
}
}
pub fn add_row(&mut self, row_pb: RowPB) {
match self.rows.iter().find(|row| row.id == row_pb.id) {
None => {
self.rows.push(row_pb);
}
Some(_) => {}
}
}
pub fn insert_row(&mut self, index: usize, row_pb: RowPB) {
if index < self.rows.len() {
self.rows.insert(index, row_pb);
} else {
tracing::error!("Insert row index:{} beyond the bounds:{},", index, self.rows.len());
}
}
pub fn index_of_row(&self, row_id: &str) -> Option<usize> {
self.rows.iter().position(|row| row.id == row_id)
}
pub fn number_of_row(&self) -> usize {
self.rows.len()
}
}
impl<C, T, G, P> GenericGroupController<C, T, G, P>
where
C: TryFrom<Bytes, Error = protobuf::ProtobufError>,
T: TypeOptionDataDeserializer,
G: GroupGenerator<ConfigurationType = C, TypeOptionType = T>,
{
pub fn new(field_rev: &Arc<FieldRevision>, configuration: GroupConfigurationRevision) -> FlowyResult<Self> {
let configuration = match configuration.content {
None => None,
Some(content) => Some(C::try_from(Bytes::from(content))?),
};
let field_type_rev = field_rev.ty;
let type_option = field_rev.get_type_option_entry::<T>(field_type_rev);
let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
let default_group = Group::new(
DEFAULT_GROUP_ID.to_owned(),
field_rev.id.clone(),
format!("No {}", field_rev.name),
"".to_string(),
);
Ok(Self {
field_id: field_rev.id.clone(),
groups_map: groups.into_iter().map(|group| (group.id.clone(), group)).collect(),
default_group,
type_option,
configuration,
group_action_phantom: PhantomData,
cell_parser_phantom: PhantomData,
})
}
}
impl<C, T, G, P> GroupControllerSharedAction for GenericGroupController<C, T, G, P>
where
P: CellBytesParser,
Self: Groupable<CellDataType = P::Object>,
{
fn field_id(&self) -> &str {
&self.field_id
}
fn groups(&self) -> Vec<Group> {
let default_group = self.default_group.clone();
let mut groups: Vec<Group> = self.groups_map.values().cloned().collect();
if !default_group.rows.is_empty() {
groups.push(default_group);
}
groups
}
fn group_rows(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()> {
if self.configuration.is_none() {
return Ok(());
}
for row_rev in row_revs {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let mut records: Vec<GroupRecord> = vec![];
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
for group in self.groups_map.values() {
if self.can_group(&group.content, &cell_data) {
records.push(GroupRecord {
row: row_rev.into(),
group_id: group.id.clone(),
});
}
}
if records.is_empty() {
self.default_group.rows.push(row_rev.into());
} else {
for record in records {
if let Some(group) = self.groups_map.get_mut(&record.group_id) {
group.rows.push(record.row);
}
}
}
} else {
self.default_group.rows.push(row_rev.into());
}
}
Ok(())
}
fn did_update_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
Ok(self.add_row_if_match(row_rev, &cell_data))
} else {
Ok(vec![])
}
}
fn did_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
Ok(self.remove_row_if_match(row_rev, &cell_data))
} else {
Ok(vec![])
}
}
fn did_move_row(
&mut self,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
field_rev: &FieldRevision,
to_row_id: &str,
) -> FlowyResult<Vec<GroupRowsChangesetPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev);
let cell_data = cell_bytes.parser::<P>()?;
tracing::trace!("Move row:{} to row:{}", row_rev.id, to_row_id);
Ok(self.move_row_if_match(field_rev, row_rev, row_changeset, &cell_data, to_row_id))
} else {
Ok(vec![])
}
}
}
struct GroupRecord {
row: RowPB,
group_id: String,
}

View File

@ -1,7 +0,0 @@
mod checkbox_group;
mod group_controller;
mod select_option_group;
pub use checkbox_group::*;
pub use group_controller::*;
pub use select_option_group::*;

View File

@ -1,287 +0,0 @@
use crate::entities::{GroupRowsChangesetPB, InsertedRowPB, RowPB, SelectOptionGroupConfigurationPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{
MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB,
};
use crate::services::group::{GenericGroupController, Group, GroupController, GroupGenerator, Groupable};
use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision};
// SingleSelect
pub type SingleSelectGroupController = GenericGroupController<
SelectOptionGroupConfigurationPB,
SingleSelectTypeOptionPB,
SingleSelectGroupGenerator,
SelectOptionCellDataParser,
>;
impl Groupable for SingleSelectGroupController {
type CellDataType = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content)
}
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
add_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn remove_row_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
remove_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn move_row_if_match(
&mut self,
field_rev: &FieldRevision,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
cell_data: &Self::CellDataType,
to_row_id: &str,
) -> Vec<GroupRowsChangesetPB> {
let mut group_changeset = vec![];
self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
move_row(
group,
&mut group_changeset,
field_rev,
row_rev,
row_changeset,
cell_data,
to_row_id,
);
});
group_changeset
}
}
impl GroupController for SingleSelectGroupController {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
let group: Option<&mut Group> = self.groups_map.get_mut(group_id);
match group {
None => {}
Some(group) => {
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
row_rev.cells.insert(field_rev.id.clone(), cell_rev);
group.add_row(RowPB::from(row_rev));
}
}
}
}
pub struct SingleSelectGroupGenerator();
impl GroupGenerator for SingleSelectGroupGenerator {
type ConfigurationType = SelectOptionGroupConfigurationPB;
type TypeOptionType = SingleSelectTypeOptionPB;
fn generate_groups(
field_id: &str,
_configuration: &Option<Self::ConfigurationType>,
type_option: &Option<Self::TypeOptionType>,
) -> Vec<Group> {
match type_option {
None => vec![],
Some(type_option) => type_option
.options
.iter()
.map(|option| {
Group::new(
option.id.clone(),
field_id.to_owned(),
option.name.clone(),
option.id.clone(),
)
})
.collect(),
}
}
}
// MultiSelect
pub type MultiSelectGroupController = GenericGroupController<
SelectOptionGroupConfigurationPB,
MultiSelectTypeOptionPB,
MultiSelectGroupGenerator,
SelectOptionCellDataParser,
>;
impl Groupable for MultiSelectGroupController {
type CellDataType = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content)
}
fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
add_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn remove_row_if_match(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
) -> Vec<GroupRowsChangesetPB> {
let mut changesets = vec![];
self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
remove_row(group, &mut changesets, cell_data, row_rev);
});
changesets
}
fn move_row_if_match(
&mut self,
field_rev: &FieldRevision,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
cell_data: &Self::CellDataType,
to_row_id: &str,
) -> Vec<GroupRowsChangesetPB> {
let mut group_changeset = vec![];
self.groups_map.iter_mut().for_each(|(_, group): (_, &mut Group)| {
move_row(
group,
&mut group_changeset,
field_rev,
row_rev,
row_changeset,
cell_data,
to_row_id,
);
});
group_changeset
}
}
impl GroupController for MultiSelectGroupController {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) {
let group: Option<&Group> = self.groups_map.get(group_id);
match group {
None => tracing::warn!("Can not find the group: {}", group_id),
Some(group) => {
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
row_rev.cells.insert(field_rev.id.clone(), cell_rev);
}
}
}
}
pub struct MultiSelectGroupGenerator();
impl GroupGenerator for MultiSelectGroupGenerator {
type ConfigurationType = SelectOptionGroupConfigurationPB;
type TypeOptionType = MultiSelectTypeOptionPB;
fn generate_groups(
field_id: &str,
_configuration: &Option<Self::ConfigurationType>,
type_option: &Option<Self::TypeOptionType>,
) -> Vec<Group> {
match type_option {
None => vec![],
Some(type_option) => type_option
.options
.iter()
.map(|option| {
Group::new(
option.id.clone(),
field_id.to_owned(),
option.name.clone(),
option.id.clone(),
)
})
.collect(),
}
}
}
fn add_row(
group: &mut Group,
changesets: &mut Vec<GroupRowsChangesetPB>,
cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision,
) {
cell_data.select_options.iter().for_each(|option| {
if option.id == group.id {
if !group.contains_row(&row_rev.id) {
let row_pb = RowPB::from(row_rev);
changesets.push(GroupRowsChangesetPB::insert(
group.id.clone(),
vec![InsertedRowPB::new(row_pb.clone())],
));
group.add_row(row_pb);
}
} else if group.contains_row(&row_rev.id) {
changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
group.remove_row(&row_rev.id);
}
});
}
fn remove_row(
group: &mut Group,
changesets: &mut Vec<GroupRowsChangesetPB>,
cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision,
) {
cell_data.select_options.iter().for_each(|option| {
if option.id == group.id && group.contains_row(&row_rev.id) {
changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
group.remove_row(&row_rev.id);
}
});
}
fn move_row(
group: &mut Group,
group_changeset: &mut Vec<GroupRowsChangesetPB>,
field_rev: &FieldRevision,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
cell_data: &SelectOptionCellDataPB,
to_row_id: &str,
) {
cell_data.select_options.iter().for_each(|option| {
// Remove the row in which group contains the row
let is_group_contains = group.contains_row(&row_rev.id);
let to_index = group.index_of_row(to_row_id);
if option.id == group.id && is_group_contains {
group_changeset.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
group.remove_row(&row_rev.id);
}
// Find the inserted group
if let Some(to_index) = to_index {
let row_pb = RowPB::from(row_rev);
let inserted_row = InsertedRowPB {
row: row_pb.clone(),
index: Some(to_index as i32),
};
group_changeset.push(GroupRowsChangesetPB::insert(group.id.clone(), vec![inserted_row]));
if group.number_of_row() == to_index {
group.add_row(row_pb);
} else {
group.insert_row(to_index, row_pb);
}
}
// If the inserted row comes from other group, it needs to update the corresponding cell content.
if to_index.is_some() && option.id != group.id {
// Update the corresponding row's cell content.
let cell_rev = insert_select_option_cell(group.id.clone(), field_rev);
row_changeset.cell_by_field_id.insert(field_rev.id.clone(), cell_rev);
}
});
}

View File

@ -1,43 +1,49 @@
use crate::entities::{
CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, GroupRowsChangesetPB,
NumberGroupConfigurationPB, SelectOptionGroupConfigurationPB, TextGroupConfigurationPB, UrlGroupConfigurationPB,
};
use crate::entities::{FieldType, GroupRowsChangesetPB};
use crate::services::group::configuration::GroupConfigurationReader;
use crate::services::group::controller::{GroupController, MoveGroupRowContext};
use crate::services::group::{
CheckboxGroupController, Group, GroupController, MultiSelectGroupController, SingleSelectGroupController,
CheckboxGroupConfiguration, CheckboxGroupController, Group, GroupConfigurationWriter, MultiSelectGroupController,
SelectOptionGroupConfiguration, SingleSelectGroupController,
};
use bytes::Bytes;
use flowy_error::FlowyResult;
use flowy_grid_data_model::revision::{
gen_grid_group_id, FieldRevision, GroupConfigurationRevision, RowChangeset, RowRevision,
CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision,
NumberGroupConfigurationRevision, RowChangeset, RowRevision, SelectOptionGroupConfigurationRevision,
TextGroupConfigurationRevision, UrlGroupConfigurationRevision,
};
use lib_infra::future::AFFuture;
use std::future::Future;
use std::sync::Arc;
use tokio::sync::RwLock;
pub trait GroupConfigurationDelegate: Send + Sync + 'static {
fn get_group_configuration(&self, field_rev: Arc<FieldRevision>) -> AFFuture<GroupConfigurationRevision>;
}
pub(crate) struct GroupService {
delegate: Box<dyn GroupConfigurationDelegate>,
group_controller: Option<Arc<RwLock<dyn GroupController>>>,
configuration_reader: Arc<dyn GroupConfigurationReader>,
configuration_writer: Arc<dyn GroupConfigurationWriter>,
group_controller: Option<Box<dyn GroupController>>,
}
impl GroupService {
pub(crate) async fn new(delegate: Box<dyn GroupConfigurationDelegate>) -> Self {
pub(crate) async fn new<R, W>(configuration_reader: R, configuration_writer: W) -> Self
where
R: GroupConfigurationReader,
W: GroupConfigurationWriter,
{
Self {
delegate,
configuration_reader: Arc::new(configuration_reader),
configuration_writer: Arc::new(configuration_writer),
group_controller: None,
}
}
pub(crate) async fn groups(&self) -> Vec<Group> {
if let Some(group_action_handler) = self.group_controller.as_ref() {
group_action_handler.read().await.groups()
} else {
vec![]
}
self.group_controller
.as_ref()
.and_then(|group_controller| Some(group_controller.groups()))
.unwrap_or(vec![])
}
pub(crate) async fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
self.group_controller
.as_ref()
.and_then(|group_controller| group_controller.get_group(group_id))
}
pub(crate) async fn load_groups(
@ -47,37 +53,37 @@ impl GroupService {
) -> Option<Vec<Group>> {
let field_rev = find_group_field(field_revs)?;
let field_type: FieldType = field_rev.ty.into();
let configuration = self.delegate.get_group_configuration(field_rev.clone()).await;
match self
.build_groups(&field_type, &field_rev, row_revs, configuration)
.await
{
Ok(groups) => Some(groups),
Err(_) => None,
}
let mut group_controller = self.make_group_controller(&field_type, &field_rev).await.ok()??;
let groups = match group_controller.fill_groups(&row_revs, &field_rev) {
Ok(groups) => groups,
Err(e) => {
tracing::error!("Fill groups failed:{:?}", e);
vec![]
}
};
self.group_controller = Some(group_controller);
Some(groups)
}
pub(crate) async fn will_create_row<F, O>(&self, row_rev: &mut RowRevision, group_id: &str, get_field_fn: F)
pub(crate) async fn will_create_row<F, O>(&mut self, row_rev: &mut RowRevision, group_id: &str, get_field_fn: F)
where
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
{
if let Some(group_controller) = self.group_controller.as_ref() {
let field_id = group_controller.read().await.field_id().to_owned();
if let Some(group_controller) = self.group_controller.as_mut() {
let field_id = group_controller.field_id().to_owned();
match get_field_fn(field_id).await {
None => {}
Some(field_rev) => {
group_controller
.write()
.await
.will_create_row(row_rev, &field_rev, group_id);
group_controller.will_create_row(row_rev, &field_rev, group_id);
}
}
}
}
pub(crate) async fn did_delete_row<F, O>(
&self,
&mut self,
row_rev: &RowRevision,
get_field_fn: F,
) -> Option<Vec<GroupRowsChangesetPB>>
@ -85,11 +91,11 @@ impl GroupService {
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
{
let group_controller = self.group_controller.as_ref()?;
let field_id = group_controller.read().await.field_id().to_owned();
let group_controller = self.group_controller.as_mut()?;
let field_id = group_controller.field_id().to_owned();
let field_rev = get_field_fn(field_id).await?;
match group_controller.write().await.did_delete_row(row_rev, &field_rev) {
match group_controller.did_delete_row(row_rev, &field_rev) {
Ok(changesets) => Some(changesets),
Err(e) => {
tracing::error!("Delete group data failed, {:?}", e);
@ -98,26 +104,30 @@ impl GroupService {
}
}
pub(crate) async fn did_move_row<F, O>(
&self,
pub(crate) async fn move_group_row<F, O>(
&mut self,
row_rev: &RowRevision,
row_changeset: &mut RowChangeset,
upper_row_id: &str,
to_group_id: &str,
to_row_id: Option<String>,
get_field_fn: F,
) -> Option<Vec<GroupRowsChangesetPB>>
where
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
{
let group_controller = self.group_controller.as_ref()?;
let field_id = group_controller.read().await.field_id().to_owned();
let group_controller = self.group_controller.as_mut()?;
let field_id = group_controller.field_id().to_owned();
let field_rev = get_field_fn(field_id).await?;
let move_row_context = MoveGroupRowContext {
row_rev,
row_changeset,
field_rev: field_rev.as_ref(),
to_group_id,
to_row_id,
};
match group_controller
.write()
.await
.did_move_row(row_rev, row_changeset, &field_rev, upper_row_id)
{
match group_controller.move_group_row(move_row_context) {
Ok(changesets) => Some(changesets),
Err(e) => {
tracing::error!("Move group data failed, {:?}", e);
@ -128,7 +138,7 @@ impl GroupService {
#[tracing::instrument(level = "trace", skip_all)]
pub(crate) async fn did_update_row<F, O>(
&self,
&mut self,
row_rev: &RowRevision,
get_field_fn: F,
) -> Option<Vec<GroupRowsChangesetPB>>
@ -136,11 +146,11 @@ impl GroupService {
F: FnOnce(String) -> O,
O: Future<Output = Option<Arc<FieldRevision>>> + Send + Sync + 'static,
{
let group_controller = self.group_controller.as_ref()?;
let field_id = group_controller.read().await.field_id().to_owned();
let group_controller = self.group_controller.as_mut()?;
let field_id = group_controller.field_id().to_owned();
let field_rev = get_field_fn(field_id).await?;
match group_controller.write().await.did_update_row(row_rev, &field_rev) {
match group_controller.did_update_row(row_rev, &field_rev) {
Ok(changeset) => Some(changeset),
Err(e) => {
tracing::error!("Update group data failed, {:?}", e);
@ -149,14 +159,24 @@ impl GroupService {
}
}
#[tracing::instrument(level = "trace", skip_all, err)]
async fn build_groups(
&mut self,
#[tracing::instrument(level = "trace", skip_all)]
pub(crate) async fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> {
match self.group_controller.as_mut() {
None => Ok(()),
Some(group_controller) => {
let _ = group_controller.move_group(from_group_id, to_group_id)?;
Ok(())
}
}
}
#[tracing::instrument(level = "trace", skip(self, field_rev), err)]
async fn make_group_controller(
&self,
field_type: &FieldType,
field_rev: &Arc<FieldRevision>,
row_revs: Vec<Arc<RowRevision>>,
configuration: GroupConfigurationRevision,
) -> FlowyResult<Vec<Group>> {
) -> FlowyResult<Option<Box<dyn GroupController>>> {
let mut group_controller: Option<Box<dyn GroupController>> = None;
match field_type {
FieldType::RichText => {
// let generator = GroupGenerator::<TextGroupConfigurationPB>::from_configuration(configuration);
@ -168,31 +188,40 @@ impl GroupService {
// let generator = GroupGenerator::<DateGroupConfigurationPB>::from_configuration(configuration);
}
FieldType::SingleSelect => {
let controller = SingleSelectGroupController::new(field_rev, configuration)?;
self.group_controller = Some(Arc::new(RwLock::new(controller)));
let configuration = SelectOptionGroupConfiguration::new(
field_rev.clone(),
self.configuration_reader.clone(),
self.configuration_writer.clone(),
)
.await?;
let controller = SingleSelectGroupController::new(field_rev, configuration).await?;
group_controller = Some(Box::new(controller));
}
FieldType::MultiSelect => {
let controller = MultiSelectGroupController::new(field_rev, configuration)?;
self.group_controller = Some(Arc::new(RwLock::new(controller)));
let configuration = SelectOptionGroupConfiguration::new(
field_rev.clone(),
self.configuration_reader.clone(),
self.configuration_writer.clone(),
)
.await?;
let controller = MultiSelectGroupController::new(field_rev, configuration).await?;
group_controller = Some(Box::new(controller));
}
FieldType::Checkbox => {
let controller = CheckboxGroupController::new(field_rev, configuration)?;
self.group_controller = Some(Arc::new(RwLock::new(controller)));
let configuration = CheckboxGroupConfiguration::new(
field_rev.clone(),
self.configuration_reader.clone(),
self.configuration_writer.clone(),
)
.await?;
let controller = CheckboxGroupController::new(field_rev, configuration).await?;
group_controller = Some(Box::new(controller));
}
FieldType::URL => {
// let generator = GroupGenerator::<UrlGroupConfigurationPB>::from_configuration(configuration);
}
};
let mut groups = vec![];
if let Some(group_action_handler) = self.group_controller.as_ref() {
let mut write_guard = group_action_handler.write().await;
let _ = write_guard.group_rows(&row_revs, field_rev)?;
groups = write_guard.groups();
drop(write_guard);
}
Ok(groups)
Ok(group_controller)
}
}
@ -208,20 +237,41 @@ fn find_group_field(field_revs: &[Arc<FieldRevision>]) -> Option<Arc<FieldRevisi
}
pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision {
let field_id = field_rev.id.clone();
let field_type_rev = field_rev.ty;
let field_type: FieldType = field_rev.ty.into();
let bytes: Bytes = match field_type {
FieldType::RichText => TextGroupConfigurationPB::default().try_into().unwrap(),
FieldType::Number => NumberGroupConfigurationPB::default().try_into().unwrap(),
FieldType::DateTime => DateGroupConfigurationPB::default().try_into().unwrap(),
FieldType::SingleSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(),
FieldType::MultiSelect => SelectOptionGroupConfigurationPB::default().try_into().unwrap(),
FieldType::Checkbox => CheckboxGroupConfigurationPB::default().try_into().unwrap(),
FieldType::URL => UrlGroupConfigurationPB::default().try_into().unwrap(),
};
GroupConfigurationRevision {
id: gen_grid_group_id(),
field_id: field_rev.id.clone(),
field_type_rev: field_rev.ty,
content: Some(bytes.to_vec()),
match field_type {
FieldType::RichText => {
GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default())
.unwrap()
}
FieldType::Number => {
GroupConfigurationRevision::new(field_id, field_type_rev, NumberGroupConfigurationRevision::default())
.unwrap()
}
FieldType::DateTime => {
GroupConfigurationRevision::new(field_id, field_type_rev, DateGroupConfigurationRevision::default())
.unwrap()
}
FieldType::SingleSelect => GroupConfigurationRevision::new(
field_id,
field_type_rev,
SelectOptionGroupConfigurationRevision::default(),
)
.unwrap(),
FieldType::MultiSelect => GroupConfigurationRevision::new(
field_id,
field_type_rev,
SelectOptionGroupConfigurationRevision::default(),
)
.unwrap(),
FieldType::Checkbox => {
GroupConfigurationRevision::new(field_id, field_type_rev, CheckboxGroupConfigurationRevision::default())
.unwrap()
}
FieldType::URL => {
GroupConfigurationRevision::new(field_id, field_type_rev, UrlGroupConfigurationRevision::default()).unwrap()
}
}
}

View File

@ -1,5 +1,11 @@
mod group_generator;
mod action;
mod configuration;
mod controller;
mod controller_impls;
mod entities;
mod group_service;
pub(crate) use group_generator::*;
pub(crate) use configuration::*;
pub(crate) use controller_impls::*;
pub(crate) use entities::*;
pub(crate) use group_service::*;

View File

@ -1,11 +1,4 @@
use crate::entities::{
GridLayout, GridLayoutPB, GridSettingPB, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB,
RepeatedGridSortPB,
};
use flowy_grid_data_model::revision::{FieldRevision, SettingRevision};
use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams};
use std::collections::HashMap;
use std::sync::Arc;
use crate::entities::{CreateFilterParams, DeleteFilterParams, GridLayout, GridSettingChangesetParams};
pub struct GridSettingChangesetBuilder {
params: GridSettingChangesetParams,
@ -20,13 +13,11 @@ impl GridSettingChangesetBuilder {
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 {
pub fn insert_filter(mut self, params: CreateFilterParams) -> Self {
self.params.insert_filter = Some(params);
self
}
@ -40,42 +31,3 @@ impl GridSettingChangesetBuilder {
self.params
}
}
pub fn make_grid_setting(grid_setting_rev: &SettingRevision, field_revs: &[Arc<FieldRevision>]) -> GridSettingPB {
let current_layout_type: GridLayout = grid_setting_rev.layout.clone().into();
let filters_by_field_id = grid_setting_rev
.get_all_filters(field_revs)
.map(|filters_by_field_id| {
filters_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridConfigurationFilterPB>>()
})
.unwrap_or_default();
let groups_by_field_id = grid_setting_rev
.get_all_groups(field_revs)
.map(|groups_by_field_id| {
groups_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridGroupConfigurationPB>>()
})
.unwrap_or_default();
let sorts_by_field_id = grid_setting_rev
.get_all_sort()
.map(|sorts_by_field_id| {
sorts_by_field_id
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect::<HashMap<String, RepeatedGridSortPB>>()
})
.unwrap_or_default();
GridSettingPB {
layouts: GridLayoutPB::all(),
current_layout_type,
filter_configuration_by_field_id: filters_by_field_id,
group_configuration_by_field_id: groups_by_field_id,
sorts_by_field_id,
}
}

View File

@ -1,7 +1,6 @@
use crate::grid::grid_editor::GridEditorTest;
use flowy_grid::entities::InsertFieldParams;
use flowy_grid::entities::{FieldChangesetParams, InsertFieldParams};
use flowy_grid_data_model::revision::FieldRevision;
use flowy_sync::entities::grid::FieldChangesetParams;
pub enum FieldScript {
CreateField {

View File

@ -1,10 +1,10 @@
use crate::grid::field_test::script::FieldScript::*;
use crate::grid::field_test::script::GridFieldTest;
use crate::grid::field_test::util::*;
use flowy_grid::entities::FieldChangesetParams;
use flowy_grid::services::field::selection_type_option::SelectOptionPB;
use flowy_grid::services::field::SingleSelectTypeOptionPB;
use flowy_grid_data_model::revision::TypeOptionDataEntry;
use flowy_sync::entities::grid::FieldChangesetParams;
#[tokio::test]
async fn grid_create_field() {

View File

@ -3,17 +3,12 @@
#![allow(dead_code)]
#![allow(unused_imports)]
use flowy_grid::entities::{CreateGridFilterPayloadPB, GridLayout, GridSettingPB};
use flowy_grid::entities::{CreateFilterParams, CreateGridFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB};
use flowy_grid::services::setting::GridSettingChangesetBuilder;
use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision};
use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams};
use crate::grid::grid_editor::GridEditorTest;
pub enum FilterScript {
#[allow(dead_code)]
UpdateGridSetting {
params: GridSettingChangesetParams,
},
InsertGridTableFilter {
payload: CreateGridFilterPayloadPB,
},
@ -50,27 +45,18 @@ impl GridFilterTest {
pub async fn run_script(&mut self, script: FilterScript) {
match script {
FilterScript::UpdateGridSetting { params } => {
let _ = self.editor.update_grid_setting(params).await.unwrap();
}
FilterScript::InsertGridTableFilter { payload } => {
let params: CreateGridFilterParams = payload.try_into().unwrap();
let layout_type = GridLayout::Table;
let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
.insert_filter(params)
.build();
let _ = self.editor.update_grid_setting(params).await.unwrap();
let params: CreateFilterParams = payload.try_into().unwrap();
let _ = self.editor.update_filter(params).await.unwrap();
}
FilterScript::AssertTableFilterCount { count } => {
let filters = self.editor.get_grid_filter().await.unwrap();
assert_eq!(count as usize, filters.len());
}
FilterScript::DeleteGridTableFilter { filter_id, field_rev} => {
let layout_type = GridLayout::Table;
let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
.delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty })
.build();
let _ = self.editor.update_grid_setting(params).await.unwrap();
let params = DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty };
let _ = self.editor.delete_filter(params).await.unwrap();
}
FilterScript::AssertGridSetting { expected_setting } => {
let setting = self.editor.get_grid_setting().await.unwrap();

View File

@ -12,9 +12,6 @@ use flowy_grid::services::setting::GridSettingChangesetBuilder;
use flowy_grid_data_model::revision::*;
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
use flowy_sync::client_grid::GridBuilder;
use flowy_sync::entities::grid::{
CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams,
};
use flowy_test::helper::ViewTest;
use flowy_test::FlowySDKTest;
use std::collections::HashMap;

View File

@ -1,14 +1,20 @@
use crate::grid::grid_editor::GridEditorTest;
use flowy_grid::entities::{CreateRowParams, FieldType, GridLayout, GroupPB, MoveRowParams, RowPB};
use flowy_grid::entities::{
CreateRowParams, FieldType, GridLayout, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB,
};
use flowy_grid::services::cell::insert_select_option_cell;
use flowy_grid_data_model::revision::RowChangeset;
pub enum GroupScript {
AssertGroup {
AssertGroupRowCount {
group_index: usize,
row_count: usize,
},
AssertGroupCount(usize),
AssertGroup {
group_index: usize,
expected_group: GroupPB,
},
AssertRow {
group_index: usize,
row_index: usize,
@ -32,6 +38,10 @@ pub enum GroupScript {
row_index: usize,
to_group_index: usize,
},
MoveGroup {
from_group_index: usize,
to_group_index: usize,
},
}
pub struct GridGroupTest {
@ -52,7 +62,7 @@ impl GridGroupTest {
pub async fn run_script(&mut self, script: GroupScript) {
match script {
GroupScript::AssertGroup { group_index, row_count } => {
GroupScript::AssertGroupRowCount { group_index, row_count } => {
assert_eq!(row_count, self.group_at_index(group_index).await.rows.len());
}
GroupScript::AssertGroupCount(count) => {
@ -67,14 +77,16 @@ impl GridGroupTest {
} => {
let groups: Vec<GroupPB> = self.editor.load_groups().await.unwrap().items;
let from_row = groups.get(from_group_index).unwrap().rows.get(from_row_index).unwrap();
let to_row = groups.get(to_group_index).unwrap().rows.get(to_row_index).unwrap();
let params = MoveRowParams {
let to_group = groups.get(to_group_index).unwrap();
let to_row = to_group.rows.get(to_row_index).unwrap();
let params = MoveGroupRowParams {
view_id: self.inner.grid_id.clone(),
from_row_id: from_row.id.clone(),
to_row_id: to_row.id.clone(),
to_group_id: to_group.group_id.clone(),
to_row_id: Some(to_row.id.clone()),
};
self.editor.move_row(params).await.unwrap();
self.editor.move_group_row(params).await.unwrap();
}
GroupScript::AssertRow {
group_index,
@ -84,7 +96,6 @@ impl GridGroupTest {
//
let group = self.group_at_index(group_index).await;
let compare_row = group.rows.get(row_index).unwrap().clone();
assert_eq!(row.id, compare_row.id);
}
GroupScript::CreateRow { group_index } => {
@ -125,6 +136,27 @@ impl GridGroupTest {
row_changeset.cell_by_field_id.insert(field_id, cell_rev);
self.editor.update_row(row_changeset).await.unwrap();
}
GroupScript::MoveGroup {
from_group_index,
to_group_index,
} => {
let from_group = self.group_at_index(from_group_index).await;
let to_group = self.group_at_index(to_group_index).await;
let params = MoveGroupParams {
view_id: self.editor.grid_id.clone(),
from_group_id: from_group.group_id,
to_group_id: to_group.group_id,
};
self.editor.move_group(params).await.unwrap();
//
}
GroupScript::AssertGroup {
group_index,
expected_group: group_pb,
} => {
let group = self.group_at_index(group_index).await;
assert_eq!(group.group_id, group_pb.group_id);
}
}
}

View File

@ -2,19 +2,19 @@ use crate::grid::group_test::script::GridGroupTest;
use crate::grid::group_test::script::GroupScript::*;
#[tokio::test]
async fn board_init_test() {
async fn group_init_test() {
let mut test = GridGroupTest::new().await;
let scripts = vec![
AssertGroupCount(3),
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 2,
},
AssertGroup {
AssertGroupRowCount {
group_index: 1,
row_count: 2,
},
AssertGroup {
AssertGroupRowCount {
group_index: 2,
row_count: 1,
},
@ -23,7 +23,7 @@ async fn board_init_test() {
}
#[tokio::test]
async fn board_move_row_test() {
async fn group_move_row_test() {
let mut test = GridGroupTest::new().await;
let group = test.group_at_index(0).await;
let scripts = vec![
@ -34,7 +34,7 @@ async fn board_move_row_test() {
to_group_index: 0,
to_row_index: 1,
},
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 2,
},
@ -48,7 +48,7 @@ async fn board_move_row_test() {
}
#[tokio::test]
async fn board_move_row_to_other_group_test() {
async fn group_move_row_to_other_group_test() {
let mut test = GridGroupTest::new().await;
let group = test.group_at_index(0).await;
let scripts = vec![
@ -58,11 +58,11 @@ async fn board_move_row_to_other_group_test() {
to_group_index: 1,
to_row_index: 1,
},
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
AssertGroup {
AssertGroupRowCount {
group_index: 1,
row_count: 3,
},
@ -76,7 +76,7 @@ async fn board_move_row_to_other_group_test() {
}
#[tokio::test]
async fn board_move_row_to_other_group_and_reorder_test() {
async fn group_move_two_row_to_other_group_test() {
let mut test = GridGroupTest::new().await;
let group = test.group_at_index(0).await;
let scripts = vec![
@ -86,15 +86,41 @@ async fn board_move_row_to_other_group_and_reorder_test() {
to_group_index: 1,
to_row_index: 1,
},
MoveRow {
from_group_index: 1,
from_row_index: 1,
to_group_index: 1,
to_row_index: 2,
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
AssertGroupRowCount {
group_index: 1,
row_count: 3,
},
AssertRow {
group_index: 1,
row_index: 2,
row_index: 1,
row: group.rows.get(0).unwrap().clone(),
},
];
test.run_scripts(scripts).await;
let group = test.group_at_index(0).await;
let scripts = vec![
MoveRow {
from_group_index: 0,
from_row_index: 0,
to_group_index: 1,
to_row_index: 1,
},
AssertGroupRowCount {
group_index: 0,
row_count: 0,
},
AssertGroupRowCount {
group_index: 1,
row_count: 4,
},
AssertRow {
group_index: 1,
row_index: 1,
row: group.rows.get(0).unwrap().clone(),
},
];
@ -102,17 +128,84 @@ async fn board_move_row_to_other_group_and_reorder_test() {
}
#[tokio::test]
async fn board_create_row_test() {
async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() {
let mut test = GridGroupTest::new().await;
let group_0 = test.group_at_index(0).await;
let group_1 = test.group_at_index(1).await;
let scripts = vec![
MoveRow {
from_group_index: 0,
from_row_index: 0,
to_group_index: 1,
to_row_index: 1,
},
AssertRow {
group_index: 1,
row_index: 1,
row: group_0.rows.get(0).unwrap().clone(),
},
];
test.run_scripts(scripts).await;
let scripts = vec![
MoveRow {
from_group_index: 1,
from_row_index: 0,
to_group_index: 1,
to_row_index: 2,
},
AssertRow {
group_index: 1,
row_index: 2,
row: group_1.rows.get(0).unwrap().clone(),
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() {
let mut test = GridGroupTest::new().await;
let scripts = vec![MoveRow {
from_group_index: 0,
from_row_index: 0,
to_group_index: 1,
to_row_index: 1,
}];
test.run_scripts(scripts).await;
let group = test.group_at_index(1).await;
let scripts = vec![
AssertGroupRowCount {
group_index: 1,
row_count: 3,
},
MoveRow {
from_group_index: 1,
from_row_index: 2,
to_group_index: 1,
to_row_index: 0,
},
AssertRow {
group_index: 1,
row_index: 0,
row: group.rows.get(2).unwrap().clone(),
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn group_create_row_test() {
let mut test = GridGroupTest::new().await;
let scripts = vec![
CreateRow { group_index: 0 },
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 3,
},
CreateRow { group_index: 1 },
CreateRow { group_index: 1 },
AssertGroup {
AssertGroupRowCount {
group_index: 1,
row_count: 4,
},
@ -121,14 +214,14 @@ async fn board_create_row_test() {
}
#[tokio::test]
async fn board_delete_row_test() {
async fn group_delete_row_test() {
let mut test = GridGroupTest::new().await;
let scripts = vec![
DeleteRow {
group_index: 0,
row_index: 0,
},
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
@ -137,7 +230,7 @@ async fn board_delete_row_test() {
}
#[tokio::test]
async fn board_delete_all_row_test() {
async fn group_delete_all_row_test() {
let mut test = GridGroupTest::new().await;
let scripts = vec![
DeleteRow {
@ -148,7 +241,7 @@ async fn board_delete_all_row_test() {
group_index: 0,
row_index: 0,
},
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 0,
},
@ -157,7 +250,7 @@ async fn board_delete_all_row_test() {
}
#[tokio::test]
async fn board_update_row_test() {
async fn group_update_row_test() {
let mut test = GridGroupTest::new().await;
let scripts = vec![
// Update the row at 0 in group0 by setting the row's group field data
@ -166,11 +259,11 @@ async fn board_update_row_test() {
row_index: 0,
to_group_index: 1,
},
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
AssertGroup {
AssertGroupRowCount {
group_index: 1,
row_count: 3,
},
@ -179,7 +272,7 @@ async fn board_update_row_test() {
}
#[tokio::test]
async fn board_reorder_group_test() {
async fn group_reorder_group_test() {
let mut test = GridGroupTest::new().await;
let scripts = vec![
// Update the row at 0 in group0 by setting the row's group field data
@ -188,14 +281,36 @@ async fn board_reorder_group_test() {
row_index: 0,
to_group_index: 1,
},
AssertGroup {
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
AssertGroup {
AssertGroupRowCount {
group_index: 1,
row_count: 3,
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn group_move_group_test() {
let mut test = GridGroupTest::new().await;
let group_0 = test.group_at_index(0).await;
let group_1 = test.group_at_index(1).await;
let scripts = vec![
MoveGroup {
from_group_index: 0,
to_group_index: 1,
},
AssertGroup {
group_index: 0,
expected_group: group_1,
},
AssertGroup {
group_index: 1,
expected_group: group_0,
},
];
test.run_scripts(scripts).await;
}

View File

@ -74,7 +74,7 @@ fn crate_log_filter(level: String) -> String {
filters.push(format!("lib_ot={}", level));
filters.push(format!("lib_ws={}", level));
filters.push(format!("lib_infra={}", level));
// filters.push(format!("flowy_sync={}", level));
filters.push(format!("flowy_sync={}", level));
// filters.push(format!("flowy_revision={}", level));
// filters.push(format!("lib_dispatch={}", level));

View File

@ -125,6 +125,9 @@ pub enum ErrorCode {
#[display(fmt = "Invalid data")]
InvalidData = 1000,
#[display(fmt = "Out of bounds")]
OutOfBounds = 10001,
}
impl ErrorCode {

View File

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct FilterConfigurationRevision {
pub id: String,
pub field_id: String,
pub condition: u8,
pub content: Option<String>,
}

View File

@ -1,8 +1,7 @@
use crate::revision::{FieldRevision, FieldTypeRevision};
use crate::revision::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision};
use indexmap::IndexMap;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use serde_repr::*;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
@ -15,6 +14,7 @@ pub fn gen_grid_group_id() -> String {
nanoid!(6)
}
#[allow(dead_code)]
pub fn gen_grid_sort_id() -> String {
nanoid!(6)
}
@ -24,120 +24,12 @@ pub type FilterConfigurationsByFieldId = HashMap<String, Vec<Arc<FilterConfigura
//
pub type GroupConfiguration = Configuration<GroupConfigurationRevision>;
pub type GroupConfigurationsByFieldId = HashMap<String, Vec<Arc<GroupConfigurationRevision>>>;
//
pub type SortConfiguration = Configuration<SortConfigurationRevision>;
pub type SortConfigurationsByFieldId = HashMap<String, Vec<Arc<SortConfigurationRevision>>>;
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
pub struct SettingRevision {
pub layout: LayoutRevision,
pub filters: FilterConfiguration,
#[serde(default)]
pub groups: GroupConfiguration,
#[serde(skip)]
pub sorts: SortConfiguration,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum LayoutRevision {
Table = 0,
Board = 1,
}
impl ToString for LayoutRevision {
fn to_string(&self) -> String {
let layout_rev = self.clone() as u8;
layout_rev.to_string()
}
}
impl std::default::Default for LayoutRevision {
fn default() -> Self {
LayoutRevision::Table
}
}
impl SettingRevision {
pub fn get_all_groups(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
self.groups.get_all_objects(field_revs)
}
pub fn get_groups(
&self,
field_id: &str,
field_type_rev: &FieldTypeRevision,
) -> Option<Vec<Arc<GroupConfigurationRevision>>> {
self.groups.get_objects(field_id, field_type_rev)
}
pub fn get_mut_groups(
&mut self,
field_id: &str,
field_type: &FieldTypeRevision,
) -> Option<&mut Vec<Arc<GroupConfigurationRevision>>> {
self.groups.get_mut_objects(field_id, field_type)
}
pub fn insert_group(
&mut self,
field_id: &str,
field_type: &FieldTypeRevision,
group_rev: GroupConfigurationRevision,
) {
// only one group can be set
self.groups.remove_all();
self.groups.insert_object(field_id, field_type, group_rev);
}
pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Option<FilterConfigurationsByFieldId> {
self.filters.get_all_objects(field_revs)
}
pub fn get_filters(
&self,
field_id: &str,
field_type_rev: &FieldTypeRevision,
) -> Option<Vec<Arc<FilterConfigurationRevision>>> {
self.filters.get_objects(field_id, field_type_rev)
}
pub fn get_mut_filters(
&mut self,
field_id: &str,
field_type: &FieldTypeRevision,
) -> Option<&mut Vec<Arc<FilterConfigurationRevision>>> {
self.filters.get_mut_objects(field_id, field_type)
}
pub fn insert_filter(
&mut self,
field_id: &str,
field_type: &FieldTypeRevision,
filter_rev: FilterConfigurationRevision,
) {
self.filters.insert_object(field_id, field_type, filter_rev);
}
pub fn get_all_sort(&self) -> Option<SortConfigurationsByFieldId> {
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct SortConfigurationRevision {
pub id: String,
pub field_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(transparent)]
pub struct Configuration<T>
where
T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
/// Key: field_id
/// Value: this value contains key/value.
@ -149,7 +41,7 @@ where
impl<T> Configuration<T>
where
T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
pub fn get_mut_objects(&mut self, field_id: &str, field_type: &FieldTypeRevision) -> Option<&mut Vec<Arc<T>>> {
let value = self
@ -157,7 +49,7 @@ where
.get_mut(field_id)
.and_then(|object_rev_map| object_rev_map.get_mut(field_type));
if value.is_none() {
tracing::warn!("Can't find the {:?} with", std::any::type_name::<T>());
tracing::warn!("[Configuration] Can't find the {:?} with", std::any::type_name::<T>());
}
value
}
@ -184,7 +76,8 @@ where
Some(objects_by_field_id)
}
pub fn insert_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) {
/// add object to the end of the list
pub fn add_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) {
let object_rev_map = self
.inner
.entry(field_id.to_string())
@ -196,16 +89,16 @@ where
.push(Arc::new(object))
}
pub fn remove_all(&mut self) {
pub fn clear(&mut self) {
self.inner.clear()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(transparent)]
pub struct ObjectIndexMap<T>
where
T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
#[serde(with = "indexmap::serde_seq")]
pub object_by_field_type: IndexMap<FieldTypeRevision, Vec<Arc<T>>>,
@ -213,7 +106,7 @@ where
impl<T> ObjectIndexMap<T>
where
T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
pub fn new() -> Self {
ObjectIndexMap::default()
@ -222,7 +115,7 @@ where
impl<T> std::ops::Deref for ObjectIndexMap<T>
where
T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
type Target = IndexMap<FieldTypeRevision, Vec<Arc<T>>>;
@ -233,25 +126,9 @@ where
impl<T> std::ops::DerefMut for ObjectIndexMap<T>
where
T: Debug + Clone + Default + Eq + PartialEq + serde::Serialize + serde::de::DeserializeOwned + 'static,
T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.object_by_field_type
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct GroupConfigurationRevision {
pub id: String,
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub content: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct FilterConfigurationRevision {
pub id: String,
pub field_id: String,
pub condition: u8,
pub content: Option<String>,
}

View File

@ -1,24 +1,50 @@
use crate::revision::SettingRevision;
use crate::revision::{FilterConfiguration, GroupConfiguration};
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use serde_repr::*;
#[allow(dead_code)]
pub fn gen_grid_view_id() -> String {
nanoid!(6)
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum LayoutRevision {
Table = 0,
Board = 1,
}
impl ToString for LayoutRevision {
fn to_string(&self) -> String {
let layout_rev = self.clone() as u8;
layout_rev.to_string()
}
}
impl std::default::Default for LayoutRevision {
fn default() -> Self {
LayoutRevision::Table
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GridViewRevision {
pub view_id: String,
pub grid_id: String,
pub setting: SettingRevision,
pub layout: LayoutRevision,
// For the moment, we just use the order returned from the GridRevision
#[allow(dead_code)]
#[serde(skip, rename = "row")]
pub row_orders: Vec<RowOrderRevision>,
#[serde(default)]
pub filters: FilterConfiguration,
#[serde(default)]
pub groups: GroupConfiguration,
// // For the moment, we just use the order returned from the GridRevision
// #[allow(dead_code)]
// #[serde(skip, rename = "rows")]
// pub row_orders: Vec<RowOrderRevision>,
}
impl GridViewRevision {
@ -26,8 +52,10 @@ impl GridViewRevision {
GridViewRevision {
view_id,
grid_id,
setting: Default::default(),
row_orders: vec![],
layout: Default::default(),
filters: Default::default(),
groups: Default::default(),
// row_orders: vec![],
}
}
}
@ -36,3 +64,24 @@ impl GridViewRevision {
pub struct RowOrderRevision {
pub row_id: String,
}
#[cfg(test)]
mod tests {
use crate::revision::GridViewRevision;
#[test]
fn grid_view_revision_serde_test() {
let grid_view_revision = GridViewRevision {
view_id: "1".to_string(),
grid_id: "1".to_string(),
layout: Default::default(),
filters: Default::default(),
groups: Default::default(),
};
let s = serde_json::to_string(&grid_view_revision).unwrap();
assert_eq!(
s,
r#"{"view_id":"1","grid_id":"1","layout":0,"filters":[],"groups":[]}"#
);
}
}

View File

@ -0,0 +1,181 @@
use crate::revision::{gen_grid_group_id, FieldTypeRevision};
use serde::{Deserialize, Serialize};
use serde_json::Error;
use serde_repr::*;
pub trait GroupConfigurationContentSerde: Sized + Send + Sync {
fn from_configuration_content(s: &str) -> Result<Self, serde_json::Error>;
fn to_configuration_content(&self) -> Result<String, serde_json::Error>;
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct GroupConfigurationRevision {
pub id: String,
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub groups: Vec<GroupRecordRevision>,
pub content: String,
}
impl GroupConfigurationRevision {
pub fn new<T>(field_id: String, field_type: FieldTypeRevision, content: T) -> Result<Self, serde_json::Error>
where
T: GroupConfigurationContentSerde,
{
let content = content.to_configuration_content()?;
Ok(Self {
id: gen_grid_group_id(),
field_id,
field_type_rev: field_type,
groups: vec![],
content,
})
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct TextGroupConfigurationRevision {
pub hide_empty: bool,
}
impl GroupConfigurationContentSerde for TextGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct NumberGroupConfigurationRevision {
pub hide_empty: bool,
}
impl GroupConfigurationContentSerde for NumberGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct UrlGroupConfigurationRevision {
pub hide_empty: bool,
}
impl GroupConfigurationContentSerde for UrlGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct CheckboxGroupConfigurationRevision {
pub hide_empty: bool,
}
impl GroupConfigurationContentSerde for CheckboxGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct SelectOptionGroupConfigurationRevision {
pub hide_empty: bool,
}
impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct GroupRecordRevision {
pub group_id: String,
#[serde(default = "DEFAULT_GROUP_RECORD_VISIBILITY")]
pub visible: bool,
}
const DEFAULT_GROUP_RECORD_VISIBILITY: fn() -> bool = || true;
impl GroupRecordRevision {
pub fn new(group_id: String) -> Self {
Self {
group_id,
visible: true,
}
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct DateGroupConfigurationRevision {
pub hide_empty: bool,
pub condition: DateCondition,
}
impl GroupConfigurationContentSerde for DateGroupConfigurationRevision {
fn from_configuration_content(s: &str) -> Result<Self, Error> {
serde_json::from_str(s)
}
fn to_configuration_content(&self) -> Result<String, Error> {
serde_json::to_string(self)
}
}
#[derive(Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum DateCondition {
Relative = 0,
Day = 1,
Week = 2,
Month = 3,
Year = 4,
}
impl std::default::Default for DateCondition {
fn default() -> Self {
DateCondition::Relative
}
}
#[cfg(test)]
mod tests {
use crate::revision::{GroupConfigurationRevision, SelectOptionGroupConfigurationRevision};
#[test]
fn group_configuration_serde_test() {
let content = SelectOptionGroupConfigurationRevision { hide_empty: false };
let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap();
let json = serde_json::to_string(&rev).unwrap();
let rev: GroupConfigurationRevision = serde_json::from_str(&json).unwrap();
let _content: SelectOptionGroupConfigurationRevision = serde_json::from_str(&rev.content).unwrap();
}
#[test]
fn group_configuration_serde_test2() {
let content = SelectOptionGroupConfigurationRevision { hide_empty: false };
let content_json = serde_json::to_string(&content).unwrap();
let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap();
assert_eq!(rev.content, content_json);
}
}

View File

@ -1,9 +1,13 @@
mod filter_rev;
mod grid_block;
mod grid_rev;
mod grid_setting_rev;
mod grid_view;
mod group_rev;
pub use filter_rev::*;
pub use grid_block::*;
pub use grid_rev::*;
pub use grid_setting_rev::*;
pub use grid_view::*;
pub use group_rev::*;

View File

@ -1,4 +1,3 @@
use crate::entities::grid::FieldChangesetParams;
use crate::entities::revision::{md5, RepeatedRevision, Revision};
use crate::errors::{internal_error, CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_text_delta_from_revisions};
@ -162,61 +161,6 @@ impl GridRevisionPad {
})
}
pub fn update_field_rev<T: JsonDeserializer>(
&mut self,
changeset: FieldChangesetParams,
deserializer: T,
) -> CollaborateResult<Option<GridRevisionChangeset>> {
let field_id = changeset.field_id.clone();
self.modify_field(&field_id, |field| {
let mut is_changed = None;
if let Some(name) = changeset.name {
field.name = name;
is_changed = Some(())
}
if let Some(desc) = changeset.desc {
field.desc = desc;
is_changed = Some(())
}
if let Some(field_type) = changeset.field_type {
field.ty = field_type;
is_changed = Some(())
}
if let Some(frozen) = changeset.frozen {
field.frozen = frozen;
is_changed = Some(())
}
if let Some(visibility) = changeset.visibility {
field.visibility = visibility;
is_changed = Some(())
}
if let Some(width) = changeset.width {
field.width = width;
is_changed = Some(())
}
if let Some(type_option_data) = changeset.type_option_data {
match deserializer.deserialize(type_option_data) {
Ok(json_str) => {
let field_type = field.ty;
field.insert_type_option_str(&field_type, json_str);
is_changed = Some(())
}
Err(err) => {
tracing::error!("Deserialize data to type option json failed: {}", err);
}
}
}
Ok(is_changed)
})
}
pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc<FieldRevision>)> {
self.grid_rev
.fields
@ -399,7 +343,7 @@ impl GridRevisionPad {
)
}
fn modify_field<F>(&mut self, field_id: &str, f: F) -> CollaborateResult<Option<GridRevisionChangeset>>
pub fn modify_field<F>(&mut self, field_id: &str, f: F) -> CollaborateResult<Option<GridRevisionChangeset>>
where
F: FnOnce(&mut FieldRevision) -> CollaborateResult<Option<()>>,
{

View File

@ -1,11 +1,9 @@
use crate::entities::grid::{CreateGridFilterParams, CreateGridGroupParams, GridSettingChangesetParams};
use crate::entities::revision::{md5, Revision};
use crate::errors::{internal_error, CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_text_delta_from_revisions};
use flowy_grid_data_model::revision::{
gen_grid_filter_id, gen_grid_group_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision,
FilterConfigurationsByFieldId, GridViewRevision, GroupConfigurationRevision, GroupConfigurationsByFieldId,
SettingRevision, SortConfigurationsByFieldId,
FieldRevision, FieldTypeRevision, FilterConfigurationRevision, FilterConfigurationsByFieldId, GridViewRevision,
GroupConfigurationRevision, GroupConfigurationsByFieldId,
};
use lib_ot::core::{OperationTransform, PhantomAttributes, TextDelta, TextDeltaBuilder};
use std::sync::Arc;
@ -50,71 +48,11 @@ impl GridViewRevisionPad {
Self::from_delta(delta)
}
pub fn get_setting_rev(&self) -> &SettingRevision {
&self.view.setting
}
pub fn update_setting(
&mut self,
changeset: GridSettingChangesetParams,
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
self.modify(|view| {
let mut is_changed = None;
if let Some(params) = changeset.insert_filter {
view.setting.filters.insert_object(
&params.field_id,
&params.field_type_rev,
make_filter_revision(&params),
);
is_changed = Some(())
}
if let Some(params) = changeset.delete_filter {
if let Some(filters) = view
.setting
.filters
.get_mut_objects(&params.field_id, &params.field_type_rev)
{
filters.retain(|filter| filter.id != params.filter_id);
is_changed = Some(())
}
}
if let Some(params) = changeset.insert_group {
view.setting.groups.remove_all();
view.setting.groups.insert_object(
&params.field_id,
&params.field_type_rev,
make_group_revision(&params),
);
is_changed = Some(());
}
if let Some(params) = changeset.delete_group {
if let Some(groups) = view
.setting
.groups
.get_mut_objects(&params.field_id, &params.field_type_rev)
{
groups.retain(|group| group.id != params.group_id);
is_changed = Some(());
}
}
Ok(is_changed)
})
}
pub fn get_all_groups(&self, field_revs: &[Arc<FieldRevision>]) -> Option<GroupConfigurationsByFieldId> {
self.setting.groups.get_all_objects(field_revs)
}
pub fn get_groups(
&self,
field_id: &str,
field_type_rev: &FieldTypeRevision,
) -> Option<Vec<Arc<GroupConfigurationRevision>>> {
self.setting.groups.get_objects(field_id, field_type_rev)
self.groups.get_all_objects(field_revs)
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub fn insert_group(
&mut self,
field_id: &str,
@ -122,13 +60,40 @@ impl GridViewRevisionPad {
group_rev: GroupConfigurationRevision,
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
self.modify(|view| {
// only one group can be set
view.setting.groups.remove_all();
view.setting.groups.insert_object(field_id, field_type, group_rev);
// Only save one group
view.groups.clear();
view.groups.add_object(field_id, field_type, group_rev);
Ok(Some(()))
})
}
#[tracing::instrument(level = "trace", skip_all)]
pub fn contains_group(&self, field_id: &str, field_type: &FieldTypeRevision) -> bool {
self.view.groups.get_objects(field_id, field_type).is_some()
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub fn with_mut_group<F: FnOnce(&mut GroupConfigurationRevision)>(
&mut self,
field_id: &str,
field_type: &FieldTypeRevision,
configuration_id: &str,
mut_configuration_fn: F,
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
self.modify(|view| match view.groups.get_mut_objects(field_id, field_type) {
None => Ok(None),
Some(configurations_revs) => {
for configuration_rev in configurations_revs {
if configuration_rev.id == configuration_id {
mut_configuration_fn(Arc::make_mut(configuration_rev));
return Ok(Some(()));
}
}
Ok(None)
}
})
}
pub fn delete_group(
&mut self,
field_id: &str,
@ -136,7 +101,7 @@ impl GridViewRevisionPad {
group_id: &str,
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
self.modify(|view| {
if let Some(groups) = view.setting.groups.get_mut_objects(field_id, field_type) {
if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) {
groups.retain(|group| group.id != group_id);
Ok(Some(()))
} else {
@ -146,7 +111,7 @@ impl GridViewRevisionPad {
}
pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Option<FilterConfigurationsByFieldId> {
self.setting.filters.get_all_objects(field_revs)
self.filters.get_all_objects(field_revs)
}
pub fn get_filters(
@ -154,7 +119,7 @@ impl GridViewRevisionPad {
field_id: &str,
field_type_rev: &FieldTypeRevision,
) -> Option<Vec<Arc<FilterConfigurationRevision>>> {
self.setting.filters.get_objects(field_id, field_type_rev)
self.filters.get_objects(field_id, field_type_rev)
}
pub fn insert_filter(
@ -164,7 +129,7 @@ impl GridViewRevisionPad {
filter_rev: FilterConfigurationRevision,
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
self.modify(|view| {
view.setting.filters.insert_object(field_id, field_type, filter_rev);
view.filters.add_object(field_id, field_type, filter_rev);
Ok(Some(()))
})
}
@ -176,7 +141,7 @@ impl GridViewRevisionPad {
filter_id: &str,
) -> CollaborateResult<Option<GridViewRevisionChangeset>> {
self.modify(|view| {
if let Some(filters) = view.setting.filters.get_mut_objects(field_id, field_type) {
if let Some(filters) = view.filters.get_mut_objects(field_id, field_type) {
filters.retain(|filter| filter.id != filter_id);
Ok(Some(()))
} else {
@ -185,10 +150,6 @@ impl GridViewRevisionPad {
})
}
pub fn get_all_sort(&self) -> Option<SortConfigurationsByFieldId> {
None
}
pub fn json_str(&self) -> CollaborateResult<String> {
make_grid_view_rev_json_str(&self.view)
}
@ -216,24 +177,7 @@ impl GridViewRevisionPad {
}
}
fn make_filter_revision(params: &CreateGridFilterParams) -> FilterConfigurationRevision {
FilterConfigurationRevision {
id: gen_grid_filter_id(),
field_id: params.field_id.clone(),
condition: params.condition,
content: params.content.clone(),
}
}
fn make_group_revision(params: &CreateGridGroupParams) -> GroupConfigurationRevision {
GroupConfigurationRevision {
id: gen_grid_group_id(),
field_id: params.field_id.clone(),
field_type_rev: params.field_type_rev,
content: params.content.clone(),
}
}
#[derive(Debug)]
pub struct GridViewRevisionChangeset {
pub delta: TextDelta,
pub md5: String,

View File

@ -1,67 +0,0 @@
use flowy_grid_data_model::revision::{FieldTypeRevision, LayoutRevision};
pub struct GridSettingChangesetParams {
pub grid_id: String,
pub layout_type: LayoutRevision,
pub insert_filter: Option<CreateGridFilterParams>,
pub delete_filter: Option<DeleteFilterParams>,
pub insert_group: Option<CreateGridGroupParams>,
pub delete_group: Option<DeleteGroupParams>,
pub insert_sort: Option<CreateGridSortParams>,
pub delete_sort: Option<String>,
}
impl GridSettingChangesetParams {
pub fn is_filter_changed(&self) -> bool {
self.insert_filter.is_some() || self.delete_filter.is_some()
}
}
pub struct CreateGridFilterParams {
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub condition: u8,
pub content: Option<String>,
}
pub struct DeleteFilterParams {
pub field_id: String,
pub filter_id: String,
pub field_type_rev: FieldTypeRevision,
}
pub struct CreateGridGroupParams {
pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub content: Option<Vec<u8>>,
}
pub struct DeleteGroupParams {
pub field_id: String,
pub group_id: String,
pub field_type_rev: FieldTypeRevision,
}
pub struct CreateGridSortParams {
pub field_id: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct FieldChangesetParams {
pub field_id: String,
pub grid_id: String,
pub name: Option<String>,
pub desc: Option<String>,
pub field_type: Option<FieldTypeRevision>,
pub frozen: Option<bool>,
pub visibility: Option<bool>,
pub width: Option<i32>,
pub type_option_data: Option<Vec<u8>>,
}

View File

@ -1,5 +1,4 @@
pub mod folder;
pub mod grid;
pub mod parser;
pub mod revision;
pub mod text_block;