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
59 changed files with 700 additions and 702 deletions

View File

@ -173,7 +173,6 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
rowCache: rowCache, rowCache: rowCache,
cardData: groupData.group.groupId, cardData: groupData.group.groupId,
groupingFieldId: groupItem.fieldInfo.id, groupingFieldId: groupItem.fieldInfo.id,
groupId: groupData.group.groupId,
isEditing: isEditing, isEditing: isEditing,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
renderHook: renderHook, 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/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/number_format_bloc.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/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/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy/util/field_type_extension.dart';
@ -56,7 +55,7 @@ class FieldOptionValues {
Future<void> create({ Future<void> create({
required String viewId, required String viewId,
}) async { }) async {
await TypeOptionBackendService.createFieldTypeOption( await FieldBackendService.createField(
viewId: viewId, viewId: viewId,
fieldType: type, fieldType: type,
fieldName: name, 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/board_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_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/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.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.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 'dart:async';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'database_view_service.dart'; import 'database_view_service.dart';
import 'defines.dart'; import 'defines.dart';
import 'field/field_info.dart';
import 'layout/layout_service.dart'; import 'layout/layout_service.dart';
import 'layout/layout_setting_listener.dart'; import 'layout/layout_setting_listener.dart';
import 'row/row_cache.dart'; import 'row/row_cache.dart';
import 'group/group_listener.dart'; import 'group/group_listener.dart';
import 'row/row_service.dart';
typedef OnGroupConfigurationChanged = void Function(List<GroupSettingPB>); typedef OnGroupConfigurationChanged = void Function(List<GroupSettingPB>);
typedef OnGroupByField = void Function(List<GroupPB>); 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({ Future<Either<Unit, FlowyError>> moveGroupRow({
required RowMetaPB fromRow, required RowMetaPB fromRow,
required String fromGroupId, 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-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.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/group.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
class DatabaseViewBackendService { class DatabaseViewBackendService {
final String viewId; final String viewId;
@ -31,29 +30,6 @@ class DatabaseViewBackendService {
return DatabaseEventGetDatabase(payload).send(); 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({ Future<Either<Unit, FlowyError>> moveGroupRow({
required RowId fromRowId, required RowId fromRowId,
required String fromGroupId, 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/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/plugins/database_view/application/field_settings/field_settings_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
@ -38,26 +37,30 @@ class FieldServices {
} }
Future<void> delete() async { Future<void> delete() async {
await fieldBackendService.deleteField(); await fieldBackendService.delete();
} }
Future<void> duplicate() async { Future<void> duplicate() async {
await fieldBackendService.duplicateField(); await fieldBackendService.duplicate();
} }
Future<void> insertLeft() async { Future<void> insertLeft() async {
await TypeOptionBackendService.createFieldTypeOption( await FieldBackendService.createField(
viewId: viewId, viewId: viewId,
position: CreateFieldPosition.Before, position: OrderObjectPositionPB(
targetFieldId: fieldId, position: OrderObjectPositionTypePB.Before,
objectId: fieldId,
),
); );
} }
Future<void> insertRight() async { Future<void> insertRight() async {
await TypeOptionBackendService.createFieldTypeOption( await FieldBackendService.createField(
viewId: viewId, viewId: viewId,
position: CreateFieldPosition.After, position: OrderObjectPositionPB(
targetFieldId: fieldId, 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/plugins/database_view/application/field_settings/field_settings_service.dart';
import 'package:appflowy_backend/log.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_entities.pb.dart';
@ -77,22 +76,14 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
_logIfError(result); _logIfError(result);
}, },
insertLeft: () async { insertLeft: () async {
final result = await TypeOptionBackendService.createFieldTypeOption( final result = await fieldService.insertBefore();
viewId: viewId,
position: CreateFieldPosition.Before,
targetFieldId: field.id,
);
result.fold( result.fold(
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id), (typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
(err) => Log.error("Failed creating field $err"), (err) => Log.error("Failed creating field $err"),
); );
}, },
insertRight: () async { insertRight: () async {
final result = await TypeOptionBackendService.createFieldTypeOption( final result = await fieldService.insertAfter();
viewId: viewId,
position: CreateFieldPosition.After,
targetFieldId: field.id,
);
result.fold( result.fold(
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id), (typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
(err) => Log.error("Failed creating field $err"), (err) => Log.error("Failed creating field $err"),
@ -111,14 +102,6 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
); );
_logIfError(result); _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.insertRight() = _InsertRight;
const factory FieldEditorEvent.toggleFieldVisibility() = const factory FieldEditorEvent.toggleFieldVisibility() =
_ToggleFieldVisiblity; _ToggleFieldVisiblity;
const factory FieldEditorEvent.deleteField() = _DeleteField;
const factory FieldEditorEvent.duplicateField() = _DuplicateField;
} }
@freezed @freezed

View File

@ -1,6 +1,7 @@
import 'dart:typed_data';
import 'package:appflowy_backend/dispatch/dispatch.dart'; 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/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.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. /// You could check out the rust-lib/flowy-database/event_map.rs for more information.
class FieldBackendService { class FieldBackendService {
FieldBackendService({required this.viewId, required this.fieldId});
final String viewId; final String viewId;
final String fieldId; 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) { return DatabaseEventCreateField(payload).send();
final payload = MoveFieldPayloadPB.create() }
..viewId = viewId
..fieldId = fieldId Future<Either<TypeOptionPB, FlowyError>> insertBefore({
..fromIndex = fromIndex FieldType fieldType = FieldType.RichText,
..toIndex = toIndex; 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(); 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({ Future<Either<Unit, FlowyError>> updateField({
String? name, String? name,
bool? frozen, bool? frozen,
@ -67,20 +148,12 @@ class FieldBackendService {
return DatabaseEventUpdateFieldType(payload).send(); return DatabaseEventUpdateFieldType(payload).send();
} }
Future<Either<Unit, FlowyError>> deleteField() { Future<Either<Unit, FlowyError>> delete() {
final payload = DeleteFieldPayloadPB.create() return deleteField(viewId: viewId, fieldId: fieldId);
..viewId = viewId
..fieldId = fieldId;
return DatabaseEventDeleteField(payload).send();
} }
Future<Either<Unit, FlowyError>> duplicateField() { Future<Either<Unit, FlowyError>> duplicate() {
final payload = DuplicateFieldPayloadPB.create() return duplicateField(viewId: viewId, fieldId: fieldId);
..viewId = viewId
..fieldId = fieldId;
return DatabaseEventDuplicateField(payload).send();
} }
Future<Either<TypeOptionPB, FlowyError>> getFieldTypeOptionData({ 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/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -24,34 +22,4 @@ class TypeOptionBackendService {
return DatabaseEventCreateSelectOption(payload).send(); 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/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.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-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
class FieldSettingsBackendService { class FieldSettingsBackendService {
final String viewId;
FieldSettingsBackendService({required this.viewId}); FieldSettingsBackendService({required this.viewId});
final String viewId;
Future<Either<FieldSettingsPB, FlowyError>> getFieldSettings( Future<Either<FieldSettingsPB, FlowyError>> getFieldSettings(
String fieldId, String fieldId,
) { ) {

View File

@ -1,7 +1,9 @@
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import '../field/field_info.dart';
typedef RowId = String; typedef RowId = String;
@ -12,14 +14,53 @@ class RowBackendService {
required this.viewId, required this.viewId,
}); });
Future<Either<RowMetaPB, FlowyError>> createRowAfterRow(RowId rowId) { static Future<Either<RowMetaPB, FlowyError>> createRow({
final payload = CreateRowPayloadPB.create() required String viewId,
..viewId = viewId String? groupId,
..startRowId = rowId; 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(); 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) { Future<Either<OptionalRowPB, FlowyError>> getRow(RowId rowId) {
final payload = RowIdPB.create() final payload = RowIdPB.create()
..viewId = viewId ..viewId = viewId
@ -73,16 +114,37 @@ class RowBackendService {
static Future<Either<Unit, FlowyError>> duplicateRow( static Future<Either<Unit, FlowyError>> duplicateRow(
String viewId, String viewId,
RowId rowId, [ RowId rowId,
String? groupId, ) {
]) { final payload = RowIdPB(
final payload = RowIdPB.create() viewId: viewId,
..viewId = viewId rowId: rowId,
..rowId = rowId; );
if (groupId != null) {
payload.groupId = groupId;
}
return DatabaseEventDuplicateRow(payload).send(); 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_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.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'; 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:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import '../field/field_service.dart'; import '../field/field_service.dart';
@ -44,25 +45,21 @@ class DatabasePropertyBloc
didReceiveFieldUpdate: (fields) { didReceiveFieldUpdate: (fields) {
emit(state.copyWith(fieldContexts: fields)); emit(state.copyWith(fieldContexts: fields));
}, },
moveField: (fieldId, fromIndex, toIndex) async { moveField: (fromIndex, toIndex) async {
if (fromIndex < toIndex) { if (fromIndex < toIndex) {
toIndex--; toIndex--;
} }
final fromId = state.fieldContexts[fromIndex].field.id;
final toId = state.fieldContexts[toIndex].field.id;
final fieldContexts = List<FieldInfo>.from(state.fieldContexts); final fieldContexts = List<FieldInfo>.from(state.fieldContexts);
fieldContexts.insert( fieldContexts.insert(toIndex, fieldContexts.removeAt(fromIndex));
toIndex,
fieldContexts.removeAt(fromIndex),
);
emit(state.copyWith(fieldContexts: fieldContexts)); emit(state.copyWith(fieldContexts: fieldContexts));
final fieldBackendService = FieldBackendService( final result = await FieldBackendService.moveField(
viewId: viewId, viewId: viewId,
fieldId: fieldId, fromFieldId: fromId,
); toFieldId: toId,
final result = await fieldBackendService.moveField(
fromIndex,
toIndex,
); );
result.fold((l) => null, (r) => Log.error(r)); result.fold((l) => null, (r) => Log.error(r));
@ -101,11 +98,8 @@ class DatabasePropertyEvent with _$DatabasePropertyEvent {
const factory DatabasePropertyEvent.didReceiveFieldUpdate( const factory DatabasePropertyEvent.didReceiveFieldUpdate(
List<FieldInfo> fields, List<FieldInfo> fields,
) = _DidReceiveFieldUpdate; ) = _DidReceiveFieldUpdate;
const factory DatabasePropertyEvent.moveField({ const factory DatabasePropertyEvent.moveField(int fromIndex, int toIndex) =
required String fieldId, _MoveField;
required int fromIndex,
required int toIndex,
}) = _MoveField;
} }
@freezed @freezed

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
await _openGrid(emit); await _openGrid(emit);
}, },
createRow: () async { createRow: () async {
final result = await databaseController.createRow(); final result = await RowBackendService.createRow(viewId: viewId);
result.fold( result.fold(
(createdRow) => emit(state.copyWith(createdRow: createdRow)), (createdRow) => emit(state.copyWith(createdRow: createdRow)),
(err) => Log.error(err), (err) => Log.error(err),
@ -88,6 +88,8 @@ class GridBloc extends Bloc<GridEvent, GridState> {
); );
} }
String get viewId => databaseController.viewId;
RowCache getRowCache(RowId rowId) { RowCache getRowCache(RowId rowId) {
return databaseController.rowCache; 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_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy_backend/log.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:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -49,8 +48,8 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
endEditingField: () { endEditingField: () {
emit(state.copyWith(editingFieldId: null, newFieldId: null)); emit(state.copyWith(editingFieldId: null, newFieldId: null));
}, },
moveField: (field, fromIndex, toIndex) async { moveField: (fromIndex, toIndex) async {
await _moveField(field, fromIndex, toIndex, emit); await _moveField(fromIndex, toIndex, emit);
}, },
); );
}, },
@ -58,17 +57,22 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
} }
Future<void> _moveField( Future<void> _moveField(
FieldPB field,
int fromIndex, int fromIndex,
int toIndex, int toIndex,
Emitter<GridHeaderState> emit, Emitter<GridHeaderState> emit,
) async { ) async {
final fromId = state.fields[fromIndex].id;
final toId = state.fields[toIndex].id;
final fields = List<FieldInfo>.from(state.fields); final fields = List<FieldInfo>.from(state.fields);
fields.insert(toIndex, fields.removeAt(fromIndex)); fields.insert(toIndex, fields.removeAt(fromIndex));
emit(state.copyWith(fields: fields)); emit(state.copyWith(fields: fields));
final fieldService = FieldBackendService(viewId: viewId, fieldId: field.id); final result = await FieldBackendService.moveField(
final result = await fieldService.moveField(fromIndex, toIndex); viewId: viewId,
fromFieldId: fromId,
toFieldId: toId,
);
result.fold((l) {}, (err) => Log.error(err)); result.fold((l) {}, (err) => Log.error(err));
} }
@ -92,7 +96,6 @@ class GridHeaderEvent with _$GridHeaderEvent {
_StartEditingNewField; _StartEditingNewField;
const factory GridHeaderEvent.endEditingField() = _EndEditingField; const factory GridHeaderEvent.endEditingField() = _EndEditingField;
const factory GridHeaderEvent.moveField( const factory GridHeaderEvent.moveField(
FieldPB field,
int fromIndex, int fromIndex,
int toIndex, int toIndex,
) = _MoveField; ) = _MoveField;

View File

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

View File

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

View File

@ -263,18 +263,19 @@ enum FieldAction {
break; break;
case FieldAction.duplicate: case FieldAction.duplicate:
PopoverContainer.of(context).close(); PopoverContainer.of(context).close();
context FieldBackendService.duplicateField(
.read<FieldEditorBloc>() viewId: viewId,
.add(const FieldEditorEvent.duplicateField()); fieldId: fieldInfo.id,
);
break; break;
case FieldAction.delete: case FieldAction.delete:
NavigatorAlertDialog( NavigatorAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () { confirm: () {
FieldBackendService( FieldBackendService.deleteField(
viewId: viewId, viewId: viewId,
fieldId: fieldInfo.id, fieldId: fieldInfo.id,
).deleteField(); );
}, },
).show(context); ).show(context);
PopoverContainer.of(context).close(); 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/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_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.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_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.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:flutter_bloc/flutter_bloc.dart';
import 'package:reorderables/reorderables.dart'; import 'package:reorderables/reorderables.dart';
import '../../../../application/field/type_option/type_option_service.dart';
import '../../layout/sizes.dart'; import '../../layout/sizes.dart';
import 'field_cell.dart'; import 'field_cell.dart';
@ -134,12 +134,14 @@ class _GridHeaderState extends State<_GridHeader> {
needsLongPressDraggable: PlatformExtension.isMobile, needsLongPressDraggable: PlatformExtension.isMobile,
footer: _CellTrailing(viewId: widget.viewId), footer: _CellTrailing(viewId: widget.viewId),
onReorder: (int oldIndex, int newIndex) { onReorder: (int oldIndex, int newIndex) {
_onReorder( // to offset removing the first field from `state.fields`
cells, if (PlatformExtension.isMobile) {
oldIndex, oldIndex++;
context, newIndex++;
newIndex, }
); context
.read<GridHeaderBloc>()
.add(GridHeaderEvent.moveField(oldIndex, newIndex));
}, },
children: cells, 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) { Widget _cellLeading(FieldInfo? fieldInfo) {
if (PlatformExtension.isDesktop) { if (PlatformExtension.isDesktop) {
return SizedBox(width: GridSize.leadingHeaderPadding); return SizedBox(width: GridSize.leadingHeaderPadding);
@ -245,7 +231,7 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
if (PlatformExtension.isMobile) { if (PlatformExtension.isMobile) {
showCreateFieldBottomSheet(context, widget.viewId); showCreateFieldBottomSheet(context, widget.viewId);
} else { } else {
final result = await TypeOptionBackendService.createFieldTypeOption( final result = await FieldBackendService.createField(
viewId: widget.viewId, viewId: widget.viewId,
); );
result.fold( result.fold(

View File

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

View File

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

View File

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

View File

@ -25,7 +25,6 @@ class RowActionList extends StatelessWidget {
RowDetailPageDuplicateButton( RowDetailPageDuplicateButton(
viewId: rowController.viewId, viewId: rowController.viewId,
rowId: rowController.rowId, rowId: rowController.rowId,
groupId: rowController.groupId,
), ),
const VSpace(4.0), const VSpace(4.0),
RowDetailPageDeleteButton( RowDetailPageDeleteButton(
@ -65,16 +64,15 @@ class RowDetailPageDeleteButton extends StatelessWidget {
} }
class RowDetailPageDuplicateButton extends StatelessWidget { class RowDetailPageDuplicateButton extends StatelessWidget {
final String viewId;
final String rowId;
final String? groupId;
const RowDetailPageDuplicateButton({ const RowDetailPageDuplicateButton({
super.key, super.key,
required this.viewId, required this.viewId,
required this.rowId, required this.rowId,
this.groupId,
}); });
final String viewId;
final String rowId;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
@ -83,7 +81,7 @@ class RowDetailPageDuplicateButton extends StatelessWidget {
text: FlowyText.regular(LocaleKeys.grid_row_duplicate.tr()), text: FlowyText.regular(LocaleKeys.grid_row_duplicate.tr()),
leftIcon: const FlowySvg(FlowySvgs.copy_s), leftIcon: const FlowySvg(FlowySvgs.copy_s),
onTap: () { onTap: () {
RowBackendService.duplicateRow(viewId, rowId, groupId); RowBackendService.duplicateRow(viewId, rowId);
FlowyOverlay.pop(context); 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/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.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/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_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
@ -57,22 +57,10 @@ class RowPropertyList extends StatelessWidget {
return ReorderableListView( return ReorderableListView(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
onReorder: (oldIndex, newIndex) { onReorder: (from, to) {
// when reorderiing downwards, need to update index context
if (oldIndex < newIndex) { .read<RowDetailBloc>()
newIndex--; .add(RowDetailEvent.reorderField(from, to));
}
final reorderedFieldId = children[oldIndex].cellContext.fieldId;
final targetFieldId = children[newIndex].cellContext.fieldId;
context.read<RowDetailBloc>().add(
RowDetailEvent.reorderField(
reorderedFieldId,
targetFieldId,
oldIndex,
newIndex,
),
);
}, },
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
proxyDecorator: (child, index, animation) => Material( proxyDecorator: (child, index, animation) => Material(
@ -391,7 +379,7 @@ class _CreateRowFieldButtonState extends State<CreateRowFieldButton> {
), ),
hoverColor: AFThemeExtension.of(context).lightGreyHover, hoverColor: AFThemeExtension.of(context).lightGreyHover,
onTap: () async { onTap: () async {
final result = await TypeOptionBackendService.createFieldTypeOption( final result = await FieldBackendService.createField(
viewId: widget.viewId, viewId: widget.viewId,
); );
result.fold( result.fold(

View File

@ -95,13 +95,9 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
shrinkWrap: true, shrinkWrap: true,
onReorder: (from, to) { onReorder: (from, to) {
context.read<DatabasePropertyBloc>().add( context
DatabasePropertyEvent.moveField( .read<DatabasePropertyBloc>()
fieldId: cells[from].fieldInfo.id, .add(DatabasePropertyEvent.moveField(from, to));
fromIndex: from,
toIndex: to,
),
);
}, },
onReorderStart: (_) => _popoverMutex.close(), onReorderStart: (_) => _popoverMutex.close(),
padding: const EdgeInsets.symmetric(vertical: 6.0), 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_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart'; import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.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_context.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_controller.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/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/plugins/database_view/grid/application/row/row_bloc.dart';
import 'package:appflowy/workspace/application/view/view_service.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-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:dartz/dartz.dart';
import '../../util.dart'; import '../../util.dart';
@ -34,10 +32,6 @@ class GridTestContext {
return gridController.fieldController; return gridController.fieldController;
} }
Future<Either<RowMetaPB, FlowyError>> createRow() async {
return gridController.createRow();
}
Future<CellController> makeCellController( Future<CellController> makeCellController(
String fieldId, String fieldId,
int rowIndex, int rowIndex,
@ -135,7 +129,7 @@ class GridTestContext {
Future<FieldEditorBloc> createFieldEditor({ Future<FieldEditorBloc> createFieldEditor({
required DatabaseController databaseController, required DatabaseController databaseController,
}) async { }) async {
final result = await TypeOptionBackendService.createFieldTypeOption( final result = await FieldBackendService.createField(
viewId: databaseController.viewId, viewId: databaseController.viewId,
); );
await gridResponseFuture(); await gridResponseFuture();
@ -211,7 +205,7 @@ class AppFlowyGridCellTest {
} }
Future<void> createTestRow() async { Future<void> createTestRow() async {
await context.createRow(); await RowBackendService.createRow(viewId: context.gridView.id);
} }
Future<SelectOptionCellController> makeSelectOptionCellController( Future<SelectOptionCellController> makeSelectOptionCellController(

View File

@ -883,7 +883,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -902,7 +902,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -932,7 +932,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -944,7 +944,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -963,7 +963,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -977,7 +977,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1019,7 +1019,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1040,7 +1040,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1066,7 +1066,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "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: # To switch to the local path, run:
# scripts/tool/update_collab_source.sh # scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
collab = { 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 = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } 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) => { const onDragEnd: OnDragEndResponder = (result) => {
if (!result.destination?.index) return; if (!result.destination?.index) return;
const fields = cells
.filter((cell) => {
return fieldsStore[cell.cellIdentifier.fieldId]?.visible;
});
void controller.moveField({ void controller.moveField({
fieldId: result.draggableId, fromFieldId: result.draggableId,
fromIndex: result.source.index, toFieldId: fields[result.source.index].fieldId,
toIndex: result.destination.index,
}); });
}; };
@ -186,9 +190,8 @@ export const EditRow = ({
return ( return (
<> <>
<div <div
className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${ 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'
unveil ? 'opacity-100' : 'opacity-0' }`}
}`}
onClick={() => onCloseClick()} onClick={() => onCloseClick()}
> >
<div <div
@ -220,9 +223,8 @@ export const EditRow = ({
<div <div
{...provided.droppableProps} {...provided.droppableProps}
ref={provided.innerRef} ref={provided.innerRef}
className={`flex flex-1 flex-col gap-8 px-8 pb-8 ${ className={`flex flex-1 flex-col gap-8 px-8 pb-8 ${showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto' }`}
}`}
> >
{cells {cells
.filter((cell) => { .filter((cell) => {

View File

@ -54,24 +54,6 @@ export const useDatabase = () => useSnapshot(useContext(DatabaseContext));
export const useContextDatabase = () => 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) => { export const useSelectorCell = (rowId: string, fieldId: string) => {
const database = useContext(DatabaseContext); const database = useContext(DatabaseContext);
const cells = useSnapshot(database.cells); const cells = useSnapshot(database.cells);

View File

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

View File

@ -2,6 +2,7 @@ import {
CreateRowPayloadPB, CreateRowPayloadPB,
MoveGroupRowPayloadPB, MoveGroupRowPayloadPB,
MoveRowPayloadPB, MoveRowPayloadPB,
OrderObjectPositionTypePB,
RowIdPB, RowIdPB,
UpdateRowMetaChangesetPB, UpdateRowMetaChangesetPB,
} from '@/services/backend'; } from '@/services/backend';
@ -17,13 +18,17 @@ import {
import { pbToRowMeta, RowMeta } from './row_types'; import { pbToRowMeta, RowMeta } from './row_types';
export async function createRow(viewId: string, params?: { export async function createRow(viewId: string, params?: {
startRowId?: string; position?: OrderObjectPositionTypePB;
rowId?: string;
groupId?: string; groupId?: string;
data?: Record<string, string>; data?: Record<string, string>;
}): Promise<RowMeta> { }): Promise<RowMeta> {
const payload = CreateRowPayloadPB.fromObject({ const payload = CreateRowPayloadPB.fromObject({
view_id: viewId, view_id: viewId,
start_row_id: params?.startRowId, row_position: {
position: params?.position,
object_id: params?.rowId,
},
group_id: params?.groupId, group_id: params?.groupId,
data: params?.data ? { cell_data_by_field_id: params.data } : undefined, 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 handleOnDragEnd = async (result: DropResult) => {
const { destination, draggableId, source } = result; const { destination, draggableId, source } = result;
const newIndex = destination?.index;
const oldIndex = source.index; const oldIndex = source.index;
const newIndex = destination?.index;
if (oldIndex === newIndex) { if (oldIndex === newIndex) {
return; return;
@ -35,11 +35,13 @@ function Properties({ onItemClick }: PropertiesProps) {
return; return;
} }
const newId = fields[newIndex ?? 0].id;
const newProperties = fieldService.reorderFields(fields as FieldType[], oldIndex, newIndex ?? 0); const newProperties = fieldService.reorderFields(fields as FieldType[], oldIndex, newIndex ?? 0);
setState(newProperties); setState(newProperties);
await fieldService.moveField(viewId, draggableId, oldIndex, newIndex); await fieldService.moveField(viewId, draggableId, newId ?? "");
}; };
return ( return (

View File

@ -44,23 +44,6 @@ function RecordProperties({ documentId, row }: Props) {
setState(properties); setState(properties);
}, [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 // move the field in the state
const handleOnDragEnd: OnDragEndResponder = useCallback( const handleOnDragEnd: OnDragEndResponder = useCallback(
async (result: DropResult) => { async (result: DropResult) => {
@ -72,20 +55,16 @@ function RecordProperties({ documentId, row }: Props) {
return; return;
} }
const newId = properties[newIndex ?? 0].id;
// reorder the properties synchronously to avoid flickering // reorder the properties synchronously to avoid flickering
const newProperties = fieldService.reorderFields(properties, oldIndex, newIndex ?? 0); const newProperties = fieldService.reorderFields(properties, oldIndex, newIndex ?? 0);
setState(newProperties); setState(newProperties);
// find the previous field id await fieldService.moveField(viewId, draggableId, newId);
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);
}, },
[onMoveProperty, properties] [properties, viewId]
); );
return ( 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 { ReactComponent as RightSvg } from '$app/assets/right.svg';
import { useViewId } from '$app/hooks'; import { useViewId } from '$app/hooks';
import { fieldService } from '$app/components/database/application'; 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 { MenuItem } from '@mui/material';
import ConfirmDialog from '$app/components/_shared/app-dialog/ConfirmDialog'; import ConfirmDialog from '$app/components/_shared/app-dialog/ConfirmDialog';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -83,7 +83,7 @@ function PropertyActions({ fieldId, onMenuItemClick, isPrimary, actions = defaul
break; break;
case FieldAction.InsertLeft: case FieldAction.InsertLeft:
case FieldAction.InsertRight: { 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({ const field = await fieldService.createField({
viewId, viewId,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -349,11 +349,12 @@ async function testMoveField() {
}); });
const fieldInfos = [...databaseController.fieldController.fieldInfos]; 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)); 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() { async function testGetSingleSelectFieldData() {

View File

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

View File

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

View File

@ -733,7 +733,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -752,7 +752,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -782,7 +782,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -794,7 +794,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -813,7 +813,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -827,7 +827,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -869,7 +869,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -890,7 +890,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -916,7 +916,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1149,7 +1149,7 @@ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa", "itoa",
"phf 0.8.0", "phf 0.11.2",
"smallvec", "smallvec",
] ]
@ -3653,7 +3653,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [ dependencies = [
"phf_macros", "phf_macros 0.8.0",
"phf_shared 0.8.0", "phf_shared 0.8.0",
"proc-macro-hack", "proc-macro-hack",
] ]
@ -3673,6 +3673,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2", "phf_shared 0.11.2",
] ]
@ -3740,6 +3741,19 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -3943,7 +3957,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [ dependencies = [
"bytes", "bytes",
"heck 0.4.1", "heck 0.4.1",
"itertools 0.10.5", "itertools 0.11.0",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@ -3964,7 +3978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.10.5", "itertools 0.11.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "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: # To switch to the local path, run:
# scripts/tool/update_collab_source.sh # scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
collab = { 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 = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "ac2ab049e367009b59c2e411c28580609a19e8b8" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "32218f1b6a9b09a9bbaa1835749e016246c092d4" } 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( pub async fn create_row(
&self, &self,
view_id: &str, view_id: &str,
start_row_id: Option<String>, row_position: OrderObjectPositionPB,
data: Option<RowDataPB>, data: Option<RowDataPB>,
) -> RowMetaPB { ) -> RowMetaPB {
EventBuilder::new(self.clone()) EventBuilder::new(self.clone())
.event(DatabaseEvent::CreateRow) .event(DatabaseEvent::CreateRow)
.payload(CreateRowPayloadPB { .payload(CreateRowPayloadPB {
view_id: view_id.to_string(), view_id: view_id.to_string(),
start_row_id, row_position,
group_id: None, group_id: None,
data, data,
}) })

View File

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

View File

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

View File

@ -12,6 +12,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::position_entities::OrderObjectPositionPB;
use crate::impl_into_field_type; use crate::impl_into_field_type;
/// [FieldPB] defines a Field's attributes. Such as the name, field_type, and width. etc. /// [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>>, pub type_option_data: Option<Vec<u8>>,
#[pb(index = 5)] #[pb(index = 5)]
pub field_position: CreateFieldPosition, pub field_position: OrderObjectPositionPB,
#[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,
} }
#[derive(Clone)] #[derive(Clone)]
@ -204,34 +192,7 @@ impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
None => None, None => None,
}; };
let position = match &self.field_position { let position = self.field_position.try_into()?;
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)
},
};
Ok(CreateFieldParams { Ok(CreateFieldParams {
view_id: view_id.0, view_id: view_id.0,

View File

@ -7,15 +7,16 @@ mod field_settings_entities;
pub mod filter_entities; pub mod filter_entities;
mod group_entities; mod group_entities;
pub mod parser; pub mod parser;
mod position_entities;
mod row_entities; mod row_entities;
pub mod setting_entities; pub mod setting_entities;
mod share_entities;
mod sort_entities; mod sort_entities;
mod type_option_entities;
mod view_entities; mod view_entities;
#[macro_use] #[macro_use]
mod macros; mod macros;
mod share_entities;
mod type_option_entities;
pub use board_entities::*; pub use board_entities::*;
pub use calendar_entities::*; pub use calendar_entities::*;
@ -25,6 +26,7 @@ pub use field_entities::*;
pub use field_settings_entities::*; pub use field_settings_entities::*;
pub use filter_entities::*; pub use filter_entities::*;
pub use group_entities::*; pub use group_entities::*;
pub use position_entities::*;
pub use row_entities::*; pub use row_entities::*;
pub use setting_entities::*; pub use setting_entities::*;
pub use share_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 std::collections::HashMap;
use collab_database::rows::{Row, RowDetail, RowId}; use collab_database::rows::{Row, RowDetail, RowId};
use collab_database::views::RowOrder; use collab_database::views::{OrderObjectPosition, RowOrder};
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::position_entities::OrderObjectPositionPB;
use crate::services::database::{InsertedRow, UpdatedRow}; use crate::services::database::{InsertedRow, UpdatedRow};
/// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row. /// [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)] #[pb(index = 1)]
pub view_id: String, pub view_id: String,
#[pb(index = 2, one_of)] #[pb(index = 2)]
pub start_row_id: Option<String>, pub row_position: OrderObjectPositionPB,
#[pb(index = 3, one_of)] #[pb(index = 3, one_of)]
pub group_id: Option<String>, pub group_id: Option<String>,
@ -358,7 +359,7 @@ pub struct RowDataPB {
#[derive(Default)] #[derive(Default)]
pub struct CreateRowParams { pub struct CreateRowParams {
pub view_id: String, pub view_id: String,
pub start_row_id: Option<RowId>, pub row_position: OrderObjectPosition,
pub group_id: Option<String>, pub group_id: Option<String>,
pub cell_data_by_field_id: Option<HashMap<String, 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> { fn try_into(self) -> Result<CreateRowParams, Self::Error> {
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?; 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 { Ok(CreateRowParams {
view_id: view_id.0, view_id: view_id.0,
start_row_id, row_position: position,
group_id: self.group_id, group_id: self.group_id,
cell_data_by_field_id: self.data.map(|data| data.cell_data_by_field_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::database::gen_row_id;
use collab_database::rows::RowId; use collab_database::rows::RowId;
use collab_database::views::OrderObjectPosition;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
@ -345,14 +344,7 @@ pub(crate) async fn move_field_handler(
let manager = upgrade_manager(manager)?; let manager = upgrade_manager(manager)?;
let params: MoveFieldParams = data.into_inner().try_into()?; let params: MoveFieldParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?; let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor database_editor.move_field(params).await?;
.move_field(
&params.view_id,
&params.field_id,
params.from_index,
params.to_index,
)
.await?;
Ok(()) Ok(())
} }
@ -416,7 +408,7 @@ pub(crate) async fn duplicate_row_handler(
let params: RowIdParams = data.into_inner().try_into()?; let params: RowIdParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?; let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor database_editor
.duplicate_row(&params.view_id, params.group_id, &params.row_id) .duplicate_row(&params.view_id, &params.row_id)
.await; .await;
Ok(()) 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?; let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
database_editor database_editor
.move_row(&params.view_id, params.from_row_id, params.to_row_id) .move_row(&params.view_id, params.from_row_id, params.to_row_id)
.await; .await?;
Ok(()) 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(); CellBuilder::with_cells(params.cell_data_by_field_id.unwrap_or_default(), &fields).build();
let view_id = params.view_id; let view_id = params.view_id;
let group_id = params.group_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 { let params = collab_database::rows::CreateRowParams {
id: gen_row_id(), id: gen_row_id(),
cells, cells,
height: 60, height: 60,
visibility: true, visibility: true,
row_position: position, row_position: params.row_position,
timestamp: timestamp(), timestamp: timestamp(),
}; };
match database_editor match database_editor

View File

@ -168,8 +168,9 @@ pub enum DatabaseEvent {
#[event(input = "DuplicateFieldPayloadPB")] #[event(input = "DuplicateFieldPayloadPB")]
DuplicateField = 21, DuplicateField = 21,
/// [MoveItem] event is used to move an item. For the moment, Item has two types defined in /// [MoveFieldPB] event is used to reorder a field in a view. The
/// [MoveItemTypePB]. /// [MoveFieldPayloadPB] contains the `field_id` of the moved field and its
/// new position.
#[event(input = "MoveFieldPayloadPB")] #[event(input = "MoveFieldPayloadPB")]
MoveField = 22, 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. // 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); let params = self.database.lock().duplicate_row(row_id);
match params { match params {
None => { None => warn!("Failed to duplicate row: {}", row_id),
warn!("Failed to duplicate row: {}", row_id);
},
Some(params) => { 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(); 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 row_detail = database.get_row_detail(&from_row_id).ok_or_else(|| {
let insert_row = InsertedRowPB::new(RowMetaPB::from(row_detail)).with_index(to_index as i32); 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]); let changes = RowsChangePB::from_move(vec![delete_row_id], vec![insert_row]);
send_notification(view_id, DatabaseNotification::DidUpdateViewRows) send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
.payload(changes) .payload(changes)
.send(); .send();
} }
Ok(())
} }
pub async fn create_row( pub async fn create_row(
@ -504,28 +514,34 @@ impl DatabaseEditor {
) )
} }
pub async fn move_field( pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
&self, let (field, new_index) = {
view_id: &str,
field_id: &str,
from: i32,
to: i32,
) -> FlowyResult<()> {
let (database_id, field) = {
let database = self.database.lock(); 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
let field = database.fields.get_field(field_id); .get_field(&params.from_field_id)
let database_id = database.get_database_id(); .ok_or_else(|| {
(database_id, field) 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 { if let Some(index) = new_index {
let delete_field = FieldIdPB::from(field_id); let delete_field = FieldIdPB::from(params.from_field_id);
let insert_field = IndexFieldPB::from_field(field, to as usize); let insert_field = IndexFieldPB::from_field(field, index);
let notified_changeset = DatabaseFieldChangesetPB { let notified_changeset = DatabaseFieldChangesetPB {
view_id: database_id, view_id: params.view_id,
inserted_fields: vec![insert_field], inserted_fields: vec![insert_field],
deleted_fields: vec![delete_field], deleted_fields: vec![delete_field],
updated_fields: vec![], updated_fields: vec![],
@ -533,6 +549,7 @@ impl DatabaseEditor {
self.notify_did_update_database(notified_changeset).await?; self.notify_did_update_database(notified_changeset).await?;
} }
Ok(()) Ok(())
} }
@ -955,7 +972,7 @@ impl DatabaseEditor {
.map(|row_detail| row_detail.row.id.clone()) .map(|row_detail| row_detail.row.id.clone())
}; };
if let Some(row_id) = to_row.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 { if from_group == to_group {