mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Feat: support new column type, Checklist.
Feat/support checklist type
This commit is contained in:
commit
c8b90974fb
@ -222,6 +222,7 @@
|
||||
"singleSelectFieldName": "Select",
|
||||
"multiSelectFieldName": "Multiselect",
|
||||
"urlFieldName": "URL",
|
||||
"checklistFieldName": "Checklist",
|
||||
"numberFormat": " Number format",
|
||||
"dateFormat": " Date format",
|
||||
"includeTime": " Include time",
|
||||
@ -264,6 +265,9 @@
|
||||
"panelTitle": "Select an option or create one",
|
||||
"searchOption": "Search for an option"
|
||||
},
|
||||
"checklist": {
|
||||
"panelTitle": "Search an option or create one"
|
||||
},
|
||||
"menuName": "Grid"
|
||||
},
|
||||
"document": {
|
||||
|
@ -10,11 +10,3 @@ class BoardGroupService {
|
||||
groupField = field;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CanBeGroupField {
|
||||
String get groupContent;
|
||||
}
|
||||
|
||||
// class SingleSelectGroup extends CanBeGroupField {
|
||||
// final SingleSelectTypeOptionContext typeOptionContext;
|
||||
// }
|
||||
|
@ -358,6 +358,8 @@ Widget? _buildHeaderIcon(GroupData customData) {
|
||||
break;
|
||||
case FieldType.URL:
|
||||
break;
|
||||
case FieldType.Checklist:
|
||||
break;
|
||||
}
|
||||
|
||||
if (widget != null) {
|
||||
|
@ -0,0 +1,35 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_cell.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardChecklistCell extends StatefulWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
const BoardChecklistCell({required this.cellControllerBuilder, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardChecklistCell> createState() => _BoardChecklistCellState();
|
||||
}
|
||||
|
||||
class _BoardChecklistCellState extends State<BoardChecklistCell> {
|
||||
late ChecklistCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridChecklistCellController;
|
||||
_cellBloc = ChecklistCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const ChecklistCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: const ChecklistProgressBar(),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'board_cell.dart';
|
||||
import 'board_checkbox_cell.dart';
|
||||
import 'board_checklist_cell.dart';
|
||||
import 'board_date_cell.dart';
|
||||
import 'board_number_cell.dart';
|
||||
import 'board_select_option_cell.dart';
|
||||
@ -58,6 +59,11 @@ class BoardCellBuilder {
|
||||
editableNotifier: cellNotifier,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Checklist:
|
||||
return BoardChecklistCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return BoardNumberCell(
|
||||
groupId: groupId,
|
||||
|
@ -5,6 +5,8 @@ typedef GridCheckboxCellController = IGridCellController<String, String>;
|
||||
typedef GridNumberCellController = IGridCellController<String, String>;
|
||||
typedef GridSelectOptionCellController
|
||||
= IGridCellController<SelectOptionCellDataPB, String>;
|
||||
typedef GridChecklistCellController
|
||||
= IGridCellController<SelectOptionCellDataPB, String>;
|
||||
typedef GridDateCellController
|
||||
= IGridCellController<DateCellDataPB, CalendarData>;
|
||||
typedef GridURLCellController = IGridCellController<URLCellDataPB, String>;
|
||||
@ -81,6 +83,7 @@ class GridCellControllerBuilder {
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.SingleSelect:
|
||||
case FieldType.Checklist:
|
||||
final cellDataLoader = GridCellDataLoader(
|
||||
cellId: _cellId,
|
||||
parser: SelectOptionCellDataParser(),
|
||||
|
@ -0,0 +1,97 @@
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service/cell_service.dart';
|
||||
import 'checklist_cell_editor_bloc.dart';
|
||||
import 'select_option_service.dart';
|
||||
part 'checklist_cell_bloc.freezed.dart';
|
||||
|
||||
class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
final GridChecklistCellController cellController;
|
||||
final SelectOptionFFIService _selectOptionService;
|
||||
void Function()? _onCellChangedFn;
|
||||
ChecklistCellBloc({
|
||||
required this.cellController,
|
||||
}) : _selectOptionService =
|
||||
SelectOptionFFIService(cellId: cellController.cellId),
|
||||
super(ChecklistCellState.initial(cellController)) {
|
||||
on<ChecklistCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
_loadOptions();
|
||||
},
|
||||
didReceiveOptions: (data) {
|
||||
emit(state.copyWith(
|
||||
allOptions: data.options,
|
||||
selectedOptions: data.selectOptions,
|
||||
percent: percentFromSelectOptionCellData(data),
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellFieldChanged: () {
|
||||
_loadOptions();
|
||||
},
|
||||
onCellChanged: (data) {
|
||||
if (!isClosed && data != null) {
|
||||
add(ChecklistCellEvent.didReceiveOptions(data));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
_selectOptionService.getOptionContext().then((result) {
|
||||
if (isClosed) return;
|
||||
|
||||
return result.fold(
|
||||
(data) => add(ChecklistCellEvent.didReceiveOptions(data)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellEvent with _$ChecklistCellEvent {
|
||||
const factory ChecklistCellEvent.initial() = _InitialCell;
|
||||
const factory ChecklistCellEvent.didReceiveOptions(
|
||||
SelectOptionCellDataPB data) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellState with _$ChecklistCellState {
|
||||
const factory ChecklistCellState({
|
||||
required List<SelectOptionPB> allOptions,
|
||||
required List<SelectOptionPB> selectedOptions,
|
||||
required double percent,
|
||||
}) = _ChecklistCellState;
|
||||
|
||||
factory ChecklistCellState.initial(
|
||||
GridChecklistCellController cellController) {
|
||||
return const ChecklistCellState(
|
||||
allOptions: [],
|
||||
selectedOptions: [],
|
||||
percent: 0,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'select_option_service.dart';
|
||||
|
||||
part 'checklist_cell_editor_bloc.freezed.dart';
|
||||
|
||||
class ChecklistCellEditorBloc
|
||||
extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
|
||||
final SelectOptionFFIService _selectOptionService;
|
||||
final GridChecklistCellController cellController;
|
||||
|
||||
ChecklistCellEditorBloc({
|
||||
required this.cellController,
|
||||
}) : _selectOptionService =
|
||||
SelectOptionFFIService(cellId: cellController.cellId),
|
||||
super(ChecklistCellEditorState.initial(cellController)) {
|
||||
on<ChecklistCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
_loadOptions();
|
||||
},
|
||||
didReceiveOptions: (data) {
|
||||
emit(state.copyWith(
|
||||
allOptions: _makeChecklistSelectOptions(data, state.predicate),
|
||||
percent: percentFromSelectOptionCellData(data),
|
||||
));
|
||||
},
|
||||
newOption: (optionName) {
|
||||
_createOption(optionName);
|
||||
emit(state.copyWith(
|
||||
predicate: '',
|
||||
));
|
||||
},
|
||||
deleteOption: (option) {
|
||||
_deleteOption([option]);
|
||||
},
|
||||
updateOption: (option) {
|
||||
_updateOption(option);
|
||||
},
|
||||
selectOption: (option) async {
|
||||
if (option.isSelected) {
|
||||
await _selectOptionService.unSelect(optionIds: [option.data.id]);
|
||||
} else {
|
||||
await _selectOptionService.select(optionIds: [option.data.id]);
|
||||
}
|
||||
},
|
||||
filterOption: (String predicate) {},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _createOption(String name) async {
|
||||
final result = await _selectOptionService.create(
|
||||
name: name,
|
||||
isSelected: false,
|
||||
);
|
||||
result.fold((l) => {}, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _deleteOption(List<SelectOptionPB> options) async {
|
||||
final result = await _selectOptionService.delete(options: options);
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _updateOption(SelectOptionPB option) async {
|
||||
final result = await _selectOptionService.update(
|
||||
option: option,
|
||||
);
|
||||
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
_selectOptionService.getOptionContext().then((result) {
|
||||
if (isClosed) return;
|
||||
|
||||
return result.fold(
|
||||
(data) => add(ChecklistCellEditorEvent.didReceiveOptions(data)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
cellController.startListening(
|
||||
onCellChanged: ((data) {
|
||||
if (!isClosed && data != null) {
|
||||
add(ChecklistCellEditorEvent.didReceiveOptions(data));
|
||||
}
|
||||
}),
|
||||
onCellFieldChanged: () {
|
||||
_loadOptions();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent {
|
||||
const factory ChecklistCellEditorEvent.initial() = _Initial;
|
||||
const factory ChecklistCellEditorEvent.didReceiveOptions(
|
||||
SelectOptionCellDataPB data) = _DidReceiveOptions;
|
||||
const factory ChecklistCellEditorEvent.newOption(String optionName) =
|
||||
_NewOption;
|
||||
const factory ChecklistCellEditorEvent.selectOption(
|
||||
ChecklistSelectOption option) = _SelectOption;
|
||||
const factory ChecklistCellEditorEvent.updateOption(SelectOptionPB option) =
|
||||
_UpdateOption;
|
||||
const factory ChecklistCellEditorEvent.deleteOption(SelectOptionPB option) =
|
||||
_DeleteOption;
|
||||
const factory ChecklistCellEditorEvent.filterOption(String predicate) =
|
||||
_FilterOption;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellEditorState with _$ChecklistCellEditorState {
|
||||
const factory ChecklistCellEditorState({
|
||||
required List<ChecklistSelectOption> allOptions,
|
||||
required Option<String> createOption,
|
||||
required double percent,
|
||||
required String predicate,
|
||||
}) = _ChecklistCellEditorState;
|
||||
|
||||
factory ChecklistCellEditorState.initial(
|
||||
GridSelectOptionCellController context) {
|
||||
final data = context.getCellData(loadIfNotExist: true);
|
||||
|
||||
return ChecklistCellEditorState(
|
||||
allOptions: _makeChecklistSelectOptions(data, ''),
|
||||
createOption: none(),
|
||||
percent: percentFromSelectOptionCellData(data),
|
||||
predicate: '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double percentFromSelectOptionCellData(SelectOptionCellDataPB? data) {
|
||||
if (data == null) return 0;
|
||||
|
||||
final b = data.options.length.toDouble();
|
||||
if (b == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final a = data.selectOptions.length.toDouble();
|
||||
if (a > b) return 1.0;
|
||||
|
||||
return a / b;
|
||||
}
|
||||
|
||||
List<ChecklistSelectOption> _makeChecklistSelectOptions(
|
||||
SelectOptionCellDataPB? data, String predicate) {
|
||||
if (data == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final List<ChecklistSelectOption> options = [];
|
||||
final List<SelectOptionPB> allOptions = List.from(data.options);
|
||||
if (predicate.isNotEmpty) {
|
||||
allOptions.retainWhere((element) => element.name.contains(predicate));
|
||||
}
|
||||
final selectedOptionIds = data.selectOptions.map((e) => e.id).toList();
|
||||
|
||||
for (final option in allOptions) {
|
||||
options.add(
|
||||
ChecklistSelectOption(selectedOptionIds.contains(option.id), option),
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
class ChecklistSelectOption {
|
||||
final bool isSelected;
|
||||
final SelectOptionPB data;
|
||||
|
||||
ChecklistSelectOption(this.isSelected, this.data);
|
||||
}
|
@ -1,26 +1,24 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'select_option_service.dart';
|
||||
|
||||
part 'select_option_editor_bloc.freezed.dart';
|
||||
|
||||
class SelectOptionCellEditorBloc
|
||||
extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
|
||||
final SelectOptionService _selectOptionService;
|
||||
final SelectOptionFFIService _selectOptionService;
|
||||
final GridSelectOptionCellController cellController;
|
||||
Timer? _delayOperation;
|
||||
|
||||
SelectOptionCellEditorBloc({
|
||||
required this.cellController,
|
||||
}) : _selectOptionService =
|
||||
SelectOptionService(cellId: cellController.cellId),
|
||||
SelectOptionFFIService(cellId: cellController.cellId),
|
||||
super(SelectOptionEditorState.initial(cellController)) {
|
||||
on<SelectOptionEditorEvent>(
|
||||
(event, emit) async {
|
||||
|
@ -6,15 +6,16 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'cell_service/cell_service.dart';
|
||||
|
||||
class SelectOptionService {
|
||||
class SelectOptionFFIService {
|
||||
final GridCellIdentifier cellId;
|
||||
SelectOptionService({required this.cellId});
|
||||
SelectOptionFFIService({required this.cellId});
|
||||
|
||||
String get gridId => cellId.gridId;
|
||||
String get fieldId => cellId.fieldInfo.id;
|
||||
String get rowId => cellId.rowId;
|
||||
|
||||
Future<Either<Unit, FlowyError>> create({required String name}) {
|
||||
Future<Either<Unit, FlowyError>> create(
|
||||
{required String name, bool isSelected = true}) {
|
||||
return TypeOptionFFIService(gridId: gridId, fieldId: fieldId)
|
||||
.newOption(name: name)
|
||||
.then(
|
||||
@ -26,8 +27,13 @@ class SelectOptionService {
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
final payload = SelectOptionChangesetPB.create()
|
||||
..insertOptions.add(option)
|
||||
..cellIdentifier = cellIdentifier;
|
||||
|
||||
if (isSelected) {
|
||||
payload.insertOptions.add(option);
|
||||
} else {
|
||||
payload.updateOptions.add(option);
|
||||
}
|
||||
return GridEventUpdateSelectOption(payload).send();
|
||||
},
|
||||
(r) => right(r),
|
||||
|
@ -503,25 +503,15 @@ class FieldInfo {
|
||||
|
||||
bool get hasFilter => _hasFilter;
|
||||
|
||||
bool get canGroup {
|
||||
bool get canBeGroup {
|
||||
switch (_field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return true;
|
||||
case FieldType.DateTime:
|
||||
return false;
|
||||
case FieldType.MultiSelect:
|
||||
return true;
|
||||
case FieldType.Number:
|
||||
return false;
|
||||
case FieldType.RichText:
|
||||
return false;
|
||||
case FieldType.SingleSelect:
|
||||
return true;
|
||||
case FieldType.URL:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get canCreateFilter {
|
||||
@ -532,6 +522,7 @@ class FieldInfo {
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.RichText:
|
||||
case FieldType.SingleSelect:
|
||||
// case FieldType.Checklist:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checklist_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@ -95,6 +96,17 @@ class MultiSelectTypeOptionWidgetDataParser
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-select
|
||||
typedef ChecklistTypeOptionContext = TypeOptionContext<ChecklistTypeOptionPB>;
|
||||
|
||||
class ChecklistTypeOptionWidgetDataParser
|
||||
extends TypeOptionDataParser<ChecklistTypeOptionPB> {
|
||||
@override
|
||||
ChecklistTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return ChecklistTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionContext<T extends GeneratedMessage> {
|
||||
T? _typeOptionObject;
|
||||
final TypeOptionDataParser<T> dataParser;
|
||||
|
@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pbserver.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
|
||||
@ -104,6 +105,11 @@ class GridCreateFilterBloc
|
||||
condition: SelectOptionCondition.OptionIs,
|
||||
fieldType: FieldType.MultiSelect,
|
||||
);
|
||||
case FieldType.Checklist:
|
||||
return _ffiService.insertChecklistFilter(
|
||||
fieldId: fieldId,
|
||||
condition: ChecklistFilterCondition.IsIncomplete,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return _ffiService.insertNumberFilter(
|
||||
fieldId: fieldId,
|
||||
|
@ -3,6 +3,7 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
|
||||
@ -145,6 +146,22 @@ class FilterFFIService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> insertChecklistFilter({
|
||||
required String fieldId,
|
||||
required ChecklistFilterCondition condition,
|
||||
String? filterId,
|
||||
List<String> optionIds = const [],
|
||||
}) {
|
||||
final filter = ChecklistFilterPB()..condition = condition;
|
||||
|
||||
return insertFilter(
|
||||
fieldId: fieldId,
|
||||
filterId: filterId,
|
||||
fieldType: FieldType.Checklist,
|
||||
data: filter.writeToBuffer(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> insertFilter({
|
||||
required String fieldId,
|
||||
String? filterId,
|
||||
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'cell_accessory.dart';
|
||||
import 'cell_shortcuts.dart';
|
||||
import 'checkbox_cell.dart';
|
||||
import 'checklist_cell/checklist_cell.dart';
|
||||
import 'date_cell/date_cell.dart';
|
||||
import 'number_cell.dart';
|
||||
import 'select_option_cell/select_option_cell.dart';
|
||||
@ -55,6 +56,11 @@ class GridCellBuilder {
|
||||
style: style,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Checklist:
|
||||
return GridChecklistCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return GridNumberCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
|
@ -0,0 +1,87 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.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 '../cell_builder.dart';
|
||||
import 'checklist_cell_editor.dart';
|
||||
import 'checklist_prograss_bar.dart';
|
||||
|
||||
class GridChecklistCell extends GridCellWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
GridChecklistCell({required this.cellControllerBuilder, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
GridChecklistCellState createState() => GridChecklistCellState();
|
||||
}
|
||||
|
||||
class GridChecklistCellState extends State<GridChecklistCell> {
|
||||
late PopoverController _popover;
|
||||
late ChecklistCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popover = PopoverController();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridChecklistCellController;
|
||||
_cellBloc = ChecklistCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const ChecklistCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: _wrapPopover(const ChecklistProgressBar()),
|
||||
),
|
||||
InkWell(onTap: () => _popover.show()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapPopover(Widget child) {
|
||||
return AppFlowyPopover(
|
||||
controller: _popover,
|
||||
constraints: BoxConstraints.loose(const Size(260, 400)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
popupBuilder: (BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onCellEditing.value = true;
|
||||
});
|
||||
return GridChecklistCellEditor(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridChecklistCellController,
|
||||
);
|
||||
},
|
||||
onClose: () => widget.onCellEditing.value = false,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChecklistProgressBar extends StatelessWidget {
|
||||
const ChecklistProgressBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
return ChecklistPrograssBar(
|
||||
percent: state.percent,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_editor_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_prograss_bar.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class GridChecklistCellEditor extends StatefulWidget {
|
||||
final GridChecklistCellController cellController;
|
||||
const GridChecklistCellEditor({required this.cellController, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<GridChecklistCellEditor> createState() =>
|
||||
_GridChecklistCellEditorState();
|
||||
}
|
||||
|
||||
class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
late ChecklistCellEditorBloc bloc;
|
||||
late PopoverMutex popoverMutex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
popoverMutex = PopoverMutex();
|
||||
bloc = ChecklistCellEditorBloc(cellController: widget.cellController);
|
||||
bloc.add(const ChecklistCellEditorEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
bloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<ChecklistCellEditorBloc, ChecklistCellEditorState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> slivers = [
|
||||
const SliverChecklistPrograssBar(),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: ListView.separated(
|
||||
controller: ScrollController(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.allOptions.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _ChecklistOptionCell(
|
||||
option: state.allOptions[index],
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return ScrollConfiguration(
|
||||
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: slivers,
|
||||
controller: ScrollController(),
|
||||
physics: StyledScrollPhysics(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChecklistOptionCell extends StatefulWidget {
|
||||
final ChecklistSelectOption option;
|
||||
final PopoverMutex popoverMutex;
|
||||
const _ChecklistOptionCell({
|
||||
required this.option,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_ChecklistOptionCell> createState() => _ChecklistOptionCellState();
|
||||
}
|
||||
|
||||
class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
|
||||
late PopoverController _popoverController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popoverController = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final icon = widget.option.isSelected
|
||||
? svgWidget('editor/editor_check')
|
||||
: svgWidget('editor/editor_uncheck');
|
||||
return _wrapPopover(
|
||||
SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyButton(
|
||||
text: FlowyText(widget.option.data.name),
|
||||
leftIcon: icon,
|
||||
onTap: () => context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.selectOption(widget.option)),
|
||||
),
|
||||
),
|
||||
_disclosureButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _disclosureButton() {
|
||||
return FlowyIconButton(
|
||||
width: 20,
|
||||
onPressed: () => _popoverController.show(),
|
||||
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||
icon: svgWidget(
|
||||
"editor/details",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapPopover(Widget child) {
|
||||
return AppFlowyPopover(
|
||||
controller: _popoverController,
|
||||
offset: const Offset(20, 0),
|
||||
asBarrier: true,
|
||||
constraints: BoxConstraints.loose(const Size(200, 300)),
|
||||
mutex: widget.popoverMutex,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
child: child,
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return SelectOptionTypeOptionEditor(
|
||||
option: widget.option.data,
|
||||
onDeleted: () {
|
||||
context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.deleteOption(widget.option.data),
|
||||
);
|
||||
|
||||
_popoverController.close();
|
||||
},
|
||||
onUpdated: (updatedOption) {
|
||||
context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.updateOption(widget.option.data),
|
||||
);
|
||||
},
|
||||
showOptions: false,
|
||||
autoFocus: false,
|
||||
// Use ValueKey to refresh the UI, otherwise, it will remain the old value.
|
||||
key: ValueKey(
|
||||
widget.option.data.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_editor_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/color_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:percent_indicator/percent_indicator.dart';
|
||||
|
||||
class ChecklistPrograssBar extends StatelessWidget {
|
||||
final double percent;
|
||||
const ChecklistPrograssBar({required this.percent, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LinearPercentIndicator(
|
||||
lineHeight: 10.0,
|
||||
percent: percent,
|
||||
padding: EdgeInsets.zero,
|
||||
progressColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: AFThemeExtension.of(context).tint9,
|
||||
barRadius: const Radius.circular(5),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SliverChecklistPrograssBar extends StatelessWidget {
|
||||
const SliverChecklistPrograssBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _SliverChecklistPrograssBarDelegate(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SliverChecklistPrograssBarDelegate
|
||||
extends SliverPersistentHeaderDelegate {
|
||||
_SliverChecklistPrograssBarDelegate();
|
||||
|
||||
double fixHeight = 54;
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
return BlocBuilder<ChecklistCellEditorBloc, ChecklistCellEditorState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: Column(
|
||||
children: [
|
||||
FlowyTextField(
|
||||
autoClearWhenDone: true,
|
||||
hintText: LocaleKeys.grid_checklist_panelTitle.tr(),
|
||||
onChanged: (text) {
|
||||
context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.filterOption(text));
|
||||
},
|
||||
onSubmitted: (text) {
|
||||
context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.newOption(text));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0),
|
||||
child: ChecklistPrograssBar(percent: state.percent),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => fixHeight;
|
||||
|
||||
@override
|
||||
double get minExtent => fixHeight;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
|
@ -11,7 +11,6 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
|
@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
// ignore: unused_import
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
|
@ -9,7 +9,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -6,7 +6,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -8,7 +8,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -0,0 +1,14 @@
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'choicechip.dart';
|
||||
|
||||
class ChecklistFilterChoicechip extends StatelessWidget {
|
||||
final FilterInfo filterInfo;
|
||||
const ChecklistFilterChoicechip({required this.filterInfo, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChoiceChipButton(filterInfo: filterInfo);
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.d
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pb.dart';
|
||||
|
@ -3,13 +3,11 @@ import 'package:app_flowy/plugins/grid/application/filter/text_filter_editor_blo
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/condition_button.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/disclosure_button.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/text_field.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -151,7 +149,7 @@ class _TextFilterEditorState extends State<TextFilterEditor> {
|
||||
|
||||
Widget _buildFilterTextField(
|
||||
BuildContext context, TextFilterEditorState state) {
|
||||
return FilterTextField(
|
||||
return FlowyTextField(
|
||||
text: state.filter.content,
|
||||
hintText: LocaleKeys.grid_settings_typeAValue.tr(),
|
||||
autoFucous: false,
|
||||
|
@ -2,13 +2,13 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/filter/filter_create_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/text_field.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -121,7 +121,7 @@ class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate {
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
height: fixHeight,
|
||||
child: FilterTextField(
|
||||
child: FlowyTextField(
|
||||
hintText: LocaleKeys.grid_settings_filterBy.tr(),
|
||||
onChanged: (text) {
|
||||
context
|
||||
|
@ -7,7 +7,6 @@ import 'package:flowy_infra/color_extension.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -2,6 +2,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pbenum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'choicechip/checkbox.dart';
|
||||
import 'choicechip/checklist.dart';
|
||||
import 'choicechip/date.dart';
|
||||
import 'choicechip/number.dart';
|
||||
import 'choicechip/select_option/select_option.dart';
|
||||
@ -35,6 +36,8 @@ Widget buildFilterChoicechip(FilterInfo filterInfo) {
|
||||
return SelectOptionFilterChoicechip(filterInfo: filterInfo);
|
||||
case FieldType.URL:
|
||||
return URLFilterChoicechip(filterInfo: filterInfo);
|
||||
case FieldType.Checklist:
|
||||
return ChecklistFilterChoicechip(filterInfo: filterInfo);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -19,6 +19,8 @@ extension FieldTypeListExtension on FieldType {
|
||||
return "grid/field/single_select";
|
||||
case FieldType.URL:
|
||||
return "grid/field/url";
|
||||
case FieldType.Checklist:
|
||||
return "grid/field/checklist";
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
@ -39,6 +41,8 @@ extension FieldTypeListExtension on FieldType {
|
||||
return LocaleKeys.grid_field_singleSelectFieldName.tr();
|
||||
case FieldType.URL:
|
||||
return LocaleKeys.grid_field_urlFieldName.tr();
|
||||
case FieldType.Checklist:
|
||||
return LocaleKeys.grid_field_checklistFieldName.tr();
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -5,7 +5,6 @@ import 'package:dartz/dartz.dart' show Either;
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -9,7 +9,6 @@ import 'package:flowy_infra/color_extension.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -5,6 +5,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/checklist_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
@ -15,6 +16,7 @@ import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'checkbox.dart';
|
||||
import 'checklist.dart';
|
||||
import 'date.dart';
|
||||
import 'multi_select.dart';
|
||||
import 'number.dart';
|
||||
@ -124,6 +126,15 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
|
||||
case FieldType.Checklist:
|
||||
return ChecklistTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<ChecklistTypeOptionPB>(
|
||||
gridId: gridId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
@ -206,6 +217,11 @@ TypeOptionContext<T>
|
||||
dataController: dataController,
|
||||
dataParser: MultiSelectTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.Checklist:
|
||||
return ChecklistTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: ChecklistTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.Number:
|
||||
return NumberTypeOptionContext(
|
||||
dataController: dataController,
|
||||
|
@ -0,0 +1,11 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
class ChecklistTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
ChecklistTypeOptionWidgetBuilder(
|
||||
ChecklistTypeOptionContext typeOptionContext);
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => null;
|
||||
}
|
@ -7,7 +7,6 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -5,7 +5,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -3,7 +3,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -4,6 +4,7 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -18,10 +19,14 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
|
||||
final SelectOptionPB option;
|
||||
final VoidCallback onDeleted;
|
||||
final Function(SelectOptionPB) onUpdated;
|
||||
final bool showOptions;
|
||||
final bool autoFocus;
|
||||
const SelectOptionTypeOptionEditor({
|
||||
required this.option,
|
||||
required this.onDeleted,
|
||||
required this.onUpdated,
|
||||
this.showOptions = true,
|
||||
this.autoFocus = true,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -50,21 +55,29 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
List<Widget> slivers = [
|
||||
SliverToBoxAdapter(
|
||||
child: _OptionNameTextField(state.option.name)),
|
||||
child: _OptionNameTextField(
|
||||
name: state.option.name,
|
||||
autoFocus: autoFocus,
|
||||
)),
|
||||
const SliverToBoxAdapter(child: VSpace(10)),
|
||||
const SliverToBoxAdapter(child: _DeleteTag()),
|
||||
const SliverToBoxAdapter(child: TypeOptionSeparator()),
|
||||
SliverToBoxAdapter(
|
||||
child:
|
||||
SelectOptionColorList(selectedColor: state.option.color)),
|
||||
];
|
||||
|
||||
if (showOptions) {
|
||||
slivers
|
||||
.add(const SliverToBoxAdapter(child: TypeOptionSeparator()));
|
||||
slivers.add(SliverToBoxAdapter(
|
||||
child: SelectOptionColorList(
|
||||
selectedColor: state.option.color)));
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 160,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: CustomScrollView(
|
||||
slivers: slivers,
|
||||
shrinkWrap: true,
|
||||
controller: ScrollController(),
|
||||
physics: StyledScrollPhysics(),
|
||||
),
|
||||
@ -102,19 +115,21 @@ class _DeleteTag extends StatelessWidget {
|
||||
|
||||
class _OptionNameTextField extends StatelessWidget {
|
||||
final String name;
|
||||
const _OptionNameTextField(this.name, {Key? key}) : super(key: key);
|
||||
final bool autoFocus;
|
||||
const _OptionNameTextField(
|
||||
{required this.name, required this.autoFocus, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InputTextField(
|
||||
return FlowyTextField(
|
||||
autoFucous: autoFocus,
|
||||
text: name,
|
||||
maxLength: 30,
|
||||
onCanceled: () {},
|
||||
onDone: (optionName) {
|
||||
if (name != optionName) {
|
||||
onSubmitted: (newName) {
|
||||
if (name != newName) {
|
||||
context
|
||||
.read<EditSelectOptionBloc>()
|
||||
.add(EditSelectOptionEvent.updateName(optionName));
|
||||
.add(EditSelectOptionEvent.updateName(newName));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -9,7 +9,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
@ -331,6 +330,10 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
|
||||
return SelectOptionCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
);
|
||||
case FieldType.Checklist:
|
||||
return SelectOptionCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
);
|
||||
case FieldType.Number:
|
||||
return null;
|
||||
case FieldType.RichText:
|
||||
|
@ -37,7 +37,7 @@ class GridGroupList extends StatelessWidget {
|
||||
key: ValueKey(fieldInfo.id),
|
||||
);
|
||||
|
||||
if (!fieldInfo.canGroup) {
|
||||
if (!fieldInfo.canBeGroup) {
|
||||
cell = IgnorePointer(child: Opacity(opacity: 0.3, child: cell));
|
||||
}
|
||||
return cell;
|
||||
|
@ -8,7 +8,6 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -6,7 +6,6 @@ import 'package:app_flowy/workspace/presentation/settings/widgets/settings_user_
|
||||
import 'package:app_flowy/workspace/presentation/settings/widgets/settings_menu.dart';
|
||||
import 'package:app_flowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
@ -88,7 +88,8 @@ class _PopoverMaskState extends State<PopoverMask> {
|
||||
}
|
||||
|
||||
bool _handleGlobalKeyEvent(KeyEvent event) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.escape &&
|
||||
event is KeyDownEvent) {
|
||||
if (widget.onExit != null) {
|
||||
widget.onExit!();
|
||||
}
|
||||
|
@ -10,3 +10,5 @@ export 'src/flowy_overlay/list_overlay.dart';
|
||||
export 'src/flowy_overlay/option_overlay.dart';
|
||||
export 'src/flowy_overlay/flowy_dialog.dart';
|
||||
export 'src/flowy_overlay/appflowy_popover.dart';
|
||||
export 'style_widget/text.dart';
|
||||
export 'style_widget/text_field.dart';
|
||||
|
@ -1,34 +1,50 @@
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class FilterTextField extends StatefulWidget {
|
||||
class FlowyTextField extends StatefulWidget {
|
||||
final String hintText;
|
||||
final String text;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onSubmitted;
|
||||
final void Function()? onCanceled;
|
||||
final bool autoFucous;
|
||||
const FilterTextField({
|
||||
final int? maxLength;
|
||||
final TextEditingController? controller;
|
||||
final bool autoClearWhenDone;
|
||||
const FlowyTextField({
|
||||
this.hintText = "",
|
||||
this.text = "",
|
||||
this.onChanged,
|
||||
this.onSubmitted,
|
||||
this.onCanceled,
|
||||
this.autoFucous = true,
|
||||
this.maxLength,
|
||||
this.controller,
|
||||
this.autoClearWhenDone = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<FilterTextField> createState() => FilterTextFieldState();
|
||||
State<FlowyTextField> createState() => FlowyTextFieldState();
|
||||
}
|
||||
|
||||
class FilterTextFieldState extends State<FilterTextField> {
|
||||
class FlowyTextFieldState extends State<FlowyTextField> {
|
||||
late FocusNode focusNode;
|
||||
late TextEditingController controller;
|
||||
var textLength = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
focusNode = FocusNode();
|
||||
controller = TextEditingController();
|
||||
controller.text = widget.text;
|
||||
focusNode.addListener(notifyDidEndEditing);
|
||||
|
||||
if (widget.controller != null) {
|
||||
controller = widget.controller!;
|
||||
} else {
|
||||
controller = TextEditingController();
|
||||
controller.text = widget.text;
|
||||
}
|
||||
if (widget.autoFucous) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
focusNode.requestFocus();
|
||||
@ -47,9 +63,15 @@ class FilterTextFieldState extends State<FilterTextField> {
|
||||
},
|
||||
onSubmitted: (text) {
|
||||
widget.onSubmitted?.call(text);
|
||||
|
||||
if (widget.autoClearWhenDone) {
|
||||
controller.text = "";
|
||||
}
|
||||
},
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLength: widget.maxLength,
|
||||
maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
@ -61,6 +83,8 @@ class FilterTextFieldState extends State<FilterTextField> {
|
||||
),
|
||||
isDense: true,
|
||||
hintText: widget.hintText,
|
||||
suffixText: _suffixText(),
|
||||
counterText: "",
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
@ -71,4 +95,29 @@ class FilterTextFieldState extends State<FilterTextField> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.removeListener(notifyDidEndEditing);
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void notifyDidEndEditing() {
|
||||
if (!focusNode.hasFocus) {
|
||||
if (controller.text.isEmpty) {
|
||||
widget.onCanceled?.call();
|
||||
} else {
|
||||
widget.onSubmitted?.call(controller.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? _suffixText() {
|
||||
if (widget.maxLength != null) {
|
||||
return '${textLength.toString()}/${widget.maxLength.toString()}';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -862,6 +862,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
percent_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: percent_indicator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.2"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -90,6 +90,7 @@ dependencies:
|
||||
shared_preferences: ^2.0.15
|
||||
google_fonts: ^3.0.1
|
||||
file_picker: <=5.0.0
|
||||
percent_indicator: ^4.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^2.0.1
|
||||
|
@ -491,6 +491,7 @@ pub enum FieldType {
|
||||
MultiSelect = 4,
|
||||
Checkbox = 5,
|
||||
URL = 6,
|
||||
Checklist = 7,
|
||||
}
|
||||
|
||||
pub const RICH_TEXT_FIELD: FieldType = FieldType::RichText;
|
||||
@ -500,6 +501,7 @@ pub const SINGLE_SELECT_FIELD: FieldType = FieldType::SingleSelect;
|
||||
pub const MULTI_SELECT_FIELD: FieldType = FieldType::MultiSelect;
|
||||
pub const CHECKBOX_FIELD: FieldType = FieldType::Checkbox;
|
||||
pub const URL_FIELD: FieldType = FieldType::URL;
|
||||
pub const CHECKLIST_FIELD: FieldType = FieldType::Checklist;
|
||||
|
||||
impl std::default::Default for FieldType {
|
||||
fn default() -> Self {
|
||||
@ -563,6 +565,10 @@ impl FieldType {
|
||||
self == &MULTI_SELECT_FIELD || self == &SINGLE_SELECT_FIELD
|
||||
}
|
||||
|
||||
pub fn is_check_list(&self) -> bool {
|
||||
self == &CHECKLIST_FIELD
|
||||
}
|
||||
|
||||
pub fn can_be_group(&self) -> bool {
|
||||
self.is_select_option()
|
||||
}
|
||||
@ -596,8 +602,9 @@ impl std::convert::From<FieldTypeRevision> for FieldType {
|
||||
4 => FieldType::MultiSelect,
|
||||
5 => FieldType::Checkbox,
|
||||
6 => FieldType::URL,
|
||||
7 => FieldType::Checklist,
|
||||
_ => {
|
||||
tracing::error!("Can't parser FieldTypeRevision: {} to FieldType", ty);
|
||||
tracing::error!("Can't convert FieldTypeRevision: {} to FieldType", ty);
|
||||
FieldType::RichText
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use grid_rev_model::FilterRevision;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct ChecklistFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: ChecklistFilterCondition,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum ChecklistFilterCondition {
|
||||
IsComplete = 0,
|
||||
IsIncomplete = 1,
|
||||
}
|
||||
|
||||
impl std::convert::From<ChecklistFilterCondition> for u32 {
|
||||
fn from(value: ChecklistFilterCondition) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for ChecklistFilterCondition {
|
||||
fn default() -> Self {
|
||||
ChecklistFilterCondition::IsIncomplete
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for ChecklistFilterCondition {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(ChecklistFilterCondition::IsComplete),
|
||||
1 => Ok(ChecklistFilterCondition::IsIncomplete),
|
||||
_ => Err(ErrorCode::InvalidData),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for ChecklistFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
ChecklistFilterPB {
|
||||
condition: ChecklistFilterCondition::try_from(rev.condition)
|
||||
.unwrap_or(ChecklistFilterCondition::IsIncomplete),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
mod checkbox_filter;
|
||||
mod checklist_filter;
|
||||
mod date_filter;
|
||||
mod filter_changeset;
|
||||
mod number_filter;
|
||||
@ -7,6 +8,7 @@ mod text_filter;
|
||||
mod util;
|
||||
|
||||
pub use checkbox_filter::*;
|
||||
pub use checklist_filter::*;
|
||||
pub use date_filter::*;
|
||||
pub use filter_changeset::*;
|
||||
pub use number_filter::*;
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, DateFilterContent, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContent, DateFilterPB, FieldType, NumberFilterPB,
|
||||
SelectOptionFilterPB, TextFilterPB,
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::FilterType;
|
||||
@ -35,6 +36,7 @@ impl std::convert::From<&FilterRevision> for FilterPB {
|
||||
FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::Checklist => ChecklistFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(),
|
||||
FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(),
|
||||
};
|
||||
@ -174,7 +176,7 @@ impl TryInto<AlterFilterParams> for AlterFilterPayloadPB {
|
||||
}
|
||||
.to_string();
|
||||
}
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
|
||||
let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
|
||||
condition = filter.condition as u8;
|
||||
content = SelectOptionIds::from(filter.option_ids).to_string();
|
||||
|
@ -104,6 +104,9 @@ impl TypeCellData {
|
||||
pub fn is_multi_select(&self) -> bool {
|
||||
self.field_type == FieldType::MultiSelect
|
||||
}
|
||||
pub fn is_checklist(&self) -> bool {
|
||||
self.field_type == FieldType::Checklist
|
||||
}
|
||||
|
||||
pub fn is_url(&self) -> bool {
|
||||
self.field_type == FieldType::URL
|
||||
|
@ -122,6 +122,7 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
|
||||
SingleSelectTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev)
|
||||
}
|
||||
FieldType::MultiSelect => MultiSelectTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::Checklist => ChecklistTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::Checkbox => CheckboxTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
FieldType::URL => URLTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
|
||||
}?;
|
||||
@ -180,6 +181,9 @@ pub fn decode_cell_data_to_string(
|
||||
FieldType::MultiSelect => field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_type)?
|
||||
.displayed_cell_string(cell_data.into(), from_field_type, field_rev),
|
||||
FieldType::Checklist => field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_type)?
|
||||
.displayed_cell_string(cell_data.into(), from_field_type, field_rev),
|
||||
FieldType::Checkbox => field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_type)?
|
||||
.displayed_cell_string(cell_data.into(), from_field_type, field_rev),
|
||||
@ -230,6 +234,9 @@ pub fn try_decode_cell_data(
|
||||
FieldType::MultiSelect => field_rev
|
||||
.get_type_option::<MultiSelectTypeOptionPB>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
|
||||
FieldType::Checklist => field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
|
||||
FieldType::Checkbox => field_rev
|
||||
.get_type_option::<CheckboxTypeOptionPB>(field_type)?
|
||||
.decode_cell_data(cell_data.into(), from_field_type, field_rev),
|
||||
|
@ -38,6 +38,7 @@ pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box<dyn
|
||||
FieldType::MultiSelect => MultiSelectTypeOptionPB::default().into(),
|
||||
FieldType::Checkbox => CheckboxTypeOptionPB::default().into(),
|
||||
FieldType::URL => URLTypeOptionPB::default().into(),
|
||||
FieldType::Checklist => ChecklistTypeOptionPB::default().into(),
|
||||
};
|
||||
|
||||
type_option_builder_from_json_str(&s, field_type)
|
||||
@ -52,6 +53,7 @@ pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box
|
||||
FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)),
|
||||
FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)),
|
||||
FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)),
|
||||
FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_json_str(s)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,5 +67,6 @@ pub fn type_option_builder_from_bytes<T: Into<Bytes>>(bytes: T, field_type: &Fie
|
||||
FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
use crate::entities::{ChecklistFilterCondition, ChecklistFilterPB};
|
||||
use crate::services::field::{SelectOptionPB, SelectedSelectOptions};
|
||||
|
||||
impl ChecklistFilterPB {
|
||||
pub fn is_visible(&self, all_options: &[SelectOptionPB], selected_options: &SelectedSelectOptions) -> bool {
|
||||
let selected_option_ids = selected_options
|
||||
.options
|
||||
.iter()
|
||||
.map(|option| option.id.as_str())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
let mut all_option_ids = all_options
|
||||
.iter()
|
||||
.map(|option| option.id.as_str())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
return match self.condition {
|
||||
ChecklistFilterCondition::IsComplete => {
|
||||
if selected_option_ids.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id));
|
||||
all_option_ids.is_empty()
|
||||
}
|
||||
ChecklistFilterCondition::IsIncomplete => {
|
||||
if selected_option_ids.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id));
|
||||
!all_option_ids.is_empty()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable};
|
||||
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
|
||||
use crate::services::field::type_options::util::get_cell_data;
|
||||
use crate::services::field::{
|
||||
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
|
||||
TypeOptionBuilder,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Multiple select
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
pub struct ChecklistTypeOptionPB {
|
||||
#[pb(index = 1)]
|
||||
pub options: Vec<SelectOptionPB>,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub disable_color: bool,
|
||||
}
|
||||
impl_type_option!(ChecklistTypeOptionPB, FieldType::Checklist);
|
||||
|
||||
impl SelectTypeOptionSharedAction for ChecklistTypeOptionPB {
|
||||
fn number_of_max_options(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
fn options(&self) -> &Vec<SelectOptionPB> {
|
||||
&self.options
|
||||
}
|
||||
|
||||
fn mut_options(&mut self) -> &mut Vec<SelectOptionPB> {
|
||||
&mut self.options
|
||||
}
|
||||
}
|
||||
|
||||
impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for ChecklistTypeOptionPB {
|
||||
fn decode_cell_data(
|
||||
&self,
|
||||
cell_data: CellData<SelectOptionIds>,
|
||||
decoded_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
) -> FlowyResult<CellBytes> {
|
||||
self.displayed_cell_bytes(cell_data, decoded_field_type, field_rev)
|
||||
}
|
||||
|
||||
fn apply_changeset(
|
||||
&self,
|
||||
changeset: AnyCellChangeset<SelectOptionCellChangeset>,
|
||||
cell_rev: Option<CellRevision>,
|
||||
) -> Result<String, FlowyError> {
|
||||
let content_changeset = changeset.try_into_inner()?;
|
||||
|
||||
let insert_option_ids = content_changeset
|
||||
.insert_option_ids
|
||||
.into_iter()
|
||||
.filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let new_cell_data: String;
|
||||
match cell_rev {
|
||||
None => {
|
||||
new_cell_data = SelectOptionIds::from(insert_option_ids).to_string();
|
||||
}
|
||||
Some(cell_rev) => {
|
||||
let cell_data = get_cell_data(&cell_rev);
|
||||
let mut select_ids: SelectOptionIds = cell_data.into();
|
||||
for insert_option_id in insert_option_ids {
|
||||
if !select_ids.contains(&insert_option_id) {
|
||||
select_ids.push(insert_option_id);
|
||||
}
|
||||
}
|
||||
|
||||
for delete_option_id in content_changeset.delete_option_ids {
|
||||
select_ids.retain(|id| id != &delete_option_id);
|
||||
}
|
||||
|
||||
new_cell_data = select_ids.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
tracing::trace!("checklist's cell data: {}", &new_cell_data);
|
||||
Ok(new_cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ChecklistTypeOptionBuilder(ChecklistTypeOptionPB);
|
||||
impl_into_box_type_option_builder!(ChecklistTypeOptionBuilder);
|
||||
impl_builder_from_json_str_and_from_bytes!(ChecklistTypeOptionBuilder, ChecklistTypeOptionPB);
|
||||
impl ChecklistTypeOptionBuilder {
|
||||
pub fn add_option(mut self, opt: SelectOptionPB) -> Self {
|
||||
self.0.options.push(opt);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionBuilder for ChecklistTypeOptionBuilder {
|
||||
fn field_type(&self) -> FieldType {
|
||||
FieldType::Checklist
|
||||
}
|
||||
|
||||
fn serializer(&self) -> &dyn TypeOptionDataSerializer {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
|
||||
SelectOptionTypeOptionTransformer::transform_type_option(&mut self.0, field_type, type_option_data)
|
||||
}
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
mod checklist_filter;
|
||||
mod checklist_type_option;
|
||||
mod multi_select_type_option;
|
||||
mod select_filter;
|
||||
mod select_type_option;
|
||||
mod single_select_type_option;
|
||||
mod type_option_transform;
|
||||
|
||||
pub use checklist_type_option::*;
|
||||
pub use multi_select_type_option::*;
|
||||
pub use select_type_option::*;
|
||||
pub use single_select_type_option::*;
|
||||
|
||||
pub use checklist_filter::*;
|
||||
|
@ -80,7 +80,7 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
|
||||
}
|
||||
|
||||
new_cell_data = select_ids.to_string();
|
||||
tracing::trace!("Multi select cell data: {}", &new_cell_data);
|
||||
tracing::trace!("Multi-select cell data: {}", &new_cell_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
#![allow(clippy::needless_collect)]
|
||||
|
||||
use crate::entities::{SelectOptionCondition, SelectOptionFilterPB};
|
||||
use crate::entities::{ChecklistFilterPB, SelectOptionCondition, SelectOptionFilterPB};
|
||||
use crate::services::cell::{CellFilterOperation, TypeCellData};
|
||||
use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||
use crate::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||
use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions};
|
||||
use flowy_error::FlowyResult;
|
||||
|
||||
@ -61,6 +61,16 @@ impl CellFilterOperation<SelectOptionFilterPB> for SingleSelectTypeOptionPB {
|
||||
}
|
||||
}
|
||||
|
||||
impl CellFilterOperation<ChecklistFilterPB> for ChecklistTypeOptionPB {
|
||||
fn apply_filter(&self, any_cell_data: TypeCellData, filter: &ChecklistFilterPB) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_checklist() {
|
||||
return Ok(true);
|
||||
}
|
||||
let selected_options = SelectedSelectOptions::from(self.get_selected_options(any_cell_data.into()));
|
||||
Ok(filter.is_visible(&self.options, &selected_options))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
|
@ -4,7 +4,7 @@ use crate::services::cell::{
|
||||
CellBytes, CellBytesParser, CellData, CellDataIsEmpty, CellDisplayable, FromCellChangeset, FromCellString,
|
||||
};
|
||||
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
|
||||
use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||
use crate::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::{internal_error, ErrorCode, FlowyResult};
|
||||
@ -212,6 +212,10 @@ pub fn select_type_option_from_field_rev(
|
||||
let type_option = MultiSelectTypeOptionPB::from(field_rev);
|
||||
Ok(Box::new(type_option))
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let type_option = ChecklistTypeOptionPB::from(field_rev);
|
||||
Ok(Box::new(type_option))
|
||||
}
|
||||
ty => {
|
||||
tracing::error!("Unsupported field type: {:?} for this handler", ty);
|
||||
Err(ErrorCode::FieldInvalidOperation.into())
|
||||
|
@ -48,7 +48,7 @@ impl SelectOptionTypeOptionTransformer {
|
||||
T: SelectTypeOptionSharedAction,
|
||||
{
|
||||
match decoded_field_type {
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
|
||||
//
|
||||
CellBytes::from(shared.get_selected_options(cell_data))
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::entities::{CheckboxFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB};
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
};
|
||||
use crate::services::filter::FilterType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -10,6 +12,7 @@ pub(crate) struct FilterMap {
|
||||
pub(crate) date_filter: HashMap<FilterType, DateFilterPB>,
|
||||
pub(crate) select_option_filter: HashMap<FilterType, SelectOptionFilterPB>,
|
||||
pub(crate) checkbox_filter: HashMap<FilterType, CheckboxFilterPB>,
|
||||
pub(crate) checklist_filter: HashMap<FilterType, ChecklistFilterPB>,
|
||||
}
|
||||
|
||||
impl FilterMap {
|
||||
@ -26,6 +29,7 @@ impl FilterMap {
|
||||
FieldType::MultiSelect => self.select_option_filter.get(filter_type).is_some(),
|
||||
FieldType::Checkbox => self.checkbox_filter.get(filter_type).is_some(),
|
||||
FieldType::URL => self.url_filter.get(filter_type).is_some(),
|
||||
FieldType::Checklist => self.checklist_filter.get(filter_type).is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +61,9 @@ impl FilterMap {
|
||||
if !self.checkbox_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if !self.checklist_filter.is_empty() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@ -83,6 +90,9 @@ impl FilterMap {
|
||||
FieldType::URL => {
|
||||
let _ = self.url_filter.remove(filter_id);
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let _ = self.checklist_filter.remove(filter_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -291,6 +291,12 @@ impl FilterController {
|
||||
.url_filter
|
||||
.insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let _ = self
|
||||
.filter_map
|
||||
.checklist_filter
|
||||
.insert(filter_type, ChecklistFilterPB::from(filter_rev.as_ref()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -413,6 +419,14 @@ fn filter_cell(
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
FieldType::Checklist => filter_map.checklist_filter.get(filter_id).and_then(|filter| {
|
||||
Some(
|
||||
field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_rev.ty)?
|
||||
.apply_filter(any_cell_data, filter)
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
}?;
|
||||
tracing::Span::current().record(
|
||||
"cell_content",
|
||||
|
@ -123,6 +123,12 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat
|
||||
SelectOptionGroupConfigurationRevision::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
FieldType::Checklist => GroupConfigurationRevision::new(
|
||||
field_id,
|
||||
field_type_rev,
|
||||
SelectOptionGroupConfigurationRevision::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
FieldType::Checkbox => {
|
||||
GroupConfigurationRevision::new(field_id, field_type_rev, CheckboxGroupConfigurationRevision::default())
|
||||
.unwrap()
|
||||
|
@ -226,6 +226,24 @@ impl GridRowTest {
|
||||
assert_eq!(s, expected);
|
||||
}
|
||||
|
||||
FieldType::Checklist => {
|
||||
let cell_data = self
|
||||
.editor
|
||||
.get_cell_bytes(&cell_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.parser::<SelectOptionCellDataParser>()
|
||||
.unwrap();
|
||||
|
||||
let s = cell_data
|
||||
.select_options
|
||||
.into_iter()
|
||||
.map(|option| option.name)
|
||||
.collect::<Vec<String>>()
|
||||
.join(SELECTION_IDS_SEPARATOR);
|
||||
|
||||
assert_eq!(s, expected);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let cell_data = self
|
||||
.editor
|
||||
|
@ -2,7 +2,7 @@ use flowy_grid::entities::FieldType;
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_grid::services::field::{
|
||||
DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
|
||||
ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
|
||||
};
|
||||
use flowy_grid::services::row::RowRevisionBuilder;
|
||||
use grid_rev_model::{FieldRevision, RowRevision};
|
||||
@ -90,6 +90,19 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
multi_select_field.id.clone()
|
||||
}
|
||||
|
||||
pub fn insert_checklist_cell<F>(&mut self, f: F) -> String
|
||||
where
|
||||
F: Fn(Vec<SelectOptionPB>) -> Vec<SelectOptionPB>,
|
||||
{
|
||||
let checklist_field = self.field_rev_with_type(&FieldType::Checklist);
|
||||
let type_option = ChecklistTypeOptionPB::from(&checklist_field);
|
||||
let options = f(type_option.options);
|
||||
let ops_ids = options.iter().map(|option| option.id.clone()).collect::<Vec<_>>();
|
||||
self.inner_builder
|
||||
.insert_select_option_cell(&checklist_field.id, ops_ids);
|
||||
|
||||
checklist_field.id.clone()
|
||||
}
|
||||
pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision {
|
||||
self.field_revs
|
||||
.iter()
|
||||
|
@ -3,7 +3,7 @@ use crate::grid::cell_test::script::GridCellTest;
|
||||
use crate::grid::field_test::util::make_date_cell_string;
|
||||
use flowy_grid::entities::{CellChangesetPB, FieldType};
|
||||
use flowy_grid::services::field::selection_type_option::SelectOptionCellChangeset;
|
||||
use flowy_grid::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||
use flowy_grid::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_cell_update() {
|
||||
@ -31,6 +31,10 @@ async fn grid_cell_update() {
|
||||
let type_option = MultiSelectTypeOptionPB::from(field_rev);
|
||||
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let type_option = ChecklistTypeOptionPB::from(field_rev);
|
||||
SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::Checkbox => "1".to_string(),
|
||||
FieldType::URL => "1".to_string(),
|
||||
};
|
||||
|
@ -0,0 +1,27 @@
|
||||
use crate::grid::filter_test::script::FilterScript::*;
|
||||
use crate::grid::filter_test::script::GridFilterTest;
|
||||
use flowy_grid::entities::{ChecklistFilterCondition, SelectOptionCondition};
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checklist_is_incomplete_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateChecklistFilter {
|
||||
condition: ChecklistFilterCondition::IsIncomplete,
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 4 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checklist_is_complete_test() {
|
||||
let mut test = GridFilterTest::new().await;
|
||||
let scripts = vec![
|
||||
CreateChecklistFilter {
|
||||
condition: ChecklistFilterCondition::IsComplete,
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 1 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
mod checkbox_filter_test;
|
||||
mod checklist_filter_test;
|
||||
mod date_filter_test;
|
||||
mod number_filter_test;
|
||||
mod script;
|
||||
|
@ -6,7 +6,7 @@
|
||||
use std::time::Duration;
|
||||
use bytes::Bytes;
|
||||
use futures::TryFutureExt;
|
||||
use flowy_grid::entities::{AlterFilterParams, AlterFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition, DateFilterContent, SelectOptionCondition, TextFilterPB, NumberFilterPB, CheckboxFilterPB, DateFilterPB, SelectOptionFilterPB, CellChangesetPB, FilterPB};
|
||||
use flowy_grid::entities::{AlterFilterParams, AlterFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition, DateFilterContent, SelectOptionCondition, TextFilterPB, NumberFilterPB, CheckboxFilterPB, DateFilterPB, SelectOptionFilterPB, CellChangesetPB, FilterPB, ChecklistFilterCondition, ChecklistFilterPB};
|
||||
use flowy_grid::services::field::{SelectOptionCellChangeset, SelectOptionIds};
|
||||
use flowy_grid::services::setting::GridSettingChangesetBuilder;
|
||||
use grid_rev_model::{FieldRevision, FieldTypeRevision};
|
||||
@ -57,6 +57,9 @@ pub enum FilterScript {
|
||||
condition: SelectOptionCondition,
|
||||
option_ids: Vec<String>,
|
||||
},
|
||||
CreateChecklistFilter {
|
||||
condition: ChecklistFilterCondition,
|
||||
},
|
||||
AssertFilterCount {
|
||||
count: i32,
|
||||
},
|
||||
@ -184,6 +187,14 @@ impl GridFilterTest {
|
||||
AlterFilterPayloadPB::new(field_rev, filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::CreateChecklistFilter { condition} => {
|
||||
let field_rev = self.get_first_field_rev(FieldType::Checklist);
|
||||
// let type_option = self.get_checklist_type_option(&field_rev.id);
|
||||
let filter = ChecklistFilterPB { condition };
|
||||
let payload =
|
||||
AlterFilterPayloadPB::new(field_rev, filter);
|
||||
self.insert_filter(payload).await;
|
||||
}
|
||||
FilterScript::AssertFilterCount { count } => {
|
||||
let filters = self.editor.get_all_filters().await.unwrap();
|
||||
assert_eq!(count as usize, filters.len());
|
||||
|
@ -131,6 +131,14 @@ impl GridEditorTest {
|
||||
type_option
|
||||
}
|
||||
|
||||
pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB {
|
||||
let field_type = FieldType::Checklist;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
let type_option = field_rev
|
||||
.get_type_option::<ChecklistTypeOptionPB>(field_type.into())
|
||||
.unwrap();
|
||||
type_option
|
||||
}
|
||||
pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB {
|
||||
let field_type = FieldType::Checkbox;
|
||||
let field_rev = self.get_field_rev(field_id, field_type.clone());
|
||||
@ -153,6 +161,9 @@ pub const COMPLETED: &str = "Completed";
|
||||
pub const PLANNED: &str = "Planned";
|
||||
pub const PAUSED: &str = "Paused";
|
||||
|
||||
pub const FIRST_THING: &str = "Wake up at 6:00 am";
|
||||
pub const SECOND_THING: &str = "Get some coffee";
|
||||
pub const THIRD_THING: &str = "Start working";
|
||||
// This grid is assumed to contain all the Fields.
|
||||
fn make_test_grid() -> BuildGridContext {
|
||||
let mut grid_builder = GridBuilder::new();
|
||||
@ -217,6 +228,14 @@ fn make_test_grid() -> BuildGridContext {
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let checklist = ChecklistTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(FIRST_THING))
|
||||
.add_option(SelectOptionPB::new(SECOND_THING))
|
||||
.add_option(SelectOptionPB::new(THIRD_THING));
|
||||
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
|
||||
grid_builder.add_field(checklist_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,6 +253,7 @@ fn make_test_grid() -> BuildGridContext {
|
||||
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
|
||||
FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
@ -370,6 +390,14 @@ fn make_test_board() -> BuildGridContext {
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
FieldType::Checklist => {
|
||||
let checklist = ChecklistTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(FIRST_THING))
|
||||
.add_option(SelectOptionPB::new(SECOND_THING))
|
||||
.add_option(SelectOptionPB::new(THIRD_THING));
|
||||
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
|
||||
grid_builder.add_field(checklist_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user