mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: show checklist items inline in row page (#3737)
* feat: show checklist items inline in row page * fix: tauri build
This commit is contained in:
parent
25a98cda81
commit
6c3d7d2079
@ -529,7 +529,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
|
||||
final widget = this.widget<ChecklistItem>(task);
|
||||
assert(
|
||||
widget.option.data.name == name && widget.option.isSelected == isChecked,
|
||||
widget.task.data.name == name && widget.task.isSelected == isChecked,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/cell_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -69,13 +68,4 @@ class ChecklistCellBackendService {
|
||||
|
||||
return DatabaseEventUpdateChecklistCell(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<ChecklistCellDataPB, FlowyError>> getCellData() {
|
||||
final payload = CellIdPB.create()
|
||||
..viewId = viewId
|
||||
..fieldId = fieldId
|
||||
..rowId = rowId;
|
||||
|
||||
return DatabaseEventGetChecklistCellData(payload).send();
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +187,10 @@ class _PropertyCellState extends State<PropertyCell> {
|
||||
final gesture = GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => cell.requestFocus.notify(),
|
||||
child: AccessoryHover(child: cell),
|
||||
child: AccessoryHover(
|
||||
fieldType: widget.cellContext.fieldType,
|
||||
child: cell,
|
||||
),
|
||||
);
|
||||
|
||||
return Container(
|
||||
|
@ -33,10 +33,9 @@ class _ChecklistCellState extends State<ChecklistCardCell> {
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.allOptions.isEmpty) {
|
||||
if (state.tasks.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: ChecklistProgressBar(percent: state.percent),
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
|
||||
@ -92,7 +93,12 @@ class _PrimaryCellAccessoryState extends State<PrimaryCellAccessory>
|
||||
|
||||
class AccessoryHover extends StatefulWidget {
|
||||
final CellAccessory child;
|
||||
const AccessoryHover({required this.child, super.key});
|
||||
final FieldType fieldType;
|
||||
const AccessoryHover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.fieldType,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AccessoryHover> createState() => _AccessoryHoverState();
|
||||
@ -106,7 +112,7 @@ class _AccessoryHoverState extends State<AccessoryHover> {
|
||||
final List<Widget> children = [
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _isHover
|
||||
color: _isHover && widget.fieldType != FieldType.Checklist
|
||||
? AFThemeExtension.of(context).lightGreyHover
|
||||
: Colors.transparent,
|
||||
borderRadius: Corners.s6Border,
|
||||
|
@ -1,7 +1,12 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -11,24 +16,30 @@ import 'checklist_cell_editor.dart';
|
||||
import 'checklist_progress_bar.dart';
|
||||
|
||||
class ChecklistCellStyle extends GridCellStyle {
|
||||
String placeholder;
|
||||
EdgeInsets? cellPadding;
|
||||
final String placeholder;
|
||||
final EdgeInsets? cellPadding;
|
||||
final bool showTasksInline;
|
||||
|
||||
ChecklistCellStyle({
|
||||
required this.placeholder,
|
||||
const ChecklistCellStyle({
|
||||
this.placeholder = "",
|
||||
this.cellPadding,
|
||||
this.showTasksInline = false,
|
||||
});
|
||||
}
|
||||
|
||||
class GridChecklistCell extends GridCellWidget {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
late final ChecklistCellStyle? cellStyle;
|
||||
late final ChecklistCellStyle cellStyle;
|
||||
GridChecklistCell({
|
||||
required this.cellControllerBuilder,
|
||||
GridCellStyle? style,
|
||||
super.key,
|
||||
}) {
|
||||
cellStyle = style as ChecklistCellStyle?;
|
||||
if (style != null) {
|
||||
cellStyle = (style as ChecklistCellStyle);
|
||||
} else {
|
||||
cellStyle = const ChecklistCellStyle();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -38,14 +49,15 @@ class GridChecklistCell extends GridCellWidget {
|
||||
class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
||||
late ChecklistCellBloc _cellBloc;
|
||||
late final PopoverController _popover;
|
||||
bool showIncompleteOnly = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popover = PopoverController();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as ChecklistCellController;
|
||||
_cellBloc = ChecklistCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const ChecklistCellEvent.initial());
|
||||
_cellBloc = ChecklistCellBloc(cellController: cellController)
|
||||
..add(const ChecklistCellEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -53,44 +65,153 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: AppFlowyPopover(
|
||||
margin: EdgeInsets.zero,
|
||||
controller: _popover,
|
||||
constraints: BoxConstraints.loose(const Size(360, 400)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
popupBuilder: (BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onCellFocus.value = true;
|
||||
});
|
||||
return GridChecklistCellEditor(
|
||||
cellController:
|
||||
widget.cellControllerBuilder.build() as ChecklistCellController,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
if (widget.cellStyle.showTasksInline) {
|
||||
final tasks = List.from(state.tasks);
|
||||
if (showIncompleteOnly) {
|
||||
tasks.removeWhere((task) => task.isSelected);
|
||||
}
|
||||
final children = tasks
|
||||
.mapIndexed(
|
||||
(index, task) => ChecklistItem(
|
||||
task: task,
|
||||
autofocus: state.newTask && index == tasks.length - 1,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding:
|
||||
widget.cellStyle.cellPadding ?? GridSize.cellContentInsets,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: ChecklistProgressBar(percent: state.percent),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
FlowyIconButton(
|
||||
tooltipText: showIncompleteOnly
|
||||
? LocaleKeys.grid_checklist_showComplete.tr()
|
||||
: LocaleKeys.grid_checklist_hideComplete.tr(),
|
||||
width: 32,
|
||||
iconColorOnHover:
|
||||
Theme.of(context).colorScheme.onSecondary,
|
||||
icon: FlowySvg(
|
||||
showIncompleteOnly
|
||||
? FlowySvgs.show_m
|
||||
: FlowySvgs.hide_m,
|
||||
size: const Size.square(16),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(
|
||||
() => showIncompleteOnly = !showIncompleteOnly,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const VSpace(4),
|
||||
...children,
|
||||
const ChecklistItemControl(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return AppFlowyPopover(
|
||||
margin: EdgeInsets.zero,
|
||||
controller: _popover,
|
||||
constraints: BoxConstraints.loose(const Size(360, 400)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
popupBuilder: (BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onCellFocus.value = true;
|
||||
});
|
||||
return GridChecklistCellEditor(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as ChecklistCellController,
|
||||
);
|
||||
},
|
||||
onClose: () => widget.onCellFocus.value = false,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding:
|
||||
widget.cellStyle.cellPadding ?? GridSize.cellContentInsets,
|
||||
child: state.tasks.isEmpty
|
||||
? FlowyText.medium(
|
||||
widget.cellStyle.placeholder,
|
||||
color: Theme.of(context).hintColor,
|
||||
)
|
||||
: ChecklistProgressBar(percent: state.percent),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onClose: () => widget.onCellFocus.value = false,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding:
|
||||
widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.allOptions.isEmpty) {
|
||||
return FlowyText.medium(
|
||||
widget.cellStyle?.placeholder ?? "",
|
||||
color: Theme.of(context).hintColor,
|
||||
);
|
||||
}
|
||||
return ChecklistProgressBar(percent: state.percent);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void requestBeginFocus() => _popover.show();
|
||||
void requestBeginFocus() {
|
||||
if (!widget.cellStyle.showTasksInline) {
|
||||
_popover.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChecklistItemControl extends StatelessWidget {
|
||||
const ChecklistItemControl({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
|
||||
child: SizedBox(
|
||||
height: 12,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => context
|
||||
.read<ChecklistCellBloc>()
|
||||
.add(const ChecklistCellEvent.createNewTask("")),
|
||||
child: Row(
|
||||
children: [
|
||||
const Flexible(child: Center(child: Divider())),
|
||||
const HSpace(12.0),
|
||||
FlowyTooltip(
|
||||
message: LocaleKeys.grid_checklist_addNew.tr(),
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size.square(12),
|
||||
maximumSize: const Size.square(12),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onPressed: () => context
|
||||
.read<ChecklistCellBloc>()
|
||||
.add(const ChecklistCellEvent.createNewTask("")),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.add_s,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(12.0),
|
||||
const Flexible(child: Center(child: Divider())),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,20 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
part 'checklist_cell_bloc.freezed.dart';
|
||||
|
||||
class ChecklistSelectOption {
|
||||
final bool isSelected;
|
||||
final SelectOptionPB data;
|
||||
|
||||
ChecklistSelectOption(this.isSelected, this.data);
|
||||
}
|
||||
|
||||
class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
final ChecklistCellController cellController;
|
||||
final ChecklistCellBackendService _checklistCellSvc;
|
||||
final ChecklistCellBackendService _checklistCellService;
|
||||
void Function()? _onCellChangedFn;
|
||||
ChecklistCellBloc({
|
||||
required this.cellController,
|
||||
}) : _checklistCellSvc = ChecklistCellBackendService(
|
||||
}) : _checklistCellService = ChecklistCellBackendService(
|
||||
viewId: cellController.viewId,
|
||||
fieldId: cellController.fieldId,
|
||||
rowId: cellController.rowId,
|
||||
@ -23,28 +30,43 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
on<ChecklistCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
initial: () {
|
||||
_startListening();
|
||||
_loadOptions();
|
||||
},
|
||||
didReceiveOptions: (data) {
|
||||
if (data == null) {
|
||||
emit(
|
||||
const ChecklistCellState(
|
||||
allOptions: [],
|
||||
selectedOptions: [],
|
||||
tasks: [],
|
||||
percent: 0,
|
||||
newTask: false,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
allOptions: data.options,
|
||||
selectedOptions: data.selectedOptions,
|
||||
percent: data.percentage,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
tasks: _makeChecklistSelectOptions(data),
|
||||
percent: data.percentage,
|
||||
),
|
||||
);
|
||||
},
|
||||
updateTaskName: (option, name) {
|
||||
_updateOption(option, name);
|
||||
},
|
||||
selectTask: (option) async {
|
||||
await _checklistCellService.select(optionId: option.id);
|
||||
},
|
||||
createNewTask: (name) async {
|
||||
final result = await _checklistCellService.create(name: name);
|
||||
result.fold(
|
||||
(l) => emit(state.copyWith(newTask: true)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
deleteTask: (option) async {
|
||||
await _deleteOption([option]);
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -63,9 +85,6 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellFieldChanged: () {
|
||||
_loadOptions();
|
||||
},
|
||||
onCellChanged: (data) {
|
||||
if (!isClosed) {
|
||||
add(ChecklistCellEvent.didReceiveOptions(data));
|
||||
@ -74,15 +93,18 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
_checklistCellSvc.getCellData().then((result) {
|
||||
if (isClosed) return;
|
||||
void _updateOption(SelectOptionPB option, String name) async {
|
||||
final result =
|
||||
await _checklistCellService.updateName(option: option, name: name);
|
||||
|
||||
return result.fold(
|
||||
(data) => add(ChecklistCellEvent.didReceiveOptions(data)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
Future<void> _deleteOption(List<SelectOptionPB> options) async {
|
||||
final result = await _checklistCellService.delete(
|
||||
optionIds: options.map((e) => e.id).toList(),
|
||||
);
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,21 +114,53 @@ class ChecklistCellEvent with _$ChecklistCellEvent {
|
||||
const factory ChecklistCellEvent.didReceiveOptions(
|
||||
ChecklistCellDataPB? data,
|
||||
) = _DidReceiveCellUpdate;
|
||||
const factory ChecklistCellEvent.updateTaskName(
|
||||
SelectOptionPB option,
|
||||
String name,
|
||||
) = _UpdateTaskName;
|
||||
const factory ChecklistCellEvent.selectTask(SelectOptionPB task) =
|
||||
_SelectTask;
|
||||
const factory ChecklistCellEvent.createNewTask(String description) =
|
||||
_CreateNewTask;
|
||||
const factory ChecklistCellEvent.deleteTask(SelectOptionPB option) =
|
||||
_DeleteTask;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellState with _$ChecklistCellState {
|
||||
const factory ChecklistCellState({
|
||||
required List<SelectOptionPB> allOptions,
|
||||
required List<SelectOptionPB> selectedOptions,
|
||||
required List<ChecklistSelectOption> tasks,
|
||||
required double percent,
|
||||
required bool newTask,
|
||||
}) = _ChecklistCellState;
|
||||
|
||||
factory ChecklistCellState.initial(ChecklistCellController cellController) {
|
||||
return const ChecklistCellState(
|
||||
allOptions: [],
|
||||
selectedOptions: [],
|
||||
percent: 0,
|
||||
final cellData = cellController.getCellData(loadIfNotExist: true);
|
||||
|
||||
return ChecklistCellState(
|
||||
tasks: _makeChecklistSelectOptions(cellData),
|
||||
percent: cellData?.percentage ?? 0,
|
||||
newTask: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<ChecklistSelectOption> _makeChecklistSelectOptions(
|
||||
ChecklistCellDataPB? data,
|
||||
) {
|
||||
if (data == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final List<ChecklistSelectOption> options = [];
|
||||
final List<SelectOptionPB> allOptions = List.from(data.options);
|
||||
final selectedOptionIds = data.selectedOptions.map((e) => e.id).toList();
|
||||
|
||||
for (final option in allOptions) {
|
||||
options.add(
|
||||
ChecklistSelectOption(selectedOptionIds.contains(option.id), option),
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'checklist_cell_editor_bloc.dart';
|
||||
import 'checklist_cell_bloc.dart';
|
||||
import 'checklist_progress_bar.dart';
|
||||
|
||||
class GridChecklistCellEditor extends StatefulWidget {
|
||||
@ -22,12 +22,11 @@ class GridChecklistCellEditor extends StatefulWidget {
|
||||
const GridChecklistCellEditor({required this.cellController, super.key});
|
||||
|
||||
@override
|
||||
State<GridChecklistCellEditor> createState() =>
|
||||
_GridChecklistCellEditorState();
|
||||
State<GridChecklistCellEditor> createState() => _GridChecklistCellState();
|
||||
}
|
||||
|
||||
class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
late ChecklistCellEditorBloc _bloc;
|
||||
class _GridChecklistCellState extends State<GridChecklistCellEditor> {
|
||||
late ChecklistCellBloc _bloc;
|
||||
|
||||
/// Focus node for the new task text field
|
||||
late final FocusNode newTaskFocusNode;
|
||||
@ -45,17 +44,17 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
);
|
||||
_bloc = ChecklistCellEditorBloc(cellController: widget.cellController)
|
||||
..add(const ChecklistCellEditorEvent.initial());
|
||||
_bloc = ChecklistCellBloc(cellController: widget.cellController)
|
||||
..add(const ChecklistCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _bloc,
|
||||
child: BlocConsumer<ChecklistCellEditorBloc, ChecklistCellEditorState>(
|
||||
child: BlocConsumer<ChecklistCellBloc, ChecklistCellState>(
|
||||
listener: (context, state) {
|
||||
if (state.allOptions.isEmpty) {
|
||||
if (state.tasks.isEmpty) {
|
||||
newTaskFocusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
@ -65,7 +64,7 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: state.allOptions.isEmpty
|
||||
child: state.tasks.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||
@ -75,10 +74,10 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
),
|
||||
),
|
||||
ChecklistItemList(
|
||||
options: state.allOptions,
|
||||
options: state.tasks,
|
||||
onUpdateTask: () => newTaskFocusNode.requestFocus(),
|
||||
),
|
||||
if (state.allOptions.isNotEmpty)
|
||||
if (state.tasks.isNotEmpty)
|
||||
const TypeOptionSeparator(spacing: 0.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
@ -123,11 +122,15 @@ class _ChecklistItemListState extends State<ChecklistItemList> {
|
||||
|
||||
final itemList = widget.options
|
||||
.mapIndexed(
|
||||
(index, option) => ChecklistItem(
|
||||
option: option,
|
||||
onSubmitted:
|
||||
index == widget.options.length - 1 ? widget.onUpdateTask : null,
|
||||
key: ValueKey(option.data.id),
|
||||
(index, option) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ChecklistItem(
|
||||
task: option,
|
||||
onSubmitted: index == widget.options.length - 1
|
||||
? widget.onUpdateTask
|
||||
: null,
|
||||
key: ValueKey(option.data.id),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
@ -147,12 +150,14 @@ class _ChecklistItemListState extends State<ChecklistItemList> {
|
||||
/// Represents an existing task
|
||||
@visibleForTesting
|
||||
class ChecklistItem extends StatefulWidget {
|
||||
final ChecklistSelectOption option;
|
||||
final ChecklistSelectOption task;
|
||||
final VoidCallback? onSubmitted;
|
||||
final bool autofocus;
|
||||
const ChecklistItem({
|
||||
required this.option,
|
||||
required this.task,
|
||||
Key? key,
|
||||
this.onSubmitted,
|
||||
this.autofocus = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -168,7 +173,7 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textController = TextEditingController(text: widget.option.data.name);
|
||||
_textController = TextEditingController(text: widget.task.data.name);
|
||||
_focusNode = FocusNode(
|
||||
onKey: (node, event) {
|
||||
if (event is RawKeyDownEvent &&
|
||||
@ -179,72 +184,83 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
);
|
||||
if (widget.autofocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ChecklistItem oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.task.data.name != oldWidget.task.data.name &&
|
||||
!_focusNode.hasFocus) {
|
||||
_textController.text = widget.task.data.name;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final icon = FlowySvg(
|
||||
widget.option.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
widget.task.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
);
|
||||
return MouseRegion(
|
||||
onEnter: (event) => setState(() => _isHovered = true),
|
||||
onExit: (event) => setState(() => _isHovered = false),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _isHovered
|
||||
? AFThemeExtension.of(context).lightGreyHover
|
||||
: Colors.transparent,
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
constraints: BoxConstraints(maxHeight: GridSize.popoverItemHeight),
|
||||
decoration: BoxDecoration(
|
||||
color: _isHovered
|
||||
? AFThemeExtension.of(context).lightGreyHover
|
||||
: Colors.transparent,
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
FlowyIconButton(
|
||||
width: 32,
|
||||
icon: icon,
|
||||
hoverColor: Colors.transparent,
|
||||
onPressed: () => context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.selectTask(widget.task.data),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
focusNode: _focusNode,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 1,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: 6.0,
|
||||
bottom: 6.0,
|
||||
left: 2.0,
|
||||
right: _isHovered ? 2.0 : 8.0,
|
||||
),
|
||||
hintText: LocaleKeys.grid_checklist_taskHint.tr(),
|
||||
),
|
||||
onChanged: _debounceOnChangedText,
|
||||
onSubmitted: (description) {
|
||||
_submitUpdateTaskDescription(description);
|
||||
widget.onSubmitted?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_isHovered)
|
||||
FlowyIconButton(
|
||||
width: 32,
|
||||
icon: icon,
|
||||
icon: const FlowySvg(FlowySvgs.delete_s),
|
||||
hoverColor: Colors.transparent,
|
||||
onPressed: () => context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.selectTask(widget.option.data),
|
||||
iconColorOnHover: Theme.of(context).colorScheme.error,
|
||||
onPressed: () => context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.deleteTask(widget.task.data),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
focusNode: _focusNode,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 1,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 6.0,
|
||||
horizontal: 2.0,
|
||||
),
|
||||
hintText: LocaleKeys.grid_checklist_taskHint.tr(),
|
||||
),
|
||||
onChanged: _debounceOnChangedText,
|
||||
onSubmitted: (description) {
|
||||
_submitUpdateTaskDescription(description);
|
||||
widget.onSubmitted?.call();
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_isHovered)
|
||||
FlowyIconButton(
|
||||
width: 32,
|
||||
icon: const FlowySvg(FlowySvgs.delete_s),
|
||||
hoverColor: Colors.transparent,
|
||||
iconColorOnHover: Theme.of(context).colorScheme.error,
|
||||
onPressed: () => context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.deleteTask(widget.option.data),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -258,10 +274,10 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
||||
}
|
||||
|
||||
void _submitUpdateTaskDescription(String description) {
|
||||
context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.updateTaskName(
|
||||
widget.option.data,
|
||||
description,
|
||||
context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.updateTaskName(
|
||||
widget.task.data,
|
||||
description.trim(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -316,8 +332,8 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
||||
),
|
||||
onSubmitted: (taskDescription) {
|
||||
if (taskDescription.trim().isNotEmpty) {
|
||||
context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.newTask(
|
||||
context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.createNewTask(
|
||||
taskDescription.trim(),
|
||||
),
|
||||
);
|
||||
@ -340,11 +356,10 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
||||
fontColor: Theme.of(context).colorScheme.onPrimary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
onPressed: () {
|
||||
if (_textEditingController.text.trim().isNotEmpty) {
|
||||
context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.newTask(
|
||||
_textEditingController.text..trim(),
|
||||
),
|
||||
final text = _textEditingController.text.trim();
|
||||
if (text.isNotEmpty) {
|
||||
context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.createNewTask(text),
|
||||
);
|
||||
}
|
||||
widget.focusNode.requestFocus();
|
||||
|
@ -1,176 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/checklist_cell_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'checklist_cell_editor_bloc.freezed.dart';
|
||||
|
||||
class ChecklistSelectOption {
|
||||
final bool isSelected;
|
||||
final SelectOptionPB data;
|
||||
|
||||
ChecklistSelectOption(this.isSelected, this.data);
|
||||
}
|
||||
|
||||
class ChecklistCellEditorBloc
|
||||
extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
|
||||
final ChecklistCellBackendService _checklistCellService;
|
||||
final ChecklistCellController cellController;
|
||||
|
||||
ChecklistCellEditorBloc({
|
||||
required this.cellController,
|
||||
}) : _checklistCellService = ChecklistCellBackendService(
|
||||
viewId: cellController.viewId,
|
||||
fieldId: cellController.fieldId,
|
||||
rowId: cellController.rowId,
|
||||
),
|
||||
super(ChecklistCellEditorState.initial(cellController)) {
|
||||
on<ChecklistCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
_loadOptions();
|
||||
},
|
||||
didReceiveTasks: (data) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
allOptions: _makeChecklistSelectOptions(data),
|
||||
percent: data?.percentage ?? 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
newTask: (optionName) async {
|
||||
await _createOption(optionName);
|
||||
emit(
|
||||
state.copyWith(
|
||||
createOption: Some(optionName),
|
||||
),
|
||||
);
|
||||
},
|
||||
deleteTask: (option) async {
|
||||
await _deleteOption([option]);
|
||||
},
|
||||
updateTaskName: (option, name) {
|
||||
_updateOption(option, name);
|
||||
},
|
||||
selectTask: (option) async {
|
||||
await _checklistCellService.select(optionId: option.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _createOption(String name) async {
|
||||
final result = await _checklistCellService.create(name: name);
|
||||
result.fold((l) => {}, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
Future<void> _deleteOption(List<SelectOptionPB> options) async {
|
||||
final result = await _checklistCellService.delete(
|
||||
optionIds: options.map((e) => e.id).toList(),
|
||||
);
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _updateOption(SelectOptionPB option, String name) async {
|
||||
final result =
|
||||
await _checklistCellService.updateName(option: option, name: name);
|
||||
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
_checklistCellService.getCellData().then((result) {
|
||||
if (isClosed) return;
|
||||
|
||||
return result.fold(
|
||||
(data) => add(ChecklistCellEditorEvent.didReceiveTasks(data)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
cellController.startListening(
|
||||
onCellChanged: ((data) {
|
||||
if (!isClosed) {
|
||||
add(ChecklistCellEditorEvent.didReceiveTasks(data));
|
||||
}
|
||||
}),
|
||||
onCellFieldChanged: () {
|
||||
_loadOptions();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent {
|
||||
const factory ChecklistCellEditorEvent.initial() = _Initial;
|
||||
const factory ChecklistCellEditorEvent.didReceiveTasks(
|
||||
ChecklistCellDataPB? data,
|
||||
) = _DidReceiveTasks;
|
||||
const factory ChecklistCellEditorEvent.newTask(String taskName) = _NewOption;
|
||||
const factory ChecklistCellEditorEvent.selectTask(
|
||||
SelectOptionPB option,
|
||||
) = _SelectTask;
|
||||
const factory ChecklistCellEditorEvent.updateTaskName(
|
||||
SelectOptionPB option,
|
||||
String name,
|
||||
) = _UpdateTaskName;
|
||||
const factory ChecklistCellEditorEvent.deleteTask(SelectOptionPB option) =
|
||||
_DeleteTask;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellEditorState with _$ChecklistCellEditorState {
|
||||
const factory ChecklistCellEditorState({
|
||||
required List<ChecklistSelectOption> allOptions,
|
||||
required Option<String> createOption,
|
||||
required double percent,
|
||||
}) = _ChecklistCellEditorState;
|
||||
|
||||
factory ChecklistCellEditorState.initial(ChecklistCellController context) {
|
||||
final data = context.getCellData(loadIfNotExist: true);
|
||||
|
||||
return ChecklistCellEditorState(
|
||||
allOptions: _makeChecklistSelectOptions(data),
|
||||
createOption: none(),
|
||||
percent: data?.percentage ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<ChecklistSelectOption> _makeChecklistSelectOptions(
|
||||
ChecklistCellDataPB? data,
|
||||
) {
|
||||
if (data == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final List<ChecklistSelectOption> options = [];
|
||||
final List<SelectOptionPB> allOptions = List.from(data.options);
|
||||
final selectedOptionIds = data.selectedOptions.map((e) => e.id).toList();
|
||||
|
||||
for (final option in allOptions) {
|
||||
options.add(
|
||||
ChecklistSelectOption(selectedOptionIds.contains(option.id), option),
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
@ -167,7 +167,10 @@ class _PropertyCellState extends State<_PropertyCell> {
|
||||
final gesture = GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => cell.requestFocus.notify(),
|
||||
child: AccessoryHover(child: cell),
|
||||
child: AccessoryHover(
|
||||
fieldType: widget.cellContext.fieldType,
|
||||
child: cell,
|
||||
),
|
||||
);
|
||||
|
||||
return Container(
|
||||
@ -271,7 +274,8 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
|
||||
case FieldType.Checklist:
|
||||
return ChecklistCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
cellPadding: const EdgeInsets.symmetric(vertical: 6),
|
||||
showTasksInline: true,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return GridNumberCellStyle(
|
||||
|
@ -86,7 +86,6 @@ import {
|
||||
DatabaseEventUpdateCell,
|
||||
DatabaseEventGetSelectOptionCellData,
|
||||
DatabaseEventUpdateSelectOptionCell,
|
||||
DatabaseEventGetChecklistCellData,
|
||||
DatabaseEventUpdateChecklistCell,
|
||||
DatabaseEventUpdateDateCell,
|
||||
DatabaseEventExportCSV,
|
||||
@ -623,18 +622,6 @@ export async function updateSelectOptionCell(
|
||||
return result.unwrap();
|
||||
}
|
||||
|
||||
export async function getChecklistCell(viewId: string, rowId: string, fieldId: string): Promise<ChecklistCellDataPB> {
|
||||
const payload = CellIdPB.fromObject({
|
||||
view_id: viewId,
|
||||
row_id: rowId,
|
||||
field_id: fieldId,
|
||||
});
|
||||
|
||||
const result = await DatabaseEventGetChecklistCellData(payload);
|
||||
|
||||
return result.unwrap();
|
||||
}
|
||||
|
||||
export async function updateChecklistCell(
|
||||
viewId: string,
|
||||
rowId: string,
|
||||
|
@ -533,7 +533,9 @@
|
||||
"checklist": {
|
||||
"taskHint": "Task description",
|
||||
"addNew": "Add a new task",
|
||||
"submitNewTask": "Create"
|
||||
"submitNewTask": "Create",
|
||||
"hideComplete": "Hide completed tasks",
|
||||
"showComplete": "Show all tasks"
|
||||
},
|
||||
"menuName": "Grid",
|
||||
"referencedGridPrefix": "View of"
|
||||
@ -870,4 +872,4 @@
|
||||
"weAreSorry": "We're sorry",
|
||||
"loadingViewError": "We're having trouble loading this view. Please check your internet connection, refresh the app, and do not hesitate to reach out to the team if the issue continues."
|
||||
}
|
||||
}
|
||||
}
|
@ -696,16 +696,8 @@ impl FlowyCoreTest {
|
||||
field_id: &str,
|
||||
row_id: &str,
|
||||
) -> ChecklistCellDataPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(DatabaseEvent::GetChecklistCellData)
|
||||
.payload(CellIdPB {
|
||||
view_id: view_id.to_string(),
|
||||
row_id: row_id.to_string(),
|
||||
field_id: field_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<ChecklistCellDataPB>()
|
||||
let cell = self.get_cell(view_id, row_id, field_id).await;
|
||||
ChecklistCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
|
||||
}
|
||||
|
||||
pub async fn update_checklist_cell(
|
||||
|
@ -5,7 +5,6 @@ use flowy_error::{ErrorCode, FlowyError};
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::SelectOptionPB;
|
||||
use crate::services::field::checklist_type_option::ChecklistCellData;
|
||||
use crate::services::field::SelectOption;
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
@ -20,25 +19,6 @@ pub struct ChecklistCellDataPB {
|
||||
pub percentage: f64,
|
||||
}
|
||||
|
||||
impl From<ChecklistCellData> for ChecklistCellDataPB {
|
||||
fn from(cell_data: ChecklistCellData) -> Self {
|
||||
let selected_options = cell_data.selected_options();
|
||||
let percentage = cell_data.percentage_complete();
|
||||
Self {
|
||||
options: cell_data
|
||||
.options
|
||||
.into_iter()
|
||||
.map(|option| option.into())
|
||||
.collect(),
|
||||
selected_options: selected_options
|
||||
.into_iter()
|
||||
.map(|option| option.into())
|
||||
.collect(),
|
||||
percentage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct ChecklistCellDataChangesetPB {
|
||||
#[pb(index = 1)]
|
||||
|
@ -596,20 +596,6 @@ pub(crate) async fn update_select_option_cell_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn get_checklist_cell_data_handler(
|
||||
data: AFPluginData<CellIdPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> DataResult<ChecklistCellDataPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: CellIdParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
let data = database_editor
|
||||
.get_checklist_option(params.row_id, ¶ms.field_id)
|
||||
.await;
|
||||
data_result_ok(data)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn update_checklist_cell_handler(
|
||||
data: AFPluginData<ChecklistCellDataChangesetPB>,
|
||||
@ -625,7 +611,7 @@ pub(crate) async fn update_checklist_cell_handler(
|
||||
update_options: params.update_options,
|
||||
};
|
||||
database_editor
|
||||
.set_checklist_options(¶ms.view_id, params.row_id, ¶ms.field_id, changeset)
|
||||
.update_cell_with_changeset(¶ms.view_id, params.row_id, ¶ms.field_id, changeset)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -50,7 +50,6 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::GetSelectOptionCellData, get_select_option_handler)
|
||||
.event(DatabaseEvent::UpdateSelectOptionCell, update_select_option_cell_handler)
|
||||
// Checklist
|
||||
.event(DatabaseEvent::GetChecklistCellData, get_checklist_cell_data_handler)
|
||||
.event(DatabaseEvent::UpdateChecklistCell, update_checklist_cell_handler)
|
||||
// Date
|
||||
.event(DatabaseEvent::UpdateDateCell, update_date_cell_handler)
|
||||
@ -256,11 +255,8 @@ pub enum DatabaseEvent {
|
||||
#[event(input = "SelectOptionCellChangesetPB")]
|
||||
UpdateSelectOptionCell = 72,
|
||||
|
||||
#[event(input = "CellIdPB", output = "ChecklistCellDataPB")]
|
||||
GetChecklistCellData = 73,
|
||||
|
||||
#[event(input = "ChecklistCellDataChangesetPB")]
|
||||
UpdateChecklistCell = 74,
|
||||
UpdateChecklistCell = 73,
|
||||
|
||||
/// [UpdateDateCell] event is used to update a date cell's data. [DateChangesetPB]
|
||||
/// contains the date and the time string. It can be cast to [CellChangesetPB] that
|
||||
|
@ -21,7 +21,7 @@ use crate::services::cell::{
|
||||
use crate::services::database::util::database_view_setting_pb_from_view;
|
||||
use crate::services::database::UpdatedRow;
|
||||
use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
|
||||
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
|
||||
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
|
||||
use crate::services::field::{
|
||||
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
|
||||
type_option_data_from_pb_or_default, type_option_to_pb, SelectOptionCellChangeset,
|
||||
@ -858,15 +858,6 @@ impl DatabaseEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_checklist_option(&self, row_id: RowId, field_id: &str) -> ChecklistCellDataPB {
|
||||
let row_cell = self.database.lock().get_cell(field_id, &row_id);
|
||||
let cell_data = match row_cell.cell {
|
||||
None => ChecklistCellData::default(),
|
||||
Some(cell) => ChecklistCellData::from(&cell),
|
||||
};
|
||||
ChecklistCellDataPB::from(cell_data)
|
||||
}
|
||||
|
||||
pub async fn set_checklist_options(
|
||||
&self,
|
||||
view_id: &str,
|
||||
|
@ -8,7 +8,7 @@ use strum::EnumCount;
|
||||
|
||||
use event_integration::folder_event::ViewTest;
|
||||
use event_integration::FlowyCoreTest;
|
||||
use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB, SelectOptionPB};
|
||||
use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB};
|
||||
use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
|
||||
use flowy_database2::services::database::DatabaseEditor;
|
||||
use flowy_database2::services::field::checklist_type_option::{
|
||||
@ -221,7 +221,7 @@ impl DatabaseEditorTest {
|
||||
pub(crate) async fn set_checklist_cell(
|
||||
&mut self,
|
||||
row_id: RowId,
|
||||
f: Box<dyn FnOnce(Vec<SelectOptionPB>) -> Vec<String>>,
|
||||
selected_options: Vec<String>,
|
||||
) -> FlowyResult<()> {
|
||||
let field = self
|
||||
.editor
|
||||
@ -233,13 +233,8 @@ impl DatabaseEditorTest {
|
||||
})
|
||||
.unwrap()
|
||||
.clone();
|
||||
let options = self
|
||||
.editor
|
||||
.get_checklist_option(row_id.clone(), &field.id)
|
||||
.await
|
||||
.options;
|
||||
let cell_changeset = ChecklistCellChangeset {
|
||||
selected_option_ids: f(options),
|
||||
selected_option_ids: selected_options,
|
||||
..Default::default()
|
||||
};
|
||||
self
|
||||
|
@ -1,4 +1,5 @@
|
||||
use flowy_database2::entities::ChecklistFilterConditionPB;
|
||||
use flowy_database2::entities::{ChecklistFilterConditionPB, FieldType};
|
||||
use flowy_database2::services::field::checklist_type_option::ChecklistCellData;
|
||||
|
||||
use crate::database::filter_test::script::FilterScript::*;
|
||||
use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
|
||||
@ -8,10 +9,12 @@ async fn grid_filter_checklist_is_incomplete_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 6;
|
||||
let row_count = test.row_details.len();
|
||||
let option_ids = get_checklist_cell_options(&test).await;
|
||||
|
||||
let scripts = vec![
|
||||
UpdateChecklistCell {
|
||||
row_id: test.row_details[0].row.id.clone(),
|
||||
f: Box::new(|options| options.into_iter().map(|option| option.id).collect()),
|
||||
selected_option_ids: option_ids,
|
||||
},
|
||||
CreateChecklistFilter {
|
||||
condition: ChecklistFilterConditionPB::IsIncomplete,
|
||||
@ -30,10 +33,11 @@ async fn grid_filter_checklist_is_complete_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 1;
|
||||
let row_count = test.row_details.len();
|
||||
let option_ids = get_checklist_cell_options(&test).await;
|
||||
let scripts = vec![
|
||||
UpdateChecklistCell {
|
||||
row_id: test.row_details[0].row.id.clone(),
|
||||
f: Box::new(|options| options.into_iter().map(|option| option.id).collect()),
|
||||
selected_option_ids: option_ids,
|
||||
},
|
||||
CreateChecklistFilter {
|
||||
condition: ChecklistFilterConditionPB::IsComplete,
|
||||
@ -46,3 +50,20 @@ async fn grid_filter_checklist_is_complete_test() {
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
async fn get_checklist_cell_options(test: &DatabaseFilterTest) -> Vec<String> {
|
||||
let field = test.get_first_field(FieldType::Checklist);
|
||||
let row_cell = test
|
||||
.editor
|
||||
.get_cell(&field.id, &test.row_details[0].row.id)
|
||||
.await;
|
||||
row_cell
|
||||
.map_or_else(
|
||||
|| ChecklistCellData::default(),
|
||||
|cell| ChecklistCellData::from(&cell),
|
||||
)
|
||||
.options
|
||||
.into_iter()
|
||||
.map(|option| option.id)
|
||||
.collect()
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ pub enum FilterScript {
|
||||
},
|
||||
UpdateChecklistCell{
|
||||
row_id: RowId,
|
||||
f: Box<dyn FnOnce(Vec<SelectOptionPB>) -> Vec<String>> ,
|
||||
selected_option_ids: Vec<String>,
|
||||
},
|
||||
UpdateSingleSelectCell {
|
||||
row_id: RowId,
|
||||
@ -138,8 +138,8 @@ impl DatabaseFilterTest {
|
||||
self.assert_future_changed(changed).await;
|
||||
self.update_text_cell(row_id, &text).await.unwrap();
|
||||
}
|
||||
FilterScript::UpdateChecklistCell { row_id, f } => {
|
||||
self.set_checklist_cell( row_id, f).await.unwrap();
|
||||
FilterScript::UpdateChecklistCell { row_id, selected_option_ids } => {
|
||||
self.set_checklist_cell( row_id, selected_option_ids).await.unwrap();
|
||||
}
|
||||
FilterScript::UpdateSingleSelectCell { row_id, option_id, changed} => {
|
||||
self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
|
||||
|
Loading…
Reference in New Issue
Block a user