mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: database field type option (#4136)
* refactor: include field type option in FieldPB * refactor: adapt changes on flutter * refactor: adapt changes on new tauri grid * refactor: adapt changes on old tauri grid/board * chore: merge
This commit is contained in:
parent
9a1ea138fc
commit
d68c847d59
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/select/select_option.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -28,8 +28,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart';
|
||||
@ -61,7 +60,6 @@ import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/clear_date_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_time_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/include_time_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
|
@ -4,13 +4,9 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_cell_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/mobile_date_editor.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart' hide State;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@ -41,17 +37,6 @@ class MobileDateCellEditScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
late final Future<Either<dynamic, FlowyError>> typeOptionFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
typeOptionFuture = widget.controller.getTypeOption(
|
||||
DateTypeOptionDataParser(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.showAsFullScreen ? _buildFullScreen() : _buildNotFullScreen();
|
||||
@ -64,7 +49,9 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
LocaleKeys.titleBar_date.tr(),
|
||||
),
|
||||
),
|
||||
body: _buildBody(),
|
||||
body: _DateCellEditBody(
|
||||
dateCellController: widget.controller,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,7 +73,9 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
),
|
||||
_buildHeader(),
|
||||
Expanded(
|
||||
child: _buildBody(),
|
||||
child: _DateCellEditBody(
|
||||
dateCellController: widget.controller,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -94,36 +83,6 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return FutureBuilder<Either<dynamic, FlowyError>>(
|
||||
future: typeOptionFuture,
|
||||
builder: (context, snapshot) {
|
||||
final data = snapshot.data;
|
||||
if (data == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
);
|
||||
}
|
||||
|
||||
return data.fold(
|
||||
(dateTypeOptionPB) {
|
||||
return _DateCellEditBody(
|
||||
dateCellController: widget.controller,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return FlowyMobileStateContainer.error(
|
||||
title: LocaleKeys.grid_field_failedToLoadDate.tr(),
|
||||
errorMsg: err.toString(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
const iconWidth = 30.0;
|
||||
const height = 44.0;
|
||||
@ -160,20 +119,16 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
class _DateCellEditBody extends StatelessWidget {
|
||||
const _DateCellEditBody({
|
||||
required this.dateCellController,
|
||||
required this.dateTypeOptionPB,
|
||||
});
|
||||
|
||||
final DateCellController dateCellController;
|
||||
final DateTypeOptionPB dateTypeOptionPB;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => DateCellCalendarBloc(
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
cellData: dateCellController.getCellData(),
|
||||
create: (context) => DateCellEditorBloc(
|
||||
cellController: dateCellController,
|
||||
)..add(const DateCellCalendarEvent.initial()),
|
||||
)..add(const DateCellEditorEvent.initial()),
|
||||
child: const Column(
|
||||
children: [
|
||||
FlowyOptionDecorateBox(
|
||||
@ -217,7 +172,7 @@ class _IncludeTimePickerState extends State<_IncludeTimePicker> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
|
||||
return BlocBuilder<DateCellEditorBloc, DateCellEditorState>(
|
||||
builder: (context, state) {
|
||||
final startDay = state.dateStr;
|
||||
final endDay = state.endDateStr;
|
||||
@ -306,7 +261,7 @@ class _IncludeTimePickerState extends State<_IncludeTimePicker> {
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final bloc = context.read<DateCellCalendarBloc>();
|
||||
final bloc = context.read<DateCellEditorBloc>();
|
||||
await showMobileBottomSheet(
|
||||
context,
|
||||
builder: (context) {
|
||||
@ -331,8 +286,8 @@ class _IncludeTimePickerState extends State<_IncludeTimePicker> {
|
||||
if (_selectedTime != null) {
|
||||
bloc.add(
|
||||
isStartDay
|
||||
? DateCellCalendarEvent.setTime(_selectedTime!)
|
||||
: DateCellCalendarEvent.setEndTime(_selectedTime!),
|
||||
? DateCellEditorEvent.setTime(_selectedTime!)
|
||||
: DateCellEditorEvent.setEndTime(_selectedTime!),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -361,7 +316,7 @@ class _EndDateSwitch extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
|
||||
return BlocSelector<DateCellEditorBloc, DateCellEditorState, bool>(
|
||||
selector: (state) => state.isRange,
|
||||
builder: (context, isRange) {
|
||||
return FlowyOptionTile.toggle(
|
||||
@ -369,8 +324,8 @@ class _EndDateSwitch extends StatelessWidget {
|
||||
isSelected: isRange,
|
||||
onValueChanged: (value) {
|
||||
context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setIsRange(value));
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.setIsRange(value));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -383,7 +338,7 @@ class _IncludeTimeSwitch extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<DateCellCalendarBloc, DateCellCalendarState, bool>(
|
||||
return BlocSelector<DateCellEditorBloc, DateCellEditorState, bool>(
|
||||
selector: (state) => state.includeTime,
|
||||
builder: (context, includeTime) {
|
||||
return FlowyOptionTile.toggle(
|
||||
@ -392,8 +347,8 @@ class _IncludeTimeSwitch extends StatelessWidget {
|
||||
isSelected: includeTime,
|
||||
onValueChanged: (value) {
|
||||
context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setIncludeTime(value));
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(DateCellEditorEvent.setIncludeTime(value));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -420,7 +375,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<DateCellCalendarBloc, DateCellCalendarState>(
|
||||
return BlocConsumer<DateCellEditorBloc, DateCellEditorState>(
|
||||
listener: (context, state) {
|
||||
_textController.text =
|
||||
widget.isEndTime ? state.endTimeStr ?? "" : state.timeStr ?? "";
|
||||
@ -437,10 +392,10 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
),
|
||||
keyboardType: TextInputType.datetime,
|
||||
onFieldSubmitted: (timeStr) {
|
||||
context.read<DateCellCalendarBloc>().add(
|
||||
context.read<DateCellEditorBloc>().add(
|
||||
widget.isEndTime
|
||||
? DateCellCalendarEvent.setEndTime(timeStr)
|
||||
: DateCellCalendarEvent.setTime(timeStr),
|
||||
? DateCellEditorEvent.setEndTime(timeStr)
|
||||
: DateCellEditorEvent.setTime(timeStr),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -463,8 +418,8 @@ class _ClearDateButton extends StatelessWidget {
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.grid_field_clearDate.tr(),
|
||||
onTap: () => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(const DateCellCalendarEvent.clearDate()),
|
||||
.read<DateCellEditorBloc>()
|
||||
.add(const DateCellEditorEvent.clearDate()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -28,19 +28,12 @@ class MobileEditPropertyScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
|
||||
late Future<FieldOptionValues?> future;
|
||||
|
||||
FieldOptionValues? optionValues;
|
||||
late FieldOptionValues optionValues;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
future = FieldOptionValues.get(
|
||||
viewId: widget.viewId,
|
||||
fieldId: widget.field.id,
|
||||
fieldType: widget.field.fieldType,
|
||||
);
|
||||
optionValues = FieldOptionValues.fromField(field: widget.field);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -66,39 +59,30 @@ class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: FutureBuilder<FieldOptionValues?>(
|
||||
future: future,
|
||||
builder: (context, snapshot) {
|
||||
final optionValues = snapshot.data;
|
||||
if (optionValues == null) {
|
||||
return const Center(child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
return FieldOptionEditor(
|
||||
mode: FieldOptionMode.edit,
|
||||
isPrimary: widget.field.isPrimary,
|
||||
defaultValues: optionValues,
|
||||
onOptionValuesChanged: (optionValues) {
|
||||
this.optionValues = optionValues;
|
||||
},
|
||||
onAction: (action) {
|
||||
final service = FieldServices(
|
||||
viewId: viewId,
|
||||
fieldId: fieldId,
|
||||
);
|
||||
switch (action) {
|
||||
case FieldOptionAction.delete:
|
||||
service.delete();
|
||||
break;
|
||||
case FieldOptionAction.duplicate:
|
||||
service.duplicate();
|
||||
break;
|
||||
case FieldOptionAction.hide:
|
||||
service.hide();
|
||||
break;
|
||||
}
|
||||
context.pop();
|
||||
},
|
||||
body: FieldOptionEditor(
|
||||
mode: FieldOptionMode.edit,
|
||||
isPrimary: widget.field.isPrimary,
|
||||
defaultValues: optionValues,
|
||||
onOptionValuesChanged: (optionValues) {
|
||||
this.optionValues = optionValues;
|
||||
},
|
||||
onAction: (action) {
|
||||
final service = FieldServices(
|
||||
viewId: viewId,
|
||||
fieldId: fieldId,
|
||||
);
|
||||
switch (action) {
|
||||
case FieldOptionAction.delete:
|
||||
service.delete();
|
||||
break;
|
||||
case FieldOptionAction.duplicate:
|
||||
service.duplicate();
|
||||
break;
|
||||
case FieldOptionAction.hide:
|
||||
service.hide();
|
||||
break;
|
||||
}
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -9,7 +9,7 @@ import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/w
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/number_format_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
@ -94,39 +94,30 @@ class FieldOptionValues {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<FieldOptionValues?> get({
|
||||
required String viewId,
|
||||
required String fieldId,
|
||||
required FieldType fieldType,
|
||||
}) async {
|
||||
final service = FieldBackendService(viewId: viewId, fieldId: fieldId);
|
||||
final result = await service.getFieldTypeOptionData(fieldType: fieldType);
|
||||
return result.fold(
|
||||
(option) {
|
||||
final type = option.field_2.fieldType;
|
||||
final buffer = option.typeOptionData;
|
||||
return FieldOptionValues(
|
||||
type: type,
|
||||
name: option.field_2.name,
|
||||
numberFormat: type == FieldType.Number
|
||||
? NumberTypeOptionPB.fromBuffer(buffer).format
|
||||
: null,
|
||||
dateFormate: type == FieldType.DateTime
|
||||
? DateTypeOptionPB.fromBuffer(buffer).dateFormat
|
||||
: null,
|
||||
timeFormat: type == FieldType.DateTime
|
||||
? DateTypeOptionPB.fromBuffer(buffer).timeFormat
|
||||
: null,
|
||||
selectOption: switch (type) {
|
||||
FieldType.SingleSelect =>
|
||||
SingleSelectTypeOptionPB.fromBuffer(buffer).options,
|
||||
FieldType.MultiSelect =>
|
||||
MultiSelectTypeOptionPB.fromBuffer(buffer).options,
|
||||
_ => [],
|
||||
},
|
||||
);
|
||||
factory FieldOptionValues.fromField({
|
||||
required FieldPB field,
|
||||
}) {
|
||||
final fieldType = field.fieldType;
|
||||
final buffer = field.typeOptionData;
|
||||
return FieldOptionValues(
|
||||
type: fieldType,
|
||||
name: field.name,
|
||||
numberFormat: fieldType == FieldType.Number
|
||||
? NumberTypeOptionPB.fromBuffer(buffer).format
|
||||
: null,
|
||||
dateFormate: fieldType == FieldType.DateTime
|
||||
? DateTypeOptionPB.fromBuffer(buffer).dateFormat
|
||||
: null,
|
||||
timeFormat: fieldType == FieldType.DateTime
|
||||
? DateTypeOptionPB.fromBuffer(buffer).timeFormat
|
||||
: null,
|
||||
selectOption: switch (fieldType) {
|
||||
FieldType.SingleSelect =>
|
||||
SingleSelectTypeOptionPB.fromBuffer(buffer).options,
|
||||
FieldType.MultiSelect =>
|
||||
MultiSelectTypeOptionPB.fromBuffer(buffer).options,
|
||||
_ => [],
|
||||
},
|
||||
(error) => null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_listener.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_meta_listener.dart';
|
||||
@ -9,8 +10,8 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../field/field_service.dart';
|
||||
import '../field/type_option/type_option_context.dart';
|
||||
|
||||
import '../field/type_option/type_option_data_parser.dart';
|
||||
import 'cell_listener.dart';
|
||||
import 'cell_service.dart';
|
||||
|
||||
@ -26,7 +27,6 @@ class CellController<T, D> extends Equatable {
|
||||
DatabaseCellContext _cellContext;
|
||||
final CellMemCache _cellCache;
|
||||
final CellCacheKey _cacheKey;
|
||||
final FieldBackendService _fieldBackendSvc;
|
||||
final CellDataLoader<T> _cellDataLoader;
|
||||
final CellDataPersistence<D> _cellDataPersistence;
|
||||
|
||||
@ -63,10 +63,6 @@ class CellController<T, D> extends Equatable {
|
||||
_cellDataPersistence = cellDataPersistence,
|
||||
_rowMetaListener = RowMetaListener(cellContext.rowId),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
|
||||
_fieldBackendSvc = FieldBackendService(
|
||||
viewId: cellContext.viewId,
|
||||
fieldId: cellContext.fieldInfo.id,
|
||||
),
|
||||
_cacheKey = CellCacheKey(
|
||||
rowId: cellContext.rowId,
|
||||
fieldId: cellContext.fieldInfo.id,
|
||||
@ -97,6 +93,9 @@ class CellController<T, D> extends Equatable {
|
||||
/// For example:
|
||||
/// ¥12 -> $12
|
||||
if (_cellDataLoader.reloadOnFieldChanged) {
|
||||
_cellContext = _cellContext.copyWith(
|
||||
fieldInfo: _cellContext.fieldInfo.copyWith(field: fieldPB),
|
||||
);
|
||||
_loadData();
|
||||
}
|
||||
_onCellFieldChanged?.call();
|
||||
@ -148,17 +147,10 @@ class CellController<T, D> extends Equatable {
|
||||
|
||||
/// Return the TypeOptionPB that can be parsed into corresponding class using the [parser].
|
||||
/// [PD] is the type that the parser return.
|
||||
Future<Either<PD, FlowyError>> getTypeOption<PD, P extends TypeOptionParser>(
|
||||
PD getTypeOption<PD, P extends TypeOptionParser>(
|
||||
P parser,
|
||||
) {
|
||||
return _fieldBackendSvc
|
||||
.getFieldTypeOptionData(fieldType: fieldType)
|
||||
.then((result) {
|
||||
return result.fold(
|
||||
(data) => left(parser.fromBuffer(data.typeOptionData)),
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
return parser.fromBuffer(_cellContext.fieldInfo.field.typeOptionData);
|
||||
}
|
||||
|
||||
/// Save the cell data to disk
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -11,32 +12,24 @@ import 'field_controller.dart';
|
||||
import 'field_info.dart';
|
||||
import 'field_listener.dart';
|
||||
import 'field_service.dart';
|
||||
import 'type_option/type_option_context.dart';
|
||||
import 'type_option/type_option_data_controller.dart';
|
||||
|
||||
part 'field_editor_bloc.freezed.dart';
|
||||
|
||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
final FieldPB field;
|
||||
|
||||
final String viewId;
|
||||
final String fieldId;
|
||||
final FieldController fieldController;
|
||||
final SingleFieldListener _singleFieldListener;
|
||||
final FieldBackendService fieldService;
|
||||
final FieldSettingsBackendService fieldSettingsService;
|
||||
final TypeOptionController typeOptionController;
|
||||
final void Function(String newFieldId)? onFieldInserted;
|
||||
|
||||
FieldEditorBloc({
|
||||
required this.viewId,
|
||||
required this.field,
|
||||
required this.fieldController,
|
||||
this.onFieldInserted,
|
||||
required FieldTypeOptionLoader loader,
|
||||
}) : typeOptionController = TypeOptionController(
|
||||
field: field,
|
||||
loader: loader,
|
||||
),
|
||||
required FieldPB field,
|
||||
}) : fieldId = field.id,
|
||||
_singleFieldListener = SingleFieldListener(fieldId: field.id),
|
||||
fieldService = FieldBackendService(
|
||||
viewId: viewId,
|
||||
@ -48,12 +41,6 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
final fieldId = field.id;
|
||||
typeOptionController.addFieldListener((field) {
|
||||
if (!isClosed) {
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||
}
|
||||
});
|
||||
_singleFieldListener.start(
|
||||
onFieldChanged: (field) {
|
||||
if (!isClosed) {
|
||||
@ -61,7 +48,6 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
}
|
||||
},
|
||||
);
|
||||
await typeOptionController.reloadTypeOption();
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||
},
|
||||
didReceiveFieldChanged: (fieldId) async {
|
||||
@ -69,23 +55,31 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
emit(state.copyWith(field: fieldController.getField(fieldId)!));
|
||||
},
|
||||
switchFieldType: (fieldType) async {
|
||||
await typeOptionController.switchToField(fieldType);
|
||||
await fieldService.updateFieldType(fieldType: fieldType);
|
||||
},
|
||||
renameField: (newName) async {
|
||||
final result = await fieldService.updateField(name: newName);
|
||||
_logIfError(result);
|
||||
},
|
||||
updateTypeOption: (typeOptionData) async {
|
||||
final result = await FieldBackendService.updateFieldTypeOption(
|
||||
viewId: viewId,
|
||||
fieldId: fieldId,
|
||||
typeOptionData: typeOptionData,
|
||||
);
|
||||
_logIfError(result);
|
||||
},
|
||||
insertLeft: () async {
|
||||
final result = await fieldService.insertBefore();
|
||||
result.fold(
|
||||
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
|
||||
(newField) => onFieldInserted?.call(newField.id),
|
||||
(err) => Log.error("Failed creating field $err"),
|
||||
);
|
||||
},
|
||||
insertRight: () async {
|
||||
final result = await fieldService.insertAfter();
|
||||
result.fold(
|
||||
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
|
||||
(newField) => onFieldInserted?.call(newField.id),
|
||||
(err) => Log.error("Failed creating field $err"),
|
||||
);
|
||||
},
|
||||
@ -129,6 +123,9 @@ class FieldEditorEvent with _$FieldEditorEvent {
|
||||
_DidReceiveFieldChanged;
|
||||
const factory FieldEditorEvent.switchFieldType(final FieldType fieldType) =
|
||||
_SwitchFieldType;
|
||||
const factory FieldEditorEvent.updateTypeOption(
|
||||
final Uint8List typeOptionData,
|
||||
) = _UpdateTypeOption;
|
||||
const factory FieldEditorEvent.renameField(final String name) = _RenameField;
|
||||
const factory FieldEditorEvent.insertLeft() = _InsertLeft;
|
||||
const factory FieldEditorEvent.insertRight() = _InsertRight;
|
||||
|
@ -15,7 +15,7 @@ class FieldBackendService {
|
||||
final String viewId;
|
||||
final String fieldId;
|
||||
|
||||
static Future<Either<TypeOptionPB, FlowyError>> createField({
|
||||
static Future<Either<FieldPB, FlowyError>> createField({
|
||||
required String viewId,
|
||||
FieldType fieldType = FieldType.RichText,
|
||||
String? fieldName,
|
||||
@ -33,7 +33,7 @@ class FieldBackendService {
|
||||
return DatabaseEventCreateField(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<TypeOptionPB, FlowyError>> insertBefore({
|
||||
Future<Either<FieldPB, FlowyError>> insertBefore({
|
||||
FieldType fieldType = FieldType.RichText,
|
||||
String? fieldName,
|
||||
Uint8List? typeOptionData,
|
||||
@ -50,7 +50,7 @@ class FieldBackendService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<TypeOptionPB, FlowyError>> insertAfter({
|
||||
Future<Either<FieldPB, FlowyError>> insertAfter({
|
||||
FieldType fieldType = FieldType.RichText,
|
||||
String? fieldName,
|
||||
Uint8List? typeOptionData,
|
||||
@ -61,7 +61,7 @@ class FieldBackendService {
|
||||
fieldName: fieldName,
|
||||
typeOptionData: typeOptionData,
|
||||
position: OrderObjectPositionPB(
|
||||
position: OrderObjectPositionTypePB.Before,
|
||||
position: OrderObjectPositionTypePB.After,
|
||||
objectId: fieldId,
|
||||
),
|
||||
);
|
||||
@ -156,21 +156,6 @@ class FieldBackendService {
|
||||
return duplicateField(viewId: viewId, fieldId: fieldId);
|
||||
}
|
||||
|
||||
Future<Either<TypeOptionPB, FlowyError>> getFieldTypeOptionData({
|
||||
required FieldType fieldType,
|
||||
}) {
|
||||
final payload = TypeOptionPathPB.create()
|
||||
..viewId = viewId
|
||||
..fieldId = fieldId
|
||||
..fieldType = fieldType;
|
||||
return DatabaseEventGetTypeOption(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(data) => left(data),
|
||||
(err) => right(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the primary field of the view.
|
||||
static Future<Either<FieldPB, FlowyError>> getPrimaryField({
|
||||
required String viewId,
|
||||
|
@ -1,66 +0,0 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'type_option/type_option_data_controller.dart';
|
||||
part 'field_type_option_edit_bloc.freezed.dart';
|
||||
|
||||
class FieldTypeOptionEditBloc
|
||||
extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
|
||||
final TypeOptionController _dataController;
|
||||
void Function()? _fieldListenFn;
|
||||
|
||||
FieldTypeOptionEditBloc(TypeOptionController dataController)
|
||||
: _dataController = dataController,
|
||||
super(FieldTypeOptionEditState.initial(dataController)) {
|
||||
on<FieldTypeOptionEditEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
_fieldListenFn = dataController.addFieldListener((field) {
|
||||
add(FieldTypeOptionEditEvent.didReceiveFieldUpdated(field));
|
||||
});
|
||||
},
|
||||
didReceiveFieldUpdated: (field) {
|
||||
emit(state.copyWith(field: field));
|
||||
},
|
||||
switchToField: (FieldType fieldType) async {
|
||||
await _dataController.switchToField(fieldType);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_fieldListenFn != null) {
|
||||
_dataController.removeFieldListener(_fieldListenFn!);
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent {
|
||||
const factory FieldTypeOptionEditEvent.initial() = _Initial;
|
||||
const factory FieldTypeOptionEditEvent.switchToField(FieldType fieldType) =
|
||||
_SwitchToField;
|
||||
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(FieldPB field) =
|
||||
_DidReceiveFieldUpdated;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
|
||||
const factory FieldTypeOptionEditState({
|
||||
required FieldPB field,
|
||||
}) = _FieldTypeOptionEditState;
|
||||
|
||||
factory FieldTypeOptionEditState.initial(
|
||||
TypeOptionController typeOptionController,
|
||||
) =>
|
||||
FieldTypeOptionEditState(
|
||||
field: typeOptionController.field,
|
||||
);
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'type_option_context.dart';
|
||||
part 'date_bloc.freezed.dart';
|
||||
|
||||
class DateTypeOptionBloc
|
||||
extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
|
||||
DateTypeOptionBloc({required DateTypeOptionContext typeOptionContext})
|
||||
: super(DateTypeOptionState.initial(typeOptionContext.typeOption)) {
|
||||
on<DateTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
didSelectDateFormat: (_DidSelectDateFormat value) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
typeOption: _updateTypeOption(dateFormat: value.format),
|
||||
),
|
||||
);
|
||||
},
|
||||
didSelectTimeFormat: (_DidSelectTimeFormat value) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
typeOption: _updateTypeOption(timeFormat: value.format),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
DateTypeOptionPB _updateTypeOption({
|
||||
DateFormatPB? dateFormat,
|
||||
TimeFormatPB? timeFormat,
|
||||
}) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
if (dateFormat != null) {
|
||||
typeOption.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
if (timeFormat != null) {
|
||||
typeOption.timeFormat = timeFormat;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateTypeOptionEvent with _$DateTypeOptionEvent {
|
||||
const factory DateTypeOptionEvent.didSelectDateFormat(DateFormatPB format) =
|
||||
_DidSelectDateFormat;
|
||||
const factory DateTypeOptionEvent.didSelectTimeFormat(TimeFormatPB format) =
|
||||
_DidSelectTimeFormat;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateTypeOptionState with _$DateTypeOptionState {
|
||||
const factory DateTypeOptionState({
|
||||
required DateTypeOptionPB typeOption,
|
||||
}) = _DateTypeOptionState;
|
||||
|
||||
factory DateTypeOptionState.initial(DateTypeOptionPB typeOption) =>
|
||||
DateTypeOptionState(typeOption: typeOption);
|
||||
}
|
@ -1,85 +1,81 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/builder.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'select_option_type_option_bloc.dart';
|
||||
import 'type_option_context.dart';
|
||||
import 'type_option_service.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
class MultiSelectAction with ISelectOptionAction {
|
||||
final String viewId;
|
||||
final String fieldId;
|
||||
import 'select_option_type_option_bloc.dart';
|
||||
import 'type_option_service.dart';
|
||||
|
||||
class MultiSelectAction implements ISelectOptionAction {
|
||||
final TypeOptionBackendService service;
|
||||
final MultiSelectTypeOptionContext typeOptionContext;
|
||||
final TypeOptionDataCallback onTypeOptionUpdated;
|
||||
|
||||
MultiSelectAction({
|
||||
required this.viewId,
|
||||
required this.fieldId,
|
||||
required this.typeOptionContext,
|
||||
}) : service = TypeOptionBackendService(
|
||||
viewId: viewId,
|
||||
fieldId: fieldId,
|
||||
);
|
||||
required this.onTypeOptionUpdated,
|
||||
required String viewId,
|
||||
required String fieldId,
|
||||
}) : service = TypeOptionBackendService(viewId: viewId, fieldId: fieldId);
|
||||
|
||||
MultiSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
|
||||
@override
|
||||
Future<List<SelectOptionPB>> insertOption(
|
||||
List<SelectOptionPB> options,
|
||||
String optionName,
|
||||
) {
|
||||
final newOptions = List<SelectOptionPB>.from(options);
|
||||
return service.newOption(name: optionName).then((result) {
|
||||
return result.fold(
|
||||
(option) {
|
||||
final exists =
|
||||
newOptions.any((element) => element.name == option.name);
|
||||
if (!exists) {
|
||||
newOptions.insert(0, option);
|
||||
}
|
||||
|
||||
set typeOption(MultiSelectTypeOptionPB newTypeOption) {
|
||||
typeOptionContext.typeOption = newTypeOption;
|
||||
_updateTypeOption(newOptions);
|
||||
return newOptions;
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return newOptions;
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
final index =
|
||||
typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options.removeAt(index);
|
||||
}
|
||||
});
|
||||
return typeOption.options;
|
||||
};
|
||||
List<SelectOptionPB> deleteOption(
|
||||
List<SelectOptionPB> options,
|
||||
SelectOptionPB deletedOption,
|
||||
) {
|
||||
final newOptions = List<SelectOptionPB>.from(options);
|
||||
final index =
|
||||
newOptions.indexWhere((option) => option.id == deletedOption.id);
|
||||
if (index != -1) {
|
||||
newOptions.removeAt(index);
|
||||
}
|
||||
|
||||
_updateTypeOption(newOptions);
|
||||
return newOptions;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SelectOptionPB>> Function(String) get insertOption {
|
||||
return (String optionName) {
|
||||
return service.newOption(name: optionName).then((result) {
|
||||
return result.fold(
|
||||
(option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
final exists = typeOption.options
|
||||
.any((element) => element.name == option.name);
|
||||
if (!exists) {
|
||||
typeOption.options.insert(0, option);
|
||||
}
|
||||
});
|
||||
List<SelectOptionPB> updateOption(
|
||||
List<SelectOptionPB> options,
|
||||
SelectOptionPB option,
|
||||
) {
|
||||
final newOptions = List<SelectOptionPB>.from(options);
|
||||
final index = newOptions.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
newOptions[index] = option;
|
||||
}
|
||||
|
||||
return typeOption.options;
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return typeOption.options;
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
_updateTypeOption(newOptions);
|
||||
return newOptions;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
final index =
|
||||
typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options[index] = option;
|
||||
}
|
||||
});
|
||||
return typeOption.options;
|
||||
};
|
||||
void _updateTypeOption(List<SelectOptionPB> options) {
|
||||
final newTypeOption = MultiSelectTypeOptionPB()..options.addAll(options);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'type_option_context.dart';
|
||||
|
||||
part 'number_bloc.freezed.dart';
|
||||
|
||||
class NumberTypeOptionBloc
|
||||
extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
|
||||
NumberTypeOptionBloc({required NumberTypeOptionContext typeOptionContext})
|
||||
: super(NumberTypeOptionState.initial(typeOptionContext.typeOption)) {
|
||||
on<NumberTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
didSelectFormat: (_DidSelectFormat value) {
|
||||
emit(state.copyWith(typeOption: _updateNumberFormat(value.format)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
NumberTypeOptionPB _updateNumberFormat(NumberFormatPB format) {
|
||||
state.typeOption.freeze();
|
||||
return state.typeOption.rebuild((typeOption) {
|
||||
typeOption.format = format;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NumberTypeOptionEvent with _$NumberTypeOptionEvent {
|
||||
const factory NumberTypeOptionEvent.didSelectFormat(NumberFormatPB format) =
|
||||
_DidSelectFormat;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NumberTypeOptionState with _$NumberTypeOptionState {
|
||||
const factory NumberTypeOptionState({
|
||||
required NumberTypeOptionPB typeOption,
|
||||
}) = _NumberTypeOptionState;
|
||||
|
||||
factory NumberTypeOptionState.initial(NumberTypeOptionPB typeOption) =>
|
||||
NumberTypeOptionState(
|
||||
typeOption: typeOption,
|
||||
);
|
||||
}
|
@ -5,12 +5,21 @@ import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
part 'select_option_type_option_bloc.freezed.dart';
|
||||
|
||||
abstract mixin class ISelectOptionAction {
|
||||
Future<List<SelectOptionPB>> Function(String) get insertOption;
|
||||
abstract class ISelectOptionAction {
|
||||
Future<List<SelectOptionPB>> insertOption(
|
||||
List<SelectOptionPB> options,
|
||||
String newOptionName,
|
||||
);
|
||||
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption;
|
||||
List<SelectOptionPB> deleteOption(
|
||||
List<SelectOptionPB> options,
|
||||
SelectOptionPB deletedOption,
|
||||
);
|
||||
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption;
|
||||
List<SelectOptionPB> updateOption(
|
||||
List<SelectOptionPB> options,
|
||||
SelectOptionPB updatedOption,
|
||||
);
|
||||
}
|
||||
|
||||
class SelectOptionTypeOptionBloc
|
||||
@ -26,7 +35,7 @@ class SelectOptionTypeOptionBloc
|
||||
await event.when(
|
||||
createOption: (optionName) async {
|
||||
final List<SelectOptionPB> options =
|
||||
await typeOptionAction.insertOption(optionName);
|
||||
await typeOptionAction.insertOption(state.options, optionName);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
addingOption: () {
|
||||
@ -37,12 +46,12 @@ class SelectOptionTypeOptionBloc
|
||||
},
|
||||
updateOption: (option) {
|
||||
final List<SelectOptionPB> options =
|
||||
typeOptionAction.updateOption(option);
|
||||
typeOptionAction.updateOption(state.options, option);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
deleteOption: (option) {
|
||||
final List<SelectOptionPB> options =
|
||||
typeOptionAction.deleteOption(option);
|
||||
typeOptionAction.deleteOption(state.options, option);
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
);
|
||||
|
@ -1,82 +1,83 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/builder.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'dart:async';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'select_option_type_option_bloc.dart';
|
||||
import 'type_option_context.dart';
|
||||
import 'type_option_service.dart';
|
||||
|
||||
class SingleSelectAction with ISelectOptionAction {
|
||||
final String viewId;
|
||||
final String fieldId;
|
||||
final SingleSelectTypeOptionContext typeOptionContext;
|
||||
class SingleSelectAction implements ISelectOptionAction {
|
||||
final TypeOptionBackendService service;
|
||||
final TypeOptionDataCallback onTypeOptionUpdated;
|
||||
|
||||
SingleSelectAction({
|
||||
required this.viewId,
|
||||
required this.fieldId,
|
||||
required this.typeOptionContext,
|
||||
required this.onTypeOptionUpdated,
|
||||
required String viewId,
|
||||
required String fieldId,
|
||||
}) : service = TypeOptionBackendService(viewId: viewId, fieldId: fieldId);
|
||||
|
||||
SingleSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
|
||||
@override
|
||||
Future<List<SelectOptionPB>> insertOption(
|
||||
List<SelectOptionPB> options,
|
||||
String optionName,
|
||||
) {
|
||||
final newOptions = List<SelectOptionPB>.from(options);
|
||||
return service.newOption(name: optionName).then((result) {
|
||||
return result.fold(
|
||||
(option) {
|
||||
final exists =
|
||||
newOptions.any((element) => element.name == option.name);
|
||||
if (!exists) {
|
||||
newOptions.insert(0, option);
|
||||
}
|
||||
|
||||
set typeOption(SingleSelectTypeOptionPB newTypeOption) {
|
||||
typeOptionContext.typeOption = newTypeOption;
|
||||
_updateTypeOption(newOptions);
|
||||
return newOptions;
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return newOptions;
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
final index =
|
||||
typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options.removeAt(index);
|
||||
}
|
||||
});
|
||||
return typeOption.options;
|
||||
};
|
||||
List<SelectOptionPB> deleteOption(
|
||||
List<SelectOptionPB> options,
|
||||
SelectOptionPB deletedOption,
|
||||
) {
|
||||
final newOptions = List<SelectOptionPB>.from(options);
|
||||
final index =
|
||||
newOptions.indexWhere((option) => option.id == deletedOption.id);
|
||||
if (index != -1) {
|
||||
newOptions.removeAt(index);
|
||||
}
|
||||
|
||||
final newTypeOption = MultiSelectTypeOptionPB()..options.addAll(newOptions);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
return newOptions;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SelectOptionPB>> Function(String) get insertOption {
|
||||
return (String optionName) {
|
||||
return service.newOption(name: optionName).then((result) {
|
||||
return result.fold(
|
||||
(option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
final exists = typeOption.options
|
||||
.any((element) => element.name == option.name);
|
||||
if (!exists) {
|
||||
typeOption.options.insert(0, option);
|
||||
}
|
||||
});
|
||||
List<SelectOptionPB> updateOption(
|
||||
List<SelectOptionPB> options,
|
||||
SelectOptionPB updatedOption,
|
||||
) {
|
||||
final newOptions = List<SelectOptionPB>.from(options);
|
||||
final index =
|
||||
newOptions.indexWhere((option) => option.id == updatedOption.id);
|
||||
if (index != -1) {
|
||||
newOptions[index] = updatedOption;
|
||||
}
|
||||
|
||||
return typeOption.options;
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return typeOption.options;
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
_updateTypeOption(newOptions);
|
||||
return newOptions;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
|
||||
return (SelectOptionPB option) {
|
||||
typeOption.freeze();
|
||||
typeOption = typeOption.rebuild((typeOption) {
|
||||
final index =
|
||||
typeOption.options.indexWhere((element) => element.id == option.id);
|
||||
if (index != -1) {
|
||||
typeOption.options[index] = option;
|
||||
}
|
||||
});
|
||||
return typeOption.options;
|
||||
};
|
||||
void _updateTypeOption(List<SelectOptionPB> options) {
|
||||
final newTypeOption = SingleSelectTypeOptionPB()..options.addAll(options);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'type_option_context.dart';
|
||||
part 'timestamp_bloc.freezed.dart';
|
||||
|
||||
class TimestampTypeOptionBloc
|
||||
extends Bloc<TimestampTypeOptionEvent, TimestampTypeOptionState> {
|
||||
TimestampTypeOptionBloc({
|
||||
required TimestampTypeOptionContext typeOptionContext,
|
||||
}) : super(TimestampTypeOptionState.initial(typeOptionContext.typeOption)) {
|
||||
on<TimestampTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
didSelectDateFormat: (_DidSelectDateFormat value) {
|
||||
_updateTypeOption(dateFormat: value.format, emit: emit);
|
||||
},
|
||||
didSelectTimeFormat: (_DidSelectTimeFormat value) {
|
||||
_updateTypeOption(timeFormat: value.format, emit: emit);
|
||||
},
|
||||
includeTime: (_IncludeTime value) {
|
||||
_updateTypeOption(includeTime: value.includeTime, emit: emit);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _updateTypeOption({
|
||||
DateFormatPB? dateFormat,
|
||||
TimeFormatPB? timeFormat,
|
||||
bool? includeTime,
|
||||
required Emitter<TimestampTypeOptionState> emit,
|
||||
}) {
|
||||
state.typeOption.freeze();
|
||||
final newTypeOption = state.typeOption.rebuild((typeOption) {
|
||||
if (dateFormat != null) {
|
||||
typeOption.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
if (timeFormat != null) {
|
||||
typeOption.timeFormat = timeFormat;
|
||||
}
|
||||
|
||||
if (includeTime != null) {
|
||||
typeOption.includeTime = includeTime;
|
||||
}
|
||||
});
|
||||
emit(state.copyWith(typeOption: newTypeOption));
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class TimestampTypeOptionEvent with _$TimestampTypeOptionEvent {
|
||||
const factory TimestampTypeOptionEvent.didSelectDateFormat(
|
||||
DateFormatPB format,
|
||||
) = _DidSelectDateFormat;
|
||||
const factory TimestampTypeOptionEvent.didSelectTimeFormat(
|
||||
TimeFormatPB format,
|
||||
) = _DidSelectTimeFormat;
|
||||
const factory TimestampTypeOptionEvent.includeTime(bool includeTime) =
|
||||
_IncludeTime;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class TimestampTypeOptionState with _$TimestampTypeOptionState {
|
||||
const factory TimestampTypeOptionState({
|
||||
required TimestampTypeOptionPB typeOption,
|
||||
}) = _TimestampTypeOptionState;
|
||||
|
||||
factory TimestampTypeOptionState.initial(TimestampTypeOptionPB typeOption) =>
|
||||
TimestampTypeOptionState(typeOption: typeOption);
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/text_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'type_option_data_controller.dart';
|
||||
|
||||
abstract class TypeOptionParser<T> {
|
||||
T fromBuffer(List<int> buffer);
|
||||
}
|
||||
|
||||
// Number
|
||||
typedef NumberTypeOptionContext = TypeOptionContext<NumberTypeOptionPB>;
|
||||
|
||||
class NumberTypeOptionWidgetDataParser
|
||||
extends TypeOptionParser<NumberTypeOptionPB> {
|
||||
@override
|
||||
NumberTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return NumberTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// RichText
|
||||
typedef RichTextTypeOptionContext = TypeOptionContext<RichTextTypeOptionPB>;
|
||||
|
||||
class RichTextTypeOptionWidgetDataParser
|
||||
extends TypeOptionParser<RichTextTypeOptionPB> {
|
||||
@override
|
||||
RichTextTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return RichTextTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
typedef CheckboxTypeOptionContext = TypeOptionContext<CheckboxTypeOptionPB>;
|
||||
|
||||
class CheckboxTypeOptionWidgetDataParser
|
||||
extends TypeOptionParser<CheckboxTypeOptionPB> {
|
||||
@override
|
||||
CheckboxTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return CheckboxTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// URL
|
||||
typedef URLTypeOptionContext = TypeOptionContext<URLTypeOptionPB>;
|
||||
|
||||
class URLTypeOptionWidgetDataParser extends TypeOptionParser<URLTypeOptionPB> {
|
||||
@override
|
||||
URLTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return URLTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// DateTime
|
||||
typedef DateTypeOptionContext = TypeOptionContext<DateTypeOptionPB>;
|
||||
|
||||
class DateTypeOptionDataParser extends TypeOptionParser<DateTypeOptionPB> {
|
||||
@override
|
||||
DateTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return DateTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// LastModified and CreatedAt
|
||||
typedef TimestampTypeOptionContext = TypeOptionContext<TimestampTypeOptionPB>;
|
||||
|
||||
class TimestampTypeOptionDataParser
|
||||
extends TypeOptionParser<TimestampTypeOptionPB> {
|
||||
@override
|
||||
TimestampTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return TimestampTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// SingleSelect
|
||||
typedef SingleSelectTypeOptionContext
|
||||
= TypeOptionContext<SingleSelectTypeOptionPB>;
|
||||
|
||||
class SingleSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionParser<SingleSelectTypeOptionPB> {
|
||||
@override
|
||||
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return SingleSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-select
|
||||
typedef MultiSelectTypeOptionContext
|
||||
= TypeOptionContext<MultiSelectTypeOptionPB>;
|
||||
|
||||
class MultiSelectTypeOptionWidgetDataParser
|
||||
extends TypeOptionParser<MultiSelectTypeOptionPB> {
|
||||
@override
|
||||
MultiSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return MultiSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-select
|
||||
typedef ChecklistTypeOptionContext = TypeOptionContext<ChecklistTypeOptionPB>;
|
||||
|
||||
class ChecklistTypeOptionWidgetDataParser
|
||||
extends TypeOptionParser<ChecklistTypeOptionPB> {
|
||||
@override
|
||||
ChecklistTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return ChecklistTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionContext<T extends GeneratedMessage> {
|
||||
T? _typeOptionObject;
|
||||
final TypeOptionParser<T> dataParser;
|
||||
final TypeOptionController _dataController;
|
||||
|
||||
TypeOptionContext({
|
||||
required this.dataParser,
|
||||
required TypeOptionController dataController,
|
||||
}) : _dataController = dataController;
|
||||
|
||||
String get viewId => _dataController.loader.viewId;
|
||||
|
||||
String get fieldId => _dataController.field.id;
|
||||
|
||||
Future<T> loadTypeOptionData({
|
||||
void Function(T)? onCompleted,
|
||||
required void Function(FlowyError) onError,
|
||||
}) async {
|
||||
await _dataController.reloadTypeOption().then((result) {
|
||||
result.fold((l) => null, (err) => onError(err));
|
||||
});
|
||||
|
||||
onCompleted?.call(typeOption);
|
||||
return typeOption;
|
||||
}
|
||||
|
||||
T get typeOption {
|
||||
if (_typeOptionObject != null) {
|
||||
return _typeOptionObject!;
|
||||
}
|
||||
|
||||
final T object = _dataController.getTypeOption(dataParser);
|
||||
_typeOptionObject = object;
|
||||
return object;
|
||||
}
|
||||
|
||||
set typeOption(T typeOption) {
|
||||
_dataController.typeOptionData = typeOption.writeToBuffer();
|
||||
_typeOptionObject = typeOption;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionFieldDelegate {
|
||||
void onFieldChanged(void Function(String) callback);
|
||||
void dispose();
|
||||
}
|
||||
|
||||
abstract class ITypeOptionLoader {
|
||||
String get viewId;
|
||||
String get fieldName;
|
||||
|
||||
Future<Either<TypeOptionPB, FlowyError>> initialize();
|
||||
}
|
||||
|
||||
/// Uses when editing a existing field
|
||||
class FieldTypeOptionLoader {
|
||||
final String viewId;
|
||||
final FieldPB field;
|
||||
|
||||
FieldTypeOptionLoader({
|
||||
required this.viewId,
|
||||
required this.field,
|
||||
});
|
||||
|
||||
Future<Either<TypeOptionPB, FlowyError>> load() {
|
||||
final payload = TypeOptionPathPB.create()
|
||||
..viewId = viewId
|
||||
..fieldId = field.id
|
||||
..fieldType = field.fieldType;
|
||||
|
||||
return DatabaseEventGetTypeOption(payload).send();
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||
|
||||
import '../field_service.dart';
|
||||
import 'type_option_context.dart';
|
||||
|
||||
class TypeOptionController {
|
||||
late TypeOptionPB _typeOption;
|
||||
final FieldTypeOptionLoader loader;
|
||||
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
|
||||
|
||||
/// Returns a [TypeOptionController] used to modify the specified
|
||||
/// [FieldPB]'s data
|
||||
///
|
||||
/// Should call [reloadTypeOption] if the passed-in [FieldInfo]
|
||||
/// is null
|
||||
///
|
||||
TypeOptionController({
|
||||
required this.loader,
|
||||
required FieldPB field,
|
||||
}) {
|
||||
_typeOption = TypeOptionPB.create()
|
||||
..viewId = loader.viewId
|
||||
..field_2 = field;
|
||||
}
|
||||
|
||||
Future<Either<TypeOptionPB, FlowyError>> reloadTypeOption() async {
|
||||
final result = await loader.load();
|
||||
return result.fold(
|
||||
(data) {
|
||||
data.freeze();
|
||||
_typeOption = data;
|
||||
_fieldNotifier.value = data.field_2;
|
||||
return left(data);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
FieldPB get field {
|
||||
return _typeOption.field_2;
|
||||
}
|
||||
|
||||
T getTypeOption<T>(TypeOptionParser<T> parser) {
|
||||
return parser.fromBuffer(_typeOption.typeOptionData);
|
||||
}
|
||||
|
||||
set fieldName(String name) {
|
||||
_typeOption = _typeOption.rebuild((rebuildData) {
|
||||
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
|
||||
rebuildField.name = name;
|
||||
});
|
||||
});
|
||||
|
||||
_fieldNotifier.value = _typeOption.field_2;
|
||||
|
||||
FieldBackendService(viewId: loader.viewId, fieldId: field.id)
|
||||
.updateField(name: name);
|
||||
}
|
||||
|
||||
set typeOptionData(List<int> typeOptionData) {
|
||||
_typeOption = _typeOption.rebuild((rebuildData) {
|
||||
if (typeOptionData.isNotEmpty) {
|
||||
rebuildData.typeOptionData = typeOptionData;
|
||||
}
|
||||
});
|
||||
|
||||
FieldBackendService.updateFieldTypeOption(
|
||||
viewId: loader.viewId,
|
||||
fieldId: field.id,
|
||||
typeOptionData: typeOptionData,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> switchToField(FieldType newFieldType) async {
|
||||
final payload = UpdateFieldTypePayloadPB.create()
|
||||
..viewId = loader.viewId
|
||||
..fieldId = field.id
|
||||
..fieldType = newFieldType;
|
||||
|
||||
final result = await DatabaseEventUpdateFieldType(payload).send();
|
||||
await result.fold(
|
||||
(_) async {
|
||||
// Should load the type-option data after switching to a new field.
|
||||
// After loading the type-option data, the editor widget that uses
|
||||
// the type-option data will be rebuild.
|
||||
await reloadTypeOption();
|
||||
},
|
||||
(err) => Future(() => Log.error(err)),
|
||||
);
|
||||
}
|
||||
|
||||
void Function() addFieldListener(void Function(FieldPB) callback) {
|
||||
void listener() {
|
||||
callback(field);
|
||||
}
|
||||
|
||||
_fieldNotifier.addListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
void removeFieldListener(void Function() listener) {
|
||||
_fieldNotifier.removeListener(listener);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_fieldNotifier.dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/text_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart';
|
||||
|
||||
abstract class TypeOptionParser<T> {
|
||||
T fromBuffer(List<int> buffer);
|
||||
}
|
||||
|
||||
class RichTextTypeOptionDataParser
|
||||
extends TypeOptionParser<RichTextTypeOptionPB> {
|
||||
@override
|
||||
RichTextTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return RichTextTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class NumberTypeOptionDataParser extends TypeOptionParser<NumberTypeOptionPB> {
|
||||
@override
|
||||
NumberTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return NumberTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxTypeOptionDataParser
|
||||
extends TypeOptionParser<CheckboxTypeOptionPB> {
|
||||
@override
|
||||
CheckboxTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return CheckboxTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class URLTypeOptionDataParser extends TypeOptionParser<URLTypeOptionPB> {
|
||||
@override
|
||||
URLTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return URLTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class DateTypeOptionDataParser extends TypeOptionParser<DateTypeOptionPB> {
|
||||
@override
|
||||
DateTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return DateTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class TimestampTypeOptionDataParser
|
||||
extends TypeOptionParser<TimestampTypeOptionPB> {
|
||||
@override
|
||||
TimestampTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return TimestampTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class SingleSelectTypeOptionDataParser
|
||||
extends TypeOptionParser<SingleSelectTypeOptionPB> {
|
||||
@override
|
||||
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return SingleSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSelectTypeOptionDataParser
|
||||
extends TypeOptionParser<MultiSelectTypeOptionPB> {
|
||||
@override
|
||||
MultiSelectTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return MultiSelectTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class ChecklistTypeOptionDataParser
|
||||
extends TypeOptionParser<ChecklistTypeOptionPB> {
|
||||
@override
|
||||
ChecklistTypeOptionPB fromBuffer(List<int> buffer) {
|
||||
return ChecklistTypeOptionPB.fromBuffer(buffer);
|
||||
}
|
||||
}
|
@ -90,17 +90,16 @@ class SelectOptionFilterEditorBloc
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
delegate.loadOptions().then((options) {
|
||||
if (!isClosed) {
|
||||
String filterDesc = '';
|
||||
for (final option in options) {
|
||||
if (state.filter.optionIds.contains(option.id)) {
|
||||
filterDesc += "${option.name} ";
|
||||
}
|
||||
if (!isClosed) {
|
||||
final options = delegate.loadOptions();
|
||||
String filterDesc = '';
|
||||
for (final option in options) {
|
||||
if (state.filter.optionIds.contains(option.id)) {
|
||||
filterDesc += "${option.name} ";
|
||||
}
|
||||
add(SelectOptionFilterEditorEvent.updateFilterDescription(filterDesc));
|
||||
}
|
||||
});
|
||||
add(SelectOptionFilterEditorEvent.updateFilterDescription(filterDesc));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -100,11 +100,10 @@ class SelectOptionFilterListBloc<T>
|
||||
}
|
||||
|
||||
void _loadOptions() {
|
||||
delegate.loadOptions().then((options) {
|
||||
if (!isClosed) {
|
||||
add(SelectOptionFilterListEvent.didReceiveOptions(options));
|
||||
}
|
||||
});
|
||||
if (!isClosed) {
|
||||
final options = delegate.loadOptions();
|
||||
add(SelectOptionFilterListEvent.didReceiveOptions(options));
|
||||
}
|
||||
}
|
||||
|
||||
void _startListening() {}
|
||||
|
@ -32,14 +32,16 @@ class SelectOptionFilterList extends StatelessWidget {
|
||||
viewId: filterInfo.viewId,
|
||||
fieldPB: filterInfo.fieldInfo.field,
|
||||
selectedOptionIds: selectedOptionIds,
|
||||
delegate: SingleSelectOptionFilterDelegateImpl(filterInfo),
|
||||
delegate:
|
||||
SingleSelectOptionFilterDelegateImpl(filterInfo: filterInfo),
|
||||
);
|
||||
} else {
|
||||
bloc = SelectOptionFilterListBloc(
|
||||
viewId: filterInfo.viewId,
|
||||
fieldPB: filterInfo.fieldInfo.field,
|
||||
selectedOptionIds: selectedOptionIds,
|
||||
delegate: MultiSelectOptionFilterDelegateImpl(filterInfo),
|
||||
delegate:
|
||||
MultiSelectOptionFilterDelegateImpl(filterInfo: filterInfo),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -31,12 +31,14 @@ class _SelectOptionFilterChoicechipState
|
||||
if (widget.filterInfo.fieldInfo.fieldType == FieldType.SingleSelect) {
|
||||
bloc = SelectOptionFilterEditorBloc(
|
||||
filterInfo: widget.filterInfo,
|
||||
delegate: SingleSelectOptionFilterDelegateImpl(widget.filterInfo),
|
||||
delegate:
|
||||
SingleSelectOptionFilterDelegateImpl(filterInfo: widget.filterInfo),
|
||||
);
|
||||
} else {
|
||||
bloc = SelectOptionFilterEditorBloc(
|
||||
filterInfo: widget.filterInfo,
|
||||
delegate: MultiSelectOptionFilterDelegateImpl(widget.filterInfo),
|
||||
delegate:
|
||||
MultiSelectOptionFilterDelegateImpl(filterInfo: widget.filterInfo),
|
||||
);
|
||||
}
|
||||
bloc.add(const SelectOptionFilterEditorEvent.initial());
|
||||
|
@ -1,50 +1,35 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/builder.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
|
||||
import '../../filter_info.dart';
|
||||
|
||||
abstract class SelectOptionFilterDelegate {
|
||||
Future<List<SelectOptionPB>> loadOptions();
|
||||
List<SelectOptionPB> loadOptions();
|
||||
}
|
||||
|
||||
class SingleSelectOptionFilterDelegateImpl
|
||||
implements SelectOptionFilterDelegate {
|
||||
final SingleSelectTypeOptionContext typeOptionContext;
|
||||
final FilterInfo filterInfo;
|
||||
|
||||
SingleSelectOptionFilterDelegateImpl(FilterInfo filterInfo)
|
||||
: typeOptionContext = makeSingleSelectTypeOptionContext(
|
||||
viewId: filterInfo.viewId,
|
||||
fieldPB: filterInfo.fieldInfo.field,
|
||||
);
|
||||
SingleSelectOptionFilterDelegateImpl({
|
||||
required this.filterInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<List<SelectOptionPB>> loadOptions() {
|
||||
return typeOptionContext
|
||||
.loadTypeOptionData(
|
||||
onError: (error) => Log.error(error),
|
||||
)
|
||||
.then((value) => value.options);
|
||||
List<SelectOptionPB> loadOptions() {
|
||||
final parser = SingleSelectTypeOptionDataParser();
|
||||
return parser.fromBuffer(filterInfo.fieldInfo.field.typeOptionData).options;
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSelectOptionFilterDelegateImpl
|
||||
implements SelectOptionFilterDelegate {
|
||||
final MultiSelectTypeOptionContext typeOptionContext;
|
||||
final FilterInfo filterInfo;
|
||||
|
||||
MultiSelectOptionFilterDelegateImpl(FilterInfo filterInfo)
|
||||
: typeOptionContext = makeMultiSelectTypeOptionContext(
|
||||
viewId: filterInfo.viewId,
|
||||
fieldPB: filterInfo.fieldInfo.field,
|
||||
);
|
||||
MultiSelectOptionFilterDelegateImpl({required this.filterInfo});
|
||||
|
||||
@override
|
||||
Future<List<SelectOptionPB>> loadOptions() {
|
||||
return typeOptionContext
|
||||
.loadTypeOptionData(
|
||||
onError: (error) => Log.error(error),
|
||||
)
|
||||
.then((value) => value.options);
|
||||
List<SelectOptionPB> loadOptions() {
|
||||
final parser = MultiSelectTypeOptionDataParser();
|
||||
return parser.fromBuffer(filterInfo.fieldInfo.field.typeOptionData).options;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GridHeaderConstants {
|
||||
static Color get backgroundColor => Colors.grey;
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||
@ -18,7 +20,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import 'field_type_option_editor.dart';
|
||||
import 'field_type_list.dart';
|
||||
import 'type_option/builder.dart';
|
||||
|
||||
enum FieldEditorPage {
|
||||
general,
|
||||
@ -70,10 +73,6 @@ class _FieldEditorState extends State<FieldEditor> {
|
||||
field: widget.field,
|
||||
fieldController: widget.fieldController,
|
||||
onFieldInserted: widget.onFieldInserted,
|
||||
loader: FieldTypeOptionLoader(
|
||||
viewId: widget.viewId,
|
||||
field: widget.field,
|
||||
),
|
||||
)..add(const FieldEditorEvent.initial()),
|
||||
child: _currentPage == FieldEditorPage.details
|
||||
? _fieldDetails()
|
||||
@ -326,12 +325,13 @@ class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
|
||||
final List<Widget> children = [
|
||||
FieldNameTextField(
|
||||
popoverMutex: popoverMutex,
|
||||
padding: const EdgeInsets.fromLTRB(12.0, 4.0, 12.0, 0.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
|
||||
textEditingController: widget.textEditingController,
|
||||
),
|
||||
const VSpace(8),
|
||||
FieldTypeOptionCell(popoverMutex: popoverMutex),
|
||||
const TypeOptionSeparator(),
|
||||
const VSpace(8.0),
|
||||
SwitchFieldButton(popoverMutex: popoverMutex),
|
||||
const TypeOptionSeparator(spacing: 8.0),
|
||||
FieldTypeOptionEditor(viewId: widget.viewId, popoverMutex: popoverMutex),
|
||||
_addFieldVisibilityToggleButton(),
|
||||
_addDuplicateFieldButton(),
|
||||
_addDeleteFieldButton(),
|
||||
@ -350,7 +350,7 @@ class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: FieldActionCell(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: state.field,
|
||||
@ -401,11 +401,13 @@ class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
|
||||
}
|
||||
}
|
||||
|
||||
class FieldTypeOptionCell extends StatelessWidget {
|
||||
class FieldTypeOptionEditor extends StatelessWidget {
|
||||
final String viewId;
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
const FieldTypeOptionCell({
|
||||
const FieldTypeOptionEditor({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.popoverMutex,
|
||||
});
|
||||
|
||||
@ -416,14 +418,28 @@ class FieldTypeOptionCell extends StatelessWidget {
|
||||
if (state.field.isPrimary) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final dataController =
|
||||
context.read<FieldEditorBloc>().typeOptionController;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
child: FieldTypeOptionEditor(
|
||||
dataController: dataController,
|
||||
popoverMutex: popoverMutex,
|
||||
),
|
||||
final typeOptionEditor = makeTypeOptionEditor(
|
||||
context: context,
|
||||
viewId: viewId,
|
||||
field: state.field.field,
|
||||
popoverMutex: popoverMutex,
|
||||
onTypeOptionUpdated: (Uint8List typeOptionData) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.updateTypeOption(typeOptionData));
|
||||
},
|
||||
);
|
||||
|
||||
if (typeOptionEditor == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
typeOptionEditor,
|
||||
const TypeOptionSeparator(spacing: 8.0),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -493,3 +509,58 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchFieldButton extends StatefulWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
const SwitchFieldButton({
|
||||
super.key,
|
||||
required this.popoverMutex,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SwitchFieldButton> createState() => _SwitchFieldButtonState();
|
||||
}
|
||||
|
||||
class _SwitchFieldButtonState extends State<SwitchFieldButton> {
|
||||
final PopoverController _popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(460, 540)),
|
||||
triggerActions: PopoverTriggerFlags.hover,
|
||||
mutex: widget.popoverMutex,
|
||||
controller: _popoverController,
|
||||
offset: const Offset(8, 0),
|
||||
margin: const EdgeInsets.all(8),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return FieldTypeList(
|
||||
onSelectField: (newFieldType) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.switchFieldType(newFieldType));
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: _buildMoreButton(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMoreButton(BuildContext context) {
|
||||
final bloc = context.read<FieldEditorBloc>();
|
||||
return FlowyButton(
|
||||
onTap: () => _popoverController.show(),
|
||||
text: FlowyText.medium(
|
||||
bloc.state.field.fieldType.title(),
|
||||
),
|
||||
leftIcon: FlowySvg(bloc.state.field.fieldType.icon()),
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,135 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_type_option_edit_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'field_type_extension.dart';
|
||||
import 'field_type_list.dart';
|
||||
import 'type_option/builder.dart';
|
||||
|
||||
typedef UpdateFieldCallback = void Function(FieldPB, Uint8List);
|
||||
typedef SwitchToFieldCallback = Future<Either<TypeOptionPB, FlowyError>>
|
||||
Function(
|
||||
String fieldId,
|
||||
FieldType fieldType,
|
||||
);
|
||||
|
||||
class FieldTypeOptionEditor extends StatelessWidget {
|
||||
final TypeOptionController _dataController;
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
const FieldTypeOptionEditor({
|
||||
super.key,
|
||||
required TypeOptionController dataController,
|
||||
required this.popoverMutex,
|
||||
}) : _dataController = dataController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return FieldTypeOptionEditBloc(_dataController)
|
||||
..add(const FieldTypeOptionEditEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<FieldTypeOptionEditBloc, FieldTypeOptionEditState>(
|
||||
builder: (context, state) {
|
||||
final typeOptionWidget = _typeOptionWidget(
|
||||
context: context,
|
||||
state: state,
|
||||
);
|
||||
|
||||
final List<Widget> children = [
|
||||
SwitchFieldButton(popoverMutex: popoverMutex),
|
||||
if (typeOptionWidget != null) typeOptionWidget,
|
||||
];
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _typeOptionWidget({
|
||||
required BuildContext context,
|
||||
required FieldTypeOptionEditState state,
|
||||
}) {
|
||||
return makeTypeOptionWidget(
|
||||
context: context,
|
||||
dataController: _dataController,
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchFieldButton extends StatefulWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
const SwitchFieldButton({
|
||||
super.key,
|
||||
required this.popoverMutex,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SwitchFieldButton> createState() => _SwitchFieldButtonState();
|
||||
}
|
||||
|
||||
class _SwitchFieldButtonState extends State<SwitchFieldButton> {
|
||||
final PopoverController _popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(460, 540)),
|
||||
triggerActions: PopoverTriggerFlags.hover,
|
||||
mutex: widget.popoverMutex,
|
||||
controller: _popoverController,
|
||||
offset: const Offset(8, 0),
|
||||
margin: const EdgeInsets.all(8),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return FieldTypeList(
|
||||
onSelectField: (newFieldType) {
|
||||
context
|
||||
.read<FieldTypeOptionEditBloc>()
|
||||
.add(FieldTypeOptionEditEvent.switchToField(newFieldType));
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: _buildMoreButton(context),
|
||||
),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMoreButton(BuildContext context) {
|
||||
final bloc = context.read<FieldTypeOptionEditBloc>();
|
||||
return FlowyButton(
|
||||
onTap: () => _popoverController.show(),
|
||||
text: FlowyText.medium(
|
||||
bloc.state.field.fieldType.title(),
|
||||
),
|
||||
leftIcon: FlowySvg(bloc.state.field.fieldType.icon()),
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionWidget extends StatelessWidget {
|
||||
const TypeOptionWidget({super.key});
|
||||
}
|
@ -196,7 +196,7 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
||||
viewId: widget.viewId,
|
||||
);
|
||||
result.fold(
|
||||
(typeOptionPB) => widget.onFieldCreated(typeOptionPB.field_2.id),
|
||||
(field) => widget.onFieldCreated(field.id),
|
||||
(err) => Log.error("Failed to create field type option: $err"),
|
||||
);
|
||||
},
|
||||
|
@ -1,19 +1,9 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_controller.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/text_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'checkbox.dart';
|
||||
import 'checklist.dart';
|
||||
import 'date.dart';
|
||||
@ -24,237 +14,47 @@ import 'single_select.dart';
|
||||
import 'timestamp.dart';
|
||||
import 'url.dart';
|
||||
|
||||
typedef TypeOptionData = Uint8List;
|
||||
typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData);
|
||||
typedef ShowOverlayCallback = void Function(
|
||||
BuildContext anchorContext,
|
||||
Widget child, {
|
||||
VoidCallback? onRemoved,
|
||||
});
|
||||
typedef HideOverlayCallback = void Function(BuildContext anchorContext);
|
||||
typedef TypeOptionDataCallback = void Function(Uint8List typeOptionData);
|
||||
|
||||
class TypeOptionOverlayDelegate {
|
||||
ShowOverlayCallback showOverlay;
|
||||
HideOverlayCallback hideOverlay;
|
||||
TypeOptionOverlayDelegate({
|
||||
required this.showOverlay,
|
||||
required this.hideOverlay,
|
||||
abstract class TypeOptionEditorFactory {
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
});
|
||||
|
||||
factory TypeOptionEditorFactory.makeBuilder(FieldType fieldType) {
|
||||
return switch (fieldType) {
|
||||
FieldType.RichText => const RichTextTypeOptionEditorFactory(),
|
||||
FieldType.Number => const NumberTypeOptionEditorFactory(),
|
||||
FieldType.URL => const URLTypeOptionEditorFactory(),
|
||||
FieldType.DateTime => const DateTypeOptionEditorFactory(),
|
||||
FieldType.LastEditedTime => const TimestampTypeOptionEditorFactory(),
|
||||
FieldType.CreatedTime => const TimestampTypeOptionEditorFactory(),
|
||||
FieldType.SingleSelect => const SingleSelectTypeOptionEditorFactory(),
|
||||
FieldType.MultiSelect => const MultiSelectTypeOptionEditorFactory(),
|
||||
FieldType.Checkbox => const CheckboxTypeOptionEditorFactory(),
|
||||
FieldType.Checklist => const ChecklistTypeOptionEditorFactory(),
|
||||
_ => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TypeOptionWidgetBuilder {
|
||||
Widget? build(BuildContext context);
|
||||
}
|
||||
|
||||
Widget? makeTypeOptionWidget({
|
||||
Widget? makeTypeOptionEditor({
|
||||
required BuildContext context,
|
||||
required TypeOptionController dataController,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) {
|
||||
final builder = makeTypeOptionWidgetBuilder(
|
||||
dataController: dataController,
|
||||
final editorBuilder = TypeOptionEditorFactory.makeBuilder(field.fieldType);
|
||||
return editorBuilder.build(
|
||||
context: context,
|
||||
viewId: viewId,
|
||||
field: field,
|
||||
onTypeOptionUpdated: onTypeOptionUpdated,
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
return builder.build(context);
|
||||
}
|
||||
|
||||
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
|
||||
required TypeOptionController dataController,
|
||||
required PopoverMutex popoverMutex,
|
||||
}) {
|
||||
final viewId = dataController.loader.viewId;
|
||||
final fieldType = dataController.field.fieldType;
|
||||
|
||||
switch (dataController.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return CheckboxTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<CheckboxTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return DateTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
popoverMutex,
|
||||
);
|
||||
case FieldType.LastEditedTime:
|
||||
case FieldType.CreatedTime:
|
||||
return TimestampTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<TimestampTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
popoverMutex,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return SingleSelectTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<SingleSelectTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
popoverMutex,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return MultiSelectTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<MultiSelectTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
popoverMutex,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return NumberTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<NumberTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
popoverMutex,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return RichTextTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<RichTextTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
|
||||
case FieldType.URL:
|
||||
return URLTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<URLTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
|
||||
case FieldType.Checklist:
|
||||
return ChecklistTypeOptionWidgetBuilder(
|
||||
makeTypeOptionContextWithDataController<ChecklistTypeOptionPB>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldType,
|
||||
dataController: dataController,
|
||||
),
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
||||
TypeOptionContext<T> makeTypeOptionContext<T extends GeneratedMessage>({
|
||||
required String viewId,
|
||||
required FieldInfo fieldInfo,
|
||||
}) {
|
||||
final loader = FieldTypeOptionLoader(viewId: viewId, field: fieldInfo.field);
|
||||
final dataController = TypeOptionController(
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
);
|
||||
return makeTypeOptionContextWithDataController(
|
||||
viewId: viewId,
|
||||
fieldType: fieldInfo.fieldType,
|
||||
dataController: dataController,
|
||||
);
|
||||
}
|
||||
|
||||
TypeOptionContext<SingleSelectTypeOptionPB> makeSingleSelectTypeOptionContext({
|
||||
required String viewId,
|
||||
required FieldPB fieldPB,
|
||||
}) {
|
||||
return makeSelectTypeOptionContext(viewId: viewId, fieldPB: fieldPB);
|
||||
}
|
||||
|
||||
TypeOptionContext<MultiSelectTypeOptionPB> makeMultiSelectTypeOptionContext({
|
||||
required String viewId,
|
||||
required FieldPB fieldPB,
|
||||
}) {
|
||||
return makeSelectTypeOptionContext(viewId: viewId, fieldPB: fieldPB);
|
||||
}
|
||||
|
||||
TypeOptionContext<T> makeSelectTypeOptionContext<T extends GeneratedMessage>({
|
||||
required String viewId,
|
||||
required FieldPB fieldPB,
|
||||
}) {
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: viewId,
|
||||
field: fieldPB,
|
||||
);
|
||||
final dataController = TypeOptionController(
|
||||
loader: loader,
|
||||
field: fieldPB,
|
||||
);
|
||||
final typeOptionContext = makeTypeOptionContextWithDataController<T>(
|
||||
viewId: viewId,
|
||||
fieldType: fieldPB.fieldType,
|
||||
dataController: dataController,
|
||||
);
|
||||
return typeOptionContext;
|
||||
}
|
||||
|
||||
TypeOptionContext<T>
|
||||
makeTypeOptionContextWithDataController<T extends GeneratedMessage>({
|
||||
required String viewId,
|
||||
required FieldType fieldType,
|
||||
required TypeOptionController dataController,
|
||||
}) {
|
||||
switch (fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return CheckboxTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: CheckboxTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.DateTime:
|
||||
return DateTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: DateTypeOptionDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.LastEditedTime:
|
||||
case FieldType.CreatedTime:
|
||||
return TimestampTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: TimestampTypeOptionDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.SingleSelect:
|
||||
return SingleSelectTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: SingleSelectTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.MultiSelect:
|
||||
return MultiSelectTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: MultiSelectTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.Checklist:
|
||||
return ChecklistTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: ChecklistTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.Number:
|
||||
return NumberTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: NumberTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
case FieldType.RichText:
|
||||
return RichTextTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: RichTextTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
|
||||
case FieldType.URL:
|
||||
return URLTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataParser: URLTypeOptionWidgetDataParser(),
|
||||
) as TypeOptionContext<T>;
|
||||
}
|
||||
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
@ -1,10 +1,19 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'builder.dart';
|
||||
|
||||
class CheckboxTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
CheckboxTypeOptionWidgetBuilder(CheckboxTypeOptionContext typeOptionContext);
|
||||
class CheckboxTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const CheckboxTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => null;
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) =>
|
||||
null;
|
||||
}
|
||||
|
@ -1,12 +1,19 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'builder.dart';
|
||||
|
||||
class ChecklistTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
ChecklistTypeOptionWidgetBuilder(
|
||||
ChecklistTypeOptionContext typeOptionContext,
|
||||
);
|
||||
class ChecklistTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const ChecklistTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => null;
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) =>
|
||||
null;
|
||||
}
|
||||
|
@ -1,80 +1,49 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/date_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide DateFormat;
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../common/type_option_separator.dart';
|
||||
import '../field_type_option_editor.dart';
|
||||
import 'builder.dart';
|
||||
import 'date/date_time_format.dart';
|
||||
|
||||
class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
final DateTypeOptionWidget _widget;
|
||||
|
||||
DateTypeOptionWidgetBuilder(
|
||||
DateTypeOptionContext typeOptionContext,
|
||||
PopoverMutex popoverMutex,
|
||||
) : _widget = DateTypeOptionWidget(
|
||||
typeOptionContext: typeOptionContext,
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
class DateTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const DateTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) {
|
||||
return _widget;
|
||||
}
|
||||
}
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) {
|
||||
final typeOption = _parseTypeOptionData(field.typeOptionData);
|
||||
|
||||
class DateTypeOptionWidget extends TypeOptionWidget {
|
||||
final DateTypeOptionContext typeOptionContext;
|
||||
final PopoverMutex popoverMutex;
|
||||
const DateTypeOptionWidget({
|
||||
required this.typeOptionContext,
|
||||
required this.popoverMutex,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
DateTypeOptionBloc(typeOptionContext: typeOptionContext),
|
||||
child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>(
|
||||
listener: (context, state) =>
|
||||
typeOptionContext.typeOption = state.typeOption,
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
const TypeOptionSeparator(),
|
||||
_renderDateFormatButton(context, state.typeOption.dateFormat),
|
||||
_renderTimeFormatButton(context, state.typeOption.timeFormat),
|
||||
];
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
}
|
||||
},
|
||||
itemCount: children.length,
|
||||
itemBuilder: (BuildContext context, int index) => children[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_renderDateFormatButton(
|
||||
typeOption,
|
||||
popoverMutex,
|
||||
onTypeOptionUpdated,
|
||||
),
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
_renderTimeFormatButton(
|
||||
typeOption,
|
||||
popoverMutex,
|
||||
onTypeOptionUpdated,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderDateFormatButton(
|
||||
BuildContext context,
|
||||
DateFormatPB dataFormat,
|
||||
DateTypeOptionPB typeOption,
|
||||
PopoverMutex popoverMutex,
|
||||
TypeOptionDataCallback onTypeOptionUpdated,
|
||||
) {
|
||||
return AppFlowyPopover(
|
||||
mutex: popoverMutex,
|
||||
@ -84,11 +53,11 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
popupBuilder: (popoverContext) {
|
||||
return DateFormatList(
|
||||
selectedFormat: dataFormat,
|
||||
selectedFormat: typeOption.dateFormat,
|
||||
onSelected: (format) {
|
||||
context
|
||||
.read<DateTypeOptionBloc>()
|
||||
.add(DateTypeOptionEvent.didSelectDateFormat(format));
|
||||
final newTypeOption =
|
||||
_updateTypeOption(typeOption: typeOption, dateFormat: format);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
);
|
||||
@ -101,8 +70,9 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
||||
}
|
||||
|
||||
Widget _renderTimeFormatButton(
|
||||
BuildContext context,
|
||||
TimeFormatPB timeFormat,
|
||||
DateTypeOptionPB typeOption,
|
||||
PopoverMutex popoverMutex,
|
||||
TypeOptionDataCallback onTypeOptionUpdated,
|
||||
) {
|
||||
return AppFlowyPopover(
|
||||
mutex: popoverMutex,
|
||||
@ -112,227 +82,40 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return TimeFormatList(
|
||||
selectedFormat: timeFormat,
|
||||
selectedFormat: typeOption.timeFormat,
|
||||
onSelected: (format) {
|
||||
context
|
||||
.read<DateTypeOptionBloc>()
|
||||
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
|
||||
final newTypeOption =
|
||||
_updateTypeOption(typeOption: typeOption, timeFormat: format);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: TimeFormatButton(timeFormat: timeFormat),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: TimeFormatButton(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatButton extends StatelessWidget {
|
||||
final VoidCallback? onTap;
|
||||
final void Function(bool)? onHover;
|
||||
const DateFormatButton({
|
||||
this.onTap,
|
||||
this.onHover,
|
||||
super.key,
|
||||
});
|
||||
DateTypeOptionPB _parseTypeOptionData(List<int> data) {
|
||||
return DateTypeOptionDataParser().fromBuffer(data);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr()),
|
||||
onTap: onTap,
|
||||
onHover: onHover,
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeFormatButton extends StatelessWidget {
|
||||
final TimeFormatPB timeFormat;
|
||||
final VoidCallback? onTap;
|
||||
final void Function(bool)? onHover;
|
||||
const TimeFormatButton({
|
||||
required this.timeFormat,
|
||||
this.onTap,
|
||||
this.onHover,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr()),
|
||||
onTap: onTap,
|
||||
onHover: onHover,
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatList extends StatelessWidget {
|
||||
final DateFormatPB selectedFormat;
|
||||
final Function(DateFormatPB format) onSelected;
|
||||
const DateFormatList({
|
||||
required this.selectedFormat,
|
||||
required this.onSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cells = DateFormatPB.values.map((format) {
|
||||
return DateFormatCell(
|
||||
dateFormat: format,
|
||||
onSelected: onSelected,
|
||||
isSelected: selectedFormat == format,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return SizedBox(
|
||||
width: 180,
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (context, index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
itemCount: cells.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return cells[index];
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatCell extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final DateFormatPB dateFormat;
|
||||
final Function(DateFormatPB format) onSelected;
|
||||
const DateFormatCell({
|
||||
required this.dateFormat,
|
||||
required this.onSelected,
|
||||
required this.isSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(FlowySvgs.check_s);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(dateFormat.title()),
|
||||
rightIcon: checkmark,
|
||||
onTap: () => onSelected(dateFormat),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension DateFormatExtension on DateFormatPB {
|
||||
String title() {
|
||||
switch (this) {
|
||||
case DateFormatPB.Friendly:
|
||||
return LocaleKeys.grid_field_dateFormatFriendly.tr();
|
||||
case DateFormatPB.ISO:
|
||||
return LocaleKeys.grid_field_dateFormatISO.tr();
|
||||
case DateFormatPB.Local:
|
||||
return LocaleKeys.grid_field_dateFormatLocal.tr();
|
||||
case DateFormatPB.US:
|
||||
return LocaleKeys.grid_field_dateFormatUS.tr();
|
||||
case DateFormatPB.DayMonthYear:
|
||||
return LocaleKeys.grid_field_dateFormatDayMonthYear.tr();
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TimeFormatList extends StatelessWidget {
|
||||
final TimeFormatPB selectedFormat;
|
||||
final Function(TimeFormatPB format) onSelected;
|
||||
const TimeFormatList({
|
||||
required this.selectedFormat,
|
||||
required this.onSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cells = TimeFormatPB.values.map((format) {
|
||||
return TimeFormatCell(
|
||||
isSelected: format == selectedFormat,
|
||||
timeFormat: format,
|
||||
onSelected: onSelected,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return SizedBox(
|
||||
width: 120,
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (context, index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
itemCount: cells.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return cells[index];
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeFormatCell extends StatelessWidget {
|
||||
final TimeFormatPB timeFormat;
|
||||
final bool isSelected;
|
||||
final Function(TimeFormatPB format) onSelected;
|
||||
const TimeFormatCell({
|
||||
required this.timeFormat,
|
||||
required this.onSelected,
|
||||
required this.isSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(FlowySvgs.check_s);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(timeFormat.title()),
|
||||
rightIcon: checkmark,
|
||||
onTap: () => onSelected(timeFormat),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension TimeFormatExtension on TimeFormatPB {
|
||||
String title() {
|
||||
switch (this) {
|
||||
case TimeFormatPB.TwelveHour:
|
||||
return LocaleKeys.grid_field_timeFormatTwelveHour.tr();
|
||||
case TimeFormatPB.TwentyFourHour:
|
||||
return LocaleKeys.grid_field_timeFormatTwentyFourHour.tr();
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
DateTypeOptionPB _updateTypeOption({
|
||||
required DateTypeOptionPB typeOption,
|
||||
DateFormatPB? dateFormat,
|
||||
TimeFormatPB? timeFormat,
|
||||
}) {
|
||||
typeOption.freeze();
|
||||
return typeOption.rebuild((typeOption) {
|
||||
if (dateFormat != null) {
|
||||
typeOption.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
if (timeFormat != null) {
|
||||
typeOption.timeFormat = timeFormat;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,254 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DateFormatButton extends StatelessWidget {
|
||||
const DateFormatButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.onHover,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final void Function(bool)? onHover;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr()),
|
||||
onTap: onTap,
|
||||
onHover: onHover,
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeFormatButton extends StatelessWidget {
|
||||
const TimeFormatButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.onHover,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final void Function(bool)? onHover;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr()),
|
||||
onTap: onTap,
|
||||
onHover: onHover,
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatList extends StatelessWidget {
|
||||
final DateFormatPB selectedFormat;
|
||||
final Function(DateFormatPB format) onSelected;
|
||||
const DateFormatList({
|
||||
required this.selectedFormat,
|
||||
required this.onSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cells = DateFormatPB.values.map((format) {
|
||||
return DateFormatCell(
|
||||
dateFormat: format,
|
||||
onSelected: onSelected,
|
||||
isSelected: selectedFormat == format,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return SizedBox(
|
||||
width: 180,
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (context, index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
itemCount: cells.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return cells[index];
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatCell extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final DateFormatPB dateFormat;
|
||||
final Function(DateFormatPB format) onSelected;
|
||||
const DateFormatCell({
|
||||
required this.dateFormat,
|
||||
required this.onSelected,
|
||||
required this.isSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(FlowySvgs.check_s);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(dateFormat.title()),
|
||||
rightIcon: checkmark,
|
||||
onTap: () => onSelected(dateFormat),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension DateFormatExtension on DateFormatPB {
|
||||
String title() {
|
||||
switch (this) {
|
||||
case DateFormatPB.Friendly:
|
||||
return LocaleKeys.grid_field_dateFormatFriendly.tr();
|
||||
case DateFormatPB.ISO:
|
||||
return LocaleKeys.grid_field_dateFormatISO.tr();
|
||||
case DateFormatPB.Local:
|
||||
return LocaleKeys.grid_field_dateFormatLocal.tr();
|
||||
case DateFormatPB.US:
|
||||
return LocaleKeys.grid_field_dateFormatUS.tr();
|
||||
case DateFormatPB.DayMonthYear:
|
||||
return LocaleKeys.grid_field_dateFormatDayMonthYear.tr();
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TimeFormatList extends StatelessWidget {
|
||||
final TimeFormatPB selectedFormat;
|
||||
final Function(TimeFormatPB format) onSelected;
|
||||
const TimeFormatList({
|
||||
required this.selectedFormat,
|
||||
required this.onSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cells = TimeFormatPB.values.map((format) {
|
||||
return TimeFormatCell(
|
||||
isSelected: format == selectedFormat,
|
||||
timeFormat: format,
|
||||
onSelected: onSelected,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return SizedBox(
|
||||
width: 120,
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (context, index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
itemCount: cells.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return cells[index];
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeFormatCell extends StatelessWidget {
|
||||
final TimeFormatPB timeFormat;
|
||||
final bool isSelected;
|
||||
final Function(TimeFormatPB format) onSelected;
|
||||
const TimeFormatCell({
|
||||
required this.timeFormat,
|
||||
required this.onSelected,
|
||||
required this.isSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(FlowySvgs.check_s);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(timeFormat.title()),
|
||||
rightIcon: checkmark,
|
||||
onTap: () => onSelected(timeFormat),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension TimeFormatExtension on TimeFormatPB {
|
||||
String title() {
|
||||
switch (this) {
|
||||
case TimeFormatPB.TwelveHour:
|
||||
return LocaleKeys.grid_field_timeFormatTwelveHour.tr();
|
||||
case TimeFormatPB.TwentyFourHour:
|
||||
return LocaleKeys.grid_field_timeFormatTwentyFourHour.tr();
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IncludeTimeButton extends StatelessWidget {
|
||||
final bool value;
|
||||
final Function(bool value) onChanged;
|
||||
const IncludeTimeButton({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: Row(
|
||||
children: [
|
||||
FlowySvg(
|
||||
FlowySvgs.clock_alarm_s,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
const HSpace(6),
|
||||
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr()),
|
||||
const Spacer(),
|
||||
Toggle(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,51 +1,38 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/multi_select_type_option.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
||||
import '../field_type_option_editor.dart';
|
||||
import 'builder.dart';
|
||||
import 'select_option.dart';
|
||||
import 'select/select_option.dart';
|
||||
|
||||
class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
final MultiSelectTypeOptionWidget _widget;
|
||||
|
||||
MultiSelectTypeOptionWidgetBuilder(
|
||||
MultiSelectTypeOptionContext typeOptionContext,
|
||||
PopoverMutex popoverMutex,
|
||||
) : _widget = MultiSelectTypeOptionWidget(
|
||||
selectOptionAction: MultiSelectAction(
|
||||
fieldId: typeOptionContext.fieldId,
|
||||
viewId: typeOptionContext.viewId,
|
||||
typeOptionContext: typeOptionContext,
|
||||
),
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
class MultiSelectTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const MultiSelectTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => _widget;
|
||||
}
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) {
|
||||
final typeOption = _parseTypeOptionData(field.typeOptionData);
|
||||
|
||||
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
final MultiSelectAction selectOptionAction;
|
||||
final PopoverMutex? popoverMutex;
|
||||
|
||||
const MultiSelectTypeOptionWidget({
|
||||
super.key,
|
||||
required this.selectOptionAction,
|
||||
this.popoverMutex,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectOptionTypeOptionWidget(
|
||||
options: selectOptionAction.typeOption.options,
|
||||
beginEdit: () {
|
||||
PopoverContainer.of(context).closeAll();
|
||||
},
|
||||
options: typeOption.options,
|
||||
beginEdit: () => PopoverContainer.of(context).closeAll(),
|
||||
popoverMutex: popoverMutex,
|
||||
typeOptionAction: selectOptionAction,
|
||||
// key: ValueKey(state.typeOption.hashCode),
|
||||
typeOptionAction: MultiSelectAction(
|
||||
viewId: viewId,
|
||||
fieldId: field.id,
|
||||
onTypeOptionUpdated: onTypeOptionUpdated,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
MultiSelectTypeOptionPB _parseTypeOptionData(List<int> data) {
|
||||
return MultiSelectTypeOptionDataParser().fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
@ -1,127 +1,106 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/number_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/number_format_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pbenum.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../common/type_option_separator.dart';
|
||||
import '../field_type_option_editor.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
final NumberTypeOptionWidget _widget;
|
||||
|
||||
NumberTypeOptionWidgetBuilder(
|
||||
NumberTypeOptionContext typeOptionContext,
|
||||
PopoverMutex popoverMutex,
|
||||
) : _widget = NumberTypeOptionWidget(
|
||||
typeOptionContext: typeOptionContext,
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
class NumberTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const NumberTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
const TypeOptionSeparator(),
|
||||
_widget,
|
||||
],
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) {
|
||||
final typeOption = _parseTypeOptionData(field.typeOptionData);
|
||||
|
||||
final selectNumUnitButton = SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
text: FlowyText.medium(
|
||||
typeOption.format.title(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
final NumberTypeOptionContext typeOptionContext;
|
||||
final PopoverMutex popoverMutex;
|
||||
final numFormatTitle = Container(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
height: GridSize.popoverItemHeight,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_field_numberFormat.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
fontSize: 11,
|
||||
),
|
||||
);
|
||||
|
||||
const NumberTypeOptionWidget({
|
||||
super.key,
|
||||
required this.typeOptionContext,
|
||||
required this.popoverMutex,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
NumberTypeOptionBloc(typeOptionContext: typeOptionContext),
|
||||
child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>(
|
||||
listener: (context, state) =>
|
||||
typeOptionContext.typeOption = state.typeOption,
|
||||
builder: (context, state) {
|
||||
final selectNumUnitButton = SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
text: FlowyText.regular(
|
||||
state.typeOption.format.title(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final numFormatTitle = Container(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
height: GridSize.popoverItemHeight,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_field_numberFormat.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
fontSize: 11,
|
||||
),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
numFormatTitle,
|
||||
AppFlowyPopover(
|
||||
mutex: popoverMutex,
|
||||
triggerActions:
|
||||
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
offset: const Offset(16, 0),
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
margin: EdgeInsets.zero,
|
||||
child: selectNumUnitButton,
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return NumberFormatList(
|
||||
onSelected: (format) {
|
||||
context
|
||||
.read<NumberTypeOptionBloc>()
|
||||
.add(NumberTypeOptionEvent.didSelectFormat(format));
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
selectedFormat: state.typeOption.format,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
numFormatTitle,
|
||||
AppFlowyPopover(
|
||||
mutex: popoverMutex,
|
||||
triggerActions:
|
||||
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
offset: const Offset(16, 0),
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
margin: EdgeInsets.zero,
|
||||
child: selectNumUnitButton,
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return NumberFormatList(
|
||||
selectedFormat: typeOption.format,
|
||||
onSelected: (format) {
|
||||
final newTypeOption = _updateNumberFormat(typeOption, format);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
NumberTypeOptionPB _parseTypeOptionData(List<int> data) {
|
||||
return NumberTypeOptionDataParser().fromBuffer(data);
|
||||
}
|
||||
|
||||
NumberTypeOptionPB _updateNumberFormat(
|
||||
NumberTypeOptionPB typeOption,
|
||||
NumberFormatPB format,
|
||||
) {
|
||||
typeOption.freeze();
|
||||
return typeOption.rebuild((typeOption) => typeOption.format = format);
|
||||
}
|
||||
}
|
||||
|
||||
typedef SelectNumberFormatCallback = Function(NumberFormatPB format);
|
||||
typedef SelectNumberFormatCallback = void Function(NumberFormatPB format);
|
||||
|
||||
class NumberFormatList extends StatelessWidget {
|
||||
final SelectNumberFormatCallback onSelected;
|
||||
final NumberFormatPB selectedFormat;
|
||||
|
||||
const NumberFormatList({
|
||||
super.key,
|
||||
required this.selectedFormat,
|
||||
required this.onSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -171,7 +150,7 @@ class NumberFormatList extends StatelessWidget {
|
||||
class NumberFormatCell extends StatelessWidget {
|
||||
final NumberFormatPB format;
|
||||
final bool isSelected;
|
||||
final Function(NumberFormatPB format) onSelected;
|
||||
final SelectNumberFormatCallback onSelected;
|
||||
const NumberFormatCell({
|
||||
required this.isSelected,
|
||||
required this.format,
|
||||
@ -181,10 +160,7 @@ class NumberFormatCell extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(FlowySvgs.check_s);
|
||||
}
|
||||
final checkmark = isSelected ? const FlowySvg(FlowySvgs.check_s) : null;
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
|
@ -1,10 +1,18 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
class RichTextTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
RichTextTypeOptionWidgetBuilder(RichTextTypeOptionContext typeOptionContext);
|
||||
class RichTextTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const RichTextTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => null;
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) =>
|
||||
null;
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/select_option_type_option_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../../../../widgets/row/cells/select_option_cell/extension.dart';
|
||||
import '../../common/type_option_separator.dart';
|
||||
import 'select_option_editor.dart';
|
||||
|
||||
class SelectOptionTypeOptionWidget extends StatelessWidget {
|
||||
@ -39,7 +37,6 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
||||
BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
const TypeOptionSeparator(spacing: 8),
|
||||
const _OptionTitle(),
|
||||
const VSpace(4),
|
||||
if (state.isEditingOption) ...[
|
||||
@ -127,13 +124,7 @@ class _OptionCell extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _OptionCellState extends State<_OptionCell> {
|
||||
late PopoverController _popoverController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popoverController = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
final PopoverController _popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -230,18 +221,18 @@ class _CreateOptionTextFieldState extends State<CreateOptionTextField> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
_focusNode = FocusNode()
|
||||
..addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
widget.popoverMutex?.listenOnPopoverChanged(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
_focusNode.unfocus();
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
@ -13,8 +13,8 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../layout/sizes.dart';
|
||||
import '../../common/type_option_separator.dart';
|
||||
import '../../../../layout/sizes.dart';
|
||||
import '../../../common/type_option_separator.dart';
|
||||
|
||||
class SelectOptionTypeOptionEditor extends StatelessWidget {
|
||||
final SelectOptionPB option;
|
@ -1,49 +1,38 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/single_select_type_option.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../field_type_option_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
||||
import 'builder.dart';
|
||||
import 'select_option.dart';
|
||||
import 'select/select_option.dart';
|
||||
|
||||
class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
final SingleSelectTypeOptionWidget _widget;
|
||||
|
||||
SingleSelectTypeOptionWidgetBuilder(
|
||||
SingleSelectTypeOptionContext singleSelectTypeOption,
|
||||
PopoverMutex popoverMutex,
|
||||
) : _widget = SingleSelectTypeOptionWidget(
|
||||
selectOptionAction: SingleSelectAction(
|
||||
fieldId: singleSelectTypeOption.fieldId,
|
||||
viewId: singleSelectTypeOption.viewId,
|
||||
typeOptionContext: singleSelectTypeOption,
|
||||
),
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
class SingleSelectTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const SingleSelectTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => _widget;
|
||||
}
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) {
|
||||
final typeOption = _parseTypeOptionData(field.typeOptionData);
|
||||
|
||||
class SingleSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
final SingleSelectAction selectOptionAction;
|
||||
final PopoverMutex? popoverMutex;
|
||||
|
||||
const SingleSelectTypeOptionWidget({
|
||||
super.key,
|
||||
required this.selectOptionAction,
|
||||
this.popoverMutex,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectOptionTypeOptionWidget(
|
||||
options: selectOptionAction.typeOption.options,
|
||||
beginEdit: () {
|
||||
PopoverContainer.of(context).closeAll();
|
||||
},
|
||||
options: typeOption.options,
|
||||
beginEdit: () => PopoverContainer.of(context).closeAll(),
|
||||
popoverMutex: popoverMutex,
|
||||
typeOptionAction: selectOptionAction,
|
||||
typeOptionAction: SingleSelectAction(
|
||||
viewId: viewId,
|
||||
fieldId: field.id,
|
||||
onTypeOptionUpdated: onTypeOptionUpdated,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SingleSelectTypeOptionPB _parseTypeOptionData(List<int> data) {
|
||||
return SingleSelectTypeOptionDataParser().fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
@ -1,88 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/timestamp_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/include_time_button.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'builder.dart';
|
||||
import 'date.dart';
|
||||
import 'date/date_time_format.dart';
|
||||
|
||||
class TimestampTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
TimestampTypeOptionWidgetBuilder(
|
||||
TimestampTypeOptionContext typeOptionContext,
|
||||
PopoverMutex popoverMutex,
|
||||
) : _widget = TimestampTypeOptionWidget(
|
||||
typeOptionContext: typeOptionContext,
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
|
||||
final TimestampTypeOptionWidget _widget;
|
||||
class TimestampTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const TimestampTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => _widget;
|
||||
}
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) {
|
||||
final typeOption = _parseTypeOptionData(field.typeOptionData);
|
||||
|
||||
class TimestampTypeOptionWidget extends TypeOptionWidget {
|
||||
const TimestampTypeOptionWidget({
|
||||
super.key,
|
||||
required this.typeOptionContext,
|
||||
required this.popoverMutex,
|
||||
});
|
||||
|
||||
final TimestampTypeOptionContext typeOptionContext;
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
TimestampTypeOptionBloc(typeOptionContext: typeOptionContext),
|
||||
child: BlocConsumer<TimestampTypeOptionBloc, TimestampTypeOptionState>(
|
||||
listener: (context, state) =>
|
||||
typeOptionContext.typeOption = state.typeOption,
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
const TypeOptionSeparator(),
|
||||
_renderDateFormatButton(context, state.typeOption.dateFormat),
|
||||
_renderTimeFormatButton(context, state.typeOption.timeFormat),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: IncludeTimeButton(
|
||||
onChanged: (value) => context
|
||||
.read<TimestampTypeOptionBloc>()
|
||||
.add(TimestampTypeOptionEvent.includeTime(!value)),
|
||||
value: state.typeOption.includeTime,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
separatorBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
}
|
||||
return SeparatedColumn(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
separatorBuilder: () => VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
children: [
|
||||
_renderDateFormatButton(typeOption, popoverMutex, onTypeOptionUpdated),
|
||||
_renderTimeFormatButton(typeOption, popoverMutex, onTypeOptionUpdated),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: IncludeTimeButton(
|
||||
onChanged: (value) {
|
||||
final newTypeOption = _updateTypeOption(
|
||||
typeOption: typeOption,
|
||||
includeTime: !value,
|
||||
);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
},
|
||||
itemCount: children.length,
|
||||
itemBuilder: (BuildContext context, int index) => children[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
value: typeOption.includeTime,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderDateFormatButton(
|
||||
BuildContext context,
|
||||
DateFormatPB dataFormat,
|
||||
TimestampTypeOptionPB typeOption,
|
||||
PopoverMutex popoverMutex,
|
||||
TypeOptionDataCallback onTypeOptionUpdated,
|
||||
) {
|
||||
return AppFlowyPopover(
|
||||
mutex: popoverMutex,
|
||||
@ -90,15 +56,17 @@ class TimestampTypeOptionWidget extends TypeOptionWidget {
|
||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
offset: const Offset(8, 0),
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
popupBuilder: (popoverContext) => DateFormatList(
|
||||
selectedFormat: dataFormat,
|
||||
onSelected: (format) {
|
||||
context
|
||||
.read<TimestampTypeOptionBloc>()
|
||||
.add(TimestampTypeOptionEvent.didSelectDateFormat(format));
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
),
|
||||
popupBuilder: (popoverContext) {
|
||||
return DateFormatList(
|
||||
selectedFormat: typeOption.dateFormat,
|
||||
onSelected: (format) {
|
||||
final newTypeOption =
|
||||
_updateTypeOption(typeOption: typeOption, dateFormat: format);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: DateFormatButton(),
|
||||
@ -107,8 +75,9 @@ class TimestampTypeOptionWidget extends TypeOptionWidget {
|
||||
}
|
||||
|
||||
Widget _renderTimeFormatButton(
|
||||
BuildContext context,
|
||||
TimeFormatPB timeFormat,
|
||||
TimestampTypeOptionPB typeOption,
|
||||
PopoverMutex popoverMutex,
|
||||
TypeOptionDataCallback onTypeOptionUpdated,
|
||||
) {
|
||||
return AppFlowyPopover(
|
||||
mutex: popoverMutex,
|
||||
@ -116,19 +85,47 @@ class TimestampTypeOptionWidget extends TypeOptionWidget {
|
||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
offset: const Offset(8, 0),
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
popupBuilder: (BuildContext popoverContext) => TimeFormatList(
|
||||
selectedFormat: timeFormat,
|
||||
onSelected: (format) {
|
||||
context
|
||||
.read<TimestampTypeOptionBloc>()
|
||||
.add(TimestampTypeOptionEvent.didSelectTimeFormat(format));
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: TimeFormatButton(timeFormat: timeFormat),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return TimeFormatList(
|
||||
selectedFormat: typeOption.timeFormat,
|
||||
onSelected: (format) {
|
||||
final newTypeOption =
|
||||
_updateTypeOption(typeOption: typeOption, timeFormat: format);
|
||||
onTypeOptionUpdated(newTypeOption.writeToBuffer());
|
||||
PopoverContainer.of(popoverContext).close();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: TimeFormatButton(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TimestampTypeOptionPB _parseTypeOptionData(List<int> data) {
|
||||
return TimestampTypeOptionDataParser().fromBuffer(data);
|
||||
}
|
||||
|
||||
TimestampTypeOptionPB _updateTypeOption({
|
||||
required TimestampTypeOptionPB typeOption,
|
||||
DateFormatPB? dateFormat,
|
||||
TimeFormatPB? timeFormat,
|
||||
bool? includeTime,
|
||||
}) {
|
||||
typeOption.freeze();
|
||||
return typeOption.rebuild((typeOption) {
|
||||
if (dateFormat != null) {
|
||||
typeOption.dateFormat = dateFormat;
|
||||
}
|
||||
|
||||
if (timeFormat != null) {
|
||||
typeOption.timeFormat = timeFormat;
|
||||
}
|
||||
|
||||
if (includeTime != null) {
|
||||
typeOption.includeTime = includeTime;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,18 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'builder.dart';
|
||||
|
||||
class URLTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
URLTypeOptionWidgetBuilder(URLTypeOptionContext typeOptionContext);
|
||||
class URLTypeOptionEditorFactory implements TypeOptionEditorFactory {
|
||||
const URLTypeOptionEditorFactory();
|
||||
|
||||
@override
|
||||
Widget? build(BuildContext context) => null;
|
||||
Widget? build({
|
||||
required BuildContext context,
|
||||
required String viewId,
|
||||
required FieldPB field,
|
||||
required PopoverMutex popoverMutex,
|
||||
required TypeOptionDataCallback onTypeOptionUpdated,
|
||||
}) =>
|
||||
null;
|
||||
}
|
||||
|
@ -47,17 +47,17 @@ class GridDateCell extends GridCellWidget {
|
||||
}
|
||||
|
||||
class _DateCellState extends GridCellState<GridDateCell> {
|
||||
late PopoverController _popover;
|
||||
final PopoverController _popover = PopoverController();
|
||||
late final DateCellController _cellController;
|
||||
late DateCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popover = PopoverController();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as DateCellController;
|
||||
_cellBloc = DateCellBloc(cellController: cellController)
|
||||
..add(const DateCellEvent.initial());
|
||||
super.initState();
|
||||
_cellController =
|
||||
widget.cellControllerBuilder.build() as DateCellController;
|
||||
_cellBloc = DateCellBloc(cellController: _cellController)
|
||||
..add(const DateCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
@ -93,8 +93,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContent) {
|
||||
return DateCellEditor(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as DateCellController,
|
||||
cellController: _cellController,
|
||||
onDismissed: () =>
|
||||
widget.cellContainerNotifier.isFocus = false,
|
||||
);
|
||||
@ -111,8 +110,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (context) {
|
||||
return MobileDateCellEditScreen(
|
||||
controller: widget.cellControllerBuilder.build()
|
||||
as DateCellController,
|
||||
controller: _cellController,
|
||||
showAsFullScreen: false,
|
||||
);
|
||||
},
|
||||
@ -169,8 +167,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
Theme.of(context).colorScheme.secondaryContainer,
|
||||
builder: (context) {
|
||||
return MobileDateCellEditScreen(
|
||||
controller: widget.cellControllerBuilder.build()
|
||||
as DateCellController,
|
||||
controller: _cellController,
|
||||
showAsFullScreen: false,
|
||||
);
|
||||
},
|
||||
@ -186,6 +183,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
_cellController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,6 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/date_cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
|
||||
@ -15,25 +16,23 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'date_cal_bloc.freezed.dart';
|
||||
part 'date_cell_editor_bloc.freezed.dart';
|
||||
|
||||
class DateCellCalendarBloc
|
||||
extends Bloc<DateCellCalendarEvent, DateCellCalendarState> {
|
||||
class DateCellEditorBloc
|
||||
extends Bloc<DateCellEditorEvent, DateCellEditorState> {
|
||||
final DateCellBackendService _dateCellBackendService;
|
||||
final DateCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
DateCellCalendarBloc({
|
||||
required DateTypeOptionPB dateTypeOptionPB,
|
||||
required DateCellDataPB? cellData,
|
||||
DateCellEditorBloc({
|
||||
required this.cellController,
|
||||
}) : _dateCellBackendService = DateCellBackendService(
|
||||
viewId: cellController.viewId,
|
||||
fieldId: cellController.fieldId,
|
||||
rowId: cellController.rowId,
|
||||
),
|
||||
super(DateCellCalendarState.initial(dateTypeOptionPB, cellData)) {
|
||||
on<DateCellCalendarEvent>(
|
||||
super(DateCellEditorState.initial(cellController)) {
|
||||
on<DateCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async => _startListening(),
|
||||
@ -222,7 +221,7 @@ class DateCellCalendarBloc
|
||||
if (!isClosed &&
|
||||
(state.parseEndTimeError != null || state.parseTimeError != null)) {
|
||||
add(
|
||||
const DateCellCalendarEvent.didReceiveTimeFormatError(null, null),
|
||||
const DateCellEditorEvent.didReceiveTimeFormatError(null, null),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -237,7 +236,7 @@ class DateCellCalendarBloc
|
||||
? (timeFormatPrompt(err), null)
|
||||
: (null, timeFormatPrompt(err));
|
||||
add(
|
||||
DateCellCalendarEvent.didReceiveTimeFormatError(
|
||||
DateCellEditorEvent.didReceiveTimeFormatError(
|
||||
startError,
|
||||
endError,
|
||||
),
|
||||
@ -259,7 +258,7 @@ class DateCellCalendarBloc
|
||||
}
|
||||
|
||||
add(
|
||||
const DateCellCalendarEvent.didReceiveTimeFormatError(null, null),
|
||||
const DateCellEditorEvent.didReceiveTimeFormatError(null, null),
|
||||
);
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
@ -300,7 +299,6 @@ class DateCellCalendarBloc
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@ -308,14 +306,14 @@ class DateCellCalendarBloc
|
||||
_onCellChangedFn = cellController.startListening(
|
||||
onCellChanged: ((cell) {
|
||||
if (!isClosed) {
|
||||
add(DateCellCalendarEvent.didReceiveCellUpdate(cell));
|
||||
add(DateCellEditorEvent.didReceiveCellUpdate(cell));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void>? _updateTypeOption(
|
||||
Emitter<DateCellCalendarState> emit, {
|
||||
Emitter<DateCellEditorState> emit, {
|
||||
DateFormatPB? dateFormat,
|
||||
TimeFormatPB? timeFormat,
|
||||
}) async {
|
||||
@ -349,49 +347,49 @@ class DateCellCalendarBloc
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateCellCalendarEvent with _$DateCellCalendarEvent {
|
||||
class DateCellEditorEvent with _$DateCellEditorEvent {
|
||||
// initial event
|
||||
const factory DateCellCalendarEvent.initial() = _Initial;
|
||||
const factory DateCellEditorEvent.initial() = _Initial;
|
||||
|
||||
// notification that cell is updated in the backend
|
||||
const factory DateCellCalendarEvent.didReceiveCellUpdate(
|
||||
const factory DateCellEditorEvent.didReceiveCellUpdate(
|
||||
DateCellDataPB? data,
|
||||
) = _DidReceiveCellUpdate;
|
||||
const factory DateCellCalendarEvent.didReceiveTimeFormatError(
|
||||
const factory DateCellEditorEvent.didReceiveTimeFormatError(
|
||||
String? parseTimeError,
|
||||
String? parseEndTimeError,
|
||||
) = _DidReceiveTimeFormatError;
|
||||
|
||||
// date cell data is modified
|
||||
const factory DateCellCalendarEvent.selectDay(DateTime day) = _SelectDay;
|
||||
const factory DateCellCalendarEvent.selectDateRange(
|
||||
const factory DateCellEditorEvent.selectDay(DateTime day) = _SelectDay;
|
||||
const factory DateCellEditorEvent.selectDateRange(
|
||||
DateTime? start,
|
||||
DateTime? end,
|
||||
) = _SelectDateRange;
|
||||
const factory DateCellCalendarEvent.setStartDay(
|
||||
const factory DateCellEditorEvent.setStartDay(
|
||||
DateTime startDay,
|
||||
) = _SetStartDay;
|
||||
const factory DateCellCalendarEvent.setEndDay(
|
||||
const factory DateCellEditorEvent.setEndDay(
|
||||
DateTime endDay,
|
||||
) = _SetEndDay;
|
||||
const factory DateCellCalendarEvent.setTime(String time) = _Time;
|
||||
const factory DateCellCalendarEvent.setEndTime(String endTime) = _EndTime;
|
||||
const factory DateCellCalendarEvent.setIncludeTime(bool includeTime) =
|
||||
const factory DateCellEditorEvent.setTime(String time) = _Time;
|
||||
const factory DateCellEditorEvent.setEndTime(String endTime) = _EndTime;
|
||||
const factory DateCellEditorEvent.setIncludeTime(bool includeTime) =
|
||||
_IncludeTime;
|
||||
const factory DateCellCalendarEvent.setIsRange(bool isRange) = _IsRange;
|
||||
const factory DateCellEditorEvent.setIsRange(bool isRange) = _IsRange;
|
||||
|
||||
// date field type options are modified
|
||||
const factory DateCellCalendarEvent.setTimeFormat(TimeFormatPB timeFormat) =
|
||||
const factory DateCellEditorEvent.setTimeFormat(TimeFormatPB timeFormat) =
|
||||
_TimeFormat;
|
||||
const factory DateCellCalendarEvent.setDateFormat(DateFormatPB dateFormat) =
|
||||
const factory DateCellEditorEvent.setDateFormat(DateFormatPB dateFormat) =
|
||||
_DateFormat;
|
||||
|
||||
const factory DateCellCalendarEvent.clearDate() = _ClearDate;
|
||||
const factory DateCellEditorEvent.clearDate() = _ClearDate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DateCellCalendarState with _$DateCellCalendarState {
|
||||
const factory DateCellCalendarState({
|
||||
class DateCellEditorState with _$DateCellEditorState {
|
||||
const factory DateCellEditorState({
|
||||
// the date field's type option
|
||||
required DateTypeOptionPB dateTypeOptionPB,
|
||||
|
||||
@ -413,15 +411,14 @@ class DateCellCalendarState with _$DateCellCalendarState {
|
||||
required String? parseTimeError,
|
||||
required String? parseEndTimeError,
|
||||
required String timeHintText,
|
||||
}) = _DateCellCalendarState;
|
||||
}) = _DateCellEditorState;
|
||||
|
||||
factory DateCellCalendarState.initial(
|
||||
DateTypeOptionPB dateTypeOptionPB,
|
||||
DateCellDataPB? cellData,
|
||||
) {
|
||||
factory DateCellEditorState.initial(DateCellController controller) {
|
||||
final typeOption = controller.getTypeOption(DateTypeOptionDataParser());
|
||||
final cellData = controller.getCellData();
|
||||
final dateCellData = _dateDataFromCellData(cellData);
|
||||
return DateCellCalendarState(
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
return DateCellEditorState(
|
||||
dateTypeOptionPB: typeOption,
|
||||
startDay: dateCellData.isRange ? dateCellData.dateTime : null,
|
||||
endDay: dateCellData.isRange ? dateCellData.endDateTime : null,
|
||||
dateTime: dateCellData.dateTime,
|
||||
@ -434,7 +431,7 @@ class DateCellCalendarState with _$DateCellCalendarState {
|
||||
isRange: dateCellData.isRange,
|
||||
parseTimeError: null,
|
||||
parseEndTimeError: null,
|
||||
timeHintText: _timeHintText(dateTypeOptionPB),
|
||||
timeHintText: _timeHintText(typeOption),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -450,13 +447,13 @@ String _timeHintText(DateTypeOptionPB typeOption) {
|
||||
}
|
||||
}
|
||||
|
||||
DateCellData _dateDataFromCellData(
|
||||
_DateCellData _dateDataFromCellData(
|
||||
DateCellDataPB? cellData,
|
||||
) {
|
||||
// a null DateCellDataPB may be returned, indicating that all the fields are
|
||||
// their default values: empty strings and false booleans
|
||||
if (cellData == null) {
|
||||
return DateCellData(
|
||||
return _DateCellData(
|
||||
dateTime: null,
|
||||
endDateTime: null,
|
||||
timeStr: null,
|
||||
@ -492,7 +489,7 @@ DateCellData _dateDataFromCellData(
|
||||
}
|
||||
final String dateStr = cellData.date;
|
||||
|
||||
return DateCellData(
|
||||
return _DateCellData(
|
||||
dateTime: dateTime,
|
||||
endDateTime: endDateTime,
|
||||
timeStr: timeStr,
|
||||
@ -504,7 +501,7 @@ DateCellData _dateDataFromCellData(
|
||||
);
|
||||
}
|
||||
|
||||
class DateCellData {
|
||||
class _DateCellData {
|
||||
final DateTime? dateTime;
|
||||
final DateTime? endDateTime;
|
||||
final String? timeStr;
|
||||
@ -514,7 +511,7 @@ class DateCellData {
|
||||
final String? dateStr;
|
||||
final String? endDateStr;
|
||||
|
||||
DateCellData({
|
||||
_DateCellData({
|
||||
required this.dateTime,
|
||||
required this.endDateTime,
|
||||
required this.timeStr,
|
@ -1,16 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/appflowy_date_picker.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'date_cal_bloc.dart';
|
||||
import 'date_cell_editor_bloc.dart';
|
||||
|
||||
class DateCellEditor extends StatefulWidget {
|
||||
const DateCellEditor({
|
||||
@ -27,48 +21,6 @@ class DateCellEditor extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DateCellEditor extends State<DateCellEditor> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<Either<dynamic, FlowyError>>(
|
||||
future: widget.cellController.getTypeOption(DateTypeOptionDataParser()),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return _buildWidget(snapshot);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWidget(AsyncSnapshot<Either<dynamic, FlowyError>> snapshot) {
|
||||
return snapshot.data!.fold(
|
||||
(dateTypeOptionPB) => _CellCalendarWidget(
|
||||
cellContext: widget.cellController,
|
||||
dateTypeOptionPB: dateTypeOptionPB,
|
||||
),
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CellCalendarWidget extends StatefulWidget {
|
||||
final DateCellController cellContext;
|
||||
final DateTypeOptionPB dateTypeOptionPB;
|
||||
|
||||
const _CellCalendarWidget({
|
||||
required this.cellContext,
|
||||
required this.dateTypeOptionPB,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_CellCalendarWidget> createState() => _CellCalendarWidgetState();
|
||||
}
|
||||
|
||||
class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
final PopoverMutex popoverMutex = PopoverMutex();
|
||||
|
||||
@override
|
||||
@ -80,22 +32,19 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => DateCellCalendarBloc(
|
||||
dateTypeOptionPB: widget.dateTypeOptionPB,
|
||||
cellData: widget.cellContext.getCellData(),
|
||||
cellController: widget.cellContext,
|
||||
)..add(const DateCellCalendarEvent.initial()),
|
||||
child: BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
|
||||
create: (context) => DateCellEditorBloc(
|
||||
cellController: widget.cellController,
|
||||
)..add(const DateCellEditorEvent.initial()),
|
||||
child: BlocBuilder<DateCellEditorBloc, DateCellEditorState>(
|
||||
builder: (context, state) {
|
||||
final bloc = context.read<DateCellEditorBloc>();
|
||||
return AppFlowyDatePicker(
|
||||
includeTime: state.includeTime,
|
||||
onIncludeTimeChanged: (value) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setIncludeTime(!value)),
|
||||
onIncludeTimeChanged: (value) =>
|
||||
bloc.add(DateCellEditorEvent.setIncludeTime(!value)),
|
||||
isRange: state.isRange,
|
||||
onIsRangeChanged: (value) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setIsRange(!value)),
|
||||
onIsRangeChanged: (value) =>
|
||||
bloc.add(DateCellEditorEvent.setIsRange(!value)),
|
||||
dateFormat: state.dateTypeOptionPB.dateFormat,
|
||||
timeFormat: state.dateTypeOptionPB.timeFormat,
|
||||
selectedDay: state.dateTime,
|
||||
@ -105,28 +54,28 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
parseEndTimeError: state.parseEndTimeError,
|
||||
parseTimeError: state.parseTimeError,
|
||||
popoverMutex: popoverMutex,
|
||||
onStartTimeSubmitted: (timeStr) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setTime(timeStr)),
|
||||
onEndTimeSubmitted: (timeStr) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setEndTime(timeStr)),
|
||||
onDaySelected: (selectedDay, _) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.selectDay(selectedDay)),
|
||||
onRangeSelected: (start, end, _) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.selectDateRange(start, end)),
|
||||
onStartTimeSubmitted: (timeStr) {
|
||||
bloc.add(DateCellEditorEvent.setTime(timeStr));
|
||||
},
|
||||
onEndTimeSubmitted: (timeStr) {
|
||||
bloc.add(DateCellEditorEvent.setEndTime(timeStr));
|
||||
},
|
||||
onDaySelected: (selectedDay, _) {
|
||||
bloc.add(DateCellEditorEvent.selectDay(selectedDay));
|
||||
},
|
||||
onRangeSelected: (start, end, _) {
|
||||
bloc.add(DateCellEditorEvent.selectDateRange(start, end));
|
||||
},
|
||||
allowFormatChanges: true,
|
||||
onDateFormatChanged: (format) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setDateFormat(format)),
|
||||
onTimeFormatChanged: (format) => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(DateCellCalendarEvent.setTimeFormat(format)),
|
||||
onClearDate: () => context
|
||||
.read<DateCellCalendarBloc>()
|
||||
.add(const DateCellCalendarEvent.clearDate()),
|
||||
onDateFormatChanged: (format) {
|
||||
bloc.add(DateCellEditorEvent.setDateFormat(format));
|
||||
},
|
||||
onTimeFormatChanged: (format) {
|
||||
bloc.add(DateCellEditorEvent.setTimeFormat(format));
|
||||
},
|
||||
onClearDate: () {
|
||||
bloc.add(const DateCellEditorEvent.clearDate());
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -7,7 +7,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
import 'date_cal_bloc.dart';
|
||||
import 'date_cell_editor_bloc.dart';
|
||||
|
||||
class MobileDatePicker extends StatefulWidget {
|
||||
const MobileDatePicker({
|
||||
@ -49,7 +49,7 @@ class _MobileDatePickerState extends State<MobileDatePicker> {
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
|
||||
child: BlocBuilder<DateCellEditorBloc, DateCellEditorState>(
|
||||
builder: (context, state) {
|
||||
return TableCalendar(
|
||||
firstDay: kFirstDay,
|
||||
@ -124,13 +124,13 @@ class _MobileDatePickerState extends State<MobileDatePicker> {
|
||||
selectedDayPredicate: (day) =>
|
||||
state.isRange ? false : isSameDay(state.dateTime, day),
|
||||
onDaySelected: (selectedDay, focusedDay) {
|
||||
context.read<DateCellCalendarBloc>().add(
|
||||
DateCellCalendarEvent.selectDay(selectedDay),
|
||||
context.read<DateCellEditorBloc>().add(
|
||||
DateCellEditorEvent.selectDay(selectedDay),
|
||||
);
|
||||
},
|
||||
onRangeSelected: (start, end, focusedDay) {
|
||||
context.read<DateCellCalendarBloc>().add(
|
||||
DateCellCalendarEvent.selectDateRange(start, end),
|
||||
context.read<DateCellEditorBloc>().add(
|
||||
DateCellEditorEvent.selectDateRange(start, end),
|
||||
);
|
||||
},
|
||||
onFormatChanged: (calendarFormat) => setState(() {
|
||||
|
@ -15,7 +15,7 @@ import 'package:textfield_tags/textfield_tags.dart';
|
||||
|
||||
import '../../../../grid/presentation/layout/sizes.dart';
|
||||
import '../../../../grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import '../../../../grid/presentation/widgets/header/type_option/select_option_editor.dart';
|
||||
import '../../../../grid/presentation/widgets/header/type_option/select/select_option_editor.dart';
|
||||
import 'extension.dart';
|
||||
import 'select_option_editor_bloc.dart';
|
||||
import 'text_field.dart';
|
||||
|
@ -353,7 +353,7 @@ class CreateRowFieldButton extends StatefulWidget {
|
||||
|
||||
class _CreateRowFieldButtonState extends State<CreateRowFieldButton> {
|
||||
late PopoverController popoverController;
|
||||
late TypeOptionPB typeOption;
|
||||
FieldPB? createdField;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -383,8 +383,8 @@ class _CreateRowFieldButtonState extends State<CreateRowFieldButton> {
|
||||
viewId: widget.viewId,
|
||||
);
|
||||
result.fold(
|
||||
(l) {
|
||||
typeOption = l;
|
||||
(newField) {
|
||||
createdField = newField;
|
||||
popoverController.show();
|
||||
},
|
||||
(r) => Log.error("Failed to create field type option: $r"),
|
||||
@ -397,9 +397,12 @@ class _CreateRowFieldButtonState extends State<CreateRowFieldButton> {
|
||||
),
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
if (createdField == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return FieldEditor(
|
||||
viewId: widget.viewId,
|
||||
field: typeOption.field_2,
|
||||
field: createdField!,
|
||||
fieldController: widget.fieldController,
|
||||
);
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
@ -6,7 +7,6 @@ import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_text_field.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_time_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/include_time_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/start_text_field.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/utils/layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../utils/layout.dart';
|
||||
|
||||
class DateTimeSetting extends StatefulWidget {
|
||||
const DateTimeSetting({
|
||||
@ -52,9 +52,9 @@ class _DateTimeSettingState extends State<DateTimeSetting> {
|
||||
selectedFormat: widget.timeFormat,
|
||||
onSelected: _onTimeFormatChanged,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: TimeFormatButton(timeFormat: widget.timeFormat),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: TimeFormatButton(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
@ -1,48 +0,0 @@
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/utils/layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
|
||||
class IncludeTimeButton extends StatelessWidget {
|
||||
const IncludeTimeButton({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final bool value;
|
||||
final Function(bool value) onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: DatePickerSize.itemHeight,
|
||||
child: Padding(
|
||||
padding: DatePickerSize.itemOptionInsets,
|
||||
child: Row(
|
||||
children: [
|
||||
FlowySvg(
|
||||
FlowySvgs.clock_alarm_s,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
const HSpace(6),
|
||||
FlowyText.medium(LocaleKeys.datePicker_includeTime.tr()),
|
||||
const Spacer(),
|
||||
Toggle(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@ -34,14 +33,9 @@ void main() {
|
||||
await boardResponseFuture();
|
||||
|
||||
final fieldInfo = context.singleSelectFieldContext();
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: context.gridView.id,
|
||||
field: fieldInfo.field,
|
||||
);
|
||||
|
||||
final editorBloc = FieldEditorBloc(
|
||||
viewId: context.gridView.id,
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
|
@ -4,7 +4,6 @@ import 'package:appflowy/plugins/database_view/application/database_controller.d
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/board/board.dart';
|
||||
@ -82,15 +81,9 @@ class BoardTestContext {
|
||||
FieldEditorBloc makeFieldEditor({
|
||||
required FieldInfo fieldInfo,
|
||||
}) {
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: gridView.id,
|
||||
field: fieldInfo.field,
|
||||
);
|
||||
|
||||
final editorBloc = FieldEditorBloc(
|
||||
viewId: databaseController.viewId,
|
||||
fieldController: fieldController,
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
);
|
||||
return editorBloc;
|
||||
|
@ -1,21 +1,15 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../util.dart';
|
||||
|
||||
Future<FieldEditorBloc> createEditorBloc(AppFlowyGridTest gridTest) async {
|
||||
final context = await gridTest.createTestGrid();
|
||||
final fieldInfo = context.singleSelectFieldContext();
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: context.gridView.id,
|
||||
field: fieldInfo.field,
|
||||
);
|
||||
|
||||
return FieldEditorBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import 'package:appflowy/plugins/database_view/application/field/field_controlle
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
@ -134,16 +133,11 @@ Future<FieldEditorBloc> createFieldEditor({
|
||||
);
|
||||
await gridResponseFuture();
|
||||
return result.fold(
|
||||
(data) {
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: databaseController.viewId,
|
||||
field: data.field_2,
|
||||
);
|
||||
(field) {
|
||||
return FieldEditorBloc(
|
||||
viewId: databaseController.viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
loader: loader,
|
||||
field: data.field_2,
|
||||
field: field,
|
||||
);
|
||||
},
|
||||
(err) => throw Exception(err),
|
||||
|
@ -14,7 +14,7 @@ export const useDateTimeFormat = (cellIdentifier: CellIdentifier, fieldControlle
|
||||
|
||||
await typeOptionController.initialize();
|
||||
const dateTypeOptionContext = makeDateTypeOptionContext(typeOptionController);
|
||||
const typeOption = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||
const typeOption = dateTypeOptionContext.getTypeOption();
|
||||
|
||||
change(typeOption);
|
||||
await dateTypeOptionContext.setTypeOption(typeOption);
|
||||
|
@ -14,7 +14,7 @@ export const useNumberFormat = (cellIdentifier: CellIdentifier, fieldController:
|
||||
|
||||
await typeOptionController.initialize();
|
||||
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||
const typeOption = await numberTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||
const typeOption = numberTypeOptionContext.getTypeOption();
|
||||
|
||||
typeOption.format = format;
|
||||
await numberTypeOptionContext.setTypeOption(typeOption);
|
||||
|
@ -26,7 +26,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
|
||||
let typeOption: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB | undefined;
|
||||
|
||||
if (field.field_type === FieldType.SingleSelect) {
|
||||
typeOption = (await makeSingleSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||
typeOption = makeSingleSelectTypeOptionContext(typeOptionController).getTypeOption();
|
||||
if (!groupingFieldSelected) {
|
||||
if (dispatch) {
|
||||
dispatch(boardActions.setGroupingFieldId({ fieldId: field.id }));
|
||||
@ -37,7 +37,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
|
||||
}
|
||||
|
||||
if (field.field_type === FieldType.MultiSelect) {
|
||||
typeOption = (await makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||
typeOption = makeMultiSelectTypeOptionContext(typeOptionController).getTypeOption();
|
||||
}
|
||||
|
||||
if (typeOption) {
|
||||
@ -63,7 +63,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
|
||||
}
|
||||
|
||||
case FieldType.Number: {
|
||||
const typeOption = (await makeNumberTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||
const typeOption = makeNumberTypeOptionContext(typeOptionController).getTypeOption();
|
||||
|
||||
return {
|
||||
fieldId: field.id,
|
||||
@ -78,7 +78,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
|
||||
}
|
||||
|
||||
case FieldType.DateTime: {
|
||||
const typeOption = (await makeDateTypeOptionContext(typeOptionController).getTypeOption()).unwrap();
|
||||
const typeOption = makeDateTypeOptionContext(typeOptionController).getTypeOption();
|
||||
|
||||
return {
|
||||
fieldId: field.id,
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
DatabaseEventGetAllFieldSettings,
|
||||
} from '@/services/backend/events/flowy-database2';
|
||||
import { Field, pbToField } from './field_types';
|
||||
import { getTypeOption } from './type_option';
|
||||
import { bytesToTypeOption } from './type_option';
|
||||
import { Database } from '$app/components/database/application';
|
||||
|
||||
export async function getFields(
|
||||
@ -64,7 +64,7 @@ export async function getFields(
|
||||
|
||||
const field = pbToField(item);
|
||||
|
||||
const typeOption = await getTypeOption(viewId, item.id, item.field_type);
|
||||
const typeOption = bytesToTypeOption(item.type_option_data, item.field_type);
|
||||
|
||||
if (typeOption) {
|
||||
typeOptions[item.id] = typeOption;
|
||||
@ -110,7 +110,7 @@ export async function createField({
|
||||
return Promise.reject('Failed to create field');
|
||||
}
|
||||
|
||||
return pbToField(result.val.field);
|
||||
return pbToField(result.val);
|
||||
}
|
||||
|
||||
export async function duplicateField(viewId: string, fieldId: string): Promise<void> {
|
||||
|
@ -1,27 +1,8 @@
|
||||
import { FieldType, TypeOptionPathPB, TypeOptionChangesetPB } from '@/services/backend';
|
||||
import { FieldType, TypeOptionChangesetPB } from '@/services/backend';
|
||||
import {
|
||||
DatabaseEventGetTypeOption,
|
||||
DatabaseEventUpdateFieldTypeOption,
|
||||
} from '@/services/backend/events/flowy-database2';
|
||||
import { bytesToTypeOption, UndeterminedTypeOptionData, typeOptionDataToPB } from './type_option_types';
|
||||
|
||||
export async function getTypeOption(viewId: string, fieldId: string, fieldType: FieldType) {
|
||||
const payload = TypeOptionPathPB.fromObject({
|
||||
view_id: viewId,
|
||||
field_id: fieldId,
|
||||
field_type: fieldType,
|
||||
});
|
||||
|
||||
const result = await DatabaseEventGetTypeOption(payload);
|
||||
|
||||
if (!result.ok) {
|
||||
return Promise.reject(result.val);
|
||||
}
|
||||
|
||||
const value = result.val;
|
||||
|
||||
return bytesToTypeOption(value.type_option_data, fieldType);
|
||||
}
|
||||
import { UndeterminedTypeOptionData, typeOptionDataToPB } from './type_option_types';
|
||||
|
||||
export async function updateTypeOption(
|
||||
viewId: string,
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
URLCellController,
|
||||
} from '$app/stores/effects/database/cell/controller_builder';
|
||||
import { None, Option, Some } from 'ts-results';
|
||||
import { TypeOptionBackendService } from '$app/stores/effects/database/field/type_option/type_option_bd_svc';
|
||||
import { DatabaseBackendService } from '$app/stores/effects/database/database_bd_svc';
|
||||
import { FieldInfo } from '$app/stores/effects/database/field/field_controller';
|
||||
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
||||
@ -175,12 +174,11 @@ export function findFirstFieldInfoWithFieldType(rowInfo: RowInfo, fieldType: Fie
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertFieldName(viewId: string, fieldId: string, fieldType: FieldType, expected: string) {
|
||||
const svc = new TypeOptionBackendService(viewId);
|
||||
const typeOptionPB = await svc.getTypeOption(fieldId, fieldType).then((result) => result.unwrap());
|
||||
export async function assertFieldName(controller: TypeOptionController, expected: string) {
|
||||
const fieldInfo = controller.getFieldInfo();
|
||||
|
||||
if (typeOptionPB.field.name !== expected) {
|
||||
throw Error('Expect field name:' + expected + 'but receive:' + typeOptionPB.field.name);
|
||||
if (fieldInfo.field.name !== expected) {
|
||||
throw Error('Expect field name:' + expected + 'but receive:' + fieldInfo.field.name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,9 +216,8 @@ export async function createSingleSelectOptions(viewId: string, fieldInfo: Field
|
||||
assert(fieldInfo.field.field_type === FieldType.SingleSelect, 'Only work on single select');
|
||||
const typeOptionController = new TypeOptionController(viewId, Some(fieldInfo));
|
||||
const singleSelectTypeOptionContext = makeSingleSelectTypeOptionContext(typeOptionController);
|
||||
const singleSelectTypeOptionPB: SingleSelectTypeOptionPB = await singleSelectTypeOptionContext
|
||||
.getTypeOption()
|
||||
.then((result) => result.unwrap());
|
||||
const singleSelectTypeOptionPB: SingleSelectTypeOptionPB = singleSelectTypeOptionContext
|
||||
.getTypeOption();
|
||||
|
||||
const backendSvc = new SelectOptionBackendService(viewId, fieldInfo.field.id);
|
||||
|
||||
|
@ -195,7 +195,7 @@ async function testEditDateFormatPB() {
|
||||
|
||||
// update date type option
|
||||
const dateTypeOptionContext = makeDateTypeOptionContext(typeOptionController);
|
||||
const typeOption = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||
const typeOption = dateTypeOptionContext.getTypeOption();
|
||||
|
||||
assert(typeOption.date_format === DateFormatPB.Friendly, 'Date format not match');
|
||||
assert(typeOption.time_format === TimeFormatPB.TwentyFourHour, 'Time format not match');
|
||||
@ -203,7 +203,7 @@ async function testEditDateFormatPB() {
|
||||
typeOption.time_format = TimeFormatPB.TwelveHour;
|
||||
await dateTypeOptionContext.setTypeOption(typeOption);
|
||||
|
||||
const typeOption2 = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||
const typeOption2 = dateTypeOptionContext.getTypeOption();
|
||||
|
||||
assert(typeOption2.date_format === DateFormatPB.Local, 'Date format not match');
|
||||
assert(typeOption2.time_format === TimeFormatPB.TwelveHour, 'Time format not match');
|
||||
@ -224,13 +224,13 @@ async function testEditNumberFormatPB() {
|
||||
|
||||
// update date type option
|
||||
const dateTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||
const typeOption = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||
const typeOption = dateTypeOptionContext.getTypeOption();
|
||||
|
||||
typeOption.format = NumberFormatPB.EUR;
|
||||
typeOption.name = 'Money';
|
||||
await dateTypeOptionContext.setTypeOption(typeOption);
|
||||
|
||||
const typeOption2 = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
|
||||
const typeOption2 = dateTypeOptionContext.getTypeOption();
|
||||
|
||||
Log.info(typeOption2);
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
@ -377,7 +377,7 @@ async function testGetSingleSelectFieldData() {
|
||||
]);
|
||||
|
||||
// Read options
|
||||
const options = await singleSelectTypeOptionContext.getTypeOption().then((result) => result.unwrap());
|
||||
const options = singleSelectTypeOptionContext.getTypeOption();
|
||||
|
||||
console.log(options);
|
||||
|
||||
@ -401,9 +401,8 @@ async function testSwitchFromSingleSelectToNumber() {
|
||||
|
||||
// Check the number type option
|
||||
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
|
||||
const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
|
||||
.getTypeOption()
|
||||
.then((result) => result.unwrap());
|
||||
const numberTypeOption: NumberTypeOptionPB = numberTypeOptionContext
|
||||
.getTypeOption();
|
||||
const format: NumberFormatPB = numberTypeOption.format;
|
||||
|
||||
if (format !== NumberFormatPB.Num) {
|
||||
@ -452,7 +451,7 @@ async function testSwitchFromMultiSelectToRichText() {
|
||||
await selectOptionCellController.dispose();
|
||||
|
||||
// Switch to RichText field type
|
||||
await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
|
||||
await typeOptionController.switchToField(FieldType.RichText);
|
||||
if (typeOptionController.fieldType !== FieldType.RichText) {
|
||||
throw Error('The field type should be text');
|
||||
}
|
||||
@ -486,7 +485,7 @@ async function testEditField() {
|
||||
await controller.setFieldName(newName);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
await assertFieldName(view.id, firstFieldInfo.field.id, firstFieldInfo.field.field_type, newName);
|
||||
await assertFieldName(controller, newName);
|
||||
await databaseController.dispose();
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,11 @@ import { CellIdentifier } from './cell_bd_svc';
|
||||
import { CellCache, CellCacheKey } from './cell_cache';
|
||||
import { CellDataLoader } from './data_parser';
|
||||
import { CellDataPersistence } from './data_persistence';
|
||||
import { FieldBackendService, TypeOptionParser } from '../field/field_bd_svc';
|
||||
import { FieldBackendService } from '../field/field_bd_svc';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { CellObserver } from './cell_observer';
|
||||
import { Log } from '$app/utils/log';
|
||||
import { Err, None, Ok, Option, Some } from 'ts-results';
|
||||
import { None, Option, Some } from 'ts-results';
|
||||
import { DatabaseFieldObserver } from '../field/field_observer';
|
||||
|
||||
type Callbacks<T> = { onCellChanged: (value: Option<T>) => void; onFieldChanged?: () => void };
|
||||
@ -70,16 +70,6 @@ export class CellController<T, D> {
|
||||
});
|
||||
};
|
||||
|
||||
getTypeOption = async <P extends TypeOptionParser<PD>, PD>(parser: P) => {
|
||||
const result = await this.fieldBackendService.getTypeOptionData(this.cellIdentifier.fieldType);
|
||||
|
||||
if (result.ok) {
|
||||
return Ok(parser.fromBuffer(result.val.type_option_data));
|
||||
} else {
|
||||
return Err(result.val);
|
||||
}
|
||||
};
|
||||
|
||||
saveCellData = async (data: D) => {
|
||||
const result = await this.cellDataPersistence.save(data);
|
||||
|
||||
|
@ -2,14 +2,11 @@ import {
|
||||
DeleteFieldPayloadPB,
|
||||
DuplicateFieldPayloadPB,
|
||||
FieldChangesetPB,
|
||||
FieldType,
|
||||
TypeOptionChangesetPB,
|
||||
TypeOptionPathPB,
|
||||
} from '@/services/backend';
|
||||
import {
|
||||
DatabaseEventDeleteField,
|
||||
DatabaseEventDuplicateField,
|
||||
DatabaseEventGetTypeOption,
|
||||
DatabaseEventUpdateField,
|
||||
DatabaseEventUpdateFieldTypeOption,
|
||||
} from '@/services/backend/events/flowy-database2';
|
||||
@ -64,14 +61,4 @@ export class FieldBackendService {
|
||||
|
||||
return DatabaseEventDuplicateField(payload);
|
||||
};
|
||||
|
||||
getTypeOptionData = (fieldType: FieldType) => {
|
||||
const payload = TypeOptionPathPB.fromObject({
|
||||
view_id: this.viewId,
|
||||
field_id: this.fieldId,
|
||||
field_type: fieldType,
|
||||
});
|
||||
|
||||
return DatabaseEventGetTypeOption(payload);
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { CreateFieldPayloadPB, FieldType, TypeOptionPathPB, UpdateFieldTypePayloadPB } from '@/services/backend';
|
||||
import { CreateFieldPayloadPB, FieldType, UpdateFieldTypePayloadPB } from '@/services/backend';
|
||||
import {
|
||||
DatabaseEventCreateField,
|
||||
DatabaseEventGetTypeOption,
|
||||
DatabaseEventUpdateFieldType,
|
||||
} from '@/services/backend/events/flowy-database2';
|
||||
|
||||
@ -14,16 +13,6 @@ export class TypeOptionBackendService {
|
||||
return DatabaseEventCreateField(payload);
|
||||
};
|
||||
|
||||
getTypeOption = (fieldId: string, fieldType: FieldType) => {
|
||||
const payload = TypeOptionPathPB.fromObject({
|
||||
view_id: this.viewId,
|
||||
field_id: fieldId,
|
||||
field_type: fieldType,
|
||||
});
|
||||
|
||||
return DatabaseEventGetTypeOption(payload);
|
||||
};
|
||||
|
||||
updateTypeOptionType = (fieldId: string, fieldType: FieldType) => {
|
||||
const payload = UpdateFieldTypePayloadPB.fromObject({
|
||||
view_id: this.viewId,
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { None, Ok, Option, Result, Some } from 'ts-results';
|
||||
import { None, Option, Some } from 'ts-results';
|
||||
import { TypeOptionController } from './type_option_controller';
|
||||
import {
|
||||
CheckboxTypeOptionPB,
|
||||
ChecklistTypeOptionPB,
|
||||
DateTypeOptionPB,
|
||||
FlowyError,
|
||||
MultiSelectTypeOptionPB,
|
||||
NumberTypeOptionPB,
|
||||
SingleSelectTypeOptionPB,
|
||||
@ -190,17 +189,12 @@ export class TypeOptionContext<T> {
|
||||
return this.controller.viewId;
|
||||
}
|
||||
|
||||
getTypeOption = async (): Promise<Result<T, FlowyError>> => {
|
||||
const result = await this.controller.getTypeOption();
|
||||
getTypeOption = (): T => {
|
||||
const type_option_data = this.controller.getTypeOption();
|
||||
const typeOption = this.parser.deserialize(type_option_data);
|
||||
|
||||
if (result.ok) {
|
||||
const typeOption = this.parser.deserialize(result.val.type_option_data);
|
||||
|
||||
this.typeOption = Some(typeOption);
|
||||
return Ok(typeOption);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
this.typeOption = Some(typeOption);
|
||||
return typeOption;
|
||||
};
|
||||
|
||||
// Save the typeOption to disk
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FieldPB, FieldType, TypeOptionPB } from '@/services/backend';
|
||||
import { FieldPB, FieldType } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { FieldBackendService } from '../field_bd_svc';
|
||||
import { Log } from '$app/utils/log';
|
||||
@ -8,7 +8,7 @@ import { TypeOptionBackendService } from './type_option_bd_svc';
|
||||
|
||||
export class TypeOptionController {
|
||||
private fieldNotifier = new ChangeNotifier<FieldPB>();
|
||||
private typeOptionData: Option<TypeOptionPB>;
|
||||
private field: Option<FieldPB>;
|
||||
private fieldBackendSvc?: FieldBackendService;
|
||||
private typeOptionBackendSvc: TypeOptionBackendService;
|
||||
|
||||
@ -18,7 +18,13 @@ export class TypeOptionController {
|
||||
private readonly initialFieldInfo: Option<FieldInfo> = None,
|
||||
private readonly defaultFieldType: FieldType = FieldType.RichText
|
||||
) {
|
||||
this.typeOptionData = None;
|
||||
if (initialFieldInfo.none) {
|
||||
this.field = None;
|
||||
} else {
|
||||
this.field = Some(initialFieldInfo.val.field);
|
||||
this.fieldBackendSvc = new FieldBackendService(this.viewId, initialFieldInfo.val.field.id);
|
||||
}
|
||||
|
||||
this.typeOptionBackendSvc = new TypeOptionBackendService(viewId);
|
||||
}
|
||||
|
||||
@ -27,8 +33,6 @@ export class TypeOptionController {
|
||||
initialize = async () => {
|
||||
if (this.initialFieldInfo.none) {
|
||||
await this.createTypeOption(this.defaultFieldType);
|
||||
} else {
|
||||
await this.getTypeOption();
|
||||
}
|
||||
};
|
||||
|
||||
@ -41,7 +45,7 @@ export class TypeOptionController {
|
||||
}
|
||||
|
||||
getFieldInfo = (): FieldInfo => {
|
||||
if (this.typeOptionData.none) {
|
||||
if (this.field.none) {
|
||||
if (this.initialFieldInfo.some) {
|
||||
return this.initialFieldInfo.val;
|
||||
} else {
|
||||
@ -49,35 +53,31 @@ export class TypeOptionController {
|
||||
}
|
||||
}
|
||||
|
||||
return new FieldInfo(this.typeOptionData.val.field);
|
||||
return new FieldInfo(this.field.val);
|
||||
};
|
||||
|
||||
switchToField = async (fieldType: FieldType) => {
|
||||
const result = await this.typeOptionBackendSvc.updateTypeOptionType(this.fieldId, fieldType);
|
||||
|
||||
if (result.ok) {
|
||||
const getResult = await this.typeOptionBackendSvc.getTypeOption(this.fieldId, fieldType);
|
||||
|
||||
if (getResult.ok) {
|
||||
this.updateTypeOptionData(getResult.val);
|
||||
}
|
||||
|
||||
return getResult;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
setFieldName = async (name: string) => {
|
||||
if (this.typeOptionData.some) {
|
||||
this.typeOptionData.val.field.name = name;
|
||||
void this.fieldBackendSvc?.updateField({ name: name });
|
||||
this.fieldNotifier.notify(this.typeOptionData.val.field);
|
||||
if (this.field.some) {
|
||||
this.field.val.field_type = fieldType;
|
||||
await this.typeOptionBackendSvc.updateTypeOptionType(this.fieldId, fieldType).then((result) => {
|
||||
if (result.err) {
|
||||
Log.error(result.val);
|
||||
}
|
||||
});
|
||||
this.fieldNotifier.notify(this.field.val);
|
||||
} else {
|
||||
throw Error('Unexpected empty type option data. Should call initialize first');
|
||||
}
|
||||
};
|
||||
|
||||
setFieldName = async (name: string) => {
|
||||
if (this.field.some) {
|
||||
this.field.val.name = name;
|
||||
void this.fieldBackendSvc?.updateField({ name: name });
|
||||
this.fieldNotifier.notify(this.field.val);
|
||||
}
|
||||
};
|
||||
|
||||
hideField = async () => {
|
||||
if (this.fieldBackendSvc) {
|
||||
void this.fieldBackendSvc.updateField({ visibility: false });
|
||||
@ -103,8 +103,8 @@ export class TypeOptionController {
|
||||
};
|
||||
|
||||
saveTypeOption = async (data: Uint8Array) => {
|
||||
if (this.typeOptionData.some) {
|
||||
this.typeOptionData.val.type_option_data = data;
|
||||
if (this.field.some) {
|
||||
this.field.val.type_option_data = data;
|
||||
await this.fieldBackendSvc?.updateTypeOption(data).then((result) => {
|
||||
if (result.err) {
|
||||
Log.error(result.val);
|
||||
@ -132,29 +132,27 @@ export class TypeOptionController {
|
||||
};
|
||||
|
||||
// Returns the type option for specific field with specific fieldType
|
||||
getTypeOption = async () => {
|
||||
return this.typeOptionBackendSvc.getTypeOption(this.fieldId, this.fieldType).then((result) => {
|
||||
if (result.ok) {
|
||||
this.updateTypeOptionData(result.val);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
getTypeOption = () => {
|
||||
if (this.field.some) {
|
||||
return this.field.val.type_option_data;
|
||||
} else {
|
||||
throw Error('Unexpected empty type option data. Should call initialize first');
|
||||
}
|
||||
};
|
||||
|
||||
private createTypeOption = (fieldType: FieldType) => {
|
||||
return this.typeOptionBackendSvc.createTypeOption(fieldType).then((result) => {
|
||||
if (result.ok) {
|
||||
this.updateTypeOptionData(result.val);
|
||||
}
|
||||
private createTypeOption = async (fieldType: FieldType) => {
|
||||
const result = await this.typeOptionBackendSvc.createTypeOption(fieldType);
|
||||
|
||||
return result;
|
||||
});
|
||||
if (result.ok) {
|
||||
this.updateField(result.val);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
private updateTypeOptionData = (typeOptionData: TypeOptionPB) => {
|
||||
this.typeOptionData = Some(typeOptionData);
|
||||
this.fieldBackendSvc = new FieldBackendService(this.viewId, typeOptionData.field.id);
|
||||
this.fieldNotifier.notify(typeOptionData.field);
|
||||
private updateField = (field: FieldPB) => {
|
||||
this.field = Some(field);
|
||||
this.fieldBackendSvc = new FieldBackendService(this.viewId, field.id);
|
||||
this.fieldNotifier.notify(field);
|
||||
};
|
||||
}
|
||||
|
@ -120,8 +120,7 @@ impl EventIntegrationTest {
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<TypeOptionPB>()
|
||||
.field
|
||||
.parse::<FieldPB>()
|
||||
}
|
||||
|
||||
pub async fn update_field(&self, changeset: FieldChangesetPB) {
|
||||
|
@ -14,6 +14,7 @@ use flowy_error::ErrorCode;
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::position_entities::OrderObjectPositionPB;
|
||||
use crate::impl_into_field_type;
|
||||
use crate::services::field::{default_type_option_data_from_type, type_option_to_pb};
|
||||
|
||||
/// [FieldPB] defines a Field's attributes. Such as the name, field_type, and width. etc.
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
@ -35,17 +36,25 @@ pub struct FieldPB {
|
||||
|
||||
#[pb(index = 6)]
|
||||
pub is_primary: bool,
|
||||
|
||||
#[pb(index = 7)]
|
||||
pub type_option_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Field> for FieldPB {
|
||||
fn from(field: Field) -> Self {
|
||||
impl FieldPB {
|
||||
pub fn new(field: Field) -> Self {
|
||||
let field_type = field.field_type.into();
|
||||
let type_option = field
|
||||
.get_any_type_option(field_type)
|
||||
.unwrap_or_else(|| default_type_option_data_from_type(&field_type));
|
||||
Self {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
field_type: FieldType::from(field.field_type),
|
||||
field_type,
|
||||
visibility: field.visibility,
|
||||
width: field.width as i32,
|
||||
is_primary: field.is_primary,
|
||||
type_option_data: type_option_to_pb(type_option, &field_type).to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,15 +148,6 @@ pub struct IndexFieldPB {
|
||||
pub index: i32,
|
||||
}
|
||||
|
||||
impl IndexFieldPB {
|
||||
pub fn from_field(field: Field, index: usize) -> Self {
|
||||
Self {
|
||||
field: FieldPB::from(field),
|
||||
index: index as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct CreateFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
@ -236,50 +236,6 @@ impl TryInto<EditFieldParams> for UpdateFieldTypePayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct TypeOptionPathPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
pub struct TypeOptionPathParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl TryInto<TypeOptionPathParams> for TypeOptionPathPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<TypeOptionPathParams, Self::Error> {
|
||||
let database_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
Ok(TypeOptionPathParams {
|
||||
view_id: database_id.0,
|
||||
field_id: field_id.0,
|
||||
field_type: self.field_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct TypeOptionPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field: FieldPB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub type_option_data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Collection of the [FieldPB]
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct RepeatedFieldPB {
|
||||
|
@ -13,7 +13,7 @@ use crate::manager::DatabaseManager;
|
||||
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,
|
||||
type_option_data_from_pb, DateCellChangeset, SelectOptionCellChangeset,
|
||||
};
|
||||
use crate::services::field_settings::FieldSettingsChangesetParams;
|
||||
use crate::services::group::GroupChangeset;
|
||||
@ -160,7 +160,7 @@ pub(crate) async fn get_fields_handler(
|
||||
let fields = database_editor
|
||||
.get_fields(¶ms.view_id, params.field_ids)
|
||||
.into_iter()
|
||||
.map(FieldPB::from)
|
||||
.map(FieldPB::new)
|
||||
.collect::<Vec<FieldPB>>()
|
||||
.into();
|
||||
data_result_ok(fields)
|
||||
@ -178,7 +178,7 @@ pub(crate) async fn get_primary_field_handler(
|
||||
.get_fields(&view_id, None)
|
||||
.into_iter()
|
||||
.filter(|field| field.is_primary)
|
||||
.map(FieldPB::from)
|
||||
.map(FieldPB::new)
|
||||
.collect::<Vec<FieldPB>>();
|
||||
|
||||
if fields.is_empty() {
|
||||
@ -217,8 +217,7 @@ pub(crate) async fn update_field_type_option_handler(
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
if let Some(old_field) = database_editor.get_field(¶ms.field_id) {
|
||||
let field_type = FieldType::from(old_field.field_type);
|
||||
let type_option_data =
|
||||
type_option_data_from_pb_or_default(params.type_option_data, &field_type);
|
||||
let type_option_data = type_option_data_from_pb(params.type_option_data, &field_type)?;
|
||||
database_editor
|
||||
.update_field_type_option(
|
||||
¶ms.view_id,
|
||||
@ -293,46 +292,19 @@ pub(crate) async fn duplicate_field_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the FieldTypeOptionData if the Field exists otherwise return record not found error.
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_field_type_option_data_handler(
|
||||
data: AFPluginData<TypeOptionPathPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> DataResult<TypeOptionPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: TypeOptionPathParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
if let Some((field, data)) = database_editor
|
||||
.get_field_type_option_data(¶ms.field_id)
|
||||
.await
|
||||
{
|
||||
let data = TypeOptionPB {
|
||||
view_id: params.view_id,
|
||||
field: FieldPB::from(field),
|
||||
type_option_data: data.to_vec(),
|
||||
};
|
||||
data_result_ok(data)
|
||||
} else {
|
||||
Err(FlowyError::record_not_found())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create TypeOptionPB and save it. Return the FieldTypeOptionData.
|
||||
/// Create a field and save it. Returns the [FieldPB] in the current view.
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn create_field_handler(
|
||||
data: AFPluginData<CreateFieldPayloadPB>,
|
||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||
) -> DataResult<TypeOptionPB, FlowyError> {
|
||||
) -> DataResult<FieldPB, FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let params: CreateFieldParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||
let (field, data) = database_editor.create_field_with_type_option(¶ms).await;
|
||||
let data = database_editor
|
||||
.create_field_with_type_option(params)
|
||||
.await?;
|
||||
|
||||
let data = TypeOptionPB {
|
||||
view_id: params.view_id,
|
||||
field: FieldPB::from(field),
|
||||
type_option_data: data.to_vec(),
|
||||
};
|
||||
data_result_ok(data)
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,6 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
||||
.event(DatabaseEvent::UpdateFieldType, switch_to_field_handler)
|
||||
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
|
||||
.event(DatabaseEvent::MoveField, move_field_handler)
|
||||
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
|
||||
.event(DatabaseEvent::CreateField, create_field_handler)
|
||||
// Row
|
||||
.event(DatabaseEvent::CreateRow, create_row_handler)
|
||||
@ -174,18 +173,9 @@ pub enum DatabaseEvent {
|
||||
#[event(input = "MoveFieldPayloadPB")]
|
||||
MoveField = 22,
|
||||
|
||||
/// [TypeOptionPathPB] event is used to get the FieldTypeOption data for a specific field type.
|
||||
///
|
||||
/// Check out the [TypeOptionPB] for more details. If the [FieldTypeOptionData] does exist
|
||||
/// for the target type, the [TypeOptionBuilder] will create the default data for that type.
|
||||
///
|
||||
/// Return the [TypeOptionPB] if there are no errors.
|
||||
#[event(input = "TypeOptionPathPB", output = "TypeOptionPB")]
|
||||
GetTypeOption = 23,
|
||||
|
||||
/// [CreateField] event is used to create a new field with an optional
|
||||
/// TypeOptionData.
|
||||
#[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")]
|
||||
#[event(input = "CreateFieldPayloadPB", output = "FieldPB")]
|
||||
CreateField = 24,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB", output = "FieldPB")]
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use collab_database::database::MutexDatabase;
|
||||
use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
|
||||
@ -28,8 +27,8 @@ use crate::services::database_view::{
|
||||
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
|
||||
use crate::services::field::{
|
||||
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
|
||||
type_option_data_from_pb_or_default, type_option_to_pb, SelectOptionCellChangeset,
|
||||
SelectOptionIds, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt,
|
||||
type_option_data_from_pb, SelectOptionCellChangeset, SelectOptionIds, TimestampCellData,
|
||||
TypeOptionCellDataHandler, TypeOptionCellExt,
|
||||
};
|
||||
use crate::services::field_settings::{
|
||||
default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams,
|
||||
@ -469,28 +468,20 @@ impl DatabaseEditor {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn get_field_type_option_data(&self, field_id: &str) -> Option<(Field, Bytes)> {
|
||||
let field = self.database.lock().fields.get_field(field_id);
|
||||
field.map(|field| {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let type_option = field
|
||||
.get_any_type_option(field_type)
|
||||
.unwrap_or_else(|| default_type_option_data_from_type(&field_type));
|
||||
(field, type_option_to_pb(type_option, &field_type))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create_field_with_type_option(&self, params: &CreateFieldParams) -> (Field, Bytes) {
|
||||
pub async fn create_field_with_type_option(
|
||||
&self,
|
||||
params: CreateFieldParams,
|
||||
) -> FlowyResult<FieldPB> {
|
||||
let name = params
|
||||
.field_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| params.field_type.default_name());
|
||||
let type_option_data = match ¶ms.type_option_data {
|
||||
None => default_type_option_data_from_type(¶ms.field_type),
|
||||
Some(type_option_data) => {
|
||||
type_option_data_from_pb_or_default(type_option_data.clone(), ¶ms.field_type)
|
||||
},
|
||||
};
|
||||
|
||||
let type_option_data = params
|
||||
.type_option_data
|
||||
.and_then(|data| type_option_data_from_pb(data, ¶ms.field_type).ok())
|
||||
.unwrap_or(default_type_option_data_from_type(¶ms.field_type));
|
||||
|
||||
let (index, field) = self.database.lock().create_field_with_mut(
|
||||
¶ms.view_id,
|
||||
name,
|
||||
@ -499,7 +490,7 @@ impl DatabaseEditor {
|
||||
|field| {
|
||||
field
|
||||
.type_options
|
||||
.insert(params.field_type.to_string(), type_option_data.clone());
|
||||
.insert(params.field_type.to_string(), type_option_data);
|
||||
},
|
||||
default_field_settings_by_layout_map(),
|
||||
);
|
||||
@ -508,10 +499,7 @@ impl DatabaseEditor {
|
||||
.notify_did_insert_database_field(field.clone(), index)
|
||||
.await;
|
||||
|
||||
(
|
||||
field,
|
||||
type_option_to_pb(type_option_data, ¶ms.field_type),
|
||||
)
|
||||
Ok(FieldPB::new(field))
|
||||
}
|
||||
|
||||
pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
|
||||
@ -539,7 +527,10 @@ impl DatabaseEditor {
|
||||
|
||||
if let Some(index) = new_index {
|
||||
let delete_field = FieldIdPB::from(params.from_field_id);
|
||||
let insert_field = IndexFieldPB::from_field(field, index);
|
||||
let insert_field = IndexFieldPB {
|
||||
field: FieldPB::new(field),
|
||||
index: index as i32,
|
||||
};
|
||||
let notified_changeset = DatabaseFieldChangesetPB {
|
||||
view_id: params.view_id,
|
||||
inserted_fields: vec![insert_field],
|
||||
@ -1058,7 +1049,10 @@ impl DatabaseEditor {
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
async fn notify_did_insert_database_field(&self, field: Field, index: usize) -> FlowyResult<()> {
|
||||
let database_id = self.database.lock().get_database_id();
|
||||
let index_field = IndexFieldPB::from_field(field, index);
|
||||
let index_field = IndexFieldPB {
|
||||
field: FieldPB::new(field),
|
||||
index: index as i32,
|
||||
};
|
||||
let notified_changeset = DatabaseFieldChangesetPB::insert(&database_id, vec![index_field]);
|
||||
let _ = self.notify_did_update_database(notified_changeset).await;
|
||||
Ok(())
|
||||
@ -1629,7 +1623,7 @@ fn notify_did_update_database_field(
|
||||
};
|
||||
|
||||
if let Some(field) = field {
|
||||
let updated_field = FieldPB::from(field);
|
||||
let updated_field = FieldPB::new(field);
|
||||
let notified_changeset =
|
||||
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);
|
||||
|
||||
|
@ -175,12 +175,12 @@ pub trait TypeOptionCellDataCompare: TypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
|
||||
pub fn type_option_data_from_pb<T: Into<Bytes>>(
|
||||
bytes: T,
|
||||
field_type: &FieldType,
|
||||
) -> TypeOptionData {
|
||||
) -> Result<TypeOptionData, ProtobufError> {
|
||||
let bytes = bytes.into();
|
||||
let result: Result<TypeOptionData, ProtobufError> = match field_type {
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
RichTextTypeOptionPB::try_from(bytes).map(|pb| RichTextTypeOption::from(pb).into())
|
||||
},
|
||||
@ -206,9 +206,7 @@ pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
|
||||
FieldType::Checklist => {
|
||||
ChecklistTypeOptionPB::try_from(bytes).map(|pb| ChecklistTypeOption::from(pb).into())
|
||||
},
|
||||
};
|
||||
|
||||
result.unwrap_or_else(|_| default_type_option_data_from_type(field_type))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_option_to_pb(type_option: TypeOptionData, field_type: &FieldType) -> Bytes {
|
||||
|
@ -64,7 +64,7 @@ impl DatabaseFieldTest {
|
||||
match script {
|
||||
FieldScript::CreateField { params } => {
|
||||
self.field_count += 1;
|
||||
self.editor.create_field_with_type_option(¶ms).await;
|
||||
let _ = self.editor.create_field_with_type_option(params).await;
|
||||
let fields = self.editor.get_fields(&self.view_id, None);
|
||||
assert_eq!(self.field_count, fields.len());
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user