Merge branch 'AppFlowy-IO:main' into main

This commit is contained in:
Simon
2024-08-29 20:38:07 +02:00
committed by GitHub
360 changed files with 9323 additions and 2400 deletions

View File

@ -1,4 +1,17 @@
# Release Notes
## Version 0.6.8 - 22/08/2024
### New Features
- Optimized date picker and mention block.
- Added the ability to open database row on mobile.
- Added the ability to invite members to workspace on mobile.
- Added support for Monochrome theme on Android.
- Added AI Bubble button on homepage on mobile.
- Settings, trash, members and help & support have been moved into the settings pop up menu.
### Bug Fixes
- Removed Wayland header from AppImage build
- Fixed the issue where pasting web image on mobile failed.
## Version 0.6.7 - 13/08/2024
### New Features
- Redesigned the icon picker design on Desktop.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

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

View File

@ -1,7 +1,7 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@ -92,7 +92,7 @@ void main() {
);
expect(finder, findsOneWidget);
await tester.tapButton(finder);
expect(find.byType(FlowyErrorPage), findsOneWidget);
expect(find.byType(AppFlowyErrorPage), findsOneWidget);
});
});
}

View File

@ -12,7 +12,7 @@ import 'util.dart';
extension AppFlowyAuthTest on WidgetTester {
Future<void> tapGoogleLoginInButton() async {
await tapButton(
find.byKey(const Key('signInWithGoogleButton')),
find.byKey(signInWithGoogleButtonKey),
);
}
@ -36,7 +36,7 @@ extension AppFlowyAuthTest on WidgetTester {
}
void expectToSeeGoogleLoginButton() {
expect(find.byKey(const Key('signInWithGoogleButton')), findsOneWidget);
expect(find.byKey(signInWithGoogleButtonKey), findsOneWidget);
}
void assertSwitchValue(Finder finder, bool value) {

View File

@ -372,6 +372,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AppFlowy;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -383,6 +384,8 @@
STRIP_STYLE = "non-global";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
@ -511,6 +514,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AppFlowy;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -522,6 +526,8 @@
STRIP_STYLE = "non-global";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -545,6 +551,7 @@
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AppFlowy;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@ -556,6 +563,8 @@
STRIP_STYLE = "non-global";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;

View File

@ -1,75 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>AppFlowy requires access to the camera.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>AppFlowy requires access to the photo library.</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
</array>
<key>FLTEnableImpeller</key>
<false />
<key>CFBundleName</key>
<string>AppFlowy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>appflowy-flutter</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>UIApplicationSupportsIndirectInputEvents</key>
<true />
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false />
<key>NSAppTransportSecurity</key>
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
</array>
<key>CFBundleName</key>
<string>AppFlowy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>appflowy-flutter</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>FLTEnableImpeller</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</plist>
<key>NSCameraUsageDescription</key>
<string>AppFlowy requires access to the camera.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>AppFlowy requires access to the photo library.</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>

View File

@ -65,7 +65,7 @@ class _MobileBottomSheetRenameWidgetState
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
textColor: Colors.white,
fontColor: Colors.white,
fillColor: Theme.of(context).primaryColor,
onPressed: () {
widget.onRename(controller.text);

View File

@ -64,6 +64,10 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
case MobileViewItemBottomSheetBodyAction.duplicate:
Navigator.pop(context);
context.read<ViewBloc>().add(const ViewEvent.duplicate());
showToastNotification(
context,
message: LocaleKeys.button_duplicateSuccessfully.tr(),
);
break;
case MobileViewItemBottomSheetBodyAction.share:
// unimplemented
@ -79,6 +83,12 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(widget.view));
showToastNotification(
context,
message: !widget.view.isFavorite
? LocaleKeys.button_favoriteSuccessfully.tr()
: LocaleKeys.button_unfavoriteSuccessfully.tr(),
);
break;
case MobileViewItemBottomSheetBodyAction.removeFromRecent:
_removeFromRecent(context);

View File

@ -3,13 +3,13 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/board/board.dart';
import 'package:appflowy/mobile/presentation/database/board/widgets/group_card_header.dart';
import 'package:appflowy/mobile/presentation/database/card/card.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:appflowy/plugins/database/widgets/card/card.dart';
import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart';
import 'package:appflowy/plugins/database/widgets/cell/card_cell_style_maps/mobile_board_card_cell_style.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
@ -69,10 +69,8 @@ class _MobileBoardPageState extends State<MobileBoardPage> {
loading: (_) => const Center(
child: CircularProgressIndicator.adaptive(),
),
error: (err) => FlowyMobileStateContainer.error(
emoji: '🛸',
title: LocaleKeys.board_mobile_failedToLoad.tr(),
errorMsg: err.toString(),
error: (err) => AppFlowyErrorPage(
error: err.error,
),
ready: (data) => const _BoardContent(),
orElse: () => const SizedBox.shrink(),

View File

@ -45,6 +45,7 @@ class MobileFolders extends StatelessWidget {
SpaceEvent.reset(
user,
state.currentWorkspace?.workspaceId ?? workspaceId,
false,
),
);
},

View File

@ -177,7 +177,13 @@ class _HomePageState extends State<_HomePage> {
getIt<CachedRecentService>().reset();
mCurrentWorkspace.value = state.currentWorkspace;
_showResultDialog(context, state);
Debounce.debounce(
'workspace_action_result',
const Duration(milliseconds: 150),
() {
_showResultDialog(context, state);
},
);
},
builder: (context, state) {
if (state.currentWorkspace == null) {

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/gesture.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
@ -47,15 +47,6 @@ class MobileHomePageHeader extends StatelessWidget {
: _MobileUser(userProfile: userProfile),
),
const HomePageSettingsPopupMenu(),
// GestureDetector(
// onTap: () => context.push(
// MobileHomeSettingPage.routeName,
// ),
// child: const Padding(
// padding: EdgeInsets.all(8.0),
// child: FlowySvg(FlowySvgs.m_notification_settings_s),
// ),
// ),
const HSpace(8.0),
],
),
@ -118,6 +109,7 @@ class _MobileWorkspace extends StatelessWidget {
return const SizedBox.shrink();
}
return AnimatedGestureDetector(
scaleFactor: 0.99,
alignment: Alignment.centerLeft,
onTapUp: () {
context.read<UserWorkspaceBloc>().add(

View File

@ -3,9 +3,11 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart';
import 'package:appflowy/shared/popup_menu/appflowy_popup_menu.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/material.dart'
hide PopupMenuButton, PopupMenuDivider, PopupMenuItem, PopupMenuEntry;
import 'package:go_router/go_router.dart';
enum _MobileSettingsPopupMenuItem {
@ -28,9 +30,9 @@ class HomePageSettingsPopupMenu extends StatelessWidget {
Radius.circular(12.0),
),
),
// todo: replace it with shadows
shadowColor: const Color(0x68000000),
elevation: 10,
color: Theme.of(context).colorScheme.surface,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlowySvg(

View File

@ -36,6 +36,7 @@ class EmptySpacePlaceholder extends StatelessWidget {
lineHeight: 1.3,
color: Theme.of(context).hintColor,
),
const VSpace(kBottomNavigationBarHeight + 36.0),
],
),
);

View File

@ -5,7 +5,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart';
import 'package:appflowy/mobile/presentation/base/gesture.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';

View File

@ -4,11 +4,13 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart';
import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/shared/list_extension.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -124,8 +126,15 @@ class _Pages extends StatelessWidget {
final spaceType = space.spacePermission == SpacePermission.publicToAll
? FolderSpaceType.public
: FolderSpaceType.private;
final childViews = state.view.childViews.unique((view) => view.id);
if (childViews.length != state.view.childViews.length) {
final duplicatedViews = state.view.childViews
.where((view) => childViews.contains(view))
.toList();
Log.error('some view id are duplicated: $duplicatedViews');
}
return Column(
children: state.view.childViews
children: childViews
.map(
(view) => MobileViewItem(
key: ValueKey(

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/gesture.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:easy_localization/easy_localization.dart';

View File

@ -1,5 +1,5 @@
import 'package:appflowy/mobile/application/notification/notification_reminder_bloc.dart';
import 'package:appflowy/mobile/presentation/base/gesture.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/mobile/presentation/notifications/mobile_notifications_screen.dart';
import 'package:appflowy/mobile/presentation/notifications/widgets/widgets.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';

View File

@ -1,6 +1,6 @@
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/application/notification/notification_reminder_bloc.dart';
import 'package:appflowy/mobile/presentation/base/gesture.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/mobile/presentation/notifications/widgets/widgets.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';

View File

@ -1,12 +1,14 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/shared/popup_menu/appflowy_popup_menu.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart'
hide PopupMenuButton, PopupMenuDivider, PopupMenuItem, PopupMenuEntry;
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';

View File

@ -16,13 +16,10 @@ class AppFlowyCloudPage extends StatelessWidget {
appBar: FlowyAppBar(
titleText: LocaleKeys.settings_menu_cloudSettings.tr(),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: SettingCloud(
restartAppFlowy: () async {
await runAppFlowy();
},
),
body: SettingCloud(
restartAppFlowy: () async {
await runAppFlowy();
},
),
);
}

View File

@ -33,7 +33,9 @@ class UserSessionSettingGroup extends StatelessWidget {
);
},
builder: (context, state) {
return const ThirdPartySignInButtons();
return const ThirdPartySignInButtons(
expanded: true,
);
},
),
),

View File

@ -34,6 +34,7 @@ class InviteMembersScreen extends StatelessWidget {
titleText: LocaleKeys.settings_appearance_members_label.tr(),
),
body: const _InviteMemberPage(),
resizeToAvoidBottomInset: false,
);
}
}
@ -193,6 +194,9 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
final actionType = actionResult.actionType;
final result = actionResult.result;
// get keyboard height
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
// only show the result dialog when the action is WorkspaceMemberActionType.add
if (actionType == WorkspaceMemberActionType.add) {
result.fold(
@ -201,6 +205,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
context,
message:
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -216,6 +221,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
showToastNotification(
context,
type: ToastificationType.error,
bottomPadding: keyboardHeight,
message: message,
);
},
@ -227,6 +233,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
context,
message:
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -244,6 +251,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
context,
type: ToastificationType.error,
message: message,
bottomPadding: keyboardHeight,
);
},
);
@ -255,6 +263,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceSuccess
.tr(),
bottomPadding: keyboardHeight,
);
},
(f) {
@ -264,6 +273,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceFailed
.tr(),
bottomPadding: keyboardHeight,
);
},
);

View File

@ -89,6 +89,15 @@ class CellController<T, D> {
fieldId: _cellContext.fieldId,
);
_rowCache.addListener(
rowId: rowId,
onRowChanged: (context, reason) {
if (reason == const ChangedReason.didFetchRow()) {
_onRowMetaChanged?.call();
}
},
);
// 1. Listen on user edit event and load the new cell data if needed.
// For example:
// user input: 12

View File

