mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
commit
276df8202a
@ -39,7 +39,7 @@ class GroupController {
|
||||
void startListening() {
|
||||
_listener.start(onGroupChanged: (result) {
|
||||
result.fold(
|
||||
(GroupChangesetPB changeset) {
|
||||
(GroupRowsNotificationPB changeset) {
|
||||
for (final deletedRow in changeset.deletedRows) {
|
||||
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
|
||||
delegate.removeRow(group, deletedRow);
|
||||
|
@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
|
||||
|
||||
typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
|
||||
typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
|
||||
|
||||
class GroupListener {
|
||||
final GroupPB group;
|
||||
@ -34,7 +34,7 @@ class GroupListener {
|
||||
case GridNotification.DidUpdateGroup:
|
||||
result.fold(
|
||||
(payload) => _groupNotifier?.value =
|
||||
left(GroupChangesetPB.fromBuffer(payload)),
|
||||
left(GroupRowsNotificationPB.fromBuffer(payload)),
|
||||
(error) => _groupNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
@ -0,0 +1,206 @@
|
||||
import 'package:app_flowy/plugins/grid/application/filter/filter_listener.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'filter_service.dart';
|
||||
|
||||
part 'filter_bloc.freezed.dart';
|
||||
|
||||
class GridFilterBloc extends Bloc<GridFilterEvent, GridFilterState> {
|
||||
final String viewId;
|
||||
final FilterFFIService _ffiService;
|
||||
final FilterListener _listener;
|
||||
GridFilterBloc({required this.viewId})
|
||||
: _ffiService = FilterFFIService(viewId: viewId),
|
||||
_listener = FilterListener(viewId: viewId),
|
||||
super(GridFilterState.initial()) {
|
||||
on<GridFilterEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
await _loadFilters();
|
||||
},
|
||||
deleteFilter: (
|
||||
String fieldId,
|
||||
String filterId,
|
||||
FieldType fieldType,
|
||||
) {
|
||||
_ffiService.deleteFilter(
|
||||
fieldId: fieldId,
|
||||
filterId: filterId,
|
||||
fieldType: fieldType,
|
||||
);
|
||||
},
|
||||
didReceiveFilters: (filters) {
|
||||
emit(state.copyWith(filters: filters));
|
||||
},
|
||||
createCheckboxFilter: (
|
||||
String fieldId,
|
||||
CheckboxFilterCondition condition,
|
||||
) {
|
||||
_ffiService.createCheckboxFilter(
|
||||
fieldId: fieldId,
|
||||
condition: condition,
|
||||
);
|
||||
},
|
||||
createNumberFilter: (
|
||||
String fieldId,
|
||||
NumberFilterCondition condition,
|
||||
String content,
|
||||
) {
|
||||
_ffiService.createNumberFilter(
|
||||
fieldId: fieldId,
|
||||
condition: condition,
|
||||
content: content,
|
||||
);
|
||||
},
|
||||
createTextFilter: (
|
||||
String fieldId,
|
||||
TextFilterCondition condition,
|
||||
String content,
|
||||
) {
|
||||
_ffiService.createTextFilter(
|
||||
fieldId: fieldId,
|
||||
condition: condition,
|
||||
);
|
||||
},
|
||||
createDateFilter: (
|
||||
String fieldId,
|
||||
DateFilterCondition condition,
|
||||
int timestamp,
|
||||
) {
|
||||
_ffiService.createDateFilter(
|
||||
fieldId: fieldId,
|
||||
condition: condition,
|
||||
timestamp: timestamp,
|
||||
);
|
||||
},
|
||||
createDateFilterInRange: (
|
||||
String fieldId,
|
||||
DateFilterCondition condition,
|
||||
int start,
|
||||
int end,
|
||||
) {
|
||||
_ffiService.createDateFilter(
|
||||
fieldId: fieldId,
|
||||
condition: condition,
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_listener.start(onFilterChanged: (result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
final List<FilterPB> filters = List.from(state.filters);
|
||||
|
||||
// Deletes the filters
|
||||
final deleteFilterIds =
|
||||
changeset.deleteFilters.map((e) => e.id).toList();
|
||||
filters.retainWhere(
|
||||
(element) => !deleteFilterIds.contains(element.id),
|
||||
);
|
||||
|
||||
// Inserts the new fitler if it's not exist
|
||||
for (final newFilter in changeset.insertFilters) {
|
||||
final index =
|
||||
filters.indexWhere((element) => element.id == newFilter.id);
|
||||
if (index == -1) {
|
||||
filters.add(newFilter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isClosed) {
|
||||
add(GridFilterEvent.didReceiveFilters(filters));
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadFilters() async {
|
||||
final result = await _ffiService.getAllFilters();
|
||||
result.fold(
|
||||
(filters) {
|
||||
if (!isClosed) {
|
||||
add(GridFilterEvent.didReceiveFilters(filters));
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _listener.stop();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridFilterEvent with _$GridFilterEvent {
|
||||
const factory GridFilterEvent.initial() = _Initial;
|
||||
const factory GridFilterEvent.didReceiveFilters(List<FilterPB> filters) =
|
||||
_DidReceiveFilters;
|
||||
|
||||
const factory GridFilterEvent.deleteFilter({
|
||||
required String fieldId,
|
||||
required String filterId,
|
||||
required FieldType fieldType,
|
||||
}) = _DeleteFilter;
|
||||
|
||||
const factory GridFilterEvent.createTextFilter({
|
||||
required String fieldId,
|
||||
required TextFilterCondition condition,
|
||||
required String content,
|
||||
}) = _CreateTextFilter;
|
||||
|
||||
const factory GridFilterEvent.createCheckboxFilter({
|
||||
required String fieldId,
|
||||
required CheckboxFilterCondition condition,
|
||||
}) = _CreateCheckboxFilter;
|
||||
|
||||
const factory GridFilterEvent.createNumberFilter({
|
||||
required String fieldId,
|
||||
required NumberFilterCondition condition,
|
||||
required String content,
|
||||
}) = _CreateCheckboxFitler;
|
||||
|
||||
const factory GridFilterEvent.createDateFilter({
|
||||
required String fieldId,
|
||||
required DateFilterCondition condition,
|
||||
required int start,
|
||||
}) = _CreateDateFitler;
|
||||
|
||||
const factory GridFilterEvent.createDateFilterInRange({
|
||||
required String fieldId,
|
||||
required DateFilterCondition condition,
|
||||
required int start,
|
||||
required int end,
|
||||
}) = _CreateDateFitlerInRange;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridFilterState with _$GridFilterState {
|
||||
const factory GridFilterState({
|
||||
required List<FilterPB> filters,
|
||||
}) = _GridFilterState;
|
||||
|
||||
factory GridFilterState.initial() => const GridFilterState(
|
||||
filters: [],
|
||||
);
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app_flowy/core/grid_notification.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/filter_changeset.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
typedef UpdateFilterNotifiedValue
|
||||
= Either<FilterChangesetNotificationPB, FlowyError>;
|
||||
|
||||
class FilterListener {
|
||||
final String viewId;
|
||||
|
||||
PublishNotifier<UpdateFilterNotifiedValue>? _filterNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
FilterListener({required this.viewId});
|
||||
|
||||
void start({
|
||||
required void Function(UpdateFilterNotifiedValue) onFilterChanged,
|
||||
}) {
|
||||
_filterNotifier?.addPublishListener(onFilterChanged);
|
||||
_listener = GridNotificationListener(
|
||||
objectId: viewId,
|
||||
handler: _handler,
|
||||
);
|
||||
}
|
||||
|
||||
void _handler(
|
||||
GridNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateFilter:
|
||||
result.fold(
|
||||
(payload) => _filterNotifier?.value =
|
||||
left(FilterChangesetNotificationPB.fromBuffer(payload)),
|
||||
(error) => _filterNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
_filterNotifier?.dispose();
|
||||
_filterNotifier = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.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/number_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
|
||||
class FilterFFIService {
|
||||
final String viewId;
|
||||
const FilterFFIService({required this.viewId});
|
||||
|
||||
Future<Either<List<FilterPB>, FlowyError>> getAllFilters() {
|
||||
final payload = GridIdPB()..value = viewId;
|
||||
|
||||
return GridEventGetAllFilters(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(repeated) => left(repeated.items),
|
||||
(r) => right(r),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createTextFilter({
|
||||
required String fieldId,
|
||||
required TextFilterCondition condition,
|
||||
String content = "",
|
||||
}) {
|
||||
final filter = TextFilterPB()
|
||||
..condition = condition
|
||||
..content = content;
|
||||
|
||||
return createFilter(
|
||||
fieldId: fieldId,
|
||||
fieldType: FieldType.RichText,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createCheckboxFilter({
|
||||
required String fieldId,
|
||||
required CheckboxFilterCondition condition,
|
||||
}) {
|
||||
final filter = CheckboxFilterPB()..condition = condition;
|
||||
|
||||
return createFilter(
|
||||
fieldId: fieldId,
|
||||
fieldType: FieldType.Checkbox,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createNumberFilter({
|
||||
required String fieldId,
|
||||
required NumberFilterCondition condition,
|
||||
String content = "",
|
||||
}) {
|
||||
final filter = NumberFilterPB()
|
||||
..condition = condition
|
||||
..content = content;
|
||||
|
||||
return createFilter(
|
||||
fieldId: fieldId,
|
||||
fieldType: FieldType.Number,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createDateFilter({
|
||||
required String fieldId,
|
||||
required DateFilterCondition condition,
|
||||
int? start,
|
||||
int? end,
|
||||
int? timestamp,
|
||||
}) {
|
||||
var filter = DateFilterPB();
|
||||
if (timestamp != null) {
|
||||
filter.timestamp = $fixnum.Int64(timestamp);
|
||||
} else {
|
||||
if (start != null && end != null) {
|
||||
filter.start = $fixnum.Int64(start);
|
||||
filter.end = $fixnum.Int64(end);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Start and end should not be null if the timestamp is null");
|
||||
}
|
||||
}
|
||||
|
||||
return createFilter(
|
||||
fieldId: fieldId,
|
||||
fieldType: FieldType.DateTime,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createURLFilter({
|
||||
required String fieldId,
|
||||
required TextFilterCondition condition,
|
||||
String content = "",
|
||||
}) {
|
||||
final filter = TextFilterPB()
|
||||
..condition = condition
|
||||
..content = content;
|
||||
|
||||
return createFilter(
|
||||
fieldId: fieldId,
|
||||
fieldType: FieldType.URL,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createSingleSelectFilter({
|
||||
required String fieldId,
|
||||
required SelectOptionCondition condition,
|
||||
List<String> optionIds = const [],
|
||||
}) {
|
||||
final filter = SelectOptionFilterPB()
|
||||
..condition = condition
|
||||
..optionIds.addAll(optionIds);
|
||||
|
||||
return createFilter(
|
||||
fieldId: fieldId,
|
||||
fieldType: FieldType.SingleSelect,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createMultiSelectFilter({
|
||||
required String fieldId,
|
||||
required SelectOptionCondition condition,
|
||||
List<String> optionIds = const [],
|
||||
}) {
|
||||
final filter = SelectOptionFilterPB()
|
||||
..condition = condition
|
||||
..optionIds.addAll(optionIds);
|
||||
|
||||
return createFilter(
|
||||
fieldId: fieldId,
|
||||
fieldType: FieldType.MultiSelect,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> createFilter({
|
||||
required String fieldId,
|
||||
required FieldType fieldType,
|
||||
required List<int> data,
|
||||
}) {
|
||||
TextFilterCondition.DoesNotContain.value;
|
||||
|
||||
final insertFilterPayload = CreateFilterPayloadPB.create()
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType
|
||||
..data = data;
|
||||
|
||||
final payload = GridSettingChangesetPB.create()
|
||||
..gridId = viewId
|
||||
..insertFilter = insertFilterPayload;
|
||||
return GridEventUpdateGridSetting(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(l) => left(l),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> deleteFilter({
|
||||
required String fieldId,
|
||||
required String filterId,
|
||||
required FieldType fieldType,
|
||||
}) {
|
||||
TextFilterCondition.DoesNotContain.value;
|
||||
|
||||
final deleteFilterPayload = DeleteFilterPayloadPB.create()
|
||||
..fieldId = fieldId
|
||||
..filterId = filterId
|
||||
..fieldType = fieldType;
|
||||
|
||||
final payload = GridSettingChangesetPB.create()
|
||||
..gridId = viewId
|
||||
..deleteFilter = deleteFilterPayload;
|
||||
return GridEventUpdateGridSetting(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(l) => left(l),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import 'package:app_flowy/plugins/grid/application/filter/filter_bloc.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'util.dart';
|
||||
|
||||
void main() {
|
||||
late AppFlowyGridTest gridTest;
|
||||
setUpAll(() async {
|
||||
gridTest = await AppFlowyGridTest.ensureInitialized();
|
||||
});
|
||||
|
||||
group('$GridFilterBloc', () {
|
||||
setUp(() async {
|
||||
await gridTest.createTestGrid();
|
||||
});
|
||||
blocTest<GridFilterBloc, GridFilterState>(
|
||||
"create a text filter",
|
||||
build: () => GridFilterBloc(viewId: gridTest.gridView.id)
|
||||
..add(const GridFilterEvent.initial()),
|
||||
act: (bloc) async {
|
||||
final textField = gridTest.textFieldContext();
|
||||
bloc.add(
|
||||
GridFilterEvent.createTextFilter(
|
||||
fieldId: textField.id,
|
||||
condition: TextFilterCondition.TextIsEmpty,
|
||||
content: ""),
|
||||
);
|
||||
},
|
||||
wait: const Duration(milliseconds: 300),
|
||||
verify: (bloc) {
|
||||
assert(bloc.state.filters.length == 1);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<GridFilterBloc, GridFilterState>(
|
||||
"delete a text filter",
|
||||
build: () => GridFilterBloc(viewId: gridTest.gridView.id)
|
||||
..add(const GridFilterEvent.initial()),
|
||||
act: (bloc) async {
|
||||
final textField = gridTest.textFieldContext();
|
||||
bloc.add(
|
||||
GridFilterEvent.createTextFilter(
|
||||
fieldId: textField.id,
|
||||
condition: TextFilterCondition.TextIsEmpty,
|
||||
content: ""),
|
||||
);
|
||||
await gridResponseFuture();
|
||||
final filter = bloc.state.filters.first;
|
||||
bloc.add(
|
||||
GridFilterEvent.deleteFilter(
|
||||
fieldId: textField.id,
|
||||
filterId: filter.id,
|
||||
fieldType: textField.fieldType,
|
||||
),
|
||||
);
|
||||
},
|
||||
wait: const Duration(milliseconds: 300),
|
||||
verify: (bloc) {
|
||||
assert(bloc.state.filters.isEmpty);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
@ -157,6 +157,12 @@ class AppFlowyGridTest {
|
||||
return GridFieldCellContext(gridId: gridView.id, field: field);
|
||||
}
|
||||
|
||||
GridFieldContext textFieldContext() {
|
||||
final fieldContext = fieldContexts
|
||||
.firstWhere((element) => element.fieldType == FieldType.RichText);
|
||||
return fieldContext;
|
||||
}
|
||||
|
||||
Future<void> createTestGrid() async {
|
||||
final app = await unitTest.createTestApp();
|
||||
final builder = GridPluginBuilder();
|
||||
|
@ -14,6 +14,7 @@ pub enum GridNotification {
|
||||
DidUpdateGroupView = 60,
|
||||
DidUpdateGroup = 61,
|
||||
DidGroupByNewField = 62,
|
||||
DidUpdateFilter = 63,
|
||||
DidUpdateGridSetting = 70,
|
||||
}
|
||||
|
||||
|
@ -40,8 +40,8 @@ impl std::convert::TryFrom<u8> for CheckboxFilterCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterRevision>> for CheckboxFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
impl std::convert::From<&FilterRevision> for CheckboxFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
CheckboxFilterPB {
|
||||
condition: CheckboxFilterCondition::try_from(rev.condition).unwrap_or(CheckboxFilterCondition::IsChecked),
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ impl std::convert::TryFrom<u8> for DateFilterCondition {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::convert::From<Arc<FilterRevision>> for DateFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
impl std::convert::From<&FilterRevision> for DateFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs);
|
||||
let mut filter = DateFilterPB {
|
||||
condition,
|
||||
|
@ -0,0 +1,31 @@
|
||||
use crate::entities::{FilterPB, InsertedRowPB, RepeatedFilterPB, RowPB};
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct FilterChangesetNotificationPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub insert_filters: Vec<FilterPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub delete_filters: Vec<FilterPB>,
|
||||
}
|
||||
|
||||
impl FilterChangesetNotificationPB {
|
||||
pub fn from_insert(view_id: &str, filters: Vec<FilterPB>) -> Self {
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
insert_filters: filters,
|
||||
delete_filters: Default::default(),
|
||||
}
|
||||
}
|
||||
pub fn from_delete(view_id: &str, filters: Vec<FilterPB>) -> Self {
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
insert_filters: Default::default(),
|
||||
delete_filters: filters,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
mod checkbox_filter;
|
||||
mod date_filter;
|
||||
mod filter_changeset;
|
||||
mod number_filter;
|
||||
mod select_option_filter;
|
||||
mod text_filter;
|
||||
@ -7,6 +8,7 @@ mod util;
|
||||
|
||||
pub use checkbox_filter::*;
|
||||
pub use date_filter::*;
|
||||
pub use filter_changeset::*;
|
||||
pub use number_filter::*;
|
||||
pub use select_option_filter::*;
|
||||
pub use text_filter::*;
|
||||
|
@ -55,8 +55,8 @@ impl std::convert::TryFrom<u8> for NumberFilterCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterRevision>> for NumberFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
impl std::convert::From<&FilterRevision> for NumberFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
NumberFilterPB {
|
||||
condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
|
||||
content: rev.content.clone(),
|
||||
|
@ -48,8 +48,8 @@ impl std::convert::TryFrom<u8> for SelectOptionCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterRevision>> for SelectOptionFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
impl std::convert::From<&FilterRevision> for SelectOptionFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
let ids = SelectOptionIds::from(rev.content.clone());
|
||||
SelectOptionFilterPB {
|
||||
condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
|
||||
|
@ -55,8 +55,8 @@ impl std::convert::TryFrom<u8> for TextFilterCondition {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FilterRevision>> for TextFilterPB {
|
||||
fn from(rev: Arc<FilterRevision>) -> Self {
|
||||
impl std::convert::From<&FilterRevision> for TextFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
TextFilterPB {
|
||||
condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
|
||||
content: rev.content.clone(),
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CheckboxFilterCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition,
|
||||
TextFilterCondition,
|
||||
CheckboxFilterPB, DateFilterContent, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::FilterType;
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterRevision};
|
||||
@ -14,29 +15,49 @@ use std::sync::Arc;
|
||||
pub struct FilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedGridFilterConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<FilterPB>,
|
||||
#[pb(index = 2)]
|
||||
pub ty: FieldType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for FilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
Self { id: rev.id.clone() }
|
||||
let field_type: FieldType = rev.field_type_rev.into();
|
||||
let bytes: Bytes = match field_type {
|
||||
FieldType::RichText => TextFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::Number => NumberFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(),
|
||||
};
|
||||
Self {
|
||||
id: rev.id.clone(),
|
||||
ty: rev.field_type_rev.into(),
|
||||
data: bytes.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedGridFilterConfigurationPB {
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<FilterPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedFilterPB {
|
||||
fn from(revs: Vec<Arc<FilterRevision>>) -> Self {
|
||||
RepeatedGridFilterConfigurationPB {
|
||||
RepeatedFilterPB {
|
||||
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<FilterPB>> for RepeatedGridFilterConfigurationPB {
|
||||
impl std::convert::From<Vec<FilterPB>> for RepeatedFilterPB {
|
||||
fn from(items: Vec<FilterPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
@ -89,20 +110,17 @@ pub struct CreateFilterPayloadPB {
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub condition: u32,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub content: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CreateFilterPayloadPB {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<T: Into<u32>>(field_rev: &FieldRevision, condition: T, content: String) -> Self {
|
||||
pub fn new<T: TryInto<Bytes, Error = ::protobuf::ProtobufError>>(field_rev: &FieldRevision, data: T) -> Self {
|
||||
let data = data.try_into().unwrap_or_else(|_| Bytes::new());
|
||||
Self {
|
||||
field_id: field_rev.id.clone(),
|
||||
field_type: field_rev.ty.into(),
|
||||
condition: condition.into(),
|
||||
content,
|
||||
data: data.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,22 +132,39 @@ impl TryInto<CreateFilterParams> for CreateFilterPayloadPB {
|
||||
let field_id = NotEmptyStr::parse(self.field_id)
|
||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||
.0;
|
||||
let condition = self.condition as u8;
|
||||
let condition;
|
||||
let mut content = "".to_string();
|
||||
let bytes: &[u8] = self.data.as_ref();
|
||||
|
||||
match self.field_type {
|
||||
FieldType::RichText | FieldType::URL => {
|
||||
let _ = TextFilterCondition::try_from(condition)?;
|
||||
let filter = TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = filter.content;
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = CheckboxFilterCondition::try_from(condition)?;
|
||||
let filter = CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = NumberFilterCondition::try_from(condition)?;
|
||||
let filter = NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = filter.content;
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let _ = DateFilterCondition::try_from(condition)?;
|
||||
let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = DateFilterContent {
|
||||
start: filter.start,
|
||||
end: filter.end,
|
||||
timestamp: filter.timestamp,
|
||||
}
|
||||
.to_string();
|
||||
}
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
let _ = SelectOptionCondition::try_from(condition)?;
|
||||
let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = SelectOptionIds::from(filter.option_ids).to_string();
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +172,7 @@ impl TryInto<CreateFilterParams> for CreateFilterPayloadPB {
|
||||
field_id,
|
||||
field_type_rev: self.field_type.into(),
|
||||
condition,
|
||||
content: self.content,
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use flowy_error::ErrorCode;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct GroupChangesetPB {
|
||||
pub struct GroupRowsNotificationPB {
|
||||
#[pb(index = 1)]
|
||||
pub group_id: String,
|
||||
|
||||
@ -22,7 +22,7 @@ pub struct GroupChangesetPB {
|
||||
pub updated_rows: Vec<RowPB>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GroupChangesetPB {
|
||||
impl std::fmt::Display for GroupRowsNotificationPB {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for inserted_row in &self.inserted_rows {
|
||||
let _ = f.write_fmt(format_args!(
|
||||
@ -39,7 +39,7 @@ impl std::fmt::Display for GroupChangesetPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupChangesetPB {
|
||||
impl GroupRowsNotificationPB {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.group_name.is_none()
|
||||
&& self.inserted_rows.is_empty()
|
||||
|
@ -1,8 +1,7 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams,
|
||||
DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB,
|
||||
RepeatedGridGroupConfigurationPB,
|
||||
DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGridGroupConfigurationPB,
|
||||
};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
@ -21,7 +20,7 @@ pub struct GridSettingPB {
|
||||
pub layout_type: GridLayout,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub filter_configurations: RepeatedGridFilterConfigurationPB,
|
||||
pub filter_configurations: RepeatedFilterPB,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub group_configurations: RepeatedGridGroupConfigurationPB,
|
||||
|
@ -61,6 +61,19 @@ pub(crate) async fn update_grid_setting_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_all_filters_handler(
|
||||
data: Data<GridIdPB>,
|
||||
manager: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<RepeatedFilterPB, FlowyError> {
|
||||
let grid_id: GridIdPB = data.into_inner();
|
||||
let editor = manager.open_grid(grid_id).await?;
|
||||
let filters = RepeatedFilterPB {
|
||||
items: editor.get_all_filters().await?,
|
||||
};
|
||||
data_result(filters)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn get_grid_blocks_handler(
|
||||
data: Data<QueryBlocksPayloadPB>,
|
||||
|
@ -12,6 +12,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
|
||||
.event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
|
||||
.event(GridEvent::GetGridSetting, get_grid_setting_handler)
|
||||
.event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
|
||||
.event(GridEvent::GetAllFilters, get_all_filters_handler)
|
||||
// Field
|
||||
.event(GridEvent::GetFields, get_fields_handler)
|
||||
.event(GridEvent::UpdateField, update_field_handler)
|
||||
@ -78,6 +79,9 @@ pub enum GridEvent {
|
||||
#[event(input = "GridSettingChangesetPB")]
|
||||
UpdateGridSetting = 3,
|
||||
|
||||
#[event(input = "GridIdPB", output = "RepeatedFilterPB")]
|
||||
GetAllFilters = 4,
|
||||
|
||||
/// [GetFields] event is used to get the grid's settings.
|
||||
///
|
||||
/// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB]
|
||||
|
@ -181,37 +181,37 @@ impl FilterController {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.text_filter
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev));
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::Number => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.number_filter
|
||||
.insert(filter_type, NumberFilterPB::from(filter_rev));
|
||||
.insert(filter_type, NumberFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.date_filter
|
||||
.insert(filter_type, DateFilterPB::from(filter_rev));
|
||||
.insert(filter_type, DateFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.select_option_filter
|
||||
.insert(filter_type, SelectOptionFilterPB::from(filter_rev));
|
||||
.insert(filter_type, SelectOptionFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.checkbox_filter
|
||||
.insert(filter_type, CheckboxFilterPB::from(filter_rev));
|
||||
.insert(filter_type, CheckboxFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::URL => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.url_filter
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev));
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +132,8 @@ impl GridViewRevisionEditor {
|
||||
index,
|
||||
is_new: true,
|
||||
};
|
||||
let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]);
|
||||
self.notify_did_update_group(changeset).await;
|
||||
let changeset = GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row]);
|
||||
self.notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,7 +150,7 @@ impl GridViewRevisionEditor {
|
||||
tracing::trace!("Delete row in view changeset: {:?}", changesets);
|
||||
if let Some(changesets) = changesets {
|
||||
for changeset in changesets {
|
||||
self.notify_did_update_group(changeset).await;
|
||||
self.notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,7 +164,7 @@ impl GridViewRevisionEditor {
|
||||
|
||||
if let Some(changesets) = changesets {
|
||||
for changeset in changesets {
|
||||
self.notify_did_update_group(changeset).await;
|
||||
self.notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,7 +175,7 @@ impl GridViewRevisionEditor {
|
||||
row_changeset: &mut RowChangeset,
|
||||
to_group_id: &str,
|
||||
to_row_id: Option<String>,
|
||||
) -> Vec<GroupChangesetPB> {
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let changesets = self
|
||||
.mut_group_controller(|group_controller, field_rev| {
|
||||
let move_row_context = MoveGroupRowContext {
|
||||
@ -252,9 +252,12 @@ impl GridViewRevisionEditor {
|
||||
self.pad.read().await.get_all_filters(&field_revs)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_view_filters(&self, filter_id: &FilterType) -> Vec<Arc<FilterRevision>> {
|
||||
let field_type_rev: FieldTypeRevision = filter_id.field_type.clone().into();
|
||||
self.pad.read().await.get_filters(&filter_id.field_id, &field_type_rev)
|
||||
pub(crate) async fn get_view_filters(&self, filter_type: &FilterType) -> Vec<Arc<FilterRevision>> {
|
||||
let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into();
|
||||
self.pad
|
||||
.read()
|
||||
.await
|
||||
.get_filters(&filter_type.field_id, &field_type_rev)
|
||||
}
|
||||
|
||||
/// Initialize new group when grouping by a new field
|
||||
@ -290,14 +293,16 @@ impl GridViewRevisionEditor {
|
||||
|
||||
pub(crate) async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
|
||||
let filter_type = FilterType::from(¶ms);
|
||||
let filter_rev = FilterRevision {
|
||||
id: gen_grid_filter_id(),
|
||||
field_id: params.field_id.clone(),
|
||||
field_type_rev: params.field_type_rev,
|
||||
condition: params.condition,
|
||||
content: params.content,
|
||||
};
|
||||
let filter_pb = FilterPB::from(&filter_rev);
|
||||
let _ = self
|
||||
.modify(|pad| {
|
||||
let filter_rev = FilterRevision {
|
||||
id: gen_grid_filter_id(),
|
||||
field_id: params.field_id.clone(),
|
||||
condition: params.condition,
|
||||
content: params.content,
|
||||
};
|
||||
let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?;
|
||||
Ok(changeset)
|
||||
})
|
||||
@ -309,12 +314,20 @@ impl GridViewRevisionEditor {
|
||||
.apply_changeset(FilterChangeset::from_insert(filter_type))
|
||||
.await;
|
||||
|
||||
let changeset = FilterChangesetNotificationPB::from_insert(&self.view_id, vec![filter_pb]);
|
||||
self.notify_did_update_filter(changeset).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
|
||||
let filter_type = params.filter_type;
|
||||
let field_type_rev = filter_type.field_type_rev();
|
||||
let filters = self
|
||||
.get_view_filters(&filter_type)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|filter| FilterPB::from(filter.as_ref()))
|
||||
.collect();
|
||||
let _ = self
|
||||
.modify(|pad| {
|
||||
let changeset = pad.delete_filter(¶ms.filter_id, &filter_type.field_id, &field_type_rev)?;
|
||||
@ -327,6 +340,9 @@ impl GridViewRevisionEditor {
|
||||
.await
|
||||
.apply_changeset(FilterChangeset::from_delete(filter_type))
|
||||
.await;
|
||||
|
||||
let changeset = FilterChangesetNotificationPB::from_delete(&self.view_id, filters);
|
||||
self.notify_did_update_filter(changeset).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -394,8 +410,14 @@ impl GridViewRevisionEditor {
|
||||
.send();
|
||||
}
|
||||
|
||||
pub async fn notify_did_update_group(&self, changeset: GroupChangesetPB) {
|
||||
send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
|
||||
pub async fn notify_did_update_group_rows(&self, payload: GroupRowsNotificationPB) {
|
||||
send_dart_notification(&payload.group_id, GridNotification::DidUpdateGroup)
|
||||
.payload(payload)
|
||||
.send();
|
||||
}
|
||||
|
||||
pub async fn notify_did_update_filter(&self, changeset: FilterChangesetNotificationPB) {
|
||||
send_dart_notification(&changeset.view_id, GridNotification::DidUpdateFilter)
|
||||
.payload(changeset)
|
||||
.send();
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ impl GridViewManager {
|
||||
}
|
||||
|
||||
for group_changeset in group_changesets {
|
||||
view_editor.notify_did_update_group(group_changeset).await;
|
||||
view_editor.notify_did_update_group_rows(group_changeset).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB};
|
||||
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB};
|
||||
use crate::services::cell::CellDataIsEmpty;
|
||||
use crate::services::group::controller::MoveGroupRowContext;
|
||||
use crate::services::group::Group;
|
||||
@ -31,13 +31,17 @@ pub trait GroupControllerCustomActions: Send + Sync {
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
) -> Vec<GroupChangesetPB>;
|
||||
) -> Vec<GroupRowsNotificationPB>;
|
||||
|
||||
/// Deletes the row from the group
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB>;
|
||||
|
||||
/// Move row from one group to another
|
||||
fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupChangesetPB>;
|
||||
fn move_row(
|
||||
&mut self,
|
||||
cell_data: &Self::CellDataType,
|
||||
context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB>;
|
||||
}
|
||||
|
||||
/// Defines the shared actions any group controller can perform.
|
||||
@ -62,17 +66,17 @@ pub trait GroupControllerSharedActions: Send + Sync {
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupChangesetPB>>;
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
|
||||
|
||||
/// Remove the row from the group if the row gets deleted
|
||||
fn did_delete_delete_row(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupChangesetPB>>;
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
|
||||
|
||||
/// Move the row from one group to another group
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>>;
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
|
||||
|
||||
/// Update the group if the corresponding field is changed
|
||||
fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
|
||||
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
|
||||
use crate::services::cell::{decode_any_cell_data, CellBytesParser, CellDataIsEmpty};
|
||||
use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions};
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
@ -89,8 +89,8 @@ where
|
||||
fn update_default_group(
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
other_group_changesets: &[GroupChangesetPB],
|
||||
) -> Option<GroupChangesetPB> {
|
||||
other_group_changesets: &[GroupRowsNotificationPB],
|
||||
) -> Option<GroupRowsNotificationPB> {
|
||||
let default_group = self.group_ctx.get_mut_no_status_group()?;
|
||||
|
||||
// [other_group_inserted_row] contains all the inserted rows except the default group.
|
||||
@ -113,7 +113,7 @@ where
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut changeset = GroupChangesetPB::new(default_group.id.clone());
|
||||
let mut changeset = GroupRowsNotificationPB::new(default_group.id.clone());
|
||||
if !default_group_inserted_row.is_empty() {
|
||||
changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into()));
|
||||
default_group.add_row(row_rev.into());
|
||||
@ -222,7 +222,7 @@ where
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupChangesetPB>> {
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
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).1;
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
@ -244,7 +244,7 @@ where
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupChangesetPB>> {
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
// if the cell_rev is none, then the row must in the default group.
|
||||
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).1;
|
||||
@ -264,7 +264,7 @@ where
|
||||
if !no_status_group.contains_row(&row_rev.id) {
|
||||
tracing::error!("The row: {} should be in the no status group", row_rev.id);
|
||||
}
|
||||
Ok(vec![GroupChangesetPB::delete(
|
||||
Ok(vec![GroupRowsNotificationPB::delete(
|
||||
no_status_group.id.clone(),
|
||||
vec![row_rev.id.clone()],
|
||||
)])
|
||||
@ -273,7 +273,7 @@ where
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> {
|
||||
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
let cell_rev = match context.row_rev.cells.get(&self.field_id) {
|
||||
Some(cell_rev) => Some(cell_rev.clone()),
|
||||
None => self.default_cell_rev(),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB};
|
||||
use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB};
|
||||
use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
|
||||
use crate::services::group::action::GroupControllerCustomActions;
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
@ -37,10 +37,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
) -> Vec<GroupChangesetPB> {
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
let mut changeset = GroupChangesetPB::new(group.id.clone());
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
let is_not_contained = !group.contains_row(&row_rev.id);
|
||||
if group.id == CHECK {
|
||||
if cell_data.is_uncheck() {
|
||||
@ -79,10 +79,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_groups(|group| {
|
||||
let mut changeset = GroupChangesetPB::new(group.id.clone());
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
if group.contains_row(&row_rev.id) {
|
||||
changeset.deleted_rows.push(row_rev.id.clone());
|
||||
group.remove_row(&row_rev.id);
|
||||
@ -95,7 +95,11 @@ impl GroupControllerCustomActions for CheckboxGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &Self::CellDataType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.group_ctx.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB};
|
||||
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, RowPB};
|
||||
use crate::services::group::action::GroupControllerSharedActions;
|
||||
use crate::services::group::{Group, GroupController, MoveGroupRowContext};
|
||||
use flowy_error::FlowyResult;
|
||||
@ -59,7 +59,7 @@ impl GroupControllerSharedActions for DefaultGroupController {
|
||||
&mut self,
|
||||
_row_rev: &RowRevision,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupChangesetPB>> {
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
@ -67,11 +67,11 @@ impl GroupControllerSharedActions for DefaultGroupController {
|
||||
&mut self,
|
||||
_row_rev: &RowRevision,
|
||||
_field_rev: &FieldRevision,
|
||||
) -> FlowyResult<Vec<GroupChangesetPB>> {
|
||||
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> {
|
||||
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupChangesetPB, RowPB};
|
||||
use crate::entities::{GroupRowsNotificationPB, RowPB};
|
||||
use crate::services::cell::insert_select_option_cell;
|
||||
use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
|
||||
use crate::services::group::action::GroupControllerCustomActions;
|
||||
@ -30,7 +30,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
) -> Vec<GroupChangesetPB> {
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
|
||||
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
|
||||
@ -50,7 +50,11 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &Self::CellDataType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.group_ctx.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{GroupChangesetPB, RowPB};
|
||||
use crate::entities::{GroupRowsNotificationPB, RowPB};
|
||||
use crate::services::cell::insert_select_option_cell;
|
||||
use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
|
||||
use crate::services::group::action::GroupControllerCustomActions;
|
||||
@ -30,7 +30,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
|
||||
&mut self,
|
||||
row_rev: &RowRevision,
|
||||
cell_data: &Self::CellDataType,
|
||||
) -> Vec<GroupChangesetPB> {
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
|
||||
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
|
||||
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets = vec![];
|
||||
self.group_ctx.iter_mut_status_groups(|group| {
|
||||
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
|
||||
@ -50,7 +50,11 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
|
||||
changesets
|
||||
}
|
||||
|
||||
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &Self::CellDataType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.group_ctx.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{FieldType, GroupChangesetPB, InsertedRowPB, RowPB};
|
||||
use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowPB};
|
||||
use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell};
|
||||
use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB, CHECK};
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
@ -12,8 +12,8 @@ pub fn add_or_remove_select_option_row(
|
||||
group: &mut Group,
|
||||
cell_data: &SelectOptionCellDataPB,
|
||||
row_rev: &RowRevision,
|
||||
) -> Option<GroupChangesetPB> {
|
||||
let mut changeset = GroupChangesetPB::new(group.id.clone());
|
||||
) -> Option<GroupRowsNotificationPB> {
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
if cell_data.select_options.is_empty() {
|
||||
if group.contains_row(&row_rev.id) {
|
||||
changeset.deleted_rows.push(row_rev.id.clone());
|
||||
@ -45,8 +45,8 @@ pub fn remove_select_option_row(
|
||||
group: &mut Group,
|
||||
cell_data: &SelectOptionCellDataPB,
|
||||
row_rev: &RowRevision,
|
||||
) -> Option<GroupChangesetPB> {
|
||||
let mut changeset = GroupChangesetPB::new(group.id.clone());
|
||||
) -> Option<GroupRowsNotificationPB> {
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
cell_data.select_options.iter().for_each(|option| {
|
||||
if option.id == group.id && group.contains_row(&row_rev.id) {
|
||||
changeset.deleted_rows.push(row_rev.id.clone());
|
||||
@ -61,8 +61,8 @@ pub fn remove_select_option_row(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option<GroupChangesetPB> {
|
||||
let mut changeset = GroupChangesetPB::new(group.id.clone());
|
||||
pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option<GroupRowsNotificationPB> {
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
let MoveGroupRowContext {
|
||||
row_rev,
|
||||
row_changeset,
|
||||
|
@ -3,8 +3,9 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::TryFutureExt;
|
||||
use flowy_grid::entities::{CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition, DateFilterContent, SelectOptionCondition};
|
||||
use flowy_grid::entities::{CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition, DateFilterContent, SelectOptionCondition, TextFilterPB, NumberFilterPB, CheckboxFilterPB, DateFilterPB, SelectOptionFilterPB};
|
||||
use flowy_grid::services::field::SelectOptionIds;
|
||||
use flowy_grid::services::setting::GridSettingChangesetBuilder;
|
||||
use grid_rev_model::{FieldRevision, FieldTypeRevision};
|
||||
@ -85,49 +86,60 @@ impl GridFilterTest {
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateTextFilter { condition, content} => {
|
||||
|
||||
let field_rev = self.get_field_rev(FieldType::RichText);
|
||||
let text_filter= TextFilterPB {
|
||||
condition,
|
||||
content
|
||||
};
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
CreateFilterPayloadPB::new(field_rev, text_filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateNumberFilter {condition, content} => {
|
||||
let field_rev = self.get_field_rev(FieldType::Number);
|
||||
let number_filter = NumberFilterPB {
|
||||
condition,
|
||||
content
|
||||
};
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
CreateFilterPayloadPB::new(field_rev, number_filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateCheckboxFilter {condition} => {
|
||||
let field_rev = self.get_field_rev(FieldType::Checkbox);
|
||||
let checkbox_filter = CheckboxFilterPB {
|
||||
condition
|
||||
};
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, "".to_string());
|
||||
CreateFilterPayloadPB::new(field_rev, checkbox_filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateDateFilter { condition, start, end, timestamp} => {
|
||||
let field_rev = self.get_field_rev(FieldType::DateTime);
|
||||
let content = DateFilterContent {
|
||||
let date_filter = DateFilterPB {
|
||||
condition,
|
||||
start,
|
||||
end,
|
||||
timestamp,
|
||||
}.to_string();
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
timestamp
|
||||
};
|
||||
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, date_filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateMultiSelectFilter { condition, option_ids} => {
|
||||
let field_rev = self.get_field_rev(FieldType::MultiSelect);
|
||||
let content =
|
||||
SelectOptionIds::from(option_ids).to_string();
|
||||
let filter = SelectOptionFilterPB { condition, option_ids };
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
CreateFilterPayloadPB::new(field_rev, filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateSingleSelectFilter { condition, option_ids} => {
|
||||
let field_rev = self.get_field_rev(FieldType::SingleSelect);
|
||||
let content =
|
||||
SelectOptionIds::from(option_ids).to_string();
|
||||
let filter = SelectOptionFilterPB { condition, option_ids };
|
||||
let payload =
|
||||
CreateFilterPayloadPB::new(field_rev, condition, content);
|
||||
CreateFilterPayloadPB::new(field_rev, filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::AssertFilterCount { count } => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::*;
|
||||
use flowy_grid::entities::{CreateFilterPayloadPB, FieldType, TextFilterCondition};
|
||||
use flowy_grid::entities::{CreateFilterPayloadPB, FieldType, TextFilterCondition, TextFilterPB};
|
||||
use flowy_grid::services::filter::FilterType;
|
||||
|
||||
#[tokio::test]
|
||||
@ -73,7 +73,11 @@ async fn grid_filter_ends_with_text_test() {
|
||||
async fn grid_filter_delete_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let field_rev = test.get_field_rev(FieldType::RichText).clone();
|
||||
let payload = CreateFilterPayloadPB::new(&field_rev, TextFilterCondition::TextIsEmpty, "".to_string());
|
||||
let text_filter = TextFilterPB {
|
||||
condition: TextFilterCondition::TextIsEmpty,
|
||||
content: "".to_string(),
|
||||
};
|
||||
let payload = CreateFilterPayloadPB::new(&field_rev, text_filter);
|
||||
let scripts = vec![
|
||||
InsertFilter { payload },
|
||||
AssertFilterCount { count: 1 },
|
||||
|
@ -129,6 +129,9 @@ pub enum ErrorCode {
|
||||
#[display(fmt = "Serde")]
|
||||
Serde = 1001,
|
||||
|
||||
#[display(fmt = "Protobuf serde")]
|
||||
ProtobufSerde = 1002,
|
||||
|
||||
#[display(fmt = "Out of bounds")]
|
||||
OutOfBounds = 10001,
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
use crate::FieldTypeRevision;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
|
||||
pub struct FilterRevision {
|
||||
pub id: String,
|
||||
pub field_id: String,
|
||||
pub field_type_rev: FieldTypeRevision,
|
||||
pub condition: u8,
|
||||
#[serde(default)]
|
||||
pub content: String,
|
||||
|
Loading…
Reference in New Issue
Block a user