feat: duplicate space (#5612)

This commit is contained in:
Lucas.Xu
2024-06-25 10:03:02 +08:00
committed by GitHub
parent 54c9d12171
commit b9ad2768cf
35 changed files with 420 additions and 224 deletions

View File

@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi" CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi" CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi" LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.6.0" APPFLOWY_VERSION = "0.6.1"
FLUTTER_DESKTOP_FEATURES = "dart" FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy" PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0" MACOSX_DEPLOYMENT_TARGET = "11.0"

View File

@ -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_header.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.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/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/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart';
@ -22,6 +23,18 @@ class MobileSpace extends StatefulWidget {
} }
class _MobileSpaceState extends State<MobileSpace> { class _MobileSpaceState extends State<MobileSpace> {
@override
void initState() {
super.initState();
createNewPageNotifier.addListener(_createNewPage);
}
@override
void dispose() {
createNewPageNotifier.removeListener(_createNewPage);
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SpaceBloc, SpaceState>( return BlocBuilder<SpaceBloc, SpaceState>(
@ -81,6 +94,14 @@ class _MobileSpaceState extends State<MobileSpace> {
}, },
); );
} }
void _createNewPage() {
context.read<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
),
);
}
} }
class _Pages extends StatelessWidget { class _Pages extends StatelessWidget {

View File

@ -1,18 +1,15 @@
import 'dart:ui'; import 'dart:ui';
import 'package:appflowy/generated/flowy_svgs.g.dart'; 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/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: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:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
final PropertyValueNotifier<ViewLayoutPB?> createNewPageNotifier =
PropertyValueNotifier(null);
const _homeLabel = 'home'; const _homeLabel = 'home';
const _addLabel = 'add'; const _addLabel = 'add';
const _notificationLabel = 'notification'; const _notificationLabel = 'notification';
@ -93,7 +90,7 @@ class MobileBottomNavigationBar extends StatelessWidget {
void _onTap(BuildContext context, int bottomBarIndex) { void _onTap(BuildContext context, int bottomBarIndex) {
if (_items[bottomBarIndex].label == _addLabel) { if (_items[bottomBarIndex].label == _addLabel) {
// show an add dialog // show an add dialog
_showCreatePageBottomSheet(context); createNewPageNotifier.value = ViewLayoutPB.Document;
return; return;
} }
// When navigating to a new branch, it's recommended to use the goBranch // 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, 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');
});
},
);
},
);
}
} }

View File

