diff --git a/frontend/.vscode/launch.json b/frontend/.vscode/launch.json index f759352c15..1bc6978a44 100644 --- a/frontend/.vscode/launch.json +++ b/frontend/.vscode/launch.json @@ -12,7 +12,7 @@ "program": "./lib/main.dart", "type": "dart", "env": { - "RUST_LOG": "trace", + "RUST_LOG": "debug", "RUST_BACKTRACE": "1" }, // uncomment the following line to testing performance. diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 0d79a16fa8..3c8164c477 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true CARGO_MAKE_CRATE_FS_NAME = "dart_ffi" CARGO_MAKE_CRATE_NAME = "dart-ffi" LIB_NAME = "dart_ffi" -APPFLOWY_VERSION = "0.5.0" +APPFLOWY_VERSION = "0.5.1" FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite" PRODUCT_NAME = "AppFlowy" MACOSX_DEPLOYMENT_TARGET = "11.0" diff --git a/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart b/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart index 8cb2386c66..b83d0f3cf4 100644 --- a/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart +++ b/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart @@ -40,19 +40,19 @@ void main() { await tester.openSettings(); await tester.openSettingsPage(SettingsPage.user); - final userAvatarFinder = find.descendant( - of: find.byType(SettingsUserView), - matching: find.byType(UserAvatar), - ); + // final userAvatarFinder = find.descendant( + // of: find.byType(SettingsUserView), + // matching: find.byType(UserAvatar), + // ); // Open icon picker dialog and select emoji - await tester.tap(userAvatarFinder); - await tester.pumpAndSettle(); - await tester.tapEmoji('😁'); - await tester.pumpAndSettle(); - final UserAvatar userAvatar = - tester.widget(userAvatarFinder) as UserAvatar; - expect(userAvatar.iconUrl, '😁'); + // await tester.tap(userAvatarFinder); + // await tester.pumpAndSettle(); + // await tester.tapEmoji('😁'); + // await tester.pumpAndSettle(); + // final UserAvatar userAvatar = + // tester.widget(userAvatarFinder) as UserAvatar; + // expect(userAvatar.iconUrl, '😁'); // enter user name final userNameFinder = find.descendant( @@ -81,12 +81,12 @@ void main() { await tester.openSettingsPage(SettingsPage.user); // verify icon - final userAvatarFinder = find.descendant( - of: find.byType(SettingsUserView), - matching: find.byType(UserAvatar), - ); - final UserAvatar userAvatar = tester.widget(userAvatarFinder) as UserAvatar; - expect(userAvatar.iconUrl, '😁'); + // final userAvatarFinder = find.descendant( + // of: find.byType(SettingsUserView), + // matching: find.byType(UserAvatar), + // ); + // final UserAvatar userAvatar = tester.widget(userAvatarFinder) as UserAvatar; + // expect(userAvatar.iconUrl, '😁'); // verify name final userNameFinder = find.descendant( diff --git a/frontend/appflowy_flutter/integration_test/util/settings.dart b/frontend/appflowy_flutter/integration_test/util/settings.dart index 4c666645de..8f48aa3694 100644 --- a/frontend/appflowy_flutter/integration_test/util/settings.dart +++ b/frontend/appflowy_flutter/integration_test/util/settings.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart'; import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart'; diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart index 89e306b18b..1fd3fed7a6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart @@ -1,13 +1,15 @@ import 'dart:io'; -import 'package:flutter/material.dart'; - import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; +import 'package:appflowy/shared/appflowy_cache_manager.dart'; +import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/share_log_files.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'widgets/widgets.dart'; @@ -51,6 +53,31 @@ class SupportSettingGroup extends StatelessWidget { ); }, ), + MobileSettingItem( + name: LocaleKeys.settings_files_clearCache.tr(), + trailing: const Icon( + Icons.chevron_right, + ), + onTap: () async { + await showFlowyMobileConfirmDialog( + context, + title: FlowyText( + LocaleKeys.settings_files_areYouSureToClearCache.tr(), + maxLines: 2, + ), + content: FlowyText( + LocaleKeys.settings_files_clearCacheDesc.tr(), + fontSize: 12, + maxLines: 4, + ), + actionButtonTitle: LocaleKeys.button_yes.tr(), + actionButtonColor: Theme.of(context).colorScheme.error, + onActionButtonPressed: () async { + await getIt().clearAllCache(); + }, + ); + }, + ), ], ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart index 711b132a4d..7fec980112 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart @@ -1,9 +1,7 @@ -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; @@ -13,6 +11,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.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 'tab_bar_add_button.dart'; @@ -235,7 +234,7 @@ class TabBarItemButton extends StatelessWidget { NavigatorTextFieldDialog( title: LocaleKeys.menuAppHeader_renameDialog.tr(), value: view.name, - confirm: (newValue) { + onConfirm: (newValue, _) { context.read().add( DatabaseTabBarEvent.renameView(view.id, newValue), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart index 47e06be639..09906e1429 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart @@ -360,9 +360,7 @@ class DepthOptionAction extends PopoverActionCell { (e) => HoverButton( onTap: () => onTap(e.inner), itemHeight: ActionListSizes.itemHeight, - leftIcon: null, name: e.name, - rightIcon: null, ), ) .toList(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart index 1e2305137d..055ae17605 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart @@ -59,9 +59,7 @@ class _ResizableImageState extends State { imageWidth = widget.width; - if (widget.type == CustomImageType.internal) { - _userProfilePB = context.read().state.userProfilePB; - } + _userProfilePB = context.read().state.userProfilePB; } @override diff --git a/frontend/appflowy_flutter/lib/shared/af_role_pb_extension.dart b/frontend/appflowy_flutter/lib/shared/af_role_pb_extension.dart new file mode 100644 index 0000000000..32b993938a --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/af_role_pb_extension.dart @@ -0,0 +1,25 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; + +extension AFRolePBExtension on AFRolePB { + bool get isOwner => this == AFRolePB.Owner; + + bool get canInvite => isOwner; + + bool get canDelete => isOwner; + + bool get canUpdate => isOwner; + + String get description { + switch (this) { + case AFRolePB.Owner: + return LocaleKeys.settings_appearance_members_owner.tr(); + case AFRolePB.Member: + return LocaleKeys.settings_appearance_members_member.tr(); + case AFRolePB.Guest: + return LocaleKeys.settings_appearance_members_guest.tr(); + } + throw UnimplementedError('Unknown role: $this'); + } +} diff --git a/frontend/appflowy_flutter/lib/shared/appflowy_cache_manager.dart b/frontend/appflowy_flutter/lib/shared/appflowy_cache_manager.dart new file mode 100644 index 0000000000..be62cc44be --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/appflowy_cache_manager.dart @@ -0,0 +1,61 @@ +import 'package:appflowy_backend/log.dart'; +import 'package:path_provider/path_provider.dart'; + +class FlowyCacheManager { + final _caches = []; + + // if you add a new cache, you should register it here. + void registerCache(ICache cache) { + _caches.add(cache); + } + + void unregisterAllCache(ICache cache) { + _caches.clear(); + } + + Future clearAllCache() async { + try { + for (final cache in _caches) { + await cache.clearAll(); + } + + Log.info('Cache cleared'); + } catch (e) { + Log.error(e); + } + } + + Future getCacheSize() async { + try { + int tmpDirSize = 0; + for (final cache in _caches) { + tmpDirSize += await cache.cacheSize(); + } + Log.info('Cache size: $tmpDirSize'); + return tmpDirSize; + } catch (e) { + Log.error(e); + return 0; + } + } +} + +abstract class ICache { + Future cacheSize(); + Future clearAll(); +} + +class TemporaryDirectoryCache implements ICache { + @override + Future cacheSize() async { + final tmpDir = await getTemporaryDirectory(); + final tmpDirStat = await tmpDir.stat(); + return tmpDirStat.size; + } + + @override + Future clearAll() async { + final tmpDir = await getTemporaryDirectory(); + await tmpDir.delete(recursive: true); + } +} diff --git a/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart b/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart index 72c6be583e..d37b2e0838 100644 --- a/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart +++ b/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart @@ -39,8 +39,10 @@ class FlowyNetworkImage extends StatelessWidget { assert(userProfilePB != null && userProfilePB!.token.isNotEmpty); } + final manager = CustomImageCacheManager(); + return CachedNetworkImage( - cacheManager: CustomImageCacheManager(), + cacheManager: manager, httpHeaders: _header(), imageUrl: url, fit: fit, @@ -50,6 +52,12 @@ class FlowyNetworkImage extends StatelessWidget { errorWidget: (context, url, error) => errorWidgetBuilder?.call(context, url, error) ?? const SizedBox.shrink(), + errorListener: (value) { + // try to clear the image cache. + manager.removeFile(url); + + Log.error(value.toString()); + }, ); } diff --git a/frontend/appflowy_flutter/lib/shared/custom_image_cache_manager.dart b/frontend/appflowy_flutter/lib/shared/custom_image_cache_manager.dart index 5e5e677193..b0624301c1 100644 --- a/frontend/appflowy_flutter/lib/shared/custom_image_cache_manager.dart +++ b/frontend/appflowy_flutter/lib/shared/custom_image_cache_manager.dart @@ -1,6 +1,9 @@ +import 'package:appflowy/shared/appflowy_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -class CustomImageCacheManager extends CacheManager with ImageCacheManager { +class CustomImageCacheManager extends CacheManager + with ImageCacheManager + implements ICache { CustomImageCacheManager._() : super(Config(key)); factory CustomImageCacheManager() => _instance; @@ -8,4 +11,16 @@ class CustomImageCacheManager extends CacheManager with ImageCacheManager { static final CustomImageCacheManager _instance = CustomImageCacheManager._(); static const key = 'appflowy_image_cache'; + + @override + Future cacheSize() async { + // https://github.com/Baseflow/flutter_cache_manager/issues/239#issuecomment-719475429 + // this package does not provide a way to get the cache size + return 0; + } + + @override + Future clearAll() async { + await emptyCache(); + } } diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index adca53db85..d9e1692dd7 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -6,6 +6,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart'; import 'package:appflowy/plugins/trash/application/prelude.dart'; +import 'package:appflowy/shared/appflowy_cache_manager.dart'; +import 'package:appflowy/shared/custom_image_cache_manager.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart'; import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart'; @@ -128,6 +130,12 @@ void _resolveCommonService( getIt.registerFactory( () => PlatformExtension.isMobile ? MobileAppearance() : DesktopAppearance(), ); + + getIt.registerFactory( + () => FlowyCacheManager() + ..registerCache(TemporaryDirectoryCache()) + ..registerCache(CustomImageCacheManager()), + ); } void _resolveUserDeps(GetIt getIt, IntegrationMode mode) { diff --git a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart index 79cd4036b5..bb39ac2703 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart @@ -5,7 +5,6 @@ import 'package:appflowy/env/backend_env.dart'; import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/user/application/auth/device_id.dart'; import 'package:appflowy_backend/appflowy_backend.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; @@ -39,7 +38,6 @@ class InitRustSDKTask extends LaunchTask { rustEnvs: context.config.rustEnvs, ); await context.getIt().init(jsonEncode(env.toJson())); - Log.info('Rust SDK initialized'); } @override diff --git a/frontend/appflowy_flutter/lib/user/application/user_service.dart b/frontend/appflowy_flutter/lib/user/application/user_service.dart index 44b067475b..1ef6d132e0 100644 --- a/frontend/appflowy_flutter/lib/user/application/user_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/user_service.dart @@ -79,15 +79,13 @@ class UserBackendService { return UserEventOpenAnonUser().send(); } - Future, FlowyError>> getWorkspaces() { - // final request = WorkspaceIdPB.create(); - // return FolderEventReadAllWorkspaces(request).send().then((result) { - // return result.fold( - // (workspaces) => FlowyResult.success(workspaces.items), - // (error) => FlowyResult.failure(error), - // ); - // }); - return Future.value(FlowyResult.success([])); + Future, FlowyError>> getWorkspaces() { + return UserEventGetAllWorkspace().send().then((value) { + return value.fold( + (workspaces) => FlowyResult.success(workspaces.items), + (error) => FlowyResult.failure(error), + ); + }); } Future> openWorkspace(String workspaceId) { @@ -118,4 +116,18 @@ class UserBackendService { ); }); } + + Future> createUserWorkspace( + String name, + ) { + final request = CreateWorkspacePB.create()..name = name; + return UserEventCreateWorkspace(request).send(); + } + + Future> deleteWorkspaceById( + String workspaceId, + ) { + final request = UserWorkspaceIdPB.create()..workspaceId = workspaceId; + return UserEventDeleteWorkspace(request).send(); + } } diff --git a/frontend/appflowy_flutter/lib/util/color_generator/color_generator.dart b/frontend/appflowy_flutter/lib/util/color_generator/color_generator.dart index f6d42101b6..c77650443e 100644 --- a/frontend/appflowy_flutter/lib/util/color_generator/color_generator.dart +++ b/frontend/appflowy_flutter/lib/util/color_generator/color_generator.dart @@ -1,11 +1,10 @@ -import 'dart:ui'; +import 'package:flutter/material.dart'; class ColorGenerator { - Color generateColorFromString(String string) { - final hash = string.hashCode; - final int r = (hash & 0xFF0000) >> 16; - final int g = (hash & 0x00FF00) >> 8; - final int b = hash & 0x0000FF; - return Color.fromRGBO(r, g, b, 0.5); + static Color generateColorFromString(String string) { + final int hash = + string.codeUnits.fold(0, (int acc, int unit) => acc + unit); + final double hue = (hash % 360).toDouble(); + return HSLColor.fromAHSL(1.0, hue, 0.5, 0.8).toColor(); } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/user/prelude.dart b/frontend/appflowy_flutter/lib/workspace/application/user/prelude.dart index f698497db9..b9bbb0ff08 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/user/prelude.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/user/prelude.dart @@ -1 +1,2 @@ export 'settings_user_bloc.dart'; +export 'user_workspace_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart new file mode 100644 index 0000000000..20d9d0ba41 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart @@ -0,0 +1,134 @@ +import 'package:appflowy/user/application/user_service.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_workspace_bloc.freezed.dart'; + +class UserWorkspaceBloc extends Bloc { + UserWorkspaceBloc({ + required this.userProfile, + }) : _userService = UserBackendService(userId: userProfile.id), + super(UserWorkspaceState.initial()) { + on( + (event, emit) async { + await event.when( + initial: () async { + // do nothing + }, + workspacesReceived: (workspaceId) async {}, + fetchWorkspaces: () async { + final result = await _fetchWorkspaces(); + if (result != null) { + emit( + state.copyWith( + currentWorkspace: result.$1, + workspaces: result.$2, + ), + ); + } + }, + createWorkspace: (name, desc) async { + final result = await _userService.createUserWorkspace(name); + emit( + state.copyWith( + openWorkspaceResult: null, + deleteWorkspaceResult: null, + createWorkspaceResult: + result.fold((s) => FlowyResult.success(null), (e) { + Log.error(e); + return FlowyResult.failure(e); + }), + ), + ); + }, + deleteWorkspace: (workspaceId) async { + final result = await _userService.deleteWorkspaceById(workspaceId); + emit( + state.copyWith( + openWorkspaceResult: null, + createWorkspaceResult: null, + deleteWorkspaceResult: + result.fold((s) => FlowyResult.success(null), (e) { + Log.error(e); + return FlowyResult.failure(e); + }), + ), + ); + }, + openWorkspace: (workspaceId) async { + final result = await _userService.openWorkspace(workspaceId); + emit( + state.copyWith( + createWorkspaceResult: null, + deleteWorkspaceResult: null, + openWorkspaceResult: + result.fold((s) => FlowyResult.success(null), (e) { + Log.error(e); + return FlowyResult.failure(e); + }), + ), + ); + }, + ); + }, + ); + } + + final UserProfilePB userProfile; + final UserBackendService _userService; + + Future<(UserWorkspacePB currentWorkspace, List workspaces)?> + _fetchWorkspaces() async { + final result = await _userService.getCurrentWorkspace(); + return result.fold((currentWorkspace) async { + final result = await _userService.getWorkspaces(); + return result.fold((workspaces) { + return ( + workspaces.firstWhere( + (e) => e.workspaceId == currentWorkspace.id, + ), + workspaces + ); + }, (e) { + Log.error(e); + return null; + }); + }, (e) { + Log.error(e); + return null; + }); + } +} + +@freezed +class UserWorkspaceEvent with _$UserWorkspaceEvent { + const factory UserWorkspaceEvent.initial() = Initial; + const factory UserWorkspaceEvent.createWorkspace(String name, String desc) = + CreateWorkspace; + const factory UserWorkspaceEvent.fetchWorkspaces() = FetchWorkspaces; + const factory UserWorkspaceEvent.deleteWorkspace(String workspaceId) = + DeleteWorkspace; + const factory UserWorkspaceEvent.openWorkspace(String workspaceId) = + OpenWorkspace; + const factory UserWorkspaceEvent.workspacesReceived( + FlowyResult, FlowyError> workspacesOrFail, + ) = WorkspacesReceived; +} + +@freezed +class UserWorkspaceState with _$UserWorkspaceState { + const factory UserWorkspaceState({ + required UserWorkspacePB? currentWorkspace, + required List workspaces, + @Default(null) FlowyResult? createWorkspaceResult, + @Default(null) FlowyResult? deleteWorkspaceResult, + @Default(null) FlowyResult? openWorkspaceResult, + }) = _UserWorkspaceState; + + factory UserWorkspaceState.initial() => + const UserWorkspaceState(currentWorkspace: null, workspaces: []); +} diff --git a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart index f74b435ad7..d8d5db45b4 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart @@ -48,7 +48,7 @@ class WorkspaceBloc extends Bloc { emit( workspacesOrFailed.fold( (workspaces) => state.copyWith( - workspaces: workspaces, + workspaces: [], successOrFailure: FlowyResult.success(null), ), (error) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart index 93cc5a81d5..1add004e82 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart @@ -182,7 +182,7 @@ class DesktopHomeScreen extends StatelessWidget { required WorkspaceSettingPB workspaceSetting, }) { final homeMenu = HomeSideBar( - user: userProfile, + userProfile: userProfile, workspaceSetting: workspaceSetting, ); return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu)); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart index e49c2786b3..356e18bded 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart @@ -1,13 +1,12 @@ import 'dart:io'; -import 'package:flutter/material.dart'; - import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart'; +import 'package:flutter/material.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:provider/provider.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart index e6352f5c38..4aa2d41355 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart @@ -119,7 +119,7 @@ class _PersonalFolderHeaderState extends State { createViewAndShowRenameDialogIfNeeded( context, LocaleKeys.newPageText.tr(), - (viewName) { + (viewName, _) { if (viewName.isNotEmpty) { context.read().add( MenuEvent.createApp( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart index 964229e4cf..bf18df1a98 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart @@ -15,7 +15,7 @@ import 'package:flutter/material.dart'; Future createViewAndShowRenameDialogIfNeeded( BuildContext context, String dialogTitle, - void Function(String viewName) createView, + void Function(String viewName, BuildContext context) createView, ) async { final value = await getIt().getWithFormat( KVKeys.showRenameDialogWhenCreatingNewFile, @@ -27,9 +27,9 @@ Future createViewAndShowRenameDialogIfNeeded( title: dialogTitle, value: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), autoSelectAllText: true, - confirm: createView, + onConfirm: createView, ).show(context); - } else { - createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr()); + } else if (context.mounted) { + createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr(), context); } } 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 af6f475698..80cee5be5e 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 @@ -1,5 +1,4 @@ -import 'package:flutter/material.dart'; - +import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/menu/menu_bloc.dart'; @@ -12,12 +11,14 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_pa import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_top_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_trash.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; /// Home Sidebar is the left side bar of the home page. @@ -30,11 +31,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class HomeSideBar extends StatelessWidget { const HomeSideBar({ super.key, - required this.user, + required this.userProfile, required this.workspaceSetting, }); - final UserProfilePB user; + final UserProfilePB userProfile; final WorkspaceSettingPB workspaceSetting; @@ -47,7 +48,7 @@ class HomeSideBar extends StatelessWidget { ), BlocProvider( create: (_) => MenuBloc( - user: user, + user: userProfile, workspaceId: workspaceSetting.workspaceId, )..add(const MenuEvent.initial()), ), @@ -103,11 +104,14 @@ class HomeSideBar extends StatelessWidget { padding: menuHorizontalInset, child: SidebarTopMenu(), ), - // user, setting + // user or workspace, setting Padding( padding: menuHorizontalInset, - child: SidebarUser(user: user, views: views), + child: FeatureFlag.collaborativeWorkspace.isOn + ? SidebarWorkspace(userProfile: userProfile, views: views) + : SidebarUser(userProfile: userProfile, views: views), ), + const VSpace(20), // scrollable document list Expanded( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart index 46cd3dfbb1..909124f70a 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart @@ -23,7 +23,7 @@ class SidebarNewPageButton extends StatelessWidget { onPressed: () async => createViewAndShowRenameDialogIfNeeded( context, LocaleKeys.newPageText.tr(), - (viewName) { + (viewName, _) { if (viewName.isNotEmpty) { context.read().add(MenuEvent.createApp(viewName)); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_setting.dart new file mode 100644 index 0000000000..24d4dd312f --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_setting.dart @@ -0,0 +1,104 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/user/application/auth/auth_service.dart'; +import 'package:appflowy/workspace/presentation/home/hotkeys.dart'; +import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart' + show UserProfilePB; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hotkey_manager/hotkey_manager.dart'; + +final GlobalKey _settingsDialogKey = GlobalKey(); + +Future openSettingsHotKey(BuildContext context) async { + final userProfileOrFailure = await getIt().getUser(); + + return userProfileOrFailure.fold( + (userProfile) => HotKeyItem( + hotKey: HotKey( + KeyCode.comma, + scope: HotKeyScope.inapp, + modifiers: [ + PlatformExtension.isMacOS ? KeyModifier.meta : KeyModifier.control, + ], + ), + keyDownHandler: (_) { + if (_settingsDialogKey.currentContext == null) { + showSettingsDialog(context, userProfile); + } else { + Navigator.of(context, rootNavigator: true) + .popUntil((route) => route.isFirst); + } + }, + ), + (e) { + Log.error('Failed to get user $e'); + return null; + }, + ); +} + +class UserSettingButton extends StatelessWidget { + const UserSettingButton({required this.userProfile, super.key}); + + final UserProfilePB userProfile; + + @override + Widget build(BuildContext context) { + return FlowyTooltip( + message: LocaleKeys.settings_menu_open.tr(), + child: IconButton( + onPressed: () => showSettingsDialog(context, userProfile), + icon: SizedBox.square( + dimension: 20, + child: FlowySvg( + FlowySvgs.settings_m, + color: Theme.of(context).colorScheme.tertiary, + ), + ), + ), + ); + } +} + +void showSettingsDialog( + BuildContext context, + UserProfilePB userProfile, +) { + showDialog( + context: context, + builder: (dialogContext) { + return BlocProvider.value( + key: _settingsDialogKey, + value: BlocProvider.of(dialogContext), + child: SettingsDialog( + userProfile, + didLogout: () async { + // Pop the dialog using the dialog context + Navigator.of(dialogContext).pop(); + await runAppFlowy(); + }, + dismissDialog: () { + if (Navigator.of(dialogContext).canPop()) { + Navigator.of(dialogContext).pop(); + } else { + Log.warn("Can't pop dialog context"); + } + }, + restartApp: () async { + // Pop the dialog using the dialog context + Navigator.of(dialogContext).pop(); + await runAppFlowy(); + }, + ), + ); + }, + ); +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart index 0308dce706..473ca6f1d3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart @@ -1,70 +1,32 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart'; -import 'package:appflowy/workspace/presentation/home/hotkeys.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart'; import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart'; -import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart' show UserProfilePB; -import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hotkey_manager/hotkey_manager.dart'; - -final GlobalKey _settingsDialogKey = GlobalKey(); - -Future openSettingsHotKey(BuildContext context) async { - final userProfileOrFailure = await getIt().getUser(); - - return userProfileOrFailure.fold( - (userProfile) => HotKeyItem( - hotKey: HotKey( - KeyCode.comma, - scope: HotKeyScope.inapp, - modifiers: [ - PlatformExtension.isMacOS ? KeyModifier.meta : KeyModifier.control, - ], - ), - keyDownHandler: (_) { - if (_settingsDialogKey.currentContext == null) { - _showSettingsDialog(context, userProfile); - } else { - Navigator.of(context, rootNavigator: true) - .popUntil((route) => route.isFirst); - } - }, - ), - (e) { - Log.error('Failed to get user $e'); - return null; - }, - ); -} +// keep this widget in case we need to roll back (lucas.xu) class SidebarUser extends StatelessWidget { const SidebarUser({ super.key, - required this.user, + required this.userProfile, required this.views, }); - final UserProfilePB user; + final UserProfilePB userProfile; final List views; @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => MenuUserBloc(user) + create: (context) => MenuUserBloc(userProfile) ..add( const MenuUserEvent.initial(), ), @@ -106,61 +68,3 @@ class SidebarUser extends StatelessWidget { return name; } } - -class UserSettingButton extends StatelessWidget { - const UserSettingButton({required this.userProfile, super.key}); - - final UserProfilePB userProfile; - - @override - Widget build(BuildContext context) { - return FlowyTooltip( - message: LocaleKeys.settings_menu_open.tr(), - child: IconButton( - onPressed: () => _showSettingsDialog(context, userProfile), - icon: SizedBox.square( - dimension: 20, - child: FlowySvg( - FlowySvgs.settings_m, - color: Theme.of(context).colorScheme.tertiary, - ), - ), - ), - ); - } -} - -void _showSettingsDialog( - BuildContext context, - UserProfilePB userProfile, -) { - showDialog( - context: context, - builder: (dialogContext) { - return BlocProvider.value( - key: _settingsDialogKey, - value: BlocProvider.of(dialogContext), - child: SettingsDialog( - userProfile, - didLogout: () async { - // Pop the dialog using the dialog context - Navigator.of(dialogContext).pop(); - await runAppFlowy(); - }, - dismissDialog: () { - if (Navigator.of(dialogContext).canPop()) { - Navigator.of(dialogContext).pop(); - } else { - Log.warn("Can't pop dialog context"); - } - }, - restartApp: () async { - // Pop the dialog using the dialog context - Navigator.of(dialogContext).pop(); - await runAppFlowy(); - }, - ), - ); - }, - ); -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart new file mode 100644 index 0000000000..c6fe4b4d5a --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart @@ -0,0 +1,183 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_item_list.dart'; +import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SidebarWorkspace extends StatelessWidget { + const SidebarWorkspace({ + super.key, + required this.userProfile, + required this.views, + }); + + final UserProfilePB userProfile; + final List views; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => UserWorkspaceBloc(userProfile: userProfile) + ..add(const UserWorkspaceEvent.fetchWorkspaces()), + child: BlocConsumer( + listener: _showResultDialog, + builder: (context, state) { + final currentWorkspace = state.currentWorkspace; + // todo: show something if there is no workspace + if (currentWorkspace == null) { + return const SizedBox.shrink(); + } + return Row( + children: [ + Expanded( + child: _WorkspaceWrapper( + userProfile: userProfile, + currentWorkspace: currentWorkspace, + ), + ), + UserSettingButton(userProfile: userProfile), + const HSpace(4), + NotificationButton(views: views), + ], + ); + }, + ), + ); + } + + void _showResultDialog(BuildContext context, UserWorkspaceState state) { + var result = state.createWorkspaceResult; + + if (result != null) { + final message = result.fold( + (s) => LocaleKeys.workspace_createSuccess.tr(), + (e) => '${LocaleKeys.workspace_createFailed.tr()}: ${e.msg}', + ); + return showSnackBarMessage(context, message); + } + + result = state.deleteWorkspaceResult; + if (result != null) { + final message = result.fold( + (s) => LocaleKeys.workspace_deleteSuccess.tr(), + (e) => '${LocaleKeys.workspace_deleteFailed.tr()}: ${e.msg}', + ); + showSnackBarMessage(context, message); + return; + } + + result = state.openWorkspaceResult; + if (result != null) { + final message = result.fold( + (s) => LocaleKeys.workspace_openSuccess.tr(), + (e) => '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}', + ); + showSnackBarMessage(context, message); + return; + } + } +} + +class _WorkspaceWrapper extends StatefulWidget { + const _WorkspaceWrapper({ + required this.userProfile, + required this.currentWorkspace, + }); + + final UserWorkspacePB currentWorkspace; + final UserProfilePB userProfile; + + @override + State<_WorkspaceWrapper> createState() => _WorkspaceWrapperState(); +} + +class _WorkspaceWrapperState extends State<_WorkspaceWrapper> { + @override + Widget build(BuildContext context) { + if (PlatformExtension.isDesktopOrWeb) { + return _DesktopWorkspaceWrapper( + userProfile: widget.userProfile, + currentWorkspace: widget.currentWorkspace, + ); + } else { + // TODO(Lucas) mobile workspace menu + return const Placeholder(); + } + } +} + +class _DesktopWorkspaceWrapper extends StatefulWidget { + const _DesktopWorkspaceWrapper({ + required this.userProfile, + required this.currentWorkspace, + }); + + final UserWorkspacePB currentWorkspace; + final UserProfilePB userProfile; + + @override + State<_DesktopWorkspaceWrapper> createState() => + _DesktopWorkspaceWrapperState(); +} + +class _DesktopWorkspaceWrapperState extends State<_DesktopWorkspaceWrapper> { + final controller = PopoverController(); + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + direction: PopoverDirection.bottomWithCenterAligned, + offset: const Offset(0, 10), + constraints: const BoxConstraints(maxWidth: 260, maxHeight: 600), + popupBuilder: (_) { + return BlocProvider.value( + value: context.read(), + child: BlocBuilder( + builder: (context, state) { + final currentWorkspace = state.currentWorkspace; + final workspaces = state.workspaces; + if (currentWorkspace == null || workspaces.isEmpty) { + return const SizedBox.shrink(); + } + return WorkspacesMenu( + userProfile: widget.userProfile, + currentWorkspace: currentWorkspace, + workspaces: workspaces, + ); + }, + ), + ); + }, + child: FlowyButton( + onTap: () => controller.show(), + margin: const EdgeInsets.symmetric(vertical: 8), + text: Row( + children: [ + const HSpace(4.0), + SizedBox( + width: 24.0, + child: WorkspaceIcon(workspace: widget.currentWorkspace), + ), + const HSpace(8), + FlowyText.medium( + widget.currentWorkspace.name, + overflow: TextOverflow.ellipsis, + ), + const FlowySvg(FlowySvgs.drop_menu_show_m), + ], + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart new file mode 100644 index 0000000000..84dd7abe52 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart @@ -0,0 +1,97 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +enum WorkspaceMoreAction { + rename, + delete, +} + +class WorkspaceMoreActionList extends StatelessWidget { + const WorkspaceMoreActionList({ + super.key, + required this.workspace, + }); + + final UserWorkspacePB workspace; + + @override + Widget build(BuildContext context) { + return PopoverActionList<_WorkspaceMoreActionWrapper>( + direction: PopoverDirection.bottomWithCenterAligned, + actions: WorkspaceMoreAction.values + .map((e) => _WorkspaceMoreActionWrapper(e, workspace)) + .toList(), + buildChild: (controller) { + return FlowyButton( + margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + useIntrinsicWidth: true, + text: const FlowySvg( + FlowySvgs.three_dots_vertical_s, + ), + onTap: () { + controller.show(); + }, + ); + }, + onSelected: (action, controller) {}, + ); + } +} + +class _WorkspaceMoreActionWrapper extends CustomActionCell { + _WorkspaceMoreActionWrapper(this.inner, this.workspace); + + final WorkspaceMoreAction inner; + final UserWorkspacePB workspace; + + @override + Widget buildWithContext(BuildContext context) { + return FlowyButton( + text: FlowyText( + name, + color: inner == WorkspaceMoreAction.delete + ? Theme.of(context).colorScheme.error + : null, + ), + margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), + onTap: () async { + switch (inner) { + case WorkspaceMoreAction.delete: + await NavigatorAlertDialog( + title: LocaleKeys.workspace_deleteWorkspaceHintText.tr(), + confirm: () { + context.read().add( + UserWorkspaceEvent.deleteWorkspace(workspace.workspaceId), + ); + }, + ).show(context); + case WorkspaceMoreAction.rename: + + // TODO(Lucas): integrate with the backend + } + + if (context.mounted) { + PopoverContainer.of(context).closeAll(); + } + }, + ); + } + + String get name { + switch (inner) { + case WorkspaceMoreAction.delete: + return LocaleKeys.button_delete.tr(); + case WorkspaceMoreAction.rename: + return LocaleKeys.button_rename.tr(); + } + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart new file mode 100644 index 0000000000..fdf7935482 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart @@ -0,0 +1,30 @@ +import 'package:appflowy/util/color_generator/color_generator.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class WorkspaceIcon extends StatelessWidget { + const WorkspaceIcon({ + super.key, + required this.workspace, + }); + + final UserWorkspacePB workspace; + + @override + Widget build(BuildContext context) { + // TODO(Lucas): support icon later + return Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: ColorGenerator.generateColorFromString(workspace.name), + borderRadius: BorderRadius.circular(4), + ), + child: FlowyText( + workspace.name.isEmpty ? '' : workspace.name.substring(0, 1), + fontSize: 16, + color: Colors.black, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_item_list.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_item_list.dart new file mode 100644 index 0000000000..359933970a --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_item_list.dart @@ -0,0 +1,195 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/af_role_pb_extension.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart'; +import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class WorkspacesMenu extends StatelessWidget { + const WorkspacesMenu({ + super.key, + required this.userProfile, + required this.currentWorkspace, + required this.workspaces, + }); + + final UserProfilePB userProfile; + final UserWorkspacePB currentWorkspace; + final List workspaces; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // user email + Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + child: Row( + children: [ + FlowyText.medium( + _getUserInfo(), + fontSize: 12.0, + overflow: TextOverflow.ellipsis, + color: Theme.of(context).hintColor, + ), + const Spacer(), + FlowyButton( + useIntrinsicWidth: true, + text: const FlowySvg(FlowySvgs.add_m), + onTap: () { + _showCreateWorkspaceDialog(context); + PopoverContainer.of(context).closeAll(); + }, + ), + ], + ), + ), + for (final workspace in workspaces) ...[ + _WorkspaceMenuItem( + workspace: workspace, + userProfile: userProfile, + isSelected: workspace.workspaceId == currentWorkspace.workspaceId, + ), + const VSpace(4.0), + ], + ], + ); + } + + String _getUserInfo() { + if (userProfile.email.isNotEmpty) { + return userProfile.email; + } + + if (userProfile.name.isNotEmpty) { + return userProfile.name; + } + + return LocaleKeys.defaultUsername.tr(); + } + + Future _showCreateWorkspaceDialog(BuildContext context) async { + if (context.mounted) { + await NavigatorTextFieldDialog( + title: LocaleKeys.workspace_create.tr(), + value: '', + hintText: '', + autoSelectAllText: true, + onConfirm: (name, context) async { + final request = CreateWorkspacePB.create()..name = name; + final result = await UserEventCreateWorkspace(request).send(); + final message = result.fold( + (s) => LocaleKeys.workspace_createSuccess.tr(), + (e) => '${LocaleKeys.workspace_createFailed.tr()}: ${e.msg}', + ); + if (context.mounted) { + showSnackBarMessage(context, message); + } + }, + ).show(context); + } + } +} + +class _WorkspaceMenuItem extends StatelessWidget { + const _WorkspaceMenuItem({ + required this.workspace, + required this.userProfile, + required this.isSelected, + }); + + final UserProfilePB userProfile; + final UserWorkspacePB workspace; + final bool isSelected; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => + WorkspaceMemberBloc(userProfile: userProfile, workspace: workspace) + ..add(const WorkspaceMemberEvent.initial()) + ..add(const WorkspaceMemberEvent.getWorkspaceMembers()), + child: BlocBuilder( + builder: (context, state) { + final members = state.members; + // settings right icon inside the flowy button will + // cause the popover dismiss intermediately when click the right icon. + // so using the stack to put the right icon on the flowy button. + return Stack( + alignment: Alignment.center, + children: [ + FlowyButton( + onTap: () { + if (!isSelected) { + context.read().add( + UserWorkspaceEvent.openWorkspace( + workspace.workspaceId, + ), + ); + } + }, + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + iconPadding: 10.0, + leftIconSize: const Size.square(32), + leftIcon: WorkspaceIcon( + workspace: workspace, + ), + text: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText.medium( + workspace.name, + fontSize: 14.0, + overflow: TextOverflow.ellipsis, + ), + if (members.length > 1) + FlowyText( + '${members.length} ${LocaleKeys.settings_appearance_members_members.tr()}', + fontSize: 10.0, + color: Theme.of(context).hintColor, + ), + ], + ), + ), + Positioned( + right: 12.0, + child: Align(child: _buildRightIcon(context)), + ), + ], + ); + }, + ), + ); + } + + Widget _buildRightIcon(BuildContext context) { + // only the owner can update or delete workspace. + // only show the more action button when the workspace is selected. + if (!isSelected || + !context.read().state.myRole.isOwner) { + return const SizedBox.shrink(); + } + + return Row( + children: [ + WorkspaceMoreActionList(workspace: workspace), + const FlowySvg( + FlowySvgs.blue_check_s, + blendMode: null, + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart index 5ce8d54288..3a2afacbfe 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; @@ -25,6 +23,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; typedef ViewItemOnSelected = void Function(ViewPB); @@ -456,7 +455,7 @@ class _SingleInnerViewItemState extends State { createViewAndShowRenameDialogIfNeeded( context, _convertLayoutToHintText(pluginBuilder.layoutType!), - (viewName) { + (viewName, _) { if (viewName.isNotEmpty) { viewBloc.add( ViewEvent.createView( @@ -499,7 +498,7 @@ class _SingleInnerViewItemState extends State { autoSelectAllText: true, value: widget.view.name, maxLength: 256, - confirm: (newValue) { + onConfirm: (newValue, _) { context.read().add(ViewEvent.rename(newValue)); }, ).show(context); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/setting_file_import_appflowy_data_view.dart similarity index 100% rename from frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart rename to frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/setting_file_import_appflowy_data_view.dart diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_export_file_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_export_file_widget.dart similarity index 95% rename from frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_export_file_widget.dart rename to frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_export_file_widget.dart index d97b8a5631..1c6441e90b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_export_file_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_export_file_widget.dart @@ -1,11 +1,11 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart'; -import 'package:flutter/material.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_exporter_widget.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:styled_widget/styled_widget.dart'; -import '../../../../generated/locale_keys.g.dart'; +import '../../../../../generated/locale_keys.g.dart'; class SettingsExportFileWidget extends StatefulWidget { const SettingsExportFileWidget({ diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_cache_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_cache_widget.dart new file mode 100644 index 0000000000..011b7ece9f --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_cache_widget.dart @@ -0,0 +1,80 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/appflowy_cache_manager.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class SettingsFileCacheWidget extends StatelessWidget { + const SettingsFileCacheWidget({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: FlowyText.medium( + LocaleKeys.settings_files_clearCache.tr(), + fontSize: 13, + overflow: TextOverflow.ellipsis, + ), + ), + const VSpace(8), + Opacity( + opacity: 0.6, + child: FlowyText( + LocaleKeys.settings_files_clearCacheDesc.tr(), + fontSize: 10, + maxLines: 3, + ), + ), + ], + ), + ), + const _ClearCacheButton(), + ], + ); + } +} + +class _ClearCacheButton extends StatelessWidget { + const _ClearCacheButton(); + + @override + Widget build(BuildContext context) { + return FlowyIconButton( + hoverColor: Theme.of(context).colorScheme.secondaryContainer, + tooltipText: LocaleKeys.settings_files_clearCache.tr(), + icon: FlowySvg( + FlowySvgs.delete_s, + size: const Size.square(18), + color: Theme.of(context).iconTheme.color, + ), + onPressed: () { + NavigatorAlertDialog( + title: LocaleKeys.settings_files_areYouSureToClearCache.tr(), + confirm: () async { + await getIt().clearAllCache(); + if (context.mounted) { + showSnackBarMessage( + context, + LocaleKeys.settings_files_clearCacheSuccess.tr(), + ); + } + }, + ).show(context); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_customize_location_view.dart similarity index 97% rename from frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart rename to frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_customize_location_view.dart index 6f2a3c556f..5fdc8ff7cc 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_customize_location_view.dart @@ -1,8 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart'; @@ -12,12 +9,14 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; -import '../../../../generated/locale_keys.g.dart'; -import '../../../../startup/startup.dart'; -import '../../../../startup/tasks/prelude.dart'; +import '../../../../../generated/locale_keys.g.dart'; +import '../../../../../startup/startup.dart'; +import '../../../../../startup/tasks/prelude.dart'; class SettingsFileLocationCustomizer extends StatefulWidget { const SettingsFileLocationCustomizer({ @@ -262,7 +261,7 @@ class _RecoverDefaultStorageButtonState tooltipText: LocaleKeys.settings_files_recoverLocationTooltips.tr(), icon: const FlowySvg( FlowySvgs.restore_s, - size: Size.square(24), + size: Size.square(20), ), onPressed: () async { // reset to the default directory and reload app diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_exporter_widget.dart similarity index 99% rename from frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart rename to frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_exporter_widget.dart index efd789ed99..ea1cd98933 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/files/settings_file_exporter_widget.dart @@ -18,7 +18,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:path/path.dart' as p; -import '../../../../generated/locale_keys.g.dart'; +import '../../../../../generated/locale_keys.g.dart'; class FileExporterWidget extends StatefulWidget { const FileExporterWidget({super.key}); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart index 8bc317390e..2d9397d7cf 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart @@ -20,10 +20,26 @@ class WorkspaceMemberBloc extends Bloc { WorkspaceMemberBloc({ required this.userProfile, + this.workspace, }) : super(WorkspaceMemberState.initial()) { on((event, emit) async { - await event.map( - getWorkspaceMembers: (_) async { + await event.when( + initial: () async { + if (workspace != null) { + workspaceId = workspace!.workspaceId; + } else { + final currentWorkspace = + await FolderEventReadCurrentWorkspace().send(); + currentWorkspace.fold((s) { + workspaceId = s.id; + }, (e) { + assert(false, 'Failed to read current workspace: $e'); + Log.error('Failed to read current workspace: $e'); + workspaceId = ''; + }); + } + }, + getWorkspaceMembers: () async { final members = await _getWorkspaceMembers(); final myRole = _getMyRole(members); emit( @@ -33,16 +49,16 @@ class WorkspaceMemberBloc ), ); }, - addWorkspaceMember: (e) async { - await _addWorkspaceMember(e.email); + addWorkspaceMember: (email) async { + await _addWorkspaceMember(email); add(const WorkspaceMemberEvent.getWorkspaceMembers()); }, - removeWorkspaceMember: (e) async { - await _removeWorkspaceMember(e.email); + removeWorkspaceMember: (email) async { + await _removeWorkspaceMember(email); add(const WorkspaceMemberEvent.getWorkspaceMembers()); }, - updateWorkspaceMember: (e) async { - await _updateWorkspaceMember(e.email, e.role); + updateWorkspaceMember: (email, role) async { + await _updateWorkspaceMember(email, role); add(const WorkspaceMemberEvent.getWorkspaceMembers()); }, ); @@ -51,18 +67,16 @@ class WorkspaceMemberBloc final UserProfilePB userProfile; + // if the workspace is null, use the current workspace + final UserWorkspacePB? workspace; + + late final String workspaceId; + Future> _getWorkspaceMembers() async { - // will the current workspace be synced across the app? - final currentWorkspace = await FolderEventReadCurrentWorkspace().send(); - return currentWorkspace.fold((s) async { - final data = QueryWorkspacePB()..workspaceId = s.id; - final result = await UserEventGetWorkspaceMember(data).send(); - return result.fold((s) => s.items, (e) { - Log.error('Failed to read workspace members: $e'); - return []; - }); - }, (e) { - Log.error('Failed to read current workspace: $e'); + final data = QueryWorkspacePB()..workspaceId = workspaceId; + final result = await UserEventGetWorkspaceMember(data).send(); + return result.fold((s) => s.items, (e) { + Log.error('Failed to read workspace members: $e'); return []; }); } @@ -81,60 +95,46 @@ class WorkspaceMemberBloc } Future _addWorkspaceMember(String email) async { - final currentWorkspace = await FolderEventReadCurrentWorkspace().send(); - return currentWorkspace.fold((s) async { - final data = AddWorkspaceMemberPB() - ..workspaceId = s.id - ..email = email; - final result = await UserEventAddWorkspaceMember(data).send(); - result.fold((s) { - Log.info('Added workspace member: $data'); - }, (e) { - Log.error('Failed to add workspace member: $e'); - }); + final data = AddWorkspaceMemberPB() + ..workspaceId = workspaceId + ..email = email; + final result = await UserEventAddWorkspaceMember(data).send(); + result.fold((s) { + Log.info('Added workspace member: $data'); }, (e) { - Log.error('Failed to read current workspace: $e'); + Log.error('Failed to add workspace member: $e'); }); } Future _removeWorkspaceMember(String email) async { - final currentWorkspace = await FolderEventReadCurrentWorkspace().send(); - return currentWorkspace.fold((s) async { - final data = RemoveWorkspaceMemberPB() - ..workspaceId = s.id - ..email = email; - final result = await UserEventRemoveWorkspaceMember(data).send(); - result.fold((s) { - Log.info('Removed workspace member: $data'); - }, (e) { - Log.error('Failed to remove workspace member: $e'); - }); + final data = RemoveWorkspaceMemberPB() + ..workspaceId = workspaceId + ..email = email; + final result = await UserEventRemoveWorkspaceMember(data).send(); + result.fold((s) { + Log.info('Removed workspace member: $data'); }, (e) { - Log.error('Failed to read current workspace: $e'); + Log.error('Failed to remove workspace member: $e'); }); } Future _updateWorkspaceMember(String email, AFRolePB role) async { - final currentWorkspace = await FolderEventReadCurrentWorkspace().send(); - return currentWorkspace.fold((s) async { - final data = UpdateWorkspaceMemberPB() - ..workspaceId = s.id - ..email = email - ..role = role; - final result = await UserEventUpdateWorkspaceMember(data).send(); - result.fold((s) { - Log.info('Updated workspace member: $data'); - }, (e) { - Log.error('Failed to update workspace member: $e'); - }); + final data = UpdateWorkspaceMemberPB() + ..workspaceId = workspaceId + ..email = email + ..role = role; + final result = await UserEventUpdateWorkspaceMember(data).send(); + result.fold((s) { + Log.info('Updated workspace member: $data'); }, (e) { - Log.error('Failed to read current workspace: $e'); + Log.error('Failed to update workspace member: $e'); }); } } @freezed class WorkspaceMemberEvent with _$WorkspaceMemberEvent { + const factory WorkspaceMemberEvent.initial() = Initial; const factory WorkspaceMemberEvent.getWorkspaceMembers() = GetWorkspaceMembers; const factory WorkspaceMemberEvent.addWorkspaceMember(String email) = diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart index f43e376340..724c9ad2be 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/af_role_pb_extension.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; @@ -454,25 +455,3 @@ class _MemberRoleActionWrapper extends ActionCell { throw UnimplementedError('Unknown role: $inner'); } } - -extension on AFRolePB { - bool get isOwner => this == AFRolePB.Owner; - - bool get canInvite => isOwner; - - bool get canDelete => isOwner; - - bool get canUpdate => isOwner; - - String get description { - switch (this) { - case AFRolePB.Owner: - return LocaleKeys.settings_appearance_members_owner.tr(); - case AFRolePB.Member: - return LocaleKeys.settings_appearance_members_member.tr(); - case AFRolePB.Guest: - return LocaleKeys.settings_appearance_members_guest.tr(); - } - throw UnimplementedError('Unknown role: $this'); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart index 78f21c4485..3e3e376df4 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart @@ -217,7 +217,7 @@ class CloudTypeItem extends StatelessWidget { confirm: () async { onSelected(cloudType); }, - hideCancleButton: true, + hideCancelButton: true, ).show(context); } PopoverContainer.of(context).close(); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart index 31aa7171bd..1def9a7b2b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart @@ -1,6 +1,8 @@ -import 'package:appflowy/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/settings_export_file_widget.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/files/setting_file_import_appflowy_data_view.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_export_file_widget.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_cache_widget.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_customize_location_view.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -19,15 +21,15 @@ class _SettingsFileSystemViewState extends State { // disable export data for v0.2.0 in release mode. if (kDebugMode) const SettingsExportFileWidget(), const ImportAppFlowyData(), + // clear the cache + const SettingsFileCacheWidget(), ]; @override Widget build(BuildContext context) { - return ListView.separated( - shrinkWrap: true, - itemBuilder: (context, index) => _items[index], - separatorBuilder: (context, index) => const Divider(), - itemCount: _items.length, + return SeparatedColumn( + separatorBuilder: () => const Divider(), + children: _items, ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart index 1b431b58d6..1458bcad5b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart @@ -1,15 +1,16 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/style_widget/text_input.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; +import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; -import 'package:appflowy/startup/tasks/app_widget.dart'; -import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra_ui/style_widget/text_input.dart'; -import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; + export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; class NavigatorTextFieldDialog extends StatefulWidget { const NavigatorTextFieldDialog({ @@ -17,17 +18,19 @@ class NavigatorTextFieldDialog extends StatefulWidget { required this.title, this.autoSelectAllText = false, required this.value, - required this.confirm, - this.cancel, + required this.onConfirm, + this.onCancel, this.maxLength, + this.hintText, }); final String value; final String title; - final void Function()? cancel; - final void Function(String) confirm; + final VoidCallback? onCancel; + final void Function(String, BuildContext) onConfirm; final bool autoSelectAllText; final int? maxLength; + final String? hintText; @override State createState() => @@ -69,7 +72,8 @@ class _NavigatorTextFieldDialogState extends State { ), VSpace(Insets.m), FlowyFormTextInput( - hintText: LocaleKeys.dialogCreatePageNameHint.tr(), + hintText: + widget.hintText ?? LocaleKeys.dialogCreatePageNameHint.tr(), controller: controller, textStyle: Theme.of(context) .textTheme @@ -82,20 +86,18 @@ class _NavigatorTextFieldDialogState extends State { newValue = text; }, onEditingComplete: () { - widget.confirm(newValue); + widget.onConfirm(newValue, context); AppGlobals.nav.pop(); }, ), VSpace(Insets.xl), OkCancelButton( onOkPressed: () { - widget.confirm(newValue); + widget.onConfirm(newValue, context); Navigator.of(context).pop(); }, onCancelPressed: () { - if (widget.cancel != null) { - widget.cancel!(); - } + widget.onCancel?.call(); Navigator.of(context).pop(); }, ), @@ -111,13 +113,13 @@ class NavigatorAlertDialog extends StatefulWidget { required this.title, this.cancel, this.confirm, - this.hideCancleButton = false, + this.hideCancelButton = false, }); final String title; final void Function()? cancel; final void Function()? confirm; - final bool hideCancleButton; + final bool hideCancelButton; @override State createState() => _CreateFlowyAlertDialog(); @@ -158,7 +160,7 @@ class _CreateFlowyAlertDialog extends State { widget.confirm?.call(); Navigator.of(context).pop(); }, - onCancelPressed: widget.hideCancleButton + onCancelPressed: widget.hideCancelButton ? null : () { widget.cancel?.call(); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart index 83a42249d5..bb285a7917 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart @@ -219,9 +219,9 @@ class HoverButton extends StatelessWidget { super.key, required this.onTap, required this.itemHeight, - required this.leftIcon, + this.leftIcon, required this.name, - required this.rightIcon, + this.rightIcon, }); final VoidCallback onTap; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart index 766f012af8..66ccbe01e0 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart @@ -29,7 +29,7 @@ class UserAvatar extends StatelessWidget { if (iconUrl.isEmpty) { final String nameOrDefault = _userName(name); - final Color color = ColorGenerator().generateColorFromString(name); + final Color color = ColorGenerator.generateColorFromString(name); const initialsCount = 2; // Taking the first letters of the name components and limiting to 2 elements diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index e2cd3bc48e..4f029c3699 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -88,7 +88,7 @@ class FlowyButton extends StatelessWidget { } Widget _render(BuildContext context) { - List children = List.empty(growable: true); + final List children = []; if (leftIcon != null) { children.add( diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index ac0141bc73..792737ea37 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.5.0 +version: 0.5.1 environment: flutter: ">=3.19.0" diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 60746c0949..7e91aa05a4 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -162,7 +162,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -714,7 +714,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "again", "anyhow", @@ -733,7 +733,6 @@ dependencies = [ "gotrue", "gotrue-entity", "governor", - "log", "mime", "mime_guess", "parking_lot 0.12.1", @@ -818,7 +817,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-trait", @@ -840,7 +839,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-trait", @@ -869,7 +868,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "collab", @@ -888,7 +887,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "bytes", @@ -903,7 +902,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "chrono", @@ -940,7 +939,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-stream", @@ -979,7 +978,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "collab", @@ -1313,7 +1312,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -2587,7 +2586,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "futures-util", @@ -2604,7 +2603,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -3059,7 +3058,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "reqwest", @@ -4790,7 +4789,7 @@ dependencies = [ [[package]] name = "realtime-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -4814,7 +4813,7 @@ dependencies = [ [[package]] name = "realtime-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -5462,7 +5461,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -6957,7 +6956,7 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "futures-channel", "futures-util", @@ -7376,7 +7375,7 @@ dependencies = [ [[package]] name = "workspace-template" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "async-trait", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index c9750a982a..5276a9bc03 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -82,7 +82,7 @@ custom-protocol = ["tauri/custom-protocol"] # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5ed6a149433e34b3dc4bfc66446033a33258e48c" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "15c03e4f85fffd35089a82c2a84aca8042a38946" } # Please use the following script to update collab. # Working directory: frontend # @@ -92,10 +92,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5ed # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_overlay/GridTableOverlay.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_overlay/GridTableOverlay.tsx index 128d046259..07ece5dec2 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_overlay/GridTableOverlay.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_overlay/GridTableOverlay.tsx @@ -20,9 +20,9 @@ function GridTableOverlay({ const [openConfirm, setOpenConfirm] = useState(false); const [confirmModalProps, setConfirmModalProps] = useState< | { - onOk: () => Promise; - onCancel: () => void; - } + onOk: () => Promise; + onCancel: () => void; + } | undefined >(undefined); @@ -59,7 +59,7 @@ function GridTableOverlay({ {openConfirm && ( { diff --git a/frontend/appflowy_web/wasm-libs/Cargo.lock b/frontend/appflowy_web/wasm-libs/Cargo.lock index 0fe2a8228a..2274c4fb42 100644 --- a/frontend/appflowy_web/wasm-libs/Cargo.lock +++ b/frontend/appflowy_web/wasm-libs/Cargo.lock @@ -221,7 +221,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -545,7 +545,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "again", "anyhow", @@ -564,7 +564,6 @@ dependencies = [ "gotrue", "gotrue-entity", "governor", - "log", "mime", "mime_guess", "parking_lot 0.12.1", @@ -618,7 +617,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-trait", @@ -640,7 +639,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "collab", @@ -659,7 +658,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "bytes", @@ -674,7 +673,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "chrono", @@ -711,7 +710,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-stream", @@ -749,7 +748,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "collab", @@ -946,7 +945,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -1700,7 +1699,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "futures-util", @@ -1717,7 +1716,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -2051,7 +2050,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "reqwest", @@ -3309,7 +3308,7 @@ dependencies = [ [[package]] name = "realtime-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -3333,7 +3332,7 @@ dependencies = [ [[package]] name = "realtime-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -3780,7 +3779,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -4722,7 +4721,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "futures-channel", "futures-util", @@ -5029,4 +5028,4 @@ dependencies = [ [[patch.unused]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" diff --git a/frontend/appflowy_web/wasm-libs/Cargo.toml b/frontend/appflowy_web/wasm-libs/Cargo.toml index 5363312ce4..5e56d2551a 100644 --- a/frontend/appflowy_web/wasm-libs/Cargo.toml +++ b/frontend/appflowy_web/wasm-libs/Cargo.toml @@ -55,7 +55,7 @@ codegen-units = 1 # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5ed6a149433e34b3dc4bfc66446033a33258e48c" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "15c03e4f85fffd35089a82c2a84aca8042a38946" } # Please use the following script to update collab. # Working directory: frontend # @@ -65,10 +65,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5ed # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } diff --git a/frontend/resources/translations/am-ET.json b/frontend/resources/translations/am-ET.json index 88d526ae09..f5f11d7495 100644 --- a/frontend/resources/translations/am-ET.json +++ b/frontend/resources/translations/am-ET.json @@ -193,7 +193,7 @@ "editContact": "እውቂያ ያርትዑ" }, "button": { - "OK": "እሺ", + "ok": "እሺ", "cancel": "ይቅር", "signIn": "ይግቡ", "signOut": "ዘግተው ውጣ", @@ -860,4 +860,4 @@ "noResult": "ምንም ውጤቶች የሉም", "caseSensitive": "መጪ" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index f07c4972a9..fdf804c38e 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -64,7 +64,14 @@ "reportIssueOnGithub": "Report an issue on Github", "exportLogFiles": "Export log files", "reachOut": "Reach out on Discord" - } + }, + "deleteWorkspaceHintText": "Are you sure you want to delete the workspace? This action cannot be undone.", + "createSuccess": "Workspace created successfully", + "createFailed": "Failed to create workspace", + "deleteSuccess": "Workspace deleted successfully", + "deleteFailed": "Failed to delete workspace", + "openSuccess": "Open workspace successfully", + "openFailed": "Failed to open workspace" }, "shareAction": { "buttonText": "Share", @@ -242,8 +249,6 @@ "helpCenter": "Help Center", "add": "Add", "yes": "Yes", - "Done": "Done", - "Cancel": "Cancel", "clear": "Clear", "remove": "Remove", "dontRemove": "Don't remove", @@ -414,7 +419,8 @@ "memberHintText": "A member can read, comment, and edit pages. Invite members and guests.", "guestHintText": "A Guest can read, react, comment, and can edit certain pages with permission.", "emailInvalidError": "Invalid email, please check and try again", - "emailSent": "Email sent, please check the inbox" + "emailSent": "Email sent, please check the inbox", + "members": "members" } }, "files": { @@ -451,7 +457,11 @@ "recoverLocationTooltips": "Reset to AppFlowy's default data directory", "exportFileSuccess": "Export file successfully!", "exportFileFail": "Export file failed!", - "export": "Export" + "export": "Export", + "clearCache": "Clear cache", + "clearCacheDesc": "Clear the cache including the images, fonts, and other temporary files. This will not delete your data.", + "areYouSureToClearCache": "Are you sure to clear the cache?", + "clearCacheSuccess": "Cache cleared successfully!" }, "user": { "name": "Name", @@ -664,7 +674,8 @@ "empty": "No active sorts", "cannotFindCreatableField": "Cannot find a suitable field to sort by", "deleteAllSorts": "Delete all sorts", - "addSort": "Add new sort" + "addSort": "Add new sort", + "removeSorting": "Would you like to remove sorting?" }, "row": { "duplicate": "Duplicate", @@ -725,8 +736,7 @@ "median": "Median", "min": "Min", "sum": "Sum" - }, - "removeSorting": "Would you like to remove sorting?" + } }, "document": { "menuName": "Document", @@ -1333,4 +1343,4 @@ "userIcon": "User icon" }, "noLogFiles": "There're no log files" -} +} \ No newline at end of file diff --git a/frontend/resources/translations/it-IT.json b/frontend/resources/translations/it-IT.json index ad7a91add2..74d72997f6 100644 --- a/frontend/resources/translations/it-IT.json +++ b/frontend/resources/translations/it-IT.json @@ -238,8 +238,6 @@ "helpCenter": "Centro assistenza", "add": "Aggiungi", "yes": "SÌ", - "Done": "Fatto", - "Cancel": "Annulla", "remove": "Rimuovi", "dontRemove": "Non rimuovere" }, @@ -621,7 +619,8 @@ "cannotFindCreatableField": "Impossibile trovare un campo adatto per l'ordinamento", "deleteAllSorts": "Elimina tutti gli ordinamenti", "addSort": "Aggiungi ordinamento", - "deleteSort": "Elimina ordinamento" + "deleteSort": "Elimina ordinamento", + "removeSorting": "Si desidera rimuovere l'ordinamento?" }, "row": { "duplicate": "Duplicare", @@ -675,8 +674,7 @@ "median": "Medio", "min": "Minimo", "sum": "Somma" - }, - "removeSorting": "Si desidera rimuovere l'ordinamento?" + } }, "document": { "menuName": "Documento", @@ -1266,4 +1264,4 @@ "userIcon": "Icona utente" }, "noLogFiles": "Non ci sono file di log" -} +} \ No newline at end of file diff --git a/frontend/resources/translations/ru-RU.json b/frontend/resources/translations/ru-RU.json index 9a18b75272..68942931d0 100644 --- a/frontend/resources/translations/ru-RU.json +++ b/frontend/resources/translations/ru-RU.json @@ -242,8 +242,6 @@ "helpCenter": "Центр помощи", "add": "Добавить", "yes": "Да", - "Done": "Готово", - "Cancel": "Отмена", "clear": "Очистить", "remove": "Удалить", "dontRemove": "Не удалять", @@ -645,7 +643,8 @@ "empty": "Нет активных сортировок", "cannotFindCreatableField": "Не могу найти подходящее поле для сортировки", "deleteAllSorts": "Удалить все сортировки", - "addSort": "Добавить сортировку" + "addSort": "Добавить сортировку", + "removeSorting": "Убрать сортировку?" }, "row": { "duplicate": "Дублировать", @@ -706,8 +705,7 @@ "median": "Медиана", "min": "Минимум", "sum": "Сумма" - }, - "removeSorting": "Убрать сортировку?" + } }, "document": { "menuName": "Документ", @@ -1315,4 +1313,4 @@ "userIcon": "Пользовательская иконка" }, "noLogFiles": "Нет файлов журналов" -} +} \ No newline at end of file diff --git a/frontend/resources/translations/zh-CN.json b/frontend/resources/translations/zh-CN.json index e2bc243762..da547efc5d 100644 --- a/frontend/resources/translations/zh-CN.json +++ b/frontend/resources/translations/zh-CN.json @@ -242,8 +242,6 @@ "helpCenter": "帮助中心", "add": "添加", "yes": "是", - "Done": "完成", - "Cancel": "取消", "clear": "清空", "remove": "删除", "dontRemove": "请勿删除", @@ -646,7 +644,8 @@ "descending": "降序", "deleteAllSorts": "删除所有排序", "addSort": "添加排序", - "deleteSort": "取消排序" + "deleteSort": "取消排序", + "removeSorting": "你确定要移除排序吗?" }, "row": { "duplicate": "复制", @@ -705,8 +704,7 @@ "median": "中位数", "min": "最小值", "sum": "求和" - }, - "removeSorting": "你确定要移除排序吗?" + } }, "document": { "menuName": "文档", @@ -1293,4 +1291,4 @@ "userIcon": "用户图标" }, "noLogFiles": "没有日志文件" -} +} \ No newline at end of file diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 0fa6737e5c..595a605df0 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -163,7 +163,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -673,7 +673,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "again", "anyhow", @@ -692,7 +692,6 @@ dependencies = [ "gotrue", "gotrue-entity", "governor", - "log", "mime", "mime_guess", "parking_lot 0.12.1", @@ -746,7 +745,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-trait", @@ -768,7 +767,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-trait", @@ -797,7 +796,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "collab", @@ -816,7 +815,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "bytes", @@ -831,7 +830,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "chrono", @@ -868,7 +867,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "async-stream", @@ -907,7 +906,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9f340e85e09af97a83d44b66069a93d63c6f2279#9f340e85e09af97a83d44b66069a93d63c6f2279" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=7148c6f94bd71469782dfad04aeef835d34900fc#7148c6f94bd71469782dfad04aeef835d34900fc" dependencies = [ "anyhow", "collab", @@ -1237,7 +1236,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -2411,7 +2410,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "futures-util", @@ -2428,7 +2427,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -2822,7 +2821,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "reqwest", @@ -4319,7 +4318,7 @@ dependencies = [ [[package]] name = "realtime-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -4343,7 +4342,7 @@ dependencies = [ [[package]] name = "realtime-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "bincode", @@ -4931,7 +4930,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "app-error", @@ -6121,7 +6120,7 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "futures-channel", "futures-util", @@ -6361,7 +6360,7 @@ dependencies = [ [[package]] name = "workspace-template" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=5ed6a149433e34b3dc4bfc66446033a33258e48c#5ed6a149433e34b3dc4bfc66446033a33258e48c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=15c03e4f85fffd35089a82c2a84aca8042a38946#15c03e4f85fffd35089a82c2a84aca8042a38946" dependencies = [ "anyhow", "async-trait", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 05920a4bba..bd3aded33c 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -105,7 +105,7 @@ incremental = false # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5ed6a149433e34b3dc4bfc66446033a33258e48c" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "15c03e4f85fffd35089a82c2a84aca8042a38946" } # Please use the following script to update collab. # Working directory: frontend # @@ -115,10 +115,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "5ed # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } -collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9f340e85e09af97a83d44b66069a93d63c6f2279" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } +collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "7148c6f94bd71469782dfad04aeef835d34900fc" } diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index a23f738748..0ae56ce015 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -176,7 +176,6 @@ async fn post_to_flutter(response: AFPluginEventResponse, port: i64) { #[no_mangle] pub extern "C" fn rust_log(level: i64, data: *const c_char) { - info!("backend_log"); // Check if the data pointer is not null if data.is_null() { error!("[flutter error]: null pointer provided to backend_log"); diff --git a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs index dc700096f0..6390df9af0 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs @@ -2,7 +2,7 @@ use flowy_storage::{ObjectIdentity, ObjectStorageService}; use std::sync::Arc; use anyhow::Error; -use client_api::collab_sync::{SinkConfig, SinkStrategy, SyncObject, SyncPlugin}; +use client_api::collab_sync::{SinkConfig, SyncObject, SyncPlugin}; use collab::core::collab::CollabDocState; use collab::core::origin::{CollabClient, CollabOrigin}; use collab::preclude::CollabPlugin; @@ -359,8 +359,7 @@ impl CollabCloudPluginProvider for ServerProvider { let (sink, stream) = (channel.sink(), channel.stream()); let sink_config = SinkConfig::new() .send_timeout(8) - .with_max_payload_size(1024 * 10) - .with_strategy(sink_strategy_from_object(&sync_object)); + .with_max_payload_size(1024 * 10); let sync_plugin = SyncPlugin::new( origin, sync_object, @@ -417,14 +416,3 @@ impl CollabCloudPluginProvider for ServerProvider { *self.user_enable_sync.read() } } - -fn sink_strategy_from_object(object: &SyncObject) -> SinkStrategy { - match object.collab_type { - CollabType::Document => SinkStrategy::FixInterval(std::time::Duration::from_millis(300)), - CollabType::Folder => SinkStrategy::ASAP, - CollabType::Database => SinkStrategy::ASAP, - CollabType::WorkspaceDatabase => SinkStrategy::ASAP, - CollabType::DatabaseRow => SinkStrategy::ASAP, - CollabType::UserAwareness => SinkStrategy::ASAP, - } -} diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/server.rs b/frontend/rust-lib/flowy-server/src/af_cloud/server.rs index 3a67a2be95..f165a86a51 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/server.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/server.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use std::time::Duration; use anyhow::Error; -use client_api::collab_sync::collab_msg::CollabMessage; +use client_api::collab_sync::collab_msg::ServerCollabMessage; use client_api::entity::UserMessage; use client_api::notify::{TokenState, TokenStateReceiver}; use client_api::ws::{ @@ -203,7 +203,7 @@ impl AppFlowyServer for AppFlowyCloudServer { _object_id: &str, ) -> FutureResult< Option<( - Arc>, + Arc>, WSConnectStateReceiver, bool, )>, diff --git a/frontend/rust-lib/flowy-server/src/server.rs b/frontend/rust-lib/flowy-server/src/server.rs index 483cd4f8da..f26676eb6c 100644 --- a/frontend/rust-lib/flowy-server/src/server.rs +++ b/frontend/rust-lib/flowy-server/src/server.rs @@ -5,7 +5,7 @@ use flowy_storage::ObjectStorageService; use std::sync::Arc; use anyhow::Error; -use client_api::collab_sync::collab_msg::CollabMessage; +use client_api::collab_sync::collab_msg::ServerCollabMessage; use parking_lot::RwLock; use tokio_stream::wrappers::WatchStream; #[cfg(feature = "enable_supabase")] @@ -125,7 +125,7 @@ pub trait AppFlowyServer: Send + Sync + 'static { _object_id: &str, ) -> FutureResult< Option<( - Arc>, + Arc>, WSConnectStateReceiver, bool, )>, diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index b4a098555c..611fd9bad1 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -197,7 +197,7 @@ pub enum UserEvent { #[event(input = "ImportAppFlowyDataPB")] ImportAppFlowyDataFolder = 41, - #[event(output = "CreateWorkspacePB")] + #[event(input = "CreateWorkspacePB", output = "UserWorkspacePB")] CreateWorkspace = 42, #[event(input = "UserWorkspaceIdPB")] diff --git a/frontend/rust-lib/lib-log/src/lib.rs b/frontend/rust-lib/lib-log/src/lib.rs index 318bbc34cb..01536fc37b 100644 --- a/frontend/rust-lib/lib-log/src/lib.rs +++ b/frontend/rust-lib/lib-log/src/lib.rs @@ -7,7 +7,7 @@ use tracing_appender::rolling::Rotation; use tracing_appender::{non_blocking::WorkerGuard, rolling::RollingFileAppender}; use tracing_bunyan_formatter::JsonStorageLayer; use tracing_subscriber::fmt::format::Writer; -use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter}; +use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; use crate::layer::FlowyFormattingLayer; @@ -48,7 +48,7 @@ impl Builder { pub fn build(self) -> Result<(), String> { let env_filter = EnvFilter::new(self.env_filter); - let std_out_layer = fmt::layer().with_writer(std::io::stdout).pretty(); + // let std_out_layer = std::fmt::layer().with_writer(std::io::stdout).pretty(); let (non_blocking, guard) = tracing_appender::non_blocking(self.file_appender); let file_layer = FlowyFormattingLayer::new(non_blocking); @@ -61,8 +61,7 @@ impl Builder { .with_env_filter(env_filter) .finish() .with(JsonStorageLayer) - .with(file_layer) - .with(std_out_layer); + .with(file_layer); set_global_default(subscriber).map_err(|e| format!("{:?}", e))?;