fix: create column errors

This commit is contained in:
appflowy 2022-08-09 18:04:23 +08:00
parent aaee3003ce
commit 292b90c14c
20 changed files with 501 additions and 197 deletions

View File

@ -1,17 +1,161 @@
// ignore_for_file: unused_field // ignore_for_file: unused_field
import 'package:appflowy_board/appflowy_board.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BoardPage extends StatelessWidget { class BoardPage extends StatefulWidget {
final ViewPB _view; final ViewPB _view;
const BoardPage({required ViewPB view, Key? key}) const BoardPage({required ViewPB view, Key? key})
: _view = view, : _view = view,
super(key: key); super(key: key);
@override
State<BoardPage> createState() => _BoardPageState();
}
class _BoardPageState extends State<BoardPage> {
final BoardDataController boardDataController = BoardDataController(
onMoveColumn: (fromIndex, toIndex) {
debugPrint('Move column from $fromIndex to $toIndex');
},
onMoveColumnItem: (columnId, fromIndex, toIndex) {
debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
},
onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
},
);
@override
void initState() {
final column1 = BoardColumnData(id: "To Do", items: [
TextItem("Card 1"),
TextItem("Card 2"),
RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
TextItem("Card 4"),
]);
final column2 = BoardColumnData(id: "In Progress", items: [
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
TextItem("Card 6"),
]);
final column3 = BoardColumnData(id: "Done", items: []);
boardDataController.addColumn(column1);
boardDataController.addColumn(column2);
boardDataController.addColumn(column3);
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container(); final config = BoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
);
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Board(
dataController: boardDataController,
footBuilder: (context, columnData) {
return AppFlowyColumnFooter(
icon: const Icon(Icons.add, size: 20),
title: const Text('New'),
height: 50,
margin: config.columnItemPadding,
);
},
headerBuilder: (context, columnData) {
return AppFlowyColumnHeader(
icon: const Icon(Icons.lightbulb_circle),
title: Text(columnData.id),
addIcon: const Icon(Icons.add, size: 20),
moreIcon: const Icon(Icons.more_horiz, size: 20),
height: 50,
margin: config.columnItemPadding,
);
},
cardBuilder: (context, item) {
return AppFlowyColumnItemCard(
key: ObjectKey(item),
child: _buildCard(item),
);
},
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: BoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
),
),
);
}
Widget _buildCard(ColumnItem item) {
if (item is TextItem) {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(item.s),
),
);
}
if (item is RichTextItem) {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title,
style: const TextStyle(fontSize: 14),
textAlign: TextAlign.left,
),
const SizedBox(height: 10),
Text(
item.subtitle,
style: const TextStyle(fontSize: 12, color: Colors.grey),
)
],
),
),
);
}
throw UnimplementedError();
}
}
class TextItem extends ColumnItem {
final String s;
TextItem(this.s);
@override
String get id => s;
}
class RichTextItem extends ColumnItem {
final String title;
final String subtitle;
RichTextItem({required this.title, required this.subtitle});
@override
String get id => title;
}
extension HexColor on Color {
static Color fromHex(String hexString) {
final buffer = StringBuffer();
if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
buffer.write(hexString.replaceFirst('#', ''));
return Color(int.parse(buffer.toString(), radix: 16));
} }
} }

View File

