fix: disable edit primary field (#2695)

* refactor: field editor

* chore: disable edit type option of primary field
This commit is contained in:
Nathan.fooo
2023-06-04 09:28:13 +08:00
committed by GitHub
parent edd58ede45
commit bec8122178
43 changed files with 362 additions and 313 deletions

View File

@ -1,6 +1,6 @@
part of 'cell_service.dart'; part of 'cell_service.dart';
typedef CellByFieldId = LinkedHashMap<String, CellIdentifier>; typedef CellContextByFieldId = LinkedHashMap<String, DatabaseCellContext>;
class DatabaseCell { class DatabaseCell {
dynamic object; dynamic object;

View File

@ -22,7 +22,7 @@ import 'cell_service.dart';
/// ///
// ignore: must_be_immutable // ignore: must_be_immutable
class CellController<T, D> extends Equatable { class CellController<T, D> extends Equatable {
final CellIdentifier cellId; final DatabaseCellContext cellContext;
final CellCache _cellCache; final CellCache _cellCache;
final CellCacheKey _cacheKey; final CellCacheKey _cacheKey;
final FieldBackendService _fieldBackendSvc; final FieldBackendService _fieldBackendSvc;
@ -37,37 +37,37 @@ class CellController<T, D> extends Equatable {
Timer? _loadDataOperation; Timer? _loadDataOperation;
Timer? _saveDataOperation; Timer? _saveDataOperation;
String get viewId => cellId.viewId; String get viewId => cellContext.viewId;
RowId get rowId => cellId.rowId; RowId get rowId => cellContext.rowId;
String get fieldId => cellId.fieldInfo.id; String get fieldId => cellContext.fieldInfo.id;
FieldInfo get fieldInfo => cellId.fieldInfo; FieldInfo get fieldInfo => cellContext.fieldInfo;
FieldType get fieldType => cellId.fieldInfo.fieldType; FieldType get fieldType => cellContext.fieldInfo.fieldType;
CellController({ CellController({
required this.cellId, required this.cellContext,
required CellCache cellCache, required CellCache cellCache,
required CellDataLoader<T> cellDataLoader, required CellDataLoader<T> cellDataLoader,
required CellDataPersistence<D> cellDataPersistence, required CellDataPersistence<D> cellDataPersistence,
}) : _cellCache = cellCache, }) : _cellCache = cellCache,
_cellDataLoader = cellDataLoader, _cellDataLoader = cellDataLoader,
_cellDataPersistence = cellDataPersistence, _cellDataPersistence = cellDataPersistence,
_fieldListener = SingleFieldListener(fieldId: cellId.fieldId), _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
_fieldBackendSvc = FieldBackendService( _fieldBackendSvc = FieldBackendService(
viewId: cellId.viewId, viewId: cellContext.viewId,
fieldId: cellId.fieldInfo.id, fieldId: cellContext.fieldInfo.id,
), ),
_cacheKey = CellCacheKey( _cacheKey = CellCacheKey(
rowId: cellId.rowId, rowId: cellContext.rowId,
fieldId: cellId.fieldInfo.id, fieldId: cellContext.fieldInfo.id,
) { ) {
_cellDataNotifier = CellDataNotifier(value: _cellCache.get(_cacheKey)); _cellDataNotifier = CellDataNotifier(value: _cellCache.get(_cacheKey));
_cellListener = CellListener( _cellListener = CellListener(
rowId: cellId.rowId, rowId: cellContext.rowId,
fieldId: cellId.fieldInfo.id, fieldId: cellContext.fieldInfo.id,
); );
/// 1.Listen on user edit event and load the new cell data if needed. /// 1.Listen on user edit event and load the new cell data if needed.
@ -195,8 +195,10 @@ class CellController<T, D> extends Equatable {
} }
@override @override
List<Object> get props => List<Object> get props => [
[_cellCache.get(_cacheKey) ?? "", cellId.rowId + cellId.fieldInfo.id]; _cellCache.get(_cacheKey) ?? "",
cellContext.rowId + cellContext.fieldInfo.id
];
} }
class CellDataNotifier<T> extends ChangeNotifier { class CellDataNotifier<T> extends ChangeNotifier {

View File

@ -17,104 +17,111 @@ typedef DateCellController = CellController<DateCellDataPB, DateCellData>;
typedef URLCellController = CellController<URLCellDataPB, String>; typedef URLCellController = CellController<URLCellDataPB, String>;
class CellControllerBuilder { class CellControllerBuilder {
final CellIdentifier _cellId; final DatabaseCellContext _cellContext;
final CellCache _cellCache; final CellCache _cellCache;
CellControllerBuilder({ CellControllerBuilder({
required CellIdentifier cellId, required DatabaseCellContext cellContext,
required CellCache cellCache, required CellCache cellCache,
}) : _cellCache = cellCache, }) : _cellCache = cellCache,
_cellId = cellId; _cellContext = cellContext;
CellController build() { CellController build() {
switch (_cellId.fieldType) { switch (_cellContext.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
final cellDataLoader = CellDataLoader( final cellDataLoader = CellDataLoader(
cellId: _cellId, cellContext: _cellContext,
parser: StringCellDataParser(), parser: StringCellDataParser(),
); );
return TextCellController( return TextCellController(
cellId: _cellId, cellContext: _cellContext,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId), cellDataPersistence:
TextCellDataPersistence(cellContext: _cellContext),
); );
case FieldType.DateTime: case FieldType.DateTime:
case FieldType.LastEditedTime: case FieldType.LastEditedTime:
case FieldType.CreatedTime: case FieldType.CreatedTime:
final cellDataLoader = CellDataLoader( final cellDataLoader = CellDataLoader(
cellId: _cellId, cellContext: _cellContext,
parser: DateCellDataParser(), parser: DateCellDataParser(),
reloadOnFieldChanged: true, reloadOnFieldChanged: true,
); );
return DateCellController( return DateCellController(
cellId: _cellId, cellContext: _cellContext,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: DateCellDataPersistence(cellId: _cellId), cellDataPersistence:
DateCellDataPersistence(cellContext: _cellContext),
); );
case FieldType.Number: case FieldType.Number:
final cellDataLoader = CellDataLoader( final cellDataLoader = CellDataLoader(
cellId: _cellId, cellContext: _cellContext,
parser: NumberCellDataParser(), parser: NumberCellDataParser(),
reloadOnFieldChanged: true, reloadOnFieldChanged: true,
); );
return NumberCellController( return NumberCellController(
cellId: _cellId, cellContext: _cellContext,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId), cellDataPersistence:
TextCellDataPersistence(cellContext: _cellContext),
); );
case FieldType.RichText: case FieldType.RichText:
final cellDataLoader = CellDataLoader( final cellDataLoader = CellDataLoader(
cellId: _cellId, cellContext: _cellContext,
parser: StringCellDataParser(), parser: StringCellDataParser(),
); );
return TextCellController( return TextCellController(
cellId: _cellId, cellContext: _cellContext,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId), cellDataPersistence:
TextCellDataPersistence(cellContext: _cellContext),
); );
case FieldType.MultiSelect: case FieldType.MultiSelect:
case FieldType.SingleSelect: case FieldType.SingleSelect:
final cellDataLoader = CellDataLoader( final cellDataLoader = CellDataLoader(
cellId: _cellId, cellContext: _cellContext,
parser: SelectOptionCellDataParser(), parser: SelectOptionCellDataParser(),
reloadOnFieldChanged: true, reloadOnFieldChanged: true,
); );
return SelectOptionCellController( return SelectOptionCellController(
cellId: _cellId, cellContext: _cellContext,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId), cellDataPersistence:
TextCellDataPersistence(cellContext: _cellContext),
); );
case FieldType.Checklist: case FieldType.Checklist:
final cellDataLoader = CellDataLoader( final cellDataLoader = CellDataLoader(
cellId: _cellId, cellContext: _cellContext,
parser: ChecklistCellDataParser(), parser: ChecklistCellDataParser(),
reloadOnFieldChanged: true, reloadOnFieldChanged: true,
); );
return ChecklistCellController( return ChecklistCellController(
cellId: _cellId, cellContext: _cellContext,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId), cellDataPersistence:
TextCellDataPersistence(cellContext: _cellContext),
); );
case FieldType.URL: case FieldType.URL:
final cellDataLoader = CellDataLoader( final cellDataLoader = CellDataLoader(
cellId: _cellId, cellContext: _cellContext,
parser: URLCellDataParser(), parser: URLCellDataParser(),
); );
return URLCellController( return URLCellController(
cellId: _cellId, cellContext: _cellContext,
cellCache: _cellCache, cellCache: _cellCache,
cellDataLoader: cellDataLoader, cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId), cellDataPersistence:
TextCellDataPersistence(cellContext: _cellContext),
); );
} }
throw UnimplementedError; throw UnimplementedError;

View File

@ -11,18 +11,18 @@ abstract class CellDataParser<T> {
class CellDataLoader<T> { class CellDataLoader<T> {
final CellBackendService service = CellBackendService(); final CellBackendService service = CellBackendService();
final CellIdentifier cellId; final DatabaseCellContext cellContext;
final CellDataParser<T> parser; final CellDataParser<T> parser;
final bool reloadOnFieldChanged; final bool reloadOnFieldChanged;
CellDataLoader({ CellDataLoader({
required this.cellId, required this.cellContext,
required this.parser, required this.parser,
this.reloadOnFieldChanged = false, this.reloadOnFieldChanged = false,
}); });
Future<T?> loadData() { Future<T?> loadData() {
final fut = service.getCell(cellId: cellId); final fut = service.getCell(cellContext: cellContext);
return fut.then( return fut.then(
(result) => result.fold( (result) => result.fold(
(CellPB cell) { (CellPB cell) {

View File

@ -7,16 +7,17 @@ abstract class CellDataPersistence<D> {
} }
class TextCellDataPersistence implements CellDataPersistence<String> { class TextCellDataPersistence implements CellDataPersistence<String> {
final CellIdentifier cellId; final DatabaseCellContext cellContext;
final _cellBackendSvc = CellBackendService(); final _cellBackendSvc = CellBackendService();
TextCellDataPersistence({ TextCellDataPersistence({
required this.cellId, required this.cellContext,
}); });
@override @override
Future<Option<FlowyError>> save(String data) async { Future<Option<FlowyError>> save(String data) async {
final fut = _cellBackendSvc.updateCell(cellId: cellId, data: data); final fut =
_cellBackendSvc.updateCell(cellContext: cellContext, data: data);
return fut.then((result) { return fut.then((result) {
return result.fold( return result.fold(
(l) => none(), (l) => none(),
@ -36,14 +37,15 @@ class DateCellData with _$DateCellData {
} }
class DateCellDataPersistence implements CellDataPersistence<DateCellData> { class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
final CellIdentifier cellId; final DatabaseCellContext cellContext;
DateCellDataPersistence({ DateCellDataPersistence({
required this.cellId, required this.cellContext,
}); });
@override @override
Future<Option<FlowyError>> save(DateCellData data) { Future<Option<FlowyError>> save(DateCellData data) {
var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId); var payload = DateChangesetPB.create()
..cellPath = _makeCellPath(cellContext);
if (data.dateTime != null) { if (data.dateTime != null) {
final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString(); final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
payload.date = date; payload.date = date;
@ -62,7 +64,7 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
} }
} }
CellIdPB _makeCellPath(CellIdentifier cellId) { CellIdPB _makeCellPath(DatabaseCellContext cellId) {
return CellIdPB.create() return CellIdPB.create()
..viewId = cellId.viewId ..viewId = cellId.viewId
..fieldId = cellId.fieldId ..fieldId = cellId.fieldId

View File

@ -25,40 +25,39 @@ class CellBackendService {
CellBackendService(); CellBackendService();
Future<Either<void, FlowyError>> updateCell({ Future<Either<void, FlowyError>> updateCell({
required CellIdentifier cellId, required DatabaseCellContext cellContext,
required String data, required String data,
}) { }) {
final payload = CellChangesetPB.create() final payload = CellChangesetPB.create()
..viewId = cellId.viewId ..viewId = cellContext.viewId
..fieldId = cellId.fieldId ..fieldId = cellContext.fieldId
..rowId = cellId.rowId ..rowId = cellContext.rowId
..cellChangeset = data; ..cellChangeset = data;
return DatabaseEventUpdateCell(payload).send(); return DatabaseEventUpdateCell(payload).send();
} }
Future<Either<CellPB, FlowyError>> getCell({ Future<Either<CellPB, FlowyError>> getCell({
required CellIdentifier cellId, required DatabaseCellContext cellContext,
}) { }) {
final payload = CellIdPB.create() final payload = CellIdPB.create()
..viewId = cellId.viewId ..viewId = cellContext.viewId
..fieldId = cellId.fieldId ..fieldId = cellContext.fieldId
..rowId = cellId.rowId; ..rowId = cellContext.rowId;
return DatabaseEventGetCell(payload).send(); return DatabaseEventGetCell(payload).send();
} }
} }
/// Id of the cell
/// We can locate the cell by using database + rowId + field.id. /// We can locate the cell by using database + rowId + field.id.
@freezed @freezed
class CellIdentifier with _$CellIdentifier { class DatabaseCellContext with _$DatabaseCellContext {
const factory CellIdentifier({ const factory DatabaseCellContext({
required String viewId, required String viewId,
required RowId rowId, required RowId rowId,
required FieldInfo fieldInfo, required FieldInfo fieldInfo,
}) = _CellIdentifier; }) = _DatabaseCellContext;
// ignore: unused_element // ignore: unused_element
const CellIdentifier._(); const DatabaseCellContext._();
String get fieldId => fieldInfo.id; String get fieldId => fieldInfo.id;

View File

@ -7,17 +7,17 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
class ChecklistCellBackendService { class ChecklistCellBackendService {
final CellIdentifier cellId; final DatabaseCellContext cellContext;
ChecklistCellBackendService({required this.cellId}); ChecklistCellBackendService({required this.cellContext});
Future<Either<Unit, FlowyError>> create({ Future<Either<Unit, FlowyError>> create({
required String name, required String name,
}) { }) {
final payload = ChecklistCellDataChangesetPB.create() final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId ..viewId = cellContext.viewId
..fieldId = cellId.fieldInfo.id ..fieldId = cellContext.fieldInfo.id
..rowId = cellId.rowId ..rowId = cellContext.rowId
..insertOptions.add(name); ..insertOptions.add(name);
return DatabaseEventUpdateChecklistCell(payload).send(); return DatabaseEventUpdateChecklistCell(payload).send();
@ -27,9 +27,9 @@ class ChecklistCellBackendService {
required List<String> optionIds, required List<String> optionIds,
}) { }) {
final payload = ChecklistCellDataChangesetPB.create() final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId ..viewId = cellContext.viewId
..fieldId = cellId.fieldInfo.id ..fieldId = cellContext.fieldInfo.id
..rowId = cellId.rowId ..rowId = cellContext.rowId
..deleteOptionIds.addAll(optionIds); ..deleteOptionIds.addAll(optionIds);
return DatabaseEventUpdateChecklistCell(payload).send(); return DatabaseEventUpdateChecklistCell(payload).send();
@ -39,9 +39,9 @@ class ChecklistCellBackendService {
required String optionId, required String optionId,
}) { }) {
final payload = ChecklistCellDataChangesetPB.create() final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId ..viewId = cellContext.viewId
..fieldId = cellId.fieldInfo.id ..fieldId = cellContext.fieldInfo.id
..rowId = cellId.rowId ..rowId = cellContext.rowId
..selectedOptionIds.add(optionId); ..selectedOptionIds.add(optionId);
return DatabaseEventUpdateChecklistCell(payload).send(); return DatabaseEventUpdateChecklistCell(payload).send();
@ -51,9 +51,9 @@ class ChecklistCellBackendService {
required SelectOptionPB option, required SelectOptionPB option,
}) { }) {
final payload = ChecklistCellDataChangesetPB.create() final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId ..viewId = cellContext.viewId
..fieldId = cellId.fieldInfo.id ..fieldId = cellContext.fieldInfo.id
..rowId = cellId.rowId ..rowId = cellContext.rowId
..updateOptions.add(option); ..updateOptions.add(option);
return DatabaseEventUpdateChecklistCell(payload).send(); return DatabaseEventUpdateChecklistCell(payload).send();
@ -61,10 +61,10 @@ class ChecklistCellBackendService {
Future<Either<ChecklistCellDataPB, FlowyError>> getCellData() { Future<Either<ChecklistCellDataPB, FlowyError>> getCellData() {
final payload = CellIdPB.create() final payload = CellIdPB.create()
..fieldId = cellId.fieldInfo.id ..fieldId = cellContext.fieldInfo.id
..viewId = cellId.viewId ..viewId = cellContext.viewId
..rowId = cellId.rowId ..rowId = cellContext.rowId
..rowId = cellId.rowId; ..rowId = cellContext.rowId;
return DatabaseEventGetChecklistCellData(payload).send(); return DatabaseEventGetChecklistCellData(payload).send();
} }

View File

@ -8,12 +8,12 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/cell_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/cell_entities.pb.dart';
class SelectOptionCellBackendService { class SelectOptionCellBackendService {
final CellIdentifier cellId; final DatabaseCellContext cellContext;
SelectOptionCellBackendService({required this.cellId}); SelectOptionCellBackendService({required this.cellContext});
String get viewId => cellId.viewId; String get viewId => cellContext.viewId;
String get fieldId => cellId.fieldInfo.id; String get fieldId => cellContext.fieldInfo.id;
RowId get rowId => cellId.rowId; RowId get rowId => cellContext.rowId;
Future<Either<Unit, FlowyError>> create({ Future<Either<Unit, FlowyError>> create({
required String name, required String name,

View File

@ -10,7 +10,7 @@ class FieldActionSheetBloc
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> { extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
final FieldBackendService fieldService; final FieldBackendService fieldService;
FieldActionSheetBloc({required FieldCellContext fieldCellContext}) FieldActionSheetBloc({required FieldContext fieldCellContext})
: fieldService = FieldBackendService( : fieldService = FieldBackendService(
viewId: fieldCellContext.viewId, viewId: fieldCellContext.viewId,
fieldId: fieldCellContext.field.id, fieldId: fieldCellContext.field.id,

View File

@ -14,7 +14,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
final FieldBackendService _fieldBackendSvc; final FieldBackendService _fieldBackendSvc;
FieldCellBloc({ FieldCellBloc({
required FieldCellContext cellContext, required FieldContext cellContext,
}) : _fieldListener = SingleFieldListener(fieldId: cellContext.field.id), }) : _fieldListener = SingleFieldListener(fieldId: cellContext.field.id),
_fieldBackendSvc = FieldBackendService( _fieldBackendSvc = FieldBackendService(
viewId: cellContext.viewId, viewId: cellContext.viewId,
@ -83,8 +83,7 @@ class FieldCellState with _$FieldCellState {
required double width, required double width,
}) = _FieldCellState; }) = _FieldCellState;
factory FieldCellState.initial(FieldCellContext cellContext) => factory FieldCellState.initial(FieldContext cellContext) => FieldCellState(
FieldCellState(
viewId: cellContext.viewId, viewId: cellContext.viewId,
field: cellContext.field, field: cellContext.field,
width: cellContext.field.width.toDouble(), width: cellContext.field.width.toDouble(),

View File

@ -12,12 +12,20 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
final TypeOptionController dataController; final TypeOptionController dataController;
FieldEditorBloc({ FieldEditorBloc({
required String viewId,
required String fieldName,
required bool isGroupField, required bool isGroupField,
required ITypeOptionLoader loader, required FieldPB field,
}) : dataController = TypeOptionController(viewId: viewId, loader: loader), required FieldTypeOptionLoader loader,
super(FieldEditorState.initial(viewId, fieldName, isGroupField)) { }) : dataController = TypeOptionController(
field: field,
loader: loader,
),
super(
FieldEditorState.initial(
loader.viewId,
loader.field.name,
isGroupField,
),
) {
on<FieldEditorEvent>( on<FieldEditorEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
@ -27,7 +35,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
add(FieldEditorEvent.didReceiveFieldChanged(field)); add(FieldEditorEvent.didReceiveFieldChanged(field));
} }
}); });
await dataController.loadTypeOptionData(); await dataController.reloadTypeOption();
add(FieldEditorEvent.didReceiveFieldChanged(dataController.field)); add(FieldEditorEvent.didReceiveFieldChanged(dataController.field));
}, },
updateName: (name) { updateName: (name) {
@ -50,7 +58,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
() => null, () => null,
(field) { (field) {
final fieldService = FieldBackendService( final fieldService = FieldBackendService(
viewId: viewId, viewId: loader.viewId,
fieldId: field.id, fieldId: field.id,
); );
fieldService.deleteField(); fieldService.deleteField();

View File

@ -107,8 +107,8 @@ class FieldBackendService {
} }
@freezed @freezed
class FieldCellContext with _$FieldCellContext { class FieldContext with _$FieldContext {
const factory FieldCellContext({ const factory FieldContext({
required String viewId, required String viewId,
required FieldPB field, required FieldPB field,
}) = _FieldCellContext; }) = _FieldCellContext;

View File

@ -113,7 +113,7 @@ class TypeOptionContext<T extends GeneratedMessage> {
required TypeOptionController dataController, required TypeOptionController dataController,
}) : _dataController = dataController; }) : _dataController = dataController;
String get viewId => _dataController.viewId; String get viewId => _dataController.loader.viewId;
String get fieldId => _dataController.field.id; String get fieldId => _dataController.field.id;
@ -121,7 +121,7 @@ class TypeOptionContext<T extends GeneratedMessage> {
void Function(T)? onCompleted, void Function(T)? onCompleted,
required void Function(FlowyError) onError, required void Function(FlowyError) onError,
}) async { }) async {
await _dataController.loadTypeOptionData().then((result) { await _dataController.reloadTypeOption().then((result) {
result.fold((l) => null, (err) => onError(err)); result.fold((l) => null, (err) => onError(err));
}); });
@ -153,54 +153,12 @@ abstract class TypeOptionFieldDelegate {
abstract class ITypeOptionLoader { abstract class ITypeOptionLoader {
String get viewId; String get viewId;
String get fieldName; String get fieldName;
Future<Either<TypeOptionPB, FlowyError>> initialize(); Future<Either<TypeOptionPB, FlowyError>> initialize();
} }
/// Uses when creating a new field
class NewFieldTypeOptionLoader extends ITypeOptionLoader {
TypeOptionPB? fieldTypeOption;
@override
final String viewId;
NewFieldTypeOptionLoader({
required this.viewId,
});
/// Creates the field type option if the fieldTypeOption is null.
/// Otherwise, it loads the type option data from the backend.
@override
Future<Either<TypeOptionPB, FlowyError>> initialize() {
if (fieldTypeOption != null) {
final payload = TypeOptionPathPB.create()
..viewId = viewId
..fieldId = fieldTypeOption!.field_2.id
..fieldType = fieldTypeOption!.field_2.fieldType;
return DatabaseEventGetTypeOption(payload).send();
} else {
final payload = CreateFieldPayloadPB.create()
..viewId = viewId
..fieldType = FieldType.RichText;
return DatabaseEventCreateTypeOption(payload).send().then((result) {
return result.fold(
(newFieldTypeOption) {
fieldTypeOption = newFieldTypeOption;
return left(newFieldTypeOption);
},
(err) => right(err),
);
});
}
}
@override
String get fieldName => fieldTypeOption?.field_2.name ?? '';
}
/// Uses when editing a existing field /// Uses when editing a existing field
class FieldTypeOptionLoader extends ITypeOptionLoader { class FieldTypeOptionLoader {
@override
final String viewId; final String viewId;
final FieldPB field; final FieldPB field;
@ -209,8 +167,7 @@ class FieldTypeOptionLoader extends ITypeOptionLoader {
required this.field, required this.field,
}); });
@override Future<Either<TypeOptionPB, FlowyError>> load() {
Future<Either<TypeOptionPB, FlowyError>> initialize() {
final payload = TypeOptionPathPB.create() final payload = TypeOptionPathPB.create()
..viewId = viewId ..viewId = viewId
..fieldId = field.id ..fieldId = field.id
@ -218,7 +175,4 @@ class FieldTypeOptionLoader extends ITypeOptionLoader {
return DatabaseEventGetTypeOption(payload).send(); return DatabaseEventGetTypeOption(payload).send();
} }
@override
String get fieldName => field.name;
} }

View File

@ -11,31 +11,27 @@ import '../field_service.dart';
import 'type_option_context.dart'; import 'type_option_context.dart';
class TypeOptionController { class TypeOptionController {
final String viewId;
late TypeOptionPB _typeOption; late TypeOptionPB _typeOption;
final ITypeOptionLoader loader; final FieldTypeOptionLoader loader;
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier(); final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
/// Returns a [TypeOptionController] used to modify the specified /// Returns a [TypeOptionController] used to modify the specified
/// [FieldPB]'s data /// [FieldPB]'s data
/// ///
/// Should call [loadTypeOptionData] if the passed-in [FieldInfo] /// Should call [reloadTypeOption] if the passed-in [FieldInfo]
/// is null /// is null
/// ///
TypeOptionController({ TypeOptionController({
required this.viewId,
required this.loader, required this.loader,
FieldInfo? fieldInfo, required FieldPB field,
}) { }) {
if (fieldInfo != null) { _typeOption = TypeOptionPB.create()
_typeOption = TypeOptionPB.create() ..viewId = loader.viewId
..viewId = viewId ..field_2 = field;
..field_2 = fieldInfo.field;
}
} }
Future<Either<TypeOptionPB, FlowyError>> loadTypeOptionData() async { Future<Either<TypeOptionPB, FlowyError>> reloadTypeOption() async {
final result = await loader.initialize(); final result = await loader.load();
return result.fold( return result.fold(
(data) { (data) {
data.freeze(); data.freeze();
@ -67,7 +63,7 @@ class TypeOptionController {
_fieldNotifier.value = _typeOption.field_2; _fieldNotifier.value = _typeOption.field_2;
FieldBackendService(viewId: viewId, fieldId: field.id) FieldBackendService(viewId: loader.viewId, fieldId: field.id)
.updateField(name: name); .updateField(name: name);
} }
@ -79,7 +75,7 @@ class TypeOptionController {
}); });
FieldBackendService.updateFieldTypeOption( FieldBackendService.updateFieldTypeOption(
viewId: viewId, viewId: loader.viewId,
fieldId: field.id, fieldId: field.id,
typeOptionData: typeOptionData, typeOptionData: typeOptionData,
); );
@ -87,7 +83,7 @@ class TypeOptionController {
Future<void> switchToField(FieldType newFieldType) async { Future<void> switchToField(FieldType newFieldType) async {
final payload = UpdateFieldTypePayloadPB.create() final payload = UpdateFieldTypePayloadPB.create()
..viewId = viewId ..viewId = loader.viewId
..fieldId = field.id ..fieldId = field.id
..fieldType = newFieldType; ..fieldType = newFieldType;
@ -97,7 +93,7 @@ class TypeOptionController {
// Should load the type-option data after switching to a new field. // Should load the type-option data after switching to a new field.
// After loading the type-option data, the editor widget that uses // After loading the type-option data, the editor widget that uses
// the type-option data will be rebuild. // the type-option data will be rebuild.
loadTypeOptionData(); reloadTypeOption();
}, },
(err) => Future(() => Log.error(err)), (err) => Future(() => Log.error(err)),
); );

View File

@ -1,8 +1,7 @@
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/cell_entities.pb.dart';
class TypeOptionBackendService { class TypeOptionBackendService {
final String viewId; final String viewId;
@ -23,4 +22,15 @@ class TypeOptionBackendService {
return DatabaseEventCreateSelectOption(payload).send(); return DatabaseEventCreateSelectOption(payload).send();
} }
static Future<Either<TypeOptionPB, FlowyError>> createFieldTypeOption({
required String viewId,
FieldType fieldType = FieldType.RichText,
}) {
final payload = CreateFieldPayloadPB.create()
..viewId = viewId
..fieldType = FieldType.RichText;
return DatabaseEventCreateTypeOption(payload).send();
}
} }

View File

@ -185,7 +185,7 @@ class RowCache {
RowUpdateCallback addListener({ RowUpdateCallback addListener({
required RowId rowId, required RowId rowId,
void Function(CellByFieldId, RowsChangedReason)? onCellUpdated, void Function(CellContextByFieldId, RowsChangedReason)? onCellUpdated,
bool Function()? listenWhen, bool Function()? listenWhen,
}) { }) {
listenerHandler() async { listenerHandler() async {
@ -197,7 +197,7 @@ class RowCache {
if (onCellUpdated != null) { if (onCellUpdated != null) {
final rowInfo = _rowList.get(rowId); final rowInfo = _rowList.get(rowId);
if (rowInfo != null) { if (rowInfo != null) {
final CellByFieldId cellDataMap = final CellContextByFieldId cellDataMap =
_makeGridCells(rowId, rowInfo.rowPB); _makeGridCells(rowId, rowInfo.rowPB);
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason); onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
} }
@ -220,7 +220,7 @@ class RowCache {
_rowChangeReasonNotifier.removeListener(callback); _rowChangeReasonNotifier.removeListener(callback);
} }
CellByFieldId loadGridCells(RowId rowId) { CellContextByFieldId loadGridCells(RowId rowId) {
final RowPB? data = _rowList.get(rowId)?.rowPB; final RowPB? data = _rowList.get(rowId)?.rowPB;
if (data == null) { if (data == null) {
_loadRow(rowId); _loadRow(rowId);
@ -240,12 +240,12 @@ class RowCache {
); );
} }
CellByFieldId _makeGridCells(RowId rowId, RowPB? row) { CellContextByFieldId _makeGridCells(RowId rowId, RowPB? row) {
// ignore: prefer_collection_literals // ignore: prefer_collection_literals
var cellDataMap = CellByFieldId(); var cellDataMap = CellContextByFieldId();
for (final field in _delegate.fields) { for (final field in _delegate.fields) {
if (field.visibility) { if (field.visibility) {
cellDataMap[field.id] = CellIdentifier( cellDataMap[field.id] = DatabaseCellContext(
rowId: rowId, rowId: rowId,
viewId: viewId, viewId: viewId,
fieldInfo: field, fieldInfo: field,

View File

@ -3,7 +3,7 @@ import '../cell/cell_service.dart';
import 'row_cache.dart'; import 'row_cache.dart';
import 'row_service.dart'; import 'row_service.dart';
typedef OnRowChanged = void Function(CellByFieldId, RowsChangedReason); typedef OnRowChanged = void Function(CellContextByFieldId, RowsChangedReason);
class RowController { class RowController {
final RowId rowId; final RowId rowId;
@ -21,7 +21,7 @@ class RowController {
this.groupId, this.groupId,
}) : _rowCache = rowCache; }) : _rowCache = rowCache;
CellByFieldId loadData() { CellContextByFieldId loadData() {
return _rowCache.loadGridCells(rowId); return _rowCache.loadGridCells(rowId);
} }

View File

@ -70,7 +70,7 @@ class RowEvent with _$RowEvent {
const factory RowEvent.initial() = _InitialRow; const factory RowEvent.initial() = _InitialRow;
const factory RowEvent.createRow() = _CreateRow; const factory RowEvent.createRow() = _CreateRow;
const factory RowEvent.didReceiveCells( const factory RowEvent.didReceiveCells(
CellByFieldId cellsByFieldId, CellContextByFieldId cellsByFieldId,
RowsChangedReason reason, RowsChangedReason reason,
) = _DidReceiveCells; ) = _DidReceiveCells;
} }
@ -79,12 +79,15 @@ class RowEvent with _$RowEvent {
class RowState with _$RowState { class RowState with _$RowState {
const factory RowState({ const factory RowState({
required RowInfo rowInfo, required RowInfo rowInfo,
required CellByFieldId cellByFieldId, required CellContextByFieldId cellByFieldId,
required UnmodifiableListView<GridCellEquatable> cells, required UnmodifiableListView<GridCellEquatable> cells,
RowsChangedReason? changeReason, RowsChangedReason? changeReason,
}) = _RowState; }) = _RowState;
factory RowState.initial(RowInfo rowInfo, CellByFieldId cellByFieldId) => factory RowState.initial(
RowInfo rowInfo,
CellContextByFieldId cellByFieldId,
) =>
RowState( RowState(
rowInfo: rowInfo, rowInfo: rowInfo,
cellByFieldId: cellByFieldId, cellByFieldId: cellByFieldId,

View File

@ -88,14 +88,14 @@ class RowDetailEvent with _$RowDetailEvent {
const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) = const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) =
_DuplicateRow; _DuplicateRow;
const factory RowDetailEvent.didReceiveCellDatas( const factory RowDetailEvent.didReceiveCellDatas(
List<CellIdentifier> gridCells, List<DatabaseCellContext> gridCells,
) = _DidReceiveCellDatas; ) = _DidReceiveCellDatas;
} }
@freezed @freezed
class RowDetailState with _$RowDetailState { class RowDetailState with _$RowDetailState {
const factory RowDetailState({ const factory RowDetailState({
required List<CellIdentifier> gridCells, required List<DatabaseCellContext> gridCells,
}) = _RowDetailState; }) = _RowDetailState;
factory RowDetailState.initial() => RowDetailState( factory RowDetailState.initial() => RowDetailState(

View File

@ -14,7 +14,7 @@ import 'field_cell_action_sheet.dart';
import 'field_type_extension.dart'; import 'field_type_extension.dart';
class GridFieldCell extends StatefulWidget { class GridFieldCell extends StatefulWidget {
final FieldCellContext cellContext; final FieldContext cellContext;
const GridFieldCell({ const GridFieldCell({
Key? key, Key? key,
required this.cellContext, required this.cellContext,

View File

@ -19,7 +19,7 @@ import '../../layout/sizes.dart';
import 'field_editor.dart'; import 'field_editor.dart';
class GridFieldCellActionSheet extends StatefulWidget { class GridFieldCellActionSheet extends StatefulWidget {
final FieldCellContext cellContext; final FieldContext cellContext;
const GridFieldCellActionSheet({required this.cellContext, Key? key}) const GridFieldCellActionSheet({required this.cellContext, Key? key})
: super(key: key); : super(key: key);
@ -69,7 +69,7 @@ class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
} }
class _EditFieldButton extends StatelessWidget { class _EditFieldButton extends StatelessWidget {
final FieldCellContext cellContext; final FieldContext cellContext;
final void Function()? onTap; final void Function()? onTap;
const _EditFieldButton({required this.cellContext, Key? key, this.onTap}) const _EditFieldButton({required this.cellContext, Key? key, this.onTap})
: super(key: key); : super(key: key);
@ -95,7 +95,7 @@ class _EditFieldButton extends StatelessWidget {
} }
class _FieldOperationList extends StatelessWidget { class _FieldOperationList extends StatelessWidget {
final FieldCellContext fieldInfo; final FieldContext fieldInfo;
const _FieldOperationList(this.fieldInfo, {Key? key}) : super(key: key); const _FieldOperationList(this.fieldInfo, {Key? key}) : super(key: key);
@override @override
@ -138,7 +138,7 @@ class _FieldOperationList extends StatelessWidget {
} }
class FieldActionCell extends StatelessWidget { class FieldActionCell extends StatelessWidget {
final FieldCellContext fieldInfo; final FieldContext fieldInfo;
final FieldAction action; final FieldAction action;
final bool enable; final bool enable;
@ -203,7 +203,7 @@ extension _FieldActionExtension on FieldAction {
} }
} }
void run(BuildContext context, FieldCellContext fieldInfo) { void run(BuildContext context, FieldContext fieldInfo) {
switch (this) { switch (this) {
case FieldAction.hide: case FieldAction.hide:
context context

View File

@ -19,7 +19,7 @@ class FieldEditor extends StatefulWidget {
final bool isGroupingField; final bool isGroupingField;
final Function(String)? onDeleted; final Function(String)? onDeleted;
final Function(String)? onHidden; final Function(String)? onHidden;
final ITypeOptionLoader typeOptionLoader; final FieldTypeOptionLoader typeOptionLoader;
const FieldEditor({ const FieldEditor({
required this.viewId, required this.viewId,
@ -53,22 +53,25 @@ class _FieldEditorState extends State<FieldEditor> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> children = [ List<Widget> children = [
_FieldNameTextField(popoverMutex: popoverMutex), _FieldNameTextField(popoverMutex: popoverMutex),
const VSpace(10),
if (widget.onDeleted != null) _addDeleteFieldButton(), if (widget.onDeleted != null) _addDeleteFieldButton(),
if (widget.onHidden != null) _addHideFieldButton(), if (widget.onHidden != null) _addHideFieldButton(),
_FieldTypeOptionCell(popoverMutex: popoverMutex), if (!widget.typeOptionLoader.field.isPrimary)
_FieldTypeOptionCell(popoverMutex: popoverMutex),
]; ];
return BlocProvider( return BlocProvider(
create: (context) => FieldEditorBloc( create: (context) {
viewId: widget.viewId, return FieldEditorBloc(
fieldName: widget.typeOptionLoader.fieldName, isGroupField: widget.isGroupingField,
isGroupField: widget.isGroupingField, loader: widget.typeOptionLoader,
loader: widget.typeOptionLoader, field: widget.typeOptionLoader.field,
)..add(const FieldEditorEvent.initial()), )..add(const FieldEditorEvent.initial());
child: ListView.builder( },
child: ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: children.length, itemCount: children.length,
itemBuilder: (context, index) => children[index], itemBuilder: (context, index) => children[index],
separatorBuilder: (context, index) =>
VSpace(GridSize.typeOptionSeparatorHeight),
padding: const EdgeInsets.symmetric(vertical: 12.0), padding: const EdgeInsets.symmetric(vertical: 12.0),
), ),
); );

View File

@ -4,6 +4,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
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_context.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/log.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
@ -13,6 +14,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:reorderables/reorderables.dart'; import 'package:reorderables/reorderables.dart';
import '../../../../application/field/type_option/type_option_service.dart';
import '../../layout/sizes.dart'; import '../../layout/sizes.dart';
import 'field_editor.dart'; import 'field_editor.dart';
import 'field_cell.dart'; import 'field_cell.dart';
@ -101,8 +103,10 @@ class _GridHeaderState extends State<_GridHeader> {
final cells = state.fields final cells = state.fields
.where((field) => field.visibility) .where((field) => field.visibility)
.map( .map(
(field) => (field) => FieldContext(
FieldCellContext(viewId: widget.viewId, field: field.field), viewId: widget.viewId,
field: field.field,
),
) )
.map( .map(
(ctx) => GridFieldCell( (ctx) => GridFieldCell(
@ -177,28 +181,52 @@ class _CellTrailing extends StatelessWidget {
} }
} }
class CreateFieldButton extends StatelessWidget { class CreateFieldButton extends StatefulWidget {
final String viewId; final String viewId;
const CreateFieldButton({required this.viewId, Key? key}) : super(key: key); const CreateFieldButton({required this.viewId, Key? key}) : super(key: key);
@override
State<CreateFieldButton> createState() => _CreateFieldButtonState();
}
class _CreateFieldButtonState extends State<CreateFieldButton> {
final popoverController = PopoverController();
late TypeOptionPB typeOption;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppFlowyPopover( return AppFlowyPopover(
controller: popoverController,
direction: PopoverDirection.bottomWithRightAligned, direction: PopoverDirection.bottomWithRightAligned,
asBarrier: true, asBarrier: true,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
constraints: BoxConstraints.loose(const Size(240, 600)), constraints: BoxConstraints.loose(const Size(240, 600)),
triggerActions: PopoverTriggerFlags.none,
child: FlowyButton( child: FlowyButton(
radius: BorderRadius.zero, radius: BorderRadius.zero,
text: FlowyText.medium(LocaleKeys.grid_field_newProperty.tr()), text: FlowyText.medium(LocaleKeys.grid_field_newProperty.tr()),
hoverColor: AFThemeExtension.of(context).greyHover, hoverColor: AFThemeExtension.of(context).greyHover,
onTap: () {}, onTap: () async {
final result = await TypeOptionBackendService.createFieldTypeOption(
viewId: widget.viewId,
);
result.fold(
(l) {
typeOption = l;
popoverController.show();
},
(r) => Log.error("Failed to create field type option: $r"),
);
},
leftIcon: const FlowySvg(name: 'home/add'), leftIcon: const FlowySvg(name: 'home/add'),
), ),
popupBuilder: (BuildContext popover) { popupBuilder: (BuildContext popover) {
return FieldEditor( return FieldEditor(
viewId: viewId, viewId: widget.viewId,
typeOptionLoader: NewFieldTypeOptionLoader(viewId: viewId), typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.viewId,
field: typeOption.field_2,
),
); );
}, },
); );

View File

@ -60,7 +60,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
required TypeOptionController dataController, required TypeOptionController dataController,
required PopoverMutex popoverMutex, required PopoverMutex popoverMutex,
}) { }) {
final viewId = dataController.viewId; final viewId = dataController.loader.viewId;
final fieldType = dataController.field.fieldType; final fieldType = dataController.field.fieldType;
switch (dataController.field.fieldType) { switch (dataController.field.fieldType) {
@ -146,9 +146,8 @@ TypeOptionContext<T> makeTypeOptionContext<T extends GeneratedMessage>({
}) { }) {
final loader = FieldTypeOptionLoader(viewId: viewId, field: fieldInfo.field); final loader = FieldTypeOptionLoader(viewId: viewId, field: fieldInfo.field);
final dataController = TypeOptionController( final dataController = TypeOptionController(
viewId: viewId,
loader: loader, loader: loader,
fieldInfo: fieldInfo, field: fieldInfo.field,
); );
return makeTypeOptionContextWithDataController( return makeTypeOptionContextWithDataController(
viewId: viewId, viewId: viewId,
@ -180,8 +179,8 @@ TypeOptionContext<T> makeSelectTypeOptionContext<T extends GeneratedMessage>({
field: fieldPB, field: fieldPB,
); );
final dataController = TypeOptionController( final dataController = TypeOptionController(
viewId: viewId,
loader: loader, loader: loader,
field: fieldPB,
); );
final typeOptionContext = makeTypeOptionContextWithDataController<T>( final typeOptionContext = makeTypeOptionContextWithDataController<T>(
viewId: viewId, viewId: viewId,

View File

@ -255,7 +255,10 @@ class RowContent extends StatelessWidget {
); );
} }
List<Widget> _makeCells(BuildContext context, CellByFieldId cellByFieldId) { List<Widget> _makeCells(
BuildContext context,
CellContextByFieldId cellByFieldId,
) {
return cellByFieldId.values.map( return cellByFieldId.values.map(
(cellId) { (cellId) {
final GridCellWidget child = builder.build(cellId); final GridCellWidget child = builder.build(cellId);

View File

@ -194,7 +194,7 @@ class _RowCardState<T> extends State<RowCard<T>> {
class _CardContent<CustomCardData> extends StatelessWidget { class _CardContent<CustomCardData> extends StatelessWidget {
final CardCellBuilder<CustomCardData> cellBuilder; final CardCellBuilder<CustomCardData> cellBuilder;
final EditableRowNotifier rowNotifier; final EditableRowNotifier rowNotifier;
final List<CellIdentifier> cells; final List<DatabaseCellContext> cells;
final RowCardRenderHook<CustomCardData>? renderHook; final RowCardRenderHook<CustomCardData>? renderHook;
final CustomCardData? cardData; final CustomCardData? cardData;
final RowCardStyleConfiguration styleConfiguration; final RowCardStyleConfiguration styleConfiguration;
@ -233,28 +233,28 @@ class _CardContent<CustomCardData> extends StatelessWidget {
List<Widget> _makeCells( List<Widget> _makeCells(
BuildContext context, BuildContext context,
List<CellIdentifier> cells, List<DatabaseCellContext> cells,
) { ) {
final List<Widget> children = []; final List<Widget> children = [];
// Remove all the cell listeners. // Remove all the cell listeners.
rowNotifier.unbind(); rowNotifier.unbind();
cells.asMap().forEach( cells.asMap().forEach(
(int index, CellIdentifier cell) { (int index, DatabaseCellContext cellContext) {
final isEditing = index == 0 ? rowNotifier.isEditing.value : false; final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
final cellNotifier = EditableCardNotifier(isEditing: isEditing); final cellNotifier = EditableCardNotifier(isEditing: isEditing);
if (index == 0) { if (index == 0) {
// Only use the first cell to receive user's input when click the edit // Only use the first cell to receive user's input when click the edit
// button // button
rowNotifier.bindCell(cell, cellNotifier); rowNotifier.bindCell(cellContext, cellNotifier);
} }
final child = Padding( final child = Padding(
key: cell.key(), key: cellContext.key(),
padding: styleConfiguration.cellPadding, padding: styleConfiguration.cellPadding,
child: cellBuilder.buildCell( child: cellBuilder.buildCell(
cellId: cell, cellContext: cellContext,
cellNotifier: cellNotifier, cellNotifier: cellNotifier,
renderHook: renderHook, renderHook: renderHook,
cardData: cardData, cardData: cardData,

View File

@ -87,11 +87,11 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
} }
} }
List<CellIdentifier> _makeCells( List<DatabaseCellContext> _makeCells(
String? groupFieldId, String? groupFieldId,
CellByFieldId originalCellMap, CellContextByFieldId originalCellMap,
) { ) {
List<CellIdentifier> cells = []; List<DatabaseCellContext> cells = [];
for (final entry in originalCellMap.entries) { for (final entry in originalCellMap.entries) {
// Filter out the cell if it's fieldId equal to the groupFieldId // Filter out the cell if it's fieldId equal to the groupFieldId
if (groupFieldId != null) { if (groupFieldId != null) {
@ -110,7 +110,7 @@ class RowCardEvent with _$RowCardEvent {
const factory RowCardEvent.initial() = _InitialRow; const factory RowCardEvent.initial() = _InitialRow;
const factory RowCardEvent.setIsEditing(bool isEditing) = _IsEditing; const factory RowCardEvent.setIsEditing(bool isEditing) = _IsEditing;
const factory RowCardEvent.didReceiveCells( const factory RowCardEvent.didReceiveCells(
List<CellIdentifier> cells, List<DatabaseCellContext> cells,
RowsChangedReason reason, RowsChangedReason reason,
) = _DidReceiveCells; ) = _DidReceiveCells;
} }
@ -119,14 +119,14 @@ class RowCardEvent with _$RowCardEvent {
class RowCardState with _$RowCardState { class RowCardState with _$RowCardState {
const factory RowCardState({ const factory RowCardState({
required RowPB rowPB, required RowPB rowPB,
required List<CellIdentifier> cells, required List<DatabaseCellContext> cells,
required bool isEditing, required bool isEditing,
RowsChangedReason? changeReason, RowsChangedReason? changeReason,
}) = _RowCardState; }) = _RowCardState;
factory RowCardState.initial( factory RowCardState.initial(
RowPB rowPB, RowPB rowPB,
List<CellIdentifier> cells, List<DatabaseCellContext> cells,
bool isEditing, bool isEditing,
) => ) =>
RowCardState( RowCardState(

View File

@ -21,18 +21,18 @@ class CardCellBuilder<CustomCardData> {
Widget buildCell({ Widget buildCell({
CustomCardData? cardData, CustomCardData? cardData,
required CellIdentifier cellId, required DatabaseCellContext cellContext,
EditableCardNotifier? cellNotifier, EditableCardNotifier? cellNotifier,
RowCardRenderHook<CustomCardData>? renderHook, RowCardRenderHook<CustomCardData>? renderHook,
}) { }) {
final cellControllerBuilder = CellControllerBuilder( final cellControllerBuilder = CellControllerBuilder(
cellId: cellId, cellContext: cellContext,
cellCache: cellCache, cellCache: cellCache,
); );
final key = cellId.key(); final key = cellContext.key();
final style = styles?[cellId.fieldType]; final style = styles?[cellContext.fieldType];
switch (cellId.fieldType) { switch (cellContext.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
return CheckboxCardCell( return CheckboxCardCell(
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,

View File

@ -106,7 +106,7 @@ class EditableRowNotifier {
: isEditing = ValueNotifier(isEditing); : isEditing = ValueNotifier(isEditing);
void bindCell( void bindCell(
CellIdentifier cellIdentifier, DatabaseCellContext cellIdentifier,
EditableCardNotifier notifier, EditableCardNotifier notifier,
) { ) {
assert( assert(
@ -171,7 +171,8 @@ class EditableCellId {
EditableCellId(this.rowId, this.fieldId); EditableCellId(this.rowId, this.fieldId);
factory EditableCellId.from(CellIdentifier cellIdentifier) => EditableCellId( factory EditableCellId.from(DatabaseCellContext cellIdentifier) =>
EditableCellId(
cellIdentifier.rowId, cellIdentifier.rowId,
cellIdentifier.fieldId, cellIdentifier.fieldId,
); );

View File

@ -20,14 +20,17 @@ class GridCellBuilder {
required this.cellCache, required this.cellCache,
}); });
GridCellWidget build(CellIdentifier cellId, {GridCellStyle? style}) { GridCellWidget build(
DatabaseCellContext cellContext, {
GridCellStyle? style,
}) {
final cellControllerBuilder = CellControllerBuilder( final cellControllerBuilder = CellControllerBuilder(
cellId: cellId, cellContext: cellContext,
cellCache: cellCache, cellCache: cellCache,
); );
final key = cellId.key(); final key = cellContext.key();
switch (cellId.fieldType) { switch (cellContext.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
return GridCheckboxCell( return GridCheckboxCell(
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,

View File

@ -15,8 +15,9 @@ class ChecklistCardCellBloc
void Function()? _onCellChangedFn; void Function()? _onCellChangedFn;
ChecklistCardCellBloc({ ChecklistCardCellBloc({
required this.cellController, required this.cellController,
}) : _checklistCellSvc = }) : _checklistCellSvc = ChecklistCellBackendService(
ChecklistCellBackendService(cellId: cellController.cellId), cellContext: cellController.cellContext,
),
super(ChecklistCellState.initial(cellController)) { super(ChecklistCellState.initial(cellController)) {
on<ChecklistCellEvent>( on<ChecklistCellEvent>(
(event, emit) async { (event, emit) async {

View File

@ -18,8 +18,9 @@ class ChecklistCellEditorBloc
ChecklistCellEditorBloc({ ChecklistCellEditorBloc({
required this.cellController, required this.cellController,
}) : _checklistCellService = }) : _checklistCellService = ChecklistCellBackendService(
ChecklistCellBackendService(cellId: cellController.cellId), cellContext: cellController.cellContext,
),
super(ChecklistCellEditorState.initial(cellController)) { super(ChecklistCellEditorState.initial(cellController)) {
on<ChecklistCellEditorEvent>( on<ChecklistCellEditorEvent>(
(event, emit) async { (event, emit) async {

View File

@ -16,8 +16,9 @@ class SelectOptionCellEditorBloc
SelectOptionCellEditorBloc({ SelectOptionCellEditorBloc({
required this.cellController, required this.cellController,
}) : _selectOptionService = }) : _selectOptionService = SelectOptionCellBackendService(
SelectOptionCellBackendService(cellId: cellController.cellId), cellContext: cellController.cellContext,
),
super(SelectOptionEditorState.initial(cellController)) { super(SelectOptionEditorState.initial(cellController)) {
on<SelectOptionEditorEvent>( on<SelectOptionEditorEvent>(
(event, emit) async { (event, emit) async {

View File

@ -1,8 +1,10 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.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_context.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
@ -134,7 +136,7 @@ class _PropertyColumn extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_RowTitle( _RowTitle(
cellId: state.gridCells cellContext: state.gridCells
.firstWhereOrNull((e) => e.fieldInfo.isPrimary), .firstWhereOrNull((e) => e.fieldInfo.isPrimary),
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
), ),
@ -145,7 +147,7 @@ class _PropertyColumn extends StatelessWidget {
(cell) => Padding( (cell) => Padding(
padding: const EdgeInsets.only(bottom: 4.0), padding: const EdgeInsets.only(bottom: 4.0),
child: _PropertyCell( child: _PropertyCell(
cellId: cell, cellContext: cell,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
), ),
), ),
@ -161,14 +163,14 @@ class _PropertyColumn extends StatelessWidget {
} }
class _RowTitle extends StatelessWidget { class _RowTitle extends StatelessWidget {
final CellIdentifier? cellId; final DatabaseCellContext? cellContext;
final GridCellBuilder cellBuilder; final GridCellBuilder cellBuilder;
const _RowTitle({this.cellId, required this.cellBuilder, Key? key}) const _RowTitle({this.cellContext, required this.cellBuilder, Key? key})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (cellId == null) { if (cellContext == null) {
return const SizedBox(); return const SizedBox();
} }
final style = GridTextCellStyle( final style = GridTextCellStyle(
@ -176,7 +178,7 @@ class _RowTitle extends StatelessWidget {
textStyle: Theme.of(context).textTheme.titleLarge, textStyle: Theme.of(context).textTheme.titleLarge,
autofocus: true, autofocus: true,
); );
return cellBuilder.build(cellId!, style: style); return cellBuilder.build(cellContext!, style: style);
} }
} }
@ -194,6 +196,7 @@ class _CreatePropertyButton extends StatefulWidget {
class _CreatePropertyButtonState extends State<_CreatePropertyButton> { class _CreatePropertyButtonState extends State<_CreatePropertyButton> {
late PopoverController popoverController; late PopoverController popoverController;
late TypeOptionPB typeOption;
@override @override
void initState() { void initState() {
@ -207,6 +210,7 @@ class _CreatePropertyButtonState extends State<_CreatePropertyButton> {
constraints: BoxConstraints.loose(const Size(240, 200)), constraints: BoxConstraints.loose(const Size(240, 200)),
controller: popoverController, controller: popoverController,
direction: PopoverDirection.topWithLeftAligned, direction: PopoverDirection.topWithLeftAligned,
triggerActions: PopoverTriggerFlags.none,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: SizedBox( child: SizedBox(
height: 40, height: 40,
@ -216,7 +220,18 @@ class _CreatePropertyButtonState extends State<_CreatePropertyButton> {
color: AFThemeExtension.of(context).textColor, color: AFThemeExtension.of(context).textColor,
), ),
hoverColor: AFThemeExtension.of(context).lightGreyHover, hoverColor: AFThemeExtension.of(context).lightGreyHover,
onTap: () {}, onTap: () async {
final result = await TypeOptionBackendService.createFieldTypeOption(
viewId: widget.viewId,
);
result.fold(
(l) {
typeOption = l;
popoverController.show();
},
(r) => Log.error("Failed to create field type option: $r"),
);
},
leftIcon: svgWidget( leftIcon: svgWidget(
"home/add", "home/add",
color: AFThemeExtension.of(context).textColor, color: AFThemeExtension.of(context).textColor,
@ -226,7 +241,10 @@ class _CreatePropertyButtonState extends State<_CreatePropertyButton> {
popupBuilder: (BuildContext popOverContext) { popupBuilder: (BuildContext popOverContext) {
return FieldEditor( return FieldEditor(
viewId: widget.viewId, viewId: widget.viewId,
typeOptionLoader: NewFieldTypeOptionLoader(viewId: widget.viewId), typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.viewId,
field: typeOption.field_2,
),
onDeleted: (fieldId) { onDeleted: (fieldId) {
popoverController.close(); popoverController.close();
NavigatorAlertDialog( NavigatorAlertDialog(
@ -245,10 +263,10 @@ class _CreatePropertyButtonState extends State<_CreatePropertyButton> {
} }
class _PropertyCell extends StatefulWidget { class _PropertyCell extends StatefulWidget {
final CellIdentifier cellId; final DatabaseCellContext cellContext;
final GridCellBuilder cellBuilder; final GridCellBuilder cellBuilder;
const _PropertyCell({ const _PropertyCell({
required this.cellId, required this.cellContext,
required this.cellBuilder, required this.cellBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -262,8 +280,8 @@ class _PropertyCellState extends State<_PropertyCell> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final style = _customCellStyle(widget.cellId.fieldType); final style = _customCellStyle(widget.cellContext.fieldType);
final cell = widget.cellBuilder.build(widget.cellId, style: style); final cell = widget.cellBuilder.build(widget.cellContext, style: style);
final gesture = GestureDetector( final gesture = GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
@ -290,7 +308,7 @@ class _PropertyCellState extends State<_PropertyCell> {
child: SizedBox( child: SizedBox(
width: 150, width: 150,
child: FieldCellButton( child: FieldCellButton(
field: widget.cellId.fieldInfo.field, field: widget.cellContext.fieldInfo.field,
onTap: () => popover.show(), onTap: () => popover.show(),
radius: BorderRadius.circular(6), radius: BorderRadius.circular(6),
), ),
@ -306,11 +324,11 @@ class _PropertyCellState extends State<_PropertyCell> {
Widget buildFieldEditor() { Widget buildFieldEditor() {
return FieldEditor( return FieldEditor(
viewId: widget.cellId.viewId, viewId: widget.cellContext.viewId,
isGroupingField: widget.cellId.fieldInfo.isGroupField, isGroupingField: widget.cellContext.fieldInfo.isGroupField,
typeOptionLoader: FieldTypeOptionLoader( typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.cellId.viewId, viewId: widget.cellContext.viewId,
field: widget.cellId.fieldInfo.field, field: widget.cellContext.fieldInfo.field,
), ),
onHidden: (fieldId) { onHidden: (fieldId) {
popover.close(); popover.close();

View File

@ -171,7 +171,7 @@ void _resolveGridDeps(GetIt getIt) {
), ),
); );
getIt.registerFactoryParam<FieldActionSheetBloc, FieldCellContext, void>( getIt.registerFactoryParam<FieldActionSheetBloc, FieldContext, void>(
(data, _) => FieldActionSheetBloc(fieldCellContext: data), (data, _) => FieldActionSheetBloc(fieldCellContext: data),
); );

View File

@ -35,10 +35,9 @@ void main() {
); );
final editorBloc = FieldEditorBloc( final editorBloc = FieldEditorBloc(
viewId: context.gridView.id,
fieldName: fieldInfo.name,
isGroupField: fieldInfo.isGroupField, isGroupField: fieldInfo.isGroupField,
loader: loader, loader: loader,
field: fieldInfo.field,
)..add(const FieldEditorEvent.initial()); )..add(const FieldEditorEvent.initial());
await boardResponseFuture(); await boardResponseFuture();

View File

@ -15,7 +15,7 @@ void main() {
boardTest = await AppFlowyBoardTest.ensureInitialized(); boardTest = await AppFlowyBoardTest.ensureInitialized();
context = await boardTest.createTestBoard(); context = await boardTest.createTestBoard();
final fieldInfo = context.singleSelectFieldContext(); final fieldInfo = context.singleSelectFieldContext();
editorBloc = context.createFieldEditor( editorBloc = context.makeFieldEditor(
fieldInfo: fieldInfo, fieldInfo: fieldInfo,
)..add(const FieldEditorEvent.initial()); )..add(const FieldEditorEvent.initial());

View File

@ -76,22 +76,18 @@ class BoardTestContext {
return _boardDataController.fieldController; return _boardDataController.fieldController;
} }
FieldEditorBloc createFieldEditor({ FieldEditorBloc makeFieldEditor({
FieldInfo? fieldInfo, required FieldInfo fieldInfo,
}) { }) {
ITypeOptionLoader loader; final loader = FieldTypeOptionLoader(
if (fieldInfo == null) { viewId: gridView.id,
loader = NewFieldTypeOptionLoader(viewId: gridView.id); field: fieldInfo.field,
} else { );
loader =
FieldTypeOptionLoader(viewId: gridView.id, field: fieldInfo.field);
}
final editorBloc = FieldEditorBloc( final editorBloc = FieldEditorBloc(
fieldName: fieldInfo?.name ?? '', isGroupField: fieldInfo.isGroupField,
isGroupField: fieldInfo?.isGroupField ?? false,
loader: loader, loader: loader,
viewId: gridView.id, field: fieldInfo.field,
); );
return editorBloc; return editorBloc;
} }
@ -120,13 +116,13 @@ class BoardTestContext {
await gridResponseFuture(); await gridResponseFuture();
return CellControllerBuilder( return CellControllerBuilder(
cellId: rowBloc.state.cellByFieldId[fieldId]!, cellContext: rowBloc.state.cellByFieldId[fieldId]!,
cellCache: rowCache.cellCache, cellCache: rowCache.cellCache,
); );
} }
Future<FieldEditorBloc> createField(FieldType fieldType) async { Future<FieldEditorBloc> createField(FieldType fieldType) async {
final editorBloc = createFieldEditor() final editorBloc = await createFieldEditor(viewId: gridView.id)
..add(const FieldEditorEvent.initial()); ..add(const FieldEditorEvent.initial());
await gridResponseFuture(); await gridResponseFuture();
editorBloc.add(FieldEditorEvent.switchToField(fieldType)); editorBloc.add(FieldEditorEvent.switchToField(fieldType));
@ -140,9 +136,9 @@ class BoardTestContext {
return fieldInfo; return fieldInfo;
} }
FieldCellContext singleSelectFieldCellContext() { FieldContext singleSelectFieldCellContext() {
final field = singleSelectFieldContext().field; final field = singleSelectFieldContext().field;
return FieldCellContext(viewId: gridView.id, field: field); return FieldContext(viewId: gridView.id, field: field);
} }
FieldInfo textFieldContext() { FieldInfo textFieldContext() {

View File

@ -13,10 +13,9 @@ Future<FieldEditorBloc> createEditorBloc(AppFlowyGridTest gridTest) async {
); );
return FieldEditorBloc( return FieldEditorBloc(
viewId: context.gridView.id,
fieldName: fieldInfo.name,
isGroupField: fieldInfo.isGroupField, isGroupField: fieldInfo.isGroupField,
loader: loader, loader: loader,
field: fieldInfo.field,
)..add(const FieldEditorEvent.initial()); )..add(const FieldEditorEvent.initial());
} }
@ -83,10 +82,9 @@ Future<FieldEditorBloc> makeEditorBloc(AppFlowyGridTest gridTest) async {
); );
final editorBloc = FieldEditorBloc( final editorBloc = FieldEditorBloc(
viewId: context.gridView.id,
fieldName: fieldInfo.name,
isGroupField: fieldInfo.isGroupField, isGroupField: fieldInfo.isGroupField,
loader: loader, loader: loader,
field: fieldInfo.field,
)..add(const FieldEditorEvent.initial()); )..add(const FieldEditorEvent.initial());
await gridResponseFuture(); await gridResponseFuture();

View File

@ -41,10 +41,9 @@ void main() {
); );
final editorBloc = FieldEditorBloc( final editorBloc = FieldEditorBloc(
viewId: context.gridView.id,
fieldName: textField.field.name,
isGroupField: false, isGroupField: false,
loader: loader, loader: loader,
field: textField.field,
)..add(const FieldEditorEvent.initial()); )..add(const FieldEditorEvent.initial());
await gridResponseFuture(); await gridResponseFuture();

View File

@ -4,6 +4,7 @@ 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_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_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_context.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_service.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
@ -38,26 +39,6 @@ class GridTestContext {
return gridController.createRow(); return gridController.createRow();
} }
FieldEditorBloc createFieldEditor({
FieldInfo? fieldInfo,
}) {
ITypeOptionLoader loader;
if (fieldInfo == null) {
loader = NewFieldTypeOptionLoader(viewId: gridView.id);
} else {
loader =
FieldTypeOptionLoader(viewId: gridView.id, field: fieldInfo.field);
}
final editorBloc = FieldEditorBloc(
fieldName: fieldInfo?.name ?? '',
isGroupField: fieldInfo?.isGroupField ?? false,
loader: loader,
viewId: gridView.id,
);
return editorBloc;
}
Future<CellController> makeCellController( Future<CellController> makeCellController(
String fieldId, String fieldId,
int rowIndex, int rowIndex,
@ -86,13 +67,13 @@ class GridTestContext {
await gridResponseFuture(); await gridResponseFuture();
return CellControllerBuilder( return CellControllerBuilder(
cellId: rowBloc.state.cellByFieldId[fieldId]!, cellContext: rowBloc.state.cellByFieldId[fieldId]!,
cellCache: rowCache.cellCache, cellCache: rowCache.cellCache,
); );
} }
Future<FieldEditorBloc> createField(FieldType fieldType) async { Future<FieldEditorBloc> createField(FieldType fieldType) async {
final editorBloc = createFieldEditor() final editorBloc = await createFieldEditor(viewId: gridView.id)
..add(const FieldEditorEvent.initial()); ..add(const FieldEditorEvent.initial());
await gridResponseFuture(); await gridResponseFuture();
editorBloc.add(FieldEditorEvent.switchToField(fieldType)); editorBloc.add(FieldEditorEvent.switchToField(fieldType));
@ -106,9 +87,9 @@ class GridTestContext {
return fieldInfo; return fieldInfo;
} }
FieldCellContext singleSelectFieldCellContext() { FieldContext singleSelectFieldCellContext() {
final field = singleSelectFieldContext().field; final field = singleSelectFieldContext().field;
return FieldCellContext(viewId: gridView.id, field: field); return FieldContext(viewId: gridView.id, field: field);
} }
FieldInfo textFieldContext() { FieldInfo textFieldContext() {
@ -155,6 +136,28 @@ class GridTestContext {
} }
} }
Future<FieldEditorBloc> createFieldEditor({
required String viewId,
}) async {
final result = await TypeOptionBackendService.createFieldTypeOption(
viewId: viewId,
);
return result.fold(
(data) {
final loader = FieldTypeOptionLoader(
viewId: viewId,
field: data.field_2,
);
return FieldEditorBloc(
isGroupField: FieldInfo(field: data.field_2).isGroupField,
loader: loader,
field: data.field_2,
);
},
(err) => throw Exception(err),
);
}
/// Create a empty Grid for test /// Create a empty Grid for test
class AppFlowyGridTest { class AppFlowyGridTest {
final AppFlowyUnitTest unitTest; final AppFlowyUnitTest unitTest;

View File

@ -196,16 +196,27 @@ impl DatabaseEditor {
} }
pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> { pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
let is_primary = self
.database
.lock()
.fields
.get_field(&params.field_id)
.map(|field| field.is_primary)
.unwrap_or(false);
self self
.database .database
.lock() .lock()
.fields .fields
.update_field(&params.field_id, |update| { .update_field(&params.field_id, |mut update| {
update update = update
.set_name_if_not_none(params.name) .set_name_if_not_none(params.name)
.set_field_type_if_not_none(params.field_type.map(|field_type| field_type.into()))
.set_width_at_if_not_none(params.width.map(|value| value as i64)) .set_width_at_if_not_none(params.width.map(|value| value as i64))
.set_visibility_if_not_none(params.visibility); .set_visibility_if_not_none(params.visibility);
if is_primary {
tracing::warn!("Cannot update primary field type");
} else {
update.set_field_type_if_not_none(params.field_type.map(|field_type| field_type.into()));
}
}); });
self self
.notify_did_update_database_field(&params.field_id) .notify_did_update_database_field(&params.field_id)
@ -238,10 +249,15 @@ impl DatabaseEditor {
.lock() .lock()
.fields .fields
.update_field(field_id, |update| { .update_field(field_id, |update| {
update.update_type_options(|type_options_update| { if old_field.is_primary {
type_options_update.insert(&field_type.to_string(), type_option_data); tracing::warn!("Cannot update primary field type");
}); } else {
update.update_type_options(|type_options_update| {
type_options_update.insert(&field_type.to_string(), type_option_data);
});
}
}); });
self self
.database_views .database_views
.did_update_field_type_option(view_id, field_id, &old_field) .did_update_field_type_option(view_id, field_id, &old_field)

View File

@ -43,7 +43,7 @@ async fn update_at_field_test() {
.unwrap(); .unwrap();
let old_updated_at = DateCellData::from(&cell).timestamp.unwrap(); let old_updated_at = DateCellData::from(&cell).timestamp.unwrap();
tokio::time::sleep(Duration::from_millis(500)).await; tokio::time::sleep(Duration::from_millis(1000)).await;
test test
.run_script(UpdateTextCell { .run_script(UpdateTextCell {
row_id: row.id.clone(), row_id: row.id.clone(),