diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index dbc74be7e4e4..6e433b75130b 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.6.0" +APPFLOWY_VERSION = "0.6.1" FLUTTER_DESKTOP_FEATURES = "dart" PRODUCT_NAME = "AppFlowy" MACOSX_DEPLOYMENT_TARGET = "11.0" diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart index 9d288c3411d3..69f5d8951ac3 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart'; +import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; @@ -22,6 +23,18 @@ class MobileSpace extends StatefulWidget { } class _MobileSpaceState extends State { + @override + void initState() { + super.initState(); + createNewPageNotifier.addListener(_createNewPage); + } + + @override + void dispose() { + createNewPageNotifier.removeListener(_createNewPage); + super.dispose(); + } + @override Widget build(BuildContext context) { return BlocBuilder( @@ -81,6 +94,14 @@ class _MobileSpaceState extends State { }, ); } + + void _createNewPage() { + context.read().add( + SpaceEvent.createPage( + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + ), + ); + } } class _Pages extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart index d50369e5f962..5d33dfa50055 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart @@ -1,18 +1,15 @@ import 'dart:ui'; import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/mobile/application/mobile_router.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/util/theme_extension.dart'; -import 'package:appflowy/workspace/application/workspace/workspace_service.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:easy_localization/easy_localization.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +final PropertyValueNotifier createNewPageNotifier = + PropertyValueNotifier(null); + const _homeLabel = 'home'; const _addLabel = 'add'; const _notificationLabel = 'notification'; @@ -93,7 +90,7 @@ class MobileBottomNavigationBar extends StatelessWidget { void _onTap(BuildContext context, int bottomBarIndex) { if (_items[bottomBarIndex].label == _addLabel) { // show an add dialog - _showCreatePageBottomSheet(context); + createNewPageNotifier.value = ViewLayoutPB.Document; return; } // When navigating to a new branch, it's recommended to use the goBranch @@ -108,40 +105,4 @@ class MobileBottomNavigationBar extends StatelessWidget { initialLocation: bottomBarIndex == navigationShell.currentIndex, ); } - - void _showCreatePageBottomSheet(BuildContext context) { - showMobileBottomSheet( - context, - showHeader: true, - title: LocaleKeys.sideBar_addAPage.tr(), - showDragHandle: true, - showCloseButton: true, - useRootNavigator: true, - builder: (sheetContext) { - return AddNewPageWidgetBottomSheet( - view: ViewPB(), - onAction: (layout) async { - Navigator.of(sheetContext).pop(); - final currentWorkspaceId = - await FolderEventReadCurrentWorkspace().send(); - await currentWorkspaceId.fold((s) async { - final workspaceService = WorkspaceService(workspaceId: s.id); - final result = await workspaceService.createView( - name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), - viewSection: ViewSectionPB.Private, - layout: layout, - ); - result.fold((s) { - context.pushView(s); - }, (e) { - Log.error('Failed to create new page: $e'); - }); - }, (e) { - Log.error('Failed to read current workspace: $e'); - }); - }, - ); - }, - ); - } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart index f6a18ff45e41..95cd85566796 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart @@ -73,8 +73,7 @@ class _AppFlowyMobileToolbarState extends State { ValueListenableBuilder( valueListenable: isKeyboardShow, builder: (context, isKeyboardShow, __) { - return AnimatedContainer( - duration: const Duration(milliseconds: 110), + return SizedBox( // only adding padding when the keyboard is triggered by editor height: isKeyboardShow && widget.editorState.selection != null ? widget.toolbarHeight @@ -383,37 +382,26 @@ class _MobileToolbarState extends State<_MobileToolbar> return ValueListenableBuilder( valueListenable: cachedKeyboardHeight, builder: (_, height, ___) { - var paddingHeight = height; - if (Platform.isAndroid) { - // use the viewInsets to get the keyboard height on Android - paddingHeight = MediaQuery.of(context).viewInsets.bottom; - // if the padding height is 0 and the keyboard height is not 0, - // use the keyboard height - if (paddingHeight == 0 && height != 0) { - paddingHeight = height + MediaQuery.of(context).viewPadding.bottom; - } - } - debugPrint('Keyboard height: $paddingHeight'); - return AnimatedContainer( - duration: const Duration(microseconds: 110), - height: paddingHeight, - child: ValueListenableBuilder( - valueListenable: showMenuNotifier, - builder: (_, showingMenu, __) { - return AnimatedContainer( - duration: const Duration(microseconds: 110), - height: height, - child: (showingMenu && selectedMenuIndex != null) - ? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call( - context, - widget.editorState, - this, - ) ?? - const SizedBox.shrink() - : const SizedBox.shrink(), - ); - }, - ), + return ValueListenableBuilder( + valueListenable: showMenuNotifier, + builder: (_, showingMenu, __) { + var paddingHeight = height; + if (Platform.isAndroid) { + paddingHeight = + height + MediaQuery.of(context).viewPadding.bottom; + } + return SizedBox( + height: paddingHeight, + child: (showingMenu && selectedMenuIndex != null) + ? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call( + context, + widget.editorState, + this, + ) ?? + const SizedBox.shrink() + : const SizedBox.shrink(), + ); + }, ); }, ); diff --git a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart index 3a390ac3e065..3fd4afd4877e 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart @@ -19,6 +19,7 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_result/appflowy_result.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/uuid.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:protobuf/protobuf.dart'; @@ -257,6 +258,10 @@ class SpaceBloc extends Bloc { ); }, reset: (userProfile, workspaceId) async { + if (workspaceId == _workspaceId) { + return; + } + _reset(userProfile, workspaceId); add( @@ -286,6 +291,18 @@ class SpaceBloc extends Bloc { final nextSpace = spaces[nextIndex]; add(SpaceEvent.open(nextSpace)); }, + duplicate: () async { + final currentSpace = state.currentSpace; + if (currentSpace == null) { + return; + } + await ViewBackendService.duplicate( + view: currentSpace, + openAfterDuplicate: false, + includeChildren: true, + ); + add(const SpaceEvent.didReceiveSpaceUpdate()); + }, ); }, ); @@ -321,6 +338,7 @@ class SpaceBloc extends Bloc { required String icon, required String iconColor, required SpacePermission permission, + String? viewId, }) async { final section = switch (permission) { SpacePermission.publicToAll => ViewSectionPB.Public, @@ -331,6 +349,7 @@ class SpaceBloc extends Bloc { name: name, viewSection: section, setAsCurrent: false, + viewId: viewId, ); return await result.fold((space) async { Log.info('Space created: $space'); @@ -476,56 +495,82 @@ class SpaceBloc extends Bloc { if (members.items.length == 1 || isOwner) { // create a new public space and a new private space // move all the views in the workspace to the new public/private space - final publicViews = - await _workspaceService.getPublicViews().getOrThrow(); + var publicViews = await _workspaceService.getPublicViews().getOrThrow(); + final containsPublicSpace = publicViews.any( + (e) => e.isSpace && e.spacePermission == SpacePermission.publicToAll, + ); + publicViews = publicViews.where((e) => !e.isSpace).toList(); + // if there is already a public space, don't migrate the public space // only migrate the public space if there are any public views - if (publicViews.isNotEmpty) { - final publicSpace = await _createSpace( - name: 'Shared', - icon: builtInSpaceIcons.first, - iconColor: builtInSpaceColors.first, - permission: SpacePermission.publicToAll, - ); - - if (publicSpace != null) { - for (final view in publicViews.reversed) { - if (view.isSpace) { - continue; - } - await ViewBackendService.moveViewV2( - viewId: view.id, - newParentId: publicSpace.id, - prevViewId: view.parentViewId, - ); - } - } + if (publicViews.isEmpty || containsPublicSpace) { + return true; } - } - // create a new private space - final privateViews = - await _workspaceService.getPrivateViews().getOrThrow(); - // only migrate the private space if there are any private views - if (privateViews.isNotEmpty) { - final privateSpace = await _createSpace( - name: 'Private', - icon: builtInSpaceIcons.last, - iconColor: builtInSpaceColors.last, - permission: SpacePermission.private, + + final viewId = fixedUuid(user.id.toInt(), UuidType.publicSpace); + final publicSpace = await _createSpace( + name: 'Shared', + icon: builtInSpaceIcons.first, + iconColor: builtInSpaceColors.first, + permission: SpacePermission.publicToAll, + viewId: viewId, ); - if (privateSpace != null) { - for (final view in privateViews.reversed) { + + Log.info('migrating: created a new public space: ${publicSpace?.id}'); + + if (publicSpace != null) { + for (final view in publicViews.reversed) { if (view.isSpace) { continue; } await ViewBackendService.moveViewV2( viewId: view.id, - newParentId: privateSpace.id, - prevViewId: view.parentViewId, + newParentId: publicSpace.id, + prevViewId: null, + ); + Log.info( + 'migrating: migrate ${view.name}(${view.id}) to public space(${publicSpace.id})', ); } } } + // create a new private space + final viewId = fixedUuid(user.id.toInt(), UuidType.privateSpace); + var privateViews = await _workspaceService.getPrivateViews().getOrThrow(); + // if there is already a private space, don't migrate the private space + final containsPrivateSpace = privateViews.any( + (e) => e.isSpace && e.spacePermission == SpacePermission.private, + ); + privateViews = privateViews.where((e) => !e.isSpace).toList(); + if (privateViews.isEmpty || containsPrivateSpace) { + return true; + } + // only migrate the private space if there are any private views + final privateSpace = await _createSpace( + name: 'Private', + icon: builtInSpaceIcons.last, + iconColor: builtInSpaceColors.last, + permission: SpacePermission.private, + viewId: viewId, + ); + Log.info('migrating: created a new private space: ${privateSpace?.id}'); + + if (privateSpace != null) { + for (final view in privateViews.reversed) { + if (view.isSpace) { + continue; + } + await ViewBackendService.moveViewV2( + viewId: view.id, + newParentId: privateSpace.id, + prevViewId: null, + ); + Log.info( + 'migrating: migrate ${view.name}(${view.id}) to private space(${privateSpace.id})', + ); + } + } + return true; } catch (e) { Log.error('migrate space error: $e'); @@ -538,14 +583,16 @@ class SpaceBloc extends Bloc { required List publicViews, required List privateViews, }) async { - final publicSpaces = - spaces.where((e) => e.spacePermission == SpacePermission.publicToAll); + final publicSpaces = spaces.where( + (e) => e.spacePermission == SpacePermission.publicToAll, + ); if (publicSpaces.isEmpty && publicViews.isNotEmpty) { return true; } - final privateSpaces = - spaces.where((e) => e.spacePermission == SpacePermission.private); + final privateSpaces = spaces.where( + (e) => e.spacePermission == SpacePermission.private, + ); if (privateSpaces.isEmpty && privateViews.isNotEmpty) { return true; } @@ -571,6 +618,7 @@ class SpaceEvent with _$SpaceEvent { const factory SpaceEvent.rename(ViewPB space, String name) = _Rename; const factory SpaceEvent.changeIcon(String icon, String iconColor) = _ChangeIcon; + const factory SpaceEvent.duplicate() = _Duplicate; const factory SpaceEvent.update({ String? name, String? icon, diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart index bf243f8a1c7b..26004225110b 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart @@ -154,7 +154,11 @@ class ViewBloc extends Bloc { ); }, duplicate: (e) async { - final result = await ViewBackendService.duplicate(view: view); + final result = await ViewBackendService.duplicate( + view: view, + openAfterDuplicate: true, + includeChildren: true, + ); emit( result.fold( (l) => diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart index b85467d41f8b..2e27ba364d44 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart @@ -38,6 +38,7 @@ class ViewBackendService { /// If the index is null, the view will be added to the end of the list. int? index, ViewSectionPB? section, + final String? viewId, }) { final payload = CreateViewPayloadPB.create() ..parentViewId = parentViewId @@ -63,6 +64,10 @@ class ViewBackendService { payload.section = section; } + if (viewId != null) { + payload.viewId = viewId; + } + return FolderEventCreateView(payload).send(); } @@ -133,8 +138,15 @@ class ViewBackendService { static Future> duplicate({ required ViewPB view, + required bool openAfterDuplicate, + // should include children views + required bool includeChildren, }) { - return FolderEventDuplicateView(view).send(); + final payload = DuplicateViewPayloadPB.create() + ..viewId = view.id + ..openAfterDuplicate = openAfterDuplicate + ..includeChildren = includeChildren; + return FolderEventDuplicateView(payload).send(); } static Future> favorite({ diff --git a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart index 9931a1e4560b..8da9ad485483 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart @@ -18,6 +18,7 @@ class WorkspaceService { int? index, ViewLayoutPB? layout, bool? setAsCurrent, + String? viewId, }) { final payload = CreateViewPayloadPB.create() ..parentViewId = workspaceId @@ -37,6 +38,10 @@ class WorkspaceService { payload.setAsCurrent = setAsCurrent; } + if (viewId != null) { + payload.viewId = viewId; + } + return FolderEventCreateView(payload).send(); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart index 6c277736cc51..65ad86d77cbb 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart @@ -211,6 +211,9 @@ class _SidebarSpaceHeaderState extends State { case SpaceMoreActionType.delete: _showDeleteSpaceDialog(context); break; + case SpaceMoreActionType.duplicate: + context.read().add(const SpaceEvent.duplicate()); + break; case SpaceMoreActionType.divider: break; } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_action_type.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_action_type.dart index 83472b7a8057..5edf2d82b626 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_action_type.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_action_type.dart @@ -11,6 +11,7 @@ enum SpaceMoreActionType { divider, addNewSpace, manage, + duplicate, } extension ViewMoreActionTypeExtension on SpaceMoreActionType { @@ -28,6 +29,8 @@ extension ViewMoreActionTypeExtension on SpaceMoreActionType { return LocaleKeys.space_addNewSpace.tr(); case SpaceMoreActionType.manage: return LocaleKeys.space_manage.tr(); + case SpaceMoreActionType.duplicate: + return LocaleKeys.space_duplicate.tr(); case SpaceMoreActionType.divider: return ''; } @@ -47,6 +50,8 @@ extension ViewMoreActionTypeExtension on SpaceMoreActionType { return const FlowySvg(FlowySvgs.space_add_s); case SpaceMoreActionType.manage: return const FlowySvg(FlowySvgs.space_manage_s); + case SpaceMoreActionType.duplicate: + return const FlowySvg(FlowySvgs.duplicate_s); case SpaceMoreActionType.divider: return const SizedBox.shrink(); } @@ -61,6 +66,7 @@ extension ViewMoreActionTypeExtension on SpaceMoreActionType { case SpaceMoreActionType.delete: case SpaceMoreActionType.addNewSpace: case SpaceMoreActionType.manage: + case SpaceMoreActionType.duplicate: return const SizedBox.shrink(); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart index 00b0bc3307eb..3ee4f215db95 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart @@ -69,6 +69,7 @@ class SpaceMorePopup extends StatelessWidget { SpaceMoreActionType.rename, SpaceMoreActionType.changeIcon, SpaceMoreActionType.manage, + SpaceMoreActionType.duplicate, SpaceMoreActionType.divider, SpaceMoreActionType.addNewSpace, SpaceMoreActionType.collapseAllPages, diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart index df8aee9ddaa8..1f4c9036256c 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/uuid.dart @@ -1,5 +1,19 @@ +import 'package:uuid/data.dart'; +import 'package:uuid/rng.dart'; import 'package:uuid/uuid.dart'; +const _uuid = Uuid(); + String uuid() { - return const Uuid().v4(); + return _uuid.v4(); +} + +String fixedUuid(int seed, UuidType type) { + return _uuid.v4(config: V4Options(null, MathRNG(seed: seed + type.index))); +} + +enum UuidType { + // 0.6.0 + publicSpace, + privateSpace, } diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index a16b2e416380..e066d6e0dd02 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -2174,10 +2174,10 @@ packages: dependency: "direct overridden" description: name: uuid - sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.4.0" value_layout_builder: dependency: transitive description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 67241c3ec0f9..dffab82055ca 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.6.0 +version: 0.6.1 environment: flutter: ">=3.22.0" @@ -199,7 +199,7 @@ dependency_overrides: ref: e44458d path: sheet - uuid: ^4.1.0 + uuid: ^4.4.0 flutter_cache_manager: git: diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index aa07be5eebcd..6ca09491cae8 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -904,7 +904,7 @@ dependencies = [ [[package]] name = "collab" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-trait", @@ -928,7 +928,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-trait", @@ -958,7 +958,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "collab", @@ -978,7 +978,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "bytes", @@ -993,7 +993,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "chrono", @@ -1031,7 +1031,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-stream", @@ -1112,7 +1112,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "collab", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index bae84180da55..f8bdb1d823d9 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -106,10 +106,10 @@ default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] [patch.crates-io] -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } +collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } diff --git a/frontend/appflowy_web/wasm-libs/Cargo.toml b/frontend/appflowy_web/wasm-libs/Cargo.toml index 9816e6ef9484..2dc5b9a11211 100644 --- a/frontend/appflowy_web/wasm-libs/Cargo.toml +++ b/frontend/appflowy_web/wasm-libs/Cargo.toml @@ -67,10 +67,10 @@ opt-level = 3 codegen-units = 1 [patch.crates-io] -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6febf0397e66ebf0a281980a2e7602d7af00c975" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6febf0397e66ebf0a281980a2e7602d7af00c975" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6febf0397e66ebf0a281980a2e7602d7af00c975" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6febf0397e66ebf0a281980a2e7602d7af00c975" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6febf0397e66ebf0a281980a2e7602d7af00c975" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6febf0397e66ebf0a281980a2e7602d7af00c975" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6febf0397e66ebf0a281980a2e7602d7af00c975" } +collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.lock b/frontend/appflowy_web_app/src-tauri/Cargo.lock index a4a230c51476..edf953de3e63 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.lock +++ b/frontend/appflowy_web_app/src-tauri/Cargo.lock @@ -887,7 +887,7 @@ dependencies = [ [[package]] name = "collab" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-trait", @@ -911,7 +911,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-trait", @@ -941,7 +941,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "collab", @@ -961,7 +961,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "bytes", @@ -976,7 +976,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "chrono", @@ -1014,7 +1014,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-stream", @@ -1095,7 +1095,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "collab", diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.toml b/frontend/appflowy_web_app/src-tauri/Cargo.toml index ddf01dabc564..feaf5a162612 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.toml +++ b/frontend/appflowy_web_app/src-tauri/Cargo.toml @@ -107,10 +107,10 @@ default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] [patch.crates-io] -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } +collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 985e17eee8d0..cbd376206901 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -2011,6 +2011,7 @@ "upgradeSpaceDescription": "Create multiple public and private Spaces to better organize your workspace.", "upgrade": "Update", "upgradeYourSpace": "Create multiple Spaces", - "quicklySwitch": "Quickly switch to the next space" + "quicklySwitch": "Quickly switch to the next space", + "duplicate": "Duplicate Space" } } \ No newline at end of file diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index ce749cbe0ca7..145a5339be0a 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -765,7 +765,7 @@ dependencies = [ [[package]] name = "collab" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-trait", @@ -789,7 +789,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-trait", @@ -819,7 +819,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "collab", @@ -839,7 +839,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "bytes", @@ -854,7 +854,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "chrono", @@ -892,7 +892,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "async-stream", @@ -973,7 +973,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=3a58d95#3a58d95a202b2814920650fa71c458fb0b49293d" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5048762#5048762dbb01abcbe75237e86c0d090e2f1d7c23" dependencies = [ "anyhow", "collab", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 27342183192f..53cb8a25e3e4 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -137,10 +137,10 @@ rocksdb = { git = "https://github.com/LucasXu0/rust-rocksdb", rev = "21cf4a23ec1 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "3a58d95" } +collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } +collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" } diff --git a/frontend/rust-lib/event-integration-test/src/chat_event.rs b/frontend/rust-lib/event-integration-test/src/chat_event.rs index 60e8ba65b1a3..bb0090fe359a 100644 --- a/frontend/rust-lib/event-integration-test/src/chat_event.rs +++ b/frontend/rust-lib/event-integration-test/src/chat_event.rs @@ -21,6 +21,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/database_event.rs b/frontend/rust-lib/event-integration-test/src/database_event.rs index 79b8b528d297..4b6968ec0666 100644 --- a/frontend/rust-lib/event-integration-test/src/database_event.rs +++ b/frontend/rust-lib/event-integration-test/src/database_event.rs @@ -45,6 +45,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) @@ -76,6 +77,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) @@ -102,6 +104,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/document/document_event.rs b/frontend/rust-lib/event-integration-test/src/document/document_event.rs index 93d3ccc80faf..8595ac056b37 100644 --- a/frontend/rust-lib/event-integration-test/src/document/document_event.rs +++ b/frontend/rust-lib/event-integration-test/src/document/document_event.rs @@ -65,6 +65,7 @@ impl DocumentEventTest { set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(core.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/document_event.rs b/frontend/rust-lib/event-integration-test/src/document_event.rs index 6f99998e5048..8374bc9de3eb 100644 --- a/frontend/rust-lib/event-integration-test/src/document_event.rs +++ b/frontend/rust-lib/event-integration-test/src/document_event.rs @@ -42,6 +42,7 @@ impl EventIntegrationTest { set_as_current: true, index: None, section: None, + view_id: None, }; let view = EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) diff --git a/frontend/rust-lib/event-integration-test/src/folder_event.rs b/frontend/rust-lib/event-integration-test/src/folder_event.rs index af4de3f5d082..c299d5ab608f 100644 --- a/frontend/rust-lib/event-integration-test/src/folder_event.rs +++ b/frontend/rust-lib/event-integration-test/src/folder_event.rs @@ -126,6 +126,8 @@ impl EventIntegrationTest { set_as_current: false, index: None, section: None, + icon: view.icon, + extra: view.extra, }) .collect::>(); @@ -230,6 +232,7 @@ impl EventIntegrationTest { set_as_current: false, index: None, section: None, + view_id: None, }; EventBuilder::new(self.clone()) .event(FolderEvent::CreateView) @@ -293,6 +296,7 @@ impl ViewTest { set_as_current: true, index: None, section: None, + view_id: None, }; let view = EventBuilder::new(sdk.clone()) diff --git a/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs b/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs index 7be83964e3ac..0ebbd64efa9b 100644 --- a/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs +++ b/frontend/rust-lib/event-integration-test/tests/folder/local_test/script.rs @@ -252,6 +252,7 @@ pub async fn create_view( set_as_current: true, index: None, section: None, + view_id: None, }; EventBuilder::new(sdk.clone()) .event(CreateView) diff --git a/frontend/rust-lib/flowy-folder/src/entities/view.rs b/frontend/rust-lib/flowy-folder/src/entities/view.rs index d5945ccaa329..25c5e1c9abb3 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/view.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/view.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use std::ops::{Deref, DerefMut}; use std::sync::Arc; -use collab_folder::{View, ViewLayout}; +use collab_folder::{View, ViewIcon, ViewLayout}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; @@ -258,6 +258,9 @@ pub struct CreateViewPayloadPB { // The view in private section will only be shown in the user's private view list. #[pb(index = 10, one_of)] pub section: Option, + + #[pb(index = 11, one_of)] + pub view_id: Option, } #[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone, Default)] @@ -305,6 +308,10 @@ pub struct CreateViewParams { pub index: Option, // The section of the view. pub section: Option, + // The icon of the view. + pub icon: Option, + // The extra data of the view. + pub extra: Option, } impl TryInto for CreateViewPayloadPB { @@ -313,7 +320,8 @@ impl TryInto for CreateViewPayloadPB { fn try_into(self) -> Result { let name = ViewName::parse(self.name)?.0; let parent_view_id = ViewIdentify::parse(self.parent_view_id)?.0; - let view_id = gen_view_id().to_string(); + // if view_id is not provided, generate a new view_id + let view_id = self.view_id.unwrap_or_else(|| gen_view_id().to_string()); Ok(CreateViewParams { parent_view_id, @@ -326,6 +334,8 @@ impl TryInto for CreateViewPayloadPB { set_as_current: self.set_as_current, index: self.index, section: self.section, + icon: None, + extra: None, }) } } @@ -348,6 +358,8 @@ impl TryInto for CreateOrphanViewPayloadPB { set_as_current: false, index: None, section: None, + icon: None, + extra: None, }) } } @@ -562,6 +574,40 @@ pub struct UpdateViewVisibilityStatusPayloadPB { pub is_public: bool, } +#[derive(Default, ProtoBuf)] +pub struct DuplicateViewPayloadPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2)] + pub open_after_duplicate: bool, + + #[pb(index = 3)] + pub include_children: bool, +} + +#[derive(Debug)] +pub struct DuplicateViewParams { + pub view_id: String, + + pub open_after_duplicate: bool, + + pub include_children: bool, +} + +impl TryInto for DuplicateViewPayloadPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let view_id = ViewIdentify::parse(self.view_id)?.0; + Ok(DuplicateViewParams { + view_id, + open_after_duplicate: self.open_after_duplicate, + include_children: self.include_children, + }) + } +} + // impl<'de> Deserialize<'de> for ViewDataType { // fn deserialize(deserializer: D) -> Result>::Error> // where diff --git a/frontend/rust-lib/flowy-folder/src/event_handler.rs b/frontend/rust-lib/flowy-folder/src/event_handler.rs index 888c26d2a6d5..03b71fd2a91c 100644 --- a/frontend/rust-lib/flowy-folder/src/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/event_handler.rs @@ -266,12 +266,12 @@ pub(crate) async fn move_nested_view_handler( #[tracing::instrument(level = "debug", skip(data, folder), err)] pub(crate) async fn duplicate_view_handler( - data: AFPluginData, + data: AFPluginData, folder: AFPluginState>, ) -> Result<(), FlowyError> { let folder = upgrade_folder(folder)?; - let view: ViewPB = data.into_inner(); - folder.duplicate_view(&view.id).await?; + let params: DuplicateViewParams = data.into_inner().try_into()?; + folder.duplicate_view(params).await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-folder/src/event_map.rs b/frontend/rust-lib/flowy-folder/src/event_map.rs index febfc49b5ef1..aa8bf551a4c8 100644 --- a/frontend/rust-lib/flowy-folder/src/event_map.rs +++ b/frontend/rust-lib/flowy-folder/src/event_map.rs @@ -85,7 +85,7 @@ pub enum FolderEvent { DeleteView = 13, /// Duplicate the view - #[event(input = "ViewPB")] + #[event(input = "DuplicateViewPayloadPB")] DuplicateView = 14, /// Close and release the resources that are used by this view. diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index fc3994056977..28d802b6dbf5 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -1,9 +1,9 @@ use crate::entities::icon::UpdateViewIconParams; use crate::entities::{ view_pb_with_child_views, view_pb_without_child_views, view_pb_without_child_views_from_arc, - CreateViewParams, CreateWorkspaceParams, DeletedViewPB, FolderSnapshotPB, MoveNestedViewParams, - RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, UpdateViewParams, ViewPB, ViewSectionPB, - WorkspacePB, WorkspaceSettingPB, + CreateViewParams, CreateWorkspaceParams, DeletedViewPB, DuplicateViewParams, FolderSnapshotPB, + MoveNestedViewParams, RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, UpdateViewParams, + ViewPB, ViewSectionPB, WorkspacePB, WorkspaceSettingPB, }; use crate::manager_observer::{ notify_child_views_changed, notify_did_update_workspace, notify_parent_view_did_change, @@ -742,46 +742,119 @@ impl FolderManager { } /// Duplicate the view with the given view id. + /// + /// Including the view data (icon, cover, extra) and the child views. #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn duplicate_view(&self, view_id: &str) -> Result<(), FlowyError> { + pub(crate) async fn duplicate_view(&self, params: DuplicateViewParams) -> Result<(), FlowyError> { let view = self - .with_folder(|| None, |folder| folder.views.get_view(view_id)) + .with_folder(|| None, |folder| folder.views.get_view(¶ms.view_id)) .ok_or_else(|| FlowyError::record_not_found().with_context("Can't duplicate the view"))?; + self + .duplicate_view_with_parent_id( + &view.id, + &view.parent_view_id, + params.open_after_duplicate, + params.include_children, + ) + .await + } - let handler = self.get_handler(&view.layout)?; - let view_data = handler.duplicate_view(&view.id).await?; + /// Duplicate the view with the given view id and parent view id. + /// + /// If the view id is the same as the parent view id, it will return an error. + /// If the view id is not found, it will return an error. + pub(crate) async fn duplicate_view_with_parent_id( + &self, + view_id: &str, + parent_view_id: &str, + open_after_duplicated: bool, + include_children: bool, + ) -> Result<(), FlowyError> { + if view_id == parent_view_id { + return Err(FlowyError::new( + ErrorCode::Internal, + format!("Can't duplicate the view({}) to itself", view_id), + )); + } - // get the current view index in the parent view, because we need to insert the duplicated view below the current view. - let index = if let Some((_, __, views)) = self.get_view_relation(&view.parent_view_id).await { - views.iter().position(|id| id == view_id).map(|i| i as u32) - } else { - None - }; + let filtered_view_ids = self.with_folder(Vec::new, |folder| { + self.get_view_ids_should_be_filtered(folder) + }); - let is_private = self.with_folder( - || false, - |folder| folder.is_view_in_section(Section::Private, &view.id), - ); - let section = if is_private { - ViewSectionPB::Private - } else { - ViewSectionPB::Public - }; + // only apply the `open_after_duplicated` and the `include_children` to the first view + let mut is_source_view = true; + // use a stack to duplicate the view and its children + let mut stack = vec![(view_id.to_string(), parent_view_id.to_string())]; - let duplicate_params = CreateViewParams { - parent_view_id: view.parent_view_id.clone(), - name: format!("{} (copy)", &view.name), - desc: view.desc.clone(), - layout: view.layout.clone().into(), - initial_data: view_data.to_vec(), - view_id: gen_view_id().to_string(), - meta: Default::default(), - set_as_current: true, - index, - section: Some(section), - }; + while let Some((current_view_id, current_parent_id)) = stack.pop() { + let view = self + .with_folder(|| None, |folder| folder.views.get_view(¤t_view_id)) + .ok_or_else(|| { + FlowyError::record_not_found() + .with_context(format!("Can't duplicate the view({})", view_id)) + })?; + + let handler = self.get_handler(&view.layout)?; + let view_data = handler.duplicate_view(&view.id).await?; + + let index = self + .get_view_relation(¤t_parent_id) + .await + .and_then(|(_, _, views)| { + views + .iter() + .filter(|id| filtered_view_ids.contains(id)) + .position(|id| *id == current_view_id) + .map(|i| i as u32) + }); + + let section = self.with_folder( + || ViewSectionPB::Private, + |folder| { + if folder.is_view_in_section(Section::Private, &view.id) { + ViewSectionPB::Private + } else { + ViewSectionPB::Public + } + }, + ); + + let name = if is_source_view { + format!("{} (copy)", &view.name) + } else { + view.name.clone() + }; + let duplicate_params = CreateViewParams { + parent_view_id: current_parent_id.clone(), + name, + desc: view.desc.clone(), + layout: view.layout.clone().into(), + initial_data: view_data.to_vec(), + view_id: gen_view_id().to_string(), + meta: Default::default(), + set_as_current: is_source_view && open_after_duplicated, + index, + section: Some(section), + extra: view.extra.clone(), + icon: view.icon.clone(), + }; + + let duplicated_view = self.create_view_with_params(duplicate_params).await?; + + if include_children { + let child_views = self.get_views_belong_to(¤t_view_id).await?; + // reverse the child views to keep the order + for child_view in child_views.iter().rev() { + // skip the view_id should be filtered and the child_view is the duplicated view + if !filtered_view_ids.contains(&child_view.id) { + stack.push((child_view.id.clone(), duplicated_view.id.clone())); + } + } + } + + is_source_view = false + } - self.create_view_with_params(duplicate_params).await?; Ok(()) } @@ -1004,6 +1077,8 @@ impl FolderManager { set_as_current: false, index: None, section: None, + extra: None, + icon: None, }; let view = create_view(self.user.user_id()?, params, import_data.view_layout); diff --git a/frontend/rust-lib/flowy-folder/src/test_helper.rs b/frontend/rust-lib/flowy-folder/src/test_helper.rs index 50e4b290ff43..f7029568286e 100644 --- a/frontend/rust-lib/flowy-folder/src/test_helper.rs +++ b/frontend/rust-lib/flowy-folder/src/test_helper.rs @@ -48,6 +48,8 @@ impl FolderManager { set_as_current: true, index: None, section: Some(ViewSectionPB::Public), + icon: None, + extra: None, }; self.create_view_with_params(params).await.unwrap(); view_id diff --git a/frontend/rust-lib/flowy-folder/src/view_operation.rs b/frontend/rust-lib/flowy-folder/src/view_operation.rs index 10d173394c0a..3132384e777b 100644 --- a/frontend/rust-lib/flowy-folder/src/view_operation.rs +++ b/frontend/rust-lib/flowy-folder/src/view_operation.rs @@ -127,14 +127,14 @@ pub(crate) fn create_view(uid: i64, params: CreateViewParams, layout: ViewLayout parent_view_id: params.parent_view_id, name: params.name, desc: params.desc, - children: Default::default(), created_at: time, is_favorite: false, layout, - icon: None, + icon: params.icon, created_by: Some(uid), last_edited_time: 0, last_edited_by: Some(uid), - extra: None, + extra: params.extra, + children: Default::default(), } } diff --git a/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml b/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml index a0da34dec96c..cd8103df9eb1 100644 --- a/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml +++ b/frontend/scripts/linux_distribution/appimage/AppImageBuilder.yml @@ -59,8 +59,6 @@ AppDir: - libwayland-cursor0:amd64 - libwayland-client0:amd64 - libwayland-egl1:amd64 - - libmpv-dev:amd64 - - mpv:amd64 files: include: [] exclude: