mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: kanban filter mvp (#4935)
* chore: add filter controller in group controller * chore: enable url filter * chore: bump collab rev * chore: fix test * chore: bump collab rev
This commit is contained in:
parent
ef9891abfe
commit
37f521ae57
@ -50,7 +50,7 @@ APP_ENVIRONMENT = "local"
|
||||
FLUTTER_FLOWY_SDK_PATH = "appflowy_flutter/packages/appflowy_backend"
|
||||
TAURI_BACKEND_SERVICE_PATH = "appflowy_tauri/src/services/backend"
|
||||
WEB_BACKEND_SERVICE_PATH = "appflowy_web/src/services/backend"
|
||||
WEB_LIB_PATH= "appflowy_web/wasm-libs/af-wasm"
|
||||
WEB_LIB_PATH = "appflowy_web/wasm-libs/af-wasm"
|
||||
# Test default config
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
TEST_LIB_EXT = "dylib"
|
||||
@ -226,9 +226,8 @@ script = ['''
|
||||
echo FEATURES: ${FLUTTER_DESKTOP_FEATURES}
|
||||
echo PRODUCT_EXT: ${PRODUCT_EXT}
|
||||
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
|
||||
echo ${platforms}
|
||||
echo ${BUILD_ARCHS}
|
||||
echo ${BUILD_VERSION}
|
||||
echo BUILD_ARCHS: ${BUILD_ARCHS}
|
||||
echo BUILD_VERSION: ${BUILD_VERSION}
|
||||
''']
|
||||
script_runner = "@shell"
|
||||
|
||||
|
@ -47,7 +47,7 @@ class FieldInfo with _$FieldInfo {
|
||||
}
|
||||
|
||||
bool get canCreateFilter {
|
||||
if (hasFilter) {
|
||||
if (isGroupField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -58,6 +58,7 @@ class FieldInfo with _$FieldInfo {
|
||||
case FieldType.RichText:
|
||||
case FieldType.SingleSelect:
|
||||
case FieldType.Checklist:
|
||||
case FieldType.URL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -385,16 +385,23 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
groupList.insert(insertGroups.index, group);
|
||||
add(BoardEvent.didReceiveGroups(groupList));
|
||||
},
|
||||
onUpdateGroup: (updatedGroups) {
|
||||
onUpdateGroup: (updatedGroups) async {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// workaround: update group most of the time gets called before fields in
|
||||
// field controller are updated. For single and multi-select group
|
||||
// renames, this is required before generating the new group name.
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
for (final group in updatedGroups) {
|
||||
// see if the column is already in the board
|
||||
|
||||
final index = groupList.indexWhere((g) => g.groupId == group.groupId);
|
||||
if (index == -1) continue;
|
||||
if (index == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final columnController =
|
||||
boardController.getGroupController(group.groupId);
|
||||
if (columnController != null) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart';
|
||||
import 'package:flutter/material.dart' hide Card;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
@ -34,6 +36,8 @@ import 'toolbar/board_setting_bar.dart';
|
||||
import 'widgets/board_hidden_groups.dart';
|
||||
|
||||
class BoardPageTabBarBuilderImpl extends DatabaseTabBarItemBuilder {
|
||||
final _toggleExtension = ToggleExtensionNotifier();
|
||||
|
||||
@override
|
||||
Widget content(
|
||||
BuildContext context,
|
||||
@ -49,14 +53,27 @@ class BoardPageTabBarBuilderImpl extends DatabaseTabBarItemBuilder {
|
||||
BoardSettingBar(
|
||||
key: _makeValueKey(controller),
|
||||
databaseController: controller,
|
||||
toggleExtension: _toggleExtension,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget settingBarExtension(
|
||||
BuildContext context,
|
||||
DatabaseController controller,
|
||||
) =>
|
||||
const SizedBox.shrink();
|
||||
) {
|
||||
return DatabaseViewSettingExtension(
|
||||
key: _makeValueKey(controller),
|
||||
viewId: controller.viewId,
|
||||
databaseController: controller,
|
||||
toggleExtension: _toggleExtension,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_toggleExtension.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
ValueKey _makeValueKey(DatabaseController controller) =>
|
||||
ValueKey(controller.viewId);
|
||||
|
@ -1,24 +1,53 @@
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/filter/filter_menu_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardSettingBar extends StatelessWidget {
|
||||
const BoardSettingBar({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
required this.toggleExtension,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final ToggleExtensionNotifier toggleExtension;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SettingButton(databaseController: databaseController),
|
||||
],
|
||||
return BlocProvider<DatabaseFilterMenuBloc>(
|
||||
create: (context) => DatabaseFilterMenuBloc(
|
||||
viewId: databaseController.viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
)..add(const DatabaseFilterMenuEvent.initial()),
|
||||
child: BlocListener<DatabaseFilterMenuBloc, DatabaseFilterMenuState>(
|
||||
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
||||
listener: (context, state) => toggleExtension.toggle(),
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: databaseController.isLoading,
|
||||
builder: (context, value, child) {
|
||||
if (value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const FilterButton(),
|
||||
const HSpace(2),
|
||||
SettingButton(
|
||||
databaseController: databaseController,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,6 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_filter.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_filter.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_filter.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_filter.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_filter.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/util.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
@ -109,25 +100,21 @@ class FilterBackendService {
|
||||
int? timestamp,
|
||||
}) {
|
||||
assert(
|
||||
[
|
||||
FieldType.DateTime,
|
||||
FieldType.LastEditedTime,
|
||||
FieldType.CreatedTime,
|
||||
].contains(fieldType),
|
||||
fieldType == FieldType.DateTime ||
|
||||
fieldType == FieldType.LastEditedTime ||
|
||||
fieldType == FieldType.CreatedTime,
|
||||
);
|
||||
|
||||
final filter = DateFilterPB();
|
||||
|
||||
if (timestamp != null) {
|
||||
filter.timestamp = $fixnum.Int64(timestamp);
|
||||
} else {
|
||||
if (start != null && end != null) {
|
||||
filter.start = $fixnum.Int64(start);
|
||||
filter.end = $fixnum.Int64(end);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Start and end should not be null if the timestamp is null",
|
||||
);
|
||||
}
|
||||
}
|
||||
if (start != null) {
|
||||
filter.start = $fixnum.Int64(start);
|
||||
}
|
||||
if (end != null) {
|
||||
filter.end = $fixnum.Int64(end);
|
||||
}
|
||||
|
||||
return filterId == null
|
||||
|
@ -8,11 +8,11 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'filter_menu_bloc.freezed.dart';
|
||||
|
||||
class GridFilterMenuBloc
|
||||
extends Bloc<GridFilterMenuEvent, GridFilterMenuState> {
|
||||
GridFilterMenuBloc({required this.viewId, required this.fieldController})
|
||||
class DatabaseFilterMenuBloc
|
||||
extends Bloc<DatabaseFilterMenuEvent, DatabaseFilterMenuState> {
|
||||
DatabaseFilterMenuBloc({required this.viewId, required this.fieldController})
|
||||
: super(
|
||||
GridFilterMenuState.initial(
|
||||
DatabaseFilterMenuState.initial(
|
||||
viewId,
|
||||
fieldController.filterInfos,
|
||||
fieldController.fieldInfos,
|
||||
@ -27,7 +27,7 @@ class GridFilterMenuBloc
|
||||
void Function(List<FieldInfo>)? _onFieldFn;
|
||||
|
||||
void _dispatch() {
|
||||
on<GridFilterMenuEvent>(
|
||||
on<DatabaseFilterMenuEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
@ -55,11 +55,11 @@ class GridFilterMenuBloc
|
||||
|
||||
void _startListening() {
|
||||
_onFilterFn = (filters) {
|
||||
add(GridFilterMenuEvent.didReceiveFilters(filters));
|
||||
add(DatabaseFilterMenuEvent.didReceiveFilters(filters));
|
||||
};
|
||||
|
||||
_onFieldFn = (fields) {
|
||||
add(GridFilterMenuEvent.didReceiveFields(fields));
|
||||
add(DatabaseFilterMenuEvent.didReceiveFields(fields));
|
||||
};
|
||||
|
||||
fieldController.addListener(
|
||||
@ -87,32 +87,33 @@ class GridFilterMenuBloc
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridFilterMenuEvent with _$GridFilterMenuEvent {
|
||||
const factory GridFilterMenuEvent.initial() = _Initial;
|
||||
const factory GridFilterMenuEvent.didReceiveFilters(
|
||||
class DatabaseFilterMenuEvent with _$DatabaseFilterMenuEvent {
|
||||
const factory DatabaseFilterMenuEvent.initial() = _Initial;
|
||||
const factory DatabaseFilterMenuEvent.didReceiveFilters(
|
||||
List<FilterInfo> filters,
|
||||
) = _DidReceiveFilters;
|
||||
const factory GridFilterMenuEvent.didReceiveFields(List<FieldInfo> fields) =
|
||||
_DidReceiveFields;
|
||||
const factory GridFilterMenuEvent.toggleMenu() = _SetMenuVisibility;
|
||||
const factory DatabaseFilterMenuEvent.didReceiveFields(
|
||||
List<FieldInfo> fields,
|
||||
) = _DidReceiveFields;
|
||||
const factory DatabaseFilterMenuEvent.toggleMenu() = _SetMenuVisibility;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridFilterMenuState with _$GridFilterMenuState {
|
||||
const factory GridFilterMenuState({
|
||||
class DatabaseFilterMenuState with _$DatabaseFilterMenuState {
|
||||
const factory DatabaseFilterMenuState({
|
||||
required String viewId,
|
||||
required List<FilterInfo> filters,
|
||||
required List<FieldInfo> fields,
|
||||
required List<FieldInfo> creatableFields,
|
||||
required bool isVisible,
|
||||
}) = _GridFilterMenuState;
|
||||
}) = _DatabaseFilterMenuState;
|
||||
|
||||
factory GridFilterMenuState.initial(
|
||||
factory DatabaseFilterMenuState.initial(
|
||||
String viewId,
|
||||
List<FilterInfo> filterInfos,
|
||||
List<FieldInfo> fields,
|
||||
) =>
|
||||
GridFilterMenuState(
|
||||
DatabaseFilterMenuState(
|
||||
viewId: viewId,
|
||||
filters: filterInfos,
|
||||
fields: fields,
|
||||
|
@ -3,8 +3,7 @@ import 'dart:async';
|
||||
import 'package:appflowy/plugins/database/domain/filter_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/filter_service.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/util.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@ -12,7 +11,7 @@ part 'text_filter_editor_bloc.freezed.dart';
|
||||
|
||||
class TextFilterEditorBloc
|
||||
extends Bloc<TextFilterEditorEvent, TextFilterEditorState> {
|
||||
TextFilterEditorBloc({required this.filterInfo})
|
||||
TextFilterEditorBloc({required this.filterInfo, required this.fieldType})
|
||||
: _filterBackendSvc = FilterBackendService(viewId: filterInfo.viewId),
|
||||
_listener = FilterListener(
|
||||
viewId: filterInfo.viewId,
|
||||
@ -23,6 +22,7 @@ class TextFilterEditorBloc
|
||||
}
|
||||
|
||||
final FilterInfo filterInfo;
|
||||
final FieldType fieldType;
|
||||
final FilterBackendService _filterBackendSvc;
|
||||
final FilterListener _listener;
|
||||
|
||||
@ -34,20 +34,34 @@ class TextFilterEditorBloc
|
||||
_startListening();
|
||||
},
|
||||
updateCondition: (TextFilterConditionPB condition) {
|
||||
_filterBackendSvc.insertTextFilter(
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldId: filterInfo.fieldInfo.id,
|
||||
condition: condition,
|
||||
content: state.filter.content,
|
||||
);
|
||||
fieldType == FieldType.RichText
|
||||
? _filterBackendSvc.insertTextFilter(
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldId: filterInfo.fieldInfo.id,
|
||||
condition: condition,
|
||||
content: state.filter.content,
|
||||
)
|
||||
: _filterBackendSvc.insertURLFilter(
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldId: filterInfo.fieldInfo.id,
|
||||
condition: condition,
|
||||
content: state.filter.content,
|
||||
);
|
||||
},
|
||||
updateContent: (content) {
|
||||
_filterBackendSvc.insertTextFilter(
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldId: filterInfo.fieldInfo.id,
|
||||
condition: state.filter.condition,
|
||||
content: content,
|
||||
);
|
||||
updateContent: (String content) {
|
||||
fieldType == FieldType.RichText
|
||||
? _filterBackendSvc.insertTextFilter(
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldId: filterInfo.fieldInfo.id,
|
||||
condition: state.filter.condition,
|
||||
content: content,
|
||||
)
|
||||
: _filterBackendSvc.insertURLFilter(
|
||||
filterId: filterInfo.filter.id,
|
||||
fieldId: filterInfo.fieldInfo.id,
|
||||
condition: state.filter.condition,
|
||||
content: content,
|
||||
);
|
||||
},
|
||||
delete: () {
|
||||
_filterBackendSvc.deleteFilter(
|
||||
|
@ -1,59 +1,44 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/filter/text_filter_editor_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../../application/filter/text_filter_editor_bloc.dart';
|
||||
|
||||
import '../condition_button.dart';
|
||||
import '../disclosure_button.dart';
|
||||
import '../filter_info.dart';
|
||||
import 'choicechip.dart';
|
||||
|
||||
class TextFilterChoicechip extends StatefulWidget {
|
||||
class TextFilterChoicechip extends StatelessWidget {
|
||||
const TextFilterChoicechip({required this.filterInfo, super.key});
|
||||
|
||||
final FilterInfo filterInfo;
|
||||
|
||||
@override
|
||||
State<TextFilterChoicechip> createState() => _TextFilterChoicechipState();
|
||||
}
|
||||
|
||||
class _TextFilterChoicechipState extends State<TextFilterChoicechip> {
|
||||
late TextFilterEditorBloc bloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
bloc = TextFilterEditorBloc(filterInfo: widget.filterInfo)
|
||||
..add(const TextFilterEditorEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
bloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
return BlocProvider(
|
||||
create: (_) => TextFilterEditorBloc(
|
||||
filterInfo: filterInfo,
|
||||
fieldType: FieldType.RichText,
|
||||
)..add(const TextFilterEditorEvent.initial()),
|
||||
child: BlocBuilder<TextFilterEditorBloc, TextFilterEditorState>(
|
||||
builder: (blocContext, state) {
|
||||
builder: (context, state) {
|
||||
return AppFlowyPopover(
|
||||
controller: PopoverController(),
|
||||
constraints: BoxConstraints.loose(const Size(200, 76)),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
popupBuilder: (BuildContext context) {
|
||||
return TextFilterEditor(bloc: bloc);
|
||||
popupBuilder: (popoverContext) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<TextFilterEditorBloc>(),
|
||||
child: const TextFilterEditor(),
|
||||
);
|
||||
},
|
||||
child: ChoiceChipButton(
|
||||
filterInfo: widget.filterInfo,
|
||||
filterInfo: filterInfo,
|
||||
filterDesc: _makeFilterDesc(state),
|
||||
),
|
||||
);
|
||||
@ -78,9 +63,7 @@ class _TextFilterChoicechipState extends State<TextFilterChoicechip> {
|
||||
}
|
||||
|
||||
class TextFilterEditor extends StatefulWidget {
|
||||
const TextFilterEditor({required this.bloc, super.key});
|
||||
|
||||
final TextFilterEditorBloc bloc;
|
||||
const TextFilterEditor({super.key});
|
||||
|
||||
@override
|
||||
State<TextFilterEditor> createState() => _TextFilterEditorState();
|
||||
@ -91,26 +74,23 @@ class _TextFilterEditorState extends State<TextFilterEditor> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: widget.bloc,
|
||||
child: BlocBuilder<TextFilterEditorBloc, TextFilterEditorState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
_buildFilterPanel(context, state),
|
||||
];
|
||||
return BlocBuilder<TextFilterEditorBloc, TextFilterEditorState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
_buildFilterPanel(context, state),
|
||||
];
|
||||
|
||||
if (state.filter.condition != TextFilterConditionPB.TextIsEmpty &&
|
||||
state.filter.condition != TextFilterConditionPB.TextIsNotEmpty) {
|
||||
children.add(const VSpace(4));
|
||||
children.add(_buildFilterTextField(context, state));
|
||||
}
|
||||
if (state.filter.condition != TextFilterConditionPB.TextIsEmpty &&
|
||||
state.filter.condition != TextFilterConditionPB.TextIsNotEmpty) {
|
||||
children.add(const VSpace(4));
|
||||
children.add(_buildFilterTextField(context, state));
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
child: IntrinsicHeight(child: Column(children: children)),
|
||||
);
|
||||
},
|
||||
),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
child: IntrinsicHeight(child: Column(children: children)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,58 @@
|
||||
import 'package:appflowy/plugins/database/grid/application/filter/text_filter_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../filter_info.dart';
|
||||
import 'choicechip.dart';
|
||||
|
||||
class URLFilterChoicechip extends StatelessWidget {
|
||||
const URLFilterChoicechip({required this.filterInfo, super.key});
|
||||
class URLFilterChoiceChip extends StatelessWidget {
|
||||
const URLFilterChoiceChip({required this.filterInfo, super.key});
|
||||
|
||||
final FilterInfo filterInfo;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChoiceChipButton(filterInfo: filterInfo);
|
||||
return BlocProvider(
|
||||
create: (_) => TextFilterEditorBloc(
|
||||
filterInfo: filterInfo,
|
||||
fieldType: FieldType.URL,
|
||||
),
|
||||
child: BlocBuilder<TextFilterEditorBloc, TextFilterEditorState>(
|
||||
builder: (context, state) {
|
||||
return AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(200, 76)),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
popupBuilder: (popoverContext) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<TextFilterEditorBloc>(),
|
||||
child: const TextFilterEditor(),
|
||||
);
|
||||
},
|
||||
child: ChoiceChipButton(
|
||||
filterInfo: filterInfo,
|
||||
filterDesc: _makeFilterDesc(state),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _makeFilterDesc(TextFilterEditorState state) {
|
||||
String filterDesc = state.filter.condition.choicechipPrefix;
|
||||
if (state.filter.condition == TextFilterConditionPB.TextIsEmpty ||
|
||||
state.filter.condition == TextFilterConditionPB.TextIsNotEmpty) {
|
||||
return filterDesc;
|
||||
}
|
||||
|
||||
if (state.filter.content.isNotEmpty) {
|
||||
filterDesc += " ${state.filter.content}";
|
||||
}
|
||||
|
||||
return filterDesc;
|
||||
}
|
||||
}
|
||||
|
@ -21,13 +21,17 @@ class FilterInfo {
|
||||
String get fieldId => filter.data.fieldId;
|
||||
|
||||
DateFilterPB? dateFilter() {
|
||||
return filter.data.fieldType == FieldType.DateTime
|
||||
final fieldType = filter.data.fieldType;
|
||||
return fieldType == FieldType.DateTime ||
|
||||
fieldType == FieldType.CreatedTime ||
|
||||
fieldType == FieldType.LastEditedTime
|
||||
? DateFilterPB.fromBuffer(filter.data.data)
|
||||
: null;
|
||||
}
|
||||
|
||||
TextFilterPB? textFilter() {
|
||||
return filter.data.fieldType == FieldType.RichText
|
||||
return filter.data.fieldType == FieldType.RichText ||
|
||||
filter.data.fieldType == FieldType.URL
|
||||
? TextFilterPB.fromBuffer(filter.data.data)
|
||||
: null;
|
||||
}
|
||||
|
@ -23,14 +23,14 @@ class FilterMenu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<GridFilterMenuBloc>(
|
||||
create: (context) => GridFilterMenuBloc(
|
||||
return BlocProvider<DatabaseFilterMenuBloc>(
|
||||
create: (context) => DatabaseFilterMenuBloc(
|
||||
viewId: fieldController.viewId,
|
||||
fieldController: fieldController,
|
||||
)..add(
|
||||
const GridFilterMenuEvent.initial(),
|
||||
const DatabaseFilterMenuEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<GridFilterMenuBloc, GridFilterMenuState>(
|
||||
child: BlocBuilder<DatabaseFilterMenuBloc, DatabaseFilterMenuState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [];
|
||||
children.addAll(
|
||||
@ -115,7 +115,7 @@ class _AddFilterButtonState extends State<AddFilterButton> {
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
child: child,
|
||||
popupBuilder: (BuildContext context) {
|
||||
final bloc = buildContext.read<GridFilterMenuBloc>();
|
||||
final bloc = buildContext.read<DatabaseFilterMenuBloc>();
|
||||
return GridCreateFilterList(
|
||||
viewId: widget.viewId,
|
||||
fieldController: bloc.fieldController,
|
||||
|
@ -26,7 +26,7 @@ class FilterMenuItem extends StatelessWidget {
|
||||
FieldType.RichText => TextFilterChoicechip(filterInfo: filterInfo),
|
||||
FieldType.SingleSelect =>
|
||||
SelectOptionFilterChoicechip(filterInfo: filterInfo),
|
||||
FieldType.URL => URLFilterChoicechip(filterInfo: filterInfo),
|
||||
FieldType.URL => URLFilterChoiceChip(filterInfo: filterInfo),
|
||||
FieldType.Checklist => ChecklistFilterChoicechip(filterInfo: filterInfo),
|
||||
_ => const SizedBox(),
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ class _FilterButtonState extends State<FilterButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GridFilterMenuBloc, GridFilterMenuState>(
|
||||
return BlocBuilder<DatabaseFilterMenuBloc, DatabaseFilterMenuState>(
|
||||
builder: (context, state) {
|
||||
final textColor = state.filters.isEmpty
|
||||
? AFThemeExtension.of(context).textColor
|
||||
@ -41,11 +41,11 @@ class _FilterButtonState extends State<FilterButton> {
|
||||
padding: GridSize.toolbarSettingButtonInsets,
|
||||
radius: Corners.s4Border,
|
||||
onPressed: () {
|
||||
final bloc = context.read<GridFilterMenuBloc>();
|
||||
final bloc = context.read<DatabaseFilterMenuBloc>();
|
||||
if (bloc.state.filters.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
bloc.add(const GridFilterMenuEvent.toggleMenu());
|
||||
bloc.add(const DatabaseFilterMenuEvent.toggleMenu());
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -63,14 +63,14 @@ class _FilterButtonState extends State<FilterButton> {
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
child: child,
|
||||
popupBuilder: (BuildContext context) {
|
||||
final bloc = buildContext.read<GridFilterMenuBloc>();
|
||||
final bloc = buildContext.read<DatabaseFilterMenuBloc>();
|
||||
return GridCreateFilterList(
|
||||
viewId: bloc.viewId,
|
||||
fieldController: bloc.fieldController,
|
||||
onClosed: () => _popoverController.close(),
|
||||
onCreateFilter: () {
|
||||
if (!bloc.state.isVisible) {
|
||||
bloc.add(const GridFilterMenuEvent.toggleMenu());
|
||||
bloc.add(const DatabaseFilterMenuEvent.toggleMenu());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -24,11 +24,11 @@ class GridSettingBar extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<GridFilterMenuBloc>(
|
||||
create: (context) => GridFilterMenuBloc(
|
||||
BlocProvider<DatabaseFilterMenuBloc>(
|
||||
create: (context) => DatabaseFilterMenuBloc(
|
||||
viewId: controller.viewId,
|
||||
fieldController: controller.fieldController,
|
||||
)..add(const GridFilterMenuEvent.initial()),
|
||||
)..add(const DatabaseFilterMenuEvent.initial()),
|
||||
),
|
||||
BlocProvider<SortEditorBloc>(
|
||||
create: (context) => SortEditorBloc(
|
||||
@ -37,7 +37,7 @@ class GridSettingBar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
child: BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
|
||||
child: BlocListener<DatabaseFilterMenuBloc, DatabaseFilterMenuState>(
|
||||
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
||||
listener: (context, state) => toggleExtension.toggle(),
|
||||
child: ValueListenableBuilder<bool>(
|
||||
|
@ -27,11 +27,11 @@ class MobileDatabaseControls extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<GridFilterMenuBloc>(
|
||||
create: (context) => GridFilterMenuBloc(
|
||||
BlocProvider<DatabaseFilterMenuBloc>(
|
||||
create: (context) => DatabaseFilterMenuBloc(
|
||||
viewId: controller.viewId,
|
||||
fieldController: controller.fieldController,
|
||||
)..add(const GridFilterMenuEvent.initial()),
|
||||
)..add(const DatabaseFilterMenuEvent.initial()),
|
||||
),
|
||||
BlocProvider<SortEditorBloc>(
|
||||
create: (context) => SortEditorBloc(
|
||||
@ -40,7 +40,7 @@ class MobileDatabaseControls extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
child: BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
|
||||
child: BlocListener<DatabaseFilterMenuBloc, DatabaseFilterMenuState>(
|
||||
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
||||
listener: (context, state) => toggleExtension.toggle(),
|
||||
child: ValueListenableBuilder<bool>(
|
||||
|
@ -13,10 +13,10 @@ void main() {
|
||||
|
||||
test('test filter menu after create a text filter)', () async {
|
||||
final context = await gridTest.createTestGrid();
|
||||
final menuBloc = GridFilterMenuBloc(
|
||||
final menuBloc = DatabaseFilterMenuBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const GridFilterMenuEvent.initial());
|
||||
)..add(const DatabaseFilterMenuEvent.initial());
|
||||
await gridResponseFuture();
|
||||
assert(menuBloc.state.creatableFields.length == 3);
|
||||
|
||||
@ -28,15 +28,15 @@ void main() {
|
||||
content: "",
|
||||
);
|
||||
await gridResponseFuture();
|
||||
assert(menuBloc.state.creatableFields.length == 2);
|
||||
assert(menuBloc.state.creatableFields.length == 3);
|
||||
});
|
||||
|
||||
test('test filter menu after update existing text filter)', () async {
|
||||
final context = await gridTest.createTestGrid();
|
||||
final menuBloc = GridFilterMenuBloc(
|
||||
final menuBloc = DatabaseFilterMenuBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const GridFilterMenuEvent.initial());
|
||||
)..add(const DatabaseFilterMenuEvent.initial());
|
||||
await gridResponseFuture();
|
||||
|
||||
final service = FilterBackendService(viewId: context.gridView.id);
|
||||
|
14
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
14
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -838,7 +838,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -862,7 +862,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -892,7 +892,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -911,7 +911,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -926,7 +926,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -963,7 +963,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1002,7 +1002,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
|
@ -96,10 +96,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
|
@ -65,10 +65,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
|
32
frontend/rust-lib/Cargo.lock
generated
32
frontend/rust-lib/Cargo.lock
generated
@ -764,7 +764,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -788,7 +788,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -818,7 +818,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -837,7 +837,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -852,7 +852,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -889,7 +889,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -928,7 +928,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1125,7 +1125,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@ -3663,7 +3663,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_macros 0.8.0",
|
||||
"phf_shared 0.8.0",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
@ -3683,6 +3683,7 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_macros 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
@ -3750,6 +3751,19 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.47",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
|
@ -120,10 +120,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
||||
|
@ -23,19 +23,19 @@ pub struct DateFilterPB {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default, Clone, Debug)]
|
||||
pub struct DateFilterContentPB {
|
||||
pub struct DateFilterContent {
|
||||
pub start: Option<i64>,
|
||||
pub end: Option<i64>,
|
||||
pub timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
impl ToString for DateFilterContentPB {
|
||||
impl ToString for DateFilterContent {
|
||||
fn to_string(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DateFilterContentPB {
|
||||
impl FromStr for DateFilterContent {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
@ -89,7 +89,7 @@ impl ParseFilterData for DateFilterPB {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Ok(content) = DateFilterContentPB::from_str(&content) {
|
||||
if let Ok(content) = DateFilterContent::from_str(&content) {
|
||||
date_filter.start = content.start;
|
||||
date_filter.end = content.end;
|
||||
date_filter.timestamp = content.timestamp;
|
||||
|
@ -6,6 +6,7 @@ use collab_database::views::RowOrder;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use lib_infra::validator_fn::required_not_empty_str;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
@ -49,7 +50,7 @@ impl From<RowOrder> for RowPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
#[derive(Debug, Default, Clone, ProtoBuf, Serialize, Deserialize)]
|
||||
pub struct RowMetaPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
@ -1078,7 +1078,7 @@ impl DatabaseEditor {
|
||||
|
||||
pub async fn group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> {
|
||||
let view = self.database_views.get_view_editor(view_id).await?;
|
||||
view.v_grouping_by_field(field_id).await?;
|
||||
view.v_group_by_field(field_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
#![allow(clippy::while_let_loop)]
|
||||
use crate::entities::{
|
||||
CalculationChangesetNotificationPB, DatabaseViewSettingPB, FilterChangesetNotificationPB,
|
||||
GroupChangesPB, GroupRowsNotificationPB, ReorderAllRowsPB, ReorderSingleRowPB,
|
||||
RowsVisibilityChangePB, SortChangesetNotificationPB,
|
||||
GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, ReorderAllRowsPB, ReorderSingleRowPB,
|
||||
RowMetaPB, RowsChangePB, RowsVisibilityChangePB, SortChangesetNotificationPB,
|
||||
};
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::filter::FilterResultNotification;
|
||||
use crate::services::sort::{InsertSortedRowResult, ReorderAllRowsResult, ReorderSingleRowResult};
|
||||
use crate::services::sort::{InsertRowResult, ReorderAllRowsResult, ReorderSingleRowResult};
|
||||
use async_stream::stream;
|
||||
use futures::stream::StreamExt;
|
||||
use tokio::sync::broadcast;
|
||||
@ -16,7 +16,7 @@ pub enum DatabaseViewChanged {
|
||||
FilterNotification(FilterResultNotification),
|
||||
ReorderAllRowsNotification(ReorderAllRowsResult),
|
||||
ReorderSingleRowNotification(ReorderSingleRowResult),
|
||||
InsertSortedRowNotification(InsertSortedRowResult),
|
||||
InsertRowNotification(InsertRowResult),
|
||||
CalculationValueNotification(CalculationChangesetNotificationPB),
|
||||
}
|
||||
|
||||
@ -79,7 +79,17 @@ impl DatabaseViewChangedReceiverRunner {
|
||||
.payload(reorder_row)
|
||||
.send()
|
||||
},
|
||||
DatabaseViewChanged::InsertSortedRowNotification(_result) => {},
|
||||
DatabaseViewChanged::InsertRowNotification(result) => {
|
||||
let inserted_row = InsertedRowPB {
|
||||
row_meta: RowMetaPB::from(result.row),
|
||||
index: Some(result.index as i32),
|
||||
is_new: true,
|
||||
};
|
||||
let changes = RowsChangePB::from_insert(inserted_row);
|
||||
send_notification(&result.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||
.payload(changes)
|
||||
.send();
|
||||
},
|
||||
DatabaseViewChanged::CalculationValueNotification(notification) => send_notification(
|
||||
¬ification.view_id,
|
||||
DatabaseNotification::DidUpdateCalculation,
|
||||
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::database::{gen_database_calculation_id, gen_database_sort_id, gen_row_id};
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||
use collab_database::views::{DatabaseLayout, DatabaseView};
|
||||
use lib_infra::util::timestamp;
|
||||
@ -16,9 +16,9 @@ use lib_dispatch::prelude::af_spawn;
|
||||
use crate::entities::{
|
||||
CalendarEventPB, CreateRowParams, CreateRowPayloadPB, DatabaseLayoutMetaPB,
|
||||
DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldType, FieldVisibility, GroupChangesPB,
|
||||
GroupPB, InsertedRowPB, LayoutSettingChangeset, LayoutSettingParams,
|
||||
RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB,
|
||||
SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB,
|
||||
GroupPB, LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB,
|
||||
ReorderSortPayloadPB, RowMetaPB, RowsChangePB, SortChangesetNotificationPB, SortPB,
|
||||
UpdateCalculationChangesetPB, UpdateSortPayloadPB,
|
||||
};
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController};
|
||||
@ -26,7 +26,7 @@ use crate::services::cell::{CellBuilder, CellCache};
|
||||
use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow};
|
||||
use crate::services::database_view::view_filter::make_filter_controller;
|
||||
use crate::services::database_view::view_group::{
|
||||
get_cell_for_row, get_cells_for_field, new_group_controller, new_group_controller_with_field,
|
||||
get_cell_for_row, get_cells_for_field, new_group_controller,
|
||||
};
|
||||
use crate::services::database_view::view_operation::DatabaseViewOperation;
|
||||
use crate::services::database_view::view_sort::make_sort_controller;
|
||||
@ -68,10 +68,6 @@ impl DatabaseViewEditor {
|
||||
) -> FlowyResult<Self> {
|
||||
let (notifier, _) = broadcast::channel(100);
|
||||
af_spawn(DatabaseViewChangedReceiverRunner(Some(notifier.subscribe())).run());
|
||||
// Group
|
||||
let group_controller = Arc::new(RwLock::new(
|
||||
new_group_controller(view_id.clone(), delegate.clone()).await?,
|
||||
));
|
||||
|
||||
// Filter
|
||||
let filter_controller = make_filter_controller(
|
||||
@ -92,6 +88,17 @@ impl DatabaseViewEditor {
|
||||
)
|
||||
.await;
|
||||
|
||||
// Group
|
||||
let group_controller = Arc::new(RwLock::new(
|
||||
new_group_controller(
|
||||
view_id.clone(),
|
||||
delegate.clone(),
|
||||
filter_controller.clone(),
|
||||
None,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
|
||||
// Calculations
|
||||
let calculations_controller =
|
||||
make_calculations_controller(&view_id, delegate.clone(), notifier.clone()).await;
|
||||
@ -142,7 +149,7 @@ impl DatabaseViewEditor {
|
||||
if let Some(controller) = self.group_controller.read().await.as_ref() {
|
||||
let field = self
|
||||
.delegate
|
||||
.get_field(controller.field_id())
|
||||
.get_field(controller.get_grouping_field_id())
|
||||
.ok_or_else(|| FlowyError::internal().with_context("Failed to get grouping field"))?;
|
||||
controller.will_create_row(&mut cells, &field, &group_id);
|
||||
}
|
||||
@ -168,24 +175,20 @@ impl DatabaseViewEditor {
|
||||
pub async fn v_did_create_row(&self, row_detail: &RowDetail, index: usize) {
|
||||
// Send the group notification if the current view has groups
|
||||
if let Some(controller) = self.group_controller.write().await.as_mut() {
|
||||
let changesets = controller.did_create_row(row_detail, index);
|
||||
let mut row_details = vec![Arc::new(row_detail.clone())];
|
||||
self.v_filter_rows(&mut row_details).await;
|
||||
|
||||
for changeset in changesets {
|
||||
notify_did_update_group_rows(changeset).await;
|
||||
if let Some(row_detail) = row_details.pop() {
|
||||
let changesets = controller.did_create_row(&row_detail, index);
|
||||
|
||||
for changeset in changesets {
|
||||
notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inserted_row = InsertedRowPB {
|
||||
row_meta: RowMetaPB::from(row_detail),
|
||||
index: Some(index as i32),
|
||||
is_new: true,
|
||||
};
|
||||
let changes = RowsChangePB::from_insert(inserted_row);
|
||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
|
||||
.payload(changes)
|
||||
.send();
|
||||
self
|
||||
.gen_did_create_row_view_tasks(row_detail.row.clone())
|
||||
.gen_did_create_row_view_tasks(index, row_detail.clone())
|
||||
.await;
|
||||
}
|
||||
|
||||
@ -239,34 +242,41 @@ impl DatabaseViewEditor {
|
||||
row_detail: &RowDetail,
|
||||
field_id: String,
|
||||
) {
|
||||
let result = self
|
||||
.mut_group_controller(|group_controller, field| {
|
||||
Ok(group_controller.did_update_group_row(old_row, row_detail, &field))
|
||||
})
|
||||
.await;
|
||||
if let Some(controller) = self.group_controller.write().await.as_mut() {
|
||||
let field = self.delegate.get_field(controller.get_grouping_field_id());
|
||||
|
||||
if let Some(Ok(result)) = result {
|
||||
let mut group_changes = GroupChangesPB {
|
||||
view_id: self.view_id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(inserted_group) = result.inserted_group {
|
||||
tracing::trace!("Create group after editing the row: {:?}", inserted_group);
|
||||
group_changes.inserted_groups.push(inserted_group);
|
||||
}
|
||||
if let Some(delete_group) = result.deleted_group {
|
||||
tracing::trace!("Delete group after editing the row: {:?}", delete_group);
|
||||
group_changes.deleted_groups.push(delete_group.group_id);
|
||||
}
|
||||
if let Some(field) = field {
|
||||
let mut row_details = vec![Arc::new(row_detail.clone())];
|
||||
self.v_filter_rows(&mut row_details).await;
|
||||
|
||||
if !group_changes.is_empty() {
|
||||
notify_did_update_num_of_groups(&self.view_id, group_changes).await;
|
||||
}
|
||||
if let Some(row_detail) = row_details.pop() {
|
||||
let result = controller.did_update_group_row(old_row, &row_detail, &field);
|
||||
|
||||
for changeset in result.row_changesets {
|
||||
if !changeset.is_empty() {
|
||||
tracing::trace!("Group change after editing the row: {:?}", changeset);
|
||||
notify_did_update_group_rows(changeset).await;
|
||||
if let Ok(result) = result {
|
||||
let mut group_changes = GroupChangesPB {
|
||||
view_id: self.view_id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(inserted_group) = result.inserted_group {
|
||||
tracing::trace!("Create group after editing the row: {:?}", inserted_group);
|
||||
group_changes.inserted_groups.push(inserted_group);
|
||||
}
|
||||
if let Some(delete_group) = result.deleted_group {
|
||||
tracing::trace!("Delete group after editing the row: {:?}", delete_group);
|
||||
group_changes.deleted_groups.push(delete_group.group_id);
|
||||
}
|
||||
|
||||
if !group_changes.is_empty() {
|
||||
notify_did_update_num_of_groups(&self.view_id, group_changes).await;
|
||||
}
|
||||
|
||||
for changeset in result.row_changesets {
|
||||
if !changeset.is_empty() {
|
||||
tracing::trace!("Group change after editing the row: {:?}", changeset);
|
||||
notify_did_update_group_rows(changeset).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -376,7 +386,7 @@ impl DatabaseViewEditor {
|
||||
|
||||
pub async fn is_grouping_field(&self, field_id: &str) -> bool {
|
||||
match self.group_controller.read().await.as_ref() {
|
||||
Some(group_controller) => group_controller.field_id() == field_id,
|
||||
Some(group_controller) => group_controller.get_grouping_field_id() == field_id,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
@ -385,7 +395,7 @@ impl DatabaseViewEditor {
|
||||
pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> {
|
||||
let is_grouping_field = self.is_grouping_field(field_id).await;
|
||||
if !is_grouping_field {
|
||||
self.v_grouping_by_field(field_id).await?;
|
||||
self.v_group_by_field(field_id).await?;
|
||||
|
||||
if let Some(view) = self.delegate.get_view(&self.view_id).await {
|
||||
let setting = database_view_setting_pb_from_view(view);
|
||||
@ -399,7 +409,7 @@ impl DatabaseViewEditor {
|
||||
let mut old_field: Option<Field> = None;
|
||||
let result = if let Some(controller) = self.group_controller.write().await.as_mut() {
|
||||
let create_group_results = controller.create_group(name.to_string())?;
|
||||
old_field = self.delegate.get_field(controller.field_id());
|
||||
old_field = self.delegate.get_field(controller.get_grouping_field_id());
|
||||
create_group_results
|
||||
} else {
|
||||
(None, None)
|
||||
@ -432,7 +442,7 @@ impl DatabaseViewEditor {
|
||||
None => return Ok(RowsChangePB::default()),
|
||||
};
|
||||
|
||||
let old_field = self.delegate.get_field(controller.field_id());
|
||||
let old_field = self.delegate.get_field(controller.get_grouping_field_id());
|
||||
let (row_ids, type_option_data) = controller.delete_group(group_id)?;
|
||||
|
||||
drop(group_controller);
|
||||
@ -462,12 +472,15 @@ impl DatabaseViewEditor {
|
||||
}
|
||||
|
||||
pub async fn v_update_group(&self, changeset: Vec<GroupChangeset>) -> FlowyResult<()> {
|
||||
let mut type_option_data = TypeOptionData::new();
|
||||
let mut type_option_data = None;
|
||||
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 old_field = self.delegate.get_field(controller.get_grouping_field_id());
|
||||
let (updated_groups, new_type_option) = controller.apply_group_changeset(&changeset)?;
|
||||
type_option_data.extend(new_type_option);
|
||||
|
||||
if new_type_option.is_some() {
|
||||
type_option_data = new_type_option;
|
||||
}
|
||||
|
||||
(old_field, updated_groups)
|
||||
} else {
|
||||
@ -475,7 +488,7 @@ impl DatabaseViewEditor {
|
||||
};
|
||||
|
||||
if let Some(old_field) = old_field {
|
||||
if !type_option_data.is_empty() {
|
||||
if let Some(type_option_data) = type_option_data {
|
||||
self
|
||||
.delegate
|
||||
.update_field(type_option_data, old_field)
|
||||
@ -644,15 +657,20 @@ impl DatabaseViewEditor {
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn v_modify_filters(&self, changeset: FilterChangeset) -> FlowyResult<()> {
|
||||
let filter_controller = self.filter_controller.clone();
|
||||
|
||||
// self.delegate.insert_filter(&self.view_id, filter.clone());
|
||||
|
||||
let notification = filter_controller.apply_changeset(changeset).await;
|
||||
|
||||
drop(filter_controller);
|
||||
let notification = self.filter_controller.apply_changeset(changeset).await;
|
||||
|
||||
notify_did_update_filter(notification).await;
|
||||
|
||||
let group_controller_read_guard = self.group_controller.read().await;
|
||||
let grouping_field_id = group_controller_read_guard
|
||||
.as_ref()
|
||||
.map(|controller| controller.get_grouping_field_id().to_string());
|
||||
drop(group_controller_read_guard);
|
||||
|
||||
if let Some(field_id) = grouping_field_id {
|
||||
self.v_group_by_field(&field_id).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -786,11 +804,6 @@ impl DatabaseViewEditor {
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub async fn v_did_update_field_type_option(&self, old_field: &Field) -> FlowyResult<()> {
|
||||
let field_id = &old_field.id;
|
||||
// If the id of the grouping field is equal to the updated field's id, then we need to
|
||||
// update the group setting
|
||||
if self.is_grouping_field(field_id).await {
|
||||
self.v_grouping_by_field(field_id).await?;
|
||||
}
|
||||
|
||||
if let Some(field) = self.delegate.get_field(field_id) {
|
||||
self
|
||||
@ -808,36 +821,58 @@ impl DatabaseViewEditor {
|
||||
notify_did_update_filter(notification).await;
|
||||
}
|
||||
}
|
||||
|
||||
// If the id of the grouping field is equal to the updated field's id, then we need to
|
||||
// update the group setting
|
||||
if self.is_grouping_field(field_id).await {
|
||||
self.v_group_by_field(field_id).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called when a grouping field is updated.
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn v_grouping_by_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
pub async fn v_group_by_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
if let Some(field) = self.delegate.get_field(field_id) {
|
||||
let new_group_controller =
|
||||
new_group_controller_with_field(self.view_id.clone(), self.delegate.clone(), field).await?;
|
||||
tracing::trace!("create new group controller");
|
||||
|
||||
let new_groups = new_group_controller
|
||||
.get_all_groups()
|
||||
.into_iter()
|
||||
.map(|group| GroupPB::from(group.clone()))
|
||||
.collect();
|
||||
let new_group_controller = new_group_controller(
|
||||
self.view_id.clone(),
|
||||
self.delegate.clone(),
|
||||
self.filter_controller.clone(),
|
||||
Some(field),
|
||||
)
|
||||
.await?;
|
||||
|
||||
*self.group_controller.write().await = Some(new_group_controller);
|
||||
let changeset = GroupChangesPB {
|
||||
view_id: self.view_id.clone(),
|
||||
initial_groups: new_groups,
|
||||
..Default::default()
|
||||
};
|
||||
if let Some(controller) = &new_group_controller {
|
||||
let new_groups = controller
|
||||
.get_all_groups()
|
||||
.into_iter()
|
||||
.map(|group| GroupPB::from(group.clone()))
|
||||
.collect();
|
||||
|
||||
debug_assert!(!changeset.is_empty());
|
||||
if !changeset.is_empty() {
|
||||
send_notification(&changeset.view_id, DatabaseNotification::DidGroupByField)
|
||||
.payload(changeset)
|
||||
.send();
|
||||
let changeset = GroupChangesPB {
|
||||
view_id: self.view_id.clone(),
|
||||
initial_groups: new_groups,
|
||||
..Default::default()
|
||||
};
|
||||
tracing::trace!("notify did group by field1");
|
||||
|
||||
debug_assert!(!changeset.is_empty());
|
||||
if !changeset.is_empty() {
|
||||
send_notification(&changeset.view_id, DatabaseNotification::DidGroupByField)
|
||||
.payload(changeset)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
tracing::trace!("notify did group by field2");
|
||||
|
||||
*self.group_controller.write().await = new_group_controller;
|
||||
|
||||
tracing::trace!("did write group_controller to cache");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -958,8 +993,13 @@ impl DatabaseViewEditor {
|
||||
}
|
||||
|
||||
// initialize the group controller if the current layout support grouping
|
||||
*self.group_controller.write().await =
|
||||
new_group_controller(self.view_id.clone(), self.delegate.clone()).await?;
|
||||
*self.group_controller.write().await = new_group_controller(
|
||||
self.view_id.clone(),
|
||||
self.delegate.clone(),
|
||||
self.filter_controller.clone(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let payload = DatabaseLayoutMetaPB {
|
||||
view_id: self.view_id.clone(),
|
||||
@ -1019,7 +1059,7 @@ impl DatabaseViewEditor {
|
||||
.read()
|
||||
.await
|
||||
.as_ref()
|
||||
.map(|group| group.field_id().to_owned())?;
|
||||
.map(|controller| controller.get_grouping_field_id().to_owned())?;
|
||||
let field = self.delegate.get_field(&group_field_id)?;
|
||||
let mut write_guard = self.group_controller.write().await;
|
||||
if let Some(group_controller) = &mut *write_guard {
|
||||
@ -1054,7 +1094,7 @@ impl DatabaseViewEditor {
|
||||
});
|
||||
}
|
||||
|
||||
async fn gen_did_create_row_view_tasks(&self, row: Row) {
|
||||
async fn gen_did_create_row_view_tasks(&self, preliminary_index: usize, row_detail: RowDetail) {
|
||||
let weak_sort_controller = Arc::downgrade(&self.sort_controller);
|
||||
let weak_calculations_controller = Arc::downgrade(&self.calculations_controller);
|
||||
af_spawn(async move {
|
||||
@ -1062,12 +1102,14 @@ impl DatabaseViewEditor {
|
||||
sort_controller
|
||||
.read()
|
||||
.await
|
||||
.did_create_row(row.id.clone())
|
||||
.did_create_row(preliminary_index, &row_detail)
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(calculations_controller) = weak_calculations_controller.upgrade() {
|
||||
calculations_controller.did_receive_row_changed(row).await;
|
||||
calculations_controller
|
||||
.did_receive_row_changed(row_detail.row.clone())
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::RowId;
|
||||
use collab_database::rows::{RowDetail, RowId};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
@ -9,60 +9,61 @@ use lib_infra::future::{to_fut, Fut};
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::database_view::DatabaseViewOperation;
|
||||
use crate::services::field::RowSingleCellData;
|
||||
use crate::services::filter::FilterController;
|
||||
use crate::services::group::{
|
||||
find_suitable_grouping_field, make_group_controller, GroupContextDelegate, GroupController,
|
||||
GroupControllerDelegate, GroupSetting,
|
||||
make_group_controller, GroupContextDelegate, GroupController, GroupControllerDelegate,
|
||||
GroupSetting,
|
||||
};
|
||||
|
||||
pub async fn new_group_controller_with_field(
|
||||
view_id: String,
|
||||
delegate: Arc<dyn DatabaseViewOperation>,
|
||||
grouping_field: Field,
|
||||
) -> FlowyResult<Box<dyn GroupController>> {
|
||||
let configuration_delegate = GroupControllerDelegateImpl(delegate.clone());
|
||||
let rows = delegate.get_rows(&view_id).await;
|
||||
make_group_controller(view_id, grouping_field, rows, configuration_delegate).await
|
||||
}
|
||||
|
||||
pub async fn new_group_controller(
|
||||
view_id: String,
|
||||
delegate: Arc<dyn DatabaseViewOperation>,
|
||||
filter_controller: Arc<FilterController>,
|
||||
grouping_field: Option<Field>,
|
||||
) -> FlowyResult<Option<Box<dyn GroupController>>> {
|
||||
let fields = delegate.get_fields(&view_id, None).await;
|
||||
let controller_delegate = GroupControllerDelegateImpl(delegate.clone());
|
||||
|
||||
// Read the grouping field or find a new grouping field
|
||||
let mut grouping_field = controller_delegate
|
||||
.get_group_setting(&view_id)
|
||||
.await
|
||||
.and_then(|setting| {
|
||||
fields
|
||||
.iter()
|
||||
.find(|field| field.id == setting.field_id)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
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_suitable_grouping_field(&fields);
|
||||
if !delegate.get_layout_for_view(&view_id).is_board() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(grouping_field) = grouping_field {
|
||||
let rows = delegate.get_rows(&view_id).await;
|
||||
Ok(Some(
|
||||
make_group_controller(view_id, grouping_field, rows, controller_delegate).await?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
let controller_delegate = GroupControllerDelegateImpl {
|
||||
delegate: delegate.clone(),
|
||||
filter_controller: filter_controller.clone(),
|
||||
};
|
||||
|
||||
let grouping_field = match grouping_field {
|
||||
Some(field) => Some(field),
|
||||
None => {
|
||||
let group_setting = controller_delegate.get_group_setting(&view_id).await;
|
||||
|
||||
let fields = delegate.get_fields(&view_id, None).await;
|
||||
|
||||
group_setting
|
||||
.and_then(|setting| {
|
||||
fields
|
||||
.iter()
|
||||
.find(|field| field.id == setting.field_id)
|
||||
.cloned()
|
||||
})
|
||||
.or_else(|| find_suitable_grouping_field(&fields))
|
||||
},
|
||||
};
|
||||
|
||||
let controller = match grouping_field {
|
||||
Some(field) => Some(make_group_controller(&view_id, field, controller_delegate).await?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(controller)
|
||||
}
|
||||
|
||||
pub(crate) struct GroupControllerDelegateImpl(pub Arc<dyn DatabaseViewOperation>);
|
||||
pub(crate) struct GroupControllerDelegateImpl {
|
||||
delegate: Arc<dyn DatabaseViewOperation>,
|
||||
filter_controller: Arc<FilterController>,
|
||||
}
|
||||
|
||||
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);
|
||||
let mut settings = self.delegate.get_group_setting(view_id);
|
||||
to_fut(async move {
|
||||
if settings.is_empty() {
|
||||
None
|
||||
@ -75,19 +76,30 @@ impl GroupContextDelegate for GroupControllerDelegateImpl {
|
||||
fn get_configuration_cells(&self, view_id: &str, field_id: &str) -> Fut<Vec<RowSingleCellData>> {
|
||||
let field_id = field_id.to_owned();
|
||||
let view_id = view_id.to_owned();
|
||||
let delegate = self.0.clone();
|
||||
let delegate = self.delegate.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);
|
||||
self.delegate.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)
|
||||
self.delegate.get_field(field_id)
|
||||
}
|
||||
|
||||
fn get_all_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>> {
|
||||
let view_id = view_id.to_string();
|
||||
let delegate = self.delegate.clone();
|
||||
let filter_controller = self.filter_controller.clone();
|
||||
to_fut(async move {
|
||||
let mut row_details = delegate.get_rows(&view_id).await;
|
||||
filter_controller.filter_rows(&mut row_details).await;
|
||||
row_details
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,3 +155,15 @@ pub(crate) async fn get_cells_for_field(
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn find_suitable_grouping_field(fields: &[Field]) -> Option<Field> {
|
||||
let groupable_field = fields
|
||||
.iter()
|
||||
.find(|field| FieldType::from(field.field_type).can_be_group());
|
||||
|
||||
if let Some(field) = groupable_field {
|
||||
Some(field.clone())
|
||||
} else {
|
||||
fields.iter().find(|field| field.is_primary).cloned()
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,16 @@ impl SortDelegate for DatabaseViewSortDelegateImpl {
|
||||
})
|
||||
}
|
||||
|
||||
fn filter_row(&self, row_detail: &RowDetail) -> Fut<bool> {
|
||||
let filter_controller = self.filter_controller.clone();
|
||||
let row_detail = row_detail.clone();
|
||||
to_fut(async move {
|
||||
let mut row_details = vec![Arc::new(row_detail)];
|
||||
filter_controller.filter_rows(&mut row_details).await;
|
||||
!row_details.is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_field(&self, field_id: &str) -> Option<Field> {
|
||||
self.delegate.get_field(field_id)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ impl From<URLCellDataPB> for URLCellData {
|
||||
|
||||
impl AsRef<str> for URLCellData {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.url
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType, FilterType,
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContent, DateFilterPB, FieldType, FilterType,
|
||||
InsertedRowPB, NumberFilterPB, RelationFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
@ -337,7 +337,7 @@ impl<'a> From<&'a Filter> for FilterMap {
|
||||
},
|
||||
FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => {
|
||||
let filter = condition_and_content.cloned::<DateFilterPB>()?;
|
||||
let content = DateFilterContentPB {
|
||||
let content = DateFilterContent {
|
||||
start: filter.start,
|
||||
end: filter.end,
|
||||
timestamp: filter.timestamp,
|
||||
|
@ -79,8 +79,9 @@ pub trait GroupCustomize: Send + Sync {
|
||||
fn update_type_option_when_update_group(
|
||||
&mut self,
|
||||
_changeset: &GroupChangeset,
|
||||
_type_option: &mut Self::GroupTypeOption,
|
||||
) {
|
||||
_type_option: &Self::GroupTypeOption,
|
||||
) -> Option<Self::GroupTypeOption> {
|
||||
None
|
||||
}
|
||||
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str);
|
||||
@ -97,7 +98,7 @@ pub trait GroupCustomize: Send + Sync {
|
||||
///
|
||||
pub trait GroupController: Send + Sync {
|
||||
/// Returns the id of field that is being used to group the rows
|
||||
fn field_id(&self) -> &str;
|
||||
fn get_grouping_field_id(&self) -> &str;
|
||||
|
||||
/// Returns all of the groups currently managed by the controller
|
||||
fn get_all_groups(&self) -> Vec<&GroupData>;
|
||||
@ -189,7 +190,7 @@ pub trait GroupController: Send + Sync {
|
||||
fn apply_group_changeset(
|
||||
&mut self,
|
||||
changesets: &[GroupChangeset],
|
||||
) -> FlowyResult<(Vec<GroupPB>, TypeOptionData)>;
|
||||
) -> FlowyResult<(Vec<GroupPB>, Option<TypeOptionData>)>;
|
||||
|
||||
/// Called before the row was created.
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str);
|
||||
|
@ -4,6 +4,7 @@ use std::sync::Arc;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||
use futures::executor::block_on;
|
||||
use lib_infra::future::Fut;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
@ -24,6 +25,8 @@ use crate::services::group::{GroupChangeset, GroupsBuilder, MoveGroupRowContext}
|
||||
|
||||
pub trait GroupControllerDelegate: Send + Sync + 'static {
|
||||
fn get_field(&self, field_id: &str) -> Option<Field>;
|
||||
|
||||
fn get_all_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
||||
}
|
||||
|
||||
/// [BaseGroupController] is a generic group controller that provides customized implementations
|
||||
@ -159,7 +162,7 @@ where
|
||||
G: GroupsBuilder<Context = GroupControllerContext<C>, GroupTypeOption = T>,
|
||||
Self: GroupCustomize<GroupTypeOption = T>,
|
||||
{
|
||||
fn field_id(&self) -> &str {
|
||||
fn get_grouping_field_id(&self) -> &str {
|
||||
&self.grouping_field_id
|
||||
}
|
||||
|
||||
@ -228,12 +231,13 @@ where
|
||||
row_detail: &RowDetail,
|
||||
index: usize,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
let mut changesets: Vec<GroupRowsNotificationPB> = vec![];
|
||||
|
||||
let cell = match row_detail.row.cells.get(&self.grouping_field_id) {
|
||||
None => self.placeholder_cell(),
|
||||
Some(cell) => Some(cell.clone()),
|
||||
};
|
||||
|
||||
let mut changesets: Vec<GroupRowsNotificationPB> = vec![];
|
||||
if let Some(cell) = cell {
|
||||
let cell_data = <T as TypeOption>::CellData::from(&cell);
|
||||
|
||||
@ -245,7 +249,7 @@ where
|
||||
let changeset = GroupRowsNotificationPB::insert(
|
||||
group.id.clone(),
|
||||
vec![InsertedRowPB {
|
||||
row_meta: row_detail.into(),
|
||||
row_meta: (*row_detail).clone().into(),
|
||||
index: Some(index as i32),
|
||||
is_new: true,
|
||||
}],
|
||||
@ -256,15 +260,15 @@ where
|
||||
if !suitable_group_ids.is_empty() {
|
||||
for group_id in suitable_group_ids.iter() {
|
||||
if let Some(group) = self.context.get_mut_group(group_id) {
|
||||
group.add_row(row_detail.clone());
|
||||
group.add_row((*row_detail).clone());
|
||||
}
|
||||
}
|
||||
} else if let Some(no_status_group) = self.context.get_mut_no_status_group() {
|
||||
no_status_group.add_row(row_detail.clone());
|
||||
no_status_group.add_row((*row_detail).clone());
|
||||
let changeset = GroupRowsNotificationPB::insert(
|
||||
no_status_group.id.clone(),
|
||||
vec![InsertedRowPB {
|
||||
row_meta: row_detail.into(),
|
||||
row_meta: (*row_detail).clone().into(),
|
||||
index: Some(index as i32),
|
||||
is_new: true,
|
||||
}],
|
||||
@ -282,18 +286,12 @@ where
|
||||
row_detail: &RowDetail,
|
||||
field: &Field,
|
||||
) -> FlowyResult<DidUpdateGroupRowResult> {
|
||||
// let cell_data = row_rev.cells.get(&self.field_id).and_then(|cell_rev| {
|
||||
// let cell_data: Option<P> = get_type_cell_data(cell_rev, field_rev, None);
|
||||
// cell_data
|
||||
// });
|
||||
let mut result = DidUpdateGroupRowResult {
|
||||
inserted_group: None,
|
||||
deleted_group: None,
|
||||
row_changesets: vec![],
|
||||
};
|
||||
|
||||
if let Some(cell_data) = get_cell_data_from_row::<P>(Some(&row_detail.row), field) {
|
||||
let _old_row = old_row_detail.as_ref();
|
||||
let old_cell_data =
|
||||
get_cell_data_from_row::<P>(old_row_detail.as_ref().map(|detail| &detail.row), field);
|
||||
if let Ok((insert, delete)) = self.create_or_delete_group_when_cell_changed(
|
||||
@ -376,7 +374,7 @@ where
|
||||
}
|
||||
|
||||
fn delete_group(&mut self, group_id: &str) -> FlowyResult<(Vec<RowId>, Option<TypeOptionData>)> {
|
||||
let group = if group_id != self.field_id() {
|
||||
let group = if group_id != self.get_grouping_field_id() {
|
||||
self.get_group(group_id)
|
||||
} else {
|
||||
None
|
||||
@ -399,17 +397,26 @@ where
|
||||
fn apply_group_changeset(
|
||||
&mut self,
|
||||
changeset: &[GroupChangeset],
|
||||
) -> FlowyResult<(Vec<GroupPB>, TypeOptionData)> {
|
||||
) -> FlowyResult<(Vec<GroupPB>, Option<TypeOptionData>)> {
|
||||
// update group visibility
|
||||
for group_changeset in changeset.iter() {
|
||||
self.context.update_group(group_changeset)?;
|
||||
}
|
||||
|
||||
let mut type_option = self.get_grouping_field_type_option().ok_or_else(|| {
|
||||
// update group name
|
||||
let type_option = self.get_grouping_field_type_option().ok_or_else(|| {
|
||||
FlowyError::internal().with_context("Failed to get grouping field type option")
|
||||
})?;
|
||||
|
||||
let mut updated_type_option = None;
|
||||
|
||||
for group_changeset in changeset.iter() {
|
||||
self.update_type_option_when_update_group(group_changeset, &mut type_option);
|
||||
if let Some(type_option) =
|
||||
self.update_type_option_when_update_group(group_changeset, &type_option)
|
||||
{
|
||||
updated_type_option = Some(type_option);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let updated_groups = changeset
|
||||
@ -421,7 +428,10 @@ where
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok((updated_groups, type_option.into()))
|
||||
Ok((
|
||||
updated_groups,
|
||||
updated_type_option.map(|type_option| type_option.into()),
|
||||
))
|
||||
}
|
||||
|
||||
fn will_create_row(&self, cells: &mut Cells, field: &Field, group_id: &str) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cells, Row, RowDetail, RowId};
|
||||
|
||||
@ -10,7 +11,9 @@ use crate::entities::{
|
||||
use crate::services::group::action::{
|
||||
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupController,
|
||||
};
|
||||
use crate::services::group::{GroupChangeset, GroupData, MoveGroupRowContext};
|
||||
use crate::services::group::{
|
||||
GroupChangeset, GroupControllerDelegate, 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
|
||||
@ -19,23 +22,24 @@ use crate::services::group::{GroupChangeset, GroupData, MoveGroupRowContext};
|
||||
pub struct DefaultGroupController {
|
||||
pub field_id: String,
|
||||
pub group: GroupData,
|
||||
pub delegate: Arc<dyn GroupControllerDelegate>,
|
||||
}
|
||||
|
||||
const DEFAULT_GROUP_CONTROLLER: &str = "DefaultGroupController";
|
||||
|
||||
impl DefaultGroupController {
|
||||
pub fn new(field: &Field) -> Self {
|
||||
pub fn new(field: &Field, delegate: Arc<dyn GroupControllerDelegate>) -> Self {
|
||||
let group = GroupData::new(DEFAULT_GROUP_CONTROLLER.to_owned(), field.id.clone(), true);
|
||||
Self {
|
||||
field_id: field.id.clone(),
|
||||
group,
|
||||
delegate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GroupController for DefaultGroupController {
|
||||
fn field_id(&self) -> &str {
|
||||
fn get_grouping_field_id(&self) -> &str {
|
||||
&self.field_id
|
||||
}
|
||||
|
||||
@ -70,12 +74,12 @@ impl GroupController for DefaultGroupController {
|
||||
row_detail: &RowDetail,
|
||||
index: usize,
|
||||
) -> Vec<GroupRowsNotificationPB> {
|
||||
self.group.add_row(row_detail.clone());
|
||||
self.group.add_row((*row_detail).clone());
|
||||
|
||||
vec![GroupRowsNotificationPB::insert(
|
||||
self.group.id.clone(),
|
||||
vec![InsertedRowPB {
|
||||
row_meta: row_detail.into(),
|
||||
row_meta: (*row_detail).clone().into(),
|
||||
index: Some(index as i32),
|
||||
is_new: true,
|
||||
}],
|
||||
@ -128,8 +132,8 @@ impl GroupController for DefaultGroupController {
|
||||
fn apply_group_changeset(
|
||||
&mut self,
|
||||
_changeset: &[GroupChangeset],
|
||||
) -> FlowyResult<(Vec<GroupPB>, TypeOptionData)> {
|
||||
Ok((Vec::new(), TypeOptionData::default()))
|
||||
) -> FlowyResult<(Vec<GroupPB>, Option<TypeOptionData>)> {
|
||||
Ok((Vec::new(), None))
|
||||
}
|
||||
|
||||
fn will_create_row(&self, _cells: &mut Cells, _field: &Field, _group_id: &str) {}
|
||||
|
@ -124,10 +124,11 @@ impl GroupCustomize for MultiSelectGroupController {
|
||||
fn update_type_option_when_update_group(
|
||||
&mut self,
|
||||
changeset: &GroupChangeset,
|
||||
type_option: &mut Self::GroupTypeOption,
|
||||
) {
|
||||
type_option: &Self::GroupTypeOption,
|
||||
) -> Option<Self::GroupTypeOption> {
|
||||
if let Some(name) = &changeset.name {
|
||||
let mut new_type_option = type_option.clone();
|
||||
|
||||
let select_option = type_option
|
||||
.options
|
||||
.iter()
|
||||
@ -139,6 +140,10 @@ impl GroupCustomize for MultiSelectGroupController {
|
||||
..select_option.to_owned()
|
||||
};
|
||||
new_type_option.insert_option(new_select_option);
|
||||
|
||||
Some(new_type_option)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,10 +126,11 @@ impl GroupCustomize for SingleSelectGroupController {
|
||||
fn update_type_option_when_update_group(
|
||||
&mut self,
|
||||
changeset: &GroupChangeset,
|
||||
type_option: &mut Self::GroupTypeOption,
|
||||
) {
|
||||
type_option: &Self::GroupTypeOption,
|
||||
) -> Option<Self::GroupTypeOption> {
|
||||
if let Some(name) = &changeset.name {
|
||||
let mut new_type_option = type_option.clone();
|
||||
|
||||
let select_option = type_option
|
||||
.options
|
||||
.iter()
|
||||
@ -141,6 +142,10 @@ impl GroupCustomize for SingleSelectGroupController {
|
||||
..select_option.to_owned()
|
||||
};
|
||||
new_type_option.insert_option(new_select_option);
|
||||
|
||||
Some(new_type_option)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,9 +86,8 @@ impl RowChangeset {
|
||||
err
|
||||
)]
|
||||
pub async fn make_group_controller<D>(
|
||||
view_id: String,
|
||||
view_id: &str,
|
||||
grouping_field: Field,
|
||||
row_details: Vec<Arc<RowDetail>>,
|
||||
delegate: D,
|
||||
) -> FlowyResult<Box<dyn GroupController>>
|
||||
where
|
||||
@ -102,68 +101,81 @@ where
|
||||
|
||||
match grouping_field_type {
|
||||
FieldType::SingleSelect => {
|
||||
let configuration =
|
||||
SingleSelectGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone())
|
||||
.await?;
|
||||
let configuration = SingleSelectGroupControllerContext::new(
|
||||
view_id.to_string(),
|
||||
grouping_field.clone(),
|
||||
delegate.clone(),
|
||||
)
|
||||
.await?;
|
||||
let controller =
|
||||
SingleSelectGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
SingleSelectGroupController::new(&grouping_field, configuration, delegate.clone()).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::MultiSelect => {
|
||||
let configuration =
|
||||
MultiSelectGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone())
|
||||
.await?;
|
||||
let configuration = MultiSelectGroupControllerContext::new(
|
||||
view_id.to_string(),
|
||||
grouping_field.clone(),
|
||||
delegate.clone(),
|
||||
)
|
||||
.await?;
|
||||
let controller =
|
||||
MultiSelectGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
MultiSelectGroupController::new(&grouping_field, configuration, delegate.clone()).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::Checkbox => {
|
||||
let configuration =
|
||||
CheckboxGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone())
|
||||
.await?;
|
||||
let configuration = CheckboxGroupControllerContext::new(
|
||||
view_id.to_string(),
|
||||
grouping_field.clone(),
|
||||
delegate.clone(),
|
||||
)
|
||||
.await?;
|
||||
let controller =
|
||||
CheckboxGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
CheckboxGroupController::new(&grouping_field, configuration, delegate.clone()).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::URL => {
|
||||
let configuration =
|
||||
URLGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone()).await?;
|
||||
let controller = URLGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
let configuration = URLGroupControllerContext::new(
|
||||
view_id.to_string(),
|
||||
grouping_field.clone(),
|
||||
delegate.clone(),
|
||||
)
|
||||
.await?;
|
||||
let controller =
|
||||
URLGroupController::new(&grouping_field, configuration, delegate.clone()).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
FieldType::DateTime => {
|
||||
let configuration =
|
||||
DateGroupControllerContext::new(view_id, grouping_field.clone(), delegate.clone()).await?;
|
||||
let controller = DateGroupController::new(&grouping_field, configuration, delegate).await?;
|
||||
let configuration = DateGroupControllerContext::new(
|
||||
view_id.to_string(),
|
||||
grouping_field.clone(),
|
||||
delegate.clone(),
|
||||
)
|
||||
.await?;
|
||||
let controller =
|
||||
DateGroupController::new(&grouping_field, configuration, delegate.clone()).await?;
|
||||
group_controller = Box::new(controller);
|
||||
},
|
||||
_ => {
|
||||
group_controller = Box::new(DefaultGroupController::new(&grouping_field));
|
||||
group_controller = Box::new(DefaultGroupController::new(
|
||||
&grouping_field,
|
||||
delegate.clone(),
|
||||
));
|
||||
},
|
||||
}
|
||||
|
||||
// Separates the rows into different groups
|
||||
let row_details = delegate.get_all_rows(view_id).await;
|
||||
|
||||
let rows = row_details
|
||||
.iter()
|
||||
.map(|row| row.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
group_controller.fill_groups(rows.as_slice(), &grouping_field)?;
|
||||
|
||||
Ok(group_controller)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub fn find_suitable_grouping_field(fields: &[Field]) -> Option<Field> {
|
||||
let groupable_field = fields
|
||||
.iter()
|
||||
.find(|field| FieldType::from(field.field_type).can_be_group());
|
||||
|
||||
if let Some(field) = groupable_field {
|
||||
Some(field.clone())
|
||||
} else {
|
||||
fields.iter().find(|field| field.is_primary).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `default` group configuration for the [Field]
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -21,14 +21,14 @@ use crate::services::field::{
|
||||
default_order, TimestampCellData, TimestampCellDataWrapper, TypeOptionCellExt,
|
||||
};
|
||||
use crate::services::sort::{
|
||||
InsertSortedRowResult, ReorderAllRowsResult, ReorderSingleRowResult, Sort, SortChangeset,
|
||||
SortCondition,
|
||||
InsertRowResult, ReorderAllRowsResult, ReorderSingleRowResult, Sort, SortChangeset, SortCondition,
|
||||
};
|
||||
|
||||
pub trait SortDelegate: Send + Sync {
|
||||
fn get_sort(&self, view_id: &str, sort_id: &str) -> Fut<Option<Arc<Sort>>>;
|
||||
/// Returns all the rows after applying grid's filter
|
||||
fn get_rows(&self, view_id: &str) -> Fut<Vec<Arc<RowDetail>>>;
|
||||
fn filter_row(&self, row_detail: &RowDetail) -> Fut<bool>;
|
||||
fn get_field(&self, field_id: &str) -> Option<Field>;
|
||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Field>>;
|
||||
}
|
||||
@ -94,14 +94,27 @@ impl SortController {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn did_create_row(&self, row_id: RowId) {
|
||||
pub async fn did_create_row(&self, preliminary_index: usize, row_detail: &RowDetail) {
|
||||
if !self.delegate.filter_row(row_detail).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.sorts.is_empty() {
|
||||
self
|
||||
.gen_task(
|
||||
SortEvent::NewRowInserted(row_id),
|
||||
SortEvent::NewRowInserted(row_detail.clone()),
|
||||
QualityOfService::Background,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
let result = InsertRowResult {
|
||||
view_id: self.view_id.clone(),
|
||||
row: row_detail.clone(),
|
||||
index: preliminary_index,
|
||||
};
|
||||
let _ = self
|
||||
.notifier
|
||||
.send(DatabaseViewChanged::InsertRowNotification(result));
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,22 +175,20 @@ impl SortController {
|
||||
_ => tracing::trace!("The row index cache is outdated"),
|
||||
}
|
||||
},
|
||||
SortEvent::NewRowInserted(row_id) => {
|
||||
SortEvent::NewRowInserted(row_detail) => {
|
||||
self.sort_rows(&mut row_details).await;
|
||||
let row_index = self.row_index_cache.get(&row_id).cloned();
|
||||
let row_index = self.row_index_cache.get(&row_detail.row.id).cloned();
|
||||
match row_index {
|
||||
Some(row_index) => {
|
||||
let notification = InsertSortedRowResult {
|
||||
row_id: row_id.clone(),
|
||||
let notification = InsertRowResult {
|
||||
view_id: self.view_id.clone(),
|
||||
row: row_detail.clone(),
|
||||
index: row_index,
|
||||
};
|
||||
self.row_index_cache.insert(row_id, row_index);
|
||||
self.row_index_cache.insert(row_detail.row.id, row_index);
|
||||
let _ = self
|
||||
.notifier
|
||||
.send(DatabaseViewChanged::InsertSortedRowNotification(
|
||||
notification,
|
||||
));
|
||||
.send(DatabaseViewChanged::InsertRowNotification(notification));
|
||||
},
|
||||
_ => tracing::trace!("The row index cache is outdated"),
|
||||
}
|
||||
@ -353,7 +364,7 @@ fn cmp_cell(
|
||||
enum SortEvent {
|
||||
SortDidChanged,
|
||||
RowDidChanged(RowId),
|
||||
NewRowInserted(RowId),
|
||||
NewRowInserted(RowDetail),
|
||||
DeleteAllSorts,
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use std::cmp::Ordering;
|
||||
|
||||
use anyhow::bail;
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::rows::RowId;
|
||||
use collab_database::rows::{RowDetail, RowId};
|
||||
use collab_database::views::{SortMap, SortMapBuilder};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -107,9 +107,9 @@ pub struct ReorderSingleRowResult {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InsertSortedRowResult {
|
||||
pub struct InsertRowResult {
|
||||
pub view_id: String,
|
||||
pub row_id: RowId,
|
||||
pub row: RowDetail,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
|
@ -212,7 +212,7 @@ async fn assert_sort_changed(
|
||||
old_row_orders.insert(changed.new_index, old);
|
||||
assert_eq!(old_row_orders, new_row_orders);
|
||||
},
|
||||
DatabaseViewChanged::InsertSortedRowNotification(_changed) => {},
|
||||
DatabaseViewChanged::InsertRowNotification(_changed) => {},
|
||||
_ => {},
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user