Compare commits
20
Commits
0.6.0
...
build/test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2991be276 | ||
|
|
3645dd1b8b | ||
|
|
4237a73810 | ||
|
|
80a2ebc216 | ||
|
|
02a9f0dfbb | ||
|
|
4a8536d403 | ||
|
|
9c3f083684 | ||
|
|
628eace341 | ||
|
|
77a53ef67a | ||
|
|
5dec6ea545 | ||
|
|
dbcf8ba86f | ||
|
|
63fe1a6ef3 | ||
|
|
a2c52508fa | ||
|
|
0709a2d2ee | ||
|
|
e73b310076 | ||
|
|
7586a0ed48 | ||
|
|
d8b32168a9 | ||
|
|
8ca541bfa1 | ||
|
|
b64da2c02f | ||
|
|
fdaca36b87 |
No files matched your search
@@ -69,4 +69,4 @@ jobs:
|
||||
SCRIPT_AFTER: |
|
||||
docker build -t appflowy-web-app .
|
||||
docker rm -f appflowy-web-app || true
|
||||
docker run -d -p 80:80 -p 443:443 --name appflowy-web-app appflowy-web-app
|
||||
docker run -d -p 80:80 -p 443:443 --env-file .env --name appflowy-web-app appflowy-web-app
|
||||
@@ -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.0"
|
||||
APPFLOWY_VERSION = "0.6.1"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
+10
-10
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -17,25 +17,25 @@ void main() {
|
||||
await tester.openSettingsPage(SettingsPage.notifications);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final switchFinder = find.byType(Switch).first;
|
||||
final toggleFinder = find.byType(Toggle).first;
|
||||
|
||||
// Defaults to enabled
|
||||
Switch switchWidget = tester.widget(switchFinder);
|
||||
expect(switchWidget.value, true);
|
||||
Toggle toggleWidget = tester.widget(toggleFinder);
|
||||
expect(toggleWidget.value, true);
|
||||
|
||||
// Disable
|
||||
await tester.tap(switchFinder);
|
||||
await tester.tap(toggleFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
switchWidget = tester.widget(switchFinder);
|
||||
expect(switchWidget.value, false);
|
||||
toggleWidget = tester.widget(toggleFinder);
|
||||
expect(toggleWidget.value, false);
|
||||
|
||||
// Enable again
|
||||
await tester.tap(switchFinder);
|
||||
await tester.tap(toggleFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
switchWidget = tester.widget(switchFinder);
|
||||
expect(switchWidget.value, true);
|
||||
toggleWidget = tester.widget(toggleFinder);
|
||||
expect(toggleWidget.value, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -58,12 +58,6 @@ PODS:
|
||||
- Flutter
|
||||
- keyboard_height_plugin (0.0.1):
|
||||
- Flutter
|
||||
- media_kit_libs_ios_video (1.0.4):
|
||||
- Flutter
|
||||
- media_kit_native_event_loop (1.0.0):
|
||||
- Flutter
|
||||
- media_kit_video (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
@@ -72,8 +66,6 @@ PODS:
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- screen_brightness_ios (0.1.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.14.2):
|
||||
- SDWebImage/Core (= 5.14.2)
|
||||
- SDWebImage/Core (5.14.2)
|
||||
@@ -91,10 +83,6 @@ PODS:
|
||||
- Toast (4.0.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- volume_controller (0.0.1):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
@@ -110,20 +98,14 @@ DEPENDENCIES:
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||
- keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`)
|
||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@@ -161,20 +143,12 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
||||
keyboard_height_plugin:
|
||||
:path: ".symlinks/plugins/keyboard_height_plugin/ios"
|
||||
media_kit_libs_ios_video:
|
||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||
media_kit_native_event_loop:
|
||||
:path: ".symlinks/plugins/media_kit_native_event_loop/ios"
|
||||
media_kit_video:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
screen_brightness_ios:
|
||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
@@ -185,10 +159,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/super_native_extensions/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
volume_controller:
|
||||
:path: ".symlinks/plugins/volume_controller/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
|
||||
@@ -206,14 +176,10 @@ SPEC CHECKSUMS:
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
@@ -222,8 +188,6 @@ SPEC CHECKSUMS:
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||
|
||||
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/window_title_bar.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.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';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CocoaWindowChannel {
|
||||
@@ -122,15 +123,21 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
|
||||
return FlowyTooltip(
|
||||
richMessage: textSpan,
|
||||
child: FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
onPressed: () => context
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerDown: (_) => context
|
||||
.read<HomeSettingBloc>()
|
||||
.add(const HomeSettingEvent.collapseMenu()),
|
||||
iconPadding: const EdgeInsets.all(4.0),
|
||||
icon: context.read<HomeSettingBloc>().state.isMenuCollapsed
|
||||
? const FlowySvg(FlowySvgs.show_menu_s)
|
||||
: const FlowySvg(FlowySvgs.hide_menu_m),
|
||||
child: FlowyHover(
|
||||
child: Container(
|
||||
width: 24,
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: const RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: FlowySvg(FlowySvgs.hide_menu_s),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
@@ -15,7 +18,6 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -112,6 +114,9 @@ class _MobileHomePageState extends State<MobileHomePage> {
|
||||
create: (context) =>
|
||||
FavoriteBloc()..add(const FavoriteEvent.initial()),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()..add(const ReminderEvent.started()),
|
||||
),
|
||||
],
|
||||
child: BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
buildWhen: (previous, current) =>
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@@ -22,6 +23,18 @@ class MobileSpace extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MobileSpaceState extends State<MobileSpace> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createNewPageNotifier.addListener(_createNewPage);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
createNewPageNotifier.removeListener(_createNewPage);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SpaceBloc, SpaceState>(
|
||||
@@ -81,6 +94,14 @@ class _MobileSpaceState extends State<MobileSpace> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _createNewPage() {
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.createPage(
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Pages extends StatelessWidget {
|
||||
|
||||
+5
-44
@@ -1,18 +1,15 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
final PropertyValueNotifier<ViewLayoutPB?> createNewPageNotifier =
|
||||
PropertyValueNotifier(null);
|
||||
|
||||
const _homeLabel = 'home';
|
||||
const _addLabel = 'add';
|
||||
const _notificationLabel = 'notification';
|
||||
@@ -93,7 +90,7 @@ class MobileBottomNavigationBar extends StatelessWidget {
|
||||
void _onTap(BuildContext context, int bottomBarIndex) {
|
||||
if (_items[bottomBarIndex].label == _addLabel) {
|
||||
// show an add dialog
|
||||
_showCreatePageBottomSheet(context);
|
||||
createNewPageNotifier.value = ViewLayoutPB.Document;
|
||||
return;
|
||||
}
|
||||
// When navigating to a new branch, it's recommended to use the goBranch
|
||||
@@ -108,40 +105,4 @@ class MobileBottomNavigationBar extends StatelessWidget {
|
||||
initialLocation: bottomBarIndex == navigationShell.currentIndex,
|
||||
);
|
||||
}
|
||||
|
||||
void _showCreatePageBottomSheet(BuildContext context) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
title: LocaleKeys.sideBar_addAPage.tr(),
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
useRootNavigator: true,
|
||||
builder: (sheetContext) {
|
||||
return AddNewPageWidgetBottomSheet(
|
||||
view: ViewPB(),
|
||||
onAction: (layout) async {
|
||||
Navigator.of(sheetContext).pop();
|
||||
final currentWorkspaceId =
|
||||
await FolderEventReadCurrentWorkspace().send();
|
||||
await currentWorkspaceId.fold((s) async {
|
||||
final workspaceService = WorkspaceService(workspaceId: s.id);
|
||||
final result = await workspaceService.createView(
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
viewSection: ViewSectionPB.Private,
|
||||
layout: layout,
|
||||
);
|
||||
result.fold((s) {
|
||||
context.pushView(s);
|
||||
}, (e) {
|
||||
Log.error('Failed to create new page: $e');
|
||||
});
|
||||
}, (e) {
|
||||
Log.error('Failed to read current workspace: $e');
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
@@ -5,12 +7,10 @@ import 'package:appflowy/plugins/database/application/setting/property_bloc.dart
|
||||
import 'package:appflowy/plugins/database/calendar/application/calendar_setting_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
/// Widget that displays a list of settings that alters the appearance of the
|
||||
@@ -325,7 +325,6 @@ Widget _toggleItem({
|
||||
Toggle(
|
||||
value: value,
|
||||
onChanged: (value) => onToggle(!value),
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
@@ -11,12 +13,10 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'field_type_list.dart';
|
||||
@@ -258,7 +258,6 @@ enum FieldAction {
|
||||
onChanged: (_) => context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(const FieldEditorEvent.toggleWrapCellContent()),
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
);
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DateFormatButton extends StatelessWidget {
|
||||
const DateFormatButton({
|
||||
@@ -248,7 +248,6 @@ class IncludeTimeButton extends StatelessWidget {
|
||||
Toggle(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -74,7 +73,6 @@ class DatabaseGroupList extends StatelessWidget {
|
||||
value: !state.layoutSettings.hideUngroupedColumn,
|
||||
onChanged: (value) =>
|
||||
_updateLayoutSettings(state.layoutSettings, value),
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -114,14 +114,14 @@ class DocumentService {
|
||||
/// Upload a file to the cloud storage.
|
||||
Future<FlowyResult<UploadedFilePB, FlowyError>> uploadFile({
|
||||
required String localFilePath,
|
||||
bool isAsync = true,
|
||||
required String documentId,
|
||||
}) async {
|
||||
final workspace = await FolderEventReadCurrentWorkspace().send();
|
||||
return workspace.fold((l) async {
|
||||
final payload = UploadFileParamsPB(
|
||||
workspaceId: l.id,
|
||||
localFilePath: localFilePath,
|
||||
isAsync: isAsync,
|
||||
documentId: documentId,
|
||||
);
|
||||
final result = await DocumentEventUploadFile(payload).send();
|
||||
return result;
|
||||
|
||||
+117
-29
@@ -1,52 +1,102 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'document_share_bloc.freezed.dart';
|
||||
|
||||
const _url = 'https://test.appflowy.io';
|
||||
|
||||
class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
||||
DocumentShareBloc({
|
||||
required this.view,
|
||||
}) : super(const DocumentShareState.initial()) {
|
||||
}) : super(DocumentShareState.initial()) {
|
||||
on<DocumentShareEvent>((event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
final publishInfo = await ViewBackendService.getPublishInfo(view);
|
||||
publishInfo.fold((s) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isPublished: true,
|
||||
url: '$_url/${s.namespace}/${s.publishName}',
|
||||
),
|
||||
);
|
||||
}, (f) {
|
||||
emit(state.copyWith(isPublished: false, url: ''));
|
||||
});
|
||||
},
|
||||
share: (type, path) async {
|
||||
if (DocumentShareType.unimplemented.contains(type)) {
|
||||
Log.error('DocumentShareType $type is not implemented');
|
||||
return;
|
||||
}
|
||||
|
||||
emit(const DocumentShareState.loading());
|
||||
emit(state.copyWith(isLoading: true));
|
||||
|
||||
final exporter = DocumentExporter(view);
|
||||
final FlowyResult<ExportDataPB, FlowyError> result =
|
||||
await exporter.export(type.exportType).then((value) {
|
||||
return value.fold(
|
||||
(s) {
|
||||
if (path != null) {
|
||||
switch (type) {
|
||||
case DocumentShareType.markdown:
|
||||
return FlowyResult.success(_saveMarkdownToPath(s, path));
|
||||
case DocumentShareType.html:
|
||||
return FlowyResult.success(_saveHTMLToPath(s, path));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return FlowyResult.failure(FlowyError());
|
||||
},
|
||||
(f) => FlowyResult.failure(f),
|
||||
final result = await _export(type, path);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoading: false,
|
||||
exportResult: result,
|
||||
),
|
||||
);
|
||||
},
|
||||
publish: (url) async {
|
||||
// todo: optimize the logic
|
||||
const prefix = 'appflowy';
|
||||
final name = '${view.name}-${uuid()}'.substring(0, 19);
|
||||
|
||||
// set space name
|
||||
|
||||
try {
|
||||
final nameSpace =
|
||||
await ViewBackendService.getPublishNameSpace().getOrThrow();
|
||||
if (nameSpace.namespace != prefix) {
|
||||
await ViewBackendService.setPublishNameSpace(prefix).getOrThrow();
|
||||
}
|
||||
|
||||
final result = await ViewBackendService.publish(view, name: name);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
isPublished: true,
|
||||
publishResult: result,
|
||||
url: '$_url/${nameSpace.namespace}/$name',
|
||||
),
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
Log.error('publish error: $e');
|
||||
|
||||
emit(DocumentShareState.finish(result));
|
||||
emit(
|
||||
state.copyWith(
|
||||
isPublished: false,
|
||||
publishResult: FlowyResult.failure(
|
||||
FlowyError(msg: 'publish error: $e'),
|
||||
),
|
||||
url: '',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
unPublish: () async {
|
||||
await ViewBackendService.unpublish(view);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isPublished: false,
|
||||
publishResult: null,
|
||||
url: '',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -54,6 +104,31 @@ class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
|
||||
|
||||
final ViewPB view;
|
||||
|
||||
late final exporter = DocumentExporter(view);
|
||||
|
||||
Future<FlowyResult<ExportDataPB, FlowyError>> _export(
|
||||
DocumentShareType type,
|
||||
String? path,
|
||||
) async {
|
||||
final result = await exporter.export(type.exportType);
|
||||
return result.fold(
|
||||
(s) {
|
||||
if (path != null) {
|
||||
switch (type) {
|
||||
case DocumentShareType.markdown:
|
||||
return FlowySuccess(_saveMarkdownToPath(s, path));
|
||||
case DocumentShareType.html:
|
||||
return FlowySuccess(_saveHTMLToPath(s, path));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return FlowyResult.failure(FlowyError());
|
||||
},
|
||||
(f) => FlowyResult.failure(f),
|
||||
);
|
||||
}
|
||||
|
||||
ExportDataPB _saveMarkdownToPath(String markdown, String path) {
|
||||
File(path).writeAsStringSync(markdown);
|
||||
return ExportDataPB()
|
||||
@@ -93,15 +168,28 @@ enum DocumentShareType {
|
||||
|
||||
@freezed
|
||||
class DocumentShareEvent with _$DocumentShareEvent {
|
||||
const factory DocumentShareEvent.share(DocumentShareType type, String? path) =
|
||||
Share;
|
||||
const factory DocumentShareEvent.initial() = _Initial;
|
||||
const factory DocumentShareEvent.share(
|
||||
DocumentShareType type,
|
||||
String? path,
|
||||
) = _Share;
|
||||
const factory DocumentShareEvent.publish(String url) = _Publish;
|
||||
const factory DocumentShareEvent.unPublish() = _UnPublish;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DocumentShareState with _$DocumentShareState {
|
||||
const factory DocumentShareState.initial() = _Initial;
|
||||
const factory DocumentShareState.loading() = _Loading;
|
||||
const factory DocumentShareState.finish(
|
||||
FlowyResult<ExportDataPB, FlowyError> successOrFail,
|
||||
) = _Finish;
|
||||
const factory DocumentShareState({
|
||||
required bool isLoading,
|
||||
FlowyResult<ExportDataPB, FlowyError>? exportResult,
|
||||
required bool isPublished,
|
||||
FlowyResult<void, FlowyError>? publishResult,
|
||||
required String url,
|
||||
}) = _DocumentShareState;
|
||||
|
||||
factory DocumentShareState.initial() => const DocumentShareState(
|
||||
isLoading: false,
|
||||
isPublished: false,
|
||||
url: '',
|
||||
);
|
||||
}
|
||||
-7
@@ -53,13 +53,6 @@ extension PasteFromImage on EditorState {
|
||||
await File(copyToPath).writeAsBytes(imageBytes);
|
||||
final String? path;
|
||||
|
||||
if (context.mounted) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
LocaleKeys.document_imageBlock_imageIsUploading.tr(),
|
||||
);
|
||||
}
|
||||
|
||||
if (isLocalMode) {
|
||||
path = await saveImageToLocalStorage(copyToPath);
|
||||
} else {
|
||||
|
||||
+3
-1
@@ -49,9 +49,11 @@ Future<(String? path, String? errorMessage)> saveImageToCloudStorage(
|
||||
);
|
||||
}
|
||||
final documentService = DocumentService();
|
||||
Log.debug("Uploading image local path: $localImagePath");
|
||||
final result = await documentService.uploadFile(
|
||||
localFilePath: localImagePath,
|
||||
isAsync: false,
|
||||
// TODO(lucas): replace with actual documentId
|
||||
documentId: "temp",
|
||||
);
|
||||
return result.fold(
|
||||
(s) async {
|
||||
|
||||
+21
-33
@@ -73,8 +73,7 @@ class _AppFlowyMobileToolbarState extends State<AppFlowyMobileToolbar> {
|
||||
ValueListenableBuilder(
|
||||
valueListenable: isKeyboardShow,
|
||||
builder: (context, isKeyboardShow, __) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 110),
|
||||
return SizedBox(
|
||||
// only adding padding when the keyboard is triggered by editor
|
||||
height: isKeyboardShow && widget.editorState.selection != null
|
||||
? widget.toolbarHeight
|
||||
@@ -383,37 +382,26 @@ class _MobileToolbarState extends State<_MobileToolbar>
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: cachedKeyboardHeight,
|
||||
builder: (_, height, ___) {
|
||||
var paddingHeight = height;
|
||||
if (Platform.isAndroid) {
|
||||
// use the viewInsets to get the keyboard height on Android
|
||||
paddingHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||
// if the padding height is 0 and the keyboard height is not 0,
|
||||
// use the keyboard height
|
||||
if (paddingHeight == 0 && height != 0) {
|
||||
paddingHeight = height + MediaQuery.of(context).viewPadding.bottom;
|
||||
}
|
||||
}
|
||||
debugPrint('Keyboard height: $paddingHeight');
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(microseconds: 110),
|
||||
height: paddingHeight,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: showMenuNotifier,
|
||||
builder: (_, showingMenu, __) {
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(microseconds: 110),
|
||||
height: height,
|
||||
child: (showingMenu && selectedMenuIndex != null)
|
||||
? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
|
||||
context,
|
||||
widget.editorState,
|
||||
this,
|
||||
) ??
|
||||
const SizedBox.shrink()
|
||||
: const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: showMenuNotifier,
|
||||
builder: (_, showingMenu, __) {
|
||||
var paddingHeight = height;
|
||||
if (Platform.isAndroid) {
|
||||
paddingHeight =
|
||||
height + MediaQuery.of(context).viewPadding.bottom;
|
||||
}
|
||||
return SizedBox(
|
||||
height: paddingHeight,
|
||||
child: (showingMenu && selectedMenuIndex != null)
|
||||
? widget.toolbarItems[selectedMenuIndex!].menuBuilder?.call(
|
||||
context,
|
||||
widget.editorState,
|
||||
this,
|
||||
) ??
|
||||
const SizedBox.shrink()
|
||||
: const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,246 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.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:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class PublishTab extends StatelessWidget {
|
||||
const PublishTab({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<DocumentShareBloc, DocumentShareState>(
|
||||
listener: (context, state) {
|
||||
if (state.publishResult != null) {
|
||||
state.publishResult!.fold(
|
||||
(value) => showSnackBarMessage(context, 'Published successfully'),
|
||||
(error) =>
|
||||
showSnackBarMessage(context, 'Failed to publish: $error'),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return state.isPublished
|
||||
? _PublishedWidget(
|
||||
url: state.url,
|
||||
onVisitSite: () {},
|
||||
onUnPublish: () {
|
||||
context
|
||||
.read<DocumentShareBloc>()
|
||||
.add(const DocumentShareEvent.unPublish());
|
||||
},
|
||||
)
|
||||
: _UnPublishWidget(
|
||||
onPublish: () => context
|
||||
.read<DocumentShareBloc>()
|
||||
.add(const DocumentShareEvent.publish('')),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PublishedWidget extends StatefulWidget {
|
||||
const _PublishedWidget({
|
||||
required this.url,
|
||||
required this.onVisitSite,
|
||||
required this.onUnPublish,
|
||||
});
|
||||
|
||||
final String url;
|
||||
final VoidCallback onVisitSite;
|
||||
final VoidCallback onUnPublish;
|
||||
|
||||
@override
|
||||
State<_PublishedWidget> createState() => _PublishedWidgetState();
|
||||
}
|
||||
|
||||
class _PublishedWidgetState extends State<_PublishedWidget> {
|
||||
final controller = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.text = widget.url;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(16),
|
||||
const _PublishTabHeader(),
|
||||
const VSpace(16),
|
||||
_PublishUrl(
|
||||
controller: controller,
|
||||
onCopy: (url) {},
|
||||
onSubmitted: (url) {},
|
||||
),
|
||||
const VSpace(16),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildButton(
|
||||
context,
|
||||
name: LocaleKeys.shareAction_unPublish.tr(),
|
||||
borderColor: const Color(0x1E14171B),
|
||||
onTap: widget.onUnPublish,
|
||||
),
|
||||
const Spacer(),
|
||||
_buildButton(
|
||||
context,
|
||||
name: LocaleKeys.shareAction_visitSite.tr(),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
textColor: Colors.white,
|
||||
onTap: () {
|
||||
safeLaunchUrl(controller.text);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildButton(
|
||||
BuildContext context, {
|
||||
required String name,
|
||||
Color? backgroundColor,
|
||||
Color borderColor = Colors.transparent,
|
||||
Color? textColor,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return SizedBox(
|
||||
height: 36,
|
||||
width: 189,
|
||||
child: FlowyButton(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
text: FlowyText.regular(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
color: textColor,
|
||||
),
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UnPublishWidget extends StatelessWidget {
|
||||
const _UnPublishWidget({
|
||||
required this.onPublish,
|
||||
});
|
||||
|
||||
final VoidCallback onPublish;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(16),
|
||||
const _PublishTabHeader(),
|
||||
const VSpace(16),
|
||||
RoundedTextButton(
|
||||
height: 36,
|
||||
title: LocaleKeys.shareAction_publish.tr(),
|
||||
padding: const EdgeInsets.symmetric(vertical: 9.0),
|
||||
fontSize: 14.0,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
onPressed: onPublish,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PublishTabHeader extends StatelessWidget {
|
||||
const _PublishTabHeader();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const FlowySvg(FlowySvgs.share_publish_s),
|
||||
const HSpace(6),
|
||||
FlowyText(LocaleKeys.shareAction_publishToTheWeb.tr()),
|
||||
],
|
||||
),
|
||||
const VSpace(4),
|
||||
FlowyText.regular(
|
||||
LocaleKeys.shareAction_publishToTheWebHint.tr(),
|
||||
fontSize: 12,
|
||||
maxLines: 3,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PublishUrl extends StatelessWidget {
|
||||
const _PublishUrl({
|
||||
required this.controller,
|
||||
required this.onCopy,
|
||||
required this.onSubmitted,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final void Function(String url) onCopy;
|
||||
final void Function(String url) onSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 36,
|
||||
child: FlowyTextField(
|
||||
autoFocus: false,
|
||||
controller: controller,
|
||||
suffixIcon: _buildCopyLinkIcon(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCopyLinkIcon() {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => onCopy(controller.text),
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(left: BorderSide(color: Color(0x141F2329))),
|
||||
),
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_toolbar_link_m,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+36
-13
@@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/share/share_menu.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/string_extension.dart';
|
||||
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||
@@ -13,6 +14,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -28,23 +30,44 @@ class DocumentShareButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<DocumentShareBloc>(param1: view),
|
||||
create: (context) => getIt<DocumentShareBloc>(param1: view)
|
||||
..add(const DocumentShareEvent.initial()),
|
||||
child: BlocListener<DocumentShareBloc, DocumentShareState>(
|
||||
listener: (context, state) {
|
||||
state.mapOrNull(
|
||||
finish: (state) {
|
||||
state.successOrFail.fold(
|
||||
(data) => _handleExportData(context, data),
|
||||
_handleExportError,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (state.isLoading == false && state.exportResult != null) {
|
||||
state.exportResult!.fold(
|
||||
(data) => _handleExportData(context, data),
|
||||
_handleExportError,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<DocumentShareBloc, DocumentShareState>(
|
||||
builder: (context, state) => SizedBox(
|
||||
height: 32.0,
|
||||
child: IntrinsicWidth(child: ShareActionList(view: view)),
|
||||
),
|
||||
builder: (context, state) {
|
||||
final shareBloc = context.read<DocumentShareBloc>();
|
||||
return SizedBox(
|
||||
height: 32.0,
|
||||
child: IntrinsicWidth(
|
||||
child: AppFlowyPopover(
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 422,
|
||||
),
|
||||
margin: const EdgeInsets.all(16),
|
||||
offset: const Offset(0, 8),
|
||||
popupBuilder: (context) => BlocProvider.value(
|
||||
value: shareBloc,
|
||||
child: const ShareMenu(),
|
||||
),
|
||||
child: RoundedTextButton(
|
||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
fontSize: 14.0,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'publish_tab.dart';
|
||||
|
||||
enum ShareMenuTab {
|
||||
share,
|
||||
publish,
|
||||
exportAs;
|
||||
|
||||
static List<ShareMenuTab> supportedTabs = [
|
||||
ShareMenuTab.share,
|
||||
ShareMenuTab.publish,
|
||||
ShareMenuTab.exportAs,
|
||||
];
|
||||
|
||||
String get i18n {
|
||||
switch (this) {
|
||||
case ShareMenuTab.share:
|
||||
return 'Share';
|
||||
case ShareMenuTab.publish:
|
||||
return LocaleKeys.shareAction_publish;
|
||||
case ShareMenuTab.exportAs:
|
||||
return 'Export as';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShareMenu extends StatefulWidget {
|
||||
const ShareMenu({super.key});
|
||||
|
||||
@override
|
||||
State<ShareMenu> createState() => _ShareMenuState();
|
||||
}
|
||||
|
||||
class _ShareMenuState extends State<ShareMenu> {
|
||||
ShareMenuTab selectedTab = ShareMenuTab.publish;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final children = {
|
||||
for (final tab in ShareMenuTab.supportedTabs)
|
||||
tab: _Segment(
|
||||
title: tab.i18n.tr(),
|
||||
isSelected: selectedTab == tab,
|
||||
),
|
||||
};
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 34,
|
||||
child: CustomSlidingSegmentedControl<ShareMenuTab>(
|
||||
initialValue: selectedTab,
|
||||
curve: Curves.linear,
|
||||
padding: 0,
|
||||
fixedWidth: 128,
|
||||
innerPadding: const EdgeInsets.all(3.0),
|
||||
children: children,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEEF0F3),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
thumbDecoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x141F2225),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
onValueChanged: (v) {
|
||||
setState(() {
|
||||
selectedTab = v;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildTab(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(BuildContext context) {
|
||||
switch (selectedTab) {
|
||||
case ShareMenuTab.publish:
|
||||
return const PublishTab();
|
||||
|
||||
default:
|
||||
return const Center(
|
||||
child: FlowyText('coming soon'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Segment extends StatelessWidget {
|
||||
const _Segment({
|
||||
required this.title,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textColor = isSelected ? null : Theme.of(context).hintColor;
|
||||
return FlowyText(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
color: textColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/user/application/user_settings_service.dart';
|
||||
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
|
||||
@@ -22,8 +24,6 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -164,9 +164,6 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
|
||||
),
|
||||
BlocProvider.value(value: getIt<RenameViewBloc>()),
|
||||
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()..add(const ReminderEvent.started()),
|
||||
),
|
||||
],
|
||||
child: BlocListener<ActionNavigationBloc, ActionNavigationState>(
|
||||
listenWhen: (_, curr) => curr.action != null,
|
||||
|
||||
@@ -64,11 +64,6 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
|
||||
if (PlatformExtension.isWindows) {
|
||||
// Hide title bar on Windows, we implement a custom solution elsewhere
|
||||
await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
|
||||
}
|
||||
|
||||
if (position != null) {
|
||||
await windowManager.setPosition(position);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
@@ -191,20 +193,23 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
final isExpanded = await _getSpaceExpandStatus(space);
|
||||
emit(state.copyWith(currentSpace: space, isExpanded: isExpanded));
|
||||
|
||||
// open the first page by default
|
||||
if (space.childViews.isNotEmpty) {
|
||||
final firstPage = space.childViews.first;
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastCreatedPage: firstPage,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastCreatedPage: ViewPB(),
|
||||
),
|
||||
);
|
||||
// don't open the page automatically on mobile
|
||||
if (PlatformExtension.isDesktop) {
|
||||
// open the first page by default
|
||||
if (space.childViews.isNotEmpty) {
|
||||
final firstPage = space.childViews.first;
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastCreatedPage: firstPage,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastCreatedPage: ViewPB(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
expand: (space, isExpanded) async {
|
||||
@@ -253,6 +258,10 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
);
|
||||
},
|
||||
reset: (userProfile, workspaceId) async {
|
||||
if (workspaceId == _workspaceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
_reset(userProfile, workspaceId);
|
||||
|
||||
add(
|
||||
@@ -317,6 +326,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
required String icon,
|
||||
required String iconColor,
|
||||
required SpacePermission permission,
|
||||
String? viewId,
|
||||
}) async {
|
||||
final section = switch (permission) {
|
||||
SpacePermission.publicToAll => ViewSectionPB.Public,
|
||||
@@ -327,6 +337,7 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
name: name,
|
||||
viewSection: section,
|
||||
setAsCurrent: false,
|
||||
viewId: viewId,
|
||||
);
|
||||
return await result.fold((space) async {
|
||||
Log.info('Space created: $space');
|
||||
@@ -472,56 +483,82 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
if (members.items.length == 1 || isOwner) {
|
||||
// create a new public space and a new private space
|
||||
// move all the views in the workspace to the new public/private space
|
||||
final publicViews =
|
||||
await _workspaceService.getPublicViews().getOrThrow();
|
||||
// only migrate the public space if there are any public views
|
||||
if (publicViews.isNotEmpty) {
|
||||
final publicSpace = await _createSpace(
|
||||
name: 'Shared',
|
||||
icon: builtInSpaceIcons.first,
|
||||
iconColor: builtInSpaceColors.first,
|
||||
permission: SpacePermission.publicToAll,
|
||||
);
|
||||
|
||||
if (publicSpace != null) {
|
||||
for (final view in publicViews.reversed) {
|
||||
if (view.isSpace) {
|
||||
continue;
|
||||
}
|
||||
await ViewBackendService.moveViewV2(
|
||||
viewId: view.id,
|
||||
newParentId: publicSpace.id,
|
||||
prevViewId: view.parentViewId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// create a new private space
|
||||
final privateViews =
|
||||
await _workspaceService.getPrivateViews().getOrThrow();
|
||||
// only migrate the private space if there are any private views
|
||||
if (privateViews.isNotEmpty) {
|
||||
final privateSpace = await _createSpace(
|
||||
name: 'Private',
|
||||
icon: builtInSpaceIcons.last,
|
||||
iconColor: builtInSpaceColors.last,
|
||||
permission: SpacePermission.private,
|
||||
var publicViews = await _workspaceService.getPublicViews().getOrThrow();
|
||||
final containsPublicSpace = publicViews.any(
|
||||
(e) => e.isSpace && e.spacePermission == SpacePermission.publicToAll,
|
||||
);
|
||||
if (privateSpace != null) {
|
||||
for (final view in privateViews.reversed) {
|
||||
publicViews = publicViews.where((e) => !e.isSpace).toList();
|
||||
// if there is already a public space, don't migrate the public space
|
||||
// only migrate the public space if there are any public views
|
||||
if (publicViews.isEmpty || containsPublicSpace) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final viewId = fixedUuid(user.id.toInt(), UuidType.publicSpace);
|
||||
final publicSpace = await _createSpace(
|
||||
name: 'Shared',
|
||||
icon: builtInSpaceIcons.first,
|
||||
iconColor: builtInSpaceColors.first,
|
||||
permission: SpacePermission.publicToAll,
|
||||
viewId: viewId,
|
||||
);
|
||||
|
||||
Log.info('migrating: created a new public space: ${publicSpace?.id}');
|
||||
|
||||
if (publicSpace != null) {
|
||||
for (final view in publicViews.reversed) {
|
||||
if (view.isSpace) {
|
||||
continue;
|
||||
}
|
||||
await ViewBackendService.moveViewV2(
|
||||
viewId: view.id,
|
||||
newParentId: privateSpace.id,
|
||||
prevViewId: view.parentViewId,
|
||||
newParentId: publicSpace.id,
|
||||
prevViewId: null,
|
||||
);
|
||||
Log.info(
|
||||
'migrating: migrate ${view.name}(${view.id}) to public space(${publicSpace.id})',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create a new private space
|
||||
final viewId = fixedUuid(user.id.toInt(), UuidType.privateSpace);
|
||||
var privateViews = await _workspaceService.getPrivateViews().getOrThrow();
|
||||
// if there is already a private space, don't migrate the private space
|
||||
final containsPrivateSpace = privateViews.any(
|
||||
(e) => e.isSpace && e.spacePermission == SpacePermission.private,
|
||||
);
|
||||
privateViews = privateViews.where((e) => !e.isSpace).toList();
|
||||
if (privateViews.isEmpty || containsPrivateSpace) {
|
||||
return true;
|
||||
}
|
||||
// only migrate the private space if there are any private views
|
||||
final privateSpace = await _createSpace(
|
||||
name: 'Private',
|
||||
icon: builtInSpaceIcons.last,
|
||||
iconColor: builtInSpaceColors.last,
|
||||
permission: SpacePermission.private,
|
||||
viewId: viewId,
|
||||
);
|
||||
Log.info('migrating: created a new private space: ${privateSpace?.id}');
|
||||
|
||||
if (privateSpace != null) {
|
||||
for (final view in privateViews.reversed) {
|
||||
if (view.isSpace) {
|
||||
continue;
|
||||
}
|
||||
await ViewBackendService.moveViewV2(
|
||||
viewId: view.id,
|
||||
newParentId: privateSpace.id,
|
||||
prevViewId: null,
|
||||
);
|
||||
Log.info(
|
||||
'migrating: migrate ${view.name}(${view.id}) to private space(${privateSpace.id})',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error('migrate space error: $e');
|
||||
@@ -534,14 +571,16 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
|
||||
required List<ViewPB> publicViews,
|
||||
required List<ViewPB> privateViews,
|
||||
}) async {
|
||||
final publicSpaces =
|
||||
spaces.where((e) => e.spacePermission == SpacePermission.publicToAll);
|
||||
final publicSpaces = spaces.where(
|
||||
(e) => e.spacePermission == SpacePermission.publicToAll,
|
||||
);
|
||||
if (publicSpaces.isEmpty && publicViews.isNotEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final privateSpaces =
|
||||
spaces.where((e) => e.spacePermission == SpacePermission.private);
|
||||
final privateSpaces = spaces.where(
|
||||
(e) => e.spacePermission == SpacePermission.private,
|
||||
);
|
||||
if (privateSpaces.isEmpty && privateViews.isNotEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ class ViewBackendService {
|
||||
/// If the index is null, the view will be added to the end of the list.
|
||||
int? index,
|
||||
ViewSectionPB? section,
|
||||
final String? viewId,
|
||||
}) {
|
||||
final payload = CreateViewPayloadPB.create()
|
||||
..parentViewId = parentViewId
|
||||
@@ -63,6 +64,10 @@ class ViewBackendService {
|
||||
payload.section = section;
|
||||
}
|
||||
|
||||
if (viewId != null) {
|
||||
payload.viewId = viewId;
|
||||
}
|
||||
|
||||
return FolderEventCreateView(payload).send();
|
||||
}
|
||||
|
||||
@@ -266,4 +271,42 @@ class ViewBackendService {
|
||||
);
|
||||
return FolderEventUpdateViewVisibilityStatus(payload).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<PublishInfoResponsePB, FlowyError>> getPublishInfo(
|
||||
ViewPB view,
|
||||
) async {
|
||||
final payload = ViewIdPB()..value = view.id;
|
||||
return FolderEventGetPublishInfo(payload).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> publish(
|
||||
ViewPB view, {
|
||||
String? name,
|
||||
}) async {
|
||||
final payload = PublishViewParamsPB()..viewId = view.id;
|
||||
|
||||
if (name != null) {
|
||||
payload.publishName = name;
|
||||
}
|
||||
return FolderEventPublishView(payload).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> unpublish(
|
||||
ViewPB view,
|
||||
) async {
|
||||
final payload = UnpublishViewsPayloadPB(viewIds: [view.id]);
|
||||
return FolderEventUnpublishViews(payload).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<void, FlowyError>> setPublishNameSpace(
|
||||
String name,
|
||||
) async {
|
||||
final payload = SetPublishNamespacePayloadPB()..newNamespace = name;
|
||||
return FolderEventSetPublishNamespace(payload).send();
|
||||
}
|
||||
|
||||
static Future<FlowyResult<PublishNamespacePB, FlowyError>>
|
||||
getPublishNameSpace() async {
|
||||
return FolderEventGetPublishNamespace().send();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class WorkspaceService {
|
||||
int? index,
|
||||
ViewLayoutPB? layout,
|
||||
bool? setAsCurrent,
|
||||
String? viewId,
|
||||
}) {
|
||||
final payload = CreateViewPayloadPB.create()
|
||||
..parentViewId = workspaceId
|
||||
@@ -37,6 +38,10 @@ class WorkspaceService {
|
||||
payload.setAsCurrent = setAsCurrent;
|
||||
}
|
||||
|
||||
if (viewId != null) {
|
||||
payload.viewId = viewId;
|
||||
}
|
||||
|
||||
return FolderEventCreateView(payload).send();
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,10 @@ class DesktopHomeScreen extends StatelessWidget {
|
||||
child: MultiBlocProvider(
|
||||
key: ValueKey(userProfile.id),
|
||||
providers: [
|
||||
BlocProvider<ReminderBloc>.value(value: getIt<ReminderBloc>()),
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()
|
||||
..add(const ReminderEvent.started()),
|
||||
),
|
||||
BlocProvider<TabsBloc>.value(value: getIt<TabsBloc>()),
|
||||
BlocProvider<HomeBloc>(
|
||||
create: (_) =>
|
||||
|
||||
+16
-60
@@ -22,7 +22,6 @@ import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@@ -288,65 +287,22 @@ class _ImportDataFieldState extends State<_ImportDataField> {
|
||||
(_) => _showToast(LocaleKeys.settings_menu_importFailed.tr()),
|
||||
),
|
||||
builder: (context, state) {
|
||||
return DottedBorder(
|
||||
radius: const Radius.circular(8),
|
||||
dashPattern: const [2, 2],
|
||||
borderType: BorderType.RRect,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
// When dragging files are enabled
|
||||
// FlowyText.regular('Drag file here or'),
|
||||
// const VSpace(8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 42,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.settings_manageDataPage_importData_action
|
||||
.tr(),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
vertical: 12,
|
||||
),
|
||||
fontWeight: FontWeight.w600,
|
||||
radius: BorderRadius.circular(12),
|
||||
fillColor: Theme.of(context).colorScheme.primary,
|
||||
hoverColor: const Color(0xFF005483),
|
||||
fontHoverColor: Colors.white,
|
||||
onPressed: () async {
|
||||
final path = await getIt<FilePickerService>()
|
||||
.getDirectoryPath();
|
||||
if (path == null || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
return SingleSettingAction(
|
||||
label:
|
||||
LocaleKeys.settings_manageDataPage_importData_description.tr(),
|
||||
labelMaxLines: 2,
|
||||
buttonLabel:
|
||||
LocaleKeys.settings_manageDataPage_importData_action.tr(),
|
||||
onPressed: () async {
|
||||
final path = await getIt<FilePickerService>().getDirectoryPath();
|
||||
if (path == null || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.read<SettingFileImportBloc>().add(
|
||||
SettingFileImportEvent
|
||||
.importAppFlowyDataFolder(
|
||||
path,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(8),
|
||||
FlowyText.regular(
|
||||
LocaleKeys.settings_manageDataPage_importData_description
|
||||
.tr(),
|
||||
// 'Supported filetypes:\nCSV, Notion, Text, and Markdown',
|
||||
maxLines: 3,
|
||||
lineHeight: 1.5,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
context
|
||||
.read<SettingFileImportBloc>()
|
||||
.add(SettingFileImportEvent.importAppFlowyDataFolder(path));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -496,7 +452,7 @@ class _DataPathActions extends StatelessWidget {
|
||||
label:
|
||||
LocaleKeys.settings_manageDataPage_dataStorage_actions_open.tr(),
|
||||
icon: const FlowySvg(FlowySvgs.folder_m, size: Size.square(20)),
|
||||
onPressed: () => afLaunchUrlString('file://$currentPath'),
|
||||
onPressed: () => afLaunchUrl(Uri.file(currentPath)),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
+47
-44
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
||||
@@ -44,6 +45,8 @@ class _SettingsPlanComparisonDialogState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLM = Theme.of(context).isLightMode;
|
||||
|
||||
return BlocListener<SettingsPlanBloc, SettingsPlanState>(
|
||||
listener: (context, state) {
|
||||
final readyState = state.mapOrNull(ready: (state) => state);
|
||||
@@ -64,15 +67,11 @@ class _SettingsPlanComparisonDialogState
|
||||
),
|
||||
),
|
||||
),
|
||||
title:
|
||||
LocaleKeys.settings_comparePlanDialog_paymentSuccess_title.tr(
|
||||
args: [readyState.subscription.label],
|
||||
),
|
||||
title: LocaleKeys.settings_comparePlanDialog_paymentSuccess_title
|
||||
.tr(args: [readyState.subscription.label]),
|
||||
subtitle: LocaleKeys
|
||||
.settings_comparePlanDialog_paymentSuccess_description
|
||||
.tr(
|
||||
args: [readyState.subscription.label],
|
||||
),
|
||||
.tr(args: [readyState.subscription.label]),
|
||||
hideCancelButton: true,
|
||||
confirm: Navigator.of(context).pop,
|
||||
confirmLabel: LocaleKeys.button_close.tr(),
|
||||
@@ -96,6 +95,7 @@ class _SettingsPlanComparisonDialogState
|
||||
FlowyText.semibold(
|
||||
LocaleKeys.settings_comparePlanDialog_title.tr(),
|
||||
fontSize: 24,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
@@ -108,13 +108,14 @@ class _SettingsPlanComparisonDialogState
|
||||
child: FlowySvg(
|
||||
FlowySvgs.m_close_m,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const VSpace(16),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
controller: horizontalController,
|
||||
@@ -140,7 +141,7 @@ class _SettingsPlanComparisonDialogState
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(26),
|
||||
const VSpace(30),
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: FlowyText.semibold(
|
||||
@@ -149,7 +150,9 @@ class _SettingsPlanComparisonDialogState
|
||||
.tr(),
|
||||
fontSize: 24,
|
||||
maxLines: 2,
|
||||
color: const Color(0xFF5C3699),
|
||||
color: isLM
|
||||
? const Color(0xFF5C3699)
|
||||
: const Color(0xFFE8E0FF),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 64),
|
||||
@@ -281,17 +284,18 @@ class _PlanTable extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final highlightPlan = !isCurrent && !canDowngrade && canUpgrade;
|
||||
final isLM = Theme.of(context).isLightMode;
|
||||
|
||||
return Container(
|
||||
width: 210,
|
||||
width: 215,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
gradient: !highlightPlan
|
||||
? null
|
||||
: const LinearGradient(
|
||||
: LinearGradient(
|
||||
colors: [
|
||||
Color(0xFF251D37),
|
||||
Color(0xFF7547C0),
|
||||
isLM ? const Color(0xFF251D37) : const Color(0xFF7459AD),
|
||||
isLM ? const Color(0xFF7547C0) : const Color(0xFFDDC8FF),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -311,6 +315,7 @@ class _PlanTable extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isCurrent) const _CurrentBadge(),
|
||||
const VSpace(4),
|
||||
_Heading(
|
||||
title: title,
|
||||
description: description,
|
||||
@@ -376,14 +381,16 @@ class _CurrentBadge extends StatelessWidget {
|
||||
height: 22,
|
||||
width: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4F3F5F),
|
||||
color: Theme.of(context).isLightMode
|
||||
? const Color(0xFF4F3F5F)
|
||||
: const Color(0xFFE8E0FF),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Center(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_comparePlanDialog_current.tr(),
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).isLightMode ? Colors.white : Colors.black,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -417,22 +424,28 @@ class _ComparisonCell extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
FlowySvg(icon!),
|
||||
FlowySvg(
|
||||
icon!,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
),
|
||||
] else ...[
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
label,
|
||||
lineHeight: 1.2,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (tooltip != null)
|
||||
FlowyTooltip(
|
||||
message: tooltip,
|
||||
child: const FlowySvg(FlowySvgs.information_s),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.information_s,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -457,9 +470,8 @@ class _ActionButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLM = Theme.of(context).brightness == Brightness.light;
|
||||
final isLM = Theme.of(context).isLightMode;
|
||||
|
||||
final gradientBorder = useGradientBorder && isLM;
|
||||
return SizedBox(
|
||||
height: 56,
|
||||
child: Row(
|
||||
@@ -475,25 +487,17 @@ class _ActionButton extends StatelessWidget {
|
||||
child: _drawGradientBorder(
|
||||
isLM: isLM,
|
||||
child: Container(
|
||||
height: gradientBorder ? 36 : 40,
|
||||
width: gradientBorder ? 148 : 152,
|
||||
height: 36,
|
||||
width: 148,
|
||||
decoration: BoxDecoration(
|
||||
color: useGradientBorder
|
||||
? Theme.of(context).cardColor
|
||||
: Colors.transparent,
|
||||
border: Border.all(
|
||||
color: gradientBorder
|
||||
? Colors.transparent
|
||||
: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
borderRadius:
|
||||
BorderRadius.circular(gradientBorder ? 14 : 16),
|
||||
border: Border.all(color: Colors.transparent),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Center(
|
||||
child: _drawText(
|
||||
label,
|
||||
isLM,
|
||||
),
|
||||
child: _drawText(label, isLM),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -511,6 +515,7 @@ class _ActionButton extends StatelessWidget {
|
||||
fontSize: 14,
|
||||
lineHeight: 1.2,
|
||||
fontWeight: useGradientBorder ? FontWeight.w600 : FontWeight.w500,
|
||||
color: const Color(0xFFC49BEC),
|
||||
);
|
||||
|
||||
if (!useGradientBorder || !isLM) {
|
||||
@@ -532,19 +537,15 @@ class _ActionButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _drawGradientBorder({required bool isLM, required Widget child}) {
|
||||
if (!useGradientBorder || !isLM) {
|
||||
return child;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
transform: GradientRotation(-1.2),
|
||||
stops: [0.4, 1],
|
||||
gradient: LinearGradient(
|
||||
transform: const GradientRotation(-1.2),
|
||||
stops: const [0.4, 1],
|
||||
colors: [
|
||||
Color(0xFF251D37),
|
||||
Color(0xFF7547C0),
|
||||
isLM ? const Color(0xFF251D37) : const Color(0xFF7459AD),
|
||||
isLM ? const Color(0xFF7547C0) : const Color(0xFFDDC8FF),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -582,7 +583,9 @@ class _Heading extends StatelessWidget {
|
||||
fontSize: 24,
|
||||
color: isPrimary
|
||||
? AFThemeExtension.of(context).strongText
|
||||
: const Color(0xFF5C3699),
|
||||
: Theme.of(context).isLightMode
|
||||
? const Color(0xFF5C3699)
|
||||
: const Color(0xFFC49BEC),
|
||||
),
|
||||
if (description != null && description!.isNotEmpty) ...[
|
||||
const VSpace(4),
|
||||
|
||||
+20
-20
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/util/int64_extension.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart';
|
||||
@@ -12,7 +13,6 @@ import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_com
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/flowy_gradient_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -62,19 +62,17 @@ class SettingsPlanView extends StatelessWidget {
|
||||
|
||||
return ErrorWidget.withDetails(message: 'Something went wrong!');
|
||||
},
|
||||
ready: (state) {
|
||||
return SettingsBody(
|
||||
autoSeparate: false,
|
||||
title: LocaleKeys.settings_planPage_title.tr(),
|
||||
children: [
|
||||
_PlanUsageSummary(
|
||||
usage: state.workspaceUsage,
|
||||
subscription: state.subscription,
|
||||
),
|
||||
_CurrentPlanBox(subscription: state.subscription),
|
||||
],
|
||||
);
|
||||
},
|
||||
ready: (state) => SettingsBody(
|
||||
autoSeparate: false,
|
||||
title: LocaleKeys.settings_planPage_title.tr(),
|
||||
children: [
|
||||
_PlanUsageSummary(
|
||||
usage: state.workspaceUsage,
|
||||
subscription: state.subscription,
|
||||
),
|
||||
_CurrentPlanBox(subscription: state.subscription),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -266,13 +264,16 @@ class _ProConItem extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
width: 18,
|
||||
child: FlowySvg(
|
||||
isPro ? FlowySvgs.check_m : FlowySvgs.close_s,
|
||||
color: isPro ? null : const Color(0xFF900000),
|
||||
isPro ? FlowySvgs.check_m : FlowySvgs.close_error_s,
|
||||
size: const Size.square(18),
|
||||
color: isPro
|
||||
? AFThemeExtension.of(context).strongText
|
||||
: const Color(0xFF900000),
|
||||
),
|
||||
),
|
||||
const HSpace(4),
|
||||
@@ -415,7 +416,7 @@ class _ToggleMoreState extends State<_ToggleMore> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLM = Brightness.light == Theme.of(context).brightness;
|
||||
final isLM = Theme.of(context).isLightMode;
|
||||
final primaryColor =
|
||||
isLM ? const Color(0xFF653E8C) : const Color(0xFFE8E2EE);
|
||||
final secondaryColor =
|
||||
@@ -426,7 +427,6 @@ class _ToggleMoreState extends State<_ToggleMore> {
|
||||
Toggle(
|
||||
value: toggleValue,
|
||||
padding: EdgeInsets.zero,
|
||||
style: ToggleStyle.big,
|
||||
onChanged: (_) {
|
||||
setState(() => toggleValue = !toggleValue);
|
||||
|
||||
|
||||
-3
@@ -31,7 +31,6 @@ import 'package:appflowy/workspace/presentation/settings/shared/settings_radio_s
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -417,7 +416,6 @@ class EnableRTLItemsSwitcher extends StatelessWidget {
|
||||
),
|
||||
const HSpace(16),
|
||||
Toggle(
|
||||
style: ToggleStyle.big,
|
||||
value: context
|
||||
.watch<AppearanceSettingsCubit>()
|
||||
.state
|
||||
@@ -564,7 +562,6 @@ class _TimeFormatSwitcher extends StatelessWidget {
|
||||
),
|
||||
const HSpace(16),
|
||||
Toggle(
|
||||
style: ToggleStyle.big,
|
||||
value: context.watch<AppearanceSettingsCubit>().state.timeFormat ==
|
||||
UserTimeFormatPB.TwentyFourHour,
|
||||
onChanged: (value) =>
|
||||
|
||||
+33
-14
@@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' show PlatformExtension;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RestartButton extends StatelessWidget {
|
||||
const RestartButton({
|
||||
@@ -17,7 +18,7 @@ class RestartButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> children = [_buildRestartButton()];
|
||||
final List<Widget> children = [_buildRestartButton(context)];
|
||||
if (showRestartHint) {
|
||||
children.add(
|
||||
Padding(
|
||||
@@ -33,25 +34,43 @@ class RestartButton extends StatelessWidget {
|
||||
return Column(children: children);
|
||||
}
|
||||
|
||||
Widget _buildRestartButton() {
|
||||
Widget _buildRestartButton(BuildContext context) {
|
||||
if (PlatformExtension.isDesktopOrWeb) {
|
||||
return Row(
|
||||
children: [
|
||||
FlowyButton(
|
||||
isSelected: true,
|
||||
useIntrinsicWidth: true,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 30,
|
||||
vertical: 10,
|
||||
SizedBox(
|
||||
height: 42,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.settings_manageDataPage_dataStorage_actions_change
|
||||
.tr(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
radius: BorderRadius.circular(12),
|
||||
fillColor: Theme.of(context).colorScheme.primary,
|
||||
hoverColor: const Color(0xFF005483),
|
||||
fontHoverColor: Colors.white,
|
||||
onPressed: onClick,
|
||||
),
|
||||
text: FlowyText(
|
||||
LocaleKeys.settings_menu_restartApp.tr(),
|
||||
),
|
||||
onTap: onClick,
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
);
|
||||
// Row(
|
||||
// children: [
|
||||
// FlowyButton(
|
||||
// isSelected: true,
|
||||
// useIntrinsicWidth: true,
|
||||
// margin: const EdgeInsets.symmetric(
|
||||
// horizontal: 30,
|
||||
// vertical: 10,
|
||||
// ),
|
||||
// text: FlowyText(
|
||||
// LocaleKeys.settings_menu_restartApp.tr(),
|
||||
// ),
|
||||
// onTap: onClick,
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// ],
|
||||
// );
|
||||
} else {
|
||||
return MobileSignInOrLogoutButton(
|
||||
labelText: LocaleKeys.settings_menu_restartApp.tr(),
|
||||
|
||||
-2
@@ -10,7 +10,6 @@ import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
|
||||
@@ -317,7 +316,6 @@ class AppFlowyCloudEnableSync extends StatelessWidget {
|
||||
FlowyText.medium(LocaleKeys.settings_menu_enableSync.tr()),
|
||||
const Spacer(),
|
||||
Toggle(
|
||||
style: ToggleStyle.big,
|
||||
value: state.setting.enableSync,
|
||||
onChanged: (value) => context
|
||||
.read<AppFlowyCloudSettingBloc>()
|
||||
|
||||
+60
-69
@@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
@@ -6,16 +8,16 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/cloud_setting_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_local_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -41,6 +43,7 @@ class SettingCloud extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_menu_cloudSettings.tr(),
|
||||
autoSeparate: false,
|
||||
children: [
|
||||
if (Env.enableCustomCloud)
|
||||
Row(
|
||||
@@ -50,11 +53,13 @@ class SettingCloud extends StatelessWidget {
|
||||
LocaleKeys.settings_menu_cloudServerType.tr(),
|
||||
),
|
||||
),
|
||||
CloudTypeSwitcher(
|
||||
cloudType: state.cloudType,
|
||||
onSelected: (type) => context
|
||||
.read<CloudSettingBloc>()
|
||||
.add(CloudSettingEvent.updateCloudType(type)),
|
||||
Flexible(
|
||||
child: CloudTypeSwitcher(
|
||||
cloudType: state.cloudType,
|
||||
onSelected: (type) => context
|
||||
.read<CloudSettingBloc>()
|
||||
.add(CloudSettingEvent.updateCloudType(type)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -74,21 +79,13 @@ class SettingCloud extends StatelessWidget {
|
||||
Widget _viewFromCloudType(AuthenticatorType cloudType) {
|
||||
switch (cloudType) {
|
||||
case AuthenticatorType.local:
|
||||
return SettingLocalCloud(
|
||||
restartAppFlowy: restartAppFlowy,
|
||||
);
|
||||
return SettingLocalCloud(restartAppFlowy: restartAppFlowy);
|
||||
case AuthenticatorType.supabase:
|
||||
return SettingSupabaseCloudView(
|
||||
restartAppFlowy: restartAppFlowy,
|
||||
);
|
||||
return SettingSupabaseCloudView(restartAppFlowy: restartAppFlowy);
|
||||
case AuthenticatorType.appflowyCloud:
|
||||
return AppFlowyCloudViewSetting(
|
||||
restartAppFlowy: restartAppFlowy,
|
||||
);
|
||||
return AppFlowyCloudViewSetting(restartAppFlowy: restartAppFlowy);
|
||||
case AuthenticatorType.appflowyCloudSelfHost:
|
||||
return CustomAppFlowyCloudView(
|
||||
restartAppFlowy: restartAppFlowy,
|
||||
);
|
||||
return CustomAppFlowyCloudView(restartAppFlowy: restartAppFlowy);
|
||||
case AuthenticatorType.appflowyCloudDevelop:
|
||||
return AppFlowyCloudViewSetting(
|
||||
serverURL: "http://localhost",
|
||||
@@ -122,63 +119,57 @@ class CloudTypeSwitcher extends StatelessWidget {
|
||||
return isDevelopMode || element != AuthenticatorType.appflowyCloudDevelop;
|
||||
}).toList();
|
||||
return PlatformExtension.isDesktopOrWeb
|
||||
? AppFlowyPopover(
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
child: FlowyTextButton(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
|
||||
titleFromCloudType(cloudType),
|
||||
fontColor: AFThemeExtension.of(context).onBackground,
|
||||
fillColor: Colors.transparent,
|
||||
onPressed: () {},
|
||||
),
|
||||
popupBuilder: (BuildContext context) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
return CloudTypeItem(
|
||||
cloudType: values[index],
|
||||
currentCloudtype: cloudType,
|
||||
onSelected: onSelected,
|
||||
);
|
||||
},
|
||||
itemCount: values.length,
|
||||
);
|
||||
? SettingsDropdown(
|
||||
selectedOption: cloudType,
|
||||
onChanged: (type) {
|
||||
if (type != cloudType) {
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.settings_menu_changeServerTip.tr(),
|
||||
confirm: () async {
|
||||
onSelected(type);
|
||||
},
|
||||
hideCancelButton: true,
|
||||
).show(context);
|
||||
}
|
||||
},
|
||||
options: values
|
||||
.map(
|
||||
(type) => buildDropdownMenuEntry(
|
||||
context,
|
||||
value: type,
|
||||
label: titleFromCloudType(type),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
: FlowyButton(
|
||||
text: FlowyText(
|
||||
titleFromCloudType(cloudType),
|
||||
),
|
||||
text: FlowyText(titleFromCloudType(cloudType)),
|
||||
useIntrinsicWidth: true,
|
||||
rightIcon: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
title: LocaleKeys.settings_menu_cloudServerType.tr(),
|
||||
builder: (context) {
|
||||
return Column(
|
||||
children: values
|
||||
.mapIndexed(
|
||||
(i, e) => FlowyOptionTile.checkbox(
|
||||
text: titleFromCloudType(values[i]),
|
||||
isSelected: cloudType == values[i],
|
||||
onTap: () {
|
||||
onSelected(e);
|
||||
context.pop();
|
||||
},
|
||||
showBottomBorder: i == values.length - 1,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
onTap: () => showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
title: LocaleKeys.settings_menu_cloudServerType.tr(),
|
||||
builder: (context) => Column(
|
||||
children: values
|
||||
.mapIndexed(
|
||||
(i, e) => FlowyOptionTile.checkbox(
|
||||
text: titleFromCloudType(values[i]),
|
||||
isSelected: cloudType == values[i],
|
||||
onTap: () {
|
||||
onSelected(e);
|
||||
context.pop();
|
||||
},
|
||||
showBottomBorder: i == values.length - 1,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -4,6 +4,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -23,10 +24,8 @@ class SettingsNotificationsView extends StatelessWidget {
|
||||
hint: LocaleKeys.settings_notifications_enableNotifications_hint
|
||||
.tr(),
|
||||
trailing: [
|
||||
Switch(
|
||||
Toggle(
|
||||
value: state.isNotificationsEnabled,
|
||||
splashRadius: 0,
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
onChanged: (value) => context
|
||||
.read<NotificationSettingsCubit>()
|
||||
.toggleNotificationsEnabled(),
|
||||
@@ -40,10 +39,8 @@ class SettingsNotificationsView extends StatelessWidget {
|
||||
hint: LocaleKeys.settings_notifications_showNotificationsIcon_hint
|
||||
.tr(),
|
||||
trailing: [
|
||||
Switch(
|
||||
Toggle(
|
||||
value: state.isShowNotificationsIconEnabled,
|
||||
splashRadius: 0,
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
onChanged: (_) => context
|
||||
.read<NotificationSettingsCubit>()
|
||||
.toogleShowNotificationIconEnabled(),
|
||||
|
||||
+2
-3
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
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/material.dart';
|
||||
|
||||
class EndTimeButton extends StatelessWidget {
|
||||
const EndTimeButton({
|
||||
@@ -38,7 +38,6 @@ class EndTimeButton extends StatelessWidget {
|
||||
Toggle(
|
||||
value: isRange,
|
||||
onChanged: onChanged,
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,13 +1,40 @@
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
|
||||
class ToggleStyle {
|
||||
const ToggleStyle({
|
||||
required this.height,
|
||||
required this.width,
|
||||
required this.thumbRadius,
|
||||
});
|
||||
|
||||
const ToggleStyle.big()
|
||||
: height = 16,
|
||||
width = 27,
|
||||
thumbRadius = 14;
|
||||
|
||||
const ToggleStyle.small()
|
||||
: height = 10,
|
||||
width = 16,
|
||||
thumbRadius = 8;
|
||||
|
||||
const ToggleStyle.mobile()
|
||||
: height = 24,
|
||||
width = 42,
|
||||
thumbRadius = 18;
|
||||
|
||||
final double height;
|
||||
final double width;
|
||||
final double thumbRadius;
|
||||
}
|
||||
|
||||
class Toggle extends StatelessWidget {
|
||||
const Toggle({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
required this.style,
|
||||
this.style = const ToggleStyle.big(),
|
||||
this.thumbColor,
|
||||
this.activeBackgroundColor,
|
||||
this.inactiveBackgroundColor,
|
||||
@@ -26,7 +53,8 @@ class Toggle extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor = value
|
||||
? activeBackgroundColor ?? Theme.of(context).colorScheme.primary
|
||||
: activeBackgroundColor ?? AFThemeExtension.of(context).toggleOffFill;
|
||||
: inactiveBackgroundColor ??
|
||||
AFThemeExtension.of(context).toggleButtonBGColor;
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(value),
|
||||
child: Padding(
|
||||
@@ -49,7 +77,7 @@ class Toggle extends StatelessWidget {
|
||||
height: style.thumbRadius,
|
||||
width: style.thumbRadius,
|
||||
decoration: BoxDecoration(
|
||||
color: thumbColor ?? Theme.of(context).colorScheme.onPrimary,
|
||||
color: thumbColor ?? Colors.white,
|
||||
borderRadius: BorderRadius.circular(style.thumbRadius / 2),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
class ToggleStyle {
|
||||
ToggleStyle({
|
||||
required this.height,
|
||||
required this.width,
|
||||
required this.thumbRadius,
|
||||
});
|
||||
|
||||
final double height;
|
||||
final double width;
|
||||
final double thumbRadius;
|
||||
|
||||
static ToggleStyle get big =>
|
||||
ToggleStyle(height: 16, width: 27, thumbRadius: 14);
|
||||
|
||||
static ToggleStyle get small =>
|
||||
ToggleStyle(height: 10, width: 16, thumbRadius: 8);
|
||||
|
||||
static ToggleStyle get mobile =>
|
||||
ToggleStyle(height: 24, width: 42, thumbRadius: 18);
|
||||
}
|
||||
@@ -4,6 +4,16 @@ import FlutterMacOS
|
||||
@NSApplicationMain
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||
if !flag {
|
||||
for window in sender.windows {
|
||||
window.makeKeyAndOrderFront(self)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -126,7 +126,7 @@ class DefaultColorScheme extends FlowyColorScheme {
|
||||
questionBubbleBG: _darkShader3,
|
||||
progressBarBGColor: _darkShader3,
|
||||
toolbarColor: _darkInput,
|
||||
toggleButtonBGColor: _darkShader1,
|
||||
toggleButtonBGColor: const Color(0xFF828282),
|
||||
calendarWeekendBGColor: _darkShader1,
|
||||
gridRowCountColor: _darkShader5,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
import 'package:uuid/data.dart';
|
||||
import 'package:uuid/rng.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
const _uuid = Uuid();
|
||||
|
||||
String uuid() {
|
||||
return const Uuid().v4();
|
||||
return _uuid.v4();
|
||||
}
|
||||
|
||||
String fixedUuid(int seed, UuidType type) {
|
||||
return _uuid.v4(config: V4Options(null, MathRNG(seed: seed + type.index)));
|
||||
}
|
||||
|
||||
enum UuidType {
|
||||
// 0.6.0
|
||||
publicSpace,
|
||||
privateSpace,
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowyButton extends StatelessWidget {
|
||||
final Widget text;
|
||||
@@ -195,7 +196,11 @@ class FlowyTextButton extends StatelessWidget {
|
||||
children.add(heading!);
|
||||
children.add(const HSpace(8));
|
||||
}
|
||||
children.add(Text(text, overflow: overflow, textAlign: TextAlign.center));
|
||||
children.add(Text(
|
||||
text,
|
||||
overflow: overflow,
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
|
||||
Widget child = Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@@ -230,6 +235,7 @@ class FlowyTextButton extends StatelessWidget {
|
||||
fontSize: fontSize,
|
||||
decoration: decoration,
|
||||
fontFamily: fontFamily,
|
||||
height: 1.1,
|
||||
),
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.resolveWith(
|
||||
|
||||
@@ -13,6 +13,7 @@ class RoundedTextButton extends StatelessWidget {
|
||||
final Color? hoverColor;
|
||||
final Color? textColor;
|
||||
final double? fontSize;
|
||||
final FontWeight? fontWeight;
|
||||
final EdgeInsets padding;
|
||||
|
||||
const RoundedTextButton({
|
||||
@@ -27,6 +28,7 @@ class RoundedTextButton extends StatelessWidget {
|
||||
this.hoverColor,
|
||||
this.textColor,
|
||||
this.fontSize,
|
||||
this.fontWeight,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
});
|
||||
|
||||
@@ -42,6 +44,7 @@ class RoundedTextButton extends StatelessWidget {
|
||||
child: SizedBox.expand(
|
||||
child: FlowyTextButton(
|
||||
title ?? '',
|
||||
fontWeight: fontWeight,
|
||||
onPressed: onPressed,
|
||||
fontSize: fontSize,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -394,6 +394,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
custom_sliding_segmented_control:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: custom_sliding_segmented_control
|
||||
sha256: "53c3e931c3ae1f696085d1ec70ac8e934da836595a9b7d9b88fdd0fcbf2a5574"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2174,10 +2182,10 @@ packages:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
|
||||
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.3"
|
||||
version: "4.4.0"
|
||||
value_layout_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -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.0
|
||||
version: 0.6.1
|
||||
|
||||
environment:
|
||||
flutter: ">=3.22.0"
|
||||
@@ -147,7 +147,9 @@ dependencies:
|
||||
|
||||
# BitsDojo Window for Windows
|
||||
bitsdojo_window: ^0.1.6
|
||||
|
||||
flutter_highlight: ^0.7.0
|
||||
custom_sliding_segmented_control: ^1.8.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^3.0.1
|
||||
@@ -198,7 +200,7 @@ dependency_overrides:
|
||||
ref: e44458d
|
||||
path: sheet
|
||||
|
||||
uuid: ^4.1.0
|
||||
uuid: ^4.4.0
|
||||
|
||||
flutter_cache_manager:
|
||||
git:
|
||||
|
||||
+51
-17
@@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
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=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -772,7 +772,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -782,17 +782,16 @@ dependencies = [
|
||||
"brotli",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"client-api-entity",
|
||||
"client-websocket",
|
||||
"collab",
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
"collab-rt-protocol",
|
||||
"database-entity",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"getrandom 0.2.10",
|
||||
"gotrue",
|
||||
"gotrue-entity",
|
||||
"infra",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"prost",
|
||||
@@ -816,10 +815,22 @@ dependencies = [
|
||||
"yrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
"database-entity",
|
||||
"gotrue-entity",
|
||||
"shared-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -1059,7 +1070,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1084,7 +1095,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1441,7 +1452,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -1933,6 +1944,7 @@ dependencies = [
|
||||
"flowy-server-pub",
|
||||
"flowy-sqlite",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"flowy-user",
|
||||
"flowy-user-pub",
|
||||
"futures",
|
||||
@@ -2058,7 +2070,7 @@ dependencies = [
|
||||
"flowy-document-pub",
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"futures",
|
||||
"getrandom 0.2.10",
|
||||
"indexmap 2.1.0",
|
||||
@@ -2266,6 +2278,7 @@ dependencies = [
|
||||
"flowy-search-pub",
|
||||
"flowy-server-pub",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"flowy-user-pub",
|
||||
"futures",
|
||||
"futures-util",
|
||||
@@ -2324,14 +2337,16 @@ dependencies = [
|
||||
name = "flowy-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flowy-error",
|
||||
"flowy-sqlite",
|
||||
"flowy-storage-pub",
|
||||
"fxhash",
|
||||
"lib-infra",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2339,6 +2354,23 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-storage-pub"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"client-api-entity",
|
||||
"flowy-error",
|
||||
"lib-infra",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-user"
|
||||
version = "0.1.0"
|
||||
@@ -2862,7 +2894,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -2879,7 +2911,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3311,12 +3343,14 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -5801,7 +5835,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@@ -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 = "430e3e15c9a1dc6aba2a9599d17d946a61ac7cae" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "fa7565d9de2a5e65e3886c8230588d58140637c5" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
|
||||
+68
-20
@@ -122,7 +122,6 @@ dependencies = [
|
||||
"flowy-notification",
|
||||
"flowy-server",
|
||||
"flowy-server-pub",
|
||||
"flowy-storage",
|
||||
"flowy-user-pub",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
@@ -216,7 +215,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -236,7 +235,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -562,7 +561,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -572,17 +571,16 @@ dependencies = [
|
||||
"brotli",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"client-api-entity",
|
||||
"client-websocket",
|
||||
"collab",
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
"collab-rt-protocol",
|
||||
"database-entity",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"getrandom 0.2.12",
|
||||
"gotrue",
|
||||
"gotrue-entity",
|
||||
"infra",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"prost",
|
||||
@@ -606,10 +604,22 @@ dependencies = [
|
||||
"yrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
"database-entity",
|
||||
"gotrue-entity",
|
||||
"shared-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -787,7 +797,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -812,7 +822,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -981,7 +991,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -1026,7 +1036,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -1380,7 +1390,7 @@ dependencies = [
|
||||
"flowy-document-pub",
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"futures",
|
||||
"getrandom 0.2.12",
|
||||
"indexmap",
|
||||
@@ -1438,8 +1448,10 @@ dependencies = [
|
||||
"fancy-regex 0.11.0",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-sqlite",
|
||||
"lib-dispatch",
|
||||
"protobuf",
|
||||
"r2d2",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1550,6 +1562,7 @@ dependencies = [
|
||||
"flowy-search-pub",
|
||||
"flowy-server-pub",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"flowy-user-pub",
|
||||
"futures",
|
||||
"futures-util",
|
||||
@@ -1608,14 +1621,16 @@ dependencies = [
|
||||
name = "flowy-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flowy-error",
|
||||
"flowy-sqlite",
|
||||
"flowy-storage-pub",
|
||||
"fxhash",
|
||||
"lib-infra",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -1623,6 +1638,23 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-storage-pub"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"client-api-entity",
|
||||
"flowy-error",
|
||||
"lib-infra",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-user-pub"
|
||||
version = "0.1.0"
|
||||
@@ -1887,7 +1919,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -1904,7 +1936,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -2205,12 +2237,14 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -2932,7 +2966,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_macros 0.8.0",
|
||||
"phf_shared 0.8.0",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
@@ -2952,6 +2986,7 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_macros 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
@@ -3019,6 +3054,19 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
@@ -3903,7 +3951,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@@ -20,7 +20,6 @@ flowy-derive = { path = "../../rust-lib/build-tool/flowy-derive" }
|
||||
flowy-codegen = { path = "../../rust-lib/build-tool/flowy-codegen" }
|
||||
flowy-document = { path = "../../rust-lib/flowy-document" }
|
||||
flowy-folder = { path = "../../rust-lib/flowy-folder" }
|
||||
flowy-storage = { path = "../../rust-lib/flowy-storage" }
|
||||
lib-infra = { path = "../../rust-lib/lib-infra" }
|
||||
bytes = { version = "1.5" }
|
||||
protobuf = { version = "2.28.0" }
|
||||
@@ -55,7 +54,7 @@ yrs = "0.18.8"
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "430e3e15c9a1dc6aba2a9599d17d946a61ac7cae" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "fa7565d9de2a5e65e3886c8230588d58140637c5" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
||||
@@ -21,7 +21,6 @@ tokio-stream.workspace = true
|
||||
|
||||
af-user.workspace = true
|
||||
af-persistence.workspace = true
|
||||
flowy-storage = { workspace = true }
|
||||
flowy-notification = { workspace = true, features = ["web_ts"] }
|
||||
flowy-user-pub = { workspace = true }
|
||||
flowy-server = { workspace = true }
|
||||
|
||||
@@ -5,7 +5,7 @@ WORKDIR /app
|
||||
RUN apt-get update && \
|
||||
apt-get install -y nginx
|
||||
|
||||
RUN bun install cheerio pino axios pino-pretty
|
||||
RUN bun install cheerio pino pino-pretty
|
||||
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"id": "9eebea03-3ed5-4298-86b2-a7f77856d48b",
|
||||
"name": "workspace",
|
||||
"icon": "",
|
||||
"owner": {
|
||||
"id": 0,
|
||||
"name": "system"
|
||||
},
|
||||
"type": 0,
|
||||
"workspaceDatabaseId": "375874be-7a4f-4b7c-8b89-1dc9a39838f4"
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
[
|
||||
{
|
||||
"database_id": "037a985f-f369-4c4a-8011-620012850a68",
|
||||
"created_at": "1713429700",
|
||||
"views": [
|
||||
"48c52cf7-bf98-43fa-96ad-b31aade9b071"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "daea6aee-9365-4703-a8e2-a2fa6a07b214",
|
||||
"created_at": "1714449533",
|
||||
"views": [
|
||||
"b6347acb-3174-4f0e-98e9-dcce07e5dbf7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "4c658817-20db-4f56-b7f9-0637a22dfeb6",
|
||||
"created_at": "0",
|
||||
"views": [
|
||||
"7d2148fc-cace-4452-9c5c-96e52e6bf8b5",
|
||||
"e410747b-5f2f-45a0-b2f7-890ad3001355",
|
||||
"2143e95d-5dcb-4e0f-bb2c-50944e6e019f",
|
||||
"a5566e49-f156-4168-9b2d-17926c5da329",
|
||||
"135615fa-66f7-4451-9b54-d7e99445fca4",
|
||||
"b4e77203-5c8b-48df-bbc5-2e1143eb0e61",
|
||||
"a6af311f-cbc8-42c2-b801-7115619c3776"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "4c658817-20db-4f56-b7f9-0637a22dfeb6",
|
||||
"created_at": "0",
|
||||
"views": [
|
||||
"7d2148fc-cace-4452-9c5c-96e52e6bf8b5",
|
||||
"e97877f5-c365-4025-9e6a-e590c4b19dbb",
|
||||
"f0c59921-04ee-4971-995c-79b7fd8c00e2",
|
||||
"7eb697cd-6a55-40bb-96ac-0d4a3bc924b2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "ee63da2b-aa2a-4d0b-aab0-59008635363a",
|
||||
"created_at": "0",
|
||||
"views": [
|
||||
"2c1ee95a-1b09-4a1f-8d5e-501bc4861a9d",
|
||||
"91ea7c08-f6b3-4b81-aa1e-d3664686186f"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "e788f014-d0d3-4dfe-81ef-aa1ebb4d6366",
|
||||
"created_at": "0",
|
||||
"views": [
|
||||
"1b0e322d-4909-4c63-914a-d034fc363097",
|
||||
"350f425b-b671-4e2d-8182-5998a6e62924"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d",
|
||||
"created_at": "0",
|
||||
"views": [
|
||||
"0ce13415-6cce-4497-94c6-475ad96c249e",
|
||||
"e4c89421-12b2-4d02-863d-20949eec9271"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "ce267d12-3b61-4ebb-bb03-d65272f5f817",
|
||||
"created_at": "0",
|
||||
"views": [
|
||||
"ee3ae8ce-959a-4df3-8734-40b535ff88e3",
|
||||
"66a6f3bc-c78f-4f74-a09e-08d4717bf1fd",
|
||||
"2bf50c03-f41f-4363-b5b1-101216a6c5cc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"database_id": "87bc006e-c1eb-47fd-9ac6-e39b17956369",
|
||||
"created_at": "0",
|
||||
"views": [
|
||||
"7f233be4-1b4d-46b2-bcfc-f341b8d75267",
|
||||
"a734a068-e73d-4b4b-853c-4daffea389c0"
|
||||
]
|
||||
}
|
||||
]
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"data":{"state_vector":[6,248,208,217,159,7,16,226,250,246,177,3,17,211,142,141,147,5,8,196,148,203,38,26,230,150,209,139,14,193,6,167,238,246,72,86],"doc_state":[6,177,5,230,150,209,139,14,0,0,2,0,1,0,5,39,0,196,148,203,38,4,6,122,45,72,76,78,68,2,4,0,230,150,209,139,14,8,4,49,49,49,49,39,0,196,148,203,38,1,6,54,70,57,105,75,107,1,40,0,230,150,209,139,14,13,2,105,100,1,119,6,54,70,57,105,75,107,40,0,230,150,209,139,14,13,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,230,150,209,139,14,13,6,112,97,114,101,110,116,1,119,10,74,118,87,74,108,105,53,79,117,84,40,0,230,150,209,139,14,13,8,99,104,105,108,100,114,101,110,1,119,6,97,118,105,69,115,45,33,0,230,150,209,139,14,13,4,100,97,116,97,1,40,0,230,150,209,139,14,13,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,230,150,209,139,14,13,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,196,148,203,38,3,6,97,118,105,69,115,45,0,72,196,148,203,38,24,1,119,6,54,70,57,105,75,107,39,0,196,148,203,38,4,6,69,115,57,50,84,74,2,33,0,196,148,203,38,1,6,98,71,84,69,74,116,1,0,7,33,0,196,148,203,38,3,6,87,56,66,102,66,110,1,129,196,148,203,38,24,1,168,230,150,209,139,14,18,1,119,39,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,49,49,49,34,125,93,44,34,108,101,118,101,108,34,58,50,125,4,0,230,150,209,139,14,23,1,35,0,1,132,230,150,209,139,14,35,1,35,0,1,132,230,150,209,139,14,37,1,35,0,1,132,230,150,209,139,14,39,1,35,0,1,39,0,196,148,203,38,4,6,56,76,76,113,48,56,2,39,0,196,148,203,38,1,6,56,70,105,117,112,98,1,40,0,230,150,209,139,14,44,2,105,100,1,119,6,56,70,105,117,112,98,40,0,230,150,209,139,14,44,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,230,150,209,139,14,44,6,112,97,114,101,110,116,1,119,10,74,118,87,74,108,105,53,79,117,84,40,0,230,150,209,139,14,44,8,99,104,105,108,100,114,101,110,1,119,6,49,66,67,68,107,53,33,0,230,150,209,139,14,44,4,100,97,116,97,1,40,0,230,150,209,139,14,44,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,230,150,209,139,14,44,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,196,148,203,38,3,6,49,66,67,68,107,53,0,200,196,148,203,38,24,230,150,209,139,14,33,1,119,6,56,70,105,117,112,98,4,0,230,150,209,139,14,43,1,50,161,230,150,209,139,14,49,1,132,230,150,209,139,14,54,1,50,161,230,150,209,139,14,55,1,132,230,150,209,139,14,56,1,50,161,230,150,209,139,14,57,1,132,230,150,209,139,14,58,1,50,161,230,150,209,139,14,59,1,39,0,196,148,203,38,4,6,103,76,57,104,70,45,2,39,0,196,148,203,38,1,6,72,99,71,82,109,56,1,40,0,230,150,209,139,14,63,2,105,100,1,119,6,72,99,71,82,109,56,40,0,230,150,209,139,14,63,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,230,150,209,139,14,63,6,112,97,114,101,110,116,1,119,10,74,118,87,74,108,105,53,79,117,84,40,0,230,150,209,139,14,63,8,99,104,105,108,100,114,101,110,1,119,6,50,114,117,49,72,109,33,0,230,150,209,139,14,63,4,100,97,116,97,1,40,0,230,150,209,139,14,63,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,230,150,209,139,14,63,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,196,148,203,38,3,6,50,114,117,49,72,109,0,136,230,150,209,139,14,33,1,119,6,72,99,71,82,109,56,168,230,150,209,139,14,61,1,119,39,123,34,108,101,118,101,108,34,58,52,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,50,50,50,50,34,125,93,125,1,0,230,150,209,139,14,62,1,161,230,150,209,139,14,68,1,129,230,150,209,139,14,74,1,161,230,150,209,139,14,75,1,129,230,150,209,139,14,76,2,161,230,150,209,139,14,77,1,129,230,150,209,139,14,79,1,161,230,150,209,139,14,80,1,129,230,150,209,139,14,81,1,161,230,150,209,139,14,82,1,68,230,150,209,139,14,74,6,228,189,160,229,165,189,168,230,150,209,139,14,84,1,119,31,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,228,189,160,229,165,189,34,125,93,125,39,0,196,148,203,38,4,6,113,111,68,56,109,111,2,33,0,196,148,203,38,1,6,113,118,66,98,82,83,1,0,7,33,0,196,148,203,38,3,6,118,76,97,51,57,90,1,129,230,150,209,139,14,72,1,1,0,230,150,209,139,14,88,1,0,1,129,230,150,209,139,14,99,1,0,1,129,230,150,209,139,14,101,1,0,4,132,230,150,209,139,14,103,1,91,0,1,132,230,150,209,139,14,108,1,93,0,1,39,0,196,148,203,38,4,6,69,114,99,104,55,72,2,39,0,196,148,203,38,1,6,51,111,95,70,117,67,1,40,0,230,150,209,139,14,113,2,105,100,1,119,6,51,111,95,70,117,67,40,0,230,150,209,139,14,113,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,230,150,209,139,14,113,6,112,97,114,101,110,116,1,119,10,74,118,87,74,108,105,53,79,117,84,40,0,230,150,209,139,14,113,8,99,104,105,108,100,114,101,110,1,119,6,68,56,109,75,57,97,33,0,230,150,209,139,14,113,4,100,97,116,97,1,40,0,230,150,209,139,14,113,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,230,150,209,139,14,113,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,196,148,203,38,3,6,68,56,109,75,57,97,0,200,230,150,209,139,14,72,230,150,209,139,14,98,1,119,6,51,111,95,70,117,67,4,0,230,150,209,139,14,112,1,49,161,230,150,209,139,14,118,1,132,230,150,209,139,14,123,1,50,161,230,150,209,139,14,Line truncated
|
||||
@@ -1 +0,0 @@
|
||||
{"data":{"state_vector":[160,1,128,252,161,128,4,33,130,180,254,251,6,18,135,232,133,203,9,15,135,240,136,178,10,45,137,226,192,199,6,20,138,202,240,189,2,134,1,139,240,196,145,14,138,2,139,152,215,249,10,34,141,216,158,150,1,3,142,130,192,134,11,3,143,184,153,180,6,6,140,228,230,243,1,2,145,190,137,224,10,2,145,144,146,185,5,12,140,152,206,145,6,165,1,135,166,246,235,6,3,147,206,229,235,1,3,137,164,190,210,1,39,152,158,185,230,10,7,153,130,203,161,6,97,152,252,186,192,1,2,154,244,246,165,8,198,1,156,148,170,169,10,16,157,240,144,231,2,3,158,156,181,152,10,49,158,182,250,251,9,15,160,192,253,131,5,3,161,178,132,150,11,101,159,156,204,250,6,27,163,236,177,169,4,12,164,188,201,172,1,3,158,184,218,165,3,38,162,238,198,212,7,3,170,140,240,234,9,31,171,204,155,217,8,3,171,142,166,254,1,6,173,252,148,184,13,64,176,154,159,227,1,20,178,162,190,217,10,3,179,252,154,44,3,180,230,210,212,13,7,182,172,247,194,5,3,183,226,184,158,8,8,187,220,199,239,8,70,195,242,227,194,8,3,196,154,250,183,6,62,197,254,154,201,10,3,200,142,208,241,4,157,1,202,160,246,212,1,6,203,184,221,173,11,37,206,220,129,131,4,21,207,228,238,162,8,50,209,250,203,254,15,3,210,228,153,221,12,3,211,202,217,232,12,7,211,166,203,229,4,195,1,214,168,149,214,3,15,219,220,239,171,8,10,225,248,138,176,2,49,226,212,179,248,2,15,229,154,128,197,12,24,234,182,182,157,9,8,235,178,165,206,5,72,234,156,130,211,12,12,241,130,161,205,7,12,244,226,228,149,2,30,245,220,194,52,39,247,200,243,247,14,100,248,136,168,181,1,3,248,210,237,129,13,2,250,198,166,187,7,2,248,196,187,185,10,31,252,218,241,167,14,3,255,140,248,220,6,3,128,211,179,216,2,10,133,159,138,205,12,131,1,135,167,156,250,14,16,135,193,208,135,7,16,141,245,194,142,11,7,141,205,220,149,4,3,143,131,148,152,6,15,141,171,170,217,4,2,145,159,164,217,14,3,149,129,169,191,12,16,149,249,242,175,4,31,149,189,189,215,8,3,149,161,132,184,14,221,2,154,243,157,196,14,12,154,193,208,134,10,9,155,165,205,152,11,3,154,235,215,240,4,13,155,159,180,195,15,6,157,207,243,216,6,2,161,239,241,154,13,106,162,159,252,196,11,3,164,155,139,169,7,35,165,139,157,171,15,103,164,203,250,235,13,7,167,131,133,162,9,11,166,201,221,141,13,72,169,197,188,221,3,3,170,255,211,105,38,166,203,155,46,6,173,187,245,170,14,46,174,151,139,93,179,2,175,225,172,150,8,11,175,147,217,214,1,33,177,161,136,243,11,10,178,203,205,182,4,1,175,205,156,228,6,10,180,205,189,133,13,20,181,175,219,209,12,10,182,143,233,195,4,111,177,219,160,167,7,4,184,201,188,172,10,3,184,231,170,67,6,186,197,166,179,15,7,187,173,214,176,15,3,188,171,136,250,8,21,188,237,223,145,6,26,190,139,191,155,1,2,191,157,147,233,9,32,193,249,142,142,4,17,198,189,216,175,6,23,200,205,214,172,10,30,201,129,238,197,4,84,201,191,159,147,14,17,200,203,236,184,2,35,200,159,185,206,9,8,205,149,231,236,11,68,213,161,242,209,13,35,214,139,213,136,8,66,213,255,156,145,1,2,219,227,140,137,6,34,221,147,167,147,15,177,2,222,205,223,235,7,31,223,209,193,147,11,81,227,209,197,253,2,14,229,153,197,202,7,241,6,231,139,244,188,8,13,232,207,157,148,2,2,233,165,139,246,14,43,233,247,183,159,1,4,235,225,184,133,10,3,234,153,236,158,4,73,234,187,164,181,1,24,231,189,134,196,8,77,239,199,189,146,3,24,240,149,229,225,6,15,241,155,213,233,1,52,240,253,240,229,1,79,243,239,182,181,13,30,236,229,225,232,8,116,246,185,174,192,6,90,248,153,216,10,3,251,189,220,155,14,3,252,163,130,200,6,30,253,205,145,137,11,10,252,171,209,175,15,4,255,255,147,249,10,3],"doc_state":[160,1,3,209,250,203,254,15,0,161,226,212,179,248,2,9,1,161,226,212,179,248,2,10,1,136,174,151,139,93,249,1,1,118,2,2,105,100,119,36,50,54,100,53,99,56,99,49,45,49,99,54,54,45,52,53,57,99,45,98,99,54,99,45,102,52,100,97,49,97,54,54,51,51,52,56,9,116,105,109,101,115,116,97,109,112,122,0,0,0,0,102,83,246,78,2,155,159,180,195,15,0,161,198,189,216,175,6,22,1,161,253,205,145,137,11,9,5,7,186,197,166,179,15,0,161,161,178,132,150,11,98,1,161,161,178,132,150,11,99,1,129,161,178,132,150,11,100,1,161,161,178,132,150,11,97,1,161,161,178,132,150,11,94,1,161,161,178,132,150,11,95,1,129,186,197,166,179,15,2,1,3,187,173,214,176,15,0,168,244,226,228,149,2,27,1,122,4,56,115,160,190,64,16,0,168,244,226,228,149,2,28,1,122,0,0,0,0,102,32,153,171,136,248,153,216,10,2,1,118,2,2,105,100,119,36,48,53,51,51,50,98,97,52,45,97,54,57,48,45,52,50,57,51,45,57,56,54,54,45,56,52,100,97,99,55,102,101,50,97,101,97,9,116,105,109,101,115,116,97,109,112,122,0,0,0,0,102,32,153,171,1,252,171,209,175,15,0,161,128,211,179,216,2,9,4,103,165,139,157,171,15,0,8,0,201,129,238,197,4,52,1,118,1,2,105,100,119,36,97,53,53,54,54,101,52,57,45,102,49,53,54,45,52,49,54,56,45,57,98,50,100,45,49,55,57,50,54,99,53,100,97,51,50,57,168,201,129,238,197,4,51,1,122,0,0,0,0,102,74,244,14,161,170,140,240,234,9,22,1,39,0,203,184,221,173,11,1,36,97,53,53,54,54,101,52,57,45,102,49,53,54,45,52,49,54,56,45,57,98,50,100,45,49,55,57,50,54,99,53,100,97,51,50,57,1,40,0,165,139,157,171,15,3,2,105,100,1,119,36,97,53,53,54,54,101,52,57,45,102,49,53,54,45,52,49,54,56,45,57,98,50,100,45,49,55,57,50,54,99,53,100,97,51,50,57,40,0,Line truncated
|
||||
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTI4Mjk2MjAsImlhdCI6MTcxMjgyNjAyMCwic3ViIjoiY2JmZjA2MGEtMTk2ZC00MTVhLWFhODAtNzU5YzAxODg2NDY2IiwiZW1haWwiOiJsdUBhcHBmbG93eS5pbyIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZ29vZ2xlIiwicHJvdmlkZXJzIjpbImdvb2dsZSJdfSwidXNlcl9tZXRhZGF0YSI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jTEhabVZBczRTb0ZlVFFuWG5CU2JiNTBBVXF0YktHNWx5MGllVHZCSklYZ1o3UmdRPXM5Ni1jIiwiY3VzdG9tX2NsYWltcyI6eyJoZCI6ImFwcGZsb3d5LmlvIn0sImVtYWlsIjoibHVAYXBwZmxvd3kuaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZnVsbF9uYW1lIjoiTHUgSGUiLCJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYW1lIjoiTHUgSGUiLCJwaG9uZV92ZXJpZmllZCI6ZmFsc2UsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NMSFptVkFzNFNvRmVUUW5YbkJTYmI1MEFVcXRiS0c1bHkwaWVUdkJKSVhnWjdSZ1E9czk2LWMiLCJwcm92aWRlcl9pZCI6IjEwMTE2OTI1MDgyOTU1NDAyODM4MSIsInN1YiI6IjEwMTE2OTI1MDgyOTU1NDAyODM4MSJ9LCJyb2xlIjoiIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3MTI4MjYwMjB9XSwic2Vzc2lvbl9pZCI6ImJmMzE5OTRlLTQwMTgtNDhjMS05Yzc0LWVmYzkyMGNjOWQ0NSJ9.QeTrRhsnBjBL1GUS3TIWOgU1SPM6RcaWwxZdMVfcFBU",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600,
|
||||
"expires_at": 4869016461,
|
||||
"refresh_token": "71vp1jJnSAVluZKaXkhG1A",
|
||||
"user": {
|
||||
"id": "cbff060a-196d-415a-aa80-759c01886466",
|
||||
"aud": "",
|
||||
"role": "",
|
||||
"email": "lu@appflowy.io",
|
||||
"email_confirmed_at": "2024-03-13T10:49:53.165361Z",
|
||||
"phone": "",
|
||||
"confirmed_at": "2024-03-13T10:49:53.165361Z",
|
||||
"last_sign_in_at": "2024-04-11T09:00:20.547468985Z",
|
||||
"app_metadata": {
|
||||
"provider": "google",
|
||||
"providers": [
|
||||
"google"
|
||||
]
|
||||
},
|
||||
"user_metadata": {
|
||||
"avatar_url": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
||||
"custom_claims": {
|
||||
"hd": "appflowy.io"
|
||||
},
|
||||
"email": "lu@appflowy.io",
|
||||
"email_verified": true,
|
||||
"full_name": "Lu He",
|
||||
"iss": "https://accounts.google.com",
|
||||
"name": "Lu He",
|
||||
"phone_verified": false,
|
||||
"picture": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
||||
"provider_id": "101169250829554028381",
|
||||
"sub": "101169250829554028381"
|
||||
},
|
||||
"identities": [
|
||||
{
|
||||
"identity_id": "e4cf8b69-7f80-42e9-aed2-e25132ad0178",
|
||||
"id": "101169250829554028381",
|
||||
"user_id": "cbff060a-196d-415a-aa80-759c01886466",
|
||||
"identity_data": {
|
||||
"avatar_url": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
||||
"custom_claims": {
|
||||
"hd": "appflowy.io"
|
||||
},
|
||||
"email": "lu@appflowy.io",
|
||||
"email_verified": true,
|
||||
"full_name": "Lu He",
|
||||
"iss": "https://accounts.google.com",
|
||||
"name": "Lu He",
|
||||
"phone_verified": false,
|
||||
"picture": "https://lh3.googleusercontent.com/a/ACg8ocLHZmVAs4SoFeTQnXnBSbb50AUqtbKG5ly0ieTvBJIXgZ7RgQ=s96-c",
|
||||
"provider_id": "101169250829554028381",
|
||||
"sub": "101169250829554028381"
|
||||
},
|
||||
"provider": "google",
|
||||
"last_sign_in_at": "2024-03-13T07:22:43.110504Z",
|
||||
"created_at": "2024-03-13T07:22:43.110543Z",
|
||||
"updated_at": "2024-04-04T06:15:14.03093Z"
|
||||
}
|
||||
],
|
||||
"created_at": "2024-03-13T07:22:43.102586Z",
|
||||
"updated_at": "2024-04-11T09:00:20.551485Z"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"uid": 304120109071339520,
|
||||
"uuid": "cbff060a-196d-415a-aa80-759c01886466",
|
||||
"email": "lu@appflowy.io",
|
||||
"password": "",
|
||||
"name": "Kilu",
|
||||
"metadata": {
|
||||
"icon_url": "🇽🇰"
|
||||
},
|
||||
"encryption_sign": null,
|
||||
"latest_workspace_id": "fcb503f9-9287-4de4-8de0-ea191e680968",
|
||||
"updated_at": 1710421586
|
||||
},
|
||||
"code": 0,
|
||||
"message": "Operation completed successfully."
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"data":{"user_profile":{"uid":304120109071339520,"uuid":"cbff060a-196d-415a-aa80-759c01886466","email":"lu@appflowy.io","password":"","name":"Kilu","metadata":{"icon_url":"🇽🇰"},"encryption_sign":null,"latest_workspace_id":"9eebea03-3ed5-4298-86b2-a7f77856d48b","updated_at":1715847453},"visiting_workspace":{"workspace_id":"9eebea03-3ed5-4298-86b2-a7f77856d48b","database_storage_id":"375874be-7a4f-4b7c-8b89-1dc9a39838f4","owner_uid":304120109071339520,"owner_name":"Kilu","workspace_type":0,"workspace_name":"Kilu Works","created_at":"2024-03-13T07:23:10.275174Z","icon":"😆"},"workspaces":[{"workspace_id":"81570fa8-8be9-4b2d-9f1c-1ef4f34079a8","database_storage_id":"6c1f1a2c-e8d5-4bc2-917f-495bce862abb","owner_uid":311828434584080384,"owner_name":"Zack Zi Xiang Fu","workspace_type":0,"workspace_name":"My Workspace","created_at":"2024-04-03T13:53:18.295918Z","icon":""},{"workspace_id":"fcb503f9-9287-4de4-8de0-ea191e680968","database_storage_id":"ae1b82a5-2b93-45c7-901a-f9357c544534","owner_uid":276169796100296704,"owner_name":"Annie Anqi Wang","workspace_type":0,"workspace_name":"AppFlowy Test","created_at":"2023-12-27T04:18:36.372013Z","icon":""},{"workspace_id":"9eebea03-3ed5-4298-86b2-a7f77856d48b","database_storage_id":"375874be-7a4f-4b7c-8b89-1dc9a39838f4","owner_uid":304120109071339520,"owner_name":"Kilu","workspace_type":0,"workspace_name":"Kilu Works","created_at":"2024-03-13T07:23:10.275174Z","icon":"😆"}]},"code":0,"message":"Operation completed successfully."}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"is_new": false
|
||||
}
|
||||
}
|
||||
@@ -25,96 +25,9 @@
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
import { YDoc } from '@/application/collab.type';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { JSDatabaseService } from '@/application/services/js-services/database.service';
|
||||
import { JSDocumentService } from '@/application/services/js-services/document.service';
|
||||
import { applyYDoc } from '@/application/ydoc/apply';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
Cypress.Commands.add('mockAPI', () => {
|
||||
cy.fixture('sign_in_success').then((json) => {
|
||||
cy.intercept('GET', `/api/user/verify/${json.access_token}`, {
|
||||
fixture: 'verify_token',
|
||||
}).as('verifyToken');
|
||||
cy.intercept('POST', '/gotrue/token?grant_type=password', json).as('loginSuccess');
|
||||
cy.intercept('POST', '/gotrue/token?grant_type=refresh_token', json).as('refreshToken');
|
||||
});
|
||||
cy.intercept('GET', '/api/user/profile', { fixture: 'user' }).as('getUserProfile');
|
||||
cy.intercept('GET', '/api/user/workspace', { fixture: 'user_workspace' }).as('getUserWorkspace');
|
||||
// Mock the API
|
||||
});
|
||||
|
||||
// Example use:
|
||||
// beforeEach(() => {
|
||||
// cy.mockAPI();
|
||||
// });
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
Cypress.Commands.add('mockCurrentWorkspace', () => {
|
||||
cy.fixture('current_workspace').then((workspace) => {
|
||||
cy.stub(JSDatabaseService.prototype, 'currentWorkspace').resolves(workspace);
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
Cypress.Commands.add('mockGetWorkspaceDatabases', () => {
|
||||
cy.fixture('database/databases').then((databases) => {
|
||||
cy.stub(JSDatabaseService.prototype, 'getWorkspaceDatabases').resolves(databases);
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
Cypress.Commands.add('mockDatabase', () => {
|
||||
cy.mockCurrentWorkspace();
|
||||
cy.mockGetWorkspaceDatabases();
|
||||
|
||||
const ids = [
|
||||
'4c658817-20db-4f56-b7f9-0637a22dfeb6',
|
||||
'ce267d12-3b61-4ebb-bb03-d65272f5f817',
|
||||
'ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d',
|
||||
];
|
||||
|
||||
const mockOpenDatabase = cy.stub(JSDatabaseService.prototype, 'openDatabase');
|
||||
|
||||
ids.forEach((id) => {
|
||||
cy.fixture(`database/${id}`).then((database) => {
|
||||
cy.fixture(`database/rows/${id}`).then((rows) => {
|
||||
const doc = new Y.Doc();
|
||||
const rootRowsDoc = new Y.Doc();
|
||||
const rowsFolder: Y.Map<YDoc> = rootRowsDoc.getMap();
|
||||
const databaseState = new Uint8Array(database.data.doc_state);
|
||||
|
||||
applyYDoc(doc, databaseState);
|
||||
|
||||
Object.keys(rows).forEach((key) => {
|
||||
const data = rows[key];
|
||||
const rowDoc = new Y.Doc();
|
||||
|
||||
applyYDoc(rowDoc, new Uint8Array(data));
|
||||
rowsFolder.set(key, rowDoc);
|
||||
});
|
||||
mockOpenDatabase.withArgs(id).resolves({
|
||||
databaseDoc: doc,
|
||||
rows: rowsFolder,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
Cypress.Commands.add('mockDocument', (id: string) => {
|
||||
cy.fixture(`document/${id}`).then((subDocument) => {
|
||||
const doc = new Y.Doc();
|
||||
const state = new Uint8Array(subDocument.data.doc_state);
|
||||
|
||||
applyYDoc(doc, state);
|
||||
|
||||
cy.stub(JSDocumentService.prototype, 'openDocument').withArgs(id).resolves(doc);
|
||||
});
|
||||
});
|
||||
export {};
|
||||
@@ -24,7 +24,7 @@
|
||||
"coverage": "pnpm run test:unit && pnpm run test:components"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appflowyinc/client-api-wasm": "0.0.3",
|
||||
"@appflowyinc/client-api-wasm": "0.0.4",
|
||||
"@atlaskit/primitives": "^5.5.3",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
@@ -43,6 +43,8 @@
|
||||
"colorthief": "^2.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"decimal.js": "^10.4.3",
|
||||
"dexie": "^4.0.7",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"events": "^3.3.0",
|
||||
@@ -56,6 +58,7 @@
|
||||
"katex": "^0.16.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^4.0.0",
|
||||
"notistack": "^3.0.1",
|
||||
"numeral": "^2.0.6",
|
||||
"prismjs": "^1.29.0",
|
||||
"protoc-gen-ts": "0.8.7",
|
||||
@@ -136,6 +139,7 @@
|
||||
"autoprefixer": "^10.4.13",
|
||||
"babel-jest": "^29.6.2",
|
||||
"chalk": "^4.1.2",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.7.2",
|
||||
"eslint": "^8.57.0",
|
||||
@@ -145,6 +149,8 @@
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"jest-environment-jsdom": "^29.6.2",
|
||||
"nyc": "^15.1.0",
|
||||
"pino": "^9.2.0",
|
||||
"pino-pretty": "^11.2.1",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "2.8.4",
|
||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||
|
||||
Generated
+256
-8
@@ -1,9 +1,13 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@appflowyinc/client-api-wasm':
|
||||
specifier: 0.0.3
|
||||
version: 0.0.3
|
||||
specifier: 0.0.4
|
||||
version: 0.0.4
|
||||
'@atlaskit/primitives':
|
||||
specifier: ^5.5.3
|
||||
version: 5.7.0(@types/react@18.2.66)(react@18.2.0)
|
||||
@@ -58,6 +62,12 @@ dependencies:
|
||||
decimal.js:
|
||||
specifier: ^10.4.3
|
||||
version: 10.4.3
|
||||
dexie:
|
||||
specifier: ^4.0.7
|
||||
version: 4.0.7
|
||||
dexie-react-hooks:
|
||||
specifier: ^1.1.7
|
||||
version: 1.1.7(@types/react@18.2.66)(dexie@4.0.7)(react@18.2.0)
|
||||
emoji-mart:
|
||||
specifier: ^5.5.2
|
||||
version: 5.6.0
|
||||
@@ -97,6 +107,9 @@ dependencies:
|
||||
nanoid:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.2
|
||||
notistack:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0)
|
||||
numeral:
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6
|
||||
@@ -333,6 +346,9 @@ devDependencies:
|
||||
chalk:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
cheerio:
|
||||
specifier: 1.0.0-rc.12
|
||||
version: 1.0.0-rc.12
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
@@ -360,6 +376,12 @@ devDependencies:
|
||||
nyc:
|
||||
specifier: ^15.1.0
|
||||
version: 15.1.0
|
||||
pino:
|
||||
specifier: ^9.2.0
|
||||
version: 9.2.0
|
||||
pino-pretty:
|
||||
specifier: ^11.2.1
|
||||
version: 11.2.1
|
||||
postcss:
|
||||
specifier: ^8.4.21
|
||||
version: 8.4.21
|
||||
@@ -429,8 +451,8 @@ packages:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
/@appflowyinc/client-api-wasm@0.0.3:
|
||||
resolution: {integrity: sha512-ARjLhiDZ8MiZ9egWDbAX9VAdXXS30av+InCPLrS/iqCMYrhuuU9rxS9jQeNEB7jucFrj158gBRusimFN7P/lyw==}
|
||||
/@appflowyinc/client-api-wasm@0.0.4:
|
||||
resolution: {integrity: sha512-R8nm811vmh4oJU7LDFV7TJP89Y+WZKYrwpR1xrfL9m+PtyA/c21HvNJrvT+NssHfuz+IKj4L1cH2uXQcggnybA==}
|
||||
dev: false
|
||||
|
||||
/@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0):
|
||||
@@ -4580,6 +4602,13 @@ packages:
|
||||
deprecated: Use your platform's native atob() and btoa() methods instead
|
||||
dev: true
|
||||
|
||||
/abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
dependencies:
|
||||
event-target-shim: 5.0.1
|
||||
dev: true
|
||||
|
||||
/acorn-globals@7.0.1:
|
||||
resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
|
||||
dependencies:
|
||||
@@ -4886,6 +4915,11 @@ packages:
|
||||
engines: {node: '>= 4.0.0'}
|
||||
dev: true
|
||||
|
||||
/atomic-sleep@1.0.0:
|
||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dev: true
|
||||
|
||||
/autoprefixer@10.4.13(postcss@8.4.21):
|
||||
resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -5180,6 +5214,13 @@ packages:
|
||||
ieee754: 1.2.1
|
||||
dev: true
|
||||
|
||||
/buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
dev: true
|
||||
|
||||
/cachedir@2.4.0:
|
||||
resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -5289,6 +5330,30 @@ packages:
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dev: true
|
||||
|
||||
/cheerio-select@2.1.0:
|
||||
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
css-select: 5.1.0
|
||||
css-what: 6.1.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
dev: true
|
||||
|
||||
/cheerio@1.0.0-rc.12:
|
||||
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
cheerio-select: 2.1.0
|
||||
dom-serializer: 2.0.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
htmlparser2: 8.0.2
|
||||
parse5: 7.1.2
|
||||
parse5-htmlparser2-tree-adapter: 7.0.0
|
||||
dev: true
|
||||
|
||||
/chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
@@ -5739,6 +5804,10 @@ packages:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.1
|
||||
|
||||
/dateformat@4.6.3:
|
||||
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
|
||||
dev: true
|
||||
|
||||
/dayjs@1.11.10:
|
||||
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
|
||||
dev: true
|
||||
@@ -5860,6 +5929,22 @@ packages:
|
||||
minimist: 1.2.8
|
||||
dev: true
|
||||
|
||||
/dexie-react-hooks@1.1.7(@types/react@18.2.66)(dexie@4.0.7)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16'
|
||||
dexie: ^3.2 || ^4.0.1-alpha
|
||||
react: '>=16'
|
||||
dependencies:
|
||||
'@types/react': 18.2.66
|
||||
dexie: 4.0.7
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/dexie@4.0.7:
|
||||
resolution: {integrity: sha512-M+Lo6rk4pekIfrc2T0o2tvVJwL6EAAM/B78DNfb8aaxFVoI1f8/rz5KTxuAnApkwqTSuxx7T5t0RKH7qprapGg==}
|
||||
dev: false
|
||||
|
||||
/didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
dev: true
|
||||
@@ -6372,6 +6457,11 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/event-target-shim@5.0.1:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/eventemitter2@6.4.7:
|
||||
resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==}
|
||||
dev: true
|
||||
@@ -6455,6 +6545,10 @@ packages:
|
||||
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
|
||||
engines: {'0': node >=0.6.0}
|
||||
|
||||
/fast-copy@3.0.2:
|
||||
resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
|
||||
dev: true
|
||||
|
||||
/fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
@@ -6492,6 +6586,15 @@ packages:
|
||||
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||
dev: true
|
||||
|
||||
/fast-redact@3.5.0:
|
||||
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/fast-safe-stringify@2.1.1:
|
||||
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
||||
dev: true
|
||||
|
||||
/fastq@1.17.1:
|
||||
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
|
||||
dependencies:
|
||||
@@ -6961,6 +7064,10 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/help-me@5.0.0:
|
||||
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
|
||||
dev: true
|
||||
|
||||
/hoist-non-react-statics@3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
dependencies:
|
||||
@@ -6983,6 +7090,15 @@ packages:
|
||||
void-elements: 3.1.0
|
||||
dev: false
|
||||
|
||||
/htmlparser2@8.0.2:
|
||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
entities: 4.5.0
|
||||
dev: true
|
||||
|
||||
/http-proxy-agent@5.0.0:
|
||||
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -7881,6 +7997,11 @@ packages:
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
/joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/jpeg-js@0.4.4:
|
||||
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
|
||||
dev: false
|
||||
@@ -8440,6 +8561,21 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/notistack@3.0.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==}
|
||||
engines: {node: '>=12.0.0', npm: '>=6.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
clsx: 1.2.1
|
||||
goober: 2.1.14(csstype@3.1.3)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- csstype
|
||||
dev: false
|
||||
|
||||
/npm-run-path@4.0.1:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -8576,6 +8712,11 @@ packages:
|
||||
resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==}
|
||||
dev: false
|
||||
|
||||
/on-exit-leak-free@2.1.2:
|
||||
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dev: true
|
||||
|
||||
/once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
@@ -8721,6 +8862,13 @@ packages:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
/parse5-htmlparser2-tree-adapter@7.0.0:
|
||||
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
parse5: 7.1.2
|
||||
dev: true
|
||||
|
||||
/parse5@7.1.2:
|
||||
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
||||
dependencies:
|
||||
@@ -8810,6 +8958,54 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/pino-abstract-transport@1.2.0:
|
||||
resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
|
||||
dependencies:
|
||||
readable-stream: 4.5.2
|
||||
split2: 4.2.0
|
||||
dev: true
|
||||
|
||||
/pino-pretty@11.2.1:
|
||||
resolution: {integrity: sha512-O05NuD9tkRasFRWVaF/uHLOvoRDFD7tb5VMertr78rbsYFjYp48Vg3477EshVAF5eZaEw+OpDl/tu+B0R5o+7g==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
colorette: 2.0.20
|
||||
dateformat: 4.6.3
|
||||
fast-copy: 3.0.2
|
||||
fast-safe-stringify: 2.1.1
|
||||
help-me: 5.0.0
|
||||
joycon: 3.1.1
|
||||
minimist: 1.2.8
|
||||
on-exit-leak-free: 2.1.2
|
||||
pino-abstract-transport: 1.2.0
|
||||
pump: 3.0.0
|
||||
readable-stream: 4.5.2
|
||||
secure-json-parse: 2.7.0
|
||||
sonic-boom: 4.0.1
|
||||
strip-json-comments: 3.1.1
|
||||
dev: true
|
||||
|
||||
/pino-std-serializers@7.0.0:
|
||||
resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
|
||||
dev: true
|
||||
|
||||
/pino@9.2.0:
|
||||
resolution: {integrity: sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
fast-redact: 3.5.0
|
||||
on-exit-leak-free: 2.1.2
|
||||
pino-abstract-transport: 1.2.0
|
||||
pino-std-serializers: 7.0.0
|
||||
process-warning: 3.0.0
|
||||
quick-format-unescaped: 4.0.4
|
||||
real-require: 0.2.0
|
||||
safe-stable-stringify: 2.4.3
|
||||
sonic-boom: 4.0.1
|
||||
thread-stream: 3.1.0
|
||||
dev: true
|
||||
|
||||
/pirates@4.0.6:
|
||||
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -9027,6 +9223,10 @@ packages:
|
||||
fromentries: 1.3.2
|
||||
dev: true
|
||||
|
||||
/process-warning@3.0.0:
|
||||
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
|
||||
dev: true
|
||||
|
||||
/process@0.11.10:
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
@@ -9115,6 +9315,10 @@ packages:
|
||||
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
|
||||
dev: true
|
||||
|
||||
/quick-format-unescaped@4.0.4:
|
||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||
dev: true
|
||||
|
||||
/quick-lru@5.1.1:
|
||||
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -9635,12 +9839,28 @@ packages:
|
||||
util-deprecate: 1.0.2
|
||||
dev: true
|
||||
|
||||
/readable-stream@4.5.2:
|
||||
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
abort-controller: 3.0.0
|
||||
buffer: 6.0.3
|
||||
events: 3.3.0
|
||||
process: 0.11.10
|
||||
string_decoder: 1.3.0
|
||||
dev: true
|
||||
|
||||
/readdirp@3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
/real-require@0.2.0:
|
||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
dev: true
|
||||
|
||||
/redux-thunk@3.1.0(redux@5.0.1):
|
||||
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
|
||||
peerDependencies:
|
||||
@@ -9924,6 +10144,11 @@ packages:
|
||||
is-regex: 1.1.4
|
||||
dev: true
|
||||
|
||||
/safe-stable-stringify@2.4.3:
|
||||
resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
@@ -9973,6 +10198,10 @@ packages:
|
||||
compute-scroll-into-view: 3.1.0
|
||||
dev: false
|
||||
|
||||
/secure-json-parse@2.7.0:
|
||||
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||
dev: true
|
||||
|
||||
/semver@5.7.2:
|
||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||
hasBin: true
|
||||
@@ -10151,6 +10380,12 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/sonic-boom@4.0.1:
|
||||
resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==}
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
dev: true
|
||||
|
||||
/source-map-js@1.2.0:
|
||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -10194,6 +10429,11 @@ packages:
|
||||
which: 2.0.2
|
||||
dev: true
|
||||
|
||||
/split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
dev: true
|
||||
|
||||
/sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
@@ -10306,6 +10546,12 @@ packages:
|
||||
safe-buffer: 5.1.2
|
||||
dev: true
|
||||
|
||||
/string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -10499,6 +10745,12 @@ packages:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
dev: true
|
||||
|
||||
/thread-stream@3.1.0:
|
||||
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||
dependencies:
|
||||
real-require: 0.2.0
|
||||
dev: true
|
||||
|
||||
/throttleit@1.0.1:
|
||||
resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==}
|
||||
dev: true
|
||||
@@ -11414,7 +11666,3 @@ packages:
|
||||
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
@@ -2,8 +2,7 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const pino = require('pino');
|
||||
const cheerio = require('cheerio');
|
||||
const axios = require('axios');
|
||||
|
||||
const { fetch } = require('bun');
|
||||
const distDir = path.join(__dirname, 'dist');
|
||||
const indexPath = path.join(distDir, 'index.html');
|
||||
|
||||
@@ -38,38 +37,74 @@ const logRequestTimer = (req) => {
|
||||
};
|
||||
|
||||
const fetchMetaData = async (url) => {
|
||||
logger.info(`Fetching meta data from ${url}`);
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
const response = await fetch(url, {
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
logger.error('Error fetching meta data', error);
|
||||
logger.error(`Error fetching meta data ${error}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const BASE_URL = process.env.AF_BASE_URL || 'https://test.appflowy.cloud';
|
||||
const createServer = async (req) => {
|
||||
const timer = logRequestTimer(req);
|
||||
const reqUrl = new URL(req.url);
|
||||
logger.info(`Request URL: ${reqUrl.pathname}`);
|
||||
const [
|
||||
namespace,
|
||||
publishName,
|
||||
] = reqUrl.pathname.slice(1).split('/');
|
||||
|
||||
logger.info(`Namespace: ${namespace}, Publish Name: ${publishName}`);
|
||||
if (namespace === '' || !publishName) {
|
||||
timer();
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
'Location': 'https://appflowy.io',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const pageId = req.url.split('/').pop();
|
||||
let metaData;
|
||||
try {
|
||||
metaData = await fetchMetaData(`${BASE_URL}/api/workspace/published/${namespace}/${publishName}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error fetching meta data: ${error}`);
|
||||
}
|
||||
|
||||
let htmlData = fs.readFileSync(indexPath, 'utf8');
|
||||
const $ = cheerio.load(htmlData);
|
||||
if (!pageId) {
|
||||
timer();
|
||||
return new Response($.html(), {
|
||||
headers: { 'Content-Type': 'text/html' },
|
||||
});
|
||||
}
|
||||
|
||||
const description = 'Write, share, comment, react, and publish docs quickly and securely on AppFlowy.';
|
||||
let title = 'AppFlowy';
|
||||
const url = 'https://appflowy.com';
|
||||
let image = 'https://d3uafhn8yrvdfn.cloudfront.net/website/production/_next/static/media/og-image.e347bfb5.png';
|
||||
// Inject meta data into the HTML to support SEO and social sharing
|
||||
// if (metaData) {
|
||||
// title = metaData.title;
|
||||
// image = metaData.image;
|
||||
// }
|
||||
if (metaData) {
|
||||
title = metaData.view.name;
|
||||
|
||||
try {
|
||||
const cover = metaData.view.extra ? JSON.parse(metaData.view.extra)?.cover : null;
|
||||
if (cover && ['unsplash', 'custom'].includes(cover.type)) {
|
||||
image = cover.value;
|
||||
}
|
||||
} catch (_) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
$('title').text(title);
|
||||
setOrUpdateMetaTag($, 'meta[name="description"]', 'name', description);
|
||||
|
||||
+56
-22
@@ -156,14 +156,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.81"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
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=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -746,7 +746,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -756,17 +756,16 @@ dependencies = [
|
||||
"brotli",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"client-api-entity",
|
||||
"client-websocket",
|
||||
"collab",
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
"collab-rt-protocol",
|
||||
"database-entity",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"getrandom 0.2.12",
|
||||
"gotrue",
|
||||
"gotrue-entity",
|
||||
"infra",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"prost",
|
||||
@@ -790,10 +789,22 @@ dependencies = [
|
||||
"yrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
"database-entity",
|
||||
"gotrue-entity",
|
||||
"shared-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -1042,7 +1053,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1067,7 +1078,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1317,7 +1328,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa 1.0.10",
|
||||
"phf 0.11.2",
|
||||
"phf 0.8.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -1428,7 +1439,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -1970,6 +1981,7 @@ dependencies = [
|
||||
"flowy-server-pub",
|
||||
"flowy-sqlite",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"flowy-user",
|
||||
"flowy-user-pub",
|
||||
"futures",
|
||||
@@ -2095,7 +2107,7 @@ dependencies = [
|
||||
"flowy-document-pub",
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"futures",
|
||||
"getrandom 0.2.12",
|
||||
"indexmap 2.2.6",
|
||||
@@ -2303,6 +2315,7 @@ dependencies = [
|
||||
"flowy-search-pub",
|
||||
"flowy-server-pub",
|
||||
"flowy-storage",
|
||||
"flowy-storage-pub",
|
||||
"flowy-user-pub",
|
||||
"futures",
|
||||
"futures-util",
|
||||
@@ -2361,14 +2374,16 @@ dependencies = [
|
||||
name = "flowy-storage"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flowy-error",
|
||||
"flowy-sqlite",
|
||||
"flowy-storage-pub",
|
||||
"fxhash",
|
||||
"lib-infra",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2376,6 +2391,23 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-storage-pub"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"client-api-entity",
|
||||
"flowy-error",
|
||||
"lib-infra",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-user"
|
||||
version = "0.1.0"
|
||||
@@ -2936,7 +2968,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -2953,7 +2985,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3390,12 +3422,14 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -4897,7 +4931,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck 0.4.1",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
@@ -4918,7 +4952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
@@ -5896,7 +5930,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=430e3e15c9a1dc6aba2a9599d17d946a61ac7cae#430e3e15c9a1dc6aba2a9599d17d946a61ac7cae"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@@ -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 = "430e3e15c9a1dc6aba2a9599d17d946a61ac7cae" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "fa7565d9de2a5e65e3886c8230588d58140637c5" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
useSortsSelector,
|
||||
} from '../selector';
|
||||
import { useDatabaseViewId } from '../context';
|
||||
import { IdProvider } from '@/components/_shared/context-provider/IdProvider';
|
||||
import { DatabaseContextProvider } from '@/components/database/DatabaseContext';
|
||||
import { withTestingDatabase } from '@/application/database-yjs/__tests__/withTestingData';
|
||||
import { expect } from '@jest/globals';
|
||||
@@ -31,11 +30,9 @@ const wrapperCreator =
|
||||
(viewId: string, doc: YDoc, rowDocMap: Y.Map<YDoc>) =>
|
||||
({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<IdProvider objectId={viewId}>
|
||||
<DatabaseContextProvider viewId={viewId} databaseDoc={doc} rowDocMap={rowDocMap} readOnly={true}>
|
||||
{children}
|
||||
</DatabaseContextProvider>
|
||||
</IdProvider>
|
||||
<DatabaseContextProvider viewId={viewId} databaseDoc={doc} rowDocMap={rowDocMap} readOnly={true}>
|
||||
{children}
|
||||
</DatabaseContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { YDatabase, YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
|
||||
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||
import { createContext, useContext } from 'react';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
@@ -9,6 +10,10 @@ export interface DatabaseContextState {
|
||||
rowDocMap: Y.Map<YDoc>;
|
||||
isDatabaseRowPage?: boolean;
|
||||
navigateToRow?: (rowId: string) => void;
|
||||
loadView?: (viewId: string) => Promise<YDoc>;
|
||||
getViewRowsMap?: (viewId: string, rowIds: string[]) => Promise<{ rows: Y.Map<YDoc>; destroy: () => void }>;
|
||||
loadViewMeta?: (viewId: string) => Promise<ViewMeta>;
|
||||
navigateToView?: (viewId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const DatabaseContext = createContext<DatabaseContextState | null>(null);
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
FieldId,
|
||||
SortId,
|
||||
YDatabaseField,
|
||||
YDoc,
|
||||
YjsDatabaseKey,
|
||||
YjsEditorKey,
|
||||
YjsFolderKey,
|
||||
} from '@/application/collab.type';
|
||||
import { FieldId, SortId, YDatabaseField, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
|
||||
import { getCell, metaIdFromRowId, MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
|
||||
import {
|
||||
useDatabase,
|
||||
@@ -19,7 +11,6 @@ import {
|
||||
import { filterBy, parseFilter } from '@/application/database-yjs/filter';
|
||||
import { groupByField } from '@/application/database-yjs/group';
|
||||
import { sortBy } from '@/application/database-yjs/sort';
|
||||
import { useViewsIdSelector } from '@/application/folder-yjs';
|
||||
import { parseYDatabaseCellToCell } from '@/application/database-yjs/cell.parse';
|
||||
import { DateTimeCell } from '@/application/database-yjs/cell.type';
|
||||
import * as dayjs from 'dayjs';
|
||||
@@ -42,9 +33,8 @@ export interface Row {
|
||||
|
||||
const defaultVisible = [FieldVisibility.AlwaysShown, FieldVisibility.HideWhenEmpty];
|
||||
|
||||
export function useDatabaseViewsSelector(iidIndex: string) {
|
||||
export function useDatabaseViewsSelector(_iidIndex: string) {
|
||||
const database = useDatabase();
|
||||
const { viewsId: visibleViewsId, views: folderViews } = useViewsIdSelector();
|
||||
|
||||
const views = database?.get(YjsDatabaseKey.views);
|
||||
const [viewIds, setViewIds] = useState<string[]>([]);
|
||||
@@ -65,22 +55,7 @@ export function useDatabaseViewsSelector(iidIndex: string) {
|
||||
return Number(viewB.created_at) - Number(viewA.created_at);
|
||||
});
|
||||
|
||||
const viewsId = [];
|
||||
|
||||
for (const viewItem of viewsSorted) {
|
||||
const [key] = viewItem;
|
||||
const view = folderViews?.get(key);
|
||||
|
||||
if (
|
||||
visibleViewsId.includes(key) &&
|
||||
view &&
|
||||
(view.get(YjsFolderKey.bid) === iidIndex || view.get(YjsFolderKey.id) === iidIndex)
|
||||
) {
|
||||
viewsId.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
setViewIds(viewsId);
|
||||
setViewIds(viewsSorted.map(([key]) => key));
|
||||
};
|
||||
|
||||
observerEvent();
|
||||
@@ -89,7 +64,7 @@ export function useDatabaseViewsSelector(iidIndex: string) {
|
||||
return () => {
|
||||
views.unobserve(observerEvent);
|
||||
};
|
||||
}, [visibleViewsId, views, folderViews, iidIndex]);
|
||||
}, [views]);
|
||||
|
||||
return {
|
||||
childViews,
|
||||
|
||||
+11
-8
@@ -2,6 +2,17 @@ import { YDoc } from '@/application/collab.type';
|
||||
import { databasePrefix } from '@/application/constants';
|
||||
import { IndexeddbPersistence } from 'y-indexeddb';
|
||||
import * as Y from 'yjs';
|
||||
import BaseDexie from 'dexie';
|
||||
import { viewMetasSchema, ViewMetasTable } from '@/application/db/tables/view_metas';
|
||||
|
||||
type DexieTables = ViewMetasTable;
|
||||
|
||||
export type Dexie<T = DexieTables> = BaseDexie & T;
|
||||
|
||||
export const db = new BaseDexie(`${databasePrefix}_cache`) as Dexie;
|
||||
const schema = Object.assign({}, viewMetasSchema);
|
||||
|
||||
db.version(1).stores(schema);
|
||||
|
||||
const openedSet = new Set<string>();
|
||||
|
||||
@@ -31,11 +42,3 @@ export async function openCollabDB(docName: string): Promise<YDoc> {
|
||||
|
||||
return doc as YDoc;
|
||||
}
|
||||
|
||||
export function getCollabDBName(id: string, type: string, uuid?: string) {
|
||||
if (!uuid) {
|
||||
return `${type}_${id}`;
|
||||
}
|
||||
|
||||
return `${uuid}_${type}_${id}`;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Table } from 'dexie';
|
||||
|
||||
export interface MetaData {
|
||||
view_id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
layout: number;
|
||||
extra: string | null;
|
||||
created_by: string | null;
|
||||
last_edited_by: string | null;
|
||||
last_edited_time: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export type ViewMeta = {
|
||||
publish_name: string;
|
||||
|
||||
child_views: MetaData[];
|
||||
ancestor_views: MetaData[];
|
||||
} & MetaData;
|
||||
|
||||
export type ViewMetasTable = {
|
||||
view_metas: Table<ViewMeta>;
|
||||
};
|
||||
|
||||
export const viewMetasSchema = {
|
||||
view_metas: 'publish_name',
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import { ViewLayout, YFolder, YjsFolderKey } from '@/application/collab.type';
|
||||
import { createContext, useContext } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export interface Crumb {
|
||||
viewId: string;
|
||||
rowId?: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export const FolderContext = createContext<{
|
||||
folder: YFolder | null;
|
||||
onNavigateToView?: (viewId: string) => void;
|
||||
crumbs?: Crumb[];
|
||||
setCrumbs?: React.Dispatch<React.SetStateAction<Crumb[]>>;
|
||||
} | null>(null);
|
||||
|
||||
export const useFolderContext = () => {
|
||||
return useContext(FolderContext)?.folder;
|
||||
};
|
||||
|
||||
export const useViewLayout = () => {
|
||||
const folder = useFolderContext();
|
||||
const { objectId } = useParams();
|
||||
const views = folder?.get(YjsFolderKey.views);
|
||||
const view = objectId ? views?.get(objectId) : null;
|
||||
|
||||
return Number(view?.get(YjsFolderKey.layout)) as ViewLayout;
|
||||
};
|
||||
|
||||
export const useNavigateToView = () => {
|
||||
return useContext(FolderContext)?.onNavigateToView;
|
||||
};
|
||||
|
||||
export const useCrumbs = () => {
|
||||
return useContext(FolderContext)?.crumbs;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
export enum CoverType {
|
||||
NormalColor = 'color',
|
||||
GradientColor = 'gradient',
|
||||
BuildInImage = 'built_in',
|
||||
CustomImage = 'custom',
|
||||
LocalImage = 'local',
|
||||
UpsplashImage = 'unsplash',
|
||||
None = 'none',
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './selector';
|
||||
export * from './context';
|
||||
@@ -1,70 +0,0 @@
|
||||
import { YjsFolderKey, YView } from '@/application/collab.type';
|
||||
import { useFolderContext } from '@/application/folder-yjs/context';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useViewsIdSelector() {
|
||||
const folder = useFolderContext();
|
||||
const [viewsId, setViewsId] = useState<string[]>([]);
|
||||
const views = folder?.get(YjsFolderKey.views);
|
||||
const trash = folder?.get(YjsFolderKey.section)?.get(YjsFolderKey.trash);
|
||||
const meta = folder?.get(YjsFolderKey.meta);
|
||||
|
||||
useEffect(() => {
|
||||
if (!views) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trashUid = trash ? Array.from(trash.keys())[0] : null;
|
||||
const userTrash = trashUid ? trash?.get(trashUid) : null;
|
||||
|
||||
const collectIds = () => {
|
||||
const trashIds = userTrash?.toJSON()?.map((item) => item.id) || [];
|
||||
|
||||
return Array.from(views.keys()).filter((id) => {
|
||||
return !trashIds.includes(id) && id !== meta?.get(YjsFolderKey.current_workspace);
|
||||
});
|
||||
};
|
||||
|
||||
setViewsId(collectIds());
|
||||
const observerEvent = () => setViewsId(collectIds());
|
||||
|
||||
views.observe(observerEvent);
|
||||
userTrash?.observe(observerEvent);
|
||||
|
||||
return () => {
|
||||
views.unobserve(observerEvent);
|
||||
userTrash?.unobserve(observerEvent);
|
||||
};
|
||||
}, [views, trash, meta]);
|
||||
|
||||
return {
|
||||
viewsId,
|
||||
views,
|
||||
};
|
||||
}
|
||||
|
||||
export function useViewSelector(viewId: string) {
|
||||
const folder = useFolderContext();
|
||||
const [clock, setClock] = useState<number>(0);
|
||||
const [view, setView] = useState<YView | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!folder) return;
|
||||
|
||||
const view = folder.get(YjsFolderKey.views)?.get(viewId);
|
||||
|
||||
setView(view || null);
|
||||
const observerEvent = () => setClock((prev) => prev + 1);
|
||||
|
||||
view?.observe(observerEvent);
|
||||
|
||||
return () => {
|
||||
view?.unobserve(observerEvent);
|
||||
};
|
||||
}, [folder, viewId]);
|
||||
|
||||
return {
|
||||
clock,
|
||||
view,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { YDoc } from '@/application/collab.type';
|
||||
import { db } from '@/application/db';
|
||||
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { AFConfigContext } from '@/components/app/AppConfig';
|
||||
import { useLiveQuery } from 'dexie-react-hooks';
|
||||
import { createContext, useCallback, useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export interface PublishContextType {
|
||||
namespace: string;
|
||||
publishName: string;
|
||||
viewMeta?: ViewMeta;
|
||||
toView: (viewId: string) => Promise<void>;
|
||||
loadViewMeta: (viewId: string) => Promise<ViewMeta>;
|
||||
getViewRowsMap?: (viewId: string, rowIds: string[]) => Promise<{ rows: Y.Map<YDoc>; destroy: () => void }>;
|
||||
|
||||
loadView: (viewId: string) => Promise<YDoc>;
|
||||
}
|
||||
|
||||
export const PublishContext = createContext<PublishContextType | null>(null);
|
||||
|
||||
export const PublishProvider = ({
|
||||
children,
|
||||
namespace,
|
||||
publishName,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
namespace: string;
|
||||
publishName: string;
|
||||
}) => {
|
||||
const viewMeta = useLiveQuery(async () => {
|
||||
const name = `${namespace}_${publishName}`;
|
||||
|
||||
return db.view_metas.get(name);
|
||||
});
|
||||
const service = useContext(AFConfigContext)?.service;
|
||||
const navigate = useNavigate();
|
||||
const toView = useCallback(
|
||||
async (viewId: string) => {
|
||||
try {
|
||||
const res = await service?.getPublishInfo(viewId);
|
||||
|
||||
if (!res) {
|
||||
throw new Error('Not found');
|
||||
}
|
||||
|
||||
const { namespace, publishName } = res;
|
||||
|
||||
navigate(`/${namespace}/${publishName}`);
|
||||
} catch (e) {
|
||||
notify.error('The view has not been published yet.');
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
[navigate, service]
|
||||
);
|
||||
|
||||
const loadViewMeta = useCallback(
|
||||
async (viewId: string) => {
|
||||
try {
|
||||
const info = await service?.getPublishInfo(viewId);
|
||||
|
||||
if (!info) {
|
||||
throw new Error('View has not been published yet');
|
||||
}
|
||||
|
||||
const res = await service?.getPublishViewMeta(namespace, publishName);
|
||||
|
||||
if (!res) {
|
||||
throw new Error('View has not been published yet');
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
[namespace, publishName, service]
|
||||
);
|
||||
|
||||
const getViewRowsMap = useCallback(
|
||||
async (viewId: string, rowIds: string[]) => {
|
||||
try {
|
||||
const info = await service?.getPublishInfo(viewId);
|
||||
|
||||
if (!info) {
|
||||
throw new Error('View has not been published yet');
|
||||
}
|
||||
|
||||
const { namespace, publishName } = info;
|
||||
const res = await service?.getPublishDatabaseViewRows(namespace, publishName, rowIds);
|
||||
|
||||
if (!res) {
|
||||
throw new Error('View has not been published yet');
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
[service]
|
||||
);
|
||||
|
||||
const loadView = useCallback(
|
||||
async (viewId: string) => {
|
||||
try {
|
||||
const res = await service?.getPublishInfo(viewId);
|
||||
|
||||
if (!res) {
|
||||
throw new Error('View has not been published yet');
|
||||
}
|
||||
|
||||
const { namespace, publishName } = res;
|
||||
|
||||
const data = service?.getPublishView(namespace, publishName);
|
||||
|
||||
if (!data) {
|
||||
throw new Error('View has not been published yet');
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
[service]
|
||||
);
|
||||
|
||||
return (
|
||||
<PublishContext.Provider
|
||||
value={{
|
||||
loadView,
|
||||
viewMeta,
|
||||
getViewRowsMap,
|
||||
loadViewMeta,
|
||||
toView,
|
||||
namespace,
|
||||
publishName,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</PublishContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function usePublishContext() {
|
||||
return useContext(PublishContext);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './context';
|
||||
-310
@@ -1,310 +0,0 @@
|
||||
import { CollabType } from '@/application/collab.type';
|
||||
import * as Y from 'yjs';
|
||||
import { withTestingYDoc } from '@/application/slate-yjs/__tests__/withTestingYjsEditor';
|
||||
import { expect } from '@jest/globals';
|
||||
import { getCollab, batchCollab, collabTypeToDBType } from '../cache';
|
||||
import { applyYDoc } from '@/application/ydoc/apply';
|
||||
import { getCollabDBName, openCollabDB } from '../cache/db';
|
||||
import { StrategyType } from '../cache/types';
|
||||
|
||||
jest.mock('@/application/ydoc/apply', () => ({
|
||||
applyYDoc: jest.fn(),
|
||||
}));
|
||||
jest.mock('../cache/db', () => ({
|
||||
openCollabDB: jest.fn(),
|
||||
getCollabDBName: jest.fn(),
|
||||
}));
|
||||
|
||||
const emptyDoc = new Y.Doc();
|
||||
const normalDoc = withTestingYDoc('1');
|
||||
const mockFetcher = jest.fn();
|
||||
const mockBatchFetcher = jest.fn();
|
||||
|
||||
describe('Cache functions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getCollab', () => {
|
||||
describe('with CACHE_ONLY strategy', () => {
|
||||
it('should throw error when no cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(emptyDoc);
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
await expect(
|
||||
getCollab(
|
||||
mockFetcher,
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
StrategyType.CACHE_ONLY
|
||||
)
|
||||
).rejects.toThrow('No cache found');
|
||||
});
|
||||
it('should fetch collab with CACHE_ONLY strategy and existing cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
const result = await getCollab(
|
||||
mockFetcher,
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
StrategyType.CACHE_ONLY
|
||||
);
|
||||
|
||||
expect(result).toBe(normalDoc);
|
||||
expect(mockFetcher).not.toHaveBeenCalled();
|
||||
expect(applyYDoc).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with CACHE_FIRST strategy', () => {
|
||||
it('should fetch collab with CACHE_FIRST strategy and existing cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
mockFetcher.mockResolvedValue({ state: new Uint8Array() });
|
||||
|
||||
const result = await getCollab(
|
||||
mockFetcher,
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
StrategyType.CACHE_FIRST
|
||||
);
|
||||
|
||||
expect(result).toBe(normalDoc);
|
||||
expect(mockFetcher).not.toHaveBeenCalled();
|
||||
expect(applyYDoc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fetch collab with CACHE_FIRST strategy and no cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(emptyDoc);
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
mockFetcher.mockResolvedValue({ state: new Uint8Array() });
|
||||
|
||||
const result = await getCollab(
|
||||
mockFetcher,
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
StrategyType.CACHE_FIRST
|
||||
);
|
||||
|
||||
expect(result).toBe(emptyDoc);
|
||||
expect(mockFetcher).toHaveBeenCalled();
|
||||
expect(applyYDoc).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with CACHE_AND_NETWORK strategy', () => {
|
||||
it('should fetch collab with CACHE_AND_NETWORK strategy and existing cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
mockFetcher.mockResolvedValue({ state: new Uint8Array() });
|
||||
|
||||
const result = await getCollab(
|
||||
mockFetcher,
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
expect(result).toBe(normalDoc);
|
||||
expect(mockFetcher).toHaveBeenCalled();
|
||||
expect(applyYDoc).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fetch collab with CACHE_AND_NETWORK strategy and no cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(emptyDoc);
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
mockFetcher.mockResolvedValue({ state: new Uint8Array() });
|
||||
|
||||
const result = await getCollab(
|
||||
mockFetcher,
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
expect(result).toBe(emptyDoc);
|
||||
expect(mockFetcher).toHaveBeenCalled();
|
||||
expect(applyYDoc).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with default strategy', () => {
|
||||
it('should fetch collab with default strategy', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
mockFetcher.mockResolvedValue({ state: new Uint8Array() });
|
||||
|
||||
const result = await getCollab(
|
||||
mockFetcher,
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
StrategyType.NETWORK_ONLY
|
||||
);
|
||||
|
||||
expect(result).toBe(normalDoc);
|
||||
expect(mockFetcher).toHaveBeenCalled();
|
||||
expect(applyYDoc).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchCollab', () => {
|
||||
describe('with CACHE_ONLY strategy', () => {
|
||||
it('should batch fetch collabs with CACHE_ONLY strategy and no cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(emptyDoc);
|
||||
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
await expect(
|
||||
batchCollab(
|
||||
mockBatchFetcher,
|
||||
[
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
],
|
||||
StrategyType.CACHE_ONLY
|
||||
)
|
||||
).rejects.toThrow('No cache found');
|
||||
});
|
||||
|
||||
it('should batch fetch collabs with CACHE_ONLY strategy and existing cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
await batchCollab(
|
||||
mockBatchFetcher,
|
||||
[
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
],
|
||||
StrategyType.CACHE_ONLY
|
||||
);
|
||||
|
||||
expect(mockBatchFetcher).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with CACHE_FIRST strategy', () => {
|
||||
it('should batch fetch collabs with CACHE_FIRST strategy and existing cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
|
||||
await batchCollab(
|
||||
mockBatchFetcher,
|
||||
[
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
],
|
||||
StrategyType.CACHE_FIRST
|
||||
);
|
||||
|
||||
expect(mockBatchFetcher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should batch fetch collabs with CACHE_FIRST strategy and no cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(emptyDoc);
|
||||
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
mockBatchFetcher.mockResolvedValue({ id1: [1, 2, 3] });
|
||||
|
||||
await batchCollab(
|
||||
mockBatchFetcher,
|
||||
[
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
],
|
||||
StrategyType.CACHE_FIRST
|
||||
);
|
||||
|
||||
expect(mockBatchFetcher).toHaveBeenCalled();
|
||||
expect(applyYDoc).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with CACHE_AND_NETWORK strategy', () => {
|
||||
it('should batch fetch collabs with CACHE_AND_NETWORK strategy', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
mockBatchFetcher.mockResolvedValue({ id1: [1, 2, 3] });
|
||||
|
||||
await batchCollab(
|
||||
mockBatchFetcher,
|
||||
[
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
],
|
||||
StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
expect(mockBatchFetcher).toHaveBeenCalled();
|
||||
expect(applyYDoc).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should batch fetch collabs with CACHE_AND_NETWORK strategy and no cache', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(emptyDoc);
|
||||
|
||||
(getCollabDBName as jest.Mock).mockReturnValue('testDB');
|
||||
mockBatchFetcher.mockResolvedValue({ id1: [1, 2, 3] });
|
||||
|
||||
await batchCollab(
|
||||
mockBatchFetcher,
|
||||
[
|
||||
{
|
||||
collabId: 'id1',
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
],
|
||||
StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
expect(mockBatchFetcher).toHaveBeenCalled();
|
||||
expect(applyYDoc).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('collabTypeToDBType', () => {
|
||||
it('should return correct DB type', () => {
|
||||
expect(collabTypeToDBType(CollabType.Document)).toBe('document');
|
||||
expect(collabTypeToDBType(CollabType.Folder)).toBe('folder');
|
||||
expect(collabTypeToDBType(CollabType.Database)).toBe('database');
|
||||
expect(collabTypeToDBType(CollabType.WorkspaceDatabase)).toBe('databases');
|
||||
expect(collabTypeToDBType(CollabType.DatabaseRow)).toBe('database_row');
|
||||
expect(collabTypeToDBType(CollabType.UserAwareness)).toBe('user_awareness');
|
||||
expect(collabTypeToDBType(CollabType.Empty)).toBe('');
|
||||
});
|
||||
});
|
||||
+83
-24
@@ -1,13 +1,13 @@
|
||||
import { expect } from '@jest/globals';
|
||||
import { fetchCollab, batchFetchCollab } from '../fetch';
|
||||
import { CollabType } from '@/application/collab.type';
|
||||
import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '../fetch';
|
||||
import { APIService } from '@/application/services/js-services/wasm';
|
||||
|
||||
jest.mock('@/application/services/js-services/wasm', () => {
|
||||
return {
|
||||
APIService: {
|
||||
getCollab: jest.fn(),
|
||||
batchGetCollab: jest.fn(),
|
||||
getPublishView: jest.fn(),
|
||||
getPublishViewMeta: jest.fn(),
|
||||
getPublishInfoWithViewId: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -17,41 +17,100 @@ describe('Collab fetch functions with deduplication', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('fetchCollab', () => {
|
||||
it('should fetch collab without duplicating requests', async () => {
|
||||
const workspaceId = 'workspace1';
|
||||
const id = 'id1';
|
||||
const type = CollabType.Document;
|
||||
describe('fetchPublishView', () => {
|
||||
it('should fetch publish view without duplicating requests', async () => {
|
||||
const namespace = 'namespace1';
|
||||
const publishName = 'publish1';
|
||||
const mockResponse = { data: 'mockData' };
|
||||
|
||||
(APIService.getCollab as jest.Mock).mockResolvedValue(mockResponse);
|
||||
(APIService.getPublishView as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result1 = fetchCollab(workspaceId, id, type);
|
||||
const result2 = fetchCollab(workspaceId, id, type);
|
||||
const result1 = fetchPublishView(namespace, publishName);
|
||||
const result2 = fetchPublishView(namespace, publishName);
|
||||
|
||||
expect(result1).toBe(result2);
|
||||
await expect(result1).resolves.toEqual(mockResponse);
|
||||
expect(APIService.getCollab).toHaveBeenCalledTimes(1);
|
||||
expect(APIService.getPublishView).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should fetch publish view with different params', async () => {
|
||||
const namespace = 'namespace1';
|
||||
const publishName = 'publish1';
|
||||
const mockResponse = { data: 'mockData' };
|
||||
|
||||
(APIService.getPublishView as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result1 = fetchPublishView(namespace, publishName);
|
||||
const result2 = fetchPublishView(namespace, 'publish2');
|
||||
|
||||
expect(result1).not.toBe(result2);
|
||||
await expect(result1).resolves.toEqual(mockResponse);
|
||||
await expect(result2).resolves.toEqual(mockResponse);
|
||||
expect(APIService.getPublishView).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchFetchCollab', () => {
|
||||
it('should batch fetch collabs without duplicating requests', async () => {
|
||||
const workspaceId = 'workspace1';
|
||||
const params = [
|
||||
{ collabId: 'id1', collabType: CollabType.Document },
|
||||
{ collabId: 'id2', collabType: CollabType.Folder },
|
||||
];
|
||||
describe('fetchViewInfo', () => {
|
||||
it('should fetch view info without duplicating requests', async () => {
|
||||
const viewId = 'view1';
|
||||
const mockResponse = { data: 'mockData' };
|
||||
|
||||
(APIService.batchGetCollab as jest.Mock).mockResolvedValue(mockResponse);
|
||||
(APIService.getPublishInfoWithViewId as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result1 = batchFetchCollab(workspaceId, params);
|
||||
const result2 = batchFetchCollab(workspaceId, params);
|
||||
const result1 = fetchViewInfo(viewId);
|
||||
const result2 = fetchViewInfo(viewId);
|
||||
|
||||
expect(result1).toBe(result2);
|
||||
await expect(result1).resolves.toEqual(mockResponse);
|
||||
expect(APIService.batchGetCollab).toHaveBeenCalledTimes(1);
|
||||
expect(APIService.getPublishInfoWithViewId).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should fetch view info with different params', async () => {
|
||||
const viewId = 'view1';
|
||||
const mockResponse = { data: 'mockData' };
|
||||
|
||||
(APIService.getPublishInfoWithViewId as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result1 = fetchViewInfo(viewId);
|
||||
const result2 = fetchViewInfo('view2');
|
||||
|
||||
expect(result1).not.toBe(result2);
|
||||
await expect(result1).resolves.toEqual(mockResponse);
|
||||
await expect(result2).resolves.toEqual(mockResponse);
|
||||
expect(APIService.getPublishInfoWithViewId).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchPublishViewMeta', () => {
|
||||
it('should fetch publish view meta without duplicating requests', async () => {
|
||||
const namespace = 'namespace1';
|
||||
const publishName = 'publish1';
|
||||
const mockResponse = { data: 'mockData' };
|
||||
|
||||
(APIService.getPublishViewMeta as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result1 = fetchPublishViewMeta(namespace, publishName);
|
||||
const result2 = fetchPublishViewMeta(namespace, publishName);
|
||||
|
||||
expect(result1).toBe(result2);
|
||||
await expect(result1).resolves.toEqual(mockResponse);
|
||||
expect(APIService.getPublishViewMeta).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should fetch publish view meta with different params', async () => {
|
||||
const namespace = 'namespace1';
|
||||
const publishName = 'publish1';
|
||||
const mockResponse = { data: 'mockData' };
|
||||
|
||||
(APIService.getPublishViewMeta as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result1 = fetchPublishViewMeta(namespace, publishName);
|
||||
const result2 = fetchPublishViewMeta(namespace, 'publish2');
|
||||
|
||||
expect(result1).not.toBe(result2);
|
||||
await expect(result1).resolves.toEqual(mockResponse);
|
||||
await expect(result2).resolves.toEqual(mockResponse);
|
||||
expect(APIService.getPublishViewMeta).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
import { withTestingYDoc } from '@/application/slate-yjs/__tests__/withTestingYjsEditor';
|
||||
import { AFClientService } from '../index';
|
||||
import { fetchViewInfo } from '@/application/services/js-services/fetch';
|
||||
import { expect, jest } from '@jest/globals';
|
||||
import { getBatchCollabs, getPublishView, getPublishViewMeta } from '@/application/services/js-services/cache';
|
||||
|
||||
jest.mock('@/application/services/js-services/wasm/client_api', () => {
|
||||
return {
|
||||
initAPIService: jest.fn(),
|
||||
};
|
||||
});
|
||||
jest.mock('nanoid', () => {
|
||||
return {
|
||||
nanoid: jest.fn().mockReturnValue('12345678'),
|
||||
};
|
||||
});
|
||||
jest.mock('@/application/services/js-services/fetch', () => {
|
||||
return {
|
||||
fetchPublishView: jest.fn(),
|
||||
fetchPublishViewMeta: jest.fn(),
|
||||
fetchViewInfo: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@/application/services/js-services/cache', () => {
|
||||
return {
|
||||
getPublishView: jest.fn(),
|
||||
getPublishViewMeta: jest.fn(),
|
||||
getBatchCollabs: jest.fn(),
|
||||
};
|
||||
});
|
||||
describe('AFClientService', () => {
|
||||
let service: AFClientService;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
service = new AFClientService({
|
||||
cloudConfig: {
|
||||
baseURL: 'http://localhost:3000',
|
||||
gotrueURL: 'http://localhost:3000',
|
||||
wsURL: 'ws://localhost:3000',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should get view meta', async () => {
|
||||
const namespace = 'namespace';
|
||||
const publishName = 'publishName';
|
||||
const mockResponse = {
|
||||
view_id: 'view_id',
|
||||
publish_name: publishName,
|
||||
metadata: {
|
||||
view: {
|
||||
name: 'viewName',
|
||||
view_id: 'view_id',
|
||||
},
|
||||
child_views: [],
|
||||
ancestor_views: [],
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
(getPublishViewMeta as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await service.getPublishViewMeta(namespace, publishName);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should get view', async () => {
|
||||
const namespace = 'namespace';
|
||||
const publishName = 'publishName';
|
||||
const mockResponse = {
|
||||
data: [1, 2, 3],
|
||||
meta: {
|
||||
metadata: {
|
||||
view: {
|
||||
name: 'viewName',
|
||||
view_id: 'view_id',
|
||||
},
|
||||
child_views: [],
|
||||
ancestor_views: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
(getPublishView as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await service.getPublishView(namespace, publishName);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should get view info', async () => {
|
||||
const viewId = 'viewId';
|
||||
const mockResponse = {
|
||||
namespace: 'namespace',
|
||||
publish_name: 'publishName',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
(fetchViewInfo as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await service.getPublishInfo(viewId);
|
||||
|
||||
expect(result).toEqual({
|
||||
namespace: 'namespace',
|
||||
publishName: 'publishName',
|
||||
});
|
||||
});
|
||||
|
||||
it('getPublishDatabaseViewRows', async () => {
|
||||
const namespace = 'namespace';
|
||||
const publishName = 'publishName';
|
||||
const rowIds = ['1', '2', '3'];
|
||||
const mockResponse = [withTestingYDoc('1'), withTestingYDoc('2'), withTestingYDoc('3')];
|
||||
|
||||
// @ts-ignore
|
||||
(getBatchCollabs as jest.Mock).mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await service.getPublishDatabaseViewRows(namespace, publishName, rowIds);
|
||||
|
||||
expect(result).toEqual({
|
||||
rows: expect.any(Object),
|
||||
destroy: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
import { AuthService } from '@/application/services/services.type';
|
||||
import { ProviderType, SignUpWithEmailPasswordParams } from '@/application/user.type';
|
||||
import { APIService } from 'src/application/services/js-services/wasm';
|
||||
import { signInSuccess } from '@/application/services/js-services/session/auth';
|
||||
import { invalidToken } from 'src/application/services/js-services/session';
|
||||
import { afterSignInDecorator } from '@/application/services/js-services/decorator';
|
||||
|
||||
export class JSAuthService implements AuthService {
|
||||
constructor() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
getOAuthURL = async (_provider: ProviderType): Promise<string> => {
|
||||
return Promise.reject('Not implemented');
|
||||
};
|
||||
|
||||
@afterSignInDecorator(signInSuccess)
|
||||
async signInWithOAuth(_: { uri: string }): Promise<void> {
|
||||
return Promise.reject('Not implemented');
|
||||
}
|
||||
|
||||
signupWithEmailPassword = async (_params: SignUpWithEmailPasswordParams): Promise<void> => {
|
||||
return Promise.reject('Not implemented');
|
||||
};
|
||||
|
||||
@afterSignInDecorator(signInSuccess)
|
||||
async signinWithEmailPassword(email: string, password: string): Promise<void> {
|
||||
try {
|
||||
return APIService.signIn(email, password);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
signOut = async (): Promise<void> => {
|
||||
invalidToken();
|
||||
return APIService.logout();
|
||||
};
|
||||
}
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
import { CollabType } from '@/application/collab.type';
|
||||
import { withTestingYDoc } from '@/application/slate-yjs/__tests__/withTestingYjsEditor';
|
||||
import { expect } from '@jest/globals';
|
||||
import {
|
||||
collabTypeToDBType,
|
||||
getPublishView,
|
||||
getPublishViewMeta,
|
||||
getBatchCollabs,
|
||||
} from '@/application/services/js-services/cache';
|
||||
import { openCollabDB, db } from '@/application/db';
|
||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||
|
||||
jest.mock('@/application/ydoc/apply', () => ({
|
||||
applyYDoc: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@/application/db', () => ({
|
||||
openCollabDB: jest.fn(),
|
||||
db: {
|
||||
view_metas: {
|
||||
get: jest.fn(),
|
||||
put: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const normalDoc = withTestingYDoc('1');
|
||||
const mockFetcher = jest.fn();
|
||||
|
||||
async function runTestWithStrategy(strategy: StrategyType) {
|
||||
return getPublishView(
|
||||
mockFetcher,
|
||||
{
|
||||
namespace: 'appflowy',
|
||||
publishName: 'test',
|
||||
},
|
||||
strategy
|
||||
);
|
||||
}
|
||||
|
||||
async function runGetPublishViewMetaWithStrategy(strategy: StrategyType) {
|
||||
return getPublishViewMeta(
|
||||
mockFetcher,
|
||||
{
|
||||
namespace: 'appflowy',
|
||||
publishName: 'test',
|
||||
},
|
||||
strategy
|
||||
);
|
||||
}
|
||||
|
||||
describe('Cache functions', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockFetcher.mockClear();
|
||||
(openCollabDB as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('getPublishView', () => {
|
||||
it('should call fetcher when no cache found', async () => {
|
||||
mockFetcher.mockResolvedValue({ data: [1, 2, 3], meta: { metadata: { view: { id: '1' } } } });
|
||||
(db.view_metas.get as jest.Mock).mockResolvedValue(undefined);
|
||||
await runTestWithStrategy(StrategyType.CACHE_FIRST);
|
||||
expect(mockFetcher).toBeCalledTimes(1);
|
||||
|
||||
await runTestWithStrategy(StrategyType.CACHE_AND_NETWORK);
|
||||
expect(mockFetcher).toBeCalledTimes(2);
|
||||
await expect(runTestWithStrategy(StrategyType.CACHE_ONLY)).rejects.toThrow('No cache found');
|
||||
});
|
||||
it('should call fetcher when cache is invalid or strategy is CACHE_AND_NETWORK', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
(db.view_metas.get as jest.Mock).mockResolvedValue({ view_id: '1' });
|
||||
mockFetcher.mockResolvedValue({ data: [1, 2, 3], meta: { metadata: { view: { id: '1' } } } });
|
||||
await runTestWithStrategy(StrategyType.CACHE_ONLY);
|
||||
expect(openCollabDB).toBeCalledTimes(1);
|
||||
|
||||
await runTestWithStrategy(StrategyType.CACHE_FIRST);
|
||||
expect(openCollabDB).toBeCalledTimes(2);
|
||||
expect(mockFetcher).toBeCalledTimes(0);
|
||||
|
||||
await runTestWithStrategy(StrategyType.CACHE_AND_NETWORK);
|
||||
expect(openCollabDB).toBeCalledTimes(3);
|
||||
expect(mockFetcher).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPublishViewMeta', () => {
|
||||
it('should call fetcher when no cache found', async () => {
|
||||
mockFetcher.mockResolvedValue({ metadata: { view: { id: '1' }, child_views: [], ancestor_views: [] } });
|
||||
(db.view_metas.get as jest.Mock).mockResolvedValue(undefined);
|
||||
await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_FIRST);
|
||||
expect(mockFetcher).toBeCalledTimes(1);
|
||||
|
||||
await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_AND_NETWORK);
|
||||
expect(mockFetcher).toBeCalledTimes(2);
|
||||
|
||||
await expect(runGetPublishViewMetaWithStrategy(StrategyType.CACHE_ONLY)).rejects.toThrow('No cache found');
|
||||
});
|
||||
|
||||
it('should call fetcher when cache is invalid or strategy is CACHE_AND_NETWORK', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
(db.view_metas.get as jest.Mock).mockResolvedValue({ view_id: '1' });
|
||||
|
||||
mockFetcher.mockResolvedValue({ metadata: { view: { id: '1' }, child_views: [], ancestor_views: [] } });
|
||||
const meta = await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_ONLY);
|
||||
expect(openCollabDB).toBeCalledTimes(0);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_FIRST);
|
||||
expect(openCollabDB).toBeCalledTimes(0);
|
||||
expect(mockFetcher).toBeCalledTimes(0);
|
||||
|
||||
await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_AND_NETWORK);
|
||||
expect(openCollabDB).toBeCalledTimes(0);
|
||||
expect(mockFetcher).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBatchCollabs', () => {
|
||||
it('should return empty array when no cache found', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(undefined);
|
||||
const collabs = await getBatchCollabs(['1', '2', '3']);
|
||||
expect(collabs).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return collabs when cache found', async () => {
|
||||
(openCollabDB as jest.Mock).mockResolvedValue(normalDoc);
|
||||
(db.view_metas.get as jest.Mock).mockResolvedValue({ view_id: '1' });
|
||||
const collabs = await getBatchCollabs(['1', '2', '3']);
|
||||
expect(collabs).toEqual([normalDoc, normalDoc, normalDoc]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('collabTypeToDBType', () => {
|
||||
it('should return correct DB type', () => {
|
||||
expect(collabTypeToDBType(CollabType.Document)).toBe('document');
|
||||
expect(collabTypeToDBType(CollabType.Folder)).toBe('folder');
|
||||
expect(collabTypeToDBType(CollabType.Database)).toBe('database');
|
||||
expect(collabTypeToDBType(CollabType.WorkspaceDatabase)).toBe('databases');
|
||||
expect(collabTypeToDBType(CollabType.DatabaseRow)).toBe('database_row');
|
||||
expect(collabTypeToDBType(CollabType.UserAwareness)).toBe('user_awareness');
|
||||
expect(collabTypeToDBType(CollabType.Empty)).toBe('');
|
||||
});
|
||||
});
|
||||
+155
-90
@@ -1,7 +1,8 @@
|
||||
import { MetaData } from '@/application/db/tables/view_metas';
|
||||
import { CollabType, YDoc, YjsEditorKey, YSharedRoot } from '@/application/collab.type';
|
||||
import { applyYDoc } from '@/application/ydoc/apply';
|
||||
import { getCollabDBName, openCollabDB } from './db';
|
||||
import { Fetcher, StrategyType } from './types';
|
||||
import { db, openCollabDB } from '@/application/db';
|
||||
import { Fetcher, StrategyType } from '@/application/services/js-services/cache/types';
|
||||
|
||||
export function collabTypeToDBType(type: CollabType) {
|
||||
switch (type) {
|
||||
@@ -32,30 +33,42 @@ const collabSharedRootKeyMap = {
|
||||
[CollabType.Empty]: YjsEditorKey.empty,
|
||||
};
|
||||
|
||||
export function hasCache(doc: YDoc, type: CollabType) {
|
||||
export function hasCollabCache(doc: YDoc) {
|
||||
const data = doc.getMap(YjsEditorKey.data_section) as YSharedRoot;
|
||||
|
||||
return data.has(collabSharedRootKeyMap[type] as string);
|
||||
return Object.values(collabSharedRootKeyMap).some((key) => {
|
||||
return data.has(key);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCollab(
|
||||
fetcher: Fetcher<{
|
||||
state: Uint8Array;
|
||||
}>,
|
||||
export async function hasViewMetaCache(name: string) {
|
||||
const data = await db.view_metas.get(name);
|
||||
|
||||
return !!data;
|
||||
}
|
||||
|
||||
export async function getPublishViewMeta<
|
||||
T extends {
|
||||
metadata: {
|
||||
view: MetaData;
|
||||
child_views: MetaData[];
|
||||
ancestor_views: MetaData[];
|
||||
};
|
||||
}
|
||||
>(
|
||||
fetcher: Fetcher<T>,
|
||||
{
|
||||
collabId,
|
||||
collabType,
|
||||
uuid,
|
||||
namespace,
|
||||
publishName,
|
||||
}: {
|
||||
uuid?: string;
|
||||
collabId: string;
|
||||
collabType: CollabType;
|
||||
namespace: string;
|
||||
publishName: string;
|
||||
},
|
||||
strategy: StrategyType = StrategyType.CACHE_AND_NETWORK
|
||||
) {
|
||||
const name = getCollabDBName(collabId, collabTypeToDBType(collabType), uuid);
|
||||
const collab = await openCollabDB(name);
|
||||
const exist = hasCache(collab, collabType);
|
||||
const name = `${namespace}_${publishName}`;
|
||||
const exist = await hasViewMetaCache(name);
|
||||
const meta = await db.view_metas.get(name);
|
||||
|
||||
switch (strategy) {
|
||||
case StrategyType.CACHE_ONLY: {
|
||||
@@ -63,103 +76,155 @@ export async function getCollab(
|
||||
throw new Error('No cache found');
|
||||
}
|
||||
|
||||
return collab;
|
||||
return meta;
|
||||
}
|
||||
|
||||
case StrategyType.CACHE_FIRST: {
|
||||
if (!exist) {
|
||||
await revalidateCollab(fetcher, collab);
|
||||
return revalidatePublishViewMeta(name, fetcher);
|
||||
}
|
||||
|
||||
return collab;
|
||||
return meta;
|
||||
}
|
||||
|
||||
case StrategyType.CACHE_AND_NETWORK: {
|
||||
if (!exist) {
|
||||
await revalidateCollab(fetcher, collab);
|
||||
return revalidatePublishViewMeta(name, fetcher);
|
||||
} else {
|
||||
void revalidateCollab(fetcher, collab);
|
||||
void revalidatePublishViewMeta(name, fetcher);
|
||||
}
|
||||
|
||||
return collab;
|
||||
return meta;
|
||||
}
|
||||
|
||||
default: {
|
||||
await revalidateCollab(fetcher, collab);
|
||||
|
||||
return collab;
|
||||
return revalidatePublishViewMeta(name, fetcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function revalidateCollab(
|
||||
fetcher: Fetcher<{
|
||||
state: Uint8Array;
|
||||
}>,
|
||||
collab: YDoc
|
||||
export async function getPublishView<
|
||||
T extends {
|
||||
data: number[];
|
||||
meta: {
|
||||
metadata: {
|
||||
view: MetaData;
|
||||
child_views: MetaData[];
|
||||
ancestor_views: MetaData[];
|
||||
};
|
||||
};
|
||||
}
|
||||
>(
|
||||
fetcher: Fetcher<T>,
|
||||
{
|
||||
namespace,
|
||||
publishName,
|
||||
}: {
|
||||
namespace: string;
|
||||
publishName: string;
|
||||
},
|
||||
strategy: StrategyType = StrategyType.CACHE_AND_NETWORK
|
||||
) {
|
||||
const { state } = await fetcher();
|
||||
const name = `${namespace}_${publishName}`;
|
||||
const doc = await openCollabDB(name);
|
||||
const exist = (await hasViewMetaCache(name)) && hasCollabCache(doc);
|
||||
|
||||
switch (strategy) {
|
||||
case StrategyType.CACHE_ONLY: {
|
||||
if (!exist) {
|
||||
throw new Error('No cache found');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case StrategyType.CACHE_FIRST: {
|
||||
if (!exist) {
|
||||
await revalidatePublishView(name, fetcher, doc);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case StrategyType.CACHE_AND_NETWORK: {
|
||||
if (!exist) {
|
||||
await revalidatePublishView(name, fetcher, doc);
|
||||
} else {
|
||||
void revalidatePublishView(name, fetcher, doc);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
await revalidatePublishView(name, fetcher, doc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
export async function revalidatePublishViewMeta<
|
||||
T extends {
|
||||
metadata: {
|
||||
view: MetaData;
|
||||
child_views: MetaData[];
|
||||
ancestor_views: MetaData[];
|
||||
};
|
||||
}
|
||||
>(name: string, fetcher: Fetcher<T>) {
|
||||
const { metadata } = await fetcher();
|
||||
|
||||
await db.view_metas.put(
|
||||
{
|
||||
publish_name: name,
|
||||
...metadata.view,
|
||||
child_views: metadata.child_views,
|
||||
ancestor_views: metadata.ancestor_views,
|
||||
},
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
export async function revalidatePublishView<
|
||||
T extends {
|
||||
data: number[];
|
||||
rows?: Record<string, number[]>;
|
||||
meta: {
|
||||
metadata: {
|
||||
view: MetaData;
|
||||
child_views: MetaData[];
|
||||
ancestor_views: MetaData[];
|
||||
};
|
||||
};
|
||||
}
|
||||
>(name: string, fetcher: Fetcher<T>, collab: YDoc) {
|
||||
const { data, meta, rows } = await fetcher();
|
||||
|
||||
await db.view_metas.put(
|
||||
{
|
||||
publish_name: name,
|
||||
...meta.metadata.view,
|
||||
child_views: meta.metadata.child_views,
|
||||
ancestor_views: meta.metadata.ancestor_views,
|
||||
},
|
||||
name
|
||||
);
|
||||
|
||||
for (const [key, value] of Object.entries(rows ?? {})) {
|
||||
const row = await openCollabDB(`${name}_${key}`);
|
||||
|
||||
applyYDoc(row, new Uint8Array(value));
|
||||
}
|
||||
|
||||
const state = new Uint8Array(data);
|
||||
|
||||
applyYDoc(collab, state);
|
||||
}
|
||||
|
||||
export async function batchCollab(
|
||||
batchFetcher: Fetcher<Record<string, number[]>>,
|
||||
collabs: {
|
||||
collabId: string;
|
||||
collabType: CollabType;
|
||||
uuid?: string;
|
||||
}[],
|
||||
strategy: StrategyType = StrategyType.CACHE_AND_NETWORK,
|
||||
itemCallback?: (id: string, doc: YDoc) => void
|
||||
) {
|
||||
const collabMap = new Map<string, YDoc>();
|
||||
export async function getBatchCollabs(names: string[]) {
|
||||
const collabs = await Promise.all(names.map((name) => openCollabDB(name)));
|
||||
|
||||
for (const { collabId, collabType, uuid } of collabs) {
|
||||
const name = getCollabDBName(collabId, collabTypeToDBType(collabType), uuid);
|
||||
const collab = await openCollabDB(name);
|
||||
const exist = hasCache(collab, collabType);
|
||||
|
||||
collabMap.set(collabId, collab);
|
||||
if (exist) {
|
||||
itemCallback?.(collabId, collab);
|
||||
}
|
||||
}
|
||||
|
||||
const notCacheIds = collabs.filter(({ collabId, collabType }) => {
|
||||
const id = collabMap.get(collabId);
|
||||
|
||||
if (!id) return false;
|
||||
|
||||
return !hasCache(id, collabType);
|
||||
});
|
||||
|
||||
if (strategy === StrategyType.CACHE_ONLY) {
|
||||
if (notCacheIds.length > 0) {
|
||||
throw new Error('No cache found');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (strategy === StrategyType.CACHE_FIRST && notCacheIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const states = await batchFetcher();
|
||||
|
||||
for (const [collabId, data] of Object.entries(states)) {
|
||||
const info = collabs.find((item) => item.collabId === collabId);
|
||||
const collab = collabMap.get(collabId);
|
||||
|
||||
if (!info || !collab) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const state = new Uint8Array(data);
|
||||
|
||||
applyYDoc(collab, state);
|
||||
|
||||
itemCallback?.(collabId, collab);
|
||||
}
|
||||
return collabs;
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
import { CollabType, YDatabase, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
|
||||
import { batchCollab, getCollab } from '@/application/services/js-services/cache';
|
||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||
import { batchFetchCollab, fetchCollab } from '@/application/services/js-services/fetch';
|
||||
import { getCurrentWorkspace } from 'src/application/services/js-services/session';
|
||||
import { DatabaseService } from '@/application/services/services.type';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class JSDatabaseService implements DatabaseService {
|
||||
private loadedDatabaseId: Set<string> = new Set();
|
||||
|
||||
private loadedWorkspaceId: Set<string> = new Set();
|
||||
|
||||
private cacheDatabaseRowDocMap: Map<string, Y.Doc> = new Map();
|
||||
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
currentWorkspace() {
|
||||
return getCurrentWorkspace();
|
||||
}
|
||||
|
||||
async getWorkspaceDatabases(): Promise<{ views: string[]; database_id: string }[]> {
|
||||
const workspace = await this.currentWorkspace();
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error('Workspace database not found');
|
||||
}
|
||||
|
||||
const isLoaded = this.loadedWorkspaceId.has(workspace.id);
|
||||
|
||||
const workspaceDatabase = await getCollab(
|
||||
() => {
|
||||
return fetchCollab(workspace.id, workspace.workspaceDatabaseId, CollabType.WorkspaceDatabase);
|
||||
},
|
||||
{
|
||||
collabId: workspace.workspaceDatabaseId,
|
||||
collabType: CollabType.WorkspaceDatabase,
|
||||
},
|
||||
isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
if (!isLoaded) {
|
||||
this.loadedWorkspaceId.add(workspace.id);
|
||||
}
|
||||
|
||||
return workspaceDatabase.getMap(YjsEditorKey.data_section).get(YjsEditorKey.workspace_database).toJSON() as {
|
||||
views: string[];
|
||||
database_id: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
async openDatabase(databaseId: string): Promise<{
|
||||
databaseDoc: YDoc;
|
||||
rows: Y.Map<YDoc>;
|
||||
}> {
|
||||
const workspace = await this.currentWorkspace();
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error('Workspace database not found');
|
||||
}
|
||||
|
||||
const workspaceId = workspace.id;
|
||||
const isLoaded = this.loadedDatabaseId.has(databaseId);
|
||||
|
||||
const rootRowsDoc =
|
||||
this.cacheDatabaseRowDocMap.get(databaseId) ??
|
||||
new Y.Doc({
|
||||
guid: databaseId,
|
||||
});
|
||||
|
||||
if (!this.cacheDatabaseRowDocMap.has(databaseId)) {
|
||||
this.cacheDatabaseRowDocMap.set(databaseId, rootRowsDoc);
|
||||
}
|
||||
|
||||
const rowsFolder: Y.Map<YDoc> = rootRowsDoc.getMap();
|
||||
|
||||
const databaseDoc = await getCollab(
|
||||
() => {
|
||||
return fetchCollab(workspaceId, databaseId, CollabType.Database);
|
||||
},
|
||||
{
|
||||
collabId: databaseId,
|
||||
collabType: CollabType.Database,
|
||||
},
|
||||
isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
if (!isLoaded) this.loadedDatabaseId.add(databaseId);
|
||||
|
||||
const database = databaseDoc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
|
||||
const viewId = database.get(YjsDatabaseKey.metas)?.get(YjsDatabaseKey.iid)?.toString();
|
||||
const rowOrders = database.get(YjsDatabaseKey.views)?.get(viewId)?.get(YjsDatabaseKey.row_orders);
|
||||
const rowOrdersIds = rowOrders.toJSON() as {
|
||||
id: string;
|
||||
}[];
|
||||
|
||||
if (!rowOrdersIds) {
|
||||
throw new Error('Database rows not found');
|
||||
}
|
||||
|
||||
const rowsParams = rowOrdersIds.map((item) => ({
|
||||
collabId: item.id,
|
||||
collabType: CollabType.DatabaseRow,
|
||||
}));
|
||||
|
||||
void batchCollab(
|
||||
() => {
|
||||
return batchFetchCollab(workspaceId, rowsParams);
|
||||
},
|
||||
rowsParams,
|
||||
isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK,
|
||||
(id: string, doc: YDoc) => {
|
||||
if (!rowsFolder.has(id)) {
|
||||
rowsFolder.set(id, doc);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Update rows if there are new rows added after the database has been loaded
|
||||
rowOrders?.observe((event) => {
|
||||
if (event.changes.added.size > 0) {
|
||||
const rowIds = rowOrders.toJSON() as {
|
||||
id: string;
|
||||
}[];
|
||||
|
||||
const params = rowIds.map((item) => ({
|
||||
collabId: item.id,
|
||||
collabType: CollabType.DatabaseRow,
|
||||
}));
|
||||
|
||||
void batchCollab(
|
||||
() => {
|
||||
return batchFetchCollab(workspaceId, params);
|
||||
},
|
||||
params,
|
||||
StrategyType.CACHE_AND_NETWORK,
|
||||
(id: string, doc: YDoc) => {
|
||||
if (!rowsFolder.has(id)) {
|
||||
rowsFolder.set(id, doc);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
databaseDoc,
|
||||
rows: rowsFolder,
|
||||
};
|
||||
}
|
||||
|
||||
async closeDatabase(databaseId: string) {
|
||||
this.cacheDatabaseRowDocMap.delete(databaseId);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @description:
|
||||
* * This is a decorator that can be used to read data from storage and fetch data from the server.
|
||||
* * If the data is already in storage, it will return the data from storage and fetch the data from the server in the background.
|
||||
*
|
||||
* @param getStorage A function that returns the data from storage. eg. `() => Promise<T | undefined>`
|
||||
*
|
||||
* @param setStorage A function that saves the data to storage. eg. `(data: T) => Promise<void>`
|
||||
*
|
||||
* @param fetchFunction A function that fetches the data from the server. eg. `(params: P) => Promise<T | undefined>`
|
||||
*
|
||||
* @returns: A function that returns the data from storage and fetches the data from the server in the background.
|
||||
*/
|
||||
export function asyncDataDecorator<P, T>(
|
||||
getStorage: () => Promise<T | undefined>,
|
||||
setStorage: (data: T) => Promise<void>,
|
||||
fetchFunction: (params: P) => Promise<T | undefined>
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
async function fetchData(params: P) {
|
||||
const data = await fetchFunction(params);
|
||||
|
||||
if (!data) return;
|
||||
await setStorage(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (params: P) {
|
||||
const data = await getStorage();
|
||||
|
||||
await originalMethod.apply(this, [params]);
|
||||
if (data) {
|
||||
void fetchData(params);
|
||||
return data;
|
||||
} else {
|
||||
return fetchData(params);
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
export function afterSignInDecorator(successCallback: () => Promise<void>) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
await originalMethod.apply(this, args);
|
||||
await successCallback();
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { CollabOrigin, CollabType, YDoc } from '@/application/collab.type';
|
||||
import { getCollab } from '@/application/services/js-services/cache';
|
||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||
import { fetchCollab } from '@/application/services/js-services/fetch';
|
||||
import { getCurrentWorkspace } from 'src/application/services/js-services/session';
|
||||
import { DocumentService } from '@/application/services/services.type';
|
||||
|
||||
export class JSDocumentService implements DocumentService {
|
||||
private loaded: Set<string> = new Set();
|
||||
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
async openDocument(docId: string): Promise<YDoc> {
|
||||
const workspace = await getCurrentWorkspace();
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error('Workspace database not found');
|
||||
}
|
||||
|
||||
const isLoaded = this.loaded.has(docId);
|
||||
|
||||
const doc = await getCollab(
|
||||
() => {
|
||||
return fetchCollab(workspace.id, docId, CollabType.Document);
|
||||
},
|
||||
{
|
||||
collabId: docId,
|
||||
collabType: CollabType.Document,
|
||||
},
|
||||
isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
if (!isLoaded) this.loaded.add(docId);
|
||||
const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => {
|
||||
if (origin === CollabOrigin.LocalSync) {
|
||||
// Send the update to the server
|
||||
console.log('update', update);
|
||||
}
|
||||
};
|
||||
|
||||
doc.on('update', handleUpdate);
|
||||
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CollabType } from '@/application/collab.type';
|
||||
import { APIService } from '@/application/services/js-services/wasm';
|
||||
|
||||
const pendingRequests = new Map();
|
||||
@@ -31,36 +30,20 @@ function fetchWithDeduplication<Req, Res>(url: string, params: Req, fetchFunctio
|
||||
return fetchPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch collab
|
||||
* @param workspaceId
|
||||
* @param id
|
||||
* @param type [CollabType]
|
||||
*/
|
||||
export function fetchCollab(workspaceId: string, id: string, type: CollabType) {
|
||||
const fetchFunction = () => APIService.getCollab(workspaceId, id, type);
|
||||
export function fetchPublishView(namespace: string, publishName: string) {
|
||||
const fetchFunction = () => APIService.getPublishView(namespace, publishName);
|
||||
|
||||
return fetchWithDeduplication(`fetchCollab_${workspaceId}`, { id, type }, fetchFunction);
|
||||
return fetchWithDeduplication(`fetchPublishView_${namespace}`, { publishName }, fetchFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch fetch collab
|
||||
* Usage:
|
||||
* // load database rows
|
||||
* const rows = await batchFetchCollab(workspaceId, databaseRows.map((row) => ({ collabId: row.id, collabType: CollabType.DatabaseRow })));
|
||||
*
|
||||
* @param workspaceId
|
||||
* @param params [{ collabId: string; collabType: CollabType }]
|
||||
*/
|
||||
export function batchFetchCollab(workspaceId: string, params: { collabId: string; collabType: CollabType }[]) {
|
||||
const fetchFunction = () =>
|
||||
APIService.batchGetCollab(
|
||||
workspaceId,
|
||||
params.map(({ collabId, collabType }) => ({
|
||||
object_id: collabId,
|
||||
collab_type: collabType,
|
||||
}))
|
||||
);
|
||||
export function fetchViewInfo(viewId: string) {
|
||||
const fetchFunction = () => APIService.getPublishInfoWithViewId(viewId);
|
||||
|
||||
return fetchWithDeduplication(`batchFetchCollab_${workspaceId}`, params, fetchFunction);
|
||||
return fetchWithDeduplication(`fetchViewInfo`, { viewId }, fetchFunction);
|
||||
}
|
||||
|
||||
export function fetchPublishViewMeta(namespace: string, publishName: string) {
|
||||
const fetchFunction = () => APIService.getPublishViewMeta(namespace, publishName);
|
||||
|
||||
return fetchWithDeduplication(`fetchPublishViewMeta_${namespace}`, { publishName }, fetchFunction);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { CollabOrigin, CollabType, YDoc } from '@/application/collab.type';
|
||||
import { getCollab } from '@/application/services/js-services/cache';
|
||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||
import { fetchCollab } from '@/application/services/js-services/fetch';
|
||||
import { FolderService } from '@/application/services/services.type';
|
||||
|
||||
export class JSFolderService implements FolderService {
|
||||
private loaded: Set<string> = new Set();
|
||||
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
async openWorkspace(workspaceId: string): Promise<YDoc> {
|
||||
const isLoaded = this.loaded.has(workspaceId);
|
||||
const doc = await getCollab(
|
||||
() => {
|
||||
return fetchCollab(workspaceId, workspaceId, CollabType.Folder);
|
||||
},
|
||||
{
|
||||
collabId: workspaceId,
|
||||
collabType: CollabType.Folder,
|
||||
},
|
||||
isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
if (!isLoaded) this.loaded.add(workspaceId);
|
||||
const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => {
|
||||
if (origin === CollabOrigin.LocalSync) {
|
||||
// Send the update to the server
|
||||
console.log('update', update);
|
||||
}
|
||||
};
|
||||
|
||||
doc.on('update', handleUpdate);
|
||||
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,28 @@
|
||||
import { JSDatabaseService } from '@/application/services/js-services/database.service';
|
||||
import {
|
||||
AFService,
|
||||
AFServiceConfig,
|
||||
AuthService,
|
||||
DatabaseService,
|
||||
DocumentService,
|
||||
FolderService,
|
||||
UserService,
|
||||
} from '@/application/services/services.type';
|
||||
import { JSUserService } from '@/application/services/js-services/user.service';
|
||||
import { JSAuthService } from '@/application/services/js-services/auth.service';
|
||||
import { JSFolderService } from '@/application/services/js-services/folder.service';
|
||||
import { JSDocumentService } from '@/application/services/js-services/document.service';
|
||||
import { YDoc } from '@/application/collab.type';
|
||||
import { getBatchCollabs, getPublishView, getPublishViewMeta } from '@/application/services/js-services/cache';
|
||||
import { StrategyType } from '@/application/services/js-services/cache/types';
|
||||
import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '@/application/services/js-services/fetch';
|
||||
import { AFService, AFServiceConfig } from '@/application/services/services.type';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { initAPIService } from '@/application/services/js-services/wasm/client_api';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class AFClientService implements AFService {
|
||||
authService: AuthService;
|
||||
|
||||
userService: UserService;
|
||||
|
||||
documentService: DocumentService;
|
||||
|
||||
folderService: FolderService;
|
||||
|
||||
databaseService: DatabaseService;
|
||||
|
||||
private deviceId: string = nanoid(8);
|
||||
|
||||
private clientId: string = 'web';
|
||||
|
||||
getDeviceID = (): string => {
|
||||
return this.deviceId;
|
||||
};
|
||||
private publishViewLoaded: Set<string> = new Set();
|
||||
|
||||
getClientID = (): string => {
|
||||
return this.clientId;
|
||||
};
|
||||
private publishViewInfo: Map<
|
||||
string,
|
||||
{
|
||||
namespace: string;
|
||||
publishName: string;
|
||||
}
|
||||
> = new Map();
|
||||
|
||||
private cacheDatabaseRowDocMap: Map<string, Y.Doc> = new Map();
|
||||
|
||||
constructor(config: AFServiceConfig) {
|
||||
initAPIService({
|
||||
@@ -44,11 +30,104 @@ export class AFClientService implements AFService {
|
||||
deviceId: this.deviceId,
|
||||
clientId: this.clientId,
|
||||
});
|
||||
}
|
||||
|
||||
this.authService = new JSAuthService();
|
||||
this.userService = new JSUserService();
|
||||
this.documentService = new JSDocumentService();
|
||||
this.folderService = new JSFolderService();
|
||||
this.databaseService = new JSDatabaseService();
|
||||
async getPublishViewMeta(namespace: string, publishName: string) {
|
||||
const viewMeta = await getPublishViewMeta(
|
||||
() => {
|
||||
return fetchPublishViewMeta(namespace, publishName);
|
||||
},
|
||||
{
|
||||
namespace,
|
||||
publishName,
|
||||
},
|
||||
StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
if (!viewMeta) {
|
||||
return Promise.reject(new Error('View has not been published yet'));
|
||||
}
|
||||
|
||||
return viewMeta;
|
||||
}
|
||||
|
||||
async getPublishView(namespace: string, publishName: string) {
|
||||
const name = `${namespace}_${publishName}`;
|
||||
const isLoaded = this.publishViewLoaded.has(name);
|
||||
const doc = await getPublishView(
|
||||
() => {
|
||||
return fetchPublishView(namespace, publishName);
|
||||
},
|
||||
{
|
||||
namespace,
|
||||
publishName,
|
||||
},
|
||||
isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK
|
||||
);
|
||||
|
||||
if (!isLoaded) {
|
||||
this.publishViewLoaded.add(name);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
async getPublishDatabaseViewRows(namespace: string, publishName: string, rowIds: string[]) {
|
||||
const name = `${namespace}_${publishName}`;
|
||||
|
||||
if (!this.publishViewLoaded.has(name)) {
|
||||
await this.getPublishView(namespace, publishName);
|
||||
}
|
||||
|
||||
const rootRowsDoc =
|
||||
this.cacheDatabaseRowDocMap.get(name) ??
|
||||
new Y.Doc({
|
||||
guid: name,
|
||||
});
|
||||
|
||||
if (!this.cacheDatabaseRowDocMap.has(name)) {
|
||||
this.cacheDatabaseRowDocMap.set(name, rootRowsDoc);
|
||||
}
|
||||
|
||||
const rowsFolder: Y.Map<YDoc> = rootRowsDoc.getMap();
|
||||
const docs = await getBatchCollabs(rowIds);
|
||||
|
||||
docs.forEach((doc, index) => {
|
||||
rowsFolder.set(rowIds[index], doc);
|
||||
});
|
||||
|
||||
return {
|
||||
rows: rowsFolder,
|
||||
destroy: () => {
|
||||
this.cacheDatabaseRowDocMap.delete(name);
|
||||
rootRowsDoc.destroy();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getPublishInfo(viewId: string) {
|
||||
if (this.publishViewInfo.has(viewId)) {
|
||||
return this.publishViewInfo.get(viewId) as {
|
||||
namespace: string;
|
||||
publishName: string;
|
||||
};
|
||||
}
|
||||
|
||||
const info = await fetchViewInfo(viewId);
|
||||
|
||||
const namespace = info.namespace;
|
||||
|
||||
if (!namespace) {
|
||||
return Promise.reject(new Error('View not found'));
|
||||
}
|
||||
|
||||
const data = {
|
||||
namespace,
|
||||
publishName: info.publish_name,
|
||||
};
|
||||
|
||||
this.publishViewInfo.set(viewId, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export async function signInSuccess() {
|
||||
// Do nothing
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './token';
|
||||
export * from './user';
|
||||
export * from './auth';
|
||||
@@ -1,37 +0,0 @@
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
|
||||
const tokenKey = 'token';
|
||||
|
||||
export function readTokenStr() {
|
||||
return sessionStorage.getItem(tokenKey);
|
||||
}
|
||||
|
||||
export function getAuthInfo() {
|
||||
const token = readTokenStr() || '';
|
||||
|
||||
try {
|
||||
const info = JSON.parse(token);
|
||||
|
||||
return {
|
||||
uuid: info.user.id,
|
||||
access_token: info.access_token,
|
||||
email: info.user.email,
|
||||
};
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeToken(token: string) {
|
||||
if (!token) {
|
||||
invalidToken();
|
||||
return;
|
||||
}
|
||||
|
||||
sessionStorage.setItem(tokenKey, token);
|
||||
}
|
||||
|
||||
export function invalidToken() {
|
||||
sessionStorage.removeItem(tokenKey);
|
||||
notify.error('Invalid token, please login again');
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { UserProfile, UserWorkspace, Workspace } from '@/application/user.type';
|
||||
|
||||
const userKey = 'user';
|
||||
const workspaceKey = 'workspace';
|
||||
|
||||
export async function getSignInUser(): Promise<UserProfile | undefined> {
|
||||
const userStr = localStorage.getItem(userKey);
|
||||
|
||||
try {
|
||||
return userStr ? JSON.parse(userStr) : undefined;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setSignInUser(profile: UserProfile) {
|
||||
const userStr = JSON.stringify(profile);
|
||||
|
||||
localStorage.setItem(userKey, userStr);
|
||||
}
|
||||
|
||||
export async function getUserWorkspace(): Promise<UserWorkspace | undefined> {
|
||||
const str = localStorage.getItem(workspaceKey);
|
||||
|
||||
try {
|
||||
return str ? JSON.parse(str) : undefined;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setUserWorkspace(workspace: UserWorkspace) {
|
||||
const str = JSON.stringify(workspace);
|
||||
|
||||
localStorage.setItem(workspaceKey, str);
|
||||
}
|
||||
|
||||
export async function getCurrentWorkspace(): Promise<Workspace | undefined> {
|
||||
const userProfile = await getSignInUser();
|
||||
const userWorkspace = await getUserWorkspace();
|
||||
|
||||
return userWorkspace?.workspaces.find((workspace) => workspace.id === userProfile?.workspaceId);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { UserService } from '@/application/services/services.type';
|
||||
import { UserProfile, UserWorkspace } from '@/application/user.type';
|
||||
import { APIService } from 'src/application/services/js-services/wasm';
|
||||
import {
|
||||
getAuthInfo,
|
||||
getSignInUser,
|
||||
getUserWorkspace,
|
||||
invalidToken,
|
||||
setSignInUser,
|
||||
setUserWorkspace,
|
||||
} from 'src/application/services/js-services/session';
|
||||
import { asyncDataDecorator } from '@/application/services/js-services/decorator';
|
||||
|
||||
async function getUser() {
|
||||
try {
|
||||
const user = await APIService.getUser();
|
||||
|
||||
return user;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
invalidToken();
|
||||
}
|
||||
}
|
||||
|
||||
export class JSUserService implements UserService {
|
||||
@asyncDataDecorator<void, UserProfile>(getSignInUser, setSignInUser, getUser)
|
||||
async getUserProfile(): Promise<UserProfile> {
|
||||
if (!getAuthInfo()) {
|
||||
return Promise.reject('Not authenticated');
|
||||
}
|
||||
|
||||
await this.getUserWorkspace();
|
||||
|
||||
return null!;
|
||||
}
|
||||
|
||||
async checkUser(): Promise<boolean> {
|
||||
return (await getSignInUser()) !== undefined;
|
||||
}
|
||||
|
||||
@asyncDataDecorator<void, UserWorkspace>(getUserWorkspace, setUserWorkspace, APIService.getUserWorkspace)
|
||||
async getUserWorkspace(): Promise<UserWorkspace> {
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import { CollabType } from '@/application/collab.type';
|
||||
import { ClientAPI } from '@appflowyinc/client-api-wasm';
|
||||
import { UserProfile, UserWorkspace } from '@/application/user.type';
|
||||
import { AFCloudConfig } from '@/application/services/services.type';
|
||||
import { invalidToken, readTokenStr, writeToken } from 'src/application/services/js-services/session';
|
||||
|
||||
let client: ClientAPI;
|
||||
|
||||
@@ -12,8 +9,14 @@ export function initAPIService(
|
||||
clientId: string;
|
||||
}
|
||||
) {
|
||||
window.refresh_token = writeToken;
|
||||
window.invalid_token = invalidToken;
|
||||
window.refresh_token = () => {
|
||||
//
|
||||
};
|
||||
|
||||
window.invalid_token = () => {
|
||||
// invalidToken();
|
||||
};
|
||||
|
||||
client = ClientAPI.new({
|
||||
base_url: config.baseURL,
|
||||
ws_addr: config.wsURL,
|
||||
@@ -26,96 +29,17 @@ export function initAPIService(
|
||||
},
|
||||
});
|
||||
|
||||
const token = readTokenStr();
|
||||
|
||||
if (token) {
|
||||
client.restore_token(token);
|
||||
}
|
||||
|
||||
client.subscribe();
|
||||
}
|
||||
|
||||
export function signIn(email: string, password: string) {
|
||||
return client.login(email, password);
|
||||
export async function getPublishView(publishNamespace: string, publishName: string) {
|
||||
return client.get_publish_view(publishNamespace, publishName);
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return client.logout();
|
||||
export async function getPublishInfoWithViewId(viewId: string) {
|
||||
return client.get_publish_info(viewId);
|
||||
}
|
||||
|
||||
export async function getUser(): Promise<UserProfile> {
|
||||
try {
|
||||
const user = await client.get_user();
|
||||
|
||||
if (!user) {
|
||||
throw new Error('No user found');
|
||||
}
|
||||
|
||||
return {
|
||||
uid: parseInt(user.uid),
|
||||
uuid: user.uuid || undefined,
|
||||
email: user.email || undefined,
|
||||
name: user.name || undefined,
|
||||
workspaceId: user.latest_workspace_id,
|
||||
iconUrl: user.icon_url || undefined,
|
||||
};
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCollab(workspaceId: string, object_id: string, collabType: CollabType) {
|
||||
const res = await client.get_collab({
|
||||
workspace_id: workspaceId,
|
||||
object_id: object_id,
|
||||
collab_type: Number(collabType) as 0 | 1 | 2 | 3 | 4 | 5,
|
||||
});
|
||||
|
||||
const state = new Uint8Array(res.doc_state);
|
||||
|
||||
return {
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
export async function batchGetCollab(
|
||||
workspaceId: string,
|
||||
params: {
|
||||
object_id: string;
|
||||
collab_type: CollabType;
|
||||
}[]
|
||||
) {
|
||||
const res = (await client.batch_get_collab(
|
||||
workspaceId,
|
||||
params.map((param) => ({
|
||||
object_id: param.object_id,
|
||||
collab_type: Number(param.collab_type) as 0 | 1 | 2 | 3 | 4 | 5,
|
||||
}))
|
||||
)) as unknown as Map<string, { doc_state: number[] }>;
|
||||
|
||||
const result: Record<string, number[]> = {};
|
||||
|
||||
res.forEach((value, key) => {
|
||||
result[key] = value.doc_state;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getUserWorkspace(): Promise<UserWorkspace> {
|
||||
const res = await client.get_user_workspace();
|
||||
|
||||
return {
|
||||
visitingWorkspaceId: res.visiting_workspace_id,
|
||||
workspaces: res.workspaces.map((workspace) => ({
|
||||
id: workspace.workspace_id,
|
||||
name: workspace.workspace_name,
|
||||
icon: workspace.icon,
|
||||
owner: {
|
||||
id: Number(workspace.owner_uid),
|
||||
name: workspace.owner_name,
|
||||
},
|
||||
type: workspace.workspace_type,
|
||||
workspaceDatabaseId: workspace.database_storage_id,
|
||||
})),
|
||||
};
|
||||
export async function getPublishViewMeta(publishNamespace: string, publishName: string) {
|
||||
return client.get_publish_view_meta(publishNamespace, publishName);
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
import { YDoc } from '@/application/collab.type';
|
||||
import { ProviderType, SignUpWithEmailPasswordParams, UserProfile } from '@/application/user.type';
|
||||
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export interface AFService {
|
||||
getDeviceID: () => string;
|
||||
getClientID: () => string;
|
||||
authService: AuthService;
|
||||
userService: UserService;
|
||||
documentService: DocumentService;
|
||||
folderService: FolderService;
|
||||
databaseService: DatabaseService;
|
||||
}
|
||||
export type AFService = PublishService;
|
||||
|
||||
export interface AFServiceConfig {
|
||||
cloudConfig: AFCloudConfig;
|
||||
@@ -22,35 +14,16 @@ export interface AFCloudConfig {
|
||||
wsURL: string;
|
||||
}
|
||||
|
||||
export interface AuthService {
|
||||
getOAuthURL: (provider: ProviderType) => Promise<string>;
|
||||
signInWithOAuth: (params: { uri: string }) => Promise<void>;
|
||||
signupWithEmailPassword: (params: SignUpWithEmailPasswordParams) => Promise<void>;
|
||||
signinWithEmailPassword: (email: string, password: string) => Promise<void>;
|
||||
signOut: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface DocumentService {
|
||||
openDocument: (docId: string) => Promise<YDoc>;
|
||||
}
|
||||
|
||||
export interface DatabaseService {
|
||||
getWorkspaceDatabases: () => Promise<{ views: string[]; database_id: string }[]>;
|
||||
openDatabase: (
|
||||
databaseId: string,
|
||||
rowIds?: string[]
|
||||
export interface PublishService {
|
||||
getPublishViewMeta: (namespace: string, publishName: string) => Promise<ViewMeta>;
|
||||
getPublishView: (namespace: string, publishName: string) => Promise<YDoc>;
|
||||
getPublishInfo: (viewId: string) => Promise<{ namespace: string; publishName: string }>;
|
||||
getPublishDatabaseViewRows: (
|
||||
namespace: string,
|
||||
publishName: string,
|
||||
rowIds: string[]
|
||||
) => Promise<{
|
||||
databaseDoc: YDoc;
|
||||
rows: Y.Map<YDoc>;
|
||||
destroy: () => void;
|
||||
}>;
|
||||
closeDatabase: (databaseId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface UserService {
|
||||
getUserProfile: () => Promise<UserProfile | null>;
|
||||
checkUser: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface FolderService {
|
||||
openWorkspace: (workspaceId: string) => Promise<YDoc>;
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import { AFCloudConfig, AuthService } from '@/application/services/services.type';
|
||||
import {
|
||||
AuthenticatorPB,
|
||||
OauthProviderPB,
|
||||
OauthSignInPB,
|
||||
SignInPayloadPB,
|
||||
SignUpPayloadPB,
|
||||
UserEventGetOauthURLWithProvider,
|
||||
UserEventOauthSignIn,
|
||||
UserEventSignInWithEmailPassword,
|
||||
UserEventSignOut,
|
||||
UserEventSignUp,
|
||||
UserProfilePB,
|
||||
} from './backend/events/flowy-user';
|
||||
import { ProviderType, SignUpWithEmailPasswordParams, UserProfile } from '@/application/user.type';
|
||||
|
||||
export class TauriAuthService implements AuthService {
|
||||
|
||||
constructor (private cloudConfig: AFCloudConfig, private clientConfig: {
|
||||
deviceId: string;
|
||||
clientId: string;
|
||||
|
||||
}) {}
|
||||
|
||||
getDeviceID = (): string => {
|
||||
return this.clientConfig.deviceId;
|
||||
};
|
||||
getOAuthURL = async (provider: ProviderType): Promise<string> => {
|
||||
const providerDataRes = await UserEventGetOauthURLWithProvider(
|
||||
OauthProviderPB.fromObject({
|
||||
provider: provider as number,
|
||||
}),
|
||||
);
|
||||
|
||||
if (!providerDataRes.ok) {
|
||||
throw new Error(providerDataRes.val.msg);
|
||||
}
|
||||
|
||||
const providerData = providerDataRes.val;
|
||||
|
||||
return providerData.oauth_url;
|
||||
};
|
||||
|
||||
signInWithOAuth = async ({ uri }: { uri: string }): Promise<void> => {
|
||||
const payload = OauthSignInPB.fromObject({
|
||||
authenticator: AuthenticatorPB.AppFlowyCloud,
|
||||
map: {
|
||||
sign_in_url: uri,
|
||||
device_id: this.getDeviceID(),
|
||||
},
|
||||
});
|
||||
|
||||
const res = await UserEventOauthSignIn(payload);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(res.val.msg);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
signinWithEmailPassword = async (email: string, password: string): Promise<void> => {
|
||||
const payload = SignInPayloadPB.fromObject({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
const res = await UserEventSignInWithEmailPassword(payload);
|
||||
|
||||
if (!res.ok) {
|
||||
return Promise.reject(res.val.msg);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
signupWithEmailPassword = async (params: SignUpWithEmailPasswordParams): Promise<void> => {
|
||||
const payload = SignUpPayloadPB.fromObject({
|
||||
name: params.name,
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
device_id: this.getDeviceID(),
|
||||
});
|
||||
|
||||
const res = await UserEventSignUp(payload);
|
||||
|
||||
if (!res.ok) {
|
||||
return Promise.reject(res.val.msg);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
signOut = async () => {
|
||||
const res = await UserEventSignOut();
|
||||
|
||||
if (!res.ok) {
|
||||
return Promise.reject(res.val.msg);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
export function parseUserProfileFrom (userPB: UserProfilePB): UserProfile {
|
||||
const user = userPB.toObject();
|
||||
|
||||
return {
|
||||
uid: user.id as number,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
iconUrl: user.icon_url,
|
||||
workspaceId: user.workspace_id,
|
||||
};
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { YDoc } from '@/application/collab.type';
|
||||
import { DatabaseService } from '@/application/services/services.type';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class TauriDatabaseService implements DatabaseService {
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
async getWorkspaceDatabases(): Promise<{ views: string[]; database_id: string }[]> {
|
||||
return Promise.reject('Not implemented');
|
||||
}
|
||||
|
||||
async closeDatabase(_databaseId: string) {
|
||||
return Promise.reject('Not implemented');
|
||||
}
|
||||
|
||||
async openDatabase(_viewId: string): Promise<{
|
||||
databaseDoc: YDoc;
|
||||
rows: Y.Map<YDoc>;
|
||||
}> {
|
||||
return Promise.reject('Not implemented');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { DocumentService } from '@/application/services/services.type';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
export class TauriDocumentService implements DocumentService {
|
||||
async openDocument(_id: string): Promise<Y.Doc> {
|
||||
return Promise.reject('Not implemented');
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { YDoc } from '@/application/collab.type';
|
||||
import { FolderService } from '@/application/services/services.type';
|
||||
|
||||
export class TauriFolderService implements FolderService {
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
async openWorkspace(_workspaceId: string): Promise<YDoc> {
|
||||
return Promise.reject('Not implemented');
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,24 @@
|
||||
import {
|
||||
AFService,
|
||||
AFServiceConfig,
|
||||
AuthService,
|
||||
DatabaseService,
|
||||
DocumentService,
|
||||
FolderService,
|
||||
UserService,
|
||||
} from '@/application/services/services.type';
|
||||
import { TauriAuthService } from '@/application/services/tauri-services/auth.service';
|
||||
import { TauriDatabaseService } from '@/application/services/tauri-services/database.service';
|
||||
import { TauriFolderService } from '@/application/services/tauri-services/folder.service';
|
||||
import { TauriUserService } from '@/application/services/tauri-services/user.service';
|
||||
import { TauriDocumentService } from '@/application/services/tauri-services/document.service';
|
||||
import { AFService } from '@/application/services/services.type';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export class AFClientService implements AFService {
|
||||
authService: AuthService;
|
||||
|
||||
userService: UserService;
|
||||
|
||||
documentService: DocumentService;
|
||||
|
||||
folderService: FolderService;
|
||||
|
||||
databaseService: DatabaseService;
|
||||
|
||||
private deviceId: string = nanoid(8);
|
||||
|
||||
private clientId: string = 'web';
|
||||
private clientId: string = 'tauri';
|
||||
|
||||
getDeviceID = (): string => {
|
||||
return this.deviceId;
|
||||
};
|
||||
async getPublishView(_namespace: string, _publishName: string) {
|
||||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
|
||||
getClientID = (): string => {
|
||||
return this.clientId;
|
||||
};
|
||||
async getPublishInfo(_viewId: string) {
|
||||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
|
||||
constructor(config: AFServiceConfig) {
|
||||
this.authService = new TauriAuthService(config.cloudConfig, {
|
||||
deviceId: this.deviceId,
|
||||
clientId: this.clientId,
|
||||
});
|
||||
this.userService = new TauriUserService();
|
||||
this.documentService = new TauriDocumentService();
|
||||
this.folderService = new TauriFolderService();
|
||||
this.databaseService = new TauriDatabaseService();
|
||||
async getPublishViewMeta(_namespace: string, _publishName: string) {
|
||||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
|
||||
async getPublishDatabaseViewRows(_namespace: string, _publishName: string, _rowIds: string[]) {
|
||||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
}
|
||||
Loaded 100 of 268 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user