mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support private section (#4882)
This commit is contained in:
parent
9201cd6347
commit
ef9891abfe
5
frontend/.vscode/launch.json
vendored
5
frontend/.vscode/launch.json
vendored
@ -115,9 +115,12 @@
|
||||
},
|
||||
{
|
||||
"name": "AF-desktop: Debug Rust",
|
||||
"request": "attach",
|
||||
"type": "lldb",
|
||||
"request": "attach",
|
||||
"pid": "${command:pickMyProcess}"
|
||||
// To launch the application directly, use the following configuration:
|
||||
// "request": "launch",
|
||||
// "program": "[YOUR_APPLICATION_PATH]",
|
||||
},
|
||||
{
|
||||
// https://tauri.app/v1/guides/debugging/vs-code
|
||||
|
@ -1,6 +1,7 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@ -14,8 +15,9 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../shared/dir.dart';
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
|
@ -28,15 +28,16 @@ void main() {
|
||||
final email = '${uuid()}@appflowy.io';
|
||||
|
||||
testWidgets('change name and icon', (tester) async {
|
||||
// only run the test when the feature flag is on
|
||||
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
email: email, // use the same email to check the next test
|
||||
);
|
||||
|
||||
// turn on the collaborative workspace feature flag before testing,
|
||||
// if the feature is released to the public, this step can be removed
|
||||
await FeatureFlag.collaborativeWorkspace.turnOn();
|
||||
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
@ -57,15 +58,16 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('verify the result again after relaunching', (tester) async {
|
||||
// only run the test when the feature flag is on
|
||||
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
email: email, // use the same email to check the next test
|
||||
);
|
||||
|
||||
// turn on the collaborative workspace feature flag before testing,
|
||||
// if the feature is released to the public, this step can be removed
|
||||
await FeatureFlag.collaborativeWorkspace.turnOn();
|
||||
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
|
@ -35,14 +35,14 @@ void main() {
|
||||
final email = '${uuid()}@appflowy.io';
|
||||
|
||||
group('collaborative workspace', () {
|
||||
// only run the test when the feature flag is on
|
||||
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// combine the create and delete workspace test to reduce the time
|
||||
testWidgets('create a new workspace, open it and then delete it',
|
||||
(tester) async {
|
||||
// only run the test when the feature flag is on
|
||||
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
email: email,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@ -13,10 +13,10 @@ void main() {
|
||||
|
||||
group('sidebar expand test', () {
|
||||
bool isExpanded({required FolderCategoryType type}) {
|
||||
if (type == FolderCategoryType.personal) {
|
||||
if (type == FolderCategoryType.private) {
|
||||
return find
|
||||
.descendant(
|
||||
of: find.byType(PersonalFolder),
|
||||
of: find.byType(PrivateSectionFolder),
|
||||
matching: find.byType(ViewItem),
|
||||
)
|
||||
.evaluate()
|
||||
@ -30,19 +30,19 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// first time is expanded
|
||||
expect(isExpanded(type: FolderCategoryType.personal), true);
|
||||
expect(isExpanded(type: FolderCategoryType.private), true);
|
||||
|
||||
// collapse the personal folder
|
||||
await tester.tapButton(
|
||||
find.byTooltip(LocaleKeys.sideBar_clickToHidePersonal.tr()),
|
||||
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
|
||||
);
|
||||
expect(isExpanded(type: FolderCategoryType.personal), false);
|
||||
expect(isExpanded(type: FolderCategoryType.private), false);
|
||||
|
||||
// expand the personal folder
|
||||
await tester.tapButton(
|
||||
find.byTooltip(LocaleKeys.sideBar_clickToHidePersonal.tr()),
|
||||
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
|
||||
);
|
||||
expect(isExpanded(type: FolderCategoryType.personal), true);
|
||||
expect(isExpanded(type: FolderCategoryType.private), true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.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/view/view_item.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'sidebar_expand_test.dart' as sidebar_expanded_test;
|
||||
import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
|
||||
import 'sidebar_icon_test.dart' as sidebar_icon_test;
|
||||
import 'sidebar_test.dart' as sidebar_test;
|
||||
@ -10,7 +9,7 @@ void startTesting() {
|
||||
|
||||
// Sidebar integration tests
|
||||
sidebar_test.main();
|
||||
sidebar_expanded_test.main();
|
||||
// sidebar_expanded_test.main();
|
||||
sidebar_favorite_test.main();
|
||||
sidebar_icon_test.main();
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_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/menu/sidebar_root_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
||||
// Contains Public And Private Sections
|
||||
class MobileFolders extends StatelessWidget {
|
||||
const MobileFolders({
|
||||
super.key,
|
||||
@ -26,9 +30,9 @@ class MobileFolders extends StatelessWidget {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) => SidebarRootViewsBloc()
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarRootViewsEvent.initial(
|
||||
SidebarSectionsEvent.initial(
|
||||
user,
|
||||
workspaceSetting.workspaceId,
|
||||
),
|
||||
@ -38,30 +42,45 @@ class MobileFolders extends StatelessWidget {
|
||||
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
|
||||
),
|
||||
],
|
||||
child: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||
listenWhen: (p, c) =>
|
||||
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||
listener: (context, state) =>
|
||||
context.pushView(state.lastCreatedRootView!),
|
||||
),
|
||||
],
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final menuState = context.watch<SidebarRootViewsBloc>().state;
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: Column(
|
||||
children: [
|
||||
MobilePersonalFolder(
|
||||
views: menuState.views,
|
||||
),
|
||||
const VSpace(8.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: BlocConsumer<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
listenWhen: (p, c) =>
|
||||
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||
listener: (context, state) {
|
||||
final lastCreatedRootView = state.lastCreatedRootView;
|
||||
if (lastCreatedRootView != null) {
|
||||
context.pushView(lastCreatedRootView);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
final isCollaborativeWorkspace =
|
||||
user.authenticator != AuthenticatorPB.Local &&
|
||||
FeatureFlag.collaborativeWorkspace.isOn;
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: Column(
|
||||
children: [
|
||||
...isCollaborativeWorkspace
|
||||
? [
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_public.tr(),
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
const VSpace(8.0),
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_private.tr(),
|
||||
views: state.section.privateViews,
|
||||
),
|
||||
]
|
||||
: [
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_personal.tr(),
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
],
|
||||
const VSpace(8.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||
@ -15,6 +16,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -82,55 +84,68 @@ class MobileHomePage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// Header
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: Platform.isAndroid ? 8.0 : 0.0,
|
||||
),
|
||||
child: MobileHomePageHeader(
|
||||
userProfile: userProfile,
|
||||
),
|
||||
return BlocProvider(
|
||||
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
||||
..add(
|
||||
const UserWorkspaceEvent.initial(),
|
||||
),
|
||||
const Divider(),
|
||||
|
||||
// Folder
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Recent files
|
||||
const MobileRecentFolder(),
|
||||
|
||||
// Folders
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: MobileFolders(
|
||||
user: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
showFavorite: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: _TrashButton(),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.currentWorkspace?.workspaceId !=
|
||||
current.currentWorkspace?.workspaceId,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
// Header
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: Platform.isAndroid ? 8.0 : 0.0,
|
||||
),
|
||||
child: MobileHomePageHeader(
|
||||
userProfile: userProfile,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const Divider(),
|
||||
|
||||
// Folder
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Recent files
|
||||
const MobileRecentFolder(),
|
||||
|
||||
// Folders
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: MobileFolders(
|
||||
user: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
showFavorite: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: _TrashButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,13 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -14,7 +17,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileHomePageHeader extends StatelessWidget {
|
||||
const MobileHomePageHeader({super.key, required this.userProfile});
|
||||
const MobileHomePageHeader({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@ -25,29 +31,18 @@ class MobileHomePageHeader extends StatelessWidget {
|
||||
..add(const SettingsUserEvent.initial()),
|
||||
child: BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
|
||||
builder: (context, state) {
|
||||
final userIcon = state.userProfile.iconUrl;
|
||||
final isCollaborativeWorkspace =
|
||||
userProfile.authenticator != AuthenticatorPB.Local &&
|
||||
FeatureFlag.collaborativeWorkspace.isOn;
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 52),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_UserIcon(userIcon: userIcon),
|
||||
const HSpace(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const FlowyText.medium('AppFlowy', fontSize: 18),
|
||||
const VSpace(4),
|
||||
FlowyText.regular(
|
||||
userProfile.email.isNotEmpty
|
||||
? state.userProfile.email
|
||||
: state.userProfile.name,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: isCollaborativeWorkspace
|
||||
? _MobileWorkspace(userProfile: userProfile)
|
||||
: _MobileUser(userProfile: userProfile),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
@ -63,6 +58,83 @@ class MobileHomePageHeader extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _MobileUser extends StatelessWidget {
|
||||
const _MobileUser({
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userIcon = userProfile.iconUrl;
|
||||
return Row(
|
||||
children: [
|
||||
_UserIcon(userIcon: userIcon),
|
||||
const HSpace(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const FlowyText.medium('AppFlowy', fontSize: 18),
|
||||
const VSpace(4),
|
||||
FlowyText.regular(
|
||||
userProfile.email.isNotEmpty
|
||||
? userProfile.email
|
||||
: userProfile.name,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MobileWorkspace extends StatelessWidget {
|
||||
const _MobileWorkspace({
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
builder: (context, state) {
|
||||
final currentWorkspace = state.currentWorkspace;
|
||||
final workspaces = state.workspaces;
|
||||
if (currentWorkspace == null || workspaces.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
const HSpace(2.0),
|
||||
SizedBox.square(
|
||||
dimension: 34.0,
|
||||
child: WorkspaceIcon(
|
||||
workspace: currentWorkspace,
|
||||
iconSize: 26,
|
||||
enableEdit: false,
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
currentWorkspace.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UserIcon extends StatelessWidget {
|
||||
const _UserIcon({
|
||||
required this.userIcon,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/personal_folder/mobile_home_personal_folder_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@ -9,18 +9,20 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobilePersonalFolder extends StatelessWidget {
|
||||
const MobilePersonalFolder({
|
||||
class MobileSectionFolder extends StatelessWidget {
|
||||
const MobileSectionFolder({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.views,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final List<ViewPB> views;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<FolderBloc>(
|
||||
create: (context) => FolderBloc(type: FolderCategoryType.personal)
|
||||
create: (context) => FolderBloc(type: FolderCategoryType.private)
|
||||
..add(
|
||||
const FolderEvent.initial(),
|
||||
),
|
||||
@ -28,7 +30,8 @@ class MobilePersonalFolder extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
MobilePersonalFolderHeader(
|
||||
MobileSectionFolderHeader(
|
||||
title: title,
|
||||
isExpanded: context.read<FolderBloc>().state.isExpanded,
|
||||
onPressed: () => context
|
||||
.read<FolderBloc>()
|
||||
@ -45,9 +48,9 @@ class MobilePersonalFolder extends StatelessWidget {
|
||||
...views.map(
|
||||
(view) => MobileViewItem(
|
||||
key: ValueKey(
|
||||
'${FolderCategoryType.personal.name} ${view.id}',
|
||||
'${FolderCategoryType.private.name} ${view.id}',
|
||||
),
|
||||
categoryType: FolderCategoryType.personal,
|
||||
categoryType: FolderCategoryType.private,
|
||||
isFirstChild: view.id == views.first.id,
|
||||
view: view,
|
||||
level: 0,
|
@ -1,30 +1,32 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobilePersonalFolderHeader extends StatefulWidget {
|
||||
const MobilePersonalFolderHeader({
|
||||
class MobileSectionFolderHeader extends StatefulWidget {
|
||||
const MobileSectionFolderHeader({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.onPressed,
|
||||
required this.onAdded,
|
||||
required this.isExpanded,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onAdded;
|
||||
final bool isExpanded;
|
||||
|
||||
@override
|
||||
State<MobilePersonalFolderHeader> createState() =>
|
||||
_MobilePersonalFolderHeaderState();
|
||||
State<MobileSectionFolderHeader> createState() =>
|
||||
_MobileSectionFolderHeaderState();
|
||||
}
|
||||
|
||||
class _MobilePersonalFolderHeaderState
|
||||
extends State<MobilePersonalFolderHeader> {
|
||||
class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
|
||||
double _turns = 0;
|
||||
|
||||
@override
|
||||
@ -35,7 +37,7 @@ class _MobilePersonalFolderHeaderState
|
||||
Expanded(
|
||||
child: FlowyButton(
|
||||
text: FlowyText.semibold(
|
||||
LocaleKeys.sideBar_personal.tr(),
|
||||
widget.title,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
@ -71,6 +73,7 @@ class _MobilePersonalFolderHeaderState
|
||||
SidebarRootViewsEvent.createRootView(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
index: 0,
|
||||
viewSection: ViewSectionPB.Private,
|
||||
),
|
||||
);
|
||||
},
|
@ -4,7 +4,7 @@ import 'package:appflowy/mobile/presentation/notifications/widgets/mobile_notifi
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/notification_filter/notification_filter_bloc.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/reminder_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/inbox_action_bar.dart';
|
||||
@ -80,15 +80,15 @@ class _NotificationScreenContent extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => SidebarRootViewsBloc()
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarRootViewsEvent.initial(
|
||||
SidebarSectionsEvent.initial(
|
||||
userProfile,
|
||||
workspaceSetting.workspaceId,
|
||||
),
|
||||
),
|
||||
child: BlocBuilder<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||
builder: (context, menuState) =>
|
||||
child: BlocBuilder<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
builder: (context, sectionState) =>
|
||||
BlocBuilder<NotificationFilterBloc, NotificationFilterState>(
|
||||
builder: (context, filterState) =>
|
||||
BlocBuilder<ReminderBloc, ReminderState>(
|
||||
@ -122,7 +122,7 @@ class _NotificationScreenContent extends StatelessWidget {
|
||||
NotificationsView(
|
||||
shownReminders: pastReminders,
|
||||
reminderBloc: reminderBloc,
|
||||
views: menuState.views,
|
||||
views: sectionState.section.publicViews,
|
||||
onAction: _onAction,
|
||||
onDelete: _onDelete,
|
||||
onReadChanged: _onReadChanged,
|
||||
@ -134,7 +134,7 @@ class _NotificationScreenContent extends StatelessWidget {
|
||||
NotificationsView(
|
||||
shownReminders: upcomingReminders,
|
||||
reminderBloc: reminderBloc,
|
||||
views: menuState.views,
|
||||
views: sectionState.section.publicViews,
|
||||
isUpcoming: true,
|
||||
onAction: _onAction,
|
||||
),
|
||||
|
@ -406,6 +406,7 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
|
||||
ViewEvent.createView(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout,
|
||||
section: widget.categoryType.toViewSectionPB,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ import 'package:appflowy/plugins/document/application/editor_transaction_adapter
|
||||
import 'package:appflowy/plugins/trash/application/trash_service.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/util/json_print.dart';
|
||||
import 'package:appflowy/workspace/application/doc/doc_listener.dart';
|
||||
import 'package:appflowy/workspace/application/doc/sync_state_listener.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||
@ -81,30 +82,24 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
final editorState = await _fetchDocumentState();
|
||||
_onViewChanged();
|
||||
_onDocumentChanged();
|
||||
await editorState.fold(
|
||||
final newState = await editorState.fold(
|
||||
(s) async {
|
||||
final result = await getIt<AuthService>().getUser();
|
||||
final userProfilePB = result.fold(
|
||||
(s) => s,
|
||||
(e) => null,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
error: null,
|
||||
editorState: s,
|
||||
isLoading: false,
|
||||
userProfilePB: userProfilePB,
|
||||
),
|
||||
final userProfilePB =
|
||||
await getIt<AuthService>().getUser().toNullable();
|
||||
return state.copyWith(
|
||||
error: null,
|
||||
editorState: s,
|
||||
isLoading: false,
|
||||
userProfilePB: userProfilePB,
|
||||
);
|
||||
},
|
||||
(f) async => emit(
|
||||
state.copyWith(
|
||||
error: f,
|
||||
editorState: null,
|
||||
isLoading: false,
|
||||
),
|
||||
(f) async => state.copyWith(
|
||||
error: f,
|
||||
editorState: null,
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
emit(newState);
|
||||
},
|
||||
moveToTrash: () async {
|
||||
emit(state.copyWith(isDeleted: true));
|
||||
@ -242,21 +237,20 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
}
|
||||
|
||||
void syncDocumentDataPB(DocEventPB docEvent) {
|
||||
// prettyPrintJson(docEvent.toProto3Json());
|
||||
// todo: integrate the document change to the editor
|
||||
// for (final event in docEvent.events) {
|
||||
// for (final blockEvent in event.event) {
|
||||
// switch (blockEvent.command) {
|
||||
// case DeltaTypePB.Inserted:
|
||||
// break;
|
||||
// case DeltaTypePB.Updated:
|
||||
// break;
|
||||
// case DeltaTypePB.Removed:
|
||||
// break;
|
||||
// default:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
prettyPrintJson(docEvent.toProto3Json());
|
||||
for (final event in docEvent.events) {
|
||||
for (final blockEvent in event.event) {
|
||||
switch (blockEvent.command) {
|
||||
case DeltaTypePB.Inserted:
|
||||
break;
|
||||
case DeltaTypePB.Updated:
|
||||
break;
|
||||
case DeltaTypePB.Removed:
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
typedef FeatureFlagMap = Map<FeatureFlag, bool>;
|
||||
|
||||
@ -19,16 +19,22 @@ enum FeatureFlag {
|
||||
|
||||
// used to control the visibility of the members settings
|
||||
// if it's on, you can see the members settings in the settings page
|
||||
membersSettings;
|
||||
membersSettings,
|
||||
|
||||
// used for ignore the conflicted feature flag
|
||||
unknown;
|
||||
|
||||
static Future<void> initialize() async {
|
||||
final values = await getIt<KeyValueStorage>().getWithFormat<FeatureFlagMap>(
|
||||
KVKeys.featureFlag,
|
||||
(value) => Map.from(jsonDecode(value)).map(
|
||||
(key, value) => MapEntry(
|
||||
FeatureFlag.values.firstWhere((e) => e.name == key),
|
||||
value as bool,
|
||||
),
|
||||
(key, value) {
|
||||
final k = FeatureFlag.values.firstWhereOrNull(
|
||||
(e) => e.name == key,
|
||||
) ??
|
||||
FeatureFlag.unknown;
|
||||
return MapEntry(k, value as bool);
|
||||
},
|
||||
),
|
||||
) ??
|
||||
{};
|
||||
@ -76,6 +82,8 @@ enum FeatureFlag {
|
||||
return false;
|
||||
case FeatureFlag.membersSettings:
|
||||
return false;
|
||||
case FeatureFlag.unknown:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +93,8 @@ enum FeatureFlag {
|
||||
return 'if it\'s on, you can see the workspace list and the workspace settings in the top-left corner of the app';
|
||||
case FeatureFlag.membersSettings:
|
||||
return 'if it\'s on, you can see the members settings in the settings page';
|
||||
case FeatureFlag.unknown:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,4 +150,44 @@ class UserBackendService {
|
||||
..newIcon = icon;
|
||||
return UserEventChangeWorkspaceIcon(request).send();
|
||||
}
|
||||
|
||||
Future<FlowyResult<RepeatedWorkspaceMemberPB, FlowyError>>
|
||||
getWorkspaceMembers(
|
||||
String workspaceId,
|
||||
) async {
|
||||
final data = QueryWorkspacePB()..workspaceId = workspaceId;
|
||||
return UserEventGetWorkspaceMember(data).send();
|
||||
}
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> addWorkspaceMember(
|
||||
String workspaceId,
|
||||
String email,
|
||||
) async {
|
||||
final data = AddWorkspaceMemberPB()
|
||||
..workspaceId = workspaceId
|
||||
..email = email;
|
||||
return UserEventAddWorkspaceMember(data).send();
|
||||
}
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> removeWorkspaceMember(
|
||||
String workspaceId,
|
||||
String email,
|
||||
) async {
|
||||
final data = RemoveWorkspaceMemberPB()
|
||||
..workspaceId = workspaceId
|
||||
..email = email;
|
||||
return UserEventRemoveWorkspaceMember(data).send();
|
||||
}
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> updateWorkspaceMember(
|
||||
String workspaceId,
|
||||
String email,
|
||||
AFRolePB role,
|
||||
) async {
|
||||
final data = UpdateWorkspaceMemberPB()
|
||||
..workspaceId = workspaceId
|
||||
..email = email
|
||||
..role = role;
|
||||
return UserEventUpdateWorkspaceMember(data).send();
|
||||
}
|
||||
}
|
||||
|
@ -33,18 +33,19 @@ class SidebarRootViewsBloc
|
||||
await event.when(
|
||||
initial: (userProfile, workspaceId) async {
|
||||
_initial(userProfile, workspaceId);
|
||||
await _fetchApps(emit);
|
||||
await _fetchRootViews(emit);
|
||||
},
|
||||
reset: (userProfile, workspaceId) async {
|
||||
await _listener?.stop();
|
||||
_initial(userProfile, workspaceId);
|
||||
await _fetchApps(emit);
|
||||
await _fetchRootViews(emit);
|
||||
},
|
||||
createRootView: (name, desc, index) async {
|
||||
final result = await _workspaceService.createApp(
|
||||
createRootView: (name, desc, index, section) async {
|
||||
final result = await _workspaceService.createView(
|
||||
name: name,
|
||||
desc: desc,
|
||||
index: index,
|
||||
viewSection: section,
|
||||
);
|
||||
result.fold(
|
||||
(view) => emit(state.copyWith(lastCreatedRootView: view)),
|
||||
@ -59,48 +60,59 @@ class SidebarRootViewsBloc
|
||||
);
|
||||
},
|
||||
didReceiveViews: (viewsOrFailure) async {
|
||||
emit(
|
||||
viewsOrFailure.fold(
|
||||
(views) => state.copyWith(
|
||||
views: views,
|
||||
successOrFailure: FlowyResult.success(null),
|
||||
),
|
||||
(err) =>
|
||||
state.copyWith(successOrFailure: FlowyResult.failure(err)),
|
||||
),
|
||||
);
|
||||
// emit(
|
||||
// viewsOrFailure.fold(
|
||||
// (views) => state.copyWith(
|
||||
// views: views,
|
||||
// successOrFailure: FlowyResult.success(null),
|
||||
// ),
|
||||
// (err) =>
|
||||
// state.copyWith(successOrFailure: FlowyResult.failure(err)),
|
||||
// ),
|
||||
// );
|
||||
},
|
||||
moveRootView: (int fromIndex, int toIndex) {
|
||||
if (state.views.length > fromIndex) {
|
||||
final view = state.views[fromIndex];
|
||||
// if (state.views.length > fromIndex) {
|
||||
// final view = state.views[fromIndex];
|
||||
|
||||
_workspaceService.moveApp(
|
||||
appId: view.id,
|
||||
fromIndex: fromIndex,
|
||||
toIndex: toIndex,
|
||||
);
|
||||
// _workspaceService.moveApp(
|
||||
// appId: view.id,
|
||||
// fromIndex: fromIndex,
|
||||
// toIndex: toIndex,
|
||||
// );
|
||||
|
||||
final views = List<ViewPB>.from(state.views);
|
||||
views.insert(toIndex, views.removeAt(fromIndex));
|
||||
emit(state.copyWith(views: views));
|
||||
}
|
||||
// final views = List<ViewPB>.from(state.views);
|
||||
// views.insert(toIndex, views.removeAt(fromIndex));
|
||||
// emit(state.copyWith(views: views));
|
||||
// }
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fetchApps(Emitter<SidebarRootViewState> emit) async {
|
||||
final viewsOrError = await _workspaceService.getViews();
|
||||
emit(
|
||||
viewsOrError.fold(
|
||||
(views) => state.copyWith(views: views),
|
||||
(error) {
|
||||
Log.error(error);
|
||||
return state.copyWith(successOrFailure: FlowyResult.failure(error));
|
||||
},
|
||||
),
|
||||
);
|
||||
Future<void> _fetchRootViews(
|
||||
Emitter<SidebarRootViewState> emit,
|
||||
) async {
|
||||
try {
|
||||
final publicViews = await _workspaceService.getPublicViews().getOrThrow();
|
||||
final privateViews =
|
||||
await _workspaceService.getPrivateViews().getOrThrow();
|
||||
emit(
|
||||
state.copyWith(
|
||||
publicViews: publicViews,
|
||||
privateViews: privateViews,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
// TODO: handle error
|
||||
// emit(
|
||||
// state.copyWith(
|
||||
// successOrFailure: FlowyResult.failure(e),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
void _handleAppsOrFail(FlowyResult<List<ViewPB>, FlowyError> viewsOrFail) {
|
||||
@ -137,9 +149,12 @@ class SidebarRootViewsEvent with _$SidebarRootViewsEvent {
|
||||
String name, {
|
||||
String? desc,
|
||||
int? index,
|
||||
required ViewSectionPB viewSection,
|
||||
}) = _createRootView;
|
||||
const factory SidebarRootViewsEvent.moveRootView(int fromIndex, int toIndex) =
|
||||
_MoveRootView;
|
||||
const factory SidebarRootViewsEvent.moveRootView(
|
||||
int fromIndex,
|
||||
int toIndex,
|
||||
) = _MoveRootView;
|
||||
const factory SidebarRootViewsEvent.didReceiveViews(
|
||||
FlowyResult<List<ViewPB>, FlowyError> appsOrFail,
|
||||
) = _ReceiveApps;
|
||||
@ -148,13 +163,13 @@ class SidebarRootViewsEvent with _$SidebarRootViewsEvent {
|
||||
@freezed
|
||||
class SidebarRootViewState with _$SidebarRootViewState {
|
||||
const factory SidebarRootViewState({
|
||||
required List<ViewPB> views,
|
||||
@Default([]) List<ViewPB> privateViews,
|
||||
@Default([]) List<ViewPB> publicViews,
|
||||
required FlowyResult<void, FlowyError> successOrFailure,
|
||||
@Default(null) ViewPB? lastCreatedRootView,
|
||||
}) = _SidebarRootViewState;
|
||||
|
||||
factory SidebarRootViewState.initial() => SidebarRootViewState(
|
||||
views: [],
|
||||
successOrFailure: FlowyResult.success(null),
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,261 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/workspace/application/workspace/workspace_sections_listener.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'sidebar_sections_bloc.freezed.dart';
|
||||
|
||||
class SidebarSection {
|
||||
const SidebarSection({
|
||||
required this.publicViews,
|
||||
required this.privateViews,
|
||||
});
|
||||
|
||||
const SidebarSection.empty()
|
||||
: publicViews = const [],
|
||||
privateViews = const [];
|
||||
|
||||
final List<ViewPB> publicViews;
|
||||
final List<ViewPB> privateViews;
|
||||
|
||||
List<ViewPB> get views => publicViews + privateViews;
|
||||
|
||||
SidebarSection copyWith({
|
||||
List<ViewPB>? publicViews,
|
||||
List<ViewPB>? privateViews,
|
||||
}) {
|
||||
return SidebarSection(
|
||||
publicViews: publicViews ?? this.publicViews,
|
||||
privateViews: privateViews ?? this.privateViews,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The [SidebarSectionsBloc] is responsible for
|
||||
/// managing the root views in different sections of the workspace.
|
||||
class SidebarSectionsBloc
|
||||
extends Bloc<SidebarSectionsEvent, SidebarSectionsState> {
|
||||
SidebarSectionsBloc() : super(SidebarSectionsState.initial()) {
|
||||
on<SidebarSectionsEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: (userProfile, workspaceId) async {
|
||||
_initial(userProfile, workspaceId);
|
||||
final sectionViews = await _getSectionViews();
|
||||
if (sectionViews != null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
section: sectionViews,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
reset: (userProfile, workspaceId) async {
|
||||
_reset(userProfile, workspaceId);
|
||||
final sectionViews = await _getSectionViews();
|
||||
if (sectionViews != null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
section: sectionViews,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
createRootViewInSection: (name, section, desc, index) async {
|
||||
final result = await _workspaceService.createView(
|
||||
name: name,
|
||||
viewSection: section,
|
||||
desc: desc,
|
||||
index: index,
|
||||
);
|
||||
result.fold(
|
||||
(view) => emit(
|
||||
state.copyWith(
|
||||
lastCreatedRootView: view,
|
||||
createRootViewResult: FlowyResult.success(null),
|
||||
),
|
||||
),
|
||||
(error) {
|
||||
Log.error('Failed to create root view: $error');
|
||||
emit(
|
||||
state.copyWith(
|
||||
createRootViewResult: FlowyResult.failure(error),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
receiveSectionViewsUpdate: (sectionViews) async {
|
||||
final section = sectionViews.section;
|
||||
switch (section) {
|
||||
case ViewSectionPB.Public:
|
||||
emit(
|
||||
state.copyWith(
|
||||
section: state.section.copyWith(
|
||||
publicViews: sectionViews.views,
|
||||
),
|
||||
),
|
||||
);
|
||||
case ViewSectionPB.Private:
|
||||
emit(
|
||||
state.copyWith(
|
||||
section: state.section.copyWith(
|
||||
privateViews: sectionViews.views,
|
||||
),
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
moveRootView: (fromIndex, toIndex, fromSection, toSection) async {
|
||||
final views = fromSection == ViewSectionPB.Public
|
||||
? List<ViewPB>.from(state.section.publicViews)
|
||||
: List<ViewPB>.from(state.section.privateViews);
|
||||
if (fromIndex < 0 || fromIndex >= views.length) {
|
||||
Log.error(
|
||||
'Invalid fromIndex: $fromIndex, maxIndex: ${views.length - 1}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final view = views[fromIndex];
|
||||
final result = await _workspaceService.moveView(
|
||||
viewId: view.id,
|
||||
fromIndex: fromIndex,
|
||||
toIndex: toIndex,
|
||||
);
|
||||
result.fold(
|
||||
(value) {
|
||||
views.insert(toIndex, views.removeAt(fromIndex));
|
||||
var newState = state;
|
||||
if (fromSection == ViewSectionPB.Public) {
|
||||
newState = newState.copyWith(
|
||||
section: newState.section.copyWith(publicViews: views),
|
||||
);
|
||||
} else if (fromSection == ViewSectionPB.Private) {
|
||||
newState = newState.copyWith(
|
||||
section: newState.section.copyWith(privateViews: views),
|
||||
);
|
||||
}
|
||||
emit(newState);
|
||||
},
|
||||
(error) {
|
||||
Log.error('Failed to move root view: $error');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
late WorkspaceService _workspaceService;
|
||||
WorkspaceSectionsListener? _listener;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _listener?.stop();
|
||||
_listener = null;
|
||||
return super.close();
|
||||
}
|
||||
|
||||
ViewSectionPB? getViewSection(ViewPB view) {
|
||||
final publicViews = state.section.publicViews.map((e) => e.id);
|
||||
final privateViews = state.section.privateViews.map((e) => e.id);
|
||||
if (publicViews.contains(view.id)) {
|
||||
return ViewSectionPB.Public;
|
||||
} else if (privateViews.contains(view.id)) {
|
||||
return ViewSectionPB.Private;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<SidebarSection?> _getSectionViews() async {
|
||||
try {
|
||||
final publicViews = await _workspaceService.getPublicViews().getOrThrow();
|
||||
final privateViews =
|
||||
await _workspaceService.getPrivateViews().getOrThrow();
|
||||
return SidebarSection(
|
||||
publicViews: publicViews,
|
||||
privateViews: privateViews,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('Failed to get section views: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _initial(UserProfilePB userProfile, String workspaceId) {
|
||||
_workspaceService = WorkspaceService(workspaceId: workspaceId);
|
||||
|
||||
_listener = WorkspaceSectionsListener(
|
||||
user: userProfile,
|
||||
workspaceId: workspaceId,
|
||||
)..start(
|
||||
sectionChanged: (result) {
|
||||
if (!isClosed) {
|
||||
result.fold(
|
||||
(s) => add(SidebarSectionsEvent.receiveSectionViewsUpdate(s)),
|
||||
(f) => Log.error('Failed to receive section views: $f'),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _reset(UserProfilePB userProfile, String workspaceId) {
|
||||
_listener?.stop();
|
||||
_listener = null;
|
||||
|
||||
_initial(userProfile, workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SidebarSectionsEvent with _$SidebarSectionsEvent {
|
||||
const factory SidebarSectionsEvent.initial(
|
||||
UserProfilePB userProfile,
|
||||
String workspaceId,
|
||||
) = _Initial;
|
||||
const factory SidebarSectionsEvent.reset(
|
||||
UserProfilePB userProfile,
|
||||
String workspaceId,
|
||||
) = _Reset;
|
||||
const factory SidebarSectionsEvent.createRootViewInSection({
|
||||
required String name,
|
||||
required ViewSectionPB viewSection,
|
||||
String? desc,
|
||||
int? index,
|
||||
}) = _CreateRootViewInSection;
|
||||
const factory SidebarSectionsEvent.moveRootView({
|
||||
required int fromIndex,
|
||||
required int toIndex,
|
||||
required ViewSectionPB fromSection,
|
||||
required ViewSectionPB toSection,
|
||||
}) = _MoveRootView;
|
||||
const factory SidebarSectionsEvent.receiveSectionViewsUpdate(
|
||||
SectionViewsPB sectionViews,
|
||||
) = _ReceiveSectionViewsUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SidebarSectionsState with _$SidebarSectionsState {
|
||||
const factory SidebarSectionsState({
|
||||
required SidebarSection section,
|
||||
@Default(null) ViewPB? lastCreatedRootView,
|
||||
FlowyResult<void, FlowyError>? createRootViewResult,
|
||||
}) = _SidebarSectionsState;
|
||||
|
||||
factory SidebarSectionsState.initial() => const SidebarSectionsState(
|
||||
section: SidebarSection.empty(),
|
||||
);
|
||||
}
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@ -10,7 +11,19 @@ part 'folder_bloc.freezed.dart';
|
||||
|
||||
enum FolderCategoryType {
|
||||
favorite,
|
||||
personal,
|
||||
private,
|
||||
public;
|
||||
|
||||
ViewSectionPB get toViewSectionPB {
|
||||
switch (this) {
|
||||
case FolderCategoryType.private:
|
||||
return ViewSectionPB.Private;
|
||||
case FolderCategoryType.public:
|
||||
return ViewSectionPB.Public;
|
||||
case FolderCategoryType.favorite:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FolderBloc extends Bloc<FolderEvent, FolderState> {
|
||||
|
@ -2,7 +2,7 @@ import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -20,14 +20,20 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
// do nothing
|
||||
add(const FetchWorkspaces());
|
||||
},
|
||||
workspacesReceived: (workspaceId) async {},
|
||||
fetchWorkspaces: () async {
|
||||
final result = await _fetchWorkspaces();
|
||||
if (result != null) {
|
||||
final members = await _userService
|
||||
.getWorkspaceMembers(
|
||||
result.$1.workspaceId,
|
||||
)
|
||||
.fold((s) => s.items.length, (f) => -1);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isCollaborativeWorkspace: members > 1,
|
||||
currentWorkspace: result.$1,
|
||||
workspaces: result.$2,
|
||||
),
|
||||
@ -258,7 +264,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
||||
workspaces.firstWhere((e) => e.workspaceId == currentWorkspace.id);
|
||||
return (currentWorkspaceInList, workspaces);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
Log.error('fetch workspace error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -292,6 +298,7 @@ class UserWorkspaceState with _$UserWorkspaceState {
|
||||
const factory UserWorkspaceState({
|
||||
required UserWorkspacePB? currentWorkspace,
|
||||
required List<UserWorkspacePB> workspaces,
|
||||
@Default(false) bool isCollaborativeWorkspace,
|
||||
@Default(null) FlowyResult<void, FlowyError>? createWorkspaceResult,
|
||||
@Default(null) FlowyResult<void, FlowyError>? deleteWorkspaceResult,
|
||||
@Default(null) FlowyResult<void, FlowyError>? openWorkspaceResult,
|
||||
|
@ -165,6 +165,8 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
viewId: value.from.id,
|
||||
newParentId: value.newParentId,
|
||||
prevViewId: value.prevId,
|
||||
fromSection: value.fromSection,
|
||||
toSection: value.toSection,
|
||||
);
|
||||
emit(
|
||||
result.fold(
|
||||
@ -184,8 +186,8 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
||||
layoutType: e.layoutType,
|
||||
ext: {},
|
||||
openAfterCreate: e.openAfterCreated,
|
||||
section: e.section,
|
||||
);
|
||||
|
||||
emit(
|
||||
result.fold(
|
||||
(view) => state.copyWith(
|
||||
@ -353,12 +355,15 @@ class ViewEvent with _$ViewEvent {
|
||||
ViewPB from,
|
||||
String newParentId,
|
||||
String? prevId,
|
||||
ViewSectionPB? fromSection,
|
||||
ViewSectionPB? toSection,
|
||||
) = Move;
|
||||
const factory ViewEvent.createView(
|
||||
String name,
|
||||
ViewLayoutPB layoutType, {
|
||||
/// open the view after created
|
||||
@Default(true) bool openAfterCreated,
|
||||
required ViewSectionPB section,
|
||||
}) = CreateView;
|
||||
const factory ViewEvent.viewDidUpdate(
|
||||
FlowyResult<ViewPB, FlowyError> result,
|
||||
|
@ -37,6 +37,7 @@ class ViewBackendService {
|
||||
/// The [index] is the index of the view in the parent view.
|
||||
/// If the index is null, the view will be added to the end of the list.
|
||||
int? index,
|
||||
ViewSectionPB? section,
|
||||
}) {
|
||||
final payload = CreateViewPayloadPB.create()
|
||||
..parentViewId = parentViewId
|
||||
@ -58,6 +59,10 @@ class ViewBackendService {
|
||||
payload.index = index;
|
||||
}
|
||||
|
||||
if (section != null) {
|
||||
payload.section = section;
|
||||
}
|
||||
|
||||
return FolderEventCreateView(payload).send();
|
||||
}
|
||||
|
||||
@ -195,11 +200,15 @@ class ViewBackendService {
|
||||
required String viewId,
|
||||
required String newParentId,
|
||||
required String? prevViewId,
|
||||
ViewSectionPB? fromSection,
|
||||
ViewSectionPB? toSection,
|
||||
}) {
|
||||
final payload = MoveNestedViewPayloadPB(
|
||||
viewId: viewId,
|
||||
newParentId: newParentId,
|
||||
prevViewId: prevViewId,
|
||||
fromSection: fromSection,
|
||||
toSection: toSection,
|
||||
);
|
||||
|
||||
return FolderEventMoveNestedView(payload).send();
|
||||
|
@ -11,23 +11,28 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
|
||||
typedef AppListNotifyValue = FlowyResult<List<ViewPB>, FlowyError>;
|
||||
typedef RootViewsNotifyValue = FlowyResult<List<ViewPB>, FlowyError>;
|
||||
typedef WorkspaceNotifyValue = FlowyResult<WorkspacePB, FlowyError>;
|
||||
|
||||
/// The [WorkspaceListener] listens to the changes including the below:
|
||||
///
|
||||
/// - The root views of the workspace. (Not including the views are inside the root views)
|
||||
/// - The workspace itself.
|
||||
class WorkspaceListener {
|
||||
WorkspaceListener({required this.user, required this.workspaceId});
|
||||
|
||||
final UserProfilePB user;
|
||||
final String workspaceId;
|
||||
|
||||
PublishNotifier<AppListNotifyValue>? _appsChangedNotifier = PublishNotifier();
|
||||
PublishNotifier<RootViewsNotifyValue>? _appsChangedNotifier =
|
||||
PublishNotifier();
|
||||
PublishNotifier<WorkspaceNotifyValue>? _workspaceUpdatedNotifier =
|
||||
PublishNotifier();
|
||||
|
||||
FolderNotificationListener? _listener;
|
||||
|
||||
void start({
|
||||
void Function(AppListNotifyValue)? appsChanged,
|
||||
void Function(RootViewsNotifyValue)? appsChanged,
|
||||
void Function(WorkspaceNotifyValue)? onWorkspaceUpdated,
|
||||
}) {
|
||||
if (appsChanged != null) {
|
||||
|
@ -0,0 +1,68 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/core/notification/folder_notification.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/notification.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
show UserProfilePB;
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
|
||||
typedef SectionNotifyValue = FlowyResult<SectionViewsPB, FlowyError>;
|
||||
|
||||
/// The [WorkspaceSectionsListener] listens to the changes including the below:
|
||||
///
|
||||
/// - The root views inside different section of the workspace. (Not including the views are inside the root views)
|
||||
/// depends on the section type(s).
|
||||
class WorkspaceSectionsListener {
|
||||
WorkspaceSectionsListener({
|
||||
required this.user,
|
||||
required this.workspaceId,
|
||||
});
|
||||
|
||||
final UserProfilePB user;
|
||||
final String workspaceId;
|
||||
|
||||
final _sectionNotifier = PublishNotifier<SectionNotifyValue>();
|
||||
late final FolderNotificationListener _listener;
|
||||
|
||||
void start({
|
||||
void Function(SectionNotifyValue)? sectionChanged,
|
||||
}) {
|
||||
if (sectionChanged != null) {
|
||||
_sectionNotifier.addPublishListener(sectionChanged);
|
||||
}
|
||||
|
||||
_listener = FolderNotificationListener(
|
||||
objectId: workspaceId,
|
||||
handler: _handleObservableType,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleObservableType(
|
||||
FolderNotification ty,
|
||||
FlowyResult<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case FolderNotification.DidUpdateSectionViews:
|
||||
final FlowyResult<SectionViewsPB, FlowyError> value = result.fold(
|
||||
(s) => FlowyResult.success(
|
||||
SectionViewsPB.fromBuffer(s),
|
||||
),
|
||||
(f) => FlowyResult.failure(f),
|
||||
);
|
||||
_sectionNotifier.value = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
_sectionNotifier.dispose();
|
||||
|
||||
await _listener.stop();
|
||||
}
|
||||
}
|
@ -2,9 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'
|
||||
show CreateViewPayloadPB, MoveViewPayloadPB, ViewLayoutPB, ViewPB;
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
class WorkspaceService {
|
||||
@ -12,15 +10,18 @@ class WorkspaceService {
|
||||
|
||||
final String workspaceId;
|
||||
|
||||
Future<FlowyResult<ViewPB, FlowyError>> createApp({
|
||||
Future<FlowyResult<ViewPB, FlowyError>> createView({
|
||||
required String name,
|
||||
required ViewSectionPB viewSection,
|
||||
String? desc,
|
||||
int? index,
|
||||
}) {
|
||||
final payload = CreateViewPayloadPB.create()
|
||||
..parentViewId = workspaceId
|
||||
..name = name
|
||||
..layout = ViewLayoutPB.Document;
|
||||
// only allow document layout for the top-level views
|
||||
..layout = ViewLayoutPB.Document
|
||||
..section = viewSection;
|
||||
|
||||
if (desc != null) {
|
||||
payload.desc = desc;
|
||||
@ -37,8 +38,8 @@ class WorkspaceService {
|
||||
return FolderEventReadCurrentWorkspace().send();
|
||||
}
|
||||
|
||||
Future<FlowyResult<List<ViewPB>, FlowyError>> getViews() {
|
||||
final payload = WorkspaceIdPB.create()..value = workspaceId;
|
||||
Future<FlowyResult<List<ViewPB>, FlowyError>> getPublicViews() {
|
||||
final payload = GetWorkspaceViewPB.create()..value = workspaceId;
|
||||
return FolderEventReadWorkspaceViews(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(views) => FlowyResult.success(views.items),
|
||||
@ -47,13 +48,23 @@ class WorkspaceService {
|
||||
});
|
||||
}
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> moveApp({
|
||||
required String appId,
|
||||
Future<FlowyResult<List<ViewPB>, FlowyError>> getPrivateViews() {
|
||||
final payload = GetWorkspaceViewPB.create()..value = workspaceId;
|
||||
return FolderEventReadPrivateViews(payload).send().then((result) {
|
||||
return result.fold(
|
||||
(views) => FlowyResult.success(views.items),
|
||||
(error) => FlowyResult.failure(error),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> moveView({
|
||||
required String viewId,
|
||||
required int fromIndex,
|
||||
required int toIndex,
|
||||
}) {
|
||||
final payload = MoveViewPayloadPB.create()
|
||||
..viewId = appId
|
||||
..viewId = viewId
|
||||
..from = fromIndex
|
||||
..to = toIndex;
|
||||
|
||||
|
@ -0,0 +1,63 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FolderHeader extends StatefulWidget {
|
||||
const FolderHeader({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.expandButtonTooltip,
|
||||
required this.addButtonTooltip,
|
||||
required this.onPressed,
|
||||
required this.onAdded,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String expandButtonTooltip;
|
||||
final String addButtonTooltip;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onAdded;
|
||||
|
||||
@override
|
||||
State<FolderHeader> createState() => _FolderHeaderState();
|
||||
}
|
||||
|
||||
class _FolderHeaderState extends State<FolderHeader> {
|
||||
bool onHover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const iconSize = 26.0;
|
||||
const textPadding = 4.0;
|
||||
return MouseRegion(
|
||||
onEnter: (event) => setState(() => onHover = true),
|
||||
onExit: (event) => setState(() => onHover = false),
|
||||
child: Row(
|
||||
children: [
|
||||
FlowyTextButton(
|
||||
widget.title,
|
||||
tooltip: widget.expandButtonTooltip,
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: iconSize + textPadding * 2,
|
||||
),
|
||||
padding: const EdgeInsets.all(textPadding),
|
||||
fillColor: Colors.transparent,
|
||||
onPressed: widget.onPressed,
|
||||
),
|
||||
if (onHover) ...[
|
||||
const Spacer(),
|
||||
FlowyIconButton(
|
||||
tooltipText: widget.addButtonTooltip,
|
||||
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
iconPadding: const EdgeInsets.all(2),
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
icon: const FlowySvg(FlowySvgs.add_s),
|
||||
onPressed: widget.onAdded,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.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/tabs/tabs_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_folder_header.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SectionFolder extends StatelessWidget {
|
||||
const SectionFolder({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.categoryType,
|
||||
required this.views,
|
||||
this.isHoverEnabled = true,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final FolderCategoryType categoryType;
|
||||
final List<ViewPB> views;
|
||||
final bool isHoverEnabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<FolderBloc>(
|
||||
create: (context) => FolderBloc(type: categoryType)
|
||||
..add(
|
||||
const FolderEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<FolderBloc, FolderState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
FolderHeader(
|
||||
title: title,
|
||||
expandButtonTooltip: expandButtonTooltip,
|
||||
addButtonTooltip: addButtonTooltip,
|
||||
onPressed: () => context
|
||||
.read<FolderBloc>()
|
||||
.add(const FolderEvent.expandOrUnExpand()),
|
||||
onAdded: () {
|
||||
createViewAndShowRenameDialogIfNeeded(
|
||||
context,
|
||||
LocaleKeys.newPageText.tr(),
|
||||
(viewName, _) {
|
||||
if (viewName.isNotEmpty) {
|
||||
context.read<SidebarSectionsBloc>().add(
|
||||
SidebarSectionsEvent.createRootViewInSection(
|
||||
name: viewName,
|
||||
index: 0,
|
||||
viewSection: categoryType.toViewSectionPB,
|
||||
),
|
||||
);
|
||||
|
||||
context.read<FolderBloc>().add(
|
||||
const FolderEvent.expandOrUnExpand(
|
||||
isExpanded: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
if (state.isExpanded)
|
||||
...views.map(
|
||||
(view) => ViewItem(
|
||||
key: ValueKey(
|
||||
'${categoryType.name} ${view.id}',
|
||||
),
|
||||
categoryType: categoryType,
|
||||
isFirstChild: view.id == views.first.id,
|
||||
view: view,
|
||||
level: 0,
|
||||
leftPadding: 16,
|
||||
isFeedback: false,
|
||||
onSelected: (view) {
|
||||
if (HardwareKeyboard.instance.isControlPressed) {
|
||||
context.read<TabsBloc>().openTab(view);
|
||||
}
|
||||
|
||||
context.read<TabsBloc>().openPlugin(view);
|
||||
},
|
||||
onTertiarySelected: (view) =>
|
||||
context.read<TabsBloc>().openTab(view),
|
||||
isHoverEnabled: isHoverEnabled,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String get expandButtonTooltip {
|
||||
return switch (categoryType) {
|
||||
FolderCategoryType.public => LocaleKeys.sideBar_clickToHidePublic.tr(),
|
||||
FolderCategoryType.private => LocaleKeys.sideBar_clickToHidePrivate.tr(),
|
||||
_ => '',
|
||||
};
|
||||
}
|
||||
|
||||
String get addButtonTooltip {
|
||||
return switch (categoryType) {
|
||||
FolderCategoryType.public => LocaleKeys.sideBar_addAPageToPublic.tr(),
|
||||
FolderCategoryType.private => LocaleKeys.sideBar_addAPageToPrivate.tr(),
|
||||
_ => '',
|
||||
};
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class PersonalFolder extends StatelessWidget {
|
||||
const PersonalFolder({
|
||||
super.key,
|
||||
required this.views,
|
||||
this.isHoverEnabled = true,
|
||||
});
|
||||
|
||||
final List<ViewPB> views;
|
||||
final bool isHoverEnabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<FolderBloc>(
|
||||
create: (context) => FolderBloc(type: FolderCategoryType.personal)
|
||||
..add(
|
||||
const FolderEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<FolderBloc, FolderState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
PersonalFolderHeader(
|
||||
onPressed: () => context
|
||||
.read<FolderBloc>()
|
||||
.add(const FolderEvent.expandOrUnExpand()),
|
||||
onAdded: () => context
|
||||
.read<FolderBloc>()
|
||||
.add(const FolderEvent.expandOrUnExpand(isExpanded: true)),
|
||||
),
|
||||
if (state.isExpanded)
|
||||
...views.map(
|
||||
(view) => ViewItem(
|
||||
key: ValueKey(
|
||||
'${FolderCategoryType.personal.name} ${view.id}',
|
||||
),
|
||||
categoryType: FolderCategoryType.personal,
|
||||
isFirstChild: view.id == views.first.id,
|
||||
view: view,
|
||||
level: 0,
|
||||
leftPadding: 16,
|
||||
isFeedback: false,
|
||||
onSelected: (view) {
|
||||
if (HardwareKeyboard.instance.isControlPressed) {
|
||||
context.read<TabsBloc>().openTab(view);
|
||||
}
|
||||
|
||||
context.read<TabsBloc>().openPlugin(view);
|
||||
},
|
||||
onTertiarySelected: (view) =>
|
||||
context.read<TabsBloc>().openTab(view),
|
||||
isHoverEnabled: isHoverEnabled,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PersonalFolderHeader extends StatefulWidget {
|
||||
const PersonalFolderHeader({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.onAdded,
|
||||
});
|
||||
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onAdded;
|
||||
|
||||
@override
|
||||
State<PersonalFolderHeader> createState() => _PersonalFolderHeaderState();
|
||||
}
|
||||
|
||||
class _PersonalFolderHeaderState extends State<PersonalFolderHeader> {
|
||||
bool onHover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const iconSize = 26.0;
|
||||
const textPadding = 4.0;
|
||||
return MouseRegion(
|
||||
onEnter: (event) => setState(() => onHover = true),
|
||||
onExit: (event) => setState(() => onHover = false),
|
||||
child: Row(
|
||||
children: [
|
||||
FlowyTextButton(
|
||||
LocaleKeys.sideBar_personal.tr(),
|
||||
tooltip: LocaleKeys.sideBar_clickToHidePersonal.tr(),
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: iconSize + textPadding * 2,
|
||||
),
|
||||
padding: const EdgeInsets.all(textPadding),
|
||||
fillColor: Colors.transparent,
|
||||
onPressed: widget.onPressed,
|
||||
),
|
||||
if (onHover) ...[
|
||||
const Spacer(),
|
||||
FlowyIconButton(
|
||||
tooltipText: LocaleKeys.sideBar_addAPage.tr(),
|
||||
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
iconPadding: const EdgeInsets.all(2),
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
icon: const FlowySvg(FlowySvgs.add_s),
|
||||
onPressed: () {
|
||||
createViewAndShowRenameDialogIfNeeded(
|
||||
context,
|
||||
LocaleKeys.newPageText.tr(),
|
||||
(viewName, _) {
|
||||
if (viewName.isNotEmpty) {
|
||||
context.read<SidebarRootViewsBloc>().add(
|
||||
SidebarRootViewsEvent.createRootView(
|
||||
viewName,
|
||||
index: 0,
|
||||
),
|
||||
);
|
||||
|
||||
widget.onAdded();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,8 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_root_views_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_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
@ -15,8 +14,8 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_top_me
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_trash.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
show UserProfilePB;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@ -31,7 +30,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
/// - settings
|
||||
/// - scrollable document list
|
||||
/// - trash
|
||||
class HomeSideBar extends StatefulWidget {
|
||||
class HomeSideBar extends StatelessWidget {
|
||||
const HomeSideBar({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
@ -42,49 +41,30 @@ class HomeSideBar extends StatefulWidget {
|
||||
|
||||
final WorkspaceSettingPB workspaceSetting;
|
||||
|
||||
@override
|
||||
State<HomeSideBar> createState() => _HomeSideBarState();
|
||||
}
|
||||
|
||||
class _HomeSideBarState extends State<HomeSideBar> {
|
||||
final _scrollController = ScrollController();
|
||||
Timer? _srollDebounce;
|
||||
bool isScrolling = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_onScrollChanged);
|
||||
}
|
||||
|
||||
void _onScrollChanged() {
|
||||
setState(() => isScrolling = true);
|
||||
|
||||
_srollDebounce?.cancel();
|
||||
_srollDebounce =
|
||||
Timer(const Duration(milliseconds: 300), _setScrollStopped);
|
||||
}
|
||||
|
||||
void _setScrollStopped() {
|
||||
if (mounted) {
|
||||
setState(() => isScrolling = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_srollDebounce?.cancel();
|
||||
_scrollController.removeListener(_onScrollChanged);
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Workspace Bloc: control the current workspace
|
||||
// |
|
||||
// +-- Workspace Menu
|
||||
// | |
|
||||
// | +-- Workspace List: control to switch workspace
|
||||
// | |
|
||||
// | +-- Workspace Settings
|
||||
// | |
|
||||
// | +-- Notification Center
|
||||
// |
|
||||
// +-- Favorite Section
|
||||
// |
|
||||
// +-- Public Or Private Section: control the sections of the workspace
|
||||
// |
|
||||
// +-- Trash Section
|
||||
return BlocProvider<UserWorkspaceBloc>(
|
||||
create: (_) => UserWorkspaceBloc(userProfile: widget.userProfile)
|
||||
..add(const UserWorkspaceEvent.fetchWorkspaces()),
|
||||
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
||||
..add(
|
||||
const UserWorkspaceEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
// Rebuild the whole sidebar when the current workspace changes
|
||||
buildWhen: (previous, current) =>
|
||||
previous.currentWorkspace?.workspaceId !=
|
||||
current.currentWorkspace?.workspaceId,
|
||||
@ -95,19 +75,19 @@ class _HomeSideBarState extends State<HomeSideBar> {
|
||||
create: (_) => getIt<NotificationActionBloc>(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => SidebarRootViewsBloc()
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarRootViewsEvent.initial(
|
||||
widget.userProfile,
|
||||
SidebarSectionsEvent.initial(
|
||||
userProfile,
|
||||
state.currentWorkspace?.workspaceId ??
|
||||
widget.workspaceSetting.workspaceId,
|
||||
workspaceSetting.workspaceId,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
listenWhen: (p, c) =>
|
||||
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||
listener: (context, state) => context.read<TabsBloc>().add(
|
||||
@ -122,28 +102,17 @@ class _HomeSideBarState extends State<HomeSideBar> {
|
||||
),
|
||||
BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
listener: (context, state) {
|
||||
context.read<SidebarRootViewsBloc>().add(
|
||||
SidebarRootViewsEvent.reset(
|
||||
widget.userProfile,
|
||||
context.read<SidebarSectionsBloc>().add(
|
||||
SidebarSectionsEvent.initial(
|
||||
userProfile,
|
||||
state.currentWorkspace?.workspaceId ??
|
||||
widget.workspaceSetting.workspaceId,
|
||||
workspaceSetting.workspaceId,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final menuState = context.watch<SidebarRootViewsBloc>().state;
|
||||
final favoriteState = context.watch<FavoriteBloc>().state;
|
||||
|
||||
return _buildSidebar(
|
||||
context,
|
||||
menuState.views,
|
||||
favoriteState.views,
|
||||
);
|
||||
},
|
||||
),
|
||||
child: _Sidebar(userProfile: userProfile),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -151,71 +120,6 @@ class _HomeSideBarState extends State<HomeSideBar> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSidebar(
|
||||
BuildContext context,
|
||||
List<ViewPB> views,
|
||||
List<ViewPB> favoriteViews,
|
||||
) {
|
||||
const menuHorizontalInset = EdgeInsets.symmetric(horizontal: 12);
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
border: Border(
|
||||
right: BorderSide(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// top menu
|
||||
const Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: SidebarTopMenu(),
|
||||
),
|
||||
// user or workspace, setting
|
||||
Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: FeatureFlag.collaborativeWorkspace.isOn
|
||||
? SidebarWorkspace(
|
||||
userProfile: widget.userProfile,
|
||||
views: views,
|
||||
)
|
||||
: SidebarUser(
|
||||
userProfile: widget.userProfile,
|
||||
views: views,
|
||||
),
|
||||
),
|
||||
|
||||
const VSpace(20),
|
||||
// scrollable document list
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: SidebarFolder(
|
||||
views: views,
|
||||
favoriteViews: favoriteViews,
|
||||
isHoverEnabled: !isScrolling,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const VSpace(10),
|
||||
// trash
|
||||
const Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: SidebarTrashButton(),
|
||||
),
|
||||
const VSpace(10),
|
||||
// new page button
|
||||
const SidebarNewPageButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onNotificationAction(
|
||||
BuildContext context,
|
||||
NotificationActionState state,
|
||||
@ -224,9 +128,10 @@ class _HomeSideBarState extends State<HomeSideBar> {
|
||||
if (action != null) {
|
||||
if (action.type == ActionType.openView) {
|
||||
final view = context
|
||||
.read<SidebarRootViewsBloc>()
|
||||
.read<SidebarSectionsBloc>()
|
||||
.state
|
||||
.views
|
||||
.section
|
||||
.publicViews
|
||||
.findView(action.objectId);
|
||||
|
||||
if (view != null) {
|
||||
@ -250,3 +155,108 @@ class _HomeSideBarState extends State<HomeSideBar> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Sidebar extends StatefulWidget {
|
||||
const _Sidebar({
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
State<_Sidebar> createState() => _SidebarState();
|
||||
}
|
||||
|
||||
class _SidebarState extends State<_Sidebar> {
|
||||
final _scrollController = ScrollController();
|
||||
Timer? _scrollDebounce;
|
||||
bool isScrolling = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_onScrollChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollDebounce?.cancel();
|
||||
_scrollController.removeListener(_onScrollChanged);
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const menuHorizontalInset = EdgeInsets.symmetric(horizontal: 12);
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
border: Border(
|
||||
right: BorderSide(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// top menu
|
||||
const Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: SidebarTopMenu(),
|
||||
),
|
||||
// user or workspace, setting
|
||||
Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: widget.userProfile.authenticator != AuthenticatorPB.Local &&
|
||||
FeatureFlag.collaborativeWorkspace.isOn
|
||||
? SidebarWorkspace(
|
||||
userProfile: widget.userProfile,
|
||||
)
|
||||
: SidebarUser(
|
||||
userProfile: widget.userProfile,
|
||||
),
|
||||
),
|
||||
|
||||
const VSpace(20),
|
||||
// scrollable document list
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: SidebarFolder(
|
||||
userProfile: widget.userProfile,
|
||||
isHoverEnabled: !isScrolling,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const VSpace(10),
|
||||
// trash
|
||||
const Padding(
|
||||
padding: menuHorizontalInset,
|
||||
child: SidebarTrashButton(),
|
||||
),
|
||||
const VSpace(10),
|
||||
// new page button
|
||||
const SidebarNewPageButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onScrollChanged() {
|
||||
setState(() => isScrolling = true);
|
||||
|
||||
_scrollDebounce?.cancel();
|
||||
_scrollDebounce =
|
||||
Timer(const Duration(milliseconds: 300), _setScrollStopped);
|
||||
}
|
||||
|
||||
void _setScrollStopped() {
|
||||
if (mounted) {
|
||||
setState(() => isScrolling = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +1,118 @@
|
||||
import 'package:flutter/material.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/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/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/personal_folder.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.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_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SidebarFolder extends StatelessWidget {
|
||||
const SidebarFolder({
|
||||
super.key,
|
||||
required this.views,
|
||||
required this.favoriteViews,
|
||||
this.isHoverEnabled = true,
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final List<ViewPB> views;
|
||||
final List<ViewPB> favoriteViews;
|
||||
final bool isHoverEnabled;
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// check if there is any duplicate views
|
||||
final views = this.views.toSet().toList();
|
||||
final favoriteViews = this.favoriteViews.toSet().toList();
|
||||
assert(views.length == this.views.length);
|
||||
assert(favoriteViews.length == favoriteViews.length);
|
||||
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: getIt<MenuSharedState>().notifier,
|
||||
builder: (context, value, child) {
|
||||
return Column(
|
||||
children: [
|
||||
// favorite
|
||||
if (favoriteViews.isNotEmpty) ...[
|
||||
FavoriteFolder(
|
||||
// remove the duplicate views
|
||||
views: favoriteViews,
|
||||
),
|
||||
const VSpace(10),
|
||||
],
|
||||
// personal
|
||||
PersonalFolder(views: views, isHoverEnabled: isHoverEnabled),
|
||||
BlocBuilder<FavoriteBloc, FavoriteState>(
|
||||
builder: (context, state) {
|
||||
if (state.views.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: FavoriteFolder(
|
||||
// remove the duplicate views
|
||||
views: state.views,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// public or private
|
||||
BlocBuilder<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
builder: (context, state) {
|
||||
// only show public and private section if the workspace is collaborative and not local
|
||||
final isCollaborativeWorkspace =
|
||||
userProfile.authenticator != AuthenticatorPB.Local &&
|
||||
FeatureFlag.collaborativeWorkspace.isOn;
|
||||
|
||||
return Column(
|
||||
children:
|
||||
// only show public and private section if the workspace is collaborative
|
||||
isCollaborativeWorkspace
|
||||
? [
|
||||
// public
|
||||
const VSpace(10),
|
||||
PublicSectionFolder(
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
|
||||
// private
|
||||
const VSpace(10),
|
||||
PrivateSectionFolder(
|
||||
views: state.section.privateViews,
|
||||
),
|
||||
]
|
||||
: [
|
||||
// personal
|
||||
const VSpace(10),
|
||||
PersonalSectionFolder(
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PrivateSectionFolder extends SectionFolder {
|
||||
PrivateSectionFolder({
|
||||
super.key,
|
||||
required super.views,
|
||||
}) : super(
|
||||
title: LocaleKeys.sideBar_private.tr(),
|
||||
categoryType: FolderCategoryType.private,
|
||||
);
|
||||
}
|
||||
|
||||
class PublicSectionFolder extends SectionFolder {
|
||||
PublicSectionFolder({
|
||||
super.key,
|
||||
required super.views,
|
||||
}) : super(
|
||||
title: LocaleKeys.sideBar_public.tr(),
|
||||
categoryType: FolderCategoryType.public,
|
||||
);
|
||||
}
|
||||
|
||||
class PersonalSectionFolder extends SectionFolder {
|
||||
PersonalSectionFolder({
|
||||
super.key,
|
||||
required super.views,
|
||||
}) : super(
|
||||
title: LocaleKeys.sideBar_personal.tr(),
|
||||
categoryType: FolderCategoryType.public,
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||
@ -25,9 +26,12 @@ class SidebarNewPageButton extends StatelessWidget {
|
||||
LocaleKeys.newPageText.tr(),
|
||||
(viewName, _) {
|
||||
if (viewName.isNotEmpty) {
|
||||
context
|
||||
.read<SidebarRootViewsBloc>()
|
||||
.add(SidebarRootViewsEvent.createRootView(viewName));
|
||||
context.read<SidebarSectionsBloc>().add(
|
||||
SidebarSectionsEvent.createRootViewInSection(
|
||||
name: viewName,
|
||||
viewSection: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -4,7 +4,7 @@ import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
@ -24,7 +24,7 @@ class SidebarTopMenu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SidebarRootViewsBloc, SidebarRootViewState>(
|
||||
return BlocBuilder<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: HomeSizes.topBarHeight,
|
||||
|
@ -3,7 +3,6 @@ import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
show UserProfilePB;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -17,11 +16,9 @@ class SidebarUser extends StatelessWidget {
|
||||
const SidebarUser({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.views,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final List<ViewPB> views;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -37,13 +34,13 @@ class SidebarUser extends StatelessWidget {
|
||||
iconUrl: state.userProfile.iconUrl,
|
||||
name: state.userProfile.name,
|
||||
),
|
||||
const HSpace(4),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: _buildUserName(context, state),
|
||||
),
|
||||
UserSettingButton(userProfile: state.userProfile),
|
||||
const HSpace(4),
|
||||
NotificationButton(views: views),
|
||||
const NotificationButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -6,7 +6,6 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sid
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
@ -20,11 +19,9 @@ class SidebarWorkspace extends StatelessWidget {
|
||||
const SidebarWorkspace({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.views,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final List<ViewPB> views;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -46,7 +43,7 @@ class SidebarWorkspace extends StatelessWidget {
|
||||
),
|
||||
UserSettingButton(userProfile: userProfile),
|
||||
const HSpace(4),
|
||||
NotificationButton(views: views),
|
||||
const NotificationButton(),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
@ -182,14 +182,14 @@ class WorkspaceMenuItem extends StatelessWidget {
|
||||
Widget _buildRightIcon(BuildContext context) {
|
||||
// only the owner can update or delete workspace.
|
||||
// only show the more action button when the workspace is selected.
|
||||
if (!isSelected ||
|
||||
!context.read<WorkspaceMemberBloc>().state.myRole.isOwner) {
|
||||
if (!isSelected) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
WorkspaceMoreActionList(workspace: workspace),
|
||||
if (context.read<WorkspaceMemberBloc>().state.myRole.isOwner)
|
||||
WorkspaceMoreActionList(workspace: workspace),
|
||||
const FlowySvg(
|
||||
FlowySvgs.blue_check_s,
|
||||
),
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/draggable_item/draggable_item.dart';
|
||||
@ -188,6 +189,9 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
|
||||
return;
|
||||
}
|
||||
|
||||
final fromSection = getViewSection(from);
|
||||
final toSection = getViewSection(to);
|
||||
|
||||
switch (position) {
|
||||
case DraggableHoverPosition.top:
|
||||
context.read<ViewBloc>().add(
|
||||
@ -195,6 +199,8 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
|
||||
from,
|
||||
to.parentViewId,
|
||||
null,
|
||||
fromSection,
|
||||
toSection,
|
||||
),
|
||||
);
|
||||
break;
|
||||
@ -204,6 +210,8 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
|
||||
from,
|
||||
to.parentViewId,
|
||||
to.id,
|
||||
fromSection,
|
||||
toSection,
|
||||
),
|
||||
);
|
||||
break;
|
||||
@ -213,6 +221,8 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
|
||||
from,
|
||||
to.id,
|
||||
to.childViews.lastOrNull?.id,
|
||||
fromSection,
|
||||
toSection,
|
||||
),
|
||||
);
|
||||
break;
|
||||
@ -251,6 +261,10 @@ class _DraggableViewItemState extends State<DraggableViewItem> {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ViewSectionPB? getViewSection(ViewPB view) {
|
||||
return context.read<SidebarSectionsBloc>().getViewSection(view);
|
||||
}
|
||||
}
|
||||
|
||||
extension on ViewPB {
|
||||
|
@ -475,6 +475,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
viewName,
|
||||
pluginBuilder.layoutType!,
|
||||
openAfterCreated: openAfterCreated,
|
||||
section: widget.categoryType.toViewSectionPB,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/notification_dialog.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@ -13,12 +13,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NotificationButton extends StatelessWidget {
|
||||
const NotificationButton({super.key, required this.views});
|
||||
|
||||
final List<ViewPB> views;
|
||||
const NotificationButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final views = context.watch<SidebarSectionsBloc>().state.section.views;
|
||||
final mutex = PopoverMutex();
|
||||
|
||||
return BlocProvider<ReminderBloc>.value(
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@ -21,7 +23,8 @@ class WorkspaceMemberBloc
|
||||
WorkspaceMemberBloc({
|
||||
required this.userProfile,
|
||||
this.workspace,
|
||||
}) : super(WorkspaceMemberState.initial()) {
|
||||
}) : _userBackendService = UserBackendService(userId: userProfile.id),
|
||||
super(WorkspaceMemberState.initial()) {
|
||||
on<WorkspaceMemberEvent>((event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
@ -73,14 +76,16 @@ class WorkspaceMemberBloc
|
||||
final UserWorkspacePB? workspace;
|
||||
|
||||
late final String workspaceId;
|
||||
late final UserBackendService _userBackendService;
|
||||
|
||||
Future<List<WorkspaceMemberPB>> _getWorkspaceMembers() async {
|
||||
final data = QueryWorkspacePB()..workspaceId = workspaceId;
|
||||
final result = await UserEventGetWorkspaceMember(data).send();
|
||||
return result.fold((s) => s.items, (e) {
|
||||
Log.error('Failed to read workspace members: $e');
|
||||
return [];
|
||||
});
|
||||
return _userBackendService.getWorkspaceMembers(workspaceId).fold(
|
||||
(s) => s.items,
|
||||
(e) {
|
||||
Log.error('Failed to read workspace members: $e');
|
||||
return [];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
AFRolePB _getMyRole(List<WorkspaceMemberPB> members) {
|
||||
@ -97,40 +102,26 @@ class WorkspaceMemberBloc
|
||||
}
|
||||
|
||||
Future<void> _addWorkspaceMember(String email) async {
|
||||
final data = AddWorkspaceMemberPB()
|
||||
..workspaceId = workspaceId
|
||||
..email = email;
|
||||
final result = await UserEventAddWorkspaceMember(data).send();
|
||||
result.fold((s) {
|
||||
Log.info('Added workspace member: $data');
|
||||
}, (e) {
|
||||
Log.error('Failed to add workspace member: $e');
|
||||
});
|
||||
return _userBackendService.addWorkspaceMember(workspaceId, email).fold(
|
||||
(s) => Log.debug('Added workspace member: $email'),
|
||||
(e) => Log.error('Failed to add workspace member: $e'),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _removeWorkspaceMember(String email) async {
|
||||
final data = RemoveWorkspaceMemberPB()
|
||||
..workspaceId = workspaceId
|
||||
..email = email;
|
||||
final result = await UserEventRemoveWorkspaceMember(data).send();
|
||||
result.fold((s) {
|
||||
Log.info('Removed workspace member: $data');
|
||||
}, (e) {
|
||||
Log.error('Failed to remove workspace member: $e');
|
||||
});
|
||||
return _userBackendService.removeWorkspaceMember(workspaceId, email).fold(
|
||||
(s) => Log.debug('Removed workspace member: $email'),
|
||||
(e) => Log.error('Failed to remove workspace member: $e'),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateWorkspaceMember(String email, AFRolePB role) async {
|
||||
final data = UpdateWorkspaceMemberPB()
|
||||
..workspaceId = workspaceId
|
||||
..email = email
|
||||
..role = role;
|
||||
final result = await UserEventUpdateWorkspaceMember(data).send();
|
||||
result.fold((s) {
|
||||
Log.info('Updated workspace member: $data');
|
||||
}, (e) {
|
||||
Log.error('Failed to update workspace member: $e');
|
||||
});
|
||||
return _userBackendService
|
||||
.updateWorkspaceMember(workspaceId, email, role)
|
||||
.fold(
|
||||
(s) => Log.debug('Updated workspace member: $email'),
|
||||
(e) => Log.error('Failed to update workspace member: $e'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ class UserAvatar extends StatelessWidget {
|
||||
),
|
||||
child: FlowyText.semibold(
|
||||
nameInitials,
|
||||
color: Colors.white,
|
||||
color: Colors.black,
|
||||
fontSize: isLarge
|
||||
? nameInitials.length == initialsCount
|
||||
? 20
|
||||
|
@ -8,6 +8,10 @@ extension FlowyAsyncResultExtension<S, F extends Object>
|
||||
return then((result) => result.getOrElse(onFailure));
|
||||
}
|
||||
|
||||
Future<S?> toNullable() {
|
||||
return then((result) => result.toNullable());
|
||||
}
|
||||
|
||||
Future<S> getOrThrow() {
|
||||
return then((result) => result.getOrThrow());
|
||||
}
|
||||
|
@ -38,8 +38,13 @@ void main() {
|
||||
final appBloc = ViewBloc(view: app)..add(const ViewEvent.initial());
|
||||
assert(appBloc.state.lastCreatedView == null);
|
||||
|
||||
appBloc
|
||||
.add(const ViewEvent.createView("New document", ViewLayoutPB.Document));
|
||||
appBloc.add(
|
||||
const ViewEvent.createView(
|
||||
"New document",
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
|
||||
assert(appBloc.state.lastCreatedView != null);
|
||||
|
@ -1,46 +0,0 @@
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../util.dart';
|
||||
|
||||
void main() {
|
||||
late AppFlowyUnitTest testContext;
|
||||
setUpAll(() async {
|
||||
testContext = await AppFlowyUnitTest.ensureInitialized();
|
||||
});
|
||||
|
||||
test('assert initial apps is the build-in app', () async {
|
||||
final menuBloc = SidebarRootViewsBloc()
|
||||
..add(
|
||||
SidebarRootViewsEvent.initial(
|
||||
testContext.userProfile,
|
||||
testContext.currentWorkspace.id,
|
||||
),
|
||||
);
|
||||
|
||||
await blocResponseFuture();
|
||||
|
||||
assert(menuBloc.state.views.length == 1);
|
||||
});
|
||||
|
||||
test('reorder apps', () async {
|
||||
final menuBloc = SidebarRootViewsBloc()
|
||||
..add(
|
||||
SidebarRootViewsEvent.initial(
|
||||
testContext.userProfile,
|
||||
testContext.currentWorkspace.id,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
menuBloc.add(const SidebarRootViewsEvent.createRootView("App 1"));
|
||||
await blocResponseFuture();
|
||||
menuBloc.add(const SidebarRootViewsEvent.createRootView("App 2"));
|
||||
await blocResponseFuture();
|
||||
menuBloc.add(const SidebarRootViewsEvent.createRootView("App 3"));
|
||||
await blocResponseFuture();
|
||||
|
||||
assert(menuBloc.state.views[1].name == 'App 1');
|
||||
assert(menuBloc.state.views[2].name == 'App 2');
|
||||
assert(menuBloc.state.views[3].name == 'App 3');
|
||||
});
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../util.dart';
|
||||
|
||||
void main() {
|
||||
late AppFlowyUnitTest testContext;
|
||||
setUpAll(() async {
|
||||
testContext = await AppFlowyUnitTest.ensureInitialized();
|
||||
});
|
||||
|
||||
test('assert initial apps is the build-in app', () async {
|
||||
final menuBloc = SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarSectionsEvent.initial(
|
||||
testContext.userProfile,
|
||||
testContext.currentWorkspace.id,
|
||||
),
|
||||
);
|
||||
|
||||
await blocResponseFuture();
|
||||
|
||||
assert(menuBloc.state.section.publicViews.length == 1);
|
||||
assert(menuBloc.state.section.privateViews.isEmpty);
|
||||
});
|
||||
|
||||
test('create views', () async {
|
||||
final menuBloc = SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarSectionsEvent.initial(
|
||||
testContext.userProfile,
|
||||
testContext.currentWorkspace.id,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
|
||||
final names = ['View 1', 'View 2', 'View 3'];
|
||||
for (final name in names) {
|
||||
menuBloc.add(
|
||||
SidebarSectionsEvent.createRootViewInSection(
|
||||
name: name,
|
||||
index: 0,
|
||||
viewSection: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
}
|
||||
|
||||
final reversedNames = names.reversed.toList();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
assert(
|
||||
menuBloc.state.section.publicViews[i].name == reversedNames[i],
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
@ -22,6 +22,7 @@ class TrashTestContext {
|
||||
const ViewEvent.createView(
|
||||
"Document 1",
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
@ -30,6 +31,7 @@ class TrashTestContext {
|
||||
const ViewEvent.createView(
|
||||
"Document 2",
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
@ -38,6 +40,7 @@ class TrashTestContext {
|
||||
const ViewEvent.createView(
|
||||
"Document 3",
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
|
@ -36,7 +36,11 @@ void main() {
|
||||
final viewBloc = await createTestViewBloc();
|
||||
// create a nested view
|
||||
viewBloc.add(
|
||||
const ViewEvent.createView(name, ViewLayoutPB.Document),
|
||||
const ViewEvent.createView(
|
||||
name,
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
expect(viewBloc.state.view.childViews.length, 1);
|
||||
@ -52,7 +56,11 @@ void main() {
|
||||
test('delete view test', () async {
|
||||
final viewBloc = await createTestViewBloc();
|
||||
viewBloc.add(
|
||||
const ViewEvent.createView(name, ViewLayoutPB.Document),
|
||||
const ViewEvent.createView(
|
||||
name,
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
expect(viewBloc.state.view.childViews.length, 1);
|
||||
@ -69,7 +77,11 @@ void main() {
|
||||
test('create nested view test', () async {
|
||||
final viewBloc = await createTestViewBloc();
|
||||
viewBloc.add(
|
||||
const ViewEvent.createView('Document 1', ViewLayoutPB.Document),
|
||||
const ViewEvent.createView(
|
||||
'Document 1',
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
final document1Bloc = ViewBloc(view: viewBloc.state.view.childViews.first)
|
||||
@ -79,7 +91,11 @@ void main() {
|
||||
await blocResponseFuture();
|
||||
const name = 'Document 1 - 1';
|
||||
document1Bloc.add(
|
||||
const ViewEvent.createView('Document 1 - 1', ViewLayoutPB.Document),
|
||||
const ViewEvent.createView(
|
||||
'Document 1 - 1',
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
expect(document1Bloc.state.view.childViews.length, 1);
|
||||
@ -91,7 +107,11 @@ void main() {
|
||||
final names = ['1', '2', '3'];
|
||||
for (final name in names) {
|
||||
viewBloc.add(
|
||||
ViewEvent.createView(name, ViewLayoutPB.Document),
|
||||
ViewEvent.createView(
|
||||
name,
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
}
|
||||
@ -106,7 +126,13 @@ void main() {
|
||||
final viewBloc = await createTestViewBloc();
|
||||
expect(viewBloc.state.lastCreatedView, isNull);
|
||||
|
||||
viewBloc.add(const ViewEvent.createView('1', ViewLayoutPB.Document));
|
||||
viewBloc.add(
|
||||
const ViewEvent.createView(
|
||||
'1',
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
expect(
|
||||
viewBloc.state.lastCreatedView!.id,
|
||||
@ -117,7 +143,13 @@ void main() {
|
||||
'1',
|
||||
);
|
||||
|
||||
viewBloc.add(const ViewEvent.createView('2', ViewLayoutPB.Document));
|
||||
viewBloc.add(
|
||||
const ViewEvent.createView(
|
||||
'2',
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
expect(
|
||||
viewBloc.state.lastCreatedView!.name,
|
||||
@ -128,13 +160,25 @@ void main() {
|
||||
test('open latest document test', () async {
|
||||
const name1 = 'document';
|
||||
final viewBloc = await createTestViewBloc();
|
||||
viewBloc.add(const ViewEvent.createView(name1, ViewLayoutPB.Document));
|
||||
viewBloc.add(
|
||||
const ViewEvent.createView(
|
||||
name1,
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
final document = viewBloc.state.lastCreatedView!;
|
||||
assert(document.name == name1);
|
||||
|
||||
const gird = 'grid';
|
||||
viewBloc.add(const ViewEvent.createView(gird, ViewLayoutPB.Document));
|
||||
viewBloc.add(
|
||||
const ViewEvent.createView(
|
||||
gird,
|
||||
ViewLayoutPB.Document,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
assert(viewBloc.state.lastCreatedView!.name == gird);
|
||||
|
||||
@ -170,7 +214,11 @@ void main() {
|
||||
for (var i = 0; i < layouts.length; i++) {
|
||||
final layout = layouts[i];
|
||||
viewBloc.add(
|
||||
ViewEvent.createView('Test $layout', layout),
|
||||
ViewEvent.createView(
|
||||
'Test $layout',
|
||||
layout,
|
||||
section: ViewSectionPB.Public,
|
||||
),
|
||||
);
|
||||
await blocResponseFuture();
|
||||
expect(viewBloc.state.view.childViews.length, i + 1);
|
||||
|
@ -74,7 +74,10 @@ class AppFlowyUnitTest {
|
||||
}
|
||||
|
||||
Future<ViewPB> createWorkspace() async {
|
||||
final result = await workspaceService.createApp(name: "Test App");
|
||||
final result = await workspaceService.createView(
|
||||
name: "Test App",
|
||||
viewSection: ViewSectionPB.Public,
|
||||
);
|
||||
return result.fold(
|
||||
(app) => app,
|
||||
(error) => throw Exception(error),
|
||||
@ -82,7 +85,7 @@ class AppFlowyUnitTest {
|
||||
}
|
||||
|
||||
Future<List<ViewPB>> loadApps() async {
|
||||
final result = await workspaceService.getViews();
|
||||
final result = await workspaceService.getPublicViews();
|
||||
|
||||
return result.fold(
|
||||
(apps) => apps,
|
||||
|
37
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
37
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -162,7 +162,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -716,7 +716,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -764,7 +764,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -838,7 +838,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -854,6 +854,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"web-sys",
|
||||
"yrs",
|
||||
]
|
||||
@ -861,7 +862,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -891,7 +892,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -910,7 +911,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -925,7 +926,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -962,7 +963,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1001,7 +1002,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1335,7 +1336,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -2637,7 +2638,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2654,7 +2655,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -3109,7 +3110,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@ -4892,7 +4893,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "realtime-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -4916,7 +4917,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "realtime-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -5588,7 +5589,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -7551,7 +7552,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "workspace-template"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -86,7 +86,7 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c5112cc761736ac91f0a518552e7bbe522bceae6" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9496c248b7c733d1aa160062abeb66c4e41325" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@ -96,10 +96,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c51
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { parserViewPBToPage } from '$app_reducers/pages/slice';
|
||||
import {
|
||||
ChangeWorkspaceIconPB,
|
||||
CreateViewPayloadPB,
|
||||
GetWorkspaceViewPB,
|
||||
RenameWorkspacePB,
|
||||
UserWorkspaceIdPB,
|
||||
WorkspaceIdPB,
|
||||
RenameWorkspacePB,
|
||||
ChangeWorkspaceIconPB,
|
||||
} from '@/services/backend';
|
||||
import {
|
||||
UserEventOpenWorkspace,
|
||||
UserEventRenameWorkspace,
|
||||
UserEventChangeWorkspaceIcon,
|
||||
UserEventGetAllWorkspace,
|
||||
} from '@/services/backend/events/flowy-user';
|
||||
import {
|
||||
FolderEventCreateView,
|
||||
FolderEventDeleteWorkspace,
|
||||
@ -18,7 +14,12 @@ import {
|
||||
FolderEventReadCurrentWorkspace,
|
||||
FolderEventReadWorkspaceViews,
|
||||
} from '@/services/backend/events/flowy-folder';
|
||||
import { parserViewPBToPage } from '$app_reducers/pages/slice';
|
||||
import {
|
||||
UserEventChangeWorkspaceIcon,
|
||||
UserEventGetAllWorkspace,
|
||||
UserEventOpenWorkspace,
|
||||
UserEventRenameWorkspace,
|
||||
} from '@/services/backend/events/flowy-user';
|
||||
|
||||
export async function openWorkspace(id: string) {
|
||||
const payload = new UserWorkspaceIdPB({
|
||||
@ -49,7 +50,7 @@ export async function deleteWorkspace(id: string) {
|
||||
}
|
||||
|
||||
export async function getWorkspaceChildViews(id: string) {
|
||||
const payload = new WorkspaceIdPB({
|
||||
const payload = new GetWorkspaceViewPB({
|
||||
value: id,
|
||||
});
|
||||
|
||||
|
35
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
35
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
@ -221,7 +221,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -545,7 +545,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -592,7 +592,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -636,7 +636,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -652,6 +652,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"web-sys",
|
||||
"yrs",
|
||||
]
|
||||
@ -659,7 +660,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -678,7 +679,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -693,7 +694,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -730,7 +731,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -768,7 +769,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -965,7 +966,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -1720,7 +1721,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -1737,7 +1738,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -2071,7 +2072,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@ -3315,7 +3316,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "realtime-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -3339,7 +3340,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "realtime-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -3792,7 +3793,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -5024,4 +5025,4 @@ dependencies = [
|
||||
[[patch.unused]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
|
@ -55,7 +55,7 @@ codegen-units = 1
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c5112cc761736ac91f0a518552e7bbe522bceae6" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9496c248b7c733d1aa160062abeb66c4e41325" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@ -65,10 +65,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c51
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
|
@ -205,10 +205,16 @@
|
||||
"closeSidebar": "Close side bar",
|
||||
"openSidebar": "Open side bar",
|
||||
"personal": "Personal",
|
||||
"private": "Private",
|
||||
"public": "Public",
|
||||
"favorites": "Favorites",
|
||||
"clickToHidePrivate": "Click to hide private section\nPages you created here are only visible to you",
|
||||
"clickToHidePublic": "Click to hide public section\nPages you created here are visible to every member",
|
||||
"clickToHidePersonal": "Click to hide personal section",
|
||||
"clickToHideFavorites": "Click to hide favorite section",
|
||||
"addAPage": "Add a page",
|
||||
"addAPageToPrivate": "Add a page to private section",
|
||||
"addAPageToPublic": "Add a page to public section",
|
||||
"recent": "Recent"
|
||||
},
|
||||
"notifications": {
|
||||
|
37
frontend/rust-lib/Cargo.lock
generated
37
frontend/rust-lib/Cargo.lock
generated
@ -163,7 +163,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -673,7 +673,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -721,7 +721,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -764,7 +764,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -780,6 +780,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"web-sys",
|
||||
"yrs",
|
||||
]
|
||||
@ -787,7 +788,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -817,7 +818,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -836,7 +837,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -851,7 +852,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -888,7 +889,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -927,7 +928,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2d7b1838e463ce0348cf700ff43f33f5718203be#2d7b1838e463ce0348cf700ff43f33f5718203be"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=0970b2e1440134af7c83bb8fc80cac5d2dedebb7#0970b2e1440134af7c83bb8fc80cac5d2dedebb7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@ -1257,7 +1258,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -2432,7 +2433,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2449,7 +2450,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -2843,7 +2844,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"reqwest",
|
||||
@ -4325,7 +4326,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "realtime-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -4349,7 +4350,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "realtime-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -4942,7 +4943,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -6355,7 +6356,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "workspace-template"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c5112cc761736ac91f0a518552e7bbe522bceae6#c5112cc761736ac91f0a518552e7bbe522bceae6"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ab9496c248b7c733d1aa160062abeb66c4e41325#ab9496c248b7c733d1aa160062abeb66c4e41325"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -110,7 +110,7 @@ rocksdb = { git = "https://github.com/LucasXu0/rust-rocksdb", rev = "21cf4a23ec1
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c5112cc761736ac91f0a518552e7bbe522bceae6" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ab9496c248b7c733d1aa160062abeb66c4e41325" }
|
||||
# Please use the following script to update collab.
|
||||
# Working directory: frontend
|
||||
#
|
||||
@ -120,10 +120,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c51
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2d7b1838e463ce0348cf700ff43f33f5718203be" }
|
||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "0970b2e1440134af7c83bb8fc80cac5d2dedebb7" }
|
||||
|
@ -36,6 +36,7 @@ impl EventIntegrationTest {
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
EventBuilder::new(self.clone())
|
||||
.event(FolderEvent::CreateView)
|
||||
@ -66,6 +67,7 @@ impl EventIntegrationTest {
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
EventBuilder::new(self.clone())
|
||||
.event(FolderEvent::CreateView)
|
||||
@ -91,6 +93,7 @@ impl EventIntegrationTest {
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
EventBuilder::new(self.clone())
|
||||
.event(FolderEvent::CreateView)
|
||||
|
@ -64,6 +64,7 @@ impl DocumentEventTest {
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
EventBuilder::new(core.clone())
|
||||
.event(FolderEvent::CreateView)
|
||||
|
@ -41,6 +41,7 @@ impl EventIntegrationTest {
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
let view = EventBuilder::new(self.clone())
|
||||
.event(FolderEvent::CreateView)
|
||||
|
@ -57,7 +57,7 @@ impl EventIntegrationTest {
|
||||
|
||||
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(FolderEvent::ReadWorkspaceViews)
|
||||
.event(FolderEvent::ReadCurrentWorkspaceViews)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RepeatedViewPB>()
|
||||
@ -115,6 +115,7 @@ impl EventIntegrationTest {
|
||||
meta: Default::default(),
|
||||
set_as_current: false,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
EventBuilder::new(self.clone())
|
||||
.event(FolderEvent::CreateView)
|
||||
@ -165,6 +166,7 @@ impl ViewTest {
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
|
||||
let view = EventBuilder::new(sdk.clone())
|
||||
|
@ -276,9 +276,9 @@ impl EventIntegrationTest {
|
||||
.parse()
|
||||
}
|
||||
|
||||
pub async fn folder_read_workspace_views(&self) -> RepeatedViewPB {
|
||||
pub async fn folder_read_current_workspace_views(&self) -> RepeatedViewPB {
|
||||
EventBuilder::new(self.clone())
|
||||
.event(FolderEvent::ReadWorkspaceViews)
|
||||
.event(FolderEvent::ReadCurrentWorkspaceViews)
|
||||
.async_send()
|
||||
.await
|
||||
.parse()
|
||||
|
@ -246,6 +246,7 @@ pub async fn create_view(
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
@ -275,6 +276,8 @@ pub async fn move_view(
|
||||
view_id,
|
||||
new_parent_id: parent_id,
|
||||
prev_view_id,
|
||||
from_section: None,
|
||||
to_section: None,
|
||||
};
|
||||
let error = EventBuilder::new(sdk.clone())
|
||||
.event(MoveNestedView)
|
||||
|
@ -549,6 +549,8 @@ async fn move_folder_nested_view(
|
||||
view_id,
|
||||
new_parent_id,
|
||||
prev_view_id,
|
||||
from_section: None,
|
||||
to_section: None,
|
||||
};
|
||||
EventBuilder::new(sdk)
|
||||
.event(flowy_folder::event_map::FolderEvent::MoveNestedView)
|
||||
|
@ -77,7 +77,7 @@ async fn af_cloud_create_workspace_test() {
|
||||
// before opening new workspace
|
||||
let folder_ws = test.folder_read_current_workspace().await;
|
||||
assert_eq!(&folder_ws.id, first_workspace_id);
|
||||
let views = test.folder_read_workspace_views().await;
|
||||
let views = test.folder_read_current_workspace_views().await;
|
||||
assert_eq!(views.items[0].parent_view_id.as_str(), first_workspace_id);
|
||||
}
|
||||
{
|
||||
@ -85,7 +85,7 @@ async fn af_cloud_create_workspace_test() {
|
||||
test.open_workspace(&created_workspace.workspace_id).await;
|
||||
let folder_ws = test.folder_read_current_workspace().await;
|
||||
assert_eq!(folder_ws.id, created_workspace.workspace_id);
|
||||
let views = test.folder_read_workspace_views().await;
|
||||
let views = test.folder_read_current_workspace_views().await;
|
||||
assert_eq!(
|
||||
views.items[0].parent_view_id.as_str(),
|
||||
created_workspace.workspace_id
|
||||
|
@ -59,6 +59,7 @@ impl ViewBuilder {
|
||||
layout: ViewLayout::Document,
|
||||
child_views: vec![],
|
||||
is_favorite: false,
|
||||
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +118,15 @@ impl std::convert::From<ViewLayout> for ViewLayoutPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)]
|
||||
pub struct SectionViewsPB {
|
||||
#[pb(index = 1)]
|
||||
pub section: ViewSectionPB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub views: Vec<ViewPB>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)]
|
||||
pub struct RepeatedViewPB {
|
||||
#[pb(index = 1)]
|
||||
@ -181,6 +190,20 @@ pub struct CreateViewPayloadPB {
|
||||
// If the index is None or the index is out of range, the view will be appended to the end of the parent view.
|
||||
#[pb(index = 9, one_of)]
|
||||
pub index: Option<u32>,
|
||||
|
||||
// The section of the view.
|
||||
// Only the view in public section will be shown in the shared workspace view list.
|
||||
// The view in private section will only be shown in the user's private view list.
|
||||
#[pb(index = 10, one_of)]
|
||||
pub section: Option<ViewSectionPB>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone, Default)]
|
||||
pub enum ViewSectionPB {
|
||||
#[default]
|
||||
// only support public and private section now.
|
||||
Private = 0,
|
||||
Public = 1,
|
||||
}
|
||||
|
||||
/// The orphan view is meant to be a view that is not attached to any parent view. By default, this
|
||||
@ -218,6 +241,8 @@ pub struct CreateViewParams {
|
||||
// The index of the view in the parent view.
|
||||
// If the index is None or the index is out of range, the view will be appended to the end of the parent view.
|
||||
pub index: Option<u32>,
|
||||
// The section of the view.
|
||||
pub section: Option<ViewSectionPB>,
|
||||
}
|
||||
|
||||
impl TryInto<CreateViewParams> for CreateViewPayloadPB {
|
||||
@ -238,6 +263,7 @@ impl TryInto<CreateViewParams> for CreateViewPayloadPB {
|
||||
meta: self.meta,
|
||||
set_as_current: self.set_as_current,
|
||||
index: self.index,
|
||||
section: self.section,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -259,6 +285,8 @@ impl TryInto<CreateViewParams> for CreateOrphanViewPayloadPB {
|
||||
meta: Default::default(),
|
||||
set_as_current: false,
|
||||
index: None,
|
||||
// TODO: lucas.xu add section to CreateOrphanViewPayloadPB
|
||||
section: Some(ViewSectionPB::Public),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -384,6 +412,12 @@ pub struct MoveNestedViewPayloadPB {
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub prev_view_id: Option<String>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub from_section: Option<ViewSectionPB>,
|
||||
|
||||
#[pb(index = 5, one_of)]
|
||||
pub to_section: Option<ViewSectionPB>,
|
||||
}
|
||||
|
||||
pub struct MoveViewParams {
|
||||
@ -405,10 +439,13 @@ impl TryInto<MoveViewParams> for MoveViewPayloadPB {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MoveNestedViewParams {
|
||||
pub view_id: String,
|
||||
pub new_parent_id: String,
|
||||
pub prev_view_id: Option<String>,
|
||||
pub from_section: Option<ViewSectionPB>,
|
||||
pub to_section: Option<ViewSectionPB>,
|
||||
}
|
||||
|
||||
impl TryInto<MoveNestedViewParams> for MoveNestedViewPayloadPB {
|
||||
@ -422,6 +459,8 @@ impl TryInto<MoveNestedViewParams> for MoveNestedViewPayloadPB {
|
||||
view_id,
|
||||
new_parent_id,
|
||||
prev_view_id,
|
||||
from_section: self.from_section,
|
||||
to_section: self.to_section,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +97,42 @@ pub struct WorkspaceIdPB {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorkspaceIdParams {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl TryInto<WorkspaceIdParams> for WorkspaceIdPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<WorkspaceIdParams, Self::Error> {
|
||||
Ok(WorkspaceIdParams {
|
||||
value: WorkspaceIdentify::parse(self.value)?.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, ProtoBuf, Default, Debug)]
|
||||
pub struct GetWorkspaceViewPB {
|
||||
#[pb(index = 1)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GetWorkspaceViewParams {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl TryInto<GetWorkspaceViewParams> for GetWorkspaceViewPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<GetWorkspaceViewParams, Self::Error> {
|
||||
Ok(GetWorkspaceViewParams {
|
||||
value: WorkspaceIdentify::parse(self.value)?.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Debug, Clone)]
|
||||
pub struct WorkspaceSettingPB {
|
||||
#[pb(index = 1)]
|
||||
|
@ -48,6 +48,18 @@ pub(crate) async fn get_all_workspace_handler(
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(folder), err)]
|
||||
pub(crate) async fn get_workspace_views_handler(
|
||||
data: AFPluginData<GetWorkspaceViewPB>,
|
||||
folder: AFPluginState<Weak<FolderManager>>,
|
||||
) -> DataResult<RepeatedViewPB, FlowyError> {
|
||||
let folder = upgrade_folder(folder)?;
|
||||
let params: GetWorkspaceViewParams = data.into_inner().try_into()?;
|
||||
let child_views = folder.get_workspace_views(¶ms.value).await?;
|
||||
let repeated_view: RepeatedViewPB = child_views.into();
|
||||
data_result_ok(repeated_view)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(folder), err)]
|
||||
pub(crate) async fn get_current_workspace_views_handler(
|
||||
folder: AFPluginState<Weak<FolderManager>>,
|
||||
) -> DataResult<RepeatedViewPB, FlowyError> {
|
||||
let folder = upgrade_folder(folder)?;
|
||||
@ -56,6 +68,18 @@ pub(crate) async fn get_workspace_views_handler(
|
||||
data_result_ok(repeated_view)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(folder), err)]
|
||||
pub(crate) async fn read_private_views_handler(
|
||||
data: AFPluginData<GetWorkspaceViewPB>,
|
||||
folder: AFPluginState<Weak<FolderManager>>,
|
||||
) -> DataResult<RepeatedViewPB, FlowyError> {
|
||||
let folder = upgrade_folder(folder)?;
|
||||
let params: GetWorkspaceViewParams = data.into_inner().try_into()?;
|
||||
let child_views = folder.get_workspace_private_views(¶ms.value).await?;
|
||||
let repeated_view: RepeatedViewPB = child_views.into();
|
||||
data_result_ok(repeated_view)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(folder), err)]
|
||||
pub(crate) async fn read_current_workspace_setting_handler(
|
||||
folder: AFPluginState<Weak<FolderManager>>,
|
||||
@ -212,9 +236,7 @@ pub(crate) async fn move_nested_view_handler(
|
||||
) -> Result<(), FlowyError> {
|
||||
let folder = upgrade_folder(folder)?;
|
||||
let params: MoveNestedViewParams = data.into_inner().try_into()?;
|
||||
folder
|
||||
.move_nested_view(params.view_id, params.new_parent_id, params.prev_view_id)
|
||||
.await?;
|
||||
folder.move_nested_view(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,8 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
|
||||
.event(FolderEvent::ToggleFavorite, toggle_favorites_handler)
|
||||
.event(FolderEvent::UpdateRecentViews, update_recent_views_handler)
|
||||
.event(FolderEvent::ReloadWorkspace, reload_workspace_handler)
|
||||
.event(FolderEvent::ReadPrivateViews, read_private_views_handler)
|
||||
.event(FolderEvent::ReadCurrentWorkspaceViews, get_current_workspace_views_handler)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||
@ -59,9 +61,9 @@ pub enum FolderEvent {
|
||||
#[event(input = "WorkspaceIdPB")]
|
||||
DeleteWorkspace = 3,
|
||||
|
||||
/// Return a list of views of the current workspace.
|
||||
/// Return a list of views of the specified workspace.
|
||||
/// Only the first level of child views are included.
|
||||
#[event(input = "WorkspaceIdPB", output = "RepeatedViewPB")]
|
||||
#[event(input = "GetWorkspaceViewPB", output = "RepeatedViewPB")]
|
||||
ReadWorkspaceViews = 5,
|
||||
|
||||
/// Create a new view in the corresponding app
|
||||
@ -156,4 +158,12 @@ pub enum FolderEvent {
|
||||
|
||||
#[event()]
|
||||
ReloadWorkspace = 38,
|
||||
|
||||
#[event(input = "GetWorkspaceViewPB", output = "RepeatedViewPB")]
|
||||
ReadPrivateViews = 39,
|
||||
|
||||
/// Return a list of views of the current workspace.
|
||||
/// Only the first level of child views are included.
|
||||
#[event(output = "RepeatedViewPB")]
|
||||
ReadCurrentWorkspaceViews = 40,
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ use lib_infra::conditional_send_sync_trait;
|
||||
use crate::entities::icon::UpdateViewIconParams;
|
||||
use crate::entities::{
|
||||
view_pb_with_child_views, view_pb_without_child_views, CreateViewParams, CreateWorkspaceParams,
|
||||
DeletedViewPB, FolderSnapshotPB, RepeatedTrashPB, RepeatedViewIdPB, RepeatedViewPB,
|
||||
UpdateViewParams, ViewPB, WorkspacePB, WorkspaceSettingPB,
|
||||
DeletedViewPB, FolderSnapshotPB, MoveNestedViewParams, RepeatedTrashPB, RepeatedViewIdPB,
|
||||
RepeatedViewPB, UpdateViewParams, ViewPB, ViewSectionPB, WorkspacePB, WorkspaceSettingPB,
|
||||
};
|
||||
use crate::manager_observer::{
|
||||
notify_child_views_changed, notify_did_update_workspace, notify_parent_view_did_change,
|
||||
@ -113,7 +113,7 @@ impl FolderManager {
|
||||
},
|
||||
|folder| {
|
||||
let workspace_pb_from_workspace = |workspace: Workspace, folder: &Folder| {
|
||||
let views = get_workspace_view_pbs(&workspace.id, folder);
|
||||
let views = get_workspace_public_view_pbs(&workspace.id, folder);
|
||||
let workspace: WorkspacePB = (workspace, views).into();
|
||||
Ok::<WorkspacePB, FlowyError>(workspace)
|
||||
};
|
||||
@ -145,7 +145,15 @@ impl FolderManager {
|
||||
|
||||
pub async fn get_workspace_views(&self, workspace_id: &str) -> FlowyResult<Vec<ViewPB>> {
|
||||
let views = self.with_folder(Vec::new, |folder| {
|
||||
get_workspace_view_pbs(workspace_id, folder)
|
||||
get_workspace_public_view_pbs(workspace_id, folder)
|
||||
});
|
||||
|
||||
Ok(views)
|
||||
}
|
||||
|
||||
pub async fn get_workspace_private_views(&self, workspace_id: &str) -> FlowyResult<Vec<ViewPB>> {
|
||||
let views = self.with_folder(Vec::new, |folder| {
|
||||
get_workspace_private_view_pbs(workspace_id, folder)
|
||||
});
|
||||
|
||||
Ok(views)
|
||||
@ -452,11 +460,16 @@ impl FolderManager {
|
||||
}
|
||||
|
||||
let index = params.index;
|
||||
let section = params.section.clone().unwrap_or(ViewSectionPB::Public);
|
||||
let is_private = section == ViewSectionPB::Private;
|
||||
let view = create_view(self.user.user_id()?, params, view_layout);
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.insert_view(view.clone(), index);
|
||||
if is_private {
|
||||
folder.add_private_view_ids(vec![view.id.clone()]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -609,18 +622,26 @@ impl FolderManager {
|
||||
/// * `prev_view_id` - An `Option<String>` that holds the id of the view after which the `view_id` should be positioned.
|
||||
///
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn move_nested_view(
|
||||
&self,
|
||||
view_id: String,
|
||||
new_parent_id: String,
|
||||
prev_view_id: Option<String>,
|
||||
) -> FlowyResult<()> {
|
||||
pub async fn move_nested_view(&self, params: MoveNestedViewParams) -> FlowyResult<()> {
|
||||
let view_id = params.view_id;
|
||||
let new_parent_id = params.new_parent_id;
|
||||
let prev_view_id = params.prev_view_id;
|
||||
let from_section = params.from_section;
|
||||
let to_section = params.to_section;
|
||||
let view = self.get_view_pb(&view_id).await?;
|
||||
let old_parent_id = view.parent_view_id;
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.move_nested_view(&view_id, &new_parent_id, prev_view_id);
|
||||
|
||||
if from_section != to_section {
|
||||
if to_section == Some(ViewSectionPB::Private) {
|
||||
folder.add_private_view_ids(vec![view_id.clone()]);
|
||||
} else {
|
||||
folder.delete_private_view_ids(vec![view_id.clone()]);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
notify_parent_view_did_change(
|
||||
@ -743,6 +764,8 @@ impl FolderManager {
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index,
|
||||
// TODO: lucas.xu fetch the section from the view
|
||||
section: Some(ViewSectionPB::Public),
|
||||
};
|
||||
|
||||
self.create_view_with_params(duplicate_params).await?;
|
||||
@ -954,6 +977,8 @@ impl FolderManager {
|
||||
meta: Default::default(),
|
||||
set_as_current: false,
|
||||
index: None,
|
||||
// TODO: Lucas.xu fetch the section from the view
|
||||
section: Some(ViewSectionPB::Public),
|
||||
};
|
||||
|
||||
let view = create_view(self.user.user_id()?, params, import_data.view_layout);
|
||||
@ -1110,16 +1135,61 @@ impl FolderManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the views that belong to the workspace. The views are filtered by the trash.
|
||||
pub(crate) fn get_workspace_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
|
||||
let items = folder.get_all_trash();
|
||||
let trash_ids = items
|
||||
/// Return the views that belong to the workspace. The views are filtered by the trash and all the private views.
|
||||
pub(crate) fn get_workspace_public_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
|
||||
// get the trash ids
|
||||
let trash_ids = folder
|
||||
.get_all_trash()
|
||||
.into_iter()
|
||||
.map(|trash| trash.id)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// get the private view ids
|
||||
let private_view_ids = folder
|
||||
.get_all_private_views()
|
||||
.into_iter()
|
||||
.map(|view| view.id)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut views = folder.get_workspace_views();
|
||||
views.retain(|view| !trash_ids.contains(&view.id));
|
||||
|
||||
// filter the views that are in the trash and all the private views
|
||||
views.retain(|view| !trash_ids.contains(&view.id) && !private_view_ids.contains(&view.id));
|
||||
|
||||
views
|
||||
.into_iter()
|
||||
.map(|view| {
|
||||
// Get child views
|
||||
let child_views = folder
|
||||
.views
|
||||
.get_views_belong_to(&view.id)
|
||||
.into_iter()
|
||||
.collect();
|
||||
view_pb_with_child_views(view, child_views)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the current private views of the user.
|
||||
pub(crate) fn get_workspace_private_view_pbs(_workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
|
||||
// get the trash ids
|
||||
let trash_ids = folder
|
||||
.get_all_trash()
|
||||
.into_iter()
|
||||
.map(|trash| trash.id)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// get the private view ids
|
||||
let private_view_ids = folder
|
||||
.get_my_private_views()
|
||||
.into_iter()
|
||||
.map(|view| view.id)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let mut views = folder.get_workspace_views();
|
||||
|
||||
// filter the views that are in the trash and not in the private view ids
|
||||
views.retain(|view| !trash_ids.contains(&view.id) && private_view_ids.contains(&view.id));
|
||||
|
||||
views
|
||||
.into_iter()
|
||||
|
@ -14,9 +14,9 @@ use lib_dispatch::prelude::af_spawn;
|
||||
|
||||
use crate::entities::{
|
||||
view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, FolderSnapshotStatePB,
|
||||
FolderSyncStatePB, RepeatedTrashPB, RepeatedViewPB, ViewPB,
|
||||
FolderSyncStatePB, RepeatedTrashPB, RepeatedViewPB, SectionViewsPB, ViewPB, ViewSectionPB,
|
||||
};
|
||||
use crate::manager::{get_workspace_view_pbs, MutexFolder};
|
||||
use crate::manager::{get_workspace_private_view_pbs, get_workspace_public_view_pbs, MutexFolder};
|
||||
use crate::notification::{send_notification, FolderNotification};
|
||||
|
||||
/// Listen on the [ViewChange] after create/delete/update events happened
|
||||
@ -161,7 +161,8 @@ pub(crate) fn notify_parent_view_did_change<T: AsRef<str>>(
|
||||
// if the view's parent id equal to workspace id. Then it will fetch the current
|
||||
// workspace views. Because the the workspace is not a view stored in the views map.
|
||||
if parent_view_id == workspace_id {
|
||||
notify_did_update_workspace(&workspace_id, folder)
|
||||
notify_did_update_workspace(&workspace_id, folder);
|
||||
notify_did_update_section_views(&workspace_id, folder);
|
||||
} else {
|
||||
// Parent view can contain a list of child views. Currently, only get the first level
|
||||
// child views.
|
||||
@ -181,8 +182,35 @@ pub(crate) fn notify_parent_view_did_change<T: AsRef<str>>(
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn notify_did_update_section_views(workspace_id: &str, folder: &Folder) {
|
||||
let public_views = get_workspace_public_view_pbs(workspace_id, folder);
|
||||
let private_views = get_workspace_private_view_pbs(workspace_id, folder);
|
||||
tracing::trace!(
|
||||
"Did update section views: public len = {}, private len = {}",
|
||||
public_views.len(),
|
||||
private_views.len()
|
||||
);
|
||||
|
||||
// TODO(Lucas.xu) - Only notify the section changed, not the public/private both.
|
||||
// Notify the public views
|
||||
send_notification(workspace_id, FolderNotification::DidUpdateSectionViews)
|
||||
.payload(SectionViewsPB {
|
||||
section: ViewSectionPB::Public,
|
||||
views: public_views,
|
||||
})
|
||||
.send();
|
||||
|
||||
// Notify the private views
|
||||
send_notification(workspace_id, FolderNotification::DidUpdateSectionViews)
|
||||
.payload(SectionViewsPB {
|
||||
section: ViewSectionPB::Private,
|
||||
views: private_views,
|
||||
})
|
||||
.send();
|
||||
}
|
||||
|
||||
pub(crate) fn notify_did_update_workspace(workspace_id: &str, folder: &Folder) {
|
||||
let repeated_view: RepeatedViewPB = get_workspace_view_pbs(workspace_id, folder).into();
|
||||
let repeated_view: RepeatedViewPB = get_workspace_public_view_pbs(workspace_id, folder).into();
|
||||
tracing::trace!("Did update workspace views: {:?}", repeated_view);
|
||||
send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceViews)
|
||||
.payload(repeated_view)
|
||||
|
@ -35,6 +35,9 @@ pub enum FolderNotification {
|
||||
DidUnfavoriteView = 37,
|
||||
|
||||
DidUpdateRecentViews = 38,
|
||||
|
||||
/// Trigger when the ROOT views (the first level) in section are updated
|
||||
DidUpdateSectionViews = 39,
|
||||
}
|
||||
|
||||
impl std::convert::From<FolderNotification> for i32 {
|
||||
@ -60,6 +63,8 @@ impl std::convert::From<i32> for FolderNotification {
|
||||
17 => FolderNotification::DidUpdateFolderSyncUpdate,
|
||||
36 => FolderNotification::DidFavoriteView,
|
||||
37 => FolderNotification::DidUnfavoriteView,
|
||||
38 => FolderNotification::DidUpdateRecentViews,
|
||||
39 => FolderNotification::DidUpdateSectionViews,
|
||||
_ => FolderNotification::Unknown,
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use flowy_folder_pub::cloud::gen_view_id;
|
||||
|
||||
use crate::entities::{CreateViewParams, ViewLayoutPB};
|
||||
use crate::entities::{CreateViewParams, ViewLayoutPB, ViewSectionPB};
|
||||
use crate::manager::FolderManager;
|
||||
|
||||
#[cfg(feature = "test_helper")]
|
||||
@ -47,6 +47,7 @@ impl FolderManager {
|
||||
meta: ext,
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: Some(ViewSectionPB::Public),
|
||||
};
|
||||
self.create_view_with_params(params).await.unwrap();
|
||||
view_id
|
||||
|
@ -54,6 +54,7 @@ impl DefaultFolderBuilder {
|
||||
favorites: Default::default(),
|
||||
recent: Default::default(),
|
||||
trash: Default::default(),
|
||||
private: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user