mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: reset space relationship when clearing cache (#5737)
* fix: space's child views didn't update when moving a page into it * chore: remove check logic * feat: integrate fix space into clear cache * fix: translation missing value * chore: update logger version
This commit is contained in:
parent
c6ad57f11d
commit
44d8def3ce
2
frontend/.vscode/launch.json
vendored
2
frontend/.vscode/launch.json
vendored
@ -138,4 +138,4 @@
|
||||
"cwd": "${workspaceRoot}/appflowy_tauri/"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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 '';
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
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));
|
||||
@ -433,6 +433,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
workspaceId: workspaceId,
|
||||
)..start(
|
||||
sectionChanged: (result) async {
|
||||
Log.info('did receive section views changed');
|
||||
add(const SpaceEvent.didReceiveSpaceUpdate());
|
||||
},
|
||||
);
|
||||
@ -503,6 +504,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
if (_workspaceId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final user =
|
||||
await UserBackendService.getCurrentUserProfile().getOrThrow();
|
||||
@ -526,6 +528,13 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
(e) => e.isSpace && e.spacePermission == SpacePermission.publicToAll,
|
||||
);
|
||||
publicViews = publicViews.where((e) => !e.isSpace).toList();
|
||||
|
||||
for (final view in publicViews) {
|
||||
Log.info(
|
||||
'migrating: the public view should be migrated: ${view.name}(${view.id})',
|
||||
);
|
||||
}
|
||||
|
||||
// if there is already a public space, don't migrate the public space
|
||||
// only migrate the public space if there are any public views
|
||||
if (publicViews.isEmpty || containsPublicSpace) {
|
||||
@ -568,6 +577,13 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
(e) => e.isSpace && e.spacePermission == SpacePermission.private,
|
||||
);
|
||||
privateViews = privateViews.where((e) => !e.isSpace).toList();
|
||||
|
||||
for (final view in privateViews) {
|
||||
Log.info(
|
||||
'migrating: the private view should be migrated: ${view.name}(${view.id})',
|
||||
);
|
||||
}
|
||||
|
||||
if (privateViews.isEmpty || containsPrivateSpace) {
|
||||
return true;
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
// 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(
|
||||
|
@ -122,17 +122,17 @@ class ViewBackendService {
|
||||
});
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> delete({
|
||||
static Future<FlowyResult<void, FlowyError>> deleteView({
|
||||
required String viewId,
|
||||
}) {
|
||||
final request = RepeatedViewIdPB.create()..items.add(viewId);
|
||||
return FolderEventDeleteView(request).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> deleteView({
|
||||
required String viewId,
|
||||
static Future<FlowyResult<void, FlowyError>> deleteViews({
|
||||
required List<String> viewIds,
|
||||
}) {
|
||||
final request = RepeatedViewIdPB.create()..items.add(viewId);
|
||||
final request = RepeatedViewIdPB.create()..items.addAll(viewIds);
|
||||
return FolderEventDeleteView(request).send();
|
||||
}
|
||||
|
||||
|
@ -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<ConfirmPopup> {
|
||||
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<ConfirmPopup> {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import 'package:appflowy/workspace/presentation/settings/shared/single_setting_a
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -24,7 +25,7 @@ class FixDataWidget extends StatelessWidget {
|
||||
.tr(),
|
||||
buttonLabel: LocaleKeys.settings_manageDataPage_data_fixButton.tr(),
|
||||
onPressed: () {
|
||||
FixDataManager.checkWorkspaceHealth(dryRun: true);
|
||||
WorkspaceDataManager.checkWorkspaceHealth(dryRun: true);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -32,7 +33,7 @@ class FixDataWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class FixDataManager {
|
||||
class WorkspaceDataManager {
|
||||
static Future<void> checkWorkspaceHealth({
|
||||
required bool dryRun,
|
||||
}) async {
|
||||
@ -63,6 +64,9 @@ class FixDataManager {
|
||||
// check the health of the spaces
|
||||
await checkSpaceHealth(workspace: workspace, allViews: allViews);
|
||||
|
||||
// check the health of the views
|
||||
await checkViewHealth(workspace: workspace, allViews: allViews);
|
||||
|
||||
// add other checks here
|
||||
// ...
|
||||
} catch (e) {
|
||||
@ -83,7 +87,6 @@ class FixDataManager {
|
||||
workspaceChildViews.map((e) => e.id).toSet();
|
||||
final spaces = allViews.where((e) => e.isSpace).toList();
|
||||
|
||||
//
|
||||
for (final space in spaces) {
|
||||
// the space is the top level view, so its parent view id should be the workspace id
|
||||
// and the workspace should have the space in its child views
|
||||
@ -106,6 +109,93 @@ class FixDataManager {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<ViewPB>> checkViewHealth({
|
||||
ViewPB? workspace,
|
||||
List<ViewPB>? allViews,
|
||||
bool dryRun = true,
|
||||
}) async {
|
||||
// Views whose parent view does not have the view in its child views
|
||||
final List<ViewPB> unlistedChildViews = [];
|
||||
// Views whose parent is not in allViews
|
||||
final List<ViewPB> orphanViews = [];
|
||||
|
||||
try {
|
||||
if (workspace == null || allViews == null) {
|
||||
final currentWorkspace =
|
||||
await UserBackendService.getCurrentWorkspace().getOrThrow();
|
||||
// get all the views in the workspace
|
||||
final result = await ViewBackendService.getAllViews().getOrThrow();
|
||||
allViews = result.items;
|
||||
workspace = allViews.firstWhereOrNull(
|
||||
(e) => e.id == currentWorkspace.id,
|
||||
);
|
||||
}
|
||||
|
||||
for (final view in allViews) {
|
||||
if (view.parentViewId == '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
final parentView = allViews.firstWhereOrNull(
|
||||
(e) => e.id == view.parentViewId,
|
||||
);
|
||||
|
||||
if (parentView == null) {
|
||||
orphanViews.add(view);
|
||||
continue;
|
||||
}
|
||||
|
||||
final childViewsOfParent =
|
||||
await ViewBackendService.getChildViews(viewId: parentView.id)
|
||||
.getOrThrow();
|
||||
final result = childViewsOfParent.any((e) => e.id == view.id);
|
||||
if (!result) {
|
||||
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');
|
||||
}
|
||||
|
||||
Log.info('[workspace] done checking view health');
|
||||
|
||||
return unlistedChildViews;
|
||||
}
|
||||
|
||||
static void dumpViews(String prefix, List<ViewPB> views) {
|
||||
for (int i = 0; i < views.length; i++) {
|
||||
final view = views[i];
|
||||
|
@ -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<FlowyCacheManager>().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);
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -369,3 +369,32 @@ Future<void> showConfirmDialog({
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
export 'package:async/async.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'dart:ffi';
|
||||
import 'ffi.dart' as ffi;
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'dart:isolate';
|
||||
import 'dart:io';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import 'ffi.dart' as ffi;
|
||||
|
||||
export 'package:async/async.dart';
|
||||
|
||||
enum ExceptionType {
|
||||
AppearanceSettingsIsEmpty,
|
||||
}
|
||||
@ -72,7 +75,8 @@ class RustLogStreamReceiver {
|
||||
lineLength: 120, // width of the output
|
||||
colors: false, // Colorful log messages
|
||||
printEmojis: false, // Print an emoji for each log message
|
||||
printTime: false, // Should each log print contain a timestamp
|
||||
dateTimeFormat:
|
||||
DateTimeFormat.none, // Should each log print contain a timestamp
|
||||
),
|
||||
level: kDebugMode ? Level.trace : Level.info,
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
// ignore: import_of_legacy_library_into_null_safe
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart' as ffi;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
@ -14,13 +15,15 @@ class Log {
|
||||
Log() {
|
||||
_logger = Logger(
|
||||
printer: PrettyPrinter(
|
||||
methodCount: 2, // number of method calls to be displayed
|
||||
errorMethodCount: 8, // number of method calls if stacktrace is provided
|
||||
lineLength: 120, // width of the output
|
||||
colors: true, // Colorful log messages
|
||||
printEmojis: true, // Print an emoji for each log message
|
||||
printTime: false, // Should each log print contain a timestamp
|
||||
),
|
||||
methodCount: 2, // number of method calls to be displayed
|
||||
errorMethodCount:
|
||||
8, // number of method calls if stacktrace is provided
|
||||
lineLength: 120, // width of the output
|
||||
colors: true, // Colorful log messages
|
||||
printEmojis: true, // Print an emoji for each log message
|
||||
dateTimeFormat:
|
||||
DateTimeFormat.none // Should each log print contain a timestamp
|
||||
),
|
||||
level: kDebugMode ? Level.trace : Level.info,
|
||||
);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ dependencies:
|
||||
isolates: ^3.0.3+8
|
||||
protobuf: ^3.1.0
|
||||
freezed_annotation:
|
||||
logger: ^2.0.0
|
||||
logger: ^2.4.0
|
||||
plugin_platform_interface: ^2.1.3
|
||||
json_annotation: ^4.7.0
|
||||
appflowy_result:
|
||||
|
@ -1251,13 +1251,13 @@ packages:
|
||||
source: hosted
|
||||
version: "0.1.5"
|
||||
logger:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
|
||||
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2+1"
|
||||
version: "2.4.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -144,6 +144,7 @@ dependencies:
|
||||
markdown_widget: ^2.3.2+6
|
||||
desktop_drop: ^0.4.4
|
||||
markdown:
|
||||
logger: ^2.4.0
|
||||
|
||||
# Window Manager for MacOS and Linux
|
||||
window_manager: ^0.3.9
|
||||
|
@ -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!"
|
||||
}
|
||||
},
|
||||
@ -757,7 +757,7 @@
|
||||
"freeLabels": {
|
||||
"itemOne": "charged per workspace",
|
||||
"itemTwo": "3",
|
||||
"itemThree": "",
|
||||
"itemThree": " ",
|
||||
"itemFour": "0",
|
||||
"itemFive": "5 GB",
|
||||
"itemSix": "yes",
|
||||
@ -767,7 +767,7 @@
|
||||
"proLabels": {
|
||||
"itemOne": "charged per workspace",
|
||||
"itemTwo": "up to 10",
|
||||
"itemThree": "",
|
||||
"itemThree": " ",
|
||||
"itemFour": "10 guests billed as one seat",
|
||||
"itemFive": "unlimited",
|
||||
"itemSix": "yes",
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user