mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: checklist cell did get notified after the cell content change
This commit is contained in:
parent
3cdd6665b3
commit
29e07089ca
@ -51,7 +51,11 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
onCellFieldChanged: () {
|
||||
_loadOptions();
|
||||
},
|
||||
onCellChanged: (_) {},
|
||||
onCellChanged: (data) {
|
||||
if (!isClosed && data != null) {
|
||||
add(ChecklistCellEvent.didReceiveOptions(data));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ class ChecklistCellEditorBloc
|
||||
extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
|
||||
final SelectOptionFFIService _selectOptionService;
|
||||
final GridChecklistCellController cellController;
|
||||
Timer? _delayOperation;
|
||||
|
||||
ChecklistCellEditorBloc({
|
||||
required this.cellController,
|
||||
@ -27,7 +26,6 @@ class ChecklistCellEditorBloc
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
_loadOptions();
|
||||
},
|
||||
didReceiveOptions: (data) {
|
||||
emit(state.copyWith(
|
||||
@ -47,11 +45,12 @@ class ChecklistCellEditorBloc
|
||||
updateOption: (option) {
|
||||
_updateOption(option);
|
||||
},
|
||||
selectOption: (optionId) {
|
||||
_selectOptionService.select(optionIds: [optionId]);
|
||||
},
|
||||
unSelectOption: (optionId) {
|
||||
_selectOptionService.unSelect(optionIds: [optionId]);
|
||||
selectOption: (option) async {
|
||||
if (option.isSelected) {
|
||||
await _selectOptionService.unSelect(optionIds: [option.data.id]);
|
||||
} else {
|
||||
await _selectOptionService.select(optionIds: [option.data.id]);
|
||||
}
|
||||
},
|
||||
filterOption: (String predicate) {},
|
||||
);
|
||||
@ -61,7 +60,6 @@ class ChecklistCellEditorBloc
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_delayOperation?.cancel();
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
@ -119,10 +117,8 @@ class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent {
|
||||
SelectOptionCellDataPB data) = _DidReceiveOptions;
|
||||
const factory ChecklistCellEditorEvent.newOption(String optionName) =
|
||||
_NewOption;
|
||||
const factory ChecklistCellEditorEvent.selectOption(String optionId) =
|
||||
_SelectOption;
|
||||
const factory ChecklistCellEditorEvent.unSelectOption(String optionId) =
|
||||
_UnSelectOption;
|
||||
const factory ChecklistCellEditorEvent.selectOption(
|
||||
ChecklistSelectOption option) = _SelectOption;
|
||||
const factory ChecklistCellEditorEvent.updateOption(SelectOptionPB option) =
|
||||
_UpdateOption;
|
||||
const factory ChecklistCellEditorEvent.deleteOption(SelectOptionPB option) =
|
||||
|
@ -522,6 +522,7 @@ class FieldInfo {
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.RichText:
|
||||
case FieldType.SingleSelect:
|
||||
// case FieldType.Checklist:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -36,20 +36,16 @@ class GridChecklistCellState extends State<GridChecklistCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
return Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: _wrapPopover(const ChecklistProgressBar()),
|
||||
),
|
||||
InkWell(onTap: () => _popover.show()),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: _wrapPopover(const ChecklistProgressBar()),
|
||||
),
|
||||
InkWell(onTap: () => _popover.show()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/s
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
@ -24,9 +25,11 @@ class GridChecklistCellEditor extends StatefulWidget {
|
||||
|
||||
class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
late ChecklistCellEditorBloc bloc;
|
||||
late PopoverMutex popoverMutex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
popoverMutex = PopoverMutex();
|
||||
bloc = ChecklistCellEditorBloc(cellController: widget.cellController);
|
||||
bloc.add(const ChecklistCellEditorEvent.initial());
|
||||
super.initState();
|
||||
@ -47,23 +50,28 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
final List<Widget> slivers = [
|
||||
const SliverChecklistPrograssBar(),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(color: Colors.red, height: 2, width: 2100)),
|
||||
SliverToBoxAdapter(
|
||||
child: ListView.separated(
|
||||
controller: ScrollController(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.allOptions.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _ChecklistOptionCell(option: state.allOptions[index]);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: ListView.separated(
|
||||
controller: ScrollController(),
|
||||
shrinkWrap: true,
|
||||
itemCount: state.allOptions.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _ChecklistOptionCell(
|
||||
option: state.allOptions[index],
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
|
||||
return ScrollConfiguration(
|
||||
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
slivers: slivers,
|
||||
@ -79,8 +87,10 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
|
||||
class _ChecklistOptionCell extends StatefulWidget {
|
||||
final ChecklistSelectOption option;
|
||||
final PopoverMutex popoverMutex;
|
||||
const _ChecklistOptionCell({
|
||||
required this.option,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -107,10 +117,15 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
icon,
|
||||
const HSpace(6),
|
||||
FlowyText(widget.option.data.name),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
child: FlowyButton(
|
||||
text: FlowyText(widget.option.data.name),
|
||||
leftIcon: icon,
|
||||
onTap: () => context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.selectOption(widget.option)),
|
||||
),
|
||||
),
|
||||
_disclosureButton(),
|
||||
],
|
||||
),
|
||||
@ -122,8 +137,7 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
|
||||
return FlowyIconButton(
|
||||
width: 20,
|
||||
onPressed: () => _popoverController.show(),
|
||||
hoverColor: Colors.transparent,
|
||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||
icon: svgWidget(
|
||||
"editor/details",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
@ -137,15 +151,30 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
|
||||
offset: const Offset(20, 0),
|
||||
asBarrier: true,
|
||||
constraints: BoxConstraints.loose(const Size(200, 300)),
|
||||
mutex: widget.popoverMutex,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
child: child,
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return SelectOptionTypeOptionEditor(
|
||||
option: widget.option.data,
|
||||
onDeleted: () {},
|
||||
onUpdated: (updatedOption) {},
|
||||
onDeleted: () {
|
||||
context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.deleteOption(widget.option.data),
|
||||
);
|
||||
|
||||
_popoverController.close();
|
||||
},
|
||||
onUpdated: (updatedOption) {
|
||||
context.read<ChecklistCellEditorBloc>().add(
|
||||
ChecklistCellEditorEvent.updateOption(widget.option.data),
|
||||
);
|
||||
},
|
||||
showOptions: false,
|
||||
autoFocus: false,
|
||||
// Use ValueKey to refresh the UI, otherwise, it will remain the old value.
|
||||
key: ValueKey(
|
||||
widget.option.data.id,
|
||||
), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_editor_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/color_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -41,34 +42,39 @@ class _SliverChecklistPrograssBarDelegate
|
||||
extends SliverPersistentHeaderDelegate {
|
||||
_SliverChecklistPrograssBarDelegate();
|
||||
|
||||
double fixHeight = 80;
|
||||
double fixHeight = 60;
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
return BlocBuilder<ChecklistCellEditorBloc, ChecklistCellEditorState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
if (state.percent != 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: ChecklistPrograssBar(percent: state.percent),
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: Column(
|
||||
children: [
|
||||
FlowyTextField(
|
||||
autoClearWhenDone: true,
|
||||
hintText: LocaleKeys.grid_checklist_panelTitle.tr(),
|
||||
onChanged: (text) {
|
||||
context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.filterOption(text));
|
||||
},
|
||||
onSubmitted: (text) {
|
||||
context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.newOption(text));
|
||||
},
|
||||
),
|
||||
FlowyTextField(
|
||||
hintText: LocaleKeys.grid_checklist_panelTitle.tr(),
|
||||
onChanged: (text) {
|
||||
context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.filterOption(text));
|
||||
},
|
||||
onSubmitted: (text) {
|
||||
context
|
||||
.read<ChecklistCellEditorBloc>()
|
||||
.add(ChecklistCellEditorEvent.newOption(text));
|
||||
},
|
||||
)
|
||||
],
|
||||
if (state.percent != 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: ChecklistPrograssBar(percent: state.percent),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -18,10 +19,14 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
|
||||
final SelectOptionPB option;
|
||||
final VoidCallback onDeleted;
|
||||
final Function(SelectOptionPB) onUpdated;
|
||||
final bool showOptions;
|
||||
final bool autoFocus;
|
||||
const SelectOptionTypeOptionEditor({
|
||||
required this.option,
|
||||
required this.onDeleted,
|
||||
required this.onUpdated,
|
||||
this.showOptions = true,
|
||||
this.autoFocus = true,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -50,21 +55,29 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
List<Widget> slivers = [
|
||||
SliverToBoxAdapter(
|
||||
child: _OptionNameTextField(state.option.name)),
|
||||
child: _OptionNameTextField(
|
||||
name: state.option.name,
|
||||
autoFocus: autoFocus,
|
||||
)),
|
||||
const SliverToBoxAdapter(child: VSpace(10)),
|
||||
const SliverToBoxAdapter(child: _DeleteTag()),
|
||||
const SliverToBoxAdapter(child: TypeOptionSeparator()),
|
||||
SliverToBoxAdapter(
|
||||
child:
|
||||
SelectOptionColorList(selectedColor: state.option.color)),
|
||||
];
|
||||
|
||||
if (showOptions) {
|
||||
slivers
|
||||
.add(const SliverToBoxAdapter(child: TypeOptionSeparator()));
|
||||
slivers.add(SliverToBoxAdapter(
|
||||
child: SelectOptionColorList(
|
||||
selectedColor: state.option.color)));
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 160,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: CustomScrollView(
|
||||
slivers: slivers,
|
||||
shrinkWrap: true,
|
||||
controller: ScrollController(),
|
||||
physics: StyledScrollPhysics(),
|
||||
),
|
||||
@ -102,19 +115,21 @@ class _DeleteTag extends StatelessWidget {
|
||||
|
||||
class _OptionNameTextField extends StatelessWidget {
|
||||
final String name;
|
||||
const _OptionNameTextField(this.name, {Key? key}) : super(key: key);
|
||||
final bool autoFocus;
|
||||
const _OptionNameTextField(
|
||||
{required this.name, required this.autoFocus, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InputTextField(
|
||||
return FlowyTextField(
|
||||
autoFucous: autoFocus,
|
||||
text: name,
|
||||
maxLength: 30,
|
||||
onCanceled: () {},
|
||||
onDone: (optionName) {
|
||||
if (name != optionName) {
|
||||
onSubmitted: (newName) {
|
||||
if (name != newName) {
|
||||
context
|
||||
.read<EditSelectOptionBloc>()
|
||||
.add(EditSelectOptionEvent.updateName(optionName));
|
||||
.add(EditSelectOptionEvent.updateName(newName));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -88,7 +88,8 @@ class _PopoverMaskState extends State<PopoverMask> {
|
||||
}
|
||||
|
||||
bool _handleGlobalKeyEvent(KeyEvent event) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.escape &&
|
||||
event is KeyDownEvent) {
|
||||
if (widget.onExit != null) {
|
||||
widget.onExit!();
|
||||
}
|
||||
|
@ -1,18 +1,28 @@
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
|
||||
class FlowyTextField extends StatefulWidget {
|
||||
final String hintText;
|
||||
final String text;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onSubmitted;
|
||||
final void Function()? onCanceled;
|
||||
final bool autoFucous;
|
||||
final int? maxLength;
|
||||
final TextEditingController? controller;
|
||||
final bool autoClearWhenDone;
|
||||
const FlowyTextField({
|
||||
this.hintText = "",
|
||||
this.text = "",
|
||||
this.onChanged,
|
||||
this.onSubmitted,
|
||||
this.onCanceled,
|
||||
this.autoFucous = true,
|
||||
this.maxLength,
|
||||
this.controller,
|
||||
this.autoClearWhenDone = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -23,12 +33,19 @@ class FlowyTextField extends StatefulWidget {
|
||||
class FlowyTextFieldState extends State<FlowyTextField> {
|
||||
late FocusNode focusNode;
|
||||
late TextEditingController controller;
|
||||
var textLength = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
focusNode = FocusNode();
|
||||
controller = TextEditingController();
|
||||
controller.text = widget.text;
|
||||
focusNode.addListener(notifyDidEndEditing);
|
||||
|
||||
if (widget.controller != null) {
|
||||
controller = widget.controller!;
|
||||
} else {
|
||||
controller = TextEditingController();
|
||||
controller.text = widget.text;
|
||||
}
|
||||
if (widget.autoFucous) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
focusNode.requestFocus();
|
||||
@ -47,9 +64,15 @@ class FlowyTextFieldState extends State<FlowyTextField> {
|
||||
},
|
||||
onSubmitted: (text) {
|
||||
widget.onSubmitted?.call(text);
|
||||
|
||||
if (widget.autoClearWhenDone) {
|
||||
controller.text = "";
|
||||
}
|
||||
},
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLength: widget.maxLength,
|
||||
maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds,
|
||||
style: TextStyles.body1.size(FontSizes.s12),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
@ -61,6 +84,8 @@ class FlowyTextFieldState extends State<FlowyTextField> {
|
||||
),
|
||||
isDense: true,
|
||||
hintText: widget.hintText,
|
||||
suffixText: _suffixText(),
|
||||
counterText: "",
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
@ -71,4 +96,29 @@ class FlowyTextFieldState extends State<FlowyTextField> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.removeListener(notifyDidEndEditing);
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void notifyDidEndEditing() {
|
||||
if (!focusNode.hasFocus) {
|
||||
if (controller.text.isEmpty) {
|
||||
widget.onCanceled?.call();
|
||||
} else {
|
||||
widget.onSubmitted?.call(controller.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? _suffixText() {
|
||||
if (widget.maxLength != null) {
|
||||
return '${textLength.toString()}/${widget.maxLength.toString()}';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,10 +80,10 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for Checklist
|
||||
}
|
||||
|
||||
new_cell_data = select_ids.to_string();
|
||||
tracing::trace!("checklist's cell data: {}", &new_cell_data);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::trace!("checklist's cell data: {}", &new_cell_data);
|
||||
Ok(new_cell_data)
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ impl SelectOptionTypeOptionTransformer {
|
||||
T: SelectTypeOptionSharedAction,
|
||||
{
|
||||
match decoded_field_type {
|
||||
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
|
||||
//
|
||||
CellBytes::from(shared.get_selected_options(cell_data))
|
||||
}
|
||||
|
@ -390,7 +390,14 @@ fn make_test_board() -> BuildGridContext {
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
FieldType::Checklist => {}
|
||||
FieldType::Checklist => {
|
||||
let checklist = ChecklistTypeOptionBuilder::default()
|
||||
.add_option(SelectOptionPB::new(FIRST_THING))
|
||||
.add_option(SelectOptionPB::new(SECOND_THING))
|
||||
.add_option(SelectOptionPB::new(THIRD_THING));
|
||||
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
|
||||
grid_builder.add_field(checklist_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user