@ -44,7 +44,8 @@ class RowCache {
for (final fieldInfo in fieldInfos) {
_cellMemCache.removeCellWithFieldId(fieldInfo.id);
}
_changedNotifier.receive(const ChangedReason.fieldDidChange());
_changedNotifier?.receive(const ChangedReason.fieldDidChange());
});
}
@ -53,7 +54,9 @@ class RowCache {
final CellMemCache _cellMemCache;
final RowLifeCycle _rowLifeCycle;
final RowFieldsDelegate _fieldDelegate;
final RowChangesetNotifier _changedNotifier;
RowChangesetNotifier? _changedNotifier;
bool _isInitialRows = false;
final List<RowsVisibilityChangePB> _pendingVisibilityChanges = [];
/// Returns a unmodifiable list of RowInfo
UnmodifiableListView<RowInfo> get rowInfos {
@ -67,7 +70,8 @@ class RowCache {
}
CellMemCache get cellCache => _cellMemCache;
ChangedReason get changeReason => _changedNotifier.reason;
ChangedReason get changeReason =>
_changedNotifier?.reason ?? const InitialListState();
RowInfo? getRow(RowId rowId) {
return _rowList.get(rowId);
@ -78,18 +82,25 @@ class RowCache {
final rowInfo = buildGridRow(row);
_rowList.add(rowInfo);
}
_changedNotifier.receive(const ChangedReason.setInitialRows());
_isInitialRows = true;
_changedNotifier?.receive(const ChangedReason.setInitialRows());
for (final changeset in _pendingVisibilityChanges) {
applyRowsVisibility(changeset);
}
_pendingVisibilityChanges.clear();
}
void setRowMeta(RowMetaPB rowMeta) {
final rowInfo = buildGridRow(rowMeta);
_rowList.add(rowInfo);
_changedNotifier.receive(const ChangedReason.didFetchRow());
_changedNotifier?.receive(const ChangedReason.didFetchRow());
}
void dispose() {
_rowLifeCycle.onRowDisposed();
_changedNotifier.dispose();
_changedNotifier?.dispose();
_changedNotifier = null;
_cellMemCache.dispose();
}
@ -100,13 +111,17 @@ class RowCache {
}
void applyRowsVisibility(RowsVisibilityChangePB changeset) {
_hideRows(changeset.invisibleRows);
_showRows(changeset.visibleRows);
if (_isInitialRows) {
_hideRows(changeset.invisibleRows);
_showRows(changeset.visibleRows);
} else {
_pendingVisibilityChanges.add(changeset);
}
}
void reorderAllRows(List<String> rowIds) {
_rowList.reorderWithRowIds(rowIds);
_changedNotifier.receive(const ChangedReason.reorderRows());
_changedNotifier?.receive(const ChangedReason.reorderRows());
}
void reorderSingleRow(ReorderSingleRowPB reorderRow) {
@ -117,7 +132,7 @@ class RowCache {
reorderRow.oldIndex,
reorderRow.newIndex,
);
_changedNotifier.receive(
_changedNotifier?.receive(
ChangedReason.reorderSingleRow(
reorderRow,
rowInfo,
@ -130,7 +145,7 @@ class RowCache {
for (final rowId in deletedRowIds) {
final deletedRow = _rowList.remove(rowId);
if (deletedRow != null) {
_changedNotifier.receive(ChangedReason.delete(deletedRow));
_changedNotifier?.receive(ChangedReason.delete(deletedRow));
}
}
}
@ -140,7 +155,7 @@ class RowCache {
final insertedIndex =
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
if (insertedIndex != null) {
_changedNotifier.receive(ChangedReason.insert(insertedIndex));
_changedNotifier?.receive(ChangedReason.insert(insertedIndex));
}
}
}
@ -165,7 +180,7 @@ class RowCache {
_rowList.updateRows(updatedList, (rowId) => buildGridRow(rowId));
if (updatedIndexs.isNotEmpty) {
_changedNotifier.receive(ChangedReason.update(updatedIndexs));
_changedNotifier?.receive(ChangedReason.update(updatedIndexs));
}
}
@ -173,7 +188,7 @@ class RowCache {
for (final rowId in invisibleRows) {
final deletedRow = _rowList.remove(rowId);
if (deletedRow != null) {
_changedNotifier.receive(ChangedReason.delete(deletedRow));
_changedNotifier?.receive(ChangedReason.delete(deletedRow));
}
}
}
@ -183,14 +198,16 @@ class RowCache {
final insertedIndex =
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
if (insertedIndex != null) {
_changedNotifier.receive(ChangedReason.insert(insertedIndex));
_changedNotifier?.receive(ChangedReason.insert(insertedIndex));
}
}
}
void onRowsChanged(void Function(ChangedReason) onRowChanged) {
_changedNotifier.addListener(() {
onRowChanged(_changedNotifier.reason);
_changedNotifier?.addListener(() {
if (_changedNotifier != null) {
onRowChanged(_changedNotifier!.reason);
}
});
}
@ -203,17 +220,19 @@ class RowCache {
final rowInfo = _rowList.get(rowId);
if (rowInfo != null) {
final cellDataMap = _makeCells(rowInfo.rowMeta);
onRowChanged(cellDataMap, _changedNotifier.reason);
if (_changedNotifier != null) {
onRowChanged(cellDataMap, _changedNotifier!.reason);
}
}
}
}
_changedNotifier.addListener(listenerHandler);
_changedNotifier?.addListener(listenerHandler);
return listenerHandler;
}
void removeRowListener(VoidCallback callback) {
_changedNotifier.removeListener(callback);
_changedNotifier?.removeListener(callback);
}
List<CellContext> loadCells(RowMetaPB rowMeta) {
@ -242,7 +261,7 @@ class RowCache {
rowId: rowMetaPB.id,
);
_changedNotifier.receive(ChangedReason.update(updatedIndexs));
_changedNotifier?.receive(ChangedReason.update(updatedIndexs));
}
},
(err) => Log.error(err),

View File

@ -1,6 +1,8 @@
import 'dart:async';
import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy/plugins/database/domain/row_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flutter/material.dart';
import '../cell/cell_cache.dart';
@ -18,21 +20,11 @@ class RowController {
}) : _rowMeta = rowMeta,
_rowCache = rowCache,
_rowBackendSvc = RowBackendService(viewId: viewId),
_rowListener = RowListener(rowMeta.id) {
_rowBackendSvc.initRow(rowMeta.id);
_rowListener.start(
onMetaChanged: (newRowMeta) {
if (_isDisposed) {
return;
}
_rowMeta = newRowMeta;
_rowCache.setRowMeta(newRowMeta);
},
);
}
_rowListener = RowListener(rowMeta.id);
RowMetaPB _rowMeta;
final String? groupId;
VoidCallback? _onRowMetaChanged;
final String viewId;
final List<VoidCallback> _onRowChangedListeners = [];
final RowCache _rowCache;
@ -40,14 +32,52 @@ class RowController {
final RowBackendService _rowBackendSvc;
bool _isDisposed = false;
CellMemCache get cellCache => _rowCache.cellCache;
String get rowId => rowMeta.id;
RowMetaPB get rowMeta => _rowMeta;
CellMemCache get cellCache => _rowCache.cellCache;
List<CellContext> loadCells() => _rowCache.loadCells(rowMeta);
void addListener({OnRowChanged? onRowChanged}) {
Future<void> initialize() async {
await _rowBackendSvc.initRow(rowMeta.id);
unawaited(
_rowBackendSvc.getRowMeta(rowId).then(
(result) {
if (_isDisposed) {
return;
}
result.fold(
(rowMeta) {
_rowMeta = rowMeta;
_rowCache.setRowMeta(rowMeta);
_onRowMetaChanged?.call();
},
(error) => debugPrint(error.toString()),
);
},
),
);
_rowListener.start(
onRowFetched: (DidFetchRowPB row) {
_rowCache.setRowMeta(row.meta);
},
onMetaChanged: (newRowMeta) {
if (_isDisposed) {
return;
}
_rowMeta = newRowMeta;
_rowCache.setRowMeta(newRowMeta);
_onRowMetaChanged?.call();
},
);
}
void addListener({
OnRowChanged? onRowChanged,
VoidCallback? onMetaChanged,
}) {
final fn = _rowCache.addListener(
rowId: rowMeta.id,
onRowChanged: (context, reasons) {
@ -60,6 +90,7 @@ class RowController {
// Add the listener to the list so that we can remove it later.
_onRowChangedListeners.add(fn);
_onRowMetaChanged = onMetaChanged;
}
Future<void> dispose() async {

View File

@ -2,9 +2,7 @@ import 'dart:async';
import 'dart:collection';
import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import '../defines.dart';
import '../field/field_controller.dart';
@ -93,17 +91,6 @@ class DatabaseViewCache {
(reorderRow) => _rowCache.reorderSingleRow(reorderRow),
(err) => Log.error(err),
),
onReloadRows: () {
final payload = DatabaseViewIdPB(value: viewId);
DatabaseEventGetAllRows(payload).send().then((result) {
result.fold(
(rows) {
_rowCache.setInitialRows(rows.items);
},
(err) => Log.error(err),
);
});
},
);
_rowCache.onRowsChanged(

View File

@ -32,7 +32,6 @@ class DatabaseViewListener {
required ReorderAllRowsCallback onReorderAllRows,
required SingleRowCallback onReorderSingleRow,
required RowsVisibilityCallback onRowsVisibilityChanged,
required void Function() onReloadRows,
}) {
// Stop any existing listener
_listener?.stop();
@ -47,7 +46,6 @@ class DatabaseViewListener {
onReorderAllRows,
onReorderSingleRow,
onRowsVisibilityChanged,
onReloadRows,
),
);
}
@ -59,7 +57,6 @@ class DatabaseViewListener {
ReorderAllRowsCallback onReorderAllRows,
SingleRowCallback onReorderSingleRow,
RowsVisibilityCallback onRowsVisibilityChanged,
void Function() onReloadRows,
) {
switch (ty) {
case DatabaseNotification.DidUpdateViewRowsVisibility:
@ -94,9 +91,6 @@ class DatabaseViewListener {
(error) => onReorderSingleRow(FlowyResult.failure(error)),
);
break;
case DatabaseNotification.ReloadRows:
onReloadRows();
break;
default:
break;
}

View File

@ -202,11 +202,18 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
);
},
endEditingHeader: (String groupId, String? groupName) async {
await groupBackendSvc.updateGroup(
fieldId: groupControllers.values.first.group.fieldId,
groupId: groupId,
name: groupName,
);
final group = groupControllers[groupId]?.group;
if (group != null) {
if (generateGroupNameFromGroup(group) != groupName) {
await groupBackendSvc.updateGroup(
fieldId: groupControllers.values.first.group.fieldId,
groupId: groupId,
name: groupName,
);
return;
}
}
state.maybeMap(
ready: (state) => emit(state.copyWith(editingHeaderId: null)),
orElse: () {},

View File

@ -556,10 +556,8 @@ class _BoardCardState extends State<_BoardCard> {
@override
Widget build(BuildContext context) {
final boardBloc = context.read<BoardBloc>();
final groupData = widget.afGroupData.customData as GroupData;
final rowCache = boardBloc.rowCache;
final databaseController = boardBloc.databaseController;
final rowMeta =
rowCache.getRow(widget.groupItem.id)?.rowMeta ?? widget.groupItem.row;
@ -653,7 +651,7 @@ class _BoardCardState extends State<_BoardCard> {
onTap: (context) => _openCard(
context: context,
databaseController: databaseController,
rowMeta: context.read<CardBloc>().state.rowMeta,
rowMeta: context.read<CardBloc>().rowController.rowMeta,
),
onShiftTap: (_) {
Focus.of(context).requestFocus();

View File

@ -42,6 +42,8 @@ class HiddenGroupsColumn extends StatelessWidget {
return const SizedBox.shrink();
}
final isCollapsed = layoutSettings.collapseHiddenGroups;
final leftPadding = margin.left +
context.read<DatabasePluginWidgetBuilderSize>().horizontalPadding;
return AnimatedSize(
alignment: AlignmentDirectional.topStart,
curve: Curves.easeOut,
@ -56,35 +58,29 @@ class HiddenGroupsColumn extends StatelessWidget {
),
),
)
: SizedBox(
: Container(
width: 274,
padding: EdgeInsets.only(
left: leftPadding,
right: margin.right + 4,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 50,
child: Padding(
padding: EdgeInsets.only(
left: margin.left +
context
.read<DatabasePluginWidgetBuilderSize>()
.horizontalPadding,
right: margin.right + 4,
),
child: Row(
children: [
Expanded(
child: FlowyText.medium(
LocaleKeys
.board_hiddenGroupSection_sectionTitle
.tr(),
overflow: TextOverflow.ellipsis,
color: Theme.of(context).hintColor,
),
child: Row(
children: [
Expanded(
child: FlowyText.medium(
LocaleKeys.board_hiddenGroupSection_sectionTitle
.tr(),
overflow: TextOverflow.ellipsis,
color: Theme.of(context).hintColor,
),
_collapseExpandIcon(context, isCollapsed),
],
),
),
_collapseExpandIcon(context, isCollapsed),
],
),
),
Expanded(
@ -204,31 +200,27 @@ class _HiddenGroupCardState extends State<HiddenGroupCard> {
final databaseController = widget.bloc.databaseController;
final primaryField = databaseController.fieldController.fieldInfos
.firstWhereOrNull((element) => element.isPrimary)!;
return Padding(
padding: const EdgeInsets.only(left: 26),
child: AppFlowyPopover(
controller: _popoverController,
direction: PopoverDirection.bottomWithCenterAligned,
triggerActions: PopoverTriggerFlags.none,
constraints: const BoxConstraints(maxWidth: 234, maxHeight: 300),
popupBuilder: (popoverContext) {
return BlocProvider.value(
value: context.read<BoardBloc>(),
child: HiddenGroupPopupItemList(
viewId: databaseController.viewId,
groupId: widget.group.groupId,
primaryFieldId: primaryField.id,
rowCache: databaseController.rowCache,
),
);
},
child: HiddenGroupButtonContent(
popoverController: _popoverController,
groupId: widget.group.groupId,
index: widget.index,
bloc: widget.bloc,
),
return AppFlowyPopover(
controller: _popoverController,
direction: PopoverDirection.bottomWithCenterAligned,
triggerActions: PopoverTriggerFlags.none,
constraints: const BoxConstraints(maxWidth: 234, maxHeight: 300),
popupBuilder: (popoverContext) {
return BlocProvider.value(
value: context.read<BoardBloc>(),
child: HiddenGroupPopupItemList(
viewId: databaseController.viewId,
groupId: widget.group.groupId,
primaryFieldId: primaryField.id,
rowCache: databaseController.rowCache,
),
);
},
child: HiddenGroupButtonContent(
popoverController: _popoverController,
groupId: widget.group.groupId,
index: widget.index,
bloc: widget.bloc,
),
);
}

View File

@ -26,6 +26,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
_dispatch();
_startListening();
_init();
rowController.initialize();
}
final FieldController fieldController;

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart';
@ -305,22 +307,26 @@ class _GridRowsState extends State<_GridRows> {
buildWhen: (previous, current) => previous.fields != current.fields,
builder: (context, state) {
return Flexible(
child: _WrapScrollView(
scrollController: widget.scrollController,
contentWidth: GridLayout.headerWidth(state.fields),
child: BlocConsumer<GridBloc, GridState>(
listenWhen: (previous, current) =>
previous.rowCount != current.rowCount,
listener: (context, state) => _evaluateFloatingCalculations(),
builder: (context, state) {
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
scrollbars: false,
),
child: _renderList(context, state),
);
},
),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints layoutConstraits) {
return _WrapScrollView(
scrollController: widget.scrollController,
contentWidth: GridLayout.headerWidth(state.fields),
child: BlocConsumer<GridBloc, GridState>(
listenWhen: (previous, current) =>
previous.rowCount != current.rowCount,
listener: (context, state) => _evaluateFloatingCalculations(),
builder: (context, state) {
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
scrollbars: false,
),
child: _renderList(context, state, layoutConstraits),
);
},
),
);
},
),
);
},
@ -330,19 +336,19 @@ class _GridRowsState extends State<_GridRows> {
Widget _renderList(
BuildContext context,
GridState state,
BoxConstraints layoutConstraints,
) {
// 1. GridRowBottomBar
// 2. GridCalculationsRow
// 3. Footer Padding
final itemCount = state.rowInfos.length + 3;
return Stack(
children: [
Positioned.fill(
child: ReorderableListView.builder(
/// This is a workaround related to
/// https://github.com/flutter/flutter/issues/25652
cacheExtent: 5000,
cacheExtent: max(layoutConstraints.maxHeight * 2, 500),
scrollController: widget.scrollController.verticalController,
physics: const ClampingScrollPhysics(),
buildDefaultDragHandles: false,
@ -421,7 +427,7 @@ class _GridRowsState extends State<_GridRows> {
);
final child = GridRow(
key: ValueKey(rowMeta.id),
key: ValueKey(rowId),
fieldController: databaseController.fieldController,
rowId: rowId,
viewId: viewId,

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart';
@ -9,6 +7,7 @@ import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/shortcuts.dart';
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy_backend/log.dart';
@ -18,7 +17,7 @@ import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
@ -106,9 +105,8 @@ class _MobileGridPageState extends State<MobileGridPage> {
_openRow(context, widget.initialRowId, true);
return result.successOrFail.fold(
(_) => GridShortcuts(child: GridPageContent(view: widget.view)),
(err) => FlowyErrorPage.message(
err.toString(),
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
(err) => AppFlowyErrorPage(
error: err,
),
);
},

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
@ -9,6 +7,7 @@ import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy/plugins/database/grid/application/row/row_bloc.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/mobile_cell_container.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../layout/sizes.dart';
@ -81,8 +80,8 @@ class _MobileGridRowState extends State<MobileGridRow> {
@override
Future<void> dispose() async {
await _rowController.dispose();
super.dispose();
await _rowController.dispose();
}
}

View File

@ -57,7 +57,7 @@ class _DatabaseViewSettingContent extends StatelessWidget {
builder: (context, state) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: GridSize.horizontalHeaderPadding + 40,
horizontal: GridSize.horizontalHeaderPadding,
),
child: DecoratedBox(
decoration: BoxDecoration(

View File

@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/mobile/presentation/database/card/card.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/row/row_cache.dart';
import 'package:appflowy/plugins/database/application/row/row_controller.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/action.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@ -73,13 +74,18 @@ class _RowCardState extends State<RowCard> {
@override
void initState() {
super.initState();
final rowController = RowController(
viewId: widget.viewId,
rowMeta: widget.rowMeta,
rowCache: widget.rowCache,
);
_cardBloc = CardBloc(
fieldController: widget.fieldController,
viewId: widget.viewId,
groupFieldId: widget.groupingFieldId,
isEditing: widget.isEditing,
rowMeta: widget.rowMeta,
rowCache: widget.rowCache,
rowController: rowController,
)..add(const CardEvent.initial());
}
@ -143,7 +149,7 @@ class _RowCardState extends State<RowCard> {
popupBuilder: (_) {
return RowActionMenu.board(
viewId: _cardBloc.viewId,
rowId: _cardBloc.rowId,
rowId: _cardBloc.rowController.rowId,
groupId: widget.groupId,
);
},

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:appflowy/plugins/database/application/row/row_controller.dart';
import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flutter/foundation.dart';
@ -7,7 +8,6 @@ import 'package:flutter/foundation.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/row/row_cache.dart';
import 'package:appflowy/plugins/database/domain/row_listener.dart';
import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -19,42 +19,35 @@ class CardBloc extends Bloc<CardEvent, CardState> {
required this.fieldController,
required this.groupFieldId,
required this.viewId,
required RowMetaPB rowMeta,
required RowCache rowCache,
required bool isEditing,
}) : rowId = rowMeta.id,
_rowListener = RowListener(rowMeta.id),
_rowCache = rowCache,
super(
required this.rowController,
}) : super(
CardState.initial(
rowMeta,
_makeCells(
fieldController,
groupFieldId,
rowCache.loadCells(rowMeta),
rowController,
),
isEditing,
rowController.rowMeta,
),
) {
_dispatch();
}
final FieldController fieldController;
final String rowId;
final String? groupFieldId;
final RowCache _rowCache;
final String viewId;
final RowListener _rowListener;
final RowController rowController;
VoidCallback? _rowCallback;
@override
Future<void> close() async {
if (_rowCallback != null) {
_rowCache.removeRowListener(_rowCallback!);
_rowCallback = null;
}
await _rowListener.stop();
await rowController.dispose();
return super.close();
}
@ -85,20 +78,17 @@ class CardBloc extends Bloc<CardEvent, CardState> {
}
Future<void> _startListening() async {
_rowCallback = _rowCache.addListener(
rowId: rowId,
rowController.addListener(
onRowChanged: (cellMap, reason) {
if (!isClosed) {
final cells = _makeCells(fieldController, groupFieldId, cellMap);
final cells =
_makeCells(fieldController, groupFieldId, rowController);
add(CardEvent.didReceiveCells(cells, reason));
}
},
);
_rowListener.start(
onMetaChanged: (rowMeta) {
onMetaChanged: () {
if (!isClosed) {
add(CardEvent.didUpdateRowMeta(rowMeta));
add(CardEvent.didUpdateRowMeta(rowController.rowMeta));
}
},
);
@ -108,16 +98,18 @@ class CardBloc extends Bloc<CardEvent, CardState> {
List<CellMeta> _makeCells(
FieldController fieldController,
String? groupFieldId,
List<CellContext> cellContexts,
RowController rowController,
) {
// Only show the non-hidden cells and cells that aren't of the grouping field
cellContexts.removeWhere((cellContext) {
final cellContext = rowController.loadCells();
cellContext.removeWhere((cellContext) {
final fieldInfo = fieldController.getField(cellContext.fieldId);
return fieldInfo == null ||
!(fieldInfo.visibility?.isVisibleState() ?? false) ||
(groupFieldId != null && cellContext.fieldId == groupFieldId);
});
return cellContexts
return cellContext
.map(
(cellCtx) => CellMeta(
fieldId: cellCtx.fieldId,
@ -157,19 +149,19 @@ class CellMeta with _$CellMeta {
class CardState with _$CardState {
const factory CardState({
required List<CellMeta> cells,
required RowMetaPB rowMeta,
required bool isEditing,
required RowMetaPB rowMeta,
ChangedReason? changeReason,
}) = _RowCardState;
factory CardState.initial(
RowMetaPB rowMeta,
List<CellMeta> cells,
bool isEditing,
RowMetaPB rowMeta,
) =>
CardState(
cells: cells,
rowMeta: rowMeta,
isEditing: isEditing,
rowMeta: rowMeta,
);
}

View File

@ -8,6 +8,7 @@ import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -202,8 +203,14 @@ class _URLAccessoryIconContainer extends StatelessWidget {
),
borderRadius: Corners.s6Border,
),
child: Center(
child: child,
child: FlowyHover(
style: HoverStyle(
backgroundColor: AFThemeExtension.of(context).background,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
child: Center(
child: child,
),
),
);
}

View File

@ -1,5 +1,5 @@
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -17,27 +17,28 @@ class MobileGridTextCellSkin extends IEditableTextCellSkin {
) {
return Row(
children: [
const HSpace(10),
BlocBuilder<TextCellBloc, TextCellState>(
buildWhen: (p, c) => p.emoji != c.emoji,
builder: (context, state) => Center(
child: FlowyText(
child: FlowyText.emoji(
state.emoji,
fontSize: 16,
fontSize: 15,
optimizeEmojiAlign: true,
),
),
),
const HSpace(6),
Expanded(
child: TextField(
controller: textEditingController,
focusNode: focusNode,
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 15),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 15,
),
decoration: const InputDecoration(
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 14, vertical: 12),
contentPadding: EdgeInsets.symmetric(horizontal: 4),
isCollapsed: true,
),
onTapOutside: (event) => focusNode.unfocus(),

View File

@ -372,7 +372,7 @@ class _NewTaskItemState extends State<NewTaskItem> {
? Theme.of(context).disabledColor
: Theme.of(context).colorScheme.primaryContainer,
fontColor: Theme.of(context).colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
onPressed: _textEditingController.text.isEmpty
? null
: () {

View File

@ -1,4 +1,3 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
@ -12,6 +11,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/cust
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
@ -22,8 +22,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:cross_file/cross_file.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
@ -115,9 +113,10 @@ class _DocumentPageState extends State<DocumentPage>
final error = state.error;
if (error != null || editorState == null) {
Log.error(error);
return FlowyErrorPage.message(
error.toString(),
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
return Center(
child: AppFlowyErrorPage(
error: error,
),
);
}

View File

@ -499,11 +499,12 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
}
void _customizeBlockComponentBackgroundColorDecorator() {
if (!context.mounted) {
return;
}
blockComponentBackgroundColorDecorator = (Node node, String colorString) =>
buildEditorCustomizedColor(context, node, colorString);
blockComponentBackgroundColorDecorator = (Node node, String colorString) {
if (mounted && context.mounted) {
return buildEditorCustomizedColor(context, node, colorString);
}
return null;
};
}
void _initEditorL10n() => AppFlowyEditorL10n.current = EditorI18n();

View File

@ -1,7 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart';
@ -106,70 +105,32 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
final view = state.data;
// memorize the result
pageMemorizer[widget.pageId] = view;
if (view == null) {
return FlowyHover(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FlowyText(
LocaleKeys.document_mention_noAccess.tr(),
color: Theme.of(context).disabledColor,
decoration: TextDecoration.underline,
fontSize: widget.textStyle?.fontSize,
fontWeight: widget.textStyle?.fontWeight,
),
),
return _NoAccessMentionPageBlock(
textStyle: widget.textStyle,
);
}
final iconSize = widget.textStyle?.fontSize ?? 16.0;
Widget child = Row(
mainAxisSize: MainAxisSize.min,
children: [
const HSpace(4),
view.icon.value.isNotEmpty
? EmojiText(
emoji: view.icon.value,
fontSize: 12,
textAlign: TextAlign.center,
lineHeight: 1.3,
)
: FlowySvg(
view.layout.icon,
size: Size.square(iconSize + 2.0),
),
const HSpace(2),
FlowyText(
view.name,
decoration: TextDecoration.underline,
fontSize: widget.textStyle?.fontSize,
fontWeight: widget.textStyle?.fontWeight,
),
const HSpace(4),
],
);
if (PlatformExtension.isDesktop) {
child = Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: FlowyHover(
cursor: SystemMouseCursors.click,
child: child,
),
if (PlatformExtension.isMobile) {
return _MobileMentionPageBlock(
view: view,
textStyle: widget.textStyle,
handleTap: handleTap,
handleDoubleTap: handleDoubleTap,
);
} else {
return _DesktopMentionPageBlock(
view: view,
textStyle: widget.textStyle,
handleTap: handleTap,
);
}
return GestureDetector(
onTap: handleTap,
onDoubleTap: PlatformExtension.isMobile ? handleDoubleTap : null,
behavior: HitTestBehavior.opaque,
child: child,
);
},
);
}
Future<void> handleTap() async {
debugPrint('handleTap');
final view = await fetchView(widget.pageId);
if (view == null) {
Log.error('Page(${widget.pageId}) not found');
@ -246,3 +207,127 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
});
}
}
class _MentionPageBlockContent extends StatelessWidget {
const _MentionPageBlockContent({
required this.view,
required this.textStyle,
});
final ViewPB view;
final TextStyle? textStyle;
@override
Widget build(BuildContext context) {
final emojiSize = textStyle?.fontSize ?? 12.0;
final iconSize = textStyle?.fontSize ?? 16.0;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
const HSpace(4),
view.icon.value.isNotEmpty
? FlowyText.emoji(
view.icon.value,
fontSize: emojiSize,
lineHeight: textStyle?.height,
optimizeEmojiAlign: true,
)
: FlowySvg(
view.layout.icon,
size: Size.square(iconSize + 2.0),
),
const HSpace(2),
FlowyText(
view.name,
decoration: TextDecoration.underline,
fontSize: textStyle?.fontSize,
fontWeight: textStyle?.fontWeight,
lineHeight: textStyle?.height,
),
const HSpace(4),
],
);
}
}
class _NoAccessMentionPageBlock extends StatelessWidget {
const _NoAccessMentionPageBlock({
required this.textStyle,
});
final TextStyle? textStyle;
@override
Widget build(BuildContext context) {
return FlowyHover(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FlowyText(
LocaleKeys.document_mention_noAccess.tr(),
color: Theme.of(context).disabledColor,
decoration: TextDecoration.underline,
fontSize: textStyle?.fontSize,
fontWeight: textStyle?.fontWeight,
),
),
);
}
}
class _MobileMentionPageBlock extends StatelessWidget {
const _MobileMentionPageBlock({
required this.view,
required this.textStyle,
required this.handleTap,
required this.handleDoubleTap,
});
final TextStyle? textStyle;
final ViewPB view;
final VoidCallback handleTap;
final VoidCallback handleDoubleTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: handleTap,
onDoubleTap: handleDoubleTap,
behavior: HitTestBehavior.opaque,
child: _MentionPageBlockContent(
view: view,
textStyle: textStyle,
),
);
}
}
class _DesktopMentionPageBlock extends StatelessWidget {
const _DesktopMentionPageBlock({
required this.view,
required this.textStyle,
required this.handleTap,
});
final TextStyle? textStyle;
final ViewPB view;
final VoidCallback handleTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: handleTap,
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: FlowyHover(
cursor: SystemMouseCursors.click,
child: _MentionPageBlockContent(
view: view,
textStyle: textStyle,
),
),
),
);
}
}

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@ -106,9 +107,9 @@ class _AppFlowyMobileToolbarIconItemState
final enable = widget.enable?.call() ?? true;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
child: AnimatedGestureDetector(
scaleFactor: 0.95,
onTapUp: () {
widget.onTap();
_rebuild();
},

View File

@ -137,7 +137,6 @@ class EditorStyleCustomizer {
textStyle: baseTextStyle.copyWith(
fontSize: fontSize,
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
color: Colors.red,
backgroundColor: Colors.grey.withOpacity(0.3),
),

View File

@ -11,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -51,6 +52,14 @@ class ExportTab extends StatelessWidget {
svg: FlowySvgs.duplicate_s,
onTap: () => _exportToClipboard(context),
),
if (kDebugMode) ...[
const VSpace(10),
_ExportButton(
title: 'JSON (Debug Mode)',
svg: FlowySvgs.duplicate_s,
onTap: () => _exportJSON(context),
),
],
],
);
}
@ -64,6 +73,14 @@ class ExportTab extends StatelessWidget {
svg: FlowySvgs.database_layout_m,
onTap: () => _exportCSV(context),
),
if (kDebugMode) ...[
const VSpace(10),
_ExportButton(
title: 'Raw Database Data (Debug Mode)',
svg: FlowySvgs.duplicate_s,
onTap: () => _exportRawDatabaseData(context),
),
],
],
);
}
@ -100,6 +117,22 @@ class ExportTab extends StatelessWidget {
}
}
Future<void> _exportJSON(BuildContext context) async {
final viewName = context.read<ShareBloc>().state.viewName;
final exportPath = await getIt<FilePickerService>().saveFile(
dialogTitle: '',
fileName: '${viewName.toFileName()}.json',
);
if (context.mounted && exportPath != null) {
context.read<ShareBloc>().add(
ShareEvent.share(
ShareType.json,
exportPath,
),
);
}
}
Future<void> _exportCSV(BuildContext context) async {
final viewName = context.read<ShareBloc>().state.viewName;
final exportPath = await getIt<FilePickerService>().saveFile(
@ -116,6 +149,22 @@ class ExportTab extends StatelessWidget {
}
}
Future<void> _exportRawDatabaseData(BuildContext context) async {
final viewName = context.read<ShareBloc>().state.viewName;
final exportPath = await getIt<FilePickerService>().saveFile(
dialogTitle: '',
fileName: '${viewName.toFileName()}.json',
);
if (context.mounted && exportPath != null) {
context.read<ShareBloc>().add(
ShareEvent.share(
ShareType.rawDatabaseData,
exportPath,
),
);
}
}
Future<void> _exportToClipboard(BuildContext context) async {
final documentExporter = DocumentExporter(context.read<ShareBloc>().view);
final result = await documentExporter.export(DocumentExportType.markdown);

View File

@ -179,6 +179,14 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
(s) => FlowyResult.success(s.data),
(f) => FlowyResult.failure(f),
);
} else if (type == ShareType.rawDatabaseData) {
final exportResult = await BackendExportService.exportDatabaseAsRawData(
view.id,
);
result = exportResult.fold(
(s) => FlowyResult.success(s.data),
(f) => FlowyResult.failure(f),
);
} else {
result = await documentExporter.export(type.documentExportType);
}
@ -189,6 +197,8 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
case ShareType.markdown:
case ShareType.html:
case ShareType.csv:
case ShareType.json:
case ShareType.rawDatabaseData:
File(path).writeAsStringSync(s);
return FlowyResult.success(type);
default:
@ -208,9 +218,11 @@ enum ShareType {
html,
text,
link,
json,
// only available in database
csv;
csv,
rawDatabaseData;
static List<ShareType> get unimplemented => [link];
@ -222,10 +234,16 @@ enum ShareType {
return DocumentExportType.html;
case ShareType.text:
return DocumentExportType.text;
case ShareType.json:
return DocumentExportType.json;
case ShareType.csv:
throw UnsupportedError('DocumentShareType.csv is not supported');
case ShareType.link:
throw UnsupportedError('DocumentShareType.link is not supported');
case ShareType.rawDatabaseData:
throw UnsupportedError(
'DocumentShareType.rawDatabaseData is not supported',
);
}
}
}

View File

@ -0,0 +1,168 @@
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' show PlatformExtension;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class AppFlowyErrorPage extends StatelessWidget {
const AppFlowyErrorPage({
super.key,
this.error,
});
final FlowyError? error;
@override
Widget build(BuildContext context) {
if (PlatformExtension.isMobile) {
return _MobileSyncErrorPage(error: error);
} else {
return _DesktopSyncErrorPage(error: error);
}
}
}
class _MobileSyncErrorPage extends StatelessWidget {
const _MobileSyncErrorPage({
this.error,
});
final FlowyError? error;
@override
Widget build(BuildContext context) {
return AnimatedGestureDetector(
scaleFactor: 0.99,
onTapUp: () {
getIt<ClipboardService>().setPlainText(error.toString());
showToastNotification(
context,
message: LocaleKeys.message_copy_success.tr(),
bottomPadding: 0,
);
},
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FlowySvg(
FlowySvgs.icon_warning_xl,
blendMode: null,
),
const VSpace(16.0),
FlowyText.medium(
LocaleKeys.error_syncError.tr(),
fontSize: 15,
),
const VSpace(8.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: FlowyText.regular(
LocaleKeys.error_syncErrorHint.tr(),
fontSize: 13,
color: Theme.of(context).hintColor,
textAlign: TextAlign.center,
maxLines: 10,
),
),
const VSpace(2.0),
FlowyText.regular(
'(${LocaleKeys.error_clickToCopy.tr()})',
fontSize: 13,
color: Theme.of(context).hintColor,
textAlign: TextAlign.center,
),
],
),
);
}
}
class _DesktopSyncErrorPage extends StatelessWidget {
const _DesktopSyncErrorPage({
this.error,
});
final FlowyError? error;
@override
Widget build(BuildContext context) {
return AnimatedGestureDetector(
scaleFactor: 0.995,
onTapUp: () {
getIt<ClipboardService>().setPlainText(error.toString());
showToastNotification(
context,
message: LocaleKeys.message_copy_success.tr(),
bottomPadding: 0,
);
},
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FlowySvg(
FlowySvgs.icon_warning_xl,
blendMode: null,
),
const VSpace(16.0),
FlowyText.medium(
error?.code.toString() ?? '',
fontSize: 16,
),
const VSpace(8.0),
RichText(
text: TextSpan(
children: [
TextSpan(
text: LocaleKeys.errorDialog_howToFixFallbackHint1.tr(),
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
TextSpan(
text: 'Github',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () {
afLaunchUrlString(
'https://github.com/AppFlowy-IO/AppFlowy/issues/new?template=bug_report.yaml',
);
},
),
TextSpan(
text: LocaleKeys.errorDialog_howToFixFallbackHint2.tr(),
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
],
),
),
const VSpace(8.0),
FlowyText.regular(
'(${LocaleKeys.error_clickToCopy.tr()})',
fontSize: 14,
color: Theme.of(context).hintColor,
textAlign: TextAlign.center,
),
],
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -109,7 +109,8 @@ class FlowyRunner {
[
// this task should be first task, for handling platform errors.
// don't catch errors in test mode
if (!mode.isUnitTest) const PlatformErrorCatcherTask(),
if (!mode.isUnitTest && !mode.isIntegrationTest)
const PlatformErrorCatcherTask(),
if (!mode.isUnitTest) const InitSentryTask(),
// this task should be second task, for handling memory leak.
// there's a flag named _enable in memory_leak_detector.dart. If it's false, the task will be ignored.

View File

@ -1,5 +1,7 @@
import 'package:appflowy_backend/log.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../startup.dart';
@ -17,6 +19,23 @@ class PlatformErrorCatcherTask extends LaunchTask {
return true;
};
}
ErrorWidget.builder = (details) {
if (kDebugMode) {
return Container(
width: double.infinity,
height: 30,
color: Colors.red,
child: FlowyText(
'ERROR: ${details.exceptionAsString()}',
color: Colors.white,
),
);
}
// hide the error widget in release mode
return const SizedBox.shrink();
};
}
@override

View File

@ -121,6 +121,8 @@ extension ProviderTypePBExtension on ProviderTypePB {
return ProviderTypePB.Google;
case 'discord':
return ProviderTypePB.Discord;
case 'apple':
return ProviderTypePB.Apple;
default:
throw UnimplementedError();
}

View File

@ -1,16 +1,16 @@
import 'package:flutter/foundation.dart';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
show UserProfilePB;
import 'package:appflowy_result/appflowy_result.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -208,6 +208,8 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
}
SignInState _stateFromCode(FlowyError error) {
Log.error('SignInState _stateFromCode: ${error.msg}');
switch (error.code) {
case ErrorCode.EmailFormatInvalid:
return state.copyWith(

View File

@ -1,11 +1,12 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/presentation/router.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/material.dart';
import 'package:toastification/toastification.dart';
void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
Log.error(error);
@ -15,24 +16,24 @@ void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
getIt<AuthRouter>().pushWorkspaceErrorScreen(context, userFolder, error);
break;
case ErrorCode.InvalidEncryptSecret:
showSnapBar(
case ErrorCode.HttpError:
showToastNotification(
context,
error.msg,
message: error.msg,
type: ToastificationType.error,
);
break;
case ErrorCode.HttpError:
showSnapBar(
context,
error.msg,
);
default:
showSnapBar(
showToastNotification(
context,
error.msg,
onClosed: () {
getIt<AuthService>().signOut();
runAppFlowy();
},
message: error.msg,
type: ToastificationType.error,
callbacks: ToastificationCallbacks(
onDismissed: (_) {
getIt<AuthService>().signOut();
runAppFlowy();
},
),
);
}
}

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/core/frameless_window.dart';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
@ -10,6 +8,7 @@ import 'package:appflowy/user/presentation/widgets/widgets.dart';
import 'package:appflowy_editor/appflowy_editor.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 DesktopSignInScreen extends StatelessWidget {
@ -45,14 +44,14 @@ class DesktopSignInScreen extends StatelessWidget {
if (isAuthEnabled) ...[
const _OrDivider(),
const VSpace(10),
const VSpace(20),
const ThirdPartySignInButtons(),
],
const VSpace(20),
// anonymous sign in
const SignInAnonymousButtonV2(),
const VSpace(10),
const VSpace(16),
// sign in agreement
const SignInAgreement(),

View File

@ -1,4 +1,4 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
@ -8,6 +8,7 @@ import 'package:appflowy/user/application/sign_in_bloc.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.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';
@ -23,26 +24,27 @@ class MobileSignInScreen extends StatelessWidget {
return BlocBuilder<SignInBloc, SignInState>(
builder: (context, state) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40),
padding: const EdgeInsets.symmetric(vertical: 38, horizontal: 40),
child: Column(
children: [
const Spacer(flex: 4),
_buildLogo(),
const VSpace(spacing * 2),
_buildWelcomeText(),
const VSpace(spacing),
_buildAppNameText(colorScheme),
const VSpace(spacing * 2),
const SignInWithMagicLinkButtons(),
const VSpace(spacing),
if (isAuthEnabled) _buildThirdPartySignInButtons(colorScheme),
const VSpace(spacing),
const SignInAnonymousButtonV2(),
const VSpace(spacing),
const VSpace(spacing * 1.5),
const SignInAgreement(),
const VSpace(spacing),
_buildSettingsButton(context),
if (!isAuthEnabled) const Spacer(flex: 2),
const Spacer(flex: 2),
const Spacer(),
Expanded(child: _buildSettingsButton(context)),
if (Platform.isAndroid) const Spacer(),
],
),
),
@ -51,19 +53,10 @@ class MobileSignInScreen extends StatelessWidget {
);
}
Widget _buildWelcomeText() {
return FlowyText(
LocaleKeys.welcomeTo.tr(),
textAlign: TextAlign.center,
fontSize: 32,
fontWeight: FontWeight.w700,
);
}
Widget _buildLogo() {
return const FlowySvg(
FlowySvgs.flowy_logo_xl,
size: Size.square(64),
size: Size.square(56),
blendMode: null,
);
}
@ -72,7 +65,7 @@ class MobileSignInScreen extends StatelessWidget {
return FlowyText(
LocaleKeys.appName.tr(),
textAlign: TextAlign.center,
fontSize: 32,
fontSize: 28,
color: const Color(0xFF00BCF0),
fontWeight: FontWeight.w700,
);
@ -89,6 +82,7 @@ class MobileSignInScreen extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 8),
child: FlowyText(
LocaleKeys.signIn_or.tr(),
fontSize: 12,
color: colorScheme.onSecondary,
),
),
@ -96,23 +90,45 @@ class MobileSignInScreen extends StatelessWidget {
],
),
const VSpace(16),
const ThirdPartySignInButtons(),
// expand third-party sign in buttons on Android by default.
// on iOS, the github and discord buttons are collapsed by default.
ThirdPartySignInButtons(
expanded: Platform.isAndroid,
),
],
);
}
Widget _buildSettingsButton(BuildContext context) {
return FlowyButton(
text: FlowyText(
LocaleKeys.signIn_settings.tr(),
textAlign: TextAlign.center,
fontSize: 12.0,
fontWeight: FontWeight.w500,
decoration: TextDecoration.underline,
),
onTap: () {
context.push(MobileLaunchSettingsPage.routeName);
},
return Row(
mainAxisSize: MainAxisSize.min,
children: [
FlowyButton(
useIntrinsicWidth: true,
text: FlowyText(
LocaleKeys.signIn_settings.tr(),
textAlign: TextAlign.center,
fontSize: 12.0,
// fontWeight: FontWeight.w500,
color: Colors.grey,
decoration: TextDecoration.underline,
),
onTap: () {
context.push(MobileLaunchSettingsPage.routeName);
},
),
const HSpace(24),
SignInAnonymousButtonV2(
child: FlowyText(
LocaleKeys.signIn_anonymous.tr(),
textAlign: TextAlign.center,
fontSize: 12.0,
// fontWeight: FontWeight.w500,
color: Colors.grey,
decoration: TextDecoration.underline,
),
),
],
);
}
}

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/sign_in_bloc.dart';
import 'package:appflowy/user/presentation/router.dart';
@ -7,6 +5,7 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/desktop_sign_i
import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_loading_screen.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../helpers/helpers.dart';
@ -21,16 +20,7 @@ class SignInScreen extends StatelessWidget {
return BlocProvider(
create: (context) => getIt<SignInBloc>(),
child: BlocConsumer<SignInBloc, SignInState>(
listener: (context, state) {
final successOrFail = state.successOrFail;
if (successOrFail != null) {
handleUserProfileResult(
successOrFail,
context,
getIt<AuthRouter>(),
);
}
},
listener: _showSignInError,
builder: (context, state) {
final isLoading = context.read<SignInBloc>().state.isSubmitting;
if (PlatformExtension.isMobile) {
@ -43,4 +33,15 @@ class SignInScreen extends StatelessWidget {
),
);
}
void _showSignInError(BuildContext context, SignInState state) {
final successOrFail = state.successOrFail;
if (successOrFail != null) {
handleUserProfileResult(
successOrFail,
context,
getIt<AuthRouter>(),
);
}
}
}

View File

@ -34,11 +34,19 @@ class _SignInWithMagicLinkButtonsState
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 48.0,
height: PlatformExtension.isMobile ? 38.0 : 48.0,
child: FlowyTextField(
autoFocus: false,
controller: controller,
borderRadius: BorderRadius.circular(4.0),
hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(),
hintStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 14.0,
color: Theme.of(context).hintColor,
),
textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontSize: 14.0,
),
keyboardType: TextInputType.emailAddress,
onSubmitted: (_) => _sendMagicLink(context, controller.text),
),
@ -88,14 +96,14 @@ class _ConfirmButton extends StatelessWidget {
if (PlatformExtension.isMobile) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 56),
minimumSize: const Size(double.infinity, 32),
maximumSize: const Size(double.infinity, 38),
),
onPressed: onTap,
child: FlowyText(
name,
fontSize: 14,
color: Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.w500,
),
);
} else {
@ -108,6 +116,7 @@ class _ConfirmButton extends StatelessWidget {
text: FlowyText.medium(
name,
textAlign: TextAlign.center,
color: Theme.of(context).colorScheme.onPrimary,
),
radius: Corners.s6Border,
),

View File

@ -21,7 +21,11 @@ class SignInAgreement extends StatelessWidget {
),
TextSpan(
text: '${LocaleKeys.web_termOfUse.tr()} ',
style: const TextStyle(color: Colors.blue, fontSize: 12),
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
decoration: TextDecoration.underline,
),
mouseCursor: SystemMouseCursors.click,
recognizer: TapGestureRecognizer()
..onTap = () => afLaunchUrlString('https://appflowy.io/terms'),
@ -32,7 +36,11 @@ class SignInAgreement extends StatelessWidget {
),
TextSpan(
text: LocaleKeys.web_privacyPolicy.tr(),
style: const TextStyle(color: Colors.blue, fontSize: 12),
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
decoration: TextDecoration.underline,
),
mouseCursor: SystemMouseCursors.click,
recognizer: TapGestureRecognizer()
..onTap = () => afLaunchUrlString('https://appflowy.io/privacy'),

View File

@ -89,8 +89,11 @@ class SignInAnonymousButton extends StatelessWidget {
class SignInAnonymousButtonV2 extends StatelessWidget {
const SignInAnonymousButtonV2({
super.key,
this.child,
});
final Widget? child;
@override
Widget build(BuildContext context) {
return BlocBuilder<SignInBloc, SignInState>(
@ -126,11 +129,12 @@ class SignInAnonymousButtonV2 extends StatelessWidget {
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: onTap,
child: FlowyText(
text,
color: Colors.blue,
fontSize: 12,
),
child: child ??
FlowyText(
text,
color: Colors.blue,
fontSize: 12,
),
),
);
},

View File

@ -20,7 +20,7 @@ class MobileSignInOrLogoutButton extends StatelessWidget {
return GestureDetector(
onTap: onPressed,
child: Container(
height: 48,
height: 38,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(4),
@ -54,7 +54,7 @@ class MobileSignInOrLogoutButton extends StatelessWidget {
FlowyText(
labelText,
fontSize: 14.0,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w400,
),
],
),

View File

@ -0,0 +1,137 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
enum ThirdPartySignInButtonType {
apple,
google,
github,
discord,
anonymous;
FlowySvgData get icon {
switch (this) {
case ThirdPartySignInButtonType.apple:
return FlowySvgs.m_apple_icon_xl;
case ThirdPartySignInButtonType.google:
return FlowySvgs.m_google_icon_xl;
case ThirdPartySignInButtonType.github:
return FlowySvgs.m_github_icon_xl;
case ThirdPartySignInButtonType.discord:
return FlowySvgs.m_discord_icon_xl;
case ThirdPartySignInButtonType.anonymous:
return FlowySvgs.m_discord_icon_xl;
}
}
String get labelText {
switch (this) {
case ThirdPartySignInButtonType.apple:
return LocaleKeys.signIn_signInWithApple.tr();
case ThirdPartySignInButtonType.google:
return LocaleKeys.signIn_signInWithGoogle.tr();
case ThirdPartySignInButtonType.github:
return LocaleKeys.signIn_signInWithGithub.tr();
case ThirdPartySignInButtonType.discord:
return LocaleKeys.signIn_signInWithDiscord.tr();
case ThirdPartySignInButtonType.anonymous:
return 'Anonymous session';
}
}
// https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple
Color backgroundColor(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
switch (this) {
case ThirdPartySignInButtonType.apple:
return isDarkMode ? Colors.white : Colors.black;
case ThirdPartySignInButtonType.google:
case ThirdPartySignInButtonType.github:
case ThirdPartySignInButtonType.discord:
case ThirdPartySignInButtonType.anonymous:
return isDarkMode ? Colors.black : Colors.grey.shade100;
}
}
Color textColor(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
switch (this) {
case ThirdPartySignInButtonType.apple:
return isDarkMode ? Colors.black : Colors.white;
case ThirdPartySignInButtonType.google:
case ThirdPartySignInButtonType.github:
case ThirdPartySignInButtonType.discord:
case ThirdPartySignInButtonType.anonymous:
return isDarkMode ? Colors.white : Colors.black;
}
}
BlendMode? get blendMode {
switch (this) {
case ThirdPartySignInButtonType.apple:
case ThirdPartySignInButtonType.github:
return BlendMode.srcIn;
default:
return null;
}
}
}
class MobileThirdPartySignInButton extends StatelessWidget {
const MobileThirdPartySignInButton({
super.key,
this.height = 38,
this.fontSize = 14.0,
required this.onPressed,
required this.type,
});
final VoidCallback onPressed;
final double height;
final double fontSize;
final ThirdPartySignInButtonType type;
@override
Widget build(BuildContext context) {
final style = Theme.of(context);
return AnimatedGestureDetector(
scaleFactor: 1.0,
onTapUp: onPressed,
child: Container(
height: height,
decoration: BoxDecoration(
color: type.backgroundColor(context),
borderRadius: const BorderRadius.all(
Radius.circular(4),
),
border: Border.all(
color: style.colorScheme.outline,
width: 0.5,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (type != ThirdPartySignInButtonType.anonymous)
FlowySvg(
type.icon,
size: Size.square(fontSize),
blendMode: type.blendMode,
color: type.textColor(context),
),
const HSpace(8.0),
FlowyText(
type.labelText,
fontSize: fontSize,
color: type.textColor(context),
),
],
),
),
);
}
}

View File

@ -1,126 +1,167 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/user/application/sign_in_bloc.dart';
import 'package:appflowy/user/presentation/presentation.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ThirdPartySignInButtons extends StatelessWidget {
import 'third_party_sign_in_button.dart';
@visibleForTesting
const Key signInWithGoogleButtonKey = Key('signInWithGoogleButton');
class ThirdPartySignInButtons extends StatefulWidget {
/// Used in DesktopSignInScreen, MobileSignInScreen and SettingThirdPartyLogin
const ThirdPartySignInButtons({super.key});
@override
Widget build(BuildContext context) {
// Get themeMode from AppearanceSettingsCubit
// When user changes themeMode, it changes the state in AppearanceSettingsCubit, but the themeMode for the MaterialApp won't change, it only got updated(get value from AppearanceSettingsCubit) when user open the app again. Thus, we should get themeMode from AppearanceSettingsCubit rather than MediaQuery.
final themeModeFromCubit =
context.watch<AppearanceSettingsCubit>().state.themeMode;
final isDarkMode = themeModeFromCubit == ThemeMode.system
? MediaQuery.of(context).platformBrightness == Brightness.dark
: themeModeFromCubit == ThemeMode.dark;
return BlocBuilder<SignInBloc, SignInState>(
builder: (context, state) {
final (googleText, githubText, discordText) = switch (state.loginType) {
LoginType.signIn => (
LocaleKeys.signIn_signInWithGoogle.tr(),
LocaleKeys.signIn_signInWithGithub.tr(),
LocaleKeys.signIn_signInWithDiscord.tr()
),
LoginType.signUp => (
LocaleKeys.signIn_signUpWithGoogle.tr(),
LocaleKeys.signIn_signUpWithGithub.tr(),
LocaleKeys.signIn_signUpWithDiscord.tr()
),
};
return Column(
children: [
_ThirdPartySignInButton(
key: const Key('signInWithGoogleButton'),
icon: FlowySvgs.google_mark_xl,
labelText: googleText,
onPressed: () {
_signInWithGoogle(context);
},
),
const VSpace(8),
_ThirdPartySignInButton(
icon: isDarkMode
? FlowySvgs.github_mark_white_xl
: FlowySvgs.github_mark_black_xl,
labelText: githubText,
onPressed: () {
_signInWithGithub(context);
},
),
const VSpace(8),
_ThirdPartySignInButton(
icon: isDarkMode
? FlowySvgs.discord_mark_white_xl
: FlowySvgs.discord_mark_blurple_xl,
labelText: discordText,
onPressed: () {
_signInWithDiscord(context);
},
),
],
);
},
);
}
}
class _ThirdPartySignInButton extends StatelessWidget {
/// Build button based on current Platform(mobile or desktop).
const _ThirdPartySignInButton({
const ThirdPartySignInButtons({
super.key,
required this.icon,
required this.labelText,
required this.onPressed,
this.expanded = false,
});
final FlowySvgData icon;
final String labelText;
final bool expanded;
final VoidCallback onPressed;
@override
State<ThirdPartySignInButtons> createState() =>
_ThirdPartySignInButtonsState();
}
class _ThirdPartySignInButtonsState extends State<ThirdPartySignInButtons> {
bool expanded = false;
@override
void initState() {
super.initState();
expanded = widget.expanded;
}
@override
Widget build(BuildContext context) {
if (PlatformExtension.isMobile) {
return MobileSignInOrLogoutButton(
icon: icon,
labelText: labelText,
onPressed: onPressed,
if (PlatformExtension.isDesktopOrWeb) {
const padding = 16.0;
return Column(
children: [
_DesktopSignInButton(
key: signInWithGoogleButtonKey,
type: ThirdPartySignInButtonType.google,
onPressed: () {
_signInWithGoogle(context);
},
),
const VSpace(padding),
_DesktopSignInButton(
type: ThirdPartySignInButtonType.github,
onPressed: () {
_signInWithGithub(context);
},
),
const VSpace(padding),
_DesktopSignInButton(
type: ThirdPartySignInButtonType.discord,
onPressed: () {
_signInWithDiscord(context);
},
),
],
);
} else {
return _DesktopSignInButton(
icon: icon,
labelText: labelText,
onPressed: onPressed,
const padding = 8.0;
return BlocBuilder<SignInBloc, SignInState>(
builder: (context, state) {
return Column(
children: [
if (Platform.isIOS) ...[
MobileThirdPartySignInButton(
type: ThirdPartySignInButtonType.apple,
onPressed: () {
_signInWithApple(context);
},
),
const VSpace(padding),
],
MobileThirdPartySignInButton(
type: ThirdPartySignInButtonType.google,
onPressed: () {
_signInWithGoogle(context);
},
),
if (expanded) ...[
const VSpace(padding),
MobileThirdPartySignInButton(
type: ThirdPartySignInButtonType.github,
onPressed: () {
_signInWithGithub(context);
},
),
const VSpace(padding),
MobileThirdPartySignInButton(
type: ThirdPartySignInButtonType.discord,
onPressed: () {
_signInWithDiscord(context);
},
),
],
if (!expanded) ...[
const VSpace(padding * 2),
GestureDetector(
onTap: () {
setState(() {
expanded = !expanded;
});
},
child: FlowyText(
LocaleKeys.signIn_continueAnotherWay.tr(),
color: Theme.of(context).colorScheme.onSurface,
decoration: TextDecoration.underline,
fontSize: 14,
),
),
],
],
);
},
);
}
}
void _signInWithApple(BuildContext context) {
context.read<SignInBloc>().add(
const SignInEvent.signedInWithOAuth('apple'),
);
}
void _signInWithGoogle(BuildContext context) {
context.read<SignInBloc>().add(
const SignInEvent.signedInWithOAuth('google'),
);
}
void _signInWithGithub(BuildContext context) {
context
.read<SignInBloc>()
.add(const SignInEvent.signedInWithOAuth('github'));
}
void _signInWithDiscord(BuildContext context) {
context
.read<SignInBloc>()
.add(const SignInEvent.signedInWithOAuth('discord'));
}
}
class _DesktopSignInButton extends StatelessWidget {
const _DesktopSignInButton({
required this.icon,
required this.labelText,
super.key,
required this.type,
required this.onPressed,
});
final FlowySvgData icon;
final String labelText;
final ThirdPartySignInButtonType type;
final VoidCallback onPressed;
@override
@ -139,8 +180,8 @@ class _DesktopSignInButton extends StatelessWidget {
// Some icons are not square, so we just use a fixed width here.
width: 24,
child: FlowySvg(
icon,
blendMode: null,
type.icon,
blendMode: type.blendMode,
),
),
),
@ -148,7 +189,7 @@ class _DesktopSignInButton extends StatelessWidget {
padding: const EdgeInsets.only(left: 8),
alignment: Alignment.centerLeft,
child: FlowyText(
labelText,
type.labelText,
fontSize: 14,
),
),
@ -177,19 +218,3 @@ class _DesktopSignInButton extends StatelessWidget {
);
}
}
void _signInWithGoogle(BuildContext context) {
context.read<SignInBloc>().add(
const SignInEvent.signedInWithOAuth('google'),
);
}
void _signInWithGithub(BuildContext context) {
context.read<SignInBloc>().add(const SignInEvent.signedInWithOAuth('github'));
}
void _signInWithDiscord(BuildContext context) {
context
.read<SignInBloc>()
.add(const SignInEvent.signedInWithOAuth('discord'));
}

View File

@ -1,7 +1,7 @@
export 'magic_link_sign_in_buttons.dart';
export 'sign_in_anonymous_button.dart';
export 'sign_in_or_logout_button.dart';
export 'third_party_sign_in_button.dart';
// export 'switch_sign_in_sign_up_button.dart';
export 'third_party_sign_in_buttons.dart';
export 'sign_in_agreement.dart';

View File

@ -12,4 +12,12 @@ class BackendExportService {
final payload = DatabaseViewIdPB.create()..value = viewId;
return DatabaseEventExportCSV(payload).send();
}
static Future<FlowyResult<DatabaseExportDataPB, FlowyError>>
exportDatabaseAsRawData(
String viewId,
) async {
final payload = DatabaseViewIdPB.create()..value = viewId;
return DatabaseEventExportRawDatabaseData(payload).send();
}
}

View File

@ -296,7 +296,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
),
);
},
reset: (userProfile, workspaceId) async {
reset: (userProfile, workspaceId, openFirstPage) async {
if (workspaceId == _workspaceId) {
return;
}
@ -452,6 +452,9 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
)..start(
sectionChanged: (result) async {
Log.info('did receive section views changed');
if (isClosed) {
return;
}
add(const SpaceEvent.didReceiveSpaceUpdate());
},
);
@ -559,7 +562,10 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
return true;
}
final viewId = fixedUuid(user.id.toInt(), UuidType.publicSpace);
final viewId = fixedUuid(
user.id.toInt() + (_workspaceId?.hashCode ?? 0),
UuidType.publicSpace,
);
final publicSpace = await _createSpace(
name: 'Shared',
icon: builtInSpaceIcons.first,
@ -732,6 +738,7 @@ class SpaceEvent with _$SpaceEvent {
const factory SpaceEvent.reset(
UserProfilePB userProfile,
String workspaceId,
bool openFirstPage,
) = _Reset;
const factory SpaceEvent.migrate() = _Migrate;
const factory SpaceEvent.switchToNextSpace() = _SwitchToNextSpace;

View File

@ -1,17 +1,17 @@
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_toast.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_toast.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'sidebar_footer_button.dart';
class SidebarFooter extends StatelessWidget {
const SidebarFooter({super.key});
@ -26,52 +26,56 @@ class SidebarFooter extends StatelessWidget {
return const SidebarToast();
},
),
const Row(
children: [
Expanded(child: SidebarTrashButton()),
// Enable it when the widget button is ready
// SizedBox(
// height: 16,
// child: VerticalDivider(width: 1, color: Color(0x141F2329)),
// ),
// Expanded(child: SidebarWidgetButton()),
],
),
const SidebarTemplateButton(),
const SidebarTrashButton(),
],
);
}
}
class SidebarTemplateButton extends StatelessWidget {
const SidebarTemplateButton({super.key});
@override
Widget build(BuildContext context) {
return SidebarFooterButton(
leftIconSize: const Size.square(24.0),
leftIcon: const Padding(
padding: EdgeInsets.all(2.0),
child: FlowySvg(
FlowySvgs.icon_template_s,
),
),
text: LocaleKeys.template_label.tr(),
onTap: () => afLaunchUrlString('https://appflowy.io/templates'),
);
}
}
class SidebarTrashButton extends StatelessWidget {
const SidebarTrashButton({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: HomeSizes.workspaceSectionHeight,
child: ValueListenableBuilder(
valueListenable: getIt<MenuSharedState>().notifier,
builder: (context, value, child) {
return FlowyButton(
leftIcon: const FlowySvg(FlowySvgs.sidebar_footer_trash_m),
leftIconSize: const Size.square(24.0),
iconPadding: 8.0,
margin: const EdgeInsets.all(4.0),
text: FlowyText.regular(
LocaleKeys.trash_text.tr(),
lineHeight: 1.15,
),
onTap: () {
getIt<MenuSharedState>().latestOpenView = null;
getIt<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: makePlugin(pluginType: PluginType.trash),
),
);
},
);
},
),
return ValueListenableBuilder(
valueListenable: getIt<MenuSharedState>().notifier,
builder: (context, value, child) {
return SidebarFooterButton(
leftIconSize: const Size.square(24.0),
leftIcon: const FlowySvg(
FlowySvgs.sidebar_footer_trash_m,
),
text: LocaleKeys.trash_text.tr(),
onTap: () {
getIt<MenuSharedState>().latestOpenView = null;
getIt<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: makePlugin(pluginType: PluginType.trash),
),
);
},
);
},
);
}
}

View File

@ -0,0 +1,39 @@
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
// This button style is used in
// - Trash button
// - Template button
class SidebarFooterButton extends StatelessWidget {
const SidebarFooterButton({
super.key,
required this.leftIcon,
required this.leftIconSize,
required this.text,
required this.onTap,
});
final Widget leftIcon;
final Size leftIconSize;
final String text;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return SizedBox(
height: HomeSizes.workspaceSectionHeight,
child: FlowyButton(
leftIcon: leftIcon,
leftIconSize: leftIconSize,
iconPadding: 8.0,
margin: const EdgeInsets.all(4.0),
text: FlowyText.regular(
text,
lineHeight: 1.15,
),
onTap: onTap,
),
);
}
}

View File

@ -48,6 +48,8 @@ enum ImportType {
bool get enableOnRelease {
switch (this) {
case ImportType.historyDatabase:
case ImportType.historyDocument:
case ImportType.databaseRawData:
return kDebugMode;
default:

View File

@ -196,6 +196,7 @@ class HomeSideBar extends StatelessWidget {
userProfile,
state.currentWorkspace?.workspaceId ??
workspaceSetting.workspaceId,
true,
),
);
}

View File

@ -3,6 +3,7 @@ import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart';
@ -31,7 +32,6 @@ class SidebarSpace extends StatelessWidget {
@override
Widget build(BuildContext context) {
// const sectionPadding = 16.0;
return ValueListenableBuilder(
valueListenable: getIt<MenuSharedState>().notifier,
builder: (context, value, child) {
@ -89,6 +89,8 @@ class _SpaceState extends State<_Space> {
@override
Widget build(BuildContext context) {
final currentWorkspace =
context.watch<UserWorkspaceBloc>().state.currentWorkspace;
return BlocBuilder<SpaceBloc, SpaceState>(
builder: (context, state) {
if (state.spaces.isEmpty) {
@ -115,7 +117,12 @@ class _SpaceState extends State<_Space> {
onEnter: (_) => isHovered.value = true,
onExit: (_) => isHovered.value = false,
child: SpacePages(
key: ValueKey(currentSpace.id),
key: ValueKey(
Object.hashAll([
currentWorkspace?.workspaceId ?? '',
currentSpace.id,
]),
),
isExpandedNotifier: isExpandedNotifier,
space: currentSpace,
isHovered: isHovered,

View File

@ -107,7 +107,6 @@ class SingleSettingAction extends StatelessWidget {
radius: Corners.s8Border,
hoverColor: hoverColor(context),
fontColor: fontColor(context),
textColor: fontColor(context),
fontHoverColor: fontHoverColor(context),
borderColor: borderColor(context),
fontSize: 12,

View File

@ -447,7 +447,7 @@ class _IncludeTimePickerState extends State<_IncludeTimePicker> {
LocaleKeys.button_confirm.tr(),
constraints: const BoxConstraints.tightFor(height: 42),
mainAxisAlignment: MainAxisAlignment.center,
textColor: Theme.of(context).colorScheme.onPrimary,
fontColor: Theme.of(context).colorScheme.onPrimary,
fillColor: Theme.of(context).primaryColor,
onPressed: () {
if (isStartDay) {

View File

@ -97,6 +97,13 @@ class _NavigatorTextFieldDialogState extends State<NavigatorTextFieldDialog> {
VSpace(Insets.xl),
OkCancelButton(
onOkPressed: () {
if (newValue.isEmpty) {
showToastNotification(
context,
message: LocaleKeys.space_spaceNameCannotBeEmpty.tr(),
);
return;
}
widget.onConfirm(newValue, context);
Navigator.of(context).pop();
},
@ -303,14 +310,18 @@ void showToastNotification(
required String message,
String? description,
ToastificationType type = ToastificationType.success,
ToastificationCallbacks? callbacks,
double bottomPadding = 100,
}) {
if (PlatformExtension.isMobile) {
toastification.showCustom(
alignment: Alignment.bottomCenter,
autoCloseDuration: const Duration(milliseconds: 3000),
callbacks: callbacks ?? const ToastificationCallbacks(),
builder: (_, __) => _MToast(
message: message,
type: type,
bottomPadding: bottomPadding,
),
);
return;
@ -346,10 +357,12 @@ class _MToast extends StatelessWidget {
const _MToast({
required this.message,
this.type = ToastificationType.success,
this.bottomPadding = 100,
});
final String message;
final ToastificationType type;
final double bottomPadding;
@override
Widget build(BuildContext context) {
@ -362,7 +375,7 @@ class _MToast extends StatelessWidget {
);
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(bottom: 100, left: 16, right: 16),
padding: EdgeInsets.only(bottom: bottomPadding, left: 16, right: 16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
decoration: BoxDecoration(

View File

@ -297,7 +297,6 @@ class FlowyTextButton extends StatelessWidget {
this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
this.hoverColor,
this.fillColor,
this.textColor,
this.heading,
this.radius,
this.mainAxisAlignment = MainAxisAlignment.start,
@ -353,7 +352,6 @@ class FlowyTextButton extends StatelessWidget {
final Widget? heading;
final Color? hoverColor;
final Color? fillColor;
final Color? textColor;
final BorderRadius? radius;
final MainAxisAlignment mainAxisAlignment;
final String? tooltip;
@ -376,9 +374,10 @@ class FlowyTextButton extends StatelessWidget {
children.add(FlowyText(
text,
overflow: overflow,
color: textColor,
color: fontColor ?? Theme.of(context).colorScheme.onPrimary,
textAlign: TextAlign.center,
lineHeight: lineHeight,
fontSize: fontSize,
));
Widget child = Row(

View File

@ -38,6 +38,7 @@ class FlowyTextField extends StatefulWidget {
final bool isDense;
final bool readOnly;
final Color? enableBorderColor;
final BorderRadius? borderRadius;
const FlowyTextField({
super.key,
@ -74,6 +75,7 @@ class FlowyTextField extends StatefulWidget {
this.isDense = true,
this.readOnly = false,
this.enableBorderColor,
this.borderRadius,
});
@override
@ -180,7 +182,7 @@ class FlowyTextFieldState extends State<FlowyTextField> {
(widget.maxLines == null || widget.maxLines! > 1) ? 12 : 0,
),
enabledBorder: OutlineInputBorder(
borderRadius: Corners.s8Border,
borderRadius: widget.borderRadius ?? Corners.s8Border,
borderSide: BorderSide(
color: widget.enableBorderColor ??
Theme.of(context).colorScheme.outline,
@ -202,7 +204,7 @@ class FlowyTextFieldState extends State<FlowyTextField> {
suffixText: widget.showCounter ? _suffixText() : "",
counterText: "",
focusedBorder: OutlineInputBorder(
borderRadius: Corners.s8Border,
borderRadius: widget.borderRadius ?? Corners.s8Border,
borderSide: BorderSide(
color: widget.readOnly
? widget.enableBorderColor ??
@ -214,13 +216,13 @@ class FlowyTextFieldState extends State<FlowyTextField> {
borderSide: BorderSide(
color: Theme.of(context).colorScheme.error,
),
borderRadius: Corners.s8Border,
borderRadius: widget.borderRadius ?? Corners.s8Border,
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.error,
),
borderRadius: Corners.s8Border,
borderRadius: widget.borderRadius ?? Corners.s8Border,
),
prefixIcon: widget.prefixIcon,
suffixIcon: widget.suffixIcon,

View File

@ -51,11 +51,9 @@ class RoundedTextButton extends StatelessWidget {
radius: borderRadius ?? Corners.s6Border,
fontColor: textColor ?? Theme.of(context).colorScheme.onPrimary,
fillColor: fillColor ?? Theme.of(context).colorScheme.primary,
textColor: textColor,
hoverColor:
hoverColor ?? Theme.of(context).colorScheme.primaryContainer,
padding: padding,
),
),
);

View File

@ -53,8 +53,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "8e17d14"
resolved-ref: "8e17d1447eea0b57ff92e31dbe88796ce759fb37"
ref: a64a516
resolved-ref: a64a5165e79bd2424e594b793843a7158e7d4fb4
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "3.2.0"

View File

@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.6.7
version: 0.6.8
environment:
flutter: ">=3.22.0"
@ -190,7 +190,7 @@ dependency_overrides:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "8e17d14"
ref: "a64a516"
appflowy_editor_plugins:
git:

View File

@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bincode",
@ -192,7 +192,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bytes",
@ -291,6 +291,17 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-stream"
version = "0.3.5"
@ -826,7 +837,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"again",
"anyhow",
@ -877,7 +888,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"collab-entity",
"collab-rt-entity",
@ -890,7 +901,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"futures-channel",
"futures-util",
@ -964,7 +975,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"arc-swap",
@ -989,14 +1000,13 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"collab",
"collab-entity",
"collab-plugins",
"dashmap 5.5.3",
"futures",
"getrandom 0.2.10",
@ -1019,7 +1029,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"arc-swap",
@ -1039,7 +1049,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"bytes",
@ -1058,7 +1068,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"arc-swap",
@ -1101,7 +1111,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"async-stream",
@ -1139,7 +1149,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bincode",
@ -1164,7 +1174,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"async-trait",
@ -1181,7 +1191,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"collab",
@ -1210,6 +1220,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console"
version = "0.14.1"
@ -1552,7 +1571,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"app-error",
@ -1865,6 +1884,27 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "event-listener"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "faccess"
version = "0.2.4"
@ -3077,7 +3117,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"futures-util",
@ -3094,7 +3134,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"app-error",
@ -3526,7 +3566,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bytes",
@ -4616,6 +4656,12 @@ dependencies = [
"system-deps 6.1.1",
]
[[package]]
name = "parking"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -6123,7 +6169,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"app-error",
@ -8401,12 +8447,14 @@ dependencies = [
[[package]]
name = "yrs"
version = "0.19.2"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8ca5126331b9a5ef5bb10f3f1c3d01b05f298d348c66f8fb15497d83ee73176"
checksum = "a8fc56b25e3aaf4b81a73f2a9a68ceae1e02d9005552e24058cfb9f96db73f33"
dependencies = [
"arc-swap",
"atomic_refcell",
"async-lock",
"async-trait",
"dashmap 6.0.1",
"fastrand",
"serde",
"serde_json",

View File

@ -53,7 +53,7 @@ collab-user = { version = "0.2" }
# Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ae3833ea91c238a66ca7bda63763d1d654740fb4" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c9504d4081a8d9893830dc6598429353b078271b" }
[dependencies]
serde_json.workspace = true
@ -116,13 +116,13 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
# Working directory: frontend
# To update the commit ID, run:

View File

@ -68,7 +68,7 @@ const createServer = async (req: Request) => {
logger.info(`Request URL: ${hostname}${reqUrl.pathname}`);
if (['/after-payment', '/login'].includes(reqUrl.pathname)) {
if (['/after-payment', '/login', '/as-template'].includes(reqUrl.pathname)) {
timer();
const htmlData = fs.readFileSync(indexPath, 'utf8');
const $ = load(htmlData);

View File

@ -73,6 +73,7 @@
"react-datepicker": "^4.23.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-hook-form": "^7.52.2",
"react-hot-toast": "^2.4.1",
"react-i18next": "^14.1.0",
"react-katex": "^3.0.1",
@ -137,6 +138,7 @@
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.13",
"axios-mock-adapter": "^2.0.0",
"babel-jest": "^29.6.2",
"chalk": "^4.1.2",
"cheerio": "1.0.0-rc.12",

View File

@ -152,6 +152,9 @@ dependencies:
react-error-boundary:
specifier: ^4.0.13
version: 4.0.13(react@18.2.0)
react-hook-form:
specifier: ^7.52.2
version: 7.52.2(react@18.2.0)
react-hot-toast:
specifier: ^2.4.1
version: 2.4.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0)
@ -340,6 +343,9 @@ devDependencies:
autoprefixer:
specifier: ^10.4.13
version: 10.4.13(postcss@8.4.21)
axios-mock-adapter:
specifier: ^2.0.0
version: 2.0.0(axios@1.7.2)
babel-jest:
specifier: ^29.6.2
version: 29.6.2(@babel/core@7.24.3)
@ -4945,6 +4951,16 @@ packages:
/aws4@1.12.0:
resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==}
/axios-mock-adapter@2.0.0(axios@1.7.2):
resolution: {integrity: sha512-D/K0J5Zm6KvaMTnsWrBQZWLzKN9GxUFZEa0mx2qeEHXDeTugCoplWehy8y36dj5vuSjhe1u/Dol8cZ8lzzmDew==}
peerDependencies:
axios: '>= 0.17.0'
dependencies:
axios: 1.7.2
fast-deep-equal: 3.1.3
is-buffer: 2.0.5
dev: true
/axios@1.7.2:
resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==}
dependencies:
@ -4953,7 +4969,6 @@ packages:
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/b4a@1.6.6:
resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==}
@ -6709,7 +6724,6 @@ packages:
peerDependenciesMeta:
debug:
optional: true
dev: false
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -7289,6 +7303,11 @@ packages:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
dev: false
/is-buffer@2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
dev: true
/is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
@ -9258,7 +9277,6 @@ packages:
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: false
/psl@1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
@ -9512,6 +9530,15 @@ packages:
/react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
/react-hook-form@7.52.2(react@18.2.0):
resolution: {integrity: sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A==}
engines: {node: '>=18.0.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
dependencies:
react: 18.2.0
dev: false
/react-hot-toast@2.4.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==}
engines: {node: '>=10'}

View File

@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bincode",
@ -183,7 +183,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bytes",
@ -301,6 +301,17 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-stream"
version = "0.3.5"
@ -800,7 +811,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"again",
"anyhow",
@ -851,7 +862,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"collab-entity",
"collab-rt-entity",
@ -864,7 +875,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"futures-channel",
"futures-util",
@ -947,7 +958,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"arc-swap",
@ -972,14 +983,13 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"collab",
"collab-entity",
"collab-plugins",
"dashmap 5.5.3",
"futures",
"getrandom 0.2.12",
@ -1002,7 +1012,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"arc-swap",
@ -1022,7 +1032,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"bytes",
@ -1041,7 +1051,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"arc-swap",
@ -1084,7 +1094,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"async-stream",
@ -1122,7 +1132,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bincode",
@ -1147,7 +1157,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"async-trait",
@ -1164,7 +1174,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=520b75dbe84ff17a55cf9424a55028781ae0bfc6#520b75dbe84ff17a55cf9424a55028781ae0bfc6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=49cf2206d7494bb3006402b807e7f171905213e3#49cf2206d7494bb3006402b807e7f171905213e3"
dependencies = [
"anyhow",
"collab",
@ -1193,6 +1203,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console"
version = "0.14.1"
@ -1542,7 +1561,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"app-error",
@ -1895,6 +1914,27 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
[[package]]
name = "event-listener"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "faccess"
version = "0.2.4"
@ -3144,7 +3184,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"futures-util",
@ -3161,7 +3201,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"app-error",
@ -3598,7 +3638,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"bytes",
@ -4678,6 +4718,12 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "parking"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -6187,7 +6233,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ae3833ea91c238a66ca7bda63763d1d654740fb4#ae3833ea91c238a66ca7bda63763d1d654740fb4"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c9504d4081a8d9893830dc6598429353b078271b#c9504d4081a8d9893830dc6598429353b078271b"
dependencies = [
"anyhow",
"app-error",
@ -8659,12 +8705,14 @@ dependencies = [
[[package]]
name = "yrs"
version = "0.19.2"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8ca5126331b9a5ef5bb10f3f1c3d01b05f298d348c66f8fb15497d83ee73176"
checksum = "a8fc56b25e3aaf4b81a73f2a9a68ceae1e02d9005552e24058cfb9f96db73f33"
dependencies = [
"arc-swap",
"atomic_refcell",
"async-lock",
"async-trait",
"dashmap 6.0.1",
"fastrand",
"serde",
"serde_json",

View File

@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
# Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ae3833ea91c238a66ca7bda63763d1d654740fb4" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c9504d4081a8d9893830dc6598429353b078271b" }
[dependencies]
serde_json.workspace = true
@ -116,13 +116,13 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "520b75dbe84ff17a55cf9424a55028781ae0bfc6" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "49cf2206d7494bb3006402b807e7f171905213e3" }
# Working directory: frontend
# To update the commit ID, run:

View File

@ -34,6 +34,7 @@ export enum BlockType {
TableBlock = 'table',
TableCell = 'table/cell',
LinkPreview = 'link_preview',
FileBlock = 'file',
}
export enum InlineBlockType {
@ -85,6 +86,18 @@ export interface LinkPreviewBlockData extends BlockData {
url?: string;
}
export enum FieldURLType {
Upload = 2,
Link = 1,
}
export interface FileBlockData extends BlockData {
name: string;
uploaded_at: number;
url: string;
url_type: FieldURLType;
}
export enum ImageType {
Local = 0,
Internal = 1,
@ -271,151 +284,151 @@ export enum YjsDatabaseKey {
export interface YDoc extends Y.Doc {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getMap(key: YjsEditorKey.data_section): YSharedRoot | any;
getMap (key: YjsEditorKey.data_section): YSharedRoot | any;
}
export interface YDatabaseRow extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): RowId;
get (key: YjsDatabaseKey.id): RowId;
get(key: YjsDatabaseKey.height): string;
get (key: YjsDatabaseKey.height): string;
get(key: YjsDatabaseKey.visibility): boolean;
get (key: YjsDatabaseKey.visibility): boolean;
get(key: YjsDatabaseKey.created_at): CreatedAt;
get (key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.last_modified): LastModified;
get (key: YjsDatabaseKey.last_modified): LastModified;
get(key: YjsDatabaseKey.cells): YDatabaseCells;
get (key: YjsDatabaseKey.cells): YDatabaseCells;
}
export interface YDatabaseCells extends Y.Map<unknown> {
get(key: FieldId): YDatabaseCell;
get (key: FieldId): YDatabaseCell;
}
export type EndTimestamp = string;
export type ReminderId = string;
export interface YDatabaseCell extends Y.Map<unknown> {
get(key: YjsDatabaseKey.created_at): CreatedAt;
get (key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.last_modified): LastModified;
get (key: YjsDatabaseKey.last_modified): LastModified;
get(key: YjsDatabaseKey.field_type): string;
get (key: YjsDatabaseKey.field_type): string;
get(key: YjsDatabaseKey.data): object | string | boolean | number;
get (key: YjsDatabaseKey.data): object | string | boolean | number;
get(key: YjsDatabaseKey.end_timestamp): EndTimestamp;
get (key: YjsDatabaseKey.end_timestamp): EndTimestamp;
get(key: YjsDatabaseKey.include_time): boolean;
get (key: YjsDatabaseKey.include_time): boolean;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.is_range): boolean;
get (key: YjsDatabaseKey.is_range): boolean;
get(key: YjsDatabaseKey.reminder_id): ReminderId;
get (key: YjsDatabaseKey.reminder_id): ReminderId;
}
export interface YSharedRoot extends Y.Map<unknown> {
get(key: YjsEditorKey.document): YDocument;
get (key: YjsEditorKey.document): YDocument;
get(key: YjsEditorKey.folder): YFolder;
get (key: YjsEditorKey.folder): YFolder;
get(key: YjsEditorKey.database): YDatabase;
get (key: YjsEditorKey.database): YDatabase;
get(key: YjsEditorKey.database_row): YDatabaseRow;
get (key: YjsEditorKey.database_row): YDatabaseRow;
}
export interface YFolder extends Y.Map<unknown> {
get(key: YjsFolderKey.views): YViews;
get (key: YjsFolderKey.views): YViews;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.meta): YFolderMeta;
get (key: YjsFolderKey.meta): YFolderMeta;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.relation): YFolderRelation;
get (key: YjsFolderKey.relation): YFolderRelation;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.section): YFolderSection;
get (key: YjsFolderKey.section): YFolderSection;
}
export interface YViews extends Y.Map<unknown> {
get(key: ViewId): YView;
get (key: ViewId): YView;
}
export interface YView extends Y.Map<unknown> {
get(key: YjsFolderKey.id): ViewId;
get (key: YjsFolderKey.id): ViewId;
get(key: YjsFolderKey.bid): string;
get (key: YjsFolderKey.bid): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.name): string;
get (key: YjsFolderKey.name): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.icon | YjsFolderKey.extra): string;
get (key: YjsFolderKey.icon | YjsFolderKey.extra): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.layout): string;
get (key: YjsFolderKey.layout): string;
}
export interface YFolderRelation extends Y.Map<unknown> {
get(key: ViewId): Y.Array<ViewId>;
get (key: ViewId): Y.Array<ViewId>;
}
export interface YFolderMeta extends Y.Map<unknown> {
get(key: YjsFolderKey.current_view | YjsFolderKey.current_workspace): string;
get (key: YjsFolderKey.current_view | YjsFolderKey.current_workspace): string;
}
export interface YFolderSection extends Y.Map<unknown> {
get(key: YjsFolderKey.favorite | YjsFolderKey.private | YjsFolderKey.recent | YjsFolderKey.trash): YFolderSectionItem;
get (key: YjsFolderKey.favorite | YjsFolderKey.private | YjsFolderKey.recent | YjsFolderKey.trash): YFolderSectionItem;
}
export interface YFolderSectionItem extends Y.Map<unknown> {
get(key: string): Y.Array<unknown>;
get (key: string): Y.Array<unknown>;
}
export interface YDocument extends Y.Map<unknown> {
get(key: YjsEditorKey.blocks | YjsEditorKey.page_id | YjsEditorKey.meta): YBlocks | YMeta | string;
get (key: YjsEditorKey.blocks | YjsEditorKey.page_id | YjsEditorKey.meta): YBlocks | YMeta | string;
}
export interface YBlocks extends Y.Map<unknown> {
get(key: BlockId): YBlock;
get (key: BlockId): YBlock;
}
export interface YBlock extends Y.Map<unknown> {
get(key: YjsEditorKey.block_id | YjsEditorKey.block_parent): BlockId;
get (key: YjsEditorKey.block_id | YjsEditorKey.block_parent): BlockId;
get(key: YjsEditorKey.block_type): BlockType;
get (key: YjsEditorKey.block_type): BlockType;
get(key: YjsEditorKey.block_data): string;
get (key: YjsEditorKey.block_data): string;
get(key: YjsEditorKey.block_children): ChildrenId;
get (key: YjsEditorKey.block_children): ChildrenId;
get(key: YjsEditorKey.block_external_id): ExternalId;
get (key: YjsEditorKey.block_external_id): ExternalId;
}
export interface YMeta extends Y.Map<unknown> {
get(key: YjsEditorKey.children_map | YjsEditorKey.text_map): YChildrenMap | YTextMap;
get (key: YjsEditorKey.children_map | YjsEditorKey.text_map): YChildrenMap | YTextMap;
}
export interface YChildrenMap extends Y.Map<unknown> {
get(key: ChildrenId): Y.Array<BlockId>;
get (key: ChildrenId): Y.Array<BlockId>;
}
export interface YTextMap extends Y.Map<unknown> {
get(key: ExternalId): Y.Text;
get (key: ExternalId): Y.Text;
}
export interface YDatabase extends Y.Map<unknown> {
get(key: YjsDatabaseKey.views): YDatabaseViews;
get (key: YjsDatabaseKey.views): YDatabaseViews;
get(key: YjsDatabaseKey.metas): YDatabaseMetas;
get (key: YjsDatabaseKey.metas): YDatabaseMetas;
get(key: YjsDatabaseKey.fields): YDatabaseFields;
get (key: YjsDatabaseKey.fields): YDatabaseFields;
get(key: YjsDatabaseKey.id): string;
get (key: YjsDatabaseKey.id): string;
}
export interface YDatabaseViews extends Y.Map<YDatabaseView> {
get(key: ViewId): YDatabaseView;
get (key: ViewId): YDatabaseView;
}
export type DatabaseId = string;
@ -431,32 +444,32 @@ export enum DatabaseViewLayout {
}
export interface YDatabaseView extends Y.Map<unknown> {
get(key: YjsDatabaseKey.database_id): DatabaseId;
get (key: YjsDatabaseKey.database_id): DatabaseId;
get(key: YjsDatabaseKey.name): string;
get (key: YjsDatabaseKey.name): string;
get(key: YjsDatabaseKey.created_at): CreatedAt;
get (key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.modified_at): ModifiedAt;
get (key: YjsDatabaseKey.modified_at): ModifiedAt;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.layout): string;
get (key: YjsDatabaseKey.layout): string;
get(key: YjsDatabaseKey.layout_settings): YDatabaseLayoutSettings;
get (key: YjsDatabaseKey.layout_settings): YDatabaseLayoutSettings;
get(key: YjsDatabaseKey.filters): YDatabaseFilters;
get (key: YjsDatabaseKey.filters): YDatabaseFilters;
get(key: YjsDatabaseKey.groups): YDatabaseGroups;
get (key: YjsDatabaseKey.groups): YDatabaseGroups;
get(key: YjsDatabaseKey.sorts): YDatabaseSorts;
get (key: YjsDatabaseKey.sorts): YDatabaseSorts;
get(key: YjsDatabaseKey.field_settings): YDatabaseFieldSettings;
get (key: YjsDatabaseKey.field_settings): YDatabaseFieldSettings;
get(key: YjsDatabaseKey.field_orders): YDatabaseFieldOrders;
get (key: YjsDatabaseKey.field_orders): YDatabaseFieldOrders;
get(key: YjsDatabaseKey.row_orders): YDatabaseRowOrders;
get (key: YjsDatabaseKey.row_orders): YDatabaseRowOrders;
get(key: YjsDatabaseKey.calculations): YDatabaseCalculations;
get (key: YjsDatabaseKey.calculations): YDatabaseCalculations;
}
export type YDatabaseFieldOrders = Y.Array<unknown>; // [ { id: FieldId } ]
@ -477,128 +490,128 @@ export type GroupId = string;
export interface YDatabaseLayoutSettings extends Y.Map<unknown> {
// DatabaseViewLayout.Board
get(key: '1'): YDatabaseBoardLayoutSetting;
get (key: '1'): YDatabaseBoardLayoutSetting;
// DatabaseViewLayout.Calendar
get(key: '2'): YDatabaseCalendarLayoutSetting;
get (key: '2'): YDatabaseCalendarLayoutSetting;
}
export interface YDatabaseBoardLayoutSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.hide_ungrouped_column | YjsDatabaseKey.collapse_hidden_groups): boolean;
get (key: YjsDatabaseKey.hide_ungrouped_column | YjsDatabaseKey.collapse_hidden_groups): boolean;
}
export interface YDatabaseCalendarLayoutSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.first_day_of_week | YjsDatabaseKey.field_id | YjsDatabaseKey.layout_ty): string;
get (key: YjsDatabaseKey.first_day_of_week | YjsDatabaseKey.field_id | YjsDatabaseKey.layout_ty): string;
get(key: YjsDatabaseKey.show_week_numbers | YjsDatabaseKey.show_weekends): boolean;
get (key: YjsDatabaseKey.show_week_numbers | YjsDatabaseKey.show_weekends): boolean;
}
export interface YDatabaseGroup extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): GroupId;
get (key: YjsDatabaseKey.id): GroupId;
get(key: YjsDatabaseKey.field_id): FieldId;
get (key: YjsDatabaseKey.field_id): FieldId;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.content): string;
get (key: YjsDatabaseKey.content): string;
get(key: YjsDatabaseKey.groups): YDatabaseGroupColumns;
get (key: YjsDatabaseKey.groups): YDatabaseGroupColumns;
}
export type YDatabaseGroupColumns = Y.Array<YDatabaseGroupColumn>;
export interface YDatabaseGroupColumn extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): string;
get (key: YjsDatabaseKey.id): string;
get(key: YjsDatabaseKey.visible): boolean;
get (key: YjsDatabaseKey.visible): boolean;
}
export interface YDatabaseRowOrder extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): SortId;
get (key: YjsDatabaseKey.id): SortId;
get(key: YjsDatabaseKey.height): number;
get (key: YjsDatabaseKey.height): number;
}
export interface YDatabaseSort extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): SortId;
get (key: YjsDatabaseKey.id): SortId;
get(key: YjsDatabaseKey.field_id): FieldId;
get (key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.condition): string;
get (key: YjsDatabaseKey.condition): string;
}
export type FilterId = string;
export interface YDatabaseFilter extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): FilterId;
get (key: YjsDatabaseKey.id): FilterId;
get(key: YjsDatabaseKey.field_id): FieldId;
get (key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.type | YjsDatabaseKey.condition | YjsDatabaseKey.content | YjsDatabaseKey.filter_type): string;
get (key: YjsDatabaseKey.type | YjsDatabaseKey.condition | YjsDatabaseKey.content | YjsDatabaseKey.filter_type): string;
}
export interface YDatabaseCalculation extends Y.Map<unknown> {
get(key: YjsDatabaseKey.field_id): FieldId;
get (key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.id | YjsDatabaseKey.type | YjsDatabaseKey.calculation_value): string;
get (key: YjsDatabaseKey.id | YjsDatabaseKey.type | YjsDatabaseKey.calculation_value): string;
}
export interface YDatabaseFieldSettings extends Y.Map<unknown> {
get(key: FieldId): YDatabaseFieldSetting;
get (key: FieldId): YDatabaseFieldSetting;
}
export interface YDatabaseFieldSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.visibility): string;
get (key: YjsDatabaseKey.visibility): string;
get(key: YjsDatabaseKey.wrap): boolean;
get (key: YjsDatabaseKey.wrap): boolean;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.width): string;
get (key: YjsDatabaseKey.width): string;
}
export interface YDatabaseMetas extends Y.Map<unknown> {
get(key: YjsDatabaseKey.iid): string;
get (key: YjsDatabaseKey.iid): string;
}
export interface YDatabaseFields extends Y.Map<YDatabaseField> {
get(key: FieldId): YDatabaseField;
get (key: FieldId): YDatabaseField;
}
export interface YDatabaseField extends Y.Map<unknown> {
get(key: YjsDatabaseKey.name): string;
get (key: YjsDatabaseKey.name): string;
get(key: YjsDatabaseKey.id): FieldId;
get (key: YjsDatabaseKey.id): FieldId;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.type): string;
get (key: YjsDatabaseKey.type): string;
get(key: YjsDatabaseKey.type_option): YDatabaseFieldTypeOption;
get (key: YjsDatabaseKey.type_option): YDatabaseFieldTypeOption;
get(key: YjsDatabaseKey.is_primary): boolean;
get (key: YjsDatabaseKey.is_primary): boolean;
get(key: YjsDatabaseKey.last_modified): LastModified;
get (key: YjsDatabaseKey.last_modified): LastModified;
}
export interface YDatabaseFieldTypeOption extends Y.Map<unknown> {
// key is the field type
get(key: string): YMapFieldTypeOption;
get (key: string): YMapFieldTypeOption;
}
export interface YMapFieldTypeOption extends Y.Map<unknown> {
get(key: YjsDatabaseKey.content): string;
get (key: YjsDatabaseKey.content): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.data): string;
get (key: YjsDatabaseKey.data): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.time_format): string;
get (key: YjsDatabaseKey.time_format): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.date_format): string;
get (key: YjsDatabaseKey.date_format): string;
get(key: YjsDatabaseKey.database_id): DatabaseId;
get (key: YjsDatabaseKey.database_id): DatabaseId;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.format): string;
get (key: YjsDatabaseKey.format): string;
}
export enum CollabType {

View File

@ -1,7 +1,7 @@
import { GetViewRowsMap, LoadView, LoadViewMeta } from '@/application/collab.type';
import { db } from '@/application/db';
import { ViewMeta } from '@/application/db/tables/view_metas';
import { AFConfigContext } from '@/components/app/AppConfig';
import { AFConfigContext } from '@/components/app/app.hooks';
import { useLiveQuery } from 'dexie-react-hooks';
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@ -9,6 +9,7 @@ import { useNavigate } from 'react-router-dom';
export interface PublishContextType {
namespace: string;
publishName: string;
isTemplateThumb?: boolean;
viewMeta?: ViewMeta;
toView: (viewId: string) => Promise<void>;
loadViewMeta: LoadViewMeta;
@ -23,10 +24,12 @@ export const PublishProvider = ({
children,
namespace,
publishName,
isTemplateThumb,
}: {
children: React.ReactNode;
namespace: string;
publishName: string;
isTemplateThumb?: boolean;
}) => {
const viewMeta = useLiveQuery(async () => {
const name = `${namespace}_${publishName}`;
@ -87,7 +90,7 @@ export const PublishProvider = ({
return Promise.reject(e);
}
},
[navigate, service]
[navigate, service],
);
const loadViewMeta = useCallback(
@ -124,7 +127,7 @@ export const PublishProvider = ({
return Promise.reject(e);
}
},
[service]
[service],
);
const getViewRowsMap = useCallback(
@ -148,7 +151,7 @@ export const PublishProvider = ({
return Promise.reject(e);
}
},
[service]
[service],
);
const loadView = useCallback(
@ -173,7 +176,7 @@ export const PublishProvider = ({
return Promise.reject(e);
}
},
[service]
[service],
);
useEffect(() => {
@ -195,6 +198,7 @@ export const PublishProvider = ({
toView,
namespace,
publishName,
isTemplateThumb,
}}
>
{children}
@ -202,6 +206,6 @@ export const PublishProvider = ({
);
};
export function usePublishContext() {
export function usePublishContext () {
return useContext(PublishContext);
}

View File

@ -15,7 +15,7 @@ import { Fetcher, StrategyType } from '@/application/services/js-services/cache/
// import { IndexeddbPersistence } from 'y-indexeddb';
import * as Y from 'yjs';
export function collabTypeToDBType(type: CollabType) {
export function collabTypeToDBType (type: CollabType) {
switch (type) {
case CollabType.Folder:
return 'folder';
@ -44,7 +44,7 @@ const collabSharedRootKeyMap = {
[CollabType.Empty]: YjsEditorKey.empty,
};
export function hasCollabCache(doc: YDoc) {
export function hasCollabCache (doc: YDoc) {
const data = doc.getMap(YjsEditorKey.data_section) as YSharedRoot;
return Object.values(collabSharedRootKeyMap).some((key) => {
@ -52,7 +52,7 @@ export function hasCollabCache(doc: YDoc) {
});
}
export async function hasViewMetaCache(name: string) {
export async function hasViewMetaCache (name: string) {
const data = await db.view_metas.get(name);
return !!data;
@ -64,7 +64,7 @@ export async function getPublishViewMeta<
child_views: PublishViewInfo[];
ancestor_views: PublishViewInfo[];
}
>(
> (
fetcher: Fetcher<T>,
{
namespace,
@ -73,7 +73,7 @@ export async function getPublishViewMeta<
namespace: string;
publishName: string;
},
strategy: StrategyType = StrategyType.CACHE_AND_NETWORK
strategy: StrategyType = StrategyType.CACHE_AND_NETWORK,
) {
const name = `${namespace}_${publishName}`;
const exist = await hasViewMetaCache(name);
@ -124,7 +124,7 @@ export async function getPublishView<
ancestor_views: PublishViewInfo[];
};
}
>(
> (
fetcher: Fetcher<T>,
{
namespace,
@ -133,7 +133,7 @@ export async function getPublishView<
namespace: string;
publishName: string;
},
strategy: StrategyType = StrategyType.CACHE_AND_NETWORK
strategy: StrategyType = StrategyType.CACHE_AND_NETWORK,
) {
const name = `${namespace}_${publishName}`;
const doc = await openCollabDB(name);
@ -197,7 +197,7 @@ export async function revalidatePublishViewMeta<
child_views: PublishViewInfo[];
ancestor_views: PublishViewInfo[];
}
>(name: string, fetcher: Fetcher<T>) {
> (name: string, fetcher: Fetcher<T>) {
const { view, child_views, ancestor_views } = await fetcher();
const dbView = await db.view_metas.get(name);
@ -211,7 +211,7 @@ export async function revalidatePublishViewMeta<
visible_view_ids: dbView?.visible_view_ids ?? [],
database_relations: dbView?.database_relations ?? {},
},
name
name,
);
return db.view_metas.get(name);
@ -225,7 +225,7 @@ export async function revalidatePublishView<
relations?: Record<DatabaseId, ViewId>;
meta: PublishViewMetaData;
}
>(name: string, fetcher: Fetcher<T>, collab: YDoc, rowMapDoc: Y.Doc) {
> (name: string, fetcher: Fetcher<T>, collab: YDoc, rowMapDoc: Y.Doc) {
const { data, meta, rows, visibleViewIds = [], relations = {} } = await fetcher();
await db.view_metas.put(
@ -237,7 +237,7 @@ export async function revalidatePublishView<
visible_view_ids: visibleViewIds,
database_relations: relations,
},
name
name,
);
if (rows) {
@ -260,16 +260,14 @@ export async function revalidatePublishView<
}
}
console.log('====', data);
applyYDoc(collab, data);
}
export async function deleteViewMeta(name: string) {
export async function deleteViewMeta (name: string) {
await db.view_metas.delete(name);
}
export async function deleteView(name: string) {
export async function deleteView (name: string) {
console.log('deleteView', name);
await deleteViewMeta(name);
await closeCollabDB(name);

Some files were not shown because too many files have changed in this diff Show More