mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: insert a new field to the left or right of an existing one (#4022)
* feat: allow inserting fields before or after a certain field * fix: tauri build * chore: implement frontend * test: rust-lib tests * test: integration test * chore: point to temp collab rev * chore: bump collab rev * chore: fix tauri build * chore: fix the tauri build, for real this time * fix: new field editor show detail not general --------- Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
@ -10,8 +10,8 @@ import '../util/util.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('grid cell', () {
|
group('edit grid cell:', () {
|
||||||
testWidgets('edit text cell', (tester) async {
|
testWidgets('text', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ void main() {
|
|||||||
|
|
||||||
// Make sure the text cells are filled with the right content when there are
|
// Make sure the text cells are filled with the right content when there are
|
||||||
// multiple text cell
|
// multiple text cell
|
||||||
testWidgets('edit multiple text cells', (tester) async {
|
testWidgets('multiple text cells', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
@ -73,7 +73,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('edit number cell', (tester) async {
|
testWidgets('number', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('edit checkbox cell', (tester) async {
|
testWidgets('checkbox', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('edit create time cell', (tester) async {
|
testWidgets('created time', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('edit last time cell', (tester) async {
|
testWidgets('last modified time', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('edit date time cell', (tester) async {
|
testWidgets('date time', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('edit single select cell', (tester) async {
|
testWidgets('single select', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -347,7 +347,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('edit multi select cell', (tester) async {
|
testWidgets('multi select', (tester) async {
|
||||||
final tags = [
|
final tags = [
|
||||||
'tag 1',
|
'tag 1',
|
||||||
'tag 2',
|
'tag 2',
|
||||||
@ -436,9 +436,8 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('edit checklist cell', (tester) async {
|
testWidgets('checklist', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -539,4 +538,5 @@ void main() {
|
|||||||
// check that the progress bar is not viisble
|
// check that the progress bar is not viisble
|
||||||
tester.assertChecklistCellInGrid(rowIndex: 0, percent: null);
|
tester.assertChecklistCellInGrid(rowIndex: 0, percent: null);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import '../util/util.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('grid page', () {
|
group('grid field editor:', () {
|
||||||
testWidgets('rename existing field', (tester) async {
|
testWidgets('rename existing field', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
@ -98,7 +98,7 @@ void main() {
|
|||||||
await tester.renameField('New field 1');
|
await tester.renameField('New field 1');
|
||||||
await tester.dismissFieldEditor();
|
await tester.dismissFieldEditor();
|
||||||
|
|
||||||
// Delete the field
|
// duplicate the field
|
||||||
await tester.tapGridFieldWithName('New field 1');
|
await tester.tapGridFieldWithName('New field 1');
|
||||||
await tester.tapDuplicatePropertyButton();
|
await tester.tapDuplicatePropertyButton();
|
||||||
|
|
||||||
@ -106,6 +106,29 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('insert field on either side of a field', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
|
|
||||||
|
await tester.scrollToRight(find.byType(GridPage));
|
||||||
|
|
||||||
|
// insert new field to the right
|
||||||
|
await tester.tapGridFieldWithName('Type');
|
||||||
|
await tester.tapInsertFieldButton(left: false, name: 'Right');
|
||||||
|
await tester.dismissFieldEditor();
|
||||||
|
await tester.findFieldWithName('Right');
|
||||||
|
|
||||||
|
// insert new field to the right
|
||||||
|
await tester.tapGridFieldWithName('Type');
|
||||||
|
await tester.tapInsertFieldButton(left: true, name: "Left");
|
||||||
|
await tester.dismissFieldEditor();
|
||||||
|
await tester.findFieldWithName('Left');
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('create checklist field', (tester) async {
|
testWidgets('create checklist field', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
@ -765,6 +765,20 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(field);
|
await tapButton(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapInsertFieldButton({
|
||||||
|
required bool left,
|
||||||
|
required String name,
|
||||||
|
}) async {
|
||||||
|
final field = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is FieldActionCell &&
|
||||||
|
(left && widget.action == FieldAction.insertLeft ||
|
||||||
|
!left && widget.action == FieldAction.insertRight),
|
||||||
|
);
|
||||||
|
await tapButton(field);
|
||||||
|
await renameField(name);
|
||||||
|
}
|
||||||
|
|
||||||
/// Should call [tapGridFieldWithName] first.
|
/// Should call [tapGridFieldWithName] first.
|
||||||
Future<void> tapHidePropertyButton() async {
|
Future<void> tapHidePropertyButton() async {
|
||||||
final field = find.byWidgetPredicate(
|
final field = find.byWidgetPredicate(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@ -25,11 +26,13 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
|||||||
final FieldBackendService fieldService;
|
final FieldBackendService fieldService;
|
||||||
final FieldSettingsBackendService fieldSettingsService;
|
final FieldSettingsBackendService fieldSettingsService;
|
||||||
final TypeOptionController typeOptionController;
|
final TypeOptionController typeOptionController;
|
||||||
|
final void Function(String newFieldId)? onFieldInserted;
|
||||||
|
|
||||||
FieldEditorBloc({
|
FieldEditorBloc({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.field,
|
required this.field,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
|
this.onFieldInserted,
|
||||||
required FieldTypeOptionLoader loader,
|
required FieldTypeOptionLoader loader,
|
||||||
}) : typeOptionController = TypeOptionController(
|
}) : typeOptionController = TypeOptionController(
|
||||||
field: field,
|
field: field,
|
||||||
@ -73,6 +76,28 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
|||||||
final result = await fieldService.updateField(name: newName);
|
final result = await fieldService.updateField(name: newName);
|
||||||
_logIfError(result);
|
_logIfError(result);
|
||||||
},
|
},
|
||||||
|
insertLeft: () async {
|
||||||
|
final result = await TypeOptionBackendService.createFieldTypeOption(
|
||||||
|
viewId: viewId,
|
||||||
|
position: CreateFieldPosition.Before,
|
||||||
|
targetFieldId: field.id,
|
||||||
|
);
|
||||||
|
result.fold(
|
||||||
|
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
|
||||||
|
(err) => Log.error("Failed creating field $err"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
insertRight: () async {
|
||||||
|
final result = await TypeOptionBackendService.createFieldTypeOption(
|
||||||
|
viewId: viewId,
|
||||||
|
position: CreateFieldPosition.After,
|
||||||
|
targetFieldId: field.id,
|
||||||
|
);
|
||||||
|
result.fold(
|
||||||
|
(typeOptionPB) => onFieldInserted?.call(typeOptionPB.field_2.id),
|
||||||
|
(err) => Log.error("Failed creating field $err"),
|
||||||
|
);
|
||||||
|
},
|
||||||
toggleFieldVisibility: () async {
|
toggleFieldVisibility: () async {
|
||||||
final currentVisibility =
|
final currentVisibility =
|
||||||
state.field.visibility ?? FieldVisibility.AlwaysShown;
|
state.field.visibility ?? FieldVisibility.AlwaysShown;
|
||||||
@ -122,6 +147,8 @@ class FieldEditorEvent with _$FieldEditorEvent {
|
|||||||
const factory FieldEditorEvent.switchFieldType(final FieldType fieldType) =
|
const factory FieldEditorEvent.switchFieldType(final FieldType fieldType) =
|
||||||
_SwitchFieldType;
|
_SwitchFieldType;
|
||||||
const factory FieldEditorEvent.renameField(final String name) = _RenameField;
|
const factory FieldEditorEvent.renameField(final String name) = _RenameField;
|
||||||
|
const factory FieldEditorEvent.insertLeft() = _InsertLeft;
|
||||||
|
const factory FieldEditorEvent.insertRight() = _InsertRight;
|
||||||
const factory FieldEditorEvent.toggleFieldVisibility() =
|
const factory FieldEditorEvent.toggleFieldVisibility() =
|
||||||
_ToggleFieldVisiblity;
|
_ToggleFieldVisiblity;
|
||||||
const factory FieldEditorEvent.deleteField() = _DeleteField;
|
const factory FieldEditorEvent.deleteField() = _DeleteField;
|
||||||
|
@ -26,11 +26,20 @@ class TypeOptionBackendService {
|
|||||||
static Future<Either<TypeOptionPB, FlowyError>> createFieldTypeOption({
|
static Future<Either<TypeOptionPB, FlowyError>> createFieldTypeOption({
|
||||||
required String viewId,
|
required String viewId,
|
||||||
FieldType fieldType = FieldType.RichText,
|
FieldType fieldType = FieldType.RichText,
|
||||||
|
CreateFieldPosition position = CreateFieldPosition.End,
|
||||||
|
String? targetFieldId,
|
||||||
}) {
|
}) {
|
||||||
final payload = CreateFieldPayloadPB.create()
|
final payload = CreateFieldPayloadPB.create()
|
||||||
..viewId = viewId
|
..viewId = viewId
|
||||||
..fieldType = FieldType.RichText;
|
..fieldType = FieldType.RichText;
|
||||||
|
|
||||||
return DatabaseEventCreateTypeOption(payload).send();
|
if (position == CreateFieldPosition.Before ||
|
||||||
|
position == CreateFieldPosition.After && targetFieldId != null) {
|
||||||
|
payload.targetFieldId = targetFieldId!;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.fieldPosition = position;
|
||||||
|
|
||||||
|
return DatabaseEventCreateField(payload).send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,17 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
|||||||
}) : super(GridHeaderState.initial()) {
|
}) : super(GridHeaderState.initial()) {
|
||||||
on<GridHeaderEvent>(
|
on<GridHeaderEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (_InitialHeader value) {
|
initial: () {
|
||||||
_startListening();
|
_startListening();
|
||||||
add(
|
add(
|
||||||
GridHeaderEvent.didReceiveFieldUpdate(fieldController.fieldInfos),
|
GridHeaderEvent.didReceiveFieldUpdate(fieldController.fieldInfos),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
didReceiveFieldUpdate: (List<FieldInfo> fields) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
fields: value.fields
|
fields: fields
|
||||||
.where(
|
.where(
|
||||||
(element) =>
|
(element) =>
|
||||||
element.visibility != null &&
|
element.visibility != null &&
|
||||||
@ -40,8 +40,17 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
moveField: (_MoveField value) async {
|
startEditingField: (fieldId) {
|
||||||
await _moveField(value, emit);
|
emit(state.copyWith(editingFieldId: fieldId));
|
||||||
|
},
|
||||||
|
startEditingNewField: (fieldId) {
|
||||||
|
emit(state.copyWith(editingFieldId: fieldId, newFieldId: fieldId));
|
||||||
|
},
|
||||||
|
endEditingField: () {
|
||||||
|
emit(state.copyWith(editingFieldId: null, newFieldId: null));
|
||||||
|
},
|
||||||
|
moveField: (field, fromIndex, toIndex) async {
|
||||||
|
await _moveField(field, fromIndex, toIndex, emit);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -49,19 +58,17 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _moveField(
|
Future<void> _moveField(
|
||||||
_MoveField value,
|
FieldPB field,
|
||||||
|
int fromIndex,
|
||||||
|
int toIndex,
|
||||||
Emitter<GridHeaderState> emit,
|
Emitter<GridHeaderState> emit,
|
||||||
) async {
|
) async {
|
||||||
final fields = List<FieldInfo>.from(state.fields);
|
final fields = List<FieldInfo>.from(state.fields);
|
||||||
fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
|
fields.insert(toIndex, fields.removeAt(fromIndex));
|
||||||
emit(state.copyWith(fields: fields));
|
emit(state.copyWith(fields: fields));
|
||||||
|
|
||||||
final fieldService =
|
final fieldService = FieldBackendService(viewId: viewId, fieldId: field.id);
|
||||||
FieldBackendService(viewId: viewId, fieldId: value.field.id);
|
final result = await fieldService.moveField(fromIndex, toIndex);
|
||||||
final result = await fieldService.moveField(
|
|
||||||
value.fromIndex,
|
|
||||||
value.toIndex,
|
|
||||||
);
|
|
||||||
result.fold((l) {}, (err) => Log.error(err));
|
result.fold((l) {}, (err) => Log.error(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +86,11 @@ class GridHeaderEvent with _$GridHeaderEvent {
|
|||||||
const factory GridHeaderEvent.initial() = _InitialHeader;
|
const factory GridHeaderEvent.initial() = _InitialHeader;
|
||||||
const factory GridHeaderEvent.didReceiveFieldUpdate(List<FieldInfo> fields) =
|
const factory GridHeaderEvent.didReceiveFieldUpdate(List<FieldInfo> fields) =
|
||||||
_DidReceiveFieldUpdate;
|
_DidReceiveFieldUpdate;
|
||||||
|
const factory GridHeaderEvent.startEditingField(String fieldId) =
|
||||||
|
_StartEditingField;
|
||||||
|
const factory GridHeaderEvent.startEditingNewField(String fieldId) =
|
||||||
|
_StartEditingNewField;
|
||||||
|
const factory GridHeaderEvent.endEditingField() = _EndEditingField;
|
||||||
const factory GridHeaderEvent.moveField(
|
const factory GridHeaderEvent.moveField(
|
||||||
FieldPB field,
|
FieldPB field,
|
||||||
int fromIndex,
|
int fromIndex,
|
||||||
@ -88,8 +100,12 @@ class GridHeaderEvent with _$GridHeaderEvent {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class GridHeaderState with _$GridHeaderState {
|
class GridHeaderState with _$GridHeaderState {
|
||||||
const factory GridHeaderState({required List<FieldInfo> fields}) =
|
const factory GridHeaderState({
|
||||||
_GridHeaderState;
|
required List<FieldInfo> fields,
|
||||||
|
required String? editingFieldId,
|
||||||
|
required String? newFieldId,
|
||||||
|
}) = _GridHeaderState;
|
||||||
|
|
||||||
factory GridHeaderState.initial() => const GridHeaderState(fields: []);
|
factory GridHeaderState.initial() =>
|
||||||
|
const GridHeaderState(fields: [], editingFieldId: null, newFieldId: null);
|
||||||
}
|
}
|
||||||
|
@ -16,16 +16,27 @@ import 'field_editor.dart';
|
|||||||
import 'field_type_extension.dart';
|
import 'field_type_extension.dart';
|
||||||
|
|
||||||
class GridFieldCell extends StatefulWidget {
|
class GridFieldCell extends StatefulWidget {
|
||||||
final String viewId;
|
|
||||||
final FieldController fieldController;
|
|
||||||
final FieldInfo fieldInfo;
|
|
||||||
const GridFieldCell({
|
const GridFieldCell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
required this.fieldInfo,
|
required this.fieldInfo,
|
||||||
|
required this.onTap,
|
||||||
|
required this.onEditorOpened,
|
||||||
|
required this.onFieldInsertedOnEitherSide,
|
||||||
|
required this.isEditing,
|
||||||
|
required this.isNew,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
final FieldInfo fieldInfo;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final VoidCallback onEditorOpened;
|
||||||
|
final void Function(String fieldId) onFieldInsertedOnEitherSide;
|
||||||
|
final bool isEditing;
|
||||||
|
final bool isNew;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GridFieldCell> createState() => _GridFieldCellState();
|
State<GridFieldCell> createState() => _GridFieldCellState();
|
||||||
}
|
}
|
||||||
@ -39,6 +50,11 @@ class _GridFieldCellState extends State<GridFieldCell> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
popoverController = PopoverController();
|
popoverController = PopoverController();
|
||||||
_bloc = FieldCellBloc(viewId: widget.viewId, fieldInfo: widget.fieldInfo);
|
_bloc = FieldCellBloc(viewId: widget.viewId, fieldInfo: widget.fieldInfo);
|
||||||
|
if (widget.isEditing) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
|
popoverController.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -46,6 +62,11 @@ class _GridFieldCellState extends State<GridFieldCell> {
|
|||||||
if (widget.fieldInfo != oldWidget.fieldInfo && !_bloc.isClosed) {
|
if (widget.fieldInfo != oldWidget.fieldInfo && !_bloc.isClosed) {
|
||||||
_bloc.add(FieldCellEvent.onFieldChanged(widget.fieldInfo));
|
_bloc.add(FieldCellEvent.onFieldChanged(widget.fieldInfo));
|
||||||
}
|
}
|
||||||
|
if (widget.isEditing) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
|
popoverController.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,16 +83,20 @@ class _GridFieldCellState extends State<GridFieldCell> {
|
|||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
controller: popoverController,
|
controller: popoverController,
|
||||||
popupBuilder: (BuildContext context) {
|
popupBuilder: (BuildContext context) {
|
||||||
|
widget.onEditorOpened();
|
||||||
return FieldEditor(
|
return FieldEditor(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldController: widget.fieldController,
|
fieldController: widget.fieldController,
|
||||||
field: widget.fieldInfo.field,
|
field: widget.fieldInfo.field,
|
||||||
initialPage: FieldEditorPage.general,
|
initialPage: widget.isNew
|
||||||
|
? FieldEditorPage.details
|
||||||
|
: FieldEditorPage.general,
|
||||||
|
onFieldInserted: widget.onFieldInsertedOnEitherSide,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: FieldCellButton(
|
child: FieldCellButton(
|
||||||
field: widget.fieldInfo.field,
|
field: widget.fieldInfo.field,
|
||||||
onTap: () => popoverController.show(),
|
onTap: widget.onTap,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ class FieldEditor extends StatefulWidget {
|
|||||||
final FieldController fieldController;
|
final FieldController fieldController;
|
||||||
final FieldPB field;
|
final FieldPB field;
|
||||||
final FieldEditorPage initialPage;
|
final FieldEditorPage initialPage;
|
||||||
|
final void Function(String fieldId)? onFieldInserted;
|
||||||
|
|
||||||
const FieldEditor({
|
const FieldEditor({
|
||||||
super.key,
|
super.key,
|
||||||
@ -37,6 +38,7 @@ class FieldEditor extends StatefulWidget {
|
|||||||
required this.field,
|
required this.field,
|
||||||
required this.fieldController,
|
required this.fieldController,
|
||||||
this.initialPage = FieldEditorPage.details,
|
this.initialPage = FieldEditorPage.details,
|
||||||
|
this.onFieldInserted,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -61,6 +63,7 @@ class _FieldEditorState extends State<FieldEditor> {
|
|||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
field: widget.field,
|
field: widget.field,
|
||||||
fieldController: widget.fieldController,
|
fieldController: widget.fieldController,
|
||||||
|
onFieldInserted: widget.onFieldInserted,
|
||||||
loader: FieldTypeOptionLoader(
|
loader: FieldTypeOptionLoader(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
field: widget.field,
|
field: widget.field,
|
||||||
@ -89,6 +92,10 @@ class _FieldEditorState extends State<FieldEditor> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
_actionCell(FieldAction.insertLeft),
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
_actionCell(FieldAction.insertRight),
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
_actionCell(FieldAction.toggleVisibility),
|
_actionCell(FieldAction.toggleVisibility),
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
_actionCell(FieldAction.duplicate),
|
_actionCell(FieldAction.duplicate),
|
||||||
@ -170,40 +177,56 @@ class FieldActionCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onHover: (_) => popoverMutex?.close(),
|
onHover: (_) => popoverMutex?.close(),
|
||||||
onTap: () => action.run(context, viewId, fieldInfo),
|
onTap: () => action.run(context, viewId, fieldInfo),
|
||||||
leftIcon: FlowySvg(
|
leftIcon: action.icon(
|
||||||
action.icon(fieldInfo),
|
fieldInfo,
|
||||||
size: const Size.square(16),
|
enable ? null : Theme.of(context).disabledColor,
|
||||||
color: enable ? null : Theme.of(context).disabledColor,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FieldAction {
|
enum FieldAction {
|
||||||
|
insertLeft,
|
||||||
|
insertRight,
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
duplicate,
|
duplicate,
|
||||||
delete,
|
delete;
|
||||||
}
|
|
||||||
|
|
||||||
extension _FieldActionExtension on FieldAction {
|
Widget icon(FieldInfo fieldInfo, Color? color) {
|
||||||
FlowySvgData icon(FieldInfo fieldInfo) {
|
late final FlowySvgData svgData;
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
case FieldAction.insertLeft:
|
||||||
|
svgData = FlowySvgs.arrow_s;
|
||||||
|
case FieldAction.insertRight:
|
||||||
|
svgData = FlowySvgs.arrow_s;
|
||||||
case FieldAction.toggleVisibility:
|
case FieldAction.toggleVisibility:
|
||||||
if (fieldInfo.visibility != null &&
|
if (fieldInfo.visibility != null &&
|
||||||
fieldInfo.visibility == FieldVisibility.AlwaysHidden) {
|
fieldInfo.visibility == FieldVisibility.AlwaysHidden) {
|
||||||
return FlowySvgs.show_m;
|
svgData = FlowySvgs.show_m;
|
||||||
} else {
|
} else {
|
||||||
return FlowySvgs.hide_s;
|
svgData = FlowySvgs.hide_s;
|
||||||
}
|
}
|
||||||
case FieldAction.duplicate:
|
case FieldAction.duplicate:
|
||||||
return FlowySvgs.copy_s;
|
svgData = FlowySvgs.copy_s;
|
||||||
case FieldAction.delete:
|
case FieldAction.delete:
|
||||||
return FlowySvgs.delete_s;
|
svgData = FlowySvgs.delete_s;
|
||||||
}
|
}
|
||||||
|
final icon = FlowySvg(
|
||||||
|
svgData,
|
||||||
|
size: const Size.square(16),
|
||||||
|
color: color,
|
||||||
|
);
|
||||||
|
return this == FieldAction.insertRight
|
||||||
|
? Transform.flip(flipX: true, child: icon)
|
||||||
|
: icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
String title(FieldInfo fieldInfo) {
|
String title(FieldInfo fieldInfo) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
case FieldAction.insertLeft:
|
||||||
|
return LocaleKeys.grid_field_insertLeft.tr();
|
||||||
|
case FieldAction.insertRight:
|
||||||
|
return LocaleKeys.grid_field_insertRight.tr();
|
||||||
case FieldAction.toggleVisibility:
|
case FieldAction.toggleVisibility:
|
||||||
if (fieldInfo.visibility != null &&
|
if (fieldInfo.visibility != null &&
|
||||||
fieldInfo.visibility == FieldVisibility.AlwaysHidden) {
|
fieldInfo.visibility == FieldVisibility.AlwaysHidden) {
|
||||||
@ -220,6 +243,18 @@ extension _FieldActionExtension on FieldAction {
|
|||||||
|
|
||||||
void run(BuildContext context, String viewId, FieldInfo fieldInfo) {
|
void run(BuildContext context, String viewId, FieldInfo fieldInfo) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
case FieldAction.insertLeft:
|
||||||
|
PopoverContainer.of(context).close();
|
||||||
|
context
|
||||||
|
.read<FieldEditorBloc>()
|
||||||
|
.add(const FieldEditorEvent.insertLeft());
|
||||||
|
break;
|
||||||
|
case FieldAction.insertRight:
|
||||||
|
PopoverContainer.of(context).close();
|
||||||
|
context
|
||||||
|
.read<FieldEditorBloc>()
|
||||||
|
.add(const FieldEditorEvent.insertRight());
|
||||||
|
break;
|
||||||
case FieldAction.toggleVisibility:
|
case FieldAction.toggleVisibility:
|
||||||
PopoverContainer.of(context).close();
|
PopoverContainer.of(context).close();
|
||||||
context
|
context
|
||||||
|
@ -7,17 +7,14 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/
|
|||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
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 '../../../../application/field/type_option/type_option_service.dart';
|
||||||
import '../../layout/sizes.dart';
|
import '../../layout/sizes.dart';
|
||||||
import 'field_editor.dart';
|
|
||||||
import 'field_cell.dart';
|
import 'field_cell.dart';
|
||||||
|
|
||||||
class GridHeaderSliverAdaptor extends StatefulWidget {
|
class GridHeaderSliverAdaptor extends StatefulWidget {
|
||||||
@ -93,7 +90,6 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
return BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
||||||
buildWhen: (previous, current) => previous.fields != current.fields,
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final cells = state.fields
|
final cells = state.fields
|
||||||
.map(
|
.map(
|
||||||
@ -103,6 +99,17 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldInfo: fieldInfo,
|
fieldInfo: fieldInfo,
|
||||||
fieldController: widget.fieldController,
|
fieldController: widget.fieldController,
|
||||||
|
onTap: () => context
|
||||||
|
.read<GridHeaderBloc>()
|
||||||
|
.add(GridHeaderEvent.startEditingField(fieldInfo.id)),
|
||||||
|
onFieldInsertedOnEitherSide: (fieldId) => context
|
||||||
|
.read<GridHeaderBloc>()
|
||||||
|
.add(GridHeaderEvent.startEditingNewField(fieldId)),
|
||||||
|
onEditorOpened: () => context
|
||||||
|
.read<GridHeaderBloc>()
|
||||||
|
.add(const GridHeaderEvent.endEditingField()),
|
||||||
|
isEditing: state.editingFieldId == fieldInfo.id,
|
||||||
|
isNew: state.newFieldId == fieldInfo.id,
|
||||||
)
|
)
|
||||||
: MobileFieldButton(
|
: MobileFieldButton(
|
||||||
key: _getKeyById(fieldInfo.id),
|
key: _getKeyById(fieldInfo.id),
|
||||||
@ -184,38 +191,34 @@ class _CellTrailing extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
padding: GridSize.headerContentInsets,
|
padding: GridSize.headerContentInsets,
|
||||||
child: CreateFieldButton(viewId: viewId),
|
child: CreateFieldButton(
|
||||||
|
viewId: viewId,
|
||||||
|
onFieldCreated: (fieldId) => context
|
||||||
|
.read<GridHeaderBloc>()
|
||||||
|
.add(GridHeaderEvent.startEditingNewField(fieldId)),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateFieldButton extends StatefulWidget {
|
class CreateFieldButton extends StatefulWidget {
|
||||||
final String viewId;
|
|
||||||
const CreateFieldButton({
|
const CreateFieldButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
|
required this.onFieldCreated,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String viewId;
|
||||||
|
final void Function(String fieldId) onFieldCreated;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CreateFieldButton> createState() => _CreateFieldButtonState();
|
State<CreateFieldButton> createState() => _CreateFieldButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CreateFieldButtonState extends State<CreateFieldButton> {
|
class _CreateFieldButtonState extends State<CreateFieldButton> {
|
||||||
final popoverController = PopoverController();
|
|
||||||
late TypeOptionPB typeOption;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final fieldController =
|
return FlowyButton(
|
||||||
context.read<GridBloc>().databaseController.fieldController;
|
|
||||||
return AppFlowyPopover(
|
|
||||||
controller: popoverController,
|
|
||||||
direction: PopoverDirection.bottomWithRightAligned,
|
|
||||||
asBarrier: true,
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
constraints: BoxConstraints.loose(const Size(240, 600)),
|
|
||||||
triggerActions: PopoverTriggerFlags.none,
|
|
||||||
child: FlowyButton(
|
|
||||||
margin: PlatformExtension.isDesktop
|
margin: PlatformExtension.isDesktop
|
||||||
? GridSize.cellContentInsets
|
? GridSize.cellContentInsets
|
||||||
: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
@ -223,8 +226,7 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
|||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
LocaleKeys.grid_field_newProperty.tr(),
|
LocaleKeys.grid_field_newProperty.tr(),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
color:
|
color: PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
|
||||||
PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
|
|
||||||
),
|
),
|
||||||
hoverColor: AFThemeExtension.of(context).greyHover,
|
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@ -232,26 +234,14 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
|||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
);
|
);
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) {
|
(typeOptionPB) => widget.onFieldCreated(typeOptionPB.field_2.id),
|
||||||
typeOption = l;
|
(err) => Log.error("Failed to create field type option: $err"),
|
||||||
popoverController.show();
|
|
||||||
},
|
|
||||||
(r) => Log.error("Failed to create field type option: $r"),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
leftIcon: FlowySvg(
|
leftIcon: FlowySvg(
|
||||||
FlowySvgs.add_s,
|
FlowySvgs.add_s,
|
||||||
color:
|
color: PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
|
||||||
PlatformExtension.isDesktop ? null : Theme.of(context).hintColor,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
popupBuilder: (BuildContext popoverContext) {
|
|
||||||
return FieldEditor(
|
|
||||||
viewId: widget.viewId,
|
|
||||||
fieldController: fieldController,
|
|
||||||
field: typeOption.field_2,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
18
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -880,7 +880,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -900,7 +900,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -930,7 +930,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -942,7 +942,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -962,7 +962,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -976,7 +976,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1018,7 +1018,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1067,7 +1067,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
|
@ -67,15 +67,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "509
|
|||||||
# 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 = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
DatabaseEventMoveField,
|
DatabaseEventMoveField,
|
||||||
DatabaseEventGetFields,
|
DatabaseEventGetFields,
|
||||||
DatabaseEventDeleteField,
|
DatabaseEventDeleteField,
|
||||||
DatabaseEventCreateTypeOption,
|
DatabaseEventCreateField,
|
||||||
DatabaseEventUpdateFieldSettings,
|
DatabaseEventUpdateFieldSettings,
|
||||||
DatabaseEventGetAllFieldSettings,
|
DatabaseEventGetAllFieldSettings,
|
||||||
} from '@/services/backend/events/flowy-database2';
|
} from '@/services/backend/events/flowy-database2';
|
||||||
@ -73,7 +73,7 @@ export async function createField(viewId: string, fieldType?: FieldType, data?:
|
|||||||
type_option_data: data,
|
type_option_data: data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await DatabaseEventCreateTypeOption(payload);
|
const result = await DatabaseEventCreateField(payload);
|
||||||
|
|
||||||
if (result.ok === false) {
|
if (result.ok === false) {
|
||||||
return Promise.reject('Failed to create field');
|
return Promise.reject('Failed to create field');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CreateFieldPayloadPB, FieldType, TypeOptionPathPB, UpdateFieldTypePayloadPB } from '@/services/backend';
|
import { CreateFieldPayloadPB, FieldType, TypeOptionPathPB, UpdateFieldTypePayloadPB } from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
DatabaseEventCreateTypeOption,
|
DatabaseEventCreateField,
|
||||||
DatabaseEventGetTypeOption,
|
DatabaseEventGetTypeOption,
|
||||||
DatabaseEventUpdateFieldType,
|
DatabaseEventUpdateFieldType,
|
||||||
} from '@/services/backend/events/flowy-database2';
|
} from '@/services/backend/events/flowy-database2';
|
||||||
@ -11,7 +11,7 @@ export class TypeOptionBackendService {
|
|||||||
createTypeOption = (fieldType: FieldType) => {
|
createTypeOption = (fieldType: FieldType) => {
|
||||||
const payload = CreateFieldPayloadPB.fromObject({ view_id: this.viewId, field_type: fieldType });
|
const payload = CreateFieldPayloadPB.fromObject({ view_id: this.viewId, field_type: fieldType });
|
||||||
|
|
||||||
return DatabaseEventCreateTypeOption(payload);
|
return DatabaseEventCreateField(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
getTypeOption = (fieldId: string, fieldType: FieldType) => {
|
getTypeOption = (fieldId: string, fieldType: FieldType) => {
|
||||||
|
3
frontend/resources/flowy_icons/16x/arrow.svg
Normal file
3
frontend/resources/flowy_icons/16x/arrow.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 5.5L3.5 8.5M3.5 8.5L6 11.5M3.5 8.5H12.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 222 B |
18
frontend/rust-lib/Cargo.lock
generated
18
frontend/rust-lib/Cargo.lock
generated
@ -730,7 +730,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -750,7 +750,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -780,7 +780,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -792,7 +792,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -812,7 +812,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -826,7 +826,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -868,7 +868,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -917,7 +917,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=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a462a49facbf3682717c3074b14fd29f19276e28#a462a49facbf3682717c3074b14fd29f19276e28"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
|
@ -109,11 +109,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "509
|
|||||||
# 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 = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
|
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a462a49facbf3682717c3074b14fd29f19276e28" }
|
||||||
|
@ -112,11 +112,11 @@ impl EventIntegrationTest {
|
|||||||
|
|
||||||
pub async fn create_field(&self, view_id: &str, field_type: FieldType) -> FieldPB {
|
pub async fn create_field(&self, view_id: &str, field_type: FieldType) -> FieldPB {
|
||||||
EventBuilder::new(self.clone())
|
EventBuilder::new(self.clone())
|
||||||
.event(DatabaseEvent::CreateTypeOption)
|
.event(DatabaseEvent::CreateField)
|
||||||
.payload(CreateFieldPayloadPB {
|
.payload(CreateFieldPayloadPB {
|
||||||
view_id: view_id.to_string(),
|
view_id: view_id.to_string(),
|
||||||
field_type,
|
field_type,
|
||||||
type_option_data: None,
|
..Default::default()
|
||||||
})
|
})
|
||||||
.async_send()
|
.async_send()
|
||||||
.await
|
.await
|
||||||
|
@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::views::FieldOrder;
|
use collab_database::views::{FieldOrder, OrderObjectPosition};
|
||||||
use serde_repr::*;
|
use serde_repr::*;
|
||||||
use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
|
use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
|
||||||
|
|
||||||
@ -155,30 +155,94 @@ pub struct CreateFieldPayloadPB {
|
|||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub field_type: FieldType,
|
pub field_type: FieldType,
|
||||||
|
|
||||||
|
#[pb(index = 3, one_of)]
|
||||||
|
pub field_name: Option<String>,
|
||||||
|
|
||||||
/// If the type_option_data is not empty, it will be used to create the field.
|
/// If the type_option_data is not empty, it will be used to create the field.
|
||||||
/// Otherwise, the default value will be used.
|
/// Otherwise, the default value will be used.
|
||||||
#[pb(index = 3, one_of)]
|
#[pb(index = 4, one_of)]
|
||||||
pub type_option_data: Option<Vec<u8>>,
|
pub type_option_data: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
#[pb(index = 5)]
|
||||||
|
pub field_position: CreateFieldPosition,
|
||||||
|
|
||||||
|
#[pb(index = 6, one_of)]
|
||||||
|
pub target_field_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, ProtoBuf_Enum)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum CreateFieldPosition {
|
||||||
|
#[default]
|
||||||
|
End = 0,
|
||||||
|
Start = 1,
|
||||||
|
Before = 2,
|
||||||
|
After = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CreateFieldParams {
|
pub struct CreateFieldParams {
|
||||||
pub view_id: String,
|
pub view_id: String,
|
||||||
|
pub field_name: Option<String>,
|
||||||
pub field_type: FieldType,
|
pub field_type: FieldType,
|
||||||
/// If the type_option_data is not empty, it will be used to create the field.
|
|
||||||
/// Otherwise, the default value will be used.
|
|
||||||
pub type_option_data: Option<Vec<u8>>,
|
pub type_option_data: Option<Vec<u8>>,
|
||||||
|
pub position: OrderObjectPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
|
impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
|
||||||
type Error = ErrorCode;
|
type Error = ErrorCode;
|
||||||
|
|
||||||
fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
|
fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
|
||||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
|
||||||
|
|
||||||
|
let field_name = match self.field_name {
|
||||||
|
Some(name) => Some(
|
||||||
|
NotEmptyStr::parse(name)
|
||||||
|
.map_err(|_| ErrorCode::InvalidParams)?
|
||||||
|
.0,
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let position = match &self.field_position {
|
||||||
|
CreateFieldPosition::Start => {
|
||||||
|
if self.target_field_id.is_some() {
|
||||||
|
return Err(ErrorCode::InvalidParams);
|
||||||
|
}
|
||||||
|
OrderObjectPosition::Start
|
||||||
|
},
|
||||||
|
CreateFieldPosition::End => {
|
||||||
|
if self.target_field_id.is_some() {
|
||||||
|
return Err(ErrorCode::InvalidParams);
|
||||||
|
}
|
||||||
|
OrderObjectPosition::End
|
||||||
|
},
|
||||||
|
CreateFieldPosition::Before => {
|
||||||
|
let field_id = self
|
||||||
|
.target_field_id
|
||||||
|
.ok_or_else(|| 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_else(|| 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,
|
||||||
|
field_name,
|
||||||
field_type: self.field_type,
|
field_type: self.field_type,
|
||||||
type_option_data: self.type_option_data,
|
type_option_data: self.type_option_data,
|
||||||
|
position,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -471,6 +535,7 @@ pub struct FieldChangesetParams {
|
|||||||
/// it would be better to append it to the end of the list.
|
/// it would be better to append it to the end of the list.
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
|
Copy,
|
||||||
Clone,
|
Clone,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
Hash,
|
Hash,
|
||||||
|
@ -2,6 +2,7 @@ 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};
|
||||||
@ -319,16 +320,14 @@ pub(crate) async fn get_field_type_option_data_handler(
|
|||||||
|
|
||||||
/// Create TypeOptionPB and save it. Return the FieldTypeOptionData.
|
/// Create TypeOptionPB and save it. Return the FieldTypeOptionData.
|
||||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||||
pub(crate) async fn create_field_type_option_data_handler(
|
pub(crate) async fn create_field_handler(
|
||||||
data: AFPluginData<CreateFieldPayloadPB>,
|
data: AFPluginData<CreateFieldPayloadPB>,
|
||||||
manager: AFPluginState<Weak<DatabaseManager>>,
|
manager: AFPluginState<Weak<DatabaseManager>>,
|
||||||
) -> DataResult<TypeOptionPB, FlowyError> {
|
) -> DataResult<TypeOptionPB, FlowyError> {
|
||||||
let manager = upgrade_manager(manager)?;
|
let manager = upgrade_manager(manager)?;
|
||||||
let params: CreateFieldParams = data.into_inner().try_into()?;
|
let params: CreateFieldParams = data.into_inner().try_into()?;
|
||||||
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?;
|
||||||
let (field, data) = database_editor
|
let (field, data) = database_editor.create_field_with_type_option(¶ms).await;
|
||||||
.create_field_with_type_option(¶ms.view_id, ¶ms.field_type, params.type_option_data)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let data = TypeOptionPB {
|
let data = TypeOptionPB {
|
||||||
view_id: params.view_id,
|
view_id: params.view_id,
|
||||||
@ -449,12 +448,16 @@ 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,
|
||||||
prev_row_id: params.start_row_id,
|
row_position: position,
|
||||||
timestamp: timestamp(),
|
timestamp: timestamp(),
|
||||||
};
|
};
|
||||||
match database_editor
|
match database_editor
|
||||||
|
@ -31,7 +31,7 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
|
|||||||
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
|
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
|
||||||
.event(DatabaseEvent::MoveField, move_field_handler)
|
.event(DatabaseEvent::MoveField, move_field_handler)
|
||||||
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
|
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
|
||||||
.event(DatabaseEvent::CreateTypeOption, create_field_type_option_data_handler)
|
.event(DatabaseEvent::CreateField, create_field_handler)
|
||||||
// Row
|
// Row
|
||||||
.event(DatabaseEvent::CreateRow, create_row_handler)
|
.event(DatabaseEvent::CreateRow, create_row_handler)
|
||||||
.event(DatabaseEvent::GetRow, get_row_handler)
|
.event(DatabaseEvent::GetRow, get_row_handler)
|
||||||
@ -182,9 +182,10 @@ pub enum DatabaseEvent {
|
|||||||
#[event(input = "TypeOptionPathPB", output = "TypeOptionPB")]
|
#[event(input = "TypeOptionPathPB", output = "TypeOptionPB")]
|
||||||
GetTypeOption = 23,
|
GetTypeOption = 23,
|
||||||
|
|
||||||
/// [CreateTypeOption] event is used to create a new FieldTypeOptionData.
|
/// [CreateField] event is used to create a new field with an optional
|
||||||
|
/// TypeOptionData.
|
||||||
#[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")]
|
#[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")]
|
||||||
CreateTypeOption = 24,
|
CreateField = 24,
|
||||||
|
|
||||||
#[event(input = "DatabaseViewIdPB", output = "FieldPB")]
|
#[event(input = "DatabaseViewIdPB", output = "FieldPB")]
|
||||||
GetPrimaryField = 25,
|
GetPrimaryField = 25,
|
||||||
|
@ -5,7 +5,7 @@ use bytes::Bytes;
|
|||||||
use collab_database::database::MutexDatabase;
|
use collab_database::database::MutexDatabase;
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
|
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
|
||||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting, OrderObjectPosition};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use tokio::sync::{broadcast, RwLock};
|
use tokio::sync::{broadcast, RwLock};
|
||||||
use tracing::{event, warn};
|
use tracing::{event, warn};
|
||||||
@ -470,25 +470,26 @@ impl DatabaseEditor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_field_with_type_option(
|
pub async fn create_field_with_type_option(&self, params: &CreateFieldParams) -> (Field, Bytes) {
|
||||||
&self,
|
let name = params
|
||||||
view_id: &str,
|
.field_name
|
||||||
field_type: &FieldType,
|
.clone()
|
||||||
type_option_data: Option<Vec<u8>>,
|
.unwrap_or_else(|| params.field_type.default_name());
|
||||||
) -> (Field, Bytes) {
|
let type_option_data = match ¶ms.type_option_data {
|
||||||
let name = field_type.default_name();
|
None => default_type_option_data_from_type(¶ms.field_type),
|
||||||
let type_option_data = match type_option_data {
|
Some(type_option_data) => {
|
||||||
None => default_type_option_data_from_type(field_type),
|
type_option_data_from_pb_or_default(type_option_data.clone(), ¶ms.field_type)
|
||||||
Some(type_option_data) => type_option_data_from_pb_or_default(type_option_data, field_type),
|
},
|
||||||
};
|
};
|
||||||
let (index, field) = self.database.lock().create_field_with_mut(
|
let (index, field) = self.database.lock().create_field_with_mut(
|
||||||
view_id,
|
¶ms.view_id,
|
||||||
name,
|
name,
|
||||||
field_type.into(),
|
params.field_type.into(),
|
||||||
|
¶ms.position,
|
||||||
|field| {
|
|field| {
|
||||||
field
|
field
|
||||||
.type_options
|
.type_options
|
||||||
.insert(field_type.to_string(), type_option_data.clone());
|
.insert(params.field_type.to_string(), type_option_data.clone());
|
||||||
},
|
},
|
||||||
default_field_settings_by_layout_map(),
|
default_field_settings_by_layout_map(),
|
||||||
);
|
);
|
||||||
@ -497,7 +498,10 @@ impl DatabaseEditor {
|
|||||||
.notify_did_insert_database_field(field.clone(), index)
|
.notify_did_insert_database_field(field.clone(), index)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
(field, type_option_to_pb(type_option_data, field_type))
|
(
|
||||||
|
field,
|
||||||
|
type_option_to_pb(type_option_data, ¶ms.field_type),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn move_field(
|
pub async fn move_field(
|
||||||
@ -1223,6 +1227,7 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
|
|||||||
view_id,
|
view_id,
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
field_type.clone().into(),
|
field_type.clone().into(),
|
||||||
|
&OrderObjectPosition::default(),
|
||||||
|field| {
|
|field| {
|
||||||
field
|
field
|
||||||
.type_options
|
.type_options
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use collab_database::database::{gen_field_id, MutexDatabase};
|
use collab_database::database::{gen_field_id, MutexDatabase};
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::views::{DatabaseLayout, LayoutSetting};
|
use collab_database::views::{DatabaseLayout, LayoutSetting, OrderObjectPosition};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
@ -87,10 +87,12 @@ impl DatabaseLayoutDepsResolver {
|
|||||||
tracing::trace!("Create a new date field after layout type change");
|
tracing::trace!("Create a new date field after layout type change");
|
||||||
let field = self.create_date_field();
|
let field = self.create_date_field();
|
||||||
let field_id = field.id.clone();
|
let field_id = field.id.clone();
|
||||||
self
|
self.database.lock().create_field(
|
||||||
.database
|
None,
|
||||||
.lock()
|
field,
|
||||||
.create_field(field, default_field_settings_by_layout_map());
|
&OrderObjectPosition::End,
|
||||||
|
default_field_settings_by_layout_map(),
|
||||||
|
);
|
||||||
field_id
|
field_id
|
||||||
},
|
},
|
||||||
Some(date_field) => date_field.id,
|
Some(date_field) => date_field.id,
|
||||||
|
@ -4,6 +4,7 @@ use std::sync::Arc;
|
|||||||
use collab_database::database::{gen_database_view_id, timestamp};
|
use collab_database::database::{gen_database_view_id, timestamp};
|
||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
use collab_database::rows::{CreateRowParams, RowDetail, RowId};
|
use collab_database::rows::{CreateRowParams, RowDetail, RowId};
|
||||||
|
use collab_database::views::OrderObjectPosition;
|
||||||
use strum::EnumCount;
|
use strum::EnumCount;
|
||||||
|
|
||||||
use event_integration::folder_event::ViewTest;
|
use event_integration::folder_event::ViewTest;
|
||||||
@ -410,7 +411,7 @@ impl<'a> TestRowBuilder<'a> {
|
|||||||
cells: self.cell_build.build(),
|
cells: self.cell_build.build(),
|
||||||
height: 60,
|
height: 60,
|
||||||
visibility: true,
|
visibility: true,
|
||||||
prev_row_id: None,
|
row_position: OrderObjectPosition::End,
|
||||||
timestamp: timestamp(),
|
timestamp: timestamp(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,10 +64,7 @@ impl DatabaseFieldTest {
|
|||||||
match script {
|
match script {
|
||||||
FieldScript::CreateField { params } => {
|
FieldScript::CreateField { params } => {
|
||||||
self.field_count += 1;
|
self.field_count += 1;
|
||||||
self
|
self.editor.create_field_with_type_option(¶ms).await;
|
||||||
.editor
|
|
||||||
.create_field_with_type_option(&self.view_id, ¶ms.field_type, params.type_option_data)
|
|
||||||
.await;
|
|
||||||
let fields = self.editor.get_fields(&self.view_id, None);
|
let fields = self.editor.get_fields(&self.view_id, None);
|
||||||
assert_eq!(self.field_count, fields.len());
|
assert_eq!(self.field_count, fields.len());
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use collab_database::fields::Field;
|
use collab_database::fields::Field;
|
||||||
|
use collab_database::views::OrderObjectPosition;
|
||||||
use flowy_database2::entities::{CreateFieldParams, FieldType};
|
use flowy_database2::entities::{CreateFieldParams, FieldType};
|
||||||
use flowy_database2::services::field::{
|
use flowy_database2::services::field::{
|
||||||
type_option_to_pb, DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder,
|
type_option_to_pb, DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder,
|
||||||
@ -19,6 +20,8 @@ pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, Field) {
|
|||||||
view_id: grid_id.to_owned(),
|
view_id: grid_id.to_owned(),
|
||||||
field_type,
|
field_type,
|
||||||
type_option_data: Some(type_option_data),
|
type_option_data: Some(type_option_data),
|
||||||
|
field_name: None,
|
||||||
|
position: OrderObjectPosition::default(),
|
||||||
};
|
};
|
||||||
(params, text_field)
|
(params, text_field)
|
||||||
}
|
}
|
||||||
@ -38,6 +41,8 @@ pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, Field) {
|
|||||||
view_id: grid_id.to_owned(),
|
view_id: grid_id.to_owned(),
|
||||||
field_type,
|
field_type,
|
||||||
type_option_data: Some(type_option_data),
|
type_option_data: Some(type_option_data),
|
||||||
|
field_name: None,
|
||||||
|
position: OrderObjectPosition::default(),
|
||||||
};
|
};
|
||||||
(params, single_select_field)
|
(params, single_select_field)
|
||||||
}
|
}
|
||||||
@ -60,6 +65,8 @@ pub fn create_date_field(grid_id: &str) -> (CreateFieldParams, Field) {
|
|||||||
view_id: grid_id.to_owned(),
|
view_id: grid_id.to_owned(),
|
||||||
field_type: FieldType::DateTime,
|
field_type: FieldType::DateTime,
|
||||||
type_option_data: Some(type_option_data),
|
type_option_data: Some(type_option_data),
|
||||||
|
field_name: None,
|
||||||
|
position: OrderObjectPosition::default(),
|
||||||
};
|
};
|
||||||
(params, field)
|
(params, field)
|
||||||
}
|
}
|
||||||
@ -92,6 +99,8 @@ pub fn create_timestamp_field(grid_id: &str, field_type: FieldType) -> (CreateFi
|
|||||||
view_id: grid_id.to_owned(),
|
view_id: grid_id.to_owned(),
|
||||||
field_type,
|
field_type,
|
||||||
type_option_data: Some(type_option_data),
|
type_option_data: Some(type_option_data),
|
||||||
|
field_name: None,
|
||||||
|
position: OrderObjectPosition::default(),
|
||||||
};
|
};
|
||||||
(params, field)
|
(params, field)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use chrono::{offset, Duration};
|
|||||||
use collab_database::database::gen_row_id;
|
use collab_database::database::gen_row_id;
|
||||||
use collab_database::rows::CreateRowParams;
|
use collab_database::rows::CreateRowParams;
|
||||||
|
|
||||||
|
use collab_database::views::OrderObjectPosition;
|
||||||
use flowy_database2::entities::FieldType;
|
use flowy_database2::entities::FieldType;
|
||||||
use flowy_database2::services::cell::CellBuilder;
|
use flowy_database2::services::cell::CellBuilder;
|
||||||
use flowy_database2::services::field::DateCellData;
|
use flowy_database2::services::field::DateCellData;
|
||||||
@ -34,7 +35,7 @@ async fn group_by_date_test() {
|
|||||||
cells,
|
cells,
|
||||||
height: 60,
|
height: 60,
|
||||||
visibility: true,
|
visibility: true,
|
||||||
prev_row_id: None,
|
row_position: OrderObjectPosition::default(),
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
};
|
};
|
||||||
let res = test.editor.create_row(&test.view_id, None, params).await;
|
let res = test.editor.create_row(&test.view_id, None, params).await;
|
||||||
|
Reference in New Issue
Block a user