@ -73,8 +73,7 @@ class _AppFlowyMobileToolbarState extends State<AppFlowyMobileToolbar> {
ValueListenableBuilder( ValueListenableBuilder(
valueListenable: isKeyboardShow, valueListenable: isKeyboardShow,
builder: (context, isKeyboardShow, __) { builder: (context, isKeyboardShow, __) {
return AnimatedContainer( return SizedBox(
duration: const Duration(milliseconds: 110),
// only adding padding when the keyboard is triggered by editor // only adding padding when the keyboard is triggered by editor
height: isKeyboardShow && widget.editorState.selection != null height: isKeyboardShow && widget.editorState.selection != null
? widget.toolbarHeight ? widget.toolbarHeight
@ -383,37 +382,26 @@ class _MobileToolbarState extends State<_MobileToolbar>
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: cachedKeyboardHeight, valueListenable: cachedKeyboardHeight,
builder: (_, height, ___) { builder: (_, height, ___) {
var paddingHeight = height; return ValueListenableBuilder(
if (Platform.isAndroid) { valueListenable: showMenuNotifier,
// use the viewInsets to get the keyboard height on Android builder: (_, showingMenu, __) {
paddingHeight = MediaQuery.of(context).viewInsets.bottom; var paddingHeight = height;
// if the padding height is 0 and the keyboard height is not 0, if (Platform.isAndroid) {
// use the keyboard height paddingHeight =
if (paddingHeight == 0 && height != 0) { height + MediaQuery.of(context).viewPadding.bottom;
paddingHeight = height + MediaQuery.of(context).viewPadding.bottom; }
} return SizedBox(
} height: paddingHeight,
debugPrint('Keyboard height: $paddingHeight'); child: (showingMenu && selectedMenuIndex != null)
return AnimatedContainer( ? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
duration: const Duration(microseconds: 110), context,
height: paddingHeight, widget.editorState,
child: ValueListenableBuilder( this,
valueListenable: showMenuNotifier, ) ??
builder: (_, showingMenu, __) { const SizedBox.shrink()
return AnimatedContainer( : const SizedBox.shrink(),
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(),
);
},
),
); );
}, },
); );

View File

@ -19,6 +19,7 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:appflowy_result/appflowy_result.dart'; import 'package:appflowy_result/appflowy_result.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:protobuf/protobuf.dart'; import 'package:protobuf/protobuf.dart';
@ -257,6 +258,10 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
); );
}, },
reset: (userProfile, workspaceId) async { reset: (userProfile, workspaceId) async {
if (workspaceId == _workspaceId) {
return;
}
_reset(userProfile, workspaceId); _reset(userProfile, workspaceId);
add( add(
@ -286,6 +291,18 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
final nextSpace = spaces[nextIndex]; final nextSpace = spaces[nextIndex];
add(SpaceEvent.open(nextSpace)); 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<SpaceEvent, SpaceState> {
required String icon, required String icon,
required String iconColor, required String iconColor,
required SpacePermission permission, required SpacePermission permission,
String? viewId,
}) async { }) async {
final section = switch (permission) { final section = switch (permission) {
SpacePermission.publicToAll => ViewSectionPB.Public, SpacePermission.publicToAll => ViewSectionPB.Public,
@ -331,6 +349,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
name: name, name: name,
viewSection: section, viewSection: section,
setAsCurrent: false, setAsCurrent: false,
viewId: viewId,
); );
return await result.fold((space) async { return await result.fold((space) async {
Log.info('Space created: $space'); Log.info('Space created: $space');
@ -476,56 +495,82 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
if (members.items.length == 1 || isOwner) { if (members.items.length == 1 || isOwner) {
// create a new public space and a new private space // create a new public space and a new private space
// move all the views in the workspace to the new public/private space // move all the views in the workspace to the new public/private space
final publicViews = var publicViews = await _workspaceService.getPublicViews().getOrThrow();
await _workspaceService.getPublicViews().getOrThrow(); final containsPublicSpace = publicViews.any(
// only migrate the public space if there are any public views (e) => e.isSpace && e.spacePermission == SpacePermission.publicToAll,
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,
);
}
}
}
}
// 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,
); );
if (privateSpace != null) { publicViews = publicViews.where((e) => !e.isSpace).toList();
for (final view in privateViews.reversed) { // if there is already a public space, don't migrate the public space
// only migrate the public space if there are any public views
if (publicViews.isEmpty || containsPublicSpace) {
return true;
}
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,
);
Log.info('migrating: created a new public space: ${publicSpace?.id}');
if (publicSpace != null) {
for (final view in publicViews.reversed) {
if (view.isSpace) { if (view.isSpace) {
continue; continue;
} }
await ViewBackendService.moveViewV2( await ViewBackendService.moveViewV2(
viewId: view.id, viewId: view.id,
newParentId: privateSpace.id, newParentId: publicSpace.id,
prevViewId: view.parentViewId, 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; return true;
} catch (e) { } catch (e) {
Log.error('migrate space error: $e'); Log.error('migrate space error: $e');
@ -538,14 +583,16 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
required List<ViewPB> publicViews, required List<ViewPB> publicViews,
required List<ViewPB> privateViews, required List<ViewPB> privateViews,
}) async { }) async {
final publicSpaces = final publicSpaces = spaces.where(
spaces.where((e) => e.spacePermission == SpacePermission.publicToAll); (e) => e.spacePermission == SpacePermission.publicToAll,
);
if (publicSpaces.isEmpty && publicViews.isNotEmpty) { if (publicSpaces.isEmpty && publicViews.isNotEmpty) {
return true; return true;
} }
final privateSpaces = final privateSpaces = spaces.where(
spaces.where((e) => e.spacePermission == SpacePermission.private); (e) => e.spacePermission == SpacePermission.private,
);
if (privateSpaces.isEmpty && privateViews.isNotEmpty) { if (privateSpaces.isEmpty && privateViews.isNotEmpty) {
return true; return true;
} }
@ -571,6 +618,7 @@ class SpaceEvent with _$SpaceEvent {
const factory SpaceEvent.rename(ViewPB space, String name) = _Rename; const factory SpaceEvent.rename(ViewPB space, String name) = _Rename;
const factory SpaceEvent.changeIcon(String icon, String iconColor) = const factory SpaceEvent.changeIcon(String icon, String iconColor) =
_ChangeIcon; _ChangeIcon;
const factory SpaceEvent.duplicate() = _Duplicate;
const factory SpaceEvent.update({ const factory SpaceEvent.update({
String? name, String? name,
String? icon, String? icon,

View File

@ -154,7 +154,11 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
); );
}, },
duplicate: (e) async { duplicate: (e) async {
final result = await ViewBackendService.duplicate(view: view); final result = await ViewBackendService.duplicate(
view: view,
openAfterDuplicate: true,
includeChildren: true,
);
emit( emit(
result.fold( result.fold(
(l) => (l) =>

View File

@ -38,6 +38,7 @@ class ViewBackendService {
/// If the index is null, the view will be added to the end of the list. /// If the index is null, the view will be added to the end of the list.
int? index, int? index,
ViewSectionPB? section, ViewSectionPB? section,
final String? viewId,
}) { }) {
final payload = CreateViewPayloadPB.create() final payload = CreateViewPayloadPB.create()
..parentViewId = parentViewId ..parentViewId = parentViewId
@ -63,6 +64,10 @@ class ViewBackendService {
payload.section = section; payload.section = section;
} }
if (viewId != null) {
payload.viewId = viewId;
}
return FolderEventCreateView(payload).send(); return FolderEventCreateView(payload).send();
} }
@ -133,8 +138,15 @@ class ViewBackendService {
static Future<FlowyResult<void, FlowyError>> duplicate({ static Future<FlowyResult<void, FlowyError>> duplicate({
required ViewPB view, 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<FlowyResult<void, FlowyError>> favorite({ static Future<FlowyResult<void, FlowyError>> favorite({

View File

@ -18,6 +18,7 @@ class WorkspaceService {
int? index, int? index,
ViewLayoutPB? layout, ViewLayoutPB? layout,
bool? setAsCurrent, bool? setAsCurrent,
String? viewId,
}) { }) {
final payload = CreateViewPayloadPB.create() final payload = CreateViewPayloadPB.create()
..parentViewId = workspaceId ..parentViewId = workspaceId
@ -37,6 +38,10 @@ class WorkspaceService {
payload.setAsCurrent = setAsCurrent; payload.setAsCurrent = setAsCurrent;
} }
if (viewId != null) {
payload.viewId = viewId;
}
return FolderEventCreateView(payload).send(); return FolderEventCreateView(payload).send();
} }

View File

@ -211,6 +211,9 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
case SpaceMoreActionType.delete: case SpaceMoreActionType.delete:
_showDeleteSpaceDialog(context); _showDeleteSpaceDialog(context);
break; break;
case SpaceMoreActionType.duplicate:
context.read<SpaceBloc>().add(const SpaceEvent.duplicate());
break;
case SpaceMoreActionType.divider: case SpaceMoreActionType.divider:
break; break;
} }

View File

@ -11,6 +11,7 @@ enum SpaceMoreActionType {
divider, divider,
addNewSpace, addNewSpace,
manage, manage,
duplicate,
} }
extension ViewMoreActionTypeExtension on SpaceMoreActionType { extension ViewMoreActionTypeExtension on SpaceMoreActionType {
@ -28,6 +29,8 @@ extension ViewMoreActionTypeExtension on SpaceMoreActionType {
return LocaleKeys.space_addNewSpace.tr(); return LocaleKeys.space_addNewSpace.tr();
case SpaceMoreActionType.manage: case SpaceMoreActionType.manage:
return LocaleKeys.space_manage.tr(); return LocaleKeys.space_manage.tr();
case SpaceMoreActionType.duplicate:
return LocaleKeys.space_duplicate.tr();
case SpaceMoreActionType.divider: case SpaceMoreActionType.divider:
return ''; return '';
} }
@ -47,6 +50,8 @@ extension ViewMoreActionTypeExtension on SpaceMoreActionType {
return const FlowySvg(FlowySvgs.space_add_s); return const FlowySvg(FlowySvgs.space_add_s);
case SpaceMoreActionType.manage: case SpaceMoreActionType.manage:
return const FlowySvg(FlowySvgs.space_manage_s); return const FlowySvg(FlowySvgs.space_manage_s);
case SpaceMoreActionType.duplicate:
return const FlowySvg(FlowySvgs.duplicate_s);
case SpaceMoreActionType.divider: case SpaceMoreActionType.divider:
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@ -61,6 +66,7 @@ extension ViewMoreActionTypeExtension on SpaceMoreActionType {
case SpaceMoreActionType.delete: case SpaceMoreActionType.delete:
case SpaceMoreActionType.addNewSpace: case SpaceMoreActionType.addNewSpace:
case SpaceMoreActionType.manage: case SpaceMoreActionType.manage:
case SpaceMoreActionType.duplicate:
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
} }

View File

@ -69,6 +69,7 @@ class SpaceMorePopup extends StatelessWidget {
SpaceMoreActionType.rename, SpaceMoreActionType.rename,
SpaceMoreActionType.changeIcon, SpaceMoreActionType.changeIcon,
SpaceMoreActionType.manage, SpaceMoreActionType.manage,
SpaceMoreActionType.duplicate,
SpaceMoreActionType.divider, SpaceMoreActionType.divider,
SpaceMoreActionType.addNewSpace, SpaceMoreActionType.addNewSpace,
SpaceMoreActionType.collapseAllPages, SpaceMoreActionType.collapseAllPages,

View File

@ -1,5 +1,19 @@
import 'package:uuid/data.dart';
import 'package:uuid/rng.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
const _uuid = Uuid();
String 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,
} }

View File

@ -2174,10 +2174,10 @@ packages:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
name: uuid name: uuid
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.3" version: "4.4.0"
value_layout_builder: value_layout_builder:
dependency: transitive dependency: transitive
description: description:

View File

@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.6.0 version: 0.6.1
environment: environment:
flutter: ">=3.22.0" flutter: ">=3.22.0"
@ -199,7 +199,7 @@ dependency_overrides:
ref: e44458d ref: e44458d
path: sheet path: sheet
uuid: ^4.1.0 uuid: ^4.4.0
flutter_cache_manager: flutter_cache_manager:
git: git:

View File

@ -907,7 +907,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -931,7 +931,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -961,7 +961,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -981,7 +981,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -996,7 +996,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1034,7 +1034,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -1115,7 +1115,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",

View File

@ -106,10 +106,10 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io] [patch.crates-io]
collab = { 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }

View File

@ -68,10 +68,10 @@ opt-level = 3
codegen-units = 1 codegen-units = 1
[patch.crates-io] [patch.crates-io]
collab = { 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 = "6febf0397e66ebf0a281980a2e7602d7af00c975" } 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 = "6febf0397e66ebf0a281980a2e7602d7af00c975" } 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 = "6febf0397e66ebf0a281980a2e7602d7af00c975" } 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 = "6febf0397e66ebf0a281980a2e7602d7af00c975" } 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 = "6febf0397e66ebf0a281980a2e7602d7af00c975" } 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 = "6febf0397e66ebf0a281980a2e7602d7af00c975" } collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }

View File

@ -890,7 +890,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -914,7 +914,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -944,7 +944,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -964,7 +964,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -979,7 +979,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1017,7 +1017,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -1098,7 +1098,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",

View File

@ -107,10 +107,10 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io] [patch.crates-io]
collab = { 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }

View File

@ -2020,6 +2020,7 @@
"upgradeSpaceDescription": "Create multiple public and private Spaces to better organize your workspace.", "upgradeSpaceDescription": "Create multiple public and private Spaces to better organize your workspace.",
"upgrade": "Update", "upgrade": "Update",
"upgradeYourSpace": "Create multiple Spaces", "upgradeYourSpace": "Create multiple Spaces",
"quicklySwitch": "Quickly switch to the next space" "quicklySwitch": "Quickly switch to the next space",
"duplicate": "Duplicate Space"
} }
} }

