Bladeren bron

feat: checklist improve (#2653)

* feat: improve checklist

* feat: reimplement checklist

* test: fix
Nathan.fooo 2 jaren geleden
bovenliggende
commit
107662dceb
54 gewijzigde bestanden met toevoegingen van 897 en 501 verwijderingen
  1. 15 3
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart
  2. 10 0
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_loader.dart
  3. 1 0
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_service.dart
  4. 71 0
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/checklist_cell_service.dart
  5. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/select_option_cell_service.dart
  6. 3 3
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart
  7. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart
  8. 9 9
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart
  9. 23 23
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart
  10. 18 36
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart
  11. 5 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart
  12. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart
  13. 3 3
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart
  14. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart
  15. 3 2
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
  16. 2 6
      frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts
  17. 1 1
      frontend/rust-lib/flowy-database2/src/entities/field_entities.rs
  18. 98 0
      frontend/rust-lib/flowy-database2/src/entities/type_option_entities/checklist_entities.rs
  19. 2 0
      frontend/rust-lib/flowy-database2/src/entities/type_option_entities/mod.rs
  20. 7 22
      frontend/rust-lib/flowy-database2/src/entities/type_option_entities/select_option.rs
  21. 33 0
      frontend/rust-lib/flowy-database2/src/event_handler.rs
  22. 9 0
      frontend/rust-lib/flowy-database2/src/event_map.rs
  23. 38 16
      frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs
  24. 31 2
      frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs
  25. 1 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs
  26. 6 6
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
  27. 207 0
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs
  28. 107 0
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs
  29. 5 0
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/mod.rs
  30. 2 2
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs
  31. 7 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs
  32. 1 0
      frontend/rust-lib/flowy-database2/src/services/field/type_options/mod.rs
  33. 1 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_tests.rs
  34. 7 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs
  35. 2 3
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/checklist_filter.rs
  36. 0 143
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/checklist_type_option.rs
  37. 0 2
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/mod.rs
  38. 5 6
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
  39. 20 72
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_filter.rs
  40. 0 12
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_option.rs
  41. 13 18
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs
  42. 5 6
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs
  43. 5 5
      frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs
  44. 9 9
      frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs
  45. 9 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs
  46. 7 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs
  47. 8 9
      frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs
  48. 39 18
      frontend/rust-lib/flowy-database2/tests/database/database_editor.rs
  49. 8 0
      frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs
  50. 9 1
      frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs
  51. 9 10
      frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs
  52. 12 12
      frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs
  53. 3 3
      frontend/rust-lib/flowy-database2/tests/database/mock_data/mod.rs
  54. 2 1
      frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs

+ 15 - 3
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart

@@ -1,3 +1,4 @@
+import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
@@ -11,8 +12,7 @@ typedef CheckboxCellController = CellController<String, String>;
 typedef NumberCellController = CellController<String, String>;
 typedef SelectOptionCellController
     = CellController<SelectOptionCellDataPB, String>;
-typedef ChecklistCellController
-    = CellController<SelectOptionCellDataPB, String>;
+typedef ChecklistCellController = CellController<ChecklistCellDataPB, String>;
 typedef DateCellController = CellController<DateCellDataPB, DateCellData>;
 typedef URLCellController = CellController<URLCellDataPB, String>;
 
@@ -79,7 +79,6 @@ class CellControllerBuilder {
         );
       case FieldType.MultiSelect:
       case FieldType.SingleSelect:
-      case FieldType.Checklist:
         final cellDataLoader = CellDataLoader(
           cellId: _cellId,
           parser: SelectOptionCellDataParser(),
@@ -93,6 +92,19 @@ class CellControllerBuilder {
           cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
         );
 
+      case FieldType.Checklist:
+        final cellDataLoader = CellDataLoader(
+          cellId: _cellId,
+          parser: ChecklistCellDataParser(),
+          reloadOnFieldChanged: true,
+        );
+
+        return ChecklistCellController(
+          cellId: _cellId,
+          cellCache: _cellCache,
+          cellDataLoader: cellDataLoader,
+          cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
+        );
       case FieldType.URL:
         final cellDataLoader = CellDataLoader(
           cellId: _cellId,

+ 10 - 0
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_loader.dart

@@ -84,6 +84,16 @@ class SelectOptionCellDataParser
   }
 }
 
+class ChecklistCellDataParser implements CellDataParser<ChecklistCellDataPB> {
+  @override
+  ChecklistCellDataPB? parserData(List<int> data) {
+    if (data.isEmpty) {
+      return null;
+    }
+    return ChecklistCellDataPB.fromBuffer(data);
+  }
+}
+
 class URLCellDataParser implements CellDataParser<URLCellDataPB> {
   @override
   URLCellDataPB? parserData(List<int> data) {

+ 1 - 0
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_service.dart

@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'dart:collection';
+import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart';

+ 71 - 0
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/checklist_cell_service.dart

@@ -0,0 +1,71 @@
+import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
+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';
+import 'package:dartz/dartz.dart';
+
+class ChecklistCellBackendService {
+  final CellIdentifier cellId;
+
+  ChecklistCellBackendService({required this.cellId});
+
+  Future<Either<Unit, FlowyError>> create({
+    required String name,
+  }) {
+    final payload = ChecklistCellDataChangesetPB.create()
+      ..viewId = cellId.viewId
+      ..fieldId = cellId.fieldInfo.id
+      ..rowId = cellId.rowId
+      ..insertOptions.add(name);
+
+    return DatabaseEventUpdateChecklistCell(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> delete({
+    required List<String> optionIds,
+  }) {
+    final payload = ChecklistCellDataChangesetPB.create()
+      ..viewId = cellId.viewId
+      ..fieldId = cellId.fieldInfo.id
+      ..rowId = cellId.rowId
+      ..deleteOptionIds.addAll(optionIds);
+
+    return DatabaseEventUpdateChecklistCell(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> select({
+    required String optionId,
+  }) {
+    final payload = ChecklistCellDataChangesetPB.create()
+      ..viewId = cellId.viewId
+      ..fieldId = cellId.fieldInfo.id
+      ..rowId = cellId.rowId
+      ..selectedOptionIds.add(optionId);
+
+    return DatabaseEventUpdateChecklistCell(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> update({
+    required SelectOptionPB option,
+  }) {
+    final payload = ChecklistCellDataChangesetPB.create()
+      ..viewId = cellId.viewId
+      ..fieldId = cellId.fieldInfo.id
+      ..rowId = cellId.rowId
+      ..updateOptions.add(option);
+
+    return DatabaseEventUpdateChecklistCell(payload).send();
+  }
+
+  Future<Either<ChecklistCellDataPB, FlowyError>> getCellData() {
+    final payload = CellIdPB.create()
+      ..fieldId = cellId.fieldInfo.id
+      ..viewId = cellId.viewId
+      ..rowId = cellId.rowId
+      ..rowId = cellId.rowId;
+
+    return DatabaseEventGetChecklistCellData(payload).send();
+  }
+}

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart → frontend/appflowy_flutter/lib/plugins/database_view/application/cell/select_option_cell_service.dart

@@ -7,9 +7,9 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/cell_entities.pb.dart';
 
-class SelectOptionBackendService {
+class SelectOptionCellBackendService {
   final CellIdentifier cellId;
-  SelectOptionBackendService({required this.cellId});
+  SelectOptionCellBackendService({required this.cellId});
 
   String get viewId => cellId.viewId;
   String get fieldId => cellId.fieldInfo.id;

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart

@@ -17,7 +17,7 @@ import 'field_type_option_editor.dart';
 class FieldEditor extends StatefulWidget {
   final String viewId;
   final String fieldName;
-  final bool isGroupField;
+  final bool isGroupingField;
   final Function(String)? onDeleted;
   final IFieldTypeOptionLoader typeOptionLoader;
 
@@ -25,7 +25,7 @@ class FieldEditor extends StatefulWidget {
     required this.viewId,
     this.fieldName = "",
     required this.typeOptionLoader,
-    this.isGroupField = false,
+    this.isGroupingField = false,
     this.onDeleted,
     Key? key,
   }) : super(key: key);
@@ -61,7 +61,7 @@ class _FieldEditorState extends State<FieldEditor> {
       create: (context) => FieldEditorBloc(
         viewId: widget.viewId,
         fieldName: widget.fieldName,
-        isGroupField: widget.isGroupField,
+        isGroupField: widget.isGroupingField,
         loader: widget.typeOptionLoader,
       )..add(const FieldEditorEvent.initial()),
       child: ListView.builder(

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart

@@ -226,7 +226,7 @@ class _SelectOptionColorCell extends StatelessWidget {
       dimension: 16,
       child: Container(
         decoration: BoxDecoration(
-          color: color.make(context),
+          color: color.toColor(context),
           shape: BoxShape.circle,
         ),
       ),

+ 9 - 9
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart

@@ -1,22 +1,22 @@
 import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
-import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.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:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-import 'checklist_cell_editor_bloc.dart';
 part 'checklist_cell_bloc.freezed.dart';
 
 class ChecklistCardCellBloc
     extends Bloc<ChecklistCellEvent, ChecklistCellState> {
   final ChecklistCellController cellController;
-  final SelectOptionBackendService _selectOptionSvc;
+  final ChecklistCellBackendService _checklistCellSvc;
   void Function()? _onCellChangedFn;
   ChecklistCardCellBloc({
     required this.cellController,
-  })  : _selectOptionSvc =
-            SelectOptionBackendService(cellId: cellController.cellId),
+  })  : _checklistCellSvc =
+            ChecklistCellBackendService(cellId: cellController.cellId),
         super(ChecklistCellState.initial(cellController)) {
     on<ChecklistCellEvent>(
       (event, emit) async {
@@ -29,8 +29,8 @@ class ChecklistCardCellBloc
             emit(
               state.copyWith(
                 allOptions: data.options,
-                selectedOptions: data.selectOptions,
-                percent: percentFromSelectOptionCellData(data),
+                selectedOptions: data.selectedOptions,
+                percent: data.percentage,
               ),
             );
           },
@@ -63,7 +63,7 @@ class ChecklistCardCellBloc
   }
 
   void _loadOptions() {
-    _selectOptionSvc.getCellData().then((result) {
+    _checklistCellSvc.getCellData().then((result) {
       if (isClosed) return;
 
       return result.fold(
@@ -78,7 +78,7 @@ class ChecklistCardCellBloc
 class ChecklistCellEvent with _$ChecklistCellEvent {
   const factory ChecklistCellEvent.initial() = _InitialCell;
   const factory ChecklistCellEvent.didReceiveOptions(
-    SelectOptionCellDataPB data,
+    ChecklistCellDataPB data,
   ) = _DidReceiveCellUpdate;
 }
 

+ 23 - 23
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart

@@ -48,33 +48,33 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
           final List<Widget> slivers = [
             const SliverChecklistProgressBar(),
             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);
-                  },
-                ),
+              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(),
+          return Padding(
+            padding: const EdgeInsets.all(8.0),
+            child: ScrollConfiguration(
+              behavior: const ScrollBehavior().copyWith(scrollbars: false),
+              child: CustomScrollView(
+                shrinkWrap: true,
+                slivers: slivers,
+                controller: ScrollController(),
+                physics: StyledScrollPhysics(),
+              ),
             ),
           );
         },

+ 18 - 36
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart

@@ -1,7 +1,8 @@
 import 'dart:async';
 
 import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
-import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart';
+import 'package:appflowy/plugins/database_view/application/cell/checklist_cell_service.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:appflowy_backend/log.dart';
@@ -12,13 +13,13 @@ part 'checklist_cell_editor_bloc.freezed.dart';
 
 class ChecklistCellEditorBloc
     extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
-  final SelectOptionBackendService _selectOptionService;
+  final ChecklistCellBackendService _checklistCellService;
   final ChecklistCellController cellController;
 
   ChecklistCellEditorBloc({
     required this.cellController,
-  })  : _selectOptionService =
-            SelectOptionBackendService(cellId: cellController.cellId),
+  })  : _checklistCellService =
+            ChecklistCellBackendService(cellId: cellController.cellId),
         super(ChecklistCellEditorState.initial(cellController)) {
     on<ChecklistCellEditorEvent>(
       (event, emit) async {
@@ -31,7 +32,7 @@ class ChecklistCellEditorBloc
             emit(
               state.copyWith(
                 allOptions: _makeChecklistSelectOptions(data, state.predicate),
-                percent: percentFromSelectOptionCellData(data),
+                percent: data.percentage,
               ),
             );
           },
@@ -50,11 +51,7 @@ class ChecklistCellEditorBloc
             _updateOption(option);
           },
           selectOption: (option) async {
-            if (option.isSelected) {
-              await _selectOptionService.unSelect(optionIds: [option.data.id]);
-            } else {
-              await _selectOptionService.select(optionIds: [option.data.id]);
-            }
+            await _checklistCellService.select(optionId: option.data.id);
           },
           filterOption: (String predicate) {},
         );
@@ -69,20 +66,19 @@ class ChecklistCellEditorBloc
   }
 
   void _createOption(String name) async {
-    final result = await _selectOptionService.create(
-      name: name,
-      isSelected: false,
-    );
+    final result = await _checklistCellService.create(name: name);
     result.fold((l) => {}, (err) => Log.error(err));
   }
 
   void _deleteOption(List<SelectOptionPB> options) async {
-    final result = await _selectOptionService.delete(options: options);
+    final result = await _checklistCellService.delete(
+      optionIds: options.map((e) => e.id).toList(),
+    );
     result.fold((l) => null, (err) => Log.error(err));
   }
 
   void _updateOption(SelectOptionPB option) async {
-    final result = await _selectOptionService.update(
+    final result = await _checklistCellService.update(
       option: option,
     );
 
@@ -90,7 +86,7 @@ class ChecklistCellEditorBloc
   }
 
   void _loadOptions() {
-    _selectOptionService.getCellData().then((result) {
+    _checklistCellService.getCellData().then((result) {
       if (isClosed) return;
 
       return result.fold(
@@ -118,7 +114,7 @@ class ChecklistCellEditorBloc
 class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent {
   const factory ChecklistCellEditorEvent.initial() = _Initial;
   const factory ChecklistCellEditorEvent.didReceiveOptions(
-    SelectOptionCellDataPB data,
+    ChecklistCellDataPB data,
   ) = _DidReceiveOptions;
   const factory ChecklistCellEditorEvent.newOption(String optionName) =
       _NewOption;
@@ -142,34 +138,20 @@ class ChecklistCellEditorState with _$ChecklistCellEditorState {
     required String predicate,
   }) = _ChecklistCellEditorState;
 
-  factory ChecklistCellEditorState.initial(SelectOptionCellController context) {
+  factory ChecklistCellEditorState.initial(ChecklistCellController context) {
     final data = context.getCellData(loadIfNotExist: true);
 
     return ChecklistCellEditorState(
       allOptions: _makeChecklistSelectOptions(data, ''),
       createOption: none(),
-      percent: percentFromSelectOptionCellData(data),
+      percent: data?.percentage ?? 0,
       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,
+  ChecklistCellDataPB? data,
   String predicate,
 ) {
   if (data == null) {
@@ -181,7 +163,7 @@ List<ChecklistSelectOption> _makeChecklistSelectOptions(
   if (predicate.isNotEmpty) {
     allOptions.retainWhere((element) => element.name.contains(predicate));
   }
-  final selectedOptionIds = data.selectOptions.map((e) => e.id).toList();
+  final selectedOptionIds = data.selectedOptions.map((e) => e.id).toList();
 
   for (final option in allOptions) {
     options.add(

+ 5 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart

@@ -1,6 +1,8 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart';
+import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
+import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme_extension.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -19,7 +21,9 @@ class ChecklistProgressBar extends StatelessWidget {
       lineHeight: 10.0,
       percent: percent,
       padding: EdgeInsets.zero,
-      progressColor: Theme.of(context).colorScheme.primary,
+      progressColor: percent < 1.0
+          ? SelectOptionColorPB.Blue.toColor(context)
+          : SelectOptionColorPB.Green.toColor(context),
       backgroundColor: AFThemeExtension.of(context).progressBarBGColor,
       barRadius: const Radius.circular(5),
     );

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart

@@ -10,7 +10,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:appflowy/generated/locale_keys.g.dart';
 
 extension SelectOptionColorExtension on SelectOptionColorPB {
-  Color make(BuildContext context) {
+  Color toColor(BuildContext context) {
     switch (this) {
       case SelectOptionColorPB.Purple:
         return AFThemeExtension.of(context).tint1;
@@ -82,7 +82,7 @@ class SelectOptionTag extends StatelessWidget {
   }) {
     return SelectOptionTag(
       name: option.name,
-      color: option.color.make(context),
+      color: option.color.toColor(context),
       onSelected: onSelected,
       onRemove: onRemove,
     );

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart

@@ -5,19 +5,19 @@ import 'package:dartz/dartz.dart';
 import 'package:appflowy_backend/log.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'select_option_service.dart';
+import '../../../../application/cell/select_option_cell_service.dart';
 
 part 'select_option_editor_bloc.freezed.dart';
 
 class SelectOptionCellEditorBloc
     extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
-  final SelectOptionBackendService _selectOptionService;
+  final SelectOptionCellBackendService _selectOptionService;
   final SelectOptionCellController cellController;
 
   SelectOptionCellEditorBloc({
     required this.cellController,
   })  : _selectOptionService =
-            SelectOptionBackendService(cellId: cellController.cellId),
+            SelectOptionCellBackendService(cellId: cellController.cellId),
         super(SelectOptionEditorState.initial(cellController)) {
     on<SelectOptionEditorEvent>(
       (event, emit) async {

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart

@@ -309,7 +309,7 @@ class _PropertyCellState extends State<_PropertyCell> {
     return FieldEditor(
       viewId: widget.cellId.viewId,
       fieldName: widget.cellId.fieldInfo.field.name,
-      isGroupField: widget.cellId.fieldInfo.isGroupField,
+      isGroupingField: widget.cellId.fieldInfo.isGroupField,
       typeOptionLoader: FieldTypeOptionLoader(
         viewId: widget.cellId.viewId,
         field: widget.cellId.fieldInfo.field,

+ 3 - 2
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart

@@ -73,7 +73,8 @@ class ColorOptionAction extends PopoverActionCell {
                 final transaction = editorState.transaction;
                 for (final node in nodes) {
                   transaction.updateNode(node, {
-                    blockComponentBackgroundColor: color.make(context).toHex(),
+                    blockComponentBackgroundColor:
+                        color.toColor(context).toHex(),
                   });
                 }
                 editorState.apply(transaction);
@@ -91,7 +92,7 @@ class ColorOptionAction extends PopoverActionCell {
       return null;
     }
     for (final value in SelectOptionColorPB.values) {
-      if (value.make(context).toHex() == hexColor) {
+      if (value.toColor(context).toHex() == hexColor) {
         return value;
       }
     }

+ 2 - 6
frontend/appflowy_tauri/src/appflowy_app/components/_shared/database-hooks/loadField.ts

@@ -22,10 +22,9 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
 
   switch (field.field_type) {
     case FieldType.SingleSelect:
-    case FieldType.MultiSelect:
-    case FieldType.Checklist: {
+    case FieldType.MultiSelect: {
       let selectOptions: ISelectOption[] = [];
-      let typeOption: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB | ChecklistTypeOptionPB | undefined;
+      let typeOption: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB | undefined;
 
       if (field.field_type === FieldType.SingleSelect) {
         typeOption = (await makeSingleSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
@@ -39,9 +38,6 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
       if (field.field_type === FieldType.MultiSelect) {
         typeOption = (await makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
       }
-      if (field.field_type === FieldType.Checklist) {
-        typeOption = (await makeChecklistTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
-      }
 
       if (typeOption) {
         selectOptions = typeOption.options.map<ISelectOption>((option) => {

+ 1 - 1
frontend/rust-lib/flowy-database2/src/entities/field_entities.rs

@@ -592,7 +592,7 @@ impl FieldType {
     self == &MULTI_SELECT_FIELD || self == &SINGLE_SELECT_FIELD
   }
 
-  pub fn is_check_list(&self) -> bool {
+  pub fn is_checklist(&self) -> bool {
     self == &CHECKLIST_FIELD
   }
 

+ 98 - 0
frontend/rust-lib/flowy-database2/src/entities/type_option_entities/checklist_entities.rs

@@ -0,0 +1,98 @@
+use crate::entities::parser::NotEmptyStr;
+use crate::entities::SelectOptionPB;
+use crate::services::field::checklist_type_option::ChecklistCellData;
+
+use crate::services::field::SelectOption;
+use collab_database::rows::RowId;
+use flowy_derive::ProtoBuf;
+use flowy_error::{ErrorCode, FlowyError};
+
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct ChecklistCellDataPB {
+  #[pb(index = 1)]
+  pub options: Vec<SelectOptionPB>,
+
+  #[pb(index = 2)]
+  pub selected_options: Vec<SelectOptionPB>,
+
+  #[pb(index = 3)]
+  pub(crate) 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)]
+  pub view_id: String,
+
+  #[pb(index = 2)]
+  pub field_id: String,
+
+  #[pb(index = 3)]
+  pub row_id: String,
+
+  #[pb(index = 4)]
+  pub insert_options: Vec<String>,
+
+  #[pb(index = 5)]
+  pub selected_option_ids: Vec<String>,
+
+  #[pb(index = 6)]
+  pub delete_option_ids: Vec<String>,
+
+  #[pb(index = 7)]
+  pub update_options: Vec<SelectOptionPB>,
+}
+
+#[derive(Debug)]
+pub struct ChecklistCellDataChangesetParams {
+  pub view_id: String,
+  pub field_id: String,
+  pub row_id: RowId,
+  pub insert_options: Vec<String>,
+  pub selected_option_ids: Vec<String>,
+  pub delete_option_ids: Vec<String>,
+  pub update_options: Vec<SelectOption>,
+}
+
+impl TryInto<ChecklistCellDataChangesetParams> for ChecklistCellDataChangesetPB {
+  type Error = FlowyError;
+
+  fn try_into(self) -> Result<ChecklistCellDataChangesetParams, Self::Error> {
+    let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
+    let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+    let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
+
+    Ok(ChecklistCellDataChangesetParams {
+      view_id: view_id.0,
+      field_id: field_id.0,
+      row_id: RowId::from(row_id.0),
+      insert_options: self.insert_options,
+      selected_option_ids: self.selected_option_ids,
+      delete_option_ids: self.delete_option_ids,
+      update_options: self
+        .update_options
+        .into_iter()
+        .map(SelectOption::from)
+        .collect(),
+    })
+  }
+}

+ 2 - 0
frontend/rust-lib/flowy-database2/src/entities/type_option_entities/mod.rs

@@ -1,4 +1,5 @@
 mod checkbox_entities;
+mod checklist_entities;
 mod date_entities;
 mod number_entities;
 mod select_option;
@@ -6,6 +7,7 @@ mod text_entities;
 mod url_entities;
 
 pub use checkbox_entities::*;
+pub use checklist_entities::*;
 pub use date_entities::*;
 pub use number_entities::*;
 pub use select_option::*;

+ 7 - 22
frontend/rust-lib/flowy-database2/src/entities/type_option_entities/select_option.rs

@@ -1,8 +1,8 @@
 use crate::entities::parser::NotEmptyStr;
 use crate::entities::{CellIdPB, CellIdParams};
+use crate::services::field::checklist_type_option::ChecklistTypeOption;
 use crate::services::field::{
-  ChecklistTypeOption, MultiSelectTypeOption, SelectOption, SelectOptionColor,
-  SingleSelectTypeOption,
+  MultiSelectTypeOption, SelectOption, SelectOptionColor, SingleSelectTypeOption,
 };
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
@@ -287,34 +287,19 @@ impl From<MultiSelectTypeOptionPB> for MultiSelectTypeOption {
 #[derive(Clone, Debug, Default, ProtoBuf)]
 pub struct ChecklistTypeOptionPB {
   #[pb(index = 1)]
-  pub options: Vec<SelectOptionPB>,
-
-  #[pb(index = 2)]
-  pub disable_color: bool,
+  pub config: String,
 }
 
 impl From<ChecklistTypeOption> for ChecklistTypeOptionPB {
-  fn from(data: ChecklistTypeOption) -> Self {
+  fn from(_data: ChecklistTypeOption) -> Self {
     Self {
-      options: data
-        .options
-        .into_iter()
-        .map(|option| option.into())
-        .collect(),
-      disable_color: data.disable_color,
+      config: "".to_string(),
     }
   }
 }
 
 impl From<ChecklistTypeOptionPB> for ChecklistTypeOption {
-  fn from(data: ChecklistTypeOptionPB) -> Self {
-    Self {
-      options: data
-        .options
-        .into_iter()
-        .map(|option| option.into())
-        .collect(),
-      disable_color: data.disable_color,
-    }
+  fn from(_data: ChecklistTypeOptionPB) -> Self {
+    Self
   }
 }

+ 33 - 0
frontend/rust-lib/flowy-database2/src/event_handler.rs

@@ -12,6 +12,7 @@ use crate::entities::*;
 use crate::manager::DatabaseManager2;
 use crate::services::cell::CellBuilder;
 
+use crate::services::field::checklist_type_option::ChecklistCellChangeset;
 use crate::services::field::{
   type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
 };
@@ -469,6 +470,38 @@ 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<Arc<DatabaseManager2>>,
+) -> DataResult<ChecklistCellDataPB, FlowyError> {
+  let params: CellIdParams = data.into_inner().try_into()?;
+  let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
+  let data = database_editor
+    .get_checklist_option(params.row_id, &params.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>,
+  manager: AFPluginState<Arc<DatabaseManager2>>,
+) -> Result<(), FlowyError> {
+  let params: ChecklistCellDataChangesetParams = data.into_inner().try_into()?;
+  let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
+  let changeset = ChecklistCellChangeset {
+    insert_options: params.insert_options,
+    selected_option_ids: params.selected_option_ids,
+    delete_option_ids: params.delete_option_ids,
+    update_options: params.update_options,
+  };
+  database_editor
+    .set_checklist_options(&params.view_id, params.row_id, &params.field_id, changeset)
+    .await?;
+  Ok(())
+}
+
 #[tracing::instrument(level = "trace", skip_all, err)]
 pub(crate) async fn update_date_cell_handler(
   data: AFPluginData<DateChangesetPB>,

+ 9 - 0
frontend/rust-lib/flowy-database2/src/event_map.rs

@@ -44,6 +44,9 @@ pub fn init(database_manager: Arc<DatabaseManager2>) -> AFPlugin {
         .event(DatabaseEvent::DeleteSelectOption, delete_select_option_handler)
         .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)
         // Group
@@ -227,6 +230,12 @@ pub enum DatabaseEvent {
   #[event(input = "SelectOptionCellChangesetPB")]
   UpdateSelectOptionCell = 72,
 
+  #[event(input = "CellIdPB", output = "ChecklistCellDataPB")]
+  GetChecklistCellData = 73,
+
+  #[event(input = "ChecklistCellDataChangesetPB")]
+  UpdateChecklistCell = 74,
+
   /// [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
   /// will be used by the `update_cell` function.

+ 38 - 16
frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs

@@ -9,43 +9,47 @@ use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 
 use crate::entities::FieldType;
 use crate::services::cell::{CellCache, CellProtobufBlob};
+use crate::services::field::checklist_type_option::ChecklistCellChangeset;
 use crate::services::field::*;
 use crate::services::group::make_no_status_group;
 
 /// Decode the opaque cell data into readable format content
 pub trait CellDataDecoder: TypeOption {
   ///
-  /// Tries to decode the opaque cell string to `decoded_field_type`'s cell data. Sometimes, the `field_type`
-  /// of the `FieldRevision` is not equal to the `decoded_field_type`(This happened When switching
-  /// the field type of the `FieldRevision` to another field type). So the cell data is need to do
+  /// Tries to decode the [Cell] to `decoded_field_type`'s cell data. Sometimes, the `field_type`
+  /// of the `Field` is not equal to the `decoded_field_type`(This happened When switching
+  /// the field type of the `Field` to another field type). So the cell data is need to do
   /// some transformation.
   ///
-  /// For example, the current field type of the `FieldRevision` is a checkbox. When switching the field
+  /// For example, the current field type of the `Field` is a checkbox. When switching the field
   /// type from the checkbox to single select, it will create two new options,`Yes` and `No`, if they don't exist.
   /// But the data of the cell doesn't change. We can't iterate all the rows to transform the cell
   /// data that can be parsed by the current field type. One approach is to transform the cell data
-  /// when it get read. For the moment, the cell data is a string, `Yes` or `No`. It needs to compare
-  /// with the option's name, if match return the id of the option.
-  fn decode_cell_str(
+  /// when reading.
+  fn decode_cell(
     &self,
     cell: &Cell,
     decoded_field_type: &FieldType,
     field: &Field,
   ) -> FlowyResult<<Self as TypeOption>::CellData>;
 
-  /// Same as `decode_cell_data` does but Decode the cell data to readable `String`
+  /// Decode the cell data to readable `String`
   /// For example, The string of the Multi-Select cell will be a list of the option's name
   /// separated by a comma.
-  fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String;
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String;
 
-  fn decode_cell_to_str(&self, cell: &Cell) -> String;
+  /// Same as [CellDataDecoder::stringify_cell_data] but the input parameter is the [Cell]
+  fn stringify_cell(&self, cell: &Cell) -> String;
 }
 
 pub trait CellDataChangeset: TypeOption {
   /// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset`
   /// implements the `FromCellChangesetString` trait.
   /// For example,the SelectOptionCellChangeset,DateCellChangeset. etc.
+  /// # Arguments
   ///
+  /// * `changeset`: the cell changeset that represents the changes of the cell.
+  /// * `cell`: the data of the cell. It will be None if the cell does not contain any data.
   fn apply_changeset(
     &self,
     changeset: <Self as TypeOption>::CellChangeset,
@@ -109,13 +113,12 @@ pub fn get_cell_protobuf(
 ///
 /// # Arguments
 ///
-/// * `cell_str`: the opaque cell string that can be decoded by corresponding structs that implement the
-/// `FromCellString` trait.
+/// * `cell`: the opaque cell string that can be decoded by corresponding structs.
 /// * `from_field_type`: the original field type of the passed-in cell data. Check the `TypeCellData`
 /// that is used to save the origin field type of the cell data.
 /// * `to_field_type`: decode the passed-in cell data to this field type. It will use the to_field_type's
 /// TypeOption to decode this cell data.
-/// * `field_rev`: used to get the corresponding TypeOption for the specified field type.
+/// * `field`: used to get the corresponding TypeOption for the specified field type.
 ///
 /// returns: CellBytes
 ///
@@ -154,11 +157,10 @@ pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
 ///
 /// # Arguments
 ///
-/// * `cell_str`: the opaque cell string that can be decoded by corresponding structs that implement the
-/// `FromCellString` trait.
+/// * `cell`: the opaque cell string that can be decoded by corresponding structs
 /// * `to_field_type`: the cell will be decoded to this field type's cell data.
 /// * `from_field_type`: the original field type of the passed-in cell data.
-/// * `field_rev`: used to get the corresponding TypeOption for the specified field type.
+/// * `field`: used to get the corresponding TypeOption for the specified field type.
 ///
 /// returns: String
 pub fn stringify_cell_data(
@@ -223,6 +225,15 @@ pub fn insert_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell
   apply_cell_changeset(changeset, None, field, None).unwrap()
 }
 
+pub fn insert_checklist_cell(insert_options: Vec<String>, field: &Field) -> Cell {
+  let changeset = ChecklistCellChangeset {
+    insert_options,
+    ..Default::default()
+  }
+  .to_cell_changeset_str();
+  apply_cell_changeset(changeset, None, field, None).unwrap()
+}
+
 pub fn delete_select_option_cell(option_ids: Vec<String>, field: &Field) -> Cell {
   let changeset =
     SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str();
@@ -434,4 +445,15 @@ impl<'a> CellBuilder<'a> {
       },
     }
   }
+  pub fn insert_checklist_cell(&mut self, field_id: &str, option_names: Vec<String>) {
+    match self.field_maps.get(&field_id.to_owned()) {
+      None => tracing::warn!("Can't find the field with id: {}", field_id),
+      Some(field) => {
+        self.cells.insert(
+          field_id.to_owned(),
+          insert_checklist_cell(option_names, field),
+        );
+      },
+    }
+  }
 }

+ 31 - 2
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs

@@ -15,8 +15,8 @@ use flowy_task::TaskDispatcher;
 use lib_infra::future::{to_fut, Fut};
 
 use crate::entities::{
-  CalendarEventPB, CellChangesetNotifyPB, CellPB, DatabaseFieldChangesetPB, DatabasePB,
-  DatabaseViewSettingPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams,
+  CalendarEventPB, CellChangesetNotifyPB, CellPB, ChecklistCellDataPB, DatabaseFieldChangesetPB,
+  DatabasePB, DatabaseViewSettingPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams,
   FieldChangesetParams, FieldIdPB, FieldPB, FieldType, GroupPB, IndexFieldPB, InsertedRowPB,
   LayoutSettingParams, RepeatedFilterPB, RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangePB,
   SelectOptionCellDataPB, SelectOptionPB, UpdateFilterParams, UpdateSortParams,
@@ -28,6 +28,7 @@ use crate::services::cell::{
 };
 use crate::services::database::util::database_view_setting_pb_from_view;
 use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
+use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
 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,
@@ -552,6 +553,7 @@ impl DatabaseEditor {
     Ok(())
   }
 
+  /// Just create an option for the field's type option. The option is save to the database.
   pub async fn create_select_option(
     &self,
     field_id: &str,
@@ -652,6 +654,33 @@ 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 {
+      None => ChecklistCellData::default(),
+      Some(row_cell) => ChecklistCellData::from(&row_cell.cell),
+    };
+    ChecklistCellDataPB::from(cell_data)
+  }
+
+  pub async fn set_checklist_options(
+    &self,
+    view_id: &str,
+    row_id: RowId,
+    field_id: &str,
+    changeset: ChecklistCellChangeset,
+  ) -> FlowyResult<()> {
+    let field = self.database.lock().fields.get_field(field_id).ok_or(
+      FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id)),
+    )?;
+    debug_assert!(FieldType::from(field.field_type).is_checklist());
+
+    self
+      .update_cell_with_changeset(view_id, row_id, field_id, changeset)
+      .await?;
+    Ok(())
+  }
+
   #[tracing::instrument(level = "trace", skip_all, err)]
   pub async fn load_groups(&self, view_id: &str) -> FlowyResult<RepeatedGroupPB> {
     let view = self.database_views.get_view_editor(view_id).await?;

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs

@@ -41,7 +41,7 @@ mod tests {
   ) {
     assert_eq!(
       type_option
-        .decode_cell_str(
+        .decode_cell(
           &CheckboxCellData::from_cell_str(input_str).unwrap().into(),
           field_type,
           field

+ 6 - 6
frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs

@@ -67,20 +67,20 @@ impl From<CheckboxTypeOption> for TypeOptionData {
 }
 
 impl TypeOptionCellData for CheckboxTypeOption {
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType {
     cell_data
   }
 
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
     Ok(CheckboxCellData::from(cell))
   }
 }
 
 impl CellDataDecoder for CheckboxTypeOption {
-  fn decode_cell_str(
+  fn decode_cell(
     &self,
     cell: &Cell,
     decoded_field_type: &FieldType,
@@ -90,14 +90,14 @@ impl CellDataDecoder for CheckboxTypeOption {
       return Ok(Default::default());
     }
 
-    self.decode_cell(cell)
+    self.parse_cell(cell)
   }
 
-  fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
     cell_data.to_string()
   }
 
-  fn decode_cell_to_str(&self, cell: &Cell) -> String {
+  fn stringify_cell(&self, cell: &Cell) -> String {
     Self::CellData::from(cell).to_string()
   }
 }

+ 207 - 0
frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs

@@ -0,0 +1,207 @@
+use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, FieldType, SelectOptionPB};
+use crate::services::cell::{CellDataChangeset, CellDataDecoder};
+use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
+use crate::services::field::{
+  SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
+  TypeOptionCellDataFilter, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
+};
+use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
+use collab_database::rows::Cell;
+use flowy_error::FlowyResult;
+use std::cmp::Ordering;
+
+#[derive(Debug, Clone, Default)]
+pub struct ChecklistTypeOption;
+
+impl TypeOption for ChecklistTypeOption {
+  type CellData = ChecklistCellData;
+  type CellChangeset = ChecklistCellChangeset;
+  type CellProtobufType = ChecklistCellDataPB;
+  type CellFilter = ChecklistFilterPB;
+}
+
+impl From<TypeOptionData> for ChecklistTypeOption {
+  fn from(_data: TypeOptionData) -> Self {
+    Self
+  }
+}
+
+impl From<ChecklistTypeOption> for TypeOptionData {
+  fn from(_data: ChecklistTypeOption) -> Self {
+    TypeOptionDataBuilder::new().build()
+  }
+}
+
+impl TypeOptionCellData for ChecklistTypeOption {
+  fn protobuf_encode(
+    &self,
+    cell_data: <Self as TypeOption>::CellData,
+  ) -> <Self as TypeOption>::CellProtobufType {
+    let percentage = cell_data.percentage_complete();
+    let selected_options = cell_data
+      .options
+      .iter()
+      .filter(|option| cell_data.selected_option_ids.contains(&option.id))
+      .map(|option| SelectOptionPB::from(option.clone()))
+      .collect();
+
+    let options = cell_data
+      .options
+      .into_iter()
+      .map(SelectOptionPB::from)
+      .collect();
+
+    ChecklistCellDataPB {
+      options,
+      selected_options,
+      percentage,
+    }
+  }
+
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+    Ok(ChecklistCellData::from(cell))
+  }
+}
+
+impl CellDataChangeset for ChecklistTypeOption {
+  fn apply_changeset(
+    &self,
+    changeset: <Self as TypeOption>::CellChangeset,
+    cell: Option<Cell>,
+  ) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
+    match cell {
+      Some(cell) => {
+        let mut cell_data = self.parse_cell(&cell)?;
+        update_cell_data_with_changeset(&mut cell_data, changeset);
+        Ok((Cell::from(cell_data.clone()), cell_data))
+      },
+      None => {
+        let cell_data = ChecklistCellData::from_options(changeset.insert_options);
+        Ok((Cell::from(cell_data.clone()), cell_data))
+      },
+    }
+  }
+}
+
+#[inline]
+fn update_cell_data_with_changeset(
+  cell_data: &mut ChecklistCellData,
+  mut changeset: ChecklistCellChangeset,
+) {
+  // Delete the options
+  cell_data
+    .options
+    .retain(|option| !changeset.delete_option_ids.contains(&option.id));
+  cell_data
+    .selected_option_ids
+    .retain(|option_id| !changeset.delete_option_ids.contains(option_id));
+
+  // Insert new options
+  changeset.insert_options.retain(|option_name| {
+    !cell_data
+      .options
+      .iter()
+      .any(|option| option.name == *option_name)
+  });
+  changeset
+    .insert_options
+    .into_iter()
+    .for_each(|option_name| {
+      let option = SelectOption::new(&option_name);
+      cell_data.options.push(option.clone());
+    });
+
+  // Update options
+  changeset
+    .update_options
+    .into_iter()
+    .for_each(|updated_option| {
+      if let Some(option) = cell_data
+        .options
+        .iter_mut()
+        .find(|option| option.id == updated_option.id)
+      {
+        option.name = updated_option.name;
+      }
+    });
+
+  // Select the options
+  changeset
+    .selected_option_ids
+    .into_iter()
+    .for_each(|option_id| {
+      if let Some(index) = cell_data
+        .selected_option_ids
+        .iter()
+        .position(|id| **id == option_id)
+      {
+        cell_data.selected_option_ids.remove(index);
+      } else {
+        cell_data.selected_option_ids.push(option_id);
+      }
+    });
+}
+
+impl CellDataDecoder for ChecklistTypeOption {
+  fn decode_cell(
+    &self,
+    cell: &Cell,
+    decoded_field_type: &FieldType,
+    _field: &Field,
+  ) -> FlowyResult<<Self as TypeOption>::CellData> {
+    if !decoded_field_type.is_checklist() {
+      return Ok(Default::default());
+    }
+
+    self.parse_cell(cell)
+  }
+
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
+    cell_data
+      .selected_options()
+      .into_iter()
+      .map(|option| option.name)
+      .collect::<Vec<_>>()
+      .join(SELECTION_IDS_SEPARATOR)
+  }
+
+  fn stringify_cell(&self, cell: &Cell) -> String {
+    let cell_data = self.parse_cell(cell).unwrap_or_default();
+    self.stringify_cell_data(cell_data)
+  }
+}
+
+impl TypeOptionCellDataFilter for ChecklistTypeOption {
+  fn apply_filter(
+    &self,
+    filter: &<Self as TypeOption>::CellFilter,
+    field_type: &FieldType,
+    cell_data: &<Self as TypeOption>::CellData,
+  ) -> bool {
+    if !field_type.is_checklist() {
+      return true;
+    }
+    let selected_options = cell_data.selected_options();
+    filter.is_visible(&cell_data.options, &selected_options)
+  }
+}
+
+impl TypeOptionCellDataCompare for ChecklistTypeOption {
+  fn apply_cmp(
+    &self,
+    cell_data: &<Self as TypeOption>::CellData,
+    other_cell_data: &<Self as TypeOption>::CellData,
+  ) -> Ordering {
+    let left = cell_data.percentage_complete();
+    let right = other_cell_data.percentage_complete();
+    if left > right {
+      Ordering::Greater
+    } else if left < right {
+      Ordering::Less
+    } else {
+      Ordering::Equal
+    }
+  }
+}
+
+impl TypeOptionTransform for ChecklistTypeOption {}

+ 107 - 0
frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs

@@ -0,0 +1,107 @@
+use crate::entities::FieldType;
+use crate::services::cell::{FromCellChangeset, ToCellChangeset};
+use crate::services::field::{SelectOption, CELL_DATA};
+use collab::core::any_map::AnyMapExtension;
+use collab_database::rows::{new_cell_builder, Cell};
+use flowy_error::{internal_error, FlowyResult};
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+
+#[derive(Default, Clone, Debug, Serialize, Deserialize)]
+pub struct ChecklistCellData {
+  pub options: Vec<SelectOption>,
+  pub selected_option_ids: Vec<String>,
+}
+
+impl ToString for ChecklistCellData {
+  fn to_string(&self) -> String {
+    serde_json::to_string(self).unwrap_or_default()
+  }
+}
+
+impl ChecklistCellData {
+  pub fn selected_options(&self) -> Vec<SelectOption> {
+    self
+      .options
+      .iter()
+      .filter(|option| self.selected_option_ids.contains(&option.id))
+      .cloned()
+      .collect()
+  }
+
+  pub fn percentage_complete(&self) -> f64 {
+    let selected_options = self.selected_option_ids.len();
+    let total_options = self.options.len();
+
+    if total_options == 0 {
+      return 0.0;
+    }
+    (selected_options as f64) / (total_options as f64)
+  }
+
+  pub fn from_options(options: Vec<String>) -> Self {
+    let options = options
+      .into_iter()
+      .map(|option_name| SelectOption::new(&option_name))
+      .collect();
+
+    Self {
+      options,
+      ..Default::default()
+    }
+  }
+}
+
+impl From<&Cell> for ChecklistCellData {
+  fn from(cell: &Cell) -> Self {
+    cell
+      .get_str_value(CELL_DATA)
+      .map(|data| serde_json::from_str::<ChecklistCellData>(&data).unwrap_or_default())
+      .unwrap_or_default()
+  }
+}
+
+impl From<ChecklistCellData> for Cell {
+  fn from(cell_data: ChecklistCellData) -> Self {
+    let data = serde_json::to_string(&cell_data).unwrap_or_default();
+    new_cell_builder(FieldType::Checklist)
+      .insert_str_value(CELL_DATA, data)
+      .build()
+  }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+pub struct ChecklistCellChangeset {
+  /// List of option names that will be inserted
+  pub insert_options: Vec<String>,
+  pub selected_option_ids: Vec<String>,
+  pub delete_option_ids: Vec<String>,
+  pub update_options: Vec<SelectOption>,
+}
+
+impl FromCellChangeset for ChecklistCellChangeset {
+  fn from_changeset(changeset: String) -> FlowyResult<Self>
+  where
+    Self: Sized,
+  {
+    serde_json::from_str::<ChecklistCellChangeset>(&changeset).map_err(internal_error)
+  }
+}
+
+impl ToCellChangeset for ChecklistCellChangeset {
+  fn to_cell_changeset_str(&self) -> String {
+    serde_json::to_string(self).unwrap_or_default()
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  #[test]
+  fn test() {
+    let a = 1;
+    let b = 2;
+
+    let c = (a as f32) / (b as f32);
+    println!("{}", c);
+  }
+}

+ 5 - 0
frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/mod.rs

@@ -0,0 +1,5 @@
+mod checklist;
+mod checklist_entities;
+
+pub use checklist::*;
+pub use checklist_entities::*;

+ 2 - 2
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs

@@ -506,9 +506,9 @@ mod tests {
     field: &Field,
   ) -> String {
     let decoded_data = type_option
-      .decode_cell_str(cell, &FieldType::DateTime, field)
+      .decode_cell(cell, &FieldType::DateTime, field)
       .unwrap();
-    let decoded_data = type_option.convert_to_protobuf(decoded_data);
+    let decoded_data = type_option.protobuf_encode(decoded_data);
     if include_time {
       format!("{} {}", decoded_data.date, decoded_data.time)
         .trim_end()

+ 7 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs

@@ -66,14 +66,14 @@ impl From<DateTypeOption> for TypeOptionData {
 }
 
 impl TypeOptionCellData for DateTypeOption {
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType {
     self.today_desc_from_timestamp(cell_data)
   }
 
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
     Ok(DateCellData::from(cell))
   }
 }
@@ -156,7 +156,7 @@ impl DateTypeOption {
 impl TypeOptionTransform for DateTypeOption {}
 
 impl CellDataDecoder for DateTypeOption {
-  fn decode_cell_str(
+  fn decode_cell(
     &self,
     cell: &Cell,
     decoded_field_type: &FieldType,
@@ -170,16 +170,16 @@ impl CellDataDecoder for DateTypeOption {
       return Ok(Default::default());
     }
 
-    self.decode_cell(cell)
+    self.parse_cell(cell)
   }
 
-  fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
     self.today_desc_from_timestamp(cell_data).date
   }
 
-  fn decode_cell_to_str(&self, cell: &Cell) -> String {
+  fn stringify_cell(&self, cell: &Cell) -> String {
     let cell_data = Self::CellData::from(cell);
-    self.decode_cell_data_to_str(cell_data)
+    self.stringify_cell_data(cell_data)
   }
 }
 

+ 1 - 0
frontend/rust-lib/flowy-database2/src/services/field/type_options/mod.rs

@@ -1,4 +1,5 @@
 pub mod checkbox_type_option;
+pub mod checklist_type_option;
 pub mod date_type_option;
 pub mod number_type_option;
 pub mod selection_type_option;

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_tests.rs

@@ -89,7 +89,7 @@ mod tests {
   ) {
     assert_eq!(
       type_option
-        .decode_cell_str(
+        .decode_cell(
           &NumberCellData(input_str.to_owned()).into(),
           field_type,
           field

+ 7 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -96,14 +96,14 @@ impl From<NumberTypeOption> for TypeOptionData {
 }
 
 impl TypeOptionCellData for NumberTypeOption {
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType {
     ProtobufStr::from(cell_data.0)
   }
 
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
     Ok(NumberCellData::from(cell))
   }
 }
@@ -171,7 +171,7 @@ impl NumberTypeOption {
 impl TypeOptionTransform for NumberTypeOption {}
 
 impl CellDataDecoder for NumberTypeOption {
-  fn decode_cell_str(
+  fn decode_cell(
     &self,
     cell: &Cell,
     decoded_field_type: &FieldType,
@@ -181,22 +181,22 @@ impl CellDataDecoder for NumberTypeOption {
       return Ok(Default::default());
     }
 
-    let num_cell_data = self.decode_cell(cell)?;
+    let num_cell_data = self.parse_cell(cell)?;
     Ok(NumberCellData::from(
       self.format_cell_data(&num_cell_data)?.to_string(),
     ))
   }
 
-  fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
     match self.format_cell_data(&cell_data) {
       Ok(cell_data) => cell_data.to_string(),
       Err(_) => "".to_string(),
     }
   }
 
-  fn decode_cell_to_str(&self, cell: &Cell) -> String {
+  fn stringify_cell(&self, cell: &Cell) -> String {
     let cell_data = Self::CellData::from(cell);
-    self.decode_cell_data_to_str(cell_data)
+    self.stringify_cell_data(cell_data)
   }
 }
 

+ 2 - 3
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/checklist_filter.rs

@@ -1,14 +1,13 @@
 use crate::entities::{ChecklistFilterConditionPB, ChecklistFilterPB};
-use crate::services::field::{SelectOption, SelectedSelectOptions};
+use crate::services::field::SelectOption;
 
 impl ChecklistFilterPB {
   pub fn is_visible(
     &self,
     all_options: &[SelectOption],
-    selected_options: &SelectedSelectOptions,
+    selected_options: &[SelectOption],
   ) -> bool {
     let selected_option_ids = selected_options
-      .options
       .iter()
       .map(|option| option.id.as_str())
       .collect::<Vec<&str>>();

+ 0 - 143
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/checklist_type_option.rs

@@ -1,143 +0,0 @@
-use crate::entities::{ChecklistFilterPB, FieldType, SelectOptionCellDataPB};
-use crate::services::cell::CellDataChangeset;
-use crate::services::field::{
-  SelectOption, SelectOptionCellChangeset, SelectOptionIds, SelectTypeOptionSharedAction,
-  SelectedSelectOptions, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
-  TypeOptionCellDataFilter,
-};
-
-use collab::core::any_map::AnyMapExtension;
-use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
-use collab_database::rows::Cell;
-use flowy_error::FlowyResult;
-use serde::{Deserialize, Serialize};
-use std::cmp::Ordering;
-
-// Multiple select
-#[derive(Clone, Debug, Default, Serialize, Deserialize)]
-pub struct ChecklistTypeOption {
-  pub options: Vec<SelectOption>,
-  pub disable_color: bool,
-}
-
-impl TypeOption for ChecklistTypeOption {
-  type CellData = SelectOptionIds;
-  type CellChangeset = SelectOptionCellChangeset;
-  type CellProtobufType = SelectOptionCellDataPB;
-  type CellFilter = ChecklistFilterPB;
-}
-
-impl From<TypeOptionData> for ChecklistTypeOption {
-  fn from(data: TypeOptionData) -> Self {
-    data
-      .get_str_value("content")
-      .map(|s| serde_json::from_str::<ChecklistTypeOption>(&s).unwrap_or_default())
-      .unwrap_or_default()
-  }
-}
-
-impl From<ChecklistTypeOption> for TypeOptionData {
-  fn from(data: ChecklistTypeOption) -> Self {
-    let content = serde_json::to_string(&data).unwrap_or_default();
-    TypeOptionDataBuilder::new()
-      .insert_str_value("content", content)
-      .build()
-  }
-}
-
-impl TypeOptionCellData for ChecklistTypeOption {
-  fn convert_to_protobuf(
-    &self,
-    cell_data: <Self as TypeOption>::CellData,
-  ) -> <Self as TypeOption>::CellProtobufType {
-    self.get_selected_options(cell_data).into()
-  }
-
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
-    Ok(SelectOptionIds::from(cell))
-  }
-}
-
-impl SelectTypeOptionSharedAction for ChecklistTypeOption {
-  fn number_of_max_options(&self) -> Option<usize> {
-    None
-  }
-
-  fn to_type_option_data(&self) -> TypeOptionData {
-    self.clone().into()
-  }
-
-  fn options(&self) -> &Vec<SelectOption> {
-    &self.options
-  }
-
-  fn mut_options(&mut self) -> &mut Vec<SelectOption> {
-    &mut self.options
-  }
-}
-
-impl CellDataChangeset for ChecklistTypeOption {
-  fn apply_changeset(
-    &self,
-    changeset: <Self as TypeOption>::CellChangeset,
-    cell: Option<Cell>,
-  ) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
-    let insert_option_ids = changeset
-      .insert_option_ids
-      .into_iter()
-      .filter(|insert_option_id| {
-        self
-          .options
-          .iter()
-          .any(|option| &option.id == insert_option_id)
-      })
-      .collect::<Vec<String>>();
-
-    let select_option_ids = match cell {
-      None => SelectOptionIds::from(insert_option_ids),
-      Some(cell) => {
-        let mut select_ids = SelectOptionIds::from(&cell);
-        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 changeset.delete_option_ids {
-          select_ids.retain(|id| id != &delete_option_id);
-        }
-
-        select_ids
-      },
-    };
-    Ok((
-      select_option_ids.to_cell_data(FieldType::Checklist),
-      select_option_ids,
-    ))
-  }
-}
-impl TypeOptionCellDataFilter for ChecklistTypeOption {
-  fn apply_filter(
-    &self,
-    filter: &<Self as TypeOption>::CellFilter,
-    field_type: &FieldType,
-    cell_data: &<Self as TypeOption>::CellData,
-  ) -> bool {
-    if !field_type.is_check_list() {
-      return true;
-    }
-    let selected_options =
-      SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
-    filter.is_visible(&self.options, &selected_options)
-  }
-}
-
-impl TypeOptionCellDataCompare for ChecklistTypeOption {
-  fn apply_cmp(
-    &self,
-    cell_data: &<Self as TypeOption>::CellData,
-    other_cell_data: &<Self as TypeOption>::CellData,
-  ) -> Ordering {
-    cell_data.len().cmp(&other_cell_data.len())
-  }
-}

+ 0 - 2
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/mod.rs

@@ -1,5 +1,4 @@
 mod checklist_filter;
-mod checklist_type_option;
 mod multi_select_type_option;
 mod select_filter;
 mod select_ids;
@@ -9,7 +8,6 @@ mod single_select_type_option;
 mod type_option_transform;
 
 pub use checklist_filter::*;
-pub use checklist_type_option::*;
 pub use multi_select_type_option::*;
 pub use select_ids::*;
 pub use select_option::*;

+ 5 - 6
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs

@@ -11,8 +11,8 @@ use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
 use crate::services::cell::CellDataChangeset;
 use crate::services::field::{
   default_order, SelectOption, SelectOptionCellChangeset, SelectOptionIds,
-  SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionCellData,
-  TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  SelectTypeOptionSharedAction, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
+  TypeOptionCellDataFilter,
 };
 
 // Multiple select
@@ -48,14 +48,14 @@ impl From<MultiSelectTypeOption> for TypeOptionData {
 }
 
 impl TypeOptionCellData for MultiSelectTypeOption {
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType {
     self.get_selected_options(cell_data).into()
   }
 
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
     Ok(SelectOptionIds::from(cell))
   }
 }
@@ -130,8 +130,7 @@ impl TypeOptionCellDataFilter for MultiSelectTypeOption {
     if !field_type.is_multi_select() {
       return true;
     }
-    let selected_options =
-      SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
+    let selected_options = self.get_selected_options(cell_data.clone()).select_options;
     filter.is_visible(&selected_options, FieldType::MultiSelect)
   }
 }

+ 20 - 72
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_filter.rs

@@ -1,19 +1,12 @@
 #![allow(clippy::needless_collect)]
 
 use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
-use crate::services::field::SelectedSelectOptions;
+use crate::services::field::SelectOption;
 
 impl SelectOptionFilterPB {
-  pub fn is_visible(
-    &self,
-    selected_options: &SelectedSelectOptions,
-    field_type: FieldType,
-  ) -> bool {
-    let selected_option_ids: Vec<&String> = selected_options
-      .options
-      .iter()
-      .map(|option| &option.id)
-      .collect();
+  pub fn is_visible(&self, selected_options: &[SelectOption], field_type: FieldType) -> bool {
+    let selected_option_ids: Vec<&String> =
+      selected_options.iter().map(|option| &option.id).collect();
     match self.condition {
       SelectOptionConditionPB::OptionIs => match field_type {
         FieldType::SingleSelect => {
@@ -21,7 +14,7 @@ impl SelectOptionFilterPB {
             return true;
           }
 
-          if selected_options.options.is_empty() {
+          if selected_options.is_empty() {
             return false;
           }
 
@@ -54,7 +47,7 @@ impl SelectOptionFilterPB {
             return true;
           }
 
-          if selected_options.options.is_empty() {
+          if selected_options.is_empty() {
             return false;
           }
 
@@ -87,7 +80,6 @@ impl SelectOptionFilterPB {
 mod tests {
   #![allow(clippy::all)]
   use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
-  use crate::services::field::selection_type_option::SelectedSelectOptions;
   use crate::services::field::SelectOption;
 
   #[test]
@@ -98,37 +90,15 @@ mod tests {
       option_ids: vec![],
     };
 
+    assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), true);
     assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions { options: vec![] },
-        FieldType::SingleSelect
-      ),
-      true
-    );
-    assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions {
-          options: vec![option.clone()]
-        },
-        FieldType::SingleSelect
-      ),
+      filter.is_visible(&vec![option.clone()], FieldType::SingleSelect),
       false,
     );
 
+    assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), true);
     assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions { options: vec![] },
-        FieldType::MultiSelect
-      ),
-      true
-    );
-    assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions {
-          options: vec![option]
-        },
-        FieldType::MultiSelect
-      ),
+      filter.is_visible(&vec![option], FieldType::MultiSelect),
       false,
     );
   }
@@ -143,38 +113,16 @@ mod tests {
     };
 
     assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions {
-          options: vec![option_1.clone()]
-        },
-        FieldType::SingleSelect
-      ),
+      filter.is_visible(&vec![option_1.clone()], FieldType::SingleSelect),
       true
     );
-    assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions { options: vec![] },
-        FieldType::SingleSelect
-      ),
-      false,
-    );
+    assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), false,);
 
     assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions {
-          options: vec![option_1.clone()]
-        },
-        FieldType::MultiSelect
-      ),
+      filter.is_visible(&vec![option_1.clone()], FieldType::MultiSelect),
       true
     );
-    assert_eq!(
-      filter.is_visible(
-        &SelectedSelectOptions { options: vec![] },
-        FieldType::MultiSelect
-      ),
-      false,
-    );
+    assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), false,);
   }
 
   #[test]
@@ -194,7 +142,7 @@ mod tests {
       (vec![option_1.clone(), option_2.clone()], false),
     ] {
       assert_eq!(
-        filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect),
+        filter.is_visible(&options, FieldType::SingleSelect),
         is_visible
       );
     }
@@ -217,7 +165,7 @@ mod tests {
       (vec![option_1.clone(), option_2.clone()], true),
     ] {
       assert_eq!(
-        filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect),
+        filter.is_visible(&options, FieldType::SingleSelect),
         is_visible
       );
     }
@@ -238,7 +186,7 @@ mod tests {
       (vec![option_1.clone(), option_2.clone()], true),
     ] {
       assert_eq!(
-        filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect),
+        filter.is_visible(&options, FieldType::SingleSelect),
         is_visible
       );
     }
@@ -266,7 +214,7 @@ mod tests {
       (vec![], true),
     ] {
       assert_eq!(
-        filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect),
+        filter.is_visible(&options, FieldType::MultiSelect),
         is_visible
       );
     }
@@ -292,7 +240,7 @@ mod tests {
       (vec![option_3.clone()], false),
     ] {
       assert_eq!(
-        filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect),
+        filter.is_visible(&options, FieldType::MultiSelect),
         is_visible
       );
     }
@@ -308,7 +256,7 @@ mod tests {
     };
     for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] {
       assert_eq!(
-        filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect),
+        filter.is_visible(&options, FieldType::MultiSelect),
         is_visible
       );
     }

+ 0 - 12
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_option.rs

@@ -83,15 +83,3 @@ pub fn make_selected_options(ids: SelectOptionIds, options: &[SelectOption]) ->
     })
     .collect()
 }
-
-pub struct SelectedSelectOptions {
-  pub(crate) options: Vec<SelectOption>,
-}
-
-impl std::convert::From<SelectOptionCellData> for SelectedSelectOptions {
-  fn from(data: SelectOptionCellData) -> Self {
-    Self {
-      options: data.select_options,
-    }
-  }
-}

+ 13 - 18
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs

@@ -9,11 +9,12 @@ use crate::entities::{FieldType, SelectOptionCellDataPB};
 use crate::services::cell::{
   CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset,
 };
+
 use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper;
 use crate::services::field::{
-  make_selected_options, CheckboxCellData, ChecklistTypeOption, MultiSelectTypeOption,
-  SelectOption, SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption,
-  TypeOption, TypeOptionCellData, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
+  make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption,
+  SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption,
+  TypeOptionCellData, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
 };
 
 /// Defines the shared actions used by SingleSelect or Multi-Select.
@@ -124,16 +125,16 @@ impl<T> CellDataDecoder for T
 where
   T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellData,
 {
-  fn decode_cell_str(
+  fn decode_cell(
     &self,
     cell: &Cell,
     _decoded_field_type: &FieldType,
     _field: &Field,
   ) -> FlowyResult<<Self as TypeOption>::CellData> {
-    self.decode_cell(cell)
+    self.parse_cell(cell)
   }
 
-  fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
     self
       .get_selected_options(cell_data)
       .select_options
@@ -143,35 +144,29 @@ where
       .join(SELECTION_IDS_SEPARATOR)
   }
 
-  fn decode_cell_to_str(&self, cell: &Cell) -> String {
+  fn stringify_cell(&self, cell: &Cell) -> String {
     let cell_data = Self::CellData::from(cell);
-    self.decode_cell_data_to_str(cell_data)
+    self.stringify_cell_data(cell_data)
   }
 }
 
 pub fn select_type_option_from_field(
-  field_rev: &Field,
+  field: &Field,
 ) -> FlowyResult<Box<dyn SelectTypeOptionSharedAction>> {
-  let field_type = FieldType::from(field_rev.field_type);
+  let field_type = FieldType::from(field.field_type);
   match &field_type {
     FieldType::SingleSelect => {
-      let type_option = field_rev
+      let type_option = field
         .get_type_option::<SingleSelectTypeOption>(field_type)
         .unwrap_or_default();
       Ok(Box::new(type_option))
     },
     FieldType::MultiSelect => {
-      let type_option = field_rev
+      let type_option = field
         .get_type_option::<MultiSelectTypeOption>(&field_type)
         .unwrap_or_default();
       Ok(Box::new(type_option))
     },
-    FieldType::Checklist => {
-      let type_option = field_rev
-        .get_type_option::<ChecklistTypeOption>(&field_type)
-        .unwrap_or_default();
-      Ok(Box::new(type_option))
-    },
     ty => {
       tracing::error!("Unsupported field type: {:?} for this handler", ty);
       Err(ErrorCode::FieldInvalidOperation.into())

+ 5 - 6
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs

@@ -1,8 +1,8 @@
 use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
 use crate::services::cell::CellDataChangeset;
 use crate::services::field::{
-  default_order, SelectOption, SelectedSelectOptions, TypeOption, TypeOptionCellData,
-  TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  default_order, SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
+  TypeOptionCellDataFilter,
 };
 use crate::services::field::{
   SelectOptionCellChangeset, SelectOptionIds, SelectTypeOptionSharedAction,
@@ -47,14 +47,14 @@ impl From<SingleSelectTypeOption> for TypeOptionData {
 }
 
 impl TypeOptionCellData for SingleSelectTypeOption {
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType {
     self.get_selected_options(cell_data).into()
   }
 
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
     Ok(SelectOptionIds::from(cell))
   }
 }
@@ -121,8 +121,7 @@ impl TypeOptionCellDataFilter for SingleSelectTypeOption {
     if !field_type.is_single_select() {
       return true;
     }
-    let selected_options =
-      SelectedSelectOptions::from(self.get_selected_options(cell_data.clone()));
+    let selected_options = self.get_selected_options(cell_data.clone()).select_options;
     filter.is_visible(&selected_options, FieldType::SingleSelect)
   }
 }

+ 5 - 5
frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs

@@ -86,20 +86,20 @@ impl TypeOptionTransform for RichTextTypeOption {
 }
 
 impl TypeOptionCellData for RichTextTypeOption {
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType {
     ProtobufStr::from(cell_data.0)
   }
 
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
     Ok(StrCellData::from(cell))
   }
 }
 
 impl CellDataDecoder for RichTextTypeOption {
-  fn decode_cell_str(
+  fn decode_cell(
     &self,
     cell: &Cell,
     _decoded_field_type: &FieldType,
@@ -108,11 +108,11 @@ impl CellDataDecoder for RichTextTypeOption {
     Ok(StrCellData::from(cell))
   }
 
-  fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
     cell_data.to_string()
   }
 
-  fn decode_cell_to_str(&self, cell: &Cell) -> String {
+  fn stringify_cell(&self, cell: &Cell) -> String {
     Self::CellData::from(cell).to_string()
   }
 }

+ 9 - 9
frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs

@@ -14,9 +14,10 @@ use crate::entities::{
   URLTypeOptionPB,
 };
 use crate::services::cell::{CellDataDecoder, FromCellChangeset, ToCellChangeset};
+use crate::services::field::checklist_type_option::ChecklistTypeOption;
 use crate::services::field::{
-  CheckboxTypeOption, ChecklistTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
-  RichTextTypeOption, SingleSelectTypeOption, URLTypeOption,
+  CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
+  SingleSelectTypeOption, URLTypeOption,
 };
 use crate::services::filter::FromFilterString;
 
@@ -53,20 +54,19 @@ pub trait TypeOption {
 }
 
 pub trait TypeOptionCellData: TypeOption {
-  /// Convert the decoded cell data into corresponding `Protobuf struct`.
+  /// Encode the cell data into corresponding `Protobuf struct`.
   /// For example:
   ///    FieldType::URL => URLCellDataPB
   ///    FieldType::Date=> DateCellDataPB
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType;
 
-  /// Decodes the opaque cell string to corresponding data struct.
-  // For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell
-  // data can not directly show to user. So it needs to be encode as the date string with custom
-  // format setting. Encode `1647251762` to `"Mar 14,2022`
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
+  /// Parse the opaque [Cell] to corresponding data struct.
+  /// The [Cell] is a map that stores list of key/value data. Each [TypeOption::CellData]
+  /// should implement the From<&Cell> trait to parse the [Cell] to corresponding data struct.
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
 }
 
 pub trait TypeOptionTransform: TypeOption {

+ 9 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs

@@ -13,10 +13,11 @@ use crate::services::cell::{
   CellCache, CellDataChangeset, CellDataDecoder, CellFilterCache, CellProtobufBlob,
   FromCellChangeset,
 };
+use crate::services::field::checklist_type_option::ChecklistTypeOption;
 use crate::services::field::{
-  CheckboxTypeOption, ChecklistTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
-  RichTextTypeOption, SingleSelectTypeOption, TypeOption, TypeOptionCellData,
-  TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOption,
+  CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
+  SingleSelectTypeOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
+  TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOption,
 };
 
 pub const CELL_DATA: &str = "data";
@@ -36,6 +37,7 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
     field_rev: &Field,
   ) -> FlowyResult<CellProtobufBlob>;
 
+  // TODO(nathan): replace cell_changeset with BoxAny to get rid of the serde process.
   fn handle_cell_changeset(
     &self,
     cell_changeset: String,
@@ -141,7 +143,7 @@ where
       }
     }
 
-    let cell_data = self.decode_cell_str(cell, decoded_field_type, field)?;
+    let cell_data = self.decode_cell(cell, decoded_field_type, field)?;
     if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
       // tracing::trace!(
       //   "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
@@ -217,7 +219,7 @@ where
       .get_cell_data(cell, decoded_field_type, field_rev)?
       .unbox_or_default::<<Self as TypeOption>::CellData>();
 
-    CellProtobufBlob::from(self.convert_to_protobuf(cell_data))
+    CellProtobufBlob::from(self.protobuf_encode(cell_data))
   }
 
   fn handle_cell_changeset(
@@ -265,10 +267,10 @@ where
     if self.transformable() {
       let cell_data = self.transform_type_option_cell(cell, field_type, field);
       if let Some(cell_data) = cell_data {
-        return self.decode_cell_data_to_str(cell_data);
+        return self.stringify_cell_data(cell_data);
       }
     }
-    self.decode_cell_to_str(cell)
+    self.stringify_cell(cell)
   }
 
   fn get_cell_data(

+ 7 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs

@@ -47,20 +47,20 @@ impl From<URLTypeOption> for TypeOptionData {
 impl TypeOptionTransform for URLTypeOption {}
 
 impl TypeOptionCellData for URLTypeOption {
-  fn convert_to_protobuf(
+  fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
   ) -> <Self as TypeOption>::CellProtobufType {
     cell_data.into()
   }
 
-  fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
+  fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
     Ok(URLCellData::from(cell))
   }
 }
 
 impl CellDataDecoder for URLTypeOption {
-  fn decode_cell_str(
+  fn decode_cell(
     &self,
     cell: &Cell,
     decoded_field_type: &FieldType,
@@ -70,16 +70,16 @@ impl CellDataDecoder for URLTypeOption {
       return Ok(Default::default());
     }
 
-    self.decode_cell(cell)
+    self.parse_cell(cell)
   }
 
-  fn decode_cell_data_to_str(&self, cell_data: <Self as TypeOption>::CellData) -> String {
+  fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
     cell_data.data
   }
 
-  fn decode_cell_to_str(&self, cell: &Cell) -> String {
+  fn stringify_cell(&self, cell: &Cell) -> String {
     let cell_data = Self::CellData::from(cell);
-    self.decode_cell_data_to_str(cell_data)
+    self.stringify_cell_data(cell_data)
   }
 }
 

+ 8 - 9
frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs

@@ -1,8 +1,9 @@
 use flowy_database2::entities::{CellChangesetPB, FieldType};
 use flowy_database2::services::cell::ToCellChangeset;
+use flowy_database2::services::field::checklist_type_option::ChecklistCellChangeset;
 use flowy_database2::services::field::{
-  ChecklistTypeOption, DateCellData, MultiSelectTypeOption, SelectOptionCellChangeset,
-  SingleSelectTypeOption, StrCellData, URLCellData,
+  DateCellData, MultiSelectTypeOption, SelectOptionCellChangeset, SingleSelectTypeOption,
+  StrCellData, URLCellData,
 };
 
 use crate::database::cell_test::script::CellScript::UpdateCell;
@@ -39,13 +40,11 @@ async fn grid_cell_update() {
           SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id)
             .to_cell_changeset_str()
         },
-        FieldType::Checklist => {
-          let type_option = field
-            .get_type_option::<ChecklistTypeOption>(field.field_type)
-            .unwrap();
-          SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id)
-            .to_cell_changeset_str()
-        },
+        FieldType::Checklist => ChecklistCellChangeset {
+          insert_options: vec!["new option".to_string()],
+          ..Default::default()
+        }
+        .to_cell_changeset_str(),
         FieldType::Checkbox => "1".to_string(),
         FieldType::URL => "1".to_string(),
       };

+ 39 - 18
frontend/rust-lib/flowy-database2/tests/database/database_editor.rs

@@ -5,11 +5,14 @@ use collab_database::fields::Field;
 use collab_database::rows::{CreateRowParams, Row, RowId};
 use strum::EnumCount;
 
-use flowy_database2::entities::{DatabaseLayoutPB, FieldType, FilterPB, RowPB};
+use flowy_database2::entities::{DatabaseLayoutPB, FieldType, FilterPB, RowPB, SelectOptionPB};
 use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
 use flowy_database2::services::database::DatabaseEditor;
+use flowy_database2::services::field::checklist_type_option::{
+  ChecklistCellChangeset, ChecklistTypeOption,
+};
 use flowy_database2::services::field::{
-  CheckboxTypeOption, ChecklistTypeOption, DateCellChangeset, MultiSelectTypeOption, SelectOption,
+  CheckboxTypeOption, DateCellChangeset, MultiSelectTypeOption, SelectOption,
   SelectOptionCellChangeset, SingleSelectTypeOption,
 };
 use flowy_database2::services::share::csv::{CSVFormat, ImportResult};
@@ -205,6 +208,36 @@ impl DatabaseEditorTest {
       .await
   }
 
+  pub(crate) async fn set_checklist_cell(
+    &mut self,
+    row_id: RowId,
+    f: Box<dyn FnOnce(Vec<SelectOptionPB>) -> Vec<String>>,
+  ) -> FlowyResult<()> {
+    let field = self
+      .editor
+      .get_fields(&self.view_id, None)
+      .iter()
+      .find(|field| {
+        let field_type = FieldType::from(field.field_type);
+        field_type == FieldType::Checklist
+      })
+      .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),
+      ..Default::default()
+    };
+    self
+      .editor
+      .set_checklist_options(&self.view_id, row_id, &field.id, cell_changeset)
+      .await
+  }
+
   pub(crate) async fn update_single_select_cell(
     &mut self,
     row_id: RowId,
@@ -320,7 +353,7 @@ impl<'a> TestRowBuilder<'a> {
   {
     let single_select_field = self.field_with_type(&FieldType::SingleSelect);
     let type_option = single_select_field
-      .get_type_option::<ChecklistTypeOption>(FieldType::SingleSelect)
+      .get_type_option::<SingleSelectTypeOption>(FieldType::SingleSelect)
       .unwrap();
     let option = f(type_option.options);
     self
@@ -336,7 +369,7 @@ impl<'a> TestRowBuilder<'a> {
   {
     let multi_select_field = self.field_with_type(&FieldType::MultiSelect);
     let type_option = multi_select_field
-      .get_type_option::<ChecklistTypeOption>(FieldType::MultiSelect)
+      .get_type_option::<MultiSelectTypeOption>(FieldType::MultiSelect)
       .unwrap();
     let options = f(type_option.options);
     let ops_ids = options
@@ -350,23 +383,11 @@ impl<'a> TestRowBuilder<'a> {
     multi_select_field.id.clone()
   }
 
-  pub fn insert_checklist_cell<F>(&mut self, f: F) -> String
-  where
-    F: Fn(Vec<SelectOption>) -> Vec<SelectOption>,
-  {
+  pub fn insert_checklist_cell(&mut self, option_names: Vec<String>) -> String {
     let checklist_field = self.field_with_type(&FieldType::Checklist);
-    let type_option = checklist_field
-      .get_type_option::<ChecklistTypeOption>(FieldType::Checklist)
-      .unwrap();
-    let options = f(type_option.options);
-    let ops_ids = options
-      .iter()
-      .map(|option| option.id.clone())
-      .collect::<Vec<_>>();
     self
       .cell_build
-      .insert_select_option_cell(&checklist_field.id, ops_ids);
-
+      .insert_checklist_cell(&checklist_field.id, option_names);
     checklist_field.id.clone()
   }
 

+ 8 - 0
frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs

@@ -8,6 +8,10 @@ async fn grid_filter_checklist_is_incomplete_test() {
   let expected = 5;
   let row_count = test.rows.len();
   let scripts = vec![
+    UpdateChecklistCell {
+      row_id: test.rows[0].id.clone(),
+      f: Box::new(|options| options.into_iter().map(|option| option.id).collect()),
+    },
     CreateChecklistFilter {
       condition: ChecklistFilterConditionPB::IsIncomplete,
       changed: Some(FilterRowChanged {
@@ -26,6 +30,10 @@ async fn grid_filter_checklist_is_complete_test() {
   let expected = 1;
   let row_count = test.rows.len();
   let scripts = vec![
+    UpdateChecklistCell {
+      row_id: test.rows[0].id.clone(),
+      f: Box::new(|options| options.into_iter().map(|option| option.id).collect()),
+    },
     CreateChecklistFilter {
       condition: ChecklistFilterConditionPB::IsComplete,
       changed: Some(FilterRowChanged {

+ 9 - 1
frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs

@@ -10,8 +10,9 @@ use collab_database::rows::{Row, RowId};
 use futures::TryFutureExt;
 use tokio::sync::broadcast::Receiver;
 
-use flowy_database2::entities::{UpdateFilterParams, UpdateFilterPayloadPB, CheckboxFilterConditionPB, CheckboxFilterPB, ChecklistFilterConditionPB, ChecklistFilterPB, DatabaseViewSettingPB, DateFilterConditionPB, DateFilterPB, DeleteFilterParams, FieldType, FilterPB, NumberFilterConditionPB, NumberFilterPB, SelectOptionConditionPB, SelectOptionFilterPB, TextFilterConditionPB, TextFilterPB};
+use flowy_database2::entities::{UpdateFilterParams, UpdateFilterPayloadPB, CheckboxFilterConditionPB, CheckboxFilterPB, ChecklistFilterConditionPB, ChecklistFilterPB, DatabaseViewSettingPB, DateFilterConditionPB, DateFilterPB, DeleteFilterParams, FieldType, FilterPB, NumberFilterConditionPB, NumberFilterPB, SelectOptionConditionPB, SelectOptionFilterPB, TextFilterConditionPB, TextFilterPB, SelectOptionPB};
 use flowy_database2::services::database_view::DatabaseViewChanged;
+use flowy_database2::services::field::SelectOption;
 use flowy_database2::services::filter::FilterType;
 
 use crate::database::database_editor::DatabaseEditorTest;
@@ -27,6 +28,10 @@ pub enum FilterScript {
         text: String,
        changed: Option<FilterRowChanged>,
     },
+    UpdateChecklistCell{
+        row_id: RowId,
+        f: Box<dyn FnOnce(Vec<SelectOptionPB>) -> Vec<String>> ,
+    },
     UpdateSingleSelectCell {
         row_id: RowId,
         option_id: String,
@@ -133,6 +138,9 @@ 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::UpdateSingleSelectCell { row_id, option_id, changed} => {
                 self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap());
                 self.assert_future_changed(changed).await;

+ 9 - 10
frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs

@@ -3,15 +3,14 @@
 // #![allow(unused_imports)]
 
 use crate::database::database_editor::TestRowBuilder;
-use crate::database::mock_data::{
-  COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER,
-};
+use crate::database::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, PLANNED, TWITTER};
 use collab_database::database::{gen_database_id, gen_database_view_id, DatabaseData};
 use collab_database::views::{DatabaseLayout, DatabaseView};
 use flowy_database2::entities::FieldType;
+use flowy_database2::services::field::checklist_type_option::ChecklistTypeOption;
 use flowy_database2::services::field::{
-  ChecklistTypeOption, DateFormat, DateTypeOption, FieldBuilder, MultiSelectTypeOption,
-  SelectOption, SelectOptionColor, SingleSelectTypeOption, TimeFormat,
+  DateFormat, DateTypeOption, FieldBuilder, MultiSelectTypeOption, SelectOption, SelectOptionColor,
+  SingleSelectTypeOption, TimeFormat,
 };
 use strum::IntoEnumIterator;
 
@@ -102,11 +101,11 @@ pub fn make_test_board() -> DatabaseData {
         fields.push(url);
       },
       FieldType::Checklist => {
-        let option1 = SelectOption::with_color(FIRST_THING, SelectOptionColor::Purple);
-        let option2 = SelectOption::with_color(SECOND_THING, SelectOptionColor::Orange);
-        let option3 = SelectOption::with_color(THIRD_THING, SelectOptionColor::Yellow);
-        let mut type_option = ChecklistTypeOption::default();
-        type_option.options.extend(vec![option1, option2, option3]);
+        // let option1 = SelectOption::with_color(FIRST_THING, SelectOptionColor::Purple);
+        // let option2 = SelectOption::with_color(SECOND_THING, SelectOptionColor::Orange);
+        // let option3 = SelectOption::with_color(THIRD_THING, SelectOptionColor::Yellow);
+        let type_option = ChecklistTypeOption::default();
+        // type_option.options.extend(vec![option1, option2, option3]);
         let checklist_field = FieldBuilder::new(field_type.clone(), type_option)
           .name("TODO")
           .visibility(true)

+ 12 - 12
frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs

@@ -1,16 +1,14 @@
-use crate::database::mock_data::{
-  COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER,
-};
+use crate::database::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, PLANNED, TWITTER};
 use collab_database::database::{gen_database_id, gen_database_view_id, DatabaseData};
 
 use collab_database::views::{DatabaseLayout, DatabaseView};
 
 use crate::database::database_editor::TestRowBuilder;
 use flowy_database2::entities::FieldType;
+use flowy_database2::services::field::checklist_type_option::ChecklistTypeOption;
 use flowy_database2::services::field::{
-  ChecklistTypeOption, DateFormat, DateTypeOption, FieldBuilder, MultiSelectTypeOption,
-  NumberFormat, NumberTypeOption, SelectOption, SelectOptionColor, SingleSelectTypeOption,
-  TimeFormat,
+  DateFormat, DateTypeOption, FieldBuilder, MultiSelectTypeOption, NumberFormat, NumberTypeOption,
+  SelectOption, SelectOptionColor, SingleSelectTypeOption, TimeFormat,
 };
 use strum::IntoEnumIterator;
 
@@ -103,11 +101,11 @@ pub fn make_test_grid() -> DatabaseData {
         fields.push(url);
       },
       FieldType::Checklist => {
-        let option1 = SelectOption::with_color(FIRST_THING, SelectOptionColor::Purple);
-        let option2 = SelectOption::with_color(SECOND_THING, SelectOptionColor::Orange);
-        let option3 = SelectOption::with_color(THIRD_THING, SelectOptionColor::Yellow);
-        let mut type_option = ChecklistTypeOption::default();
-        type_option.options.extend(vec![option1, option2, option3]);
+        // let option1 = SelectOption::with_color(FIRST_THING, SelectOptionColor::Purple);
+        // let option2 = SelectOption::with_color(SECOND_THING, SelectOptionColor::Orange);
+        // let option3 = SelectOption::with_color(THIRD_THING, SelectOptionColor::Yellow);
+        let type_option = ChecklistTypeOption::default();
+        // type_option.options.extend(vec![option1, option2, option3]);
         let checklist_field = FieldBuilder::new(field_type.clone(), type_option)
           .name("TODO")
           .visibility(true)
@@ -135,11 +133,13 @@ pub fn make_test_grid() -> DatabaseData {
               ),
             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"),
             FieldType::URL => {
               row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io")
             },
+            FieldType::Checklist => {
+              row_builder.insert_checklist_cell(vec!["First thing".to_string()])
+            },
             _ => "".to_owned(),
           };
         }

+ 3 - 3
frontend/rust-lib/flowy-database2/tests/database/mock_data/mod.rs

@@ -14,6 +14,6 @@ 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";
+// 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";

+ 2 - 1
frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs

@@ -28,13 +28,14 @@ async fn export_csv_test() {
   let database = test.editor.clone();
   let s = database.export_csv(CSVFormat::Original).await.unwrap();
   let expected = r#"Name,Price,Time,Status,Platform,is urgent,link,TODO,Updated At,Created At
-A,$1,2022/03/14,,"Google,Facebook",Yes,AppFlowy website - https://www.appflowy.io,"Wake up at 6:00 am,Get some coffee,Start working",2022/03/14,2022/03/14
+A,$1,2022/03/14,,"Google,Facebook",Yes,AppFlowy website - https://www.appflowy.io,,2022/03/14,2022/03/14
 ,$2,2022/03/14,,"Google,Twitter",Yes,,,2022/03/14,2022/03/14
 C,$3,2022/03/14,Completed,Facebook,No,,,2022/03/14,2022/03/14
 DA,$14,2022/11/17,Completed,,No,,,2022/11/17,2022/11/17
 AE,,2022/11/13,Planned,,No,,,2022/11/13,2022/11/13
 AE,$5,2022/12/24,Planned,,Yes,,,2022/12/24,2022/12/24
 "#;
+  println!("{}", s);
   assert_eq!(s, expected);
 }