diff --git a/frontend/.vscode/launch.json b/frontend/.vscode/launch.json index 72d398e0fac56..317392654bb69 100644 --- a/frontend/.vscode/launch.json +++ b/frontend/.vscode/launch.json @@ -138,4 +138,4 @@ "cwd": "${workspaceRoot}/appflowy_tauri/" }, ] -} \ No newline at end of file +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart index 943f8ec20c5e4..e4eedd87fd0f6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart @@ -52,7 +52,7 @@ class DatabaseTabBarBloc _createLinkedView(layout.layoutType, name ?? layout.layoutName); }, deleteView: (String viewId) async { - final result = await ViewBackendService.delete(viewId: viewId); + final result = await ViewBackendService.deleteView(viewId: viewId); result.fold( (l) {}, (r) => Log.error(r), diff --git a/frontend/appflowy_flutter/lib/shared/feature_flags.dart b/frontend/appflowy_flutter/lib/shared/feature_flags.dart index 31e61ebb08578..2c727a8d464df 100644 --- a/frontend/appflowy_flutter/lib/shared/feature_flags.dart +++ b/frontend/appflowy_flutter/lib/shared/feature_flags.dart @@ -35,6 +35,9 @@ enum FeatureFlag { // used for controlling whether to show plan+billing options in settings planBilling, + // used for space design + spaceDesign, + // used for ignore the conflicted feature flag unknown; @@ -88,6 +91,8 @@ enum FeatureFlag { bool get isOn { if ([ + // release this feature in version 0.6.1 + FeatureFlag.spaceDesign, // release this feature in version 0.5.9 FeatureFlag.search, // release this feature in version 0.5.6 @@ -105,15 +110,16 @@ enum FeatureFlag { } switch (this) { + case FeatureFlag.search: + case FeatureFlag.syncDocument: + case FeatureFlag.syncDatabase: + case FeatureFlag.spaceDesign: + return true; case FeatureFlag.collaborativeWorkspace: case FeatureFlag.membersSettings: case FeatureFlag.planBilling: case FeatureFlag.unknown: return false; - case FeatureFlag.search: - case FeatureFlag.syncDocument: - case FeatureFlag.syncDatabase: - return true; } } @@ -131,6 +137,8 @@ enum FeatureFlag { return 'if it\'s on, the command palette and search button will be available'; case FeatureFlag.planBilling: return 'if it\'s on, plan and billing pages will be available in Settings'; + case FeatureFlag.spaceDesign: + return 'if it\'s on, the space design feature will be available'; case FeatureFlag.unknown: return ''; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart index ebab63428a70d..9d79ca8550b20 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart @@ -87,7 +87,6 @@ class SpaceBloc extends Bloc { isExpanded: isExpanded, shouldShowUpgradeDialog: shouldShowUpgradeDialog, isInitialized: true, - issueViews: [], ), ); @@ -142,7 +141,7 @@ class SpaceBloc extends Bloc { if (deletedSpace == null) { return; } - await ViewBackendService.delete(viewId: deletedSpace.id); + await ViewBackendService.deleteView(viewId: deletedSpace.id); }, rename: (space, name) async { add(SpaceEvent.update(name: name)); @@ -330,12 +329,6 @@ class SpaceBloc extends Bloc { emit(state.copyWith(isDuplicatingSpace: false)); }, - reassignIssueViews: () async { - await _reassignIssueViews(); - }, - updateIssueViews: (issueViews) async { - emit(state.copyWith(issueViews: issueViews)); - }, ); }, ); @@ -440,6 +433,7 @@ class SpaceBloc extends Bloc { workspaceId: workspaceId, )..start( sectionChanged: (result) async { + Log.info('did receive section views changed'); add(const SpaceEvent.didReceiveSpaceUpdate()); }, ); @@ -680,28 +674,6 @@ class SpaceBloc extends Bloc { return newSpace; } - - Future _reassignIssueViews() async { - final issueViews = state.issueViews; - if (issueViews.isEmpty) { - return; - } - for (final view in issueViews) { - final result = await ViewBackendService.moveViewV2( - viewId: view.id, - newParentId: view.parentViewId, - prevViewId: null, - ); - result.fold( - (_) { - Log.info('space: reassign issue view: ${view.name}(${view.id})'); - }, - (error) { - Log.error('space: failed to reassign issue view: $error'); - }, - ); - } - } } @freezed @@ -743,9 +715,6 @@ class SpaceEvent with _$SpaceEvent { ) = _Reset; const factory SpaceEvent.migrate() = _Migrate; const factory SpaceEvent.switchToNextSpace() = _SwitchToNextSpace; - const factory SpaceEvent.reassignIssueViews() = _ReassignIssueViews; - const factory SpaceEvent.updateIssueViews(List issueViews) = - _UpdateIssueViews; } @freezed @@ -760,7 +729,6 @@ class SpaceState with _$SpaceState { @Default(false) bool shouldShowUpgradeDialog, @Default(false) bool isDuplicatingSpace, @Default(false) bool isInitialized, - @Default([]) List issueViews, }) = _SpaceState; factory SpaceState.initial() => const SpaceState(); 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 44300468a15bb..2a1ce58400d02 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart @@ -143,7 +143,7 @@ class ViewBloc extends Bloc { // unpublish the page and all its child pages if they are published await _unpublishPage(view); - final result = await ViewBackendService.delete(viewId: view.id); + final result = await ViewBackendService.deleteView(viewId: view.id); emit( result.fold( 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 f07b90ef4f2bf..d1c865937074a 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart @@ -122,17 +122,17 @@ class ViewBackendService { }); } - static Future> delete({ + static Future> deleteView({ required String viewId, }) { final request = RepeatedViewIdPB.create()..items.add(viewId); return FolderEventDeleteView(request).send(); } - static Future> deleteView({ - required String viewId, + static Future> deleteViews({ + required List viewIds, }) { - final request = RepeatedViewIdPB.create()..items.add(viewId); + final request = RepeatedViewIdPB.create()..items.addAll(viewIds); return FolderEventDeleteView(request).send(); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart index 8bbdd1322c47d..04719039092d8 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart @@ -27,7 +27,6 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_migration.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart' show UserProfilePB; @@ -140,8 +139,7 @@ class HomeSideBar extends StatelessWidget { BlocListener( listenWhen: (p, c) => p.lastCreatedPage?.id != c.lastCreatedPage?.id || - p.isDuplicatingSpace != c.isDuplicatingSpace || - p.issueViews != c.issueViews, + p.isDuplicatingSpace != c.isDuplicatingSpace, listener: (context, state) { final page = state.lastCreatedPage; if (page == null || page.id.isEmpty) { @@ -166,20 +164,6 @@ class HomeSideBar extends StatelessWidget { _duplicateSpaceLoading?.stop(); _duplicateSpaceLoading = null; } - - if (state.issueViews.isNotEmpty) { - showConfirmDialog( - context: context, - title: 'Some errors happened', - description: - 'There are 2 pages (Module 3, Module 4) that have not been synced to the space. Click OK to sync them.', - onConfirm: () { - context.read().add( - const SpaceEvent.reassignIssueViews(), - ); - }, - ); - } }, ), BlocListener( @@ -193,7 +177,8 @@ class HomeSideBar extends StatelessWidget { if (actionType == UserWorkspaceActionType.create || actionType == UserWorkspaceActionType.delete || actionType == UserWorkspaceActionType.open) { - if (context.read().state.spaces.isEmpty) { + if (!FeatureFlag.spaceDesign.isOn || + context.read().state.spaces.isEmpty) { context.read().add( SidebarSectionsEvent.reload( userProfile, @@ -390,7 +375,8 @@ class _SidebarState extends State<_Sidebar> { context.read().add(const SpaceEvent.didReceiveSpaceUpdate()); } - return !containsSpace || + return !FeatureFlag.spaceDesign.isOn || + !containsSpace || spaceState.spaces.isEmpty || !workspaceState.isCollabWorkspaceOn ? Expanded( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart index efd98647afcac..f06866695828e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart @@ -292,11 +292,15 @@ class ConfirmPopup extends StatefulWidget { required this.title, required this.description, required this.onConfirm, + this.confirmLabel, + this.confirmButtonColor, }); final String title; final String description; final VoidCallback onConfirm; + final String? confirmLabel; + final Color? confirmButtonColor; final ConfirmPopupStyle style; @override @@ -376,8 +380,9 @@ class _ConfirmPopupState extends State { widget.onConfirm(); Navigator.of(context).pop(); }, - confirmButtonName: LocaleKeys.button_ok.tr(), - confirmButtonColor: Theme.of(context).colorScheme.primary, + confirmButtonName: widget.confirmLabel ?? LocaleKeys.button_ok.tr(), + confirmButtonColor: widget.confirmButtonColor ?? + Theme.of(context).colorScheme.primary, ); case ConfirmPopupStyle.cancelAndOk: return SpaceCancelOrConfirmButton( @@ -386,8 +391,10 @@ class _ConfirmPopupState extends State { widget.onConfirm(); Navigator.of(context).pop(); }, - confirmButtonName: LocaleKeys.space_delete.tr(), - confirmButtonColor: Theme.of(context).colorScheme.error, + confirmButtonName: + widget.confirmLabel ?? LocaleKeys.space_delete.tr(), + confirmButtonColor: + widget.confirmButtonColor ?? Theme.of(context).colorScheme.error, ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart index 5664f0f2ce589..6f556ec5b6202 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart @@ -114,7 +114,10 @@ class WorkspaceDataManager { List? allViews, bool dryRun = true, }) async { - final List issues = []; + // Views whose parent view does not have the view in its child views + final List unlistedChildViews = []; + // Views whose parent is not in allViews + final List orphanViews = []; try { if (workspace == null || allViews == null) { @@ -129,12 +132,16 @@ class WorkspaceDataManager { } for (final view in allViews) { + if (view.parentViewId == '') { + continue; + } + final parentView = allViews.firstWhereOrNull( (e) => e.id == view.parentViewId, ); if (parentView == null) { - Log.info('found an issue: can not find the parent view: $view'); + orphanViews.add(view); continue; } @@ -143,17 +150,50 @@ class WorkspaceDataManager { .getOrThrow(); final result = childViewsOfParent.any((e) => e.id == view.id); if (!result) { - Log.info( - 'found an issue: view is not in the parent view\'s child views: $view', - ); - issues.add(view); + unlistedChildViews.add(view); } } } catch (e) { Log.error('Failed to check space health: $e'); + return []; + } + + for (final view in unlistedChildViews) { + Log.info( + '[workspace] found an issue: view is not in the parent view\'s child views, view: ${view.toProto3Json()}}', + ); + } + + for (final view in orphanViews) { + Log.debug('[workspace] orphanViews: ${view.toProto3Json()}'); + } + + if (!dryRun && unlistedChildViews.isNotEmpty) { + Log.info( + '[workspace] start to fix ${unlistedChildViews.length} unlistedChildViews ...', + ); + for (final view in unlistedChildViews) { + // move the view to the parent view if it is not in the parent view's child views + Log.info( + '[workspace] move view: $view to its parent view ${view.parentViewId}', + ); + await ViewBackendService.moveViewV2( + viewId: view.id, + newParentId: view.parentViewId, + prevViewId: null, + ); + } + + Log.info('[workspace] end to fix unlistedChildViews'); + } + + if (unlistedChildViews.isEmpty && orphanViews.isEmpty) { + Log.info('[workspace] all views are healthy'); } - return issues; + Log.info('[workspace] done checking view health'); + + return unlistedChildViews; } static void dumpViews(String prefix, List views) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart index 5b8965ffbe620..3e169eb806cf8 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart @@ -126,26 +126,34 @@ class SettingsManageDataView extends StatelessWidget { buttonLabel: LocaleKeys.settings_manageDataPage_cache_title.tr(), onPressed: () { - SettingsAlertDialog( + showCancelAndConfirmDialog( + context: context, title: LocaleKeys .settings_manageDataPage_cache_dialog_title .tr(), - subtitle: LocaleKeys + description: LocaleKeys .settings_manageDataPage_cache_dialog_description .tr(), - confirm: () async { + confirmLabel: LocaleKeys.button_ok.tr(), + onConfirm: () async { + // clear all cache await getIt().clearAllCache(); + + // check the workspace and space health + await WorkspaceDataManager.checkViewHealth( + dryRun: false, + ); + if (context.mounted) { - showSnackBarMessage( + showToastNotification( context, - LocaleKeys + message: LocaleKeys .settings_manageDataPage_cache_dialog_successHint .tr(), ); - Navigator.of(context).pop(); } }, - ).show(context); + ); }, ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart index 1fe4cc7502519..868c53201df97 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart @@ -367,3 +367,32 @@ Future showConfirmDialog({ }, ); } + +Future showCancelAndConfirmDialog({ + required BuildContext context, + required String title, + required String description, + VoidCallback? onConfirm, + String? confirmLabel, +}) { + return showDialog( + context: context, + builder: (_) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: SizedBox( + width: 440, + child: ConfirmPopup( + title: title, + description: description, + onConfirm: () => onConfirm?.call(), + confirmLabel: confirmLabel, + confirmButtonColor: Theme.of(context).colorScheme.primary, + ), + ), + ); + }, + ); +} diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index b2bceb1152e39..ede6a24f8616d 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -494,10 +494,10 @@ }, "cache": { "title": "Clear cache", - "description": "Clear app cache, this can help resolve issues like images or fonts not loading. This will not affect your data.", + "description": "Help resolve issues like image not loading, missing pages in a space, and fonts not loading. This will not affect your data.", "dialog": { - "title": "Are you sure?", - "description": "Clearing the cache will cause images and fonts to be re-downloaded on load. This action will not remove or modify your data.", + "title": "Clear cache", + "description": "Help resolve issues like image not loading, missing pages in a space, and fonts not loading. This will not affect your data.", "successHint": "Cache cleared!" } }, diff --git a/frontend/rust-lib/flowy-folder/src/manager_observer.rs b/frontend/rust-lib/flowy-folder/src/manager_observer.rs index ef604b3a11796..91bd450a70e2b 100644 --- a/frontend/rust-lib/flowy-folder/src/manager_observer.rs +++ b/frontend/rust-lib/flowy-folder/src/manager_observer.rs @@ -235,7 +235,6 @@ pub(crate) fn notify_did_update_section_views(workspace_id: &str, folder: &Folde private_views.len() ); - // TODO(Lucas.xu) - Only notify the section changed, not the public/private both. // Notify the public views send_notification(workspace_id, FolderNotification::DidUpdateSectionViews) .payload(SectionViewsPB {