diff --git a/frontend/appflowy_flutter/assets/images/grid/setting/database_layout.svg b/frontend/appflowy_flutter/assets/images/grid/setting/database_layout.svg new file mode 100644 index 0000000000..e1bf39190a --- /dev/null +++ b/frontend/appflowy_flutter/assets/images/grid/setting/database_layout.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/appflowy_flutter/assets/translations/en.json b/frontend/appflowy_flutter/assets/translations/en.json index 5349359a63..8518f7a4e2 100644 --- a/frontend/appflowy_flutter/assets/translations/en.json +++ b/frontend/appflowy_flutter/assets/translations/en.json @@ -231,8 +231,9 @@ "addFilter": "Add Filter", "deleteFilter": "Delete filter", "filterBy": "Filter by...", - "typeAValue": "Type a value...", - "layout": "Layout" + "typeAValue": "Type a value...", + "layout": "Layout", + "databaseLayout": "Layout" }, "textFilter": { "contains": "Contains", @@ -439,7 +440,8 @@ "firstDayOfWeek": "Start week on", "layoutDateField": "Layout calendar by", "noDateTitle": "No Date", - "noDateHint": "Unscheduled events will show up here" + "noDateHint": "Unscheduled events will show up here", + "clickToAdd": "Click to add to the calendar" } } } \ No newline at end of file diff --git a/frontend/appflowy_flutter/lib/plugins/blank/blank.dart b/frontend/appflowy_flutter/lib/plugins/blank/blank.dart index ff3c081403..385f6d52a2 100644 --- a/frontend/appflowy_flutter/lib/plugins/blank/blank.dart +++ b/frontend/appflowy_flutter/lib/plugins/blank/blank.dart @@ -28,16 +28,17 @@ class BlankPluginConfig implements PluginConfig { class BlankPagePlugin extends Plugin { @override - PluginDisplay get display => BlankPagePluginDisplay(); + PluginWidgetBuilder get widgetBuilder => BlankPagePluginWidgetBuilder(); @override PluginId get id => "BlankStack"; @override - PluginType get ty => PluginType.blank; + PluginType get pluginType => PluginType.blank; } -class BlankPagePluginDisplay extends PluginDisplay with NavigationItem { +class BlankPagePluginWidgetBuilder extends PluginWidgetBuilder + with NavigationItem { @override Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr()); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart index 546386dfe9..a628bae9b8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart @@ -39,18 +39,18 @@ class GroupCallbacks { }); } -class LayoutCallbacks { - final void Function(LayoutSettingPB) onLayoutChanged; - final void Function(LayoutSettingPB) onLoadLayout; +class DatabaseLayoutSettingCallbacks { + final void Function(DatabaseLayoutSettingPB) onLayoutChanged; + final void Function(DatabaseLayoutSettingPB) onLoadLayout; - LayoutCallbacks({ + DatabaseLayoutSettingCallbacks({ required this.onLayoutChanged, required this.onLoadLayout, }); } class CalendarLayoutCallbacks { - final void Function(LayoutSettingPB) onCalendarLayoutChanged; + final void Function(DatabaseLayoutSettingPB) onCalendarLayoutChanged; CalendarLayoutCallbacks({required this.onCalendarLayoutChanged}); } @@ -59,14 +59,14 @@ class DatabaseCallbacks { OnDatabaseChanged? onDatabaseChanged; OnFieldsChanged? onFieldsChanged; OnFiltersChanged? onFiltersChanged; - OnRowsChanged? onRowsChanged; + OnNumOfRowsChanged? onNumOfRowsChanged; OnRowsDeleted? onRowsDeleted; OnRowsUpdated? onRowsUpdated; OnRowsCreated? onRowsCreated; DatabaseCallbacks({ this.onDatabaseChanged, - this.onRowsChanged, + this.onNumOfRowsChanged, this.onFieldsChanged, this.onFiltersChanged, this.onRowsUpdated, @@ -79,13 +79,13 @@ class DatabaseController { final String viewId; final DatabaseViewBackendService _databaseViewBackendSvc; final FieldController fieldController; + DatabaseLayoutPB? databaseLayout; late DatabaseViewCache _viewCache; - final DatabaseLayoutPB layoutType; // Callbacks DatabaseCallbacks? _databaseCallbacks; GroupCallbacks? _groupCallbacks; - LayoutCallbacks? _layoutCallbacks; + DatabaseLayoutSettingCallbacks? _layoutCallbacks; CalendarLayoutCallbacks? _calendarLayoutCallbacks; // Getters @@ -93,15 +93,15 @@ class DatabaseController { // Listener final DatabaseGroupListener groupListener; - final DatabaseLayoutListener layoutListener; + final DatabaseLayoutSettingListener layoutListener; final DatabaseCalendarLayoutListener calendarLayoutListener; - DatabaseController({required ViewPB view, required this.layoutType}) + DatabaseController({required ViewPB view}) : viewId = view.id, _databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id), fieldController = FieldController(viewId: view.id), groupListener = DatabaseGroupListener(view.id), - layoutListener = DatabaseLayoutListener(view.id), + layoutListener = DatabaseLayoutSettingListener(view.id), calendarLayoutListener = DatabaseCalendarLayoutListener(view.id) { _viewCache = DatabaseViewCache( viewId: viewId, @@ -111,14 +111,11 @@ class DatabaseController { _listenOnFieldsChanged(); _listenOnGroupChanged(); _listenOnLayoutChanged(); - if (layoutType == DatabaseLayoutPB.Calendar) { - _listenOnCalendarLayoutChanged(); - } } void setListener({ DatabaseCallbacks? onDatabaseChanged, - LayoutCallbacks? onLayoutChanged, + DatabaseLayoutSettingCallbacks? onLayoutChanged, GroupCallbacks? onGroupChanged, CalendarLayoutCallbacks? onCalendarLayoutChanged, }) { @@ -132,6 +129,12 @@ class DatabaseController { return _databaseViewBackendSvc.openGrid().then((result) { return result.fold( (database) async { + databaseLayout = database.layoutType; + + if (databaseLayout == DatabaseLayoutPB.Calendar) { + _listenOnCalendarLayoutChanged(); + } + _databaseCallbacks?.onDatabaseChanged?.call(database); _viewCache.rowCache.setInitialRows(database.rows); return await fieldController @@ -242,20 +245,20 @@ class DatabaseController { } Future _loadLayoutSetting() async { - _databaseViewBackendSvc.getLayoutSetting(layoutType).then((result) { - result.fold( - (l) { - _layoutCallbacks?.onLoadLayout(l); - }, - (r) => Log.error(r), - ); - }); + if (databaseLayout != null) { + _databaseViewBackendSvc.getLayoutSetting(databaseLayout!).then((result) { + result.fold( + (l) => _layoutCallbacks?.onLoadLayout(l), + (r) => Log.error(r), + ); + }); + } } void _listenOnRowsChanged() { final callbacks = DatabaseViewCallbacks( - onRowsChanged: (rows, rowByRowId, reason) { - _databaseCallbacks?.onRowsChanged?.call(rows, rowByRowId, reason); + onNumOfRowsChanged: (rows, rowByRowId, reason) { + _databaseCallbacks?.onNumOfRowsChanged?.call(rows, rowByRowId, reason); }, onRowsDeleted: (ids) { _databaseCallbacks?.onRowsDeleted?.call(ids); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_view_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_view_service.dart index 0925190a0a..cff62ead7f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_view_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_view_service.dart @@ -97,10 +97,10 @@ class DatabaseViewBackendService { }); } - Future> getLayoutSetting( + Future> getLayoutSetting( DatabaseLayoutPB layoutType, ) { - final payload = DatabaseLayoutIdPB.create() + final payload = DatabaseLayoutMetaPB.create() ..viewId = viewId ..layout = layoutType; return DatabaseEventGetLayoutSetting(payload).send(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/defines.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/defines.dart index 526ae7a257..64bf27538c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/defines.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/defines.dart @@ -15,7 +15,7 @@ typedef OnDatabaseChanged = void Function(DatabasePB); typedef OnRowsCreated = void Function(List ids); typedef OnRowsUpdated = void Function(List ids); typedef OnRowsDeleted = void Function(List ids); -typedef OnRowsChanged = void Function( +typedef OnNumOfRowsChanged = void Function( UnmodifiableListView rows, UnmodifiableMapView rowByRowId, RowsChangedReason reason, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/calendar_setting_listener.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/calendar_setting_listener.dart index 11c729b30c..57e2c112a0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/calendar_setting_listener.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/calendar_setting_listener.dart @@ -6,7 +6,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:dartz/dartz.dart'; -typedef NewLayoutFieldValue = Either; +typedef NewLayoutFieldValue = Either; class DatabaseCalendarLayoutListener { final String viewId; @@ -33,7 +33,7 @@ class DatabaseCalendarLayoutListener { case DatabaseNotification.DidSetNewLayoutField: result.fold( (payload) => _newLayoutFieldNotifier?.value = - left(LayoutSettingPB.fromBuffer(payload)), + left(DatabaseLayoutSettingPB.fromBuffer(payload)), (error) => _newLayoutFieldNotifier?.value = right(error), ); break; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_bloc.dart new file mode 100644 index 0000000000..1098a39f59 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_bloc.dart @@ -0,0 +1,56 @@ +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'layout_service.dart'; +part 'layout_bloc.freezed.dart'; + +class DatabaseLayoutBloc + extends Bloc { + final DatabaseLayoutBackendService layoutService; + + DatabaseLayoutBloc({ + required String viewId, + required DatabaseLayoutPB databaseLayout, + }) : layoutService = DatabaseLayoutBackendService(viewId), + super(DatabaseLayoutState.initial(viewId, databaseLayout)) { + on( + (event, emit) async { + event.when( + initial: () {}, + updateLayout: (DatabaseLayoutPB layout) { + layoutService.updateLayout( + fieldId: viewId, + layout: layout, + ); + emit(state.copyWith(databaseLayout: layout)); + }, + ); + }, + ); + } +} + +@freezed +class DatabaseLayoutEvent with _$DatabaseLayoutEvent { + const factory DatabaseLayoutEvent.initial() = _Initial; + const factory DatabaseLayoutEvent.updateLayout(DatabaseLayoutPB layout) = + _UpdateLayout; +} + +@freezed +class DatabaseLayoutState with _$DatabaseLayoutState { + const factory DatabaseLayoutState({ + required String viewId, + required DatabaseLayoutPB databaseLayout, + }) = _DatabaseLayoutState; + + factory DatabaseLayoutState.initial( + String viewId, + DatabaseLayoutPB databaseLayout, + ) => + DatabaseLayoutState( + viewId: viewId, + databaseLayout: databaseLayout, + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_listener.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_listener.dart new file mode 100644 index 0000000000..c3683d921e --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_listener.dart @@ -0,0 +1,50 @@ +import 'dart:typed_data'; + +import 'package:appflowy/core/notification/grid_notification.dart'; +import 'package:flowy_infra/notifier.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:dartz/dartz.dart'; + +/// Listener for database layout changes. +class DatabaseLayoutListener { + final String viewId; + PublishNotifier>? _layoutNotifier = + PublishNotifier(); + DatabaseNotificationListener? _listener; + DatabaseLayoutListener(this.viewId); + + void start({ + required void Function(Either) + onLayoutChanged, + }) { + _layoutNotifier?.addPublishListener(onLayoutChanged); + _listener = DatabaseNotificationListener( + objectId: viewId, + handler: _handler, + ); + } + + void _handler( + DatabaseNotification ty, + Either result, + ) { + switch (ty) { + case DatabaseNotification.DidUpdateDatabaseLayout: + result.fold( + (payload) => _layoutNotifier?.value = + left(DatabaseLayoutMetaPB.fromBuffer(payload).layout), + (error) => _layoutNotifier?.value = right(error), + ); + break; + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _layoutNotifier?.dispose(); + _layoutNotifier = null; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_service.dart new file mode 100644 index 0000000000..9c635882a7 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_service.dart @@ -0,0 +1,35 @@ +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; +import 'package:dartz/dartz.dart'; + +class DatabaseLayoutBackendService { + final String viewId; + + DatabaseLayoutBackendService(this.viewId); + + Future> updateLayout({ + required String fieldId, + required DatabaseLayoutPB layout, + }) { + var payload = UpdateViewPayloadPB.create() + ..viewId = viewId + ..layout = _viewLayoutFromDatabaseLayout(layout); + + return FolderEventUpdateView(payload).send(); + } +} + +ViewLayoutPB _viewLayoutFromDatabaseLayout(DatabaseLayoutPB databaseLayout) { + switch (databaseLayout) { + case DatabaseLayoutPB.Board: + return ViewLayoutPB.Board; + case DatabaseLayoutPB.Calendar: + return ViewLayoutPB.Calendar; + case DatabaseLayoutPB.Grid: + return ViewLayoutPB.Grid; + default: + throw UnimplementedError; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_setting_listener.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_setting_listener.dart index f5497225fd..fc1553a573 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_setting_listener.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/layout/layout_setting_listener.dart @@ -8,15 +8,15 @@ import 'package:dartz/dartz.dart'; typedef LayoutSettingsValue = Either; -class DatabaseLayoutListener { +class DatabaseLayoutSettingListener { final String viewId; - PublishNotifier>? _settingNotifier = - PublishNotifier(); + PublishNotifier>? + _settingNotifier = PublishNotifier(); DatabaseNotificationListener? _listener; - DatabaseLayoutListener(this.viewId); + DatabaseLayoutSettingListener(this.viewId); void start({ - required void Function(LayoutSettingsValue) + required void Function(LayoutSettingsValue) onLayoutChanged, }) { _settingNotifier?.addPublishListener(onLayoutChanged); @@ -34,7 +34,7 @@ class DatabaseLayoutListener { case DatabaseNotification.DidUpdateLayoutSettings: result.fold( (payload) => _settingNotifier?.value = - left(LayoutSettingPB.fromBuffer(payload)), + left(DatabaseLayoutSettingPB.fromBuffer(payload)), (error) => _settingNotifier?.value = right(error), ); break; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/setting_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/setting_bloc.dart index b0e9595592..1fde8adfcf 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/setting_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/setting_bloc.dart @@ -1,3 +1,5 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:dartz/dartz.dart'; @@ -40,7 +42,31 @@ class DatabaseSettingState with _$DatabaseSettingState { } enum DatabaseSettingAction { - showFilters, - sortBy, showProperties, + showLayout, + showGroup, +} + +extension DatabaseSettingActionExtension on DatabaseSettingAction { + String iconName() { + switch (this) { + case DatabaseSettingAction.showProperties: + return 'grid/setting/properties'; + case DatabaseSettingAction.showLayout: + return 'grid/setting/database_layout'; + case DatabaseSettingAction.showGroup: + return 'grid/setting/group'; + } + } + + String title() { + switch (this) { + case DatabaseSettingAction.showProperties: + return LocaleKeys.grid_settings_Properties.tr(); + case DatabaseSettingAction.showLayout: + return LocaleKeys.grid_settings_databaseLayout.tr(); + case DatabaseSettingAction.showGroup: + return LocaleKeys.grid_settings_group.tr(); + } + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/view/view_cache.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/view/view_cache.dart index f9dc2f7b6b..70eb76a5d9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/view/view_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/view/view_cache.dart @@ -9,21 +9,21 @@ import 'view_listener.dart'; class DatabaseViewCallbacks { /// Will get called when number of rows were changed that includes - /// update/delete/insert rows. The [onRowsChanged] will return all + /// update/delete/insert rows. The [onNumOfRowsChanged] will return all /// the rows of the current database - final OnRowsChanged? onRowsChanged; + final OnNumOfRowsChanged? onNumOfRowsChanged; // Will get called when creating new rows final OnRowsCreated? onRowsCreated; - /// Will get called when number of rows were updated + /// Will get called when rows were updated final OnRowsUpdated? onRowsUpdated; /// Will get called when number of rows were deleted final OnRowsDeleted? onRowsDeleted; const DatabaseViewCallbacks({ - this.onRowsChanged, + this.onNumOfRowsChanged, this.onRowsCreated, this.onRowsUpdated, this.onRowsDeleted, @@ -101,7 +101,7 @@ class DatabaseViewCache { ); _rowCache.onRowsChanged( - (reason) => _callbacks?.onRowsChanged?.call( + (reason) => _callbacks?.onNumOfRowsChanged?.call( rowInfos, _rowCache.rowByRowId, reason, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart index a3f0717f63..dd7f053ecb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart @@ -20,18 +20,17 @@ import 'group_controller.dart'; part 'board_bloc.freezed.dart'; class BoardBloc extends Bloc { - final DatabaseController _databaseController; + final DatabaseController databaseController; late final AppFlowyBoardController boardController; final LinkedHashMap groupControllers = LinkedHashMap(); - FieldController get fieldController => _databaseController.fieldController; - String get viewId => _databaseController.viewId; + FieldController get fieldController => databaseController.fieldController; + String get viewId => databaseController.viewId; BoardBloc({required ViewPB view}) - : _databaseController = DatabaseController( + : databaseController = DatabaseController( view: view, - layoutType: DatabaseLayoutPB.Board, ), super(BoardState.initial(view.id)) { boardController = AppFlowyBoardController( @@ -41,7 +40,7 @@ class BoardBloc extends Bloc { toGroupId, toIndex, ) { - _databaseController.moveGroup( + databaseController.moveGroup( fromGroupId: fromGroupId, toGroupId: toGroupId, ); @@ -54,7 +53,7 @@ class BoardBloc extends Bloc { final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex); final toRow = groupControllers[groupId]?.rowAtIndex(toIndex); if (fromRow != null) { - _databaseController.moveGroupRow( + databaseController.moveGroupRow( fromRow: fromRow, toRow: toRow, groupId: groupId, @@ -70,7 +69,7 @@ class BoardBloc extends Bloc { final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex); final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex); if (fromRow != null) { - _databaseController.moveGroupRow( + databaseController.moveGroupRow( fromRow: fromRow, toRow: toRow, groupId: toGroupId, @@ -88,7 +87,7 @@ class BoardBloc extends Bloc { }, createBottomRow: (groupId) async { final startRowId = groupControllers[groupId]?.lastRow()?.id; - final result = await _databaseController.createRow( + final result = await databaseController.createRow( groupId: groupId, startRowId: startRowId, ); @@ -98,8 +97,7 @@ class BoardBloc extends Bloc { ); }, createHeaderRow: (String groupId) async { - final result = - await _databaseController.createRow(groupId: groupId); + final result = await databaseController.createRow(groupId: groupId); result.fold( (_) {}, (err) => Log.error(err), @@ -170,7 +168,7 @@ class BoardBloc extends Bloc { @override Future close() async { - await _databaseController.dispose(); + await databaseController.dispose(); for (final controller in groupControllers.values) { controller.dispose(); } @@ -198,7 +196,7 @@ class BoardBloc extends Bloc { } RowCache? getRowCache() { - return _databaseController.rowCache; + return databaseController.rowCache; } void _startListening() { @@ -237,7 +235,7 @@ class BoardBloc extends Bloc { }, ); - _databaseController.setListener( + databaseController.setListener( onDatabaseChanged: onDatabaseChanged, onGroupChanged: onGroupChanged, ); @@ -256,7 +254,7 @@ class BoardBloc extends Bloc { } Future _openGrid(Emitter emit) async { - final result = await _databaseController.open(); + final result = await databaseController.open(); result.fold( (grid) => emit( state.copyWith(loadingState: GridLoadingState.finish(left(unit))), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/board.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/board.dart index 15adee1187..05a583c792 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/board.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/board.dart @@ -49,18 +49,19 @@ class BoardPlugin extends Plugin { notifier = ViewPluginNotifier(view: view); @override - PluginDisplay get display => GridPluginDisplay(notifier: notifier); + PluginWidgetBuilder get widgetBuilder => + BoardPluginWidgetBuilder(notifier: notifier); @override PluginId get id => notifier.view.id; @override - PluginType get ty => _pluginType; + PluginType get pluginType => _pluginType; } -class GridPluginDisplay extends PluginDisplay { +class BoardPluginWidgetBuilder extends PluginWidgetBuilder { final ViewPluginNotifier notifier; - GridPluginDisplay({required this.notifier, Key? key}); + BoardPluginWidgetBuilder({required this.notifier, Key? key}); ViewPB get view => notifier.view; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart index 616c332d0b..deb02004d2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart @@ -340,15 +340,7 @@ class _ToolbarBlocAdaptor extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - builder: (context, state) { - final bloc = context.read(); - final toolbarContext = BoardToolbarContext( - viewId: bloc.viewId, - fieldController: bloc.fieldController, - ); - - return BoardToolbar(toolbarContext: toolbarContext); - }, + builder: (context, state) => const BoardToolbar(), ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_setting.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_setting.dart deleted file mode 100644 index 47c07c92a6..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_setting.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; -import 'package:appflowy/plugins/database_view/board/application/toolbar/board_setting_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_group.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/image.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_bloc/flutter_bloc.dart'; -import 'package:styled_widget/styled_widget.dart'; - -import 'board_toolbar.dart'; - -class BoardSettingContext { - final String viewId; - final FieldController fieldController; - BoardSettingContext({ - required this.viewId, - required this.fieldController, - }); - - factory BoardSettingContext.from(BoardToolbarContext toolbarContext) => - BoardSettingContext( - viewId: toolbarContext.viewId, - fieldController: toolbarContext.fieldController, - ); -} - -class BoardSettingList extends StatelessWidget { - final BoardSettingContext settingContext; - final Function(BoardSettingAction, BoardSettingContext) onAction; - const BoardSettingList({ - required this.settingContext, - required this.onAction, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => BoardSettingBloc(viewId: settingContext.viewId), - child: BlocListener( - listenWhen: (previous, current) => - previous.selectedAction != current.selectedAction, - listener: (context, state) { - state.selectedAction.foldLeft(null, (_, action) { - onAction(action, settingContext); - }); - }, - child: BlocBuilder( - builder: (context, state) { - return _renderList(); - }, - ), - ), - ); - } - - Widget _renderList() { - final cells = BoardSettingAction.values.map((action) { - return _SettingItem(action: action); - }).toList(); - - return SizedBox( - width: 140, - child: ListView.separated( - shrinkWrap: true, - controller: ScrollController(), - itemCount: cells.length, - separatorBuilder: (context, index) { - return VSpace(GridSize.typeOptionSeparatorHeight); - }, - physics: StyledScrollPhysics(), - itemBuilder: (BuildContext context, int index) { - return cells[index]; - }, - ), - ); - } -} - -class _SettingItem extends StatelessWidget { - final BoardSettingAction action; - - const _SettingItem({ - required this.action, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final isSelected = context - .read() - .state - .selectedAction - .foldLeft(false, (_, selectedAction) => selectedAction == action); - - return SizedBox( - height: 30, - child: FlowyButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - isSelected: isSelected, - text: FlowyText.medium( - action.title(), - color: AFThemeExtension.of(context).textColor, - ), - onTap: () { - context - .read() - .add(BoardSettingEvent.performAction(action)); - }, - leftIcon: svgWidget( - action.iconName(), - color: Theme.of(context).iconTheme.color, - ), - ), - ); - } -} - -extension _GridSettingExtension on BoardSettingAction { - String iconName() { - switch (this) { - case BoardSettingAction.properties: - return 'grid/setting/properties'; - case BoardSettingAction.groups: - return 'grid/setting/group'; - } - } - - String title() { - switch (this) { - case BoardSettingAction.properties: - return LocaleKeys.grid_settings_Properties.tr(); - case BoardSettingAction.groups: - return LocaleKeys.grid_settings_group.tr(); - } - } -} - -class BoardSettingListPopover extends StatefulWidget { - final PopoverController popoverController; - final BoardSettingContext settingContext; - - const BoardSettingListPopover({ - Key? key, - required this.popoverController, - required this.settingContext, - }) : super(key: key); - - @override - State createState() => _BoardSettingListPopoverState(); -} - -class _BoardSettingListPopoverState extends State { - BoardSettingAction? _action; - - @override - Widget build(BuildContext context) { - if (_action != null) { - switch (_action!) { - case BoardSettingAction.groups: - return GridGroupList( - viewId: widget.settingContext.viewId, - fieldController: widget.settingContext.fieldController, - onDismissed: () { - widget.popoverController.close(); - }, - ); - case BoardSettingAction.properties: - return GridPropertyList( - viewId: widget.settingContext.viewId, - fieldController: widget.settingContext.fieldController, - ); - } - } - - return BoardSettingList( - settingContext: widget.settingContext, - onAction: (action, settingContext) { - setState(() => _action = action); - }, - ).padding(all: 6.0); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart index c225b9f8b3..6e22a77ef6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/toolbar/board_toolbar.dart @@ -1,28 +1,10 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart'; +import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart'; import 'package:flutter/material.dart'; - -import 'board_setting.dart'; - -class BoardToolbarContext { - final String viewId; - final FieldController fieldController; - - BoardToolbarContext({ - required this.viewId, - required this.fieldController, - }); -} +import 'package:flutter_bloc/flutter_bloc.dart'; class BoardToolbar extends StatelessWidget { - final BoardToolbarContext toolbarContext; const BoardToolbar({ - required this.toolbarContext, Key? key, }) : super(key: key); @@ -33,58 +15,11 @@ class BoardToolbar extends StatelessWidget { child: Row( children: [ const Spacer(), - _SettingButton( - settingContext: BoardSettingContext.from(toolbarContext), + SettingButton( + databaseController: context.read().databaseController, ), ], ), ); } } - -class _SettingButton extends StatefulWidget { - final BoardSettingContext settingContext; - const _SettingButton({required this.settingContext, Key? key}) - : super(key: key); - - @override - State<_SettingButton> createState() => _SettingButtonState(); -} - -class _SettingButtonState extends State<_SettingButton> { - late PopoverController popoverController; - - @override - void initState() { - popoverController = PopoverController(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AppFlowyPopover( - controller: popoverController, - direction: PopoverDirection.leftWithTopAligned, - offset: const Offset(-8, 0), - triggerActions: PopoverTriggerFlags.none, - constraints: BoxConstraints.loose(const Size(260, 400)), - margin: EdgeInsets.zero, - child: FlowyTextButton( - LocaleKeys.settings_title.tr(), - fontColor: AFThemeExtension.of(context).textColor, - fillColor: Colors.transparent, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - padding: GridSize.typeOptionContentInsets, - onPressed: () { - popoverController.show(); - }, - ), - popupBuilder: (BuildContext popoverContext) { - return BoardSettingListPopover( - settingContext: widget.settingContext, - popoverController: popoverController, - ); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart index 151c26cb01..dadf2e3516 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart @@ -18,20 +18,17 @@ import '../../application/row/row_cache.dart'; part 'calendar_bloc.freezed.dart'; class CalendarBloc extends Bloc { - final DatabaseController _databaseController; + final DatabaseController databaseController; Map fieldInfoByFieldId = {}; // Getters - String get viewId => _databaseController.viewId; - FieldController get fieldController => _databaseController.fieldController; - CellCache get cellCache => _databaseController.rowCache.cellCache; - RowCache get rowCache => _databaseController.rowCache; + String get viewId => databaseController.viewId; + FieldController get fieldController => databaseController.fieldController; + CellCache get cellCache => databaseController.rowCache.cellCache; + RowCache get rowCache => databaseController.rowCache; CalendarBloc({required ViewPB view}) - : _databaseController = DatabaseController( - view: view, - layoutType: DatabaseLayoutPB.Calendar, - ), + : databaseController = DatabaseController(view: view), super(CalendarState.initial()) { on( (event, emit) async { @@ -110,12 +107,12 @@ class CalendarBloc extends Bloc { @override Future close() async { - await _databaseController.dispose(); + await databaseController.dispose(); return super.close(); } FieldInfo? _getCalendarFieldInfo(String fieldId) { - final fieldInfos = _databaseController.fieldController.fieldInfos; + final fieldInfos = databaseController.fieldController.fieldInfos; final index = fieldInfos.indexWhere( (element) => element.field.id == fieldId, ); @@ -127,7 +124,7 @@ class CalendarBloc extends Bloc { } FieldInfo? _getTitleFieldInfo() { - final fieldInfos = _databaseController.fieldController.fieldInfos; + final fieldInfos = databaseController.fieldController.fieldInfos; final index = fieldInfos.indexWhere( (element) => element.field.isPrimary, ); @@ -139,7 +136,7 @@ class CalendarBloc extends Bloc { } Future _openDatabase(Emitter emit) async { - final result = await _databaseController.open(); + final result = await databaseController.open(); result.fold( (database) => emit( state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))), @@ -157,7 +154,7 @@ class CalendarBloc extends Bloc { final dateField = _getCalendarFieldInfo(settings.fieldId); final titleField = _getTitleFieldInfo(); if (dateField != null && titleField != null) { - final newRow = await _databaseController.createRow( + final newRow = await databaseController.createRow( withCells: (builder) { builder.insertDate(dateField, date); builder.insertText(titleField, title); @@ -210,7 +207,7 @@ class CalendarBloc extends Bloc { Future _updateCalendarLayoutSetting( CalendarLayoutSettingPB layoutSetting, ) async { - return _databaseController.updateCalenderLayoutSetting(layoutSetting); + return databaseController.updateCalenderLayoutSetting(layoutSetting); } Future?> _loadEvent(RowId rowId) async { @@ -331,7 +328,7 @@ class CalendarBloc extends Bloc { }, ); - final onLayoutChanged = LayoutCallbacks( + final onLayoutChanged = DatabaseLayoutSettingCallbacks( onLayoutChanged: _didReceiveLayoutSetting, onLoadLayout: _didReceiveLayoutSetting, ); @@ -340,14 +337,14 @@ class CalendarBloc extends Bloc { onCalendarLayoutChanged: _didReceiveNewLayoutField, ); - _databaseController.setListener( + databaseController.setListener( onDatabaseChanged: onDatabaseChanged, onLayoutChanged: onLayoutChanged, onCalendarLayoutChanged: onCalendarLayoutFieldChanged, ); } - void _didReceiveLayoutSetting(LayoutSettingPB layoutSetting) { + void _didReceiveLayoutSetting(DatabaseLayoutSettingPB layoutSetting) { if (layoutSetting.hasCalendar()) { if (isClosed) { return; @@ -356,7 +353,7 @@ class CalendarBloc extends Bloc { } } - void _didReceiveNewLayoutField(LayoutSettingPB layoutSetting) { + void _didReceiveNewLayoutField(DatabaseLayoutSettingPB layoutSetting) { if (layoutSetting.hasCalendar()) { if (isClosed) return; add(CalendarEvent.didReceiveNewLayoutField(layoutSetting.calendar)); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart index 558db1419a..242f204258 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/calendar.dart @@ -49,18 +49,19 @@ class CalendarPlugin extends Plugin { notifier = ViewPluginNotifier(view: view); @override - PluginDisplay get display => CalendarPluginDisplay(notifier: notifier); + PluginWidgetBuilder get widgetBuilder => + CalendarPluginWidgetBuilder(notifier: notifier); @override PluginId get id => notifier.view.id; @override - PluginType get ty => _pluginType; + PluginType get pluginType => _pluginType; } -class CalendarPluginDisplay extends PluginDisplay { +class CalendarPluginWidgetBuilder extends PluginWidgetBuilder { final ViewPluginNotifier notifier; - CalendarPluginDisplay({required this.notifier, Key? key}); + CalendarPluginWidgetBuilder({required this.notifier, Key? key}); ViewPB get view => notifier.view; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting.dart index 6d81ad10b1..4de67f3e86 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart'; +import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; @@ -41,7 +41,7 @@ class CalendarSetting extends StatelessWidget { state.selectedAction.foldLeft(null, (previous, action) => action); switch (action) { case CalendarSettingAction.properties: - return GridPropertyList( + return DatabasePropertyList( viewId: settingContext.viewId, fieldController: settingContext.fieldController, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart index 5a88391f77..9592888ccc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -17,13 +18,15 @@ class CalendarToolbar extends StatelessWidget { @override Widget build(BuildContext context) { - return const SizedBox( + return SizedBox( height: 40, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - _UnscheduleEventsButton(), - _SettingButton(), + const _UnscheduleEventsButton(), + SettingButton( + databaseController: context.read().databaseController, + ), ], ), ); @@ -115,12 +118,16 @@ class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> { ), popupBuilder: (context) { final cells = [ - FlowyText.medium( - LocaleKeys.calendar_settings_noDateHint.tr(), - color: Theme.of(context).hintColor, - overflow: TextOverflow.ellipsis, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + child: FlowyText.medium( + // LocaleKeys.calendar_settings_noDateHint.tr(), + LocaleKeys.calendar_settings_clickToAdd.tr(), + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), ), - const VSpace(10), + const VSpace(6), ...unscheduledEvents.map( (e) => _UnscheduledEventItem( event: e, @@ -164,7 +171,11 @@ class _UnscheduledEventItem extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(event.title), + text: FlowyText.medium( + event.title.isEmpty + ? LocaleKeys.calendar_defaultNewCalendarTitle.tr() + : event.title, + ), onTap: onPressed, ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/database_view.dart b/frontend/appflowy_flutter/lib/plugins/database_view/database_view.dart new file mode 100644 index 0000000000..cba0cc7572 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/database_view.dart @@ -0,0 +1,54 @@ +import 'package:appflowy/startup/plugin/plugin.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_listener.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; +import '../../workspace/presentation/home/home_stack.dart'; + +/// [DatabaseViewPlugin] is used to build the grid, calendar, and board. +/// It is a wrapper of the [Plugin] class. The underlying [Plugin] is +/// determined by the [ViewPB.pluginType] field. +/// +class DatabaseViewPlugin extends Plugin { + final ViewListener _viewListener; + ViewPB _view; + Plugin _innerPlugin; + + DatabaseViewPlugin({ + required ViewPB view, + }) : _view = view, + _innerPlugin = _makeInnerPlugin(view), + _viewListener = ViewListener(view: view) { + _listenOnLayoutChanged(); + } + + @override + PluginId get id => _innerPlugin.id; + + @override + PluginType get pluginType => _innerPlugin.pluginType; + + @override + PluginWidgetBuilder get widgetBuilder => _innerPlugin.widgetBuilder; + + void _listenOnLayoutChanged() { + _viewListener.start( + onViewUpdated: (result) { + result.fold( + (updatedView) { + if (_view.layout != updatedView.layout) { + _innerPlugin = _makeInnerPlugin(updatedView); + getIt().setPlugin(_innerPlugin); + } + _view = updatedView; + }, + (r) => null, + ); + }, + ); + } +} + +Plugin _makeInnerPlugin(ViewPB view) { + return makePlugin(pluginType: view.pluginType, data: view); +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart index 77cf2fbe67..f688b3a6e7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart @@ -87,7 +87,7 @@ class GridBloc extends Bloc { add(GridEvent.didReceiveGridUpdate(database)); } }, - onRowsChanged: (rowInfos, _, reason) { + onNumOfRowsChanged: (rowInfos, _, reason) { if (!isClosed) { add(GridEvent.didReceiveRowUpdate(rowInfos, reason)); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/grid.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/grid.dart index 283eca5701..6f12f1cd88 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/grid.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/grid.dart @@ -49,20 +49,21 @@ class GridPlugin extends Plugin { notifier = ViewPluginNotifier(view: view); @override - PluginDisplay get display => GridPluginDisplay(notifier: notifier); + PluginWidgetBuilder get widgetBuilder => + GridPluginWidgetBuilder(notifier: notifier); @override PluginId get id => notifier.view.id; @override - PluginType get ty => _pluginType; + PluginType get pluginType => _pluginType; } -class GridPluginDisplay extends PluginDisplay { +class GridPluginWidgetBuilder extends PluginWidgetBuilder { final ViewPluginNotifier notifier; ViewPB get view => notifier.view; - GridPluginDisplay({required this.notifier, Key? key}); + GridPluginWidgetBuilder({required this.notifier, Key? key}); @override Widget get leftBarItem => ViewLeftBarItem(view: view); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart index ae66be99da..a5ed56a7da 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart @@ -1,6 +1,5 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; @@ -36,10 +35,7 @@ class GridPage extends StatefulWidget { required this.view, this.onDeleted, Key? key, - }) : databaseController = DatabaseController( - view: view, - layoutType: DatabaseLayoutPB.Grid, - ), + }) : databaseController = DatabaseController(view: view), super(key: key); final ViewPB view; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart new file mode 100644 index 0000000000..b110b3cc87 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart @@ -0,0 +1,133 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/layout/layout_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:styled_widget/styled_widget.dart'; + +import '../../layout/sizes.dart'; + +class DatabaseLayoutList extends StatefulWidget { + final String viewId; + final DatabaseLayoutPB currentLayout; + const DatabaseLayoutList({ + required this.viewId, + required this.currentLayout, + Key? key, + }) : super(key: key); + + @override + State createState() => _DatabaseLayoutListState(); +} + +class _DatabaseLayoutListState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => DatabaseLayoutBloc( + viewId: widget.viewId, + databaseLayout: widget.currentLayout, + )..add(const DatabaseLayoutEvent.initial()), + child: BlocBuilder( + builder: (context, state) { + final cells = DatabaseLayoutPB.values.map((layout) { + final isSelected = state.databaseLayout == layout; + return DatabaseViewLayoutCell( + databaseLayout: layout, + isSelected: isSelected, + onTap: (selectedLayout) { + context + .read() + .add(DatabaseLayoutEvent.updateLayout(selectedLayout)); + }, + ); + }).toList(); + + return ListView.separated( + controller: ScrollController(), + shrinkWrap: true, + itemCount: cells.length, + itemBuilder: (BuildContext context, int index) => cells[index], + separatorBuilder: (BuildContext context, int index) => + VSpace(GridSize.typeOptionSeparatorHeight), + padding: const EdgeInsets.symmetric(vertical: 6.0), + ); + }, + ), + ); + } +} + +extension DatabaseLayoutExtension on DatabaseLayoutPB { + String layoutName() { + switch (this) { + case DatabaseLayoutPB.Board: + return LocaleKeys.board_menuName.tr(); + case DatabaseLayoutPB.Calendar: + return LocaleKeys.calendar_menuName.tr(); + case DatabaseLayoutPB.Grid: + return LocaleKeys.grid_menuName.tr(); + default: + return ""; + } + } + + String iconName() { + switch (this) { + case DatabaseLayoutPB.Board: + return 'editor/board'; + case DatabaseLayoutPB.Calendar: + return "editor/grid"; + case DatabaseLayoutPB.Grid: + return "editor/grid"; + default: + return ""; + } + } +} + +class DatabaseViewLayoutCell extends StatelessWidget { + final bool isSelected; + final DatabaseLayoutPB databaseLayout; + final void Function(DatabaseLayoutPB) onTap; + const DatabaseViewLayoutCell({ + required this.databaseLayout, + required this.isSelected, + required this.onTap, + super.key, + }); + + @override + Widget build(BuildContext context) { + Widget? checkmark; + if (isSelected) { + checkmark = svgWidget("grid/checkmark"); + } + + return SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: FlowyText.medium( + databaseLayout.layoutName(), + color: AFThemeExtension.of(context).textColor, + ), + leftIcon: svgWidget( + databaseLayout.iconName(), + color: Theme.of(context).iconTheme.color, + ), + rightIcon: checkmark, + onTap: () => onTap(databaseLayout), + ).padding(horizontal: 6.0), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_setting.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_setting.dart index 9f5cd54cf9..f04d5d6314 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_setting.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_setting.dart @@ -1,6 +1,6 @@ -import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; +import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart'; -import 'package:easy_localization/easy_localization.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; @@ -9,36 +9,29 @@ import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; import '../../layout/sizes.dart'; -class GridSettingContext { - final String viewId; - final FieldController fieldController; - - GridSettingContext({ - required this.viewId, - required this.fieldController, - }); -} - -class GridSettingList extends StatelessWidget { - final GridSettingContext settingContext; - final Function(DatabaseSettingAction, GridSettingContext) onAction; - const GridSettingList({ - required this.settingContext, +class DatabaseSettingList extends StatelessWidget { + final DatabaseController databaseContoller; + final Function(DatabaseSettingAction, DatabaseController) onAction; + const DatabaseSettingList({ + required this.databaseContoller, required this.onAction, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { - final cells = DatabaseSettingAction.values - .where((value) => value.enable()) - .map((action) { + final cells = DatabaseSettingAction.values.where((element) { + if (element == DatabaseSettingAction.showGroup) { + return databaseContoller.databaseLayout == DatabaseLayoutPB.Board; + } else { + return true; + } + }).map((action) { return _SettingItem( action: action, - onAction: (action) => onAction(action, settingContext), + onAction: (action) => onAction(action, databaseContoller), ); }).toList(); @@ -78,9 +71,7 @@ class _SettingItem extends StatelessWidget { hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( action.title(), - color: action.enable() - ? AFThemeExtension.of(context).textColor - : Theme.of(context).disabledColor, + color: AFThemeExtension.of(context).textColor, ), onTap: () => onAction(action), leftIcon: svgWidget( @@ -91,36 +82,3 @@ class _SettingItem extends StatelessWidget { ); } } - -extension _GridSettingExtension on DatabaseSettingAction { - String iconName() { - switch (this) { - case DatabaseSettingAction.showFilters: - return 'grid/setting/filter'; - case DatabaseSettingAction.sortBy: - return 'grid/setting/sort'; - case DatabaseSettingAction.showProperties: - return 'grid/setting/properties'; - } - } - - String title() { - switch (this) { - case DatabaseSettingAction.showFilters: - return LocaleKeys.grid_settings_filter.tr(); - case DatabaseSettingAction.sortBy: - return LocaleKeys.grid_settings_sortBy.tr(); - case DatabaseSettingAction.showProperties: - return LocaleKeys.grid_settings_Properties.tr(); - } - } - - bool enable() { - switch (this) { - case DatabaseSettingAction.showProperties: - return true; - default: - return false; - } - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_toolbar.dart index 69ad698b2f..4f572e65ed 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_toolbar.dart @@ -1,9 +1,11 @@ import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; +import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import '../../layout/sizes.dart'; import 'filter_button.dart'; -import 'setting_button.dart'; +import '../../../../widgets/setting/setting_button.dart'; import 'sort_button.dart'; class GridToolbarContext { @@ -29,7 +31,9 @@ class GridToolbar extends StatelessWidget { const Spacer(), const FilterButton(), const SortButton(), - const SettingButton(), + SettingButton( + databaseController: context.read().databaseController, + ), ], ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/setting_button.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/setting_button.dart deleted file mode 100644 index b9b63eb5fb..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/setting_button.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart'; -import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:styled_widget/styled_widget.dart'; - -import '../../layout/sizes.dart'; -import 'grid_property.dart'; -import 'grid_setting.dart'; - -class SettingButton extends StatefulWidget { - const SettingButton({Key? key}) : super(key: key); - - @override - State createState() => _SettingButtonState(); -} - -class _SettingButtonState extends State { - late PopoverController _popoverController; - - @override - void initState() { - _popoverController = PopoverController(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BlocSelector( - selector: (state) { - final fieldController = - context.read().databaseController.fieldController; - return GridSettingContext( - viewId: state.viewId, - fieldController: fieldController, - ); - }, - builder: (context, settingContext) { - return SizedBox( - height: 26, - child: AppFlowyPopover( - controller: _popoverController, - constraints: BoxConstraints.loose(const Size(260, 400)), - direction: PopoverDirection.bottomWithLeftAligned, - offset: const Offset(0, 8), - margin: EdgeInsets.zero, - triggerActions: PopoverTriggerFlags.none, - child: FlowyTextButton( - LocaleKeys.settings_title.tr(), - fontColor: AFThemeExtension.of(context).textColor, - fillColor: Colors.transparent, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - padding: GridSize.typeOptionContentInsets, - onPressed: () => _popoverController.show(), - ), - popupBuilder: (BuildContext context) { - return _GridSettingListPopover(settingContext: settingContext); - }, - ), - ); - }, - ); - } -} - -class _GridSettingListPopover extends StatefulWidget { - final GridSettingContext settingContext; - - const _GridSettingListPopover({Key? key, required this.settingContext}) - : super(key: key); - - @override - State createState() => _GridSettingListPopoverState(); -} - -class _GridSettingListPopoverState extends State<_GridSettingListPopover> { - DatabaseSettingAction? _action; - - @override - Widget build(BuildContext context) { - if (_action == DatabaseSettingAction.showProperties) { - return GridPropertyList( - viewId: widget.settingContext.viewId, - fieldController: widget.settingContext.fieldController, - ); - } - - return GridSettingList( - settingContext: widget.settingContext, - onAction: (action, settingContext) { - setState(() { - _action = action; - }); - }, - ).padding(all: 6.0); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart similarity index 93% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart index c8ca724559..b290691693 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart @@ -11,23 +11,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; -import '../../layout/sizes.dart'; -import '../header/field_editor.dart'; +import '../../grid/presentation/layout/sizes.dart'; +import '../../grid/presentation/widgets/header/field_editor.dart'; -class GridPropertyList extends StatefulWidget { +class DatabasePropertyList extends StatefulWidget { final String viewId; final FieldController fieldController; - const GridPropertyList({ + const DatabasePropertyList({ required this.viewId, required this.fieldController, Key? key, }) : super(key: key); @override - State createState() => _GridPropertyListState(); + State createState() => _DatabasePropertyListState(); } -class _GridPropertyListState extends State { +class _DatabasePropertyListState extends State { late PopoverMutex _popoverMutex; @override diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_group.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/group/database_group.dart similarity index 97% rename from frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_group.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/widgets/group/database_group.dart index 07767a591d..a85616e51c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_group.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/group/database_group.dart @@ -11,11 +11,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class GridGroupList extends StatelessWidget { +class DatabaseGroupList extends StatelessWidget { final String viewId; final FieldController fieldController; final VoidCallback onDismissed; - const GridGroupList({ + const DatabaseGroupList({ required this.viewId, required this.fieldController, required this.onDismissed, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_button.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_button.dart new file mode 100644 index 0000000000..89f6152b66 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/setting_button.dart @@ -0,0 +1,116 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/database_controller.dart'; +import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart'; +import 'package:appflowy/plugins/database_view/widgets/group/database_group.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:styled_widget/styled_widget.dart'; + +import '../../grid/presentation/layout/sizes.dart'; +import '../../grid/presentation/widgets/toolbar/grid_layout.dart'; +import '../field/grid_property.dart'; +import '../../grid/presentation/widgets/toolbar/grid_setting.dart'; + +class SettingButton extends StatefulWidget { + final DatabaseController databaseController; + const SettingButton({ + required this.databaseController, + Key? key, + }) : super(key: key); + + @override + State createState() => _SettingButtonState(); +} + +class _SettingButtonState extends State { + late PopoverController _popoverController; + + @override + void initState() { + _popoverController = PopoverController(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 26, + child: AppFlowyPopover( + controller: _popoverController, + constraints: BoxConstraints.loose(const Size(200, 400)), + direction: PopoverDirection.bottomWithLeftAligned, + offset: const Offset(0, 8), + margin: EdgeInsets.zero, + triggerActions: PopoverTriggerFlags.none, + child: FlowyTextButton( + LocaleKeys.settings_title.tr(), + fontColor: AFThemeExtension.of(context).textColor, + fillColor: Colors.transparent, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + padding: GridSize.typeOptionContentInsets, + onPressed: () => _popoverController.show(), + ), + popupBuilder: (BuildContext context) { + return _DatabaseSettingListPopover( + databaseController: widget.databaseController, + ); + }, + ), + ); + } +} + +class _DatabaseSettingListPopover extends StatefulWidget { + final DatabaseController databaseController; + + const _DatabaseSettingListPopover({ + required this.databaseController, + Key? key, + }) : super(key: key); + + @override + State createState() => _DatabaseSettingListPopoverState(); +} + +class _DatabaseSettingListPopoverState + extends State<_DatabaseSettingListPopover> { + DatabaseSettingAction? _action; + + @override + Widget build(BuildContext context) { + if (_action == null) { + return DatabaseSettingList( + databaseContoller: widget.databaseController, + onAction: (action, settingContext) { + setState(() { + _action = action; + }); + }, + ).padding(all: 6.0); + } else { + switch (_action!) { + case DatabaseSettingAction.showLayout: + return DatabaseLayoutList( + viewId: widget.databaseController.viewId, + currentLayout: widget.databaseController.databaseLayout!, + ); + case DatabaseSettingAction.showGroup: + return DatabaseGroupList( + viewId: widget.databaseController.viewId, + fieldController: widget.databaseController.fieldController, + onDismissed: () { + // widget.popoverController.close(); + }, + ); + case DatabaseSettingAction.showProperties: + return DatabasePropertyList( + viewId: widget.databaseController.viewId, + fieldController: widget.databaseController.fieldController, + ); + } + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/document.dart b/frontend/appflowy_flutter/lib/plugins/document/document.dart index 904dfd527f..b6d4605ea7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document.dart @@ -61,32 +61,36 @@ class DocumentPlugin extends Plugin { } @override - PluginDisplay get display { - return DocumentPluginDisplay( + PluginWidgetBuilder get widgetBuilder { + return DocumentPluginWidgetBuilder( notifier: notifier, documentAppearanceCubit: _documentAppearanceCubit, ); } @override - PluginType get ty => _pluginType; + PluginType get pluginType => _pluginType; @override PluginId get id => notifier.view.id; } -class DocumentPluginDisplay extends PluginDisplay with NavigationItem { +class DocumentPluginWidgetBuilder extends PluginWidgetBuilder + with NavigationItem { final ViewPluginNotifier notifier; ViewPB get view => notifier.view; int? deletedViewIndex; DocumentAppearanceCubit documentAppearanceCubit; - DocumentPluginDisplay({ + DocumentPluginWidgetBuilder({ required this.notifier, required this.documentAppearanceCubit, Key? key, }); + @override + EdgeInsets get contentPadding => EdgeInsets.zero; + @override Widget buildWidget(PluginContext context) { notifier.isDeleted.addListener(() { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart index c2ba207f1f..0d23510182 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart @@ -1,6 +1,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart'; import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; @@ -38,13 +38,18 @@ class _BuiltInPageWidgetState extends State { late Future> future; final focusNode = FocusNode(); - String get appId => widget.node.attributes[DatabaseBlockKeys.kAppID]; - String get viewId => widget.node.attributes[DatabaseBlockKeys.kViewID]; + String get parentViewId => widget.node.attributes[DatabaseBlockKeys.parentID]; + String get childViewId => widget.node.attributes[DatabaseBlockKeys.viewID]; @override void initState() { super.initState(); - future = AppBackendService().getChildView(viewId, appId).then( + future = ViewBackendService() + .getChildView( + parentViewId: parentViewId, + childViewId: childViewId, + ) + .then( (value) => value.swap(), ); } @@ -153,6 +158,7 @@ class _BuiltInPageWidgetState extends State { switch (action.inner) { case _ActionType.viewDatabase: getIt().latestOpenView = viewPB; + getIt().setPlugin(viewPB.plugin()); break; case _ActionType.delete: diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart index 2cb8c7ac99..a33a154f10 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart @@ -2,20 +2,20 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/database_view_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/board/board_node_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/grid/grid_node_widget.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; class DatabaseBlockKeys { const DatabaseBlockKeys._(); - static const String kAppID = 'app_id'; - static const String kViewID = 'view_id'; + static const String parentID = 'parent_id'; + static const String viewID = 'view_id'; } extension InsertDatabase on EditorState { - Future insertPage(ViewPB appPB, ViewPB viewPB) async { + Future insertPage(ViewPB parentView, ViewPB childView) async { final selection = this.selection; if (selection == null || !selection.isCollapsed) { return; @@ -26,23 +26,22 @@ extension InsertDatabase on EditorState { } // get the database that the view is associated with - final database = await DatabaseViewBackendService(viewId: viewPB.id) + final database = await DatabaseViewBackendService(viewId: childView.id) .openGrid() .then((value) => value.swap().toOption().toNullable()); + if (database == null) { throw StateError( - 'The database associated with ${viewPB.id} could not be found while attempting to create a referenced ${viewPB.layout.name}.', + 'The database associated with ${childView.id} could not be found while attempting to create a referenced ${childView.layout.name}.', ); } - final prefix = _referencedDatabasePrefix(viewPB.layout); - final ref = await AppBackendService().createView( - parentViewId: appPB.id, - name: "$prefix ${viewPB.name}", - layoutType: viewPB.layout, - ext: { - 'database_id': database.id, - }, + final prefix = _referencedDatabasePrefix(childView.layout); + final ref = await ViewBackendService.createDatabaseReferenceView( + parentViewId: parentView.id, + name: "$prefix ${childView.name}", + layoutType: childView.layout, + databaseId: database.id, ).then((value) => value.swap().toOption().toNullable()); // TODO(a-wallen): Show error dialog here. @@ -54,10 +53,10 @@ extension InsertDatabase on EditorState { transaction.insertNode( selection.end.path, Node( - type: _convertPageType(viewPB), + type: _convertPageType(childView), attributes: { - DatabaseBlockKeys.kAppID: appPB.id, - DatabaseBlockKeys.kViewID: ref.id, + DatabaseBlockKeys.parentID: parentView.id, + DatabaseBlockKeys.viewID: ref.id, }, ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart index 44d86af55d..29168fb3d8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart @@ -1,5 +1,5 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra/image.dart'; @@ -69,7 +69,7 @@ class _LinkToPageMenuState extends State { final Map _items = {}; Future)>> fetchItems() async { - final items = await AppBackendService().fetchViews(widget.layoutType); + final items = await ViewBackendService().fetchViews(widget.layoutType); int index = 0; for (final (app, children) in items) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_node_widget.dart index f17c96fd42..6491148aed 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_node_widget.dart @@ -37,8 +37,8 @@ class BoardBlockComponentBuilder extends BlockComponentBuilder { @override bool validate(Node node) => node.children.isEmpty && - node.attributes[DatabaseBlockKeys.kAppID] is String && - node.attributes[DatabaseBlockKeys.kViewID] is String; + node.attributes[DatabaseBlockKeys.parentID] is String && + node.attributes[DatabaseBlockKeys.viewID] is String; } class BoardBlockComponentWidget extends BlockComponentStatefulWidget { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_view_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_view_menu_item.dart index 4947d80ccf..4d78ba4f55 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_view_menu_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_view_menu_item.dart @@ -1,10 +1,10 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; import 'package:easy_localization/easy_localization.dart'; SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) => @@ -22,9 +22,9 @@ SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) => } final appId = documentBloc.view.parentViewId; - final service = AppBackendService(); + final service = ViewBackendService(); - final result = (await service.createView( + final result = (await ViewBackendService.createView( parentViewId: appId, name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), layoutType: ViewLayoutPB.Board, @@ -42,7 +42,10 @@ SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) => return; } - final view = (await service.getChildView(result.viewId, result.id)) + final view = (await service.getChildView( + parentViewId: result.viewId, + childViewId: result.id, + )) .getLeftOrNull(); // As this. if (view == null) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_node_widget.dart index 580991e178..63e25235e0 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_node_widget.dart @@ -37,8 +37,8 @@ class GridBlockComponentBuilder extends BlockComponentBuilder { @override bool validate(Node node) => node.children.isEmpty && - node.attributes[DatabaseBlockKeys.kAppID] is String && - node.attributes[DatabaseBlockKeys.kViewID] is String; + node.attributes[DatabaseBlockKeys.parentID] is String && + node.attributes[DatabaseBlockKeys.viewID] is String; } class GridBlockComponentWidget extends BlockComponentStatefulWidget { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_view_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_view_menu_item.dart index c8685fc251..84c32c5a41 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_view_menu_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_view_menu_item.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/doc_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -22,9 +22,9 @@ SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) => } final appId = documentBloc.view.parentViewId; - final service = AppBackendService(); + final service = ViewBackendService(); - final result = (await service.createView( + final result = (await ViewBackendService.createView( parentViewId: appId, name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), layoutType: ViewLayoutPB.Grid, @@ -42,7 +42,10 @@ SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) => return; } - final view = (await service.getChildView(result.viewId, result.id)) + final view = (await service.getChildView( + parentViewId: result.viewId, + childViewId: result.id, + )) .getLeftOrNull(); // As this. if (view == null) { diff --git a/frontend/appflowy_flutter/lib/plugins/trash/trash.dart b/frontend/appflowy_flutter/lib/plugins/trash/trash.dart index a305fca5ae..e4e6e724b9 100644 --- a/frontend/appflowy_flutter/lib/plugins/trash/trash.dart +++ b/frontend/appflowy_flutter/lib/plugins/trash/trash.dart @@ -38,16 +38,16 @@ class TrashPlugin extends Plugin { TrashPlugin({required PluginType pluginType}) : _pluginType = pluginType; @override - PluginDisplay get display => TrashPluginDisplay(); + PluginWidgetBuilder get widgetBuilder => TrashPluginDisplay(); @override PluginId get id => "TrashStack"; @override - PluginType get ty => _pluginType; + PluginType get pluginType => _pluginType; } -class TrashPluginDisplay extends PluginDisplay { +class TrashPluginDisplay extends PluginWidgetBuilder { @override Widget get leftBarItem => FlowyText.medium(LocaleKeys.trash_text.tr()); diff --git a/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart b/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart index f6d6cad9fa..0d07a92926 100644 --- a/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart +++ b/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart @@ -22,11 +22,11 @@ typedef PluginId = String; abstract class Plugin { PluginId get id; - PluginDisplay get display; + PluginWidgetBuilder get widgetBuilder; PluginNotifier? get notifier => null; - PluginType get ty; + PluginType get pluginType; void dispose() { notifier?.dispose(); @@ -37,7 +37,7 @@ abstract class PluginNotifier { /// Notify if the plugin get deleted ValueNotifier get isDeleted; - /// Notify if the [PluginDisplay]'s content was changed + /// Notify if the [PluginWidgetBuilder]'s content was changed ValueNotifier get isDisplayChanged; void dispose() {} @@ -50,8 +50,11 @@ abstract class PluginBuilder { String get menuIcon; + /// The type of this [Plugin]. Each [Plugin] should have a unique [PluginType] PluginType get pluginType; + /// The layoutType is used in the backend to determine the layout of the view. + /// Currrently, AppFlowy supports 4 layout types: Document, Grid, Board, Calendar. ViewLayoutPB? get layoutType => ViewLayoutPB.Document; } @@ -60,9 +63,12 @@ abstract class PluginConfig { bool get creatable => true; } -abstract class PluginDisplay with NavigationItem { +abstract class PluginWidgetBuilder with NavigationItem { List get navigationItems; + EdgeInsets get contentPadding => + const EdgeInsets.symmetric(horizontal: 40, vertical: 28); + Widget buildWidget(PluginContext context); } @@ -78,6 +84,8 @@ void registerPlugin({required PluginBuilder builder, PluginConfig? config}) { .registerPlugin(builder.pluginType, builder, config: config); } +/// Make the correct plugin from the [pluginType] and [data]. If the plugin +/// is not registered, it will return a blank plugin. Plugin makePlugin({required PluginType pluginType, dynamic data}) { final plugin = getIt().buildPlugin(pluginType, data); return plugin; diff --git a/frontend/appflowy_flutter/lib/startup/plugin/src/sandbox.dart b/frontend/appflowy_flutter/lib/startup/plugin/src/sandbox.dart index 4876262d4a..a10ff9631a 100644 --- a/frontend/appflowy_flutter/lib/startup/plugin/src/sandbox.dart +++ b/frontend/appflowy_flutter/lib/startup/plugin/src/sandbox.dart @@ -1,5 +1,6 @@ import 'dart:collection'; +import 'package:appflowy/plugins/blank/blank.dart'; import 'package:flutter/services.dart'; import '../plugin.dart'; @@ -28,9 +29,11 @@ class PluginSandbox { return index; } + /// Build a plugin from [data] with [pluginType] + /// If the [pluginType] is not registered, it will return a blank plugin Plugin buildPlugin(PluginType pluginType, dynamic data) { - final plugin = _pluginBuilders[pluginType]!.build(data); - return plugin; + final builder = _pluginBuilders[pluginType] ?? BlankPluginBuilder(); + return builder.build(data); } void registerPlugin( diff --git a/frontend/appflowy_flutter/lib/workspace/application/app/app_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/app/app_bloc.dart index b44e136218..58bd51c32b 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/app/app_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/app/app_bloc.dart @@ -3,7 +3,7 @@ import 'dart:collection'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/app/app_listener.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy/workspace/presentation/home/menu/menu.dart'; import 'package:expandable/expandable.dart'; import 'package:appflowy_backend/log.dart'; @@ -17,11 +17,11 @@ import 'package:dartz/dartz.dart'; part 'app_bloc.freezed.dart'; class AppBloc extends Bloc { - final AppBackendService appService; + final ViewBackendService appService; final AppListener appListener; AppBloc({required ViewPB view}) - : appService = AppBackendService(), + : appService = ViewBackendService(), appListener = AppListener(viewId: view.id), super(AppState.initial(view)) { on((event, emit) async { @@ -77,8 +77,10 @@ class AppBloc extends Bloc { } Future _renameView(Rename e, Emitter emit) async { - final result = - await appService.updateApp(appId: state.view.id, name: e.newName); + final result = await ViewBackendService.updateView( + viewId: state.view.id, + name: e.newName, + ); result.fold( (l) => emit(state.copyWith(successOrFailure: left(unit))), (error) => emit(state.copyWith(successOrFailure: right(error))), @@ -87,7 +89,7 @@ class AppBloc extends Bloc { // Delete the current app Future _deleteApp(Emitter emit) async { - final result = await appService.delete(viewId: state.view.id); + final result = await ViewBackendService.delete(viewId: state.view.id); result.fold( (unit) => emit(state.copyWith(successOrFailure: left(unit))), (error) => emit(state.copyWith(successOrFailure: right(error))), @@ -95,7 +97,7 @@ class AppBloc extends Bloc { } Future _deleteView(Emitter emit, String viewId) async { - final result = await appService.deleteView(viewId: viewId); + final result = await ViewBackendService.deleteView(viewId: viewId); result.fold( (unit) => emit(state.copyWith(successOrFailure: left(unit))), (error) => emit(state.copyWith(successOrFailure: right(error))), @@ -103,7 +105,7 @@ class AppBloc extends Bloc { } Future _createView(CreateView value, Emitter emit) async { - final result = await appService.createView( + final result = await ViewBackendService.createView( parentViewId: state.view.id, name: value.name, desc: value.desc ?? "", @@ -132,7 +134,8 @@ class AppBloc extends Bloc { } Future _loadViews(Emitter emit) async { - final viewsOrFailed = await appService.getViews(viewId: state.view.id); + final viewsOrFailed = + await ViewBackendService.getViews(viewId: state.view.id); viewsOrFailed.fold( (views) => emit(state.copyWith(views: views)), (error) { diff --git a/frontend/appflowy_flutter/lib/workspace/application/app/app_service.dart b/frontend/appflowy_flutter/lib/workspace/application/app/app_service.dart deleted file mode 100644 index 0e9f9fb477..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/application/app/app_service.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart'; -import 'package:dartz/dartz.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; - -class AppBackendService { - Future> createView({ - required String parentViewId, - required String name, - String? desc, - required ViewLayoutPB layoutType, - - /// The initial data should be a JSON that represent the DocumentDataPB. - /// Currently, only support create document with initial data. - List? initialDataBytes, - - /// The [ext] is used to pass through the custom configuration - /// to the backend. - /// Linking the view to the existing database, it needs to pass - /// the database id. For example: "database_id": "xxx" - /// - Map ext = const {}, - }) { - final payload = CreateViewPayloadPB.create() - ..parentViewId = parentViewId - ..name = name - ..desc = desc ?? "" - ..layout = layoutType - ..initialData = initialDataBytes ?? []; - - if (ext.isNotEmpty) { - payload.ext.addAll(ext); - } - - return FolderEventCreateView(payload).send(); - } - - Future, FlowyError>> getViews({required String viewId}) { - final payload = ViewIdPB.create()..value = viewId; - - return FolderEventReadView(payload).send().then((result) { - return result.fold( - (app) => left(app.childViews), - (error) => right(error), - ); - }); - } - - Future> delete({required String viewId}) { - final request = RepeatedViewIdPB.create()..items.add(viewId); - return FolderEventDeleteView(request).send(); - } - - Future> deleteView({required String viewId}) { - final request = RepeatedViewIdPB.create()..items.add(viewId); - return FolderEventDeleteView(request).send(); - } - - Future> updateApp({ - required String appId, - String? name, - }) { - var payload = UpdateViewPayloadPB.create()..viewId = appId; - - if (name != null) { - payload.name = name; - } - return FolderEventUpdateView(payload).send(); - } - - Future> moveView({ - required String viewId, - required int fromIndex, - required int toIndex, - }) { - final payload = MoveFolderItemPayloadPB.create() - ..itemId = viewId - ..from = fromIndex - ..to = toIndex - ..ty = MoveFolderItemType.MoveView; - - return FolderEventMoveItem(payload).send(); - } - - Future)>> fetchViews( - ViewLayoutPB layoutType, - ) async { - final result = <(ViewPB, List)>[]; - return FolderEventReadCurrentWorkspace().send().then((value) async { - final workspaces = value.getLeftOrNull(); - if (workspaces != null) { - final views = workspaces.workspace.views; - for (var view in views) { - final childViews = await getViews(viewId: view.id).then( - (value) => value - .getLeftOrNull>() - ?.where((e) => e.layout == layoutType) - .toList(), - ); - if (childViews != null && childViews.isNotEmpty) { - result.add((view, childViews)); - } - } - } - return result; - }); - } - - Future> getView( - String viewID, - ) async { - final payload = ViewIdPB.create()..value = viewID; - return FolderEventReadView(payload).send(); - } - - Future> getChildView( - String viewID, - String childViewID, - ) async { - final payload = ViewIdPB.create()..value = viewID; - return FolderEventReadView(payload).send().then((result) { - return result.fold( - (app) => left( - app.childViews.firstWhere((e) => e.id == childViewID), - ), - (error) => right(error), - ); - }); - } -} - -extension AppFlowy on Either { - T? getLeftOrNull() { - if (isLeft()) { - final result = fold((l) => l, (r) => null); - return result; - } - return null; - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/app/prelude.dart b/frontend/appflowy_flutter/lib/workspace/application/app/prelude.dart index f8477049d3..b8dbdc67e0 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/app/prelude.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/app/prelude.dart @@ -1,3 +1,2 @@ export 'app_bloc.dart'; export 'app_listener.dart'; -export 'app_service.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/application/menu/menu_view_section_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/menu/menu_view_section_bloc.dart index 85c2ce5cf7..33ddf97a08 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/menu/menu_view_section_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/menu/menu_view_section_bloc.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:appflowy/workspace/application/app/app_bloc.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -13,12 +13,10 @@ class ViewSectionBloc extends Bloc { void Function()? _viewsListener; void Function()? _selectedViewlistener; final AppViewDataContext _appViewData; - late final AppBackendService _appService; ViewSectionBloc({ required AppViewDataContext appViewData, - }) : _appService = AppBackendService(), - _appViewData = appViewData, + }) : _appViewData = appViewData, super(ViewSectionState.initial(appViewData)) { on((event, emit) async { await event.map( @@ -69,7 +67,7 @@ class ViewSectionBloc extends Bloc { views.insert(value.toIndex, views.removeAt(value.fromIndex)); emit(state.copyWith(views: views)); - final result = await _appService.moveView( + final result = await ViewBackendService.moveView( viewId: viewId, fromIndex: value.fromIndex, toIndex: value.toIndex, diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart index c2a3313e4c..bdf623c9fb 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart @@ -9,13 +9,13 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'view_bloc.freezed.dart'; class ViewBloc extends Bloc { - final ViewService service; + final ViewBackendService viewBackendSvc; final ViewListener listener; final ViewPB view; ViewBloc({ required this.view, - }) : service = ViewService(), + }) : viewBackendSvc = ViewBackendService(), listener = ViewListener(view: view), super(ViewState.init(view)) { on((event, emit) async { @@ -42,7 +42,7 @@ class ViewBloc extends Bloc { ); }, rename: (e) async { - final result = await service.updateView( + final result = await ViewBackendService.updateView( viewId: view.id, name: e.newName, ); @@ -54,7 +54,7 @@ class ViewBloc extends Bloc { ); }, delete: (e) async { - final result = await service.delete(viewId: view.id); + final result = await ViewBackendService.delete(viewId: view.id); emit( result.fold( (l) => state.copyWith(successOrFailure: left(unit)), @@ -63,7 +63,7 @@ class ViewBloc extends Bloc { ); }, duplicate: (e) async { - final result = await service.duplicate(view: view); + final result = await ViewBackendService.duplicate(view: view); emit( result.fold( (l) => state.copyWith(successOrFailure: left(unit)), diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart index 069cc8e07f..364407701c 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/plugins/database_view/database_view.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:flowy_infra/image.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; @@ -56,7 +57,14 @@ extension ViewExtension on ViewPB { } Plugin plugin() { - final plugin = makePlugin(pluginType: pluginType, data: this); - return plugin; + switch (layout) { + case ViewLayoutPB.Board: + case ViewLayoutPB.Calendar: + case ViewLayoutPB.Grid: + return DatabaseViewPlugin(view: this); + case ViewLayoutPB.Document: + return makePlugin(pluginType: pluginType, data: this); + } + throw UnimplementedError; } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart index 51ecbf822b..92f6b7bf93 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart @@ -1,34 +1,165 @@ import 'dart:async'; + +import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart'; import 'package:dartz/dartz.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; -class ViewService { - Future> updateView({ - required String viewId, - String? name, +class ViewBackendService { + static Future> createView({ + required ViewLayoutPB layoutType, + required String parentViewId, + required String name, String? desc, + + /// The initial data should be a JSON that represent the DocumentDataPB. + /// Currently, only support create document with initial data. + List? initialDataBytes, + + /// The [ext] is used to pass through the custom configuration + /// to the backend. + /// Linking the view to the existing database, it needs to pass + /// the database id. For example: "database_id": "xxx" + /// + Map ext = const {}, }) { - final request = UpdateViewPayloadPB.create()..viewId = viewId; + final payload = CreateViewPayloadPB.create() + ..parentViewId = parentViewId + ..name = name + ..desc = desc ?? "" + ..layout = layoutType + ..initialData = initialDataBytes ?? []; - if (name != null) { - request.name = name; + if (ext.isNotEmpty) { + payload.ext.addAll(ext); } - if (desc != null) { - request.desc = desc; - } - - return FolderEventUpdateView(request).send(); + return FolderEventCreateView(payload).send(); } - Future> delete({required String viewId}) { + static Future> createDatabaseReferenceView({ + required String parentViewId, + required String databaseId, + required ViewLayoutPB layoutType, + required String name, + }) { + return ViewBackendService.createView( + layoutType: layoutType, + parentViewId: parentViewId, + name: name, + ext: { + 'database_id': databaseId, + }, + ); + } + + static Future, FlowyError>> getViews({ + required String viewId, + }) { + final payload = ViewIdPB.create()..value = viewId; + + return FolderEventReadView(payload).send().then((result) { + return result.fold( + (app) => left(app.childViews), + (error) => right(error), + ); + }); + } + + static Future> delete({required String viewId}) { final request = RepeatedViewIdPB.create()..items.add(viewId); return FolderEventDeleteView(request).send(); } - Future> duplicate({required ViewPB view}) { + static Future> deleteView({required String viewId}) { + final request = RepeatedViewIdPB.create()..items.add(viewId); + return FolderEventDeleteView(request).send(); + } + + static Future> duplicate({required ViewPB view}) { return FolderEventDuplicateView(view).send(); } + + static Future> updateView({ + required String viewId, + String? name, + }) { + var payload = UpdateViewPayloadPB.create()..viewId = viewId; + + if (name != null) { + payload.name = name; + } + return FolderEventUpdateView(payload).send(); + } + + static Future> moveView({ + required String viewId, + required int fromIndex, + required int toIndex, + }) { + final payload = MoveFolderItemPayloadPB.create() + ..itemId = viewId + ..from = fromIndex + ..to = toIndex + ..ty = MoveFolderItemType.MoveView; + + return FolderEventMoveItem(payload).send(); + } + + Future)>> fetchViews( + ViewLayoutPB layoutType, + ) async { + final result = <(ViewPB, List)>[]; + return FolderEventReadCurrentWorkspace().send().then((value) async { + final workspaces = value.getLeftOrNull(); + if (workspaces != null) { + final views = workspaces.workspace.views; + for (var view in views) { + final childViews = await getViews(viewId: view.id).then( + (value) => value + .getLeftOrNull>() + ?.where((e) => e.layout == layoutType) + .toList(), + ); + if (childViews != null && childViews.isNotEmpty) { + result.add((view, childViews)); + } + } + } + return result; + }); + } + + Future> getView( + String viewID, + ) async { + final payload = ViewIdPB.create()..value = viewID; + return FolderEventReadView(payload).send(); + } + + Future> getChildView({ + required String parentViewId, + required String childViewId, + }) async { + final payload = ViewIdPB.create()..value = parentViewId; + return FolderEventReadView(payload).send().then((result) { + return result.fold( + (app) => left( + app.childViews.firstWhere((e) => e.id == childViewId), + ), + (error) => right(error), + ); + }); + } +} + +extension AppFlowy on Either { + T? getLeftOrNull() { + if (isLeft()) { + final result = fold((l) => l, (r) => null); + return result; + } + return null; + } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart index 79acfc2e36..4998d4ba4e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart @@ -76,7 +76,7 @@ class _HomeScreenState extends State { if (view != null) { // Only open the last opened view if the [HomeStackManager] current opened plugin is blank and the last opened view is not null. // All opened widgets that display on the home screen are in the form of plugins. There is a list of built-in plugins defined in the [PluginType] enum, including board, grid and trash. - if (getIt().plugin.ty == + if (getIt().plugin.pluginType == PluginType.blank) { final plugin = makePlugin( pluginType: view.pluginType, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart index 119dc64224..18e8557992 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart @@ -112,16 +112,14 @@ abstract mixin class NavigationItem { class HomeStackNotifier extends ChangeNotifier { Plugin _plugin; - Widget get titleWidget => _plugin.display.leftBarItem; + Widget get titleWidget => _plugin.widgetBuilder.leftBarItem; HomeStackNotifier({Plugin? plugin}) : _plugin = plugin ?? makePlugin(pluginType: PluginType.blank); + /// This is the only place where the plugin is set. + /// No need compare the old plugin with the new plugin. Just set it. set plugin(Plugin newPlugin) { - if (newPlugin.id == _plugin.id) { - return; - } - _plugin.notifier?.isDisplayChanged.addListener(notifyListeners); _plugin.dispose(); @@ -139,7 +137,7 @@ class HomeStackManager { HomeStackManager(); Widget title() { - return _notifier.plugin.display.leftBarItem; + return _notifier.plugin.widgetBuilder.leftBarItem; } Plugin get plugin => _notifier.plugin; @@ -172,20 +170,22 @@ class HomeStackManager { child: Consumer( builder: (_, HomeStackNotifier notifier, __) { return FadingIndexedStack( - index: getIt().indexOf(notifier.plugin.ty), + index: getIt().indexOf(notifier.plugin.pluginType), children: getIt().supportPluginTypes.map( (pluginType) { - if (pluginType == notifier.plugin.ty) { - final pluginWidget = notifier.plugin.display - .buildWidget(PluginContext(onDeleted: onDeleted)); - if (pluginType == PluginType.editor) { - return pluginWidget; - } + if (pluginType == notifier.plugin.pluginType) { + final builder = notifier.plugin.widgetBuilder; + final pluginWidget = builder.buildWidget( + PluginContext(onDeleted: onDeleted), + ); - return pluginWidget.padding(horizontal: 40, vertical: 28); + return Padding( + padding: builder.contentPadding, + child: pluginWidget, + ); + } else { + return const BlankPage(); } - - return const BlankPage(); }, ).toList(), ); @@ -219,7 +219,7 @@ class HomeTopBar extends StatelessWidget { value: Provider.of(context, listen: false), child: Consumer( builder: (_, HomeStackNotifier notifier, __) => - notifier.plugin.display.rightBarItem ?? + notifier.plugin.widgetBuilder.rightBarItem ?? const SizedBox.shrink(), ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/navigation.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/navigation.dart index 6669803a97..7130c893b7 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/navigation.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/navigation.dart @@ -21,8 +21,8 @@ class NavigationNotifier with ChangeNotifier { void update(HomeStackNotifier notifier) { bool shouldNotify = false; - if (navigationItems != notifier.plugin.display.navigationItems) { - navigationItems = notifier.plugin.display.navigationItems; + if (navigationItems != notifier.plugin.widgetBuilder.navigationItems) { + navigationItems = notifier.plugin.widgetBuilder.navigationItems; shouldNotify = true; } @@ -41,7 +41,7 @@ class FlowyNavigation extends StatelessWidget { create: (_) { final notifier = Provider.of(context, listen: false); return NavigationNotifier( - navigationItems: notifier.plugin.display.navigationItems, + navigationItems: notifier.plugin.widgetBuilder.navigationItems, ); }, update: (_, notifier, controller) => controller!..update(notifier), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart index 9c0a2e362e..95c23eb04e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart @@ -17,7 +17,6 @@ class ViewLeftBarItem extends StatefulWidget { class _ViewLeftBarItemState extends State { final _controller = TextEditingController(); final _focusNode = FocusNode(); - late final ViewService _viewService; late final ViewListener _viewListener; late ViewPB view; @@ -25,7 +24,6 @@ class _ViewLeftBarItemState extends State { void initState() { super.initState(); view = widget.view; - _viewService = ViewService(); _focusNode.addListener(_handleFocusChanged); _viewListener = ViewListener(view: widget.view); _viewListener.start( @@ -86,7 +84,7 @@ class _ViewLeftBarItemState extends State { } if (_controller.text != view.name) { - _viewService.updateView(viewId: view.id, name: _controller.text); + ViewBackendService.updateView(viewId: view.id, name: _controller.text); } } } diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart index b2d9cd21bf..347b603faf 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart @@ -9,8 +9,7 @@ import 'package:appflowy/plugins/database_view/application/row/row_data_controll import 'package:appflowy/plugins/database_view/board/board.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; @@ -30,21 +29,16 @@ class AppFlowyBoardTest { Future createTestBoard() async { final app = await unitTest.createTestApp(); final builder = BoardPluginBuilder(); - return AppBackendService() - .createView( + return ViewBackendService.createView( parentViewId: app.id, name: "Test Board", layoutType: builder.layoutType!, - ) - .then((result) { + ).then((result) { return result.fold( (view) async { final context = BoardTestContext( view, - DatabaseController( - view: view, - layoutType: DatabaseLayoutPB.Board, - ), + DatabaseController(view: view), ); final result = await context._boardDataController.open(); result.fold((l) => null, (r) => throw Exception(r)); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart index f68506030b..c823639792 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart @@ -2,7 +2,6 @@ import 'package:appflowy/plugins/database_view/application/filter/filter_service import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_filter.pbenum.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/text_filter.pb.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -55,7 +54,6 @@ void main() { final service = FilterBackendService(viewId: context.gridView.id); final gridController = DatabaseController( view: context.gridView, - layoutType: DatabaseLayoutPB.Grid, ); final gridBloc = GridBloc( view: context.gridView, @@ -80,7 +78,6 @@ void main() { final service = FilterBackendService(viewId: context.gridView.id); final gridController = DatabaseController( view: context.gridView, - layoutType: DatabaseLayoutPB.Grid, ); final gridBloc = GridBloc( view: context.gridView, @@ -126,7 +123,6 @@ void main() { final service = FilterBackendService(viewId: context.gridView.id); final gridController = DatabaseController( view: context.gridView, - layoutType: DatabaseLayoutPB.Grid, ); final gridBloc = GridBloc( view: context.gridView, @@ -148,7 +144,6 @@ void main() { final service = FilterBackendService(viewId: context.gridView.id); final gridController = DatabaseController( view: context.gridView, - layoutType: DatabaseLayoutPB.Grid, ); final gridBloc = GridBloc( view: context.gridView, diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart index 63c7e47554..c7184c9e96 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_util.dart @@ -1,28 +1,22 @@ import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/grid/grid.dart'; -import 'package:appflowy/workspace/application/app/app_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import '../util.dart'; Future createTestFilterGrid(AppFlowyGridTest gridTest) async { final app = await gridTest.unitTest.createTestApp(); final builder = GridPluginBuilder(); - final context = await AppBackendService() - .createView( + final context = await ViewBackendService.createView( parentViewId: app.id, name: "Filter Grid", layoutType: builder.layoutType!, - ) - .then((result) { + ).then((result) { return result.fold( (view) async { final context = GridTestContext( view, - DatabaseController( - view: view, - layoutType: DatabaseLayoutPB.Grid, - ), + DatabaseController(view: view), ); final result = await context.gridController.open(); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart index 2d87efed74..678036798b 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart @@ -1,6 +1,5 @@ import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:bloc_test/bloc_test.dart'; import 'util.dart'; @@ -23,10 +22,7 @@ void main() { "create a row", build: () => GridBloc( view: context.gridView, - databaseController: DatabaseController( - view: context.gridView, - layoutType: DatabaseLayoutPB.Grid, - ), + databaseController: DatabaseController(view: context.gridView), )..add(const GridEvent.initial()), act: (bloc) => bloc.add(const GridEvent.createRow()), wait: const Duration(milliseconds: 300), @@ -39,10 +35,7 @@ void main() { "delete the last row", build: () => GridBloc( view: context.gridView, - databaseController: DatabaseController( - view: context.gridView, - layoutType: DatabaseLayoutPB.Grid, - ), + databaseController: DatabaseController(view: context.gridView), )..add(const GridEvent.initial()), act: (bloc) async { await gridResponseFuture(); @@ -65,10 +58,7 @@ void main() { 'reorder rows', build: () => GridBloc( view: context.gridView, - databaseController: DatabaseController( - view: context.gridView, - layoutType: DatabaseLayoutPB.Grid, - ), + databaseController: DatabaseController(view: context.gridView), )..add(const GridEvent.initial()), act: (bloc) async { await gridResponseFuture(); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart index 326e2a6e62..30535402c3 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart @@ -9,9 +9,8 @@ import 'package:appflowy/plugins/database_view/application/row/row_data_controll import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/grid.dart'; -import 'package:appflowy/workspace/application/app/app_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-database2/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; @@ -170,21 +169,16 @@ class AppFlowyGridTest { Future createTestGrid() async { final app = await unitTest.createTestApp(); final builder = GridPluginBuilder(); - final context = await AppBackendService() - .createView( + final context = await ViewBackendService.createView( parentViewId: app.id, name: "Test Grid", layoutType: builder.layoutType!, - ) - .then((result) { + ).then((result) { return result.fold( (view) async { final context = GridTestContext( view, - DatabaseController( - view: view, - layoutType: DatabaseLayoutPB.Grid, - ), + DatabaseController(view: view), ); final result = await context.gridController.open(); result.fold((l) => null, (r) => throw Exception(r)); diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 739bc99a2a..ae60491422 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "appflowy-integrate" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "collab", @@ -1024,7 +1024,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "bytes", @@ -1042,7 +1042,7 @@ dependencies = [ [[package]] name = "collab-client-ws" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "bytes", "collab-sync", @@ -1060,7 +1060,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "async-trait", @@ -1086,7 +1086,7 @@ dependencies = [ [[package]] name = "collab-derive" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "proc-macro2", "quote", @@ -1098,7 +1098,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "collab", @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "collab", @@ -1134,7 +1134,7 @@ dependencies = [ [[package]] name = "collab-persistence" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "bincode", "chrono", @@ -1154,7 +1154,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "async-trait", @@ -1184,7 +1184,7 @@ dependencies = [ [[package]] name = "collab-sync" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "bytes", "collab", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index 1ecdf3e3d3..6c52cf4f6e 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -34,17 +34,17 @@ default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] [patch.crates-io] -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } #collab = { path = "../../AppFlowy-Collab/collab" } #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" } #collab-document = { path = "../../AppFlowy-Collab/collab-document" } -#collab-database= { path = "../../AppFlowy-Collab/collab-database" } +#collab-database = { path = "../../AppFlowy-Collab/collab-database" } #appflowy-integrate = { path = "../../AppFlowy-Collab/appflowy-integrate" } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 0ff7929f02..6341b80e2f 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "appflowy-integrate" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "collab", @@ -887,7 +887,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "bytes", @@ -905,7 +905,7 @@ dependencies = [ [[package]] name = "collab-client-ws" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "bytes", "collab-sync", @@ -923,7 +923,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "async-trait", @@ -949,7 +949,7 @@ dependencies = [ [[package]] name = "collab-derive" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "proc-macro2", "quote", @@ -961,7 +961,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "collab", @@ -978,7 +978,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "collab", @@ -997,7 +997,7 @@ dependencies = [ [[package]] name = "collab-persistence" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "bincode", "chrono", @@ -1017,7 +1017,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "anyhow", "async-trait", @@ -1047,7 +1047,7 @@ dependencies = [ [[package]] name = "collab-sync" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=12e373#12e373f972d893d5f34b18794e77d3b49783ddcf" dependencies = [ "bytes", "collab", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 5a29089f4f..386810a2d9 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -33,11 +33,11 @@ opt-level = 3 incremental = false [patch.crates-io] -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } -appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } +appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "12e373" } #collab = { path = "../AppFlowy-Collab/collab" } #collab-folder = { path = "../AppFlowy-Collab/collab-folder" } diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs index 5022401583..fd5303a1af 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs @@ -18,7 +18,7 @@ use flowy_error::FlowyError; use flowy_folder2::deps::{FolderCloudService, FolderUser}; use flowy_folder2::entities::ViewLayoutPB; use flowy_folder2::manager::Folder2Manager; -use flowy_folder2::view_ext::{FolderOperationHandler, FolderOperationHandlers}; +use flowy_folder2::view_operation::{FolderOperationHandler, FolderOperationHandlers, View}; use flowy_folder2::ViewLayout; use flowy_user::services::UserSession; use lib_dispatch::prelude::ToBytes; @@ -111,7 +111,7 @@ impl FolderOperationHandler for DocumentFolderOperation { _name: &str, data: Vec, layout: ViewLayout, - _ext: HashMap, + _meta: HashMap, ) -> FutureResult<(), FlowyError> { debug_assert_eq!(layout, ViewLayout::Document); let view_id = view_id.to_string(); @@ -130,7 +130,6 @@ impl FolderOperationHandler for DocumentFolderOperation { view_id: &str, _name: &str, layout: ViewLayout, - _ext: HashMap, ) -> FutureResult<(), FlowyError> { debug_assert_eq!(layout, ViewLayout::Document); @@ -200,9 +199,9 @@ impl FolderOperationHandler for DatabaseFolderOperation { name: &str, data: Vec, layout: ViewLayout, - ext: HashMap, + meta: HashMap, ) -> FutureResult<(), FlowyError> { - match CreateDatabaseExtParams::from_map(ext) { + match CreateDatabaseExtParams::from_map(meta) { None => { let database_manager = self.0.clone(); let view_id = view_id.to_string(); @@ -217,17 +216,11 @@ impl FolderOperationHandler for DatabaseFolderOperation { let database_manager = self.0.clone(); let layout = layout_type_from_view_layout(layout.into()); let name = name.to_string(); - let target_view_id = view_id.to_string(); + let database_view_id = view_id.to_string(); FutureResult::new(async move { database_manager - .create_linked_view( - name, - layout, - params.database_id, - target_view_id, - params.duplicated_view_id, - ) + .create_linked_view(name, layout, params.database_id, database_view_id) .await?; Ok(()) }) @@ -245,7 +238,6 @@ impl FolderOperationHandler for DatabaseFolderOperation { view_id: &str, name: &str, layout: ViewLayout, - _meta: HashMap, ) -> FutureResult<(), FlowyError> { let name = name.to_string(); let database_manager = self.0.clone(); @@ -296,12 +288,37 @@ impl FolderOperationHandler for DatabaseFolderOperation { Ok(()) }) } + + fn did_update_view(&self, old: &View, new: &View) -> FutureResult<(), FlowyError> { + let database_layout = match new.layout { + ViewLayout::Document => { + return FutureResult::new(async { + Err(FlowyError::internal().context("Can't handle document layout type")) + }); + }, + ViewLayout::Grid => DatabaseLayoutPB::Grid, + ViewLayout::Board => DatabaseLayoutPB::Board, + ViewLayout::Calendar => DatabaseLayoutPB::Calendar, + }; + + let database_manager = self.0.clone(); + let view_id = new.id.clone(); + if old.layout != new.layout { + FutureResult::new(async move { + database_manager + .update_database_layout(&view_id, database_layout) + .await?; + Ok(()) + }) + } else { + FutureResult::new(async move { Ok(()) }) + } + } } #[derive(Debug, serde::Deserialize)] struct CreateDatabaseExtParams { database_id: String, - duplicated_view_id: Option, } impl CreateDatabaseExtParams { diff --git a/frontend/rust-lib/flowy-database2/src/entities/calendar_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/calendar_entities.rs index 986a5436df..9e0c33007f 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/calendar_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/calendar_entities.rs @@ -134,3 +134,18 @@ pub struct MoveCalendarEventPB { #[pb(index = 2)] pub timestamp: i64, } + +#[derive(Debug, Clone, Default, ProtoBuf)] +pub struct NoDateCalendarEventPB { + #[pb(index = 1)] + pub row_id: String, + + #[pb(index = 2)] + pub title: String, +} + +#[derive(Debug, Clone, Default, ProtoBuf)] +pub struct RepeatedNoDateCalendarEventPB { + #[pb(index = 1)] + pub items: Vec, +} diff --git a/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs index f4a570fdcf..fe986af9fc 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/database_entities.rs @@ -3,10 +3,11 @@ use collab_database::user::DatabaseRecord; use collab_database::views::DatabaseLayout; use flowy_derive::ProtoBuf; -use flowy_error::ErrorCode; +use flowy_error::{ErrorCode, FlowyError}; use crate::entities::parser::NotEmptyStr; use crate::entities::{DatabaseLayoutPB, FieldIdPB, RowPB}; +use crate::services::database::CreateDatabaseViewParams; /// [DatabasePB] describes how many fields and blocks the grid has #[derive(Debug, Clone, Default, ProtoBuf)] @@ -19,12 +20,34 @@ pub struct DatabasePB { #[pb(index = 3)] pub rows: Vec, + + #[pb(index = 4)] + pub layout_type: DatabaseLayoutPB, } #[derive(ProtoBuf, Default)] -pub struct CreateDatabasePayloadPB { +pub struct CreateDatabaseViewPayloadPB { #[pb(index = 1)] pub name: String, + + #[pb(index = 2)] + pub view_id: String, + + #[pb(index = 3)] + pub layout_type: DatabaseLayoutPB, +} + +impl TryInto for CreateDatabaseViewPayloadPB { + type Error = FlowyError; + + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; + Ok(CreateDatabaseViewParams { + name: self.name, + view_id: view_id.0, + layout_type: self.layout_type.into(), + }) + } } #[derive(Clone, ProtoBuf, Default, Debug)] @@ -198,7 +221,7 @@ impl TryInto for DatabaseGroupIdPB { } } #[derive(Clone, ProtoBuf, Default, Debug)] -pub struct DatabaseLayoutIdPB { +pub struct DatabaseLayoutMetaPB { #[pb(index = 1)] pub view_id: String, @@ -207,18 +230,18 @@ pub struct DatabaseLayoutIdPB { } #[derive(Clone, Debug)] -pub struct DatabaseLayoutId { +pub struct DatabaseLayoutMeta { pub view_id: String, pub layout: DatabaseLayout, } -impl TryInto for DatabaseLayoutIdPB { +impl TryInto for DatabaseLayoutMetaPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; let layout = self.layout.into(); - Ok(DatabaseLayoutId { + Ok(DatabaseLayoutMeta { view_id: view_id.0, layout, }) diff --git a/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs index c6c5404693..e724695d15 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/row_entities.rs @@ -121,7 +121,7 @@ pub struct UpdatedRowPB { #[pb(index = 1)] pub row: RowPB, - // represents as the cells that were updated in this row. + // Indicates the field ids of the cells that were updated in this row. #[pb(index = 2)] pub field_ids: Vec, } diff --git a/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs index d8689ff19c..9960f0e697 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs @@ -18,10 +18,10 @@ use crate::services::setting::CalendarLayoutSetting; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct DatabaseViewSettingPB { #[pb(index = 1)] - pub current_layout: DatabaseLayoutPB, + pub layout_type: DatabaseLayoutPB, #[pb(index = 2)] - pub layout_setting: LayoutSettingPB, + pub layout_setting: DatabaseLayoutSettingPB, #[pb(index = 3)] pub filters: RepeatedFilterPB, @@ -72,8 +72,8 @@ pub struct DatabaseSettingChangesetPB { #[pb(index = 1)] pub view_id: String, - #[pb(index = 2)] - pub layout_type: DatabaseLayoutPB, + #[pb(index = 2, one_of)] + pub layout_type: Option, #[pb(index = 3, one_of)] pub update_filter: Option, @@ -121,7 +121,7 @@ impl TryInto for DatabaseSettingChangesetPB { Ok(DatabaseSettingChangesetParams { view_id, - layout_type: self.layout_type.into(), + layout_type: self.layout_type.map(|ty| ty.into()), insert_filter, delete_filter, alert_sort, @@ -132,7 +132,7 @@ impl TryInto for DatabaseSettingChangesetPB { pub struct DatabaseSettingChangesetParams { pub view_id: String, - pub layout_type: DatabaseLayout, + pub layout_type: Option, pub insert_filter: Option, pub delete_filter: Option, pub alert_sort: Option, @@ -146,19 +146,24 @@ impl DatabaseSettingChangesetParams { } #[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)] -pub struct LayoutSettingPB { - #[pb(index = 1, one_of)] +pub struct DatabaseLayoutSettingPB { + #[pb(index = 1)] + pub layout_type: DatabaseLayoutPB, + + #[pb(index = 2, one_of)] pub calendar: Option, } #[derive(Debug, Clone, Default)] pub struct LayoutSettingParams { + pub layout_type: DatabaseLayout, pub calendar: Option, } -impl From for LayoutSettingPB { +impl From for DatabaseLayoutSettingPB { fn from(data: LayoutSettingParams) -> Self { Self { + layout_type: data.layout_type.into(), calendar: data.calendar.map(|calendar| calendar.into()), } } @@ -169,13 +174,17 @@ pub struct LayoutSettingChangesetPB { #[pb(index = 1)] pub view_id: String, - #[pb(index = 2, one_of)] + #[pb(index = 2)] + pub layout_type: DatabaseLayoutPB, + + #[pb(index = 3, one_of)] pub calendar: Option, } #[derive(Debug)] pub struct LayoutSettingChangeset { pub view_id: String, + pub layout_type: DatabaseLayout, pub calendar: Option, } @@ -189,6 +198,7 @@ impl TryInto for LayoutSettingChangesetPB { Ok(LayoutSettingChangeset { view_id, + layout_type: self.layout_type.into(), calendar: self.calendar.map(|calendar| calendar.into()), }) } diff --git a/frontend/rust-lib/flowy-database2/src/event_handler.rs b/frontend/rust-lib/flowy-database2/src/event_handler.rs index 7c0f010c02..2e1d7799b1 100644 --- a/frontend/rust-lib/flowy-database2/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database2/src/event_handler.rs @@ -2,7 +2,6 @@ use collab_database::database::gen_row_id; use std::sync::Arc; use collab_database::rows::RowId; -use collab_database::views::DatabaseLayout; use lib_infra::util::timestamp; use flowy_error::{FlowyError, FlowyResult}; @@ -25,7 +24,7 @@ pub(crate) async fn get_database_data_handler( ) -> DataResult { let view_id: DatabaseViewIdPB = data.into_inner(); let database_editor = manager.get_database_with_view_id(view_id.as_ref()).await?; - let data = database_editor.get_database_data(view_id.as_ref()).await; + let data = database_editor.get_database_data(view_id.as_ref()).await?; data_result_ok(data) } @@ -64,6 +63,12 @@ pub(crate) async fn update_database_setting_handler( if let Some(delete_sort) = params.delete_sort { editor.delete_sort(delete_sort).await?; } + + if let Some(layout_type) = params.layout_type { + editor + .update_view_layout(¶ms.view_id, layout_type) + .await?; + } Ok(()) } @@ -626,24 +631,25 @@ pub(crate) async fn set_layout_setting_handler( let params: LayoutSettingChangeset = data.into_inner().try_into()?; let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?; let layout_params = LayoutSettingParams { + layout_type: params.layout_type, calendar: params.calendar, }; database_editor - .set_layout_setting(¶ms.view_id, DatabaseLayout::Calendar, layout_params) + .set_layout_setting(¶ms.view_id, layout_params) .await; Ok(()) } pub(crate) async fn get_layout_setting_handler( - data: AFPluginData, + data: AFPluginData, manager: AFPluginState>, -) -> DataResult { - let params: DatabaseLayoutId = data.into_inner().try_into()?; +) -> DataResult { + let params: DatabaseLayoutMeta = data.into_inner().try_into()?; let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?; let layout_setting_pb = database_editor .get_layout_setting(¶ms.view_id, params.layout) .await - .map(LayoutSettingPB::from) + .map(DatabaseLayoutSettingPB::from) .unwrap_or_default(); data_result_ok(layout_setting_pb) } @@ -661,6 +667,19 @@ pub(crate) async fn get_calendar_events_handler( data_result_ok(RepeatedCalendarEventPB { items: events }) } +#[tracing::instrument(level = "debug", skip(data, manager), err)] +pub(crate) async fn get_no_date_calendar_events_handler( + data: AFPluginData, + manager: AFPluginState>, +) -> DataResult { + let params: CalendarEventRequestParams = data.into_inner().try_into()?; + let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?; + let _events = database_editor + .get_all_no_date_calendar_events(¶ms.view_id) + .await; + todo!() +} + #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_calendar_event_handler( data: AFPluginData, @@ -699,3 +718,12 @@ pub(crate) async fn move_calendar_event_handler( .await?; Ok(()) } + +#[tracing::instrument(level = "debug", skip_all, err)] +pub(crate) async fn create_database_view( + _data: AFPluginData, + _manager: AFPluginState>, +) -> FlowyResult<()> { + // let data: CreateDatabaseViewParams = data.into_inner().try_into()?; + Ok(()) +} diff --git a/frontend/rust-lib/flowy-database2/src/event_map.rs b/frontend/rust-lib/flowy-database2/src/event_map.rs index 564a433f93..08e8383ec9 100644 --- a/frontend/rust-lib/flowy-database2/src/event_map.rs +++ b/frontend/rust-lib/flowy-database2/src/event_map.rs @@ -60,12 +60,13 @@ pub fn init(database_manager: Arc) -> AFPlugin { .event(DatabaseEvent::GetDatabases, get_databases_handler) // Calendar .event(DatabaseEvent::GetAllCalendarEvents, get_calendar_events_handler) + .event(DatabaseEvent::GetNoDateCalendarEvents, get_no_date_calendar_events_handler) .event(DatabaseEvent::GetCalendarEvent, get_calendar_event_handler) .event(DatabaseEvent::MoveCalendarEvent, move_calendar_event_handler) // Layout setting .event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler) .event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler) - // import + .event(DatabaseEvent::CreateDatabaseView, create_database_view) } /// [DatabaseEvent] defines events that are used to interact with the Grid. You could check [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/backend/protobuf) @@ -265,15 +266,24 @@ pub enum DatabaseEvent { #[event(input = "LayoutSettingChangesetPB")] SetLayoutSetting = 121, - #[event(input = "DatabaseLayoutIdPB", output = "LayoutSettingPB")] + #[event(input = "DatabaseLayoutMetaPB", output = "DatabaseLayoutSettingPB")] GetLayoutSetting = 122, #[event(input = "CalendarEventRequestPB", output = "RepeatedCalendarEventPB")] GetAllCalendarEvents = 123, + #[event( + input = "CalendarEventRequestPB", + output = "RepeatedNoDateCalendarEventPB" + )] + GetNoDateCalendarEvents = 124, + #[event(input = "RowIdPB", output = "CalendarEventPB")] - GetCalendarEvent = 124, + GetCalendarEvent = 125, #[event(input = "MoveCalendarEventPB")] - MoveCalendarEvent = 125, + MoveCalendarEvent = 126, + + #[event(input = "CreateDatabaseViewPayloadPB")] + CreateDatabaseView = 130, } diff --git a/frontend/rust-lib/flowy-database2/src/manager.rs b/frontend/rust-lib/flowy-database2/src/manager.rs index f0d166cce4..089faef999 100644 --- a/frontend/rust-lib/flowy-database2/src/manager.rs +++ b/frontend/rust-lib/flowy-database2/src/manager.rs @@ -171,8 +171,7 @@ impl DatabaseManager2 { name: String, layout: DatabaseLayoutPB, database_id: String, - target_view_id: String, - duplicated_view_id: Option, + database_view_id: String, ) -> FlowyResult<()> { self.with_user_database( Err(FlowyError::internal().context("Create database view failed")), @@ -180,15 +179,8 @@ impl DatabaseManager2 { let database = user_database .get_database(&database_id) .ok_or_else(FlowyError::record_not_found)?; - match duplicated_view_id { - None => { - let params = CreateViewParams::new(database_id, target_view_id, name, layout.into()); - database.create_linked_view(params)?; - }, - Some(duplicated_view_id) => { - database.duplicate_linked_view(&duplicated_view_id); - }, - } + let params = CreateViewParams::new(database_id, database_view_id, name, layout.into()); + database.create_linked_view(params)?; Ok(()) }, )?; @@ -228,6 +220,15 @@ impl DatabaseManager2 { database.export_csv(style).await } + pub async fn update_database_layout( + &self, + view_id: &str, + layout: DatabaseLayoutPB, + ) -> FlowyResult<()> { + let database = self.get_database_with_view_id(view_id).await?; + database.update_view_layout(view_id, layout.into()).await + } + fn with_user_database(&self, default_value: Output, f: F) -> Output where F: FnOnce(&InnerUserDatabase) -> Output, diff --git a/frontend/rust-lib/flowy-database2/src/notification.rs b/frontend/rust-lib/flowy-database2/src/notification.rs index 831877315f..72e8287982 100644 --- a/frontend/rust-lib/flowy-database2/src/notification.rs +++ b/frontend/rust-lib/flowy-database2/src/notification.rs @@ -35,6 +35,8 @@ pub enum DatabaseNotification { DidUpdateLayoutSettings = 80, // Trigger when the layout field of the database is changed DidSetNewLayoutField = 81, + // Trigger when the layout of the database is changed + DidUpdateDatabaseLayout = 82, } impl std::default::Default for DatabaseNotification { diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index 4f106f1232..90c73e086a 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -18,8 +18,9 @@ use crate::entities::{ CalendarEventPB, CellChangesetNotifyPB, CellPB, ChecklistCellDataPB, DatabaseFieldChangesetPB, DatabasePB, DatabaseViewSettingPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams, FieldChangesetParams, FieldIdPB, FieldPB, FieldType, GroupPB, IndexFieldPB, InsertedRowPB, - LayoutSettingParams, RepeatedFilterPB, RepeatedGroupPB, RepeatedSortPB, RowPB, RowsChangePB, - SelectOptionCellDataPB, SelectOptionPB, UpdateFilterParams, UpdateSortParams, + LayoutSettingParams, NoDateCalendarEventPB, RepeatedFilterPB, RepeatedGroupPB, RepeatedSortPB, + RowPB, RowsChangePB, SelectOptionCellDataPB, SelectOptionPB, UpdateFilterParams, + UpdateSortParams, UpdatedRowPB, }; use crate::notification::{send_notification, DatabaseNotification}; use crate::services::cell::{ @@ -38,6 +39,7 @@ use crate::services::filter::Filter; use crate::services::group::{ default_group_setting, GroupSetting, GroupSettingChangeset, RowChangeset, }; + use crate::services::share::csv::{CSVExport, CSVFormat}; use crate::services::sort::Sort; @@ -76,6 +78,17 @@ impl DatabaseEditor { pub async fn close(&self) {} + pub async fn update_view_layout( + &self, + view_id: &str, + layout_type: DatabaseLayout, + ) -> FlowyResult<()> { + let view_editor = self.database_views.get_view_editor(view_id).await?; + view_editor.v_update_layout_type(layout_type).await?; + + Ok(()) + } + pub async fn subscribe_view_changed( &self, view_id: &str, @@ -442,7 +455,7 @@ impl DatabaseEditor { let (field, cell) = { let database = self.database.lock(); let field = database.fields.get_field(field_id); - let cell = database.get_cell(field_id, &row_id); + let cell = database.get_cell(field_id, &row_id).cell; (field, cell) }; @@ -484,12 +497,7 @@ impl DatabaseEditor { Err(FlowyError::internal().context(msg)) }, }?; - ( - field, - database - .get_cell(field_id, &row_id) - .map(|row_cell| row_cell.cell), - ) + (field, database.get_cell(field_id, &row_id).cell) }; let new_cell = apply_cell_changeset(cell_changeset, cell, &field, Some(self.cell_cache.clone()))?; @@ -529,6 +537,15 @@ impl DatabaseEditor { let option_row = self.database.lock().get_row(&row_id); if let Some(new_row) = option_row { + let updated_row = UpdatedRowPB { + row: RowPB::from(&new_row), + field_ids: vec![field_id.to_string()], + }; + let changes = RowsChangePB::from_update(view_id.to_string(), updated_row); + send_notification(view_id, DatabaseNotification::DidUpdateViewRows) + .payload(changes) + .send(); + for view in self.database_views.editors().await { view.v_did_update_row(&old_row, &new_row, field_id).await; } @@ -646,10 +663,10 @@ impl DatabaseEditor { match field { None => SelectOptionCellDataPB::default(), Some(field) => { - let row_cell = self.database.lock().get_cell(field_id, &row_id); - let ids = match row_cell { + let cell = self.database.lock().get_cell(field_id, &row_id).cell; + let ids = match cell { None => SelectOptionIds::new(), - Some(row_cell) => SelectOptionIds::from(&row_cell.cell), + Some(cell) => SelectOptionIds::from(&cell), }; match select_type_option_from_field(&field) { Ok(type_option) => type_option.get_selected_options(ids).into(), @@ -661,9 +678,9 @@ impl DatabaseEditor { pub async fn get_checklist_option(&self, row_id: RowId, field_id: &str) -> ChecklistCellDataPB { let row_cell = self.database.lock().get_cell(field_id, &row_id); - let cell_data = match row_cell { + let cell_data = match row_cell.cell { None => ChecklistCellData::default(), - Some(row_cell) => ChecklistCellData::from(&row_cell.cell), + Some(cell) => ChecklistCellData::from(&cell), }; ChecklistCellDataPB::from(cell_data) } @@ -763,14 +780,9 @@ impl DatabaseEditor { Ok(()) } - pub async fn set_layout_setting( - &self, - view_id: &str, - layout_ty: DatabaseLayout, - layout_setting: LayoutSettingParams, - ) { + pub async fn set_layout_setting(&self, view_id: &str, layout_setting: LayoutSettingParams) { if let Ok(view) = self.database_views.get_view_editor(view_id).await { - let _ = view.v_set_layout_settings(&layout_ty, layout_setting).await; + let _ = view.v_set_layout_settings(layout_setting).await; } } @@ -795,6 +807,15 @@ impl DatabaseEditor { } } + #[tracing::instrument(level = "trace", skip_all)] + pub async fn get_all_no_date_calendar_events( + &self, + view_id: &str, + ) -> FlowyResult> { + let _database_view = self.database_views.get_view_editor(view_id).await?; + todo!() + } + #[tracing::instrument(level = "trace", skip_all)] pub async fn get_calendar_event(&self, view_id: &str, row_id: RowId) -> Option { let view = self.database_views.get_view_editor(view_id).await.ok()?; @@ -858,8 +879,13 @@ impl DatabaseEditor { Ok(database_view_setting_pb_from_view(view)) } - pub async fn get_database_data(&self, view_id: &str) -> DatabasePB { - let rows = self.get_rows(view_id).await.unwrap_or_default(); + pub async fn get_database_data(&self, view_id: &str) -> FlowyResult { + let database_view = self.database_views.get_view_editor(view_id).await?; + let view = database_view + .get_view() + .await + .ok_or(FlowyError::record_not_found())?; + let rows = database_view.v_get_rows().await; let (database_id, fields) = { let database = self.database.lock(); let database_id = database.get_database_id(); @@ -876,11 +902,12 @@ impl DatabaseEditor { .into_iter() .map(|row| RowPB::from(row.as_ref())) .collect::>(); - DatabasePB { + Ok(DatabasePB { id: database_id, fields, rows, - } + layout_type: view.layout.into(), + }) } pub async fn export_csv(&self, style: CSVFormat) -> FlowyResult { @@ -946,7 +973,7 @@ struct DatabaseViewDataImpl { } impl DatabaseViewData for DatabaseViewDataImpl { - fn get_view_setting(&self, view_id: &str) -> Fut> { + fn get_view(&self, view_id: &str) -> Fut> { let view = self.database.lock().get_view(view_id); to_fut(async move { view }) } @@ -966,6 +993,26 @@ impl DatabaseViewData for DatabaseViewDataImpl { to_fut(async move { field }) } + fn create_field( + &self, + view_id: &str, + name: &str, + field_type: FieldType, + type_option_data: TypeOptionData, + ) -> Fut { + let (_, field) = self.database.lock().create_default_field( + view_id, + name.to_string(), + field_type.clone().into(), + |field| { + field + .type_options + .insert(field_type.to_string(), type_option_data); + }, + ); + to_fut(async move { field }) + } + fn get_primary_field(&self) -> Fut>> { let field = self .database @@ -1002,18 +1049,13 @@ impl DatabaseViewData for DatabaseViewDataImpl { to_fut(async move { cells.into_iter().map(Arc::new).collect() }) } - fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut>> { + fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut> { let cell = self.database.lock().get_cell(field_id, row_id); - to_fut(async move { cell.map(Arc::new) }) + to_fut(async move { Arc::new(cell) }) } fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout { - self - .database - .lock() - .views - .get_view_layout(view_id) - .unwrap_or_default() + self.database.lock().views.get_database_view_layout(view_id) } fn get_group_setting(&self, view_id: &str) -> Vec { @@ -1077,11 +1119,7 @@ impl DatabaseViewData for DatabaseViewDataImpl { } fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option { - self - .database - .lock() - .views - .get_layout_setting(view_id, layout_ty) + self.database.lock().get_layout_setting(view_id, layout_ty) } fn insert_layout_setting( @@ -1096,6 +1134,17 @@ impl DatabaseViewData for DatabaseViewDataImpl { .insert_layout_setting(view_id, layout_ty, layout_setting); } + fn get_layout_type(&self, view_id: &str) -> DatabaseLayout { + self.database.lock().views.get_database_view_layout(view_id) + } + + fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout) { + self + .database + .lock() + .update_layout_type(view_id, layout_type); + } + fn get_task_scheduler(&self) -> Arc> { self.task_scheduler.clone() } diff --git a/frontend/rust-lib/flowy-database2/src/services/database/entities.rs b/frontend/rust-lib/flowy-database2/src/services/database/entities.rs index 66693b02e2..ec3df071a2 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/entities.rs @@ -1,5 +1,5 @@ use collab_database::rows::RowId; -use collab_database::views::RowOrder; +use collab_database::views::{DatabaseLayout, RowOrder}; #[derive(Debug, Clone)] pub enum DatabaseRowEvent { @@ -25,3 +25,10 @@ pub struct UpdatedRow { // represents as the cells that were updated in this row. pub field_ids: Vec, } + +#[derive(Debug, Clone)] +pub struct CreateDatabaseViewParams { + pub name: String, + pub view_id: String, + pub layout_type: DatabaseLayout, +} diff --git a/frontend/rust-lib/flowy-database2/src/services/database/util.rs b/frontend/rust-lib/flowy-database2/src/services/database/util.rs index f47d1b7f59..b22bd0fff2 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/util.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/util.rs @@ -1,6 +1,6 @@ use crate::entities::{ - CalendarLayoutSettingPB, DatabaseLayoutPB, DatabaseViewSettingPB, FilterPB, GroupSettingPB, - LayoutSettingPB, SortPB, + CalendarLayoutSettingPB, DatabaseLayoutPB, DatabaseLayoutSettingPB, DatabaseViewSettingPB, + FilterPB, GroupSettingPB, SortPB, }; use crate::services::filter::Filter; use crate::services::group::GroupSetting; @@ -9,17 +9,18 @@ use crate::services::sort::Sort; use collab_database::views::DatabaseView; pub(crate) fn database_view_setting_pb_from_view(view: DatabaseView) -> DatabaseViewSettingPB { + let layout_type: DatabaseLayoutPB = view.layout.clone().into(); let layout_setting = if let Some(layout_setting) = view.layout_settings.get(&view.layout) { let calendar_setting = CalendarLayoutSettingPB::from(CalendarLayoutSetting::from(layout_setting.clone())); - LayoutSettingPB { + DatabaseLayoutSettingPB { + layout_type: layout_type.clone(), calendar: Some(calendar_setting), } } else { - LayoutSettingPB::default() + DatabaseLayoutSettingPB::default() }; - let current_layout: DatabaseLayoutPB = view.layout.into(); let filters = view .filters .into_iter() @@ -47,7 +48,7 @@ pub(crate) fn database_view_setting_pb_from_view(view: DatabaseView) -> Database .collect::>(); DatabaseViewSettingPB { - current_layout, + layout_type, filters: filters.into(), group_settings: group_settings.into(), sorts: sorts.into(), diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 4325f7e91e..3823311b74 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::sync::Arc; use collab_database::database::{gen_database_filter_id, gen_database_sort_id}; -use collab_database::fields::Field; +use collab_database::fields::{Field, TypeOptionData}; use collab_database::rows::{Cells, Row, RowCell, RowId}; use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting, RowOrder}; use tokio::sync::{broadcast, RwLock}; @@ -13,9 +13,9 @@ use flowy_task::TaskDispatcher; use lib_infra::future::Fut; use crate::entities::{ - CalendarEventPB, DeleteFilterParams, DeleteGroupParams, DeleteSortParams, FieldType, - GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedRowPB, LayoutSettingPB, - LayoutSettingParams, RowPB, RowsChangePB, SortChangesetNotificationPB, SortPB, + CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterParams, + DeleteGroupParams, DeleteSortParams, FieldType, GroupChangesPB, GroupPB, GroupRowsNotificationPB, + InsertedRowPB, LayoutSettingParams, RowPB, RowsChangePB, SortChangesetNotificationPB, SortPB, UpdateFilterParams, UpdateSortParams, }; use crate::notification::{send_notification, DatabaseNotification}; @@ -31,7 +31,7 @@ use crate::services::database_view::{ notify_did_update_setting, notify_did_update_sort, DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner, }; -use crate::services::field::TypeOptionCellDataHandler; +use crate::services::field::{DateTypeOption, TypeOptionCellDataHandler}; use crate::services::filter::{ Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType, }; @@ -42,13 +42,21 @@ use crate::services::setting::CalendarLayoutSetting; use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType}; pub trait DatabaseViewData: Send + Sync + 'static { - fn get_view_setting(&self, view_id: &str) -> Fut>; + fn get_view(&self, view_id: &str) -> Fut>; /// If the field_ids is None, then it will return all the field revisions fn get_fields(&self, view_id: &str, field_ids: Option>) -> Fut>>; /// Returns the field with the field_id fn get_field(&self, field_id: &str) -> Fut>>; + fn create_field( + &self, + view_id: &str, + name: &str, + field_type: FieldType, + type_option_data: TypeOptionData, + ) -> Fut; + fn get_primary_field(&self) -> Fut>>; /// Returns the index of the row with row_id @@ -62,7 +70,7 @@ pub trait DatabaseViewData: Send + Sync + 'static { fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> Fut>>; - fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut>>; + fn get_cell_in_row(&self, field_id: &str, row_id: &RowId) -> Fut>; fn get_layout_for_view(&self, view_id: &str) -> DatabaseLayout; @@ -99,6 +107,12 @@ pub trait DatabaseViewData: Send + Sync + 'static { layout_setting: LayoutSetting, ); + /// Return the database layout type for the view with given view_id + /// The default layout type is [DatabaseLayout::Grid] + fn get_layout_type(&self, view_id: &str) -> DatabaseLayout; + + fn update_layout_type(&self, view_id: &str, layout_type: &DatabaseLayout); + /// Returns a `TaskDispatcher` used to poll a `Task` fn get_task_scheduler(&self) -> Arc>; @@ -167,6 +181,10 @@ impl DatabaseViewEditor { self.filter_controller.close().await; } + pub async fn get_view(&self) -> Option { + self.delegate.get_view(&self.view_id).await + } + pub async fn v_will_create_row(&self, cells: &mut Cells, group_id: &Option) { if group_id.is_none() { return; @@ -398,7 +416,7 @@ impl DatabaseViewEditor { if !is_grouping_field { self.v_update_grouping_field(field_id).await?; - if let Some(view) = self.delegate.get_view_setting(&self.view_id).await { + if let Some(view) = self.delegate.get_view(&self.view_id).await { let setting = database_view_setting_pb_from_view(view); notify_did_update_setting(&self.view_id, setting).await; } @@ -571,16 +589,11 @@ impl DatabaseViewEditor { }, } - tracing::debug!("{:?}", layout_setting); layout_setting } /// Update the calendar settings and send the notification to refresh the UI - pub async fn v_set_layout_settings( - &self, - _layout_ty: &DatabaseLayout, - params: LayoutSettingParams, - ) -> FlowyResult<()> { + pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> { // Maybe it needs no send notification to refresh the UI if let Some(new_calendar_setting) = params.calendar { if let Some(field) = self @@ -593,16 +606,19 @@ impl DatabaseViewEditor { return Err(FlowyError::unexpect_calendar_field_type()); } - let layout_ty = DatabaseLayout::Calendar; - let old_calender_setting = self.v_get_layout_settings(&layout_ty).await.calendar; + let old_calender_setting = self + .v_get_layout_settings(¶ms.layout_type) + .await + .calendar; self.delegate.insert_layout_setting( &self.view_id, - &layout_ty, + ¶ms.layout_type, new_calendar_setting.clone().into(), ); let new_field_id = new_calendar_setting.field_id.clone(); - let layout_setting_pb: LayoutSettingPB = LayoutSettingParams { + let layout_setting_pb: DatabaseLayoutSettingPB = LayoutSettingParams { + layout_type: params.layout_type, calendar: Some(new_calendar_setting), } .into(); @@ -620,8 +636,6 @@ impl DatabaseViewEditor { .payload(layout_setting_pb) .send(); } - } else { - tracing::warn!("Calendar setting should not be empty") } } } @@ -788,6 +802,66 @@ impl DatabaseViewEditor { Some(events) } + #[tracing::instrument(level = "trace", skip_all)] + pub async fn v_update_layout_type(&self, layout_type: DatabaseLayout) -> FlowyResult<()> { + self + .delegate + .update_layout_type(&self.view_id, &layout_type); + + // Update the layout type in the database might add a new field to the database. If the new + // layout type is a calendar and there is not date field in the database, it will add a new + // date field to the database and create the corresponding layout setting. + // + let fields = self.delegate.get_fields(&self.view_id, None).await; + let date_field_id = match fields + .into_iter() + .find(|field| FieldType::from(field.field_type) == FieldType::DateTime) + { + None => { + tracing::trace!("Create a new date field after layout type change"); + let default_date_type_option = DateTypeOption::default(); + let field = self + .delegate + .create_field( + &self.view_id, + "Date", + FieldType::DateTime, + default_date_type_option.into(), + ) + .await; + field.id + }, + Some(date_field) => date_field.id.clone(), + }; + + let layout_setting = self.v_get_layout_settings(&layout_type).await; + match layout_type { + DatabaseLayout::Grid => {}, + DatabaseLayout::Board => {}, + DatabaseLayout::Calendar => { + if layout_setting.calendar.is_none() { + let layout_setting = CalendarLayoutSetting::new(date_field_id.clone()); + self + .v_set_layout_settings(LayoutSettingParams { + layout_type, + calendar: Some(layout_setting), + }) + .await?; + } + }, + } + + let payload = DatabaseLayoutMetaPB { + view_id: self.view_id.clone(), + layout: layout_type.into(), + }; + send_notification(&self.view_id, DatabaseNotification::DidUpdateDatabaseLayout) + .payload(payload) + .send(); + + Ok(()) + } + pub async fn handle_row_event(&self, event: Cow<'_, DatabaseRowEvent>) { let changeset = match event.into_owned() { DatabaseRowEvent::InsertRow(row) => { diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs index 797822abaf..128635e9dd 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs @@ -52,10 +52,8 @@ pub async fn new_group_controller( let layout = delegate.get_layout_for_view(&view_id); // If the view is a board and the grouping field is empty, we need to find a new grouping field - if layout.is_board() { - if grouping_field.is_none() { - grouping_field = find_new_grouping_field(&fields, &layout); - } + if layout.is_board() && grouping_field.is_none() { + grouping_field = find_new_grouping_field(&fields, &layout); } if let Some(grouping_field) = grouping_field { @@ -104,21 +102,20 @@ pub(crate) async fn get_cell_for_row( row_id: &RowId, ) -> Option { let field = delegate.get_field(field_id).await?; - let cell = delegate.get_cell_in_row(field_id, row_id).await?; + let row_cell = delegate.get_cell_in_row(field_id, row_id).await; let field_type = FieldType::from(field.field_type); + let handler = delegate.get_type_option_cell_handler(&field, &field_type)?; - if let Some(handler) = delegate.get_type_option_cell_handler(&field, &field_type) { - return match handler.get_cell_data(&cell, &field_type, &field) { - Ok(cell_data) => Some(RowSingleCellData { - row_id: cell.row_id.clone(), - field_id: field.id.clone(), - field_type: field_type.clone(), - cell_data, - }), - Err(_) => None, - }; - } - None + let cell_data = match &row_cell.cell { + None => None, + Some(cell) => handler.get_cell_data(&cell, &field_type, &field).ok(), + }; + Some(RowSingleCellData { + row_id: row_cell.row_id.clone(), + field_id: field.id.clone(), + field_type: field_type.clone(), + cell_data, + }) } // Returns the list of cells corresponding to the given field. @@ -133,17 +130,18 @@ pub(crate) async fn get_cells_for_field( let cells = delegate.get_cells_for_field(view_id, field_id).await; return cells .iter() - .flat_map( - |cell| match handler.get_cell_data(cell, &field_type, &field) { - Ok(cell_data) => Some(RowSingleCellData { - row_id: cell.row_id.clone(), - field_id: field.id.clone(), - field_type: field_type.clone(), - cell_data, - }), - Err(_) => None, - }, - ) + .map(|row_cell| { + let cell_data = match &row_cell.cell { + None => None, + Some(cell) => handler.get_cell_data(&cell, &field_type, &field).ok(), + }; + RowSingleCellData { + row_id: row_cell.row_id.clone(), + field_id: field.id.clone(), + field_type: field_type.clone(), + cell_data, + } + }) .collect(); } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs index b572427afe..b25de5ff59 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs @@ -19,7 +19,7 @@ use std::str::FromStr; /// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime]. /// So, storing the field type is necessary to distinguish the field type. /// Most of the cases, each [FieldType] has its own [TypeOption] implementation. -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct DateTypeOption { pub date_format: DateFormat, pub time_format: TimeFormat, @@ -27,6 +27,17 @@ pub struct DateTypeOption { pub field_type: FieldType, } +impl Default for DateTypeOption { + fn default() -> Self { + Self { + date_format: Default::default(), + time_format: Default::default(), + timezone_id: Default::default(), + field_type: FieldType::DateTime, + } + } +} + impl TypeOption for DateTypeOption { type CellData = DateCellData; type CellChangeset = DateCellChangeset; diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs index 8ebc6fd0f5..97eb2459a3 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs @@ -498,14 +498,14 @@ pub struct RowSingleCellData { pub row_id: RowId, pub field_id: String, pub field_type: FieldType, - pub cell_data: BoxCellData, + pub cell_data: Option, } macro_rules! into_cell_data { ($func_name:ident,$return_ty:ty) => { #[allow(dead_code)] pub fn $func_name(self) -> Option<$return_ty> { - self.cell_data.unbox_or_none() + self.cell_data?.unbox_or_none() } }; } diff --git a/frontend/rust-lib/flowy-database2/src/services/group/controller.rs b/frontend/rust-lib/flowy-database2/src/services/group/controller.rs index a23b240eda..e484229931 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/controller.rs @@ -254,6 +254,7 @@ where continue; } } + match self.context.get_mut_no_status_group() { None => {}, Some(no_status_group) => no_status_group.add_row((*row).clone()), @@ -349,12 +350,12 @@ where deleted_group: None, row_changesets: vec![], }; - let cell_rev = match context.row.cells.get(&self.grouping_field_id) { - Some(cell_rev) => Some(cell_rev.clone()), + let cell = match context.row.cells.get(&self.grouping_field_id) { + Some(cell) => Some(cell.clone()), None => self.placeholder_cell(), }; - if let Some(cell) = cell_rev { + if let Some(cell) = cell { let cell_bytes = get_cell_protobuf(&cell, context.field, None); let cell_data = cell_bytes.parser::

()?; result.deleted_group = self.delete_group_when_move_row(context.row, &cell_data); diff --git a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs index 091e51a21c..48f1c6f138 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs @@ -1,4 +1,4 @@ -use crate::entities::{GroupRowsNotificationPB, SelectOptionCellDataPB}; +use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB}; use crate::services::cell::insert_select_option_cell; use crate::services::field::{MultiSelectTypeOption, SelectOptionCellDataParser}; use crate::services::group::action::GroupCustomize; @@ -10,7 +10,7 @@ use crate::services::group::{ move_group_row, remove_select_option_row, GeneratedGroups, GroupContext, }; use collab_database::fields::Field; -use collab_database::rows::{Cells, Row}; +use collab_database::rows::{new_cell_builder, Cell, Cells, Row}; use std::sync::Arc; use serde::{Deserialize, Serialize}; @@ -39,6 +39,14 @@ impl GroupCustomize for MultiSelectGroupController { .any(|option| option.id == content) } + fn placeholder_cell(&self) -> Option { + Some( + new_cell_builder(FieldType::MultiSelect) + .insert_str_value("data", "") + .build(), + ) + } + fn add_or_remove_row_when_cell_changed( &mut self, row: &Row, diff --git a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/single_select_controller.rs b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/single_select_controller.rs index f8bef6d857..ffe3d678b3 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/single_select_controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/single_select_controller.rs @@ -1,9 +1,9 @@ -use crate::entities::{GroupRowsNotificationPB, SelectOptionCellDataPB}; +use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB}; use crate::services::cell::insert_select_option_cell; use crate::services::field::{SelectOptionCellDataParser, SingleSelectTypeOption}; use crate::services::group::action::GroupCustomize; use collab_database::fields::Field; -use collab_database::rows::{Cells, Row}; +use collab_database::rows::{new_cell_builder, Cell, Cells, Row}; use std::sync::Arc; use crate::services::group::controller::{ @@ -39,6 +39,14 @@ impl GroupCustomize for SingleSelectGroupController { .any(|option| option.id == content) } + fn placeholder_cell(&self) -> Option { + Some( + new_cell_builder(FieldType::SingleSelect) + .insert_str_value("data", "") + .build(), + ) + } + fn add_or_remove_row_when_cell_changed( &mut self, row: &Row, diff --git a/frontend/rust-lib/flowy-database2/src/services/group/group_util.rs b/frontend/rust-lib/flowy-database2/src/services/group/group_builder.rs similarity index 100% rename from frontend/rust-lib/flowy-database2/src/services/group/group_util.rs rename to frontend/rust-lib/flowy-database2/src/services/group/group_builder.rs diff --git a/frontend/rust-lib/flowy-database2/src/services/group/mod.rs b/frontend/rust-lib/flowy-database2/src/services/group/mod.rs index b73ac511b6..c9f9e91b65 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/mod.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/mod.rs @@ -3,10 +3,10 @@ mod configuration; mod controller; mod controller_impls; mod entities; -mod group_util; +mod group_builder; pub(crate) use configuration::*; pub(crate) use controller::*; pub(crate) use controller_impls::*; pub(crate) use entities::*; -pub(crate) use group_util::*; +pub(crate) use group_builder::*; diff --git a/frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs b/frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs index 741a816c93..b3b6bc0001 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs @@ -74,8 +74,8 @@ async fn text_cell_data_test() { .get_cells_for_field(&test.view_id, &text_field.id) .await; - for (i, cell) in cells.into_iter().enumerate() { - let text = StrCellData::from(cell.as_ref()); + for (i, row_cell) in cells.into_iter().enumerate() { + let text = StrCellData::from(row_cell.cell.as_ref().unwrap()); match i { 0 => assert_eq!(text.as_str(), "A"), 1 => assert_eq!(text.as_str(), ""), @@ -97,10 +97,12 @@ async fn url_cell_data_test() { .get_cells_for_field(&test.view_id, &url_field.id) .await; - for (i, cell) in cells.into_iter().enumerate() { - let cell = URLCellData::from(cell.as_ref()); - if i == 0 { - assert_eq!(cell.url.as_str(), "https://www.appflowy.io/"); + for (i, row_cell) in cells.into_iter().enumerate() { + if let Some(cell) = row_cell.cell.as_ref() { + let cell = URLCellData::from(cell); + if i == 0 { + assert_eq!(cell.url.as_str(), "https://www.appflowy.io/"); + } } } } @@ -135,8 +137,10 @@ async fn update_updated_at_field_on_other_cell_update() { .get_cells_for_field(&test.view_id, &updated_at_field.id) .await; assert!(!cells.is_empty()); - for (i, cell) in cells.into_iter().enumerate() { - let timestamp = DateCellData::from(cell.as_ref()).timestamp.unwrap(); + for (i, row_cell) in cells.into_iter().enumerate() { + let timestamp = DateCellData::from(row_cell.cell.as_ref().unwrap()) + .timestamp + .unwrap(); println!( "{}, bf: {}, af: {}", timestamp, before_update_timestamp, after_update_timestamp diff --git a/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs b/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs index 7c1fac3461..9b20320cb3 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs @@ -6,7 +6,7 @@ use collab_database::fields::Field; use collab_database::rows::{CreateRowParams, Row, RowId}; use strum::EnumCount; -use flowy_database2::entities::{DatabaseLayoutPB, FieldType, FilterPB, RowPB, SelectOptionPB}; +use flowy_database2::entities::{FieldType, FilterPB, RowPB, SelectOptionPB}; use flowy_database2::services::cell::{CellBuilder, ToCellChangeset}; use flowy_database2::services::database::DatabaseEditor; use flowy_database2::services::field::checklist_type_option::{ @@ -21,7 +21,9 @@ use flowy_error::FlowyResult; use flowy_test::folder_event::ViewTest; use flowy_test::FlowyCoreTest; -use crate::database::mock_data::{make_test_board, make_test_calendar, make_test_grid}; +use crate::database::mock_data::{ + make_no_date_test_grid, make_test_board, make_test_calendar, make_test_grid, +}; pub struct DatabaseEditorTest { pub sdk: FlowyCoreTest, @@ -36,35 +38,42 @@ pub struct DatabaseEditorTest { impl DatabaseEditorTest { pub async fn new_grid() -> Self { - Self::new(DatabaseLayoutPB::Grid).await + let sdk = FlowyCoreTest::new(); + let _ = sdk.init_user().await; + + let params = make_test_grid(); + let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await; + Self::new(sdk, view_test).await + } + + pub async fn new_no_date_grid() -> Self { + let sdk = FlowyCoreTest::new(); + let _ = sdk.init_user().await; + + let params = make_no_date_test_grid(); + let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await; + Self::new(sdk, view_test).await } pub async fn new_board() -> Self { - Self::new(DatabaseLayoutPB::Board).await + let sdk = FlowyCoreTest::new(); + let _ = sdk.init_user().await; + + let params = make_test_board(); + let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await; + Self::new(sdk, view_test).await } pub async fn new_calendar() -> Self { - Self::new(DatabaseLayoutPB::Calendar).await - } - - pub async fn new(layout: DatabaseLayoutPB) -> Self { let sdk = FlowyCoreTest::new(); let _ = sdk.init_user().await; - let test = match layout { - DatabaseLayoutPB::Grid => { - let params = make_test_grid(); - ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await - }, - DatabaseLayoutPB::Board => { - let data = make_test_board(); - ViewTest::new_board_view(&sdk, data.to_json_bytes().unwrap()).await - }, - DatabaseLayoutPB::Calendar => { - let data = make_test_calendar(); - ViewTest::new_calendar_view(&sdk, data.to_json_bytes().unwrap()).await - }, - }; + let params = make_test_calendar(); + let view_test = ViewTest::new_grid_view(&sdk, params.to_json_bytes().unwrap()).await; + Self::new(sdk, view_test).await + } + + pub async fn new(sdk: FlowyCoreTest, test: ViewTest) -> Self { let editor = sdk .database_manager .get_database_with_view_id(&test.child_view.id) diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs index 0bd5382fb8..51765eab51 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs @@ -265,7 +265,7 @@ impl DatabaseFilterTest { assert_eq!(expected_setting, setting); } FilterScript::AssertNumberOfVisibleRows { expected } => { - let grid = self.editor.get_database_data(&self.view_id).await; + let grid = self.editor.get_database_data(&self.view_id).await.unwrap(); assert_eq!(grid.rows.len(), expected); } FilterScript::Wait { millisecond } => { diff --git a/frontend/rust-lib/flowy-database2/tests/database/layout_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/layout_test/script.rs index 356c1837e0..02103f81b7 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/layout_test/script.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/layout_test/script.rs @@ -8,7 +8,9 @@ use crate::database::database_editor::DatabaseEditorTest; pub enum LayoutScript { AssertCalendarLayoutSetting { expected: CalendarLayoutSetting }, - GetCalendarEvents, + AssertDefaultAllCalendarEvents, + AssertAllCalendarEventsCount { expected: usize }, + UpdateDatabaseLayout { layout: DatabaseLayout }, } pub struct DatabaseLayoutTest { @@ -16,6 +18,11 @@ pub struct DatabaseLayoutTest { } impl DatabaseLayoutTest { + pub async fn new_no_date_grid() -> Self { + let database_test = DatabaseEditorTest::new_no_date_grid().await; + Self { database_test } + } + pub async fn new_calendar() -> Self { let database_test = DatabaseEditorTest::new_calendar().await; Self { database_test } @@ -33,6 +40,22 @@ impl DatabaseLayoutTest { pub async fn run_script(&mut self, script: LayoutScript) { match script { + LayoutScript::UpdateDatabaseLayout { layout } => { + self + .database_test + .editor + .update_view_layout(&self.database_test.view_id, layout) + .await + .unwrap(); + }, + LayoutScript::AssertAllCalendarEventsCount { expected } => { + let events = self + .database_test + .editor + .get_all_calendar_events(&self.database_test.view_id) + .await; + assert_eq!(events.len(), expected); + }, LayoutScript::AssertCalendarLayoutSetting { expected } => { let view_id = self.database_test.view_id.clone(); let layout_ty = DatabaseLayout::Calendar; @@ -53,7 +76,7 @@ impl DatabaseLayoutTest { ); assert_eq!(calendar_setting.show_weekends, expected.show_weekends); }, - LayoutScript::GetCalendarEvents => { + LayoutScript::AssertDefaultAllCalendarEvents => { let events = self .database_test .editor diff --git a/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs b/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs index 10824c5bdc..a380e4b511 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs @@ -1,3 +1,4 @@ +use collab_database::views::DatabaseLayout; use flowy_database2::services::setting::CalendarLayoutSetting; use crate::database::layout_test::script::DatabaseLayoutTest; @@ -17,6 +18,18 @@ async fn calendar_initial_layout_setting_test() { #[tokio::test] async fn calendar_get_events_test() { let mut test = DatabaseLayoutTest::new_calendar().await; - let scripts = vec![GetCalendarEvents]; + let scripts = vec![AssertDefaultAllCalendarEvents]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_to_calendar_layout_test() { + let mut test = DatabaseLayoutTest::new_no_date_grid().await; + let scripts = vec![ + UpdateDatabaseLayout { + layout: DatabaseLayout::Calendar, + }, + AssertAllCalendarEventsCount { expected: 3 }, + ]; test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs b/frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs index dd980db835..73ecdec6fb 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs @@ -240,3 +240,80 @@ pub fn make_test_grid() -> DatabaseData { DatabaseData { view, fields, rows } } + +pub fn make_no_date_test_grid() -> DatabaseData { + let mut fields = vec![]; + let mut rows = vec![]; + // Iterate through the FieldType to create the corresponding Field. + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => { + let text_field = FieldBuilder::from_field_type(field_type.clone()) + .name("Name") + .visibility(true) + .primary(true) + .build(); + fields.push(text_field); + }, + FieldType::Number => { + // Number + let mut type_option = NumberTypeOption::default(); + type_option.set_format(NumberFormat::USD); + + let number_field = FieldBuilder::new(field_type.clone(), type_option) + .name("Price") + .visibility(true) + .build(); + fields.push(number_field); + }, + _ => {}, + } + } + + for i in 0..3 { + let mut row_builder = TestRowBuilder::new(i.into(), &fields); + match i { + 0 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("A"), + FieldType::Number => row_builder.insert_number_cell("1"), + _ => "".to_owned(), + }; + } + }, + 1 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell(""), + FieldType::Number => row_builder.insert_number_cell("2"), + _ => "".to_owned(), + }; + } + }, + 2 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("C"), + FieldType::Number => row_builder.insert_number_cell("3"), + _ => "".to_owned(), + }; + } + }, + _ => {}, + } + + let row = row_builder.build(); + rows.push(row); + } + + let view = DatabaseView { + id: gen_database_view_id(), + database_id: gen_database_id(), + name: "".to_string(), + layout: DatabaseLayout::Grid, + ..Default::default() + }; + + DatabaseData { view, fields, rows } +} diff --git a/frontend/rust-lib/flowy-folder2/src/entities/view.rs b/frontend/rust-lib/flowy-folder2/src/entities/view.rs index 9a14766c00..5b5a674221 100644 --- a/frontend/rust-lib/flowy-folder2/src/entities/view.rs +++ b/frontend/rust-lib/flowy-folder2/src/entities/view.rs @@ -1,5 +1,5 @@ use crate::entities::parser::view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail}; -use crate::view_ext::gen_view_id; +use crate::view_operation::gen_view_id; use collab_folder::core::{View, ViewLayout}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; @@ -31,7 +31,7 @@ pub struct ViewPB { pub fn view_pb_without_child_views(view: View) -> ViewPB { ViewPB { id: view.id, - parent_view_id: view.bid, + parent_view_id: view.parent_view_id, name: view.name, create_time: view.created_at, child_views: Default::default(), @@ -43,7 +43,7 @@ pub fn view_pb_without_child_views(view: View) -> ViewPB { pub fn view_pb_with_child_views(view: View, child_views: Vec) -> ViewPB { ViewPB { id: view.id, - parent_view_id: view.bid, + parent_view_id: view.parent_view_id, name: view.name, create_time: view.created_at, child_views: child_views @@ -219,6 +219,9 @@ pub struct UpdateViewPayloadPB { #[pb(index = 4, one_of)] pub thumbnail: Option, + + #[pb(index = 5, one_of)] + pub layout: Option, } #[derive(Clone, Debug)] @@ -227,6 +230,7 @@ pub struct UpdateViewParams { pub name: Option, pub desc: Option, pub thumbnail: Option, + pub layout: Option, } impl TryInto for UpdateViewPayloadPB { @@ -255,6 +259,7 @@ impl TryInto for UpdateViewPayloadPB { name, desc, thumbnail, + layout: self.layout.map(|ty| ty.into()), }) } } diff --git a/frontend/rust-lib/flowy-folder2/src/lib.rs b/frontend/rust-lib/flowy-folder2/src/lib.rs index 8ce0cfc1f4..b7ae39e2b0 100644 --- a/frontend/rust-lib/flowy-folder2/src/lib.rs +++ b/frontend/rust-lib/flowy-folder2/src/lib.rs @@ -5,7 +5,7 @@ pub mod manager; mod notification; pub mod protobuf; mod user_default; -pub mod view_ext; +pub mod view_operation; pub mod deps; mod share; diff --git a/frontend/rust-lib/flowy-folder2/src/manager.rs b/frontend/rust-lib/flowy-folder2/src/manager.rs index 46a3dee881..fc720b6b72 100644 --- a/frontend/rust-lib/flowy-folder2/src/manager.rs +++ b/frontend/rust-lib/flowy-folder2/src/manager.rs @@ -29,7 +29,9 @@ use crate::notification::{ }; use crate::share::ImportParams; use crate::user_default::DefaultFolderBuilder; -use crate::view_ext::{create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers}; +use crate::view_operation::{ + create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers, +}; pub struct Folder2Manager { mutex_folder: Arc, @@ -200,18 +202,12 @@ impl Folder2Manager { let view_layout: ViewLayout = params.layout.clone().into(); let handler = self.get_handler(&view_layout)?; let user_id = self.user.user_id()?; - let ext = params.meta.clone(); + let meta = params.meta.clone(); match params.initial_data.is_empty() { true => { tracing::trace!("Create view with build-in data"); handler - .create_built_in_view( - user_id, - ¶ms.view_id, - ¶ms.name, - view_layout.clone(), - ext, - ) + .create_built_in_view(user_id, ¶ms.view_id, ¶ms.name, view_layout.clone()) .await?; }, false => { @@ -223,7 +219,7 @@ impl Folder2Manager { ¶ms.name, params.initial_data.clone(), view_layout.clone(), - ext, + meta, ) .await?; }, @@ -233,7 +229,7 @@ impl Folder2Manager { folder.insert_view(view.clone()); }); - notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid.clone()]); + notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]); Ok(view) } @@ -336,7 +332,7 @@ impl Folder2Manager { match view { None => tracing::error!("Couldn't find the view. It should not be empty"), Some(view) => { - notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid]); + notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id]); }, } Ok(()) @@ -350,18 +346,23 @@ impl Folder2Manager { #[tracing::instrument(level = "trace", skip(self), err)] pub async fn update_view_with_params(&self, params: UpdateViewParams) -> FlowyResult<()> { - let _ = self - .mutex_folder - .lock() - .as_ref() - .ok_or_else(folder_not_init_error)? - .views - .update_view(¶ms.view_id, |update| { + let value = self.with_folder(None, |folder| { + let old_view = folder.views.get_view(¶ms.view_id); + let new_view = folder.views.update_view(¶ms.view_id, |update| { update .set_name_if_not_none(params.name) .set_desc_if_not_none(params.desc) + .set_layout_if_not_none(params.layout) .done() }); + Some((old_view, new_view)) + }); + + if let Some((Some(old_view), Some(new_view))) = value { + if let Ok(handler) = self.get_handler(&old_view.layout) { + handler.did_update_view(&old_view, &new_view).await?; + } + } if let Ok(view_pb) = self.get_view(¶ms.view_id).await { notify_parent_view_did_change( @@ -388,7 +389,7 @@ impl Folder2Manager { // meta.insert("database_id".to_string(), database_id); // } let duplicate_params = CreateViewParams { - parent_view_id: view.bid.clone(), + parent_view_id: view.parent_view_id.clone(), name: format!("{} (copy)", &view.name), desc: view.desc, layout: view.layout.into(), @@ -501,7 +502,7 @@ impl Folder2Manager { self.with_folder((), |folder| { folder.insert_view(view.clone()); }); - notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid.clone()]); + notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]); Ok(view) } @@ -529,11 +530,11 @@ fn listen_on_view_change(mut rx: ViewChangeReceiver, weak_mutex_folder: &Weak { - notify_parent_view_did_change(folder.clone(), vec![view.bid]); + notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]); }, ViewChange::DidDeleteView { views: _ } => {}, ViewChange::DidUpdate { view } => { - notify_parent_view_did_change(folder.clone(), vec![view.bid]); + notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]); }, }; } @@ -580,7 +581,7 @@ fn listen_on_trash_change(mut rx: TrashChangeReceiver, weak_mutex_folder: &Weak< if let Some(folder) = folder.lock().as_ref() { let views = folder.views.get_views(&ids); for view in views { - unique_ids.insert(view.bid); + unique_ids.insert(view.parent_view_id); } let repeated_trash: RepeatedTrashPB = folder.trash.get_all_trash().into(); diff --git a/frontend/rust-lib/flowy-folder2/src/test_helper.rs b/frontend/rust-lib/flowy-folder2/src/test_helper.rs index 62eb5621a7..91e78a16bf 100644 --- a/frontend/rust-lib/flowy-folder2/src/test_helper.rs +++ b/frontend/rust-lib/flowy-folder2/src/test_helper.rs @@ -1,6 +1,6 @@ use crate::entities::{CreateViewParams, ViewLayoutPB}; use crate::manager::Folder2Manager; -use crate::view_ext::gen_view_id; +use crate::view_operation::gen_view_id; use std::collections::HashMap; #[cfg(feature = "test_helper")] diff --git a/frontend/rust-lib/flowy-folder2/src/user_default.rs b/frontend/rust-lib/flowy-folder2/src/user_default.rs index 7c6b62ceae..689be6a626 100644 --- a/frontend/rust-lib/flowy-folder2/src/user_default.rs +++ b/frontend/rust-lib/flowy-folder2/src/user_default.rs @@ -1,11 +1,9 @@ -use std::collections::HashMap; - use chrono::Utc; use collab_folder::core::{FolderData, RepeatedView, View, ViewIdentifier, ViewLayout, Workspace}; use nanoid::nanoid; use crate::entities::{view_pb_with_child_views, WorkspacePB}; -use crate::view_ext::{gen_view_id, FolderOperationHandlers}; +use crate::view_operation::{gen_view_id, FolderOperationHandlers}; pub struct DefaultFolderBuilder(); impl DefaultFolderBuilder { @@ -21,7 +19,7 @@ impl DefaultFolderBuilder { let child_view_layout = ViewLayout::Document; let child_view = View { id: child_view_id.clone(), - bid: view_id.clone(), + parent_view_id: view_id.clone(), name: "Read me".to_string(), desc: "".to_string(), created_at: time, @@ -39,14 +37,13 @@ impl DefaultFolderBuilder { &child_view.id, &child_view.name, child_view_layout.clone(), - HashMap::default(), ) .await .unwrap(); let view = View { id: view_id, - bid: workspace_id.clone(), + parent_view_id: workspace_id.clone(), name: "⭐️ Getting started".to_string(), desc: "".to_string(), children: RepeatedView::new(vec![ViewIdentifier { diff --git a/frontend/rust-lib/flowy-folder2/src/view_ext.rs b/frontend/rust-lib/flowy-folder2/src/view_operation.rs similarity index 73% rename from frontend/rust-lib/flowy-folder2/src/view_ext.rs rename to frontend/rust-lib/flowy-folder2/src/view_operation.rs index 2f1a3172f5..c338fd8b45 100644 --- a/frontend/rust-lib/flowy-folder2/src/view_ext.rs +++ b/frontend/rust-lib/flowy-folder2/src/view_operation.rs @@ -1,6 +1,6 @@ use crate::entities::{CreateViewParams, ViewLayoutPB}; use bytes::Bytes; -use collab_folder::core::{View, ViewLayout}; +use collab_folder::core::ViewLayout; use flowy_error::FlowyError; use lib_infra::future::FutureResult; use lib_infra::util::timestamp; @@ -9,6 +9,7 @@ use std::collections::HashMap; use std::sync::Arc; pub type ViewData = Bytes; +pub use collab_folder::core::View; /// The handler will be used to handler the folder operation for a specific /// view layout. Each [ViewLayout] will have a handler. So when creating a new @@ -22,7 +23,18 @@ pub trait FolderOperationHandler { /// Returns the [ViewData] that can be used to create the same view. fn duplicate_view(&self, view_id: &str) -> FutureResult; - /// Create a view with custom data + /// Create a view with the data. + /// + /// # Arguments + /// + /// * `user_id`: the user id + /// * `view_id`: the view id + /// * `name`: the name of the view + /// * `data`: initial data of the view. The data should be parsed by the [FolderOperationHandler] + /// implementation. For example, the data of the database will be [DatabaseData]. + /// * `layout`: the layout of the view + /// * `meta`: use to carry extra information. For example, the database view will use this + /// to carry the reference database id. fn create_view_with_view_data( &self, user_id: i64, @@ -30,7 +42,7 @@ pub trait FolderOperationHandler { name: &str, data: Vec, layout: ViewLayout, - ext: HashMap, + meta: HashMap, ) -> FutureResult<(), FlowyError>; /// Create a view with the pre-defined data. @@ -42,7 +54,6 @@ pub trait FolderOperationHandler { view_id: &str, name: &str, layout: ViewLayout, - meta: HashMap, ) -> FutureResult<(), FlowyError>; /// Create a view by importing data @@ -60,6 +71,11 @@ pub trait FolderOperationHandler { name: &str, path: String, ) -> FutureResult<(), FlowyError>; + + /// Called when the view is updated. The handler is the `old` registered handler. + fn did_update_view(&self, _old: &View, _new: &View) -> FutureResult<(), FlowyError> { + FutureResult::new(async move { Ok(()) }) + } } pub type FolderOperationHandlers = @@ -80,7 +96,7 @@ pub(crate) fn create_view(params: CreateViewParams, layout: ViewLayout) -> View let time = timestamp(); View { id: params.view_id, - bid: params.parent_view_id, + parent_view_id: params.parent_view_id, name: params.name, desc: params.desc, children: Default::default(), diff --git a/frontend/rust-lib/flowy-folder2/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder2/tests/workspace/script.rs index fae767d96a..22e8d64c87 100644 --- a/frontend/rust-lib/flowy-folder2/tests/workspace/script.rs +++ b/frontend/rust-lib/flowy-folder2/tests/workspace/script.rs @@ -274,6 +274,7 @@ pub async fn update_view( name, desc, thumbnail: None, + layout: None, }; EventBuilder::new(sdk.clone()) .event(UpdateView)