fix: collab workspace issues (#4961)

This commit is contained in:
Lucas.Xu 2024-03-22 16:15:18 +07:00 committed by GitHub
parent 99ee60a60d
commit c0642d3ff3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 651 additions and 483 deletions

View File

@ -13,7 +13,7 @@ import 'base.dart';
extension AppFlowyWorkspace on WidgetTester {
/// Open workspace menu
Future<void> openWorkspaceMenu() async {
final workspaceWrapper = find.byType(SidebarWorkspaceWrapper);
final workspaceWrapper = find.byType(SidebarSwitchWorkspaceButton);
expect(workspaceWrapper, findsOneWidget);
await tapButton(workspaceWrapper);
final workspaceMenu = find.byType(WorkspacesMenu);

View File

@ -1,9 +1,9 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
@ -53,8 +53,7 @@ class MobileFolders extends StatelessWidget {
},
builder: (context, state) {
final isCollaborativeWorkspace =
user.authenticator != AuthenticatorPB.Local &&
FeatureFlag.collaborativeWorkspace.isOn;
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
return SlidableAutoCloseBehavior(
child: Column(
children: [

View File

@ -3,7 +3,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
@ -32,8 +31,7 @@ class MobileHomePageHeader extends StatelessWidget {
child: BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
builder: (context, state) {
final isCollaborativeWorkspace =
userProfile.authenticator != AuthenticatorPB.Local &&
FeatureFlag.collaborativeWorkspace.isOn;
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 52),
child: Row(

View File

@ -10,7 +10,7 @@ class DocumentService {
required ViewPB view,
}) async {
final canOpen = await openDocument(viewId: view.id);
if (canOpen.isSuccess()) {
if (canOpen.isSuccess) {
return FlowyResult.success(null);
}
final payload = CreateDocumentPayloadPB()..documentId = view.id;

View File

@ -83,9 +83,9 @@ enum FeatureFlag {
switch (this) {
case FeatureFlag.collaborativeWorkspace:
return false;
return true;
case FeatureFlag.membersSettings:
return false;
return true;
case FeatureFlag.syncDocument:
return false;
case FeatureFlag.unknown:

View File

@ -105,7 +105,7 @@ class SplashScreen extends StatelessWidget {
Future<void> _registerIfNeeded() async {
final result = await UserEventGetUserProfile().send();
if (result.isFailure()) {
if (result.isFailure) {
await getIt<AuthService>().signUpAsGuest();
}
}

View File

@ -1,9 +1,13 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -20,141 +24,133 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
(event, emit) async {
await event.when(
initial: () async {
add(const FetchWorkspaces());
final result = await _fetchWorkspaces();
final isCollabWorkspaceOn =
userProfile.authenticator != AuthenticatorPB.Local &&
FeatureFlag.collaborativeWorkspace.isOn;
emit(
state.copyWith(
currentWorkspace: result?.$1,
workspaces: result?.$2 ?? [],
isCollabWorkspaceOn: isCollabWorkspaceOn,
actionResult: null,
),
);
},
workspacesReceived: (workspaceId) async {},
fetchWorkspaces: () async {
final result = await _fetchWorkspaces();
if (result != null) {
final members = await _userService
.getWorkspaceMembers(
result.$1.workspaceId,
)
.fold((s) => s.items.length, (f) => -1);
emit(
state.copyWith(
isCollaborativeWorkspace: members > 1,
currentWorkspace: result.$1,
workspaces: result.$2,
),
);
} else {
emit(
state.copyWith(
actionResult: UserWorkspaceActionResult(
actionType: UserWorkspaceActionType.none,
result: FlowyResult.failure(
FlowyError(
code: ErrorCode.Internal,
msg: LocaleKeys.workspace_fetchWorkspacesFailed.tr(),
),
),
),
),
);
}
},
createWorkspace: (name, desc) async {
createWorkspace: (name) async {
final result = await _userService.createUserWorkspace(name);
final (workspaces, createWorkspaceResult) = result.fold(
(s) {
final workspaces = [...state.workspaces, s];
return (
workspaces,
FlowyResult<void, FlowyError>.success(null)
);
},
(e) {
Log.error(e);
return (state.workspaces, FlowyResult.failure(e));
},
final workspaces = result.fold(
(s) => [...state.workspaces, s],
(e) => state.workspaces,
);
emit(
state.copyWith(
openWorkspaceResult: null,
deleteWorkspaceResult: null,
updateWorkspaceIconResult: null,
createWorkspaceResult: createWorkspaceResult,
workspaces: workspaces,
actionResult: UserWorkspaceActionResult(
actionType: UserWorkspaceActionType.create,
result: result,
),
),
);
// open the created workspace by default
result.onSuccess((s) {
add(OpenWorkspace(s.workspaceId));
});
},
deleteWorkspace: (workspaceId) async {
if (state.workspaces.length <= 1) {
// do not allow to delete the last workspace
return emit(
state.copyWith(
openWorkspaceResult: null,
createWorkspaceResult: null,
updateWorkspaceIconResult: null,
renameWorkspaceResult: null,
deleteWorkspaceResult: FlowyResult.failure(
// do not allow to delete the last workspace, otherwise the user
// cannot do create workspace again
final result = FlowyResult.failure(
FlowyError(
code: ErrorCode.Internal,
msg: 'Cannot delete the last workspace',
msg: LocaleKeys.workspace_cannotDeleteTheOnlyWorkspace.tr(),
),
);
return emit(
state.copyWith(
actionResult: UserWorkspaceActionResult(
actionType: UserWorkspaceActionType.delete,
result: result,
),
),
);
}
final result = await _userService.deleteWorkspaceById(workspaceId);
final (workspaces, deleteWorkspaceResult) = result.fold(
(s) {
// if the current workspace is deleted, open the first workspace
if (state.currentWorkspace?.workspaceId == workspaceId) {
add(OpenWorkspace(state.workspaces.first.workspaceId));
}
final workspaces = result.fold(
// remove the deleted workspace from the list instead of fetching
// the workspaces again
final workspaces = [...state.workspaces]..removeWhere(
(e) => e.workspaceId == workspaceId,
(s) => state.workspaces
.where((e) => e.workspaceId != workspaceId)
.toList(),
(e) => state.workspaces,
);
return (
workspaces,
FlowyResult<void, FlowyError>.success(null)
);
},
(e) {
Log.error(e);
return (state.workspaces, FlowyResult.failure(e));
},
);
result.onSuccess((_) {
// if the current workspace is deleted, open the first workspace
if (state.currentWorkspace?.workspaceId == workspaceId) {
add(OpenWorkspace(workspaces.first.workspaceId));
}
});
emit(
state.copyWith(
openWorkspaceResult: null,
createWorkspaceResult: null,
updateWorkspaceIconResult: null,
renameWorkspaceResult: null,
deleteWorkspaceResult: deleteWorkspaceResult,
workspaces: workspaces,
actionResult: UserWorkspaceActionResult(
actionType: UserWorkspaceActionType.delete,
result: result,
),
),
);
},
openWorkspace: (workspaceId) async {
final (currentWorkspace, openWorkspaceResult) =
await _userService.openWorkspace(workspaceId).fold(
(s) {
final openedWorkspace = state.workspaces.firstWhere(
final result = await _userService.openWorkspace(workspaceId);
final currentWorkspace = result.fold(
(s) => state.workspaces.firstWhereOrNull(
(e) => e.workspaceId == workspaceId,
),
(e) => state.currentWorkspace,
);
return (
openedWorkspace,
FlowyResult<void, FlowyError>.success(null)
);
},
(f) {
Log.error(f);
return (state.currentWorkspace, FlowyResult.failure(f));
},
);
emit(
state.copyWith(
createWorkspaceResult: null,
deleteWorkspaceResult: null,
updateWorkspaceIconResult: null,
openWorkspaceResult: openWorkspaceResult,
currentWorkspace: currentWorkspace,
actionResult: UserWorkspaceActionResult(
actionType: UserWorkspaceActionType.open,
result: result,
),
),
);
},
renameWorkspace: (workspaceId, name) async {
final result = await _userService.renameWorkspace(
workspaceId,
name,
);
final (workspaces, currentWorkspace, renameWorkspaceResult) =
result.fold(
(s) {
final workspaces = state.workspaces.map((e) {
final result =
await _userService.renameWorkspace(workspaceId, name);
final workspaces = result.fold(
(s) => state.workspaces.map(
(e) {
if (e.workspaceId == workspaceId) {
e.freeze();
return e.rebuild((p0) {
@ -162,36 +158,21 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
});
}
return e;
}).toList();
},
).toList(),
(f) => state.workspaces,
);
final currentWorkspace = workspaces.firstWhere(
(e) => e.workspaceId == state.currentWorkspace?.workspaceId,
);
return (
workspaces,
currentWorkspace,
FlowyResult<void, FlowyError>.success(null),
);
},
(e) {
Log.error(e);
return (
state.workspaces,
state.currentWorkspace,
FlowyResult.failure(e),
);
},
);
emit(
state.copyWith(
createWorkspaceResult: null,
deleteWorkspaceResult: null,
openWorkspaceResult: null,
updateWorkspaceIconResult: null,
workspaces: workspaces,
currentWorkspace: currentWorkspace,
renameWorkspaceResult: renameWorkspaceResult,
actionResult: UserWorkspaceActionResult(
actionType: UserWorkspaceActionType.rename,
result: result,
),
),
);
},
@ -200,11 +181,9 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
workspaceId,
icon,
);
final (workspaces, currentWorkspace, updateWorkspaceIconResult) =
result.fold(
(s) {
final workspaces = state.workspaces.map((e) {
final workspaces = result.fold(
(s) => state.workspaces.map(
(e) {
if (e.workspaceId == workspaceId) {
e.freeze();
return e.rebuild((p0) {
@ -212,37 +191,21 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
});
}
return e;
}).toList();
},
).toList(),
(f) => state.workspaces,
);
final currentWorkspace = workspaces.firstWhere(
(e) => e.workspaceId == state.currentWorkspace?.workspaceId,
);
return (
workspaces,
currentWorkspace,
FlowyResult<void, FlowyError>.success(null),
);
},
(e) {
Log.error(e);
return (
state.workspaces,
state.currentWorkspace,
FlowyResult.failure(e),
);
},
);
emit(
state.copyWith(
createWorkspaceResult: null,
deleteWorkspaceResult: null,
openWorkspaceResult: null,
renameWorkspaceResult: null,
updateWorkspaceIconResult: updateWorkspaceIconResult,
workspaces: workspaces,
currentWorkspace: currentWorkspace,
actionResult: UserWorkspaceActionResult(
actionType: UserWorkspaceActionType.updateIcon,
result: result,
),
),
);
},
@ -273,9 +236,9 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
@freezed
class UserWorkspaceEvent with _$UserWorkspaceEvent {
const factory UserWorkspaceEvent.initial() = Initial;
const factory UserWorkspaceEvent.createWorkspace(String name, String desc) =
CreateWorkspace;
const factory UserWorkspaceEvent.fetchWorkspaces() = FetchWorkspaces;
const factory UserWorkspaceEvent.createWorkspace(String name) =
CreateWorkspace;
const factory UserWorkspaceEvent.deleteWorkspace(String workspaceId) =
DeleteWorkspace;
const factory UserWorkspaceEvent.openWorkspace(String workspaceId) =
@ -288,24 +251,51 @@ class UserWorkspaceEvent with _$UserWorkspaceEvent {
String workspaceId,
String icon,
) = _UpdateWorkspaceIcon;
const factory UserWorkspaceEvent.workspacesReceived(
FlowyResult<List<UserWorkspacePB>, FlowyError> workspacesOrFail,
) = WorkspacesReceived;
}
enum UserWorkspaceActionType {
none,
create,
delete,
open,
rename,
updateIcon,
fetchWorkspaces;
}
class UserWorkspaceActionResult {
const UserWorkspaceActionResult({
required this.actionType,
required this.result,
});
final UserWorkspaceActionType actionType;
final FlowyResult<void, FlowyError> result;
}
@freezed
class UserWorkspaceState with _$UserWorkspaceState {
const UserWorkspaceState._();
const factory UserWorkspaceState({
required UserWorkspacePB? currentWorkspace,
required List<UserWorkspacePB> workspaces,
@Default(false) bool isCollaborativeWorkspace,
@Default(null) FlowyResult<void, FlowyError>? createWorkspaceResult,
@Default(null) FlowyResult<void, FlowyError>? deleteWorkspaceResult,
@Default(null) FlowyResult<void, FlowyError>? openWorkspaceResult,
@Default(null) FlowyResult<void, FlowyError>? renameWorkspaceResult,
@Default(null) FlowyResult<void, FlowyError>? updateWorkspaceIconResult,
@Default(null) UserWorkspacePB? currentWorkspace,
@Default([]) List<UserWorkspacePB> workspaces,
@Default(null) UserWorkspaceActionResult? actionResult,
@Default(false) bool isCollabWorkspaceOn,
}) = _UserWorkspaceState;
factory UserWorkspaceState.initial() =>
const UserWorkspaceState(currentWorkspace: null, workspaces: []);
factory UserWorkspaceState.initial() => const UserWorkspaceState();
@override
int get hashCode => runtimeType.hashCode;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is UserWorkspaceState &&
other.currentWorkspace == currentWorkspace &&
other.workspaces == workspaces &&
identical(other.actionResult, actionResult);
}
}

View File

@ -96,7 +96,7 @@ extension ViewExtension on ViewPB {
}
FlowyResult<ViewPB, FlowyError> parent =
await ViewBackendService.getView(parentViewId);
while (parent.isSuccess()) {
while (parent.isSuccess) {
// parent is not null
final view = parent.fold((s) => s, (e) => null);
if (view == null || (!includeRoot && view.parentViewId.isEmpty)) {

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
@ -15,7 +14,6 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_trash.
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
show UserProfilePB;
import 'package:appflowy_editor/appflowy_editor.dart';
@ -207,8 +205,7 @@ class _SidebarState extends State<_Sidebar> {
// user or workspace, setting
Padding(
padding: menuHorizontalInset,
child: widget.userProfile.authenticator != AuthenticatorPB.Local &&
FeatureFlag.collaborativeWorkspace.isOn
child: context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn
? SidebarWorkspace(
userProfile: widget.userProfile,
)

View File

@ -1,9 +1,9 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_favorite_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_section_folder.dart';
@ -50,8 +50,7 @@ class SidebarFolder extends StatelessWidget {
builder: (context, state) {
// only show public and private section if the workspace is collaborative and not local
final isCollaborativeWorkspace =
userProfile.authenticator != AuthenticatorPB.Local &&
FeatureFlag.collaborativeWorkspace.isOn;
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
return Column(
children:

View File

@ -1,6 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
@ -26,10 +27,15 @@ class SidebarNewPageButton extends StatelessWidget {
LocaleKeys.newPageText.tr(),
(viewName, _) {
if (viewName.isNotEmpty) {
// if the workspace is collaborative, create the view in the private section by default.
final section =
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn
? ViewSectionPB.Private
: ViewSectionPB.Public;
context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.createRootViewInSection(
name: viewName,
viewSection: ViewSectionPB.Public,
viewSection: section,
),
);
}

View File

@ -6,13 +6,14 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sid
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SidebarWorkspace extends StatelessWidget {
@ -29,14 +30,13 @@ class SidebarWorkspace extends StatelessWidget {
listener: _showResultDialog,
builder: (context, state) {
final currentWorkspace = state.currentWorkspace;
// todo: show something if there is no workspace
if (currentWorkspace == null) {
return const SizedBox.shrink();
}
return Row(
children: [
Expanded(
child: SidebarWorkspaceWrapper(
child: SidebarSwitchWorkspaceButton(
userProfile: userProfile,
currentWorkspace: currentWorkspace,
),
@ -51,60 +51,79 @@ class SidebarWorkspace extends StatelessWidget {
}
void _showResultDialog(BuildContext context, UserWorkspaceState state) {
var result = state.createWorkspaceResult;
final actionResult = state.actionResult;
if (actionResult == null) {
return;
}
if (result != null) {
final message = result.fold(
final actionType = actionResult.actionType;
final result = actionResult.result;
result.onFailure((f) {
Log.error(
'[Workspace] Failed to perform ${actionType.toString()} action: $f',
);
});
// show a confirmation dialog if the action is create and the result is LimitExceeded failure
if (actionType == UserWorkspaceActionType.create &&
result.isFailure &&
result.getFailure().code == ErrorCode.WorkspaceLimitExceeded) {
showDialog(
context: context,
builder: (context) => NavigatorOkCancelDialog(
message: LocaleKeys.workspace_createLimitExceeded.tr(),
),
);
return;
}
final String? message;
switch (actionType) {
case UserWorkspaceActionType.create:
message = result.fold(
(s) => LocaleKeys.workspace_createSuccess.tr(),
(e) => '${LocaleKeys.workspace_createFailed.tr()}: ${e.msg}',
);
return showSnackBarMessage(context, message);
}
result = state.deleteWorkspaceResult;
if (result != null) {
final message = result.fold(
break;
case UserWorkspaceActionType.delete:
message = result.fold(
(s) => LocaleKeys.workspace_deleteSuccess.tr(),
(e) => '${LocaleKeys.workspace_deleteFailed.tr()}: ${e.msg}',
);
showSnackBarMessage(context, message);
return;
}
result = state.openWorkspaceResult;
if (result != null) {
final message = result.fold(
break;
case UserWorkspaceActionType.open:
message = result.fold(
(s) => LocaleKeys.workspace_openSuccess.tr(),
(e) => '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}',
);
showSnackBarMessage(context, message);
return;
}
result = state.updateWorkspaceIconResult;
if (result != null) {
final message = result.fold(
break;
case UserWorkspaceActionType.updateIcon:
message = result.fold(
(s) => LocaleKeys.workspace_updateIconSuccess.tr(),
(e) => '${LocaleKeys.workspace_updateIconFailed.tr()}: ${e.msg}',
);
showSnackBarMessage(context, message);
return;
}
result = state.renameWorkspaceResult;
if (result != null) {
final message = result.fold(
break;
case UserWorkspaceActionType.rename:
message = result.fold(
(s) => LocaleKeys.workspace_renameSuccess.tr(),
(e) => '${LocaleKeys.workspace_renameFailed.tr()}: ${e.msg}',
);
break;
case UserWorkspaceActionType.none:
case UserWorkspaceActionType.fetchWorkspaces:
message = null;
break;
}
if (message != null) {
showSnackBarMessage(context, message);
return;
}
}
}
class SidebarWorkspaceWrapper extends StatefulWidget {
const SidebarWorkspaceWrapper({
class SidebarSwitchWorkspaceButton extends StatefulWidget {
const SidebarSwitchWorkspaceButton({
super.key,
required this.userProfile,
required this.currentWorkspace,
@ -114,40 +133,12 @@ class SidebarWorkspaceWrapper extends StatefulWidget {
final UserProfilePB userProfile;
@override
State<SidebarWorkspaceWrapper> createState() =>
_SidebarWorkspaceWrapperState();
State<SidebarSwitchWorkspaceButton> createState() =>
_SidebarSwitchWorkspaceButtonState();
}
class _SidebarWorkspaceWrapperState extends State<SidebarWorkspaceWrapper> {
@override
Widget build(BuildContext context) {
if (PlatformExtension.isDesktopOrWeb) {
return _DesktopWorkspaceWrapper(
userProfile: widget.userProfile,
currentWorkspace: widget.currentWorkspace,
);
} else {
// TODO(Lucas) mobile workspace menu
return const Placeholder();
}
}
}
class _DesktopWorkspaceWrapper extends StatefulWidget {
const _DesktopWorkspaceWrapper({
required this.userProfile,
required this.currentWorkspace,
});
final UserWorkspacePB currentWorkspace;
final UserProfilePB userProfile;
@override
State<_DesktopWorkspaceWrapper> createState() =>
_DesktopWorkspaceWrapperState();
}
class _DesktopWorkspaceWrapperState extends State<_DesktopWorkspaceWrapper> {
class _SidebarSwitchWorkspaceButtonState
extends State<SidebarSwitchWorkspaceButton> {
final controller = PopoverController();
@override

View File

@ -89,7 +89,7 @@ class WorkspacesMenu extends StatelessWidget {
final workspaceBloc = context.read<UserWorkspaceBloc>();
await CreateWorkspaceDialog(
onConfirm: (name) {
workspaceBloc.add(UserWorkspaceEvent.createWorkspace(name, ''));
workspaceBloc.add(UserWorkspaceEvent.createWorkspace(name));
},
).show(context);
}
@ -120,7 +120,9 @@ class WorkspaceMenuItem extends StatelessWidget {
// settings right icon inside the flowy button will
// cause the popover dismiss intermediately when click the right icon.
// so using the stack to put the right icon on the flowy button.
return Stack(
return SizedBox(
height: 52,
child: Stack(
alignment: Alignment.center,
children: [
FlowyButton(
@ -131,9 +133,11 @@ class WorkspaceMenuItem extends StatelessWidget {
workspace.workspaceId,
),
);
PopoverContainer.of(context).closeAll();
}
},
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
margin:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
iconPadding: 10.0,
leftIconSize: const Size.square(32),
leftIcon: const SizedBox.square(
@ -142,15 +146,21 @@ class WorkspaceMenuItem extends StatelessWidget {
rightIcon: const HSpace(42.0),
text: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.center,
children: [
FlowyText.medium(
workspace.name,
fontSize: 14.0,
overflow: TextOverflow.ellipsis,
),
if (members.length > 1)
FlowyText(
'${members.length} ${LocaleKeys.settings_appearance_members_members.tr()}',
state.isLoading
? ''
: LocaleKeys
.settings_appearance_members_membersCount
.plural(
members.length,
),
fontSize: 10.0,
color: Theme.of(context).hintColor,
),
@ -170,9 +180,12 @@ class WorkspaceMenuItem extends StatelessWidget {
),
Positioned(
right: 12.0,
child: Align(child: _buildRightIcon(context)),
child: Align(
child: _buildRightIcon(context),
),
),
],
),
);
},
),
@ -182,7 +195,7 @@ class WorkspaceMenuItem extends StatelessWidget {
Widget _buildRightIcon(BuildContext context) {
// only the owner can update or delete workspace.
// only show the more action button when the workspace is selected.
if (!isSelected) {
if (!isSelected || context.read<WorkspaceMemberBloc>().state.isLoading) {
return const SizedBox.shrink();
}

View File

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class FlowyMessageToast extends StatelessWidget {
@ -70,6 +69,7 @@ void showSnackBarMessage(
content: FlowyText(
message,
color: Colors.white,
maxLines: 2,
),
),
);

View File

@ -50,6 +50,7 @@ class SettingsDialog extends StatelessWidget {
color: Theme.of(context).colorScheme.tertiary,
),
),
width: MediaQuery.of(context).size.width * 0.7,
child: ScaffoldMessenger(
child: Scaffold(
backgroundColor: Colors.transparent,

View File

@ -1,11 +1,13 @@
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:protobuf/protobuf.dart';
part 'workspace_member_bloc.freezed.dart';
@ -28,43 +30,113 @@ class WorkspaceMemberBloc
on<WorkspaceMemberEvent>((event, emit) async {
await event.when(
initial: () async {
if (workspace != null) {
workspaceId = workspace!.workspaceId;
} else {
final currentWorkspace =
await FolderEventReadCurrentWorkspace().send();
currentWorkspace.fold((s) {
workspaceId = s.id;
}, (e) {
assert(false, 'Failed to read current workspace: $e');
Log.error('Failed to read current workspace: $e');
workspaceId = '';
});
}
await _setCurrentWorkspaceId();
add(const WorkspaceMemberEvent.getWorkspaceMembers());
},
getWorkspaceMembers: () async {
final members = await _getWorkspaceMembers();
final result = await _userBackendService.getWorkspaceMembers(
_workspaceId,
);
final members = result.fold<List<WorkspaceMemberPB>>(
(s) => s.items,
(e) => [],
);
final myRole = _getMyRole(members);
emit(
state.copyWith(
members: members,
myRole: myRole,
isLoading: false,
actionResult: WorkspaceMemberActionResult(
actionType: WorkspaceMemberActionType.get,
result: result,
),
),
);
},
getWorkspaceMembers: () async {
final result = await _userBackendService.getWorkspaceMembers(
_workspaceId,
);
final members = result.fold<List<WorkspaceMemberPB>>(
(s) => s.items,
(e) => [],
);
final myRole = _getMyRole(members);
emit(
state.copyWith(
members: members,
myRole: myRole,
actionResult: WorkspaceMemberActionResult(
actionType: WorkspaceMemberActionType.get,
result: result,
),
),
);
},
addWorkspaceMember: (email) async {
await _addWorkspaceMember(email);
final result = await _userBackendService.addWorkspaceMember(
_workspaceId,
email,
);
emit(
state.copyWith(
actionResult: WorkspaceMemberActionResult(
actionType: WorkspaceMemberActionType.add,
result: result,
),
),
);
// the addWorkspaceMember doesn't return the updated members,
// so we need to get the members again
result.onSuccess((s) {
add(const WorkspaceMemberEvent.getWorkspaceMembers());
});
},
removeWorkspaceMember: (email) async {
await _removeWorkspaceMember(email);
add(const WorkspaceMemberEvent.getWorkspaceMembers());
final result = await _userBackendService.removeWorkspaceMember(
_workspaceId,
email,
);
final members = result.fold(
(s) => state.members.where((e) => e.email != email).toList(),
(e) => state.members,
);
emit(
state.copyWith(
members: members,
actionResult: WorkspaceMemberActionResult(
actionType: WorkspaceMemberActionType.remove,
result: result,
),
),
);
},
updateWorkspaceMember: (email, role) async {
await _updateWorkspaceMember(email, role);
add(const WorkspaceMemberEvent.getWorkspaceMembers());
final result = await _userBackendService.updateWorkspaceMember(
_workspaceId,
email,
role,
);
final members = result.fold(
(s) => state.members.map((e) {
if (e.email == email) {
e.freeze();
return e.rebuild((p0) {
p0.role = role;
});
}
return e;
}).toList(),
(e) => state.members,
);
emit(
state.copyWith(
members: members,
actionResult: WorkspaceMemberActionResult(
actionType: WorkspaceMemberActionType.updateRole,
result: result,
),
),
);
},
);
});
@ -75,18 +147,8 @@ class WorkspaceMemberBloc
// if the workspace is null, use the current workspace
final UserWorkspacePB? workspace;
late final String workspaceId;
late final UserBackendService _userBackendService;
Future<List<WorkspaceMemberPB>> _getWorkspaceMembers() async {
return _userBackendService.getWorkspaceMembers(workspaceId).fold(
(s) => s.items,
(e) {
Log.error('Failed to read workspace members: $e');
return [];
},
);
}
late final String _workspaceId;
final UserBackendService _userBackendService;
AFRolePB _getMyRole(List<WorkspaceMemberPB> members) {
final role = members
@ -101,27 +163,19 @@ class WorkspaceMemberBloc
return role;
}
Future<void> _addWorkspaceMember(String email) async {
return _userBackendService.addWorkspaceMember(workspaceId, email).fold(
(s) => Log.debug('Added workspace member: $email'),
(e) => Log.error('Failed to add workspace member: $e'),
);
Future<void> _setCurrentWorkspaceId() async {
if (workspace != null) {
_workspaceId = workspace!.workspaceId;
} else {
final currentWorkspace = await FolderEventReadCurrentWorkspace().send();
currentWorkspace.fold((s) {
_workspaceId = s.id;
}, (e) {
assert(false, 'Failed to read current workspace: $e');
Log.error('Failed to read current workspace: $e');
_workspaceId = '';
});
}
Future<void> _removeWorkspaceMember(String email) async {
return _userBackendService.removeWorkspaceMember(workspaceId, email).fold(
(s) => Log.debug('Removed workspace member: $email'),
(e) => Log.error('Failed to remove workspace member: $e'),
);
}
Future<void> _updateWorkspaceMember(String email, AFRolePB role) async {
return _userBackendService
.updateWorkspaceMember(workspaceId, email, role)
.fold(
(s) => Log.debug('Updated workspace member: $email'),
(e) => Log.error('Failed to update workspace member: $e'),
);
}
}
@ -140,12 +194,47 @@ class WorkspaceMemberEvent with _$WorkspaceMemberEvent {
) = UpdateWorkspaceMember;
}
enum WorkspaceMemberActionType {
none,
get,
add,
remove,
updateRole,
}
class WorkspaceMemberActionResult {
const WorkspaceMemberActionResult({
required this.actionType,
required this.result,
});
final WorkspaceMemberActionType actionType;
final FlowyResult<void, FlowyError> result;
}
@freezed
class WorkspaceMemberState with _$WorkspaceMemberState {
const WorkspaceMemberState._();
const factory WorkspaceMemberState({
@Default([]) List<WorkspaceMemberPB> members,
@Default(AFRolePB.Guest) AFRolePB myRole,
@Default(null) WorkspaceMemberActionResult? actionResult,
@Default(true) bool isLoading,
}) = _WorkspaceMemberState;
factory WorkspaceMemberState.initial() => const WorkspaceMemberState();
@override
int get hashCode => runtimeType.hashCode;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is WorkspaceMemberState &&
other.members == members &&
other.myRole == myRole &&
identical(other.actionResult, actionResult);
}
}

View File

@ -3,12 +3,14 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/af_role_pb_extension.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart';
@ -28,7 +30,8 @@ class WorkspaceMembersPage extends StatelessWidget {
return BlocProvider<WorkspaceMemberBloc>(
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
..add(const WorkspaceMemberEvent.initial()),
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
child: BlocConsumer<WorkspaceMemberBloc, WorkspaceMemberState>(
listener: _showResultDialog,
builder: (context, state) {
return SingleChildScrollView(
child: Column(
@ -46,6 +49,7 @@ class WorkspaceMembersPage extends StatelessWidget {
userProfile: userProfile,
myRole: state.myRole,
),
const VSpace(48.0),
],
),
);
@ -53,6 +57,43 @@ class WorkspaceMembersPage extends StatelessWidget {
),
);
}
void _showResultDialog(BuildContext context, WorkspaceMemberState state) {
final actionResult = state.actionResult;
if (actionResult == null) {
return;
}
final actionType = actionResult.actionType;
final result = actionResult.result;
// only show the result dialog when the action is WorkspaceMemberActionType.add
if (actionType == WorkspaceMemberActionType.add) {
result.fold(
(s) {
showSnackBarMessage(
context,
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
);
},
(f) {
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr()
: LocaleKeys.settings_appearance_members_failedToAddMember.tr();
showDialog(
context: context,
builder: (context) => NavigatorOkCancelDialog(message: message),
);
},
);
}
result.onFailure((f) {
Log.error(
'[Member] Failed to perform ${actionType.toString()} action: $f',
);
});
}
}
class _InviteMember extends StatefulWidget {
@ -111,6 +152,7 @@ class _InviteMemberState extends State<_InviteMember> {
],
),
const VSpace(16.0),
/* Enable this when the feature is ready
PrimaryButton(
backgroundColor: const Color(0xFFE0E0E0),
child: Padding(
@ -140,6 +182,7 @@ class _InviteMemberState extends State<_InviteMember> {
},
),
const VSpace(16.0),
*/
const Divider(
height: 1.0,
thickness: 1.0,
@ -160,10 +203,6 @@ class _InviteMemberState extends State<_InviteMember> {
context
.read<WorkspaceMemberBloc>()
.add(WorkspaceMemberEvent.addWorkspaceMember(email));
showSnackBarMessage(
context,
LocaleKeys.settings_appearance_members_emailSent.tr(),
);
}
}
@ -310,13 +349,23 @@ class _MemberMoreActionList extends StatelessWidget {
},
);
},
onSelected: (action, controller) async {
onSelected: (action, controller) {
switch (action.inner) {
case _MemberMoreAction.delete:
context.read<WorkspaceMemberBloc>().add(
showDialog(
context: context,
builder: (_) => NavigatorOkCancelDialog(
title: LocaleKeys.settings_appearance_members_removeMember.tr(),
message: LocaleKeys
.settings_appearance_members_areYouSureToRemoveMember
.tr(),
onOkPressed: () => context.read<WorkspaceMemberBloc>().add(
WorkspaceMemberEvent.removeWorkspaceMember(
action.member.email,
),
),
okTitle: LocaleKeys.button_yes.tr(),
),
);
break;
}
@ -353,7 +402,7 @@ class _MemberRoleActionList extends StatelessWidget {
return PopoverActionList<_MemberRoleActionWrapper>(
asBarrier: true,
direction: PopoverDirection.bottomWithLeftAligned,
actions: [AFRolePB.Member, AFRolePB.Guest]
actions: [AFRolePB.Member]
.map((e) => _MemberRoleActionWrapper(e, member))
.toList(),
offset: const Offset(0, 10),

View File

@ -186,7 +186,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
this.okTitle,
this.cancelTitle,
this.title,
required this.message,
this.message,
this.maxWidth,
});
@ -195,13 +195,14 @@ class NavigatorOkCancelDialog extends StatelessWidget {
final String? okTitle;
final String? cancelTitle;
final String? title;
final String message;
final String? message;
final double? maxWidth;
@override
Widget build(BuildContext context) {
return StyledDialog(
maxWidth: maxWidth ?? 500,
padding: EdgeInsets.symmetric(horizontal: Insets.xl, vertical: Insets.l),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@ -209,6 +210,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
FlowyText.medium(
title!.toUpperCase(),
fontSize: FontSizes.s16,
maxLines: 3,
),
VSpace(Insets.sm * 1.5),
Container(
@ -217,7 +219,11 @@ class NavigatorOkCancelDialog extends StatelessWidget {
),
VSpace(Insets.m * 1.5),
],
FlowyText.medium(message),
if (message != null)
FlowyText.medium(
message!,
maxLines: 3,
),
SizedBox(height: Insets.l),
OkCancelButton(
onOkPressed: () {

View File

@ -24,11 +24,11 @@ extension FlowyAsyncResultExtension<S, F extends Object>
}
Future<bool> isError() {
return then((result) => result.isFailure());
return then((result) => result.isFailure);
}
Future<bool> isSuccess() {
return then((result) => result.isSuccess());
return then((result) => result.isSuccess);
}
FlowyAsyncResult<S, F> onFailure(void Function(F failure) onFailure) {

View File

@ -10,8 +10,8 @@ abstract class FlowyResult<S, F extends Object> {
FlowyResult<T, F> map<T>(T Function(S success) fn);
FlowyResult<S, T> mapError<T extends Object>(T Function(F failure) fn);
bool isSuccess();
bool isFailure();
bool get isSuccess;
bool get isFailure;
S? toNullable();
@ -20,6 +20,8 @@ abstract class FlowyResult<S, F extends Object> {
S getOrElse(S Function(F failure) onFailure);
S getOrThrow();
F getFailure();
}
class FlowySuccess<S, F extends Object> implements FlowyResult<S, F> {
@ -57,14 +59,10 @@ class FlowySuccess<S, F extends Object> implements FlowyResult<S, F> {
}
@override
bool isSuccess() {
return true;
}
bool get isSuccess => true;
@override
bool isFailure() {
return false;
}
bool get isFailure => false;
@override
S? toNullable() {
@ -88,6 +86,11 @@ class FlowySuccess<S, F extends Object> implements FlowyResult<S, F> {
S getOrThrow() {
return _value;
}
@override
F getFailure() {
throw UnimplementedError();
}
}
class FlowyFailure<S, F extends Object> implements FlowyResult<S, F> {
@ -125,14 +128,10 @@ class FlowyFailure<S, F extends Object> implements FlowyResult<S, F> {
}
@override
bool isSuccess() {
return false;
}
bool get isSuccess => false;
@override
bool isFailure() {
return true;
}
bool get isFailure => true;
@override
S? toNullable() {
@ -156,4 +155,9 @@ class FlowyFailure<S, F extends Object> implements FlowyResult<S, F> {
S getOrThrow() {
throw _value;
}
@override
F getFailure() {
return _value;
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:flutter/material.dart';
const _overlayContainerPadding = EdgeInsets.symmetric(vertical: 12);
const overlayContainerMaxWidth = 760.0;
const overlayContainerMinWidth = 320.0;
@ -14,6 +15,7 @@ class FlowyDialog extends StatelessWidget {
this.constraints,
this.padding = _overlayContainerPadding,
this.backgroundColor,
this.width,
});
final Widget? title;
@ -22,11 +24,12 @@ class FlowyDialog extends StatelessWidget {
final BoxConstraints? constraints;
final EdgeInsets padding;
final Color? backgroundColor;
final double? width;
@override
Widget build(BuildContext context) {
final windowSize = MediaQuery.of(context).size;
final size = windowSize * 0.7;
final size = windowSize * 0.6;
return SimpleDialog(
contentPadding: EdgeInsets.zero,
backgroundColor: backgroundColor ?? Theme.of(context).cardColor,
@ -38,8 +41,11 @@ class FlowyDialog extends StatelessWidget {
type: MaterialType.transparency,
child: Container(
height: size.height,
width: max(min(size.width, overlayContainerMaxWidth),
overlayContainerMinWidth),
width: width ??
max(
min(size.width, overlayContainerMaxWidth),
overlayContainerMinWidth,
),
constraints: constraints,
child: child,
),

View File

@ -838,7 +838,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"async-trait",
@ -862,7 +862,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"async-trait",
@ -892,7 +892,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"collab",
@ -911,7 +911,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"bytes",
@ -926,7 +926,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"chrono",
@ -963,7 +963,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"async-stream",
@ -1002,7 +1002,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"collab",

View File

@ -96,10 +96,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }

View File

@ -65,10 +65,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }

View File

@ -68,6 +68,7 @@
"deleteWorkspaceHintText": "Are you sure you want to delete the workspace? This action cannot be undone.",
"createSuccess": "Workspace created successfully",
"createFailed": "Failed to create workspace",
"createLimitExceeded": "You've reached the maximum workspace limit allowed for your account. If you need additional workspaces to continue your work, please request on Github",
"deleteSuccess": "Workspace deleted successfully",
"deleteFailed": "Failed to delete workspace",
"openSuccess": "Open workspace successfully",
@ -75,7 +76,9 @@
"renameSuccess": "Workspace renamed successfully",
"renameFailed": "Failed to rename workspace",
"updateIconSuccess": "Updated workspace icon successfully",
"updateIconFailed": "Updated workspace icon failed"
"updateIconFailed": "Updated workspace icon failed",
"cannotDeleteTheOnlyWorkspace": "Cannot delete the only workspace",
"fetchWorkspacesFailed": "Failed to fetch workspaces"
},
"shareAction": {
"buttonText": "Share",
@ -436,7 +439,17 @@
"guestHintText": "A Guest can read, react, comment, and can edit certain pages with permission.",
"emailInvalidError": "Invalid email, please check and try again",
"emailSent": "Email sent, please check the inbox",
"members": "members"
"members": "members",
"membersCount": {
"zero": "{} members",
"one": "{} member",
"other": "{} members"
},
"memberLimitExceeded": "You've reached the maximum member limit allowed for your account. If you want to add more additional members to continue your work, please request on Github",
"failedToAddMember": "Failed to add member",
"addMemberSuccess": "Member added successfully",
"removeMember": "Remove Member",
"areYouSureToRemoveMember": "Are you sure you want to remove this member?"
}
},
"files": {
@ -1420,7 +1433,7 @@
},
"syncState": {
"syncing": "Syncing",
"synced": "Everything is up to date",
"synced": "Synced",
"noNetworkConnected": "No network connected"
}
}

View File

@ -764,7 +764,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"async-trait",
@ -788,7 +788,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"async-trait",
@ -818,7 +818,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"collab",
@ -837,7 +837,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"bytes",
@ -852,7 +852,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"chrono",
@ -889,7 +889,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"async-stream",
@ -928,7 +928,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4b25d7d021a11c51583e6a404c139f49ee6a3bf9#4b25d7d021a11c51583e6a404c139f49ee6a3bf9"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=25c4be5#25c4be5d60fa67f0d2de7f69cc8292a4506e07de"
dependencies = [
"anyhow",
"collab",

View File

@ -120,10 +120,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }

View File

@ -123,7 +123,7 @@ async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let old_views = test
.folder_manager
.get_current_workspace_views()
.get_current_workspace_public_views()
.await
.unwrap();
let old_workspace = test.folder_manager.get_current_workspace().await.unwrap();
@ -132,7 +132,7 @@ async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
let new_views = test
.folder_manager
.get_current_workspace_views()
.get_current_workspace_public_views()
.await
.unwrap();
let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();
@ -163,7 +163,7 @@ async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
let old_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
let old_cloud_views = test
.folder_manager
.get_current_workspace_views()
.get_current_workspace_public_views()
.await
.unwrap();
assert_eq!(old_cloud_views.len(), 1);
@ -189,7 +189,7 @@ async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
let new_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
let new_cloud_views = test
.folder_manager
.get_current_workspace_views()
.get_current_workspace_public_views()
.await
.unwrap();
assert_eq!(new_cloud_workspace, old_cloud_workspace);

View File

@ -261,10 +261,10 @@ pub enum ErrorCode {
CloudRequestPayloadTooLarge = 90,
#[error("Workspace limit exceeded")]
WorkspaceLimitExeceeded = 91,
WorkspaceLimitExceeded = 91,
#[error("Workspace member limit exceeded")]
WorkspaceMemberLimitExeceeded = 92,
WorkspaceMemberLimitExceeded = 92,
}
impl ErrorCode {

View File

@ -22,8 +22,8 @@ impl From<AppResponseError> for FlowyError {
AppErrorCode::NetworkError => ErrorCode::HttpError,
AppErrorCode::PayloadTooLarge => ErrorCode::CloudRequestPayloadTooLarge,
AppErrorCode::UserUnAuthorized => match &*error.message {
"Workspace Limit Exceeded" => ErrorCode::WorkspaceLimitExeceeded,
"Workspace Member Limit Exceeded" => ErrorCode::WorkspaceMemberLimitExeceeded,
"Workspace Limit Exceeded" => ErrorCode::WorkspaceLimitExceeded,
"Workspace Member Limit Exceeded" => ErrorCode::WorkspaceMemberLimitExceeded,
_ => ErrorCode::UserUnauthorized,
},
_ => ErrorCode::Internal,

View File

@ -285,8 +285,7 @@ impl TryInto<CreateViewParams> for CreateOrphanViewPayloadPB {
meta: Default::default(),
set_as_current: false,
index: None,
// TODO: lucas.xu add section to CreateOrphanViewPayloadPB
section: Some(ViewSectionPB::Public),
section: None,
})
}
}

View File

@ -53,7 +53,7 @@ pub(crate) async fn get_workspace_views_handler(
) -> DataResult<RepeatedViewPB, FlowyError> {
let folder = upgrade_folder(folder)?;
let params: GetWorkspaceViewParams = data.into_inner().try_into()?;
let child_views = folder.get_workspace_views(&params.value).await?;
let child_views = folder.get_workspace_public_views(&params.value).await?;
let repeated_view: RepeatedViewPB = child_views.into();
data_result_ok(repeated_view)
}
@ -63,7 +63,7 @@ pub(crate) async fn get_current_workspace_views_handler(
folder: AFPluginState<Weak<FolderManager>>,
) -> DataResult<RepeatedViewPB, FlowyError> {
let folder = upgrade_folder(folder)?;
let child_views = folder.get_current_workspace_views().await?;
let child_views = folder.get_current_workspace_public_views().await?;
let repeated_view: RepeatedViewPB = child_views.into();
data_result_ok(repeated_view)
}
@ -286,7 +286,7 @@ pub(crate) async fn read_trash_handler(
folder: AFPluginState<Weak<FolderManager>>,
) -> DataResult<RepeatedTrashPB, FlowyError> {
let folder = upgrade_folder(folder)?;
let trash = folder.get_all_trash().await;
let trash = folder.get_my_trash_info().await;
data_result_ok(trash.into())
}
@ -323,11 +323,11 @@ pub(crate) async fn restore_all_trash_handler(
}
#[tracing::instrument(level = "debug", skip(folder), err)]
pub(crate) async fn delete_all_trash_handler(
pub(crate) async fn delete_my_trash_handler(
folder: AFPluginState<Weak<FolderManager>>,
) -> Result<(), FlowyError> {
let folder = upgrade_folder(folder)?;
folder.delete_all_trash().await;
folder.delete_my_trash().await;
Ok(())
}

View File

@ -29,7 +29,7 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
.event(FolderEvent::RestoreTrashItem, putback_trash_handler)
.event(FolderEvent::PermanentlyDeleteTrashItem, delete_trash_handler)
.event(FolderEvent::RecoverAllTrashItems, restore_all_trash_handler)
.event(FolderEvent::PermanentlyDeleteAllTrashItem, delete_all_trash_handler)
.event(FolderEvent::PermanentlyDeleteAllTrashItem, delete_my_trash_handler)
.event(FolderEvent::ImportData, import_data_handler)
.event(FolderEvent::GetFolderSnapshots, get_folder_snapshots_handler)
.event(FolderEvent::UpdateViewIcon, update_view_icon_handler)

View File

@ -128,7 +128,7 @@ impl FolderManager {
/// Return a list of views of the current workspace.
/// Only the first level of child views are included.
pub async fn get_current_workspace_views(&self) -> FlowyResult<Vec<ViewPB>> {
pub async fn get_current_workspace_public_views(&self) -> FlowyResult<Vec<ViewPB>> {
let workspace_id = self
.mutex_folder
.lock()
@ -136,14 +136,14 @@ impl FolderManager {
.map(|folder| folder.get_workspace_id());
if let Some(workspace_id) = workspace_id {
self.get_workspace_views(&workspace_id).await
self.get_workspace_public_views(&workspace_id).await
} else {
tracing::warn!("Can't get current workspace views");
Ok(vec![])
}
}
pub async fn get_workspace_views(&self, workspace_id: &str) -> FlowyResult<Vec<ViewPB>> {
pub async fn get_workspace_public_views(&self, workspace_id: &str) -> FlowyResult<Vec<ViewPB>> {
let views = self.with_folder(Vec::new, |folder| {
get_workspace_public_view_pbs(workspace_id, folder)
});
@ -519,7 +519,7 @@ impl FolderManager {
let folder = self.mutex_folder.lock();
let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
let trash_ids = folder
.get_all_trash()
.get_all_trash_sections()
.into_iter()
.map(|trash| trash.id)
.collect::<Vec<String>>();
@ -559,7 +559,7 @@ impl FolderManager {
|folder| {
if let Some(view) = folder.views.get_view(view_id) {
self.unfavorite_view_and_decendants(view.clone(), folder);
folder.add_trash(vec![view_id.to_string()]);
folder.add_trash_view_ids(vec![view_id.to_string()]);
// notify the parent view that the view is moved to trash
send_notification(view_id, FolderNotification::DidMoveViewToTrash)
.payload(DeletedViewPB {
@ -590,7 +590,7 @@ impl FolderManager {
.collect();
if !favorite_descendant_views.is_empty() {
folder.delete_favorites(
folder.delete_favorite_view_ids(
favorite_descendant_views
.iter()
.map(|v| v.id.clone())
@ -754,6 +754,16 @@ impl FolderManager {
None
};
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
};
let duplicate_params = CreateViewParams {
parent_view_id: view.parent_view_id.clone(),
name: format!("{} (copy)", &view.name),
@ -764,8 +774,7 @@ impl FolderManager {
meta: Default::default(),
set_as_current: true,
index,
// TODO: lucas.xu fetch the section from the view
section: Some(ViewSectionPB::Public),
section: Some(section),
};
self.create_view_with_params(duplicate_params).await?;
@ -801,9 +810,9 @@ impl FolderManager {
|folder| {
if let Some(old_view) = folder.views.get_view(view_id) {
if old_view.is_favorite {
folder.delete_favorites(vec![view_id.to_string()]);
folder.delete_favorite_view_ids(vec![view_id.to_string()]);
} else {
folder.add_favorites(vec![view_id.to_string()]);
folder.add_favorite_view_ids(vec![view_id.to_string()]);
}
}
},
@ -878,8 +887,8 @@ impl FolderManager {
}
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) async fn get_all_trash(&self) -> Vec<TrashInfo> {
self.with_folder(Vec::new, |folder| folder.get_all_trash())
pub(crate) async fn get_my_trash_info(&self) -> Vec<TrashInfo> {
self.with_folder(Vec::new, |folder| folder.get_my_trash_info())
}
#[tracing::instrument(level = "trace", skip(self))]
@ -887,7 +896,7 @@ impl FolderManager {
self.with_folder(
|| (),
|folder| {
folder.remote_all_trash();
folder.remove_all_my_trash_sections();
},
);
send_notification("trash", FolderNotification::DidUpdateTrash)
@ -900,15 +909,15 @@ impl FolderManager {
self.with_folder(
|| (),
|folder| {
folder.delete_trash(vec![trash_id.to_string()]);
folder.delete_trash_view_ids(vec![trash_id.to_string()]);
},
);
}
/// Delete all the trash permanently.
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) async fn delete_all_trash(&self) {
let deleted_trash = self.with_folder(Vec::new, |folder| folder.get_all_trash());
pub(crate) async fn delete_my_trash(&self) {
let deleted_trash = self.with_folder(Vec::new, |folder| folder.get_my_trash_info());
for trash in deleted_trash {
let _ = self.delete_trash(&trash.id).await;
}
@ -926,7 +935,7 @@ impl FolderManager {
self.with_folder(
|| (),
|folder| {
folder.delete_trash(vec![view_id.to_string()]);
folder.delete_trash_view_ids(vec![view_id.to_string()]);
folder.views.delete_views(vec![view_id]);
},
);
@ -977,8 +986,7 @@ impl FolderManager {
meta: Default::default(),
set_as_current: false,
index: None,
// TODO: Lucas.xu fetch the section from the view
section: Some(ViewSectionPB::Public),
section: None,
};
let view = create_view(self.user.user_id()?, params, import_data.view_layout);
@ -1117,14 +1125,14 @@ impl FolderManager {
fn get_sections(&self, section_type: Section) -> Vec<SectionItem> {
self.with_folder(Vec::new, |folder| {
let trash_ids = folder
.get_all_trash()
.get_all_trash_sections()
.into_iter()
.map(|trash| trash.id)
.collect::<Vec<String>>();
let mut views = match section_type {
Section::Favorite => folder.get_all_favorites(),
Section::Recent => folder.get_all_recent_sections(),
Section::Favorite => folder.get_my_favorite_sections(),
Section::Recent => folder.get_my_recent_sections(),
_ => vec![],
};
@ -1139,14 +1147,14 @@ impl FolderManager {
pub(crate) fn get_workspace_public_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
// get the trash ids
let trash_ids = folder
.get_all_trash()
.get_all_trash_sections()
.into_iter()
.map(|trash| trash.id)
.collect::<Vec<String>>();
// get the private view ids
let private_view_ids = folder
.get_all_private_views()
.get_all_private_sections()
.into_iter()
.map(|view| view.id)
.collect::<Vec<String>>();
@ -1174,14 +1182,14 @@ pub(crate) fn get_workspace_public_view_pbs(_workspace_id: &str, folder: &Folder
pub(crate) fn get_workspace_private_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
// get the trash ids
let trash_ids = folder
.get_all_trash()
.get_all_trash_sections()
.into_iter()
.map(|trash| trash.id)
.collect::<Vec<String>>();
// get the private view ids
let private_view_ids = folder
.get_my_private_views()
.get_my_private_sections()
.into_iter()
.map(|view| view.id)
.collect::<Vec<String>>();

View File

@ -125,7 +125,7 @@ pub(crate) fn subscribe_folder_trash_changed(
unique_ids.insert(view.parent_view_id.clone());
}
let repeated_trash: RepeatedTrashPB = folder.get_all_trash().into();
let repeated_trash: RepeatedTrashPB = folder.get_my_trash_info().into();
send_notification("trash", FolderNotification::DidUpdateTrash)
.payload(repeated_trash)
.send();
@ -150,7 +150,7 @@ pub(crate) fn notify_parent_view_did_change<T: AsRef<str>>(
let folder = folder.as_ref()?;
let workspace_id = folder.get_workspace_id();
let trash_ids = folder
.get_all_trash()
.get_all_trash_sections()
.into_iter()
.map(|trash| trash.id)
.collect::<Vec<String>>();

View File

@ -42,7 +42,7 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
.collect::<Vec<String>>();
if !favorite_view_ids.is_empty() {
folder.add_favorites(favorite_view_ids);
folder.add_favorite_view_ids(favorite_view_ids);
}
let encode = folder.encode_collab_v1();

View File

@ -38,7 +38,7 @@ impl UserDataMigration for WorkspaceTrashMapToSectionMigration {
.collect::<Vec<String>>();
if !trash_ids.is_empty() {
folder.add_trash(trash_ids);
folder.add_trash_view_ids(trash_ids);
}
let encode = folder.encode_collab_v1();