chore: add option & color selection

This commit is contained in:
appflowy 2022-03-29 22:58:38 +08:00
parent 55b888e364
commit 065a72a8da
40 changed files with 828 additions and 190 deletions

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="6" r="1" fill="#333333"/>
<circle cx="8" cy="10" r="1" fill="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 194 B

View File

@ -167,6 +167,17 @@
"addSelectOption": "Add an option",
"optionTitle": "Options",
"addOption": "Add option"
},
"selectOption": {
"purpleColor": "Purple",
"pinkColor": "Pink",
"lightPinkColor": "Light Pink",
"orangeColor": "Orange",
"yellowColor": "Yellow",
"limeColor": "Lime",
"greenColor": "Green",
"aquaColor": "Aqua",
"blueColor": "Blue"
}
}
}

View File

@ -3,6 +3,7 @@ import 'package:app_flowy/user/application/user_listener.dart';
import 'package:app_flowy/user/application/user_service.dart';
import 'package:app_flowy/workspace/application/app/prelude.dart';
import 'package:app_flowy/workspace/application/doc/prelude.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/application/grid/row/row_listener.dart';
import 'package:app_flowy/workspace/application/trash/prelude.dart';
@ -20,6 +21,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
import 'package:get_it/get_it.dart';
@ -215,8 +217,12 @@ void _resolveGridDeps(GetIt getIt) {
(context, _) => FieldTypeSwitchBloc(context),
);
getIt.registerFactory<SelectionTypeOptionBloc>(
() => SelectionTypeOptionBloc(),
getIt.registerFactoryParam<SingleSelectTypeOptionBloc, SingleSelectTypeOption, String>(
(typeOption, fieldId) => SingleSelectTypeOptionBloc(typeOption, fieldId),
);
getIt.registerFactoryParam<MultiSelectTypeOptionBloc, MultiSelectTypeOption, void>(
(typeOption, _) => MultiSelectTypeOptionBloc(typeOption),
);
getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(

View File

@ -1,5 +1,3 @@
import 'dart:typed_data';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';

View File

@ -0,0 +1,42 @@
import 'dart:typed_data';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
part 'multi_select_bloc.freezed.dart';
class MultiSelectTypeOptionBloc extends Bloc<MultiSelectTypeOptionEvent, MultiSelectTypeOptionState> {
MultiSelectTypeOptionBloc(MultiSelectTypeOption typeOption) : super(MultiSelectTypeOptionState.initial(typeOption)) {
on<MultiSelectTypeOptionEvent>(
(event, emit) async {
await event.map(
createOption: (_CreateOption value) {},
updateOptions: (_UpdateOptions value) async {},
);
},
);
}
@override
Future<void> close() async {
return super.close();
}
}
@freezed
class MultiSelectTypeOptionEvent with _$MultiSelectTypeOptionEvent {
const factory MultiSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
const factory MultiSelectTypeOptionEvent.updateOptions(List<SelectOption> options) = _UpdateOptions;
}
@freezed
class MultiSelectTypeOptionState with _$MultiSelectTypeOptionState {
const factory MultiSelectTypeOptionState({
required MultiSelectTypeOption typeOption,
}) = _MultiSelectTypeOptionState;
factory MultiSelectTypeOptionState.initial(MultiSelectTypeOption typeOption) => MultiSelectTypeOptionState(
typeOption: typeOption,
);
}

View File

@ -1,13 +1,8 @@
import 'dart:typed_data';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
part 'option_pannel_bloc.freezed.dart';
class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> {
@ -16,13 +11,13 @@ class OptionPannelBloc extends Bloc<OptionPannelEvent, OptionPannelState> {
(event, emit) async {
await event.map(
createOption: (_CreateOption value) async {
emit(state.copyWith(isAddingOption: false));
emit(state.copyWith(isEditingOption: false, newOptionName: Some(value.optionName)));
},
beginAddingOption: (_BeginAddingOption value) {
emit(state.copyWith(isAddingOption: true));
emit(state.copyWith(isEditingOption: true, newOptionName: none()));
},
endAddingOption: (_EndAddingOption value) {
emit(state.copyWith(isAddingOption: false));
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
},
);
},
@ -46,11 +41,13 @@ class OptionPannelEvent with _$OptionPannelEvent {
class OptionPannelState with _$OptionPannelState {
const factory OptionPannelState({
required List<SelectOption> options,
required bool isAddingOption,
required bool isEditingOption,
required Option<String> newOptionName,
}) = _OptionPannelState;
factory OptionPannelState.initial(List<SelectOption> options) => OptionPannelState(
options: options,
isAddingOption: false,
isEditingOption: false,
newOptionName: none(),
);
}

View File

@ -1,39 +0,0 @@
import 'dart:typed_data';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
part 'selection_bloc.freezed.dart';
class SelectionTypeOptionBloc extends Bloc<SelectionTypeOptionEvent, SelectionTypeOptionState> {
SelectionTypeOptionBloc() : super(SelectionTypeOptionState.initial()) {
on<SelectionTypeOptionEvent>(
(event, emit) async {
await event.map(
initial: (_InitialField value) async {},
);
},
);
}
@override
Future<void> close() async {
return super.close();
}
}
@freezed
class SelectionTypeOptionEvent with _$SelectionTypeOptionEvent {
const factory SelectionTypeOptionEvent.initial(Uint8List? typeOptionData) = _InitialField;
}
@freezed
class SelectionTypeOptionState with _$SelectionTypeOptionState {
const factory SelectionTypeOptionState() = _SelectionTypeOptionState;
factory SelectionTypeOptionState.initial() => SelectionTypeOptionState();
}

View File

@ -0,0 +1,57 @@
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'type_option_service.dart';
part 'single_select_bloc.freezed.dart';
class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, SingleSelectTypeOptionState> {
final TypeOptionService service;
SingleSelectTypeOptionBloc(SingleSelectTypeOption typeOption, String fieldId)
: service = TypeOptionService(fieldId: fieldId),
super(SingleSelectTypeOptionState.initial(typeOption)) {
on<SingleSelectTypeOptionEvent>(
(event, emit) async {
await event.map(
createOption: (_CreateOption value) async {
final result = await service.createOption(value.optionName);
result.fold(
(option) {
state.typeOption.options.insert(0, option);
emit(state);
},
(err) => Log.error(err),
);
},
updateOptions: (_UpdateOptions value) async {},
);
},
);
}
@override
Future<void> close() async {
return super.close();
}
}
@freezed
class SingleSelectTypeOptionEvent with _$SingleSelectTypeOptionEvent {
const factory SingleSelectTypeOptionEvent.createOption(String optionName) = _CreateOption;
const factory SingleSelectTypeOptionEvent.updateOptions(List<SelectOption> options) = _UpdateOptions;
}
@freezed
class SingleSelectTypeOptionState with _$SingleSelectTypeOptionState {
const factory SingleSelectTypeOptionState({
required SingleSelectTypeOption typeOption,
}) = _SingleSelectTypeOptionState;
factory SingleSelectTypeOptionState.initial(SingleSelectTypeOption typeOption) => SingleSelectTypeOptionState(
typeOption: typeOption,
);
}

View File

@ -0,0 +1,17 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
class TypeOptionService {
String fieldId;
TypeOptionService({
required this.fieldId,
});
Future<Either<SelectOption, FlowyError>> createOption(String name) {
final payload = CreateSelectOptionPayload.create()..optionName = name;
return GridEventCreateSelectOption(payload).send();
}
}

View File

@ -14,7 +14,7 @@ export 'field/switch_field_type_bloc.dart';
// Field Type Option
export 'field/type_option/date_bloc.dart';
export 'field/type_option/number_bloc.dart';
export 'field/type_option/selection_bloc.dart';
export 'field/type_option/single_select_bloc.dart';
// Cell
export 'cell_bloc/text_cell_bloc.dart';

View File

@ -61,6 +61,7 @@ class _DocumentPageState extends State<DocumentPage> {
@override
Future<void> dispose() async {
documentBloc.close();
_focusNode.dispose();
super.dispose();
}

View File

@ -20,7 +20,7 @@ class CreateFieldPannel extends FlowyOverlayDelegate {
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
child: _CreateFieldPannelWidget(_createFieldBloc),
constraints: BoxConstraints.loose(const Size(220, 500)),
constraints: BoxConstraints.loose(const Size(220, 400)),
),
identifier: identifier(),
anchorContext: context,

View File

@ -2,7 +2,6 @@ import 'dart:typed_data';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/selection.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -14,12 +13,13 @@ import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
import 'type_option/multi_select.dart';
import 'type_option/number.dart';
import 'type_option/single_select.dart';
typedef SelectFieldCallback = void Function(Field, Uint8List);
@ -50,7 +50,7 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> {
final typeOptionWidget = _typeOptionWidget(
context: context,
fieldType: state.field.fieldType,
field: state.field,
data: state.typeOptionData,
);
@ -89,7 +89,7 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> {
Widget? _typeOptionWidget({
required BuildContext context,
required FieldType fieldType,
required Field field,
required TypeOptionData data,
}) {
final delegate = TypeOptionOperationDelegate(
@ -97,8 +97,9 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> {
context.read<FieldTypeSwitchBloc>().add(FieldTypeSwitchEvent.didUpdateTypeOptionData(data));
},
requireToShowOverlay: _showOverlay,
hideOverlay: _hideOverlay,
);
final builder = _makeTypeOptionBuild(fieldType: fieldType, data: data, delegate: delegate);
final builder = _makeTypeOptionBuild(field: field, data: data, delegate: delegate);
return builder.customWidget;
}
@ -120,6 +121,12 @@ class _FieldTypeSwitcherState extends State<FieldTypeSwitcher> {
anchorOffset: const Offset(-20, 0),
);
}
void _hideOverlay(BuildContext context) {
if (currentOverlayIdentifier != null) {
FlowyOverlay.of(context).remove(currentOverlayIdentifier!);
}
}
}
abstract class TypeOptionBuilder {
@ -127,23 +134,24 @@ abstract class TypeOptionBuilder {
}
TypeOptionBuilder _makeTypeOptionBuild({
required FieldType fieldType,
required Field field,
required TypeOptionData data,
required TypeOptionOperationDelegate delegate,
}) {
switch (fieldType) {
switch (field.fieldType) {
case FieldType.Checkbox:
return CheckboxTypeOptionBuilder(data);
case FieldType.DateTime:
return DateTypeOptionBuilder(data, delegate);
case FieldType.SingleSelect:
return SingleSelectTypeOptionBuilder(field.id, data, delegate);
case FieldType.MultiSelect:
return MultiSelectTypeOptionBuilder(data);
return MultiSelectTypeOptionBuilder(data, delegate);
case FieldType.Number:
return NumberTypeOptionBuilder(data, delegate);
case FieldType.RichText:
return RichTextTypeOptionBuilder(data);
case FieldType.SingleSelect:
return SingleSelectTypeOptionBuilder(data);
default:
throw UnimplementedError;
}
@ -156,13 +164,16 @@ abstract class TypeOptionWidget extends StatelessWidget {
typedef TypeOptionData = Uint8List;
typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData);
typedef ShowOverlayCallback = void Function(BuildContext anchorContext, String overlayIdentifier, Widget child);
typedef HideOverlayCallback = void Function(BuildContext anchorContext);
class TypeOptionOperationDelegate {
TypeOptionDataCallback didUpdateTypeOptionData;
ShowOverlayCallback requireToShowOverlay;
HideOverlayCallback hideOverlay;
TypeOptionOperationDelegate({
required this.didUpdateTypeOptionData,
required this.requireToShowOverlay,
required this.hideOverlay,
});
}

View File

@ -0,0 +1,115 @@
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
class SelectOptionColorList extends StatelessWidget {
const SelectOptionColorList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
class _SelectOptionColorItem extends StatelessWidget {
final SelectOptionColor option;
final bool isSelected;
const _SelectOptionColorItem({required this.option, required this.isSelected, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
Widget? checkmark;
if (isSelected) {
checkmark = svg("grid/details", color: theme.iconColor);
}
final colorIcon = SizedBox.square(
dimension: 16,
child: Container(
decoration: BoxDecoration(
color: option.color(context),
shape: BoxShape.circle,
),
),
);
return FlowyButton(
text: FlowyText.medium(
option.name(),
fontSize: 12,
),
hoverColor: theme.hover,
leftIcon: colorIcon,
rightIcon: checkmark,
onTap: () {},
);
}
}
enum SelectOptionColor {
purple,
pink,
lightPink,
orange,
yellow,
lime,
green,
aqua,
blue,
}
extension SelectOptionColorExtension on SelectOptionColor {
Color color(BuildContext context) {
final theme = context.watch<AppTheme>();
switch (this) {
case SelectOptionColor.purple:
return theme.tint1;
case SelectOptionColor.pink:
return theme.tint2;
case SelectOptionColor.lightPink:
return theme.tint3;
case SelectOptionColor.orange:
return theme.tint4;
case SelectOptionColor.yellow:
return theme.tint5;
case SelectOptionColor.lime:
return theme.tint6;
case SelectOptionColor.green:
return theme.tint7;
case SelectOptionColor.aqua:
return theme.tint8;
case SelectOptionColor.blue:
return theme.tint9;
}
}
String name() {
switch (this) {
case SelectOptionColor.purple:
return LocaleKeys.grid_selectOption_purpleColor.tr();
case SelectOptionColor.pink:
return LocaleKeys.grid_selectOption_pinkColor.tr();
case SelectOptionColor.lightPink:
return LocaleKeys.grid_selectOption_lightPinkColor.tr();
case SelectOptionColor.orange:
return LocaleKeys.grid_selectOption_orangeColor.tr();
case SelectOptionColor.yellow:
return LocaleKeys.grid_selectOption_yellowColor.tr();
case SelectOptionColor.lime:
return LocaleKeys.grid_selectOption_limeColor.tr();
case SelectOptionColor.green:
return LocaleKeys.grid_selectOption_greenColor.tr();
case SelectOptionColor.aqua:
return LocaleKeys.grid_selectOption_aquaColor.tr();
case SelectOptionColor.blue:
return LocaleKeys.grid_selectOption_blueColor.tr();
}
}
}

View File

@ -0,0 +1,48 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'option_pannel.dart';
class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
MultiSelectTypeOption typeOption;
TypeOptionOperationDelegate delegate;
MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData, this.delegate)
: typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData);
@override
Widget? get customWidget => MultiSelectTypeOptionWidget(typeOption, delegate);
}
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
final MultiSelectTypeOption typeOption;
final TypeOptionOperationDelegate delegate;
const MultiSelectTypeOptionWidget(this.typeOption, this.delegate, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<MultiSelectTypeOptionBloc>(param1: typeOption),
child: BlocBuilder<MultiSelectTypeOptionBloc, MultiSelectTypeOptionState>(
builder: (context, state) {
return OptionPannel(
options: state.typeOption.options,
beginEdit: () {
delegate.hideOverlay(context);
},
createOptionCallback: (name) {
context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.createOption(name));
},
updateOptionsCallback: (options) {
context.read<MultiSelectTypeOptionBloc>().add(MultiSelectTypeOptionEvent.updateOptions(options));
},
);
},
),
);
}
}

View File

@ -1,8 +1,5 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/option_pannel_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/selection_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
@ -16,73 +13,48 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'widget.dart';
class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
SingleSelectTypeOption typeOption;
SingleSelectTypeOptionBuilder(TypeOptionData typeOptionData)
: typeOption = SingleSelectTypeOption.fromBuffer(typeOptionData);
@override
Widget? get customWidget => SingleSelectTypeOptionWidget(typeOption);
}
class SingleSelectTypeOptionWidget extends TypeOptionWidget {
final SingleSelectTypeOption typeOption;
const SingleSelectTypeOptionWidget(this.typeOption, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<SelectionTypeOptionBloc>(),
child: OptionPannel(options: typeOption.options),
);
}
}
class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
MultiSelectTypeOption typeOption;
MultiSelectTypeOptionBuilder(TypeOptionData typeOptionData)
: typeOption = MultiSelectTypeOption.fromBuffer(typeOptionData);
@override
Widget? get customWidget => MultiSelectTypeOptionWidget(typeOption);
}
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
final MultiSelectTypeOption typeOption;
const MultiSelectTypeOptionWidget(this.typeOption, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<SelectionTypeOptionBloc>(),
child: OptionPannel(options: typeOption.options),
);
}
}
class OptionPannel extends StatelessWidget {
final List<SelectOption> options;
const OptionPannel({required this.options, Key? key}) : super(key: key);
final VoidCallback beginEdit;
final Function(String optionName) createOptionCallback;
final Function(List<SelectOption>) updateOptionsCallback;
const OptionPannel({
required this.options,
required this.beginEdit,
required this.createOptionCallback,
required this.updateOptionsCallback,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => OptionPannelBloc(options: options),
child: BlocBuilder<OptionPannelBloc, OptionPannelState>(
child: BlocConsumer<OptionPannelBloc, OptionPannelState>(
listener: (context, state) {
if (state.isEditingOption) {
beginEdit();
}
state.newOptionName.fold(
() => null,
(optionName) => createOptionCallback(optionName),
);
},
builder: (context, state) {
List<Widget> children = [const OptionTitle()];
if (state.isAddingOption) {
List<Widget> children = [
const TypeOptionSeparator(),
const OptionTitle(),
];
if (state.isEditingOption) {
children.add(const _AddOptionTextField());
}
if (state.options.isEmpty && !state.isAddingOption) {
if (state.options.isEmpty && !state.isEditingOption) {
children.add(const _AddOptionButton());
}
if (state.options.isNotEmpty) {
children.add(const _OptionList());
children.add(_OptionList(key: ObjectKey(state.options)));
}
return Column(children: children);
@ -100,19 +72,27 @@ class OptionTitle extends StatelessWidget {
final theme = context.watch<AppTheme>();
return BlocBuilder<OptionPannelBloc, OptionPannelState>(
buildWhen: (previous, current) => previous.options.length != current.options.length,
builder: (context, state) {
List<Widget> children = [FlowyText.medium(LocaleKeys.grid_field_optionTitle.tr(), fontSize: 12)];
if (state.options.isNotEmpty && state.isAddingOption == false) {
children.add(FlowyButton(
text: FlowyText.medium(LocaleKeys.grid_field_addOption.tr(), fontSize: 12),
hoverColor: theme.hover,
onTap: () {
context.read<OptionPannelBloc>().add(const OptionPannelEvent.beginAddingOption());
},
rightIcon: svg("grid/more", color: theme.iconColor),
));
if (state.options.isNotEmpty) {
children.add(const Spacer());
children.add(
SizedBox(
width: 100,
height: 26,
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_addOption.tr(),
fontSize: 12,
textAlign: TextAlign.center,
),
hoverColor: theme.hover,
onTap: () {
context.read<OptionPannelBloc>().add(const OptionPannelEvent.beginAddingOption());
},
),
),
);
}
return SizedBox(
@ -135,19 +115,16 @@ class _OptionList extends StatelessWidget {
return _OptionItem(option: option);
}).toList();
return SizedBox(
width: 120,
child: ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
itemCount: optionItems.length,
itemBuilder: (BuildContext context, int index) {
return optionItems[index];
},
),
return ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
itemCount: optionItems.length,
itemBuilder: (BuildContext context, int index) {
return optionItems[index];
},
);
},
);
@ -167,7 +144,7 @@ class _OptionItem extends StatelessWidget {
text: FlowyText.medium(option.name, fontSize: 12),
hoverColor: theme.hover,
onTap: () {},
rightIcon: svg("grid/more", color: theme.iconColor),
rightIcon: svg("grid/details", color: theme.iconColor),
),
);
}

View File

@ -0,0 +1,55 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'option_pannel.dart';
class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
final SingleSelectTypeOptionWidget _widget;
SingleSelectTypeOptionBuilder(
String fieldId,
TypeOptionData typeOptionData,
TypeOptionOperationDelegate delegate,
) : _widget = SingleSelectTypeOptionWidget(
fieldId,
SingleSelectTypeOption.fromBuffer(typeOptionData),
delegate,
);
@override
Widget? get customWidget => _widget;
}
class SingleSelectTypeOptionWidget extends TypeOptionWidget {
final String fieldId;
final SingleSelectTypeOption typeOption;
final TypeOptionOperationDelegate delegate;
const SingleSelectTypeOptionWidget(this.fieldId, this.typeOption, this.delegate, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<SingleSelectTypeOptionBloc>(param1: typeOption, param2: fieldId),
child: BlocConsumer<SingleSelectTypeOptionBloc, SingleSelectTypeOptionState>(
listener: (context, state) => delegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer()),
builder: (context, state) {
return OptionPannel(
options: state.typeOption.options,
beginEdit: () {
delegate.hideOverlay(context);
},
createOptionCallback: (name) {
context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.createOption(name));
},
updateOptionsCallback: (options) {
context.read<SingleSelectTypeOptionBloc>().add(SingleSelectTypeOptionEvent.updateOptions(options));
},
);
},
),
);
}
}

View File

@ -21,6 +21,7 @@ class NameTextField extends StatefulWidget {
class _NameTextFieldState extends State<NameTextField> {
late FocusNode _focusNode;
var isEdited = false;
late TextEditingController _controller;
@override
@ -35,31 +36,53 @@ class _NameTextFieldState extends State<NameTextField> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return RoundedInputField(
controller: _controller,
focusNode: _focusNode,
height: 36,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
normalBorderColor: theme.shader4,
errorBorderColor: theme.red,
focusBorderColor: theme.main1,
cursorColor: theme.main1,
onChanged: (text) {
print(text);
});
controller: _controller,
focusNode: _focusNode,
autoFocus: true,
height: 36,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
normalBorderColor: theme.shader4,
focusBorderColor: theme.main1,
cursorColor: theme.main1,
onEditingComplete: () {
widget.onDone(_controller.text);
},
);
}
@override
void dispose() {
_focusNode.removeListener(notifyDidEndEditing);
_focusNode.dispose();
super.dispose();
}
void notifyDidEndEditing() {
if (_controller.text.isEmpty) {
// widget.onCanceled();
if (isEdited) {
widget.onCanceled();
}
isEdited = true;
} else {
widget.onDone(_controller.text);
}
}
}
class TypeOptionSeparator extends StatelessWidget {
const TypeOptionSeparator({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Container(
color: theme.shader4,
height: 0.25,
),
);
}
}