@ -11,7 +11,9 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
class DocumentBanner extends StatelessWidget { class DocumentBanner extends StatelessWidget {
final void Function() onRestore; final void Function() onRestore;
final void Function() onDelete; final void Function() onDelete;
const DocumentBanner({required this.onRestore, required this.onDelete, Key? key}) : super(key: key); const DocumentBanner(
{required this.onRestore, required this.onDelete, Key? key})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -26,7 +28,8 @@ class DocumentBanner extends StatelessWidget {
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Row( child: Row(
children: [ children: [
FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(), color: Colors.white), FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(),
color: Colors.white),
const HSpace(20), const HSpace(20),
BaseStyledButton( BaseStyledButton(
minWidth: 160, minWidth: 160,
@ -37,7 +40,10 @@ class DocumentBanner extends StatelessWidget {
downColor: theme.main1, downColor: theme.main1,
outlineColor: Colors.white, outlineColor: Colors.white,
borderRadius: Corners.s8Border, borderRadius: Corners.s8Border,
child: FlowyText.medium(LocaleKeys.deletePagePrompt_restore.tr(), color: Colors.white, fontSize: 14), child: FlowyText.medium(
LocaleKeys.deletePagePrompt_restore.tr(),
color: Colors.white,
fontSize: 14),
onPressed: onRestore), onPressed: onRestore),
const HSpace(20), const HSpace(20),
BaseStyledButton( BaseStyledButton(
@ -49,8 +55,10 @@ class DocumentBanner extends StatelessWidget {
downColor: theme.main1, downColor: theme.main1,
outlineColor: Colors.white, outlineColor: Colors.white,
borderRadius: Corners.s8Border, borderRadius: Corners.s8Border,
child: FlowyText.medium(LocaleKeys.deletePagePrompt_deletePermanent.tr(), child: FlowyText.medium(
color: Colors.white, fontSize: 14), LocaleKeys.deletePagePrompt_deletePermanent.tr(),
color: Colors.white,
fontSize: 14),
onPressed: onDelete), onPressed: onDelete),
], ],
), ),

View File

@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
import 'cell_service.dart'; import 'cell_service.dart';
abstract class GridFieldChangedNotifier { abstract class IGridFieldChangedNotifier {
void onFieldChanged(void Function(GridFieldPB) callback); void onFieldChanged(void Function(GridFieldPB) callback);
void dispose(); void dispose();
} }
@ -12,9 +12,10 @@ abstract class GridFieldChangedNotifier {
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen. /// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
class GridCellFieldNotifier { class GridCellFieldNotifier {
/// fieldId: {objectId: callback} /// fieldId: {objectId: callback}
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {}; final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId =
{};
GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) { GridCellFieldNotifier({required IGridFieldChangedNotifier notifier}) {
notifier.onFieldChanged( notifier.onFieldChanged(
(field) { (field) {
final map = _fieldListenerByFieldId[field.id]; final map = _fieldListenerByFieldId[field.id];

View File

@ -246,7 +246,7 @@ class IGridCellController<T, D> extends Equatable {
} }
/// Save the cell data to disk /// Save the cell data to disk
/// You can set [dedeplicate] to true (default is false) to reduce the save operation. /// You can set [deduplicate] to true (default is false) to reduce the save operation.
/// It's useful when you call this method when user editing the [TextField]. /// It's useful when you call this method when user editing the [TextField].
/// The default debounce interval is 300 milliseconds. /// The default debounce interval is 300 milliseconds.
void saveCellData(D data, void saveCellData(D data,
@ -304,7 +304,7 @@ class IGridCellController<T, D> extends Equatable {
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id]; [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
} }
class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier { class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier {
final GridFieldCache _cache; final GridFieldCache _cache;
FieldChangesetCallback? _onChangesetFn; FieldChangesetCallback? _onChangesetFn;

View File

@ -1,40 +1,23 @@
import 'dart:async'; import 'dart:async';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'block/block_cache.dart'; import 'block/block_cache.dart';
import 'grid_service.dart'; import 'grid_data_controller.dart';
import 'row/row_service.dart'; import 'row/row_service.dart';
import 'dart:collection'; import 'dart:collection';
part 'grid_bloc.freezed.dart'; part 'grid_bloc.freezed.dart';
class GridBloc extends Bloc<GridEvent, GridState> { class GridBloc extends Bloc<GridEvent, GridState> {
final String gridId; final GridDataController dataController;
final GridService _gridService;
final GridFieldCache fieldCache;
// key: the block id
final LinkedHashMap<String, GridBlockCache> _blocks;
List<GridRowInfo> get rowInfos {
final List<GridRowInfo> rows = [];
for (var block in _blocks.values) {
rows.addAll(block.rows);
}
return rows;
}
GridBloc({required ViewPB view}) GridBloc({required ViewPB view})
: gridId = view.id, : dataController = GridDataController(view: view),
_blocks = LinkedHashMap.identity(),
_gridService = GridService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id),
super(GridState.initial(view.id)) { super(GridState.initial(view.id)) {
on<GridEvent>( on<GridEvent>(
(event, emit) async { (event, emit) async {
@ -44,13 +27,21 @@ class GridBloc extends Bloc<GridEvent, GridState> {
await _loadGrid(emit); await _loadGrid(emit);
}, },
createRow: () { createRow: () {
_gridService.createRow(); dataController.createRow();
}, },
didReceiveRowUpdate: (newRowInfos, reason) { didReceiveGridUpdate: (grid) {
emit(state.copyWith(rowInfos: newRowInfos, reason: reason)); emit(state.copyWith(grid: Some(grid)));
}, },
didReceiveFieldUpdate: (fields) { didReceiveFieldUpdate: (fields) {
emit(state.copyWith(rowInfos: rowInfos, fields: GridFieldEquatable(fields))); emit(state.copyWith(
fields: GridFieldEquatable(fields),
));
},
didReceiveRowUpdate: (newRowInfos, reason) {
emit(state.copyWith(
rowInfos: newRowInfos,
reason: reason,
));
}, },
); );
}, },
@ -59,89 +50,63 @@ class GridBloc extends Bloc<GridEvent, GridState> {
@override @override
Future<void> close() async { Future<void> close() async {
await _gridService.closeGrid(); await dataController.dispose();
await fieldCache.dispose();
for (final blockCache in _blocks.values) {
blockCache.dispose();
}
return super.close(); return super.close();
} }
GridRowCache? getRowCache(String blockId, String rowId) { GridRowCache? getRowCache(String blockId, String rowId) {
final GridBlockCache? blockCache = _blocks[blockId]; final GridBlockCache? blockCache = dataController.blocks[blockId];
return blockCache?.rowCache; return blockCache?.rowCache;
} }
void _startListening() { void _startListening() {
fieldCache.addListener( dataController.addListener(
listenWhen: () => !isClosed, onGridChanged: (grid) {
onFields: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)), if (!isClosed) {
add(GridEvent.didReceiveGridUpdate(grid));
}
},
onRowsChanged: (rowInfos, reason) {
if (!isClosed) {
add(GridEvent.didReceiveRowUpdate(rowInfos, reason));
}
},
onFieldsChanged: (fields) {
if (!isClosed) {
add(GridEvent.didReceiveFieldUpdate(fields));
}
},
); );
} }
Future<void> _loadGrid(Emitter<GridState> emit) async { Future<void> _loadGrid(Emitter<GridState> emit) async {
final result = await _gridService.loadGrid(); final result = await dataController.loadData();
return Future( result.fold(
() => result.fold( (grid) => emit(
(grid) async { state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
_initialBlocks(grid.blocks); ),
await _loadFields(grid, emit); (err) => emit(
}, state.copyWith(loadingState: GridLoadingState.finish(right(err))),
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
), ),
); );
} }
Future<void> _loadFields(GridPB grid, Emitter<GridState> emit) async {
final result = await _gridService.getFields(fieldIds: grid.fields);
return Future(
() => result.fold(
(fields) {
fieldCache.fields = fields.items;
emit(state.copyWith(
grid: Some(grid),
fields: GridFieldEquatable(fieldCache.fields),
rowInfos: rowInfos,
loadingState: GridLoadingState.finish(left(unit)),
));
},
(err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
),
);
}
void _initialBlocks(List<GridBlockPB> blocks) {
for (final block in blocks) {
if (_blocks[block.id] != null) {
Log.warn("Intial duplicate block's cache: ${block.id}");
return;
}
final cache = GridBlockCache(
gridId: gridId,
block: block,
fieldCache: fieldCache,
);
cache.addListener(
listenWhen: () => !isClosed,
onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rowInfos, reason)),
);
_blocks[block.id] = cache;
}
}
} }
@freezed @freezed
class GridEvent with _$GridEvent { class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid; const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow; const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.didReceiveRowUpdate(List<GridRowInfo> rows, GridRowChangeReason listState) = const factory GridEvent.didReceiveRowUpdate(
_DidReceiveRowUpdate; List<GridRowInfo> rows,
const factory GridEvent.didReceiveFieldUpdate(List<GridFieldPB> fields) = _DidReceiveFieldUpdate; GridRowChangeReason listState,
) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(
UnmodifiableListView<GridFieldPB> fields,
) = _DidReceiveFieldUpdate;
const factory GridEvent.didReceiveGridUpdate(
GridPB grid,
) = _DidReceiveGridUpdate;
} }
@freezed @freezed
@ -156,7 +121,7 @@ class GridState with _$GridState {
}) = _GridState; }) = _GridState;
factory GridState.initial(String gridId) => GridState( factory GridState.initial(String gridId) => GridState(
fields: const GridFieldEquatable([]), fields: GridFieldEquatable(UnmodifiableListView([])),
rowInfos: [], rowInfos: [],
grid: none(), grid: none(),
gridId: gridId, gridId: gridId,
@ -168,18 +133,27 @@ class GridState with _$GridState {
@freezed @freezed
class GridLoadingState with _$GridLoadingState { class GridLoadingState with _$GridLoadingState {
const factory GridLoadingState.loading() = _Loading; const factory GridLoadingState.loading() = _Loading;
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish; const factory GridLoadingState.finish(
Either<Unit, FlowyError> successOrFail) = _Finish;
} }
class GridFieldEquatable extends Equatable { class GridFieldEquatable extends Equatable {
final List<GridFieldPB> _fields; final UnmodifiableListView<GridFieldPB> _fields;
const GridFieldEquatable(List<GridFieldPB> fields) : _fields = fields; const GridFieldEquatable(
UnmodifiableListView<GridFieldPB> fields,
) : _fields = fields;
@override @override
List<Object?> get props { List<Object?> get props {
if (_fields.isEmpty) {
return [];
}
return [ return [
_fields.length, _fields.length,
_fields.map((field) => field.width).reduce((value, element) => value + element), _fields
.map((field) => field.width)
.reduce((value, element) => value + element),
]; ];
} }

View File

@ -0,0 +1,128 @@
import 'dart:collection';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'block/block_cache.dart';
import 'prelude.dart';
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldPB>);
typedef OnGridChanged = void Function(GridPB);
typedef OnRowsChanged = void Function(
List<GridRowInfo> rowInfos,
GridRowChangeReason,
);
typedef ListenONRowChangedCondition = bool Function();
class GridDataController {
final String gridId;
final GridService _gridFFIService;
final GridFieldCache fieldCache;
// key: the block id
final LinkedHashMap<String, GridBlockCache> _blocks;
UnmodifiableMapView<String, GridBlockCache> get blocks =>
UnmodifiableMapView(_blocks);
OnRowsChanged? _onRowChanged;
OnFieldsChanged? _onFieldsChanged;
OnGridChanged? _onGridChanged;
List<GridRowInfo> get rowInfos {
final List<GridRowInfo> rows = [];
for (var block in _blocks.values) {
rows.addAll(block.rows);
}
return rows;
}
GridDataController({required ViewPB view})
: gridId = view.id,
_blocks = LinkedHashMap.identity(),
_gridFFIService = GridService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id);
void addListener({
required OnGridChanged onGridChanged,
required OnRowsChanged onRowsChanged,
required OnFieldsChanged onFieldsChanged,
}) {
_onGridChanged = onGridChanged;
_onRowChanged = onRowsChanged;
_onFieldsChanged = onFieldsChanged;
fieldCache.addListener(onFields: (fields) {
_onFieldsChanged?.call(UnmodifiableListView(fields));
});
}
Future<Either<Unit, FlowyError>> loadData() async {
final result = await _gridFFIService.loadGrid();
return Future(
() => result.fold(
(grid) async {
_initialBlocks(grid.blocks);
_onGridChanged?.call(grid);
return await _loadFields(grid);
},
(err) => right(err),
),
);
}
void createRow() {
_gridFFIService.createRow();
}
Future<void> dispose() async {
await _gridFFIService.closeGrid();
await fieldCache.dispose();
for (final blockCache in _blocks.values) {
blockCache.dispose();
}
}
void _initialBlocks(List<GridBlockPB> blocks) {
for (final block in blocks) {
if (_blocks[block.id] != null) {
Log.warn("Initial duplicate block's cache: ${block.id}");
return;
}
final cache = GridBlockCache(
gridId: gridId,
block: block,
fieldCache: fieldCache,
);
cache.addListener(
onChangeReason: (reason) {
_onRowChanged?.call(rowInfos, reason);
},
);
_blocks[block.id] = cache;
}
}
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
return Future(
() => result.fold(
(fields) {
fieldCache.fields = fields.items;
_onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields));
return left(unit);
},
(err) => right(err),
),
);
}
}

View File

@ -5,25 +5,25 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
import 'row_data_controller.dart';
import 'row_service.dart'; import 'row_service.dart';
part 'row_bloc.freezed.dart'; part 'row_bloc.freezed.dart';
class RowBloc extends Bloc<RowEvent, RowState> { class RowBloc extends Bloc<RowEvent, RowState> {
final RowService _rowService; final RowService _rowService;
final GridRowCache _rowCache; final GridRowDataController _dataController;
void Function()? _rowListenFn;
RowBloc({ RowBloc({
required GridRowInfo rowInfo, required GridRowInfo rowInfo,
required GridRowCache rowCache, required GridRowDataController dataController,
}) : _rowService = RowService( }) : _rowService = RowService(
gridId: rowInfo.gridId, gridId: rowInfo.gridId,
blockId: rowInfo.blockId, blockId: rowInfo.blockId,
rowId: rowInfo.id, rowId: rowInfo.id,
), ),
_rowCache = rowCache, _dataController = dataController,
super(RowState.initial(rowInfo, rowCache.loadGridCells(rowInfo.id))) { super(RowState.initial(rowInfo, dataController.loadData())) {
on<RowEvent>( on<RowEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.map(
@ -33,7 +33,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
createRow: (_CreateRow value) { createRow: (_CreateRow value) {
_rowService.createRow(); _rowService.createRow();
}, },
didReceiveCellDatas: (_DidReceiveCellDatas value) async { didReceiveCells: (_DidReceiveCells value) async {
final fields = value.gridCellMap.values final fields = value.gridCellMap.values
.map((e) => GridCellEquatable(e.field)) .map((e) => GridCellEquatable(e.field))
.toList(); .toList();
@ -51,19 +51,17 @@ class RowBloc extends Bloc<RowEvent, RowState> {
@override @override
Future<void> close() async { Future<void> close() async {
if (_rowListenFn != null) { _dataController.dispose();
_rowCache.removeRowListener(_rowListenFn!);
}
return super.close(); return super.close();
} }
Future<void> _startListening() async { Future<void> _startListening() async {
_rowListenFn = _rowCache.addListener( _dataController.addListener(
rowId: state.rowInfo.id, onRowChanged: (cells, reason) {
onCellUpdated: (cellDatas, reason) => if (!isClosed) {
add(RowEvent.didReceiveCellDatas(cellDatas, reason)), add(RowEvent.didReceiveCells(cells, reason));
listenWhen: () => !isClosed, }
},
); );
} }
} }
@ -72,9 +70,8 @@ class RowBloc extends Bloc<RowEvent, RowState> {
class RowEvent with _$RowEvent { 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.didReceiveCellDatas( const factory RowEvent.didReceiveCells(
GridCellMap gridCellMap, GridRowChangeReason reason) = GridCellMap gridCellMap, GridRowChangeReason reason) = _DidReceiveCells;
_DidReceiveCellDatas;
} }
@freezed @freezed

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import '../cell/cell_service/cell_service.dart';
import '../grid_service.dart';
import 'row_service.dart';
typedef OnRowChanged = void Function(GridCellMap, GridRowChangeReason);
class GridRowDataController {
final String rowId;
VoidCallback? _onRowChangedListener;
final GridFieldCache _fieldCache;
final GridRowCache _rowCache;
GridFieldCache get fieldCache => _fieldCache;
GridRowCache get rowCache => _rowCache;
GridRowDataController({
required this.rowId,
required GridFieldCache fieldCache,
required GridRowCache rowCache,
}) : _fieldCache = fieldCache,
_rowCache = rowCache;
GridCellMap loadData() {
return _rowCache.loadGridCells(rowId);
}
void addListener({OnRowChanged? onRowChanged}) {
_onRowChangedListener = _rowCache.addListener(
rowId: rowId,
onCellUpdated: onRowChanged,
);
}
void dispose() {
if (_onRowChangedListener != null) {
_rowCache.removeRowListener(_onRowChangedListener!);
}
}
}

View File

@ -158,7 +158,7 @@ class GridRowCache {
void Function(GridCellMap, GridRowChangeReason)? onCellUpdated, void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
bool Function()? listenWhen, bool Function()? listenWhen,
}) { }) {
listenrHandler() async { listenerHandler() async {
if (listenWhen != null && listenWhen() == false) { if (listenWhen != null && listenWhen() == false) {
return; return;
} }
@ -181,8 +181,8 @@ class GridRowCache {
); );
} }
_rowChangeReasonNotifier.addListener(listenrHandler); _rowChangeReasonNotifier.addListener(listenerHandler);
return listenrHandler; return listenerHandler;
} }
void removeRowListener(VoidCallback callback) { void removeRowListener(VoidCallback callback) {

View File

@ -2,19 +2,20 @@ import 'package:flutter/material.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart'; import 'package:linked_scroll_controller/linked_scroll_controller.dart';
class GridScrollController { class GridScrollController {
final LinkedScrollControllerGroup _scrollGroupContorller; final LinkedScrollControllerGroup _scrollGroupController;
final ScrollController verticalController; final ScrollController verticalController;
final ScrollController horizontalController; final ScrollController horizontalController;
final List<ScrollController> _linkHorizontalControllers = []; final List<ScrollController> _linkHorizontalControllers = [];
GridScrollController({required LinkedScrollControllerGroup scrollGroupContorller}) GridScrollController(
: _scrollGroupContorller = scrollGroupContorller, {required LinkedScrollControllerGroup scrollGroupController})
: _scrollGroupController = scrollGroupController,
verticalController = ScrollController(), verticalController = ScrollController(),
horizontalController = scrollGroupContorller.addAndGet(); horizontalController = scrollGroupController.addAndGet();
ScrollController linkHorizontalController() { ScrollController linkHorizontalController() {
final controller = _scrollGroupContorller.addAndGet(); final controller = _scrollGroupController.addAndGet();
_linkHorizontalControllers.add(controller); _linkHorizontalControllers.add(controller);
return controller; return controller;
} }

View File

@ -1,3 +1,4 @@
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart'; import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
import 'package:app_flowy/plugins/grid/application/row/row_service.dart'; import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
@ -79,7 +80,7 @@ class FlowyGrid extends StatefulWidget {
class _FlowyGridState extends State<FlowyGrid> { class _FlowyGridState extends State<FlowyGrid> {
final _scrollController = GridScrollController( final _scrollController = GridScrollController(
scrollGroupContorller: LinkedScrollControllerGroup()); scrollGroupController: LinkedScrollControllerGroup());
late ScrollController headerScrollController; late ScrollController headerScrollController;
@override @override
@ -153,7 +154,7 @@ class _FlowyGridState extends State<FlowyGrid> {
} }
Widget _gridHeader(BuildContext context, String gridId) { Widget _gridHeader(BuildContext context, String gridId) {
final fieldCache = context.read<GridBloc>().fieldCache; final fieldCache = context.read<GridBloc>().dataController.fieldCache;
return GridHeaderSliverAdaptor( return GridHeaderSliverAdaptor(
gridId: gridId, gridId: gridId,
fieldCache: fieldCache, fieldCache: fieldCache,
@ -169,7 +170,7 @@ class _GridToolbarAdaptor extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocSelector<GridBloc, GridState, GridToolbarContext>( return BlocSelector<GridBloc, GridState, GridToolbarContext>(
selector: (state) { selector: (state) {
final fieldCache = context.read<GridBloc>().fieldCache; final fieldCache = context.read<GridBloc>().dataController.fieldCache;
return GridToolbarContext( return GridToolbarContext(
gridId: state.gridId, gridId: state.gridId,
fieldCache: fieldCache, fieldCache: fieldCache,
@ -237,14 +238,20 @@ class _GridRowsState extends State<_GridRows> {
) { ) {
final rowCache = final rowCache =
context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id); context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id);
final fieldCache = context.read<GridBloc>().fieldCache;
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
if (rowCache != null) { if (rowCache != null) {
final dataController = GridRowDataController(
rowId: rowInfo.id,
fieldCache: fieldCache,
rowCache: rowCache,
);
return SizeTransition( return SizeTransition(
sizeFactor: animation, sizeFactor: animation,
child: GridRowWidget( child: GridRowWidget(
rowData: rowInfo, rowData: rowInfo,
rowCache: rowCache, dataController: dataController,
fieldCache: fieldCache,
key: ValueKey(rowInfo.id), key: ValueKey(rowInfo.id),
), ),
); );

View File

@ -27,39 +27,49 @@ class GridCellBuilder {
cellCache: cellCache, cellCache: cellCache,
fieldCache: fieldCache, fieldCache: fieldCache,
); );
final key = cell.key(); final key = cell.key();
switch (cell.fieldType) { switch (cell.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:
return GridCheckboxCell( return GridCheckboxCell(
cellControllerBuilder: cellControllerBuilder, key: key); cellControllerBuilder: cellControllerBuilder,
key: key,
);
case FieldType.DateTime: case FieldType.DateTime:
return GridDateCell( return GridDateCell(
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
key: key, key: key,
style: style); style: style,
);
case FieldType.SingleSelect: case FieldType.SingleSelect:
return GridSingleSelectCell( return GridSingleSelectCell(
cellContorllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
style: style, style: style,
key: key); key: key,
);
case FieldType.MultiSelect: case FieldType.MultiSelect:
return GridMultiSelectCell( return GridMultiSelectCell(
cellContorllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
style: style, style: style,
key: key); key: key,
);
case FieldType.Number: case FieldType.Number:
return GridNumberCell( return GridNumberCell(
cellContorllerBuilder: cellControllerBuilder, key: key); cellControllerBuilder: cellControllerBuilder,
key: key,
);
case FieldType.RichText: case FieldType.RichText:
return GridTextCell( return GridTextCell(
cellContorllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
style: style, style: style,
key: key); key: key,
);
case FieldType.URL: case FieldType.URL:
return GridURLCell( return GridURLCell(
cellContorllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
style: style, style: style,
key: key); key: key,
);
} }
throw UnimplementedError; throw UnimplementedError;
} }
@ -93,7 +103,7 @@ abstract class GridCellWidget extends StatefulWidget
@override @override
final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false); final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
// When the cell is focused, we assume that the accessory alse be hovered. // When the cell is focused, we assume that the accessory also be hovered.
@override @override
ValueNotifier<bool> get onAccessoryHover => onCellFocus; ValueNotifier<bool> get onAccessoryHover => onCellFocus;
@ -150,7 +160,7 @@ abstract class GridCellState<T extends GridCellWidget> extends State<T> {
abstract class GridFocusNodeCellState<T extends GridCellWidget> abstract class GridFocusNodeCellState<T extends GridCellWidget>
extends GridCellState<T> { extends GridCellState<T> {
SingleListenrFocusNode focusNode = SingleListenrFocusNode(); SingleListenerFocusNode focusNode = SingleListenerFocusNode();
@override @override
void initState() { void initState() {
@ -219,7 +229,7 @@ class GridCellFocusListener extends ChangeNotifier {
abstract class GridCellStyle {} abstract class GridCellStyle {}
class SingleListenrFocusNode extends FocusNode { class SingleListenerFocusNode extends FocusNode {
VoidCallback? _listener; VoidCallback? _listener;
void setListener(VoidCallback listener) { void setListener(VoidCallback listener) {

View File

@ -7,10 +7,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_builder.dart'; import 'cell_builder.dart';
class GridNumberCell extends GridCellWidget { class GridNumberCell extends GridCellWidget {
final GridCellControllerBuilder cellContorllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
GridNumberCell({ GridNumberCell({
required this.cellContorllerBuilder, required this.cellControllerBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -25,7 +25,7 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
@override @override
void initState() { void initState() {
final cellContext = widget.cellContorllerBuilder.build(); final cellContext = widget.cellControllerBuilder.build();
_cellBloc = getIt<NumberCellBloc>(param1: cellContext) _cellBloc = getIt<NumberCellBloc>(param1: cellContext)
..add(const NumberCellEvent.initial()); ..add(const NumberCellEvent.initial());
_controller = _controller =

View File

@ -22,11 +22,11 @@ class SelectOptionCellStyle extends GridCellStyle {
} }
class GridSingleSelectCell extends GridCellWidget { class GridSingleSelectCell extends GridCellWidget {
final GridCellControllerBuilder cellContorllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
late final SelectOptionCellStyle? cellStyle; late final SelectOptionCellStyle? cellStyle;
GridSingleSelectCell({ GridSingleSelectCell({
required this.cellContorllerBuilder, required this.cellControllerBuilder,
GridCellStyle? style, GridCellStyle? style,
Key? key, Key? key,
}) : super(key: key) { }) : super(key: key) {
@ -47,7 +47,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
@override @override
void initState() { void initState() {
final cellContext = final cellContext =
widget.cellContorllerBuilder.build() as GridSelectOptionCellController; widget.cellControllerBuilder.build() as GridSelectOptionCellController;
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext) _cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
..add(const SelectOptionCellEvent.initial()); ..add(const SelectOptionCellEvent.initial());
super.initState(); super.initState();
@ -63,7 +63,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
selectOptions: state.selectedOptions, selectOptions: state.selectedOptions,
cellStyle: widget.cellStyle, cellStyle: widget.cellStyle,
onFocus: (value) => widget.onCellEditing.value = value, onFocus: (value) => widget.onCellEditing.value = value,
cellContorllerBuilder: widget.cellContorllerBuilder); cellControllerBuilder: widget.cellControllerBuilder);
}, },
), ),
); );
@ -78,11 +78,11 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
//---------------------------------------------------------------- //----------------------------------------------------------------
class GridMultiSelectCell extends GridCellWidget { class GridMultiSelectCell extends GridCellWidget {
final GridCellControllerBuilder cellContorllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
late final SelectOptionCellStyle? cellStyle; late final SelectOptionCellStyle? cellStyle;
GridMultiSelectCell({ GridMultiSelectCell({
required this.cellContorllerBuilder, required this.cellControllerBuilder,
GridCellStyle? style, GridCellStyle? style,
Key? key, Key? key,
}) : super(key: key) { }) : super(key: key) {
@ -103,7 +103,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
@override @override
void initState() { void initState() {
final cellContext = final cellContext =
widget.cellContorllerBuilder.build() as GridSelectOptionCellController; widget.cellControllerBuilder.build() as GridSelectOptionCellController;
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext) _cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
..add(const SelectOptionCellEvent.initial()); ..add(const SelectOptionCellEvent.initial());
super.initState(); super.initState();
@ -119,7 +119,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
selectOptions: state.selectedOptions, selectOptions: state.selectedOptions,
cellStyle: widget.cellStyle, cellStyle: widget.cellStyle,
onFocus: (value) => widget.onCellEditing.value = value, onFocus: (value) => widget.onCellEditing.value = value,
cellContorllerBuilder: widget.cellContorllerBuilder); cellControllerBuilder: widget.cellControllerBuilder);
}, },
), ),
); );
@ -136,12 +136,12 @@ class _SelectOptionCell extends StatelessWidget {
final List<SelectOptionPB> selectOptions; final List<SelectOptionPB> selectOptions;
final void Function(bool) onFocus; final void Function(bool) onFocus;
final SelectOptionCellStyle? cellStyle; final SelectOptionCellStyle? cellStyle;
final GridCellControllerBuilder cellContorllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
const _SelectOptionCell({ const _SelectOptionCell({
required this.selectOptions, required this.selectOptions,
required this.onFocus, required this.onFocus,
required this.cellStyle, required this.cellStyle,
required this.cellContorllerBuilder, required this.cellControllerBuilder,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -179,7 +179,7 @@ class _SelectOptionCell extends StatelessWidget {
onTap: () { onTap: () {
onFocus(true); onFocus(true);
final cellContext = final cellContext =
cellContorllerBuilder.build() as GridSelectOptionCellController; cellControllerBuilder.build() as GridSelectOptionCellController;
SelectOptionCellEditor.show( SelectOptionCellEditor.show(
context, cellContext, () => onFocus(false)); context, cellContext, () => onFocus(false));
}, },

View File

@ -14,10 +14,10 @@ class GridTextCellStyle extends GridCellStyle {
} }
class GridTextCell extends GridCellWidget { class GridTextCell extends GridCellWidget {
final GridCellControllerBuilder cellContorllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
late final GridTextCellStyle? cellStyle; late final GridTextCellStyle? cellStyle;
GridTextCell({ GridTextCell({
required this.cellContorllerBuilder, required this.cellControllerBuilder,
GridCellStyle? style, GridCellStyle? style,
Key? key, Key? key,
}) : super(key: key) { }) : super(key: key) {
@ -39,7 +39,7 @@ class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
@override @override
void initState() { void initState() {
final cellContext = widget.cellContorllerBuilder.build(); final cellContext = widget.cellControllerBuilder.build();
_cellBloc = getIt<TextCellBloc>(param1: cellContext); _cellBloc = getIt<TextCellBloc>(param1: cellContext);
_cellBloc.add(const TextCellEvent.initial()); _cellBloc.add(const TextCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content); _controller = TextEditingController(text: _cellBloc.state.content);

View File

@ -31,10 +31,10 @@ enum GridURLCellAccessoryType {
} }
class GridURLCell extends GridCellWidget { class GridURLCell extends GridCellWidget {
final GridCellControllerBuilder cellContorllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
late final GridURLCellStyle? cellStyle; late final GridURLCellStyle? cellStyle;
GridURLCell({ GridURLCell({
required this.cellContorllerBuilder, required this.cellControllerBuilder,
GridCellStyle? style, GridCellStyle? style,
Key? key, Key? key,
}) : super(key: key) { }) : super(key: key) {
@ -53,14 +53,14 @@ class GridURLCell extends GridCellWidget {
switch (ty) { switch (ty) {
case GridURLCellAccessoryType.edit: case GridURLCellAccessoryType.edit:
final cellContext = final cellContext =
cellContorllerBuilder.build() as GridURLCellController; cellControllerBuilder.build() as GridURLCellController;
return _EditURLAccessory( return _EditURLAccessory(
cellContext: cellContext, cellContext: cellContext,
anchorContext: buildContext.anchorContext); anchorContext: buildContext.anchorContext);
case GridURLCellAccessoryType.copyURL: case GridURLCellAccessoryType.copyURL:
final cellContext = final cellContext =
cellContorllerBuilder.build() as GridURLCellController; cellControllerBuilder.build() as GridURLCellController;
return _CopyURLAccessory(cellContext: cellContext); return _CopyURLAccessory(cellContext: cellContext);
} }
} }
@ -91,7 +91,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
@override @override
void initState() { void initState() {
final cellContext = final cellContext =
widget.cellContorllerBuilder.build() as GridURLCellController; widget.cellControllerBuilder.build() as GridURLCellController;
_cellBloc = URLCellBloc(cellContext: cellContext); _cellBloc = URLCellBloc(cellContext: cellContext);
_cellBloc.add(const URLCellEvent.initial()); _cellBloc.add(const URLCellEvent.initial());
super.initState(); super.initState();
@ -141,7 +141,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
await launchUrl(uri); await launchUrl(uri);
} else { } else {
final cellContext = final cellContext =
widget.cellContorllerBuilder.build() as GridURLCellController; widget.cellControllerBuilder.build() as GridURLCellController;
widget.onCellEditing.value = true; widget.onCellEditing.value = true;
URLCellEditor.show(context, cellContext, () { URLCellEditor.show(context, cellContext, () {
widget.onCellEditing.value = false; widget.onCellEditing.value = false;

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/prelude.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
@ -9,24 +10,23 @@ import 'package:provider/provider.dart';
import '../../layout/sizes.dart'; import '../../layout/sizes.dart';
import '../cell/cell_accessory.dart'; import '../cell/cell_accessory.dart';
import '../cell/cell_cotainer.dart'; import '../cell/cell_container.dart';
import '../cell/prelude.dart'; import '../cell/prelude.dart';
import 'row_action_sheet.dart'; import 'row_action_sheet.dart';
import 'row_detail.dart'; import 'row_detail.dart';
class GridRowWidget extends StatefulWidget { class GridRowWidget extends StatefulWidget {
final GridRowInfo rowData; final GridRowInfo rowData;
final GridRowCache rowCache; final GridRowDataController dataController;
final GridCellBuilder cellBuilder; final GridCellBuilder cellBuilder;
GridRowWidget({ GridRowWidget({
required this.rowData, required this.rowData,
required this.rowCache, required this.dataController,
required GridFieldCache fieldCache,
Key? key, Key? key,
}) : cellBuilder = GridCellBuilder( }) : cellBuilder = GridCellBuilder(
cellCache: rowCache.cellCache, cellCache: dataController.rowCache.cellCache,
fieldCache: fieldCache, fieldCache: dataController.fieldCache,
), ),
super(key: key); super(key: key);
@ -41,7 +41,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
void initState() { void initState() {
_rowBloc = RowBloc( _rowBloc = RowBloc(
rowInfo: widget.rowData, rowInfo: widget.rowData,
rowCache: widget.rowCache, dataController: widget.dataController,
); );
_rowBloc.add(const RowEvent.initial()); _rowBloc.add(const RowEvent.initial());
super.initState(); super.initState();
@ -81,7 +81,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
void _expandRow(BuildContext context) { void _expandRow(BuildContext context) {
final page = RowDetailPage( final page = RowDetailPage(
rowInfo: widget.rowData, rowInfo: widget.rowData,
rowCache: widget.rowCache, rowCache: widget.dataController.rowCache,
cellBuilder: widget.cellBuilder, cellBuilder: widget.cellBuilder,
); );
page.show(context); page.show(context);

View File

@ -62,8 +62,10 @@ class _RowDetailPageState extends State<RowDetailPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) { create: (context) {
final bloc = final bloc = RowDetailBloc(
RowDetailBloc(rowInfo: widget.rowInfo, rowCache: widget.rowCache); rowInfo: widget.rowInfo,
rowCache: widget.rowCache,
);
bloc.add(const RowDetailEvent.initial()); bloc.add(const RowDetailEvent.initial());
return bloc; return bloc;
}, },

View File

@ -164,18 +164,11 @@ pub struct CreateFieldPayloadPB {
pub grid_id: String, pub grid_id: String,
#[pb(index = 2)] #[pb(index = 2)]
pub field_id: String,
#[pb(index = 3)]
pub field_type: FieldType, pub field_type: FieldType,
#[pb(index = 4)]
pub create_if_not_exist: bool,
} }
pub struct CreateFieldParams { pub struct CreateFieldParams {
pub grid_id: String, pub grid_id: String,
pub field_id: String,
pub field_type: FieldType, pub field_type: FieldType,
} }
@ -184,10 +177,8 @@ impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
fn try_into(self) -> Result<CreateFieldParams, Self::Error> { fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
Ok(CreateFieldParams { Ok(CreateFieldParams {
grid_id: grid_id.0, grid_id: grid_id.0,
field_id: field_id.0,
field_type: self.field_type, field_type: self.field_type,
}) })
} }