mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: collab workspace issues (#4961)
This commit is contained in:
parent
99ee60a60d
commit
c0642d3ff3
@ -13,7 +13,7 @@ import 'base.dart';
|
|||||||
extension AppFlowyWorkspace on WidgetTester {
|
extension AppFlowyWorkspace on WidgetTester {
|
||||||
/// Open workspace menu
|
/// Open workspace menu
|
||||||
Future<void> openWorkspaceMenu() async {
|
Future<void> openWorkspaceMenu() async {
|
||||||
final workspaceWrapper = find.byType(SidebarWorkspaceWrapper);
|
final workspaceWrapper = find.byType(SidebarSwitchWorkspaceButton);
|
||||||
expect(workspaceWrapper, findsOneWidget);
|
expect(workspaceWrapper, findsOneWidget);
|
||||||
await tapButton(workspaceWrapper);
|
await tapButton(workspaceWrapper);
|
||||||
final workspaceMenu = find.byType(WorkspacesMenu);
|
final workspaceMenu = find.byType(WorkspacesMenu);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/application/mobile_router.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/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/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_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-folder/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -53,8 +53,7 @@ class MobileFolders extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final isCollaborativeWorkspace =
|
final isCollaborativeWorkspace =
|
||||||
user.authenticator != AuthenticatorPB.Local &&
|
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
|
||||||
FeatureFlag.collaborativeWorkspace.isOn;
|
|
||||||
return SlidableAutoCloseBehavior(
|
return SlidableAutoCloseBehavior(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -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/mobile/presentation/home/mobile_home_setting_page.dart';
|
||||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
||||||
import 'package:appflowy/plugins/base/icon/icon_picker.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/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
@ -32,8 +31,7 @@ class MobileHomePageHeader extends StatelessWidget {
|
|||||||
child: BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
|
child: BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final isCollaborativeWorkspace =
|
final isCollaborativeWorkspace =
|
||||||
userProfile.authenticator != AuthenticatorPB.Local &&
|
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
|
||||||
FeatureFlag.collaborativeWorkspace.isOn;
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minHeight: 52),
|
constraints: const BoxConstraints(minHeight: 52),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -10,7 +10,7 @@ class DocumentService {
|
|||||||
required ViewPB view,
|
required ViewPB view,
|
||||||
}) async {
|
}) async {
|
||||||
final canOpen = await openDocument(viewId: view.id);
|
final canOpen = await openDocument(viewId: view.id);
|
||||||
if (canOpen.isSuccess()) {
|
if (canOpen.isSuccess) {
|
||||||
return FlowyResult.success(null);
|
return FlowyResult.success(null);
|
||||||
}
|
}
|
||||||
final payload = CreateDocumentPayloadPB()..documentId = view.id;
|
final payload = CreateDocumentPayloadPB()..documentId = view.id;
|
||||||
|
@ -83,9 +83,9 @@ enum FeatureFlag {
|
|||||||
|
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case FeatureFlag.collaborativeWorkspace:
|
case FeatureFlag.collaborativeWorkspace:
|
||||||
return false;
|
return true;
|
||||||
case FeatureFlag.membersSettings:
|
case FeatureFlag.membersSettings:
|
||||||
return false;
|
return true;
|
||||||
case FeatureFlag.syncDocument:
|
case FeatureFlag.syncDocument:
|
||||||
return false;
|
return false;
|
||||||
case FeatureFlag.unknown:
|
case FeatureFlag.unknown:
|
||||||
|
@ -105,7 +105,7 @@ class SplashScreen extends StatelessWidget {
|
|||||||
|
|
||||||
Future<void> _registerIfNeeded() async {
|
Future<void> _registerIfNeeded() async {
|
||||||
final result = await UserEventGetUserProfile().send();
|
final result = await UserEventGetUserProfile().send();
|
||||||
if (result.isFailure()) {
|
if (result.isFailure) {
|
||||||
await getIt<AuthService>().signUpAsGuest();
|
await getIt<AuthService>().signUpAsGuest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/user/application/user_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.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-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.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/foundation.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';
|
||||||
@ -20,141 +24,133 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
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 {
|
fetchWorkspaces: () async {
|
||||||
final result = await _fetchWorkspaces();
|
final result = await _fetchWorkspaces();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
final members = await _userService
|
|
||||||
.getWorkspaceMembers(
|
|
||||||
result.$1.workspaceId,
|
|
||||||
)
|
|
||||||
.fold((s) => s.items.length, (f) => -1);
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
isCollaborativeWorkspace: members > 1,
|
|
||||||
currentWorkspace: result.$1,
|
currentWorkspace: result.$1,
|
||||||
workspaces: result.$2,
|
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 result = await _userService.createUserWorkspace(name);
|
||||||
final (workspaces, createWorkspaceResult) = result.fold(
|
final workspaces = result.fold(
|
||||||
(s) {
|
(s) => [...state.workspaces, s],
|
||||||
final workspaces = [...state.workspaces, s];
|
(e) => state.workspaces,
|
||||||
return (
|
|
||||||
workspaces,
|
|
||||||
FlowyResult<void, FlowyError>.success(null)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(e) {
|
|
||||||
Log.error(e);
|
|
||||||
return (state.workspaces, FlowyResult.failure(e));
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
openWorkspaceResult: null,
|
|
||||||
deleteWorkspaceResult: null,
|
|
||||||
updateWorkspaceIconResult: null,
|
|
||||||
createWorkspaceResult: createWorkspaceResult,
|
|
||||||
workspaces: workspaces,
|
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 {
|
deleteWorkspace: (workspaceId) async {
|
||||||
if (state.workspaces.length <= 1) {
|
if (state.workspaces.length <= 1) {
|
||||||
// do not allow to delete the last workspace
|
// 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: LocaleKeys.workspace_cannotDeleteTheOnlyWorkspace.tr(),
|
||||||
|
),
|
||||||
|
);
|
||||||
return emit(
|
return emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
openWorkspaceResult: null,
|
actionResult: UserWorkspaceActionResult(
|
||||||
createWorkspaceResult: null,
|
actionType: UserWorkspaceActionType.delete,
|
||||||
updateWorkspaceIconResult: null,
|
result: result,
|
||||||
renameWorkspaceResult: null,
|
|
||||||
deleteWorkspaceResult: FlowyResult.failure(
|
|
||||||
FlowyError(
|
|
||||||
code: ErrorCode.Internal,
|
|
||||||
msg: 'Cannot delete the last workspace',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await _userService.deleteWorkspaceById(workspaceId);
|
final result = await _userService.deleteWorkspaceById(workspaceId);
|
||||||
final (workspaces, deleteWorkspaceResult) = result.fold(
|
final workspaces = result.fold(
|
||||||
(s) {
|
// remove the deleted workspace from the list instead of fetching
|
||||||
// if the current workspace is deleted, open the first workspace
|
// the workspaces again
|
||||||
if (state.currentWorkspace?.workspaceId == workspaceId) {
|
(s) => state.workspaces
|
||||||
add(OpenWorkspace(state.workspaces.first.workspaceId));
|
.where((e) => e.workspaceId != workspaceId)
|
||||||
}
|
.toList(),
|
||||||
// remove the deleted workspace from the list instead of fetching
|
(e) => state.workspaces,
|
||||||
// the workspaces again
|
|
||||||
final workspaces = [...state.workspaces]..removeWhere(
|
|
||||||
(e) => e.workspaceId == workspaceId,
|
|
||||||
);
|
|
||||||
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(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
openWorkspaceResult: null,
|
|
||||||
createWorkspaceResult: null,
|
|
||||||
updateWorkspaceIconResult: null,
|
|
||||||
renameWorkspaceResult: null,
|
|
||||||
deleteWorkspaceResult: deleteWorkspaceResult,
|
|
||||||
workspaces: workspaces,
|
workspaces: workspaces,
|
||||||
|
actionResult: UserWorkspaceActionResult(
|
||||||
|
actionType: UserWorkspaceActionType.delete,
|
||||||
|
result: result,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
openWorkspace: (workspaceId) async {
|
openWorkspace: (workspaceId) async {
|
||||||
final (currentWorkspace, openWorkspaceResult) =
|
final result = await _userService.openWorkspace(workspaceId);
|
||||||
await _userService.openWorkspace(workspaceId).fold(
|
final currentWorkspace = result.fold(
|
||||||
(s) {
|
(s) => state.workspaces.firstWhereOrNull(
|
||||||
final openedWorkspace = state.workspaces.firstWhere(
|
(e) => e.workspaceId == workspaceId,
|
||||||
(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(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
createWorkspaceResult: null,
|
|
||||||
deleteWorkspaceResult: null,
|
|
||||||
updateWorkspaceIconResult: null,
|
|
||||||
openWorkspaceResult: openWorkspaceResult,
|
|
||||||
currentWorkspace: currentWorkspace,
|
currentWorkspace: currentWorkspace,
|
||||||
|
actionResult: UserWorkspaceActionResult(
|
||||||
|
actionType: UserWorkspaceActionType.open,
|
||||||
|
result: result,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
renameWorkspace: (workspaceId, name) async {
|
renameWorkspace: (workspaceId, name) async {
|
||||||
final result = await _userService.renameWorkspace(
|
final result =
|
||||||
workspaceId,
|
await _userService.renameWorkspace(workspaceId, name);
|
||||||
name,
|
final workspaces = result.fold(
|
||||||
);
|
(s) => state.workspaces.map(
|
||||||
final (workspaces, currentWorkspace, renameWorkspaceResult) =
|
(e) {
|
||||||
result.fold(
|
|
||||||
(s) {
|
|
||||||
final workspaces = state.workspaces.map((e) {
|
|
||||||
if (e.workspaceId == workspaceId) {
|
if (e.workspaceId == workspaceId) {
|
||||||
e.freeze();
|
e.freeze();
|
||||||
return e.rebuild((p0) {
|
return e.rebuild((p0) {
|
||||||
@ -162,36 +158,21 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
}).toList();
|
},
|
||||||
|
).toList(),
|
||||||
final currentWorkspace = workspaces.firstWhere(
|
(f) => state.workspaces,
|
||||||
(e) => e.workspaceId == state.currentWorkspace?.workspaceId,
|
);
|
||||||
);
|
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(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
createWorkspaceResult: null,
|
|
||||||
deleteWorkspaceResult: null,
|
|
||||||
openWorkspaceResult: null,
|
|
||||||
updateWorkspaceIconResult: null,
|
|
||||||
workspaces: workspaces,
|
workspaces: workspaces,
|
||||||
currentWorkspace: currentWorkspace,
|
currentWorkspace: currentWorkspace,
|
||||||
renameWorkspaceResult: renameWorkspaceResult,
|
actionResult: UserWorkspaceActionResult(
|
||||||
|
actionType: UserWorkspaceActionType.rename,
|
||||||
|
result: result,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -200,11 +181,9 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
icon,
|
icon,
|
||||||
);
|
);
|
||||||
|
final workspaces = result.fold(
|
||||||
final (workspaces, currentWorkspace, updateWorkspaceIconResult) =
|
(s) => state.workspaces.map(
|
||||||
result.fold(
|
(e) {
|
||||||
(s) {
|
|
||||||
final workspaces = state.workspaces.map((e) {
|
|
||||||
if (e.workspaceId == workspaceId) {
|
if (e.workspaceId == workspaceId) {
|
||||||
e.freeze();
|
e.freeze();
|
||||||
return e.rebuild((p0) {
|
return e.rebuild((p0) {
|
||||||
@ -212,37 +191,21 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
}).toList();
|
},
|
||||||
|
).toList(),
|
||||||
final currentWorkspace = workspaces.firstWhere(
|
(f) => state.workspaces,
|
||||||
(e) => e.workspaceId == state.currentWorkspace?.workspaceId,
|
);
|
||||||
);
|
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(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
createWorkspaceResult: null,
|
|
||||||
deleteWorkspaceResult: null,
|
|
||||||
openWorkspaceResult: null,
|
|
||||||
renameWorkspaceResult: null,
|
|
||||||
updateWorkspaceIconResult: updateWorkspaceIconResult,
|
|
||||||
workspaces: workspaces,
|
workspaces: workspaces,
|
||||||
currentWorkspace: currentWorkspace,
|
currentWorkspace: currentWorkspace,
|
||||||
|
actionResult: UserWorkspaceActionResult(
|
||||||
|
actionType: UserWorkspaceActionType.updateIcon,
|
||||||
|
result: result,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -273,9 +236,9 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class UserWorkspaceEvent with _$UserWorkspaceEvent {
|
class UserWorkspaceEvent with _$UserWorkspaceEvent {
|
||||||
const factory UserWorkspaceEvent.initial() = Initial;
|
const factory UserWorkspaceEvent.initial() = Initial;
|
||||||
const factory UserWorkspaceEvent.createWorkspace(String name, String desc) =
|
|
||||||
CreateWorkspace;
|
|
||||||
const factory UserWorkspaceEvent.fetchWorkspaces() = FetchWorkspaces;
|
const factory UserWorkspaceEvent.fetchWorkspaces() = FetchWorkspaces;
|
||||||
|
const factory UserWorkspaceEvent.createWorkspace(String name) =
|
||||||
|
CreateWorkspace;
|
||||||
const factory UserWorkspaceEvent.deleteWorkspace(String workspaceId) =
|
const factory UserWorkspaceEvent.deleteWorkspace(String workspaceId) =
|
||||||
DeleteWorkspace;
|
DeleteWorkspace;
|
||||||
const factory UserWorkspaceEvent.openWorkspace(String workspaceId) =
|
const factory UserWorkspaceEvent.openWorkspace(String workspaceId) =
|
||||||
@ -288,24 +251,51 @@ class UserWorkspaceEvent with _$UserWorkspaceEvent {
|
|||||||
String workspaceId,
|
String workspaceId,
|
||||||
String icon,
|
String icon,
|
||||||
) = _UpdateWorkspaceIcon;
|
) = _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
|
@freezed
|
||||||
class UserWorkspaceState with _$UserWorkspaceState {
|
class UserWorkspaceState with _$UserWorkspaceState {
|
||||||
|
const UserWorkspaceState._();
|
||||||
|
|
||||||
const factory UserWorkspaceState({
|
const factory UserWorkspaceState({
|
||||||
required UserWorkspacePB? currentWorkspace,
|
@Default(null) UserWorkspacePB? currentWorkspace,
|
||||||
required List<UserWorkspacePB> workspaces,
|
@Default([]) List<UserWorkspacePB> workspaces,
|
||||||
@Default(false) bool isCollaborativeWorkspace,
|
@Default(null) UserWorkspaceActionResult? actionResult,
|
||||||
@Default(null) FlowyResult<void, FlowyError>? createWorkspaceResult,
|
@Default(false) bool isCollabWorkspaceOn,
|
||||||
@Default(null) FlowyResult<void, FlowyError>? deleteWorkspaceResult,
|
|
||||||
@Default(null) FlowyResult<void, FlowyError>? openWorkspaceResult,
|
|
||||||
@Default(null) FlowyResult<void, FlowyError>? renameWorkspaceResult,
|
|
||||||
@Default(null) FlowyResult<void, FlowyError>? updateWorkspaceIconResult,
|
|
||||||
}) = _UserWorkspaceState;
|
}) = _UserWorkspaceState;
|
||||||
|
|
||||||
factory UserWorkspaceState.initial() =>
|
factory UserWorkspaceState.initial() => const UserWorkspaceState();
|
||||||
const UserWorkspaceState(currentWorkspace: null, workspaces: []);
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ extension ViewExtension on ViewPB {
|
|||||||
}
|
}
|
||||||
FlowyResult<ViewPB, FlowyError> parent =
|
FlowyResult<ViewPB, FlowyError> parent =
|
||||||
await ViewBackendService.getView(parentViewId);
|
await ViewBackendService.getView(parentViewId);
|
||||||
while (parent.isSuccess()) {
|
while (parent.isSuccess) {
|
||||||
// parent is not null
|
// parent is not null
|
||||||
final view = parent.fold((s) => s, (e) => null);
|
final view = parent.fold((s) => s, (e) => null);
|
||||||
if (view == null || (!includeRoot && view.parentViewId.isEmpty)) {
|
if (view == null || (!includeRoot && view.parentViewId.isEmpty)) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/shared/feature_flags.dart';
|
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/notifications/notification_action.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_user.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.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-folder/workspace.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||||
show UserProfilePB;
|
show UserProfilePB;
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
@ -207,8 +205,7 @@ class _SidebarState extends State<_Sidebar> {
|
|||||||
// user or workspace, setting
|
// user or workspace, setting
|
||||||
Padding(
|
Padding(
|
||||||
padding: menuHorizontalInset,
|
padding: menuHorizontalInset,
|
||||||
child: widget.userProfile.authenticator != AuthenticatorPB.Local &&
|
child: context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn
|
||||||
FeatureFlag.collaborativeWorkspace.isOn
|
|
||||||
? SidebarWorkspace(
|
? SidebarWorkspace(
|
||||||
userProfile: widget.userProfile,
|
userProfile: widget.userProfile,
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/shared/feature_flags.dart';
|
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.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/menu/sidebar_sections_bloc.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/user/user_workspace_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.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/_favorite_folder.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_section_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) {
|
builder: (context, state) {
|
||||||
// only show public and private section if the workspace is collaborative and not local
|
// only show public and private section if the workspace is collaborative and not local
|
||||||
final isCollaborativeWorkspace =
|
final isCollaborativeWorkspace =
|
||||||
userProfile.authenticator != AuthenticatorPB.Local &&
|
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
|
||||||
FeatureFlag.collaborativeWorkspace.isOn;
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children:
|
children:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_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/workspace/presentation/home/menu/sidebar/rename_view_dialog.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:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -26,10 +27,15 @@ class SidebarNewPageButton extends StatelessWidget {
|
|||||||
LocaleKeys.newPageText.tr(),
|
LocaleKeys.newPageText.tr(),
|
||||||
(viewName, _) {
|
(viewName, _) {
|
||||||
if (viewName.isNotEmpty) {
|
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(
|
context.read<SidebarSectionsBloc>().add(
|
||||||
SidebarSectionsEvent.createRootViewInSection(
|
SidebarSectionsEvent.createRootViewInSection(
|
||||||
name: viewName,
|
name: viewName,
|
||||||
viewSection: ViewSectionPB.Public,
|
viewSection: section,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.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_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class SidebarWorkspace extends StatelessWidget {
|
class SidebarWorkspace extends StatelessWidget {
|
||||||
@ -29,14 +30,13 @@ class SidebarWorkspace extends StatelessWidget {
|
|||||||
listener: _showResultDialog,
|
listener: _showResultDialog,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final currentWorkspace = state.currentWorkspace;
|
final currentWorkspace = state.currentWorkspace;
|
||||||
// todo: show something if there is no workspace
|
|
||||||
if (currentWorkspace == null) {
|
if (currentWorkspace == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SidebarWorkspaceWrapper(
|
child: SidebarSwitchWorkspaceButton(
|
||||||
userProfile: userProfile,
|
userProfile: userProfile,
|
||||||
currentWorkspace: currentWorkspace,
|
currentWorkspace: currentWorkspace,
|
||||||
),
|
),
|
||||||
@ -51,60 +51,79 @@ class SidebarWorkspace extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showResultDialog(BuildContext context, UserWorkspaceState state) {
|
void _showResultDialog(BuildContext context, UserWorkspaceState state) {
|
||||||
var result = state.createWorkspaceResult;
|
final actionResult = state.actionResult;
|
||||||
|
if (actionResult == null) {
|
||||||
if (result != null) {
|
|
||||||
final message = result.fold(
|
|
||||||
(s) => LocaleKeys.workspace_createSuccess.tr(),
|
|
||||||
(e) => '${LocaleKeys.workspace_createFailed.tr()}: ${e.msg}',
|
|
||||||
);
|
|
||||||
return showSnackBarMessage(context, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = state.deleteWorkspaceResult;
|
|
||||||
if (result != null) {
|
|
||||||
final message = result.fold(
|
|
||||||
(s) => LocaleKeys.workspace_deleteSuccess.tr(),
|
|
||||||
(e) => '${LocaleKeys.workspace_deleteFailed.tr()}: ${e.msg}',
|
|
||||||
);
|
|
||||||
showSnackBarMessage(context, message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = state.openWorkspaceResult;
|
final actionType = actionResult.actionType;
|
||||||
if (result != null) {
|
final result = actionResult.result;
|
||||||
final message = result.fold(
|
|
||||||
(s) => LocaleKeys.workspace_openSuccess.tr(),
|
result.onFailure((f) {
|
||||||
(e) => '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}',
|
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(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
showSnackBarMessage(context, message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = state.updateWorkspaceIconResult;
|
final String? message;
|
||||||
if (result != null) {
|
switch (actionType) {
|
||||||
final message = result.fold(
|
case UserWorkspaceActionType.create:
|
||||||
(s) => LocaleKeys.workspace_updateIconSuccess.tr(),
|
message = result.fold(
|
||||||
(e) => '${LocaleKeys.workspace_updateIconFailed.tr()}: ${e.msg}',
|
(s) => LocaleKeys.workspace_createSuccess.tr(),
|
||||||
);
|
(e) => '${LocaleKeys.workspace_createFailed.tr()}: ${e.msg}',
|
||||||
showSnackBarMessage(context, message);
|
);
|
||||||
return;
|
break;
|
||||||
|
case UserWorkspaceActionType.delete:
|
||||||
|
message = result.fold(
|
||||||
|
(s) => LocaleKeys.workspace_deleteSuccess.tr(),
|
||||||
|
(e) => '${LocaleKeys.workspace_deleteFailed.tr()}: ${e.msg}',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case UserWorkspaceActionType.open:
|
||||||
|
message = result.fold(
|
||||||
|
(s) => LocaleKeys.workspace_openSuccess.tr(),
|
||||||
|
(e) => '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case UserWorkspaceActionType.updateIcon:
|
||||||
|
message = result.fold(
|
||||||
|
(s) => LocaleKeys.workspace_updateIconSuccess.tr(),
|
||||||
|
(e) => '${LocaleKeys.workspace_updateIconFailed.tr()}: ${e.msg}',
|
||||||
|
);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = state.renameWorkspaceResult;
|
if (message != null) {
|
||||||
if (result != null) {
|
|
||||||
final message = result.fold(
|
|
||||||
(s) => LocaleKeys.workspace_renameSuccess.tr(),
|
|
||||||
(e) => '${LocaleKeys.workspace_renameFailed.tr()}: ${e.msg}',
|
|
||||||
);
|
|
||||||
showSnackBarMessage(context, message);
|
showSnackBarMessage(context, message);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SidebarWorkspaceWrapper extends StatefulWidget {
|
class SidebarSwitchWorkspaceButton extends StatefulWidget {
|
||||||
const SidebarWorkspaceWrapper({
|
const SidebarSwitchWorkspaceButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.userProfile,
|
required this.userProfile,
|
||||||
required this.currentWorkspace,
|
required this.currentWorkspace,
|
||||||
@ -114,40 +133,12 @@ class SidebarWorkspaceWrapper extends StatefulWidget {
|
|||||||
final UserProfilePB userProfile;
|
final UserProfilePB userProfile;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SidebarWorkspaceWrapper> createState() =>
|
State<SidebarSwitchWorkspaceButton> createState() =>
|
||||||
_SidebarWorkspaceWrapperState();
|
_SidebarSwitchWorkspaceButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SidebarWorkspaceWrapperState extends State<SidebarWorkspaceWrapper> {
|
class _SidebarSwitchWorkspaceButtonState
|
||||||
@override
|
extends State<SidebarSwitchWorkspaceButton> {
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (PlatformExtension.isDesktopOrWeb) {
|
|
||||||
return _DesktopWorkspaceWrapper(
|
|
||||||
userProfile: widget.userProfile,
|
|
||||||
currentWorkspace: widget.currentWorkspace,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// TODO(Lucas) mobile workspace menu
|
|
||||||
return const Placeholder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DesktopWorkspaceWrapper extends StatefulWidget {
|
|
||||||
const _DesktopWorkspaceWrapper({
|
|
||||||
required this.userProfile,
|
|
||||||
required this.currentWorkspace,
|
|
||||||
});
|
|
||||||
|
|
||||||
final UserWorkspacePB currentWorkspace;
|
|
||||||
final UserProfilePB userProfile;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_DesktopWorkspaceWrapper> createState() =>
|
|
||||||
_DesktopWorkspaceWrapperState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DesktopWorkspaceWrapperState extends State<_DesktopWorkspaceWrapper> {
|
|
||||||
final controller = PopoverController();
|
final controller = PopoverController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -89,7 +89,7 @@ class WorkspacesMenu extends StatelessWidget {
|
|||||||
final workspaceBloc = context.read<UserWorkspaceBloc>();
|
final workspaceBloc = context.read<UserWorkspaceBloc>();
|
||||||
await CreateWorkspaceDialog(
|
await CreateWorkspaceDialog(
|
||||||
onConfirm: (name) {
|
onConfirm: (name) {
|
||||||
workspaceBloc.add(UserWorkspaceEvent.createWorkspace(name, ''));
|
workspaceBloc.add(UserWorkspaceEvent.createWorkspace(name));
|
||||||
},
|
},
|
||||||
).show(context);
|
).show(context);
|
||||||
}
|
}
|
||||||
@ -120,59 +120,72 @@ class WorkspaceMenuItem extends StatelessWidget {
|
|||||||
// settings right icon inside the flowy button will
|
// settings right icon inside the flowy button will
|
||||||
// cause the popover dismiss intermediately when click the right icon.
|
// cause the popover dismiss intermediately when click the right icon.
|
||||||
// so using the stack to put the right icon on the flowy button.
|
// so using the stack to put the right icon on the flowy button.
|
||||||
return Stack(
|
return SizedBox(
|
||||||
alignment: Alignment.center,
|
height: 52,
|
||||||
children: [
|
child: Stack(
|
||||||
FlowyButton(
|
alignment: Alignment.center,
|
||||||
onTap: () {
|
children: [
|
||||||
if (!isSelected) {
|
FlowyButton(
|
||||||
context.read<UserWorkspaceBloc>().add(
|
onTap: () {
|
||||||
UserWorkspaceEvent.openWorkspace(
|
if (!isSelected) {
|
||||||
workspace.workspaceId,
|
context.read<UserWorkspaceBloc>().add(
|
||||||
),
|
UserWorkspaceEvent.openWorkspace(
|
||||||
);
|
workspace.workspaceId,
|
||||||
}
|
),
|
||||||
},
|
);
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
PopoverContainer.of(context).closeAll();
|
||||||
iconPadding: 10.0,
|
}
|
||||||
leftIconSize: const Size.square(32),
|
},
|
||||||
leftIcon: const SizedBox.square(
|
margin:
|
||||||
dimension: 32,
|
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
),
|
iconPadding: 10.0,
|
||||||
rightIcon: const HSpace(42.0),
|
leftIconSize: const Size.square(32),
|
||||||
text: Column(
|
leftIcon: const SizedBox.square(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
dimension: 32,
|
||||||
children: [
|
),
|
||||||
FlowyText.medium(
|
rightIcon: const HSpace(42.0),
|
||||||
workspace.name,
|
text: Column(
|
||||||
fontSize: 14.0,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
overflow: TextOverflow.ellipsis,
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
),
|
children: [
|
||||||
if (members.length > 1)
|
FlowyText.medium(
|
||||||
|
workspace.name,
|
||||||
|
fontSize: 14.0,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
FlowyText(
|
FlowyText(
|
||||||
'${members.length} ${LocaleKeys.settings_appearance_members_members.tr()}',
|
state.isLoading
|
||||||
|
? ''
|
||||||
|
: LocaleKeys
|
||||||
|
.settings_appearance_members_membersCount
|
||||||
|
.plural(
|
||||||
|
members.length,
|
||||||
|
),
|
||||||
fontSize: 10.0,
|
fontSize: 10.0,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: 8,
|
|
||||||
child: SizedBox.square(
|
|
||||||
dimension: 32,
|
|
||||||
child: WorkspaceIcon(
|
|
||||||
workspace: workspace,
|
|
||||||
iconSize: 26,
|
|
||||||
enableEdit: true,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
Positioned(
|
left: 8,
|
||||||
right: 12.0,
|
child: SizedBox.square(
|
||||||
child: Align(child: _buildRightIcon(context)),
|
dimension: 32,
|
||||||
),
|
child: WorkspaceIcon(
|
||||||
],
|
workspace: workspace,
|
||||||
|
iconSize: 26,
|
||||||
|
enableEdit: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 12.0,
|
||||||
|
child: Align(
|
||||||
|
child: _buildRightIcon(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -182,7 +195,7 @@ class WorkspaceMenuItem extends StatelessWidget {
|
|||||||
Widget _buildRightIcon(BuildContext context) {
|
Widget _buildRightIcon(BuildContext context) {
|
||||||
// only the owner can update or delete workspace.
|
// only the owner can update or delete workspace.
|
||||||
// only show the more action button when the workspace is selected.
|
// only show the more action button when the workspace is selected.
|
||||||
if (!isSelected) {
|
if (!isSelected || context.read<WorkspaceMemberBloc>().state.isLoading) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
class FlowyMessageToast extends StatelessWidget {
|
class FlowyMessageToast extends StatelessWidget {
|
||||||
@ -70,6 +69,7 @@ void showSnackBarMessage(
|
|||||||
content: FlowyText(
|
content: FlowyText(
|
||||||
message,
|
message,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -50,6 +50,7 @@ class SettingsDialog extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.tertiary,
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
child: ScaffoldMessenger(
|
child: ScaffoldMessenger(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/log.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_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
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: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';
|
||||||
|
|
||||||
part 'workspace_member_bloc.freezed.dart';
|
part 'workspace_member_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -28,43 +30,113 @@ class WorkspaceMemberBloc
|
|||||||
on<WorkspaceMemberEvent>((event, emit) async {
|
on<WorkspaceMemberEvent>((event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
if (workspace != null) {
|
await _setCurrentWorkspaceId();
|
||||||
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 = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
add(const WorkspaceMemberEvent.getWorkspaceMembers());
|
final result = await _userBackendService.getWorkspaceMembers(
|
||||||
},
|
_workspaceId,
|
||||||
getWorkspaceMembers: () async {
|
);
|
||||||
final members = await _getWorkspaceMembers();
|
final members = result.fold<List<WorkspaceMemberPB>>(
|
||||||
|
(s) => s.items,
|
||||||
|
(e) => [],
|
||||||
|
);
|
||||||
final myRole = _getMyRole(members);
|
final myRole = _getMyRole(members);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
members: members,
|
members: members,
|
||||||
myRole: myRole,
|
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 {
|
addWorkspaceMember: (email) async {
|
||||||
await _addWorkspaceMember(email);
|
final result = await _userBackendService.addWorkspaceMember(
|
||||||
add(const WorkspaceMemberEvent.getWorkspaceMembers());
|
_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 {
|
removeWorkspaceMember: (email) async {
|
||||||
await _removeWorkspaceMember(email);
|
final result = await _userBackendService.removeWorkspaceMember(
|
||||||
add(const WorkspaceMemberEvent.getWorkspaceMembers());
|
_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 {
|
updateWorkspaceMember: (email, role) async {
|
||||||
await _updateWorkspaceMember(email, role);
|
final result = await _userBackendService.updateWorkspaceMember(
|
||||||
add(const WorkspaceMemberEvent.getWorkspaceMembers());
|
_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
|
// if the workspace is null, use the current workspace
|
||||||
final UserWorkspacePB? workspace;
|
final UserWorkspacePB? workspace;
|
||||||
|
|
||||||
late final String workspaceId;
|
late final String _workspaceId;
|
||||||
late final UserBackendService _userBackendService;
|
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 [];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AFRolePB _getMyRole(List<WorkspaceMemberPB> members) {
|
AFRolePB _getMyRole(List<WorkspaceMemberPB> members) {
|
||||||
final role = members
|
final role = members
|
||||||
@ -101,27 +163,19 @@ class WorkspaceMemberBloc
|
|||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addWorkspaceMember(String email) async {
|
Future<void> _setCurrentWorkspaceId() async {
|
||||||
return _userBackendService.addWorkspaceMember(workspaceId, email).fold(
|
if (workspace != null) {
|
||||||
(s) => Log.debug('Added workspace member: $email'),
|
_workspaceId = workspace!.workspaceId;
|
||||||
(e) => Log.error('Failed to add workspace member: $e'),
|
} else {
|
||||||
);
|
final currentWorkspace = await FolderEventReadCurrentWorkspace().send();
|
||||||
}
|
currentWorkspace.fold((s) {
|
||||||
|
_workspaceId = s.id;
|
||||||
Future<void> _removeWorkspaceMember(String email) async {
|
}, (e) {
|
||||||
return _userBackendService.removeWorkspaceMember(workspaceId, email).fold(
|
assert(false, 'Failed to read current workspace: $e');
|
||||||
(s) => Log.debug('Removed workspace member: $email'),
|
Log.error('Failed to read current workspace: $e');
|
||||||
(e) => Log.error('Failed to remove workspace member: $e'),
|
_workspaceId = '';
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
) = 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
|
@freezed
|
||||||
class WorkspaceMemberState with _$WorkspaceMemberState {
|
class WorkspaceMemberState with _$WorkspaceMemberState {
|
||||||
|
const WorkspaceMemberState._();
|
||||||
|
|
||||||
const factory WorkspaceMemberState({
|
const factory WorkspaceMemberState({
|
||||||
@Default([]) List<WorkspaceMemberPB> members,
|
@Default([]) List<WorkspaceMemberPB> members,
|
||||||
@Default(AFRolePB.Guest) AFRolePB myRole,
|
@Default(AFRolePB.Guest) AFRolePB myRole,
|
||||||
|
@Default(null) WorkspaceMemberActionResult? actionResult,
|
||||||
|
@Default(true) bool isLoading,
|
||||||
}) = _WorkspaceMemberState;
|
}) = _WorkspaceMemberState;
|
||||||
|
|
||||||
factory WorkspaceMemberState.initial() => const 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.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/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/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_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.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/flowy_tooltip.dart';
|
||||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -28,7 +30,8 @@ class WorkspaceMembersPage extends StatelessWidget {
|
|||||||
return BlocProvider<WorkspaceMemberBloc>(
|
return BlocProvider<WorkspaceMemberBloc>(
|
||||||
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
|
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
|
||||||
..add(const WorkspaceMemberEvent.initial()),
|
..add(const WorkspaceMemberEvent.initial()),
|
||||||
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
|
child: BlocConsumer<WorkspaceMemberBloc, WorkspaceMemberState>(
|
||||||
|
listener: _showResultDialog,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -46,6 +49,7 @@ class WorkspaceMembersPage extends StatelessWidget {
|
|||||||
userProfile: userProfile,
|
userProfile: userProfile,
|
||||||
myRole: state.myRole,
|
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 {
|
class _InviteMember extends StatefulWidget {
|
||||||
@ -111,6 +152,7 @@ class _InviteMemberState extends State<_InviteMember> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const VSpace(16.0),
|
const VSpace(16.0),
|
||||||
|
/* Enable this when the feature is ready
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
backgroundColor: const Color(0xFFE0E0E0),
|
backgroundColor: const Color(0xFFE0E0E0),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -140,6 +182,7 @@ class _InviteMemberState extends State<_InviteMember> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const VSpace(16.0),
|
const VSpace(16.0),
|
||||||
|
*/
|
||||||
const Divider(
|
const Divider(
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
thickness: 1.0,
|
thickness: 1.0,
|
||||||
@ -160,10 +203,6 @@ class _InviteMemberState extends State<_InviteMember> {
|
|||||||
context
|
context
|
||||||
.read<WorkspaceMemberBloc>()
|
.read<WorkspaceMemberBloc>()
|
||||||
.add(WorkspaceMemberEvent.addWorkspaceMember(email));
|
.add(WorkspaceMemberEvent.addWorkspaceMember(email));
|
||||||
showSnackBarMessage(
|
|
||||||
context,
|
|
||||||
LocaleKeys.settings_appearance_members_emailSent.tr(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,14 +349,24 @@ class _MemberMoreActionList extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onSelected: (action, controller) async {
|
onSelected: (action, controller) {
|
||||||
switch (action.inner) {
|
switch (action.inner) {
|
||||||
case _MemberMoreAction.delete:
|
case _MemberMoreAction.delete:
|
||||||
context.read<WorkspaceMemberBloc>().add(
|
showDialog(
|
||||||
WorkspaceMemberEvent.removeWorkspaceMember(
|
context: context,
|
||||||
action.member.email,
|
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;
|
break;
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
@ -353,7 +402,7 @@ class _MemberRoleActionList extends StatelessWidget {
|
|||||||
return PopoverActionList<_MemberRoleActionWrapper>(
|
return PopoverActionList<_MemberRoleActionWrapper>(
|
||||||
asBarrier: true,
|
asBarrier: true,
|
||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
actions: [AFRolePB.Member, AFRolePB.Guest]
|
actions: [AFRolePB.Member]
|
||||||
.map((e) => _MemberRoleActionWrapper(e, member))
|
.map((e) => _MemberRoleActionWrapper(e, member))
|
||||||
.toList(),
|
.toList(),
|
||||||
offset: const Offset(0, 10),
|
offset: const Offset(0, 10),
|
||||||
|
@ -186,7 +186,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
|||||||
this.okTitle,
|
this.okTitle,
|
||||||
this.cancelTitle,
|
this.cancelTitle,
|
||||||
this.title,
|
this.title,
|
||||||
required this.message,
|
this.message,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -195,13 +195,14 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
|||||||
final String? okTitle;
|
final String? okTitle;
|
||||||
final String? cancelTitle;
|
final String? cancelTitle;
|
||||||
final String? title;
|
final String? title;
|
||||||
final String message;
|
final String? message;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StyledDialog(
|
return StyledDialog(
|
||||||
maxWidth: maxWidth ?? 500,
|
maxWidth: maxWidth ?? 500,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: Insets.xl, vertical: Insets.l),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -209,6 +210,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
|||||||
FlowyText.medium(
|
FlowyText.medium(
|
||||||
title!.toUpperCase(),
|
title!.toUpperCase(),
|
||||||
fontSize: FontSizes.s16,
|
fontSize: FontSizes.s16,
|
||||||
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
VSpace(Insets.sm * 1.5),
|
VSpace(Insets.sm * 1.5),
|
||||||
Container(
|
Container(
|
||||||
@ -217,7 +219,11 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
VSpace(Insets.m * 1.5),
|
VSpace(Insets.m * 1.5),
|
||||||
],
|
],
|
||||||
FlowyText.medium(message),
|
if (message != null)
|
||||||
|
FlowyText.medium(
|
||||||
|
message!,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
SizedBox(height: Insets.l),
|
SizedBox(height: Insets.l),
|
||||||
OkCancelButton(
|
OkCancelButton(
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
@ -24,11 +24,11 @@ extension FlowyAsyncResultExtension<S, F extends Object>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isError() {
|
Future<bool> isError() {
|
||||||
return then((result) => result.isFailure());
|
return then((result) => result.isFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isSuccess() {
|
Future<bool> isSuccess() {
|
||||||
return then((result) => result.isSuccess());
|
return then((result) => result.isSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlowyAsyncResult<S, F> onFailure(void Function(F failure) onFailure) {
|
FlowyAsyncResult<S, F> onFailure(void Function(F failure) onFailure) {
|
||||||
|
@ -10,8 +10,8 @@ abstract class FlowyResult<S, F extends Object> {
|
|||||||
FlowyResult<T, F> map<T>(T Function(S success) fn);
|
FlowyResult<T, F> map<T>(T Function(S success) fn);
|
||||||
FlowyResult<S, T> mapError<T extends Object>(T Function(F failure) fn);
|
FlowyResult<S, T> mapError<T extends Object>(T Function(F failure) fn);
|
||||||
|
|
||||||
bool isSuccess();
|
bool get isSuccess;
|
||||||
bool isFailure();
|
bool get isFailure;
|
||||||
|
|
||||||
S? toNullable();
|
S? toNullable();
|
||||||
|
|
||||||
@ -20,6 +20,8 @@ abstract class FlowyResult<S, F extends Object> {
|
|||||||
|
|
||||||
S getOrElse(S Function(F failure) onFailure);
|
S getOrElse(S Function(F failure) onFailure);
|
||||||
S getOrThrow();
|
S getOrThrow();
|
||||||
|
|
||||||
|
F getFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowySuccess<S, F extends Object> implements FlowyResult<S, F> {
|
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
|
@override
|
||||||
bool isSuccess() {
|
bool get isSuccess => true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isFailure() {
|
bool get isFailure => false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
S? toNullable() {
|
S? toNullable() {
|
||||||
@ -88,6 +86,11 @@ class FlowySuccess<S, F extends Object> implements FlowyResult<S, F> {
|
|||||||
S getOrThrow() {
|
S getOrThrow() {
|
||||||
return _value;
|
return _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
F getFailure() {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowyFailure<S, F extends Object> implements FlowyResult<S, F> {
|
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
|
@override
|
||||||
bool isSuccess() {
|
bool get isSuccess => false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isFailure() {
|
bool get isFailure => true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
S? toNullable() {
|
S? toNullable() {
|
||||||
@ -156,4 +155,9 @@ class FlowyFailure<S, F extends Object> implements FlowyResult<S, F> {
|
|||||||
S getOrThrow() {
|
S getOrThrow() {
|
||||||
throw _value;
|
throw _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
F getFailure() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
const _overlayContainerPadding = EdgeInsets.symmetric(vertical: 12);
|
const _overlayContainerPadding = EdgeInsets.symmetric(vertical: 12);
|
||||||
const overlayContainerMaxWidth = 760.0;
|
const overlayContainerMaxWidth = 760.0;
|
||||||
const overlayContainerMinWidth = 320.0;
|
const overlayContainerMinWidth = 320.0;
|
||||||
@ -14,6 +15,7 @@ class FlowyDialog extends StatelessWidget {
|
|||||||
this.constraints,
|
this.constraints,
|
||||||
this.padding = _overlayContainerPadding,
|
this.padding = _overlayContainerPadding,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
|
this.width,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget? title;
|
final Widget? title;
|
||||||
@ -22,11 +24,12 @@ class FlowyDialog extends StatelessWidget {
|
|||||||
final BoxConstraints? constraints;
|
final BoxConstraints? constraints;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
final double? width;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final windowSize = MediaQuery.of(context).size;
|
final windowSize = MediaQuery.of(context).size;
|
||||||
final size = windowSize * 0.7;
|
final size = windowSize * 0.6;
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
backgroundColor: backgroundColor ?? Theme.of(context).cardColor,
|
backgroundColor: backgroundColor ?? Theme.of(context).cardColor,
|
||||||
@ -38,8 +41,11 @@ class FlowyDialog extends StatelessWidget {
|
|||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: size.height,
|
height: size.height,
|
||||||
width: max(min(size.width, overlayContainerMaxWidth),
|
width: width ??
|
||||||
overlayContainerMinWidth),
|
max(
|
||||||
|
min(size.width, overlayContainerMaxWidth),
|
||||||
|
overlayContainerMinWidth,
|
||||||
|
),
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
|
14
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
14
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -838,7 +838,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -862,7 +862,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-database"
|
name = "collab-database"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -892,7 +892,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-document"
|
name = "collab-document"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -911,7 +911,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-entity"
|
name = "collab-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -926,7 +926,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-folder"
|
name = "collab-folder"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -963,7 +963,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-plugins"
|
name = "collab-plugins"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1002,7 +1002,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-user"
|
name = "collab-user"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
|
@ -96,10 +96,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
|
|||||||
# 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 = { 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 = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
|
@ -65,10 +65,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
|
|||||||
# 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 = { 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 = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
"deleteWorkspaceHintText": "Are you sure you want to delete the workspace? This action cannot be undone.",
|
"deleteWorkspaceHintText": "Are you sure you want to delete the workspace? This action cannot be undone.",
|
||||||
"createSuccess": "Workspace created successfully",
|
"createSuccess": "Workspace created successfully",
|
||||||
"createFailed": "Failed to create workspace",
|
"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",
|
"deleteSuccess": "Workspace deleted successfully",
|
||||||
"deleteFailed": "Failed to delete workspace",
|
"deleteFailed": "Failed to delete workspace",
|
||||||
"openSuccess": "Open workspace successfully",
|
"openSuccess": "Open workspace successfully",
|
||||||
@ -75,7 +76,9 @@
|
|||||||
"renameSuccess": "Workspace renamed successfully",
|
"renameSuccess": "Workspace renamed successfully",
|
||||||
"renameFailed": "Failed to rename workspace",
|
"renameFailed": "Failed to rename workspace",
|
||||||
"updateIconSuccess": "Updated workspace icon successfully",
|
"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": {
|
"shareAction": {
|
||||||
"buttonText": "Share",
|
"buttonText": "Share",
|
||||||
@ -436,7 +439,17 @@
|
|||||||
"guestHintText": "A Guest can read, react, comment, and can edit certain pages with permission.",
|
"guestHintText": "A Guest can read, react, comment, and can edit certain pages with permission.",
|
||||||
"emailInvalidError": "Invalid email, please check and try again",
|
"emailInvalidError": "Invalid email, please check and try again",
|
||||||
"emailSent": "Email sent, please check the inbox",
|
"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": {
|
"files": {
|
||||||
@ -1420,7 +1433,7 @@
|
|||||||
},
|
},
|
||||||
"syncState": {
|
"syncState": {
|
||||||
"syncing": "Syncing",
|
"syncing": "Syncing",
|
||||||
"synced": "Everything is up to date",
|
"synced": "Synced",
|
||||||
"noNetworkConnected": "No network connected"
|
"noNetworkConnected": "No network connected"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
frontend/rust-lib/Cargo.lock
generated
14
frontend/rust-lib/Cargo.lock
generated
@ -764,7 +764,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -788,7 +788,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-database"
|
name = "collab-database"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -818,7 +818,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-document"
|
name = "collab-document"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -837,7 +837,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-entity"
|
name = "collab-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -852,7 +852,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-folder"
|
name = "collab-folder"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -889,7 +889,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-plugins"
|
name = "collab-plugins"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -928,7 +928,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-user"
|
name = "collab-user"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
|
@ -120,10 +120,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9
|
|||||||
# 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 = { 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 = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4b25d7d021a11c51583e6a404c139f49ee6a3bf9" }
|
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "25c4be5" }
|
||||||
|
@ -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 test = EventIntegrationTest::new_with_guest_user().await;
|
||||||
let old_views = test
|
let old_views = test
|
||||||
.folder_manager
|
.folder_manager
|
||||||
.get_current_workspace_views()
|
.get_current_workspace_public_views()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let old_workspace = test.folder_manager.get_current_workspace().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();
|
test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
|
||||||
let new_views = test
|
let new_views = test
|
||||||
.folder_manager
|
.folder_manager
|
||||||
.get_current_workspace_views()
|
.get_current_workspace_public_views()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let new_workspace = test.folder_manager.get_current_workspace().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_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
||||||
let old_cloud_views = test
|
let old_cloud_views = test
|
||||||
.folder_manager
|
.folder_manager
|
||||||
.get_current_workspace_views()
|
.get_current_workspace_public_views()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(old_cloud_views.len(), 1);
|
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_workspace = test.folder_manager.get_current_workspace().await.unwrap();
|
||||||
let new_cloud_views = test
|
let new_cloud_views = test
|
||||||
.folder_manager
|
.folder_manager
|
||||||
.get_current_workspace_views()
|
.get_current_workspace_public_views()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(new_cloud_workspace, old_cloud_workspace);
|
assert_eq!(new_cloud_workspace, old_cloud_workspace);
|
||||||
|
@ -261,10 +261,10 @@ pub enum ErrorCode {
|
|||||||
CloudRequestPayloadTooLarge = 90,
|
CloudRequestPayloadTooLarge = 90,
|
||||||
|
|
||||||
#[error("Workspace limit exceeded")]
|
#[error("Workspace limit exceeded")]
|
||||||
WorkspaceLimitExeceeded = 91,
|
WorkspaceLimitExceeded = 91,
|
||||||
|
|
||||||
#[error("Workspace member limit exceeded")]
|
#[error("Workspace member limit exceeded")]
|
||||||
WorkspaceMemberLimitExeceeded = 92,
|
WorkspaceMemberLimitExceeded = 92,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCode {
|
impl ErrorCode {
|
||||||
|
@ -22,8 +22,8 @@ impl From<AppResponseError> for FlowyError {
|
|||||||
AppErrorCode::NetworkError => ErrorCode::HttpError,
|
AppErrorCode::NetworkError => ErrorCode::HttpError,
|
||||||
AppErrorCode::PayloadTooLarge => ErrorCode::CloudRequestPayloadTooLarge,
|
AppErrorCode::PayloadTooLarge => ErrorCode::CloudRequestPayloadTooLarge,
|
||||||
AppErrorCode::UserUnAuthorized => match &*error.message {
|
AppErrorCode::UserUnAuthorized => match &*error.message {
|
||||||
"Workspace Limit Exceeded" => ErrorCode::WorkspaceLimitExeceeded,
|
"Workspace Limit Exceeded" => ErrorCode::WorkspaceLimitExceeded,
|
||||||
"Workspace Member Limit Exceeded" => ErrorCode::WorkspaceMemberLimitExeceeded,
|
"Workspace Member Limit Exceeded" => ErrorCode::WorkspaceMemberLimitExceeded,
|
||||||
_ => ErrorCode::UserUnauthorized,
|
_ => ErrorCode::UserUnauthorized,
|
||||||
},
|
},
|
||||||
_ => ErrorCode::Internal,
|
_ => ErrorCode::Internal,
|
||||||
|
@ -285,8 +285,7 @@ impl TryInto<CreateViewParams> for CreateOrphanViewPayloadPB {
|
|||||||
meta: Default::default(),
|
meta: Default::default(),
|
||||||
set_as_current: false,
|
set_as_current: false,
|
||||||
index: None,
|
index: None,
|
||||||
// TODO: lucas.xu add section to CreateOrphanViewPayloadPB
|
section: None,
|
||||||
section: Some(ViewSectionPB::Public),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ pub(crate) async fn get_workspace_views_handler(
|
|||||||
) -> DataResult<RepeatedViewPB, FlowyError> {
|
) -> DataResult<RepeatedViewPB, FlowyError> {
|
||||||
let folder = upgrade_folder(folder)?;
|
let folder = upgrade_folder(folder)?;
|
||||||
let params: GetWorkspaceViewParams = data.into_inner().try_into()?;
|
let params: GetWorkspaceViewParams = data.into_inner().try_into()?;
|
||||||
let child_views = folder.get_workspace_views(¶ms.value).await?;
|
let child_views = folder.get_workspace_public_views(¶ms.value).await?;
|
||||||
let repeated_view: RepeatedViewPB = child_views.into();
|
let repeated_view: RepeatedViewPB = child_views.into();
|
||||||
data_result_ok(repeated_view)
|
data_result_ok(repeated_view)
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ pub(crate) async fn get_current_workspace_views_handler(
|
|||||||
folder: AFPluginState<Weak<FolderManager>>,
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
) -> DataResult<RepeatedViewPB, FlowyError> {
|
) -> DataResult<RepeatedViewPB, FlowyError> {
|
||||||
let folder = upgrade_folder(folder)?;
|
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();
|
let repeated_view: RepeatedViewPB = child_views.into();
|
||||||
data_result_ok(repeated_view)
|
data_result_ok(repeated_view)
|
||||||
}
|
}
|
||||||
@ -286,7 +286,7 @@ pub(crate) async fn read_trash_handler(
|
|||||||
folder: AFPluginState<Weak<FolderManager>>,
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
) -> DataResult<RepeatedTrashPB, FlowyError> {
|
) -> DataResult<RepeatedTrashPB, FlowyError> {
|
||||||
let folder = upgrade_folder(folder)?;
|
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())
|
data_result_ok(trash.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,11 +323,11 @@ pub(crate) async fn restore_all_trash_handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(folder), err)]
|
#[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>>,
|
folder: AFPluginState<Weak<FolderManager>>,
|
||||||
) -> Result<(), FlowyError> {
|
) -> Result<(), FlowyError> {
|
||||||
let folder = upgrade_folder(folder)?;
|
let folder = upgrade_folder(folder)?;
|
||||||
folder.delete_all_trash().await;
|
folder.delete_my_trash().await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
|
|||||||
.event(FolderEvent::RestoreTrashItem, putback_trash_handler)
|
.event(FolderEvent::RestoreTrashItem, putback_trash_handler)
|
||||||
.event(FolderEvent::PermanentlyDeleteTrashItem, delete_trash_handler)
|
.event(FolderEvent::PermanentlyDeleteTrashItem, delete_trash_handler)
|
||||||
.event(FolderEvent::RecoverAllTrashItems, restore_all_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::ImportData, import_data_handler)
|
||||||
.event(FolderEvent::GetFolderSnapshots, get_folder_snapshots_handler)
|
.event(FolderEvent::GetFolderSnapshots, get_folder_snapshots_handler)
|
||||||
.event(FolderEvent::UpdateViewIcon, update_view_icon_handler)
|
.event(FolderEvent::UpdateViewIcon, update_view_icon_handler)
|
||||||
|
@ -128,7 +128,7 @@ impl FolderManager {
|
|||||||
|
|
||||||
/// Return a list of views of the current workspace.
|
/// Return a list of views of the current workspace.
|
||||||
/// Only the first level of child views are included.
|
/// 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
|
let workspace_id = self
|
||||||
.mutex_folder
|
.mutex_folder
|
||||||
.lock()
|
.lock()
|
||||||
@ -136,14 +136,14 @@ impl FolderManager {
|
|||||||
.map(|folder| folder.get_workspace_id());
|
.map(|folder| folder.get_workspace_id());
|
||||||
|
|
||||||
if let Some(workspace_id) = 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 {
|
} else {
|
||||||
tracing::warn!("Can't get current workspace views");
|
tracing::warn!("Can't get current workspace views");
|
||||||
Ok(vec![])
|
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| {
|
let views = self.with_folder(Vec::new, |folder| {
|
||||||
get_workspace_public_view_pbs(workspace_id, folder)
|
get_workspace_public_view_pbs(workspace_id, folder)
|
||||||
});
|
});
|
||||||
@ -519,7 +519,7 @@ impl FolderManager {
|
|||||||
let folder = self.mutex_folder.lock();
|
let folder = self.mutex_folder.lock();
|
||||||
let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
|
let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
|
||||||
let trash_ids = folder
|
let trash_ids = folder
|
||||||
.get_all_trash()
|
.get_all_trash_sections()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|trash| trash.id)
|
.map(|trash| trash.id)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
@ -559,7 +559,7 @@ impl FolderManager {
|
|||||||
|folder| {
|
|folder| {
|
||||||
if let Some(view) = folder.views.get_view(view_id) {
|
if let Some(view) = folder.views.get_view(view_id) {
|
||||||
self.unfavorite_view_and_decendants(view.clone(), folder);
|
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
|
// notify the parent view that the view is moved to trash
|
||||||
send_notification(view_id, FolderNotification::DidMoveViewToTrash)
|
send_notification(view_id, FolderNotification::DidMoveViewToTrash)
|
||||||
.payload(DeletedViewPB {
|
.payload(DeletedViewPB {
|
||||||
@ -590,7 +590,7 @@ impl FolderManager {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !favorite_descendant_views.is_empty() {
|
if !favorite_descendant_views.is_empty() {
|
||||||
folder.delete_favorites(
|
folder.delete_favorite_view_ids(
|
||||||
favorite_descendant_views
|
favorite_descendant_views
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| v.id.clone())
|
.map(|v| v.id.clone())
|
||||||
@ -754,6 +754,16 @@ impl FolderManager {
|
|||||||
None
|
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 {
|
let duplicate_params = CreateViewParams {
|
||||||
parent_view_id: view.parent_view_id.clone(),
|
parent_view_id: view.parent_view_id.clone(),
|
||||||
name: format!("{} (copy)", &view.name),
|
name: format!("{} (copy)", &view.name),
|
||||||
@ -764,8 +774,7 @@ impl FolderManager {
|
|||||||
meta: Default::default(),
|
meta: Default::default(),
|
||||||
set_as_current: true,
|
set_as_current: true,
|
||||||
index,
|
index,
|
||||||
// TODO: lucas.xu fetch the section from the view
|
section: Some(section),
|
||||||
section: Some(ViewSectionPB::Public),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.create_view_with_params(duplicate_params).await?;
|
self.create_view_with_params(duplicate_params).await?;
|
||||||
@ -801,9 +810,9 @@ impl FolderManager {
|
|||||||
|folder| {
|
|folder| {
|
||||||
if let Some(old_view) = folder.views.get_view(view_id) {
|
if let Some(old_view) = folder.views.get_view(view_id) {
|
||||||
if old_view.is_favorite {
|
if old_view.is_favorite {
|
||||||
folder.delete_favorites(vec![view_id.to_string()]);
|
folder.delete_favorite_view_ids(vec![view_id.to_string()]);
|
||||||
} else {
|
} 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))]
|
#[tracing::instrument(level = "trace", skip(self))]
|
||||||
pub(crate) async fn get_all_trash(&self) -> Vec<TrashInfo> {
|
pub(crate) async fn get_my_trash_info(&self) -> Vec<TrashInfo> {
|
||||||
self.with_folder(Vec::new, |folder| folder.get_all_trash())
|
self.with_folder(Vec::new, |folder| folder.get_my_trash_info())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
#[tracing::instrument(level = "trace", skip(self))]
|
||||||
@ -887,7 +896,7 @@ impl FolderManager {
|
|||||||
self.with_folder(
|
self.with_folder(
|
||||||
|| (),
|
|| (),
|
||||||
|folder| {
|
|folder| {
|
||||||
folder.remote_all_trash();
|
folder.remove_all_my_trash_sections();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
send_notification("trash", FolderNotification::DidUpdateTrash)
|
send_notification("trash", FolderNotification::DidUpdateTrash)
|
||||||
@ -900,15 +909,15 @@ impl FolderManager {
|
|||||||
self.with_folder(
|
self.with_folder(
|
||||||
|| (),
|
|| (),
|
||||||
|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.
|
/// Delete all the trash permanently.
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
#[tracing::instrument(level = "trace", skip(self))]
|
||||||
pub(crate) async fn delete_all_trash(&self) {
|
pub(crate) async fn delete_my_trash(&self) {
|
||||||
let deleted_trash = self.with_folder(Vec::new, |folder| folder.get_all_trash());
|
let deleted_trash = self.with_folder(Vec::new, |folder| folder.get_my_trash_info());
|
||||||
for trash in deleted_trash {
|
for trash in deleted_trash {
|
||||||
let _ = self.delete_trash(&trash.id).await;
|
let _ = self.delete_trash(&trash.id).await;
|
||||||
}
|
}
|
||||||
@ -926,7 +935,7 @@ impl FolderManager {
|
|||||||
self.with_folder(
|
self.with_folder(
|
||||||
|| (),
|
|| (),
|
||||||
|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]);
|
folder.views.delete_views(vec![view_id]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -977,8 +986,7 @@ impl FolderManager {
|
|||||||
meta: Default::default(),
|
meta: Default::default(),
|
||||||
set_as_current: false,
|
set_as_current: false,
|
||||||
index: None,
|
index: None,
|
||||||
// TODO: Lucas.xu fetch the section from the view
|
section: None,
|
||||||
section: Some(ViewSectionPB::Public),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
||||||
@ -1117,14 +1125,14 @@ impl FolderManager {
|
|||||||
fn get_sections(&self, section_type: Section) -> Vec<SectionItem> {
|
fn get_sections(&self, section_type: Section) -> Vec<SectionItem> {
|
||||||
self.with_folder(Vec::new, |folder| {
|
self.with_folder(Vec::new, |folder| {
|
||||||
let trash_ids = folder
|
let trash_ids = folder
|
||||||
.get_all_trash()
|
.get_all_trash_sections()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|trash| trash.id)
|
.map(|trash| trash.id)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let mut views = match section_type {
|
let mut views = match section_type {
|
||||||
Section::Favorite => folder.get_all_favorites(),
|
Section::Favorite => folder.get_my_favorite_sections(),
|
||||||
Section::Recent => folder.get_all_recent_sections(),
|
Section::Recent => folder.get_my_recent_sections(),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1139,14 +1147,14 @@ impl FolderManager {
|
|||||||
pub(crate) fn get_workspace_public_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
|
pub(crate) fn get_workspace_public_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
|
||||||
// get the trash ids
|
// get the trash ids
|
||||||
let trash_ids = folder
|
let trash_ids = folder
|
||||||
.get_all_trash()
|
.get_all_trash_sections()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|trash| trash.id)
|
.map(|trash| trash.id)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
// get the private view ids
|
// get the private view ids
|
||||||
let private_view_ids = folder
|
let private_view_ids = folder
|
||||||
.get_all_private_views()
|
.get_all_private_sections()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|view| view.id)
|
.map(|view| view.id)
|
||||||
.collect::<Vec<String>>();
|
.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> {
|
pub(crate) fn get_workspace_private_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
|
||||||
// get the trash ids
|
// get the trash ids
|
||||||
let trash_ids = folder
|
let trash_ids = folder
|
||||||
.get_all_trash()
|
.get_all_trash_sections()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|trash| trash.id)
|
.map(|trash| trash.id)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
// get the private view ids
|
// get the private view ids
|
||||||
let private_view_ids = folder
|
let private_view_ids = folder
|
||||||
.get_my_private_views()
|
.get_my_private_sections()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|view| view.id)
|
.map(|view| view.id)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
@ -125,7 +125,7 @@ pub(crate) fn subscribe_folder_trash_changed(
|
|||||||
unique_ids.insert(view.parent_view_id.clone());
|
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)
|
send_notification("trash", FolderNotification::DidUpdateTrash)
|
||||||
.payload(repeated_trash)
|
.payload(repeated_trash)
|
||||||
.send();
|
.send();
|
||||||
@ -150,7 +150,7 @@ pub(crate) fn notify_parent_view_did_change<T: AsRef<str>>(
|
|||||||
let folder = folder.as_ref()?;
|
let folder = folder.as_ref()?;
|
||||||
let workspace_id = folder.get_workspace_id();
|
let workspace_id = folder.get_workspace_id();
|
||||||
let trash_ids = folder
|
let trash_ids = folder
|
||||||
.get_all_trash()
|
.get_all_trash_sections()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|trash| trash.id)
|
.map(|trash| trash.id)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
@ -42,7 +42,7 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
|
|||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
if !favorite_view_ids.is_empty() {
|
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();
|
let encode = folder.encode_collab_v1();
|
||||||
|
@ -38,7 +38,7 @@ impl UserDataMigration for WorkspaceTrashMapToSectionMigration {
|
|||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
if !trash_ids.is_empty() {
|
if !trash_ids.is_empty() {
|
||||||
folder.add_trash(trash_ids);
|
folder.add_trash_view_ids(trash_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
let encode = folder.encode_collab_v1();
|
let encode = folder.encode_collab_v1();
|
||||||
|
Loading…
Reference in New Issue
Block a user