View File

@ -15,7 +15,7 @@ class FlowyButton extends StatelessWidget {
Key? key,
required this.text,
this.onTap,
this.padding = const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
this.padding = const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
this.leftIcon,
this.rightIcon,
this.hoverColor = Colors.transparent,
@ -44,12 +44,13 @@ class FlowyButton extends StatelessWidget {
if (rightIcon != null) {
children.add(SizedBox.fromSize(size: const Size.square(16), child: rightIcon!));
children.add(const HSpace(6));
}
return Padding(
padding: padding,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);

View File

@ -15,6 +15,7 @@ class RoundedInputField extends StatefulWidget {
final String errorText;
final TextStyle style;
final ValueChanged<String>? onChanged;
final VoidCallback? onEditingComplete;
final String? initialValue;
final EdgeInsets margin;
final EdgeInsets padding;
@ -22,6 +23,7 @@ class RoundedInputField extends StatefulWidget {
final double height;
final FocusNode? focusNode;
final TextEditingController? controller;
final bool autoFocus;
const RoundedInputField({
Key? key,
@ -32,6 +34,7 @@ class RoundedInputField extends StatefulWidget {
this.obscureIcon,
this.obscureHideIcon,
this.onChanged,
this.onEditingComplete,
this.normalBorderColor = Colors.transparent,
this.errorBorderColor = Colors.transparent,
this.focusBorderColor,
@ -43,6 +46,7 @@ class RoundedInputField extends StatefulWidget {
this.height = 48,
this.focusNode,
this.controller,
this.autoFocus = false,
}) : super(key: key);
@override
@ -78,6 +82,7 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
controller: widget.controller,
initialValue: widget.initialValue,
focusNode: widget.focusNode,
autofocus: widget.autoFocus,
onChanged: (value) {
inputText = value;
if (widget.onChanged != null) {
@ -85,6 +90,7 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
}
setState(() {});
},
onEditingComplete: widget.onEditingComplete,
cursorColor: widget.cursorColor,
obscureText: obscuteText,
style: widget.style,

View File

@ -137,6 +137,23 @@ class GridEventCreateEditFieldContext {
}
}
class GridEventCreateSelectOption {
CreateSelectOptionPayload request;
GridEventCreateSelectOption(this.request);
Future<Either<SelectOption, FlowyError>> send() {
final request = FFIRequest.create()
..event = GridEvent.CreateSelectOption.toString()
..payload = requestToBytes(this.request);
return Dispatch.asyncRequest(request)
.then((bytesResult) => bytesResult.fold(
(okBytes) => left(SelectOption.fromBuffer(okBytes)),
(errBytes) => right(FlowyError.fromBuffer(errBytes)),
));
}
}
class GridEventCreateRow {
CreateRowPayload request;
GridEventCreateRow(this.request);

View File

@ -4,6 +4,7 @@ import 'package:flowy_sdk/log.dart';
// ignore: unnecessary_import
import 'package:flowy_sdk/protobuf/dart-ffi/ffi_response.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-net/event.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-net/network_state.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user/event_map.pb.dart';

View File

@ -47,6 +47,7 @@ class ErrorCode extends $pb.ProtobufEnum {
static const ErrorCode RowIdIsEmpty = ErrorCode._(430, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RowIdIsEmpty');
static const ErrorCode FieldIdIsEmpty = ErrorCode._(440, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldIdIsEmpty');
static const ErrorCode FieldDoesNotExist = ErrorCode._(441, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'FieldDoesNotExist');
static const ErrorCode SelectOptionNameIsEmpty = ErrorCode._(442, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SelectOptionNameIsEmpty');
static const ErrorCode TypeOptionDataIsEmpty = ErrorCode._(450, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'TypeOptionDataIsEmpty');
static const ErrorCode InvalidData = ErrorCode._(500, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InvalidData');
@ -88,6 +89,7 @@ class ErrorCode extends $pb.ProtobufEnum {
RowIdIsEmpty,
FieldIdIsEmpty,
FieldDoesNotExist,
SelectOptionNameIsEmpty,
TypeOptionDataIsEmpty,
InvalidData,
];

View File

@ -49,10 +49,11 @@ const ErrorCode$json = const {
const {'1': 'RowIdIsEmpty', '2': 430},
const {'1': 'FieldIdIsEmpty', '2': 440},
const {'1': 'FieldDoesNotExist', '2': 441},
const {'1': 'SelectOptionNameIsEmpty', '2': 442},
const {'1': 'TypeOptionDataIsEmpty', '2': 450},
const {'1': 'InvalidData', '2': 500},
],
};
/// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxITCg5GaWVsZElkSXNFbXB0eRC4AxIWChFGaWVsZERvZXNOb3RFeGlzdBC5AxIaChVUeXBlT3B0aW9uRGF0YUlzRW1wdHkQwgMSEAoLSW52YWxpZERhdGEQ9AM=');
final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSDAoISW50ZXJuYWwQABIUChBVc2VyVW5hdXRob3JpemVkEAISEgoOUmVjb3JkTm90Rm91bmQQAxIRCg1Vc2VySWRJc0VtcHR5EAQSGAoUV29ya3NwYWNlTmFtZUludmFsaWQQZBIWChJXb3Jrc3BhY2VJZEludmFsaWQQZRIYChRBcHBDb2xvclN0eWxlSW52YWxpZBBmEhgKFFdvcmtzcGFjZURlc2NUb29Mb25nEGcSGAoUV29ya3NwYWNlTmFtZVRvb0xvbmcQaBIQCgxBcHBJZEludmFsaWQQbhISCg5BcHBOYW1lSW52YWxpZBBvEhMKD1ZpZXdOYW1lSW52YWxpZBB4EhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEHkSEQoNVmlld0lkSW52YWxpZBB6EhMKD1ZpZXdEZXNjVG9vTG9uZxB7EhMKD1ZpZXdEYXRhSW52YWxpZBB8EhMKD1ZpZXdOYW1lVG9vTG9uZxB9EhEKDENvbm5lY3RFcnJvchDIARIRCgxFbWFpbElzRW1wdHkQrAISFwoSRW1haWxGb3JtYXRJbnZhbGlkEK0CEhcKEkVtYWlsQWxyZWFkeUV4aXN0cxCuAhIUCg9QYXNzd29yZElzRW1wdHkQrwISFAoPUGFzc3dvcmRUb29Mb25nELACEiUKIFBhc3N3b3JkQ29udGFpbnNGb3JiaWRDaGFyYWN0ZXJzELECEhoKFVBhc3N3b3JkRm9ybWF0SW52YWxpZBCyAhIVChBQYXNzd29yZE5vdE1hdGNoELMCEhQKD1VzZXJOYW1lVG9vTG9uZxC0AhInCiJVc2VyTmFtZUNvbnRhaW5Gb3JiaWRkZW5DaGFyYWN0ZXJzELUCEhQKD1VzZXJOYW1lSXNFbXB0eRC2AhISCg1Vc2VySWRJbnZhbGlkELcCEhEKDFVzZXJOb3RFeGlzdBC4AhIQCgtUZXh0VG9vTG9uZxCQAxISCg1HcmlkSWRJc0VtcHR5EJoDEhMKDkJsb2NrSWRJc0VtcHR5EKQDEhEKDFJvd0lkSXNFbXB0eRCuAxITCg5GaWVsZElkSXNFbXB0eRC4AxIWChFGaWVsZERvZXNOb3RFeGlzdBC5AxIcChdTZWxlY3RPcHRpb25OYW1lSXNFbXB0eRC6AxIaChVUeXBlT3B0aW9uRGF0YUlzRW1wdHkQwgMSEAoLSW52YWxpZERhdGEQ9AM=');

View File

@ -1536,3 +1536,50 @@ class QueryRowPayload extends $pb.GeneratedMessage {
void clearRowId() => clearField(3);
}
class CreateSelectOptionPayload extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateSelectOptionPayload', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'optionName')
..hasRequiredFields = false
;
CreateSelectOptionPayload._() : super();
factory CreateSelectOptionPayload({
$core.String? optionName,
}) {
final _result = create();
if (optionName != null) {
_result.optionName = optionName;
}
return _result;
}
factory CreateSelectOptionPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory CreateSelectOptionPayload.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
CreateSelectOptionPayload clone() => CreateSelectOptionPayload()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
CreateSelectOptionPayload copyWith(void Function(CreateSelectOptionPayload) updates) => super.copyWith((message) => updates(message as CreateSelectOptionPayload)) as CreateSelectOptionPayload; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static CreateSelectOptionPayload create() => CreateSelectOptionPayload._();
CreateSelectOptionPayload createEmptyInstance() => create();
static $pb.PbList<CreateSelectOptionPayload> createRepeated() => $pb.PbList<CreateSelectOptionPayload>();
@$core.pragma('dart2js:noInline')
static CreateSelectOptionPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateSelectOptionPayload>(create);
static CreateSelectOptionPayload? _defaultInstance;
@$pb.TagNumber(1)
$core.String get optionName => $_getSZ(0);
@$pb.TagNumber(1)
set optionName($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasOptionName() => $_has(0);
@$pb.TagNumber(1)
void clearOptionName() => clearField(1);
}

View File

@ -302,3 +302,13 @@ const QueryRowPayload$json = const {
/// Descriptor for `QueryRowPayload`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List queryRowPayloadDescriptor = $convert.base64Decode('Cg9RdWVyeVJvd1BheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhkKCGJsb2NrX2lkGAIgASgJUgdibG9ja0lkEhUKBnJvd19pZBgDIAEoCVIFcm93SWQ=');
@$core.Deprecated('Use createSelectOptionPayloadDescriptor instead')
const CreateSelectOptionPayload$json = const {
'1': 'CreateSelectOptionPayload',
'2': const [
const {'1': 'option_name', '3': 1, '4': 1, '5': 9, '10': 'optionName'},
],
};
/// Descriptor for `CreateSelectOptionPayload`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createSelectOptionPayloadDescriptor = $convert.base64Decode('ChlDcmVhdGVTZWxlY3RPcHRpb25QYXlsb2FkEh8KC29wdGlvbl9uYW1lGAEgASgJUgpvcHRpb25OYW1l');

View File

@ -18,9 +18,10 @@ class GridEvent extends $pb.ProtobufEnum {
static const GridEvent DeleteField = GridEvent._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField');
static const GridEvent DuplicateField = GridEvent._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
static const GridEvent CreateEditFieldContext = GridEvent._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateEditFieldContext');
static const GridEvent CreateRow = GridEvent._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
static const GridEvent GetRow = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRow');
static const GridEvent UpdateCell = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell');
static const GridEvent CreateSelectOption = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateSelectOption');
static const GridEvent CreateRow = GridEvent._(50, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
static const GridEvent GetRow = GridEvent._(51, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRow');
static const GridEvent UpdateCell = GridEvent._(70, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell');
static const $core.List<GridEvent> values = <GridEvent> [
GetGridData,
@ -31,6 +32,7 @@ class GridEvent extends $pb.ProtobufEnum {
DeleteField,
DuplicateField,
CreateEditFieldContext,
CreateSelectOption,
CreateRow,
GetRow,
UpdateCell,

View File

@ -20,11 +20,12 @@ const GridEvent$json = const {
const {'1': 'DeleteField', '2': 13},
const {'1': 'DuplicateField', '2': 15},
const {'1': 'CreateEditFieldContext', '2': 16},
const {'1': 'CreateRow', '2': 21},
const {'1': 'GetRow', '2': 22},
const {'1': 'UpdateCell', '2': 30},
const {'1': 'CreateSelectOption', '2': 30},
const {'1': 'CreateRow', '2': 50},
const {'1': 'GetRow', '2': 51},
const {'1': 'UpdateCell', '2': 70},
],
};
/// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtDcmVhdGVGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEgoORHVwbGljYXRlRmllbGQQDxIaChZDcmVhdGVFZGl0RmllbGRDb250ZXh0EBASDQoJQ3JlYXRlUm93EBUSCgoGR2V0Um93EBYSDgoKVXBkYXRlQ2VsbBAe');
final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtDcmVhdGVGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEgoORHVwbGljYXRlRmllbGQQDxIaChZDcmVhdGVFZGl0RmllbGRDb250ZXh0EBASFgoSQ3JlYXRlU2VsZWN0T3B0aW9uEB4SDQoJQ3JlYXRlUm93EDISCgoGR2V0Um93EDMSDgoKVXBkYXRlQ2VsbBBG');

View File

@ -1,5 +1,5 @@
use crate::manager::GridManager;
use crate::services::field::type_option_data_from_str;
use crate::services::field::{type_option_data_from_str, SelectOption};
use flowy_error::FlowyError;
use flowy_grid_data_model::entities::*;
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
@ -88,6 +88,14 @@ pub(crate) async fn duplicate_field_handler(
Ok(())
}
#[tracing::instrument(level = "debug", skip(data), err)]
pub(crate) async fn create_select_option_handler(
data: Data<CreateSelectOptionPayload>,
) -> DataResult<SelectOption, FlowyError> {
let params: CreateSelectOptionParams = data.into_inner().try_into()?;
data_result(SelectOption::new(&params.option_name))
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn create_edit_field_context_handler(
data: Data<CreateEditFieldContextParams>,

View File

@ -16,6 +16,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
.event(GridEvent::DeleteField, delete_field_handler)
.event(GridEvent::DuplicateField, duplicate_field_handler)
.event(GridEvent::CreateEditFieldContext, create_edit_field_context_handler)
.event(GridEvent::CreateSelectOption, create_select_option_handler)
.event(GridEvent::CreateRow, create_row_handler)
.event(GridEvent::GetRow, get_row_handler)
.event(GridEvent::UpdateCell, update_cell_handler);
@ -50,12 +51,15 @@ pub enum GridEvent {
#[event(input = "CreateEditFieldContextParams", output = "EditFieldContext")]
CreateEditFieldContext = 16,
#[event(input = "CreateSelectOptionPayload", output = "SelectOption")]
CreateSelectOption = 30,
#[event(input = "CreateRowPayload", output = "Row")]
CreateRow = 21,
CreateRow = 50,
#[event(input = "QueryRowPayload", output = "Row")]
GetRow = 22,
GetRow = 51,
#[event(input = "CellMetaChangeset")]
UpdateCell = 30,
UpdateCell = 70,
}

View File

@ -33,9 +33,10 @@ pub enum GridEvent {
DeleteField = 13,
DuplicateField = 15,
CreateEditFieldContext = 16,
CreateRow = 21,
GetRow = 22,
UpdateCell = 30,
CreateSelectOption = 30,
CreateRow = 50,
GetRow = 51,
UpdateCell = 70,
}
impl ::protobuf::ProtobufEnum for GridEvent {
@ -53,9 +54,10 @@ impl ::protobuf::ProtobufEnum for GridEvent {
13 => ::std::option::Option::Some(GridEvent::DeleteField),
15 => ::std::option::Option::Some(GridEvent::DuplicateField),
16 => ::std::option::Option::Some(GridEvent::CreateEditFieldContext),
21 => ::std::option::Option::Some(GridEvent::CreateRow),
22 => ::std::option::Option::Some(GridEvent::GetRow),
30 => ::std::option::Option::Some(GridEvent::UpdateCell),
30 => ::std::option::Option::Some(GridEvent::CreateSelectOption),
50 => ::std::option::Option::Some(GridEvent::CreateRow),
51 => ::std::option::Option::Some(GridEvent::GetRow),
70 => ::std::option::Option::Some(GridEvent::UpdateCell),
_ => ::std::option::Option::None
}
}
@ -70,6 +72,7 @@ impl ::protobuf::ProtobufEnum for GridEvent {
GridEvent::DeleteField,
GridEvent::DuplicateField,
GridEvent::CreateEditFieldContext,
GridEvent::CreateSelectOption,
GridEvent::CreateRow,
GridEvent::GetRow,
GridEvent::UpdateCell,
@ -101,12 +104,13 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x0fevent_map.proto*\xcc\x01\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
\n\x0fevent_map.proto*\xe4\x01\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
\0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\
\x0bUpdateField\x10\x0b\x12\x0f\n\x0bCreateField\x10\x0c\x12\x0f\n\x0bDe\
leteField\x10\r\x12\x12\n\x0eDuplicateField\x10\x0f\x12\x1a\n\x16CreateE\
ditFieldContext\x10\x10\x12\r\n\tCreateRow\x10\x15\x12\n\n\x06GetRow\x10\
\x16\x12\x0e\n\nUpdateCell\x10\x1eb\x06proto3\
ditFieldContext\x10\x10\x12\x16\n\x12CreateSelectOption\x10\x1e\x12\r\n\
\tCreateRow\x102\x12\n\n\x06GetRow\x103\x12\x0e\n\nUpdateCell\x10Fb\x06p\
roto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -9,7 +9,8 @@ enum GridEvent {
DeleteField = 13;
DuplicateField = 15;
CreateEditFieldContext = 16;
CreateRow = 21;
GetRow = 22;
UpdateCell = 30;
CreateSelectOption = 30;
CreateRow = 50;
GetRow = 51;
UpdateCell = 70;
}

View File

@ -99,6 +99,8 @@ pub enum ErrorCode {
FieldIdIsEmpty = 440,
#[display(fmt = "Field doesn't exist")]
FieldDoesNotExist = 441,
#[display(fmt = "The name of the option should not be empty")]
SelectOptionNameIsEmpty = 442,
#[display(fmt = "Field's type option data should not be empty")]
TypeOptionDataIsEmpty = 450,

View File

@ -62,6 +62,7 @@ pub enum ErrorCode {
RowIdIsEmpty = 430,
FieldIdIsEmpty = 440,
FieldDoesNotExist = 441,
SelectOptionNameIsEmpty = 442,
TypeOptionDataIsEmpty = 450,
InvalidData = 500,
}
@ -110,6 +111,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
430 => ::std::option::Option::Some(ErrorCode::RowIdIsEmpty),
440 => ::std::option::Option::Some(ErrorCode::FieldIdIsEmpty),
441 => ::std::option::Option::Some(ErrorCode::FieldDoesNotExist),
442 => ::std::option::Option::Some(ErrorCode::SelectOptionNameIsEmpty),
450 => ::std::option::Option::Some(ErrorCode::TypeOptionDataIsEmpty),
500 => ::std::option::Option::Some(ErrorCode::InvalidData),
_ => ::std::option::Option::None
@ -155,6 +157,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
ErrorCode::RowIdIsEmpty,
ErrorCode::FieldIdIsEmpty,
ErrorCode::FieldDoesNotExist,
ErrorCode::SelectOptionNameIsEmpty,
ErrorCode::TypeOptionDataIsEmpty,
ErrorCode::InvalidData,
];
@ -185,7 +188,7 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\ncode.proto*\x80\x07\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
\n\ncode.proto*\x9e\x07\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
\n\x10UserUnauthorized\x10\x02\x12\x12\n\x0eRecordNotFound\x10\x03\x12\
\x11\n\rUserIdIsEmpty\x10\x04\x12\x18\n\x14WorkspaceNameInvalid\x10d\x12\
\x16\n\x12WorkspaceIdInvalid\x10e\x12\x18\n\x14AppColorStyleInvalid\x10f\
@ -205,8 +208,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
erNotExist\x10\xb8\x02\x12\x10\n\x0bTextTooLong\x10\x90\x03\x12\x12\n\rG\
ridIdIsEmpty\x10\x9a\x03\x12\x13\n\x0eBlockIdIsEmpty\x10\xa4\x03\x12\x11\
\n\x0cRowIdIsEmpty\x10\xae\x03\x12\x13\n\x0eFieldIdIsEmpty\x10\xb8\x03\
\x12\x16\n\x11FieldDoesNotExist\x10\xb9\x03\x12\x1a\n\x15TypeOptionDataI\
sEmpty\x10\xc2\x03\x12\x10\n\x0bInvalidData\x10\xf4\x03b\x06proto3\
\x12\x16\n\x11FieldDoesNotExist\x10\xb9\x03\x12\x1c\n\x17SelectOptionNam\
eIsEmpty\x10\xba\x03\x12\x1a\n\x15TypeOptionDataIsEmpty\x10\xc2\x03\x12\
\x10\n\x0bInvalidData\x10\xf4\x03b\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -38,6 +38,7 @@ enum ErrorCode {
RowIdIsEmpty = 430;
FieldIdIsEmpty = 440;
FieldDoesNotExist = 441;
SelectOptionNameIsEmpty = 442;
TypeOptionDataIsEmpty = 450;
InvalidData = 500;
}

View File

@ -1,5 +1,5 @@
use crate::entities::{FieldMeta, FieldType, RowMeta};
use crate::parser::NotEmptyUuid;
use crate::parser::{NotEmptyStr, NotEmptyUuid};
use flowy_derive::ProtoBuf;
use flowy_error_code::ErrorCode;
use std::collections::HashMap;
@ -494,3 +494,24 @@ impl TryInto<QueryRowParams> for QueryRowPayload {
})
}
}
#[derive(ProtoBuf, Default)]
pub struct CreateSelectOptionPayload {
#[pb(index = 1)]
pub option_name: String,
}
pub struct CreateSelectOptionParams {
pub option_name: String,
}
impl TryInto<CreateSelectOptionParams> for CreateSelectOptionPayload {
type Error = ErrorCode;
fn try_into(self) -> Result<CreateSelectOptionParams, Self::Error> {
let option_name = NotEmptyStr::parse(self.option_name).map_err(|_| ErrorCode::SelectOptionNameIsEmpty)?;
Ok(CreateSelectOptionParams {
option_name: option_name.0,
})
}
}

View File

@ -1,3 +1,3 @@
mod id_parser;
mod str_parser;
pub use id_parser::*;
pub use str_parser::*;

View File

@ -19,3 +19,21 @@ impl AsRef<str> for NotEmptyUuid {
&self.0
}
}
#[derive(Debug)]
pub struct NotEmptyStr(pub String);
impl NotEmptyStr {
pub fn parse(s: String) -> Result<Self, String> {
if s.trim().is_empty() {
return Err("Input string is empty".to_owned());
}
Ok(Self(s))
}
}
impl AsRef<str> for NotEmptyStr {
fn as_ref(&self) -> &str {
&self.0
}
}

View File

@ -5258,6 +5258,165 @@ impl ::protobuf::reflect::ProtobufValue for QueryRowPayload {
}
}
#[derive(PartialEq,Clone,Default)]
pub struct CreateSelectOptionPayload {
// message fields
pub option_name: ::std::string::String,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
pub cached_size: ::protobuf::CachedSize,
}
impl<'a> ::std::default::Default for &'a CreateSelectOptionPayload {
fn default() -> &'a CreateSelectOptionPayload {
<CreateSelectOptionPayload as ::protobuf::Message>::default_instance()
}
}
impl CreateSelectOptionPayload {
pub fn new() -> CreateSelectOptionPayload {
::std::default::Default::default()
}
// string option_name = 1;
pub fn get_option_name(&self) -> &str {
&self.option_name
}
pub fn clear_option_name(&mut self) {
self.option_name.clear();
}
// Param is passed by value, moved
pub fn set_option_name(&mut self, v: ::std::string::String) {
self.option_name = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_option_name(&mut self) -> &mut ::std::string::String {
&mut self.option_name
}
// Take field
pub fn take_option_name(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.option_name, ::std::string::String::new())
}
}
impl ::protobuf::Message for CreateSelectOptionPayload {
fn is_initialized(&self) -> bool {
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
while !is.eof()? {
let (field_number, wire_type) = is.read_tag_unpack()?;
match field_number {
1 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.option_name)?;
},
_ => {
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u32 {
let mut my_size = 0;
if !self.option_name.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.option_name);
}
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
self.cached_size.set(my_size);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
if !self.option_name.is_empty() {
os.write_string(1, &self.option_name)?;
}
os.write_unknown_fields(self.get_unknown_fields())?;
::std::result::Result::Ok(())
}
fn get_cached_size(&self) -> u32 {
self.cached_size.get()
}
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
&self.unknown_fields
}
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
&mut self.unknown_fields
}
fn as_any(&self) -> &dyn (::std::any::Any) {
self as &dyn (::std::any::Any)
}
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
self as &mut dyn (::std::any::Any)
}
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
self
}
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
Self::descriptor_static()
}
fn new() -> CreateSelectOptionPayload {
CreateSelectOptionPayload::new()
}
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
descriptor.get(|| {
let mut fields = ::std::vec::Vec::new();
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"option_name",
|m: &CreateSelectOptionPayload| { &m.option_name },
|m: &mut CreateSelectOptionPayload| { &mut m.option_name },
));
::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateSelectOptionPayload>(
"CreateSelectOptionPayload",
fields,
file_descriptor_proto()
)
})
}
fn default_instance() -> &'static CreateSelectOptionPayload {
static instance: ::protobuf::rt::LazyV2<CreateSelectOptionPayload> = ::protobuf::rt::LazyV2::INIT;
instance.get(CreateSelectOptionPayload::new)
}
}
impl ::protobuf::Clear for CreateSelectOptionPayload {
fn clear(&mut self) {
self.option_name.clear();
self.unknown_fields.clear();
}
}
impl ::std::fmt::Debug for CreateSelectOptionPayload {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for CreateSelectOptionPayload {
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
::protobuf::reflect::ReflectValueRef::Message(self)
}
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\ngrid.proto\x1a\nmeta.proto\"z\n\x04Grid\x12\x0e\n\x02id\x18\x01\x20\
\x01(\tR\x02id\x12.\n\x0cfield_orders\x18\x02\x20\x03(\x0b2\x0b.FieldOrd\
@ -5311,7 +5470,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
ridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\
\x0bblockOrders\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\
\x20\x01(\tR\x06gridId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07bloc\
kId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowIdb\x06proto3\
kId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\"<\n\x19CreateSelec\
tOptionPayload\x12\x1f\n\x0boption_name\x18\x01\x20\x01(\tR\noptionNameb\
\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -103,3 +103,6 @@ message QueryRowPayload {
string block_id = 2;
string row_id = 3;
}
message CreateSelectOptionPayload {
string option_name = 1;
}