mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #460 from AppFlowy-IO/feat_row_detail
Feature: show row detail
This commit is contained in:
commit
8a7c3a9cfc
6
frontend/app_flowy/assets/images/grid/expander.svg
Normal file
6
frontend/app_flowy/assets/images/grid/expander.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 13H3V10" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 3H13V6" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 13L7 9" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 3L9 7" stroke="#00BCF0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 449 B |
@ -177,7 +177,8 @@
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Duplicate",
|
||||
"delete": "Delete"
|
||||
"delete": "Delete",
|
||||
"textPlaceholder": "Empty"
|
||||
},
|
||||
"selectOption": {
|
||||
"purpleColor": "Purple",
|
||||
|
@ -3,7 +3,6 @@ import 'package:app_flowy/user/application/user_listener.dart';
|
||||
import 'package:app_flowy/user/application/user_service.dart';
|
||||
import 'package:app_flowy/workspace/application/app/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/doc/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/trash/prelude.dart';
|
||||
import 'package:app_flowy/workspace/application/workspace/prelude.dart';
|
||||
@ -19,7 +18,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
@ -170,32 +168,31 @@ void _resolveGridDeps(GetIt getIt) {
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<TextCellBloc, GridCellIdentifier, void>(
|
||||
getIt.registerFactoryParam<TextCellBloc, GridCell, void>(
|
||||
(cellData, _) => TextCellBloc(
|
||||
service: CellService(),
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<SelectionCellBloc, GridCellIdentifier, void>(
|
||||
getIt.registerFactoryParam<SelectionCellBloc, GridCell, void>(
|
||||
(cellData, _) => SelectionCellBloc(
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<NumberCellBloc, GridCellIdentifier, void>(
|
||||
getIt.registerFactoryParam<NumberCellBloc, GridCell, void>(
|
||||
(cellData, _) => NumberCellBloc(
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<DateCellBloc, GridCellIdentifier, void>(
|
||||
getIt.registerFactoryParam<DateCellBloc, GridCell, void>(
|
||||
(cellData, _) => DateCellBloc(
|
||||
cellIdentifier: cellData,
|
||||
cellData: cellData,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<CheckboxCellBloc, GridCellIdentifier, void>(
|
||||
getIt.registerFactoryParam<CheckboxCellBloc, GridCell, void>(
|
||||
(cellData, _) => CheckboxCellBloc(
|
||||
service: CellService(),
|
||||
cellData: cellData,
|
||||
@ -206,14 +203,6 @@ void _resolveGridDeps(GetIt getIt) {
|
||||
(context, _) => FieldSwitcherBloc(context),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<SingleSelectTypeOptionBloc, SingleSelectTypeOption, String>(
|
||||
(typeOption, fieldId) => SingleSelectTypeOptionBloc(typeOption, fieldId),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<MultiSelectTypeOptionBloc, MultiSelectTypeOption, String>(
|
||||
(typeOption, fieldId) => MultiSelectTypeOptionBloc(typeOption, fieldId),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<DateTypeOptionBloc, DateTypeOption, void>(
|
||||
(typeOption, _) => DateTypeOptionBloc(typeOption: typeOption),
|
||||
);
|
||||
|
@ -44,7 +44,7 @@ class CellCache {
|
||||
|
||||
CellCache() : _cellService = CellService();
|
||||
|
||||
Future<Option<Cell>> getCellData(GridCellIdentifier identifier) async {
|
||||
Future<Option<Cell>> getCellData(GridCell identifier) async {
|
||||
final cellId = _cellId(identifier);
|
||||
final Cell? data = _cellDataMap[cellId];
|
||||
if (data != null) {
|
||||
@ -69,7 +69,7 @@ class CellCache {
|
||||
);
|
||||
}
|
||||
|
||||
String _cellId(GridCellIdentifier identifier) {
|
||||
String _cellId(GridCell identifier) {
|
||||
return "${identifier.rowId}/${identifier.field.id}";
|
||||
}
|
||||
}
|
||||
|
@ -11,13 +11,13 @@ part 'checkbox_cell_bloc.freezed.dart';
|
||||
|
||||
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
final CellService _service;
|
||||
final CellListener _listener;
|
||||
final CellListener _cellListener;
|
||||
|
||||
CheckboxCellBloc({
|
||||
required CellService service,
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
}) : _service = service,
|
||||
_listener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
super(CheckboxCellState.initial(cellData)) {
|
||||
on<CheckboxCellEvent>(
|
||||
(event, emit) async {
|
||||
@ -38,18 +38,18 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _listener.stop();
|
||||
await _cellListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_listener.updateCellNotifier?.addPublishListener((result) {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) async => await _loadCellData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_listener.start();
|
||||
_cellListener.start();
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
@ -87,11 +87,11 @@ class CheckboxCellEvent with _$CheckboxCellEvent {
|
||||
@freezed
|
||||
class CheckboxCellState with _$CheckboxCellState {
|
||||
const factory CheckboxCellState({
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
required bool isSelected,
|
||||
}) = _CheckboxCellState;
|
||||
|
||||
factory CheckboxCellState.initial(GridCellIdentifier cellData) {
|
||||
factory CheckboxCellState.initial(GridCell cellData) {
|
||||
return CheckboxCellState(cellData: cellData, isSelected: _isSelected(cellData.cell));
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
final CellListener _cellListener;
|
||||
final SingleFieldListener _fieldListener;
|
||||
|
||||
DateCellBloc({required GridCellIdentifier cellIdentifier})
|
||||
DateCellBloc({required GridCell cellData})
|
||||
: _service = CellService(),
|
||||
_cellListener = CellListener(rowId: cellIdentifier.rowId, fieldId: cellIdentifier.field.id),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellIdentifier.field.id),
|
||||
super(DateCellState.initial(cellIdentifier)) {
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
|
||||
super(DateCellState.initial(cellData)) {
|
||||
on<DateCellEvent>(
|
||||
(event, emit) async {
|
||||
event.map(
|
||||
@ -106,13 +106,13 @@ class DateCellEvent with _$DateCellEvent {
|
||||
@freezed
|
||||
class DateCellState with _$DateCellState {
|
||||
const factory DateCellState({
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
required String content,
|
||||
required Field field,
|
||||
DateTime? selectedDay,
|
||||
}) = _DateCellState;
|
||||
|
||||
factory DateCellState.initial(GridCellIdentifier cellData) => DateCellState(
|
||||
factory DateCellState.initial(GridCell cellData) => DateCellState(
|
||||
cellData: cellData,
|
||||
field: cellData.field,
|
||||
content: cellData.cell?.content ?? "",
|
||||
|
@ -16,7 +16,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
final SingleFieldListener _fieldListener;
|
||||
|
||||
NumberCellBloc({
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
}) : _service = CellService(),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
|
||||
@ -105,11 +105,11 @@ class NumberCellEvent with _$NumberCellEvent {
|
||||
@freezed
|
||||
class NumberCellState with _$NumberCellState {
|
||||
const factory NumberCellState({
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
required String content,
|
||||
}) = _NumberCellState;
|
||||
|
||||
factory NumberCellState.initial(GridCellIdentifier cellData) {
|
||||
factory NumberCellState.initial(GridCell cellData) {
|
||||
return NumberCellState(cellData: cellData, content: cellData.cell?.content ?? "");
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
@ -13,7 +14,7 @@ class SelectOptionService {
|
||||
required String rowId,
|
||||
required String name,
|
||||
}) {
|
||||
return GridEventNewSelectOption(SelectOptionName.create()..name = name).send().then(
|
||||
return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then(
|
||||
(result) {
|
||||
return result.fold(
|
||||
(option) {
|
||||
|
@ -16,7 +16,7 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
|
||||
final SingleFieldListener _fieldListener;
|
||||
|
||||
SelectionCellBloc({
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
}) : _service = SelectOptionService(),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
|
||||
@ -93,12 +93,12 @@ class SelectionCellEvent with _$SelectionCellEvent {
|
||||
@freezed
|
||||
class SelectionCellState with _$SelectionCellState {
|
||||
const factory SelectionCellState({
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
required List<SelectOption> options,
|
||||
required List<SelectOption> selectedOptions,
|
||||
}) = _SelectionCellState;
|
||||
|
||||
factory SelectionCellState.initial(GridCellIdentifier cellData) => SelectionCellState(
|
||||
factory SelectionCellState.initial(GridCell cellData) => SelectionCellState(
|
||||
cellData: cellData,
|
||||
options: [],
|
||||
selectedOptions: [],
|
||||
|
@ -18,7 +18,7 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
|
||||
Timer? _delayOperation;
|
||||
|
||||
SelectOptionEditorBloc({
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
required List<SelectOption> options,
|
||||
required List<SelectOption> selectedOptions,
|
||||
}) : _selectOptionService = SelectOptionService(),
|
||||
@ -174,7 +174,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
|
||||
}) = _SelectOptionEditorState;
|
||||
|
||||
factory SelectOptionEditorState.initial(
|
||||
GridCellIdentifier cellData,
|
||||
GridCell cellData,
|
||||
List<SelectOption> options,
|
||||
List<SelectOption> selectedOptions,
|
||||
) {
|
||||
|
@ -1,22 +1,29 @@
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_listener.dart';
|
||||
import 'cell_service.dart';
|
||||
|
||||
part 'text_cell_bloc.freezed.dart';
|
||||
|
||||
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
final CellService service;
|
||||
final CellService _service;
|
||||
final CellListener _cellListener;
|
||||
|
||||
TextCellBloc({
|
||||
required this.service,
|
||||
required GridCellIdentifier cellData,
|
||||
}) : super(TextCellState.initial(cellData)) {
|
||||
required GridCell cellData,
|
||||
}) : _service = CellService(),
|
||||
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
|
||||
super(TextCellState.initial(cellData)) {
|
||||
on<TextCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialCell value) async {},
|
||||
initial: (_InitialCell value) async {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (_UpdateText value) {
|
||||
updateCellContent(value.text);
|
||||
emit(state.copyWith(content: value.text));
|
||||
@ -27,16 +34,28 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
content: value.cellData.cell?.content ?? "",
|
||||
));
|
||||
},
|
||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
||||
emit(state.copyWith(
|
||||
cellData: state.cellData.copyWith(cell: value.cell),
|
||||
content: value.cell.content,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _cellListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void updateCellContent(String content) {
|
||||
final fieldId = state.cellData.field.id;
|
||||
final gridId = state.cellData.gridId;
|
||||
final rowId = state.cellData.rowId;
|
||||
service.updateCell(
|
||||
_service.updateCell(
|
||||
data: content,
|
||||
fieldId: fieldId,
|
||||
gridId: gridId,
|
||||
@ -44,16 +63,37 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
return super.close();
|
||||
void _startListening() {
|
||||
_cellListener.updateCellNotifier?.addPublishListener((result) {
|
||||
result.fold(
|
||||
(notificationData) async => await _loadCellData(),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
_cellListener.start();
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final result = await _service.getCell(
|
||||
gridId: state.cellData.gridId,
|
||||
fieldId: state.cellData.field.id,
|
||||
rowId: state.cellData.rowId,
|
||||
);
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
result.fold(
|
||||
(cell) => add(TextCellEvent.didReceiveCellUpdate(cell)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class TextCellEvent with _$TextCellEvent {
|
||||
const factory TextCellEvent.initial() = _InitialCell;
|
||||
const factory TextCellEvent.didReceiveCellData(GridCellIdentifier cellData) = _DidReceiveCellData;
|
||||
const factory TextCellEvent.didReceiveCellData(GridCell cellData) = _DidReceiveCellData;
|
||||
const factory TextCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
|
||||
const factory TextCellEvent.updateText(String text) = _UpdateText;
|
||||
}
|
||||
|
||||
@ -61,10 +101,10 @@ class TextCellEvent with _$TextCellEvent {
|
||||
class TextCellState with _$TextCellState {
|
||||
const factory TextCellState({
|
||||
required String content,
|
||||
required GridCellIdentifier cellData,
|
||||
required GridCell cellData,
|
||||
}) = _TextCellState;
|
||||
|
||||
factory TextCellState.initial(GridCellIdentifier cellData) => TextCellState(
|
||||
factory TextCellState.initial(GridCell cellData) => TextCellState(
|
||||
content: cellData.cell?.content ?? "",
|
||||
cellData: cellData,
|
||||
);
|
||||
|
@ -11,14 +11,14 @@ part 'multi_select_bloc.freezed.dart';
|
||||
class MultiSelectTypeOptionBloc extends Bloc<MultiSelectTypeOptionEvent, MultiSelectTypeOptionState> {
|
||||
final TypeOptionService service;
|
||||
|
||||
MultiSelectTypeOptionBloc(MultiSelectTypeOption typeOption, String fieldId)
|
||||
: service = TypeOptionService(fieldId: fieldId),
|
||||
super(MultiSelectTypeOptionState.initial(typeOption)) {
|
||||
MultiSelectTypeOptionBloc(TypeOptionContext typeOptionContext)
|
||||
: service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
|
||||
super(MultiSelectTypeOptionState.initial(MultiSelectTypeOption.fromBuffer(typeOptionContext.data))) {
|
||||
on<MultiSelectTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
createOption: (_CreateOption value) async {
|
||||
final result = await service.newOption(value.optionName);
|
||||
final result = await service.newOption(name: value.optionName);
|
||||
result.fold(
|
||||
(option) {
|
||||
emit(state.copyWith(typeOption: _insertOption(option)));
|
||||
|
@ -12,17 +12,16 @@ class SingleSelectTypeOptionBloc extends Bloc<SingleSelectTypeOptionEvent, Singl
|
||||
final TypeOptionService service;
|
||||
|
||||
SingleSelectTypeOptionBloc(
|
||||
SingleSelectTypeOption typeOption,
|
||||
String fieldId,
|
||||
) : service = TypeOptionService(fieldId: fieldId),
|
||||
TypeOptionContext typeOptionContext,
|
||||
) : service = TypeOptionService(gridId: typeOptionContext.gridId, fieldId: typeOptionContext.field.id),
|
||||
super(
|
||||
SingleSelectTypeOptionState.initial(typeOption),
|
||||
SingleSelectTypeOptionState.initial(SingleSelectTypeOption.fromBuffer(typeOptionContext.data)),
|
||||
) {
|
||||
on<SingleSelectTypeOptionEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
createOption: (_CreateOption value) async {
|
||||
final result = await service.newOption(value.optionName);
|
||||
final result = await service.newOption(name: value.optionName);
|
||||
result.fold(
|
||||
(option) {
|
||||
emit(state.copyWith(typeOption: _insertOption(option)));
|
||||
|
@ -1,17 +1,44 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
|
||||
class TypeOptionService {
|
||||
final String gridId;
|
||||
final String fieldId;
|
||||
|
||||
TypeOptionService({
|
||||
required this.gridId,
|
||||
required this.fieldId,
|
||||
});
|
||||
|
||||
Future<Either<SelectOption, FlowyError>> newOption(String name, {bool selected = false}) {
|
||||
final payload = SelectOptionName.create()..name = name;
|
||||
Future<Either<SelectOption, FlowyError>> newOption({
|
||||
required String name,
|
||||
}) {
|
||||
final fieldIdentifier = FieldIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId;
|
||||
|
||||
final payload = CreateSelectOptionPayload.create()
|
||||
..optionName = name
|
||||
..fieldIdentifier = fieldIdentifier;
|
||||
|
||||
return GridEventNewSelectOption(payload).send();
|
||||
}
|
||||
}
|
||||
|
||||
class TypeOptionContext {
|
||||
final String gridId;
|
||||
final Field field;
|
||||
final Uint8List data;
|
||||
const TypeOptionContext({
|
||||
required this.gridId,
|
||||
required this.field,
|
||||
required this.data,
|
||||
});
|
||||
}
|
||||
|
@ -13,13 +13,17 @@ part 'grid_bloc.freezed.dart';
|
||||
class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
final GridService _gridService;
|
||||
final GridFieldCache fieldCache;
|
||||
final GridRowCache rowCache;
|
||||
late final GridRowCache rowCache;
|
||||
|
||||
GridBloc({required View view})
|
||||
: _gridService = GridService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id),
|
||||
rowCache = GridRowCache(gridId: view.id),
|
||||
super(GridState.initial(view.id)) {
|
||||
rowCache = GridRowCache(
|
||||
gridId: view.id,
|
||||
dataDelegate: GridRowDataDelegateAdaptor(fieldCache),
|
||||
);
|
||||
|
||||
on<GridEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
@ -51,13 +55,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
|
||||
void _startListening() {
|
||||
fieldCache.addListener(
|
||||
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
|
||||
listenWhen: () => !isClosed,
|
||||
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
|
||||
);
|
||||
|
||||
rowCache.addListener(
|
||||
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
|
||||
listenWhen: () => !isClosed,
|
||||
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -77,7 +81,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
() => result.fold(
|
||||
(fields) {
|
||||
fieldCache.fields = fields.items;
|
||||
rowCache.updateWithBlock(grid.blockOrders, fieldCache.unmodifiableFields);
|
||||
rowCache.updateWithBlock(grid.blockOrders);
|
||||
|
||||
emit(state.copyWith(
|
||||
grid: Some(grid),
|
||||
|
@ -8,6 +8,8 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'row/row_service.dart';
|
||||
|
||||
class GridService {
|
||||
final String gridId;
|
||||
GridService({
|
||||
@ -152,3 +154,44 @@ class GridFieldCache {
|
||||
_fieldNotifier.fields = fields;
|
||||
}
|
||||
}
|
||||
|
||||
class GridRowDataDelegateAdaptor extends GridRowDataDelegate {
|
||||
final GridFieldCache _cache;
|
||||
|
||||
GridRowDataDelegateAdaptor(GridFieldCache cache) : _cache = cache;
|
||||
@override
|
||||
UnmodifiableListView<Field> get fields => _cache.unmodifiableFields;
|
||||
|
||||
@override
|
||||
GridRow buildGridRow(RowOrder rowOrder) {
|
||||
return GridRow(
|
||||
gridId: _cache.gridId,
|
||||
fields: _cache.unmodifiableFields,
|
||||
rowId: rowOrder.rowId,
|
||||
height: rowOrder.height.toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onFieldChanged(FieldDidUpdateCallback callback) {
|
||||
_cache.addListener(listener: () {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
CellDataMap buildCellDataMap(Row rowData) {
|
||||
var map = CellDataMap.new();
|
||||
for (final field in fields) {
|
||||
if (field.visibility) {
|
||||
map[field.id] = GridCell(
|
||||
rowId: rowData.id,
|
||||
gridId: _cache.gridId,
|
||||
cell: rowData.cellByFieldId[field.id],
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
@ -10,21 +7,15 @@ import 'package:dartz/dartz.dart';
|
||||
|
||||
part 'row_bloc.freezed.dart';
|
||||
|
||||
typedef CellDataMap = LinkedHashMap<String, GridCellIdentifier>;
|
||||
|
||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
final RowService _rowService;
|
||||
final GridFieldCache _fieldCache;
|
||||
final GridRowCache _rowCache;
|
||||
void Function()? _rowListenCallback;
|
||||
void Function()? _fieldListenCallback;
|
||||
void Function()? _rowListenFn;
|
||||
|
||||
RowBloc({
|
||||
required GridRow rowData,
|
||||
required GridFieldCache fieldCache,
|
||||
required GridRowCache rowCache,
|
||||
}) : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
|
||||
_fieldCache = fieldCache,
|
||||
_rowCache = rowCache,
|
||||
super(RowState.initial(rowData)) {
|
||||
on<RowEvent>(
|
||||
@ -37,87 +28,37 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
createRow: (_CreateRow value) {
|
||||
_rowService.createRow();
|
||||
},
|
||||
didUpdateRow: (_DidUpdateRow value) async {
|
||||
_handleRowUpdate(value.row, emit);
|
||||
},
|
||||
fieldsDidUpdate: (_FieldsDidUpdate value) async {
|
||||
await _handleFieldUpdate(emit);
|
||||
},
|
||||
didLoadRow: (_DidLoadRow value) {
|
||||
_handleRowUpdate(value.row, emit);
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
||||
emit(state.copyWith(cellDataMap: Some(value.cellData)));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _handleRowUpdate(Row row, Emitter<RowState> emit) {
|
||||
final CellDataMap cellDataMap = _makeCellDatas(row, state.rowData.fields);
|
||||
emit(state.copyWith(
|
||||
row: Future(() => Some(row)),
|
||||
cellDataMap: Some(cellDataMap),
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _handleFieldUpdate(Emitter<RowState> emit) async {
|
||||
final optionRow = await state.row;
|
||||
final CellDataMap cellDataMap = optionRow.fold(
|
||||
() => CellDataMap.identity(),
|
||||
(row) => _makeCellDatas(row, _fieldCache.unmodifiableFields),
|
||||
);
|
||||
|
||||
emit(state.copyWith(
|
||||
rowData: state.rowData.copyWith(fields: _fieldCache.unmodifiableFields),
|
||||
cellDataMap: Some(cellDataMap),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_rowListenCallback != null) {
|
||||
_rowCache.removeRowListener(_rowListenCallback!);
|
||||
}
|
||||
|
||||
if (_fieldListenCallback != null) {
|
||||
_fieldCache.removeListener(_fieldListenCallback!);
|
||||
if (_rowListenFn != null) {
|
||||
_rowCache.removeRowListener(_rowListenFn!);
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_fieldListenCallback = _fieldCache.addListener(
|
||||
listener: () => add(const RowEvent.fieldsDidUpdate()),
|
||||
listenWhen: () => !isClosed,
|
||||
);
|
||||
|
||||
_rowListenCallback = _rowCache.addRowListener(
|
||||
_rowListenFn = _rowCache.addRowListener(
|
||||
rowId: state.rowData.rowId,
|
||||
onUpdated: (row) => add(RowEvent.didUpdateRow(row)),
|
||||
onUpdated: (cellDatas) => add(RowEvent.didReceiveCellDatas(cellDatas)),
|
||||
listenWhen: () => !isClosed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(Emitter<RowState> emit) async {
|
||||
final data = await _rowCache.getRowData(state.rowData.rowId);
|
||||
if (isClosed) {
|
||||
return;
|
||||
final data = _rowCache.loadCellData(state.rowData.rowId);
|
||||
data.foldRight(null, (cellDatas, _) {
|
||||
if (!isClosed) {
|
||||
add(RowEvent.didReceiveCellDatas(cellDatas));
|
||||
}
|
||||
data.foldRight(null, (data, _) => add(RowEvent.didLoadRow(data)));
|
||||
}
|
||||
|
||||
CellDataMap _makeCellDatas(Row row, List<Field> fields) {
|
||||
var map = CellDataMap.new();
|
||||
for (final field in fields) {
|
||||
if (field.visibility) {
|
||||
map[field.id] = GridCellIdentifier(
|
||||
rowId: row.id,
|
||||
gridId: _rowService.gridId,
|
||||
cell: row.cellByFieldId[field.id],
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,22 +66,18 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
||||
class RowEvent with _$RowEvent {
|
||||
const factory RowEvent.initial() = _InitialRow;
|
||||
const factory RowEvent.createRow() = _CreateRow;
|
||||
const factory RowEvent.fieldsDidUpdate() = _FieldsDidUpdate;
|
||||
const factory RowEvent.didLoadRow(Row row) = _DidLoadRow;
|
||||
const factory RowEvent.didUpdateRow(Row row) = _DidUpdateRow;
|
||||
const factory RowEvent.didReceiveCellDatas(CellDataMap cellData) = _DidReceiveCellDatas;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowState with _$RowState {
|
||||
const factory RowState({
|
||||
required GridRow rowData,
|
||||
required Future<Option<Row>> row,
|
||||
required Option<CellDataMap> cellDataMap,
|
||||
}) = _RowState;
|
||||
|
||||
factory RowState.initial(GridRow rowData) => RowState(
|
||||
rowData: rowData,
|
||||
row: Future(() => none()),
|
||||
cellDataMap: none(),
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'row_service.dart';
|
||||
|
||||
part 'row_detail_bloc.freezed.dart';
|
||||
|
||||
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
final GridRow rowData;
|
||||
final GridRowCache _rowCache;
|
||||
void Function()? _rowListenFn;
|
||||
|
||||
RowDetailBloc({
|
||||
required this.rowData,
|
||||
required GridRowCache rowCache,
|
||||
}) : _rowCache = rowCache,
|
||||
super(RowDetailState.initial()) {
|
||||
on<RowDetailEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_Initial value) async {
|
||||
await _startListening();
|
||||
_loadCellData();
|
||||
},
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) {
|
||||
emit(state.copyWith(cellDatas: value.cellDatas));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_rowListenFn != null) {
|
||||
_rowCache.removeRowListener(_rowListenFn!);
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_rowListenFn = _rowCache.addRowListener(
|
||||
rowId: rowData.rowId,
|
||||
onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
|
||||
listenWhen: () => !isClosed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadCellData() async {
|
||||
final data = _rowCache.loadCellData(rowData.rowId);
|
||||
data.foldRight(null, (cellDataMap, _) {
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowDetailEvent with _$RowDetailEvent {
|
||||
const factory RowDetailEvent.initial() = _Initial;
|
||||
const factory RowDetailEvent.didReceiveCellDatas(List<GridCell> cellDatas) = _DidReceiveCellDatas;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowDetailState with _$RowDetailState {
|
||||
const factory RowDetailState({
|
||||
required List<GridCell> cellDatas,
|
||||
}) = _RowDetailState;
|
||||
|
||||
factory RowDetailState.initial() => RowDetailState(
|
||||
cellDatas: List.empty(),
|
||||
);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
@ -8,43 +9,42 @@ import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
|
||||
|
||||
part 'row_service.freezed.dart';
|
||||
|
||||
class RowsNotifier extends ChangeNotifier {
|
||||
List<GridRow> _rows = [];
|
||||
GridRowChangeReason _changeReason = const InitialListState();
|
||||
typedef RowUpdateCallback = void Function();
|
||||
typedef FieldDidUpdateCallback = void Function();
|
||||
typedef CellDataMap = LinkedHashMap<String, GridCell>;
|
||||
|
||||
void updateRows(List<GridRow> rows, GridRowChangeReason changeReason) {
|
||||
_rows = rows;
|
||||
_changeReason = changeReason;
|
||||
|
||||
changeReason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
|
||||
List<GridRow> get rows => _rows;
|
||||
abstract class GridRowDataDelegate {
|
||||
UnmodifiableListView<Field> get fields;
|
||||
GridRow buildGridRow(RowOrder rowOrder);
|
||||
CellDataMap buildCellDataMap(Row rowData);
|
||||
void onFieldChanged(FieldDidUpdateCallback callback);
|
||||
}
|
||||
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final RowsNotifier _rowNotifier;
|
||||
final GridRowListener _rowsListener;
|
||||
final RowsNotifier _rowNotifier = RowsNotifier();
|
||||
final HashMap<String, Row> _rowDataMap = HashMap();
|
||||
UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
|
||||
final GridRowDataDelegate _dataDelegate;
|
||||
|
||||
GridRowCache({required this.gridId}) : _rowsListener = GridRowListener(gridId: gridId) {
|
||||
List<GridRow> get clonedRows => _rowNotifier.clonedRows;
|
||||
|
||||
GridRowCache({required this.gridId, required GridRowDataDelegate dataDelegate})
|
||||
: _rowNotifier = RowsNotifier(rowBuilder: dataDelegate.buildGridRow),
|
||||
_rowsListener = GridRowListener(gridId: gridId),
|
||||
_dataDelegate = dataDelegate {
|
||||
//
|
||||
dataDelegate.onFieldChanged(() => _rowNotifier.fieldDidChange());
|
||||
|
||||
// listen on the row update
|
||||
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
|
||||
result.fold(
|
||||
(changesets) {
|
||||
for (final changeset in changesets) {
|
||||
_deleteRows(changeset.deletedRows);
|
||||
_insertRows(changeset.insertedRows);
|
||||
_updateRows(changeset.updatedRows);
|
||||
_rowNotifier.deleteRows(changeset.deletedRows);
|
||||
_rowNotifier.insertRows(changeset.insertedRows);
|
||||
_rowNotifier.updateRows(changeset.updatedRows);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
@ -58,29 +58,29 @@ class GridRowCache {
|
||||
_rowNotifier.dispose();
|
||||
}
|
||||
|
||||
List<GridRow> get clonedRows => [..._rowNotifier.rows];
|
||||
|
||||
void addListener({
|
||||
void Function(List<GridRow>, GridRowChangeReason)? onChanged,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
_rowNotifier.addListener(() {
|
||||
if (onChanged == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onChanged != null) {
|
||||
onChanged(clonedRows, _rowNotifier._changeReason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
VoidCallback addRowListener({
|
||||
RowUpdateCallback addRowListener({
|
||||
required String rowId,
|
||||
void Function(Row)? onUpdated,
|
||||
void Function(CellDataMap)? onUpdated,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
f() {
|
||||
listenrHandler() {
|
||||
if (onUpdated == null) {
|
||||
return;
|
||||
}
|
||||
@ -89,67 +89,83 @@ class GridRowCache {
|
||||
return;
|
||||
}
|
||||
|
||||
_rowNotifier._changeReason.whenOrNull(update: (indexs) {
|
||||
final row = _rowDataMap[rowId];
|
||||
if (indexs[rowId] != null && row != null) {
|
||||
onUpdated(row);
|
||||
notify() {
|
||||
final row = _rowNotifier.rowDataWithId(rowId);
|
||||
if (row != null) {
|
||||
final cellDataMap = _dataDelegate.buildCellDataMap(row);
|
||||
onUpdated(cellDataMap);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_rowNotifier.addListener(f);
|
||||
return f;
|
||||
_rowNotifier._changeReason.whenOrNull(
|
||||
update: (indexs) {
|
||||
if (indexs[rowId] != null) {
|
||||
notify();
|
||||
}
|
||||
},
|
||||
fieldDidChange: () => notify(),
|
||||
);
|
||||
}
|
||||
|
||||
_rowNotifier.addListener(listenrHandler);
|
||||
return listenrHandler;
|
||||
}
|
||||
|
||||
void removeRowListener(VoidCallback callback) {
|
||||
_rowNotifier.removeListener(callback);
|
||||
}
|
||||
|
||||
Future<Option<Row>> getRowData(String rowId) async {
|
||||
final Row? data = _rowDataMap[rowId];
|
||||
Option<CellDataMap> loadCellData(String rowId) {
|
||||
final Row? data = _rowNotifier.rowDataWithId(rowId);
|
||||
if (data != null) {
|
||||
return Future(() => Some(data));
|
||||
return Some(_dataDelegate.buildCellDataMap(data));
|
||||
}
|
||||
|
||||
final payload = RowIdentifierPayload.create()
|
||||
..gridId = gridId
|
||||
..rowId = rowId;
|
||||
|
||||
final result = await GridEventGetRow(payload).send();
|
||||
return Future(() {
|
||||
return result.fold(
|
||||
(data) {
|
||||
data.freeze();
|
||||
_rowDataMap[data.id] = data;
|
||||
return Some(data);
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return none();
|
||||
},
|
||||
GridEventGetRow(payload).send().then((result) {
|
||||
result.fold(
|
||||
(rowData) => _rowNotifier.rowData = rowData,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
return none();
|
||||
}
|
||||
|
||||
void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
|
||||
_fields = fields;
|
||||
final newRows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
|
||||
return GridRow.fromBlockRow(gridId, rowOrder, _fields);
|
||||
}).toList();
|
||||
void updateWithBlock(List<GridBlockOrder> blocks) {
|
||||
final rowOrders = blocks.expand((block) => block.rowOrders).toList();
|
||||
_rowNotifier.reset(rowOrders);
|
||||
}
|
||||
}
|
||||
|
||||
_rowNotifier.updateRows(newRows, const GridRowChangeReason.initial());
|
||||
class RowsNotifier extends ChangeNotifier {
|
||||
List<GridRow> _rows = [];
|
||||
HashMap<String, Row> _rowDataMap = HashMap();
|
||||
GridRowChangeReason _changeReason = const InitialListState();
|
||||
final GridRow Function(RowOrder) rowBuilder;
|
||||
|
||||
RowsNotifier({
|
||||
required this.rowBuilder,
|
||||
});
|
||||
|
||||
void reset(List<RowOrder> rowOrders) {
|
||||
_rowDataMap = HashMap();
|
||||
final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList();
|
||||
_update(rows, const GridRowChangeReason.initial());
|
||||
}
|
||||
|
||||
void _deleteRows(List<RowOrder> deletedRows) {
|
||||
void deleteRows(List<RowOrder> deletedRows) {
|
||||
if (deletedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<GridRow> newRows = [];
|
||||
final DeletedIndexs deletedIndex = [];
|
||||
final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
|
||||
final Map<String, RowOrder> deletedRowMap = {for (var e in deletedRows) e.rowId: e};
|
||||
|
||||
_rowNotifier.rows.asMap().forEach((index, row) {
|
||||
_rows.asMap().forEach((index, row) {
|
||||
if (deletedRowMap[row.rowId] == null) {
|
||||
newRows.add(row);
|
||||
} else {
|
||||
@ -157,48 +173,93 @@ class GridRowCache {
|
||||
}
|
||||
});
|
||||
|
||||
_rowNotifier.updateRows(newRows, GridRowChangeReason.delete(deletedIndex));
|
||||
_update(newRows, GridRowChangeReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void _insertRows(List<IndexRowOrder> createdRows) {
|
||||
void insertRows(List<IndexRowOrder> createdRows) {
|
||||
if (createdRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
final List<GridRow> newRows = _rowNotifier.rows;
|
||||
final List<GridRow> newRows = clonedRows;
|
||||
for (final createdRow in createdRows) {
|
||||
final gridRow = GridRow.fromBlockRow(gridId, createdRow.rowOrder, _fields);
|
||||
insertIndexs.add(
|
||||
InsertedIndex(
|
||||
final insertIndex = InsertedIndex(
|
||||
index: createdRow.index,
|
||||
rowId: gridRow.rowId,
|
||||
),
|
||||
rowId: createdRow.rowOrder.rowId,
|
||||
);
|
||||
newRows.insert(createdRow.index, gridRow);
|
||||
insertIndexs.add(insertIndex);
|
||||
newRows.insert(createdRow.index, (rowBuilder(createdRow.rowOrder)));
|
||||
}
|
||||
_rowNotifier.updateRows(newRows, GridRowChangeReason.insert(insertIndexs));
|
||||
_update(newRows, GridRowChangeReason.insert(insertIndexs));
|
||||
}
|
||||
|
||||
void _updateRows(List<RowOrder> updatedRows) {
|
||||
void updateRows(List<RowOrder> updatedRows) {
|
||||
if (updatedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
final List<GridRow> newRows = _rowNotifier.rows;
|
||||
final List<GridRow> newRows = clonedRows;
|
||||
for (final rowOrder in updatedRows) {
|
||||
final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
|
||||
if (index != -1) {
|
||||
newRows.removeAt(index);
|
||||
newRows.insert(index, GridRow.fromBlockRow(gridId, rowOrder, _fields));
|
||||
// Remove the old row data, the data will be filled if the loadRow method gets called.
|
||||
_rowDataMap.remove(rowOrder.rowId);
|
||||
|
||||
newRows.removeAt(index);
|
||||
newRows.insert(index, rowBuilder(rowOrder));
|
||||
updatedIndexs[rowOrder.rowId] = UpdatedIndex(index: index, rowId: rowOrder.rowId);
|
||||
}
|
||||
}
|
||||
|
||||
_rowNotifier.updateRows(newRows, GridRowChangeReason.update(updatedIndexs));
|
||||
_update(newRows, GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
|
||||
void fieldDidChange() {
|
||||
_update(_rows, const GridRowChangeReason.fieldDidChange());
|
||||
}
|
||||
|
||||
void _update(List<GridRow> rows, GridRowChangeReason reason) {
|
||||
_rows = rows;
|
||||
_changeReason = reason;
|
||||
|
||||
_changeReason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
|
||||
set rowData(Row rowData) {
|
||||
rowData.freeze();
|
||||
|
||||
_rowDataMap[rowData.id] = rowData;
|
||||
final index = _rows.indexWhere((row) => row.rowId == rowData.id);
|
||||
if (index != -1) {
|
||||
// update the corresponding row in _rows if they are not the same
|
||||
if (_rows[index].data != rowData) {
|
||||
final row = _rows.removeAt(index).copyWith(data: rowData);
|
||||
_rows.insert(index, row);
|
||||
|
||||
// Calculate the update index
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId);
|
||||
_changeReason = GridRowChangeReason.update(updatedIndexs);
|
||||
|
||||
//
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row? rowDataWithId(String rowId) {
|
||||
return _rowDataMap[rowId];
|
||||
}
|
||||
|
||||
List<GridRow> get clonedRows => [..._rows];
|
||||
}
|
||||
|
||||
class RowService {
|
||||
@ -251,16 +312,6 @@ class RowService {
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridCellIdentifier with _$GridCellIdentifier {
|
||||
const factory GridCellIdentifier({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required Field field,
|
||||
Cell? cell,
|
||||
}) = _CellData;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridRow with _$GridRow {
|
||||
const factory GridRow({
|
||||
@ -268,27 +319,36 @@ class GridRow with _$GridRow {
|
||||
required String rowId,
|
||||
required List<Field> fields,
|
||||
required double height,
|
||||
required Future<Option<Row>> data,
|
||||
Row? data,
|
||||
}) = _GridRow;
|
||||
}
|
||||
|
||||
factory GridRow.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
|
||||
return GridRow(
|
||||
gridId: gridId,
|
||||
fields: fields,
|
||||
rowId: row.rowId,
|
||||
data: Future(() => none()),
|
||||
height: row.height.toDouble(),
|
||||
);
|
||||
}
|
||||
@freezed
|
||||
class GridCell with _$GridCell {
|
||||
const factory GridCell({
|
||||
required String gridId,
|
||||
required String rowId,
|
||||
required Field field,
|
||||
Cell? cell,
|
||||
}) = _GridCell;
|
||||
}
|
||||
|
||||
typedef InsertedIndexs = List<InsertedIndex>;
|
||||
typedef DeletedIndexs = List<DeletedIndex>;
|
||||
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
|
||||
|
||||
@freezed
|
||||
class GridRowChangeReason with _$GridRowChangeReason {
|
||||
const factory GridRowChangeReason.insert(InsertedIndexs items) = _Insert;
|
||||
const factory GridRowChangeReason.delete(DeletedIndexs items) = _Delete;
|
||||
const factory GridRowChangeReason.update(UpdatedIndexs indexs) = _Update;
|
||||
const factory GridRowChangeReason.fieldDidChange() = _FieldDidChange;
|
||||
const factory GridRowChangeReason.initial() = InitialListState;
|
||||
}
|
||||
|
||||
class InsertedIndex {
|
||||
int index;
|
||||
String rowId;
|
||||
final int index;
|
||||
final String rowId;
|
||||
InsertedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
@ -296,8 +356,8 @@ class InsertedIndex {
|
||||
}
|
||||
|
||||
class DeletedIndex {
|
||||
int index;
|
||||
GridRow row;
|
||||
final int index;
|
||||
final GridRow row;
|
||||
DeletedIndex({
|
||||
required this.index,
|
||||
required this.row,
|
||||
@ -305,18 +365,10 @@ class DeletedIndex {
|
||||
}
|
||||
|
||||
class UpdatedIndex {
|
||||
int index;
|
||||
String rowId;
|
||||
final int index;
|
||||
final String rowId;
|
||||
UpdatedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridRowChangeReason with _$GridRowChangeReason {
|
||||
const factory GridRowChangeReason.insert(InsertedIndexs items) = _Insert;
|
||||
const factory GridRowChangeReason.delete(DeletedIndexs items) = _Delete;
|
||||
const factory GridRowChangeReason.update(UpdatedIndexs indexs) = _Update;
|
||||
const factory GridRowChangeReason.initial() = InitialListState;
|
||||
}
|
||||
|
@ -82,16 +82,14 @@ class CreateItem extends StatelessWidget {
|
||||
|
||||
return FlowyHover(
|
||||
style: config,
|
||||
builder: (context, onHover) {
|
||||
return GestureDetector(
|
||||
child: GestureDetector(
|
||||
onTap: () => onSelected(pluginBuilder),
|
||||
child: FlowyText.medium(
|
||||
pluginBuilder.menuName,
|
||||
color: theme.textColor,
|
||||
fontSize: 12,
|
||||
).padding(horizontal: 10, vertical: 6),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -95,9 +95,11 @@ class _MenuAppState extends State<MenuApp> {
|
||||
Widget _renderViewSection(AppDataNotifier notifier) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider.value(value: notifier)],
|
||||
child: Consumer(builder: (context, AppDataNotifier notifier, child) {
|
||||
child: Consumer(
|
||||
builder: (context, AppDataNotifier notifier, child) {
|
||||
return ViewSection(appData: notifier);
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
||||
@ -214,27 +213,24 @@ class _GridRowsState extends State<_GridRows> {
|
||||
key: _key,
|
||||
initialItemCount: context.read<GridBloc>().state.rows.length,
|
||||
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
||||
final rowData = context.read<GridBloc>().state.rows[index];
|
||||
return _renderRow(context, rowData, animation);
|
||||
return _renderRow(context, state.rows[index], animation);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderRow(BuildContext context, GridRow rowData, Animation<double> animation) {
|
||||
final bloc = context.read<GridBloc>();
|
||||
final fieldCache = bloc.fieldCache;
|
||||
final rowCache = bloc.rowCache;
|
||||
|
||||
Widget _renderRow(
|
||||
BuildContext context,
|
||||
GridRow rowData,
|
||||
Animation<double> animation,
|
||||
) {
|
||||
final rowCache = context.read<GridBloc>().rowCache;
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: GridRowWidget(
|
||||
blocBuilder: () => RowBloc(
|
||||
rowData: rowData,
|
||||
fieldCache: fieldCache,
|
||||
rowCache: rowCache,
|
||||
),
|
||||
key: ValueKey(rowData.rowId),
|
||||
),
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'checkbox_cell.dart';
|
||||
@ -7,7 +8,7 @@ import 'number_cell.dart';
|
||||
import 'selection_cell/selection_cell.dart';
|
||||
import 'text_cell.dart';
|
||||
|
||||
Widget buildGridCell(GridCellIdentifier cellData) {
|
||||
GridCellWidget buildGridCell(GridCell cellData, {GridCellStyle? style}) {
|
||||
final key = ValueKey(cellData.field.id + cellData.rowId);
|
||||
switch (cellData.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
@ -19,7 +20,7 @@ Widget buildGridCell(GridCellIdentifier cellData) {
|
||||
case FieldType.Number:
|
||||
return NumberCell(cellData: cellData, key: key);
|
||||
case FieldType.RichText:
|
||||
return GridTextCell(cellData: cellData, key: key);
|
||||
return GridTextCell(cellData: cellData, key: key, style: style);
|
||||
case FieldType.SingleSelect:
|
||||
return SingleSelectCell(cellData: cellData, key: key);
|
||||
default:
|
||||
@ -35,3 +36,11 @@ class BlankCell extends StatelessWidget {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GridCellWidget extends HoverWidget {
|
||||
@override
|
||||
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
|
||||
GridCellWidget({Key? key}) : super(key: key);
|
||||
}
|
||||
|
||||
abstract class GridCellStyle {}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class CellStateNotifier extends ChangeNotifier {
|
||||
bool _isFocus = false;
|
||||
bool _onEnter = false;
|
||||
|
||||
set isFocus(bool value) {
|
||||
if (_isFocus != value) {
|
||||
@ -14,38 +15,59 @@ class CellStateNotifier extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
set onEnter(bool value) {
|
||||
if (_onEnter != value) {
|
||||
_onEnter = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isFocus => _isFocus;
|
||||
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
||||
|
||||
class CellContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final GridCellWidget child;
|
||||
final Widget? expander;
|
||||
final double width;
|
||||
const CellContainer({
|
||||
Key? key,
|
||||
required this.child,
|
||||
required this.width,
|
||||
this.expander,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => CellStateNotifier(),
|
||||
child: Consumer<CellStateNotifier>(
|
||||
builder: (context, state, _) {
|
||||
child: Selector<CellStateNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.isFocus,
|
||||
builder: (context, isFocus, _) {
|
||||
Widget container = Center(child: child);
|
||||
child.onFocus.addListener(() {
|
||||
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
|
||||
});
|
||||
|
||||
if (expander != null) {
|
||||
container = _CellEnterRegion(child: container, expander: expander!);
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxWidth: width),
|
||||
decoration: _makeBoxDecoration(context, state),
|
||||
decoration: _makeBoxDecoration(context, isFocus),
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: Center(child: child),
|
||||
child: container,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BoxDecoration _makeBoxDecoration(BuildContext context, CellStateNotifier state) {
|
||||
BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
if (state.isFocus) {
|
||||
if (isFocus) {
|
||||
final borderSide = BorderSide(color: theme.main1, width: 1.0);
|
||||
return BoxDecoration(border: Border.fromBorderSide(borderSide));
|
||||
} else {
|
||||
@ -55,34 +77,31 @@ class CellContainer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GridCell extends StatefulWidget {
|
||||
const GridCell({Key? key}) : super(key: key);
|
||||
|
||||
void setFocus(BuildContext context, bool value) {
|
||||
Provider.of<CellStateNotifier>(context, listen: false).isFocus = value;
|
||||
}
|
||||
}
|
||||
|
||||
class CellFocusNode extends FocusNode {
|
||||
VoidCallback? focusCallback;
|
||||
|
||||
void addCallback(BuildContext context, VoidCallback callback) {
|
||||
if (focusCallback != null) {
|
||||
removeListener(focusCallback!);
|
||||
}
|
||||
focusCallback = () {
|
||||
Provider.of<CellStateNotifier>(context, listen: false).isFocus = hasFocus;
|
||||
callback();
|
||||
};
|
||||
|
||||
addListener(focusCallback!);
|
||||
}
|
||||
class _CellEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Widget expander;
|
||||
const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (focusCallback != null) {
|
||||
removeListener(focusCallback!);
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<CellStateNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.onEnter,
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [Expanded(child: child)];
|
||||
if (onEnter) {
|
||||
children.add(expander);
|
||||
}
|
||||
super.dispose();
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true,
|
||||
onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false,
|
||||
child: Row(
|
||||
// alignment: AlignmentDirectional.centerEnd,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class CheckboxCell extends StatefulWidget {
|
||||
final GridCellIdentifier cellData;
|
||||
class CheckboxCell extends GridCellWidget {
|
||||
final GridCell cellData;
|
||||
|
||||
const CheckboxCell({
|
||||
CheckboxCell({
|
||||
required this.cellData,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
@ -1,17 +1,22 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class DateCell extends GridCell {
|
||||
final GridCellIdentifier cellData;
|
||||
abstract class GridCellDelegate {
|
||||
void onFocus(bool isFocus);
|
||||
GridCellDelegate get delegate;
|
||||
}
|
||||
|
||||
const DateCell({
|
||||
class DateCell extends GridCellWidget {
|
||||
final GridCell cellData;
|
||||
|
||||
DateCell({
|
||||
required this.cellData,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -39,13 +44,13 @@ class _DateCellState extends State<DateCell> {
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
widget.setFocus(context, true);
|
||||
widget.onFocus.value = true;
|
||||
_CellCalendar.show(
|
||||
context,
|
||||
onSelected: (day) {
|
||||
context.read<DateCellBloc>().add(DateCellEvent.selectDay(day));
|
||||
},
|
||||
onDismissed: () => widget.setFocus(context, false),
|
||||
onDismissed: () => widget.onFocus.value = false,
|
||||
);
|
||||
},
|
||||
child: MouseRegion(
|
||||
|
@ -2,14 +2,15 @@ import 'dart:async';
|
||||
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NumberCell extends GridCell {
|
||||
final GridCellIdentifier cellData;
|
||||
import 'cell_builder.dart';
|
||||
|
||||
const NumberCell({
|
||||
class NumberCell extends GridCellWidget {
|
||||
final GridCell cellData;
|
||||
|
||||
NumberCell({
|
||||
required this.cellData,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -21,21 +22,23 @@ class NumberCell extends GridCell {
|
||||
class _NumberCellState extends State<NumberCell> {
|
||||
late NumberCellBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
late CellFocusNode _focusNode;
|
||||
late FocusNode _focusNode;
|
||||
Timer? _delayOperation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData)..add(const NumberCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
_focusNode = CellFocusNode();
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(() {
|
||||
widget.onFocus.value = _focusNode.hasFocus;
|
||||
focusChanged();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_focusNode.addCallback(context, focusChanged);
|
||||
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocConsumer<NumberCellBloc, NumberCellState>(
|
||||
|
@ -1,16 +1,16 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'extension.dart';
|
||||
import 'selection_editor.dart';
|
||||
|
||||
class SingleSelectCell extends GridCell {
|
||||
final GridCellIdentifier cellData;
|
||||
class SingleSelectCell extends GridCellWidget {
|
||||
final GridCell cellData;
|
||||
|
||||
const SingleSelectCell({
|
||||
SingleSelectCell({
|
||||
required this.cellData,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -38,16 +38,16 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||
return SizedBox.expand(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
widget.setFocus(context, true);
|
||||
widget.onFocus.value = true;
|
||||
SelectOptionCellEditor.show(
|
||||
context,
|
||||
state.cellData,
|
||||
state.options,
|
||||
state.selectedOptions,
|
||||
() => widget.setFocus(context, false),
|
||||
() => widget.onFocus.value = false,
|
||||
);
|
||||
},
|
||||
child: Row(children: children),
|
||||
child: ClipRRect(child: Row(children: children)),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -63,10 +63,10 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
class MultiSelectCell extends GridCell {
|
||||
final GridCellIdentifier cellData;
|
||||
class MultiSelectCell extends GridCellWidget {
|
||||
final GridCell cellData;
|
||||
|
||||
const MultiSelectCell({
|
||||
MultiSelectCell({
|
||||
required this.cellData,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -94,16 +94,16 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
||||
return SizedBox.expand(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
widget.setFocus(context, true);
|
||||
widget.onFocus.value = true;
|
||||
SelectOptionCellEditor.show(
|
||||
context,
|
||||
state.cellData,
|
||||
state.options,
|
||||
state.selectedOptions,
|
||||
() => widget.setFocus(context, false),
|
||||
() => widget.onFocus.value = false,
|
||||
);
|
||||
},
|
||||
child: Row(children: children),
|
||||
child: ClipRRect(child: Row(children: children)),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ import 'text_field.dart';
|
||||
const double _editorPannelWidth = 300;
|
||||
|
||||
class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
||||
final GridCellIdentifier cellData;
|
||||
final GridCell cellData;
|
||||
final List<SelectOption> options;
|
||||
final List<SelectOption> selectedOptions;
|
||||
final VoidCallback onDismissed;
|
||||
@ -66,7 +66,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
||||
|
||||
static void show(
|
||||
BuildContext context,
|
||||
GridCellIdentifier cellData,
|
||||
GridCell cellData,
|
||||
List<SelectOption> options,
|
||||
List<SelectOption> selectedOptions,
|
||||
VoidCallback onDismissed,
|
||||
|
@ -1,16 +1,32 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'cell_container.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'cell_builder.dart';
|
||||
|
||||
class GridTextCell extends GridCell {
|
||||
final GridCellIdentifier cellData;
|
||||
const GridTextCell({
|
||||
class GridTextCellStyle extends GridCellStyle {
|
||||
String? placeholder;
|
||||
|
||||
GridTextCellStyle({
|
||||
this.placeholder,
|
||||
});
|
||||
}
|
||||
|
||||
class GridTextCell extends GridCellWidget {
|
||||
final GridCell cellData;
|
||||
late final GridTextCellStyle? cellStyle;
|
||||
GridTextCell({
|
||||
required this.cellData,
|
||||
GridCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
}) : super(key: key) {
|
||||
if (style != null) {
|
||||
cellStyle = (style as GridTextCellStyle);
|
||||
} else {
|
||||
cellStyle = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
State<GridTextCell> createState() => _GridTextCellState();
|
||||
@ -19,21 +35,25 @@ class GridTextCell extends GridCell {
|
||||
class _GridTextCellState extends State<GridTextCell> {
|
||||
late TextCellBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
late CellFocusNode _focusNode;
|
||||
late FocusNode _focusNode;
|
||||
|
||||
Timer? _delayOperation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = getIt<TextCellBloc>(param1: widget.cellData);
|
||||
_cellBloc.add(const TextCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
_focusNode = CellFocusNode();
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(() {
|
||||
widget.onFocus.value = _focusNode.hasFocus;
|
||||
focusChanged();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_focusNode.addCallback(context, focusChanged);
|
||||
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocConsumer<TextCellBloc, TextCellState>(
|
||||
@ -42,6 +62,7 @@ class _GridTextCellState extends State<GridTextCell> {
|
||||
_controller.text = state.content;
|
||||
}
|
||||
},
|
||||
buildWhen: (previous, current) => previous.content != current.content,
|
||||
builder: (context, state) {
|
||||
return TextField(
|
||||
controller: _controller,
|
||||
@ -50,9 +71,10 @@ class _GridTextCellState extends State<GridTextCell> {
|
||||
onEditingComplete: () => _focusNode.unfocus(),
|
||||
maxLines: 1,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: InputBorder.none,
|
||||
hintText: widget.cellStyle?.placeholder,
|
||||
isDense: true,
|
||||
),
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'field_type_extension.dart';
|
||||
@ -20,21 +21,21 @@ class GridFieldCell extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
|
||||
child: BlocBuilder<FieldCellBloc, FieldCellState>(
|
||||
builder: (context, state) {
|
||||
final button = FlowyButton(
|
||||
hoverColor: theme.shader6,
|
||||
final button = FieldCellButton(
|
||||
field: state.field,
|
||||
onTap: () => _showActionSheet(context),
|
||||
leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
|
||||
text: FlowyText.medium(state.field.name, fontSize: 12),
|
||||
padding: GridSize.cellContentInsets,
|
||||
);
|
||||
|
||||
const line = Positioned(top: 0, bottom: 0, right: 0, child: _DragToExpandLine());
|
||||
const line = Positioned(
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: _DragToExpandLine(),
|
||||
);
|
||||
|
||||
return _CellContainer(
|
||||
width: state.field.width.toDouble(),
|
||||
@ -125,9 +126,31 @@ class _DragToExpandLine extends StatelessWidget {
|
||||
borderRadius: BorderRadius.zero,
|
||||
contentMargin: const EdgeInsets.only(left: 5),
|
||||
),
|
||||
builder: (_, onHover) => const SizedBox(width: 2),
|
||||
child: const SizedBox(width: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FieldCellButton extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
final Field field;
|
||||
const FieldCellButton({
|
||||
required this.field,
|
||||
required this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return FlowyButton(
|
||||
hoverColor: theme.shader6,
|
||||
onTap: onTap,
|
||||
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
|
||||
text: FlowyText.medium(field.name, fontSize: 12),
|
||||
padding: GridSize.cellContentInsets,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -90,16 +90,6 @@ class _FieldOperationList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final actions = FieldAction.values
|
||||
.map(
|
||||
(action) => FieldActionCell(
|
||||
fieldId: fieldData.field.id,
|
||||
action: action,
|
||||
onTap: onDismissed,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return GridView(
|
||||
// https://api.flutter.dev/flutter/widgets/AnimatedList/shrinkWrap.html
|
||||
shrinkWrap: true,
|
||||
@ -108,20 +98,44 @@ class _FieldOperationList extends StatelessWidget {
|
||||
childAspectRatio: 4.0,
|
||||
mainAxisSpacing: 8,
|
||||
),
|
||||
children: actions,
|
||||
children: buildCells(),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> buildCells() {
|
||||
return FieldAction.values.map(
|
||||
(action) {
|
||||
bool enable = true;
|
||||
switch (action) {
|
||||
case FieldAction.delete:
|
||||
enable = !fieldData.field.isPrimary;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return FieldActionCell(
|
||||
fieldId: fieldData.field.id,
|
||||
action: action,
|
||||
onTap: onDismissed,
|
||||
enable: enable,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldActionCell extends StatelessWidget {
|
||||
final String fieldId;
|
||||
final VoidCallback onTap;
|
||||
final FieldAction action;
|
||||
final bool enable;
|
||||
|
||||
const FieldActionCell({
|
||||
required this.fieldId,
|
||||
required this.action,
|
||||
required this.onTap,
|
||||
required this.enable,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -129,11 +143,13 @@ class FieldActionCell extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return FlowyButton(
|
||||
text: FlowyText.medium(action.title(), fontSize: 12),
|
||||
text: FlowyText.medium(action.title(), fontSize: 12, color: enable ? null : theme.shader4),
|
||||
hoverColor: theme.hover,
|
||||
onTap: () {
|
||||
if (enable) {
|
||||
action.run(context);
|
||||
onTap();
|
||||
}
|
||||
},
|
||||
leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
|
||||
);
|
||||
|
@ -2,12 +2,14 @@ import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'field_name_input.dart';
|
||||
import 'field_switcher.dart';
|
||||
|
||||
@ -70,7 +72,7 @@ class _FieldEditorWidget extends StatelessWidget {
|
||||
(field) => ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
const FlowyText.medium("Edit property", fontSize: 12),
|
||||
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
|
||||
const VSpace(10),
|
||||
const _FieldNameTextField(),
|
||||
const VSpace(10),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -14,11 +14,14 @@ import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pbserver.dart
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart';
|
||||
|
||||
import 'field_type_extension.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
import 'type_option/multi_select.dart';
|
||||
import 'type_option/number.dart';
|
||||
import 'type_option/single_select.dart';
|
||||
@ -58,11 +61,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
|
||||
},
|
||||
builder: (context, state) {
|
||||
List<Widget> children = [_switchFieldTypeButton(context, state.field)];
|
||||
final typeOptionWidget = _typeOptionWidget(
|
||||
context: context,
|
||||
field: state.field,
|
||||
data: state.typeOptionData,
|
||||
);
|
||||
final typeOptionWidget = _typeOptionWidget(context: context, state: state);
|
||||
|
||||
if (typeOptionWidget != null) {
|
||||
children.add(typeOptionWidget);
|
||||
@ -111,8 +110,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
|
||||
|
||||
Widget? _typeOptionWidget({
|
||||
required BuildContext context,
|
||||
required Field field,
|
||||
required TypeOptionData data,
|
||||
required FieldSwitchState state,
|
||||
}) {
|
||||
final overlayDelegate = TypeOptionOverlayDelegate(
|
||||
showOverlay: _showOverlay,
|
||||
@ -123,9 +121,14 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
|
||||
context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
|
||||
});
|
||||
|
||||
final typeOptionContext = TypeOptionContext(
|
||||
gridId: state.gridId,
|
||||
field: state.field,
|
||||
data: state.typeOptionData,
|
||||
);
|
||||
|
||||
final builder = _makeTypeOptionBuild(
|
||||
field: field,
|
||||
data: data,
|
||||
typeOptionContext: typeOptionContext,
|
||||
overlayDelegate: overlayDelegate,
|
||||
dataDelegate: dataDelegate,
|
||||
);
|
||||
@ -165,24 +168,23 @@ abstract class TypeOptionBuilder {
|
||||
}
|
||||
|
||||
TypeOptionBuilder _makeTypeOptionBuild({
|
||||
required Field field,
|
||||
required TypeOptionData data,
|
||||
required TypeOptionContext typeOptionContext,
|
||||
required TypeOptionOverlayDelegate overlayDelegate,
|
||||
required TypeOptionDataDelegate dataDelegate,
|
||||
}) {
|
||||
switch (field.fieldType) {
|
||||
switch (typeOptionContext.field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return CheckboxTypeOptionBuilder(data);
|
||||
return CheckboxTypeOptionBuilder(typeOptionContext.data);
|
||||
case FieldType.DateTime:
|
||||
return DateTypeOptionBuilder(data, overlayDelegate, dataDelegate);
|
||||
return DateTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
|
||||
case FieldType.SingleSelect:
|
||||
return SingleSelectTypeOptionBuilder(field.id, data, overlayDelegate, dataDelegate);
|
||||
return SingleSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
|
||||
case FieldType.MultiSelect:
|
||||
return MultiSelectTypeOptionBuilder(field.id, data, overlayDelegate, dataDelegate);
|
||||
return MultiSelectTypeOptionBuilder(typeOptionContext, overlayDelegate, dataDelegate);
|
||||
case FieldType.Number:
|
||||
return NumberTypeOptionBuilder(data, overlayDelegate, dataDelegate);
|
||||
return NumberTypeOptionBuilder(typeOptionContext.data, overlayDelegate, dataDelegate);
|
||||
case FieldType.RichText:
|
||||
return RichTextTypeOptionBuilder(data);
|
||||
return RichTextTypeOptionBuilder(typeOptionContext.data);
|
||||
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -11,13 +10,11 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
|
||||
final MultiSelectTypeOptionWidget _widget;
|
||||
|
||||
MultiSelectTypeOptionBuilder(
|
||||
String fieldId,
|
||||
TypeOptionData typeOptionData,
|
||||
TypeOptionContext typeOptionContext,
|
||||
TypeOptionOverlayDelegate overlayDelegate,
|
||||
TypeOptionDataDelegate dataDelegate,
|
||||
) : _widget = MultiSelectTypeOptionWidget(
|
||||
fieldId: fieldId,
|
||||
typeOption: MultiSelectTypeOption.fromBuffer(typeOptionData),
|
||||
typeOptionContext: typeOptionContext,
|
||||
overlayDelegate: overlayDelegate,
|
||||
dataDelegate: dataDelegate,
|
||||
);
|
||||
@ -27,13 +24,11 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder {
|
||||
}
|
||||
|
||||
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
final String fieldId;
|
||||
final MultiSelectTypeOption typeOption;
|
||||
final TypeOptionContext typeOptionContext;
|
||||
final TypeOptionOverlayDelegate overlayDelegate;
|
||||
final TypeOptionDataDelegate dataDelegate;
|
||||
const MultiSelectTypeOptionWidget({
|
||||
required this.fieldId,
|
||||
required this.typeOption,
|
||||
required this.typeOptionContext,
|
||||
required this.overlayDelegate,
|
||||
required this.dataDelegate,
|
||||
Key? key,
|
||||
@ -42,7 +37,7 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<MultiSelectTypeOptionBloc>(param1: typeOption, param2: fieldId),
|
||||
create: (context) => MultiSelectTypeOptionBloc(typeOptionContext),
|
||||
child: BlocConsumer<MultiSelectTypeOptionBloc, MultiSelectTypeOptionState>(
|
||||
listener: (context, state) {
|
||||
dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'field_option_pannel.dart';
|
||||
@ -10,13 +9,11 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
|
||||
final SingleSelectTypeOptionWidget _widget;
|
||||
|
||||
SingleSelectTypeOptionBuilder(
|
||||
String fieldId,
|
||||
TypeOptionData typeOptionData,
|
||||
TypeOptionContext typeOptionContext,
|
||||
TypeOptionOverlayDelegate overlayDelegate,
|
||||
TypeOptionDataDelegate dataDelegate,
|
||||
) : _widget = SingleSelectTypeOptionWidget(
|
||||
fieldId: fieldId,
|
||||
typeOption: SingleSelectTypeOption.fromBuffer(typeOptionData),
|
||||
typeOptionContext: typeOptionContext,
|
||||
dataDelegate: dataDelegate,
|
||||
overlayDelegate: overlayDelegate,
|
||||
);
|
||||
@ -26,13 +23,11 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder {
|
||||
}
|
||||
|
||||
class SingleSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
final String fieldId;
|
||||
final SingleSelectTypeOption typeOption;
|
||||
final TypeOptionContext typeOptionContext;
|
||||
final TypeOptionOverlayDelegate overlayDelegate;
|
||||
final TypeOptionDataDelegate dataDelegate;
|
||||
const SingleSelectTypeOptionWidget({
|
||||
required this.fieldId,
|
||||
required this.typeOption,
|
||||
required this.typeOptionContext,
|
||||
required this.dataDelegate,
|
||||
required this.overlayDelegate,
|
||||
Key? key,
|
||||
@ -41,7 +36,7 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<SingleSelectTypeOptionBloc>(param1: typeOption, param2: fieldId),
|
||||
create: (context) => SingleSelectTypeOptionBloc(typeOptionContext),
|
||||
child: BlocConsumer<SingleSelectTypeOptionBloc, SingleSelectTypeOptionState>(
|
||||
listener: (context, state) {
|
||||
dataDelegate.didUpdateTypeOptionData(state.typeOption.writeToBuffer());
|
||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NumberCell extends StatefulWidget {
|
||||
final GridCellIdentifier cellData;
|
||||
final GridCell cellData;
|
||||
|
||||
const NumberCell({
|
||||
required this.cellData,
|
||||
|
@ -7,14 +7,18 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'row_action_sheet.dart';
|
||||
import 'package:dartz/dartz.dart' show Option;
|
||||
|
||||
import 'row_detail.dart';
|
||||
|
||||
class GridRowWidget extends StatefulWidget {
|
||||
final RowBloc Function() blocBuilder;
|
||||
final GridRow rowData;
|
||||
final GridRowCache rowCache;
|
||||
|
||||
const GridRowWidget({
|
||||
required this.blocBuilder,
|
||||
required this.rowData,
|
||||
required this.rowCache,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -24,13 +28,14 @@ class GridRowWidget extends StatefulWidget {
|
||||
|
||||
class _GridRowWidgetState extends State<GridRowWidget> {
|
||||
late RowBloc _rowBloc;
|
||||
late _RegionStateNotifier _rowStateNotifier;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_rowBloc = widget.blocBuilder();
|
||||
_rowBloc = RowBloc(
|
||||
rowData: widget.rowData,
|
||||
rowCache: widget.rowCache,
|
||||
);
|
||||
_rowBloc.add(const RowEvent.initial());
|
||||
_rowStateNotifier = _RegionStateNotifier();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -38,40 +43,39 @@ class _GridRowWidgetState extends State<GridRowWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _rowBloc,
|
||||
child: ChangeNotifierProvider.value(
|
||||
value: _rowStateNotifier,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) => _rowStateNotifier.onEnter = true,
|
||||
onExit: (p) => _rowStateNotifier.onEnter = false,
|
||||
child: _RowEnterRegion(
|
||||
child: BlocBuilder<RowBloc, RowState>(
|
||||
buildWhen: (p, c) => p.rowData.height != c.rowData.height,
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: 42,
|
||||
child: Row(
|
||||
final children = [
|
||||
const _RowLeading(),
|
||||
_RowCells(onExpand: () => onExpandCell(context)),
|
||||
const _RowTrailing(),
|
||||
];
|
||||
|
||||
final child = Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: const [
|
||||
_RowLeading(),
|
||||
_RowCells(),
|
||||
_RowTrailing(),
|
||||
],
|
||||
),
|
||||
children: children,
|
||||
);
|
||||
|
||||
return SizedBox(height: 42, child: child);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_rowBloc.close();
|
||||
_rowStateNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void onExpandCell(BuildContext context) {
|
||||
final page = RowDetailPage(rowData: widget.rowData, rowCache: widget.rowCache);
|
||||
page.show(context);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowLeading extends StatelessWidget {
|
||||
@ -143,32 +147,41 @@ class _DeleteRowButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _RowCells extends StatelessWidget {
|
||||
const _RowCells({Key? key}) : super(key: key);
|
||||
final VoidCallback onExpand;
|
||||
const _RowCells({required this.onExpand, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RowBloc, RowState>(
|
||||
buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = state.cellDataMap.fold(() => [], _toCells);
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: children,
|
||||
children: _makeCells(state.cellDataMap),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _toCells(CellDataMap dataMap) {
|
||||
return dataMap.values.map(
|
||||
List<Widget> _makeCells(Option<CellDataMap> data) {
|
||||
return data.fold(
|
||||
() => [],
|
||||
(cellDataMap) => cellDataMap.values.map(
|
||||
(cellData) {
|
||||
Widget? expander;
|
||||
if (cellData.field.isPrimary) {
|
||||
expander = _CellExpander(onExpand: onExpand);
|
||||
}
|
||||
|
||||
return CellContainer(
|
||||
width: cellData.field.width.toDouble(),
|
||||
child: buildGridCell(cellData),
|
||||
expander: expander,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,3 +197,56 @@ class _RegionStateNotifier extends ChangeNotifier {
|
||||
|
||||
bool get onEnter => _onEnter;
|
||||
}
|
||||
|
||||
class _CellExpander extends StatelessWidget {
|
||||
final VoidCallback onExpand;
|
||||
const _CellExpander({required this.onExpand, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return FlowyIconButton(
|
||||
width: 20,
|
||||
onPressed: onExpand,
|
||||
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||
icon: svgWidget("grid/expander", color: theme.main1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowEnterRegion extends StatefulWidget {
|
||||
final Widget child;
|
||||
const _RowEnterRegion({required this.child, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_RowEnterRegion> createState() => _RowEnterRegionState();
|
||||
}
|
||||
|
||||
class _RowEnterRegionState extends State<_RowEnterRegion> {
|
||||
late _RegionStateNotifier _rowStateNotifier;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_rowStateNotifier = _RegionStateNotifier();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: _rowStateNotifier,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) => _rowStateNotifier.onEnter = true,
|
||||
onExit: (p) => _rowStateNotifier.onEnter = false,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_rowStateNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NumberCell extends StatefulWidget {
|
||||
final GridCellIdentifier cellData;
|
||||
final GridCell cellData;
|
||||
|
||||
const NumberCell({
|
||||
required this.cellData,
|
||||
|
@ -0,0 +1,157 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
|
||||
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
|
||||
final GridRow rowData;
|
||||
final GridRowCache rowCache;
|
||||
|
||||
const RowDetailPage({
|
||||
required this.rowData,
|
||||
required this.rowCache,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<RowDetailPage> createState() => _RowDetailPageState();
|
||||
|
||||
void show(BuildContext context) async {
|
||||
final window = await getWindowInfo();
|
||||
final size = Size(window.frame.size.width * 0.7, window.frame.size.height * 0.7);
|
||||
FlowyOverlay.of(context).insertWithRect(
|
||||
widget: OverlayContainer(
|
||||
child: this,
|
||||
constraints: BoxConstraints.tight(size),
|
||||
),
|
||||
identifier: RowDetailPage.identifier(),
|
||||
anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
|
||||
anchorSize: window.frame.size,
|
||||
anchorDirection: AnchorDirection.center,
|
||||
style: FlowyOverlayStyle(blur: false),
|
||||
delegate: this,
|
||||
);
|
||||
}
|
||||
|
||||
static String identifier() {
|
||||
return (RowDetailPage).toString();
|
||||
}
|
||||
}
|
||||
|
||||
class _RowDetailPageState extends State<RowDetailPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
final bloc = RowDetailBloc(rowData: widget.rowData, rowCache: widget.rowCache);
|
||||
bloc.add(const RowDetailEvent.initial());
|
||||
return bloc;
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 80, vertical: 40),
|
||||
child: _PropertyList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PropertyList extends StatelessWidget {
|
||||
const _PropertyList({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RowDetailBloc, RowDetailState>(
|
||||
buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
|
||||
builder: (context, state) {
|
||||
return ListView.separated(
|
||||
itemCount: state.cellDatas.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _RowDetailCell(cellData: state.cellDatas[index]);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const VSpace(2);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowDetailCell extends StatelessWidget {
|
||||
final GridCell cellData;
|
||||
const _RowDetailCell({required this.cellData, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
final cell = buildGridCell(
|
||||
cellData,
|
||||
style: _buildCellStyle(theme, cellData.field.fieldType),
|
||||
);
|
||||
return SizedBox(
|
||||
height: 36,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 150,
|
||||
child: FieldCellButton(field: cellData.field, onTap: () => _showFieldEditor(context)),
|
||||
),
|
||||
const HSpace(10),
|
||||
Expanded(
|
||||
child: FlowyHover2(
|
||||
child: cell,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showFieldEditor(BuildContext context) {
|
||||
FieldEditor(
|
||||
gridId: cellData.gridId,
|
||||
fieldContextLoader: FieldContextLoaderAdaptor(
|
||||
gridId: cellData.gridId,
|
||||
field: cellData.field,
|
||||
),
|
||||
).show(context);
|
||||
}
|
||||
}
|
||||
|
||||
GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
||||
switch (fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return null;
|
||||
case FieldType.DateTime:
|
||||
return null;
|
||||
case FieldType.MultiSelect:
|
||||
return null;
|
||||
case FieldType.Number:
|
||||
return null;
|
||||
case FieldType.RichText:
|
||||
return GridTextCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
@ -67,7 +67,7 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
|
||||
}
|
||||
|
||||
String identifier() {
|
||||
return toString();
|
||||
return (GridPropertyList).toString();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -86,8 +86,7 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
|
||||
|
||||
return FlowyHover(
|
||||
style: HoverStyle(hoverColor: theme.hover),
|
||||
builder: (context, onHover) {
|
||||
return GestureDetector(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => onSelected(action),
|
||||
child: SizedBox(
|
||||
@ -107,8 +106,7 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
|
||||
horizontal: ActionListSizes.padding,
|
||||
vertical: ActionListSizes.padding,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -207,8 +207,14 @@ class FlowyOverlayState extends State<FlowyOverlay> {
|
||||
|
||||
final reveredList = _overlayList.reversed.toList();
|
||||
final firstItem = reveredList.removeAt(0);
|
||||
firstItem.delegate?.didRemove();
|
||||
_overlayList.remove(firstItem);
|
||||
if (firstItem.delegate != null) {
|
||||
firstItem.delegate!.didRemove();
|
||||
|
||||
if (firstItem.delegate!.asBarrier()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (final element in reveredList) {
|
||||
if (element.delegate?.asBarrier() ?? false) {
|
||||
@ -286,27 +292,23 @@ class FlowyOverlayState extends State<FlowyOverlay> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final overlays = _overlayList.map((item) => item.widget);
|
||||
List<Widget> children = <Widget>[widget.child];
|
||||
|
||||
Widget? child;
|
||||
if (overlays.isNotEmpty) {
|
||||
child = Container(
|
||||
final overlays = _overlayList.map((item) {
|
||||
var widget = item.widget;
|
||||
if (item.delegate?.asBarrier() ?? false) {
|
||||
widget = Container(
|
||||
color: style.barrierColor,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _handleTapOnBackground,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
|
||||
if (style.blur) {
|
||||
child = BackdropFilter(
|
||||
child: child,
|
||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||
);
|
||||
}
|
||||
}
|
||||
return widget;
|
||||
}).toList();
|
||||
|
||||
List<Widget> children = <Widget>[widget.child];
|
||||
Widget? child = _renderBackground(overlays);
|
||||
if (child != null) {
|
||||
children.add(child);
|
||||
}
|
||||
@ -335,4 +337,25 @@ class FlowyOverlayState extends State<FlowyOverlay> {
|
||||
void _handleTapOnBackground() {
|
||||
removeAll();
|
||||
}
|
||||
|
||||
Widget? _renderBackground(List<Widget> overlays) {
|
||||
Widget? child;
|
||||
if (overlays.isNotEmpty) {
|
||||
child = Container(
|
||||
color: style.barrierColor,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _handleTapOnBackground,
|
||||
),
|
||||
);
|
||||
|
||||
if (style.blur) {
|
||||
child = BackdropFilter(
|
||||
child: child,
|
||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||
);
|
||||
}
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
// ignore: unused_import
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
|
||||
|
||||
class FlowyHover extends StatefulWidget {
|
||||
final HoverStyle style;
|
||||
final HoverBuilder builder;
|
||||
final HoverBuilder? builder;
|
||||
final Widget? child;
|
||||
final bool Function()? setSelected;
|
||||
|
||||
const FlowyHover({
|
||||
Key? key,
|
||||
required this.builder,
|
||||
this.builder,
|
||||
this.child,
|
||||
required this.style,
|
||||
this.setSelected,
|
||||
}) : super(key: key);
|
||||
@ -27,25 +32,27 @@ class _FlowyHoverState extends State<FlowyHover> {
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
opaque: false,
|
||||
onEnter: (p) => setState(() => _onHover = true),
|
||||
onExit: (p) => setState(() => _onHover = false),
|
||||
child: render(),
|
||||
child: renderWidget(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget render() {
|
||||
Widget renderWidget() {
|
||||
var showHover = _onHover;
|
||||
if (!showHover && widget.setSelected != null) {
|
||||
showHover = widget.setSelected!();
|
||||
}
|
||||
|
||||
final child = widget.child ?? widget.builder!(context, _onHover);
|
||||
if (showHover) {
|
||||
return FlowyHoverContainer(
|
||||
style: widget.style,
|
||||
child: widget.builder(context, _onHover),
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return widget.builder(context, _onHover);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,11 +74,11 @@ class HoverStyle {
|
||||
|
||||
class FlowyHoverContainer extends StatelessWidget {
|
||||
final HoverStyle style;
|
||||
final Widget child;
|
||||
final Widget? child;
|
||||
|
||||
const FlowyHoverContainer({
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.child,
|
||||
required this.style,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -93,3 +100,112 @@ class FlowyHoverContainer extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
abstract class HoverWidget extends StatefulWidget {
|
||||
const HoverWidget({Key? key}) : super(key: key);
|
||||
|
||||
ValueNotifier<bool> get onFocus;
|
||||
}
|
||||
|
||||
class FlowyHover2 extends StatefulWidget {
|
||||
final HoverWidget child;
|
||||
final EdgeInsets contentPadding;
|
||||
const FlowyHover2({
|
||||
required this.child,
|
||||
this.contentPadding = EdgeInsets.zero,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<FlowyHover2> createState() => _FlowyHover2State();
|
||||
}
|
||||
|
||||
class _FlowyHover2State extends State<FlowyHover2> {
|
||||
late FlowyHoverState _hoverState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_hoverState = FlowyHoverState();
|
||||
widget.child.onFocus.addListener(() {
|
||||
_hoverState.onFocus = widget.child.onFocus.value;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hoverState.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: _hoverState,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
opaque: false,
|
||||
onEnter: (p) => setState(() => _hoverState.onHover = true),
|
||||
onExit: (p) => setState(() => _hoverState.onHover = false),
|
||||
child: Stack(
|
||||
fit: StackFit.loose,
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
const _HoverBackground(),
|
||||
Padding(
|
||||
padding: widget.contentPadding,
|
||||
child: widget.child,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HoverBackground extends StatelessWidget {
|
||||
const _HoverBackground({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Consumer<FlowyHoverState>(
|
||||
builder: (context, state, child) {
|
||||
if (state.onHover || state.onFocus) {
|
||||
return FlowyHoverContainer(
|
||||
style: HoverStyle(
|
||||
borderRadius: Corners.s6Border,
|
||||
hoverColor: theme.shader6,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FlowyHoverState extends ChangeNotifier {
|
||||
bool _onHover = false;
|
||||
bool _onFocus = false;
|
||||
|
||||
set onHover(bool value) {
|
||||
if (_onHover != value) {
|
||||
_onHover = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get onHover => _onHover;
|
||||
|
||||
set onFocus(bool value) {
|
||||
if (_onFocus != value) {
|
||||
_onFocus = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool get onFocus => _onFocus;
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ class GridEventMoveItem {
|
||||
}
|
||||
|
||||
class GridEventNewSelectOption {
|
||||
SelectOptionName request;
|
||||
CreateSelectOptionPayload request;
|
||||
GridEventNewSelectOption(this.request);
|
||||
|
||||
Future<Either<SelectOption, FlowyError>> send() {
|
||||
|
@ -85,6 +85,7 @@ class Field extends $pb.GeneratedMessage {
|
||||
..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
|
||||
..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
|
||||
..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
|
||||
..aOB(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'isPrimary')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -97,6 +98,7 @@ class Field extends $pb.GeneratedMessage {
|
||||
$core.bool? frozen,
|
||||
$core.bool? visibility,
|
||||
$core.int? width,
|
||||
$core.bool? isPrimary,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (id != null) {
|
||||
@ -120,6 +122,9 @@ class Field extends $pb.GeneratedMessage {
|
||||
if (width != null) {
|
||||
_result.width = width;
|
||||
}
|
||||
if (isPrimary != null) {
|
||||
_result.isPrimary = isPrimary;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory Field.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
@ -205,6 +210,15 @@ class Field extends $pb.GeneratedMessage {
|
||||
$core.bool hasWidth() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearWidth() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool get isPrimary => $_getBF(7);
|
||||
@$pb.TagNumber(8)
|
||||
set isPrimary($core.bool v) { $_setBool(7, v); }
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasIsPrimary() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearIsPrimary() => clearField(8);
|
||||
}
|
||||
|
||||
class FieldOrder extends $pb.GeneratedMessage {
|
||||
|
@ -57,11 +57,12 @@ const Field$json = const {
|
||||
const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '10': 'frozen'},
|
||||
const {'1': 'visibility', '3': 6, '4': 1, '5': 8, '10': 'visibility'},
|
||||
const {'1': 'width', '3': 7, '4': 1, '5': 5, '10': 'width'},
|
||||
const {'1': 'is_primary', '3': 8, '4': 1, '5': 8, '10': 'isPrimary'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Field`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIeCgp2aXNpYmlsaXR5GAYgASgIUgp2aXNpYmlsaXR5EhQKBXdpZHRoGAcgASgFUgV3aWR0aA==');
|
||||
final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIeCgp2aXNpYmlsaXR5GAYgASgIUgp2aXNpYmlsaXR5EhQKBXdpZHRoGAcgASgFUgV3aWR0aBIdCgppc19wcmltYXJ5GAggASgIUglpc1ByaW1hcnk=');
|
||||
@$core.Deprecated('Use fieldOrderDescriptor instead')
|
||||
const FieldOrder$json = const {
|
||||
'1': 'FieldOrder',
|
||||
|
@ -9,21 +9,23 @@ import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'field_entities.pb.dart' as $0;
|
||||
|
||||
class CreateSelectOptionPayload extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateSelectOptionPayload', createEmptyInstance: create)
|
||||
..aOM<CellIdentifierPayload>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cellIdentifier', subBuilder: CellIdentifierPayload.create)
|
||||
..aOM<$0.FieldIdentifierPayload>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldIdentifier', subBuilder: $0.FieldIdentifierPayload.create)
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'optionName')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
CreateSelectOptionPayload._() : super();
|
||||
factory CreateSelectOptionPayload({
|
||||
CellIdentifierPayload? cellIdentifier,
|
||||
$0.FieldIdentifierPayload? fieldIdentifier,
|
||||
$core.String? optionName,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (cellIdentifier != null) {
|
||||
_result.cellIdentifier = cellIdentifier;
|
||||
if (fieldIdentifier != null) {
|
||||
_result.fieldIdentifier = fieldIdentifier;
|
||||
}
|
||||
if (optionName != null) {
|
||||
_result.optionName = optionName;
|
||||
@ -52,15 +54,15 @@ class CreateSelectOptionPayload extends $pb.GeneratedMessage {
|
||||
static CreateSelectOptionPayload? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
CellIdentifierPayload get cellIdentifier => $_getN(0);
|
||||
$0.FieldIdentifierPayload get fieldIdentifier => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set cellIdentifier(CellIdentifierPayload v) { setField(1, v); }
|
||||
set fieldIdentifier($0.FieldIdentifierPayload v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasCellIdentifier() => $_has(0);
|
||||
$core.bool hasFieldIdentifier() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearCellIdentifier() => clearField(1);
|
||||
void clearFieldIdentifier() => clearField(1);
|
||||
@$pb.TagNumber(1)
|
||||
CellIdentifierPayload ensureCellIdentifier() => $_ensure(0);
|
||||
$0.FieldIdentifierPayload ensureFieldIdentifier() => $_ensure(0);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get optionName => $_getSZ(1);
|
||||
@ -147,50 +149,3 @@ class CellIdentifierPayload extends $pb.GeneratedMessage {
|
||||
void clearRowId() => clearField(3);
|
||||
}
|
||||
|
||||
class SelectOptionName extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'SelectOptionName', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
SelectOptionName._() : super();
|
||||
factory SelectOptionName({
|
||||
$core.String? name,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (name != null) {
|
||||
_result.name = name;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory SelectOptionName.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SelectOptionName.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SelectOptionName clone() => SelectOptionName()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SelectOptionName copyWith(void Function(SelectOptionName) updates) => super.copyWith((message) => updates(message as SelectOptionName)) as SelectOptionName; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SelectOptionName create() => SelectOptionName._();
|
||||
SelectOptionName createEmptyInstance() => create();
|
||||
static $pb.PbList<SelectOptionName> createRepeated() => $pb.PbList<SelectOptionName>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SelectOptionName getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SelectOptionName>(create);
|
||||
static SelectOptionName? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get name => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set name($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasName() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearName() => clearField(1);
|
||||
}
|
||||
|
||||
|
@ -12,13 +12,13 @@ import 'dart:typed_data' as $typed_data;
|
||||
const CreateSelectOptionPayload$json = const {
|
||||
'1': 'CreateSelectOptionPayload',
|
||||
'2': const [
|
||||
const {'1': 'cell_identifier', '3': 1, '4': 1, '5': 11, '6': '.CellIdentifierPayload', '10': 'cellIdentifier'},
|
||||
const {'1': 'field_identifier', '3': 1, '4': 1, '5': 11, '6': '.FieldIdentifierPayload', '10': 'fieldIdentifier'},
|
||||
const {'1': 'option_name', '3': 2, '4': 1, '5': 9, '10': 'optionName'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `CreateSelectOptionPayload`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List createSelectOptionPayloadDescriptor = $convert.base64Decode('ChlDcmVhdGVTZWxlY3RPcHRpb25QYXlsb2FkEj8KD2NlbGxfaWRlbnRpZmllchgBIAEoCzIWLkNlbGxJZGVudGlmaWVyUGF5bG9hZFIOY2VsbElkZW50aWZpZXISHwoLb3B0aW9uX25hbWUYAiABKAlSCm9wdGlvbk5hbWU=');
|
||||
final $typed_data.Uint8List createSelectOptionPayloadDescriptor = $convert.base64Decode('ChlDcmVhdGVTZWxlY3RPcHRpb25QYXlsb2FkEkIKEGZpZWxkX2lkZW50aWZpZXIYASABKAsyFy5GaWVsZElkZW50aWZpZXJQYXlsb2FkUg9maWVsZElkZW50aWZpZXISHwoLb3B0aW9uX25hbWUYAiABKAlSCm9wdGlvbk5hbWU=');
|
||||
@$core.Deprecated('Use cellIdentifierPayloadDescriptor instead')
|
||||
const CellIdentifierPayload$json = const {
|
||||
'1': 'CellIdentifierPayload',
|
||||
@ -31,13 +31,3 @@ const CellIdentifierPayload$json = const {
|
||||
|
||||
/// Descriptor for `CellIdentifierPayload`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List cellIdentifierPayloadDescriptor = $convert.base64Decode('ChVDZWxsSWRlbnRpZmllclBheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhkKCGZpZWxkX2lkGAIgASgJUgdmaWVsZElkEhUKBnJvd19pZBgDIAEoCVIFcm93SWQ=');
|
||||
@$core.Deprecated('Use selectOptionNameDescriptor instead')
|
||||
const SelectOptionName$json = const {
|
||||
'1': 'SelectOptionName',
|
||||
'2': const [
|
||||
const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `SelectOptionName`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List selectOptionNameDescriptor = $convert.base64Decode('ChBTZWxlY3RPcHRpb25OYW1lEhIKBG5hbWUYASABKAlSBG5hbWU=');
|
||||
|
@ -2,9 +2,7 @@
|
||||
proto_crates = [
|
||||
"src/event_map.rs",
|
||||
"src/services/field/type_options",
|
||||
"src/services/field/field_entities.rs",
|
||||
"src/services/cell/cell_entities.rs",
|
||||
"src/services/row/row_entities.rs",
|
||||
"src/services/entities",
|
||||
"src/dart_notification.rs"
|
||||
]
|
||||
event_files = ["src/event_map.rs"]
|
@ -1,10 +1,8 @@
|
||||
use crate::manager::GridManager;
|
||||
use crate::services::cell::cell_entities::*;
|
||||
use crate::services::field::field_entities::*;
|
||||
use crate::services::entities::*;
|
||||
use crate::services::field::type_options::*;
|
||||
use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str};
|
||||
use crate::services::grid_editor::ClientGridEditor;
|
||||
use crate::services::row::row_entities::*;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::entities::*;
|
||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
@ -248,9 +246,20 @@ pub(crate) async fn update_cell_handler(
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn new_select_option_handler(data: Data<SelectOptionName>) -> DataResult<SelectOption, FlowyError> {
|
||||
let params = data.into_inner();
|
||||
data_result(SelectOption::new(¶ms.name))
|
||||
pub(crate) async fn new_select_option_handler(
|
||||
data: Data<CreateSelectOptionPayload>,
|
||||
manager: AppData<Arc<GridManager>>,
|
||||
) -> DataResult<SelectOption, FlowyError> {
|
||||
let params: CreateSelectOptionParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_grid_editor(¶ms.grid_id)?;
|
||||
match editor.get_field_meta(¶ms.field_id).await {
|
||||
None => Err(ErrorCode::InvalidData.into()),
|
||||
Some(field_meta) => {
|
||||
let type_option = select_option_operation(&field_meta)?;
|
||||
let select_option = type_option.create_option(¶ms.option_name);
|
||||
data_result(select_option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
@ -337,20 +346,3 @@ pub(crate) async fn update_cell_select_option_handler(
|
||||
let _ = editor.update_cell(changeset).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn select_option_operation(field_meta: &FieldMeta) -> FlowyResult<Box<dyn SelectOptionOperation>> {
|
||||
match &field_meta.field_type {
|
||||
FieldType::SingleSelect => {
|
||||
let type_option = SingleSelectTypeOption::from(field_meta);
|
||||
Ok(Box::new(type_option))
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let type_option = MultiSelectTypeOption::from(field_meta);
|
||||
Ok(Box::new(type_option))
|
||||
}
|
||||
ty => {
|
||||
tracing::error!("Unsupported field type: {:?} for this handler", ty);
|
||||
Err(ErrorCode::FieldInvalidOperation.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ pub enum GridEvent {
|
||||
#[event(input = "MoveItemPayload")]
|
||||
MoveItem = 17,
|
||||
|
||||
#[event(input = "SelectOptionName", output = "SelectOption")]
|
||||
#[event(input = "CreateSelectOptionPayload", output = "SelectOption")]
|
||||
NewSelectOption = 30,
|
||||
|
||||
#[event(input = "CellIdentifierPayload", output = "SelectOptionContext")]
|
||||
|
@ -26,7 +26,7 @@
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct CreateSelectOptionPayload {
|
||||
// message fields
|
||||
pub cell_identifier: ::protobuf::SingularPtrField<CellIdentifierPayload>,
|
||||
pub field_identifier: ::protobuf::SingularPtrField<super::field_entities::FieldIdentifierPayload>,
|
||||
pub option_name: ::std::string::String,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
@ -44,37 +44,37 @@ impl CreateSelectOptionPayload {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// .CellIdentifierPayload cell_identifier = 1;
|
||||
// .FieldIdentifierPayload field_identifier = 1;
|
||||
|
||||
|
||||
pub fn get_cell_identifier(&self) -> &CellIdentifierPayload {
|
||||
self.cell_identifier.as_ref().unwrap_or_else(|| <CellIdentifierPayload as ::protobuf::Message>::default_instance())
|
||||
pub fn get_field_identifier(&self) -> &super::field_entities::FieldIdentifierPayload {
|
||||
self.field_identifier.as_ref().unwrap_or_else(|| <super::field_entities::FieldIdentifierPayload as ::protobuf::Message>::default_instance())
|
||||
}
|
||||
pub fn clear_cell_identifier(&mut self) {
|
||||
self.cell_identifier.clear();
|
||||
pub fn clear_field_identifier(&mut self) {
|
||||
self.field_identifier.clear();
|
||||
}
|
||||
|
||||
pub fn has_cell_identifier(&self) -> bool {
|
||||
self.cell_identifier.is_some()
|
||||
pub fn has_field_identifier(&self) -> bool {
|
||||
self.field_identifier.is_some()
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_cell_identifier(&mut self, v: CellIdentifierPayload) {
|
||||
self.cell_identifier = ::protobuf::SingularPtrField::some(v);
|
||||
pub fn set_field_identifier(&mut self, v: super::field_entities::FieldIdentifierPayload) {
|
||||
self.field_identifier = ::protobuf::SingularPtrField::some(v);
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_cell_identifier(&mut self) -> &mut CellIdentifierPayload {
|
||||
if self.cell_identifier.is_none() {
|
||||
self.cell_identifier.set_default();
|
||||
pub fn mut_field_identifier(&mut self) -> &mut super::field_entities::FieldIdentifierPayload {
|
||||
if self.field_identifier.is_none() {
|
||||
self.field_identifier.set_default();
|
||||
}
|
||||
self.cell_identifier.as_mut().unwrap()
|
||||
self.field_identifier.as_mut().unwrap()
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_cell_identifier(&mut self) -> CellIdentifierPayload {
|
||||
self.cell_identifier.take().unwrap_or_else(|| CellIdentifierPayload::new())
|
||||
pub fn take_field_identifier(&mut self) -> super::field_entities::FieldIdentifierPayload {
|
||||
self.field_identifier.take().unwrap_or_else(|| super::field_entities::FieldIdentifierPayload::new())
|
||||
}
|
||||
|
||||
// string option_name = 2;
|
||||
@ -106,7 +106,7 @@ impl CreateSelectOptionPayload {
|
||||
|
||||
impl ::protobuf::Message for CreateSelectOptionPayload {
|
||||
fn is_initialized(&self) -> bool {
|
||||
for v in &self.cell_identifier {
|
||||
for v in &self.field_identifier {
|
||||
if !v.is_initialized() {
|
||||
return false;
|
||||
}
|
||||
@ -119,7 +119,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.cell_identifier)?;
|
||||
::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.field_identifier)?;
|
||||
},
|
||||
2 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.option_name)?;
|
||||
@ -136,7 +136,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if let Some(ref v) = self.cell_identifier.as_ref() {
|
||||
if let Some(ref v) = self.field_identifier.as_ref() {
|
||||
let len = v.compute_size();
|
||||
my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
|
||||
}
|
||||
@ -149,7 +149,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if let Some(ref v) = self.cell_identifier.as_ref() {
|
||||
if let Some(ref v) = self.field_identifier.as_ref() {
|
||||
os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
|
||||
os.write_raw_varint32(v.get_cached_size())?;
|
||||
v.write_to_with_cached_sizes(os)?;
|
||||
@ -195,10 +195,10 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<CellIdentifierPayload>>(
|
||||
"cell_identifier",
|
||||
|m: &CreateSelectOptionPayload| { &m.cell_identifier },
|
||||
|m: &mut CreateSelectOptionPayload| { &mut m.cell_identifier },
|
||||
fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<super::field_entities::FieldIdentifierPayload>>(
|
||||
"field_identifier",
|
||||
|m: &CreateSelectOptionPayload| { &m.field_identifier },
|
||||
|m: &mut CreateSelectOptionPayload| { &mut m.field_identifier },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"option_name",
|
||||
@ -221,7 +221,7 @@ impl ::protobuf::Message for CreateSelectOptionPayload {
|
||||
|
||||
impl ::protobuf::Clear for CreateSelectOptionPayload {
|
||||
fn clear(&mut self) {
|
||||
self.cell_identifier.clear();
|
||||
self.field_identifier.clear();
|
||||
self.option_name.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
@ -482,173 +482,14 @@ impl ::protobuf::reflect::ProtobufValue for CellIdentifierPayload {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct SelectOptionName {
|
||||
// message fields
|
||||
pub name: ::std::string::String,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a SelectOptionName {
|
||||
fn default() -> &'a SelectOptionName {
|
||||
<SelectOptionName as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectOptionName {
|
||||
pub fn new() -> SelectOptionName {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// string name = 1;
|
||||
|
||||
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
pub fn clear_name(&mut self) {
|
||||
self.name.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_name(&mut self, v: ::std::string::String) {
|
||||
self.name = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_name(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.name
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_name(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.name, ::std::string::String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for SelectOptionName {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.name.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.name);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.name.is_empty() {
|
||||
os.write_string(1, &self.name)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> SelectOptionName {
|
||||
SelectOptionName::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"name",
|
||||
|m: &SelectOptionName| { &m.name },
|
||||
|m: &mut SelectOptionName| { &mut m.name },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<SelectOptionName>(
|
||||
"SelectOptionName",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static SelectOptionName {
|
||||
static instance: ::protobuf::rt::LazyV2<SelectOptionName> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(SelectOptionName::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for SelectOptionName {
|
||||
fn clear(&mut self) {
|
||||
self.name.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for SelectOptionName {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for SelectOptionName {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x13cell_entities.proto\"}\n\x19CreateSelectOptionPayload\x12?\n\x0fce\
|
||||
ll_identifier\x18\x01\x20\x01(\x0b2\x16.CellIdentifierPayloadR\x0ecellId\
|
||||
entifier\x12\x1f\n\x0boption_name\x18\x02\x20\x01(\tR\noptionName\"b\n\
|
||||
\x15CellIdentifierPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gr\
|
||||
idId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12\x15\n\x06r\
|
||||
ow_id\x18\x03\x20\x01(\tR\x05rowId\"&\n\x10SelectOptionName\x12\x12\n\
|
||||
\x04name\x18\x01\x20\x01(\tR\x04nameb\x06proto3\
|
||||
\n\x13cell_entities.proto\x1a\x14field_entities.proto\"\x80\x01\n\x19Cre\
|
||||
ateSelectOptionPayload\x12B\n\x10field_identifier\x18\x01\x20\x01(\x0b2\
|
||||
\x17.FieldIdentifierPayloadR\x0ffieldIdentifier\x12\x1f\n\x0boption_name\
|
||||
\x18\x02\x20\x01(\tR\noptionName\"b\n\x15CellIdentifierPayload\x12\x17\n\
|
||||
\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_id\x18\x02\
|
||||
\x20\x01(\tR\x07fieldId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\
|
||||
b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
@ -1,7 +1,8 @@
|
||||
syntax = "proto3";
|
||||
import "field_entities.proto";
|
||||
|
||||
message CreateSelectOptionPayload {
|
||||
CellIdentifierPayload cell_identifier = 1;
|
||||
FieldIdentifierPayload field_identifier = 1;
|
||||
string option_name = 2;
|
||||
}
|
||||
message CellIdentifierPayload {
|
||||
@ -9,6 +10,3 @@ message CellIdentifierPayload {
|
||||
string field_id = 2;
|
||||
string row_id = 3;
|
||||
}
|
||||
message SelectOptionName {
|
||||
string name = 1;
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
pub(crate) mod cell_entities;
|
||||
|
||||
pub use cell_entities::*;
|
@ -1,3 +1,4 @@
|
||||
use crate::services::entities::{FieldIdentifier, FieldIdentifierPayload};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use flowy_grid_data_model::parser::NotEmptyStr;
|
||||
@ -5,25 +6,33 @@ use flowy_grid_data_model::parser::NotEmptyStr;
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct CreateSelectOptionPayload {
|
||||
#[pb(index = 1)]
|
||||
pub cell_identifier: CellIdentifierPayload,
|
||||
pub field_identifier: FieldIdentifierPayload,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub option_name: String,
|
||||
}
|
||||
|
||||
pub struct CreateSelectOptionParams {
|
||||
pub cell_identifier: CellIdentifier,
|
||||
pub field_identifier: FieldIdentifier,
|
||||
pub option_name: String,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CreateSelectOptionParams {
|
||||
type Target = FieldIdentifier;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.field_identifier
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<CreateSelectOptionParams> for CreateSelectOptionPayload {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CreateSelectOptionParams, Self::Error> {
|
||||
let option_name = NotEmptyStr::parse(self.option_name).map_err(|_| ErrorCode::SelectOptionNameIsEmpty)?;
|
||||
let cell_identifier = self.cell_identifier.try_into()?;
|
||||
let field_identifier = self.field_identifier.try_into()?;
|
||||
Ok(CreateSelectOptionParams {
|
||||
cell_identifier,
|
||||
field_identifier,
|
||||
option_name: option_name.0,
|
||||
})
|
||||
}
|
||||
@ -61,9 +70,3 @@ impl TryInto<CellIdentifier> for CellIdentifierPayload {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct SelectOptionName {
|
||||
#[pb(index = 1)]
|
||||
pub name: String,
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
mod cell_entities;
|
||||
mod field_entities;
|
||||
mod row_entities;
|
||||
|
||||
pub use cell_entities::*;
|
||||
pub use field_entities::*;
|
||||
pub use row_entities::*;
|
@ -0,0 +1,31 @@
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use flowy_grid_data_model::parser::NotEmptyStr;
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct RowIdentifierPayload {
|
||||
#[pb(index = 1)]
|
||||
pub grid_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub row_id: String,
|
||||
}
|
||||
|
||||
pub struct RowIdentifier {
|
||||
pub grid_id: String,
|
||||
pub row_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<RowIdentifier> for RowIdentifierPayload {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<RowIdentifier, Self::Error> {
|
||||
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
|
||||
let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
|
||||
Ok(RowIdentifier {
|
||||
grid_id: grid_id.0,
|
||||
row_id: row_id.0,
|
||||
})
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ pub type BoxTypeOptionBuilder = Box<dyn TypeOptionBuilder + 'static>;
|
||||
impl FieldBuilder {
|
||||
pub fn new<T: Into<BoxTypeOptionBuilder>>(type_option_builder: T) -> Self {
|
||||
let type_option_builder = type_option_builder.into();
|
||||
let field_meta = FieldMeta::new("", "", type_option_builder.field_type());
|
||||
let field_meta = FieldMeta::new("", "", type_option_builder.field_type(), false);
|
||||
Self {
|
||||
field_meta,
|
||||
type_option_builder,
|
||||
@ -35,6 +35,7 @@ impl FieldBuilder {
|
||||
visibility: field.visibility,
|
||||
width: field.width,
|
||||
type_options: IndexMap::default(),
|
||||
is_primary: field.is_primary,
|
||||
};
|
||||
Self {
|
||||
field_meta,
|
||||
@ -52,6 +53,11 @@ impl FieldBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn primary(mut self, is_primary: bool) -> Self {
|
||||
self.field_meta.is_primary = is_primary;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn visibility(mut self, visibility: bool) -> Self {
|
||||
self.field_meta.visibility = visibility;
|
||||
self
|
||||
|
@ -1,7 +1,5 @@
|
||||
mod field_builder;
|
||||
pub(crate) mod field_entities;
|
||||
pub(crate) mod type_options;
|
||||
|
||||
pub use field_builder::*;
|
||||
pub use field_entities::*;
|
||||
pub use type_options::*;
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::impl_type_option;
|
||||
use crate::services::cell::{CellIdentifier, CellIdentifierPayload};
|
||||
use crate::services::entities::{CellIdentifier, CellIdentifierPayload};
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{CellDataChangeset, CellDataOperation, TypeOptionCellData};
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::entities::{
|
||||
CellChangeset, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
|
||||
};
|
||||
@ -36,10 +36,35 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_option(&self, name: &str) -> SelectOption {
|
||||
let color = select_option_color_from_index(self.options().len());
|
||||
SelectOption::with_color(name, color)
|
||||
}
|
||||
|
||||
fn option_context(&self, cell_meta: &Option<CellMeta>) -> SelectOptionContext;
|
||||
|
||||
fn options(&self) -> &Vec<SelectOption>;
|
||||
|
||||
fn mut_options(&mut self) -> &mut Vec<SelectOption>;
|
||||
}
|
||||
|
||||
pub fn select_option_operation(field_meta: &FieldMeta) -> FlowyResult<Box<dyn SelectOptionOperation>> {
|
||||
match &field_meta.field_type {
|
||||
FieldType::SingleSelect => {
|
||||
let type_option = SingleSelectTypeOption::from(field_meta);
|
||||
Ok(Box::new(type_option))
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let type_option = MultiSelectTypeOption::from(field_meta);
|
||||
Ok(Box::new(type_option))
|
||||
}
|
||||
ty => {
|
||||
tracing::error!("Unsupported field type: {:?} for this handler", ty);
|
||||
Err(ErrorCode::FieldInvalidOperation.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single select
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
pub struct SingleSelectTypeOption {
|
||||
@ -60,6 +85,10 @@ impl SelectOptionOperation for SingleSelectTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> &Vec<SelectOption> {
|
||||
&self.options
|
||||
}
|
||||
|
||||
fn mut_options(&mut self) -> &mut Vec<SelectOption> {
|
||||
&mut self.options
|
||||
}
|
||||
@ -155,6 +184,10 @@ impl SelectOptionOperation for MultiSelectTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> &Vec<SelectOption> {
|
||||
&self.options
|
||||
}
|
||||
|
||||
fn mut_options(&mut self) -> &mut Vec<SelectOption> {
|
||||
&mut self.options
|
||||
}
|
||||
@ -265,6 +298,14 @@ impl SelectOption {
|
||||
color: SelectOptionColor::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_color(name: &str, color: SelectOptionColor) -> Self {
|
||||
SelectOption {
|
||||
id: nanoid!(4),
|
||||
name: name.to_owned(),
|
||||
color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, ProtoBuf)]
|
||||
@ -430,6 +471,21 @@ pub enum SelectOptionColor {
|
||||
Blue = 8,
|
||||
}
|
||||
|
||||
pub fn select_option_color_from_index(index: usize) -> SelectOptionColor {
|
||||
match index % 8 {
|
||||
0 => SelectOptionColor::Purple,
|
||||
1 => SelectOptionColor::Pink,
|
||||
2 => SelectOptionColor::LightPink,
|
||||
3 => SelectOptionColor::Orange,
|
||||
4 => SelectOptionColor::Yellow,
|
||||
5 => SelectOptionColor::Lime,
|
||||
6 => SelectOptionColor::Green,
|
||||
7 => SelectOptionColor::Aqua,
|
||||
8 => SelectOptionColor::Blue,
|
||||
_ => SelectOptionColor::Purple,
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for SelectOptionColor {
|
||||
fn default() -> Self {
|
||||
SelectOptionColor::Purple
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::dart_notification::{send_dart_notification, GridNotification};
|
||||
use crate::manager::GridUser;
|
||||
use crate::services::block_meta_manager::GridBlockMetaEditorManager;
|
||||
use crate::services::cell::CellIdentifier;
|
||||
use crate::services::entities::CellIdentifier;
|
||||
use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder};
|
||||
use crate::services::persistence::block_index::BlockIndexPersistence;
|
||||
use crate::services::row::*;
|
||||
@ -22,7 +22,7 @@ use tokio::sync::RwLock;
|
||||
pub struct ClientGridEditor {
|
||||
grid_id: String,
|
||||
user: Arc<dyn GridUser>,
|
||||
pad: Arc<RwLock<GridMetaPad>>,
|
||||
grid_pad: Arc<RwLock<GridMetaPad>>,
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
block_meta_manager: Arc<GridBlockMetaEditorManager>,
|
||||
}
|
||||
@ -38,14 +38,14 @@ impl ClientGridEditor {
|
||||
let cloud = Arc::new(GridRevisionCloudService { token });
|
||||
let grid_pad = rev_manager.load::<GridPadBuilder>(Some(cloud)).await?;
|
||||
let rev_manager = Arc::new(rev_manager);
|
||||
let pad = Arc::new(RwLock::new(grid_pad));
|
||||
let blocks = pad.read().await.get_block_metas();
|
||||
let grid_pad = Arc::new(RwLock::new(grid_pad));
|
||||
let blocks = grid_pad.read().await.get_block_metas();
|
||||
|
||||
let block_meta_manager = Arc::new(GridBlockMetaEditorManager::new(grid_id, &user, blocks, persistence).await?);
|
||||
Ok(Arc::new(Self {
|
||||
grid_id: grid_id.to_owned(),
|
||||
user,
|
||||
pad,
|
||||
grid_pad,
|
||||
rev_manager,
|
||||
block_meta_manager,
|
||||
}))
|
||||
@ -94,18 +94,18 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
pub async fn create_next_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
|
||||
let name = format!("Property {}", self.pad.read().await.fields().len() + 1);
|
||||
let name = format!("Property {}", self.grid_pad.read().await.fields().len() + 1);
|
||||
let field_meta = FieldBuilder::from_field_type(field_type).name(&name).build();
|
||||
Ok(field_meta)
|
||||
}
|
||||
|
||||
pub async fn contain_field(&self, field_id: &str) -> bool {
|
||||
self.pad.read().await.contain_field(field_id)
|
||||
self.grid_pad.read().await.contain_field(field_id)
|
||||
}
|
||||
|
||||
pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
|
||||
let field_id = params.field_id.clone();
|
||||
let json_deserializer = match self.pad.read().await.get_field_meta(params.field_id.as_str()) {
|
||||
let json_deserializer = match self.grid_pad.read().await.get_field_meta(params.field_id.as_str()) {
|
||||
None => return Err(ErrorCode::FieldDoesNotExist.into()),
|
||||
Some((_, field_meta)) => TypeOptionJsonDeserializer(field_meta.field_type.clone()),
|
||||
};
|
||||
@ -169,7 +169,7 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
pub async fn get_field_meta(&self, field_id: &str) -> Option<FieldMeta> {
|
||||
let field_meta = self.pad.read().await.get_field_meta(field_id)?.1.clone();
|
||||
let field_meta = self.grid_pad.read().await.get_field_meta(field_id)?.1.clone();
|
||||
Some(field_meta)
|
||||
}
|
||||
|
||||
@ -178,14 +178,14 @@ impl ClientGridEditor {
|
||||
T: Into<FieldOrder>,
|
||||
{
|
||||
if field_ids.is_none() {
|
||||
let field_metas = self.pad.read().await.get_field_metas(None)?;
|
||||
let field_metas = self.grid_pad.read().await.get_field_metas(None)?;
|
||||
return Ok(field_metas);
|
||||
}
|
||||
|
||||
let to_field_orders = |item: Vec<T>| item.into_iter().map(|data| data.into()).collect();
|
||||
let field_orders = field_ids.map_or(vec![], to_field_orders);
|
||||
let expected_len = field_orders.len();
|
||||
let field_metas = self.pad.read().await.get_field_metas(Some(field_orders))?;
|
||||
let field_metas = self.grid_pad.read().await.get_field_metas(Some(field_orders))?;
|
||||
if expected_len != 0 && field_metas.len() != expected_len {
|
||||
tracing::error!(
|
||||
"This is a bug. The len of the field_metas should equal to {}",
|
||||
@ -207,7 +207,7 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
pub async fn create_row(&self, start_row_id: Option<String>) -> FlowyResult<RowOrder> {
|
||||
let field_metas = self.pad.read().await.get_field_metas(None)?;
|
||||
let field_metas = self.grid_pad.read().await.get_field_metas(None)?;
|
||||
let block_id = self.block_id().await?;
|
||||
|
||||
// insert empty row below the row whose id is upper_row_id
|
||||
@ -314,7 +314,7 @@ impl ClientGridEditor {
|
||||
let cell_data_changeset = changeset.data.unwrap();
|
||||
let cell_meta = self.get_cell_meta(&changeset.row_id, &changeset.field_id).await?;
|
||||
tracing::trace!("{}: {:?}", &changeset.field_id, cell_meta);
|
||||
match self.pad.read().await.get_field_meta(&changeset.field_id) {
|
||||
match self.grid_pad.read().await.get_field_meta(&changeset.field_id) {
|
||||
None => {
|
||||
let msg = format!("Field not found with id: {}", &changeset.field_id);
|
||||
Err(FlowyError::internal().context(msg))
|
||||
@ -334,7 +334,7 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
pub async fn get_block_metas(&self) -> FlowyResult<Vec<GridBlockMeta>> {
|
||||
let grid_blocks = self.pad.read().await.get_block_metas();
|
||||
let grid_blocks = self.grid_pad.read().await.get_block_metas();
|
||||
Ok(grid_blocks)
|
||||
}
|
||||
|
||||
@ -347,7 +347,7 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
pub async fn grid_data(&self) -> FlowyResult<Grid> {
|
||||
let pad_read_guard = self.pad.read().await;
|
||||
let pad_read_guard = self.grid_pad.read().await;
|
||||
let field_orders = pad_read_guard.get_field_orders();
|
||||
let mut block_orders = vec![];
|
||||
for block_order in pad_read_guard.get_block_metas() {
|
||||
@ -369,7 +369,7 @@ impl ClientGridEditor {
|
||||
pub async fn grid_block_snapshots(&self, block_ids: Option<Vec<String>>) -> FlowyResult<Vec<GridBlockSnapshot>> {
|
||||
let block_ids = match block_ids {
|
||||
None => self
|
||||
.pad
|
||||
.grid_pad
|
||||
.read()
|
||||
.await
|
||||
.get_block_metas()
|
||||
@ -396,7 +396,7 @@ impl ClientGridEditor {
|
||||
let _ = self
|
||||
.modify(|grid_pad| Ok(grid_pad.move_field(field_id, from as usize, to as usize)?))
|
||||
.await?;
|
||||
if let Some((index, field_meta)) = self.pad.read().await.get_field_meta(field_id) {
|
||||
if let Some((index, field_meta)) = self.grid_pad.read().await.get_field_meta(field_id) {
|
||||
let delete_field_order = FieldOrder::from(field_id);
|
||||
let insert_field = IndexField::from_field_meta(field_meta, index);
|
||||
let notified_changeset = GridFieldChangeset {
|
||||
@ -420,14 +420,14 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
pub async fn delta_bytes(&self) -> Bytes {
|
||||
self.pad.read().await.delta_bytes()
|
||||
self.grid_pad.read().await.delta_bytes()
|
||||
}
|
||||
|
||||
async fn modify<F>(&self, f: F) -> FlowyResult<()>
|
||||
where
|
||||
F: for<'a> FnOnce(&'a mut GridMetaPad) -> FlowyResult<Option<GridChangeset>>,
|
||||
{
|
||||
let mut write_guard = self.pad.write().await;
|
||||
let mut write_guard = self.grid_pad.write().await;
|
||||
if let Some(changeset) = f(&mut *write_guard)? {
|
||||
let _ = self.apply_change(changeset).await?;
|
||||
}
|
||||
@ -455,7 +455,7 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
async fn block_id(&self) -> FlowyResult<String> {
|
||||
match self.pad.read().await.get_block_metas().last() {
|
||||
match self.grid_pad.read().await.get_block_metas().last() {
|
||||
None => Err(FlowyError::internal().context("There is no grid block in this grid")),
|
||||
Some(grid_block) => Ok(grid_block.block_id.clone()),
|
||||
}
|
||||
@ -463,7 +463,7 @@ impl ClientGridEditor {
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
async fn notify_did_insert_grid_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
if let Some((index, field_meta)) = self.pad.read().await.get_field_meta(field_id) {
|
||||
if let Some((index, field_meta)) = self.grid_pad.read().await.get_field_meta(field_id) {
|
||||
let index_field = IndexField::from_field_meta(field_meta, index);
|
||||
let notified_changeset = GridFieldChangeset::insert(&self.grid_id, vec![index_field]);
|
||||
let _ = self.notify_did_update_grid(notified_changeset).await?;
|
||||
@ -473,10 +473,13 @@ impl ClientGridEditor {
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
async fn notify_did_update_grid_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||
let mut field_metas = self.get_field_metas(Some(vec![field_id])).await?;
|
||||
debug_assert!(field_metas.len() == 1);
|
||||
|
||||
if let Some(field_meta) = field_metas.pop() {
|
||||
if let Some((_, field_meta)) = self
|
||||
.grid_pad
|
||||
.read()
|
||||
.await
|
||||
.get_field_meta(field_id)
|
||||
.map(|(index, field)| (index, field.clone()))
|
||||
{
|
||||
let updated_field = Field::from(field_meta);
|
||||
let notified_changeset = GridFieldChangeset::update(&self.grid_id, vec![updated_field.clone()]);
|
||||
let _ = self.notify_did_update_grid(notified_changeset).await?;
|
||||
|
@ -2,7 +2,7 @@ mod util;
|
||||
|
||||
pub mod block_meta_editor;
|
||||
mod block_meta_manager;
|
||||
pub mod cell;
|
||||
pub mod entities;
|
||||
pub mod field;
|
||||
pub mod grid_editor;
|
||||
pub mod persistence;
|
||||
|
@ -7,6 +7,7 @@ pub fn make_default_grid() -> BuildGridContext {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
|
||||
// single select
|
||||
|
@ -271,6 +271,7 @@ pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
|
||||
frozen: field_meta.frozen,
|
||||
visibility: field_meta.visibility,
|
||||
width: field_meta.width,
|
||||
is_primary: false,
|
||||
};
|
||||
|
||||
let params = InsertFieldParams {
|
||||
@ -303,6 +304,7 @@ pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldMet
|
||||
frozen: field_meta.frozen,
|
||||
visibility: field_meta.visibility,
|
||||
width: field_meta.width,
|
||||
is_primary: false,
|
||||
};
|
||||
|
||||
let params = InsertFieldParams {
|
||||
|
@ -42,6 +42,9 @@ pub struct Field {
|
||||
|
||||
#[pb(index = 7)]
|
||||
pub width: i32,
|
||||
|
||||
#[pb(index = 8)]
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
impl std::convert::From<FieldMeta> for Field {
|
||||
@ -54,6 +57,7 @@ impl std::convert::From<FieldMeta> for Field {
|
||||
frozen: field_meta.frozen,
|
||||
visibility: field_meta.visibility,
|
||||
width: field_meta.width,
|
||||
is_primary: field_meta.is_primary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,13 +98,21 @@ pub struct FieldMeta {
|
||||
// #[pb(index = 8)]
|
||||
/// type_options contains key/value pairs
|
||||
/// key: id of the FieldType
|
||||
/// value: type option data string
|
||||
/// value: type option data that can be parsed into specified TypeOptionStruct.
|
||||
/// For example, CheckboxTypeOption, MultiSelectTypeOption etc.
|
||||
#[serde(with = "indexmap::serde_seq")]
|
||||
pub type_options: IndexMap<String, String>,
|
||||
|
||||
#[serde(default = "default_is_primary")]
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
fn default_is_primary() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
impl FieldMeta {
|
||||
pub fn new(name: &str, desc: &str, field_type: FieldType) -> Self {
|
||||
pub fn new(name: &str, desc: &str, field_type: FieldType, is_primary: bool) -> Self {
|
||||
let width = field_type.default_cell_width();
|
||||
Self {
|
||||
id: gen_field_id(),
|
||||
@ -115,6 +123,7 @@ impl FieldMeta {
|
||||
visibility: true,
|
||||
width,
|
||||
type_options: Default::default(),
|
||||
is_primary,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,6 +290,7 @@ pub struct Field {
|
||||
pub frozen: bool,
|
||||
pub visibility: bool,
|
||||
pub width: i32,
|
||||
pub is_primary: bool,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
@ -443,6 +444,21 @@ impl Field {
|
||||
pub fn set_width(&mut self, v: i32) {
|
||||
self.width = v;
|
||||
}
|
||||
|
||||
// bool is_primary = 8;
|
||||
|
||||
|
||||
pub fn get_is_primary(&self) -> bool {
|
||||
self.is_primary
|
||||
}
|
||||
pub fn clear_is_primary(&mut self) {
|
||||
self.is_primary = false;
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_is_primary(&mut self, v: bool) {
|
||||
self.is_primary = v;
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for Field {
|
||||
@ -487,6 +503,13 @@ impl ::protobuf::Message for Field {
|
||||
let tmp = is.read_int32()?;
|
||||
self.width = tmp;
|
||||
},
|
||||
8 => {
|
||||
if wire_type != ::protobuf::wire_format::WireTypeVarint {
|
||||
return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
|
||||
}
|
||||
let tmp = is.read_bool()?;
|
||||
self.is_primary = tmp;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
@ -520,6 +543,9 @@ impl ::protobuf::Message for Field {
|
||||
if self.width != 0 {
|
||||
my_size += ::protobuf::rt::value_size(7, self.width, ::protobuf::wire_format::WireTypeVarint);
|
||||
}
|
||||
if self.is_primary != false {
|
||||
my_size += 2;
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
@ -547,6 +573,9 @@ impl ::protobuf::Message for Field {
|
||||
if self.width != 0 {
|
||||
os.write_int32(7, self.width)?;
|
||||
}
|
||||
if self.is_primary != false {
|
||||
os.write_bool(8, self.is_primary)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
@ -620,6 +649,11 @@ impl ::protobuf::Message for Field {
|
||||
|m: &Field| { &m.width },
|
||||
|m: &mut Field| { &mut m.width },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
|
||||
"is_primary",
|
||||
|m: &Field| { &m.is_primary },
|
||||
|m: &mut Field| { &mut m.is_primary },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<Field>(
|
||||
"Field",
|
||||
fields,
|
||||
@ -643,6 +677,7 @@ impl ::protobuf::Clear for Field {
|
||||
self.frozen = false;
|
||||
self.visibility = false;
|
||||
self.width = 0;
|
||||
self.is_primary = false;
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
@ -7770,93 +7805,94 @@ static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\ngrid.proto\"z\n\x04Grid\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
|
||||
\x12.\n\x0cfield_orders\x18\x02\x20\x03(\x0b2\x0b.FieldOrderR\x0bfieldOr\
|
||||
ders\x122\n\x0cblock_orders\x18\x03\x20\x03(\x0b2\x0f.GridBlockOrderR\
|
||||
\x0bblockOrders\"\xb8\x01\n\x05Field\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
|
||||
\x0bblockOrders\"\xd7\x01\n\x05Field\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
|
||||
\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\
|
||||
\x18\x03\x20\x01(\tR\x04desc\x12)\n\nfield_type\x18\x04\x20\x01(\x0e2\n.\
|
||||
FieldTypeR\tfieldType\x12\x16\n\x06frozen\x18\x05\x20\x01(\x08R\x06froze\
|
||||
n\x12\x1e\n\nvisibility\x18\x06\x20\x01(\x08R\nvisibility\x12\x14\n\x05w\
|
||||
idth\x18\x07\x20\x01(\x05R\x05width\"'\n\nFieldOrder\x12\x19\n\x08field_\
|
||||
id\x18\x01\x20\x01(\tR\x07fieldId\"\xc6\x01\n\x12GridFieldChangeset\x12\
|
||||
\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x124\n\x0finserted_field\
|
||||
s\x18\x02\x20\x03(\x0b2\x0b.IndexFieldR\x0einsertedFields\x122\n\x0edele\
|
||||
ted_fields\x18\x03\x20\x03(\x0b2\x0b.FieldOrderR\rdeletedFields\x12-\n\
|
||||
\x0eupdated_fields\x18\x04\x20\x03(\x0b2\x06.FieldR\rupdatedFields\"@\n\
|
||||
\nIndexField\x12\x1c\n\x05field\x18\x01\x20\x01(\x0b2\x06.FieldR\x05fiel\
|
||||
d\x12\x14\n\x05index\x18\x02\x20\x01(\x05R\x05index\"\x90\x01\n\x1aGetEd\
|
||||
itFieldContextPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\
|
||||
\x12\x1b\n\x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\n\nfield_t\
|
||||
ype\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fone_of_field\
|
||||
_id\"q\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
|
||||
\x06gridId\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12)\n\n\
|
||||
field_type\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldType\"|\n\x10EditFie\
|
||||
ldContext\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12%\n\ngri\
|
||||
d_field\x18\x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\x10type_optio\
|
||||
n_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\x12\
|
||||
\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12Repeat\
|
||||
edFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\x05it\
|
||||
ems\"T\n\x08RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\
|
||||
\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06heigh\
|
||||
t\x18\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\x18\
|
||||
\x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\x0b2\
|
||||
\x17.Row.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x03\
|
||||
\x20\x01(\x05R\x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03key\
|
||||
\x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\x05\
|
||||
.CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\x01\
|
||||
\x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\x05\
|
||||
items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"U\n\x0eGridBlockOrder\
|
||||
\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_orders\
|
||||
\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"_\n\rIndexRowOrder\x12&\n\
|
||||
\trow_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x05i\
|
||||
ndex\x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"\xbf\x01\
|
||||
\n\x11GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blo\
|
||||
ckId\x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrderR\x0ci\
|
||||
nsertedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0b\
|
||||
deletedRows\x12,\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\t.RowOrderR\x0b\
|
||||
updatedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
|
||||
\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\
|
||||
\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\
|
||||
\x07content\x18\x02\x20\x01(\tR\x07content\"\x8f\x01\n\x14CellNotificati\
|
||||
onData\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08f\
|
||||
ield_id\x18\x02\x20\x01(\tR\x07fieldId\x12\x15\n\x06row_id\x18\x03\x20\
|
||||
\x01(\tR\x05rowId\x12\x1a\n\x07content\x18\x04\x20\x01(\tH\0R\x07content\
|
||||
B\x10\n\x0eone_of_content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\
|
||||
\x01\x20\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\
|
||||
\n\x04name\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05va\
|
||||
lue\x18\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\
|
||||
\x18\x01\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid\
|
||||
_id\x18\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\
|
||||
\x01(\tH\0R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12Ins\
|
||||
ertFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
|
||||
\x1c\n\x05field\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type\
|
||||
_option_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_fie\
|
||||
ld_id\x18\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_fiel\
|
||||
d_id\"d\n\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
|
||||
\x06gridId\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFiel\
|
||||
dOrderR\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_\
|
||||
id\x18\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\
|
||||
\x0b2\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetPa\
|
||||
yload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07\
|
||||
grid_id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\
|
||||
\tH\0R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\
|
||||
\nfield_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\
|
||||
\n\x06frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\
|
||||
\x18\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\
|
||||
\x01(\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\
|
||||
\x06R\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\
|
||||
\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\
|
||||
\x0e\n\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\x01\n\x0f\
|
||||
MoveItemPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\
|
||||
\x17\n\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_index\
|
||||
\x18\x03\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\x01(\
|
||||
\x05R\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTypeR\
|
||||
\x02ty\"\x7f\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\
|
||||
\x06gridId\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\
|
||||
\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\x18\x04\
|
||||
\x20\x01(\tH\0R\x04dataB\r\n\x0bone_of_data**\n\x0cMoveItemType\x12\r\n\
|
||||
\tMoveField\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\
|
||||
\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\
|
||||
\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\
|
||||
\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
|
||||
idth\x18\x07\x20\x01(\x05R\x05width\x12\x1d\n\nis_primary\x18\x08\x20\
|
||||
\x01(\x08R\tisPrimary\"'\n\nFieldOrder\x12\x19\n\x08field_id\x18\x01\x20\
|
||||
\x01(\tR\x07fieldId\"\xc6\x01\n\x12GridFieldChangeset\x12\x17\n\x07grid_\
|
||||
id\x18\x01\x20\x01(\tR\x06gridId\x124\n\x0finserted_fields\x18\x02\x20\
|
||||
\x03(\x0b2\x0b.IndexFieldR\x0einsertedFields\x122\n\x0edeleted_fields\
|
||||
\x18\x03\x20\x03(\x0b2\x0b.FieldOrderR\rdeletedFields\x12-\n\x0eupdated_\
|
||||
fields\x18\x04\x20\x03(\x0b2\x06.FieldR\rupdatedFields\"@\n\nIndexField\
|
||||
\x12\x1c\n\x05field\x18\x01\x20\x01(\x0b2\x06.FieldR\x05field\x12\x14\n\
|
||||
\x05index\x18\x02\x20\x01(\x05R\x05index\"\x90\x01\n\x1aGetEditFieldCont\
|
||||
extPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1b\n\
|
||||
\x08field_id\x18\x02\x20\x01(\tH\0R\x07fieldId\x12)\n\nfield_type\x18\
|
||||
\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldTypeB\x11\n\x0fone_of_field_id\"q\
|
||||
\n\x10EditFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridI\
|
||||
d\x12\x19\n\x08field_id\x18\x02\x20\x01(\tR\x07fieldId\x12)\n\nfield_typ\
|
||||
e\x18\x03\x20\x01(\x0e2\n.FieldTypeR\tfieldType\"|\n\x10EditFieldContext\
|
||||
\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12%\n\ngrid_field\
|
||||
\x18\x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\x10type_option_data\
|
||||
\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\x12\x1c\n\
|
||||
\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12RepeatedFiel\
|
||||
dOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\x05items\"T\
|
||||
\n\x08RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\
|
||||
\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06height\x18\
|
||||
\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\x18\x01\
|
||||
\x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\x0b2\x17.\
|
||||
Row.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x03\x20\
|
||||
\x01(\x05R\x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\
|
||||
\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\x05.Cel\
|
||||
lR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\x01\x20\
|
||||
\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\x05item\
|
||||
s\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"U\n\x0eGridBlockOrder\x12\
|
||||
\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\x12(\n\nrow_orders\x18\
|
||||
\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\"_\n\rIndexRowOrder\x12&\n\tro\
|
||||
w_order\x18\x01\x20\x01(\x0b2\t.RowOrderR\x08rowOrder\x12\x16\n\x05index\
|
||||
\x18\x02\x20\x01(\x05H\0R\x05indexB\x0e\n\x0cone_of_index\"\xbf\x01\n\
|
||||
\x11GridRowsChangeset\x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07block\
|
||||
Id\x123\n\rinserted_rows\x18\x02\x20\x03(\x0b2\x0e.IndexRowOrderR\x0cins\
|
||||
ertedRows\x12,\n\x0cdeleted_rows\x18\x03\x20\x03(\x0b2\t.RowOrderR\x0bde\
|
||||
letedRows\x12,\n\x0cupdated_rows\x18\x04\x20\x03(\x0b2\t.RowOrderR\x0bup\
|
||||
datedRows\"E\n\tGridBlock\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\
|
||||
\n\nrow_orders\x18\x02\x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\x04Cell\
|
||||
\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07conte\
|
||||
nt\x18\x02\x20\x01(\tR\x07content\"\x8f\x01\n\x14CellNotificationData\
|
||||
\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08field_i\
|
||||
d\x18\x02\x20\x01(\tR\x07fieldId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\
|
||||
\x05rowId\x12\x1a\n\x07content\x18\x04\x20\x01(\tH\0R\x07contentB\x10\n\
|
||||
\x0eone_of_content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\x20\
|
||||
\x03(\x0b2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\n\x04nam\
|
||||
e\x18\x01\x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\
|
||||
\x01\x20\x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\x18\x01\
|
||||
\x20\x01(\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid_id\x18\
|
||||
\x01\x20\x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0\
|
||||
R\nstartRowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12InsertFieldPa\
|
||||
yload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05fi\
|
||||
eld\x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type_option_data\
|
||||
\x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_field_id\x18\
|
||||
\x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_field_id\"d\n\
|
||||
\x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\
|
||||
\x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\
|
||||
\x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\
|
||||
\x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\
|
||||
\x0f.GridBlockOrderR\x0bblockOrders\"\xa8\x03\n\x15FieldChangesetPayload\
|
||||
\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07grid_\
|
||||
id\x18\x02\x20\x01(\tR\x06gridId\x12\x14\n\x04name\x18\x03\x20\x01(\tH\0\
|
||||
R\x04name\x12\x14\n\x04desc\x18\x04\x20\x01(\tH\x01R\x04desc\x12+\n\nfie\
|
||||
ld_type\x18\x05\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\n\x06\
|
||||
frozen\x18\x06\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\x18\
|
||||
\x07\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x08\x20\x01(\
|
||||
\x05H\x05R\x05width\x12*\n\x10type_option_data\x18\t\x20\x01(\x0cH\x06R\
|
||||
\x0etypeOptionDataB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\x11one\
|
||||
_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\x0e\n\
|
||||
\x0cone_of_widthB\x19\n\x17one_of_type_option_data\"\x9c\x01\n\x0fMoveIt\
|
||||
emPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x17\n\
|
||||
\x07item_id\x18\x02\x20\x01(\tR\x06itemId\x12\x1d\n\nfrom_index\x18\x03\
|
||||
\x20\x01(\x05R\tfromIndex\x12\x19\n\x08to_index\x18\x04\x20\x01(\x05R\
|
||||
\x07toIndex\x12\x1d\n\x02ty\x18\x05\x20\x01(\x0e2\r.MoveItemTypeR\x02ty\
|
||||
\"\x7f\n\rCellChangeset\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06grid\
|
||||
Id\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_i\
|
||||
d\x18\x03\x20\x01(\tR\x07fieldId\x12\x14\n\x04data\x18\x04\x20\x01(\tH\0\
|
||||
R\x04dataB\r\n\x0bone_of_data**\n\x0cMoveItemType\x12\r\n\tMoveField\x10\
|
||||
\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\x10\
|
||||
\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0c\
|
||||
SingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Check\
|
||||
box\x10\x05b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
@ -13,6 +13,7 @@ message Field {
|
||||
bool frozen = 5;
|
||||
bool visibility = 6;
|
||||
int32 width = 7;
|
||||
bool is_primary = 8;
|
||||
}
|
||||
message FieldOrder {
|
||||
string field_id = 1;
|
||||
|
@ -47,8 +47,8 @@ mod tests {
|
||||
fn create_default_grid_test() {
|
||||
let grid_id = "1".to_owned();
|
||||
let build_context = GridBuilder::default()
|
||||
.add_field(FieldMeta::new("Name", "", FieldType::RichText))
|
||||
.add_field(FieldMeta::new("Tags", "", FieldType::SingleSelect))
|
||||
.add_field(FieldMeta::new("Name", "", FieldType::RichText, true))
|
||||
.add_field(FieldMeta::new("Tags", "", FieldType::SingleSelect, false))
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
|
Loading…
Reference in New Issue
Block a user