diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index 7e65130fa8..698cc63531 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -15,7 +15,6 @@ import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldTypeOptionData; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; @@ -157,13 +156,6 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( - (gridId, fieldLoader) => FieldEditorBloc( - gridId: gridId, - fieldLoader: fieldLoader, - ), - ); - getIt.registerFactoryParam( (context, _) => TextCellBloc( cellContext: context, @@ -195,10 +187,6 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( - (context, _) => FieldEditorPannelBloc(context), - ); - getIt.registerFactoryParam( (typeOption, _) => DateTypeOptionBloc(typeOption: typeOption), ); diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index 4f81e119fc..ed191c7d60 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -19,7 +19,7 @@ class GridCellContextBuilder { return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: CellDataLoader(gridCell: _gridCell), + cellDataLoader: GridCellDataLoader(gridCell: _gridCell), cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.DateTime: @@ -30,17 +30,24 @@ class GridCellContextBuilder { cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell), ); case FieldType.Number: + final cellDataLoader = GridCellDataLoader( + gridCell: _gridCell, + config: const GridCellDataConfig( + reloadOnCellChanged: true, + reloadOnFieldChanged: true, + ), + ); return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: CellDataLoader(gridCell: _gridCell, reloadOnCellChanged: true), + cellDataLoader: cellDataLoader, cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.RichText: return GridCellContext( gridCell: _gridCell, cellCache: _cellCache, - cellDataLoader: CellDataLoader(gridCell: _gridCell), + cellDataLoader: GridCellDataLoader(gridCell: _gridCell), cellDataPersistence: CellDataPersistence(gridCell: _gridCell), ); case FieldType.MultiSelect: @@ -62,7 +69,7 @@ class _GridCellContext extends Equatable { final GridCell gridCell; final GridCellCache cellCache; final GridCellCacheKey _cacheKey; - final _GridCellDataLoader cellDataLoader; + final IGridCellDataLoader cellDataLoader; final _GridCellDataPersistence cellDataPersistence; final FieldService _fieldService; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart index f625af9ad0..5be0435323 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/data_loader.dart @@ -1,6 +1,6 @@ part of 'cell_service.dart'; -abstract class GridCellDataConfig { +abstract class IGridCellDataConfig { // The cell data will reload if it receives the field's change notification. bool get reloadOnFieldChanged; @@ -11,34 +11,36 @@ abstract class GridCellDataConfig { bool get reloadOnCellChanged; } -class DefaultCellDataConfig implements GridCellDataConfig { +class GridCellDataConfig implements IGridCellDataConfig { @override final bool reloadOnCellChanged; @override final bool reloadOnFieldChanged; - DefaultCellDataConfig({ + const GridCellDataConfig({ this.reloadOnCellChanged = false, this.reloadOnFieldChanged = false, }); } -abstract class _GridCellDataLoader { +abstract class IGridCellDataLoader { Future loadData(); - GridCellDataConfig get config; + IGridCellDataConfig get config; } -class CellDataLoader extends _GridCellDataLoader { +class GridCellDataLoader extends IGridCellDataLoader { final CellService service = CellService(); final GridCell gridCell; - final GridCellDataConfig _config; - CellDataLoader({ + @override + final IGridCellDataConfig config; + + GridCellDataLoader({ required this.gridCell, - bool reloadOnCellChanged = false, - }) : _config = DefaultCellDataConfig(reloadOnCellChanged: reloadOnCellChanged); + this.config = const GridCellDataConfig(), + }); @override Future loadData() { @@ -54,20 +56,17 @@ class CellDataLoader extends _GridCellDataLoader { }); }); } - - @override - GridCellDataConfig get config => _config; } -class DateCellDataLoader extends _GridCellDataLoader { +class DateCellDataLoader extends IGridCellDataLoader { final GridCell gridCell; - final GridCellDataConfig _config; + final IGridCellDataConfig _config; DateCellDataLoader({ required this.gridCell, - }) : _config = DefaultCellDataConfig(reloadOnFieldChanged: true); + }) : _config = const GridCellDataConfig(reloadOnFieldChanged: true); @override - GridCellDataConfig get config => _config; + IGridCellDataConfig get config => _config; @override Future loadData() { @@ -88,7 +87,7 @@ class DateCellDataLoader extends _GridCellDataLoader { } } -class SelectOptionCellDataLoader extends _GridCellDataLoader { +class SelectOptionCellDataLoader extends IGridCellDataLoader { final SelectOptionService service; final GridCell gridCell; SelectOptionCellDataLoader({ @@ -108,5 +107,5 @@ class SelectOptionCellDataLoader extends _GridCellDataLoader DefaultCellDataConfig(); + IGridCellDataConfig get config => const GridCellDataConfig(reloadOnFieldChanged: true); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart index db84425f86..ba2c25fec6 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart @@ -1,40 +1,31 @@ -import 'dart:typed_data'; -import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; import 'field_service.dart'; import 'package:dartz/dartz.dart'; -import 'package:protobuf/protobuf.dart'; - part 'field_editor_bloc.freezed.dart'; class FieldEditorBloc extends Bloc { - final String gridId; - final FieldContextLoader _loader; - FieldEditorBloc({ - required this.gridId, - required FieldContextLoader fieldLoader, - }) : _loader = fieldLoader, - super(FieldEditorState.initial(gridId)) { + required String gridId, + required String fieldName, + required FieldContextLoader fieldContextLoader, + }) : super(FieldEditorState.initial(gridId, fieldName, fieldContextLoader)) { on( (event, emit) async { - await event.map( - initial: (_InitialField value) async { - await _getFieldTypeOptionContext(emit); + await event.when( + initial: () async { + final fieldContext = GridFieldContext(gridId: gridId, loader: fieldContextLoader); + await fieldContext.loadData().then((result) { + result.fold( + (l) => emit(state.copyWith(fieldContext: Some(fieldContext))), + (r) => null, + ); + }); }, - updateName: (_UpdateName value) { - final newContext = _updateEditContext(name: value.name); - emit(state.copyWith(fieldTypeOptionData: newContext)); - }, - updateField: (_UpdateField value) { - final data = _updateEditContext(field: value.field, typeOptionData: value.typeOptionData); - emit(state.copyWith(fieldTypeOptionData: data)); - }, - done: (_Done value) async { - await _saveField(emit); + updateName: (name) { + state.fieldContext.fold(() => null, (fieldContext) => fieldContext.fieldName = name); + emit(state.copyWith(name: name)); }, ); }, @@ -45,78 +36,12 @@ class FieldEditorBloc extends Bloc { Future close() async { return super.close(); } - - Option _updateEditContext({ - String? name, - Field? field, - List? typeOptionData, - }) { - return state.fieldTypeOptionData.fold( - () => none(), - (context) { - context.freeze(); - final newFieldTypeOptionData = context.rebuild((newContext) { - newContext.field_2.rebuild((newField) { - if (name != null) { - newField.name = name; - } - - newContext.field_2 = newField; - }); - - if (field != null) { - newContext.field_2 = field; - } - - if (typeOptionData != null) { - newContext.typeOptionData = typeOptionData; - } - }); - - FieldService.insertField( - gridId: gridId, - field: newFieldTypeOptionData.field_2, - typeOptionData: newFieldTypeOptionData.typeOptionData, - ); - - return Some(newFieldTypeOptionData); - }, - ); - } - - Future _saveField(Emitter emit) async { - await state.fieldTypeOptionData.fold( - () async => null, - (data) async { - final result = await FieldService.insertField( - gridId: gridId, - field: data.field_2, - typeOptionData: data.typeOptionData, - ); - result.fold((l) => null, (r) => null); - }, - ); - } - - Future _getFieldTypeOptionContext(Emitter emit) async { - final result = await _loader.load(); - result.fold( - (context) { - emit(state.copyWith( - fieldTypeOptionData: Some(context), - )); - }, - (err) => Log.error(err), - ); - } } @freezed class FieldEditorEvent with _$FieldEditorEvent { const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.updateName(String name) = _UpdateName; - const factory FieldEditorEvent.updateField(Field field, Uint8List typeOptionData) = _UpdateField; - const factory FieldEditorEvent.done() = _Done; } @freezed @@ -124,12 +49,14 @@ class FieldEditorState with _$FieldEditorState { const factory FieldEditorState({ required String gridId, required String errorText, - required Option fieldTypeOptionData, + required String name, + required Option fieldContext, }) = _FieldEditorState; - factory FieldEditorState.initial(String gridId) => FieldEditorState( + factory FieldEditorState.initial(String gridId, String fieldName, FieldContextLoader loader) => FieldEditorState( gridId: gridId, - fieldTypeOptionData: none(), + fieldContext: none(), errorText: '', + name: fieldName, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart index ea10e4222f..c6d48c9d18 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart @@ -1,24 +1,29 @@ -import 'dart:typed_data'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; +import 'field_service.dart'; + part 'field_editor_pannel_bloc.freezed.dart'; class FieldEditorPannelBloc extends Bloc { - FieldEditorPannelBloc(FieldTypeOptionData editContext) : super(FieldEditorPannelState.initial(editContext)) { + final GridFieldContext _fieldContext; + void Function()? _fieldListenFn; + + FieldEditorPannelBloc(GridFieldContext fieldContext) + : _fieldContext = fieldContext, + super(FieldEditorPannelState.initial(fieldContext)) { on( (event, emit) async { - await event.map( - toFieldType: (_ToFieldType value) async { - emit(state.copyWith( - field: value.field, - typeOptionData: Uint8List.fromList(value.typeOptionData), - )); + event.when( + initial: () { + _fieldListenFn = fieldContext.addFieldListener((field) { + add(FieldEditorPannelEvent.didReceiveFieldUpdated(field)); + }); }, - didUpdateTypeOptionData: (_DidUpdateTypeOptionData value) { - emit(state.copyWith(typeOptionData: value.typeOptionData)); + didReceiveFieldUpdated: (field) { + emit(state.copyWith(field: field)); }, ); }, @@ -27,27 +32,26 @@ class FieldEditorPannelBloc extends Bloc close() async { + if (_fieldListenFn != null) { + _fieldContext.removeFieldListener(_fieldListenFn!); + } return super.close(); } } @freezed class FieldEditorPannelEvent with _$FieldEditorPannelEvent { - const factory FieldEditorPannelEvent.toFieldType(Field field, List typeOptionData) = _ToFieldType; - const factory FieldEditorPannelEvent.didUpdateTypeOptionData(Uint8List typeOptionData) = _DidUpdateTypeOptionData; + const factory FieldEditorPannelEvent.initial() = _Initial; + const factory FieldEditorPannelEvent.didReceiveFieldUpdated(Field field) = _DidReceiveFieldUpdated; } @freezed class FieldEditorPannelState with _$FieldEditorPannelState { const factory FieldEditorPannelState({ - required String gridId, required Field field, - required Uint8List typeOptionData, }) = _FieldEditorPannelState; - factory FieldEditorPannelState.initial(FieldTypeOptionData data) => FieldEditorPannelState( - gridId: data.gridId, - field: data.field_2, - typeOptionData: Uint8List.fromList(data.typeOptionData), + factory FieldEditorPannelState.initial(GridFieldContext fieldContext) => FieldEditorPannelState( + field: fieldContext.field, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart index a760f13176..03f27dffe7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart @@ -1,9 +1,12 @@ import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:protobuf/protobuf.dart'; part 'field_service.freezed.dart'; class FieldService { @@ -199,3 +202,111 @@ class DefaultFieldContextLoader extends FieldContextLoader { return fieldService.switchToField(fieldType); } } + +class GridFieldContext { + final String gridId; + final FieldContextLoader _loader; + + late FieldTypeOptionData _data; + ValueNotifier? _fieldNotifier; + + GridFieldContext({ + required this.gridId, + required FieldContextLoader loader, + }) : _loader = loader; + + Future> loadData() async { + final result = await _loader.load(); + return result.fold( + (data) { + data.freeze(); + _data = data; + + if (_fieldNotifier == null) { + _fieldNotifier = ValueNotifier(data.field_2); + } else { + _fieldNotifier?.value = data.field_2; + } + + return left(unit); + }, + (err) { + Log.error(err); + return right(err); + }, + ); + } + + Field get field => _data.field_2; + + set field(Field field) { + _updateData(newField: field); + } + + List get typeOptionData => _data.typeOptionData; + + set fieldName(String name) { + _updateData(name: name); + } + + set typeOptionData(List typeOptionData) { + _updateData(typeOptionData: typeOptionData); + } + + void _updateData({String? name, Field? newField, List? typeOptionData}) { + _data = _data.rebuild((rebuildData) { + if (name != null) { + rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) { + rebuildField.name = name; + }); + } + + if (newField != null) { + rebuildData.field_2 = newField; + } + + if (typeOptionData != null) { + rebuildData.typeOptionData = typeOptionData; + } + }); + + if (_data.field_2 != _fieldNotifier?.value) { + _fieldNotifier?.value = _data.field_2; + } + + FieldService.insertField( + gridId: gridId, + field: field, + typeOptionData: typeOptionData, + ); + } + + Future switchToField(FieldType newFieldType) { + return _loader.switchToField(field.id, newFieldType).then((result) { + return result.fold( + (fieldTypeOptionData) { + _updateData( + newField: fieldTypeOptionData.field_2, + typeOptionData: fieldTypeOptionData.typeOptionData, + ); + }, + (err) { + Log.error(err); + }, + ); + }); + } + + void Function() addFieldListener(void Function(Field) callback) { + listener() { + callback(field); + } + + _fieldNotifier?.addListener(listener); + return listener; + } + + void removeFieldListener(void Function() listener) { + _fieldNotifier?.removeListener(listener); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart index c762f14c1d..cf9b69780d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart @@ -38,15 +38,17 @@ abstract class TypeOptionDataBuilder { } class TypeOptionContext { - final String gridId; - final Field field; - final Uint8List data; + final GridFieldContext _fieldContext; TypeOptionContext({ - required this.gridId, - required this.field, - required this.data, - }); + required GridFieldContext fieldContext, + }) : _fieldContext = fieldContext; + + String get gridId => _fieldContext.gridId; + + Field get field => _fieldContext.field; + + Uint8List get data => Uint8List.fromList(_fieldContext.typeOptionData); } abstract class TypeOptionFieldDelegate { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart index 2effe120c2..14e1ee3d33 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart @@ -224,48 +224,46 @@ class _SelectOptionCell extends StatelessWidget { final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, - child: Stack( - fit: StackFit.expand, + child: Row( children: [ - _body(theme, context), - InkWell( - onTap: () { - context.read().add(SelectOptionEditorEvent.selectOption(option.id)); - }, - ), + Expanded(child: _body(theme, context)), + FlowyIconButton( + width: 30, + onPressed: () => _showEditPannel(context), + iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), + icon: svgWidget("editor/details", color: theme.iconColor), + ) ], ), ); } - FlowyHover _body(AppTheme theme, BuildContext context) { - return FlowyHover( - style: HoverStyle(hoverColor: theme.hover), - builder: (_, onHover) { - List children = [ - SelectOptionTag( - name: option.name, - color: option.color.make(context), - isSelected: isSelected, - ), - const Spacer(), - ]; - - if (isSelected) { - children.add(svgWidget("grid/checkmark")); - } - - if (onHover) { - children.add(FlowyIconButton( - width: 30, - onPressed: () => _showEditPannel(context), - iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), - icon: svgWidget("editor/details", color: theme.iconColor), - )); - } - - return Row(children: children); - }, + Widget _body(AppTheme theme, BuildContext context) { + return Stack( + fit: StackFit.expand, + children: [ + FlowyHover( + style: HoverStyle(hoverColor: theme.hover), + builder: (_, onHover) { + return InkWell( + child: Row(children: [ + const HSpace(6), + SelectOptionTag( + name: option.name, + color: option.color.make(context), + isSelected: isSelected, + ), + const Spacer(), + if (isSelected) svgWidget("grid/checkmark"), + const HSpace(6), + ]), + onTap: () { + context.read().add(SelectOptionEditorEvent.selectOption(option.id)); + }, + ); + }, + ), + ], ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart index 1e67f4c70b..9eec395ce0 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart @@ -63,6 +63,7 @@ class GridFieldCell extends StatelessWidget { FieldEditor( gridId: state.gridId, + fieldName: state.field.name, contextLoader: DefaultFieldContextLoader( gridId: state.gridId, field: state.field, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart index cc3a12930c..b3d96949f0 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart @@ -1,4 +1,3 @@ -import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -11,16 +10,42 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'field_name_input.dart'; import 'field_editor_pannel.dart'; -class FieldEditor extends FlowyOverlayDelegate { +class FieldEditor extends StatelessWidget with FlowyOverlayDelegate { final String gridId; - final FieldEditorBloc _fieldEditorBloc; + final String fieldName; + final FieldContextLoader contextLoader; - FieldEditor({ + const FieldEditor({ required this.gridId, + required this.fieldName, required this.contextLoader, Key? key, - }) : _fieldEditorBloc = getIt(param1: gridId, param2: contextLoader) { - _fieldEditorBloc.add(const FieldEditorEvent.initial()); + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => FieldEditorBloc( + gridId: gridId, + fieldName: fieldName, + fieldContextLoader: contextLoader, + )..add(const FieldEditorEvent.initial()), + child: BlocBuilder( + buildWhen: (p, c) => false, + builder: (context, state) { + return ListView( + shrinkWrap: true, + children: [ + FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12), + const VSpace(10), + const _FieldNameTextField(), + const VSpace(10), + const _FieldPannel(), + ], + ); + }, + ), + ); } void show( @@ -28,10 +53,9 @@ class FieldEditor extends FlowyOverlayDelegate { AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned, }) { FlowyOverlay.of(context).remove(identifier()); - final child = _FieldEditorPage(_fieldEditorBloc, contextLoader); FlowyOverlay.of(context).insertWithAnchor( widget: OverlayContainer( - child: child, + child: this, constraints: BoxConstraints.loose(const Size(280, 400)), ), identifier: identifier(), @@ -46,49 +70,23 @@ class FieldEditor extends FlowyOverlayDelegate { return (FieldEditor).toString(); } - @override - void didRemove() { - _fieldEditorBloc.add(const FieldEditorEvent.done()); - } - @override bool asBarrier() => true; } -class _FieldEditorPage extends StatelessWidget { - final FieldEditorBloc editorBloc; - final FieldContextLoader contextLoader; - const _FieldEditorPage(this.editorBloc, this.contextLoader, {Key? key}) : super(key: key); +class _FieldPannel extends StatelessWidget { + const _FieldPannel({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return BlocProvider.value( - value: editorBloc, - child: BlocBuilder( - builder: (context, state) { - return state.fieldTypeOptionData.fold( - () => const SizedBox(), - (fieldTypeOptionContext) => ListView( - shrinkWrap: true, - children: [ - FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12), - const VSpace(10), - const _FieldNameTextField(), - const VSpace(10), - FieldEditorPannel( - fieldTypeOptionData: fieldTypeOptionContext, - onSwitchToField: (fieldId, fieldType) { - return contextLoader.switchToField(fieldId, fieldType); - }, - onUpdated: (field, typeOptionData) { - context.read().add(FieldEditorEvent.updateField(field, typeOptionData)); - }, - ), - ], - ), - ); - }, - ), + return BlocBuilder( + buildWhen: (p, c) => p.fieldContext != c.fieldContext, + builder: (context, state) { + return state.fieldContext.fold( + () => const SizedBox(), + (fieldContext) => FieldEditorPannel(fieldContext: fieldContext), + ); + }, ); } } @@ -98,16 +96,11 @@ class _FieldNameTextField extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocSelector( - selector: (state) { - return state.fieldTypeOptionData.fold( - () => "", - (fieldTypeOptionContext) => fieldTypeOptionContext.field_2.name, - ); - }, - builder: (context, name) { + return BlocBuilder( + buildWhen: (p, c) => p.name != c.name, + builder: (context, state) { return FieldNameTextField( - name: name, + name: state.name, errorText: context.read().state.errorText, onNameChanged: (newName) { context.read().add(FieldEditorEvent.updateName(newName)); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart index 5d96f92655..21df621705 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart @@ -7,14 +7,12 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart'; @@ -31,14 +29,10 @@ typedef SwitchToFieldCallback = Future> ); class FieldEditorPannel extends StatefulWidget { - final FieldTypeOptionData fieldTypeOptionData; - final UpdateFieldCallback onUpdated; - final SwitchToFieldCallback onSwitchToField; + final GridFieldContext fieldContext; const FieldEditorPannel({ - required this.fieldTypeOptionData, - required this.onUpdated, - required this.onSwitchToField, + required this.fieldContext, Key? key, }) : super(key: key); @@ -52,13 +46,10 @@ class _FieldEditorPannelState extends State { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => getIt(param1: widget.fieldTypeOptionData), - child: BlocConsumer( - listener: (context, state) { - widget.onUpdated(state.field, state.typeOptionData); - }, + create: (context) => FieldEditorPannelBloc(widget.fieldContext)..add(const FieldEditorPannelEvent.initial()), + child: BlocBuilder( builder: (context, state) { - List children = [_switchFieldTypeButton(context, state.field)]; + List children = [_switchFieldTypeButton(context, widget.fieldContext.field)]; final typeOptionWidget = _typeOptionWidget(context: context, state: state); if (typeOptionWidget != null) { @@ -84,19 +75,7 @@ class _FieldEditorPannelState extends State { hoverColor: theme.hover, onTap: () { final list = FieldTypeList(onSelectField: (newFieldType) { - widget.onSwitchToField(field.id, newFieldType).then((result) { - result.fold( - (fieldTypeOptionContext) { - context.read().add( - FieldEditorPannelEvent.toFieldType( - fieldTypeOptionContext.field_2, - fieldTypeOptionContext.typeOptionData, - ), - ); - }, - (err) => Log.error(err), - ); - }); + widget.fieldContext.switchToField(newFieldType); }); _showOverlay(context, list); }, @@ -116,15 +95,11 @@ class _FieldEditorPannelState extends State { ); final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) { - context.read().add(FieldEditorPannelEvent.didUpdateTypeOptionData(data)); + widget.fieldContext.typeOptionData = data; }); final builder = _makeTypeOptionBuild( - typeOptionContext: TypeOptionContext( - gridId: state.gridId, - field: state.field, - data: state.typeOptionData, - ), + typeOptionContext: TypeOptionContext(fieldContext: widget.fieldContext), overlayDelegate: overlayDelegate, dataDelegate: dataDelegate, ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart index d7b24b4d8c..5e1296191b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart @@ -150,6 +150,7 @@ class CreateFieldButton extends StatelessWidget { hoverColor: theme.hover, onTap: () => FieldEditor( gridId: gridId, + fieldName: "", contextLoader: NewFieldContextLoader(gridId: gridId), ).show(context), leftIcon: svgWidget("home/add"), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index 474a21773d..c8672d4ae3 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -178,6 +178,7 @@ class _RowDetailCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { FieldEditor( gridId: gridCell.gridId, + fieldName: gridCell.field.name, contextLoader: DefaultFieldContextLoader( gridId: gridCell.gridId, field: gridCell.field, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart index 151afc1e3f..ec5ca40b1e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart @@ -115,6 +115,7 @@ class _GridPropertyCell extends StatelessWidget { onTap: () { FieldEditor( gridId: gridId, + fieldName: field.name, contextLoader: DefaultFieldContextLoader(gridId: gridId, field: field), ).show(context, anchorDirection: AnchorDirection.bottomRight); }, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart index 5189908192..4f06a40b9b 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart @@ -109,7 +109,7 @@ abstract class HoverWidget extends StatefulWidget { } class FlowyHover2 extends StatefulWidget { - final HoverWidget child; + final Widget child; final EdgeInsets contentPadding; const FlowyHover2({ required this.child, @@ -127,9 +127,14 @@ class _FlowyHover2State extends State { @override void initState() { _hoverState = FlowyHoverState(); - widget.child.onFocus.addListener(() { - _hoverState.onFocus = widget.child.onFocus.value; - }); + + if (widget.child is HoverWidget) { + final hoverWidget = widget.child as HoverWidget; + hoverWidget.onFocus.addListener(() { + _hoverState.onFocus = hoverWidget.onFocus.value; + }); + } + super.initState(); } diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs index ccbaff82e9..06b4f7222f 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs @@ -212,7 +212,7 @@ pub struct FieldTypeOptionData { pub grid_id: String, #[pb(index = 2)] - pub field_id: String, + pub field: Field, #[pb(index = 3)] pub type_option_data: Vec, diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs index c0c03f65a7..6df3c847f0 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs +++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs @@ -2174,7 +2174,7 @@ impl ::protobuf::reflect::ProtobufValue for FieldTypeOptionContext { pub struct FieldTypeOptionData { // message fields pub grid_id: ::std::string::String, - pub field_id: ::std::string::String, + pub field: ::protobuf::SingularPtrField, pub type_option_data: ::std::vec::Vec, // special fields pub unknown_fields: ::protobuf::UnknownFields, @@ -2218,30 +2218,37 @@ impl FieldTypeOptionData { ::std::mem::replace(&mut self.grid_id, ::std::string::String::new()) } - // string field_id = 2; + // .Field field = 2; - pub fn get_field_id(&self) -> &str { - &self.field_id + pub fn get_field(&self) -> &Field { + self.field.as_ref().unwrap_or_else(|| ::default_instance()) } - pub fn clear_field_id(&mut self) { - self.field_id.clear(); + pub fn clear_field(&mut self) { + self.field.clear(); + } + + pub fn has_field(&self) -> bool { + self.field.is_some() } // Param is passed by value, moved - pub fn set_field_id(&mut self, v: ::std::string::String) { - self.field_id = v; + pub fn set_field(&mut self, v: Field) { + self.field = ::protobuf::SingularPtrField::some(v); } // Mutable pointer to the field. // If field is not initialized, it is initialized with default value first. - pub fn mut_field_id(&mut self) -> &mut ::std::string::String { - &mut self.field_id + pub fn mut_field(&mut self) -> &mut Field { + if self.field.is_none() { + self.field.set_default(); + } + self.field.as_mut().unwrap() } // Take field - pub fn take_field_id(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.field_id, ::std::string::String::new()) + pub fn take_field(&mut self) -> Field { + self.field.take().unwrap_or_else(|| Field::new()) } // bytes type_option_data = 3; @@ -2273,6 +2280,11 @@ impl FieldTypeOptionData { impl ::protobuf::Message for FieldTypeOptionData { fn is_initialized(&self) -> bool { + for v in &self.field { + if !v.is_initialized() { + return false; + } + }; true } @@ -2284,7 +2296,7 @@ impl ::protobuf::Message for FieldTypeOptionData { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?; }, 2 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?; + ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.field)?; }, 3 => { ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.type_option_data)?; @@ -2304,8 +2316,9 @@ impl ::protobuf::Message for FieldTypeOptionData { if !self.grid_id.is_empty() { my_size += ::protobuf::rt::string_size(1, &self.grid_id); } - if !self.field_id.is_empty() { - my_size += ::protobuf::rt::string_size(2, &self.field_id); + if let Some(ref v) = self.field.as_ref() { + let len = v.compute_size(); + my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len; } if !self.type_option_data.is_empty() { my_size += ::protobuf::rt::bytes_size(3, &self.type_option_data); @@ -2319,8 +2332,10 @@ impl ::protobuf::Message for FieldTypeOptionData { if !self.grid_id.is_empty() { os.write_string(1, &self.grid_id)?; } - if !self.field_id.is_empty() { - os.write_string(2, &self.field_id)?; + if let Some(ref v) = self.field.as_ref() { + os.write_tag(2, ::protobuf::wire_format::WireTypeLengthDelimited)?; + os.write_raw_varint32(v.get_cached_size())?; + v.write_to_with_cached_sizes(os)?; } if !self.type_option_data.is_empty() { os.write_bytes(3, &self.type_option_data)?; @@ -2368,10 +2383,10 @@ impl ::protobuf::Message for FieldTypeOptionData { |m: &FieldTypeOptionData| { &m.grid_id }, |m: &mut FieldTypeOptionData| { &mut m.grid_id }, )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "field_id", - |m: &FieldTypeOptionData| { &m.field_id }, - |m: &mut FieldTypeOptionData| { &mut m.field_id }, + fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage>( + "field", + |m: &FieldTypeOptionData| { &m.field }, + |m: &mut FieldTypeOptionData| { &mut m.field }, )); fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( "type_option_data", @@ -2395,7 +2410,7 @@ impl ::protobuf::Message for FieldTypeOptionData { impl ::protobuf::Clear for FieldTypeOptionData { fn clear(&mut self) { self.grid_id.clear(); - self.field_id.clear(); + self.field.clear(); self.type_option_data.clear(); self.unknown_fields.clear(); } @@ -8302,25 +8317,25 @@ static file_descriptor_proto_data: &'static [u8] = b"\ ne_of_field_id\"\x82\x01\n\x16FieldTypeOptionContext\x12\x17\n\x07grid_i\ d\x18\x01\x20\x01(\tR\x06gridId\x12%\n\ngrid_field\x18\x02\x20\x01(\x0b2\ \x06.FieldR\tgridField\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\ - \x0etypeOptionData\"s\n\x13FieldTypeOptionData\x12\x17\n\x07grid_id\x18\ - \x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07\ - fieldId\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionDa\ - ta\"-\n\rRepeatedField\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.Fiel\ - dR\x05items\"7\n\x12RepeatedFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\ - \x0b2\x0b.FieldOrderR\x05items\"T\n\x08RowOrder\x12\x15\n\x06row_id\x18\ - \x01\x20\x01(\tR\x05rowId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07b\ - lockId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\ - \x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_fiel\ - d_id\x18\x02\x20\x03(\x0b2\x17.Row.CellByFieldIdEntryR\rcellByFieldId\ - \x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\x1aG\n\x12CellByFie\ - ldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\ - \x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\ - \x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11Repe\ - atedGridBlock\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05i\ - tems\"U\n\x0eGridBlockOrder\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\ - \x07blockId\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrd\ - ers\"_\n\rIndexRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrd\ - erR\x08rowOrder\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\ + \x0etypeOptionData\"v\n\x13FieldTypeOptionData\x12\x17\n\x07grid_id\x18\ + \x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.\ + FieldR\x05field\x12(\n\x10type_option_data\x18\x03\x20\x01(\x0cR\x0etype\ + OptionData\"-\n\rRepeatedField\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\ + \x06.FieldR\x05items\"7\n\x12RepeatedFieldOrder\x12!\n\x05items\x18\x01\ + \x20\x03(\x0b2\x0b.FieldOrderR\x05items\"T\n\x08RowOrder\x12\x15\n\x06ro\ + w_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\n\x08block_id\x18\x02\x20\x01(\ + \tR\x07blockId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\"\xb8\ + \x01\n\x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12@\n\x10cell_b\ + y_field_id\x18\x02\x20\x03(\x0b2\x17.Row.CellByFieldIdEntryR\rcellByFiel\ + dId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\x06height\x1aG\n\x12CellBy\ + FieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05va\ + lue\x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\x028\x01\")\n\x0bRepeatedR\ + ow\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11Re\ + peatedGridBlock\x12\x20\n\x05items\x18\x01\x20\x03(\x0b2\n.GridBlockR\ + \x05items\"U\n\x0eGridBlockOrder\x12\x19\n\x08block_id\x18\x01\x20\x01(\ + \tR\x07blockId\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trow\ + Orders\"_\n\rIndexRowOrder\x12&\n\trow_order\x18\x01\x20\x01(\x0b2\t.Row\ + OrderR\x08rowOrder\x12\x16\n\x05index\x18\x02\x20\x01(\x05H\0R\x05indexB\ \x0e\n\x0cone_of_index\"Q\n\x0fUpdatedRowOrder\x12&\n\trow_order\x18\x01\ \x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x03row\x18\x02\x20\x01(\ \x0b2\x04.RowR\x03row\"\xc6\x01\n\x11GridRowsChangeset\x12\x19\n\x08bloc\ diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto index c1aa74b0e7..99024d3841 100644 --- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto +++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto @@ -45,7 +45,7 @@ message FieldTypeOptionContext { } message FieldTypeOptionData { string grid_id = 1; - string field_id = 2; + Field field = 2; bytes type_option_data = 3; } message RepeatedField {