Merge pull request #457 from AppFlowy-IO/fix_0.0.4_beta_2_bugs

Fix 0.0.4 beta 2 bugs
This commit is contained in:
Nathan.fooo 2022-04-17 21:14:04 +08:00 committed by GitHub
commit 3af558fd94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 3375 additions and 1311 deletions

View File

@ -44,7 +44,7 @@ jobs:
- name: Run rust-lib tests
working-directory: frontend/rust-lib
run: RUST_LOG=info cargo test --no-default-features
run: RUST_LOG=info cargo test --no-default-features --features="sync"
- name: Run shared-lib tests
working-directory: shared-lib

View File

@ -17,7 +17,6 @@ import 'package:app_flowy/user/presentation/router.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
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';
@ -150,16 +149,10 @@ void _resolveGridDeps(GetIt getIt) {
(view, _) => GridBloc(view: view),
);
getIt.registerFactoryParam<RowBloc, RowData, void>(
(data, _) => RowBloc(
rowData: data,
),
);
getIt.registerFactoryParam<GridHeaderBloc, String, List<Field>>(
(gridId, fields) => GridHeaderBloc(
data: GridHeaderData(gridId: gridId, fields: fields),
service: FieldService(gridId: gridId),
getIt.registerFactoryParam<GridHeaderBloc, String, GridFieldCache>(
(gridId, fieldCache) => GridHeaderBloc(
gridId: gridId,
fieldCache: fieldCache,
),
);
@ -177,32 +170,32 @@ void _resolveGridDeps(GetIt getIt) {
),
);
getIt.registerFactoryParam<TextCellBloc, CellData, void>(
getIt.registerFactoryParam<TextCellBloc, GridCellIdentifier, void>(
(cellData, _) => TextCellBloc(
service: CellService(),
cellData: cellData,
),
);
getIt.registerFactoryParam<SelectionCellBloc, CellData, void>(
getIt.registerFactoryParam<SelectionCellBloc, GridCellIdentifier, void>(
(cellData, _) => SelectionCellBloc(
cellData: cellData,
),
);
getIt.registerFactoryParam<NumberCellBloc, CellData, void>(
getIt.registerFactoryParam<NumberCellBloc, GridCellIdentifier, void>(
(cellData, _) => NumberCellBloc(
cellData: cellData,
),
);
getIt.registerFactoryParam<DateCellBloc, CellData, void>(
getIt.registerFactoryParam<DateCellBloc, GridCellIdentifier, void>(
(cellData, _) => DateCellBloc(
cellData: cellData,
cellIdentifier: cellData,
),
);
getIt.registerFactoryParam<CheckboxCellBloc, CellData, void>(
getIt.registerFactoryParam<CheckboxCellBloc, GridCellIdentifier, void>(
(cellData, _) => CheckboxCellBloc(
service: CellService(),
cellData: cellData,
@ -229,7 +222,7 @@ void _resolveGridDeps(GetIt getIt) {
(typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption),
);
getIt.registerFactoryParam<GridPropertyBloc, String, List<Field>>(
(gridId, fields) => GridPropertyBloc(gridId: gridId, fields: fields),
getIt.registerFactoryParam<GridPropertyBloc, String, GridFieldCache>(
(gridId, cache) => GridPropertyBloc(gridId: gridId, fieldCache: cache),
);
}

View File

@ -123,9 +123,9 @@ class ApplicationBlocObserver extends BlocObserver {
super.onError(bloc, error, stackTrace);
}
@override
void onEvent(Bloc bloc, Object? event) {
Log.debug("$event");
super.onEvent(bloc, event);
}
// @override
// void onEvent(Bloc bloc, Object? event) {
// Log.debug("$event");
// super.onEvent(bloc, event);
// }
}

View File

@ -12,7 +12,7 @@ typedef UpdateFieldNotifiedValue = Either<CellNotificationData, FlowyError>;
class CellListener {
final String rowId;
final String fieldId;
PublishNotifier<UpdateFieldNotifiedValue> updateCellNotifier = PublishNotifier();
PublishNotifier<UpdateFieldNotifiedValue>? updateCellNotifier = PublishNotifier();
GridNotificationListener? _listener;
CellListener({required this.rowId, required this.fieldId});
@ -24,8 +24,8 @@ class CellListener {
switch (ty) {
case GridNotification.DidUpdateCell:
result.fold(
(payload) => updateCellNotifier.value = left(CellNotificationData.fromBuffer(payload)),
(error) => updateCellNotifier.value = right(error),
(payload) => updateCellNotifier?.value = left(CellNotificationData.fromBuffer(payload)),
(error) => updateCellNotifier?.value = right(error),
);
break;
default:
@ -35,6 +35,7 @@ class CellListener {
Future<void> stop() async {
await _listener?.stop();
updateCellNotifier.dispose();
updateCellNotifier?.dispose();
updateCellNotifier = null;
}
}

View File

@ -0,0 +1,75 @@
import 'dart:collection';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
class CellService {
CellService();
Future<Either<void, FlowyError>> updateCell({
required String gridId,
required String fieldId,
required String rowId,
required String data,
}) {
final payload = CellChangeset.create()
..gridId = gridId
..fieldId = fieldId
..rowId = rowId
..data = data;
return GridEventUpdateCell(payload).send();
}
Future<Either<Cell, FlowyError>> getCell({
required String gridId,
required String fieldId,
required String rowId,
}) {
final payload = CellIdentifierPayload.create()
..gridId = gridId
..fieldId = fieldId
..rowId = rowId;
return GridEventGetCell(payload).send();
}
}
class CellCache {
final CellService _cellService;
final HashMap<String, Cell> _cellDataMap = HashMap();
CellCache() : _cellService = CellService();
Future<Option<Cell>> getCellData(GridCellIdentifier identifier) async {
final cellId = _cellId(identifier);
final Cell? data = _cellDataMap[cellId];
if (data != null) {
return Future(() => Some(data));
}
final result = await _cellService.getCell(
gridId: identifier.gridId,
fieldId: identifier.field.id,
rowId: identifier.rowId,
);
return result.fold(
(cell) {
_cellDataMap[_cellId(identifier)] = cell;
return Some(cell);
},
(err) {
Log.error(err);
return none();
},
);
}
String _cellId(GridCellIdentifier identifier) {
return "${identifier.rowId}/${identifier.field.id}";
}
}

View File

@ -1,4 +1,4 @@
import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
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;
@ -15,7 +15,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
CheckboxCellBloc({
required CellService service,
required CellData cellData,
required GridCellIdentifier cellData,
}) : _service = service,
_listener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
super(CheckboxCellState.initial(cellData)) {
@ -43,7 +43,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
}
void _startListening() {
_listener.updateCellNotifier.addPublishListener((result) {
_listener.updateCellNotifier?.addPublishListener((result) {
result.fold(
(notificationData) async => await _loadCellData(),
(err) => Log.error(err),
@ -58,12 +58,11 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(cell) {
if (!isClosed) {
add(CheckboxCellEvent.didReceiveCellUpdate(cell));
}
},
(cell) => add(CheckboxCellEvent.didReceiveCellUpdate(cell)),
(err) => Log.error(err),
);
}
@ -88,11 +87,11 @@ class CheckboxCellEvent with _$CheckboxCellEvent {
@freezed
class CheckboxCellState with _$CheckboxCellState {
const factory CheckboxCellState({
required CellData cellData,
required GridCellIdentifier cellData,
required bool isSelected,
}) = _CheckboxCellState;
factory CheckboxCellState.initial(CellData cellData) {
factory CheckboxCellState.initial(GridCellIdentifier cellData) {
return CheckboxCellState(cellData: cellData, isSelected: _isSelected(cellData.cell));
}
}

View File

@ -1,8 +1,8 @@
import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
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:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, Field;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
@ -13,13 +13,13 @@ part 'date_cell_bloc.freezed.dart';
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
final CellService _service;
final CellListener _cellListener;
final FieldListener _fieldListener;
final SingleFieldListener _fieldListener;
DateCellBloc({required CellData cellData})
DateCellBloc({required GridCellIdentifier cellIdentifier})
: _service = CellService(),
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
_fieldListener = FieldListener(fieldId: cellData.field.id),
super(DateCellState.initial(cellData)) {
_cellListener = CellListener(rowId: cellIdentifier.rowId, fieldId: cellIdentifier.field.id),
_fieldListener = SingleFieldListener(fieldId: cellIdentifier.field.id),
super(DateCellState.initial(cellIdentifier)) {
on<DateCellEvent>(
(event, emit) async {
event.map(
@ -35,6 +35,10 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
content: value.cell.content,
));
},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit(state.copyWith(field: value.field));
_loadCellData();
},
);
},
);
@ -48,20 +52,20 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
}
void _startListening() {
_cellListener.updateCellNotifier.addPublishListener((result) {
_cellListener.updateCellNotifier?.addPublishListener((result) {
result.fold(
(notificationData) => _loadCellData(),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_cellListener.start();
_fieldListener.updateFieldNotifier.addPublishListener((result) {
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) => _loadCellData(),
(field) => add(DateCellEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_fieldListener.start();
}
@ -71,12 +75,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(cell) {
if (!isClosed) {
add(DateCellEvent.didReceiveCellUpdate(cell));
}
},
(cell) => add(DateCellEvent.didReceiveCellUpdate(cell)),
(err) => Log.error(err),
);
}
@ -97,18 +100,21 @@ class DateCellEvent with _$DateCellEvent {
const factory DateCellEvent.initial() = _InitialCell;
const factory DateCellEvent.selectDay(DateTime day) = _SelectDay;
const factory DateCellEvent.didReceiveCellUpdate(Cell cell) = _DidReceiveCellUpdate;
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
}
@freezed
class DateCellState with _$DateCellState {
const factory DateCellState({
required CellData cellData,
required GridCellIdentifier cellData,
required String content,
required Field field,
DateTime? selectedDay,
}) = _DateCellState;
factory DateCellState.initial(CellData cellData) => DateCellState(
factory DateCellState.initial(GridCellIdentifier cellData) => DateCellState(
cellData: cellData,
field: cellData.field,
content: cellData.cell?.content ?? "",
);
}

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
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';
@ -11,12 +12,14 @@ part 'number_cell_bloc.freezed.dart';
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
final CellService _service;
final CellListener _listener;
final CellListener _cellListener;
final SingleFieldListener _fieldListener;
NumberCellBloc({
required CellData cellData,
required GridCellIdentifier cellData,
}) : _service = CellService(),
_listener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
super(NumberCellState.initial(cellData)) {
on<NumberCellEvent>(
(event, emit) async {
@ -27,36 +30,36 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
emit(state.copyWith(content: value.cell.content));
},
updateCell: (_UpdateCell value) {
_updateCellValue(value, emit);
updateCell: (_UpdateCell value) async {
await _updateCellValue(value, emit);
},
);
},
);
}
void _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) {
final number = num.tryParse(value.text);
if (number == null) {
emit(state.copyWith(content: ""));
} else {
_service.updateCell(
gridId: state.cellData.gridId,
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
data: value.text,
);
}
Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
final result = await _service.updateCell(
gridId: state.cellData.gridId,
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
data: value.text,
);
result.fold(
(field) => _getCellData(),
(err) => Log.error(err),
);
}
@override
Future<void> close() async {
await _listener.stop();
await _cellListener.stop();
await _fieldListener.stop();
return super.close();
}
void _startListening() {
_listener.updateCellNotifier.addPublishListener((result) {
_cellListener.updateCellNotifier?.addPublishListener((result) {
result.fold(
(notificationData) async {
await _getCellData();
@ -64,7 +67,15 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
(err) => Log.error(err),
);
});
_listener.start();
_cellListener.start();
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) => _getCellData(),
(err) => Log.error(err),
);
});
_fieldListener.start();
}
Future<void> _getCellData() async {
@ -73,12 +84,12 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(cell) {
if (!isClosed) {
add(NumberCellEvent.didReceiveCellUpdate(cell));
}
},
(cell) => add(NumberCellEvent.didReceiveCellUpdate(cell)),
(err) => Log.error(err),
);
}
@ -94,11 +105,11 @@ class NumberCellEvent with _$NumberCellEvent {
@freezed
class NumberCellState with _$NumberCellState {
const factory NumberCellState({
required CellData cellData,
required GridCellIdentifier cellData,
required String content,
}) = _NumberCellState;
factory NumberCellState.initial(CellData cellData) {
factory NumberCellState.initial(GridCellIdentifier cellData) {
return NumberCellState(cellData: cellData, content: cellData.cell?.content ?? "");
}
}

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/cell_bloc/select_option_service.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_sdk/log.dart';
@ -13,13 +13,13 @@ part 'selection_cell_bloc.freezed.dart';
class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
final SelectOptionService _service;
final CellListener _cellListener;
final FieldListener _fieldListener;
final SingleFieldListener _fieldListener;
SelectionCellBloc({
required CellData cellData,
required GridCellIdentifier cellData,
}) : _service = SelectOptionService(),
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
_fieldListener = FieldListener(fieldId: cellData.field.id),
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
super(SelectionCellState.initial(cellData)) {
on<SelectionCellEvent>(
(event, emit) async {
@ -49,22 +49,21 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(selectOptionContext) {
if (!isClosed) {
add(SelectionCellEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
));
}
},
(selectOptionContext) => add(SelectionCellEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
)),
(err) => Log.error(err),
);
}
void _startListening() {
_cellListener.updateCellNotifier.addPublishListener((result) {
_cellListener.updateCellNotifier?.addPublishListener((result) {
result.fold(
(notificationData) => _loadOptions(),
(err) => Log.error(err),
@ -72,7 +71,7 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
});
_cellListener.start();
_fieldListener.updateFieldNotifier.addPublishListener((result) {
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) => _loadOptions(),
(err) => Log.error(err),
@ -94,12 +93,12 @@ class SelectionCellEvent with _$SelectionCellEvent {
@freezed
class SelectionCellState with _$SelectionCellState {
const factory SelectionCellState({
required CellData cellData,
required GridCellIdentifier cellData,
required List<SelectOption> options,
required List<SelectOption> selectedOptions,
}) = _SelectionCellState;
factory SelectionCellState.initial(CellData cellData) => SelectionCellState(
factory SelectionCellState.initial(GridCellIdentifier cellData) => SelectionCellState(
cellData: cellData,
options: [],
selectedOptions: [],

View File

@ -1,4 +1,4 @@
import 'package:app_flowy/workspace/application/grid/cell_bloc/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
import 'package:app_flowy/workspace/application/grid/field/field_listener.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_sdk/log.dart';
@ -13,16 +13,16 @@ part 'selection_editor_bloc.freezed.dart';
class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
final SelectOptionService _selectOptionService;
final FieldListener _fieldListener;
final SingleFieldListener _fieldListener;
final CellListener _cellListener;
Timer? _delayOperation;
SelectOptionEditorBloc({
required CellData cellData,
required GridCellIdentifier cellData,
required List<SelectOption> options,
required List<SelectOption> selectedOptions,
}) : _selectOptionService = SelectOptionService(),
_fieldListener = FieldListener(fieldId: cellData.field.id),
_fieldListener = SingleFieldListener(fieldId: cellData.field.id),
_cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id),
super(SelectOptionEditorState.initial(cellData, options, selectedOptions)) {
on<SelectOptionEditorEvent>(
@ -117,16 +117,15 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
fieldId: state.field.id,
rowId: state.rowId,
);
if (isClosed) {
return;
}
result.fold(
(selectOptionContext) {
if (!isClosed) {
add(SelectOptionEditorEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
));
}
},
(selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
)),
(err) => Log.error(err),
);
},
@ -134,7 +133,7 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
}
void _startListening() {
_cellListener.updateCellNotifier.addPublishListener((result) {
_cellListener.updateCellNotifier?.addPublishListener((result) {
result.fold(
(notificationData) => _loadOptions(),
(err) => Log.error(err),
@ -142,12 +141,12 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
});
_cellListener.start();
_fieldListener.updateFieldNotifier.addPublishListener((result) {
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_fieldListener.start();
}
}
@ -175,7 +174,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
}) = _SelectOptionEditorState;
factory SelectOptionEditorState.initial(
CellData cellData,
GridCellIdentifier cellData,
List<SelectOption> options,
List<SelectOption> selectedOptions,
) {

View File

@ -11,7 +11,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
TextCellBloc({
required this.service,
required CellData cellData,
required GridCellIdentifier cellData,
}) : super(TextCellState.initial(cellData)) {
on<TextCellEvent>(
(event, emit) async {
@ -53,7 +53,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
@freezed
class TextCellEvent with _$TextCellEvent {
const factory TextCellEvent.initial() = _InitialCell;
const factory TextCellEvent.didReceiveCellData(CellData cellData) = _DidReceiveCellData;
const factory TextCellEvent.didReceiveCellData(GridCellIdentifier cellData) = _DidReceiveCellData;
const factory TextCellEvent.updateText(String text) = _UpdateText;
}
@ -61,10 +61,10 @@ class TextCellEvent with _$TextCellEvent {
class TextCellState with _$TextCellState {
const factory TextCellState({
required String content,
required CellData cellData,
required GridCellIdentifier cellData,
}) = _TextCellState;
factory TextCellState.initial(CellData cellData) => TextCellState(
factory TextCellState.initial(GridCellIdentifier cellData) => TextCellState(
content: cellData.cell?.content ?? "",
cellData: cellData,
);

View File

@ -1,35 +0,0 @@
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
class CellService {
CellService();
Future<Either<void, FlowyError>> updateCell({
required String gridId,
required String fieldId,
required String rowId,
required String data,
}) {
final payload = CellChangeset.create()
..gridId = gridId
..fieldId = fieldId
..rowId = rowId
..data = data;
return GridEventUpdateCell(payload).send();
}
Future<Either<Cell, FlowyError>> getCell({
required String gridId,
required String fieldId,
required String rowId,
}) {
final payload = CellIdentifierPayload.create()
..gridId = gridId
..fieldId = fieldId
..rowId = rowId;
return GridEventGetCell(payload).send();
}
}

View File

@ -1,8 +0,0 @@
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
class GridHeaderData {
final String gridId;
final List<Field> fields;
GridHeaderData({required this.gridId, required this.fields});
}

View File

@ -9,11 +9,13 @@ import 'dart:async';
part 'field_cell_bloc.freezed.dart';
class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
final FieldListener _fieldListener;
final SingleFieldListener _fieldListener;
final FieldService _fieldService;
FieldCellBloc({
required GridFieldCellContext cellContext,
}) : _fieldListener = FieldListener(fieldId: cellContext.field.id),
}) : _fieldListener = SingleFieldListener(fieldId: cellContext.field.id),
_fieldService = FieldService(gridId: cellContext.gridId),
super(FieldCellState.initial(cellContext)) {
on<FieldCellEvent>(
(event, emit) async {
@ -24,6 +26,13 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit(state.copyWith(field: value.field));
},
updateWidth: (_UpdateWidth value) {
final defaultWidth = state.field.width.toDouble();
final width = defaultWidth + value.offset;
if (width > defaultWidth && width < 300) {
_fieldService.updateField(fieldId: state.field.id, width: width);
}
},
);
},
);
@ -36,12 +45,12 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
}
void _startListening() {
_fieldListener.updateFieldNotifier.addPublishListener((result) {
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_fieldListener.start();
}
}
@ -50,6 +59,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
class FieldCellEvent with _$FieldCellEvent {
const factory FieldCellEvent.initial() = _InitialCell;
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
const factory FieldCellEvent.updateWidth(double offset) = _UpdateWidth;
}
@freezed

View File

@ -9,12 +9,12 @@ import 'package:app_flowy/core/notification_helper.dart';
typedef UpdateFieldNotifiedValue = Either<Field, FlowyError>;
class FieldListener {
class SingleFieldListener {
final String fieldId;
PublishNotifier<UpdateFieldNotifiedValue> updateFieldNotifier = PublishNotifier();
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldNotifier = PublishNotifier();
GridNotificationListener? _listener;
FieldListener({required this.fieldId});
SingleFieldListener({required this.fieldId});
void start() {
_listener = GridNotificationListener(
@ -30,8 +30,8 @@ class FieldListener {
switch (ty) {
case GridNotification.DidUpdateField:
result.fold(
(payload) => updateFieldNotifier.value = left(Field.fromBuffer(payload)),
(error) => updateFieldNotifier.value = right(error),
(payload) => updateFieldNotifier?.value = left(Field.fromBuffer(payload)),
(error) => updateFieldNotifier?.value = right(error),
);
break;
default:
@ -41,6 +41,7 @@ class FieldListener {
Future<void> stop() async {
await _listener?.stop();
updateFieldNotifier.dispose();
updateFieldNotifier?.dispose();
updateFieldNotifier = null;
}
}

View File

@ -1,9 +1,10 @@
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.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/field_entities.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'field_service.freezed.dart';
class FieldService {
final String gridId;
@ -19,6 +20,26 @@ class FieldService {
return GridEventSwitchToField(payload).send();
}
Future<Either<EditFieldContext, FlowyError>> getEditFieldContext(String fieldId, FieldType fieldType) {
final payload = GetEditFieldContextPayload.create()
..gridId = gridId
..fieldId = fieldId
..fieldType = fieldType;
return GridEventGetEditFieldContext(payload).send();
}
Future<Either<Unit, FlowyError>> moveField(String fieldId, int fromIndex, int toIndex) {
final payload = MoveItemPayload.create()
..gridId = gridId
..itemId = fieldId
..ty = MoveItemType.MoveField
..fromIndex = fromIndex
..toIndex = toIndex;
return GridEventMoveItem(payload).send();
}
Future<Either<Unit, FlowyError>> updateField({
required String fieldId,
String? name,
@ -98,17 +119,12 @@ class FieldService {
}
}
class GridFieldCellContext extends Equatable {
final String gridId;
final Field field;
const GridFieldCellContext({
required this.gridId,
required this.field,
});
@override
List<Object> get props => [field.id];
@freezed
class GridFieldCellContext with _$GridFieldCellContext {
const factory GridFieldCellContext({
required String gridId,
required Field field,
}) = _GridFieldCellContext;
}
abstract class EditFieldContextLoader {

View File

@ -7,11 +7,11 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
typedef UpdateFieldNotifiedValue = Either<List<Field>, FlowyError>;
typedef UpdateFieldNotifiedValue = Either<GridFieldChangeset, FlowyError>;
class GridFieldsListener {
final String gridId;
PublishNotifier<UpdateFieldNotifiedValue> updateFieldsNotifier = PublishNotifier();
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier = PublishNotifier();
GridNotificationListener? _listener;
GridFieldsListener({required this.gridId});
@ -24,10 +24,10 @@ class GridFieldsListener {
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGrid:
case GridNotification.DidUpdateGridField:
result.fold(
(payload) => updateFieldsNotifier.value = left(RepeatedField.fromBuffer(payload).items),
(error) => updateFieldsNotifier.value = right(error),
(payload) => updateFieldsNotifier?.value = left(GridFieldChangeset.fromBuffer(payload)),
(error) => updateFieldsNotifier?.value = right(error),
);
break;
default:
@ -37,6 +37,7 @@ class GridFieldsListener {
Future<void> stop() async {
await _listener?.stop();
updateFieldsNotifier.dispose();
updateFieldsNotifier?.dispose();
updateFieldsNotifier = null;
}
}

View File

@ -1,13 +1,10 @@
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'field/grid_listenr.dart';
import 'grid_listener.dart';
import 'grid_service.dart';
import 'row/row_service.dart';
@ -15,20 +12,20 @@ part 'grid_bloc.freezed.dart';
class GridBloc extends Bloc<GridEvent, GridState> {
final GridService _gridService;
final GridListener _gridListener;
final GridFieldsListener _fieldListener;
final GridFieldCache fieldCache;
final GridRowCache rowCache;
GridBloc({required View view})
: _fieldListener = GridFieldsListener(gridId: view.id),
_gridService = GridService(gridId: view.id),
_gridListener = GridListener(gridId: view.id),
: _gridService = GridService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id),
rowCache = GridRowCache(gridId: view.id),
super(GridState.initial(view.id)) {
on<GridEvent>(
(event, emit) async {
await event.map(
initial: (InitialGrid value) async {
await _initGrid(emit);
_startListening();
await _loadGrid(emit);
},
createRow: (_CreateRow value) {
_gridService.createRow();
@ -37,12 +34,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
emit(state.copyWith(rows: value.rows, listState: value.listState));
},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
final rows = state.rows.map((row) => row.copyWith(fields: value.fields)).toList();
emit(state.copyWith(
rows: rows,
fields: value.fields,
listState: const GridListState.reload(),
));
emit(state.copyWith(rows: rowCache.clonedRows, fields: value.fields));
},
);
},
@ -52,42 +44,21 @@ class GridBloc extends Bloc<GridEvent, GridState> {
@override
Future<void> close() async {
await _gridService.closeGrid();
await _fieldListener.stop();
await _gridListener.stop();
await fieldCache.dispose();
await rowCache.dispose();
return super.close();
}
Future<void> _initGrid(Emitter<GridState> emit) async {
_fieldListener.updateFieldsNotifier.addPublishListener((result) {
result.fold(
(fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
(err) => Log.error(err),
);
});
_fieldListener.start();
await _loadGrid(emit);
}
void _startListening() {
_gridListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold((gridBlockChangeset) {
for (final changeset in gridBlockChangeset) {
if (changeset.insertedRows.isNotEmpty) {
_insertRows(changeset.insertedRows);
}
fieldCache.addListener(
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed,
);
if (changeset.deletedRows.isNotEmpty) {
_deleteRows(changeset.deletedRows);
}
if (changeset.updatedRows.isNotEmpty) {
_updateRows(changeset.updatedRows);
}
}
}, (err) => Log.error(err));
});
_gridListener.start();
rowCache.addListener(
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
listenWhen: () => !isClosed,
);
}
Future<void> _loadGrid(Emitter<GridState> emit) async {
@ -105,10 +76,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
return Future(
() => result.fold(
(fields) {
fieldCache.fields = fields.items;
rowCache.updateWithBlock(grid.blockOrders, fieldCache.unmodifiableFields);
emit(state.copyWith(
grid: Some(grid),
fields: fields.items,
rows: _buildRows(grid.blockOrders, fields.items),
fields: fieldCache.clonedFields,
rows: rowCache.clonedRows,
loadingState: GridLoadingState.finish(left(unit)),
));
},
@ -116,67 +90,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
),
);
}
void _deleteRows(List<RowOrder> deletedRows) {
final List<RowData> rows = [];
final List<Tuple2<int, RowData>> deletedIndex = [];
final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
state.rows.asMap().forEach((index, value) {
if (deletedRowMap[value.rowId] == null) {
rows.add(value);
} else {
deletedIndex.add(Tuple2(index, value));
}
});
add(GridEvent.didReceiveRowUpdate(rows, GridListState.delete(deletedIndex)));
}
void _insertRows(List<IndexRowOrder> createdRows) {
final List<RowData> rows = List.from(state.rows);
List<int> insertIndexs = [];
for (final newRow in createdRows) {
if (newRow.hasIndex()) {
insertIndexs.add(newRow.index);
rows.insert(newRow.index, _toRowData(newRow.rowOrder));
} else {
insertIndexs.add(rows.length);
rows.add(_toRowData(newRow.rowOrder));
}
}
add(GridEvent.didReceiveRowUpdate(rows, GridListState.insert(insertIndexs)));
}
void _updateRows(List<RowOrder> updatedRows) {
final List<RowData> rows = List.from(state.rows);
final List<int> updatedIndexs = [];
for (final updatedRow in updatedRows) {
final index = rows.indexWhere((row) => row.rowId == updatedRow.rowId);
if (index != -1) {
rows.removeAt(index);
rows.insert(index, _toRowData(updatedRow));
updatedIndexs.add(index);
}
}
add(GridEvent.didReceiveRowUpdate(rows, const GridListState.reload()));
}
List<RowData> _buildRows(List<GridBlockOrder> blockOrders, List<Field> fields) {
return blockOrders.expand((blockOrder) => blockOrder.rowOrders).map((rowOrder) {
return RowData.fromBlockRow(state.gridId, rowOrder, fields);
}).toList();
}
RowData _toRowData(RowOrder rowOrder) {
return RowData.fromBlockRow(state.gridId, rowOrder, state.fields);
}
}
@freezed
class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.didReceiveRowUpdate(List<RowData> rows, GridListState listState) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveRowUpdate(List<GridRow> rows, GridRowChangeReason listState) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
}
@ -186,9 +106,9 @@ class GridState with _$GridState {
required String gridId,
required Option<Grid> grid,
required List<Field> fields,
required List<RowData> rows,
required List<GridRow> rows,
required GridLoadingState loadingState,
required GridListState listState,
required GridRowChangeReason listState,
}) = _GridState;
factory GridState.initial(String gridId) => GridState(
@ -197,7 +117,7 @@ class GridState with _$GridState {
grid: none(),
gridId: gridId,
loadingState: const _Loading(),
listState: const _Reload(),
listState: const InitialListState(),
);
}
@ -206,10 +126,3 @@ class GridLoadingState with _$GridLoadingState {
const factory GridLoadingState.loading() = _Loading;
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
}
@freezed
class GridListState with _$GridListState {
const factory GridListState.insert(List<int> indexs) = _Insert;
const factory GridListState.delete(List<Tuple2<int, RowData>> indexs) = _Delete;
const factory GridListState.reload() = _Reload;
}

View File

@ -1,54 +1,49 @@
import 'package:app_flowy/workspace/application/grid/data.dart';
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
import 'grid_service.dart';
part 'grid_header_bloc.freezed.dart';
class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
final FieldService service;
final GridFieldsListener _fieldListener;
final FieldService _fieldService;
final GridFieldCache fieldCache;
GridHeaderBloc({
required GridHeaderData data,
required this.service,
}) : _fieldListener = GridFieldsListener(gridId: data.gridId),
super(GridHeaderState.initial(data.fields)) {
required String gridId,
required this.fieldCache,
}) : _fieldService = FieldService(gridId: gridId),
super(GridHeaderState.initial(fieldCache.clonedFields)) {
on<GridHeaderEvent>(
(event, emit) async {
await event.map(
initial: (_InitialHeader value) async {
_startListening();
},
createField: (_CreateField value) {},
insertField: (_InsertField value) {},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
value.fields.retainWhere((field) => field.visibility);
emit(state.copyWith(fields: value.fields));
},
moveField: (_MoveField value) async {
final result = await _fieldService.moveField(value.field.id, value.fromIndex, value.toIndex);
result.fold((l) {}, (err) => Log.error(err));
},
);
},
);
}
Future<void> _startListening() async {
_fieldListener.updateFieldsNotifier.addPublishListener((result) {
result.fold(
(fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
(err) => Log.error(err),
);
});
_fieldListener.start();
fieldCache.addListener(
onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed,
);
}
@override
Future<void> close() async {
await _fieldListener.stop();
return super.close();
}
}
@ -56,9 +51,8 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
@freezed
class GridHeaderEvent with _$GridHeaderEvent {
const factory GridHeaderEvent.initial() = _InitialHeader;
const factory GridHeaderEvent.createField() = _CreateField;
const factory GridHeaderEvent.insertField({required bool onLeft}) = _InsertField;
const factory GridHeaderEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
const factory GridHeaderEvent.moveField(Field field, int fromIndex, int toIndex) = _MoveField;
}
@freezed
@ -66,7 +60,8 @@ class GridHeaderState with _$GridHeaderState {
const factory GridHeaderState({required List<Field> fields}) = _GridHeaderState;
factory GridHeaderState.initial(List<Field> fields) {
fields.retainWhere((field) => field.visibility);
// final List<Field> newFields = List.from(fields);
// newFields.retainWhere((field) => field.visibility);
return GridHeaderState(fields: fields);
}
}

View File

@ -7,13 +7,12 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
class GridListener {
class GridRowListener {
final String gridId;
PublishNotifier<Either<List<GridBlockOrderChangeset>, FlowyError>> rowsUpdateNotifier =
PublishNotifier(comparable: null);
PublishNotifier<Either<List<GridRowsChangeset>, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
GridNotificationListener? _listener;
GridListener({required this.gridId});
GridRowListener({required this.gridId});
void start() {
_listener = GridNotificationListener(
@ -24,9 +23,9 @@ class GridListener {
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
switch (ty) {
case GridNotification.DidUpdateGridBlock:
case GridNotification.DidUpdateGridRow:
result.fold(
(payload) => rowsUpdateNotifier.value = left([GridBlockOrderChangeset.fromBuffer(payload)]),
(payload) => rowsUpdateNotifier.value = left([GridRowsChangeset.fromBuffer(payload)]),
(error) => rowsUpdateNotifier.value = right(error),
);
break;

View File

@ -1,8 +1,12 @@
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
class GridService {
final String gridId;
@ -35,3 +39,116 @@ class GridService {
return FolderEventCloseView(request).send();
}
}
class FieldsNotifier extends ChangeNotifier {
List<Field> _fields = [];
set fields(List<Field> fields) {
_fields = fields;
notifyListeners();
}
List<Field> get fields => _fields;
}
class GridFieldCache {
final String gridId;
late final GridFieldsListener _fieldListener;
final FieldsNotifier _fieldNotifier = FieldsNotifier();
GridFieldCache({required this.gridId}) {
_fieldListener = GridFieldsListener(gridId: gridId);
_fieldListener.updateFieldsNotifier?.addPublishListener((result) {
result.fold(
(changeset) {
_deleteFields(changeset.deletedFields);
_insertFields(changeset.insertedFields);
_updateFields(changeset.updatedFields);
},
(err) => Log.error(err),
);
});
_fieldListener.start();
}
Future<void> dispose() async {
await _fieldListener.stop();
_fieldNotifier.dispose();
}
void applyChangeset(GridFieldChangeset changeset) {}
UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields);
List<Field> get clonedFields => [..._fieldNotifier.fields];
set fields(List<Field> fields) {
_fieldNotifier.fields = [...fields];
}
VoidCallback addListener(
{VoidCallback? listener, void Function(List<Field>)? onChanged, bool Function()? listenWhen}) {
f() {
if (listenWhen != null && listenWhen() == false) {
return;
}
if (onChanged != null) {
onChanged(clonedFields);
}
if (listener != null) {
listener();
}
}
_fieldNotifier.addListener(f);
return f;
}
void removeListener(VoidCallback f) {
_fieldNotifier.removeListener(f);
}
void _deleteFields(List<FieldOrder> deletedFields) {
if (deletedFields.isEmpty) {
return;
}
final List<Field> fields = _fieldNotifier.fields;
final Map<String, FieldOrder> deletedFieldMap = {
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
};
fields.retainWhere((field) => (deletedFieldMap[field.id] == null));
_fieldNotifier.fields = fields;
}
void _insertFields(List<IndexField> insertedFields) {
if (insertedFields.isEmpty) {
return;
}
final List<Field> fields = _fieldNotifier.fields;
for (final indexField in insertedFields) {
if (fields.length > indexField.index) {
fields.insert(indexField.index, indexField.field_1);
} else {
fields.add(indexField.field_1);
}
}
_fieldNotifier.fields = fields;
}
void _updateFields(List<Field> updatedFields) {
if (updatedFields.isEmpty) {
return;
}
final List<Field> fields = _fieldNotifier.fields;
for (final updatedField in updatedFields) {
final index = fields.indexWhere((field) => field.id == updatedField.id);
if (index != -1) {
fields.removeAt(index);
fields.insert(index, updatedField);
}
}
_fieldNotifier.fields = fields;
}
}

View File

@ -2,11 +2,10 @@ export 'grid_bloc.dart';
export 'row/row_bloc.dart';
export 'row/row_service.dart';
export 'grid_service.dart';
export 'data.dart';
export 'grid_header_bloc.dart';
// Field
export 'field/field_service.dart';
export 'field/grid_header_bloc.dart';
export 'field/field_action_sheet_bloc.dart';
export 'field/field_editor_bloc.dart';
export 'field/field_switch_bloc.dart';
@ -17,12 +16,12 @@ export 'field/type_option/number_bloc.dart';
export 'field/type_option/single_select_bloc.dart';
// Cell
export 'cell_bloc/text_cell_bloc.dart';
export 'cell_bloc/number_cell_bloc.dart';
export 'cell_bloc/selection_cell_bloc.dart';
export 'cell_bloc/date_cell_bloc.dart';
export 'cell_bloc/checkbox_cell_bloc.dart';
export 'cell_bloc/cell_service.dart';
export 'cell/text_cell_bloc.dart';
export 'cell/number_cell_bloc.dart';
export 'cell/selection_cell_bloc.dart';
export 'cell/date_cell_bloc.dart';
export 'cell/checkbox_cell_bloc.dart';
export 'cell/cell_service.dart';
// Setting
export 'setting/setting_bloc.dart';

View File

@ -11,7 +11,7 @@ part 'row_action_sheet_bloc.freezed.dart';
class RowActionSheetBloc extends Bloc<RowActionSheetEvent, RowActionSheetState> {
final RowService _rowService;
RowActionSheetBloc({required RowData rowData})
RowActionSheetBloc({required GridRow rowData})
: _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
super(RowActionSheetState.initial(rowData)) {
on<RowActionSheetEvent>(
@ -49,10 +49,10 @@ class RowActionSheetEvent with _$RowActionSheetEvent {
@freezed
class RowActionSheetState with _$RowActionSheetState {
const factory RowActionSheetState({
required RowData rowData,
required GridRow rowData,
}) = _RowActionSheetState;
factory RowActionSheetState.initial(RowData rowData) => RowActionSheetState(
factory RowActionSheetState.initial(GridRow rowData) => RowActionSheetState(
rowData: rowData,
);
}

View File

@ -1,111 +1,115 @@
import 'dart:collection';
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
import 'package:flowy_sdk/log.dart';
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';
import 'row_listener.dart';
import 'row_service.dart';
import 'package:dartz/dartz.dart';
part 'row_bloc.freezed.dart';
typedef CellDataMap = LinkedHashMap<String, CellData>;
typedef CellDataMap = LinkedHashMap<String, GridCellIdentifier>;
class RowBloc extends Bloc<RowEvent, RowState> {
final RowService _rowService;
final RowListener _rowlistener;
final GridFieldsListener _fieldListener;
final GridFieldCache _fieldCache;
final GridRowCache _rowCache;
void Function()? _rowListenCallback;
void Function()? _fieldListenCallback;
RowBloc({required RowData rowData})
: _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
_fieldListener = GridFieldsListener(gridId: rowData.gridId),
_rowlistener = RowListener(rowId: rowData.rowId),
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>(
(event, emit) async {
await event.map(
initial: (_InitialRow value) async {
_startListening();
await _startListening();
await _loadRow(emit);
},
createRow: (_CreateRow value) {
_rowService.createRow();
},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) async {
await _handleFieldUpdate(emit, value);
},
didUpdateRow: (_DidUpdateRow value) async {
_handleRowUpdate(value, emit);
_handleRowUpdate(value.row, emit);
},
fieldsDidUpdate: (_FieldsDidUpdate value) async {
await _handleFieldUpdate(emit);
},
didLoadRow: (_DidLoadRow value) {
_handleRowUpdate(value.row, emit);
},
);
},
);
}
void _handleRowUpdate(_DidUpdateRow value, Emitter<RowState> emit) {
final CellDataMap cellDataMap = _makeCellDatas(value.row, state.rowData.fields);
void _handleRowUpdate(Row row, Emitter<RowState> emit) {
final CellDataMap cellDataMap = _makeCellDatas(row, state.rowData.fields);
emit(state.copyWith(
row: Future(() => Some(value.row)),
row: Future(() => Some(row)),
cellDataMap: Some(cellDataMap),
));
}
Future<void> _handleFieldUpdate(Emitter<RowState> emit, _DidReceiveFieldUpdate value) async {
Future<void> _handleFieldUpdate(Emitter<RowState> emit) async {
final optionRow = await state.row;
final CellDataMap cellDataMap = optionRow.fold(
() => CellDataMap.identity(),
(row) => _makeCellDatas(row, value.fields),
(row) => _makeCellDatas(row, _fieldCache.unmodifiableFields),
);
emit(state.copyWith(
rowData: state.rowData.copyWith(fields: value.fields),
rowData: state.rowData.copyWith(fields: _fieldCache.unmodifiableFields),
cellDataMap: Some(cellDataMap),
));
}
@override
Future<void> close() async {
await _rowlistener.stop();
await _fieldListener.stop();
if (_rowListenCallback != null) {
_rowCache.removeRowListener(_rowListenCallback!);
}
if (_fieldListenCallback != null) {
_fieldCache.removeListener(_fieldListenCallback!);
}
return super.close();
}
Future<void> _startListening() async {
_rowlistener.updateRowNotifier.addPublishListener((result) {
result.fold(
(row) => add(RowEvent.didUpdateRow(row)),
(err) => Log.error(err),
);
});
_fieldListenCallback = _fieldCache.addListener(
listener: () => add(const RowEvent.fieldsDidUpdate()),
listenWhen: () => !isClosed,
);
_fieldListener.updateFieldsNotifier.addPublishListener((result) {
result.fold(
(fields) => add(RowEvent.didReceiveFieldUpdate(fields)),
(err) => Log.error(err),
);
});
_rowlistener.start();
_fieldListener.start();
_rowListenCallback = _rowCache.addRowListener(
rowId: state.rowData.rowId,
onUpdated: (row) => add(RowEvent.didUpdateRow(row)),
listenWhen: () => !isClosed,
);
}
Future<void> _loadRow(Emitter<RowState> emit) async {
_rowService.getRow().then((result) {
return result.fold(
(row) => add(RowEvent.didUpdateRow(row)),
(err) => Log.error(err),
);
});
final data = await _rowCache.getRowData(state.rowData.rowId);
if (isClosed) {
return;
}
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] = CellData(
map[field.id] = GridCellIdentifier(
rowId: row.id,
gridId: _rowService.gridId,
cell: row.cellByFieldId[field.id],
@ -121,19 +125,20 @@ class RowBloc extends Bloc<RowEvent, RowState> {
class RowEvent with _$RowEvent {
const factory RowEvent.initial() = _InitialRow;
const factory RowEvent.createRow() = _CreateRow;
const factory RowEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
const factory RowEvent.fieldsDidUpdate() = _FieldsDidUpdate;
const factory RowEvent.didLoadRow(Row row) = _DidLoadRow;
const factory RowEvent.didUpdateRow(Row row) = _DidUpdateRow;
}
@freezed
class RowState with _$RowState {
const factory RowState({
required RowData rowData,
required GridRow rowData,
required Future<Option<Row>> row,
required Option<CellDataMap> cellDataMap,
}) = _RowState;
factory RowState.initial(RowData rowData) => RowState(
factory RowState.initial(GridRow rowData) => RowState(
rowData: rowData,
row: Future(() => none()),
cellDataMap: none(),

View File

@ -12,7 +12,7 @@ typedef UpdateFieldNotifiedValue = Either<List<Field>, FlowyError>;
class RowListener {
final String rowId;
PublishNotifier<UpdateRowNotifiedValue> updateRowNotifier = PublishNotifier();
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier = PublishNotifier();
GridNotificationListener? _listener;
RowListener({required this.rowId});
@ -25,8 +25,8 @@ class RowListener {
switch (ty) {
case GridNotification.DidUpdateRow:
result.fold(
(payload) => updateRowNotifier.value = left(Row.fromBuffer(payload)),
(error) => updateRowNotifier.value = right(error),
(payload) => updateRowNotifier?.value = left(Row.fromBuffer(payload)),
(error) => updateRowNotifier?.value = right(error),
);
break;
default:
@ -36,6 +36,7 @@ class RowListener {
Future<void> stop() async {
await _listener?.stop();
updateRowNotifier.dispose();
updateRowNotifier?.dispose();
updateRowNotifier = null;
}
}

View File

@ -1,12 +1,206 @@
import 'dart:collection';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/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();
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;
}
class GridRowCache {
final String gridId;
final GridRowListener _rowsListener;
final RowsNotifier _rowNotifier = RowsNotifier();
final HashMap<String, Row> _rowDataMap = HashMap();
UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
GridRowCache({required this.gridId}) : _rowsListener = GridRowListener(gridId: gridId) {
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold(
(changesets) {
for (final changeset in changesets) {
_deleteRows(changeset.deletedRows);
_insertRows(changeset.insertedRows);
_updateRows(changeset.updatedRows);
}
},
(err) => Log.error(err),
);
});
_rowsListener.start();
}
Future<void> dispose() async {
await _rowsListener.stop();
_rowNotifier.dispose();
}
List<GridRow> get clonedRows => [..._rowNotifier.rows];
void addListener({
void Function(List<GridRow>, GridRowChangeReason)? onChanged,
bool Function()? listenWhen,
}) {
_rowNotifier.addListener(() {
if (listenWhen != null && listenWhen() == false) {
return;
}
if (onChanged != null) {
onChanged(clonedRows, _rowNotifier._changeReason);
}
});
}
VoidCallback addRowListener({
required String rowId,
void Function(Row)? onUpdated,
bool Function()? listenWhen,
}) {
f() {
if (onUpdated == null) {
return;
}
if (listenWhen != null && listenWhen() == false) {
return;
}
_rowNotifier._changeReason.whenOrNull(update: (indexs) {
final row = _rowDataMap[rowId];
if (indexs[rowId] != null && row != null) {
onUpdated(row);
}
});
}
_rowNotifier.addListener(f);
return f;
}
void removeRowListener(VoidCallback callback) {
_rowNotifier.removeListener(callback);
}
Future<Option<Row>> getRowData(String rowId) async {
final Row? data = _rowDataMap[rowId];
if (data != null) {
return Future(() => Some(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();
},
);
});
}
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();
_rowNotifier.updateRows(newRows, const GridRowChangeReason.initial());
}
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};
_rowNotifier.rows.asMap().forEach((index, row) {
if (deletedRowMap[row.rowId] == null) {
newRows.add(row);
} else {
deletedIndex.add(DeletedIndex(index: index, row: row));
}
});
_rowNotifier.updateRows(newRows, GridRowChangeReason.delete(deletedIndex));
}
void _insertRows(List<IndexRowOrder> createdRows) {
if (createdRows.isEmpty) {
return;
}
InsertedIndexs insertIndexs = [];
final List<GridRow> newRows = _rowNotifier.rows;
for (final createdRow in createdRows) {
final gridRow = GridRow.fromBlockRow(gridId, createdRow.rowOrder, _fields);
insertIndexs.add(
InsertedIndex(
index: createdRow.index,
rowId: gridRow.rowId,
),
);
newRows.insert(createdRow.index, gridRow);
}
_rowNotifier.updateRows(newRows, GridRowChangeReason.insert(insertIndexs));
}
void _updateRows(List<RowOrder> updatedRows) {
if (updatedRows.isEmpty) {
return;
}
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
final List<GridRow> newRows = _rowNotifier.rows;
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));
_rowDataMap.remove(rowOrder.rowId);
updatedIndexs[rowOrder.rowId] = UpdatedIndex(index: index, rowId: rowOrder.rowId);
}
}
_rowNotifier.updateRows(newRows, GridRowChangeReason.update(updatedIndexs));
}
}
class RowService {
final String gridId;
final String rowId;
@ -21,6 +215,17 @@ class RowService {
return GridEventCreateRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveRow(String rowId, int fromIndex, int toIndex) {
final payload = MoveItemPayload.create()
..gridId = gridId
..itemId = rowId
..ty = MoveItemType.MoveRow
..fromIndex = fromIndex
..toIndex = toIndex;
return GridEventMoveItem(payload).send();
}
Future<Either<Row, FlowyError>> getRow() {
final payload = RowIdentifierPayload.create()
..gridId = gridId
@ -47,8 +252,8 @@ class RowService {
}
@freezed
class CellData with _$CellData {
const factory CellData({
class GridCellIdentifier with _$GridCellIdentifier {
const factory GridCellIdentifier({
required String gridId,
required String rowId,
required Field field,
@ -57,20 +262,61 @@ class CellData with _$CellData {
}
@freezed
class RowData with _$RowData {
const factory RowData({
class GridRow with _$GridRow {
const factory GridRow({
required String gridId,
required String rowId,
required List<Field> fields,
required double height,
}) = _RowData;
required Future<Option<Row>> data,
}) = _GridRow;
factory RowData.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
return RowData(
factory GridRow.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
return GridRow(
gridId: gridId,
rowId: row.rowId,
fields: fields,
rowId: row.rowId,
data: Future(() => none()),
height: row.height.toDouble(),
);
}
}
typedef InsertedIndexs = List<InsertedIndex>;
typedef DeletedIndexs = List<DeletedIndex>;
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
class InsertedIndex {
int index;
String rowId;
InsertedIndex({
required this.index,
required this.rowId,
});
}
class DeletedIndex {
int index;
GridRow row;
DeletedIndex({
required this.index,
required this.row,
});
}
class UpdatedIndex {
int index;
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;
}

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -10,12 +10,13 @@ part 'property_bloc.freezed.dart';
class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
final FieldService _service;
final GridFieldsListener _fieldListener;
final GridFieldCache _fieldCache;
Function()? _listenFieldCallback;
GridPropertyBloc({required String gridId, required List<Field> fields})
GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
: _service = FieldService(gridId: gridId),
_fieldListener = GridFieldsListener(gridId: gridId),
super(GridPropertyState.initial(gridId, fields)) {
_fieldCache = fieldCache,
super(GridPropertyState.initial(gridId, fieldCache.clonedFields)) {
on<GridPropertyEvent>(
(event, emit) async {
await event.map(
@ -32,6 +33,9 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit(state.copyWith(fields: value.fields));
},
moveField: (_MoveField value) {
//
},
);
},
);
@ -39,20 +43,17 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
@override
Future<void> close() async {
await _fieldListener.stop();
if (_listenFieldCallback != null) {
_fieldCache.removeListener(_listenFieldCallback!);
}
return super.close();
}
void _startListening() {
_fieldListener.updateFieldsNotifier.addPublishListener((result) {
result.fold(
(fields) {
add(GridPropertyEvent.didReceiveFieldUpdate(fields));
},
(err) => Log.error(err),
);
});
_fieldListener.start();
_listenFieldCallback = _fieldCache.addListener(
onChanged: (fields) => add(GridPropertyEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed,
);
}
}
@ -61,6 +62,7 @@ class GridPropertyEvent with _$GridPropertyEvent {
const factory GridPropertyEvent.initial() = _Initial;
const factory GridPropertyEvent.setFieldVisibility(String fieldId, bool visibility) = _SetFieldVisibility;
const factory GridPropertyEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
const factory GridPropertyEvent.moveField(int fromIndex, int toIndex) = _MoveField;
}
@freezed

View File

@ -78,10 +78,10 @@ class CreateItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final config = HoverDisplayConfig(hoverColor: theme.hover);
final config = HoverStyle(hoverColor: theme.hover);
return FlowyHover(
config: config,
style: config,
builder: (context, onHover) {
return GestureDetector(
onTap: () => onSelected(pluginBuilder),

View File

@ -43,7 +43,7 @@ class ViewSectionItem extends StatelessWidget {
return InkWell(
onTap: () => onSelected(context.read<ViewBloc>().state.view),
child: FlowyHover(
config: HoverDisplayConfig(hoverColor: theme.bg3),
style: HoverStyle(hoverColor: theme.bg3),
builder: (_, onHover) => _render(context, onHover, state, theme.iconColor),
setSelected: () => state.isEditing || isSelected,
),

View File

@ -1,21 +1,28 @@
import 'package:flutter/material.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
class GridScrollController {
final ScrollController _verticalController = ScrollController();
final ScrollController _horizontalController = ScrollController();
final LinkedScrollControllerGroup _scrollGroupContorller;
final ScrollController verticalController;
final ScrollController horizontalController;
ScrollController get verticalController => _verticalController;
ScrollController get horizontalController => _horizontalController;
final List<ScrollController> _linkHorizontalControllers = [];
GridScrollController();
GridScrollController({required LinkedScrollControllerGroup scrollGroupContorller})
: _scrollGroupContorller = scrollGroupContorller,
verticalController = ScrollController(),
horizontalController = scrollGroupContorller.addAndGet();
// final SelectionChangeCallback? onSelectionChanged;
// final ShouldApplySelection? shouldApplySelection;
// final ScrollCallback? onScroll;
ScrollController linkHorizontalController() {
final controller = _scrollGroupContorller.addAndGet();
_linkHorizontalControllers.add(controller);
return controller;
}
void dispose() {
for (final controller in _linkHorizontalControllers) {
controller.dispose();
}
verticalController.dispose();
horizontalController.dispose();
}

View File

@ -1,14 +1,15 @@
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';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
import 'controller/grid_scroll.dart';
import 'layout/layout.dart';
import 'layout/sizes.dart';
@ -69,12 +70,18 @@ class FlowyGrid extends StatefulWidget {
const FlowyGrid({Key? key}) : super(key: key);
@override
_FlowyGridState createState() => _FlowyGridState();
State<FlowyGrid> createState() => _FlowyGridState();
}
class _FlowyGridState extends State<FlowyGrid> {
final _scrollController = GridScrollController();
final _key = GlobalKey<SliverAnimatedListState>();
final _scrollController = GridScrollController(scrollGroupContorller: LinkedScrollControllerGroup());
late ScrollController headerScrollController;
@override
void initState() {
headerScrollController = _scrollController.linkHorizontalController();
super.initState();
}
@override
void dispose() {
@ -85,87 +92,120 @@ class _FlowyGridState extends State<FlowyGrid> {
@override
Widget build(BuildContext context) {
return BlocBuilder<GridBloc, GridState>(
buildWhen: (previous, current) => previous.fields != current.fields,
buildWhen: (previous, current) => previous.fields.length != current.fields.length,
builder: (context, state) {
if (state.fields.isEmpty) {
return const Center(child: CircularProgressIndicator.adaptive());
}
final child = SizedBox(
width: GridLayout.headerWidth(state.fields),
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: CustomScrollView(
physics: StyledScrollPhysics(),
controller: _scrollController.verticalController,
slivers: [
_renderToolbar(state.gridId),
_renderGridHeader(state.gridId),
_renderRows(gridId: state.gridId, context: context),
const GridFooter(),
],
),
),
final contentWidth = GridLayout.headerWidth(state.fields);
final child = _wrapScrollView(
contentWidth,
[
const _GridRows(),
const _GridFooter(),
],
);
return _wrapScrollbar(child);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _GridToolbarAdaptor(),
_gridHeader(context, state.gridId),
Flexible(child: child),
],
);
},
);
}
Widget _wrapScrollbar(Widget child) {
Widget _wrapScrollView(
double contentWidth,
List<Widget> slivers,
) {
final verticalScrollView = ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: CustomScrollView(
physics: StyledScrollPhysics(),
controller: _scrollController.verticalController,
slivers: slivers,
),
);
final sizedVerticalScrollView = SizedBox(
width: contentWidth,
child: verticalScrollView,
);
final horizontalScrollView = StyledSingleChildScrollView(
controller: _scrollController.horizontalController,
axis: Axis.horizontal,
child: sizedVerticalScrollView,
);
return ScrollbarListStack(
axis: Axis.vertical,
controller: _scrollController.verticalController,
barSize: GridSize.scrollBarSize,
child: StyledSingleChildScrollView(
controller: _scrollController.horizontalController,
axis: Axis.horizontal,
child: child,
),
child: horizontalScrollView,
);
}
Widget _renderGridHeader(String gridId) {
return BlocSelector<GridBloc, GridState, List<Field>>(
selector: (state) => state.fields,
builder: (context, fields) {
return GridHeader(gridId: gridId, fields: List.from(fields));
Widget _gridHeader(BuildContext context, String gridId) {
final fieldCache = context.read<GridBloc>().fieldCache;
return GridHeaderSliverAdaptor(
gridId: gridId,
fieldCache: fieldCache,
anchorScrollController: headerScrollController,
);
}
}
class _GridToolbarAdaptor extends StatelessWidget {
const _GridToolbarAdaptor({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocSelector<GridBloc, GridState, GridToolbarContext>(
selector: (state) {
final fieldCache = context.read<GridBloc>().fieldCache;
return GridToolbarContext(
gridId: state.gridId,
fieldCache: fieldCache,
);
},
builder: (context, toolbarContext) {
return GridToolbar(toolbarContext: toolbarContext);
},
);
}
}
Widget _renderToolbar(String gridId) {
return BlocSelector<GridBloc, GridState, List<Field>>(
selector: (state) => state.fields,
builder: (context, fields) {
final toolbarContext = GridToolbarContext(
gridId: gridId,
fields: fields,
);
class _GridRows extends StatefulWidget {
const _GridRows({Key? key}) : super(key: key);
return SliverToBoxAdapter(
child: GridToolbar(toolbarContext: toolbarContext),
);
},
);
}
@override
State<_GridRows> createState() => _GridRowsState();
}
Widget _renderRows({required String gridId, required BuildContext context}) {
class _GridRowsState extends State<_GridRows> {
final _key = GlobalKey<SliverAnimatedListState>();
@override
Widget build(BuildContext context) {
return BlocConsumer<GridBloc, GridState>(
listenWhen: (previous, current) => previous.listState != current.listState,
listener: (context, state) {
state.listState.map(
state.listState.mapOrNull(
insert: (value) {
for (final index in value.indexs) {
_key.currentState?.insertItem(index);
for (final item in value.items) {
_key.currentState?.insertItem(item.index);
}
},
delete: (value) {
for (final index in value.indexs) {
_key.currentState?.removeItem(index.value1, (context, animation) => _renderRow(index.value2, animation));
for (final item in value.items) {
_key.currentState?.removeItem(
item.index,
(context, animation) => _renderRow(context, item.row, animation),
);
}
},
reload: (updatedIndexs) {},
);
},
buildWhen: (previous, current) => false,
@ -175,17 +215,53 @@ class _FlowyGridState extends State<FlowyGrid> {
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(rowData, animation);
return _renderRow(context, rowData, animation);
},
);
},
);
}
Widget _renderRow(RowData rowData, Animation<double> animation) {
Widget _renderRow(BuildContext context, GridRow rowData, Animation<double> animation) {
final bloc = context.read<GridBloc>();
final fieldCache = bloc.fieldCache;
final rowCache = bloc.rowCache;
return SizeTransition(
sizeFactor: animation,
child: GridRowWidget(data: rowData, key: ValueKey(rowData.rowId)),
child: GridRowWidget(
blocBuilder: () => RowBloc(
rowData: rowData,
fieldCache: fieldCache,
rowCache: rowCache,
),
key: ValueKey(rowData.rowId),
),
);
}
}
class _GridFooter extends StatelessWidget {
const _GridFooter({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SliverPadding(
padding: const EdgeInsets.only(bottom: 200),
sliver: SliverToBoxAdapter(
child: SizedBox(
height: GridSize.footerHeight,
child: Padding(
padding: GridSize.headerContentInsets,
child: Row(
children: [
SizedBox(width: GridSize.leadingHeaderPadding),
const SizedBox(width: 120, child: GridAddRowButton()),
],
),
),
),
),
);
}
}

View File

@ -7,7 +7,7 @@ import 'number_cell.dart';
import 'selection_cell/selection_cell.dart';
import 'text_cell.dart';
Widget buildGridCell(CellData cellData) {
Widget buildGridCell(GridCellIdentifier cellData) {
final key = ValueKey(cellData.field.id + cellData.rowId);
switch (cellData.field.fieldType) {
case FieldType.Checkbox:

View File

@ -33,9 +33,7 @@ class CellContainer extends StatelessWidget {
child: Consumer<CellStateNotifier>(
builder: (context, state, _) {
return Container(
constraints: BoxConstraints(
maxWidth: width,
),
constraints: BoxConstraints(maxWidth: width),
decoration: _makeBoxDecoration(context, state),
padding: GridSize.cellContentInsets,
child: Center(child: child),

View File

@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CheckboxCell extends StatefulWidget {
final CellData cellData;
final GridCellIdentifier cellData;
const CheckboxCell({
required this.cellData,

View File

@ -9,7 +9,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart';
class DateCell extends GridCell {
final CellData cellData;
final GridCellIdentifier cellData;
const DateCell({
required this.cellData,
@ -74,7 +74,13 @@ final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
class _CellCalendar extends StatefulWidget with FlowyOverlayDelegate {
final void Function(DateTime) onSelected;
final VoidCallback onDismissed;
const _CellCalendar({required this.onSelected, required this.onDismissed, Key? key}) : super(key: key);
final bool includeTime;
const _CellCalendar({
required this.onSelected,
required this.onDismissed,
required this.includeTime,
Key? key,
}) : super(key: key);
@override
State<_CellCalendar> createState() => _CellCalendarState();
@ -86,7 +92,11 @@ class _CellCalendar extends StatefulWidget with FlowyOverlayDelegate {
}) async {
_CellCalendar.remove(context);
final calendar = _CellCalendar(onSelected: onSelected, onDismissed: onDismissed);
final calendar = _CellCalendar(
onSelected: onSelected,
onDismissed: onDismissed,
includeTime: false,
);
// const size = Size(460, 400);
// final window = await getWindowInfo();
// FlowyOverlay.of(context).insertWithRect(
@ -105,11 +115,11 @@ class _CellCalendar extends StatefulWidget with FlowyOverlayDelegate {
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
child: calendar,
constraints: BoxConstraints.loose(const Size(300, 320)),
constraints: BoxConstraints.tight(const Size(320, 320)),
),
identifier: _CellCalendar.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomWithCenterAligned,
anchorDirection: AnchorDirection.leftWithCenterAligned,
style: FlowyOverlayStyle(blur: false),
delegate: calendar,
);

View File

@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NumberCell extends GridCell {
final CellData cellData;
final GridCellIdentifier cellData;
const NumberCell({
required this.cellData,
@ -26,7 +26,7 @@ class _NumberCellState extends State<NumberCell> {
@override
void initState() {
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData)..add(const NumberCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content);
_focusNode = CellFocusNode();
super.initState();
@ -48,7 +48,6 @@ class _NumberCellState extends State<NumberCell> {
return TextField(
controller: _controller,
focusNode: _focusNode,
onChanged: (value) => focusChanged(),
onEditingComplete: () => _focusNode.unfocus(),
maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
@ -76,7 +75,12 @@ class _NumberCellState extends State<NumberCell> {
_delayOperation?.cancel();
_delayOperation = Timer(const Duration(milliseconds: 300), () {
if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) {
_cellBloc.add(NumberCellEvent.updateCell(_controller.text));
final number = num.tryParse(_controller.text);
if (number != null) {
_cellBloc.add(NumberCellEvent.updateCell(_controller.text));
} else {
_controller.text = "";
}
}
});
}

View File

@ -8,7 +8,7 @@ import 'extension.dart';
import 'selection_editor.dart';
class SingleSelectCell extends GridCell {
final CellData cellData;
final GridCellIdentifier cellData;
const SingleSelectCell({
required this.cellData,
@ -64,7 +64,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
//----------------------------------------------------------------
class MultiSelectCell extends GridCell {
final CellData cellData;
final GridCellIdentifier cellData;
const MultiSelectCell({
required this.cellData,

View File

@ -1,5 +1,5 @@
import 'dart:collection';
import 'package:app_flowy/workspace/application/grid/cell_bloc/selection_editor_bloc.dart';
import 'package:app_flowy/workspace/application/grid/cell/selection_editor_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
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/edit_option_pannel.dart';
@ -25,7 +25,7 @@ import 'text_field.dart';
const double _editorPannelWidth = 300;
class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
final CellData cellData;
final GridCellIdentifier 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,
CellData cellData,
GridCellIdentifier cellData,
List<SelectOption> options,
List<SelectOption> selectedOptions,
VoidCallback onDismissed,
@ -199,7 +199,7 @@ class _SelectOptionCell extends StatelessWidget {
context.read<SelectOptionEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
},
child: FlowyHover(
config: HoverDisplayConfig(hoverColor: theme.hover),
style: HoverStyle(hoverColor: theme.hover),
builder: (_, onHover) {
List<Widget> children = [
SelectOptionTag(option: option, isSelected: isSelected),

View File

@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'cell_container.dart';
class GridTextCell extends GridCell {
final CellData cellData;
final GridCellIdentifier cellData;
const GridTextCell({
required this.cellData,
Key? key,

View File

@ -1,5 +1,4 @@
import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
@ -7,30 +6,8 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class GridFooter extends StatelessWidget {
const GridFooter({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: SizedBox(
height: GridSize.footerHeight,
child: Padding(
padding: GridSize.headerContentInsets,
child: Row(
children: [
SizedBox(width: GridSize.leadingHeaderPadding),
const SizedBox(width: 120, child: _AddRowButton()),
],
),
),
),
);
}
}
class _AddRowButton extends StatelessWidget {
const _AddRowButton({Key? key}) : super(key: key);
class GridAddRowButton extends StatelessWidget {
const GridAddRowButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {

View File

@ -4,7 +4,9 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.d
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'field_type_extension.dart';
@ -25,21 +27,22 @@ class GridFieldCell extends StatelessWidget {
child: BlocBuilder<FieldCellBloc, FieldCellState>(
builder: (context, state) {
final button = FlowyButton(
hoverColor: theme.hover,
hoverColor: theme.shader6,
onTap: () => _showActionSheet(context),
rightIcon: svgWidget("editor/details", color: theme.iconColor),
leftIcon: svgWidget(state.field.fieldType.iconName(), color: theme.iconColor),
text: FlowyText.medium(state.field.name, fontSize: 12),
padding: GridSize.cellContentInsets,
);
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
const line = Positioned(top: 0, bottom: 0, right: 0, child: _DragToExpandLine());
return Container(
return _CellContainer(
width: state.field.width.toDouble(),
decoration: decoration,
child: button,
child: Stack(
alignment: Alignment.centerRight,
fit: StackFit.expand,
children: [button, line],
),
);
},
),
@ -66,3 +69,65 @@ class GridFieldCell extends StatelessWidget {
).show(context);
}
}
class _CellContainer extends StatelessWidget {
final Widget child;
final double width;
const _CellContainer({
required this.child,
required this.width,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
final decoration = BoxDecoration(
border: Border(
top: borderSide,
right: borderSide,
bottom: borderSide,
));
return Container(
width: width,
decoration: decoration,
child: ConstrainedBox(constraints: const BoxConstraints.expand(), child: child),
);
}
}
class _DragToExpandLine extends StatelessWidget {
const _DragToExpandLine({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return InkWell(
onTap: () {},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragCancel: () {},
onHorizontalDragUpdate: (value) {
// context.read<FieldCellBloc>().add(FieldCellEvent.updateWidth(value.delta.dx));
Log.info(value);
},
onHorizontalDragEnd: (end) {
Log.info(end);
},
child: FlowyHover(
style: HoverStyle(
hoverColor: theme.main1,
borderRadius: BorderRadius.zero,
contentMargin: const EdgeInsets.only(left: 5),
),
builder: (_, onHover) => const SizedBox(width: 2),
),
),
);
}
}

View File

@ -8,92 +8,107 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:reorderables/reorderables.dart';
import 'field_editor.dart';
import 'field_cell.dart';
class GridHeader extends StatelessWidget {
class GridHeaderSliverAdaptor extends StatefulWidget {
final String gridId;
final List<Field> fields;
const GridHeader({Key? key, required this.gridId, required this.fields}) : super(key: key);
final GridFieldCache fieldCache;
final ScrollController anchorScrollController;
const GridHeaderSliverAdaptor({
required this.gridId,
required this.fieldCache,
required this.anchorScrollController,
Key? key,
}) : super(key: key);
@override
State<GridHeaderSliverAdaptor> createState() => _GridHeaderSliverAdaptorState();
}
class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
final bloc = getIt<GridHeaderBloc>(param1: gridId, param2: fields);
final bloc = getIt<GridHeaderBloc>(param1: widget.gridId, param2: widget.fieldCache);
bloc.add(const GridHeaderEvent.initial());
return bloc;
},
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
buildWhen: (previous, current) => previous.fields.length != current.fields.length,
builder: (context, state) {
return SliverPersistentHeader(
delegate: _GridHeaderDelegate(gridId: gridId, fields: List.from(state.fields)),
floating: true,
pinned: true,
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: widget.anchorScrollController,
child: SizedBox(
height: GridSize.headerHeight,
child: _GridHeader(gridId: widget.gridId),
),
);
// return SliverPersistentHeader(
// delegate: SliverHeaderDelegateImplementation(gridId: gridId, fields: state.fields),
// floating: true,
// pinned: true,
// );
},
),
);
}
}
class _GridHeaderDelegate extends SliverPersistentHeaderDelegate {
class _GridHeader extends StatefulWidget {
final String gridId;
final List<Field> fields;
_GridHeaderDelegate({required this.gridId, required this.fields});
const _GridHeader({Key? key, required this.gridId}) : super(key: key);
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return _GridHeaderWidget(gridId: gridId, fields: fields, key: ObjectKey(fields));
}
@override
double get maxExtent => GridSize.headerHeight;
@override
double get minExtent => GridSize.headerHeight;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
if (oldDelegate is _GridHeaderDelegate) {
return fields.length != oldDelegate.fields.length;
}
return true;
}
State<_GridHeader> createState() => _GridHeaderState();
}
class _GridHeaderWidget extends StatelessWidget {
final String gridId;
final List<Field> fields;
const _GridHeaderWidget({required this.gridId, required this.fields, Key? key}) : super(key: key);
class _GridHeaderState extends State<_GridHeader> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final cells = fields.map(
(field) => GridFieldCell(
GridFieldCellContext(gridId: gridId, field: field),
),
);
return BlocBuilder<GridHeaderBloc, GridHeaderState>(
buildWhen: (previous, current) => previous.fields != current.fields,
builder: (context, state) {
final cells = state.fields
.where((field) => field.visibility)
.map((field) => GridFieldCellContext(gridId: widget.gridId, field: field))
.map((ctx) => GridFieldCell(ctx, key: ValueKey(ctx.field.id)))
.toList();
final row = Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _HeaderLeading(),
...cells,
_HeaderTrailing(gridId: gridId),
],
return Container(
color: theme.surface,
child: RepaintBoundary(
child: ReorderableRow(
crossAxisAlignment: CrossAxisAlignment.stretch,
scrollController: ScrollController(),
header: const _CellLeading(),
footer: _CellTrailing(gridId: widget.gridId),
onReorder: (int oldIndex, int newIndex) {
_onReorder(cells, oldIndex, context, newIndex);
},
children: cells,
),
),
);
},
);
}
return Container(height: GridSize.headerHeight, color: theme.surface, child: row);
void _onReorder(List<GridFieldCell> cells, int oldIndex, BuildContext context, int newIndex) {
if (cells.length > oldIndex) {
final field = cells[oldIndex].cellContext.field;
context.read<GridHeaderBloc>().add(GridHeaderEvent.moveField(field, oldIndex, newIndex));
}
}
}
class _HeaderLeading extends StatelessWidget {
const _HeaderLeading({Key? key}) : super(key: key);
class _CellLeading extends StatelessWidget {
const _CellLeading({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -103,9 +118,9 @@ class _HeaderLeading extends StatelessWidget {
}
}
class _HeaderTrailing extends StatelessWidget {
class _CellTrailing extends StatelessWidget {
final String gridId;
const _HeaderTrailing({required this.gridId, Key? key}) : super(key: key);
const _CellTrailing({required this.gridId, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -141,3 +156,29 @@ class CreateFieldButton extends StatelessWidget {
);
}
}
class SliverHeaderDelegateImplementation extends SliverPersistentHeaderDelegate {
final String gridId;
final List<Field> fields;
SliverHeaderDelegateImplementation({required this.gridId, required this.fields});
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return _GridHeader(gridId: gridId);
}
@override
double get maxExtent => GridSize.headerHeight;
@override
double get minExtent => GridSize.headerHeight;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
if (oldDelegate is SliverHeaderDelegateImplementation) {
return fields.length != oldDelegate.fields.length;
}
return true;
}
}

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NumberCell extends StatefulWidget {
final CellData cellData;
final GridCellIdentifier cellData;
const NumberCell({
required this.cellData,

View File

@ -1,4 +1,3 @@
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/cell/prelude.dart';
@ -12,8 +11,12 @@ import 'package:provider/provider.dart';
import 'row_action_sheet.dart';
class GridRowWidget extends StatefulWidget {
final RowData data;
const GridRowWidget({required this.data, Key? key}) : super(key: key);
final RowBloc Function() blocBuilder;
const GridRowWidget({
required this.blocBuilder,
Key? key,
}) : super(key: key);
@override
State<GridRowWidget> createState() => _GridRowWidgetState();
@ -25,7 +28,8 @@ class _GridRowWidgetState extends State<GridRowWidget> {
@override
void initState() {
_rowBloc = getIt<RowBloc>(param1: widget.data)..add(const RowEvent.initial());
_rowBloc = widget.blocBuilder();
_rowBloc.add(const RowEvent.initial());
_rowStateNotifier = _RegionStateNotifier();
super.initState();
}
@ -44,9 +48,10 @@ class _GridRowWidgetState extends State<GridRowWidget> {
buildWhen: (p, c) => p.rowData.height != c.rowData.height,
builder: (context, state) {
return SizedBox(
height: _rowBloc.state.rowData.height,
height: 42,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
_RowLeading(),
_RowCells(),
@ -146,7 +151,11 @@ class _RowCells extends StatelessWidget {
buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
builder: (context, state) {
final List<Widget> children = state.cellDataMap.fold(() => [], _toCells);
return Row(children: children);
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: children,
);
},
);
}

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NumberCell extends StatefulWidget {
final CellData cellData;
final GridCellIdentifier cellData;
const NumberCell({
required this.cellData,

View File

@ -14,7 +14,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class GridRowActionSheet extends StatelessWidget {
final RowData rowData;
final GridRow rowData;
const GridRowActionSheet({required this.rowData, Key? key}) : super(key: key);
@override

View File

@ -1,5 +1,6 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
import 'package:app_flowy/workspace/application/grid/setting/property_bloc.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_editor.dart';
@ -18,10 +19,10 @@ import 'package:styled_widget/styled_widget.dart';
class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
final String gridId;
final List<Field> fields;
final GridFieldCache fieldCache;
const GridPropertyList({
required this.gridId,
required this.fields,
required this.fieldCache,
Key? key,
}) : super(key: key);
@ -43,23 +44,22 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
getIt<GridPropertyBloc>(param1: gridId, param2: fields)..add(const GridPropertyEvent.initial()),
getIt<GridPropertyBloc>(param1: gridId, param2: fieldCache)..add(const GridPropertyEvent.initial()),
child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
builder: (context, state) {
final cells = state.fields.map((field) {
return _GridPropertyCell(gridId: gridId, field: field);
return _GridPropertyCell(gridId: gridId, field: field, key: ValueKey(field.id));
}).toList();
return ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
itemCount: cells.length,
itemBuilder: (BuildContext context, int index) {
return cells[index];
},
separatorBuilder: (BuildContext context, int index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
);
},
),
@ -92,17 +92,7 @@ class _GridPropertyCell extends StatelessWidget {
Expanded(
child: SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyButton(
text: FlowyText.medium(field.name, fontSize: 12),
hoverColor: theme.hover,
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
onTap: () {
FieldEditor(
gridId: gridId,
fieldContextLoader: FieldContextLoaderAdaptor(gridId: gridId, field: field),
).show(context, anchorDirection: AnchorDirection.bottomRight);
},
),
child: _editFieldButton(theme, context),
),
),
FlowyIconButton(
@ -116,4 +106,18 @@ class _GridPropertyCell extends StatelessWidget {
],
);
}
FlowyButton _editFieldButton(AppTheme theme, BuildContext context) {
return FlowyButton(
text: FlowyText.medium(field.name, fontSize: 12),
hoverColor: theme.hover,
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
onTap: () {
FieldEditor(
gridId: gridId,
fieldContextLoader: FieldContextLoaderAdaptor(gridId: gridId, field: field),
).show(context, anchorDirection: AnchorDirection.bottomRight);
},
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
import 'package:app_flowy/workspace/application/grid/setting/setting_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
@ -7,7 +8,6 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.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';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -18,11 +18,11 @@ import 'grid_property.dart';
class GridSettingContext {
final String gridId;
final List<Field> fields;
final GridFieldCache fieldCache;
GridSettingContext({
required this.gridId,
required this.fields,
required this.fieldCache,
});
}
@ -41,7 +41,7 @@ class GridSettingList extends StatelessWidget {
case GridSettingAction.sortBy:
break;
case GridSettingAction.properties:
GridPropertyList(gridId: settingContext.gridId, fields: settingContext.fields).show(context);
GridPropertyList(gridId: settingContext.gridId, fieldCache: settingContext.fieldCache).show(context);
break;
}
},

View File

@ -1,8 +1,8 @@
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -12,10 +12,10 @@ import 'grid_setting.dart';
class GridToolbarContext {
final String gridId;
final List<Field> fields;
final GridFieldCache fieldCache;
GridToolbarContext({
required this.gridId,
required this.fields,
required this.fieldCache,
});
}
@ -27,7 +27,7 @@ class GridToolbar extends StatelessWidget {
Widget build(BuildContext context) {
final settingContext = GridSettingContext(
gridId: toolbarContext.gridId,
fields: toolbarContext.fields,
fieldCache: toolbarContext.fieldCache,
);
return SizedBox(
height: 40,

View File

@ -85,7 +85,7 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
final theme = context.watch<AppTheme>();
return FlowyHover(
config: HoverDisplayConfig(hoverColor: theme.hover),
style: HoverStyle(hoverColor: theme.hover),
builder: (context, onHover) {
return GestureDetector(
behavior: HitTestBehavior.opaque,

View File

@ -31,12 +31,18 @@ class PublishNotifier<T> extends ChangeNotifier {
T? get currentValue => _value;
void addPublishListener(void Function(T) callback) {
void addPublishListener(void Function(T) callback, {bool Function()? listenWhen}) {
super.addListener(
() {
if (_value != null) {
callback(_value!);
if (_value == null) {
return;
}
if (listenWhen != null && listenWhen() == false) {
return;
}
callback(_value!);
},
);
}

View File

@ -28,7 +28,7 @@ class FlowyButton extends StatelessWidget {
return InkWell(
onTap: onTap,
child: FlowyHover(
config: HoverDisplayConfig(borderRadius: Corners.s6Border, hoverColor: hoverColor),
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: hoverColor),
setSelected: () => isSelected,
builder: (context, onHover) => _render(),
),

View File

@ -5,14 +5,14 @@ import 'package:flowy_infra/time/duration.dart';
typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
class FlowyHover extends StatefulWidget {
final HoverDisplayConfig config;
final HoverStyle style;
final HoverBuilder builder;
final bool Function()? setSelected;
const FlowyHover({
Key? key,
required this.builder,
required this.config,
required this.style,
this.setSelected,
}) : super(key: key);
@ -41,7 +41,7 @@ class _FlowyHoverState extends State<FlowyHover> {
if (showHover) {
return FlowyHoverContainer(
config: widget.config,
style: widget.style,
child: widget.builder(context, _onHover),
);
} else {
@ -50,41 +50,44 @@ class _FlowyHoverState extends State<FlowyHover> {
}
}
class HoverDisplayConfig {
class HoverStyle {
final Color borderColor;
final double borderWidth;
final Color hoverColor;
final BorderRadius borderRadius;
final EdgeInsets contentMargin;
const HoverDisplayConfig(
const HoverStyle(
{this.borderColor = Colors.transparent,
this.borderWidth = 0,
this.borderRadius = const BorderRadius.all(Radius.circular(6)),
this.contentMargin = EdgeInsets.zero,
required this.hoverColor});
}
class FlowyHoverContainer extends StatelessWidget {
final HoverDisplayConfig config;
final HoverStyle style;
final Widget child;
const FlowyHoverContainer({
Key? key,
required this.child,
required this.config,
required this.style,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final hoverBorder = Border.all(
color: config.borderColor,
width: config.borderWidth,
color: style.borderColor,
width: style.borderWidth,
);
return Container(
margin: style.contentMargin,
decoration: BoxDecoration(
border: hoverBorder,
color: config.hoverColor,
borderRadius: config.borderRadius,
color: style.hoverColor,
borderRadius: style.borderRadius,
),
child: child,
);

View File

@ -74,21 +74,21 @@ class _StyledSingleChildScrollViewState extends State<StyledSingleChildScrollVie
}
class StyledCustomScrollView extends StatefulWidget {
final double? contentSize;
final Axis axis;
final Color? trackColor;
final Color? handleColor;
final ScrollController? controller;
final ScrollController? verticalController;
final List<Widget> slivers;
final double barSize;
const StyledCustomScrollView({
Key? key,
this.contentSize,
this.axis = Axis.vertical,
this.trackColor,
this.handleColor,
this.controller,
this.verticalController,
this.slivers = const <Widget>[],
this.barSize = 12,
}) : super(key: key);
@override
@ -96,17 +96,17 @@ class StyledCustomScrollView extends StatefulWidget {
}
class _StyledCustomScrollViewState extends State<StyledCustomScrollView> {
late ScrollController scrollController;
late ScrollController controller;
@override
void initState() {
scrollController = widget.controller ?? ScrollController();
controller = widget.verticalController ?? ScrollController();
super.initState();
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@ -120,19 +120,23 @@ class _StyledCustomScrollViewState extends State<StyledCustomScrollView> {
@override
Widget build(BuildContext context) {
return ScrollbarListStack(
contentSize: widget.contentSize,
axis: widget.axis,
controller: scrollController,
barSize: 12,
trackColor: widget.trackColor,
handleColor: widget.handleColor,
var child = ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: CustomScrollView(
scrollDirection: widget.axis,
physics: StyledScrollPhysics(),
controller: scrollController,
controller: controller,
slivers: widget.slivers,
),
);
return ScrollbarListStack(
axis: widget.axis,
controller: controller,
barSize: widget.barSize,
trackColor: widget.trackColor,
handleColor: widget.handleColor,
child: child,
);
}
}

View File

@ -154,6 +154,23 @@ class GridEventGetEditFieldContext {
}
}
class GridEventMoveItem {
MoveItemPayload request;
GridEventMoveItem(this.request);
Future<Either<Unit, FlowyError>> send() {
final request = FFIRequest.create()
..event = GridEvent.MoveItem.toString()
..payload = requestToBytes(this.request);
return Dispatch.asyncRequest(request)
.then((bytesResult) => bytesResult.fold(
(bytes) => left(unit),
(errBytes) => right(FlowyError.fromBuffer(errBytes)),
));
}
}
class GridEventNewSelectOption {
SelectOptionName request;
GridEventNewSelectOption(this.request);

View File

@ -254,6 +254,140 @@ class FieldOrder extends $pb.GeneratedMessage {
void clearFieldId() => clearField(1);
}
class GridFieldChangeset extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridFieldChangeset', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
..pc<IndexField>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertedFields', $pb.PbFieldType.PM, subBuilder: IndexField.create)
..pc<FieldOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deletedFields', $pb.PbFieldType.PM, subBuilder: FieldOrder.create)
..pc<Field>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'updatedFields', $pb.PbFieldType.PM, subBuilder: Field.create)
..hasRequiredFields = false
;
GridFieldChangeset._() : super();
factory GridFieldChangeset({
$core.String? gridId,
$core.Iterable<IndexField>? insertedFields,
$core.Iterable<FieldOrder>? deletedFields,
$core.Iterable<Field>? updatedFields,
}) {
final _result = create();
if (gridId != null) {
_result.gridId = gridId;
}
if (insertedFields != null) {
_result.insertedFields.addAll(insertedFields);
}
if (deletedFields != null) {
_result.deletedFields.addAll(deletedFields);
}
if (updatedFields != null) {
_result.updatedFields.addAll(updatedFields);
}
return _result;
}
factory GridFieldChangeset.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory GridFieldChangeset.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')
GridFieldChangeset clone() => GridFieldChangeset()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
GridFieldChangeset copyWith(void Function(GridFieldChangeset) updates) => super.copyWith((message) => updates(message as GridFieldChangeset)) as GridFieldChangeset; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GridFieldChangeset create() => GridFieldChangeset._();
GridFieldChangeset createEmptyInstance() => create();
static $pb.PbList<GridFieldChangeset> createRepeated() => $pb.PbList<GridFieldChangeset>();
@$core.pragma('dart2js:noInline')
static GridFieldChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridFieldChangeset>(create);
static GridFieldChangeset? _defaultInstance;
@$pb.TagNumber(1)
$core.String get gridId => $_getSZ(0);
@$pb.TagNumber(1)
set gridId($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasGridId() => $_has(0);
@$pb.TagNumber(1)
void clearGridId() => clearField(1);
@$pb.TagNumber(2)
$core.List<IndexField> get insertedFields => $_getList(1);
@$pb.TagNumber(3)
$core.List<FieldOrder> get deletedFields => $_getList(2);
@$pb.TagNumber(4)
$core.List<Field> get updatedFields => $_getList(3);
}
class IndexField extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'IndexField', createEmptyInstance: create)
..aOM<Field>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'field', subBuilder: Field.create)
..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'index', $pb.PbFieldType.O3)
..hasRequiredFields = false
;
IndexField._() : super();
factory IndexField({
Field? field_1,
$core.int? index,
}) {
final _result = create();
if (field_1 != null) {
_result.field_1 = field_1;
}
if (index != null) {
_result.index = index;
}
return _result;
}
factory IndexField.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory IndexField.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')
IndexField clone() => IndexField()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
IndexField copyWith(void Function(IndexField) updates) => super.copyWith((message) => updates(message as IndexField)) as IndexField; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static IndexField create() => IndexField._();
IndexField createEmptyInstance() => create();
static $pb.PbList<IndexField> createRepeated() => $pb.PbList<IndexField>();
@$core.pragma('dart2js:noInline')
static IndexField getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<IndexField>(create);
static IndexField? _defaultInstance;
@$pb.TagNumber(1)
Field get field_1 => $_getN(0);
@$pb.TagNumber(1)
set field_1(Field v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasField_1() => $_has(0);
@$pb.TagNumber(1)
void clearField_1() => clearField(1);
@$pb.TagNumber(1)
Field ensureField_1() => $_ensure(0);
@$pb.TagNumber(2)
$core.int get index => $_getIZ(1);
@$pb.TagNumber(2)
set index($core.int v) { $_setSignedInt32(1, v); }
@$pb.TagNumber(2)
$core.bool hasIndex() => $_has(1);
@$pb.TagNumber(2)
void clearIndex() => clearField(2);
}
enum GetEditFieldContextPayload_OneOfFieldId {
fieldId,
notSet
@ -857,77 +991,6 @@ class GridBlockOrder extends $pb.GeneratedMessage {
$core.List<RowOrder> get rowOrders => $_getList(1);
}
class GridBlockOrderChangeset extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlockOrderChangeset', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
..pc<IndexRowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertedRows', $pb.PbFieldType.PM, subBuilder: IndexRowOrder.create)
..pc<RowOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deletedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
..pc<RowOrder>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'updatedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
..hasRequiredFields = false
;
GridBlockOrderChangeset._() : super();
factory GridBlockOrderChangeset({
$core.String? blockId,
$core.Iterable<IndexRowOrder>? insertedRows,
$core.Iterable<RowOrder>? deletedRows,
$core.Iterable<RowOrder>? updatedRows,
}) {
final _result = create();
if (blockId != null) {
_result.blockId = blockId;
}
if (insertedRows != null) {
_result.insertedRows.addAll(insertedRows);
}
if (deletedRows != null) {
_result.deletedRows.addAll(deletedRows);
}
if (updatedRows != null) {
_result.updatedRows.addAll(updatedRows);
}
return _result;
}
factory GridBlockOrderChangeset.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory GridBlockOrderChangeset.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')
GridBlockOrderChangeset clone() => GridBlockOrderChangeset()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
GridBlockOrderChangeset copyWith(void Function(GridBlockOrderChangeset) updates) => super.copyWith((message) => updates(message as GridBlockOrderChangeset)) as GridBlockOrderChangeset; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GridBlockOrderChangeset create() => GridBlockOrderChangeset._();
GridBlockOrderChangeset createEmptyInstance() => create();
static $pb.PbList<GridBlockOrderChangeset> createRepeated() => $pb.PbList<GridBlockOrderChangeset>();
@$core.pragma('dart2js:noInline')
static GridBlockOrderChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridBlockOrderChangeset>(create);
static GridBlockOrderChangeset? _defaultInstance;
@$pb.TagNumber(1)
$core.String get blockId => $_getSZ(0);
@$pb.TagNumber(1)
set blockId($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasBlockId() => $_has(0);
@$pb.TagNumber(1)
void clearBlockId() => clearField(1);
@$pb.TagNumber(2)
$core.List<IndexRowOrder> get insertedRows => $_getList(1);
@$pb.TagNumber(3)
$core.List<RowOrder> get deletedRows => $_getList(2);
@$pb.TagNumber(4)
$core.List<RowOrder> get updatedRows => $_getList(3);
}
enum IndexRowOrder_OneOfIndex {
index_,
notSet
@ -1004,6 +1067,77 @@ class IndexRowOrder extends $pb.GeneratedMessage {
void clearIndex() => clearField(2);
}
class GridRowsChangeset extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridRowsChangeset', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'blockId')
..pc<IndexRowOrder>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'insertedRows', $pb.PbFieldType.PM, subBuilder: IndexRowOrder.create)
..pc<RowOrder>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deletedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
..pc<RowOrder>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'updatedRows', $pb.PbFieldType.PM, subBuilder: RowOrder.create)
..hasRequiredFields = false
;
GridRowsChangeset._() : super();
factory GridRowsChangeset({
$core.String? blockId,
$core.Iterable<IndexRowOrder>? insertedRows,
$core.Iterable<RowOrder>? deletedRows,
$core.Iterable<RowOrder>? updatedRows,
}) {
final _result = create();
if (blockId != null) {
_result.blockId = blockId;
}
if (insertedRows != null) {
_result.insertedRows.addAll(insertedRows);
}
if (deletedRows != null) {
_result.deletedRows.addAll(deletedRows);
}
if (updatedRows != null) {
_result.updatedRows.addAll(updatedRows);
}
return _result;
}
factory GridRowsChangeset.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory GridRowsChangeset.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')
GridRowsChangeset clone() => GridRowsChangeset()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
GridRowsChangeset copyWith(void Function(GridRowsChangeset) updates) => super.copyWith((message) => updates(message as GridRowsChangeset)) as GridRowsChangeset; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static GridRowsChangeset create() => GridRowsChangeset._();
GridRowsChangeset createEmptyInstance() => create();
static $pb.PbList<GridRowsChangeset> createRepeated() => $pb.PbList<GridRowsChangeset>();
@$core.pragma('dart2js:noInline')
static GridRowsChangeset getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<GridRowsChangeset>(create);
static GridRowsChangeset? _defaultInstance;
@$pb.TagNumber(1)
$core.String get blockId => $_getSZ(0);
@$pb.TagNumber(1)
set blockId($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasBlockId() => $_has(0);
@$pb.TagNumber(1)
void clearBlockId() => clearField(1);
@$pb.TagNumber(2)
$core.List<IndexRowOrder> get insertedRows => $_getList(1);
@$pb.TagNumber(3)
$core.List<RowOrder> get deletedRows => $_getList(2);
@$pb.TagNumber(4)
$core.List<RowOrder> get updatedRows => $_getList(3);
}
class GridBlock extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'GridBlock', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
@ -1950,6 +2084,109 @@ class FieldChangesetPayload extends $pb.GeneratedMessage {
void clearTypeOptionData() => clearField(9);
}
class MoveItemPayload extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'MoveItemPayload', createEmptyInstance: create)
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'itemId')
..a<$core.int>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fromIndex', $pb.PbFieldType.O3)
..a<$core.int>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'toIndex', $pb.PbFieldType.O3)
..e<MoveItemType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ty', $pb.PbFieldType.OE, defaultOrMaker: MoveItemType.MoveField, valueOf: MoveItemType.valueOf, enumValues: MoveItemType.values)
..hasRequiredFields = false
;
MoveItemPayload._() : super();
factory MoveItemPayload({
$core.String? gridId,
$core.String? itemId,
$core.int? fromIndex,
$core.int? toIndex,
MoveItemType? ty,
}) {
final _result = create();
if (gridId != null) {
_result.gridId = gridId;
}
if (itemId != null) {
_result.itemId = itemId;
}
if (fromIndex != null) {
_result.fromIndex = fromIndex;
}
if (toIndex != null) {
_result.toIndex = toIndex;
}
if (ty != null) {
_result.ty = ty;
}
return _result;
}
factory MoveItemPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory MoveItemPayload.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')
MoveItemPayload clone() => MoveItemPayload()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
MoveItemPayload copyWith(void Function(MoveItemPayload) updates) => super.copyWith((message) => updates(message as MoveItemPayload)) as MoveItemPayload; // ignore: deprecated_member_use
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static MoveItemPayload create() => MoveItemPayload._();
MoveItemPayload createEmptyInstance() => create();
static $pb.PbList<MoveItemPayload> createRepeated() => $pb.PbList<MoveItemPayload>();
@$core.pragma('dart2js:noInline')
static MoveItemPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<MoveItemPayload>(create);
static MoveItemPayload? _defaultInstance;
@$pb.TagNumber(1)
$core.String get gridId => $_getSZ(0);
@$pb.TagNumber(1)
set gridId($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasGridId() => $_has(0);
@$pb.TagNumber(1)
void clearGridId() => clearField(1);
@$pb.TagNumber(2)
$core.String get itemId => $_getSZ(1);
@$pb.TagNumber(2)
set itemId($core.String v) { $_setString(1, v); }
@$pb.TagNumber(2)
$core.bool hasItemId() => $_has(1);
@$pb.TagNumber(2)
void clearItemId() => clearField(2);
@$pb.TagNumber(3)
$core.int get fromIndex => $_getIZ(2);
@$pb.TagNumber(3)
set fromIndex($core.int v) { $_setSignedInt32(2, v); }
@$pb.TagNumber(3)
$core.bool hasFromIndex() => $_has(2);
@$pb.TagNumber(3)
void clearFromIndex() => clearField(3);
@$pb.TagNumber(4)
$core.int get toIndex => $_getIZ(3);
@$pb.TagNumber(4)
set toIndex($core.int v) { $_setSignedInt32(3, v); }
@$pb.TagNumber(4)
$core.bool hasToIndex() => $_has(3);
@$pb.TagNumber(4)
void clearToIndex() => clearField(4);
@$pb.TagNumber(5)
MoveItemType get ty => $_getN(4);
@$pb.TagNumber(5)
set ty(MoveItemType v) { setField(5, v); }
@$pb.TagNumber(5)
$core.bool hasTy() => $_has(4);
@$pb.TagNumber(5)
void clearTy() => clearField(5);
}
enum CellChangeset_OneOfData {
data,
notSet

View File

@ -9,6 +9,21 @@
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
class MoveItemType extends $pb.ProtobufEnum {
static const MoveItemType MoveField = MoveItemType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveField');
static const MoveItemType MoveRow = MoveItemType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveRow');
static const $core.List<MoveItemType> values = <MoveItemType> [
MoveField,
MoveRow,
];
static final $core.Map<$core.int, MoveItemType> _byValue = $pb.ProtobufEnum.initByValue(values);
static MoveItemType? valueOf($core.int value) => _byValue[value];
const MoveItemType._($core.int v, $core.String n) : super(v, n);
}
class FieldType extends $pb.ProtobufEnum {
static const FieldType RichText = FieldType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RichText');
static const FieldType Number = FieldType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Number');

View File

@ -8,6 +8,17 @@
import 'dart:core' as $core;
import 'dart:convert' as $convert;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use moveItemTypeDescriptor instead')
const MoveItemType$json = const {
'1': 'MoveItemType',
'2': const [
const {'1': 'MoveField', '2': 0},
const {'1': 'MoveRow', '2': 1},
],
};
/// Descriptor for `MoveItemType`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List moveItemTypeDescriptor = $convert.base64Decode('CgxNb3ZlSXRlbVR5cGUSDQoJTW92ZUZpZWxkEAASCwoHTW92ZVJvdxAB');
@$core.Deprecated('Use fieldTypeDescriptor instead')
const FieldType$json = const {
'1': 'FieldType',
@ -61,6 +72,30 @@ const FieldOrder$json = const {
/// Descriptor for `FieldOrder`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List fieldOrderDescriptor = $convert.base64Decode('CgpGaWVsZE9yZGVyEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElk');
@$core.Deprecated('Use gridFieldChangesetDescriptor instead')
const GridFieldChangeset$json = const {
'1': 'GridFieldChangeset',
'2': const [
const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
const {'1': 'inserted_fields', '3': 2, '4': 3, '5': 11, '6': '.IndexField', '10': 'insertedFields'},
const {'1': 'deleted_fields', '3': 3, '4': 3, '5': 11, '6': '.FieldOrder', '10': 'deletedFields'},
const {'1': 'updated_fields', '3': 4, '4': 3, '5': 11, '6': '.Field', '10': 'updatedFields'},
],
};
/// Descriptor for `GridFieldChangeset`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List gridFieldChangesetDescriptor = $convert.base64Decode('ChJHcmlkRmllbGRDaGFuZ2VzZXQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEjQKD2luc2VydGVkX2ZpZWxkcxgCIAMoCzILLkluZGV4RmllbGRSDmluc2VydGVkRmllbGRzEjIKDmRlbGV0ZWRfZmllbGRzGAMgAygLMgsuRmllbGRPcmRlclINZGVsZXRlZEZpZWxkcxItCg51cGRhdGVkX2ZpZWxkcxgEIAMoCzIGLkZpZWxkUg11cGRhdGVkRmllbGRz');
@$core.Deprecated('Use indexFieldDescriptor instead')
const IndexField$json = const {
'1': 'IndexField',
'2': const [
const {'1': 'field', '3': 1, '4': 1, '5': 11, '6': '.Field', '10': 'field'},
const {'1': 'index', '3': 2, '4': 1, '5': 5, '10': 'index'},
],
};
/// Descriptor for `IndexField`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List indexFieldDescriptor = $convert.base64Decode('CgpJbmRleEZpZWxkEhwKBWZpZWxkGAEgASgLMgYuRmllbGRSBWZpZWxkEhQKBWluZGV4GAIgASgFUgVpbmRleA==');
@$core.Deprecated('Use getEditFieldContextPayloadDescriptor instead')
const GetEditFieldContextPayload$json = const {
'1': 'GetEditFieldContextPayload',
@ -186,19 +221,6 @@ const GridBlockOrder$json = const {
/// Descriptor for `GridBlockOrder`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List gridBlockOrderDescriptor = $convert.base64Decode('Cg5HcmlkQmxvY2tPcmRlchIZCghibG9ja19pZBgBIAEoCVIHYmxvY2tJZBIoCgpyb3dfb3JkZXJzGAIgAygLMgkuUm93T3JkZXJSCXJvd09yZGVycw==');
@$core.Deprecated('Use gridBlockOrderChangesetDescriptor instead')
const GridBlockOrderChangeset$json = const {
'1': 'GridBlockOrderChangeset',
'2': const [
const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
const {'1': 'inserted_rows', '3': 2, '4': 3, '5': 11, '6': '.IndexRowOrder', '10': 'insertedRows'},
const {'1': 'deleted_rows', '3': 3, '4': 3, '5': 11, '6': '.RowOrder', '10': 'deletedRows'},
const {'1': 'updated_rows', '3': 4, '4': 3, '5': 11, '6': '.RowOrder', '10': 'updatedRows'},
],
};
/// Descriptor for `GridBlockOrderChangeset`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List gridBlockOrderChangesetDescriptor = $convert.base64Decode('ChdHcmlkQmxvY2tPcmRlckNoYW5nZXNldBIZCghibG9ja19pZBgBIAEoCVIHYmxvY2tJZBIzCg1pbnNlcnRlZF9yb3dzGAIgAygLMg4uSW5kZXhSb3dPcmRlclIMaW5zZXJ0ZWRSb3dzEiwKDGRlbGV0ZWRfcm93cxgDIAMoCzIJLlJvd09yZGVyUgtkZWxldGVkUm93cxIsCgx1cGRhdGVkX3Jvd3MYBCADKAsyCS5Sb3dPcmRlclILdXBkYXRlZFJvd3M=');
@$core.Deprecated('Use indexRowOrderDescriptor instead')
const IndexRowOrder$json = const {
'1': 'IndexRowOrder',
@ -213,6 +235,19 @@ const IndexRowOrder$json = const {
/// Descriptor for `IndexRowOrder`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List indexRowOrderDescriptor = $convert.base64Decode('Cg1JbmRleFJvd09yZGVyEiYKCXJvd19vcmRlchgBIAEoCzIJLlJvd09yZGVyUghyb3dPcmRlchIWCgVpbmRleBgCIAEoBUgAUgVpbmRleEIOCgxvbmVfb2ZfaW5kZXg=');
@$core.Deprecated('Use gridRowsChangesetDescriptor instead')
const GridRowsChangeset$json = const {
'1': 'GridRowsChangeset',
'2': const [
const {'1': 'block_id', '3': 1, '4': 1, '5': 9, '10': 'blockId'},
const {'1': 'inserted_rows', '3': 2, '4': 3, '5': 11, '6': '.IndexRowOrder', '10': 'insertedRows'},
const {'1': 'deleted_rows', '3': 3, '4': 3, '5': 11, '6': '.RowOrder', '10': 'deletedRows'},
const {'1': 'updated_rows', '3': 4, '4': 3, '5': 11, '6': '.RowOrder', '10': 'updatedRows'},
],
};
/// Descriptor for `GridRowsChangeset`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List gridRowsChangesetDescriptor = $convert.base64Decode('ChFHcmlkUm93c0NoYW5nZXNldBIZCghibG9ja19pZBgBIAEoCVIHYmxvY2tJZBIzCg1pbnNlcnRlZF9yb3dzGAIgAygLMg4uSW5kZXhSb3dPcmRlclIMaW5zZXJ0ZWRSb3dzEiwKDGRlbGV0ZWRfcm93cxgDIAMoCzIJLlJvd09yZGVyUgtkZWxldGVkUm93cxIsCgx1cGRhdGVkX3Jvd3MYBCADKAsyCS5Sb3dPcmRlclILdXBkYXRlZFJvd3M=');
@$core.Deprecated('Use gridBlockDescriptor instead')
const GridBlock$json = const {
'1': 'GridBlock',
@ -370,6 +405,20 @@ const FieldChangesetPayload$json = const {
/// Descriptor for `FieldChangesetPayload`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List fieldChangesetPayloadDescriptor = $convert.base64Decode('ChVGaWVsZENoYW5nZXNldFBheWxvYWQSGQoIZmllbGRfaWQYASABKAlSB2ZpZWxkSWQSFwoHZ3JpZF9pZBgCIAEoCVIGZ3JpZElkEhQKBG5hbWUYAyABKAlIAFIEbmFtZRIUCgRkZXNjGAQgASgJSAFSBGRlc2MSKwoKZmllbGRfdHlwZRgFIAEoDjIKLkZpZWxkVHlwZUgCUglmaWVsZFR5cGUSGAoGZnJvemVuGAYgASgISANSBmZyb3plbhIgCgp2aXNpYmlsaXR5GAcgASgISARSCnZpc2liaWxpdHkSFgoFd2lkdGgYCCABKAVIBVIFd2lkdGgSKgoQdHlwZV9vcHRpb25fZGF0YRgJIAEoDEgGUg50eXBlT3B0aW9uRGF0YUINCgtvbmVfb2ZfbmFtZUINCgtvbmVfb2ZfZGVzY0ITChFvbmVfb2ZfZmllbGRfdHlwZUIPCg1vbmVfb2ZfZnJvemVuQhMKEW9uZV9vZl92aXNpYmlsaXR5Qg4KDG9uZV9vZl93aWR0aEIZChdvbmVfb2ZfdHlwZV9vcHRpb25fZGF0YQ==');
@$core.Deprecated('Use moveItemPayloadDescriptor instead')
const MoveItemPayload$json = const {
'1': 'MoveItemPayload',
'2': const [
const {'1': 'grid_id', '3': 1, '4': 1, '5': 9, '10': 'gridId'},
const {'1': 'item_id', '3': 2, '4': 1, '5': 9, '10': 'itemId'},
const {'1': 'from_index', '3': 3, '4': 1, '5': 5, '10': 'fromIndex'},
const {'1': 'to_index', '3': 4, '4': 1, '5': 5, '10': 'toIndex'},
const {'1': 'ty', '3': 5, '4': 1, '5': 14, '6': '.MoveItemType', '10': 'ty'},
],
};
/// Descriptor for `MoveItemPayload`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List moveItemPayloadDescriptor = $convert.base64Decode('Cg9Nb3ZlSXRlbVBheWxvYWQSFwoHZ3JpZF9pZBgBIAEoCVIGZ3JpZElkEhcKB2l0ZW1faWQYAiABKAlSBml0ZW1JZBIdCgpmcm9tX2luZGV4GAMgASgFUglmcm9tSW5kZXgSGQoIdG9faW5kZXgYBCABKAVSB3RvSW5kZXgSHQoCdHkYBSABKA4yDS5Nb3ZlSXRlbVR5cGVSAnR5');
@$core.Deprecated('Use cellChangesetDescriptor instead')
const CellChangeset$json = const {
'1': 'CellChangeset',

View File

@ -12,19 +12,19 @@ import 'package:protobuf/protobuf.dart' as $pb;
class GridNotification extends $pb.ProtobufEnum {
static const GridNotification Unknown = GridNotification._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
static const GridNotification DidCreateBlock = GridNotification._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidCreateBlock');
static const GridNotification DidUpdateGridBlock = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateGridBlock');
static const GridNotification DidUpdateGridRow = GridNotification._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateGridRow');
static const GridNotification DidUpdateGridField = GridNotification._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateGridField');
static const GridNotification DidUpdateRow = GridNotification._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateRow');
static const GridNotification DidUpdateCell = GridNotification._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateCell');
static const GridNotification DidUpdateGrid = GridNotification._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateGrid');
static const GridNotification DidUpdateField = GridNotification._(41, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateField');
static const GridNotification DidUpdateCell = GridNotification._(40, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateCell');
static const GridNotification DidUpdateField = GridNotification._(50, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DidUpdateField');
static const $core.List<GridNotification> values = <GridNotification> [
Unknown,
DidCreateBlock,
DidUpdateGridBlock,
DidUpdateGridRow,
DidUpdateGridField,
DidUpdateRow,
DidUpdateCell,
DidUpdateGrid,
DidUpdateField,
];

View File

@ -14,13 +14,13 @@ const GridNotification$json = const {
'2': const [
const {'1': 'Unknown', '2': 0},
const {'1': 'DidCreateBlock', '2': 11},
const {'1': 'DidUpdateGridBlock', '2': 20},
const {'1': 'DidUpdateGridRow', '2': 20},
const {'1': 'DidUpdateGridField', '2': 21},
const {'1': 'DidUpdateRow', '2': 30},
const {'1': 'DidUpdateCell', '2': 31},
const {'1': 'DidUpdateGrid', '2': 40},
const {'1': 'DidUpdateField', '2': 41},
const {'1': 'DidUpdateCell', '2': 40},
const {'1': 'DidUpdateField', '2': 50},
],
};
/// Descriptor for `GridNotification`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABISCg5EaWRDcmVhdGVCbG9jaxALEhYKEkRpZFVwZGF0ZUdyaWRCbG9jaxAUEhAKDERpZFVwZGF0ZVJvdxAeEhEKDURpZFVwZGF0ZUNlbGwQHxIRCg1EaWRVcGRhdGVHcmlkECgSEgoORGlkVXBkYXRlRmllbGQQKQ==');
final $typed_data.Uint8List gridNotificationDescriptor = $convert.base64Decode('ChBHcmlkTm90aWZpY2F0aW9uEgsKB1Vua25vd24QABISCg5EaWRDcmVhdGVCbG9jaxALEhQKEERpZFVwZGF0ZUdyaWRSb3cQFBIWChJEaWRVcGRhdGVHcmlkRmllbGQQFRIQCgxEaWRVcGRhdGVSb3cQHhIRCg1EaWRVcGRhdGVDZWxsECgSEgoORGlkVXBkYXRlRmllbGQQMg==');

View File

@ -19,6 +19,7 @@ class GridEvent extends $pb.ProtobufEnum {
static const GridEvent SwitchToField = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SwitchToField');
static const GridEvent DuplicateField = GridEvent._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
static const GridEvent GetEditFieldContext = GridEvent._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetEditFieldContext');
static const GridEvent MoveItem = GridEvent._(17, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MoveItem');
static const GridEvent NewSelectOption = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewSelectOption');
static const GridEvent GetSelectOptionContext = GridEvent._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetSelectOptionContext');
static const GridEvent UpdateSelectOption = GridEvent._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateSelectOption');
@ -40,6 +41,7 @@ class GridEvent extends $pb.ProtobufEnum {
SwitchToField,
DuplicateField,
GetEditFieldContext,
MoveItem,
NewSelectOption,
GetSelectOptionContext,
UpdateSelectOption,

View File

@ -21,6 +21,7 @@ const GridEvent$json = const {
const {'1': 'SwitchToField', '2': 14},
const {'1': 'DuplicateField', '2': 15},
const {'1': 'GetEditFieldContext', '2': 16},
const {'1': 'MoveItem', '2': 17},
const {'1': 'NewSelectOption', '2': 30},
const {'1': 'GetSelectOptionContext', '2': 31},
const {'1': 'UpdateSelectOption', '2': 32},
@ -35,4 +36,4 @@ const GridEvent$json = const {
};
/// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtJbnNlcnRGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEQoNU3dpdGNoVG9GaWVsZBAOEhIKDkR1cGxpY2F0ZUZpZWxkEA8SFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAQEhMKD05ld1NlbGVjdE9wdGlvbhAeEhoKFkdldFNlbGVjdE9wdGlvbkNvbnRleHQQHxIWChJVcGRhdGVTZWxlY3RPcHRpb24QIBINCglDcmVhdGVSb3cQMhIKCgZHZXRSb3cQMxINCglEZWxldGVSb3cQNBIQCgxEdXBsaWNhdGVSb3cQNRILCgdHZXRDZWxsEEYSDgoKVXBkYXRlQ2VsbBBHEhoKFlVwZGF0ZUNlbGxTZWxlY3RPcHRpb24QSA==');
final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtJbnNlcnRGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEQoNU3dpdGNoVG9GaWVsZBAOEhIKDkR1cGxpY2F0ZUZpZWxkEA8SFwoTR2V0RWRpdEZpZWxkQ29udGV4dBAQEgwKCE1vdmVJdGVtEBESEwoPTmV3U2VsZWN0T3B0aW9uEB4SGgoWR2V0U2VsZWN0T3B0aW9uQ29udGV4dBAfEhYKElVwZGF0ZVNlbGVjdE9wdGlvbhAgEg0KCUNyZWF0ZVJvdxAyEgoKBkdldFJvdxAzEg0KCURlbGV0ZVJvdxA0EhAKDER1cGxpY2F0ZVJvdxA1EgsKB0dldENlbGwQRhIOCgpVcGRhdGVDZWxsEEcSGgoWVXBkYXRlQ2VsbFNlbGVjdE9wdGlvbhBI');

View File

@ -653,6 +653,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
linked_scroll_controller:
dependency: "direct main"
description:
name: linked_scroll_controller
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
lint:
dependency: transitive
description:
@ -947,6 +954,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1+1"
reorderables:
dependency: "direct main"
description:
name: reorderables
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
shared_preferences:
dependency: transitive
description:

View File

@ -74,6 +74,8 @@ dependencies:
device_info_plus: ^3.2.1
fluttertoast: ^8.0.8
table_calendar: ^3.0.5
reorderables:
linked_scroll_controller: ^0.2.0
dev_dependencies:
flutter_lints: ^1.0.0

View File

@ -31,7 +31,7 @@ flowy-derive = {path = "../../../shared-lib/flowy-derive" }
[features]
default = ["flowy-sdk/dart", "dart-notify/dart", "flutter"]
flutter = []
http_server = ["flowy-sdk/http_server", "flowy-sdk/use_bunyan"]
http_sync = ["flowy-sdk/http_sync", "flowy-sdk/use_bunyan"]
#use_serde = ["bincode"]
#use_protobuf= ["protobuf"]

View File

@ -44,6 +44,7 @@ lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file
[features]
default = []
http_server = []
sync = []
cloud_sync = ["sync"]
flowy_unit_test = ["lib-ot/flowy_unit_test", "flowy-revision/flowy_unit_test"]
dart = ["lib-infra/dart"]

View File

@ -1,4 +1,3 @@
use crate::services::web_socket::make_folder_ws_manager;
use flowy_sync::{
client_folder::{FolderChange, FolderPad},
entities::{revision::Revision, ws_data::ServerRevisionWSData},
@ -11,7 +10,6 @@ use flowy_sync::util::make_delta_from_revisions;
use flowy_revision::{
RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder, RevisionWebSocket,
RevisionWebSocketManager,
};
use lib_infra::future::FutureResult;
use lib_ot::core::PlainTextAttributes;
@ -21,13 +19,16 @@ use std::sync::Arc;
pub struct ClientFolderEditor {
user_id: String,
#[allow(dead_code)]
pub(crate) folder_id: FolderId,
pub(crate) folder: Arc<RwLock<FolderPad>>,
rev_manager: Arc<RevisionManager>,
ws_manager: Arc<RevisionWebSocketManager>,
#[cfg(feature = "sync")]
ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,
}
impl ClientFolderEditor {
#[allow(unused_variables)]
pub async fn new(
user_id: &str,
folder_id: &FolderId,
@ -40,7 +41,9 @@ impl ClientFolderEditor {
});
let folder = Arc::new(RwLock::new(rev_manager.load::<FolderPadBuilder>(Some(cloud)).await?));
let rev_manager = Arc::new(rev_manager);
let ws_manager = make_folder_ws_manager(
#[cfg(feature = "sync")]
let ws_manager = crate::services::web_socket::make_folder_ws_manager(
user_id,
folder_id.as_ref(),
rev_manager.clone(),
@ -56,15 +59,23 @@ impl ClientFolderEditor {
folder_id,
folder,
rev_manager,
#[cfg(feature = "sync")]
ws_manager,
})
}
#[cfg(feature = "sync")]
pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> FlowyResult<()> {
let _ = self.ws_manager.ws_passthrough_tx.send(data).await.map_err(|e| {
let err_msg = format!("{} passthrough error: {}", self.folder_id, e);
FlowyError::internal().context(err_msg)
})?;
Ok(())
}
#[cfg(not(feature = "sync"))]
pub async fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FlowyResult<()> {
Ok(())
}

View File

@ -14,6 +14,7 @@ use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta};
use parking_lot::RwLock;
use std::{sync::Arc, time::Duration};
#[allow(dead_code)]
pub(crate) async fn make_folder_ws_manager(
user_id: &str,
folder_id: &str,

View File

@ -6,11 +6,11 @@ const OBSERVABLE_CATEGORY: &str = "Grid";
pub enum GridNotification {
Unknown = 0,
DidCreateBlock = 11,
DidUpdateGridBlock = 20,
DidUpdateGridRow = 20,
DidUpdateGridField = 21,
DidUpdateRow = 30,
DidUpdateCell = 31,
DidUpdateGrid = 40,
DidUpdateField = 41,
DidUpdateCell = 40,
DidUpdateField = 50,
}
impl std::default::Default for GridNotification {

View File

@ -130,6 +130,17 @@ pub(crate) async fn get_field_context_handler(
data_result(edit_context)
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn move_item_handler(
data: Data<MoveItemPayload>,
manager: AppData<Arc<GridManager>>,
) -> Result<(), FlowyError> {
let params: MoveItemParams = data.into_inner().try_into()?;
let editor = manager.get_grid_editor(&params.grid_id)?;
let _ = editor.move_item(params).await?;
Ok(())
}
async fn make_field_edit_context(
grid_id: &str,
field_id: Option<String>,

View File

@ -17,6 +17,8 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
.event(GridEvent::DeleteField, delete_field_handler)
.event(GridEvent::SwitchToField, switch_to_field_handler)
.event(GridEvent::DuplicateField, duplicate_field_handler)
.event(GridEvent::GetEditFieldContext, get_field_context_handler)
.event(GridEvent::MoveItem, move_item_handler)
// Row
.event(GridEvent::CreateRow, create_row_handler)
.event(GridEvent::GetRow, get_row_handler)
@ -29,9 +31,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
.event(GridEvent::NewSelectOption, new_select_option_handler)
.event(GridEvent::UpdateSelectOption, update_select_option_handler)
.event(GridEvent::GetSelectOptionContext, get_select_option_handler)
.event(GridEvent::UpdateCellSelectOption, update_cell_select_option_handler)
//
.event(GridEvent::GetEditFieldContext, get_field_context_handler);
.event(GridEvent::UpdateCellSelectOption, update_cell_select_option_handler);
module
}
@ -66,6 +66,9 @@ pub enum GridEvent {
#[event(input = "GetEditFieldContextPayload", output = "EditFieldContext")]
GetEditFieldContext = 16,
#[event(input = "MoveItemPayload")]
MoveItem = 17,
#[event(input = "SelectOptionName", output = "SelectOption")]
NewSelectOption = 30,

View File

@ -27,11 +27,11 @@
pub enum GridNotification {
Unknown = 0,
DidCreateBlock = 11,
DidUpdateGridBlock = 20,
DidUpdateGridRow = 20,
DidUpdateGridField = 21,
DidUpdateRow = 30,
DidUpdateCell = 31,
DidUpdateGrid = 40,
DidUpdateField = 41,
DidUpdateCell = 40,
DidUpdateField = 50,
}
impl ::protobuf::ProtobufEnum for GridNotification {
@ -43,11 +43,11 @@ impl ::protobuf::ProtobufEnum for GridNotification {
match value {
0 => ::std::option::Option::Some(GridNotification::Unknown),
11 => ::std::option::Option::Some(GridNotification::DidCreateBlock),
20 => ::std::option::Option::Some(GridNotification::DidUpdateGridBlock),
20 => ::std::option::Option::Some(GridNotification::DidUpdateGridRow),
21 => ::std::option::Option::Some(GridNotification::DidUpdateGridField),
30 => ::std::option::Option::Some(GridNotification::DidUpdateRow),
31 => ::std::option::Option::Some(GridNotification::DidUpdateCell),
40 => ::std::option::Option::Some(GridNotification::DidUpdateGrid),
41 => ::std::option::Option::Some(GridNotification::DidUpdateField),
40 => ::std::option::Option::Some(GridNotification::DidUpdateCell),
50 => ::std::option::Option::Some(GridNotification::DidUpdateField),
_ => ::std::option::Option::None
}
}
@ -56,10 +56,10 @@ impl ::protobuf::ProtobufEnum for GridNotification {
static values: &'static [GridNotification] = &[
GridNotification::Unknown,
GridNotification::DidCreateBlock,
GridNotification::DidUpdateGridBlock,
GridNotification::DidUpdateGridRow,
GridNotification::DidUpdateGridField,
GridNotification::DidUpdateRow,
GridNotification::DidUpdateCell,
GridNotification::DidUpdateGrid,
GridNotification::DidUpdateField,
];
values
@ -89,11 +89,11 @@ impl ::protobuf::reflect::ProtobufValue for GridNotification {
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x17dart_notification.proto*\x97\x01\n\x10GridNotification\x12\x0b\n\
\x07Unknown\x10\0\x12\x12\n\x0eDidCreateBlock\x10\x0b\x12\x16\n\x12DidUp\
dateGridBlock\x10\x14\x12\x10\n\x0cDidUpdateRow\x10\x1e\x12\x11\n\rDidUp\
dateCell\x10\x1f\x12\x11\n\rDidUpdateGrid\x10(\x12\x12\n\x0eDidUpdateFie\
ld\x10)b\x06proto3\
\n\x17dart_notification.proto*\x9a\x01\n\x10GridNotification\x12\x0b\n\
\x07Unknown\x10\0\x12\x12\n\x0eDidCreateBlock\x10\x0b\x12\x14\n\x10DidUp\
dateGridRow\x10\x14\x12\x16\n\x12DidUpdateGridField\x10\x15\x12\x10\n\
\x0cDidUpdateRow\x10\x1e\x12\x11\n\rDidUpdateCell\x10(\x12\x12\n\x0eDidU\
pdateField\x102b\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -34,6 +34,7 @@ pub enum GridEvent {
SwitchToField = 14,
DuplicateField = 15,
GetEditFieldContext = 16,
MoveItem = 17,
NewSelectOption = 30,
GetSelectOptionContext = 31,
UpdateSelectOption = 32,
@ -62,6 +63,7 @@ impl ::protobuf::ProtobufEnum for GridEvent {
14 => ::std::option::Option::Some(GridEvent::SwitchToField),
15 => ::std::option::Option::Some(GridEvent::DuplicateField),
16 => ::std::option::Option::Some(GridEvent::GetEditFieldContext),
17 => ::std::option::Option::Some(GridEvent::MoveItem),
30 => ::std::option::Option::Some(GridEvent::NewSelectOption),
31 => ::std::option::Option::Some(GridEvent::GetSelectOptionContext),
32 => ::std::option::Option::Some(GridEvent::UpdateSelectOption),
@ -87,6 +89,7 @@ impl ::protobuf::ProtobufEnum for GridEvent {
GridEvent::SwitchToField,
GridEvent::DuplicateField,
GridEvent::GetEditFieldContext,
GridEvent::MoveItem,
GridEvent::NewSelectOption,
GridEvent::GetSelectOptionContext,
GridEvent::UpdateSelectOption,
@ -125,16 +128,16 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x0fevent_map.proto*\xef\x02\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
\n\x0fevent_map.proto*\xfd\x02\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
\0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\
\x0bUpdateField\x10\x0b\x12\x0f\n\x0bInsertField\x10\x0c\x12\x0f\n\x0bDe\
leteField\x10\r\x12\x11\n\rSwitchToField\x10\x0e\x12\x12\n\x0eDuplicateF\
ield\x10\x0f\x12\x17\n\x13GetEditFieldContext\x10\x10\x12\x13\n\x0fNewSe\
lectOption\x10\x1e\x12\x1a\n\x16GetSelectOptionContext\x10\x1f\x12\x16\n\
\x12UpdateSelectOption\x10\x20\x12\r\n\tCreateRow\x102\x12\n\n\x06GetRow\
\x103\x12\r\n\tDeleteRow\x104\x12\x10\n\x0cDuplicateRow\x105\x12\x0b\n\
\x07GetCell\x10F\x12\x0e\n\nUpdateCell\x10G\x12\x1a\n\x16UpdateCellSelec\
tOption\x10Hb\x06proto3\
ield\x10\x0f\x12\x17\n\x13GetEditFieldContext\x10\x10\x12\x0c\n\x08MoveI\
tem\x10\x11\x12\x13\n\x0fNewSelectOption\x10\x1e\x12\x1a\n\x16GetSelectO\
ptionContext\x10\x1f\x12\x16\n\x12UpdateSelectOption\x10\x20\x12\r\n\tCr\
eateRow\x102\x12\n\n\x06GetRow\x103\x12\r\n\tDeleteRow\x104\x12\x10\n\
\x0cDuplicateRow\x105\x12\x0b\n\x07GetCell\x10F\x12\x0e\n\nUpdateCell\
\x10G\x12\x1a\n\x16UpdateCellSelectOption\x10Hb\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -3,9 +3,9 @@ syntax = "proto3";
enum GridNotification {
Unknown = 0;
DidCreateBlock = 11;
DidUpdateGridBlock = 20;
DidUpdateGridRow = 20;
DidUpdateGridField = 21;
DidUpdateRow = 30;
DidUpdateCell = 31;
DidUpdateGrid = 40;
DidUpdateField = 41;
DidUpdateCell = 40;
DidUpdateField = 50;
}

View File

@ -10,6 +10,7 @@ enum GridEvent {
SwitchToField = 14;
DuplicateField = 15;
GetEditFieldContext = 16;
MoveItem = 17;
NewSelectOption = 30;
GetSelectOptionContext = 31;
UpdateSelectOption = 32;

View File

@ -51,16 +51,16 @@ impl ClientGridBlockMetaEditor {
let mut row_count = 0;
let mut row_index = None;
let _ = self
.modify(|pad| {
.modify(|block_pad| {
if let Some(start_row_id) = start_row_id.as_ref() {
match pad.index_of_row(start_row_id) {
match block_pad.index_of_row(start_row_id) {
None => {}
Some(index) => row_index = Some(index + 1),
}
}
let change = pad.add_row_meta(row, start_row_id)?;
row_count = pad.number_of_rows();
let change = block_pad.add_row_meta(row, start_row_id)?;
row_count = block_pad.number_of_rows();
Ok(change)
})
.await?;
@ -71,9 +71,9 @@ impl ClientGridBlockMetaEditor {
pub async fn delete_rows(&self, ids: Vec<Cow<'_, String>>) -> FlowyResult<i32> {
let mut row_count = 0;
let _ = self
.modify(|pad| {
let changeset = pad.delete_rows(ids)?;
row_count = pad.number_of_rows();
.modify(|block_pad| {
let changeset = block_pad.delete_rows(ids)?;
row_count = block_pad.number_of_rows();
Ok(changeset)
})
.await?;
@ -81,7 +81,14 @@ impl ClientGridBlockMetaEditor {
}
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
let _ = self.modify(|pad| Ok(pad.update_row(changeset)?)).await?;
let _ = self.modify(|block_pad| Ok(block_pad.update_row(changeset)?)).await?;
Ok(())
}
pub async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> {
let _ = self
.modify(|block_pad| Ok(block_pad.move_row(row_id, from, to)?))
.await?;
Ok(())
}

View File

@ -2,14 +2,14 @@ use crate::dart_notification::{send_dart_notification, GridNotification};
use crate::manager::GridUser;
use crate::services::block_meta_editor::ClientGridBlockMetaEditor;
use crate::services::persistence::block_index::BlockIndexPersistence;
use crate::services::row::{group_row_orders, make_rows_from_row_metas, GridBlockSnapshot};
use crate::services::row::{group_row_orders, GridBlockSnapshot};
use std::borrow::Cow;
use dashmap::DashMap;
use flowy_error::FlowyResult;
use flowy_grid_data_model::entities::{
CellChangeset, CellMeta, CellNotificationData, FieldMeta, GridBlockMeta, GridBlockMetaChangeset,
GridBlockOrderChangeset, IndexRowOrder, RowMeta, RowMetaChangeset, RowOrder,
CellChangeset, CellMeta, CellNotificationData, GridBlockMeta, GridBlockMetaChangeset, GridRowsChangeset,
IndexRowOrder, RowMeta, RowMetaChangeset, RowOrder,
};
use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
use flowy_revision::{RevisionManager, RevisionPersistence};
@ -76,7 +76,7 @@ impl GridBlockMetaEditorManager {
index_row_order.index = row_index;
let _ = self
.notify_did_update_grid_rows(GridBlockOrderChangeset::from_insert(block_id, vec![index_row_order]))
.notify_did_update_block(GridRowsChangeset::insert(block_id, vec![index_row_order]))
.await?;
Ok(row_count)
}
@ -98,7 +98,7 @@ impl GridBlockMetaEditorManager {
changesets.push(GridBlockMetaChangeset::from_row_count(&block_id, row_count));
let _ = self
.notify_did_update_grid_rows(GridBlockOrderChangeset::from_insert(&block_id, inserted_row_orders))
.notify_did_update_block(GridRowsChangeset::insert(&block_id, inserted_row_orders))
.await?;
}
@ -108,19 +108,7 @@ impl GridBlockMetaEditorManager {
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
let editor = self.get_editor_from_row_id(&changeset.row_id).await?;
let _ = editor.update_row(changeset.clone()).await?;
match editor
.get_row_orders(Some(vec![Cow::Borrowed(&changeset.row_id)]))
.await?
.pop()
{
None => {}
Some(row_order) => {
let block_order_changeset = GridBlockOrderChangeset::from_update(&editor.block_id, vec![row_order]);
let _ = self.notify_did_update_grid_rows(block_order_changeset).await?;
}
}
let _ = self.notify_did_update_block_row(&changeset.row_id).await?;
Ok(())
}
@ -131,7 +119,7 @@ impl GridBlockMetaEditorManager {
let row_orders = editor.get_row_orders(Some(vec![Cow::Borrowed(&row_id)])).await?;
let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?;
let _ = self
.notify_did_update_grid_rows(GridBlockOrderChangeset::from_delete(&block_id, row_orders))
.notify_did_update_block(GridRowsChangeset::delete(&block_id, row_orders))
.await?;
Ok(())
@ -154,11 +142,35 @@ impl GridBlockMetaEditorManager {
Ok(changesets)
}
pub(crate) async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> {
let editor = self.get_editor_from_row_id(row_id).await?;
let _ = editor.move_row(row_id, from, to).await?;
match editor.get_row_metas(Some(vec![Cow::Borrowed(row_id)])).await?.pop() {
None => {}
Some(row_meta) => {
let row_order = RowOrder::from(&row_meta);
let insert_row = IndexRowOrder {
row_order: row_order.clone(),
index: Some(to as i32),
};
let notified_changeset = GridRowsChangeset {
block_id: editor.block_id.clone(),
inserted_rows: vec![insert_row],
deleted_rows: vec![row_order],
updated_rows: vec![],
};
let _ = self.notify_did_update_block(notified_changeset).await?;
}
}
Ok(())
}
pub async fn update_cell(&self, changeset: CellChangeset) -> FlowyResult<()> {
let row_id = changeset.row_id.clone();
let editor = self.get_editor_from_row_id(&row_id).await?;
let row_changeset: RowMetaChangeset = changeset.clone().into();
let _ = editor.update_row(row_changeset).await?;
let _ = self.update_row(row_changeset).await?;
let cell_notification_data = CellNotificationData {
grid_id: changeset.grid_id,
@ -167,6 +179,7 @@ impl GridBlockMetaEditorManager {
content: changeset.data,
};
self.notify_did_update_cell(cell_notification_data).await?;
Ok(())
}
@ -213,8 +226,22 @@ impl GridBlockMetaEditorManager {
Ok(block_cell_metas)
}
async fn notify_did_update_grid_rows(&self, changeset: GridBlockOrderChangeset) -> FlowyResult<()> {
send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridBlock)
async fn notify_did_update_block_row(&self, row_id: &str) -> FlowyResult<()> {
let editor = self.get_editor_from_row_id(row_id).await?;
let row_ids = Some(vec![Cow::Borrowed(&row_id)]);
match editor.get_row_orders(row_ids).await?.pop() {
None => {}
Some(row_order) => {
let block_order_changeset = GridRowsChangeset::update(&editor.block_id, vec![row_order]);
let _ = self.notify_did_update_block(block_order_changeset).await?;
}
}
Ok(())
}
async fn notify_did_update_block(&self, changeset: GridRowsChangeset) -> FlowyResult<()> {
send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridRow)
.payload(changeset)
.send();
Ok(())
@ -227,22 +254,6 @@ impl GridBlockMetaEditorManager {
.send();
Ok(())
}
#[allow(dead_code)]
async fn notify_did_update_row(&self, row_id: &str, field_metas: &[FieldMeta]) -> FlowyResult<()> {
match self.get_row_meta(row_id).await? {
None => {}
Some(row_meta) => {
let row_metas = vec![row_meta];
if let Some(row) = make_rows_from_row_metas(field_metas, &row_metas).pop() {
send_dart_notification(row_id, GridNotification::DidUpdateRow)
.payload(row)
.send();
}
}
}
Ok(())
}
}
async fn make_block_meta_editor_map(

View File

@ -47,7 +47,7 @@ impl DateTypeOption {
if self.include_time {
format!("{} {}", self.date_format.format_str(), self.time_format.format_str())
} else {
format!("{}", self.date_format.format_str())
self.date_format.format_str().to_string()
}
}
}

View File

@ -59,9 +59,9 @@ impl ClientGridEditor {
grid_id,
} = params;
let field_id = field.id.clone();
let _ = self
.modify(|grid| {
if grid.contain_field(&field.id) {
if self.contain_field(&field_id).await {
let _ = self
.modify(|grid| {
let deserializer = TypeOptionJsonDeserializer(field.field_type.clone());
let changeset = FieldChangesetParams {
field_id: field.id,
@ -74,17 +74,22 @@ impl ClientGridEditor {
width: Some(field.width),
type_option_data: Some(type_option_data),
};
Ok(grid.update_field(changeset, deserializer)?)
} else {
// let type_option_json = type_option_json_str_from_bytes(type_option_data, &field.field_type);
Ok(grid.update_field_meta(changeset, deserializer)?)
})
.await?;
let _ = self.notify_did_update_grid_field(&field_id).await?;
} else {
let _ = self
.modify(|grid| {
let builder = type_option_builder_from_bytes(type_option_data, &field.field_type);
let field_meta = FieldBuilder::from_field(field, builder).build();
Ok(grid.create_field(field_meta, start_field_id)?)
}
})
.await?;
let _ = self.notify_did_update_grid().await?;
let _ = self.notify_did_update_field(&field_id).await?;
Ok(grid.create_field_meta(field_meta, start_field_id)?)
})
.await?;
let _ = self.notify_did_insert_grid_field(&field_id).await?;
}
Ok(())
}
@ -100,29 +105,31 @@ impl ClientGridEditor {
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(params.field_id.as_str()) {
let json_deserializer = match self.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()),
Some((_, field_meta)) => TypeOptionJsonDeserializer(field_meta.field_type.clone()),
};
let _ = self
.modify(|grid| Ok(grid.update_field(params, json_deserializer)?))
.modify(|grid| Ok(grid.update_field_meta(params, json_deserializer)?))
.await?;
let _ = self.notify_did_update_grid().await?;
let _ = self.notify_did_update_field(&field_id).await?;
let _ = self.notify_did_update_grid_field(&field_id).await?;
Ok(())
}
pub async fn replace_field(&self, field_meta: FieldMeta) -> FlowyResult<()> {
let field_id = field_meta.id.clone();
let _ = self.modify(|pad| Ok(pad.replace_field(field_meta)?)).await?;
let _ = self.notify_did_update_field(&field_id).await?;
let _ = self.modify(|pad| Ok(pad.replace_field_meta(field_meta)?)).await?;
let _ = self.notify_did_update_grid_field(&field_id).await?;
Ok(())
}
pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> {
let _ = self.modify(|grid| Ok(grid.delete_field(field_id)?)).await?;
let _ = self.notify_did_update_grid().await?;
let _ = self.modify(|grid| Ok(grid.delete_field_meta(field_id)?)).await?;
let field_order = FieldOrder::from(field_id);
let notified_changeset = GridFieldChangeset::delete(&self.grid_id, vec![field_order]);
let _ = self.notify_did_update_grid(notified_changeset).await?;
Ok(())
}
@ -145,19 +152,24 @@ impl ClientGridEditor {
let _ = self
.modify(|grid| Ok(grid.switch_to_field(field_id, field_type.clone(), type_option_json_builder)?))
.await?;
let _ = self.notify_did_update_grid().await?;
let _ = self.notify_did_update_field(field_id).await?;
let _ = self.notify_did_update_grid_field(field_id).await?;
Ok(())
}
pub async fn duplicate_field(&self, field_id: &str) -> FlowyResult<()> {
let _ = self.modify(|grid| Ok(grid.duplicate_field(field_id)?)).await?;
let _ = self.notify_did_update_grid().await?;
let duplicated_field_id = gen_field_id();
let _ = self
.modify(|grid| Ok(grid.duplicate_field_meta(field_id, &duplicated_field_id)?))
.await?;
let _ = self.notify_did_insert_grid_field(&duplicated_field_id).await?;
Ok(())
}
pub async fn get_field_meta(&self, field_id: &str) -> Option<FieldMeta> {
let field_meta = self.pad.read().await.get_field(field_id)?.clone();
let field_meta = self.pad.read().await.get_field_meta(field_id)?.1.clone();
Some(field_meta)
}
@ -302,12 +314,12 @@ 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(&changeset.field_id) {
match self.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))
}
Some(field_meta) => {
Some((_, field_meta)) => {
// Update the changeset.data property with the return value.
changeset.data = Some(apply_cell_data_changeset(cell_data_changeset, cell_meta, field_meta)?);
let _ = self.block_meta_manager.update_cell(changeset).await?;
@ -326,11 +338,6 @@ impl ClientGridEditor {
Ok(grid_blocks)
}
// pub async fn get_field_metas<T>(&self, field_ids: Option<Vec<T>>) -> FlowyResult<Vec<FieldMeta>>
// where
// T: Into<FieldOrder>,
// {
pub async fn delete_rows(&self, row_orders: Vec<RowOrder>) -> FlowyResult<()> {
let changesets = self.block_meta_manager.delete_rows(row_orders).await?;
for changeset in changesets {
@ -375,6 +382,43 @@ impl ClientGridEditor {
Ok(snapshots)
}
pub async fn move_item(&self, params: MoveItemParams) -> FlowyResult<()> {
match params.ty {
MoveItemType::MoveField => {
self.move_field(&params.item_id, params.from_index, params.to_index)
.await
}
MoveItemType::MoveRow => self.move_row(&params.item_id, params.from_index, params.to_index).await,
}
}
pub async fn move_field(&self, field_id: &str, from: i32, to: i32) -> FlowyResult<()> {
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) {
let delete_field_order = FieldOrder::from(field_id);
let insert_field = IndexField::from_field_meta(field_meta, index);
let notified_changeset = GridFieldChangeset {
grid_id: self.grid_id.clone(),
inserted_fields: vec![insert_field],
deleted_fields: vec![delete_field_order],
updated_fields: vec![],
};
let _ = self.notify_did_update_grid(notified_changeset).await?;
}
Ok(())
}
pub async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> {
let _ = self
.block_meta_manager
.move_row(row_id, from as usize, to as usize)
.await?;
Ok(())
}
pub async fn delta_bytes(&self) -> Bytes {
self.pad.read().await.delta_bytes()
}
@ -417,28 +461,40 @@ impl ClientGridEditor {
}
}
async fn notify_did_update_grid(&self) -> FlowyResult<()> {
let field_metas = self.get_field_metas::<FieldOrder>(None).await?;
let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
send_dart_notification(&self.grid_id, GridNotification::DidUpdateGrid)
.payload(repeated_field)
.send();
#[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) {
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?;
}
Ok(())
}
#[tracing::instrument(level = "trace", skip_all, err)]
async fn notify_did_update_field(&self, field_id: &str) -> FlowyResult<()> {
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() {
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?;
send_dart_notification(field_id, GridNotification::DidUpdateField)
.payload(Field::from(field_meta))
.payload(updated_field)
.send();
}
Ok(())
}
async fn notify_did_update_grid(&self, changeset: GridFieldChangeset) -> FlowyResult<()> {
send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridField)
.payload(changeset)
.send();
Ok(())
}
}
#[cfg(feature = "flowy_unit_test")]

View File

@ -38,6 +38,7 @@ tokio = { version = "1", features = ["full"]}
futures-util = "0.3.15"
[features]
http_server = ["flowy-user/http_server", "flowy-folder/http_server", "flowy-text-block/http_server"]
http_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
native_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
use_bunyan = ["lib-log/use_bunyan"]
dart = ["flowy-user/dart", "flowy-net/dart", "flowy-folder/dart", "flowy-sync/dart", "flowy-grid/dart", "flowy-text-block/dart"]

View File

@ -193,7 +193,7 @@ fn mk_local_server(
server_config: &ClientServerConfiguration,
) -> (Option<Arc<LocalServer>>, Arc<FlowyWebSocketConnect>) {
let ws_addr = server_config.ws_addr();
if cfg!(feature = "http_server") {
if cfg!(feature = "http_sync") {
let ws_conn = Arc::new(FlowyWebSocketConnect::new(ws_addr));
(None, ws_conn)
} else {

View File

@ -51,6 +51,7 @@ rand = "0.7.3"
lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] }
[features]
http_server = []
sync = []
cloud_sync = ["sync"]
flowy_unit_test = ["lib-ot/flowy_unit_test", "flowy-revision/flowy_unit_test"]
dart = ["lib-infra/dart"]

View File

@ -1,4 +1,4 @@
use crate::web_socket::{make_block_ws_manager, EditorCommandSender};
use crate::web_socket::EditorCommandSender;
use crate::{
errors::FlowyError,
queue::{EditBlockQueue, EditorCommand},
@ -6,9 +6,7 @@ use crate::{
};
use bytes::Bytes;
use flowy_error::{internal_error, FlowyResult};
use flowy_revision::{
RevisionCloudService, RevisionManager, RevisionObjectBuilder, RevisionWebSocket, RevisionWebSocketManager,
};
use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilder, RevisionWebSocket};
use flowy_sync::entities::ws_data::ServerRevisionWSData;
use flowy_sync::{
entities::{revision::Revision, text_block_info::TextBlockInfo},
@ -27,11 +25,13 @@ pub struct ClientTextBlockEditor {
pub doc_id: String,
#[allow(dead_code)]
rev_manager: Arc<RevisionManager>,
ws_manager: Arc<RevisionWebSocketManager>,
#[cfg(feature = "sync")]
ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,
edit_cmd_tx: EditorCommandSender,
}
impl ClientTextBlockEditor {
#[allow(unused_variables)]
pub(crate) async fn new(
doc_id: &str,
user: Arc<dyn TextBlockUser>,
@ -46,7 +46,8 @@ impl ClientTextBlockEditor {
let user_id = user.user_id()?;
let edit_cmd_tx = spawn_edit_queue(user, rev_manager.clone(), delta);
let ws_manager = make_block_ws_manager(
#[cfg(feature = "sync")]
let ws_manager = crate::web_socket::make_block_ws_manager(
doc_id.clone(),
user_id.clone(),
edit_cmd_tx.clone(),
@ -57,6 +58,7 @@ impl ClientTextBlockEditor {
let editor = Arc::new(Self {
doc_id,
rev_manager,
#[cfg(feature = "sync")]
ws_manager,
edit_cmd_tx,
});
@ -158,17 +160,29 @@ impl ClientTextBlockEditor {
Ok(())
}
#[cfg(feature = "sync")]
pub fn stop(&self) {
self.ws_manager.stop();
}
#[cfg(not(feature = "sync"))]
pub fn stop(&self) {}
#[cfg(feature = "sync")]
pub(crate) async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> {
self.ws_manager.receive_ws_data(data).await
}
#[cfg(not(feature = "sync"))]
pub(crate) async fn receive_ws_data(&self, _data: ServerRevisionWSData) -> Result<(), FlowyError> {
Ok(())
}
#[cfg(feature = "sync")]
pub(crate) fn receive_ws_state(&self, state: &WSConnectState) {
self.ws_manager.connect_state_changed(state.clone());
}
#[cfg(not(feature = "sync"))]
pub(crate) fn receive_ws_state(&self, _state: &WSConnectState) {}
}
impl std::ops::Drop for ClientTextBlockEditor {

View File

@ -23,6 +23,7 @@ use tokio::sync::{
pub(crate) type EditorCommandSender = Sender<EditorCommand>;
pub(crate) type EditorCommandReceiver = Receiver<EditorCommand>;
#[allow(dead_code)]
pub(crate) async fn make_block_ws_manager(
doc_id: String,
user_id: String,
@ -49,6 +50,7 @@ pub(crate) async fn make_block_ws_manager(
ws_manager
}
#[allow(dead_code)]
fn listen_document_ws_state(_user_id: &str, _doc_id: &str, mut subscriber: broadcast::Receiver<WSConnectState>) {
tokio::spawn(async move {
while let Ok(state) = subscriber.recv().await {
@ -67,6 +69,7 @@ pub(crate) struct TextBlockRevisionWSDataStream {
}
impl TextBlockRevisionWSDataStream {
#[allow(dead_code)]
pub fn new(conflict_controller: RichTextConflictController) -> Self {
Self {
conflict_controller: Arc::new(conflict_controller),

View File

@ -37,7 +37,6 @@ futures = "0.3.15"
nanoid = "0.4.0"
[features]
http_server = []
dart = ["lib-infra/dart"]
[build-dependencies]

View File

@ -84,6 +84,68 @@ impl std::convert::From<String> for FieldOrder {
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct GridFieldChangeset {
#[pb(index = 1)]
pub grid_id: String,
#[pb(index = 2)]
pub inserted_fields: Vec<IndexField>,
#[pb(index = 3)]
pub deleted_fields: Vec<FieldOrder>,
#[pb(index = 4)]
pub updated_fields: Vec<Field>,
}
impl GridFieldChangeset {
pub fn insert(grid_id: &str, inserted_fields: Vec<IndexField>) -> Self {
Self {
grid_id: grid_id.to_owned(),
inserted_fields,
deleted_fields: vec![],
updated_fields: vec![],
}
}
pub fn delete(grid_id: &str, deleted_fields: Vec<FieldOrder>) -> Self {
Self {
grid_id: grid_id.to_string(),
inserted_fields: vec![],
deleted_fields,
updated_fields: vec![],
}
}
pub fn update(grid_id: &str, updated_fields: Vec<Field>) -> Self {
Self {
grid_id: grid_id.to_string(),
inserted_fields: vec![],
deleted_fields: vec![],
updated_fields,
}
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct IndexField {
#[pb(index = 1)]
pub field: Field,
#[pb(index = 2)]
pub index: i32,
}
impl IndexField {
pub fn from_field_meta(field_meta: &FieldMeta, index: usize) -> Self {
Self {
field: Field::from(field_meta.clone()),
index: index as i32,
}
}
}
#[derive(Debug, Default, ProtoBuf)]
pub struct GetEditFieldContextPayload {
#[pb(index = 1)]
@ -278,7 +340,16 @@ impl GridBlockOrder {
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct GridBlockOrderChangeset {
pub struct IndexRowOrder {
#[pb(index = 1)]
pub row_order: RowOrder,
#[pb(index = 2, one_of)]
pub index: Option<i32>,
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct GridRowsChangeset {
#[pb(index = 1)]
pub block_id: String,
@ -292,15 +363,6 @@ pub struct GridBlockOrderChangeset {
pub updated_rows: Vec<RowOrder>,
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct IndexRowOrder {
#[pb(index = 1)]
pub row_order: RowOrder,
#[pb(index = 2, one_of)]
pub index: Option<i32>,
}
impl std::convert::From<RowOrder> for IndexRowOrder {
fn from(row_order: RowOrder) -> Self {
Self { row_order, index: None }
@ -314,8 +376,8 @@ impl std::convert::From<&RowMeta> for IndexRowOrder {
}
}
impl GridBlockOrderChangeset {
pub fn from_insert(block_id: &str, inserted_rows: Vec<IndexRowOrder>) -> Self {
impl GridRowsChangeset {
pub fn insert(block_id: &str, inserted_rows: Vec<IndexRowOrder>) -> Self {
Self {
block_id: block_id.to_owned(),
inserted_rows,
@ -324,7 +386,7 @@ impl GridBlockOrderChangeset {
}
}
pub fn from_delete(block_id: &str, deleted_rows: Vec<RowOrder>) -> Self {
pub fn delete(block_id: &str, deleted_rows: Vec<RowOrder>) -> Self {
Self {
block_id: block_id.to_owned(),
inserted_rows: vec![],
@ -333,7 +395,7 @@ impl GridBlockOrderChangeset {
}
}
pub fn from_update(block_id: &str, updated_rows: Vec<RowOrder>) -> Self {
pub fn update(block_id: &str, updated_rows: Vec<RowOrder>) -> Self {
Self {
block_id: block_id.to_owned(),
inserted_rows: vec![],
@ -656,6 +718,61 @@ impl TryInto<FieldChangesetParams> for FieldChangesetPayload {
}
}
#[derive(Debug, Clone, ProtoBuf_Enum)]
pub enum MoveItemType {
MoveField = 0,
MoveRow = 1,
}
impl std::default::Default for MoveItemType {
fn default() -> Self {
MoveItemType::MoveField
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct MoveItemPayload {
#[pb(index = 1)]
pub grid_id: String,
#[pb(index = 2)]
pub item_id: String,
#[pb(index = 3)]
pub from_index: i32,
#[pb(index = 4)]
pub to_index: i32,
#[pb(index = 5)]
pub ty: MoveItemType,
}
#[derive(Clone)]
pub struct MoveItemParams {
pub grid_id: String,
pub item_id: String,
pub from_index: i32,
pub to_index: i32,
pub ty: MoveItemType,
}
impl TryInto<MoveItemParams> for MoveItemPayload {
type Error = ErrorCode;
fn try_into(self) -> Result<MoveItemParams, Self::Error> {
let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
let item_id = NotEmptyStr::parse(self.item_id).map_err(|_| ErrorCode::InvalidData)?;
Ok(MoveItemParams {
grid_id: grid_id.0,
item_id: item_id.0,
from_index: self.from_index,
to_index: self.to_index,
ty: self.ty,
})
}
}
#[derive(
Debug,
Clone,

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,16 @@ message Field {
message FieldOrder {
string field_id = 1;
}
message GridFieldChangeset {
string grid_id = 1;
repeated IndexField inserted_fields = 2;
repeated FieldOrder deleted_fields = 3;
repeated Field updated_fields = 4;
}
message IndexField {
Field field = 1;
int32 index = 2;
}
message GetEditFieldContextPayload {
string grid_id = 1;
oneof one_of_field_id { string field_id = 2; };
@ -58,16 +68,16 @@ message GridBlockOrder {
string block_id = 1;
repeated RowOrder row_orders = 2;
}
message GridBlockOrderChangeset {
message IndexRowOrder {
RowOrder row_order = 1;
oneof one_of_index { int32 index = 2; };
}
message GridRowsChangeset {
string block_id = 1;
repeated IndexRowOrder inserted_rows = 2;
repeated RowOrder deleted_rows = 3;
repeated RowOrder updated_rows = 4;
}
message IndexRowOrder {
RowOrder row_order = 1;
oneof one_of_index { int32 index = 2; };
}
message GridBlock {
string id = 1;
repeated RowOrder row_orders = 2;
@ -123,12 +133,23 @@ message FieldChangesetPayload {
oneof one_of_width { int32 width = 8; };
oneof one_of_type_option_data { bytes type_option_data = 9; };
}
message MoveItemPayload {
string grid_id = 1;
string item_id = 2;
int32 from_index = 3;
int32 to_index = 4;
MoveItemType ty = 5;
}
message CellChangeset {
string grid_id = 1;
string row_id = 2;
string field_id = 3;
oneof one_of_data { string data = 4; };
}
enum MoveItemType {
MoveField = 0;
MoveRow = 1;
}
enum FieldType {
RichText = 0;
Number = 1;

View File

@ -149,6 +149,19 @@ impl GridBlockMetaPad {
})
}
pub fn move_row(&mut self, row_id: &str, from: usize, to: usize) -> CollaborateResult<Option<GridBlockMetaChange>> {
self.modify(|row_metas| {
if let Some(position) = row_metas.iter().position(|row_meta| row_meta.id == row_id) {
debug_assert_eq!(from, position);
let row_meta = row_metas.remove(position);
row_metas.insert(to, row_meta);
Ok(Some(()))
} else {
Ok(None)
}
})
}
pub fn modify<F>(&mut self, f: F) -> CollaborateResult<Option<GridBlockMetaChange>>
where
F: for<'a> FnOnce(&'a mut Vec<Arc<RowMeta>>) -> CollaborateResult<Option<()>>,

View File

@ -3,12 +3,11 @@ use crate::errors::{internal_error, CollaborateError, CollaborateResult};
use crate::util::{cal_diff, make_delta_from_revisions};
use bytes::Bytes;
use flowy_grid_data_model::entities::{
gen_field_id, gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta,
GridBlockMetaChangeset, GridMeta,
gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockMeta, GridBlockMetaChangeset,
GridMeta,
};
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
use std::collections::HashMap;
use std::sync::Arc;
pub type GridMetaDelta = PlainTextDelta;
@ -41,7 +40,7 @@ impl GridMetaPad {
}
#[tracing::instrument(level = "debug", skip_all, err)]
pub fn create_field(
pub fn create_field_meta(
&mut self,
new_field_meta: FieldMeta,
start_field_id: Option<String>,
@ -70,7 +69,7 @@ impl GridMetaPad {
})
}
pub fn delete_field(&mut self, field_id: &str) -> CollaborateResult<Option<GridChangeset>> {
pub fn delete_field_meta(&mut self, field_id: &str) -> CollaborateResult<Option<GridChangeset>> {
self.modify_grid(
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
None => Ok(None),
@ -82,13 +81,17 @@ impl GridMetaPad {
)
}
pub fn duplicate_field(&mut self, field_id: &str) -> CollaborateResult<Option<GridChangeset>> {
pub fn duplicate_field_meta(
&mut self,
field_id: &str,
duplicated_field_id: &str,
) -> CollaborateResult<Option<GridChangeset>> {
self.modify_grid(
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
None => Ok(None),
Some(index) => {
let mut duplicate_field_meta = grid_meta.fields[index].clone();
duplicate_field_meta.id = gen_field_id();
duplicate_field_meta.id = duplicated_field_id.to_string();
duplicate_field_meta.name = format!("{} (copy)", duplicate_field_meta.name);
grid_meta.fields.insert(index + 1, duplicate_field_meta);
Ok(Some(()))
@ -126,7 +129,7 @@ impl GridMetaPad {
})
}
pub fn update_field<T: JsonDeserializer>(
pub fn update_field_meta<T: JsonDeserializer>(
&mut self,
changeset: FieldChangesetParams,
deserializer: T,
@ -181,11 +184,15 @@ impl GridMetaPad {
})
}
pub fn get_field(&self, field_id: &str) -> Option<&FieldMeta> {
self.grid_meta.fields.iter().find(|field| field.id == field_id)
pub fn get_field_meta(&self, field_id: &str) -> Option<(usize, &FieldMeta)> {
self.grid_meta
.fields
.iter()
.enumerate()
.find(|(_, field)| field.id == field_id)
}
pub fn replace_field(&mut self, field_meta: FieldMeta) -> CollaborateResult<Option<GridChangeset>> {
pub fn replace_field_meta(&mut self, field_meta: FieldMeta) -> CollaborateResult<Option<GridChangeset>> {
self.modify_grid(
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_meta.id) {
None => Ok(None),
@ -198,6 +205,25 @@ impl GridMetaPad {
)
}
pub fn move_field(
&mut self,
field_id: &str,
_from_index: usize,
to_index: usize,
) -> CollaborateResult<Option<GridChangeset>> {
self.modify_grid(
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
None => Ok(None),
Some(index) => {
// debug_assert_eq!(index, from_index);
let field_meta = grid_meta.fields.remove(index);
grid_meta.fields.insert(to_index, field_meta);
Ok(Some(()))
}
},
)
}
pub fn contain_field(&self, field_id: &str) -> bool {
self.grid_meta.fields.iter().any(|field| field.id == field_id)
}