View File

@ -768,7 +768,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -792,7 +792,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -822,7 +822,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -842,7 +842,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -857,7 +857,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -895,7 +895,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -976,7 +976,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.2.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",

View File

@ -136,10 +136,10 @@ rocksdb = { git = "https://github.com/LucasXu0/rust-rocksdb", rev = "21cf4a23ec1
# To switch to the local path, run: # To switch to the local path, run:
# scripts/tool/update_collab_source.sh # scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
collab = { 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } 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 = "3a58d95" } collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5048762" }

View File

@ -21,6 +21,7 @@ impl EventIntegrationTest {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
EventBuilder::new(self.clone()) EventBuilder::new(self.clone())
.event(FolderEvent::CreateView) .event(FolderEvent::CreateView)

View File

@ -45,6 +45,7 @@ impl EventIntegrationTest {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
EventBuilder::new(self.clone()) EventBuilder::new(self.clone())
.event(FolderEvent::CreateView) .event(FolderEvent::CreateView)
@ -76,6 +77,7 @@ impl EventIntegrationTest {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
EventBuilder::new(self.clone()) EventBuilder::new(self.clone())
.event(FolderEvent::CreateView) .event(FolderEvent::CreateView)
@ -102,6 +104,7 @@ impl EventIntegrationTest {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
EventBuilder::new(self.clone()) EventBuilder::new(self.clone())
.event(FolderEvent::CreateView) .event(FolderEvent::CreateView)

View File

@ -65,6 +65,7 @@ impl DocumentEventTest {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
EventBuilder::new(core.clone()) EventBuilder::new(core.clone())
.event(FolderEvent::CreateView) .event(FolderEvent::CreateView)

View File

@ -42,6 +42,7 @@ impl EventIntegrationTest {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
let view = EventBuilder::new(self.clone()) let view = EventBuilder::new(self.clone())
.event(FolderEvent::CreateView) .event(FolderEvent::CreateView)

View File

@ -131,6 +131,8 @@ impl EventIntegrationTest {
set_as_current: false, set_as_current: false,
index: None, index: None,
section: None, section: None,
icon: view.icon,
extra: view.extra,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -235,6 +237,7 @@ impl EventIntegrationTest {
set_as_current: false, set_as_current: false,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
EventBuilder::new(self.clone()) EventBuilder::new(self.clone())
.event(FolderEvent::CreateView) .event(FolderEvent::CreateView)
@ -298,6 +301,7 @@ impl ViewTest {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
let view = EventBuilder::new(sdk.clone()) let view = EventBuilder::new(sdk.clone())

View File

@ -252,6 +252,7 @@ pub async fn create_view(
set_as_current: true, set_as_current: true,
index: None, index: None,
section: None, section: None,
view_id: None,
}; };
EventBuilder::new(sdk.clone()) EventBuilder::new(sdk.clone())
.event(CreateView) .event(CreateView)

View File

@ -3,7 +3,7 @@ use std::convert::TryInto;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::Arc; use std::sync::Arc;
use collab_folder::{View, ViewLayout}; use collab_folder::{View, ViewIcon, ViewLayout};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode; 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. // The view in private section will only be shown in the user's private view list.
#[pb(index = 10, one_of)] #[pb(index = 10, one_of)]
pub section: Option<ViewSectionPB>, pub section: Option<ViewSectionPB>,
#[pb(index = 11, one_of)]
pub view_id: Option<String>,
} }
#[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone, Default)] #[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone, Default)]
@ -305,6 +308,10 @@ pub struct CreateViewParams {
pub index: Option<u32>, pub index: Option<u32>,
// The section of the view. // The section of the view.
pub section: Option<ViewSectionPB>, pub section: Option<ViewSectionPB>,
// The icon of the view.
pub icon: Option<ViewIcon>,
// The extra data of the view.
pub extra: Option<String>,
} }
impl TryInto<CreateViewParams> for CreateViewPayloadPB { impl TryInto<CreateViewParams> for CreateViewPayloadPB {
@ -313,7 +320,8 @@ impl TryInto<CreateViewParams> for CreateViewPayloadPB {
fn try_into(self) -> Result<CreateViewParams, Self::Error> { fn try_into(self) -> Result<CreateViewParams, Self::Error> {
let name = ViewName::parse(self.name)?.0; let name = ViewName::parse(self.name)?.0;
let parent_view_id = ViewIdentify::parse(self.parent_view_id)?.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 { Ok(CreateViewParams {
parent_view_id, parent_view_id,
@ -326,6 +334,8 @@ impl TryInto<CreateViewParams> for CreateViewPayloadPB {
set_as_current: self.set_as_current, set_as_current: self.set_as_current,
index: self.index, index: self.index,
section: self.section, section: self.section,
icon: None,
extra: None,
}) })
} }
} }
@ -348,6 +358,8 @@ impl TryInto<CreateViewParams> for CreateOrphanViewPayloadPB {
set_as_current: false, set_as_current: false,
index: None, index: None,
section: None, section: None,
icon: None,
extra: None,
}) })
} }
} }
@ -562,6 +574,40 @@ pub struct UpdateViewVisibilityStatusPayloadPB {
pub is_public: bool, 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<DuplicateViewParams> for DuplicateViewPayloadPB {
type Error = ErrorCode;
fn try_into(self) -> Result<DuplicateViewParams, Self::Error> {
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 { // impl<'de> Deserialize<'de> for ViewDataType {
// fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> // fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
// where // where

View File

@ -266,12 +266,12 @@ pub(crate) async fn move_nested_view_handler(
#[tracing::instrument(level = "debug", skip(data, folder), err)] #[tracing::instrument(level = "debug", skip(data, folder), err)]
pub(crate) async fn duplicate_view_handler( pub(crate) async fn duplicate_view_handler(
data: AFPluginData<ViewPB>, data: AFPluginData<DuplicateViewPayloadPB>,
folder: AFPluginState<Weak<FolderManager>>, folder: AFPluginState<Weak<FolderManager>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let folder = upgrade_folder(folder)?; let folder = upgrade_folder(folder)?;
let view: ViewPB = data.into_inner(); let params: DuplicateViewParams = data.into_inner().try_into()?;
folder.duplicate_view(&view.id).await?; folder.duplicate_view(params).await?;
Ok(()) Ok(())
} }

View File

@ -85,7 +85,7 @@ pub enum FolderEvent {
DeleteView = 13, DeleteView = 13,
/// Duplicate the view /// Duplicate the view
#[event(input = "ViewPB")] #[event(input = "DuplicateViewPayloadPB")]
DuplicateView = 14, DuplicateView = 14,
/// Close and release the resources that are used by this view. /// Close and release the resources that are used by this view.

View File

@ -1,9 +1,9 @@
use crate::entities::icon::UpdateViewIconParams; use crate::entities::icon::UpdateViewIconParams;
use crate::entities::{ use crate::entities::{
view_pb_with_child_views, view_pb_without_child_views, view_pb_without_child_views_from_arc, view_pb_with_child_views, view_pb_without_child_views, view_pb_without_child_views_from_arc,
CreateViewParams, CreateWorkspaceParams, DeletedViewPB, FolderSnapshotPB, MoveNestedViewParams, CreateViewParams, CreateWorkspaceParams, DeletedViewPB, DuplicateViewParams, FolderSnapshotPB,
RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, UpdateViewParams, ViewPB, ViewSectionPB, MoveNestedViewParams, RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB, UpdateViewParams,
WorkspacePB, WorkspaceSettingPB, ViewPB, ViewSectionPB, WorkspacePB, WorkspaceSettingPB,
}; };
use crate::manager_observer::{ use crate::manager_observer::{
notify_child_views_changed, notify_did_update_workspace, notify_parent_view_did_change, 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. /// 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)] #[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 let view = self
.with_folder(|| None, |folder| folder.views.get_view(view_id)) .with_folder(|| None, |folder| folder.views.get_view(&params.view_id))
.ok_or_else(|| FlowyError::record_not_found().with_context("Can't duplicate the view"))?; .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)?; /// Duplicate the view with the given view id and parent view id.
let view_data = handler.duplicate_view(&view.id).await?; ///
/// 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 filtered_view_ids = self.with_folder(Vec::new, |folder| {
let index = if let Some((_, __, views)) = self.get_view_relation(&view.parent_view_id).await { self.get_view_ids_should_be_filtered(folder)
views.iter().position(|id| id == view_id).map(|i| i as u32) });
} else {
None
};
let is_private = self.with_folder( // only apply the `open_after_duplicated` and the `include_children` to the first view
|| false, let mut is_source_view = true;
|folder| folder.is_view_in_section(Section::Private, &view.id), // use a stack to duplicate the view and its children
); let mut stack = vec![(view_id.to_string(), parent_view_id.to_string())];
let section = if is_private {
ViewSectionPB::Private
} else {
ViewSectionPB::Public
};
let duplicate_params = CreateViewParams { while let Some((current_view_id, current_parent_id)) = stack.pop() {
parent_view_id: view.parent_view_id.clone(), let view = self
name: format!("{} (copy)", &view.name), .with_folder(|| None, |folder| folder.views.get_view(&current_view_id))
desc: view.desc.clone(), .ok_or_else(|| {
layout: view.layout.clone().into(), FlowyError::record_not_found()
initial_data: view_data.to_vec(), .with_context(format!("Can't duplicate the view({})", view_id))
view_id: gen_view_id().to_string(), })?;
meta: Default::default(),
set_as_current: true, let handler = self.get_handler(&view.layout)?;
index, let view_data = handler.duplicate_view(&view.id).await?;
section: Some(section),
}; let index = self
.get_view_relation(&current_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(&current_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(()) Ok(())
} }
@ -1004,6 +1077,8 @@ impl FolderManager {
set_as_current: false, set_as_current: false,
index: None, index: None,
section: None, section: None,
extra: None,
icon: None,
}; };
let view = create_view(self.user.user_id()?, params, import_data.view_layout); let view = create_view(self.user.user_id()?, params, import_data.view_layout);

View File

@ -48,6 +48,8 @@ impl FolderManager {
set_as_current: true, set_as_current: true,
index: None, index: None,
section: Some(ViewSectionPB::Public), section: Some(ViewSectionPB::Public),
icon: None,
extra: None,
}; };
self.create_view_with_params(params).await.unwrap(); self.create_view_with_params(params).await.unwrap();
view_id view_id

View File

@ -127,14 +127,14 @@ pub(crate) fn create_view(uid: i64, params: CreateViewParams, layout: ViewLayout
parent_view_id: params.parent_view_id, parent_view_id: params.parent_view_id,
name: params.name, name: params.name,
desc: params.desc, desc: params.desc,
children: Default::default(),
created_at: time, created_at: time,
is_favorite: false, is_favorite: false,
layout, layout,
icon: None, icon: params.icon,
created_by: Some(uid), created_by: Some(uid),
last_edited_time: 0, last_edited_time: 0,
last_edited_by: Some(uid), last_edited_by: Some(uid),
extra: None, extra: params.extra,
children: Default::default(),
} }
} }

View File

@ -59,8 +59,6 @@ AppDir:
- libwayland-cursor0:amd64 - libwayland-cursor0:amd64
- libwayland-client0:amd64 - libwayland-client0:amd64
- libwayland-egl1:amd64 - libwayland-egl1:amd64
- libmpv-dev:amd64
- mpv:amd64
files: files:
include: [] include: []
exclude: exclude: