refactor: order object position and field service (#4118)

* refactor: create OrderObjectPositionPB

* refactor: use ObjectOrderPosition for creating rows

* refactor: field backend service

* refactor: use field_id for reordering fields

* style: reorder dependencies

* fix: changes on tauri

* feat: insert row above

* chore: don't pass group_id while duplicating a row
This commit is contained in:
Richard Shiue 2023-12-11 11:19:20 +08:00 committed by GitHub
parent 7586930ef0
commit 4e6643eca8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 700 additions and 702 deletions

View File

@ -173,7 +173,6 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
rowCache: rowCache,
cardData: groupData.group.groupId,
groupingFieldId: groupItem.fieldInfo.id,
groupId: groupData.group.groupId,
isEditing: isEditing,
cellBuilder: cellBuilder,
renderHook: renderHook,

View File

@ -10,7 +10,6 @@ import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/w
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/number_format_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
import 'package:appflowy/util/field_type_extension.dart';
@ -56,7 +55,7 @@ class FieldOptionValues {
Future<void> create({
required String viewId,
}) async {
await TypeOptionBackendService.createFieldTypeOption(
await FieldBackendService.createField(
viewId: viewId,
fieldType: type,
fieldName: name,

View File

@ -4,7 +4,6 @@ import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
@ -15,14 +14,13 @@ import 'package:collection/collection.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'database_view_service.dart';
import 'defines.dart';
import 'field/field_info.dart';
import 'layout/layout_service.dart';
import 'layout/layout_setting_listener.dart';
import 'row/row_cache.dart';
import 'group/group_listener.dart';
import 'row/row_service.dart';
typedef OnGroupConfigurationChanged = void Function(List<GroupSettingPB>);
typedef OnGroupByField = void Function(List<GroupPB>);
@ -174,28 +172,6 @@ class DatabaseController {
});
}
Future<Either<RowMetaPB, FlowyError>> createRow({
RowId? startRowId,
String? groupId,
bool fromBeginning = false,
void Function(RowDataBuilder builder)? withCells,
}) {
Map<String, String>? cellDataByFieldId;
if (withCells != null) {
final rowBuilder = RowDataBuilder();
withCells(rowBuilder);
cellDataByFieldId = rowBuilder.build();
}
return _databaseViewBackendSvc.createRow(
startRowId: startRowId,
groupId: groupId,
cellDataByFieldId: cellDataByFieldId,
fromBeginning: fromBeginning,
);
}
Future<Either<Unit, FlowyError>> moveGroupRow({
required RowMetaPB fromRow,
required String fromGroupId,
@ -385,27 +361,3 @@ class DatabaseController {
);
}
}
class RowDataBuilder {
final _cellDataByFieldId = <String, String>{};
void insertText(FieldInfo fieldInfo, String text) {
assert(fieldInfo.fieldType == FieldType.RichText);
_cellDataByFieldId[fieldInfo.field.id] = text;
}
void insertNumber(FieldInfo fieldInfo, int num) {
assert(fieldInfo.fieldType == FieldType.Number);
_cellDataByFieldId[fieldInfo.field.id] = num.toString();
}
void insertDate(FieldInfo fieldInfo, DateTime date) {
assert(FieldType.DateTime == fieldInfo.fieldType);
final timestamp = date.millisecondsSinceEpoch ~/ 1000;
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
}
Map<String, String> build() {
return _cellDataByFieldId;
}
}

View File

@ -10,7 +10,6 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
class DatabaseViewBackendService {
final String viewId;
@ -31,29 +30,6 @@ class DatabaseViewBackendService {
return DatabaseEventGetDatabase(payload).send();
}
Future<Either<RowMetaPB, FlowyError>> createRow({
RowId? startRowId,
String? groupId,
Map<String, String>? cellDataByFieldId,
bool fromBeginning = false,
}) {
final payload = CreateRowPayloadPB.create()..viewId = viewId;
if (!fromBeginning || startRowId != null) {
payload.startRowId = startRowId ?? "";
}
if (groupId != null) {
payload.groupId = groupId;
}
if (cellDataByFieldId != null && cellDataByFieldId.isNotEmpty) {
payload.data = RowDataPB(cellDataByFieldId: cellDataByFieldId);
}
return DatabaseEventCreateRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveGroupRow({
required RowId fromRowId,
required String fromGroupId,

View File

@ -1,5 +1,4 @@
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
@ -38,26 +37,30 @@ class FieldServices {
}
Future<void> delete() async {
await fieldBackendService.deleteField();
await fieldBackendService.delete();
}
Future<void> duplicate() async {
await fieldBackendService.duplicateField();
await fieldBackendService.duplicate();
}
Future<void> insertLeft() async {
await TypeOptionBackendService.createFieldTypeOption(
await FieldBackendService.createField(
viewId: viewId,
position: CreateFieldPosition.Before,
targetFieldId: fieldId,
position: OrderObjectPositionPB(
position: OrderObjectPositionTypePB.Before,
objectId: fieldId,
),
);
}
Future<void> insertRight() async {
await TypeOptionBackendService.createFieldTypeOption(
await FieldBackendService.createField(
viewId: viewId,
position: CreateFieldPosition.After,
targetFieldId: fieldId,
position: OrderObjectPositionPB(
position: OrderObjectPositionTypePB.After,
objectId: fieldId,
),
);
}

View File

@ -1,4 +1,3 @@
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
@ -77,22 +76,14 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
_logIfError(result);
},
insertLeft: () async {
final result = await TypeOptionBackendService.createFieldTypeOption(
viewId: viewId,
position: CreateFieldPosition.Before,
targetFieldId: field.id,
);
final result = await fieldService.insertBefore();
result.fold(
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
(err) => Log.error("Failed creating field $err"),
);
},
insertRight: () async {
final result = await TypeOptionBackendService.createFieldTypeOption(
viewId: viewId,
position: CreateFieldPosition.After,
targetFieldId: field.id,
);
final result = await fieldService.insertAfter();
result.fold(
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
(err) => Log.error("Failed creating field $err"),
@ -111,14 +102,6 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
);
_logIfError(result);
},
deleteField: () async {
final result = await fieldService.deleteField();
_logIfError(result);
},
duplicateField: () async {
final result = await fieldService.duplicateField();
_logIfError(result);
},
);
},
);
@ -151,8 +134,6 @@ class FieldEditorEvent with _$FieldEditorEvent {
const factory FieldEditorEvent.insertRight() = _InsertRight;
const factory FieldEditorEvent.toggleFieldVisibility() =
_ToggleFieldVisiblity;
const factory FieldEditorEvent.deleteField() = _DeleteField;
const factory FieldEditorEvent.duplicateField() = _DuplicateField;
}
@freezed

View File

@ -1,6 +1,7 @@
import 'dart:typed_data';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
@ -9,21 +10,101 @@ import 'package:dartz/dartz.dart';
///
/// You could check out the rust-lib/flowy-database/event_map.rs for more information.
class FieldBackendService {
FieldBackendService({required this.viewId, required this.fieldId});
final String viewId;
final String fieldId;
FieldBackendService({required this.viewId, required this.fieldId});
static Future<Either<TypeOptionPB, FlowyError>> createField({
required String viewId,
FieldType fieldType = FieldType.RichText,
String? fieldName,
Uint8List? typeOptionData,
OrderObjectPositionPB? position,
}) {
final payload = CreateFieldPayloadPB(
viewId: viewId,
fieldType: fieldType,
fieldName: fieldName,
typeOptionData: typeOptionData,
fieldPosition: position,
);
Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
final payload = MoveFieldPayloadPB.create()
..viewId = viewId
..fieldId = fieldId
..fromIndex = fromIndex
..toIndex = toIndex;
return DatabaseEventCreateField(payload).send();
}
Future<Either<TypeOptionPB, FlowyError>> insertBefore({
FieldType fieldType = FieldType.RichText,
String? fieldName,
Uint8List? typeOptionData,
}) {
return createField(
viewId: viewId,
fieldType: fieldType,
fieldName: fieldName,
typeOptionData: typeOptionData,
position: OrderObjectPositionPB(
position: OrderObjectPositionTypePB.Before,
objectId: fieldId,
),
);
}
Future<Either<TypeOptionPB, FlowyError>> insertAfter({
FieldType fieldType = FieldType.RichText,
String? fieldName,
Uint8List? typeOptionData,
}) {
return createField(
viewId: viewId,
fieldType: fieldType,
fieldName: fieldName,
typeOptionData: typeOptionData,
position: OrderObjectPositionPB(
position: OrderObjectPositionTypePB.Before,
objectId: fieldId,
),
);
}
static Future<Either<Unit, FlowyError>> moveField({
required String viewId,
required String fromFieldId,
required String toFieldId,
}) {
final payload = MoveFieldPayloadPB(
viewId: viewId,
fromFieldId: fromFieldId,
toFieldId: toFieldId,
);
return DatabaseEventMoveField(payload).send();
}
static Future<Either<Unit, FlowyError>> deleteField({
required String viewId,
required String fieldId,
}) {
final payload = DeleteFieldPayloadPB(
viewId: viewId,
fieldId: fieldId,
);
return DatabaseEventDeleteField(payload).send();
}
static Future<Either<Unit, FlowyError>> duplicateField({
required String viewId,
required String fieldId,
}) {
final payload = DuplicateFieldPayloadPB(
viewId: viewId,
fieldId: fieldId,
);
return DatabaseEventDuplicateField(payload).send();
}
Future<Either<Unit, FlowyError>> updateField({
String? name,
bool? frozen,
@ -67,20 +148,12 @@ class FieldBackendService {
return DatabaseEventUpdateFieldType(payload).send();
}
Future<Either<Unit, FlowyError>> deleteField() {
final payload = DeleteFieldPayloadPB.create()
..viewId = viewId
..fieldId = fieldId;
return DatabaseEventDeleteField(payload).send();
Future<Either<Unit, FlowyError>> delete() {
return deleteField(viewId: viewId, fieldId: fieldId);
}
Future<Either<Unit, FlowyError>> duplicateField() {
final payload = DuplicateFieldPayloadPB.create()
..viewId = viewId
..fieldId = fieldId;
return DatabaseEventDuplicateField(payload).send();
Future<Either<Unit, FlowyError>> duplicate() {
return duplicateField(viewId: viewId, fieldId: fieldId);
}
Future<Either<TypeOptionPB, FlowyError>> getFieldTypeOptionData({

View File

@ -1,5 +1,3 @@
import 'dart:typed_data';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -24,34 +22,4 @@ class TypeOptionBackendService {
return DatabaseEventCreateSelectOption(payload).send();
}
static Future<Either<TypeOptionPB, FlowyError>> createFieldTypeOption({
required String viewId,
FieldType fieldType = FieldType.RichText,
String? fieldName,
Uint8List? typeOptionData,
CreateFieldPosition position = CreateFieldPosition.End,
String? targetFieldId,
}) {
final payload = CreateFieldPayloadPB.create()
..viewId = viewId
..fieldType = fieldType;
if (fieldName != null) {
payload.fieldName = fieldName;
}
if (typeOptionData != null) {
payload.typeOptionData = typeOptionData;
}
if (position == CreateFieldPosition.Before ||
position == CreateFieldPosition.After && targetFieldId != null) {
payload.targetFieldId = targetFieldId!;
}
payload.fieldPosition = position;
return DatabaseEventCreateField(payload).send();
}
}

View File

@ -1,14 +1,13 @@
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
class FieldSettingsBackendService {
final String viewId;
FieldSettingsBackendService({required this.viewId});
final String viewId;
Future<Either<FieldSettingsPB, FlowyError>> getFieldSettings(
String fieldId,
) {

View File

@ -1,7 +1,9 @@
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import '../field/field_info.dart';
typedef RowId = String;
@ -12,14 +14,53 @@ class RowBackendService {
required this.viewId,
});
Future<Either<RowMetaPB, FlowyError>> createRowAfterRow(RowId rowId) {
final payload = CreateRowPayloadPB.create()
..viewId = viewId
..startRowId = rowId;
static Future<Either<RowMetaPB, FlowyError>> createRow({
required String viewId,
String? groupId,
void Function(RowDataBuilder builder)? withCells,
OrderObjectPositionTypePB? position,
String? targetRowId,
}) {
final payload = CreateRowPayloadPB(
viewId: viewId,
groupId: groupId,
rowPosition: OrderObjectPositionPB(
position: position,
objectId: targetRowId,
),
);
Map<String, String>? cellDataByFieldId;
if (withCells != null) {
final rowBuilder = RowDataBuilder();
withCells(rowBuilder);
cellDataByFieldId = rowBuilder.build();
}
if (cellDataByFieldId != null) {
payload.data = RowDataPB(cellDataByFieldId: cellDataByFieldId);
}
return DatabaseEventCreateRow(payload).send();
}
Future<Either<RowMetaPB, FlowyError>> createRowBefore(RowId rowId) {
return createRow(
viewId: viewId,
position: OrderObjectPositionTypePB.Before,
targetRowId: rowId,
);
}
Future<Either<RowMetaPB, FlowyError>> createRowAfter(RowId rowId) {
return createRow(
viewId: viewId,
position: OrderObjectPositionTypePB.After,
targetRowId: rowId,
);
}
Future<Either<OptionalRowPB, FlowyError>> getRow(RowId rowId) {
final payload = RowIdPB.create()
..viewId = viewId
@ -73,16 +114,37 @@ class RowBackendService {
static Future<Either<Unit, FlowyError>> duplicateRow(
String viewId,
RowId rowId, [
String? groupId,
]) {
final payload = RowIdPB.create()
..viewId = viewId
..rowId = rowId;
if (groupId != null) {
payload.groupId = groupId;
}
RowId rowId,
) {
final payload = RowIdPB(
viewId: viewId,
rowId: rowId,
);
return DatabaseEventDuplicateRow(payload).send();
}
}
class RowDataBuilder {
final _cellDataByFieldId = <String, String>{};
void insertText(FieldInfo fieldInfo, String text) {
assert(fieldInfo.fieldType == FieldType.RichText);
_cellDataByFieldId[fieldInfo.field.id] = text;
}
void insertNumber(FieldInfo fieldInfo, int num) {
assert(fieldInfo.fieldType == FieldType.Number);
_cellDataByFieldId[fieldInfo.field.id] = num.toString();
}
void insertDate(FieldInfo fieldInfo, DateTime date) {
assert(fieldInfo.fieldType == FieldType.DateTime);
final timestamp = date.millisecondsSinceEpoch ~/ 1000;
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
}
Map<String, String> build() {
return _cellDataByFieldId;
}
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
@ -5,7 +7,6 @@ import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import '../field/field_service.dart';
@ -44,25 +45,21 @@ class DatabasePropertyBloc
didReceiveFieldUpdate: (fields) {
emit(state.copyWith(fieldContexts: fields));
},
moveField: (fieldId, fromIndex, toIndex) async {
moveField: (fromIndex, toIndex) async {
if (fromIndex < toIndex) {
toIndex--;
}
final fromId = state.fieldContexts[fromIndex].field.id;
final toId = state.fieldContexts[toIndex].field.id;
final fieldContexts = List<FieldInfo>.from(state.fieldContexts);
fieldContexts.insert(
toIndex,
fieldContexts.removeAt(fromIndex),
);
fieldContexts.insert(toIndex, fieldContexts.removeAt(fromIndex));
emit(state.copyWith(fieldContexts: fieldContexts));
final fieldBackendService = FieldBackendService(
final result = await FieldBackendService.moveField(
viewId: viewId,
fieldId: fieldId,
);
final result = await fieldBackendService.moveField(
fromIndex,
toIndex,
fromFieldId: fromId,
toFieldId: toId,
);
result.fold((l) => null, (r) => Log.error(r));
@ -101,11 +98,8 @@ class DatabasePropertyEvent with _$DatabasePropertyEvent {
const factory DatabasePropertyEvent.didReceiveFieldUpdate(
List<FieldInfo> fields,
) = _DidReceiveFieldUpdate;
const factory DatabasePropertyEvent.moveField({
required String fieldId,
required int fromIndex,
required int toIndex,
}) = _MoveField;
const factory DatabasePropertyEvent.moveField(int fromIndex, int toIndex) =
_MoveField;
}
@freezed

View File

@ -80,11 +80,16 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
_startListening();
await _openGrid(emit);
},
createBottomRow: (groupId) async {
final startRowId = groupControllers[groupId]?.lastRow()?.id;
final result = await databaseController.createRow(
createHeaderRow: (groupId) async {
final rowId = groupControllers[groupId]?.firstRow()?.id;
final position = rowId == null
? OrderObjectPositionTypePB.Start
: OrderObjectPositionTypePB.Before;
final result = await RowBackendService.createRow(
viewId: databaseController.viewId,
groupId: groupId,
startRowId: startRowId,
position: position,
targetRowId: rowId,
);
result.fold(
@ -94,10 +99,16 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
(err) => Log.error(err),
);
},
createHeaderRow: (String groupId) async {
final result = await databaseController.createRow(
createBottomRow: (groupId) async {
final rowId = groupControllers[groupId]?.lastRow()?.id;
final position = rowId == null
? OrderObjectPositionTypePB.End
: OrderObjectPositionTypePB.After;
final result = await RowBackendService.createRow(
viewId: databaseController.viewId,
groupId: groupId,
fromBeginning: true,
position: position,
targetRowId: rowId,
);
result.fold(

View File

@ -40,6 +40,11 @@ class GroupController {
}
}
RowMetaPB? firstRow() {
if (group.rows.isEmpty) return null;
return group.rows.first;
}
RowMetaPB? lastRow() {
if (group.rows.isEmpty) return null;
return group.rows.last;

View File

@ -269,7 +269,6 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
rowCache: rowCache,
cardData: groupData.group.groupId,
groupingFieldId: groupItem.fieldInfo.id,
groupId: groupData.group.groupId,
isEditing: isEditing,
cellBuilder: cellBuilder,
renderHook: renderHook,

View File

@ -146,19 +146,18 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
(settings) async {
final dateField = _getCalendarFieldInfo(settings.fieldId);
if (dateField != null) {
final newRow = await databaseController
.createRow(
withCells: (builder) => builder.insertDate(dateField, date),
)
.then(
(result) => result.fold(
(newRow) => newRow,
(err) {
Log.error(err);
return null;
},
),
);
final newRow = await RowBackendService.createRow(
viewId: viewId,
withCells: (builder) => builder.insertDate(dateField, date),
).then(
(result) => result.fold(
(newRow) => newRow,
(err) {
Log.error(err);
return null;
},
),
);
if (newRow != null) {
final event = await _loadEvent(newRow.id);

View File

@ -28,7 +28,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
await _openGrid(emit);
},
createRow: () async {
final result = await databaseController.createRow();
final result = await RowBackendService.createRow(viewId: viewId);
result.fold(
(createdRow) => emit(state.copyWith(createdRow: createdRow)),
(err) => Log.error(err),
@ -88,6 +88,8 @@ class GridBloc extends Bloc<GridEvent, GridState> {
);
}
String get viewId => databaseController.viewId;
RowCache getRowCache(RowId rowId) {
return databaseController.rowCache;
}

View File

@ -1,7 +1,6 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -49,8 +48,8 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
endEditingField: () {
emit(state.copyWith(editingFieldId: null, newFieldId: null));
},
moveField: (field, fromIndex, toIndex) async {
await _moveField(field, fromIndex, toIndex, emit);
moveField: (fromIndex, toIndex) async {
await _moveField(fromIndex, toIndex, emit);
},
);
},
@ -58,17 +57,22 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
}
Future<void> _moveField(
FieldPB field,
int fromIndex,
int toIndex,
Emitter<GridHeaderState> emit,
) async {
final fromId = state.fields[fromIndex].id;
final toId = state.fields[toIndex].id;
final fields = List<FieldInfo>.from(state.fields);
fields.insert(toIndex, fields.removeAt(fromIndex));
emit(state.copyWith(fields: fields));
final fieldService = FieldBackendService(viewId: viewId, fieldId: field.id);
final result = await fieldService.moveField(fromIndex, toIndex);
final result = await FieldBackendService.moveField(
viewId: viewId,
fromFieldId: fromId,
toFieldId: toId,
);
result.fold((l) {}, (err) => Log.error(err));
}
@ -92,7 +96,6 @@ class GridHeaderEvent with _$GridHeaderEvent {
_StartEditingNewField;
const factory GridHeaderEvent.endEditingField() = _EndEditingField;
const factory GridHeaderEvent.moveField(
FieldPB field,
int fromIndex,
int toIndex,
) = _MoveField;

View File

@ -36,7 +36,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
await _startListening();
},
createRow: () {
_rowBackendSvc.createRowAfterRow(rowId);
_rowBackendSvc.createRowAfter(rowId);
},
didReceiveCells: (cellByFieldId, reason) async {
cellByFieldId

View File

@ -30,12 +30,12 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
),
);
},
deleteField: (fieldId) {
final fieldService = FieldBackendService(
deleteField: (fieldId) async {
final result = await FieldBackendService.deleteField(
viewId: rowController.viewId,
fieldId: fieldId,
);
fieldService.deleteField();
result.fold((l) {}, (err) => Log.error(err));
},
toggleFieldVisibility: (fieldId) async {
final fieldInfo = state.allCells
@ -57,15 +57,8 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
(err) => Log.error(err),
);
},
reorderField:
(reorderedFieldId, targetFieldId, fromIndex, toIndex) async {
await _reorderField(
reorderedFieldId,
targetFieldId,
fromIndex,
toIndex,
emit,
);
reorderField: (fromIndex, toIndex) async {
await _reorderField(fromIndex, toIndex, emit);
},
toggleHiddenFieldVisibility: () {
final showHiddenFields = !state.showHiddenFields;
@ -127,28 +120,24 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
}
Future<void> _reorderField(
String reorderedFieldId,
String targetFieldId,
int fromIndex,
int toIndex,
Emitter<RowDetailState> emit,
) async {
if (fromIndex < toIndex) {
toIndex--;
}
final fromId = state.visibleCells[fromIndex].fieldId;
final toId = state.visibleCells[toIndex].fieldId;
final cells = List<DatabaseCellContext>.from(state.visibleCells);
cells.insert(toIndex, cells.removeAt(fromIndex));
emit(state.copyWith(visibleCells: cells));
final fromIndexInAllFields =
state.allCells.indexWhere((cell) => cell.fieldId == reorderedFieldId);
final toIndexInAllFields =
state.allCells.indexWhere((cell) => cell.fieldId == targetFieldId);
final fieldService = FieldBackendService(
final result = await FieldBackendService.moveField(
viewId: rowController.viewId,
fieldId: reorderedFieldId,
);
final result = await fieldService.moveField(
fromIndexInAllFields,
toIndexInAllFields,
fromFieldId: fromId,
toFieldId: toId,
);
result.fold((l) {}, (err) => Log.error(err));
}
@ -161,8 +150,6 @@ class RowDetailEvent with _$RowDetailEvent {
const factory RowDetailEvent.toggleFieldVisibility(String fieldId) =
_ToggleFieldVisibility;
const factory RowDetailEvent.reorderField(
String reorderFieldID,
String targetFieldID,
int fromIndex,
int toIndex,
) = _ReorderField;

View File

@ -263,18 +263,19 @@ enum FieldAction {
break;
case FieldAction.duplicate:
PopoverContainer.of(context).close();
context
.read<FieldEditorBloc>()
.add(const FieldEditorEvent.duplicateField());
FieldBackendService.duplicateField(
viewId: viewId,
fieldId: fieldInfo.id,
);
break;
case FieldAction.delete:
NavigatorAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {
FieldBackendService(
FieldBackendService.deleteField(
viewId: viewId,
fieldId: fieldInfo.id,
).deleteField();
);
},
).show(context);
PopoverContainer.of(context).close();

View File

@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart';
@ -15,7 +16,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:reorderables/reorderables.dart';
import '../../../../application/field/type_option/type_option_service.dart';
import '../../layout/sizes.dart';
import 'field_cell.dart';
@ -134,12 +134,14 @@ class _GridHeaderState extends State<_GridHeader> {
needsLongPressDraggable: PlatformExtension.isMobile,
footer: _CellTrailing(viewId: widget.viewId),
onReorder: (int oldIndex, int newIndex) {
_onReorder(
cells,
oldIndex,
context,
newIndex,
);
// to offset removing the first field from `state.fields`
if (PlatformExtension.isMobile) {
oldIndex++;
newIndex++;
}
context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.moveField(oldIndex, newIndex));
},
children: cells,
),
@ -148,22 +150,6 @@ class _GridHeaderState extends State<_GridHeader> {
);
}
void _onReorder(
List<Widget> cells,
int oldIndex,
BuildContext context,
int newIndex,
) {
if (cells.length > oldIndex) {
final field = PlatformExtension.isDesktop
? (cells[oldIndex] as GridFieldCell).fieldInfo.field
: (cells[oldIndex] as MobileFieldButton).fieldInfo.field;
context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.moveField(field, oldIndex, newIndex));
}
}
Widget _cellLeading(FieldInfo? fieldInfo) {
if (PlatformExtension.isDesktop) {
return SizedBox(width: GridSize.leadingHeaderPadding);
@ -245,7 +231,7 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
if (PlatformExtension.isMobile) {
showCreateFieldBottomSheet(context, widget.viewId);
} else {
final result = await TypeOptionBackendService.createFieldTypeOption(
final result = await FieldBackendService.createField(
viewId: widget.viewId,
);
result.fold(

View File

@ -1,113 +1,107 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/theme_extension.dart';
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_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import '../../layout/sizes.dart';
class RowActions extends StatelessWidget {
final String viewId;
final RowId rowId;
final String? groupId;
const RowActions({
class RowActionMenu extends StatelessWidget {
const RowActionMenu({
super.key,
required this.viewId,
required this.rowId,
this.actions = RowAction.values,
this.groupId,
super.key,
});
const RowActionMenu.board({
super.key,
required this.viewId,
required this.rowId,
required this.groupId,
}) : actions = const [RowAction.duplicate, RowAction.delete];
final String viewId;
final RowId rowId;
final List<RowAction> actions;
final String? groupId;
@override
Widget build(BuildContext context) {
final cells = _RowAction.values
.where((value) => value.enable())
.map((action) => _actionCell(context, action))
.toList();
final cells =
actions.map((action) => _actionCell(context, action)).toList();
return ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
itemCount: cells.length,
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return cells[index];
},
return SeparatedColumn(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
separatorBuilder: () => VSpace(GridSize.typeOptionSeparatorHeight),
children: cells,
);
}
Widget _actionCell(BuildContext context, _RowAction action) {
Widget _actionCell(BuildContext context, RowAction action) {
Widget icon = FlowySvg(action.icon);
if (action == RowAction.insertAbove) {
icon = RotatedBox(quarterTurns: 1, child: icon);
}
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover,
useIntrinsicWidth: true,
text: FlowyText.medium(
action.title(),
color: action.enable()
? AFThemeExtension.of(context).textColor
: Theme.of(context).disabledColor,
),
text: FlowyText.medium(action.text, overflow: TextOverflow.ellipsis),
onTap: () {
if (action.enable()) {
action.performAction(context, viewId, rowId);
}
action.performAction(context, viewId, rowId);
PopoverContainer.of(context).close();
},
leftIcon: FlowySvg(
action.icon(),
color: Theme.of(context).iconTheme.color,
),
leftIcon: icon,
),
);
}
}
enum _RowAction {
delete,
enum RowAction {
insertAbove,
insertBelow,
duplicate,
}
delete;
extension _RowActionExtension on _RowAction {
FlowySvgData icon() {
switch (this) {
case _RowAction.duplicate:
return FlowySvgs.copy_s;
case _RowAction.delete:
return FlowySvgs.delete_s;
}
FlowySvgData get icon {
return switch (this) {
insertAbove => FlowySvgs.arrow_s,
insertBelow => FlowySvgs.add_s,
duplicate => FlowySvgs.copy_s,
delete => FlowySvgs.delete_s,
};
}
String title() {
switch (this) {
case _RowAction.duplicate:
return LocaleKeys.grid_row_duplicate.tr();
case _RowAction.delete:
return LocaleKeys.grid_row_delete.tr();
}
}
bool enable() {
switch (this) {
case _RowAction.duplicate:
case _RowAction.delete:
return true;
}
String get text {
return switch (this) {
insertAbove => LocaleKeys.grid_row_insertRecordAbove.tr(),
insertBelow => LocaleKeys.grid_row_insertRecordBelow.tr(),
duplicate => LocaleKeys.grid_row_duplicate.tr(),
delete => LocaleKeys.grid_row_delete.tr(),
};
}
void performAction(BuildContext context, String viewId, String rowId) {
switch (this) {
case _RowAction.duplicate:
case insertAbove:
case insertBelow:
final position = this == insertAbove
? OrderObjectPositionTypePB.Before
: OrderObjectPositionTypePB.After;
RowBackendService.createRow(
viewId: viewId,
position: position,
targetRowId: rowId,
);
break;
case duplicate:
RowBackendService.duplicateRow(viewId, rowId);
break;
case _RowAction.delete:
case delete:
RowBackendService.deleteRow(viewId, rowId);
break;
}

View File

@ -113,25 +113,19 @@ class _RowLeading extends StatefulWidget {
}
class _RowLeadingState extends State<_RowLeading> {
late final PopoverController popoverController;
@override
void initState() {
super.initState();
popoverController = PopoverController();
}
final PopoverController popoverController = PopoverController();
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
controller: popoverController,
triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(140, 200)),
constraints: BoxConstraints.loose(const Size(176, 200)),
direction: PopoverDirection.rightWithCenterAligned,
margin: const EdgeInsets.all(6),
popupBuilder: (BuildContext popoverContext) {
margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 8),
popupBuilder: (_) {
final bloc = context.read<RowBloc>();
return RowActions(
return RowActionMenu(
viewId: bloc.viewId,
rowId: bloc.rowId,
);

View File

@ -138,11 +138,13 @@ class _RowCardState<T> extends State<RowCard<T>> {
triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(140, 200)),
direction: PopoverDirection.rightWithCenterAligned,
popupBuilder: (_) => RowActions(
viewId: _cardBloc.viewId,
rowId: _cardBloc.rowMeta.id,
groupId: widget.groupId,
),
popupBuilder: (_) {
return RowActionMenu.board(
viewId: _cardBloc.viewId,
rowId: _cardBloc.rowMeta.id,
groupId: widget.groupId,
);
},
child: RowCardContainer(
buildAccessoryWhen: () => state.isEditing == false,
accessories: [

View File

@ -25,7 +25,6 @@ class RowActionList extends StatelessWidget {
RowDetailPageDuplicateButton(
viewId: rowController.viewId,
rowId: rowController.rowId,
groupId: rowController.groupId,
),
const VSpace(4.0),
RowDetailPageDeleteButton(
@ -65,16 +64,15 @@ class RowDetailPageDeleteButton extends StatelessWidget {
}
class RowDetailPageDuplicateButton extends StatelessWidget {
final String viewId;
final String rowId;
final String? groupId;
const RowDetailPageDuplicateButton({
super.key,
required this.viewId,
required this.rowId,
this.groupId,
});
final String viewId;
final String rowId;
@override
Widget build(BuildContext context) {
return SizedBox(
@ -83,7 +81,7 @@ class RowDetailPageDuplicateButton extends StatelessWidget {
text: FlowyText.regular(LocaleKeys.grid_row_duplicate.tr()),
leftIcon: const FlowySvg(FlowySvgs.copy_s),
onTap: () {
RowBackendService.duplicateRow(viewId, rowId, groupId);
RowBackendService.duplicateRow(viewId, rowId);
FlowyOverlay.pop(context);
},
),

View File

@ -4,7 +4,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
@ -57,22 +57,10 @@ class RowPropertyList extends StatelessWidget {
return ReorderableListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
onReorder: (oldIndex, newIndex) {
// when reorderiing downwards, need to update index
if (oldIndex < newIndex) {
newIndex--;
}
final reorderedFieldId = children[oldIndex].cellContext.fieldId;
final targetFieldId = children[newIndex].cellContext.fieldId;
context.read<RowDetailBloc>().add(
RowDetailEvent.reorderField(
reorderedFieldId,
targetFieldId,
oldIndex,
newIndex,
),
);
onReorder: (from, to) {
context
.read<RowDetailBloc>()
.add(RowDetailEvent.reorderField(from, to));
},
buildDefaultDragHandles: false,
proxyDecorator: (child, index, animation) => Material(
@ -391,7 +379,7 @@ class _CreateRowFieldButtonState extends State<CreateRowFieldButton> {
),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
onTap: () async {
final result = await TypeOptionBackendService.createFieldTypeOption(
final result = await FieldBackendService.createField(
viewId: widget.viewId,
);
result.fold(

View File

@ -95,13 +95,9 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
buildDefaultDragHandles: false,
shrinkWrap: true,
onReorder: (from, to) {
context.read<DatabasePropertyBloc>().add(
DatabasePropertyEvent.moveField(
fieldId: cells[from].fieldInfo.id,
fromIndex: from,
toIndex: to,
),
);
context
.read<DatabasePropertyBloc>()
.add(DatabasePropertyEvent.moveField(from, to));
},
onReorderStart: (_) => _popoverMutex.close(),
padding: const EdgeInsets.symmetric(vertical: 6.0),

View File

@ -3,18 +3,16 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:dartz/dartz.dart';
import '../../util.dart';
@ -34,10 +32,6 @@ class GridTestContext {
return gridController.fieldController;
}
Future<Either<RowMetaPB, FlowyError>> createRow() async {
return gridController.createRow();
}
Future<CellController> makeCellController(
String fieldId,
int rowIndex,
@ -135,7 +129,7 @@ class GridTestContext {
Future<FieldEditorBloc> createFieldEditor({
required DatabaseController databaseController,
}) async {
final result = await TypeOptionBackendService.createFieldTypeOption(
final result = await FieldBackendService.createField(
viewId: databaseController.viewId,
);
await gridResponseFuture();
@ -211,7 +205,7 @@ class AppFlowyGridCellTest {
}
Future<void> createTestRow() async {
await context.createRow();
await RowBackendService.createRow(viewId: context.gridView.id);
}
Future<SelectOptionCellController> makeSelectOptionCellController(

View File

@ -883,7 +883,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -902,7 +902,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -932,7 +932,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"proc-macro2",
"quote",
@ -944,7 +944,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"collab",
@ -963,7 +963,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"bytes",
@ -977,7 +977,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"chrono",
@ -1019,7 +1019,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -1040,7 +1040,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -1066,7 +1066,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"collab",

View File

@ -67,14 +67,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9da
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }

View File

@ -157,10 +157,14 @@ export const EditRow = ({
const onDragEnd: OnDragEndResponder = (result) => {
if (!result.destination?.index) return;
const fields = cells
.filter((cell) => {
return fieldsStore[cell.cellIdentifier.fieldId]?.visible;
});
void controller.moveField({
fieldId: result.draggableId,
fromIndex: result.source.index,
toIndex: result.destination.index,
fromFieldId: result.draggableId,
toFieldId: fields[result.source.index].fieldId,
});
};
@ -186,9 +190,8 @@ export const EditRow = ({
return (
<>
<div
className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${
unveil ? 'opacity-100' : 'opacity-0'
}`}
className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${unveil ? 'opacity-100' : 'opacity-0'
}`}
onClick={() => onCloseClick()}
>
<div
@ -220,9 +223,8 @@ export const EditRow = ({
<div
{...provided.droppableProps}
ref={provided.innerRef}
className={`flex flex-1 flex-col gap-8 px-8 pb-8 ${
showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
}`}
className={`flex flex-1 flex-col gap-8 px-8 pb-8 ${showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
}`}
>
{cells
.filter((cell) => {

View File

@ -54,24 +54,6 @@ export const useDatabase = () => useSnapshot(useContext(DatabaseContext));
export const useContextDatabase = () => useContext(DatabaseContext);
export const useGetPrevRowId = () => {
const database = useContextDatabase();
return useCallback(
(id: string) => {
const rowMetas = database.rowMetas;
const index = rowMetas.findIndex((rowMeta) => rowMeta.id === id);
if (index === 0) {
return null;
}
return rowMetas[index - 1].id;
},
[database]
);
};
export const useSelectorCell = (rowId: string, fieldId: string) => {
const database = useContext(DatabaseContext);
const cells = useSnapshot(database.cells);

View File

@ -11,7 +11,7 @@ import {
FieldSettingsChangesetPB,
FieldVisibility,
DatabaseViewIdPB,
CreateFieldPosition,
OrderObjectPositionTypePB,
} from '@/services/backend';
import {
DatabaseEventDuplicateField,
@ -90,7 +90,7 @@ export async function createField({
}: {
viewId: string;
targetFieldId?: string;
fieldPosition?: CreateFieldPosition;
fieldPosition?: OrderObjectPositionTypePB;
fieldType?: FieldType;
data?: Uint8Array;
}): Promise<Field> {
@ -98,8 +98,10 @@ export async function createField({
view_id: viewId,
field_type: fieldType,
type_option_data: data,
target_field_id: targetFieldId,
field_position: fieldPosition,
field_position: {
position: fieldPosition,
object_id: targetFieldId,
},
});
const result = await DatabaseEventCreateField(payload);
@ -157,12 +159,11 @@ export async function updateFieldType(viewId: string, fieldId: string, fieldType
return result.unwrap();
}
export async function moveField(viewId: string, fieldId: string, fromIndex: number, toIndex: number): Promise<void> {
export async function moveField(viewId: string, fromFieldId: string, toFieldId: string): Promise<void> {
const payload = MoveFieldPayloadPB.fromObject({
view_id: viewId,
field_id: fieldId,
from_index: fromIndex,
to_index: toIndex,
from_field_id: fromFieldId,
to_field_id: toFieldId,
});
const result = await DatabaseEventMoveField(payload);

View File

@ -2,6 +2,7 @@ import {
CreateRowPayloadPB,
MoveGroupRowPayloadPB,
MoveRowPayloadPB,
OrderObjectPositionTypePB,
RowIdPB,
UpdateRowMetaChangesetPB,
} from '@/services/backend';
@ -17,13 +18,17 @@ import {
import { pbToRowMeta, RowMeta } from './row_types';
export async function createRow(viewId: string, params?: {
startRowId?: string;
position?: OrderObjectPositionTypePB;
rowId?: string;
groupId?: string;
data?: Record<string, string>;
}): Promise<RowMeta> {
const payload = CreateRowPayloadPB.fromObject({
view_id: viewId,
start_row_id: params?.startRowId,
row_position: {
position: params?.position,
object_id: params?.rowId,
},
group_id: params?.groupId,
data: params?.data ? { cell_data_by_field_id: params.data } : undefined,
});

View File

@ -24,8 +24,8 @@ function Properties({ onItemClick }: PropertiesProps) {
const handleOnDragEnd = async (result: DropResult) => {
const { destination, draggableId, source } = result;
const newIndex = destination?.index;
const oldIndex = source.index;
const newIndex = destination?.index;
if (oldIndex === newIndex) {
return;
@ -35,11 +35,13 @@ function Properties({ onItemClick }: PropertiesProps) {
return;
}
const newId = fields[newIndex ?? 0].id;
const newProperties = fieldService.reorderFields(fields as FieldType[], oldIndex, newIndex ?? 0);
setState(newProperties);
await fieldService.moveField(viewId, draggableId, oldIndex, newIndex);
await fieldService.moveField(viewId, draggableId, newId ?? "");
};
return (

View File

@ -44,23 +44,6 @@ function RecordProperties({ documentId, row }: Props) {
setState(properties);
}, [properties]);
// move the field in the database
const onMoveProperty = useCallback(
async (fieldId: string, prevId?: string) => {
const fromIndex = fields.findIndex((field) => field.id === fieldId);
const prevIndex = prevId ? fields.findIndex((field) => field.id === prevId) : 0;
const toIndex = prevIndex > fromIndex ? prevIndex : prevIndex + 1;
if (fromIndex === toIndex) {
return;
}
await fieldService.moveField(viewId, fieldId, fromIndex, toIndex);
},
[fields, viewId]
);
// move the field in the state
const handleOnDragEnd: OnDragEndResponder = useCallback(
async (result: DropResult) => {
@ -72,20 +55,16 @@ function RecordProperties({ documentId, row }: Props) {
return;
}
const newId = properties[newIndex ?? 0].id;
// reorder the properties synchronously to avoid flickering
const newProperties = fieldService.reorderFields(properties, oldIndex, newIndex ?? 0);
setState(newProperties);
// find the previous field id
const prevIndex = newProperties.findIndex((field) => field.id === draggableId) - 1;
const prevId = prevIndex >= 0 ? newProperties[prevIndex].id : undefined;
// update the order in the database.
// why not prevIndex? because the properties was filtered, we need to use the previous id to find the correct index
await onMoveProperty(draggableId, prevId);
await fieldService.moveField(viewId, draggableId, newId);
},
[onMoveProperty, properties]
[properties, viewId]
);
return (

View File

@ -8,7 +8,7 @@ import { ReactComponent as LeftSvg } from '$app/assets/left.svg';
import { ReactComponent as RightSvg } from '$app/assets/right.svg';
import { useViewId } from '$app/hooks';
import { fieldService } from '$app/components/database/application';
import { CreateFieldPosition, FieldVisibility } from '@/services/backend';
import { OrderObjectPositionTypePB, FieldVisibility } from '@/services/backend';
import { MenuItem } from '@mui/material';
import ConfirmDialog from '$app/components/_shared/app-dialog/ConfirmDialog';
import { useTranslation } from 'react-i18next';
@ -83,7 +83,7 @@ function PropertyActions({ fieldId, onMenuItemClick, isPrimary, actions = defaul
break;
case FieldAction.InsertLeft:
case FieldAction.InsertRight: {
const fieldPosition = action === FieldAction.InsertLeft ? CreateFieldPosition.Before : CreateFieldPosition.After;
const fieldPosition = action === FieldAction.InsertLeft ? OrderObjectPositionTypePB.Before : OrderObjectPositionTypePB.After;
const field = await fieldService.createField({
viewId,

View File

@ -58,7 +58,6 @@ export const GridCell = memo(({ row, column, columnIndex, style, onEditRecord, g
<GridNewRow
getContainerRef={getContainerRef}
index={columnIndex}
startRowId={row.data.startRowId}
groupId={row.data.groupId}
/>
</div>

View File

@ -4,7 +4,6 @@ import { throttle } from '$app/utils/tool';
import { useViewId } from '$app/hooks';
import { DragItem, DropPosition, DragType, useDraggable, useDroppable, ScrollDirection } from '../../_shared';
import { fieldService, Field } from '../../application';
import { useDatabase } from '../../Database.hooks';
import { Property } from '$app/components/database/components/property';
import GridResizer from '$app/components/database/grid/GridField/GridResizer';
import GridFieldMenu from '$app/components/database/grid/GridField/GridFieldMenu';
@ -23,7 +22,6 @@ export const GridField: FC<GridFieldProps> = memo(
({ getScrollElement, resizeColumnWidth, onOpenMenu, onCloseMenu, field, ...props }) => {
const menuOpened = useOpenMenu(field.id);
const viewId = useViewId();
const { fields } = useDatabase();
const [openTooltip, setOpenTooltip] = useState(false);
const [propertyMenuOpened, setPropertyMenuOpened] = useState(false);
const [dropPosition, setDropPosition] = useState<DropPosition>(DropPosition.Before);
@ -70,17 +68,14 @@ export const GridField: FC<GridFieldProps> = memo(
const onDrop = useCallback(
({ data }: DragItem) => {
const dragField = data.field as Field;
const fromIndex = fields.findIndex((item) => item.id === dragField.id);
const dropIndex = fields.findIndex((item) => item.id === field.id);
const toIndex = dropIndex + dropPosition + (fromIndex < dropIndex ? -1 : 0);
if (fromIndex === toIndex) {
if (dragField.id === field.id) {
return;
}
void fieldService.moveField(viewId, dragField.id, fromIndex, toIndex);
void fieldService.moveField(viewId, dragField.id, field.id);
},
[viewId, field, fields, dropPosition]
[viewId, field]
);
const { isOver, listeners: dropListeners } = useDroppable({
@ -92,9 +87,9 @@ export const GridField: FC<GridFieldProps> = memo(
const [menuAnchorPosition, setMenuAnchorPosition] = useState<
| {
top: number;
left: number;
}
top: number;
left: number;
}
| undefined
>(undefined);
@ -168,9 +163,8 @@ export const GridField: FC<GridFieldProps> = memo(
/>
{isOver && (
<div
className={`absolute bottom-0 top-0 z-10 w-0.5 bg-blue-500 ${
dropPosition === DropPosition.Before ? 'left-[-1px]' : 'left-full'
}`}
className={`absolute bottom-0 top-0 z-10 w-0.5 bg-blue-500 ${dropPosition === DropPosition.Before ? 'left-[-1px]' : 'left-full'
}`}
/>
)}
<GridResizer field={field} onWidthChange={resizeColumnWidth} />

View File

@ -6,22 +6,20 @@ import { ReactComponent as AddSvg } from '$app/assets/add.svg';
interface Props {
index: number;
startRowId?: string;
groupId?: string;
getContainerRef?: () => React.RefObject<HTMLDivElement>;
}
const CSS_HIGHLIGHT_PROPERTY = 'bg-content-blue-50';
function GridNewRow({ index, startRowId, groupId, getContainerRef }: Props) {
function GridNewRow({ index, groupId, getContainerRef }: Props) {
const viewId = useViewId();
const handleClick = useCallback(() => {
void rowService.createRow(viewId, {
startRowId,
groupId,
});
}, [viewId, groupId, startRowId]);
}, [viewId, groupId]);
const toggleCssProperty = useCallback(
(status: boolean) => {

View File

@ -1,6 +1,5 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useViewId } from '$app/hooks';
import { useGetPrevRowId } from '$app/components/database';
import { rowService } from '$app/components/database/application';
import { autoScrollOnEdge, ScrollDirection } from '$app/components/database/_shared/dnd/utils';
@ -71,7 +70,6 @@ export function useDraggableGridRow(
const dropRowIdRef = useRef<string | undefined>(undefined);
const previewRef = useRef<HTMLDivElement | undefined>();
const viewId = useViewId();
const getPrevRowId = useGetPrevRowId();
const onDragStart = useCallback(
(e: React.DragEvent<HTMLButtonElement>) => {
e.dataTransfer.effectAllowed = 'move';
@ -172,7 +170,7 @@ export function useDraggableGridRow(
container.addEventListener('dragover', onDragOver);
container.addEventListener('dragend', onDragEnd);
container.addEventListener('drop', onDrop);
}, [containerRef, getPrevRowId, isDragging, rowId, viewId]);
}, [containerRef, isDragging, rowId, viewId]);
return {
isDragging,

View File

@ -7,6 +7,7 @@ import { rowService } from '$app/components/database/application';
import { useViewId } from '$app/hooks';
import GridRowDragButton from '$app/components/database/grid/GridRowActions/GridRowDragButton';
import GridRowMenu from '$app/components/database/grid/GridRowActions/GridRowMenu';
import { OrderObjectPositionTypePB } from '@/services/backend';
function GridRowActions({
rowId,
@ -33,7 +34,8 @@ function GridRowActions({
const handleInsertRecordBelow = useCallback(() => {
void rowService.createRow(viewId, {
startRowId: rowId,
position: OrderObjectPositionTypePB.After,
rowId: rowId,
});
}, [viewId, rowId]);

View File

@ -4,11 +4,11 @@ import { ReactComponent as AddSvg } from '$app/assets/add.svg';
import { ReactComponent as DelSvg } from '$app/assets/delete.svg';
import { ReactComponent as CopySvg } from '$app/assets/copy.svg';
import Popover, { PopoverProps } from '@mui/material/Popover';
import { useGetPrevRowId } from '$app/components/database';
import { useViewId } from '$app/hooks';
import { useTranslation } from 'react-i18next';
import { rowService } from '$app/components/database/application';
import { Icon, MenuItem, MenuList } from '@mui/material';
import { OrderObjectPositionTypePB } from '@/services/backend';
interface Option {
label: string;
@ -22,25 +22,23 @@ interface Props extends PopoverProps {
}
function GridRowMenu({ rowId, ...props }: Props) {
const getPrevRowId = useGetPrevRowId();
const viewId = useViewId();
const { t } = useTranslation();
const handleInsertRecordBelow = useCallback(() => {
void rowService.createRow(viewId, {
startRowId: rowId,
position: OrderObjectPositionTypePB.After,
rowId: rowId,
});
}, [viewId, rowId]);
const handleInsertRecordAbove = useCallback(() => {
const prevRowId = getPrevRowId(rowId);
void rowService.createRow(viewId, {
startRowId: prevRowId || undefined,
position: OrderObjectPositionTypePB.Before,
rowId: rowId,
});
}, [getPrevRowId, rowId, viewId]);
}, [rowId, viewId]);
const handleDelRow = useCallback(() => {
void rowService.deleteRow(viewId, rowId);

View File

@ -31,7 +31,6 @@ export interface CellRenderRow {
export interface NewRenderRow {
type: RenderRowType.NewRow;
data: {
startRowId?: string;
groupId?: string;
};
}
@ -66,9 +65,7 @@ export const rowMetasToRenderRow = (rowMetas: RowMeta[]): RenderRow[] => {
})),
{
type: RenderRowType.NewRow,
data: {
startRowId: rowMetas.at(-1)?.id,
},
data: { },
},
{
type: RenderRowType.CalculateRow,

View File

@ -349,11 +349,12 @@ async function testMoveField() {
});
const fieldInfos = [...databaseController.fieldController.fieldInfos];
const field_id = fieldInfos[0].field.id;
const fromFieldId = fieldInfos[0].field.id;
const toFieldId = fieldInfos[1].field.id;
await databaseController.moveField({ fieldId: field_id, fromIndex: 0, toIndex: 1 });
await databaseController.moveField({ fromFieldId: fromFieldId, toFieldId: toFieldId });
await new Promise((resolve) => setTimeout(resolve, 200));
assert(databaseController.fieldController.fieldInfos[1].field.id === field_id);
assert(databaseController.fieldController.fieldInfos[1].field.id === fromFieldId);
}
async function testGetSingleSelectFieldData() {

View File

@ -29,6 +29,7 @@ import {
DatabaseViewIdPB,
CreateRowPayloadPB,
ViewIdPB,
OrderObjectPositionTypePB,
} from '@/services/backend';
import { FolderEventCloseView } from '@/services/backend/events/flowy-folder2';
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
@ -63,16 +64,19 @@ export class DatabaseBackendService {
/// 2.The row will be placed after the passed-in rowId
/// 3.The row will be moved to the group with groupId. Currently, grouping is
/// only support in kanban board.
createRow = async (params?: { rowId?: string; groupId?: string }) => {
const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId });
if (params?.rowId !== undefined) {
payload.start_row_id = params.rowId;
}
if (params?.groupId !== undefined) {
payload.group_id = params.groupId;
}
createRow = async (params?: {
groupId?: string;
position?: OrderObjectPositionTypePB;
rowId?: string;
}) => {
const payload = CreateRowPayloadPB.fromObject({
view_id: this.viewId,
row_position: {
position: params?.position,
object_id: params?.rowId,
},
group_id: params?.groupId,
});
return DatabaseEventCreateRow(payload);
};
@ -139,12 +143,11 @@ export class DatabaseBackendService {
return DatabaseEventGetGroup(payload);
};
moveField = (params: { fieldId: string; fromIndex: number; toIndex: number }) => {
moveField = (params: { fromFieldId: string; toFieldId: string }) => {
const payload = MoveFieldPayloadPB.fromObject({
view_id: this.viewId,
field_id: params.fieldId,
from_index: params.fromIndex,
to_index: params.toIndex,
from_field_id: params.fromFieldId,
to_field_id: params.toFieldId,
});
return DatabaseEventMoveField(payload);

View File

@ -1,7 +1,7 @@
import { DatabaseBackendService } from './database_bd_svc';
import { FieldController, FieldInfo } from './field/field_controller';
import { DatabaseViewCache } from './view/database_view_cache';
import { DatabasePB, GroupPB, FlowyError } from '@/services/backend';
import { DatabasePB, GroupPB, FlowyError, OrderObjectPositionTypePB } from '@/services/backend';
import { RowChangedReason, RowInfo } from './row/row_cache';
import { Err, Ok } from 'ts-results';
import { DatabaseGroupController } from './group/group_controller';
@ -108,7 +108,7 @@ export class DatabaseController {
};
createRowAfter = (rowId: string) => {
return this.backendService.createRow({ rowId });
return this.backendService.createRow({ rowId, position: OrderObjectPositionTypePB.After });
};
duplicateRow = async (rowId: string) => {
@ -136,7 +136,7 @@ export class DatabaseController {
return this.backendService.moveGroup(fromGroupId, toGroupId);
};
moveField = (params: { fieldId: string; fromIndex: number; toIndex: number }) => {
moveField = (params: { fromFieldId: string; toFieldId: string }) => {
return this.backendService.moveField(params);
};
@ -149,30 +149,28 @@ export class DatabaseController {
};
addFieldToLeft = async (fieldId: string) => {
const index = this.fieldController.fieldInfos.findIndex((fieldInfo) => fieldInfo.field.id === fieldId);
await this.backendService.createField();
const newFieldId = this.fieldController.fieldInfos[this.fieldController.fieldInfos.length - 1].field.id;
await this.moveField({
fieldId: newFieldId,
fromIndex: this.fieldController.fieldInfos.length - 1,
toIndex: index,
fromFieldId: newFieldId,
toFieldId: fieldId,
});
};
addFieldToRight = async (fieldId: string) => {
const index = this.fieldController.fieldInfos.findIndex((fieldInfo) => fieldInfo.field.id === fieldId);
await this.backendService.createField();
const newFieldId = this.fieldController.fieldInfos[this.fieldController.fieldInfos.length - 1].field.id;
const index = this.fieldController.fieldInfos.findIndex((fieldInfo) => fieldInfo.field.id === fieldId);
const toFieldId = this.fieldController.fieldInfos[index + 1].field.id;
await this.moveField({
fieldId: newFieldId,
fromIndex: this.fieldController.fieldInfos.length - 1,
toIndex: index + 1,
fromFieldId: newFieldId,
toFieldId: toFieldId,
});
};

View File

@ -733,7 +733,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -752,7 +752,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -782,7 +782,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"proc-macro2",
"quote",
@ -794,7 +794,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"collab",
@ -813,7 +813,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"bytes",
@ -827,7 +827,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"chrono",
@ -869,7 +869,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -890,7 +890,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"async-trait",
@ -916,7 +916,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=32218f1b6a9b09a9bbaa1835749e016246c092d4#32218f1b6a9b09a9bbaa1835749e016246c092d4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ac2ab049e367009b59c2e411c28580609a19e8b8#ac2ab049e367009b59c2e411c28580609a19e8b8"
dependencies = [
"anyhow",
"collab",
@ -1149,7 +1149,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.8.0",
"phf 0.11.2",
"smallvec",
]
@ -3653,7 +3653,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_macros 0.8.0",
"phf_shared 0.8.0",
"proc-macro-hack",
]
@ -3673,6 +3673,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2",
]
@ -3740,6 +3741,19 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.31",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
@ -3943,7 +3957,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [
"bytes",
"heck 0.4.1",
"itertools 0.10.5",
"itertools 0.11.0",
"log",
"multimap",
"once_cell",
@ -3964,7 +3978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32"
dependencies = [
"anyhow",
"itertools 0.10.5",
"itertools 0.11.0",
"proc-macro2",
"quote",
"syn 2.0.31",

View File

@ -109,11 +109,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9da
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }

View File

@ -188,14 +188,14 @@ impl EventIntegrationTest {
pub async fn create_row(
&self,
view_id: &str,
start_row_id: Option<String>,
row_position: OrderObjectPositionPB,
data: Option<RowDataPB>,
) -> RowMetaPB {
EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateRow)
.payload(CreateRowPayloadPB {
view_id: view_id.to_string(),
start_row_id,
row_position,
group_id: None,
data,
})

View File

@ -6,8 +6,8 @@ use event_integration::event_builder::EventBuilder;
use event_integration::EventIntegrationTest;
use flowy_database2::entities::{
CellChangesetPB, CellIdPB, ChecklistCellDataChangesetPB, DatabaseLayoutPB,
DatabaseSettingChangesetPB, DatabaseViewIdPB, DateChangesetPB, FieldType, SelectOptionCellDataPB,
UpdateRowMetaChangesetPB,
DatabaseSettingChangesetPB, DatabaseViewIdPB, DateChangesetPB, FieldType, OrderObjectPositionPB,
SelectOptionCellDataPB, UpdateRowMetaChangesetPB,
};
use lib_infra::util::timestamp;
@ -202,7 +202,9 @@ async fn create_row_event_test() {
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
.await;
let _ = test.create_row(&grid_view.id, None, None).await;
let _ = test
.create_row(&grid_view.id, OrderObjectPositionPB::default(), None)
.await;
let database = test.get_database(&grid_view.id).await;
assert_eq!(database.rows.len(), 4);
}
@ -755,7 +757,9 @@ async fn create_calendar_event_test() {
.unwrap();
// create a new row
let row = test.create_row(&calendar_view.id, None, None).await;
let row = test
.create_row(&calendar_view.id, OrderObjectPositionPB::default(), None)
.await;
// Insert data into the date cell of the first row.
let error = test

View File

@ -84,21 +84,17 @@ pub struct MoveFieldPayloadPB {
pub view_id: String,
#[pb(index = 2)]
pub field_id: String,
pub from_field_id: String,
#[pb(index = 3)]
pub from_index: i32,
#[pb(index = 4)]
pub to_index: i32,
pub to_field_id: String,
}
#[derive(Clone)]
pub struct MoveFieldParams {
pub view_id: String,
pub field_id: String,
pub from_index: i32,
pub to_index: i32,
pub from_field_id: String,
pub to_field_id: String,
}
impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
@ -106,12 +102,13 @@ impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
fn try_into(self) -> Result<MoveFieldParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidParams)?;
let from_field_id =
NotEmptyStr::parse(self.from_field_id).map_err(|_| ErrorCode::InvalidParams)?;
let to_field_id = NotEmptyStr::parse(self.to_field_id).map_err(|_| ErrorCode::InvalidParams)?;
Ok(MoveFieldParams {
view_id: view_id.0,
field_id: item_id.0,
from_index: self.from_index,
to_index: self.to_index,
from_field_id: from_field_id.0,
to_field_id: to_field_id.0,
})
}
}

View File

@ -12,6 +12,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use crate::entities::parser::NotEmptyStr;
use crate::entities::position_entities::OrderObjectPositionPB;
use crate::impl_into_field_type;
/// [FieldPB] defines a Field's attributes. Such as the name, field_type, and width. etc.
@ -164,20 +165,7 @@ pub struct CreateFieldPayloadPB {
pub type_option_data: Option<Vec<u8>>,
#[pb(index = 5)]
pub field_position: CreateFieldPosition,
#[pb(index = 6, one_of)]
pub target_field_id: Option<String>,
}
#[derive(Debug, Default, ProtoBuf_Enum)]
#[repr(u8)]
pub enum CreateFieldPosition {
#[default]
End = 0,
Start = 1,
Before = 2,
After = 3,
pub field_position: OrderObjectPositionPB,
}
#[derive(Clone)]
@ -204,34 +192,7 @@ impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
None => None,
};
let position = match &self.field_position {
CreateFieldPosition::Start => {
if self.target_field_id.is_some() {
return Err(ErrorCode::InvalidParams);
}
OrderObjectPosition::Start
},
CreateFieldPosition::End => {
if self.target_field_id.is_some() {
return Err(ErrorCode::InvalidParams);
}
OrderObjectPosition::End
},
CreateFieldPosition::Before => {
let field_id = self.target_field_id.ok_or(ErrorCode::InvalidParams)?;
let field_id = NotEmptyStr::parse(field_id)
.map_err(|_| ErrorCode::InvalidParams)?
.0;
OrderObjectPosition::Before(field_id)
},
CreateFieldPosition::After => {
let field_id = self.target_field_id.ok_or(ErrorCode::InvalidParams)?;
let field_id = NotEmptyStr::parse(field_id)
.map_err(|_| ErrorCode::InvalidParams)?
.0;
OrderObjectPosition::After(field_id)
},
};
let position = self.field_position.try_into()?;
Ok(CreateFieldParams {
view_id: view_id.0,

View File

@ -7,15 +7,16 @@ mod field_settings_entities;
pub mod filter_entities;
mod group_entities;
pub mod parser;
mod position_entities;
mod row_entities;
pub mod setting_entities;
mod share_entities;
mod sort_entities;
mod type_option_entities;
mod view_entities;
#[macro_use]
mod macros;
mod share_entities;
mod type_option_entities;
pub use board_entities::*;
pub use calendar_entities::*;
@ -25,6 +26,7 @@ pub use field_entities::*;
pub use field_settings_entities::*;
pub use filter_entities::*;
pub use group_entities::*;
pub use position_entities::*;
pub use row_entities::*;
pub use setting_entities::*;
pub use share_entities::*;

View File

@ -0,0 +1,89 @@
use collab_database::views::OrderObjectPosition;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode;
use crate::entities::parser::NotEmptyStr;
#[derive(Debug, Default, ProtoBuf)]
pub struct OrderObjectPositionPB {
#[pb(index = 1)]
pub position: OrderObjectPositionTypePB,
#[pb(index = 2, one_of)]
pub object_id: Option<String>,
}
impl OrderObjectPositionPB {
pub fn start() -> Self {
Self {
position: OrderObjectPositionTypePB::Start,
object_id: None,
}
}
pub fn end() -> Self {
Self {
position: OrderObjectPositionTypePB::End,
object_id: None,
}
}
pub fn before(object_id: String) -> Self {
Self {
position: OrderObjectPositionTypePB::Before,
object_id: Some(object_id),
}
}
pub fn after(object_id: String) -> Self {
Self {
position: OrderObjectPositionTypePB::After,
object_id: Some(object_id),
}
}
}
#[derive(Debug, Default, ProtoBuf_Enum)]
#[repr(u8)]
pub enum OrderObjectPositionTypePB {
#[default]
End = 0,
Start = 1,
Before = 2,
After = 3,
}
impl TryFrom<OrderObjectPositionPB> for OrderObjectPosition {
type Error = ErrorCode;
fn try_from(value: OrderObjectPositionPB) -> Result<Self, Self::Error> {
match value.position {
OrderObjectPositionTypePB::Start => {
if value.object_id.is_some() {
return Err(ErrorCode::InvalidParams);
}
Ok(OrderObjectPosition::Start)
},
OrderObjectPositionTypePB::End => {
if value.object_id.is_some() {
return Err(ErrorCode::InvalidParams);
}
Ok(OrderObjectPosition::End)
},
OrderObjectPositionTypePB::Before => {
let field_id = value.object_id.ok_or(ErrorCode::InvalidParams)?;
let field_id = NotEmptyStr::parse(field_id)
.map_err(|_| ErrorCode::InvalidParams)?
.0;
Ok(OrderObjectPosition::Before(field_id))
},
OrderObjectPositionTypePB::After => {
let field_id = value.object_id.ok_or(ErrorCode::InvalidParams)?;
let field_id = NotEmptyStr::parse(field_id)
.map_err(|_| ErrorCode::InvalidParams)?
.0;
Ok(OrderObjectPosition::After(field_id))
},
}
}
}

View File

@ -1,12 +1,13 @@
use std::collections::HashMap;
use collab_database::rows::{Row, RowDetail, RowId};
use collab_database::views::RowOrder;
use collab_database::views::{OrderObjectPosition, RowOrder};
use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode;
use crate::entities::parser::NotEmptyStr;
use crate::entities::position_entities::OrderObjectPositionPB;
use crate::services::database::{InsertedRow, UpdatedRow};
/// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row.
@ -339,8 +340,8 @@ pub struct CreateRowPayloadPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2, one_of)]
pub start_row_id: Option<String>,
#[pb(index = 2)]
pub row_position: OrderObjectPositionPB,
#[pb(index = 3, one_of)]
pub group_id: Option<String>,
@ -358,7 +359,7 @@ pub struct RowDataPB {
#[derive(Default)]
pub struct CreateRowParams {
pub view_id: String,
pub start_row_id: Option<RowId>,
pub row_position: OrderObjectPosition,
pub group_id: Option<String>,
pub cell_data_by_field_id: Option<HashMap<String, String>>,
}
@ -368,10 +369,10 @@ impl TryInto<CreateRowParams> for CreateRowPayloadPB {
fn try_into(self) -> Result<CreateRowParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
let start_row_id = self.start_row_id.map(RowId::from);
let position = self.row_position.try_into()?;
Ok(CreateRowParams {
view_id: view_id.0,
start_row_id,
row_position: position,
group_id: self.group_id,
cell_data_by_field_id: self.data.map(|data| data.cell_data_by_field_id),
})

View File

@ -2,7 +2,6 @@ use std::sync::{Arc, Weak};
use collab_database::database::gen_row_id;
use collab_database::rows::RowId;
use collab_database::views::OrderObjectPosition;
use tokio::sync::oneshot;
use flowy_error::{FlowyError, FlowyResult};
@ -345,14 +344,7 @@ pub(crate) async fn move_field_handler(
let manager = upgrade_manager(manager)?;
let params: MoveFieldParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor
.move_field(
&params.view_id,
&params.field_id,
params.from_index,
params.to_index,
)
.await?;
database_editor.move_field(params).await?;
Ok(())
}
@ -416,7 +408,7 @@ pub(crate) async fn duplicate_row_handler(
let params: RowIdParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor
.duplicate_row(&params.view_id, params.group_id, &params.row_id)
.duplicate_row(&params.view_id, &params.row_id)
.await;
Ok(())
}
@ -431,7 +423,7 @@ pub(crate) async fn move_row_handler(
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor
.move_row(&params.view_id, params.from_row_id, params.to_row_id)
.await;
.await?;
Ok(())
}
@ -448,16 +440,12 @@ pub(crate) async fn create_row_handler(
CellBuilder::with_cells(params.cell_data_by_field_id.unwrap_or_default(), &fields).build();
let view_id = params.view_id;
let group_id = params.group_id;
let position = match params.start_row_id {
Some(row_id) => OrderObjectPosition::After(row_id.into()),
None => OrderObjectPosition::Start,
};
let params = collab_database::rows::CreateRowParams {
id: gen_row_id(),
cells,
height: 60,
visibility: true,
row_position: position,
row_position: params.row_position,
timestamp: timestamp(),
};
match database_editor

View File

@ -168,8 +168,9 @@ pub enum DatabaseEvent {
#[event(input = "DuplicateFieldPayloadPB")]
DuplicateField = 21,
/// [MoveItem] event is used to move an item. For the moment, Item has two types defined in
/// [MoveItemTypePB].
/// [MoveFieldPB] event is used to reorder a field in a view. The
/// [MoveFieldPayloadPB] contains the `field_id` of the moved field and its
/// new position.
#[event(input = "MoveFieldPayloadPB")]
MoveField = 22,

View File

@ -402,37 +402,47 @@ impl DatabaseEditor {
}
// consider returning a result. But most of the time, it should be fine to just ignore the error.
pub async fn duplicate_row(&self, view_id: &str, group_id: Option<String>, row_id: &RowId) {
pub async fn duplicate_row(&self, view_id: &str, row_id: &RowId) {
let params = self.database.lock().duplicate_row(row_id);
match params {
None => {
warn!("Failed to duplicate row: {}", row_id);
},
None => warn!("Failed to duplicate row: {}", row_id),
Some(params) => {
let _ = self.create_row(view_id, group_id, params).await;
let _ = self.create_row(view_id, None, params).await;
},
}
}
pub async fn move_row(&self, view_id: &str, from: RowId, to: RowId) {
pub async fn move_row(
&self,
view_id: &str,
from_row_id: RowId,
to_row_id: RowId,
) -> FlowyResult<()> {
let database = self.database.lock();
if let (Some(row_detail), Some(from_index), Some(to_index)) = (
database.get_row_detail(&from),
database.index_of_row(view_id, &from),
database.index_of_row(view_id, &to),
) {
database.views.update_database_view(view_id, |view| {
view.move_row_order(from_index as u32, to_index as u32);
});
drop(database);
let delete_row_id = from.into_inner();
let insert_row = InsertedRowPB::new(RowMetaPB::from(row_detail)).with_index(to_index as i32);
let row_detail = database.get_row_detail(&from_row_id).ok_or_else(|| {
let msg = format!("Cannot find row {}", from_row_id);
FlowyError::internal().with_context(msg)
})?;
database.views.update_database_view(view_id, |view| {
view.move_row_order(&from_row_id, &to_row_id);
});
let new_index = database.index_of_row(view_id, &from_row_id);
drop(database);
if let Some(index) = new_index {
let delete_row_id = from_row_id.into_inner();
let insert_row = InsertedRowPB::new(RowMetaPB::from(row_detail)).with_index(index as i32);
let changes = RowsChangePB::from_move(vec![delete_row_id], vec![insert_row]);
send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
.payload(changes)
.send();
}
Ok(())
}
pub async fn create_row(
@ -504,28 +514,34 @@ impl DatabaseEditor {
)
}
pub async fn move_field(
&self,
view_id: &str,
field_id: &str,
from: i32,
to: i32,
) -> FlowyResult<()> {
let (database_id, field) = {
pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
let (field, new_index) = {
let database = self.database.lock();
database.views.update_database_view(view_id, |view_update| {
view_update.move_field_order(from as u32, to as u32);
});
let field = database.fields.get_field(field_id);
let database_id = database.get_database_id();
(database_id, field)
let field = database
.fields
.get_field(&params.from_field_id)
.ok_or_else(|| {
let msg = format!("Field with id: {} not found", &params.from_field_id);
FlowyError::internal().with_context(msg)
})?;
database
.views
.update_database_view(&params.view_id, |view_update| {
view_update.move_field_order(&params.from_field_id, &params.to_field_id);
});
let new_index = database.index_of_field(&params.view_id, &params.from_field_id);
(field, new_index)
};
if let Some(field) = field {
let delete_field = FieldIdPB::from(field_id);
let insert_field = IndexFieldPB::from_field(field, to as usize);
if let Some(index) = new_index {
let delete_field = FieldIdPB::from(params.from_field_id);
let insert_field = IndexFieldPB::from_field(field, index);
let notified_changeset = DatabaseFieldChangesetPB {
view_id: database_id,
view_id: params.view_id,
inserted_fields: vec![insert_field],
deleted_fields: vec![delete_field],
updated_fields: vec![],
@ -533,6 +549,7 @@ impl DatabaseEditor {
self.notify_did_update_database(notified_changeset).await?;
}
Ok(())
}
@ -955,7 +972,7 @@ impl DatabaseEditor {
.map(|row_detail| row_detail.row.id.clone())
};
if let Some(row_id) = to_row.clone() {
self.move_row(view_id, from_row.clone(), row_id).await;
self.move_row(view_id, from_row.clone(), row_id).await?;
}
if from_group == to_group {