mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: group controller traits (#4880)
* chore: remove async trait * chore: require From<AnyMap> for trait TypeOption * refactor: simplify group controller by removing GroupController trait * chore: rename GroupControllerOperation trait to GroupController * chore: add some docs * chore: remove plural struct and use Vec directly * chore: unnecessary mut * chore: use same name to indicate passthrough * chore: remove unused trait * chore: rename group context to group controller context * chore: remove group name * chore: move type option interceptor to GroupCustomize and split off delegates for group context and group controller * chore: adapt tests to changes * chore: adapt flutter frontend to changes * chore: code cleanup * chore: fix clippy and adapt tauri frontend to changes * chore: group controller code clean up * chore: no need to pass cell data when moving row * chore: rename some functions and variables * chore: remove content filter
This commit is contained in:
parent
0f006fa60b
commit
b557f89829
@ -199,7 +199,7 @@ class MobileHiddenGroup extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
group.groupName,
|
||||
context.read<BoardBloc>().generateGroupNameFromGroup(group),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
@ -11,9 +11,11 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||
|
||||
import '../../application/database_controller.dart';
|
||||
@ -397,7 +399,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
boardController.getGroupController(group.groupId);
|
||||
if (columnController != null) {
|
||||
// remove the group or update its name
|
||||
columnController.updateGroupName(group.groupName);
|
||||
columnController.updateGroupName(generateGroupNameFromGroup(group));
|
||||
if (!group.isVisible) {
|
||||
boardController.removeGroup(group.groupId);
|
||||
}
|
||||
@ -491,7 +493,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
AppFlowyGroupData _initializeGroupData(GroupPB group) {
|
||||
return AppFlowyGroupData(
|
||||
id: group.groupId,
|
||||
name: group.groupName,
|
||||
name: generateGroupNameFromGroup(group),
|
||||
items: _buildGroupItems(group),
|
||||
customData: GroupData(
|
||||
group: group,
|
||||
@ -499,6 +501,72 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String generateGroupNameFromGroup(GroupPB group) {
|
||||
final field = fieldController.getField(group.fieldId);
|
||||
if (field == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// if the group is the default group, then
|
||||
if (group.isDefault) {
|
||||
return "No ${field.name}";
|
||||
}
|
||||
|
||||
switch (field.fieldType) {
|
||||
case FieldType.SingleSelect:
|
||||
final options =
|
||||
SingleSelectTypeOptionPB.fromBuffer(field.field.typeOptionData)
|
||||
.options;
|
||||
final option =
|
||||
options.firstWhereOrNull((option) => option.id == group.groupId);
|
||||
return option == null ? "" : option.name;
|
||||
case FieldType.MultiSelect:
|
||||
final options =
|
||||
MultiSelectTypeOptionPB.fromBuffer(field.field.typeOptionData)
|
||||
.options;
|
||||
final option =
|
||||
options.firstWhereOrNull((option) => option.id == group.groupId);
|
||||
return option == null ? "" : option.name;
|
||||
case FieldType.Checkbox:
|
||||
return group.groupId;
|
||||
case FieldType.URL:
|
||||
return group.groupId;
|
||||
case FieldType.DateTime:
|
||||
// Assume DateCondition::Relative as there isn't an option for this
|
||||
// right now.
|
||||
final dateFormat = DateFormat("y/MM/dd");
|
||||
try {
|
||||
final targetDateTime = dateFormat.parseLoose(group.groupId);
|
||||
final targetDateTimeDay = DateTime(
|
||||
targetDateTime.year,
|
||||
targetDateTime.month,
|
||||
targetDateTime.day,
|
||||
);
|
||||
final now = DateTime.now();
|
||||
final nowDay = DateTime(
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
);
|
||||
final diff = targetDateTimeDay.difference(nowDay).inDays;
|
||||
return switch (diff) {
|
||||
0 => "Today",
|
||||
-1 => "Yesterday",
|
||||
1 => "Tomorrow",
|
||||
-7 => "Last 7 days",
|
||||
2 => "Next 7 days",
|
||||
-30 => "Last 30 days",
|
||||
8 => "Next 30 days",
|
||||
_ => DateFormat("MMM y").format(targetDateTimeDay)
|
||||
};
|
||||
} on FormatException {
|
||||
return "";
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -31,23 +31,11 @@ class GroupController {
|
||||
final GroupControllerDelegate delegate;
|
||||
final void Function(GroupPB group) onGroupChanged;
|
||||
|
||||
RowMetaPB? rowAtIndex(int index) {
|
||||
if (index < group.rows.length) {
|
||||
return group.rows[index];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
RowMetaPB? rowAtIndex(int index) => group.rows.elementAtOrNull(index);
|
||||
|
||||
RowMetaPB? firstRow() {
|
||||
if (group.rows.isEmpty) return null;
|
||||
return group.rows.first;
|
||||
}
|
||||
RowMetaPB? firstRow() => group.rows.firstOrNull;
|
||||
|
||||
RowMetaPB? lastRow() {
|
||||
if (group.rows.isEmpty) return null;
|
||||
return group.rows.last;
|
||||
}
|
||||
RowMetaPB? lastRow() => group.rows.lastOrNull;
|
||||
|
||||
void startListening() {
|
||||
_listener.start(
|
||||
|
@ -269,7 +269,7 @@ class HiddenGroupButtonContent extends StatelessWidget {
|
||||
),
|
||||
const HSpace(4),
|
||||
FlowyText.medium(
|
||||
group.groupName,
|
||||
bloc.generateGroupNameFromGroup(group),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const HSpace(6),
|
||||
@ -369,7 +369,7 @@ class HiddenGroupPopupItemList extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
child: FlowyText.medium(
|
||||
group.groupName,
|
||||
context.read<BoardBloc>().generateGroupNameFromGroup(group),
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).hintColor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
@ -49,12 +49,6 @@ void main() {
|
||||
boardBloc.groupControllers.values.length == 1,
|
||||
"Expected 1, but receive ${boardBloc.groupControllers.values.length}",
|
||||
);
|
||||
final expectedGroupName = "No ${multiSelectField.name}";
|
||||
assert(
|
||||
boardBloc.groupControllers.values.first.group.groupName ==
|
||||
expectedGroupName,
|
||||
"Expected $expectedGroupName, but receive ${boardBloc.groupControllers.values.first.group.groupName}",
|
||||
);
|
||||
});
|
||||
|
||||
test('group by multi select with no options test', () async {
|
||||
@ -105,11 +99,5 @@ void main() {
|
||||
boardBloc.groupControllers.values.length == 3,
|
||||
"Expected 3, but receive ${boardBloc.groupControllers.values.length}",
|
||||
);
|
||||
|
||||
final groups =
|
||||
boardBloc.groupControllers.values.map((e) => e.group).toList();
|
||||
assert(groups[0].groupName == "No ${multiSelectField.name}");
|
||||
assert(groups[1].groupName == "B");
|
||||
assert(groups[2].groupName == "A");
|
||||
});
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ export interface GroupSetting {
|
||||
|
||||
export interface Group {
|
||||
id: string;
|
||||
name: string;
|
||||
isDefault: boolean;
|
||||
isVisible: boolean;
|
||||
fieldId: string;
|
||||
@ -18,7 +17,6 @@ export interface Group {
|
||||
export function pbToGroup(pb: GroupPB): Group {
|
||||
return {
|
||||
id: pb.group_id,
|
||||
name: pb.group_name,
|
||||
isDefault: pb.is_default,
|
||||
isVisible: pb.is_visible,
|
||||
fieldId: pb.field_id,
|
||||
|
@ -89,7 +89,6 @@ async fn rename_group_event_test() {
|
||||
.create_board(¤t_workspace.id, "my board view".to_owned(), vec![])
|
||||
.await;
|
||||
|
||||
// Empty to group id
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
let error = test
|
||||
.update_group(
|
||||
@ -101,9 +100,6 @@ async fn rename_group_event_test() {
|
||||
)
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
assert_eq!(groups[1].group_name, "new name".to_owned());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -144,9 +140,6 @@ async fn update_group_name_test() {
|
||||
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
assert_eq!(groups.len(), 4);
|
||||
assert_eq!(groups[1].group_name, "To Do");
|
||||
assert_eq!(groups[2].group_name, "Doing");
|
||||
assert_eq!(groups[3].group_name, "Done");
|
||||
|
||||
test
|
||||
.update_group(
|
||||
@ -160,8 +153,6 @@ async fn update_group_name_test() {
|
||||
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
assert_eq!(groups.len(), 4);
|
||||
assert_eq!(groups[1].group_name, "To Do?");
|
||||
assert_eq!(groups[2].group_name, "Doing");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -174,14 +165,9 @@ async fn delete_group_test() {
|
||||
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
assert_eq!(groups.len(), 4);
|
||||
assert_eq!(groups[1].group_name, "To Do");
|
||||
assert_eq!(groups[2].group_name, "Doing");
|
||||
assert_eq!(groups[3].group_name, "Done");
|
||||
|
||||
test.delete_group(&board_view.id, &groups[1].group_id).await;
|
||||
|
||||
let groups = test.get_groups(&board_view.id).await;
|
||||
assert_eq!(groups.len(), 3);
|
||||
assert_eq!(groups[1].group_name, "Doing");
|
||||
assert_eq!(groups[2].group_name, "Done");
|
||||
}
|
||||
|
@ -76,9 +76,6 @@ pub struct GroupPB {
|
||||
#[pb(index = 2)]
|
||||
pub group_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub group_name: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub rows: Vec<RowMetaPB>,
|
||||
|
||||
@ -94,7 +91,6 @@ impl std::convert::From<GroupData> for GroupPB {
|
||||
Self {
|
||||
field_id: group_data.field_id,
|
||||
group_id: group_data.id,
|
||||
group_name: group_data.name,
|
||||
rows: group_data.rows.into_iter().map(RowMetaPB::from).collect(),
|
||||
is_default: group_data.is_default,
|
||||
is_visible: group_data.is_visible,
|
||||
|
@ -11,16 +11,13 @@ pub struct GroupRowsNotificationPB {
|
||||
#[pb(index = 1)]
|
||||
pub group_id: String,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub group_name: Option<String>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
#[pb(index = 2)]
|
||||
pub inserted_rows: Vec<InsertedRowPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
#[pb(index = 3)]
|
||||
pub deleted_rows: Vec<String>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
#[pb(index = 4)]
|
||||
pub updated_rows: Vec<RowMetaPB>,
|
||||
}
|
||||
|
||||
@ -43,10 +40,7 @@ impl std::fmt::Display for GroupRowsNotificationPB {
|
||||
|
||||
impl GroupRowsNotificationPB {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.group_name.is_none()
|
||||
&& self.inserted_rows.is_empty()
|
||||
&& self.deleted_rows.is_empty()
|
||||
&& self.updated_rows.is_empty()
|
||||
self.inserted_rows.is_empty() && self.deleted_rows.is_empty() && self.updated_rows.is_empty()
|
||||
}
|
||||
|
||||
pub fn new(group_id: String) -> Self {
|
||||
@ -56,14 +50,6 @@ impl GroupRowsNotificationPB {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(group_id: String, name: &str) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
group_name: Some(name.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(group_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
|
@ -668,7 +668,7 @@ pub(crate) async fn update_group_handler(
|
||||
let (tx, rx) = oneshot::channel();
|
||||
af_spawn(async move {
|
||||
let result = database_editor
|
||||
.update_group(&view_id, vec![group_changeset].into())
|
||||
.update_group(&view_id, vec![group_changeset])
|
||||
.await;
|
||||
let _ = tx.send(result);
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ use crate::services::field_settings::{
|
||||
default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams,
|
||||
};
|
||||
use crate::services::filter::{Filter, FilterChangeset};
|
||||
use crate::services::group::{default_group_setting, GroupChangesets, GroupSetting, RowChangeset};
|
||||
use crate::services::group::{default_group_setting, GroupChangeset, GroupSetting, RowChangeset};
|
||||
use crate::services::share::csv::{CSVExport, CSVFormat};
|
||||
use crate::services::sort::Sort;
|
||||
use crate::utils::cache::AnyTypeCache;
|
||||
@ -210,7 +210,11 @@ impl DatabaseEditor {
|
||||
Ok(self.database.lock().delete_view(view_id))
|
||||
}
|
||||
|
||||
pub async fn update_group(&self, view_id: &str, changesets: GroupChangesets) -> FlowyResult<()> {
|
||||
pub async fn update_group(
|
||||
&self,
|
||||
view_id: &str,
|
||||
changesets: Vec<GroupChangeset>,
|
||||
) -> FlowyResult<()> {
|
||||
let view_editor = self.database_views.get_view_editor(view_id).await?;
|
||||
view_editor.v_update_group(changesets).await?;
|
||||
Ok(())
|
||||
|
@ -37,7 +37,7 @@ use crate::services::database_view::{
|
||||
};
|
||||
use crate::services::field_settings::FieldSettings;
|
||||
use crate::services::filter::{Filter, FilterChangeset, FilterController};
|
||||
use crate::services::group::{GroupChangesets, GroupController, MoveGroupRowContext, RowChangeset};
|
||||
use crate::services::group::{GroupChangeset, GroupController, MoveGroupRowContext, RowChangeset};
|
||||
use crate::services::setting::CalendarLayoutSetting;
|
||||
use crate::services::sort::{Sort, SortChangeset, SortController};
|
||||
|
||||
@ -139,12 +139,13 @@ impl DatabaseViewEditor {
|
||||
|
||||
// fill in cells according to group_id if supplied
|
||||
if let Some(group_id) = params.group_id {
|
||||
let _ = self
|
||||
.mut_group_controller(|group_controller, field| {
|
||||
group_controller.will_create_row(&mut cells, &field, &group_id);
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
if let Some(controller) = self.group_controller.read().await.as_ref() {
|
||||
let field = self
|
||||
.delegate
|
||||
.get_field(controller.field_id())
|
||||
.ok_or_else(|| FlowyError::internal().with_context("Failed to get grouping field"))?;
|
||||
controller.will_create_row(&mut cells, &field, &group_id);
|
||||
}
|
||||
}
|
||||
|
||||
// fill in cells according to active filters
|
||||
@ -460,13 +461,12 @@ impl DatabaseViewEditor {
|
||||
Ok(changes)
|
||||
}
|
||||
|
||||
pub async fn v_update_group(&self, changeset: GroupChangesets) -> FlowyResult<()> {
|
||||
pub async fn v_update_group(&self, changeset: Vec<GroupChangeset>) -> FlowyResult<()> {
|
||||
let mut type_option_data = TypeOptionData::new();
|
||||
let (old_field, updated_groups) = if let Some(controller) =
|
||||
self.group_controller.write().await.as_mut()
|
||||
{
|
||||
let (old_field, updated_groups) =
|
||||
if let Some(controller) = self.group_controller.write().await.as_mut() {
|
||||
let old_field = self.delegate.get_field(controller.field_id());
|
||||
let (updated_groups, new_type_option) = controller.apply_group_changeset(&changeset).await?;
|
||||
let (updated_groups, new_type_option) = controller.apply_group_changeset(&changeset)?;
|
||||
type_option_data.extend(new_type_option);
|
||||
|
||||
(old_field, updated_groups)
|
||||
@ -800,13 +800,6 @@ impl DatabaseViewEditor {
|
||||
.did_update_field_type_option(&field)
|
||||
.await;
|
||||
|
||||
self
|
||||
.mut_group_controller(|group_controller, _| {
|
||||
group_controller.did_update_field_type_option(&field);
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
|
||||
if old_field.field_type != field.field_type {
|
||||
let changeset = FilterChangeset::DeleteAllWithFieldId {
|
||||
field_id: field.id.clone(),
|
||||
|
@ -1,8 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{Cell, RowId};
|
||||
use collab_database::rows::RowId;
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
@ -11,8 +10,8 @@ use crate::entities::FieldType;
|
||||
use crate::services::database_view::DatabaseViewOperation;
|
||||
use crate::services::field::RowSingleCellData;
|
||||
use crate::services::group::{
|
||||
find_new_grouping_field, make_group_controller, GroupController, GroupSetting,
|
||||
GroupSettingReader, GroupSettingWriter, GroupTypeOptionCellOperation,
|
||||
find_suitable_grouping_field, make_group_controller, GroupContextDelegate, GroupController,
|
||||
GroupControllerDelegate, GroupSetting,
|
||||
};
|
||||
|
||||
pub async fn new_group_controller_with_field(
|
||||
@ -20,19 +19,9 @@ pub async fn new_group_controller_with_field(
|
||||
delegate: Arc<dyn DatabaseViewOperation>,
|
||||
grouping_field: Field,
|
||||
) -> FlowyResult<Box<dyn GroupController>> {
|
||||
let setting_reader = GroupSettingReaderImpl(delegate.clone());
|
||||
let configuration_delegate = GroupControllerDelegateImpl(delegate.clone());
|
||||
let rows = delegate.get_rows(&view_id).await;
|
||||
let setting_writer = GroupSettingWriterImpl(delegate.clone());
|
||||
let type_option_writer = GroupTypeOptionCellWriterImpl(delegate.clone());
|
||||
make_group_controller(
|
||||
view_id,
|
||||
grouping_field,
|
||||
rows,
|
||||
setting_reader,
|
||||
setting_writer,
|
||||
type_option_writer,
|
||||
)
|
||||
.await
|
||||
make_group_controller(view_id, grouping_field, rows, configuration_delegate).await
|
||||
}
|
||||
|
||||
pub async fn new_group_controller(
|
||||
@ -40,10 +29,10 @@ pub async fn new_group_controller(
|
||||
delegate: Arc<dyn DatabaseViewOperation>,
|
||||
) -> FlowyResult<Option<Box<dyn GroupController>>> {
|
||||
let fields = delegate.get_fields(&view_id, None).await;
|
||||
let setting_reader = GroupSettingReaderImpl(delegate.clone());
|
||||
let controller_delegate = GroupControllerDelegateImpl(delegate.clone());
|
||||
|
||||
// Read the grouping field or find a new grouping field
|
||||
let mut grouping_field = setting_reader
|
||||
let mut grouping_field = controller_delegate
|
||||
.get_group_setting(&view_id)
|
||||
.await
|
||||
.and_then(|setting| {
|
||||
@ -56,32 +45,22 @@ pub async fn new_group_controller(
|
||||
let layout = delegate.get_layout_for_view(&view_id);
|
||||
// If the view is a board and the grouping field is empty, we need to find a new grouping field
|
||||
if layout.is_board() && grouping_field.is_none() {
|
||||
grouping_field = find_new_grouping_field(&fields, &layout);
|
||||
grouping_field = find_suitable_grouping_field(&fields);
|
||||
}
|
||||
|
||||
if let Some(grouping_field) = grouping_field {
|
||||
let rows = delegate.get_rows(&view_id).await;
|
||||
let setting_writer = GroupSettingWriterImpl(delegate.clone());
|
||||
let type_option_writer = GroupTypeOptionCellWriterImpl(delegate.clone());
|
||||
Ok(Some(
|
||||
make_group_controller(
|
||||
view_id,
|
||||
grouping_field,
|
||||
rows,
|
||||
setting_reader,
|
||||
setting_writer,
|
||||
type_option_writer,
|
||||
)
|
||||
.await?,
|
||||
make_group_controller(view_id, grouping_field, rows, controller_delegate).await?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GroupSettingReaderImpl(pub Arc<dyn DatabaseViewOperation>);
|
||||
pub(crate) struct GroupControllerDelegateImpl(pub Arc<dyn DatabaseViewOperation>);
|
||||
|
||||
impl GroupSettingReader for GroupSettingReaderImpl {
|
||||
impl GroupContextDelegate for GroupControllerDelegateImpl {
|
||||
fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>> {
|
||||
let mut settings = self.0.get_group_setting(view_id);
|
||||
to_fut(async move {
|
||||
@ -99,6 +78,17 @@ impl GroupSettingReader for GroupSettingReaderImpl {
|
||||
let delegate = self.0.clone();
|
||||
to_fut(async move { get_cells_for_field(delegate, &view_id, &field_id).await })
|
||||
}
|
||||
|
||||
fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>> {
|
||||
self.0.insert_group_setting(view_id, group_setting);
|
||||
to_fut(async move { Ok(()) })
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupControllerDelegate for GroupControllerDelegateImpl {
|
||||
fn get_field(&self, field_id: &str) -> Option<Field> {
|
||||
self.0.get_field(field_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_cell_for_row(
|
||||
@ -153,31 +143,3 @@ pub(crate) async fn get_cells_for_field(
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
struct GroupSettingWriterImpl(Arc<dyn DatabaseViewOperation>);
|
||||
impl GroupSettingWriter for GroupSettingWriterImpl {
|
||||
fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>> {
|
||||
self.0.insert_group_setting(view_id, group_setting);
|
||||
to_fut(async move { Ok(()) })
|
||||
}
|
||||
}
|
||||
|
||||
struct GroupTypeOptionCellWriterImpl(Arc<dyn DatabaseViewOperation>);
|
||||
|
||||
#[async_trait]
|
||||
impl GroupTypeOptionCellOperation for GroupTypeOptionCellWriterImpl {
|
||||
async fn get_cell(&self, _row_id: &RowId, _field_id: &str) -> FlowyResult<Option<Cell>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
async fn update_cell(
|
||||
&self,
|
||||
_view_id: &str,
|
||||
_row_id: &RowId,
|
||||
_field_id: &str,
|
||||
_cell: Cell,
|
||||
) -> FlowyResult<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,14 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::database::MutexDatabase;
|
||||
use collab_database::rows::{RowDetail, RowId};
|
||||
use nanoid::nanoid;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_infra::future::Fut;
|
||||
|
||||
use crate::services::cell::CellCache;
|
||||
use crate::services::database::DatabaseRowEvent;
|
||||
use crate::services::database_view::{DatabaseViewEditor, DatabaseViewOperation};
|
||||
use crate::services::group::RowChangeset;
|
||||
|
||||
pub type RowEventSender = broadcast::Sender<DatabaseRowEvent>;
|
||||
pub type RowEventReceiver = broadcast::Receiver<DatabaseRowEvent>;
|
||||
@ -59,30 +56,6 @@ impl DatabaseViews {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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 async fn move_group_row(
|
||||
&self,
|
||||
view_id: &str,
|
||||
row_detail: Arc<RowDetail>,
|
||||
to_group_id: String,
|
||||
to_row_id: Option<RowId>,
|
||||
recv_row_changeset: impl FnOnce(RowChangeset) -> Fut<()>,
|
||||
) -> FlowyResult<()> {
|
||||
let view_editor = self.get_view_editor(view_id).await?;
|
||||
let mut row_changeset = RowChangeset::new(row_detail.row.id.clone());
|
||||
view_editor
|
||||
.v_move_group_row(&row_detail, &mut row_changeset, &to_group_id, to_row_id)
|
||||
.await;
|
||||
|
||||
if !row_changeset.is_empty() {
|
||||
recv_row_changeset(row_changeset).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult<Arc<DatabaseViewEditor>> {
|
||||
debug_assert!(!view_id.is_empty());
|
||||
if let Some(editor) = self.editor_by_view_id.read().await.get(view_id) {
|
||||
|
@ -22,7 +22,7 @@ use crate::services::field::{
|
||||
use crate::services::filter::{ParseFilterData, PreFillCellsWithFilter};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
pub trait TypeOption {
|
||||
pub trait TypeOption: From<TypeOptionData> + Into<TypeOptionData> {
|
||||
/// `CellData` represents the decoded model for the current type option. Each of them must
|
||||
/// implement the From<&Cell> trait. If the `Cell` cannot be decoded into this type, the default
|
||||
/// value will be returned.
|
||||
|
@ -138,7 +138,7 @@ where
|
||||
cell: &Cell,
|
||||
decoded_field_type: &FieldType,
|
||||
field: &Field,
|
||||
) -> FlowyResult<<Self as TypeOption>::CellData> {
|
||||
) -> FlowyResult<T::CellData> {
|
||||
let key = CellDataCacheKey::new(field, *decoded_field_type, cell);
|
||||
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
|
||||
let read_guard = cell_data_cache.read();
|
||||
@ -168,12 +168,7 @@ where
|
||||
Ok(cell_data)
|
||||
}
|
||||
|
||||
fn set_decoded_cell_data(
|
||||
&self,
|
||||
cell: &Cell,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
field: &Field,
|
||||
) {
|
||||
fn set_decoded_cell_data(&self, cell: &Cell, cell_data: T::CellData, field: &Field) {
|
||||
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let key = CellDataCacheKey::new(field, field_type, cell);
|
||||
@ -196,16 +191,6 @@ impl<T> std::ops::Deref for TypeOptionCellDataHandlerImpl<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypeOption for TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption + Send + Sync,
|
||||
{
|
||||
type CellData = T::CellData;
|
||||
type CellChangeset = T::CellChangeset;
|
||||
type CellProtobufType = T::CellProtobufType;
|
||||
type CellFilter = T::CellFilter;
|
||||
}
|
||||
|
||||
impl<T> TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl<T>
|
||||
where
|
||||
T: TypeOption
|
||||
@ -227,7 +212,7 @@ where
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
let cell_data = self
|
||||
.get_cell_data(cell, decoded_field_type, field_rev)?
|
||||
.unbox_or_default::<<Self as TypeOption>::CellData>();
|
||||
.unbox_or_default::<T::CellData>();
|
||||
|
||||
CellProtobufBlob::from(self.protobuf_encode(cell_data))
|
||||
}
|
||||
@ -238,7 +223,7 @@ where
|
||||
old_cell: Option<Cell>,
|
||||
field: &Field,
|
||||
) -> FlowyResult<Cell> {
|
||||
let changeset = cell_changeset.unbox_or_error::<<Self as TypeOption>::CellChangeset>()?;
|
||||
let changeset = cell_changeset.unbox_or_error::<T::CellChangeset>()?;
|
||||
let (cell, cell_data) = self.apply_changeset(changeset, old_cell)?;
|
||||
self.set_decoded_cell_data(&cell, cell_data, field);
|
||||
Ok(cell)
|
||||
@ -306,7 +291,7 @@ where
|
||||
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool {
|
||||
let perform_filter = || {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let cell_filter = filter.downcast_ref::<<Self as TypeOption>::CellFilter>()?;
|
||||
let cell_filter = filter.downcast_ref::<T::CellFilter>()?;
|
||||
let cell_data = self.get_decoded_cell_data(cell, &field_type, field).ok()?;
|
||||
Some(self.apply_filter(cell_filter, &cell_data))
|
||||
};
|
||||
|
@ -1,16 +1,15 @@
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cell, Row, RowDetail, RowId};
|
||||
use collab_database::rows::{Cell, Cells, Row, RowDetail, RowId};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
|
||||
use crate::services::field::TypeOption;
|
||||
use crate::services::group::{GroupChangesets, GroupData, MoveGroupRowContext};
|
||||
use crate::services::group::{GroupChangeset, GroupData, MoveGroupRowContext};
|
||||
|
||||
/// Using polymorphism to provides the customs action for different group controller.
|
||||
///
|
||||
/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior.
|
||||
/// [GroupCustomize] is implemented by parameterized `BaseGroupController`s to provide different
|
||||
/// behaviors. This allows the BaseGroupController to call these actions indescriminantly using
|
||||
/// polymorphism.
|
||||
///
|
||||
pub trait GroupCustomize: Send + Sync {
|
||||
type GroupTypeOption: TypeOption;
|
||||
@ -57,11 +56,7 @@ pub trait GroupCustomize: Send + Sync {
|
||||
) -> (Option<GroupPB>, Vec<GroupRowsNotificationPB>);
|
||||
|
||||
/// Move row from one group to another
|
||||
fn move_row(
|
||||
&mut self,
|
||||
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB>;
|
||||
fn move_row(&mut self, context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB>;
|
||||
|
||||
/// Returns None if there is no need to delete the group when corresponding row get removed
|
||||
fn delete_group_when_move_row(
|
||||
@ -72,19 +67,35 @@ pub trait GroupCustomize: Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_new_group(
|
||||
fn create_group(
|
||||
&mut self,
|
||||
_name: String,
|
||||
) -> FlowyResult<(Option<TypeOptionData>, Option<InsertedGroupPB>)> {
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>>;
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>>;
|
||||
|
||||
fn update_type_option_when_update_group(
|
||||
&mut self,
|
||||
_changeset: &GroupChangeset,
|
||||
_type_option: &mut Self::GroupTypeOption,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Defines the shared actions any group controller can perform.
|
||||
#[async_trait]
|
||||
pub trait GroupControllerOperation: Send + Sync {
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str);
|
||||
}
|
||||
|
||||
/// The `GroupController` trait defines the behavior of the group controller when performing any
|
||||
/// group-related tasks, such as managing rows within a group, transferring rows between groups,
|
||||
/// manipulating groups themselves, and even pre-filling a row's cells before it is created.
|
||||
///
|
||||
/// Depending on the type of the field that is being grouped, a parameterized `BaseGroupController`
|
||||
/// or a `DefaultGroupController` may be the actual object that provides the functionality of
|
||||
/// this trait. For example, a `Single-Select` group controller will be a `BaseGroupController`,
|
||||
/// while a `URL` group controller will be a `DefaultGroupController`.
|
||||
///
|
||||
pub trait GroupController: Send + Sync {
|
||||
/// Returns the id of field that is being used to group the rows
|
||||
fn field_id(&self) -> &str;
|
||||
|
||||
@ -175,10 +186,13 @@ pub trait GroupControllerOperation: Send + Sync {
|
||||
/// in the field type option data.
|
||||
///
|
||||
/// * `changesets`: list of changesets to be made to one or more groups
|
||||
async fn apply_group_changeset(
|
||||
fn apply_group_changeset(
|
||||
&mut self,
|
||||
changesets: &GroupChangesets,
|
||||
changesets: &[GroupChangeset],
|
||||
) -> FlowyResult<(Vec<GroupPB>, TypeOptionData)>;
|
||||
|
||||
/// Called before the row was created.
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1,11 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Formatter;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{Cell, RowId};
|
||||
use indexmap::IndexMap;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
@ -21,28 +18,15 @@ use crate::services::group::{
|
||||
default_group_setting, GeneratedGroups, Group, GroupChangeset, GroupData, GroupSetting,
|
||||
};
|
||||
|
||||
pub trait GroupSettingReader: Send + Sync + 'static {
|
||||
pub trait GroupContextDelegate: Send + Sync + 'static {
|
||||
fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>>;
|
||||
fn get_configuration_cells(&self, view_id: &str, field_id: &str) -> Fut<Vec<RowSingleCellData>>;
|
||||
}
|
||||
|
||||
pub trait GroupSettingWriter: Send + Sync + 'static {
|
||||
fn get_configuration_cells(&self, view_id: &str, field_id: &str) -> Fut<Vec<RowSingleCellData>>;
|
||||
|
||||
fn save_configuration(&self, view_id: &str, group_setting: GroupSetting) -> Fut<FlowyResult<()>>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait GroupTypeOptionCellOperation: Send + Sync + 'static {
|
||||
async fn get_cell(&self, row_id: &RowId, field_id: &str) -> FlowyResult<Option<Cell>>;
|
||||
async fn update_cell(
|
||||
&self,
|
||||
view_id: &str,
|
||||
row_id: &RowId,
|
||||
field_id: &str,
|
||||
cell: Cell,
|
||||
) -> FlowyResult<()>;
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Display for GroupContext<T> {
|
||||
impl<T> std::fmt::Display for GroupControllerContext<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.group_by_id.iter().for_each(|(_, group)| {
|
||||
let _ = f.write_fmt(format_args!(
|
||||
@ -56,12 +40,12 @@ impl<T> std::fmt::Display for GroupContext<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [GroupContext] represents as the groups memory cache
|
||||
/// Each [GenericGroupController] has its own [GroupContext], the `context` has its own configuration
|
||||
/// A [GroupControllerContext] represents as the groups memory cache
|
||||
/// Each [GenericGroupController] has its own [GroupControllerContext], the `context` has its own configuration
|
||||
/// that is restored from the disk.
|
||||
///
|
||||
/// The `context` contains a list of [GroupData]s and the grouping [Field]
|
||||
pub struct GroupContext<C> {
|
||||
pub struct GroupControllerContext<C> {
|
||||
pub view_id: String,
|
||||
/// The group configuration restored from the disk.
|
||||
///
|
||||
@ -70,24 +54,18 @@ pub struct GroupContext<C> {
|
||||
|
||||
configuration_phantom: PhantomData<C>,
|
||||
|
||||
/// The grouping field
|
||||
field: Field,
|
||||
/// The grouping field id
|
||||
field_id: String,
|
||||
|
||||
/// Cache all the groups. Cache the group by its id.
|
||||
/// We use the id of the [Field] as the [No Status] group id.
|
||||
group_by_id: IndexMap<String, GroupData>,
|
||||
|
||||
/// A reader that implement the [GroupSettingReader] trait
|
||||
///
|
||||
reader: Arc<dyn GroupSettingReader>,
|
||||
|
||||
/// A writer that implement the [GroupSettingWriter] trait is used to save the
|
||||
/// configuration to disk
|
||||
///
|
||||
writer: Arc<dyn GroupSettingWriter>,
|
||||
/// delegate that reads and writes data to and from disk
|
||||
delegate: Arc<dyn GroupContextDelegate>,
|
||||
}
|
||||
|
||||
impl<C> GroupContext<C>
|
||||
impl<C> GroupControllerContext<C>
|
||||
where
|
||||
C: Serialize + DeserializeOwned,
|
||||
{
|
||||
@ -95,14 +73,13 @@ where
|
||||
pub async fn new(
|
||||
view_id: String,
|
||||
field: Field,
|
||||
reader: Arc<dyn GroupSettingReader>,
|
||||
writer: Arc<dyn GroupSettingWriter>,
|
||||
delegate: Arc<dyn GroupContextDelegate>,
|
||||
) -> FlowyResult<Self> {
|
||||
event!(tracing::Level::TRACE, "GroupContext::new");
|
||||
let setting = match reader.get_group_setting(&view_id).await {
|
||||
event!(tracing::Level::TRACE, "GroupControllerContext::new");
|
||||
let setting = match delegate.get_group_setting(&view_id).await {
|
||||
None => {
|
||||
let default_configuration = default_group_setting(&field);
|
||||
writer
|
||||
delegate
|
||||
.save_configuration(&view_id, default_configuration.clone())
|
||||
.await?;
|
||||
Arc::new(default_configuration)
|
||||
@ -112,10 +89,9 @@ where
|
||||
|
||||
Ok(Self {
|
||||
view_id,
|
||||
field,
|
||||
field_id: field.id,
|
||||
group_by_id: IndexMap::new(),
|
||||
reader,
|
||||
writer,
|
||||
delegate,
|
||||
setting,
|
||||
configuration_phantom: PhantomData,
|
||||
})
|
||||
@ -126,11 +102,11 @@ where
|
||||
/// We take the `id` of the `field` as the no status group id
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn get_no_status_group(&self) -> Option<&GroupData> {
|
||||
self.group_by_id.get(&self.field.id)
|
||||
self.group_by_id.get(&self.field_id)
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut_no_status_group(&mut self) -> Option<&mut GroupData> {
|
||||
self.group_by_id.get_mut(&self.field.id)
|
||||
self.group_by_id.get_mut(&self.field_id)
|
||||
}
|
||||
|
||||
pub(crate) fn groups(&self) -> Vec<&GroupData> {
|
||||
@ -155,7 +131,7 @@ where
|
||||
/// Iterate mut the groups without `No status` group
|
||||
pub(crate) fn iter_mut_status_groups(&mut self, mut each: impl FnMut(&mut GroupData)) {
|
||||
self.group_by_id.iter_mut().for_each(|(_, group)| {
|
||||
if group.id != self.field.id {
|
||||
if group.id != self.field_id {
|
||||
each(group);
|
||||
}
|
||||
});
|
||||
@ -168,13 +144,7 @@ where
|
||||
}
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub(crate) fn add_new_group(&mut self, group: Group) -> FlowyResult<InsertedGroupPB> {
|
||||
let group_data = GroupData::new(
|
||||
group.id.clone(),
|
||||
self.field.id.clone(),
|
||||
group.name.clone(),
|
||||
group.id.clone(),
|
||||
group.visible,
|
||||
);
|
||||
let group_data = GroupData::new(group.id.clone(), self.field_id.clone(), group.visible);
|
||||
self.group_by_id.insert(group.id.clone(), group_data);
|
||||
let (index, group_data) = self.get_group(&group.id).unwrap();
|
||||
let insert_group = InsertedGroupPB {
|
||||
@ -232,7 +202,7 @@ where
|
||||
configuration
|
||||
.groups
|
||||
.iter()
|
||||
.map(|group| group.name.clone())
|
||||
.map(|group| group.id.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
);
|
||||
@ -268,22 +238,12 @@ where
|
||||
) -> FlowyResult<Option<GroupChangesPB>> {
|
||||
let GeneratedGroups {
|
||||
no_status_group,
|
||||
group_configs,
|
||||
groups,
|
||||
} = generated_groups;
|
||||
|
||||
let mut new_groups = vec![];
|
||||
let mut filter_content_map = HashMap::new();
|
||||
group_configs.into_iter().for_each(|generate_group| {
|
||||
filter_content_map.insert(
|
||||
generate_group.group.id.clone(),
|
||||
generate_group.filter_content,
|
||||
);
|
||||
new_groups.push(generate_group.group);
|
||||
});
|
||||
|
||||
let mut old_groups = self.setting.groups.clone();
|
||||
// clear all the groups if grouping by a new field
|
||||
if self.setting.field_id != self.field.id {
|
||||
if self.setting.field_id != self.field_id {
|
||||
old_groups.clear();
|
||||
}
|
||||
|
||||
@ -292,7 +252,7 @@ where
|
||||
mut all_groups,
|
||||
new_groups,
|
||||
deleted_groups,
|
||||
} = merge_groups(no_status_group, old_groups, new_groups);
|
||||
} = merge_groups(no_status_group, old_groups, groups);
|
||||
|
||||
let deleted_group_ids = deleted_groups
|
||||
.into_iter()
|
||||
@ -321,12 +281,10 @@ where
|
||||
Some(pos) => {
|
||||
let old_group = configuration.groups.get_mut(pos).unwrap();
|
||||
// Take the old group setting
|
||||
group.visible = old_group.visible;
|
||||
if !is_changed {
|
||||
is_changed = is_group_changed(group, old_group);
|
||||
if group.visible != old_group.visible {
|
||||
is_changed = true;
|
||||
}
|
||||
// Consider the the name of the `group_rev` as the newest.
|
||||
old_group.name = group.name.clone();
|
||||
group.visible = old_group.visible;
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -335,31 +293,14 @@ where
|
||||
|
||||
// Update the memory cache of the groups
|
||||
all_groups.into_iter().for_each(|group| {
|
||||
let filter_content = filter_content_map
|
||||
.get(&group.id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "".to_owned());
|
||||
let group = GroupData::new(
|
||||
group.id,
|
||||
self.field.id.clone(),
|
||||
group.name,
|
||||
filter_content,
|
||||
group.visible,
|
||||
);
|
||||
let group = GroupData::new(group.id, self.field_id.clone(), group.visible);
|
||||
self.group_by_id.insert(group.id.clone(), group);
|
||||
});
|
||||
|
||||
let initial_groups = new_groups
|
||||
.into_iter()
|
||||
.flat_map(|group_rev| {
|
||||
let filter_content = filter_content_map.get(&group_rev.id)?;
|
||||
let group = GroupData::new(
|
||||
group_rev.id,
|
||||
self.field.id.clone(),
|
||||
group_rev.name,
|
||||
filter_content.clone(),
|
||||
group_rev.visible,
|
||||
);
|
||||
let group = GroupData::new(group_rev.id, self.field_id.clone(), group_rev.visible);
|
||||
Some(GroupPB::from(group))
|
||||
})
|
||||
.collect();
|
||||
@ -385,14 +326,10 @@ where
|
||||
if let Some(visible) = group_changeset.visible {
|
||||
group.visible = visible;
|
||||
}
|
||||
if let Some(name) = &group_changeset.name {
|
||||
group.name = name.clone();
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(group) = update_group {
|
||||
if let Some(group_data) = self.group_by_id.get_mut(&group.id) {
|
||||
group_data.name = group.name.clone();
|
||||
group_data.is_visible = group.visible;
|
||||
};
|
||||
}
|
||||
@ -401,8 +338,8 @@ where
|
||||
|
||||
pub(crate) async fn get_all_cells(&self) -> Vec<RowSingleCellData> {
|
||||
self
|
||||
.reader
|
||||
.get_configuration_cells(&self.view_id, &self.field.id)
|
||||
.delegate
|
||||
.get_configuration_cells(&self.view_id, &self.field_id)
|
||||
.await
|
||||
}
|
||||
|
||||
@ -423,10 +360,10 @@ where
|
||||
let is_changed = mut_configuration_fn(configuration);
|
||||
if is_changed {
|
||||
let configuration = (*self.setting).clone();
|
||||
let writer = self.writer.clone();
|
||||
let delegate = self.delegate.clone();
|
||||
let view_id = self.view_id.clone();
|
||||
af_spawn(async move {
|
||||
match writer.save_configuration(&view_id, configuration).await {
|
||||
match delegate.save_configuration(&view_id, configuration).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
tracing::error!("Save group configuration failed: {}", e);
|
||||
@ -504,13 +441,6 @@ fn merge_groups(
|
||||
merge_result
|
||||
}
|
||||
|
||||
fn is_group_changed(new: &Group, old: &Group) -> bool {
|
||||
if new.name != old.name {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
struct MergeGroupResult {
|
||||
// Contains the new groups and the updated groups
|
||||
all_groups: Vec<Group>,
|
||||
@ -545,13 +475,13 @@ mod tests {
|
||||
exp_deleted_groups: Vec<&'a str>,
|
||||
}
|
||||
|
||||
let new_group = |name: &str| Group::new(name.to_string(), name.to_string());
|
||||
let new_group = |name: &str| Group::new(name.to_string());
|
||||
let groups_from_strings =
|
||||
|strings: Vec<&str>| strings.iter().map(|s| new_group(s)).collect::<Vec<Group>>();
|
||||
let group_stringify = |groups: Vec<Group>| {
|
||||
groups
|
||||
.iter()
|
||||
.map(|group| group.name.clone())
|
||||
.map(|group| group.id.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
};
|
||||
|
@ -1,13 +1,13 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||
use futures::executor::block_on;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
|
||||
use crate::entities::{
|
||||
FieldType, GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB,
|
||||
@ -16,67 +16,43 @@ use crate::entities::{
|
||||
use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser};
|
||||
use crate::services::field::{default_type_option_data_from_type, TypeOption, TypeOptionCellData};
|
||||
use crate::services::group::action::{
|
||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize,
|
||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupController, GroupCustomize,
|
||||
};
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
use crate::services::group::configuration::GroupControllerContext;
|
||||
use crate::services::group::entities::GroupData;
|
||||
use crate::services::group::{GroupChangeset, GroupChangesets, GroupsBuilder, MoveGroupRowContext};
|
||||
use crate::services::group::{GroupChangeset, GroupsBuilder, MoveGroupRowContext};
|
||||
|
||||
// use collab_database::views::Group;
|
||||
pub trait GroupControllerDelegate: Send + Sync + 'static {
|
||||
fn get_field(&self, field_id: &str) -> Option<Field>;
|
||||
}
|
||||
|
||||
/// The [GroupController] trait defines the group actions, including create/delete/move items
|
||||
/// For example, the group will insert a item if the one of the new [RowRevision]'s [CellRevision]s
|
||||
/// content match the group filter.
|
||||
/// [BaseGroupController] is a generic group controller that provides customized implementations
|
||||
/// of the `GroupController` trait for different field types.
|
||||
///
|
||||
/// Different [FieldType] has a different controller that implements the [GroupController] trait.
|
||||
/// If the [FieldType] doesn't implement its group controller, then the [DefaultGroupController] will
|
||||
/// be used.
|
||||
/// - `C`: represents the group configuration that impl [GroupConfigurationSerde]
|
||||
/// - `G`: group generator, [GroupsBuilder]
|
||||
/// - `P`: parser that impl [CellProtobufBlobParser] for the CellBytes
|
||||
///
|
||||
pub trait GroupController: GroupControllerOperation + Send + Sync {
|
||||
/// Called when the type option of the [Field] was updated.
|
||||
fn did_update_field_type_option(&mut self, field: &Field);
|
||||
|
||||
/// Called before the row was created.
|
||||
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait GroupOperationInterceptor {
|
||||
type GroupTypeOption: TypeOption;
|
||||
async fn type_option_from_group_changeset(
|
||||
&self,
|
||||
_changeset: &GroupChangeset,
|
||||
_type_option: &Self::GroupTypeOption,
|
||||
_view_id: &str,
|
||||
) -> Option<TypeOptionData> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// C: represents the group configuration that impl [GroupConfigurationSerde]
|
||||
/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer]
|
||||
/// G: the group generator, [GroupsBuilder]
|
||||
/// P: the parser that impl [CellProtobufBlobParser] for the CellBytes
|
||||
pub struct BaseGroupController<C, T, G, P, I> {
|
||||
/// See also: [DefaultGroupController] which contains the most basic implementation of
|
||||
/// `GroupController` that only has one group.
|
||||
pub struct BaseGroupController<C, G, P> {
|
||||
pub grouping_field_id: String,
|
||||
pub type_option: T,
|
||||
pub context: GroupContext<C>,
|
||||
pub context: GroupControllerContext<C>,
|
||||
group_builder_phantom: PhantomData<G>,
|
||||
cell_parser_phantom: PhantomData<P>,
|
||||
pub operation_interceptor: I,
|
||||
pub delegate: Arc<dyn GroupControllerDelegate>,
|
||||
}
|
||||
|
||||
impl<C, T, G, P, I> BaseGroupController<C, T, G, P, I>
|
||||
impl<C, T, G, P> BaseGroupController<C, G, P>
|
||||
where
|
||||
C: Serialize + DeserializeOwned,
|
||||
T: TypeOption + From<TypeOptionData> + Send + Sync,
|
||||
G: GroupsBuilder<Context = GroupContext<C>, GroupTypeOption = T>,
|
||||
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
|
||||
T: TypeOption + Send + Sync,
|
||||
G: GroupsBuilder<Context = GroupControllerContext<C>, GroupTypeOption = T>,
|
||||
{
|
||||
pub async fn new(
|
||||
grouping_field: &Field,
|
||||
mut configuration: GroupContext<C>,
|
||||
operation_interceptor: I,
|
||||
mut configuration: GroupControllerContext<C>,
|
||||
delegate: Arc<dyn GroupControllerDelegate>,
|
||||
) -> FlowyResult<Self> {
|
||||
let field_type = FieldType::from(grouping_field.field_type);
|
||||
let type_option = grouping_field
|
||||
@ -89,14 +65,20 @@ where
|
||||
|
||||
Ok(Self {
|
||||
grouping_field_id: grouping_field.id.clone(),
|
||||
type_option,
|
||||
context: configuration,
|
||||
group_builder_phantom: PhantomData,
|
||||
cell_parser_phantom: PhantomData,
|
||||
operation_interceptor,
|
||||
delegate,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_grouping_field_type_option(&self) -> Option<T> {
|
||||
self
|
||||
.delegate
|
||||
.get_field(&self.grouping_field_id)
|
||||
.and_then(|field| field.get_type_option::<T>(FieldType::from(field.field_type)))
|
||||
}
|
||||
|
||||
fn update_no_status_group(
|
||||
&mut self,
|
||||
row_detail: &RowDetail,
|
||||
@ -169,14 +151,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, T, G, P, I> GroupControllerOperation for BaseGroupController<C, T, G, P, I>
|
||||
impl<C, T, G, P> GroupController for BaseGroupController<C, G, P>
|
||||
where
|
||||
P: CellProtobufBlobParser<Object = <T as TypeOption>::CellProtobufType>,
|
||||
C: Serialize + DeserializeOwned + Sync + Send,
|
||||
T: TypeOption + From<TypeOptionData> + Send + Sync,
|
||||
G: GroupsBuilder<Context = GroupContext<C>, GroupTypeOption = T>,
|
||||
I: GroupOperationInterceptor<GroupTypeOption = T> + Send + Sync,
|
||||
T: TypeOption + Send + Sync,
|
||||
G: GroupsBuilder<Context = GroupControllerContext<C>, GroupTypeOption = T>,
|
||||
Self: GroupCustomize<GroupTypeOption = T>,
|
||||
{
|
||||
fn field_id(&self) -> &str {
|
||||
@ -204,7 +184,7 @@ where
|
||||
let mut grouped_rows: Vec<GroupedRow> = vec![];
|
||||
let cell_data = <T as TypeOption>::CellData::from(&cell);
|
||||
for group in self.context.groups() {
|
||||
if self.can_group(&group.filter_content, &cell_data) {
|
||||
if self.can_group(&group.id, &cell_data) {
|
||||
grouped_rows.push(GroupedRow {
|
||||
row_detail: (*row_detail).clone(),
|
||||
group_id: group.id.clone(),
|
||||
@ -236,7 +216,7 @@ where
|
||||
&mut self,
|
||||
name: String,
|
||||
) -> FlowyResult<(Option<TypeOptionData>, Option<InsertedGroupPB>)> {
|
||||
self.generate_new_group(name)
|
||||
<Self as GroupCustomize>::create_group(self, name)
|
||||
}
|
||||
|
||||
fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> {
|
||||
@ -260,7 +240,7 @@ where
|
||||
let mut suitable_group_ids = vec![];
|
||||
|
||||
for group in self.get_all_groups() {
|
||||
if self.can_group(&group.filter_content, &cell_data) {
|
||||
if self.can_group(&group.id, &cell_data) {
|
||||
suitable_group_ids.push(group.id.clone());
|
||||
let changeset = GroupRowsNotificationPB::insert(
|
||||
group.id.clone(),
|
||||
@ -384,7 +364,7 @@ where
|
||||
let cell_bytes = get_cell_protobuf(&cell, context.field, None);
|
||||
let cell_data = cell_bytes.parser::<P>()?;
|
||||
result.deleted_group = self.delete_group_when_move_row(&context.row_detail.row, &cell_data);
|
||||
result.row_changesets = self.move_row(&cell_data, context);
|
||||
result.row_changesets = self.move_row(context);
|
||||
} else {
|
||||
tracing::warn!("Unexpected moving group row, changes should not be empty");
|
||||
}
|
||||
@ -409,32 +389,30 @@ where
|
||||
.iter()
|
||||
.map(|row| row.row.id.clone())
|
||||
.collect();
|
||||
let type_option_data = self.delete_group_custom(group_id)?;
|
||||
let type_option_data = <Self as GroupCustomize>::delete_group(self, group_id)?;
|
||||
Ok((row_ids, type_option_data))
|
||||
},
|
||||
None => Ok((vec![], None)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_group_changeset(
|
||||
fn apply_group_changeset(
|
||||
&mut self,
|
||||
changeset: &GroupChangesets,
|
||||
changeset: &[GroupChangeset],
|
||||
) -> FlowyResult<(Vec<GroupPB>, TypeOptionData)> {
|
||||
for group_changeset in changeset.changesets.iter() {
|
||||
for group_changeset in changeset.iter() {
|
||||
self.context.update_group(group_changeset)?;
|
||||
}
|
||||
let mut type_option_data = TypeOptionData::new();
|
||||
for group_changeset in changeset.changesets.iter() {
|
||||
if let Some(new_type_option_data) = self
|
||||
.operation_interceptor
|
||||
.type_option_from_group_changeset(group_changeset, &self.type_option, &self.context.view_id)
|
||||
.await
|
||||
{
|
||||
type_option_data.extend(new_type_option_data);
|
||||
}
|
||||
|
||||
let mut type_option = self.get_grouping_field_type_option().ok_or_else(|| {
|
||||
FlowyError::internal().with_context("Failed to get grouping field type option")
|
||||
})?;
|
||||
|
||||
for group_changeset in changeset.iter() {
|
||||
self.update_type_option_when_update_group(group_changeset, &mut type_option);
|
||||
}
|
||||
|
||||
let updated_groups = changeset
|
||||
.changesets
|
||||
.iter()
|
||||
.filter_map(|changeset| {
|
||||
self
|
||||
@ -442,7 +420,12 @@ where
|
||||
.map(|(_, group)| GroupPB::from(group))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok((updated_groups, type_option_data))
|
||||
|
||||
Ok((updated_groups, type_option.into()))
|
||||
}
|
||||
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
<Self as GroupCustomize>::will_create_row(self, cells, field, group_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,10 @@ use crate::services::field::{
|
||||
CheckboxCellDataParser, CheckboxTypeOption, TypeOption, CHECK, UNCHECK,
|
||||
};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
use crate::services::group::controller::{BaseGroupController, GroupController};
|
||||
use crate::services::group::configuration::GroupControllerContext;
|
||||
use crate::services::group::controller::BaseGroupController;
|
||||
use crate::services::group::{
|
||||
move_group_row, GeneratedGroupConfig, GeneratedGroups, Group, GroupOperationInterceptor,
|
||||
GroupsBuilder, MoveGroupRowContext,
|
||||
move_group_row, GeneratedGroups, Group, GroupsBuilder, MoveGroupRowContext,
|
||||
};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
@ -22,16 +21,10 @@ pub struct CheckboxGroupConfiguration {
|
||||
pub hide_empty: bool,
|
||||
}
|
||||
|
||||
pub type CheckboxGroupController = BaseGroupController<
|
||||
CheckboxGroupConfiguration,
|
||||
CheckboxTypeOption,
|
||||
CheckboxGroupBuilder,
|
||||
CheckboxCellDataParser,
|
||||
CheckboxGroupOperationInterceptorImpl,
|
||||
>;
|
||||
|
||||
pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfiguration>;
|
||||
pub type CheckboxGroupController =
|
||||
BaseGroupController<CheckboxGroupConfiguration, CheckboxGroupBuilder, CheckboxCellDataParser>;
|
||||
|
||||
pub type CheckboxGroupControllerContext = GroupControllerContext<CheckboxGroupConfiguration>;
|
||||
impl GroupCustomize for CheckboxGroupController {
|
||||
type GroupTypeOption = CheckboxTypeOption;
|
||||
fn placeholder_cell(&self) -> Option<Cell> {
|
||||
@ -126,11 +119,7 @@ impl GroupCustomize for CheckboxGroupController {
|
||||
(None, changesets)
|
||||
}
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
fn move_row(&mut self, mut context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.context.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
@ -140,17 +129,11 @@ impl GroupCustomize for CheckboxGroupController {
|
||||
group_changeset
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, _group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
fn delete_group(&mut self, _group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for CheckboxGroupController {
|
||||
fn did_update_field_type_option(&mut self, _field: &Field) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
match self.context.get_group(group_id) {
|
||||
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||
Some((_, group)) => {
|
||||
@ -165,7 +148,7 @@ impl GroupController for CheckboxGroupController {
|
||||
pub struct CheckboxGroupBuilder();
|
||||
#[async_trait]
|
||||
impl GroupsBuilder for CheckboxGroupBuilder {
|
||||
type Context = CheckboxGroupContext;
|
||||
type Context = CheckboxGroupControllerContext;
|
||||
type GroupTypeOption = CheckboxTypeOption;
|
||||
|
||||
async fn build(
|
||||
@ -173,26 +156,12 @@ impl GroupsBuilder for CheckboxGroupBuilder {
|
||||
_context: &Self::Context,
|
||||
_type_option: &Self::GroupTypeOption,
|
||||
) -> GeneratedGroups {
|
||||
let check_group = GeneratedGroupConfig {
|
||||
group: Group::new(CHECK.to_string(), "".to_string()),
|
||||
filter_content: CHECK.to_string(),
|
||||
};
|
||||
|
||||
let uncheck_group = GeneratedGroupConfig {
|
||||
group: Group::new(UNCHECK.to_string(), "".to_string()),
|
||||
filter_content: UNCHECK.to_string(),
|
||||
};
|
||||
let check_group = Group::new(CHECK.to_string());
|
||||
let uncheck_group = Group::new(UNCHECK.to_string());
|
||||
|
||||
GeneratedGroups {
|
||||
no_status_group: None,
|
||||
group_configs: vec![check_group, uncheck_group],
|
||||
groups: vec![check_group, uncheck_group],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CheckboxGroupOperationInterceptorImpl {}
|
||||
|
||||
#[async_trait]
|
||||
impl GroupOperationInterceptor for CheckboxGroupOperationInterceptorImpl {
|
||||
type GroupTypeOption = CheckboxTypeOption;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
use std::format;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Datelike, Days, Duration, Local, NaiveDate, NaiveDateTime};
|
||||
use chrono::{DateTime, Datelike, Days, Duration, Local, NaiveDateTime};
|
||||
use collab_database::database::timestamp;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
@ -16,28 +14,24 @@ use crate::entities::{
|
||||
use crate::services::cell::insert_date_cell;
|
||||
use crate::services::field::{DateCellData, DateCellDataParser, DateTypeOption, TypeOption};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
use crate::services::group::controller::{BaseGroupController, GroupController};
|
||||
use crate::services::group::configuration::GroupControllerContext;
|
||||
use crate::services::group::controller::BaseGroupController;
|
||||
use crate::services::group::{
|
||||
make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group,
|
||||
GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
|
||||
make_no_status_group, move_group_row, GeneratedGroups, Group, GroupsBuilder, MoveGroupRowContext,
|
||||
};
|
||||
|
||||
pub trait GroupConfigurationContentSerde: Sized + Send + Sync {
|
||||
fn from_json(s: &str) -> Result<Self, serde_json::Error>;
|
||||
fn to_json(&self) -> Result<String, serde_json::Error>;
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct DateGroupConfiguration {
|
||||
pub hide_empty: bool,
|
||||
pub condition: DateCondition,
|
||||
}
|
||||
|
||||
impl GroupConfigurationContentSerde for DateGroupConfiguration {
|
||||
impl DateGroupConfiguration {
|
||||
fn from_json(s: &str) -> Result<Self, serde_json::Error> {
|
||||
serde_json::from_str(s)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn to_json(&self) -> Result<String, serde_json::Error> {
|
||||
serde_json::to_string(self)
|
||||
}
|
||||
@ -54,15 +48,10 @@ pub enum DateCondition {
|
||||
Year = 4,
|
||||
}
|
||||
|
||||
pub type DateGroupController = BaseGroupController<
|
||||
DateGroupConfiguration,
|
||||
DateTypeOption,
|
||||
DateGroupBuilder,
|
||||
DateCellDataParser,
|
||||
DateGroupOperationInterceptorImpl,
|
||||
>;
|
||||
pub type DateGroupController =
|
||||
BaseGroupController<DateGroupConfiguration, DateGroupBuilder, DateCellDataParser>;
|
||||
|
||||
pub type DateGroupContext = GroupContext<DateGroupConfiguration>;
|
||||
pub type DateGroupControllerContext = GroupControllerContext<DateGroupConfiguration>;
|
||||
|
||||
impl GroupCustomize for DateGroupController {
|
||||
type GroupTypeOption = DateTypeOption;
|
||||
@ -80,7 +69,7 @@ impl GroupCustomize for DateGroupController {
|
||||
content: &str,
|
||||
cell_data: &<Self::GroupTypeOption as TypeOption>::CellData,
|
||||
) -> bool {
|
||||
content == group_id(cell_data, &self.context.get_setting_content())
|
||||
content == get_date_group_id(cell_data, &self.context.get_setting_content())
|
||||
}
|
||||
|
||||
fn create_or_delete_group_when_cell_changed(
|
||||
@ -93,7 +82,7 @@ impl GroupCustomize for DateGroupController {
|
||||
let mut inserted_group = None;
|
||||
if self
|
||||
.context
|
||||
.get_group(&group_id(&_cell_data.into(), &setting_content))
|
||||
.get_group(&get_date_group_id(&_cell_data.into(), &setting_content))
|
||||
.is_none()
|
||||
{
|
||||
let group = make_group_from_date_cell(&_cell_data.into(), &setting_content);
|
||||
@ -106,7 +95,7 @@ impl GroupCustomize for DateGroupController {
|
||||
let deleted_group = match _old_cell_data.and_then(|old_cell_data| {
|
||||
self
|
||||
.context
|
||||
.get_group(&group_id(&old_cell_data.into(), &setting_content))
|
||||
.get_group(&get_date_group_id(&old_cell_data.into(), &setting_content))
|
||||
}) {
|
||||
None => None,
|
||||
Some((_, group)) => {
|
||||
@ -138,7 +127,7 @@ impl GroupCustomize for DateGroupController {
|
||||
let setting_content = self.context.get_setting_content();
|
||||
self.context.iter_mut_status_groups(|group| {
|
||||
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
|
||||
if group.id == group_id(&cell_data.into(), &setting_content) {
|
||||
if group.id == get_date_group_id(&cell_data.into(), &setting_content) {
|
||||
if !group.contains_row(&row_detail.row.id) {
|
||||
changeset
|
||||
.inserted_rows
|
||||
@ -180,7 +169,7 @@ impl GroupCustomize for DateGroupController {
|
||||
let setting_content = self.context.get_setting_content();
|
||||
let deleted_group = match self
|
||||
.context
|
||||
.get_group(&group_id(cell_data, &setting_content))
|
||||
.get_group(&get_date_group_id(cell_data, &setting_content))
|
||||
{
|
||||
Some((_, group)) if group.rows.len() == 1 => Some(group.clone()),
|
||||
_ => None,
|
||||
@ -194,11 +183,7 @@ impl GroupCustomize for DateGroupController {
|
||||
(deleted_group, changesets)
|
||||
}
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
fn move_row(&mut self, mut context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.context.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
@ -211,13 +196,13 @@ impl GroupCustomize for DateGroupController {
|
||||
fn delete_group_when_move_row(
|
||||
&mut self,
|
||||
_row: &Row,
|
||||
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
) -> Option<GroupPB> {
|
||||
let mut deleted_group = None;
|
||||
let setting_content = self.context.get_setting_content();
|
||||
if let Some((_, group)) = self
|
||||
.context
|
||||
.get_group(&group_id(&_cell_data.into(), &setting_content))
|
||||
.get_group(&get_date_group_id(&cell_data.into(), &setting_content))
|
||||
{
|
||||
if group.rows.len() == 1 {
|
||||
deleted_group = Some(GroupPB::from(group.clone()));
|
||||
@ -229,16 +214,12 @@ impl GroupCustomize for DateGroupController {
|
||||
deleted_group
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
self.context.delete_group(group_id)?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for DateGroupController {
|
||||
fn did_update_field_type_option(&mut self, _field: &Field) {}
|
||||
|
||||
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
match self.context.get_group(group_id) {
|
||||
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||
Some((_, _)) => {
|
||||
@ -253,7 +234,7 @@ impl GroupController for DateGroupController {
|
||||
pub struct DateGroupBuilder();
|
||||
#[async_trait]
|
||||
impl GroupsBuilder for DateGroupBuilder {
|
||||
type Context = DateGroupContext;
|
||||
type Context = DateGroupControllerContext;
|
||||
type GroupTypeOption = DateTypeOption;
|
||||
|
||||
async fn build(
|
||||
@ -265,39 +246,31 @@ impl GroupsBuilder for DateGroupBuilder {
|
||||
let cells = context.get_all_cells().await;
|
||||
|
||||
// Generate the groups
|
||||
let mut group_configs: Vec<GeneratedGroupConfig> = cells
|
||||
let mut groups: Vec<Group> = cells
|
||||
.into_iter()
|
||||
.flat_map(|value| value.into_date_field_cell_data())
|
||||
.filter(|cell| cell.timestamp.is_some())
|
||||
.map(|cell| {
|
||||
let group = make_group_from_date_cell(&cell, &context.get_setting_content());
|
||||
GeneratedGroupConfig {
|
||||
filter_content: group.id.clone(),
|
||||
group,
|
||||
}
|
||||
})
|
||||
.map(|cell| make_group_from_date_cell(&cell, &context.get_setting_content()))
|
||||
.collect();
|
||||
group_configs.sort_by(|a, b| a.filter_content.cmp(&b.filter_content));
|
||||
groups.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
|
||||
let no_status_group = Some(make_no_status_group(field));
|
||||
|
||||
GeneratedGroups {
|
||||
no_status_group,
|
||||
group_configs,
|
||||
groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_group_from_date_cell(cell_data: &DateCellData, setting_content: &str) -> Group {
|
||||
let group_id = group_id(cell_data, setting_content);
|
||||
Group::new(
|
||||
group_id.clone(),
|
||||
group_name_from_id(&group_id, setting_content),
|
||||
)
|
||||
let group_id = get_date_group_id(cell_data, setting_content);
|
||||
Group::new(group_id)
|
||||
}
|
||||
|
||||
const GROUP_ID_DATE_FORMAT: &str = "%Y/%m/%d";
|
||||
|
||||
fn group_id(cell_data: &DateCellData, setting_content: &str) -> String {
|
||||
fn get_date_group_id(cell_data: &DateCellData, setting_content: &str) -> String {
|
||||
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
|
||||
let date_time = date_time_from_timestamp(cell_data.timestamp);
|
||||
|
||||
@ -354,63 +327,6 @@ fn group_id(cell_data: &DateCellData, setting_content: &str) -> String {
|
||||
date.to_string()
|
||||
}
|
||||
|
||||
fn group_name_from_id(group_id: &str, setting_content: &str) -> String {
|
||||
let config = DateGroupConfiguration::from_json(setting_content).unwrap_or_default();
|
||||
let date = NaiveDate::parse_from_str(group_id, GROUP_ID_DATE_FORMAT).unwrap();
|
||||
|
||||
let tmp;
|
||||
match config.condition {
|
||||
DateCondition::Day => {
|
||||
tmp = format!("{} {}, {}", date.format("%b"), date.day(), date.year(),);
|
||||
tmp
|
||||
},
|
||||
DateCondition::Week => {
|
||||
let begin_of_week = date
|
||||
.checked_sub_days(Days::new(date.weekday().num_days_from_monday() as u64))
|
||||
.unwrap()
|
||||
.format("%d");
|
||||
let end_of_week = date
|
||||
.checked_add_days(Days::new(6 - date.weekday().num_days_from_monday() as u64))
|
||||
.unwrap()
|
||||
.format("%d");
|
||||
|
||||
tmp = format!(
|
||||
"Week of {} {}-{} {}",
|
||||
date.format("%b"),
|
||||
begin_of_week,
|
||||
end_of_week,
|
||||
date.year()
|
||||
);
|
||||
tmp
|
||||
},
|
||||
DateCondition::Month => {
|
||||
tmp = format!("{} {}", date.format("%b"), date.year(),);
|
||||
tmp
|
||||
},
|
||||
DateCondition::Year => date.year().to_string(),
|
||||
DateCondition::Relative => {
|
||||
let now = date_time_from_timestamp(Some(timestamp()));
|
||||
|
||||
let diff = date.signed_duration_since(now.date_naive());
|
||||
let result = match diff.num_days() {
|
||||
0 => "Today",
|
||||
-1 => "Yesterday",
|
||||
1 => "Tomorrow",
|
||||
-7 => "Last 7 days",
|
||||
2 => "Next 7 days",
|
||||
-30 => "Last 30 days",
|
||||
8 => "Next 30 days",
|
||||
_ => {
|
||||
tmp = format!("{} {}", date.format("%b"), date.year(),);
|
||||
&tmp
|
||||
},
|
||||
};
|
||||
|
||||
result.to_string()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn date_time_from_timestamp(timestamp: Option<i64>) -> DateTime<Local> {
|
||||
match timestamp {
|
||||
Some(timestamp) => {
|
||||
@ -423,24 +339,14 @@ fn date_time_from_timestamp(timestamp: Option<i64>) -> DateTime<Local> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DateGroupOperationInterceptorImpl {}
|
||||
|
||||
#[async_trait]
|
||||
impl GroupOperationInterceptor for DateGroupOperationInterceptorImpl {
|
||||
type GroupTypeOption = DateTypeOption;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::vec;
|
||||
|
||||
use chrono::{offset, Days, Duration, NaiveDateTime};
|
||||
|
||||
use crate::services::{
|
||||
field::{date_type_option::DateTypeOption, DateCellData},
|
||||
group::controller_impls::date_controller::{
|
||||
group_id, group_name_from_id, GROUP_ID_DATE_FORMAT,
|
||||
},
|
||||
use crate::services::field::date_type_option::DateTypeOption;
|
||||
use crate::services::field::DateCellData;
|
||||
use crate::services::group::controller_impls::date_controller::{
|
||||
get_date_group_id, GROUP_ID_DATE_FORMAT,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@ -449,7 +355,6 @@ mod tests {
|
||||
cell_data: DateCellData,
|
||||
setting_content: String,
|
||||
exp_group_id: String,
|
||||
exp_group_name: String,
|
||||
}
|
||||
|
||||
let mar_14_2022 = NaiveDateTime::from_timestamp_opt(1647251762, 0).unwrap();
|
||||
@ -471,7 +376,6 @@ mod tests {
|
||||
cell_data: mar_14_2022_cd.clone(),
|
||||
setting_content: r#"{"condition": 0, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: "2022/03/01".to_string(),
|
||||
exp_group_name: "Mar 2022".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: DateCellData {
|
||||
@ -481,7 +385,6 @@ mod tests {
|
||||
},
|
||||
setting_content: r#"{"condition": 0, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: today.format(GROUP_ID_DATE_FORMAT).to_string(),
|
||||
exp_group_name: "Today".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: DateCellData {
|
||||
@ -495,13 +398,11 @@ mod tests {
|
||||
.unwrap()
|
||||
.format(GROUP_ID_DATE_FORMAT)
|
||||
.to_string(),
|
||||
exp_group_name: "Last 7 days".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: mar_14_2022_cd.clone(),
|
||||
setting_content: r#"{"condition": 1, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: "2022/03/14".to_string(),
|
||||
exp_group_name: "Mar 14, 2022".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: DateCellData {
|
||||
@ -516,19 +417,16 @@ mod tests {
|
||||
},
|
||||
setting_content: r#"{"condition": 2, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: "2022/03/14".to_string(),
|
||||
exp_group_name: "Week of Mar 14-20 2022".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: mar_14_2022_cd.clone(),
|
||||
setting_content: r#"{"condition": 3, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: "2022/03/01".to_string(),
|
||||
exp_group_name: "Mar 2022".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: mar_14_2022_cd,
|
||||
setting_content: r#"{"condition": 4, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: "2022/01/01".to_string(),
|
||||
exp_group_name: "2022".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: DateCellData {
|
||||
@ -538,7 +436,6 @@ mod tests {
|
||||
},
|
||||
setting_content: r#"{"condition": 1, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: "2023/06/02".to_string(),
|
||||
exp_group_name: "".to_string(),
|
||||
},
|
||||
GroupIDTest {
|
||||
cell_data: DateCellData {
|
||||
@ -548,18 +445,12 @@ mod tests {
|
||||
},
|
||||
setting_content: r#"{"condition": 1, "hide_empty": false}"#.to_string(),
|
||||
exp_group_id: "2023/06/03".to_string(),
|
||||
exp_group_name: "".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
for (i, test) in tests.iter().enumerate() {
|
||||
let group_id = group_id(&test.cell_data, &test.setting_content);
|
||||
let group_id = get_date_group_id(&test.cell_data, &test.setting_content);
|
||||
assert_eq!(test.exp_group_id, group_id, "test {}", i);
|
||||
|
||||
if !test.exp_group_name.is_empty() {
|
||||
let group_name = group_name_from_id(&group_id, &test.setting_content);
|
||||
assert_eq!(test.exp_group_name, group_name, "test {}", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ use crate::entities::{
|
||||
GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB,
|
||||
};
|
||||
use crate::services::group::action::{
|
||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation,
|
||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupController,
|
||||
};
|
||||
use crate::services::group::{GroupChangesets, GroupController, GroupData, MoveGroupRowContext};
|
||||
use crate::services::group::{GroupChangeset, GroupData, MoveGroupRowContext};
|
||||
|
||||
/// A [DefaultGroupController] is used to handle the group actions for the [FieldType] that doesn't
|
||||
/// implement its own group controller. The default group controller only contains one group, which
|
||||
@ -25,13 +25,7 @@ const DEFAULT_GROUP_CONTROLLER: &str = "DefaultGroupController";
|
||||
|
||||
impl DefaultGroupController {
|
||||
pub fn new(field: &Field) -> Self {
|
||||
let group = GroupData::new(
|
||||
DEFAULT_GROUP_CONTROLLER.to_owned(),
|
||||
field.id.clone(),
|
||||
"".to_owned(),
|
||||
"".to_owned(),
|
||||
true,
|
||||
);
|
||||
let group = GroupData::new(DEFAULT_GROUP_CONTROLLER.to_owned(), field.id.clone(), true);
|
||||
Self {
|
||||
field_id: field.id.clone(),
|
||||
group,
|
||||
@ -40,7 +34,7 @@ impl DefaultGroupController {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GroupControllerOperation for DefaultGroupController {
|
||||
impl GroupController for DefaultGroupController {
|
||||
fn field_id(&self) -> &str {
|
||||
&self.field_id
|
||||
}
|
||||
@ -131,18 +125,12 @@ impl GroupControllerOperation for DefaultGroupController {
|
||||
Ok((vec![], None))
|
||||
}
|
||||
|
||||
async fn apply_group_changeset(
|
||||
fn apply_group_changeset(
|
||||
&mut self,
|
||||
_changeset: &GroupChangesets,
|
||||
_changeset: &[GroupChangeset],
|
||||
) -> FlowyResult<(Vec<GroupPB>, TypeOptionData)> {
|
||||
Ok((Vec::new(), TypeOptionData::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for DefaultGroupController {
|
||||
fn did_update_field_type_option(&mut self, _field: &Field) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
fn will_create_row(&mut self, _cells: &mut Cells, _field: &Field, _group_id: &str) {}
|
||||
fn will_create_row(&self, _cells: &mut Cells, _field: &Field, _group_id: &str) {}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::entities::{FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
|
||||
@ -11,11 +11,11 @@ use crate::services::field::{
|
||||
TypeOption,
|
||||
};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::controller::{BaseGroupController, GroupController};
|
||||
use crate::services::group::controller::BaseGroupController;
|
||||
use crate::services::group::{
|
||||
add_or_remove_select_option_row, generate_select_option_groups, make_no_status_group,
|
||||
move_group_row, remove_select_option_row, GeneratedGroups, Group, GroupChangeset, GroupContext,
|
||||
GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
|
||||
move_group_row, remove_select_option_row, GeneratedGroups, Group, GroupChangeset,
|
||||
GroupControllerContext, GroupsBuilder, MoveGroupRowContext,
|
||||
};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
@ -23,14 +23,12 @@ pub struct MultiSelectGroupConfiguration {
|
||||
pub hide_empty: bool,
|
||||
}
|
||||
|
||||
pub type MultiSelectOptionGroupContext = GroupContext<MultiSelectGroupConfiguration>;
|
||||
pub type MultiSelectGroupControllerContext = GroupControllerContext<MultiSelectGroupConfiguration>;
|
||||
// MultiSelect
|
||||
pub type MultiSelectGroupController = BaseGroupController<
|
||||
MultiSelectGroupConfiguration,
|
||||
MultiSelectTypeOption,
|
||||
MultiSelectGroupBuilder,
|
||||
SelectOptionCellDataParser,
|
||||
MultiSelectGroupOperationInterceptorImpl,
|
||||
>;
|
||||
|
||||
impl GroupCustomize for MultiSelectGroupController {
|
||||
@ -80,11 +78,7 @@ impl GroupCustomize for MultiSelectGroupController {
|
||||
(None, changesets)
|
||||
}
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
fn move_row(&mut self, mut context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.context.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
@ -94,83 +88,44 @@ impl GroupCustomize for MultiSelectGroupController {
|
||||
group_changeset
|
||||
}
|
||||
|
||||
fn generate_new_group(
|
||||
fn create_group(
|
||||
&mut self,
|
||||
name: String,
|
||||
) -> FlowyResult<(Option<TypeOptionData>, Option<InsertedGroupPB>)> {
|
||||
let mut new_type_option = self.type_option.clone();
|
||||
let new_select_option = self.type_option.create_option(&name);
|
||||
let mut new_type_option = self.get_grouping_field_type_option().ok_or_else(|| {
|
||||
FlowyError::internal().with_context("Failed to get grouping field type option")
|
||||
})?;
|
||||
let new_select_option = new_type_option.create_option(&name);
|
||||
new_type_option.insert_option(new_select_option.clone());
|
||||
|
||||
let new_group = Group::new(new_select_option.id, new_select_option.name);
|
||||
let new_group = Group::new(new_select_option.id);
|
||||
let inserted_group_pb = self.context.add_new_group(new_group)?;
|
||||
|
||||
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
if let Some(option_index) = self
|
||||
.type_option
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
let mut new_type_option = self.get_grouping_field_type_option().ok_or_else(|| {
|
||||
FlowyError::internal().with_context("Failed to get grouping field type option")
|
||||
})?;
|
||||
if let Some(option_index) = new_type_option
|
||||
.options
|
||||
.iter()
|
||||
.position(|option| option.id == group_id)
|
||||
{
|
||||
// Remove the option if the group is found
|
||||
let mut new_type_option = self.type_option.clone();
|
||||
new_type_option.options.remove(option_index);
|
||||
Ok(Some(new_type_option.into()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for MultiSelectGroupController {
|
||||
fn did_update_field_type_option(&mut self, _field: &Field) {}
|
||||
|
||||
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
match self.context.get_group(group_id) {
|
||||
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||
Some((_, group)) => {
|
||||
let cell = insert_select_option_cell(vec![group.id.clone()], field);
|
||||
cells.insert(field.id.clone(), cell);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MultiSelectGroupBuilder;
|
||||
#[async_trait]
|
||||
impl GroupsBuilder for MultiSelectGroupBuilder {
|
||||
type Context = MultiSelectOptionGroupContext;
|
||||
type GroupTypeOption = MultiSelectTypeOption;
|
||||
|
||||
async fn build(
|
||||
field: &Field,
|
||||
_context: &Self::Context,
|
||||
type_option: &Self::GroupTypeOption,
|
||||
) -> GeneratedGroups {
|
||||
let group_configs = generate_select_option_groups(&field.id, &type_option.options);
|
||||
GeneratedGroups {
|
||||
no_status_group: Some(make_no_status_group(field)),
|
||||
group_configs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MultiSelectGroupOperationInterceptorImpl;
|
||||
|
||||
#[async_trait]
|
||||
impl GroupOperationInterceptor for MultiSelectGroupOperationInterceptorImpl {
|
||||
type GroupTypeOption = MultiSelectTypeOption;
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn type_option_from_group_changeset(
|
||||
&self,
|
||||
fn update_type_option_when_update_group(
|
||||
&mut self,
|
||||
changeset: &GroupChangeset,
|
||||
type_option: &Self::GroupTypeOption,
|
||||
_view_id: &str,
|
||||
) -> Option<TypeOptionData> {
|
||||
type_option: &mut Self::GroupTypeOption,
|
||||
) {
|
||||
if let Some(name) = &changeset.name {
|
||||
let mut new_type_option = type_option.clone();
|
||||
let select_option = type_option
|
||||
@ -184,9 +139,36 @@ impl GroupOperationInterceptor for MultiSelectGroupOperationInterceptorImpl {
|
||||
..select_option.to_owned()
|
||||
};
|
||||
new_type_option.insert_option(new_select_option);
|
||||
return Some(new_type_option.into());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
match self.context.get_group(group_id) {
|
||||
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||
Some((_index, group)) => {
|
||||
let cell = insert_select_option_cell(vec![group.id.clone()], field);
|
||||
cells.insert(field.id.clone(), cell);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MultiSelectGroupBuilder;
|
||||
#[async_trait]
|
||||
impl GroupsBuilder for MultiSelectGroupBuilder {
|
||||
type Context = MultiSelectGroupControllerContext;
|
||||
type GroupTypeOption = MultiSelectTypeOption;
|
||||
|
||||
async fn build(
|
||||
field: &Field,
|
||||
_context: &Self::Context,
|
||||
type_option: &Self::GroupTypeOption,
|
||||
) -> GeneratedGroups {
|
||||
let groups = generate_select_option_groups(&field.id, &type_option.options);
|
||||
|
||||
GeneratedGroups {
|
||||
no_status_group: Some(make_no_status_group(field)),
|
||||
groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::entities::{FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
|
||||
@ -11,12 +11,11 @@ use crate::services::field::{
|
||||
TypeOption,
|
||||
};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::controller::{BaseGroupController, GroupController};
|
||||
use crate::services::group::controller::BaseGroupController;
|
||||
use crate::services::group::controller_impls::select_option_controller::util::*;
|
||||
use crate::services::group::entities::GroupData;
|
||||
use crate::services::group::{
|
||||
make_no_status_group, GeneratedGroups, Group, GroupChangeset, GroupContext,
|
||||
GroupOperationInterceptor, GroupsBuilder, MoveGroupRowContext,
|
||||
make_no_status_group, GeneratedGroups, Group, GroupChangeset, GroupControllerContext,
|
||||
GroupsBuilder, MoveGroupRowContext,
|
||||
};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
@ -24,19 +23,19 @@ pub struct SingleSelectGroupConfiguration {
|
||||
pub hide_empty: bool,
|
||||
}
|
||||
|
||||
pub type SingleSelectOptionGroupContext = GroupContext<SingleSelectGroupConfiguration>;
|
||||
pub type SingleSelectGroupControllerContext =
|
||||
GroupControllerContext<SingleSelectGroupConfiguration>;
|
||||
|
||||
// SingleSelect
|
||||
pub type SingleSelectGroupController = BaseGroupController<
|
||||
SingleSelectGroupConfiguration,
|
||||
SingleSelectTypeOption,
|
||||
SingleSelectGroupBuilder,
|
||||
SelectOptionCellDataParser,
|
||||
SingleSelectGroupOperationInterceptorImpl,
|
||||
>;
|
||||
|
||||
impl GroupCustomize for SingleSelectGroupController {
|
||||
type GroupTypeOption = SingleSelectTypeOption;
|
||||
|
||||
fn can_group(
|
||||
&self,
|
||||
content: &str,
|
||||
@ -81,11 +80,7 @@ impl GroupCustomize for SingleSelectGroupController {
|
||||
(None, changesets)
|
||||
}
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
fn move_row(&mut self, mut context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.context.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
@ -95,85 +90,44 @@ impl GroupCustomize for SingleSelectGroupController {
|
||||
group_changeset
|
||||
}
|
||||
|
||||
fn generate_new_group(
|
||||
fn create_group(
|
||||
&mut self,
|
||||
name: String,
|
||||
) -> FlowyResult<(Option<TypeOptionData>, Option<InsertedGroupPB>)> {
|
||||
let mut new_type_option = self.type_option.clone();
|
||||
let new_select_option = self.type_option.create_option(&name);
|
||||
let mut new_type_option = self.get_grouping_field_type_option().ok_or_else(|| {
|
||||
FlowyError::internal().with_context("Failed to get grouping field type option")
|
||||
})?;
|
||||
let new_select_option = new_type_option.create_option(&name);
|
||||
new_type_option.insert_option(new_select_option.clone());
|
||||
|
||||
let new_group = Group::new(new_select_option.id, new_select_option.name);
|
||||
let new_group = Group::new(new_select_option.id);
|
||||
let inserted_group_pb = self.context.add_new_group(new_group)?;
|
||||
|
||||
Ok((Some(new_type_option.into()), Some(inserted_group_pb)))
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
if let Some(option_index) = self
|
||||
.type_option
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
let mut new_type_option = self.get_grouping_field_type_option().ok_or_else(|| {
|
||||
FlowyError::internal().with_context("Failed to get grouping field type option")
|
||||
})?;
|
||||
if let Some(option_index) = new_type_option
|
||||
.options
|
||||
.iter()
|
||||
.position(|option| option.id == group_id)
|
||||
{
|
||||
// Remove the option if the group is found
|
||||
let mut new_type_option = self.type_option.clone();
|
||||
new_type_option.options.remove(option_index);
|
||||
Ok(Some(new_type_option.into()))
|
||||
} else {
|
||||
// Return None if no matching group is found
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for SingleSelectGroupController {
|
||||
fn did_update_field_type_option(&mut self, _field: &Field) {}
|
||||
|
||||
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
let group: Option<&mut GroupData> = self.context.get_mut_group(group_id);
|
||||
match group {
|
||||
None => {},
|
||||
Some(group) => {
|
||||
let cell = insert_select_option_cell(vec![group.id.clone()], field);
|
||||
cells.insert(field.id.clone(), cell);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SingleSelectGroupBuilder();
|
||||
#[async_trait]
|
||||
impl GroupsBuilder for SingleSelectGroupBuilder {
|
||||
type Context = SingleSelectOptionGroupContext;
|
||||
type GroupTypeOption = SingleSelectTypeOption;
|
||||
async fn build(
|
||||
field: &Field,
|
||||
_context: &Self::Context,
|
||||
type_option: &Self::GroupTypeOption,
|
||||
) -> GeneratedGroups {
|
||||
let group_configs = generate_select_option_groups(&field.id, &type_option.options);
|
||||
|
||||
GeneratedGroups {
|
||||
no_status_group: Some(make_no_status_group(field)),
|
||||
group_configs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SingleSelectGroupOperationInterceptorImpl;
|
||||
|
||||
#[async_trait]
|
||||
impl GroupOperationInterceptor for SingleSelectGroupOperationInterceptorImpl {
|
||||
type GroupTypeOption = SingleSelectTypeOption;
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn type_option_from_group_changeset(
|
||||
&self,
|
||||
fn update_type_option_when_update_group(
|
||||
&mut self,
|
||||
changeset: &GroupChangeset,
|
||||
type_option: &Self::GroupTypeOption,
|
||||
_view_id: &str,
|
||||
) -> Option<TypeOptionData> {
|
||||
type_option: &mut Self::GroupTypeOption,
|
||||
) {
|
||||
if let Some(name) = &changeset.name {
|
||||
let mut new_type_option = type_option.clone();
|
||||
let select_option = type_option
|
||||
@ -187,9 +141,35 @@ impl GroupOperationInterceptor for SingleSelectGroupOperationInterceptorImpl {
|
||||
..select_option.to_owned()
|
||||
};
|
||||
new_type_option.insert_option(new_select_option);
|
||||
return Some(new_type_option.into());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
match self.context.get_group(group_id) {
|
||||
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||
Some((_index, group)) => {
|
||||
let cell = insert_select_option_cell(vec![group.id.clone()], field);
|
||||
cells.insert(field.id.clone(), cell);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SingleSelectGroupBuilder();
|
||||
#[async_trait]
|
||||
impl GroupsBuilder for SingleSelectGroupBuilder {
|
||||
type Context = SingleSelectGroupControllerContext;
|
||||
type GroupTypeOption = SingleSelectTypeOption;
|
||||
async fn build(
|
||||
field: &Field,
|
||||
_context: &Self::Context,
|
||||
type_option: &Self::GroupTypeOption,
|
||||
) -> GeneratedGroups {
|
||||
let groups = generate_select_option_groups(&field.id, &type_option.options);
|
||||
|
||||
GeneratedGroups {
|
||||
no_status_group: Some(make_no_status_group(field)),
|
||||
groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use crate::services::cell::{
|
||||
insert_checkbox_cell, insert_date_cell, insert_select_option_cell, insert_url_cell,
|
||||
};
|
||||
use crate::services::field::{SelectOption, SelectOptionIds, CHECK};
|
||||
use crate::services::group::{GeneratedGroupConfig, Group, GroupData, MoveGroupRowContext};
|
||||
use crate::services::group::{Group, GroupData, MoveGroupRowContext};
|
||||
|
||||
pub fn add_or_remove_select_option_row(
|
||||
group: &mut GroupData,
|
||||
@ -186,16 +186,10 @@ pub fn make_inserted_cell(group_id: &str, field: &Field) -> Option<Cell> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_select_option_groups(
|
||||
_field_id: &str,
|
||||
options: &[SelectOption],
|
||||
) -> Vec<GeneratedGroupConfig> {
|
||||
pub fn generate_select_option_groups(_field_id: &str, options: &[SelectOption]) -> Vec<Group> {
|
||||
let groups = options
|
||||
.iter()
|
||||
.map(|option| GeneratedGroupConfig {
|
||||
group: Group::new(option.id.clone(), option.name.clone()),
|
||||
filter_content: option.id.clone(),
|
||||
})
|
||||
.map(|option| Group::new(option.id.clone()))
|
||||
.collect();
|
||||
|
||||
groups
|
||||
|
@ -1,5 +1,3 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
|
||||
@ -13,11 +11,10 @@ use crate::entities::{
|
||||
use crate::services::cell::insert_url_cell;
|
||||
use crate::services::field::{TypeOption, URLCellData, URLCellDataParser, URLTypeOption};
|
||||
use crate::services::group::action::GroupCustomize;
|
||||
use crate::services::group::configuration::GroupContext;
|
||||
use crate::services::group::controller::{BaseGroupController, GroupController};
|
||||
use crate::services::group::configuration::GroupControllerContext;
|
||||
use crate::services::group::controller::BaseGroupController;
|
||||
use crate::services::group::{
|
||||
make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroups, Group,
|
||||
GroupOperationInterceptor, GroupTypeOptionCellOperation, GroupsBuilder, MoveGroupRowContext,
|
||||
make_no_status_group, move_group_row, GeneratedGroups, Group, GroupsBuilder, MoveGroupRowContext,
|
||||
};
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
@ -25,15 +22,10 @@ pub struct URLGroupConfiguration {
|
||||
pub hide_empty: bool,
|
||||
}
|
||||
|
||||
pub type URLGroupController = BaseGroupController<
|
||||
URLGroupConfiguration,
|
||||
URLTypeOption,
|
||||
URLGroupGenerator,
|
||||
URLCellDataParser,
|
||||
URLGroupOperationInterceptorImpl,
|
||||
>;
|
||||
pub type URLGroupController =
|
||||
BaseGroupController<URLGroupConfiguration, URLGroupGenerator, URLCellDataParser>;
|
||||
|
||||
pub type URLGroupContext = GroupContext<URLGroupConfiguration>;
|
||||
pub type URLGroupControllerContext = GroupControllerContext<URLGroupConfiguration>;
|
||||
|
||||
impl GroupCustomize for URLGroupController {
|
||||
type GroupTypeOption = URLTypeOption;
|
||||
@ -64,7 +56,7 @@ impl GroupCustomize for URLGroupController {
|
||||
let mut inserted_group = None;
|
||||
if self.context.get_group(&_cell_data.url).is_none() {
|
||||
let cell_data: URLCellData = _cell_data.clone().into();
|
||||
let group = make_group_from_url_cell(&cell_data);
|
||||
let group = Group::new(cell_data.data);
|
||||
let mut new_group = self.context.add_new_group(group)?;
|
||||
new_group.group.rows.push(RowMetaPB::from(_row_detail));
|
||||
inserted_group = Some(new_group);
|
||||
@ -155,11 +147,7 @@ impl GroupCustomize for URLGroupController {
|
||||
(deleted_group, changesets)
|
||||
}
|
||||
|
||||
fn move_row(
|
||||
&mut self,
|
||||
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
mut context: MoveGroupRowContext,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
fn move_row(&mut self, mut context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut group_changeset = vec![];
|
||||
self.context.iter_mut_groups(|group| {
|
||||
if let Some(changeset) = move_group_row(group, &mut context) {
|
||||
@ -168,13 +156,14 @@ impl GroupCustomize for URLGroupController {
|
||||
});
|
||||
group_changeset
|
||||
}
|
||||
|
||||
fn delete_group_when_move_row(
|
||||
&mut self,
|
||||
_row: &Row,
|
||||
_cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
cell_data: &<Self::GroupTypeOption as TypeOption>::CellProtobufType,
|
||||
) -> Option<GroupPB> {
|
||||
let mut deleted_group = None;
|
||||
if let Some((_, group)) = self.context.get_group(&_cell_data.content) {
|
||||
if let Some((_index, group)) = self.context.get_group(&cell_data.content) {
|
||||
if group.rows.len() == 1 {
|
||||
deleted_group = Some(GroupPB::from(group.clone()));
|
||||
}
|
||||
@ -185,16 +174,12 @@ impl GroupCustomize for URLGroupController {
|
||||
deleted_group
|
||||
}
|
||||
|
||||
fn delete_group_custom(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<Option<TypeOptionData>> {
|
||||
self.context.delete_group(group_id)?;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupController for URLGroupController {
|
||||
fn did_update_field_type_option(&mut self, _field: &Field) {}
|
||||
|
||||
fn will_create_row(&mut self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
match self.context.get_group(group_id) {
|
||||
None => tracing::warn!("Can not find the group: {}", group_id),
|
||||
Some((_, group)) => {
|
||||
@ -208,7 +193,7 @@ impl GroupController for URLGroupController {
|
||||
pub struct URLGroupGenerator();
|
||||
#[async_trait]
|
||||
impl GroupsBuilder for URLGroupGenerator {
|
||||
type Context = URLGroupContext;
|
||||
type Context = URLGroupControllerContext;
|
||||
type GroupTypeOption = URLTypeOption;
|
||||
|
||||
async fn build(
|
||||
@ -220,36 +205,18 @@ impl GroupsBuilder for URLGroupGenerator {
|
||||
let cells = context.get_all_cells().await;
|
||||
|
||||
// Generate the groups
|
||||
let group_configs = cells
|
||||
let groups = cells
|
||||
.into_iter()
|
||||
.flat_map(|value| value.into_url_field_cell_data())
|
||||
.filter(|cell| !cell.data.is_empty())
|
||||
.map(|cell| GeneratedGroupConfig {
|
||||
group: make_group_from_url_cell(&cell),
|
||||
filter_content: cell.data,
|
||||
})
|
||||
.map(|cell| Group::new(cell.data.clone()))
|
||||
.collect();
|
||||
|
||||
let no_status_group = Some(make_no_status_group(field));
|
||||
|
||||
GeneratedGroups {
|
||||
no_status_group,
|
||||
group_configs,
|
||||
groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_group_from_url_cell(cell: &URLCellData) -> Group {
|
||||
let group_id = cell.data.clone();
|
||||
let group_name = cell.data.clone();
|
||||
Group::new(group_id, group_name)
|
||||
}
|
||||
|
||||
pub struct URLGroupOperationInterceptorImpl {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) cell_writer: Arc<dyn GroupTypeOptionCellOperation>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl GroupOperationInterceptor for URLGroupOperationInterceptorImpl {
|
||||
type GroupTypeOption = URLTypeOption;
|
||||
}
|
||||
|
@ -14,16 +14,6 @@ pub struct GroupSetting {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
pub struct GroupChangesets {
|
||||
pub changesets: Vec<GroupChangeset>,
|
||||
}
|
||||
|
||||
impl From<Vec<GroupChangeset>> for GroupChangesets {
|
||||
fn from(changesets: Vec<GroupChangeset>) -> Self {
|
||||
Self { changesets }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct GroupChangeset {
|
||||
pub group_id: String,
|
||||
@ -92,7 +82,6 @@ impl From<GroupSetting> for GroupSettingMap {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct Group {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
#[serde(default = "GROUP_VISIBILITY")]
|
||||
pub visible: bool,
|
||||
}
|
||||
@ -104,9 +93,8 @@ impl TryFrom<GroupMap> for Group {
|
||||
match value.get_str_value("id") {
|
||||
None => bail!("Invalid group data"),
|
||||
Some(id) => {
|
||||
let name = value.get_str_value("name").unwrap_or_default();
|
||||
let visible = value.get_bool_value("visible").unwrap_or_default();
|
||||
Ok(Self { id, name, visible })
|
||||
Ok(Self { id, visible })
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -116,7 +104,6 @@ impl From<Group> for GroupMap {
|
||||
fn from(group: Group) -> Self {
|
||||
GroupMapBuilder::new()
|
||||
.insert_str_value("id", group.id)
|
||||
.insert_str_value("name", group.name)
|
||||
.insert_bool_value("visible", group.visible)
|
||||
.build()
|
||||
}
|
||||
@ -125,12 +112,8 @@ impl From<Group> for GroupMap {
|
||||
const GROUP_VISIBILITY: fn() -> bool = || true;
|
||||
|
||||
impl Group {
|
||||
pub fn new(id: String, name: String) -> Self {
|
||||
Self {
|
||||
id,
|
||||
name,
|
||||
visible: true,
|
||||
}
|
||||
pub fn new(id: String) -> Self {
|
||||
Self { id, visible: true }
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,32 +121,20 @@ impl Group {
|
||||
pub struct GroupData {
|
||||
pub id: String,
|
||||
pub field_id: String,
|
||||
pub name: String,
|
||||
pub is_default: bool,
|
||||
pub is_visible: bool,
|
||||
pub(crate) rows: Vec<RowDetail>,
|
||||
|
||||
/// [filter_content] is used to determine which group the cell belongs to.
|
||||
pub filter_content: String,
|
||||
}
|
||||
|
||||
impl GroupData {
|
||||
pub fn new(
|
||||
id: String,
|
||||
field_id: String,
|
||||
name: String,
|
||||
filter_content: String,
|
||||
is_visible: bool,
|
||||
) -> Self {
|
||||
pub fn new(id: String, field_id: String, is_visible: bool) -> Self {
|
||||
let is_default = id == field_id;
|
||||
Self {
|
||||
id,
|
||||
field_id,
|
||||
is_default,
|
||||
is_visible,
|
||||
name,
|
||||
rows: vec![],
|
||||
filter_content,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,21 +4,17 @@ use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{Cell, RowDetail, RowId};
|
||||
use collab_database::views::DatabaseLayout;
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::field::TypeOption;
|
||||
use crate::services::group::{
|
||||
CheckboxGroupContext, CheckboxGroupController, CheckboxGroupOperationInterceptorImpl,
|
||||
DateGroupContext, DateGroupController, DateGroupOperationInterceptorImpl, DefaultGroupController,
|
||||
Group, GroupController, GroupSetting, GroupSettingReader, GroupSettingWriter,
|
||||
GroupTypeOptionCellOperation, MultiSelectGroupController,
|
||||
MultiSelectGroupOperationInterceptorImpl, MultiSelectOptionGroupContext,
|
||||
SingleSelectGroupController, SingleSelectGroupOperationInterceptorImpl,
|
||||
SingleSelectOptionGroupContext, URLGroupContext, URLGroupController,
|
||||
URLGroupOperationInterceptorImpl,
|
||||
CheckboxGroupController, CheckboxGroupControllerContext, DateGroupController,
|
||||
DateGroupControllerContext, DefaultGroupController, Group, GroupContextDelegate, GroupController,
|
||||
GroupControllerDelegate, GroupSetting, MultiSelectGroupController,
|
||||
MultiSelectGroupControllerContext, SingleSelectGroupController,
|
||||
SingleSelectGroupControllerContext, URLGroupController, URLGroupControllerContext,
|
||||
};
|
||||
|
||||
/// The [GroupsBuilder] trait is used to generate the groups for different [FieldType]
|
||||
@ -36,12 +32,7 @@ pub trait GroupsBuilder: Send + Sync + 'static {
|
||||
|
||||
pub struct GeneratedGroups {
|
||||
pub no_status_group: Option<Group>,
|
||||
pub group_configs: Vec<GeneratedGroupConfig>,
|
||||
}
|
||||
|
||||
pub struct GeneratedGroupConfig {
|
||||
pub group: Group,
|
||||
pub filter_content: String,
|
||||
pub groups: Vec<Group>,
|
||||
}
|
||||
|
||||
pub struct MoveGroupRowContext<'a> {
|
||||
@ -94,95 +85,56 @@ impl RowChangeset {
|
||||
fields(grouping_field_id=%grouping_field.id, grouping_field_type)
|
||||
err
|
||||
)]
|
||||
pub async fn make_group_controller<R, W, TW>(
|
||||
pub async fn make_group_controller<D>(
|
||||
view_id: String,
|
||||
grouping_field: Field,
|
||||
row_details: Vec<Arc<RowDetail>>,
|
||||
setting_reader: R,
|
||||
setting_writer: W,
|
||||
type_option_cell_writer: TW,
|
||||
delegate: D,
|
||||
) -> FlowyResult<Box<dyn GroupController>>
|
||||
where
|
||||
R: GroupSettingReader,
|
||||
W: GroupSettingWriter,
|
||||
TW: GroupTypeOptionCellOperation,
|
||||
D: GroupContextDelegate + GroupControllerDelegate,
|
||||
{
|
||||
let grouping_field_type = FieldType::from(grouping_field.field_type);
|
||||
tracing::Span::current().record("grouping_field", &grouping_field_type.default_name());
|
||||
|
||||
let mut group_controller: Box<dyn GroupController>;
|
||||
let configuration_reader = Arc::new(setting_reader);
|
||||
let configuration_writer = Arc::new(setting_writer);
|
||||
let type_option_cell_writer = Arc::new(type_option_cell_writer);
|
||||
let delegate = Arc::new(delegate);
|
||||
|
||||
match grouping_field_type {
|
||||
FieldType::SingleSelect => {
|
||||
let configuration = SingleSelectOptionGroupContext::new(
|
||||
view_id,
|
||||
grouping_field.clone(),
|
||||
configuration_reader,
|
||||
configuration_writer,
|
||||
)
|
||||
let configuration =
|
||||
SingleSelectGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone())
|
||||
.await?;
|
||||
let operation_interceptor = SingleSelectGroupOperationInterceptorImpl;
|
||||
let controller =
|
||||
SingleSelectGroupController::new(&grouping_field, configuration, operation_interceptor)
|
||||
.await?;
|
||||
SingleSelectGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::MultiSelect => {
|
||||
let configuration = MultiSelectOptionGroupContext::new(
|
||||
view_id,
|
||||
grouping_field.clone(),
|
||||
configuration_reader,
|
||||
configuration_writer,
|
||||
)
|
||||
let configuration =
|
||||
MultiSelectGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone())
|
||||
.await?;
|
||||
let operation_interceptor = MultiSelectGroupOperationInterceptorImpl;
|
||||
let controller =
|
||||
MultiSelectGroupController::new(&grouping_field, configuration, operation_interceptor)
|
||||
.await?;
|
||||
MultiSelectGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::Checkbox => {
|
||||
let configuration = CheckboxGroupContext::new(
|
||||
view_id,
|
||||
grouping_field.clone(),
|
||||
configuration_reader,
|
||||
configuration_writer,
|
||||
)
|
||||
let configuration =
|
||||
CheckboxGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone())
|
||||
.await?;
|
||||
let operation_interceptor = CheckboxGroupOperationInterceptorImpl {};
|
||||
let controller =
|
||||
CheckboxGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
|
||||
CheckboxGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::URL => {
|
||||
let configuration = URLGroupContext::new(
|
||||
view_id,
|
||||
grouping_field.clone(),
|
||||
configuration_reader,
|
||||
configuration_writer,
|
||||
)
|
||||
.await?;
|
||||
let operation_interceptor = URLGroupOperationInterceptorImpl {
|
||||
cell_writer: type_option_cell_writer,
|
||||
};
|
||||
let controller =
|
||||
URLGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
|
||||
let configuration =
|
||||
URLGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone()).await?;
|
||||
let controller = URLGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::DateTime => {
|
||||
let configuration = DateGroupContext::new(
|
||||
view_id,
|
||||
grouping_field.clone(),
|
||||
configuration_reader,
|
||||
configuration_writer,
|
||||
)
|
||||
.await?;
|
||||
let operation_interceptor = DateGroupOperationInterceptorImpl {};
|
||||
let controller =
|
||||
DateGroupController::new(&grouping_field, configuration, operation_interceptor).await?;
|
||||
let configuration =
|
||||
DateGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone()).await?;
|
||||
let controller = DateGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
_ => {
|
||||
@ -194,32 +146,21 @@ where
|
||||
let rows = row_details
|
||||
.iter()
|
||||
.map(|row| row.as_ref())
|
||||
.collect::<Vec<&RowDetail>>();
|
||||
.collect::<Vec<_>>();
|
||||
group_controller.fill_groups(rows.as_slice(), &grouping_field)?;
|
||||
Ok(group_controller)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub fn find_new_grouping_field(fields: &[Field], _layout: &DatabaseLayout) -> Option<Field> {
|
||||
let mut groupable_field_revs = fields
|
||||
pub fn find_suitable_grouping_field(fields: &[Field]) -> Option<Field> {
|
||||
let groupable_field = fields
|
||||
.iter()
|
||||
.flat_map(|field_rev| {
|
||||
let field_type = FieldType::from(field_rev.field_type);
|
||||
match field_type.can_be_group() {
|
||||
true => Some(field_rev.clone()),
|
||||
false => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Field>>();
|
||||
.find(|field| FieldType::from(field.field_type).can_be_group());
|
||||
|
||||
if groupable_field_revs.is_empty() {
|
||||
// If there is not groupable fields then we use the primary field.
|
||||
fields
|
||||
.iter()
|
||||
.find(|field_rev| field_rev.is_primary)
|
||||
.cloned()
|
||||
if let Some(field) = groupable_field {
|
||||
Some(field.clone())
|
||||
} else {
|
||||
Some(groupable_field_revs.remove(0))
|
||||
fields.iter().find(|field| field.is_primary).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +178,6 @@ pub fn default_group_setting(field: &Field) -> GroupSetting {
|
||||
pub fn make_no_status_group(field: &Field) -> Group {
|
||||
Group {
|
||||
id: field.id.clone(),
|
||||
name: format!("No {}", field.name),
|
||||
visible: true,
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ mod controller_impls;
|
||||
mod entities;
|
||||
mod group_builder;
|
||||
|
||||
pub(crate) use action::GroupController;
|
||||
pub(crate) use configuration::*;
|
||||
pub(crate) use controller::*;
|
||||
pub(crate) use controller_impls::*;
|
||||
|
@ -63,56 +63,50 @@ async fn group_by_date_test() {
|
||||
row_count: 0,
|
||||
},
|
||||
// Added via `make_test_board`
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: 1,
|
||||
group_id: "2022/03/01".to_string(),
|
||||
group_name: "Mar 2022".to_string(),
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 3,
|
||||
},
|
||||
// Added via `make_test_board`
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: 2,
|
||||
group_id: "2022/11/01".to_string(),
|
||||
group_name: "Nov 2022".to_string(),
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 2,
|
||||
row_count: 2,
|
||||
},
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: 3,
|
||||
group_id: last_30_days,
|
||||
group_name: "Last 30 days".to_string(),
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 3,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: 4,
|
||||
group_id: last_day,
|
||||
group_name: "Yesterday".to_string(),
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 4,
|
||||
row_count: 2,
|
||||
},
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: 5,
|
||||
group_id: today.format("%Y/%m/%d").to_string(),
|
||||
group_name: "Today".to_string(),
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 5,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: 6,
|
||||
group_id: next_7_days,
|
||||
group_name: "Next 7 days".to_string(),
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 6,
|
||||
@ -180,10 +174,9 @@ async fn change_date_on_moving_row_to_another_group() {
|
||||
group_index: 2,
|
||||
row_count: 3,
|
||||
},
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: 2,
|
||||
group_id: "2022/11/01".to_string(),
|
||||
group_name: "Nov 2022".to_string(),
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
@ -60,10 +60,9 @@ pub enum GroupScript {
|
||||
GroupByField {
|
||||
field_id: String,
|
||||
},
|
||||
AssertGroupIDName {
|
||||
AssertGroupId {
|
||||
group_index: usize,
|
||||
group_id: String,
|
||||
group_name: String,
|
||||
},
|
||||
CreateGroup {
|
||||
name: String,
|
||||
@ -241,7 +240,6 @@ impl DatabaseGroupTest {
|
||||
} => {
|
||||
let group = self.group_at_index(group_index).await;
|
||||
assert_eq!(group.group_id, group_pb.group_id);
|
||||
assert_eq!(group.group_name, group_pb.group_name);
|
||||
},
|
||||
GroupScript::UpdateSingleSelectSelectOption { inserted_options } => {
|
||||
self
|
||||
@ -259,14 +257,12 @@ impl DatabaseGroupTest {
|
||||
.await
|
||||
.unwrap();
|
||||
},
|
||||
GroupScript::AssertGroupIDName {
|
||||
GroupScript::AssertGroupId {
|
||||
group_index,
|
||||
group_id,
|
||||
group_name,
|
||||
} => {
|
||||
let group = self.group_at_index(group_index).await;
|
||||
assert_eq!(group_id, group.group_id, "group index: {}", group_index);
|
||||
assert_eq!(group_name, group.group_name, "group index: {}", group_index);
|
||||
},
|
||||
GroupScript::CreateGroup { name } => self
|
||||
.editor
|
||||
|
@ -457,13 +457,17 @@ async fn group_insert_single_select_option_test() {
|
||||
let scripts = vec![
|
||||
AssertGroupCount(4),
|
||||
UpdateSingleSelectSelectOption {
|
||||
inserted_options: vec![SelectOption::new(new_option_name)],
|
||||
inserted_options: vec![SelectOption {
|
||||
id: new_option_name.to_string(),
|
||||
name: new_option_name.to_string(),
|
||||
color: Default::default(),
|
||||
}],
|
||||
},
|
||||
AssertGroupCount(5),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
let new_group = test.group_at_index(4).await;
|
||||
assert_eq!(new_group.group_name, new_option_name);
|
||||
assert_eq!(new_group.group_id, new_option_name);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -499,6 +503,4 @@ async fn group_manual_create_new_group() {
|
||||
AssertGroupCount(5),
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
let new_group = test.group_at_index(4).await;
|
||||
assert_eq!(new_group.group_name, new_group_name);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user