diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000000..24cd55c6b6 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,26 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. + +echo "Running the AppFlowy commit-msg hook." + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} + +npx --no -- commitlint --edit $1 + +if [ $? -ne 0 ] +then + echo "Please fix your commit message to match AppFlowy coding standards" + exit 1 +fi + diff --git a/.githooks/pre-commit b/.githooks/pre-commit old mode 100644 new mode 100755 index d7617246c9..d27345fe71 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,5 +1,7 @@ #!/usr/bin/env bash +echo "Running local AppFlowy pre-commit hook." + #flutter format . ##https://gist.github.com/benmccallum/28e4f216d9d72f5965133e6c43aaff6e limit=$(( 1 * 2**20 )) # 1MB @@ -31,4 +33,4 @@ for file in $( git diff-index --cached --name-only $against ); do file_too_large $filename $file_size exit 1; fi -done \ No newline at end of file +done diff --git a/.githooks/pre-push b/.githooks/pre-push old mode 100644 new mode 100755 index 1636fb8661..ad7d19a16e --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -1,15 +1,20 @@ #!/usr/bin/env bash +echo "Running local AppFlowy pre-push hook." + if [[ `git status --porcelain` ]]; then printf "\e[31;1m%s\e[0m\n" 'This script needs to run against committed code only. Please commit or stash you changes.' exit 1 fi + printf "\e[33;1m%s\e[0m\n" 'Running the Flutter analyzer' flutter analyze + if [ $? -ne 0 ]; then printf "\e[31;1m%s\e[0m\n" 'Flutter analyzer error' exit 1 fi + printf "\e[33;1m%s\e[0m\n" 'Finished running the Flutter analyzer' printf "\e[33;1m%s\e[0m\n" 'Running unit tests' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d3dd050e2c..dbbd729c81 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,7 @@ jobs: os: [ubuntu-latest, macos-latest] include: - os: ubuntu-latest - flutter_profile: development-linux-x86 + flutter_profile: development-linux-x86_64 - os: macos-latest flutter_profile: development-mac-x86_64 runs-on: ${{ matrix.os }} @@ -82,4 +82,4 @@ jobs: - name: Build working-directory: frontend run: | - cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev \ No newline at end of file + cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev diff --git a/.github/workflows/dart_lint.yml b/.github/workflows/dart_lint.yml index 65f78f80cd..eb5703cd72 100644 --- a/.github/workflows/dart_lint.yml +++ b/.github/workflows/dart_lint.yml @@ -42,7 +42,7 @@ jobs: - name: Build FlowySDK working-directory: frontend run: | - cargo make --profile development-linux-x86 flowy-sdk-dev + cargo make --profile development-linux-x86_64 flowy-sdk-dev - name: Code Generation working-directory: frontend/app_flowy diff --git a/.github/workflows/dart_test.yml b/.github/workflows/dart_test.yml index 0689345b68..74b20a2425 100644 --- a/.github/workflows/dart_test.yml +++ b/.github/workflows/dart_test.yml @@ -56,7 +56,7 @@ jobs: - name: Build FlowySDK working-directory: frontend run: | - cargo make --profile development-linux-x86 flowy-sdk-dev + cargo make --profile development-linux-x86_64 flowy-sdk-dev - name: Code Generation working-directory: frontend/app_flowy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2c9c7b133..f574b63137 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,7 +67,7 @@ jobs: working-directory: frontend run: | flutter config --enable-linux-desktop - cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86 appflowy + cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy - name: Upload Release Asset id: upload-release-asset diff --git a/.github/workflows/rust_lint.yml b/.github/workflows/rust_lint.yml index 543f7f5b2e..4f364a616a 100644 --- a/.github/workflows/rust_lint.yml +++ b/.github/workflows/rust_lint.yml @@ -30,7 +30,7 @@ jobs: - name: Build FlowySDK working-directory: frontend run: | - cargo make --profile development-linux-x86 flowy-sdk-dev + cargo make --profile development-linux-x86_64 flowy-sdk-dev - run: rustup component add rustfmt working-directory: frontend/rust-lib diff --git a/.gitignore b/.gitignore index ab5f64486f..c956a0ad13 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,8 @@ frontend/.vscode/* !frontend/.vscode/tasks.json !frontend/.vscode/launch.json !frontend/.vscode/extensions.json -!frontend/.vscode/*.code-snippets \ No newline at end of file +!frontend/.vscode/*.code-snippets + +# Commit the highest level pubspec.lock, but ignore the others +pubspec.lock +!frontend/app_flowy/pubspec.lock diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index 7fed48507b..0000000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx --no -- commitlint --edit diff --git a/Makefile.toml b/Makefile.toml deleted file mode 100644 index 2e75c10277..0000000000 --- a/Makefile.toml +++ /dev/null @@ -1,35 +0,0 @@ -[tasks.install-commitlint.mac] -script = [ - """ - brew install npm - yarn install - yarn husky install - """, -] -script_runner = "@shell" - -[tasks.install-commitlint.windows] -script = [ - """ - echo "WIP" - """, -] -script_runner = "@duckscript" - -[tasks.install-commitlint.linux] -script = [ - """ - if command -v apt &> /dev/null - then - echo "Installing node.js and yarn (sudo apt install nodejs yarn)" - sudo apt install nodejs yarn - else - echo "Installing node.js and yarn (sudo pacman -S nodejs yarn)" - sudo pacman -S nodejs yarn - fi - - yarn install - yarn husky install - """, -] -script_runner = "@shell" diff --git a/frontend/Brewfile b/frontend/Brewfile deleted file mode 100644 index 470312025d..0000000000 --- a/frontend/Brewfile +++ /dev/null @@ -1,2 +0,0 @@ -brew 'sqlite3' -brew 'rustup-init' diff --git a/frontend/Makefile b/frontend/Makefile deleted file mode 100644 index c393bec0e0..0000000000 --- a/frontend/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.PHONY: flowy_dev_install flowy_clean - -flowy_dev_install: - brew bundle - rustup-init -y --default-toolchain=stable - cargo install --force cargo-make - cargo install --force duckscript_cli - cargo make flowy_dev - - -flowy_clean: - sh ./scripts/clean.sh - - diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 8418c30141..7449bfeef3 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -8,6 +8,7 @@ extend = [ { path = "scripts/makefile/env.toml" }, { path = "scripts/makefile/flutter.toml" }, { path = "scripts/makefile/tool.toml" }, + { path = "scripts/makefile/githooks.toml" }, ] [config] @@ -100,7 +101,7 @@ CRATE_TYPE = "cdylib" SDK_EXT = "dll" APP_ENVIRONMENT = "production" -[env.development-linux-x86] +[env.development-linux-x86_64] TARGET_OS = "linux" RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu" BUILD_FLAG = "debug" @@ -109,7 +110,7 @@ FLUTTER_OUTPUT_DIR = "Debug" SDK_EXT = "so" LINUX_ARCH = "x64" -[env.production-linux-x86] +[env.production-linux-x86_64] BUILD_FLAG = "release" TARGET_OS = "linux" RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu" diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 656c28be2e..2dbb970611 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -173,8 +173,8 @@ "includeTime": " Include time", "dateFormatFriendly": "Month Day,Year", "dateFormatISO": "Year-Month-Day", - "dateFormatLocal": "Month/Month/Day", - "dateFormatUS": "Month/Month/Day", + "dateFormatLocal": "Year/Month/Day", + "dateFormatUS": "Year/Month/Day", "timeFormat": " Time format", "invalidTimeFormat": "Invalid format", "timeFormatTwelveHour": "12 hour", @@ -215,4 +215,4 @@ "timeHintTextInTwentyFourHour": "12:00" } } -} +} \ No newline at end of file diff --git a/frontend/app_flowy/lib/core/grid_notification.dart b/frontend/app_flowy/lib/core/grid_notification.dart index e45bf5efe2..9a2429a417 100644 --- a/frontend/app_flowy/lib/core/grid_notification.dart +++ b/frontend/app_flowy/lib/core/grid_notification.dart @@ -8,7 +8,7 @@ import 'package:flowy_sdk/rust_stream.dart'; import 'notification_helper.dart'; -// Grid +// GridPB typedef GridNotificationCallback = void Function(GridNotification, Either); class GridNotificationParser extends NotificationParser { diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index bafbb46919..e0ab40eea7 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -53,14 +53,14 @@ void _resolveHomeDeps(GetIt getIt) { getIt.registerSingleton(MenuSharedState()); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (user, _) => UserListener(userProfile: user), ); // getIt.registerLazySingleton(() => HomeStackManager()); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (user, _) => WelcomeBloc( userService: UserService(userId: user.id), userWorkspaceListener: UserWorkspaceListener(userProfile: user), @@ -69,21 +69,21 @@ void _resolveHomeDeps(GetIt getIt) { // share getIt.registerLazySingleton(() => ShareService()); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (view, _) => DocShareBloc(view: view, service: getIt())); } void _resolveFolderDeps(GetIt getIt) { //workspace - getIt.registerFactoryParam( + getIt.registerFactoryParam( (user, workspaceId) => WorkspaceListener(user: user, workspaceId: workspaceId)); - // View - getIt.registerFactoryParam( + // ViewPB + getIt.registerFactoryParam( (view, _) => ViewListener(view: view), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (view, _) => ViewBloc( view: view, service: ViewService(), @@ -92,29 +92,29 @@ void _resolveFolderDeps(GetIt getIt) { ); //Menu - getIt.registerFactoryParam( + getIt.registerFactoryParam( (user, workspaceId) => MenuBloc( workspaceId: workspaceId, listener: getIt(param1: user, param2: workspaceId), ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (user, _) => MenuUserBloc(user), ); //Settings - getIt.registerFactoryParam( + getIt.registerFactoryParam( (user, _) => SettingsDialogBloc(user), ); //User - getIt.registerFactoryParam( + getIt.registerFactoryParam( (user, _) => SettingsUserViewBloc(user), ); - // App - getIt.registerFactoryParam( + // AppPB + getIt.registerFactoryParam( (app, _) => AppBloc( app: app, appService: AppService(appId: app.id), @@ -135,7 +135,7 @@ void _resolveFolderDeps(GetIt getIt) { void _resolveDocDeps(GetIt getIt) { // Doc - getIt.registerFactoryParam( + getIt.registerFactoryParam( (view, _) => DocumentBloc( view: view, service: DocumentService(), @@ -146,8 +146,8 @@ void _resolveDocDeps(GetIt getIt) { } void _resolveGridDeps(GetIt getIt) { - // Grid - getIt.registerFactoryParam( + // GridPB + getIt.registerFactoryParam( (view, _) => GridBloc(view: view), ); @@ -165,31 +165,31 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (context, _) => TextCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (context, _) => SelectOptionCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (context, _) => NumberCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (context, _) => DateCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam( (cellData, _) => CheckboxCellBloc( service: CellService(), cellContext: cellData, diff --git a/frontend/app_flowy/lib/user/application/auth_service.dart b/frontend/app_flowy/lib/user/application/auth_service.dart index 28f7dc5d0e..c2ce625ccf 100644 --- a/frontend/app_flowy/lib/user/application/auth_service.dart +++ b/frontend/app_flowy/lib/user/application/auth_service.dart @@ -1,21 +1,21 @@ import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show SignInPayload, SignUpPayload, UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show SignInPayloadPB, SignUpPayloadPB, UserProfilePB; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; class AuthService { - Future> signIn({required String? email, required String? password}) { + Future> signIn({required String? email, required String? password}) { // - final request = SignInPayload.create() + final request = SignInPayloadPB.create() ..email = email ?? '' ..password = password ?? ''; return UserEventSignIn(request).send(); } - Future> signUp( + Future> signUp( {required String? name, required String? password, required String? email}) { - final request = SignUpPayload.create() + final request = SignUpPayloadPB.create() ..email = email ?? '' ..name = name ?? '' ..password = password ?? ''; diff --git a/frontend/app_flowy/lib/user/application/sign_in_bloc.dart b/frontend/app_flowy/lib/user/application/sign_in_bloc.dart index 45b6ee1eb5..1b5838d9c4 100644 --- a/frontend/app_flowy/lib/user/application/sign_in_bloc.dart +++ b/frontend/app_flowy/lib/user/application/sign_in_bloc.dart @@ -2,7 +2,7 @@ import 'package:app_flowy/user/application/auth_service.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -69,7 +69,7 @@ class SignInState with _$SignInState { required bool isSubmitting, required Option passwordError, required Option emailError, - required Option> successOrFail, + required Option> successOrFail, }) = _SignInState; factory SignInState.initial() => SignInState( diff --git a/frontend/app_flowy/lib/user/application/sign_up_bloc.dart b/frontend/app_flowy/lib/user/application/sign_up_bloc.dart index b94a02bf09..0c103fd4d7 100644 --- a/frontend/app_flowy/lib/user/application/sign_up_bloc.dart +++ b/frontend/app_flowy/lib/user/application/sign_up_bloc.dart @@ -2,7 +2,7 @@ import 'package:app_flowy/user/application/auth_service.dart'; import 'package:dartz/dartz.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -120,7 +120,7 @@ class SignUpState with _$SignUpState { required Option passwordError, required Option repeatPasswordError, required Option emailError, - required Option> successOrFail, + required Option> successOrFail, }) = _SignUpState; factory SignUpState.initial() => SignUpState( diff --git a/frontend/app_flowy/lib/user/application/user_listener.dart b/frontend/app_flowy/lib/user/application/user_listener.dart index 89fc393e2d..5483926e71 100644 --- a/frontend/app_flowy/lib/user/application/user_listener.dart +++ b/frontend/app_flowy/lib/user/application/user_listener.dart @@ -13,7 +13,7 @@ import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user; import 'package:flowy_sdk/rust_stream.dart'; -typedef UserProfileNotifyValue = Either; +typedef UserProfileNotifyValue = Either; typedef AuthNotifyValue = Either; class UserListener { @@ -22,9 +22,9 @@ class UserListener { PublishNotifier? _profileNotifier = PublishNotifier(); UserNotificationParser? _userParser; - final UserProfile _userProfile; + final UserProfilePB _userProfile; UserListener({ - required UserProfile userProfile, + required UserProfilePB userProfile, }) : _userProfile = userProfile; void start({ @@ -65,7 +65,7 @@ class UserListener { break; case user.UserNotification.UserProfileUpdated: result.fold( - (payload) => _profileNotifier?.value = left(UserProfile.fromBuffer(payload)), + (payload) => _profileNotifier?.value = left(UserProfilePB.fromBuffer(payload)), (error) => _profileNotifier?.value = right(error), ); break; @@ -75,8 +75,8 @@ class UserListener { } } -typedef WorkspaceListNotifyValue = Either, FlowyError>; -typedef WorkspaceSettingNotifyValue = Either; +typedef WorkspaceListNotifyValue = Either, FlowyError>; +typedef WorkspaceSettingNotifyValue = Either; class UserWorkspaceListener { PublishNotifier? _authNotifier = PublishNotifier(); @@ -84,10 +84,10 @@ class UserWorkspaceListener { PublishNotifier? _settingChangedNotifier = PublishNotifier(); FolderNotificationListener? _listener; - final UserProfile _userProfile; + final UserProfilePB _userProfile; UserWorkspaceListener({ - required UserProfile userProfile, + required UserProfilePB userProfile, }) : _userProfile = userProfile; void start({ @@ -119,13 +119,13 @@ class UserWorkspaceListener { case FolderNotification.UserDeleteWorkspace: case FolderNotification.WorkspaceListUpdated: result.fold( - (payload) => _workspacesChangedNotifier?.value = left(RepeatedWorkspace.fromBuffer(payload).items), + (payload) => _workspacesChangedNotifier?.value = left(RepeatedWorkspacePB.fromBuffer(payload).items), (error) => _workspacesChangedNotifier?.value = right(error), ); break; case FolderNotification.WorkspaceSetting: result.fold( - (payload) => _settingChangedNotifier?.value = left(CurrentWorkspaceSetting.fromBuffer(payload)), + (payload) => _settingChangedNotifier?.value = left(CurrentWorkspaceSettingPB.fromBuffer(payload)), (error) => _settingChangedNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/user/application/user_service.dart b/frontend/app_flowy/lib/user/application/user_service.dart index 8417a069f9..48bea6aa41 100644 --- a/frontend/app_flowy/lib/user/application/user_service.dart +++ b/frontend/app_flowy/lib/user/application/user_service.dart @@ -11,7 +11,7 @@ class UserService { UserService({ required this.userId, }); - Future> getUserProfile({required String userId}) { + Future> getUserProfile({required String userId}) { return UserEventGetUserProfile().send(); } @@ -20,7 +20,7 @@ class UserService { String? password, String? email, }) { - var payload = UpdateUserProfilePayload.create()..id = userId; + var payload = UpdateUserProfilePayloadPB.create()..id = userId; if (name != null) { payload.name = name; @@ -49,8 +49,8 @@ class UserService { return UserEventInitUser().send(); } - Future, FlowyError>> getWorkspaces() { - final request = WorkspaceId.create(); + Future, FlowyError>> getWorkspaces() { + final request = WorkspaceIdPB.create(); return FolderEventReadWorkspaces(request).send().then((result) { return result.fold( @@ -60,8 +60,8 @@ class UserService { }); } - Future> openWorkspace(String workspaceId) { - final request = WorkspaceId.create()..value = workspaceId; + Future> openWorkspace(String workspaceId) { + final request = WorkspaceIdPB.create()..value = workspaceId; return FolderEventOpenWorkspace(request).send().then((result) { return result.fold( (workspace) => left(workspace), @@ -70,8 +70,8 @@ class UserService { }); } - Future> createWorkspace(String name, String desc) { - final request = CreateWorkspacePayload.create() + Future> createWorkspace(String name, String desc) { + final request = CreateWorkspacePayloadPB.create() ..name = name ..desc = desc; return FolderEventCreateWorkspace(request).send().then((result) { diff --git a/frontend/app_flowy/lib/user/application/user_settings_service.dart b/frontend/app_flowy/lib/user/application/user_settings_service.dart index eb93ab150d..28309d202c 100644 --- a/frontend/app_flowy/lib/user/application/user_settings_service.dart +++ b/frontend/app_flowy/lib/user/application/user_settings_service.dart @@ -5,11 +5,11 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart'; class UserSettingsService { - Future getAppearanceSettings() async { + Future getAppearanceSettings() async { final result = await UserEventGetAppearanceSetting().send(); return result.fold( - (AppearanceSettings setting) { + (AppearanceSettingsPB setting) { return setting; }, (error) { @@ -18,7 +18,7 @@ class UserSettingsService { ); } - Future> setAppearanceSettings(AppearanceSettings settings) { + Future> setAppearanceSettings(AppearanceSettingsPB settings) { return UserEventSetAppearanceSetting(settings).send(); } } diff --git a/frontend/app_flowy/lib/user/domain/auth_state.dart b/frontend/app_flowy/lib/user/domain/auth_state.dart index e2d3a33b09..ae0c259573 100644 --- a/frontend/app_flowy/lib/user/domain/auth_state.dart +++ b/frontend/app_flowy/lib/user/domain/auth_state.dart @@ -1,11 +1,11 @@ -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'auth_state.freezed.dart'; @freezed class AuthState with _$AuthState { - const factory AuthState.authenticated(UserProfile userProfile) = Authenticated; + const factory AuthState.authenticated(UserProfilePB userProfile) = Authenticated; const factory AuthState.unauthenticated(FlowyError error) = Unauthenticated; const factory AuthState.initial() = _Initial; } diff --git a/frontend/app_flowy/lib/user/presentation/router.dart b/frontend/app_flowy/lib/user/presentation/router.dart index 3bcfa11502..2928154ebe 100644 --- a/frontend/app_flowy/lib/user/presentation/router.dart +++ b/frontend/app_flowy/lib/user/presentation/router.dart @@ -7,7 +7,7 @@ import 'package:app_flowy/user/presentation/welcome_screen.dart'; import 'package:app_flowy/workspace/presentation/home/home_screen.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:flowy_infra_ui/widget/route/animation.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart'; import 'package:flutter/material.dart'; @@ -16,7 +16,7 @@ class AuthRouter { // TODO: implement showForgetPasswordScreen } - void pushWelcomeScreen(BuildContext context, UserProfile userProfile) { + void pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) { getIt().pushWelcomeScreen(context, userProfile); } @@ -28,7 +28,7 @@ class AuthRouter { ); } - void pushHomeScreen(BuildContext context, UserProfile profile, CurrentWorkspaceSetting workspaceSetting) { + void pushHomeScreen(BuildContext context, UserProfilePB profile, CurrentWorkspaceSettingPB workspaceSetting) { Navigator.push( context, PageRoutes.fade(() => HomeScreen(profile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001), @@ -37,7 +37,7 @@ class AuthRouter { } class SplashRoute { - Future pushWelcomeScreen(BuildContext context, UserProfile userProfile) async { + Future pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) async { final screen = WelcomeScreen(userProfile: userProfile); final workspaceId = await Navigator.of(context).push( PageRoutes.fade( @@ -49,7 +49,7 @@ class SplashRoute { pushHomeScreen(context, userProfile, workspaceId); } - void pushHomeScreen(BuildContext context, UserProfile userProfile, CurrentWorkspaceSetting workspaceSetting) { + void pushHomeScreen(BuildContext context, UserProfilePB userProfile, CurrentWorkspaceSettingPB workspaceSetting) { Navigator.push( context, PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001), diff --git a/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart b/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart index baa8ef0ccb..ee3600e782 100644 --- a/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart @@ -10,7 +10,7 @@ import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:dartz/dartz.dart'; @@ -39,7 +39,7 @@ class SignInScreen extends StatelessWidget { ); } - void _handleSuccessOrFail(Either result, BuildContext context) { + void _handleSuccessOrFail(Either result, BuildContext context) { result.fold( (user) => router.pushWelcomeScreen(context, user), (error) => showSnapBar(context, error.msg), diff --git a/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart b/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart index 6a0b8f1d85..d0cb7f8b90 100644 --- a/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart @@ -8,7 +8,7 @@ import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -36,7 +36,7 @@ class SignUpScreen extends StatelessWidget { ); } - void _handleSuccessOrFail(BuildContext context, Either result) { + void _handleSuccessOrFail(BuildContext context, Either result) { result.fold( (user) => router.pushWelcomeScreen(context, user), (error) => showSnapBar(context, error.msg), diff --git a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart index c8fb9ba06b..6e3ae5ea52 100644 --- a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart @@ -116,8 +116,8 @@ class _SkipLogInScreenState extends State { void _openCurrentWorkspace( BuildContext context, - UserProfile user, - dartz.Either workspacesOrError, + UserProfilePB user, + dartz.Either workspacesOrError, ) { workspacesOrError.fold( (workspaceSetting) { diff --git a/frontend/app_flowy/lib/user/presentation/welcome_screen.dart b/frontend/app_flowy/lib/user/presentation/welcome_screen.dart index 60ec3bf8a2..31b06d8bd1 100644 --- a/frontend/app_flowy/lib/user/presentation/welcome_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/welcome_screen.dart @@ -12,7 +12,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; class WelcomeScreen extends StatelessWidget { - final UserProfile userProfile; + final UserProfilePB userProfile; const WelcomeScreen({ Key? key, required this.userProfile, @@ -65,7 +65,7 @@ class WelcomeScreen extends StatelessWidget { ); } - Widget _renderList(List workspaces) { + Widget _renderList(List workspaces) { return Expanded( child: StyledListView( itemBuilder: (BuildContext context, int index) { @@ -80,7 +80,7 @@ class WelcomeScreen extends StatelessWidget { ); } - void _handleOnPress(BuildContext context, Workspace workspace) { + void _handleOnPress(BuildContext context, WorkspacePB workspace) { context.read().add(WelcomeEvent.openWorkspace(workspace)); Navigator.of(context).pop(workspace.id); @@ -88,8 +88,8 @@ class WelcomeScreen extends StatelessWidget { } class WorkspaceItem extends StatelessWidget { - final Workspace workspace; - final void Function(Workspace workspace) onPressed; + final WorkspacePB workspace; + final void Function(WorkspacePB workspace) onPressed; const WorkspaceItem({Key? key, required this.workspace, required this.onPressed}) : super(key: key); @override diff --git a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart index 18f93abd6f..e8d2335168 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart @@ -18,7 +18,7 @@ import 'package:dartz/dartz.dart'; part 'app_bloc.freezed.dart'; class AppBloc extends Bloc { - final App app; + final AppPB app; final AppService appService; final AppListener appListener; @@ -103,7 +103,7 @@ class AppBloc extends Bloc { return super.close(); } - Future _didReceiveViewUpdated(List views, Emitter emit) async { + Future _didReceiveViewUpdated(List views, Emitter emit) async { final latestCreatedView = state.latestCreatedView; AppState newState = state.copyWith(views: views); if (latestCreatedView != null) { @@ -139,20 +139,20 @@ class AppEvent with _$AppEvent { ) = CreateView; const factory AppEvent.delete() = Delete; const factory AppEvent.rename(String newName) = Rename; - const factory AppEvent.didReceiveViewUpdated(List views) = ReceiveViews; - const factory AppEvent.appDidUpdate(App app) = AppDidUpdate; + const factory AppEvent.didReceiveViewUpdated(List views) = ReceiveViews; + const factory AppEvent.appDidUpdate(AppPB app) = AppDidUpdate; } @freezed class AppState with _$AppState { const factory AppState({ - required App app, - required List views, - View? latestCreatedView, + required AppPB app, + required List views, + ViewPB? latestCreatedView, required Either successOrFailure, }) = _AppState; - factory AppState.initial(App app) => AppState( + factory AppState.initial(AppPB app) => AppState( app: app, views: [], successOrFailure: left(unit), @@ -161,8 +161,8 @@ class AppState with _$AppState { class AppViewDataContext extends ChangeNotifier { final String appId; - final ValueNotifier> _viewsNotifier = ValueNotifier([]); - final ValueNotifier _selectedViewNotifier = ValueNotifier(null); + final ValueNotifier> _viewsNotifier = ValueNotifier([]); + final ValueNotifier _selectedViewNotifier = ValueNotifier(null); VoidCallback? _menuSharedStateListener; ExpandableController expandController = ExpandableController(initialExpanded: false); @@ -173,7 +173,7 @@ class AppViewDataContext extends ChangeNotifier { }); } - VoidCallback addSelectedViewChangeListener(void Function(View?) callback) { + VoidCallback addSelectedViewChangeListener(void Function(ViewPB?) callback) { listener() { callback(_selectedViewNotifier.value); } @@ -186,7 +186,7 @@ class AppViewDataContext extends ChangeNotifier { _selectedViewNotifier.removeListener(listener); } - void _setLatestView(View? view) { + void _setLatestView(ViewPB? view) { view?.freeze(); if (_selectedViewNotifier.value != view) { @@ -196,9 +196,9 @@ class AppViewDataContext extends ChangeNotifier { } } - View? get selectedView => _selectedViewNotifier.value; + ViewPB? get selectedView => _selectedViewNotifier.value; - set views(List views) { + set views(List views) { if (_viewsNotifier.value != views) { _viewsNotifier.value = views; _expandIfNeed(); @@ -206,9 +206,9 @@ class AppViewDataContext extends ChangeNotifier { } } - UnmodifiableListView get views => UnmodifiableListView(_viewsNotifier.value); + UnmodifiableListView get views => UnmodifiableListView(_viewsNotifier.value); - VoidCallback addViewsChangeListener(void Function(UnmodifiableListView) callback) { + VoidCallback addViewsChangeListener(void Function(UnmodifiableListView) callback) { listener() { callback(views); } diff --git a/frontend/app_flowy/lib/workspace/application/app/app_listener.dart b/frontend/app_flowy/lib/workspace/application/app/app_listener.dart index 6a30b270af..6edf1a4df2 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_listener.dart @@ -10,8 +10,8 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart'; import 'package:flowy_sdk/rust_stream.dart'; -typedef AppDidUpdateCallback = void Function(App app); -typedef ViewsDidChangeCallback = void Function(Either, FlowyError> viewsOrFailed); +typedef AppDidUpdateCallback = void Function(AppPB app); +typedef ViewsDidChangeCallback = void Function(Either, FlowyError> viewsOrFailed); class AppListener { StreamSubscription? _subscription; @@ -37,7 +37,7 @@ class AppListener { if (_viewsChanged != null) { result.fold( (payload) { - final repeatedView = RepeatedView.fromBuffer(payload); + final repeatedView = RepeatedViewPB.fromBuffer(payload); _viewsChanged!(left(repeatedView.items)); }, (error) => _viewsChanged!(right(error)), @@ -48,7 +48,7 @@ class AppListener { if (_updated != null) { result.fold( (payload) { - final app = App.fromBuffer(payload); + final app = AppPB.fromBuffer(payload); _updated!(app); }, (error) => Log.error(error), diff --git a/frontend/app_flowy/lib/workspace/application/app/app_service.dart b/frontend/app_flowy/lib/workspace/application/app/app_service.dart index 51513a4032..cc75751e49 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_service.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_service.dart @@ -14,20 +14,20 @@ class AppService { required this.appId, }); - Future> getAppDesc({required String appId}) { - final payload = AppId.create()..value = appId; + Future> getAppDesc({required String appId}) { + final payload = AppIdPB.create()..value = appId; return FolderEventReadApp(payload).send(); } - Future> createView({ + Future> createView({ required String appId, required String name, required String desc, required PluginDataType dataType, required PluginType pluginType, }) { - final payload = CreateViewPayload.create() + final payload = CreateViewPayloadPB.create() ..belongToId = appId ..name = name ..desc = desc @@ -37,8 +37,8 @@ class AppService { return FolderEventCreateView(payload).send(); } - Future, FlowyError>> getViews({required String appId}) { - final payload = AppId.create()..value = appId; + Future, FlowyError>> getViews({required String appId}) { + final payload = AppIdPB.create()..value = appId; return FolderEventReadApp(payload).send().then((result) { return result.fold( @@ -49,12 +49,12 @@ class AppService { } Future> delete({required String appId}) { - final request = AppId.create()..value = appId; + final request = AppIdPB.create()..value = appId; return FolderEventDeleteApp(request).send(); } Future> updateApp({required String appId, String? name}) { - UpdateAppPayload payload = UpdateAppPayload.create()..appId = appId; + UpdateAppPayloadPB payload = UpdateAppPayloadPB.create()..appId = appId; if (name != null) { payload.name = name; @@ -67,7 +67,7 @@ class AppService { required int fromIndex, required int toIndex, }) { - final payload = MoveFolderItemPayload.create() + final payload = MoveFolderItemPayloadPB.create() ..itemId = viewId ..from = fromIndex ..to = toIndex diff --git a/frontend/app_flowy/lib/workspace/application/appearance.dart b/frontend/app_flowy/lib/workspace/application/appearance.dart index f3ff7801ce..0e47fa2be6 100644 --- a/frontend/app_flowy/lib/workspace/application/appearance.dart +++ b/frontend/app_flowy/lib/workspace/application/appearance.dart @@ -9,7 +9,7 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; class AppearanceSettingModel extends ChangeNotifier with EquatableMixin { - AppearanceSettings setting; + AppearanceSettingsPB setting; AppTheme _theme; Locale _locale; Timer? _saveOperation; diff --git a/frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart b/frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart index 1c77fa1cbd..8be4c40d85 100644 --- a/frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/doc/doc_bloc.dart @@ -17,7 +17,7 @@ part 'doc_bloc.freezed.dart'; typedef FlutterQuillDocument = Document; class DocumentBloc extends Bloc { - final View view; + final ViewPB view; final DocumentService service; final ViewListener listener; diff --git a/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart b/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart index 992dfdeb63..659a99e371 100644 --- a/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart +++ b/frontend/app_flowy/lib/workspace/application/doc/doc_service.dart @@ -6,24 +6,24 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-sync/text_block.pb.dart'; class DocumentService { - Future> openDocument({ + Future> openDocument({ required String docId, }) async { - await FolderEventSetLatestView(ViewId(value: docId)).send(); + await FolderEventSetLatestView(ViewIdPB(value: docId)).send(); - final payload = TextBlockId(value: docId); + final payload = TextBlockIdPB(value: docId); return TextBlockEventGetBlockData(payload).send(); } - Future> composeDelta({required String docId, required String data}) { - final payload = TextBlockDelta.create() + Future> composeDelta({required String docId, required String data}) { + final payload = TextBlockDeltaPB.create() ..blockId = docId ..deltaStr = data; return TextBlockEventApplyDelta(payload).send(); } Future> closeDocument({required String docId}) { - final request = ViewId(value: docId); + final request = ViewIdPB(value: docId); return FolderEventCloseView(request).send(); } } diff --git a/frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart b/frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart index 90c041eb75..ba3c7676c6 100644 --- a/frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/doc/share_bloc.dart @@ -13,7 +13,7 @@ part 'share_bloc.freezed.dart'; class DocShareBloc extends Bloc { ShareService service; - View view; + ViewPB view; DocShareBloc({required this.view, required this.service}) : super(const DocShareState.initial()) { on((event, emit) async { await event.map( @@ -33,7 +33,7 @@ class DocShareBloc extends Bloc { }); } - ExportData _convertDeltaToMarkdown(ExportData value) { + ExportDataPB _convertDeltaToMarkdown(ExportDataPB value) { final result = deltaToMarkdown(value.data); value.data = result; writeFile(result); @@ -73,5 +73,5 @@ class DocShareEvent with _$DocShareEvent { class DocShareState with _$DocShareState { const factory DocShareState.initial() = _Initial; const factory DocShareState.loading() = _Loading; - const factory DocShareState.finish(Either successOrFail) = _Finish; + const factory DocShareState.finish(Either successOrFail) = _Finish; } diff --git a/frontend/app_flowy/lib/workspace/application/doc/share_service.dart b/frontend/app_flowy/lib/workspace/application/doc/share_service.dart index 7e5545f109..db6ad406b7 100644 --- a/frontend/app_flowy/lib/workspace/application/doc/share_service.dart +++ b/frontend/app_flowy/lib/workspace/application/doc/share_service.dart @@ -5,23 +5,23 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-text-block/protobuf.dart'; class ShareService { - Future> export(String docId, ExportType type) { - final request = ExportPayload.create() + Future> export(String docId, ExportType type) { + final request = ExportPayloadPB.create() ..viewId = docId ..exportType = type; return TextBlockEventExportDocument(request).send(); } - Future> exportText(String docId) { + Future> exportText(String docId) { return export(docId, ExportType.Text); } - Future> exportMarkdown(String docId) { + Future> exportMarkdown(String docId) { return export(docId, ExportType.Markdown); } - Future> exportURL(String docId) { + Future> exportURL(String docId) { return export(docId, ExportType.Link); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart b/frontend/app_flowy/lib/workspace/application/grid/block/block_cache.dart similarity index 73% rename from frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart rename to frontend/app_flowy/lib/workspace/application/grid/block/block_cache.dart index 3fb734db70..3c99c0e0e1 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/block/block_cache.dart @@ -6,24 +6,25 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; import 'block_listener.dart'; -class GridBlockCacheService { +/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information +class GridBlockCache { final String gridId; - final GridBlock block; - late GridRowCacheService _rowCache; + final GridBlockPB block; + late GridRowCache _rowCache; late GridBlockListener _listener; - List get rows => _rowCache.rows; - GridRowCacheService get rowCache => _rowCache; + List get rows => _rowCache.rows; + GridRowCache get rowCache => _rowCache; - GridBlockCacheService({ + GridBlockCache({ required this.gridId, required this.block, required GridFieldCache fieldCache, }) { - _rowCache = GridRowCacheService( + _rowCache = GridRowCache( gridId: gridId, block: block, - delegate: GridRowCacheDelegateImpl(fieldCache), + notifier: GridRowCacheFieldNotifierImpl(fieldCache), ); _listener = GridBlockListener(blockId: block.id); diff --git a/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart index 63accc38d0..91f93c61fe 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/block/block_listener.dart @@ -7,7 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart'; -typedef GridBlockUpdateNotifierValue = Either, FlowyError>; +typedef GridBlockUpdateNotifierValue = Either, FlowyError>; class GridBlockListener { final String blockId; @@ -33,7 +33,7 @@ class GridBlockListener { switch (ty) { case GridNotification.DidUpdateGridBlock: result.fold( - (payload) => _rowsUpdateNotifier?.value = left([GridRowsChangeset.fromBuffer(payload)]), + (payload) => _rowsUpdateNotifier?.value = left([GridBlockChangesetPB.fromBuffer(payload)]), (error) => _rowsUpdateNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart deleted file mode 100644 index ccf47fddb3..0000000000 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart +++ /dev/null @@ -1,109 +0,0 @@ -part of 'cell_service.dart'; - -typedef GridCellMap = LinkedHashMap; - -class _GridCellCacheObject { - _GridCellCacheKey key; - dynamic object; - _GridCellCacheObject({ - required this.key, - required this.object, - }); -} - -class _GridCellCacheKey { - final String fieldId; - final String rowId; - _GridCellCacheKey({ - required this.fieldId, - required this.rowId, - }); -} - -abstract class GridCellCacheDelegate { - void onFieldUpdated(void Function(Field) callback); -} - -class GridCellCacheService { - final String gridId; - final GridCellCacheDelegate delegate; - - /// fieldId: {objectId: callback} - final Map>> _fieldListenerByFieldId = {}; - - /// fieldId: {cacheKey: cacheData} - final Map> _cellDataByFieldId = {}; - GridCellCacheService({ - required this.gridId, - required this.delegate, - }) { - delegate.onFieldUpdated((field) { - _cellDataByFieldId.remove(field.id); - final map = _fieldListenerByFieldId[field.id]; - if (map != null) { - for (final callbacks in map.values) { - for (final callback in callbacks) { - callback(); - } - } - } - }); - } - - void addFieldListener(_GridCellCacheKey cacheKey, VoidCallback onFieldChanged) { - var map = _fieldListenerByFieldId[cacheKey.fieldId]; - if (map == null) { - _fieldListenerByFieldId[cacheKey.fieldId] = {}; - map = _fieldListenerByFieldId[cacheKey.fieldId]; - map![cacheKey.rowId] = [onFieldChanged]; - } else { - var objects = map[cacheKey.rowId]; - if (objects == null) { - map[cacheKey.rowId] = [onFieldChanged]; - } else { - objects.add(onFieldChanged); - } - } - } - - void removeFieldListener(_GridCellCacheKey cacheKey, VoidCallback fn) { - var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId]; - final index = callbacks?.indexWhere((callback) => callback == fn); - if (index != null && index != -1) { - callbacks?.removeAt(index); - } - } - - void insert(T item) { - var map = _cellDataByFieldId[item.key.fieldId]; - if (map == null) { - _cellDataByFieldId[item.key.fieldId] = {}; - map = _cellDataByFieldId[item.key.fieldId]; - } - - map![item.key.rowId] = item.object; - } - - T? get(_GridCellCacheKey key) { - final map = _cellDataByFieldId[key.fieldId]; - if (map == null) { - return null; - } else { - final object = map[key.rowId]; - if (object is T) { - return object; - } else { - if (object != null) { - Log.error("Cache data type does not match the cache data type"); - } - - return null; - } - } - } - - Future dispose() async { - _fieldListenerByFieldId.clear(); - _cellDataByFieldId.clear(); - } -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_cache.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_cache.dart new file mode 100644 index 0000000000..1f14c7c54a --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_cache.dart @@ -0,0 +1,70 @@ +part of 'cell_service.dart'; + +typedef GridCellMap = LinkedHashMap; + +class GridCell { + dynamic object; + GridCell({ + required this.object, + }); +} + +/// Use to index the cell in the grid. +/// We use [fieldId + rowId] to identify the cell. +class GridCellCacheKey { + final String fieldId; + final String rowId; + GridCellCacheKey({ + required this.fieldId, + required this.rowId, + }); +} + +/// GridCellCache is used to cache cell data of each block. +/// We use GridCellCacheKey to index the cell in the cache. +/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid +/// for more information +class GridCellCache { + final String gridId; + + /// fieldId: {cacheKey: GridCell} + final Map> _cellDataByFieldId = {}; + GridCellCache({ + required this.gridId, + }); + + void remove(String fieldId) { + _cellDataByFieldId.remove(fieldId); + } + + void insert(GridCellCacheKey key, T value) { + var map = _cellDataByFieldId[key.fieldId]; + if (map == null) { + _cellDataByFieldId[key.fieldId] = {}; + map = _cellDataByFieldId[key.fieldId]; + } + + map![key.rowId] = value.object; + } + + T? get(GridCellCacheKey key) { + final map = _cellDataByFieldId[key.fieldId]; + if (map == null) { + return null; + } else { + final value = map[key.rowId]; + if (value is T) { + return value; + } else { + if (value != null) { + Log.error("Expected value type: $T, but receive $value"); + } + return null; + } + } + } + + Future dispose() async { + _cellDataByFieldId.clear(); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart index 676e3f66d0..c4b3430199 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart @@ -3,60 +3,28 @@ part of 'cell_service.dart'; abstract class IGridCellDataConfig { // The cell data will reload if it receives the field's change notification. bool get reloadOnFieldChanged; - - // When the reloadOnCellChanged is true, it will load the cell data after user input. - // For example: The number cell reload the cell data that carries the format - // user input: 12 - // cell display: $12 - bool get reloadOnCellChanged; } -class GridCellDataConfig implements IGridCellDataConfig { - @override - final bool reloadOnCellChanged; - - @override - final bool reloadOnFieldChanged; - - const GridCellDataConfig({ - this.reloadOnCellChanged = false, - this.reloadOnFieldChanged = false, - }); -} - -abstract class IGridCellDataLoader { - Future loadData(); - - IGridCellDataConfig get config; -} - -abstract class ICellDataParser { +abstract class IGridCellDataParser { T? parserData(List data); } -class GridCellDataLoader extends IGridCellDataLoader { +class GridCellDataLoader { final CellService service = CellService(); - final GridCell gridCell; - final ICellDataParser parser; - - @override - final IGridCellDataConfig config; + final GridCellIdentifier cellId; + final IGridCellDataParser parser; + final bool reloadOnFieldChanged; GridCellDataLoader({ - required this.gridCell, + required this.cellId, required this.parser, - this.config = const GridCellDataConfig(), + this.reloadOnFieldChanged = false, }); - @override Future loadData() { - final fut = service.getCell( - gridId: gridCell.gridId, - fieldId: gridCell.field.id, - rowId: gridCell.rowId, - ); + final fut = service.getCell(cellId: cellId); return fut.then( - (result) => result.fold((Cell cell) { + (result) => result.fold((GridCellPB cell) { try { return parser.parserData(cell.data); } catch (e, s) { @@ -72,30 +40,7 @@ class GridCellDataLoader extends IGridCellDataLoader { } } -class SelectOptionCellDataLoader extends IGridCellDataLoader { - final SelectOptionService service; - final GridCell gridCell; - SelectOptionCellDataLoader({ - required this.gridCell, - }) : service = SelectOptionService(gridCell: gridCell); - @override - Future loadData() async { - return service.getOpitonContext().then((result) { - return result.fold( - (data) => data, - (err) { - Log.error(err); - return null; - }, - ); - }); - } - - @override - IGridCellDataConfig get config => const GridCellDataConfig(reloadOnFieldChanged: true); -} - -class StringCellDataParser implements ICellDataParser { +class StringCellDataParser implements IGridCellDataParser { @override String? parserData(List data) { final s = utf8.decode(data); @@ -103,32 +48,32 @@ class StringCellDataParser implements ICellDataParser { } } -class DateCellDataParser implements ICellDataParser { +class DateCellDataParser implements IGridCellDataParser { @override - DateCellData? parserData(List data) { + DateCellDataPB? parserData(List data) { if (data.isEmpty) { return null; } - return DateCellData.fromBuffer(data); + return DateCellDataPB.fromBuffer(data); } } -class SelectOptionCellDataParser implements ICellDataParser { +class SelectOptionCellDataParser implements IGridCellDataParser { @override - SelectOptionCellData? parserData(List data) { + SelectOptionCellDataPB? parserData(List data) { if (data.isEmpty) { return null; } - return SelectOptionCellData.fromBuffer(data); + return SelectOptionCellDataPB.fromBuffer(data); } } -class URLCellDataParser implements ICellDataParser { +class URLCellDataParser implements IGridCellDataParser { @override - URLCellData? parserData(List data) { + URLCellDataPB? parserData(List data) { if (data.isEmpty) { return null; } - return URLCellData.fromBuffer(data); + return URLCellDataPB.fromBuffer(data); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart index 2ad217e062..a38a771158 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart @@ -1,25 +1,22 @@ part of 'cell_service.dart'; -abstract class _GridCellDataPersistence { +/// Save the cell data to disk +/// You can extend this class to do custom operations. For example, the DateCellDataPersistence. +abstract class IGridCellDataPersistence { Future> save(D data); } -class CellDataPersistence implements _GridCellDataPersistence { - final GridCell gridCell; +class CellDataPersistence implements IGridCellDataPersistence { + final GridCellIdentifier cellId; CellDataPersistence({ - required this.gridCell, + required this.cellId, }); final CellService _cellService = CellService(); @override Future> save(String data) async { - final fut = _cellService.updateCell( - gridId: gridCell.gridId, - fieldId: gridCell.field.id, - rowId: gridCell.rowId, - data: data, - ); + final fut = _cellService.updateCell(cellId: cellId, data: data); return fut.then((result) { return result.fold( @@ -35,15 +32,15 @@ class CalendarData with _$CalendarData { const factory CalendarData({required DateTime date, String? time}) = _CalendarData; } -class DateCellDataPersistence implements _GridCellDataPersistence { - final GridCell gridCell; +class DateCellDataPersistence implements IGridCellDataPersistence { + final GridCellIdentifier cellId; DateCellDataPersistence({ - required this.gridCell, + required this.cellId, }); @override Future> save(CalendarData data) { - var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell); + var payload = DateChangesetPayloadPB.create()..cellIdentifier = _makeCellIdPayload(cellId); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); payload.date = date; @@ -61,9 +58,9 @@ class DateCellDataPersistence implements _GridCellDataPersistence } } -CellIdentifierPayload _cellIdentifier(GridCell gridCell) { - return CellIdentifierPayload.create() - ..gridId = gridCell.gridId - ..fieldId = gridCell.field.id - ..rowId = gridCell.rowId; +GridCellIdentifierPayloadPB _makeCellIdPayload(GridCellIdentifier cellId) { + return GridCellIdentifierPayloadPB.create() + ..gridId = cellId.gridId + ..fieldId = cellId.fieldId + ..rowId = cellId.rowId; } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart new file mode 100644 index 0000000000..72f1bc787d --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart @@ -0,0 +1,60 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter/foundation.dart'; + +import 'cell_service.dart'; + +abstract class GridFieldChangedNotifier { + void onFieldChanged(void Function(GridFieldPB) callback); + void dispose(); +} + +/// GridPB's cell helper wrapper that enables each cell will get notified when the corresponding field was changed. +/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen. +class GridCellFieldNotifier { + /// fieldId: {objectId: callback} + final Map>> _fieldListenerByFieldId = {}; + + GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) { + notifier.onFieldChanged( + (field) { + final map = _fieldListenerByFieldId[field.id]; + if (map != null) { + for (final callbacks in map.values) { + for (final callback in callbacks) { + callback(); + } + } + } + }, + ); + } + + /// + void register(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) { + var map = _fieldListenerByFieldId[cacheKey.fieldId]; + if (map == null) { + _fieldListenerByFieldId[cacheKey.fieldId] = {}; + map = _fieldListenerByFieldId[cacheKey.fieldId]; + map![cacheKey.rowId] = [onFieldChanged]; + } else { + var objects = map[cacheKey.rowId]; + if (objects == null) { + map[cacheKey.rowId] = [onFieldChanged]; + } else { + objects.add(onFieldChanged); + } + } + } + + void unregister(GridCellCacheKey cacheKey, VoidCallback fn) { + var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId]; + final index = callbacks?.indexWhere((callback) => callback == fn); + if (index != null && index != -1) { + callbacks?.removeAt(index); + } + } + + Future dispose() async { + _fieldListenerByFieldId.clear(); + } +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart index 25c51e9c07..3e8746c20a 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart @@ -1,26 +1,29 @@ import 'dart:async'; import 'dart:collection'; +import 'package:app_flowy/workspace/application/grid/grid_service.dart'; import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; -import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'dart:convert' show utf8; + +import '../../field/type_option/type_option_service.dart'; +import 'cell_field_notifier.dart'; part 'cell_service.freezed.dart'; part 'cell_data_loader.dart'; part 'context_builder.dart'; -part 'cache.dart'; +part 'cell_cache.dart'; part 'cell_data_persistence.dart'; // key: rowId @@ -29,44 +32,46 @@ class CellService { CellService(); Future> updateCell({ - required String gridId, - required String fieldId, - required String rowId, + required GridCellIdentifier cellId, required String data, }) { - final payload = CellChangeset.create() - ..gridId = gridId - ..fieldId = fieldId - ..rowId = rowId - ..cellContentChangeset = data; + final payload = CellChangesetPB.create() + ..gridId = cellId.gridId + ..fieldId = cellId.fieldId + ..rowId = cellId.rowId + ..content = data; return GridEventUpdateCell(payload).send(); } - Future> getCell({ - required String gridId, - required String fieldId, - required String rowId, + Future> getCell({ + required GridCellIdentifier cellId, }) { - final payload = CellIdentifierPayload.create() - ..gridId = gridId - ..fieldId = fieldId - ..rowId = rowId; + final payload = GridCellIdentifierPayloadPB.create() + ..gridId = cellId.gridId + ..fieldId = cellId.fieldId + ..rowId = cellId.rowId; return GridEventGetCell(payload).send(); } } +/// Id of the cell +/// We can locate the cell by using gridId + rowId + field.id. @freezed -class GridCell with _$GridCell { - const factory GridCell({ +class GridCellIdentifier with _$GridCellIdentifier { + const factory GridCellIdentifier({ required String gridId, required String rowId, - required Field field, - }) = _GridCell; + required GridFieldPB field, + }) = _GridCellIdentifier; // ignore: unused_element - const GridCell._(); + const GridCellIdentifier._(); - String cellId() { - return rowId + field.id + "${field.fieldType}"; + String get fieldId => field.id; + + FieldType get fieldType => field.fieldType; + + ValueKey key() { + return ValueKey(rowId + fieldId + "${field.fieldType}"); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart index 00264ad4b0..48e88e56ac 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart @@ -1,154 +1,183 @@ part of 'cell_service.dart'; -typedef GridCellContext = _GridCellContext; -typedef GridSelectOptionCellContext = _GridCellContext; -typedef GridDateCellContext = _GridCellContext; -typedef GridURLCellContext = _GridCellContext; +typedef GridCellController = IGridCellController; +typedef GridSelectOptionCellController = IGridCellController; +typedef GridDateCellController = IGridCellController; +typedef GridURLCellController = IGridCellController; -class GridCellContextBuilder { - final GridCellCacheService _cellCache; - final GridCell _gridCell; - GridCellContextBuilder({ - required GridCellCacheService cellCache, - required GridCell gridCell, +class GridCellControllerBuilder { + final GridCellIdentifier _cellId; + final GridCellCache _cellCache; + final GridFieldCache _fieldCache; + + GridCellControllerBuilder({ + required GridCellIdentifier cellId, + required GridCellCache cellCache, + required GridFieldCache fieldCache, }) : _cellCache = cellCache, - _gridCell = gridCell; + _fieldCache = fieldCache, + _cellId = cellId; - _GridCellContext build() { - switch (_gridCell.field.fieldType) { + IGridCellController build() { + final cellFieldNotifier = GridCellFieldNotifier(notifier: _GridFieldChangedNotifierImpl(_fieldCache)); + + switch (_cellId.fieldType) { case FieldType.Checkbox: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: StringCellDataParser(), ); - return GridCellContext( - gridCell: _gridCell, + return GridCellController( + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.DateTime: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: DateCellDataParser(), - config: const GridCellDataConfig(reloadOnFieldChanged: true), + reloadOnFieldChanged: true, ); - return GridDateCellContext( - gridCell: _gridCell, + return GridDateCellController( + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: DateCellDataPersistence(cellId: _cellId), ); case FieldType.Number: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: StringCellDataParser(), - config: const GridCellDataConfig(reloadOnCellChanged: true, reloadOnFieldChanged: true), + reloadOnFieldChanged: true, ); - return GridCellContext( - gridCell: _gridCell, + return GridCellController( + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.RichText: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: StringCellDataParser(), ); - return GridCellContext( - gridCell: _gridCell, + return GridCellController( + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.MultiSelect: case FieldType.SingleSelect: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: SelectOptionCellDataParser(), - config: const GridCellDataConfig(reloadOnFieldChanged: true), + reloadOnFieldChanged: true, ); - return GridSelectOptionCellContext( - gridCell: _gridCell, + return GridSelectOptionCellController( + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); case FieldType.URL: final cellDataLoader = GridCellDataLoader( - gridCell: _gridCell, + cellId: _cellId, parser: URLCellDataParser(), ); - return GridURLCellContext( - gridCell: _gridCell, + return GridURLCellController( + cellId: _cellId, cellCache: _cellCache, cellDataLoader: cellDataLoader, - cellDataPersistence: CellDataPersistence(gridCell: _gridCell), + fieldNotifier: cellFieldNotifier, + cellDataPersistence: CellDataPersistence(cellId: _cellId), ); } throw UnimplementedError; } } -// T: the type of the CellData -// D: the type of the data that will be save to disk +/// IGridCellController is used to manipulate the cell and receive notifications. +/// * Read/Write cell data +/// * Listen on field/cell notifications. +/// +/// Generic T represents the type of the cell data. +/// Generic D represents the type of data that will be saved to the disk +/// // ignore: must_be_immutable -class _GridCellContext extends Equatable { - final GridCell gridCell; - final GridCellCacheService cellCache; - final _GridCellCacheKey _cacheKey; - final IGridCellDataLoader cellDataLoader; - final _GridCellDataPersistence cellDataPersistence; +class IGridCellController extends Equatable { + final GridCellIdentifier cellId; + final GridCellCache _cellsCache; + final GridCellCacheKey _cacheKey; final FieldService _fieldService; + final GridCellFieldNotifier _fieldNotifier; + final GridCellDataLoader _cellDataLoader; + final IGridCellDataPersistence _cellDataPersistence; late final CellListener _cellListener; - late final ValueNotifier? _cellDataNotifier; + ValueNotifier? _cellDataNotifier; + bool isListening = false; VoidCallback? _onFieldChangedFn; Timer? _loadDataOperation; Timer? _saveDataOperation; + bool _isDispose = false; - _GridCellContext({ - required this.gridCell, - required this.cellCache, - required this.cellDataLoader, - required this.cellDataPersistence, - }) : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id), - _cacheKey = _GridCellCacheKey(rowId: gridCell.rowId, fieldId: gridCell.field.id); + IGridCellController({ + required this.cellId, + required GridCellCache cellCache, + required GridCellFieldNotifier fieldNotifier, + required GridCellDataLoader cellDataLoader, + required IGridCellDataPersistence cellDataPersistence, + }) : _cellsCache = cellCache, + _cellDataLoader = cellDataLoader, + _cellDataPersistence = cellDataPersistence, + _fieldNotifier = fieldNotifier, + _fieldService = FieldService(gridId: cellId.gridId, fieldId: cellId.field.id), + _cacheKey = GridCellCacheKey(rowId: cellId.rowId, fieldId: cellId.field.id); - _GridCellContext clone() { - return _GridCellContext( - gridCell: gridCell, - cellDataLoader: cellDataLoader, - cellCache: cellCache, - cellDataPersistence: cellDataPersistence); + IGridCellController clone() { + return IGridCellController( + cellId: cellId, + cellDataLoader: _cellDataLoader, + cellCache: _cellsCache, + fieldNotifier: _fieldNotifier, + cellDataPersistence: _cellDataPersistence); } - String get gridId => gridCell.gridId; + String get gridId => cellId.gridId; - String get rowId => gridCell.rowId; + String get rowId => cellId.rowId; - String get cellId => gridCell.rowId + gridCell.field.id; + String get fieldId => cellId.field.id; - String get fieldId => gridCell.field.id; + GridFieldPB get field => cellId.field; - Field get field => gridCell.field; + FieldType get fieldType => cellId.field.fieldType; - FieldType get fieldType => gridCell.field.fieldType; - - VoidCallback? startListening({required void Function(T?) onCellChanged}) { + VoidCallback? startListening({required void Function(T?) onCellChanged, VoidCallback? onCellFieldChanged}) { if (isListening) { Log.error("Already started. It seems like you should call clone first"); return null; } - isListening = true; - _cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey)); - _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id); + + _cellDataNotifier = ValueNotifier(_cellsCache.get(_cacheKey)); + _cellListener = CellListener(rowId: cellId.rowId, fieldId: cellId.field.id); + + /// 1.Listen on user edit event and load the new cell data if needed. + /// For example: + /// user input: 12 + /// cell display: $12 _cellListener.start(onCellChanged: (result) { result.fold( (_) => _loadData(), @@ -156,22 +185,27 @@ class _GridCellContext extends Equatable { ); }); - if (cellDataLoader.config.reloadOnFieldChanged) { - _onFieldChangedFn = () { - _loadData(); - }; - cellCache.addFieldListener(_cacheKey, _onFieldChangedFn!); - } + /// 2.Listen on the field event and load the cell data if needed. + _onFieldChangedFn = () { + if (onCellFieldChanged != null) { + onCellFieldChanged(); + } - onCellChangedFn() { - onCellChanged(_cellDataNotifier?.value); - - if (cellDataLoader.config.reloadOnCellChanged) { + /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed + /// For example: + /// ¥12 -> $12 + if (_cellDataLoader.reloadOnFieldChanged) { _loadData(); } - } + }; + _fieldNotifier.register(_cacheKey, _onFieldChangedFn!); + + /// Notify the listener, the cell data was changed. + onCellChangedFn() => onCellChanged(_cellDataNotifier?.value); _cellDataNotifier?.addListener(onCellChangedFn); + + // Return the function pointer that can be used when calling removeListener. return onCellChangedFn; } @@ -179,29 +213,45 @@ class _GridCellContext extends Equatable { _cellDataNotifier?.removeListener(fn); } - T? getCellData({bool loadIfNoCache = true}) { - final data = cellCache.get(_cacheKey); - if (data == null && loadIfNoCache) { + /// Return the cell data. + /// The cell data will be read from the Cache first, and load from disk if it does not exist. + /// You can set [loadIfNotExist] to false (default is true) to disable loading the cell data. + T? getCellData({bool loadIfNotExist = true}) { + final data = _cellsCache.get(_cacheKey); + if (data == null && loadIfNotExist) { _loadData(); } return data; } - Future> getTypeOptionData() { - return _fieldService.getFieldTypeOptionData(fieldType: fieldType); + /// Return the FieldTypeOptionDataPB that can be parsed into corresponding class using the [parser]. + /// [PD] is the type that the parser return. + Future> getFieldTypeOption(P parser) { + return _fieldService.getFieldTypeOptionData(fieldType: fieldType).then((result) { + return result.fold( + (data) => parser.fromBuffer(data.typeOptionData), + (err) => right(err), + ); + }); } + /// Save the cell data to disk + /// You can set [dedeplicate] to true (default is false) to reduce the save operation. + /// It's useful when you call this method when user editing the [TextField]. + /// The default debounce interval is 300 milliseconds. void saveCellData(D data, {bool deduplicate = false, void Function(Option)? resultCallback}) async { if (deduplicate) { _loadDataOperation?.cancel(); - _loadDataOperation = Timer(const Duration(milliseconds: 300), () async { - final result = await cellDataPersistence.save(data); + + _saveDataOperation?.cancel(); + _saveDataOperation = Timer(const Duration(milliseconds: 300), () async { + final result = await _cellDataPersistence.save(data); if (resultCallback != null) { resultCallback(result); } }); } else { - final result = await cellDataPersistence.save(data); + final result = await _cellDataPersistence.save(data); if (resultCallback != null) { resultCallback(result); } @@ -209,26 +259,59 @@ class _GridCellContext extends Equatable { } void _loadData() { + _saveDataOperation?.cancel(); + _loadDataOperation?.cancel(); _loadDataOperation = Timer(const Duration(milliseconds: 10), () { - cellDataLoader.loadData().then((data) { + _cellDataLoader.loadData().then((data) { _cellDataNotifier?.value = data; - cellCache.insert(_GridCellCacheObject(key: _cacheKey, object: data)); + _cellsCache.insert(_cacheKey, GridCell(object: data)); }); }); } void dispose() { + if (_isDispose) { + Log.error("$this should only dispose once"); + return; + } + _isDispose = true; _cellListener.stop(); _loadDataOperation?.cancel(); _saveDataOperation?.cancel(); + _cellDataNotifier = null; if (_onFieldChangedFn != null) { - cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!); + _fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!); _onFieldChangedFn = null; } } @override - List get props => [cellCache.get(_cacheKey) ?? "", cellId]; + List get props => [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id]; +} + +class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier { + final GridFieldCache _cache; + FieldChangesetCallback? _onChangesetFn; + + _GridFieldChangedNotifierImpl(GridFieldCache cache) : _cache = cache; + + @override + void dispose() { + if (_onChangesetFn != null) { + _cache.removeListener(onChangsetListener: _onChangesetFn!); + _onChangesetFn = null; + } + } + + @override + void onFieldChanged(void Function(GridFieldPB p1) callback) { + _onChangesetFn = (GridFieldChangesetPB changeset) { + for (final updatedField in changeset.updatedFields) { + callback(updatedField); + } + }; + _cache.addListener(onChangeset: _onChangesetFn); + } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart index b8e2b13bbc..041e687c9b 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart @@ -6,7 +6,7 @@ import 'cell_service/cell_service.dart'; part 'checkbox_cell_bloc.freezed.dart'; class CheckboxCellBloc extends Bloc { - final GridCellContext cellContext; + final GridCellController cellContext; void Function()? _onCellChangedFn; CheckboxCellBloc({ @@ -67,7 +67,7 @@ class CheckboxCellState with _$CheckboxCellState { required bool isSelected, }) = _CheckboxCellState; - factory CheckboxCellState.initial(GridCellContext context) { + factory CheckboxCellState.initial(GridCellController context) { return CheckboxCellState(isSelected: _isSelected(context.getCellData())); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart index 28f8bf1b10..d99bdbf817 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cal_bloc.dart @@ -5,6 +5,7 @@ import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -16,12 +17,12 @@ import 'package:fixnum/fixnum.dart' as $fixnum; part 'date_cal_bloc.freezed.dart'; class DateCalBloc extends Bloc { - final GridDateCellContext cellContext; + final GridDateCellController cellContext; void Function()? _onCellChangedFn; DateCalBloc({ required DateTypeOption dateTypeOption, - required DateCellData? cellData, + required DateCellDataPB? cellData, required this.cellContext, }) : super(DateCalState.initial(dateTypeOption, cellData)) { on( @@ -37,7 +38,7 @@ class DateCalBloc extends Bloc { setFocusedDay: (focusedDay) { emit(state.copyWith(focusedDay: focusedDay)); }, - didReceiveCellUpdate: (DateCellData? cellData) { + didReceiveCellUpdate: (DateCellDataPB? cellData) { final calData = calDataFromCellData(cellData); final time = calData.foldRight("", (dateData, previous) => dateData.time); emit(state.copyWith(calData: calData, time: time)); @@ -187,7 +188,7 @@ class DateCalEvent with _$DateCalEvent { const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat; const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime; const factory DateCalEvent.setTime(String time) = _Time; - const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; + const factory DateCalEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate; const factory DateCalEvent.didUpdateCalData(Option data, Option timeFormatError) = _DidUpdateCalData; } @@ -206,7 +207,7 @@ class DateCalState with _$DateCalState { factory DateCalState.initial( DateTypeOption dateTypeOption, - DateCellData? cellData, + DateCellDataPB? cellData, ) { Option calData = calDataFromCellData(cellData); final time = calData.foldRight("", (dateData, previous) => dateData.time); @@ -232,7 +233,7 @@ String _timeHintText(DateTypeOption typeOption) { return ""; } -Option calDataFromCellData(DateCellData? cellData) { +Option calDataFromCellData(DateCellDataPB? cellData) { String? time = timeFromCellData(cellData); Option calData = none(); if (cellData != null) { @@ -248,7 +249,7 @@ $fixnum.Int64 timestampFromDateTime(DateTime dateTime) { return $fixnum.Int64(timestamp); } -String? timeFromCellData(DateCellData? cellData) { +String? timeFromCellData(DateCellDataPB? cellData) { String? time; if (cellData?.hasTime() ?? false) { time = cellData?.time; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart index 00780143cc..c4f79f1f90 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -7,7 +7,7 @@ import 'cell_service/cell_service.dart'; part 'date_cell_bloc.freezed.dart'; class DateCellBloc extends Bloc { - final GridDateCellContext cellContext; + final GridDateCellController cellContext; void Function()? _onCellChangedFn; DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) { @@ -15,10 +15,10 @@ class DateCellBloc extends Bloc { (event, emit) async { event.when( initial: () => _startListening(), - didReceiveCellUpdate: (DateCellData? cellData) { + didReceiveCellUpdate: (DateCellDataPB? cellData) { emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData))); }, - didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)), + didReceiveFieldUpdate: (GridFieldPB value) => emit(state.copyWith(field: value)), ); }, ); @@ -48,19 +48,19 @@ class DateCellBloc extends Bloc { @freezed class DateCellEvent with _$DateCellEvent { const factory DateCellEvent.initial() = _InitialCell; - const factory DateCellEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate; - const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; + const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate; + const factory DateCellEvent.didReceiveFieldUpdate(GridFieldPB field) = _DidReceiveFieldUpdate; } @freezed class DateCellState with _$DateCellState { const factory DateCellState({ - required DateCellData? data, + required DateCellDataPB? data, required String dateStr, - required Field field, + required GridFieldPB field, }) = _DateCellState; - factory DateCellState.initial(GridDateCellContext context) { + factory DateCellState.initial(GridDateCellController context) { final cellData = context.getCellData(); return DateCellState( @@ -71,7 +71,7 @@ class DateCellState with _$DateCellState { } } -String _dateStrFromCellData(DateCellData? cellData) { +String _dateStrFromCellData(DateCellDataPB? cellData) { String dateStr = ""; if (cellData != null) { dateStr = cellData.date + " " + cellData.time; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart index adcfee71e6..65eec13e6c 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart @@ -8,7 +8,7 @@ import 'cell_service/cell_service.dart'; part 'number_cell_bloc.freezed.dart'; class NumberCellBloc extends Bloc { - final GridCellContext cellContext; + final GridCellController cellContext; void Function()? _onCellChangedFn; NumberCellBloc({ @@ -72,7 +72,7 @@ class NumberCellState with _$NumberCellState { required Either content, }) = _NumberCellState; - factory NumberCellState.initial(GridCellContext context) { + factory NumberCellState.initial(GridCellController context) { final cellContent = context.getCellData() ?? ""; return NumberCellState( content: left(cellContent), diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart index c6393e4831..d0db8669fa 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_cell_bloc.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; @@ -7,7 +7,7 @@ import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_serv part 'select_option_cell_bloc.freezed.dart'; class SelectOptionCellBloc extends Bloc { - final GridSelectOptionCellContext cellContext; + final GridSelectOptionCellController cellContext; void Function()? _onCellChangedFn; SelectOptionCellBloc({ @@ -56,17 +56,17 @@ class SelectOptionCellBloc extends Bloc selectedOptions, + List selectedOptions, ) = _DidReceiveOptions; } @freezed class SelectOptionCellState with _$SelectOptionCellState { const factory SelectOptionCellState({ - required List selectedOptions, + required List selectedOptions, }) = _SelectOptionCellState; - factory SelectOptionCellState.initial(GridSelectOptionCellContext context) { + factory SelectOptionCellState.initial(GridSelectOptionCellController context) { final data = context.getCellData(); return SelectOptionCellState( diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart index 87eabdf759..0990dca28a 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_editor_bloc.dart @@ -1,8 +1,7 @@ import 'dart:async'; -import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; @@ -13,16 +12,13 @@ part 'select_option_editor_bloc.freezed.dart'; class SelectOptionCellEditorBloc extends Bloc { final SelectOptionService _selectOptionService; - final GridSelectOptionCellContext cellContext; - late final GridFieldsListener _fieldListener; - void Function()? _onCellChangedFn; + final GridSelectOptionCellController cellController; Timer? _delayOperation; SelectOptionCellEditorBloc({ - required this.cellContext, - }) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell), - _fieldListener = GridFieldsListener(gridId: cellContext.gridId), - super(SelectOptionEditorState.initial(cellContext)) { + required this.cellController, + }) : _selectOptionService = SelectOptionService(cellId: cellController.cellId), + super(SelectOptionEditorState.initial(cellController)) { on( (event, emit) async { await event.map( @@ -64,13 +60,8 @@ class SelectOptionCellEditorBloc extends Bloc close() async { - if (_onCellChangedFn != null) { - cellContext.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; - } _delayOperation?.cancel(); - await _fieldListener.stop(); - cellContext.dispose(); + cellController.dispose(); return super.close(); } @@ -79,7 +70,7 @@ class SelectOptionCellEditorBloc extends Bloc {}, (err) => Log.error(err)); } - void _deleteOption(SelectOption option) async { + void _deleteOption(SelectOptionPB option) async { final result = await _selectOptionService.delete( option: option, ); @@ -87,7 +78,7 @@ class SelectOptionCellEditorBloc extends Bloc null, (err) => Log.error(err)); } - void _updateOption(SelectOption option) async { + void _updateOption(SelectOptionPB option) async { final result = await _selectOptionService.update( option: option, ); @@ -131,8 +122,8 @@ class SelectOptionCellEditorBloc extends Bloc filter, List allOptions) { - final List options = List.from(allOptions); + _MakeOptionResult _makeOptions(Option filter, List allOptions) { + final List options = List.from(allOptions); Option createOption = filter; filter.foldRight(null, (filter, previous) { @@ -157,24 +148,16 @@ class SelectOptionCellEditorBloc extends Bloc Log.error(err), - ); - }); } } @@ -182,26 +165,26 @@ class SelectOptionCellEditorBloc extends Bloc options, List selectedOptions) = _DidReceiveOptions; + List options, List selectedOptions) = _DidReceiveOptions; const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption; const factory SelectOptionEditorEvent.selectOption(String optionId) = _SelectOption; - const factory SelectOptionEditorEvent.updateOption(SelectOption option) = _UpdateOption; - const factory SelectOptionEditorEvent.deleteOption(SelectOption option) = _DeleteOption; + const factory SelectOptionEditorEvent.updateOption(SelectOptionPB option) = _UpdateOption; + const factory SelectOptionEditorEvent.deleteOption(SelectOptionPB option) = _DeleteOption; const factory SelectOptionEditorEvent.filterOption(String optionName) = _SelectOptionFilter; } @freezed class SelectOptionEditorState with _$SelectOptionEditorState { const factory SelectOptionEditorState({ - required List options, - required List allOptions, - required List selectedOptions, + required List options, + required List allOptions, + required List selectedOptions, required Option createOption, required Option filter, }) = _SelectOptionEditorState; - factory SelectOptionEditorState.initial(GridSelectOptionCellContext context) { - final data = context.getCellData(loadIfNoCache: false); + factory SelectOptionEditorState.initial(GridSelectOptionCellController context) { + final data = context.getCellData(loadIfNotExist: false); return SelectOptionEditorState( options: data?.options ?? [], allOptions: data?.options ?? [], @@ -213,7 +196,7 @@ class SelectOptionEditorState with _$SelectOptionEditorState { } class _MakeOptionResult { - List options; + List options; Option createOption; _MakeOptionResult({ diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart index 806389d67c..54ac384267 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart @@ -2,28 +2,28 @@ import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'cell_service/cell_service.dart'; class SelectOptionService { - final GridCell gridCell; - SelectOptionService({required this.gridCell}); + final GridCellIdentifier cellId; + SelectOptionService({required this.cellId}); - String get gridId => gridCell.gridId; - String get fieldId => gridCell.field.id; - String get rowId => gridCell.rowId; + String get gridId => cellId.gridId; + String get fieldId => cellId.field.id; + String get rowId => cellId.rowId; Future> create({required String name}) { return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then( (result) { return result.fold( (option) { - final cellIdentifier = CellIdentifierPayload.create() + final cellIdentifier = GridCellIdentifierPayloadPB.create() ..gridId = gridId ..fieldId = fieldId ..rowId = rowId; - final payload = SelectOptionChangesetPayload.create() + final payload = SelectOptionChangesetPayloadPB.create() ..insertOption = option ..cellIdentifier = cellIdentifier; return GridEventUpdateSelectOption(payload).send(); @@ -35,26 +35,26 @@ class SelectOptionService { } Future> update({ - required SelectOption option, + required SelectOptionPB option, }) { - final payload = SelectOptionChangesetPayload.create() + final payload = SelectOptionChangesetPayloadPB.create() ..updateOption = option ..cellIdentifier = _cellIdentifier(); return GridEventUpdateSelectOption(payload).send(); } Future> delete({ - required SelectOption option, + required SelectOptionPB option, }) { - final payload = SelectOptionChangesetPayload.create() + final payload = SelectOptionChangesetPayloadPB.create() ..deleteOption = option ..cellIdentifier = _cellIdentifier(); return GridEventUpdateSelectOption(payload).send(); } - Future> getOpitonContext() { - final payload = CellIdentifierPayload.create() + Future> getOpitonContext() { + final payload = GridCellIdentifierPayloadPB.create() ..gridId = gridId ..fieldId = fieldId ..rowId = rowId; @@ -63,21 +63,21 @@ class SelectOptionService { } Future> select({required String optionId}) { - final payload = SelectOptionCellChangesetPayload.create() + final payload = SelectOptionCellChangesetPayloadPB.create() ..cellIdentifier = _cellIdentifier() ..insertOptionId = optionId; return GridEventUpdateSelectOptionCell(payload).send(); } Future> unSelect({required String optionId}) { - final payload = SelectOptionCellChangesetPayload.create() + final payload = SelectOptionCellChangesetPayloadPB.create() ..cellIdentifier = _cellIdentifier() ..deleteOptionId = optionId; return GridEventUpdateSelectOptionCell(payload).send(); } - CellIdentifierPayload _cellIdentifier() { - return CellIdentifierPayload.create() + GridCellIdentifierPayloadPB _cellIdentifier() { + return GridCellIdentifierPayloadPB.create() ..gridId = gridId ..fieldId = fieldId ..rowId = rowId; diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart index e3b7fd2dca..783564b5fa 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart @@ -6,7 +6,7 @@ import 'cell_service/cell_service.dart'; part 'text_cell_bloc.freezed.dart'; class TextCellBloc extends Bloc { - final GridCellContext cellContext; + final GridCellController cellContext; void Function()? _onCellChangedFn; TextCellBloc({ required this.cellContext, @@ -63,7 +63,7 @@ class TextCellState with _$TextCellState { required String content, }) = _TextCellState; - factory TextCellState.initial(GridCellContext context) => TextCellState( + factory TextCellState.initial(GridCellController context) => TextCellState( content: context.getCellData() ?? "", ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart index e1fe39c3bf..e43f561542 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -7,7 +7,7 @@ import 'cell_service/cell_service.dart'; part 'url_cell_bloc.freezed.dart'; class URLCellBloc extends Bloc { - final GridURLCellContext cellContext; + final GridURLCellController cellContext; void Function()? _onCellChangedFn; URLCellBloc({ required this.cellContext, @@ -57,7 +57,7 @@ class URLCellBloc extends Bloc { class URLCellEvent with _$URLCellEvent { const factory URLCellEvent.initial() = _InitialCell; const factory URLCellEvent.updateURL(String url) = _UpdateURL; - const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; + const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate; } @freezed @@ -67,7 +67,7 @@ class URLCellState with _$URLCellState { required String url, }) = _URLCellState; - factory URLCellState.initial(GridURLCellContext context) { + factory URLCellState.initial(GridURLCellController context) { final cellData = context.getCellData(); return URLCellState( content: cellData?.content ?? "", diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart index 6e4990943f..067be84b7b 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/url_cell_editor_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -7,7 +7,7 @@ import 'cell_service/cell_service.dart'; part 'url_cell_editor_bloc.freezed.dart'; class URLCellEditorBloc extends Bloc { - final GridURLCellContext cellContext; + final GridURLCellController cellContext; void Function()? _onCellChangedFn; URLCellEditorBloc({ required this.cellContext, @@ -54,7 +54,7 @@ class URLCellEditorBloc extends Bloc { @freezed class URLCellEditorEvent with _$URLCellEditorEvent { const factory URLCellEditorEvent.initial() = _InitialCell; - const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate; + const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate; const factory URLCellEditorEvent.updateText(String text) = _UpdateText; } @@ -64,7 +64,7 @@ class URLCellEditorState with _$URLCellEditorState { required String content, }) = _URLCellEditorState; - factory URLCellEditorState.initial(GridURLCellContext context) { + factory URLCellEditorState.initial(GridURLCellController context) { final cellData = context.getCellData(); return URLCellEditorState( content: cellData?.content ?? "", diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart index 801678d929..3caef12f73 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_action_sheet_bloc.dart @@ -10,8 +10,8 @@ part 'field_action_sheet_bloc.freezed.dart'; class FieldActionSheetBloc extends Bloc { final FieldService fieldService; - FieldActionSheetBloc({required Field field, required this.fieldService}) - : super(FieldActionSheetState.initial(FieldTypeOptionData.create()..field_2 = field)) { + FieldActionSheetBloc({required GridFieldPB field, required this.fieldService}) + : super(FieldActionSheetState.initial(FieldTypeOptionDataPB.create()..field_2 = field)) { on( (event, emit) async { await event.map( @@ -67,12 +67,12 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent { @freezed class FieldActionSheetState with _$FieldActionSheetState { const factory FieldActionSheetState({ - required FieldTypeOptionData fieldTypeOptionData, + required FieldTypeOptionDataPB fieldTypeOptionData, required String errorText, required String fieldName, }) = _FieldActionSheetState; - factory FieldActionSheetState.initial(FieldTypeOptionData data) => FieldActionSheetState( + factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) => FieldActionSheetState( fieldTypeOptionData: data, errorText: '', fieldName: data.field_2.name, diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart index 774a7fb82b..8a2a0e2d06 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart @@ -62,7 +62,7 @@ class FieldCellBloc extends Bloc { @freezed class FieldCellEvent with _$FieldCellEvent { const factory FieldCellEvent.initial() = _InitialCell; - const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; + const factory FieldCellEvent.didReceiveFieldUpdate(GridFieldPB field) = _DidReceiveFieldUpdate; const factory FieldCellEvent.startUpdateWidth(double offset) = _StartUpdateWidth; const factory FieldCellEvent.endUpdateWidth() = _EndUpdateWidth; } @@ -71,7 +71,7 @@ class FieldCellEvent with _$FieldCellEvent { class FieldCellState with _$FieldCellState { const factory FieldCellState({ required String gridId, - required Field field, + required GridFieldPB field, required double width, }) = _FieldCellState; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart index 2815d5519d..8d44edf1ff 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart @@ -1,3 +1,4 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -6,27 +7,32 @@ import 'package:dartz/dartz.dart'; part 'field_editor_bloc.freezed.dart'; class FieldEditorBloc extends Bloc { + final TypeOptionDataController dataController; + FieldEditorBloc({ required String gridId, required String fieldName, - required IFieldContextLoader fieldContextLoader, - }) : super(FieldEditorState.initial(gridId, fieldName, fieldContextLoader)) { + required IFieldTypeOptionLoader loader, + }) : dataController = TypeOptionDataController(gridId: gridId, loader: loader), + super(FieldEditorState.initial(gridId, fieldName)) { on( (event, emit) async { await event.when( initial: () async { - final fieldContext = GridFieldContext(gridId: gridId, loader: fieldContextLoader); - await fieldContext.loadData().then((result) { - result.fold( - (l) => emit(state.copyWith(fieldContext: Some(fieldContext), name: fieldContext.field.name)), - (r) => null, - ); + dataController.addFieldListener((field) { + if (!isClosed) { + add(FieldEditorEvent.didReceiveFieldChanged(field)); + } }); + await dataController.loadData(); }, updateName: (name) { - state.fieldContext.fold(() => null, (fieldContext) => fieldContext.fieldName = name); + dataController.fieldName = name; emit(state.copyWith(name: name)); }, + didReceiveFieldChanged: (GridFieldPB field) { + emit(state.copyWith(field: Some(field))); + }, ); }, ); @@ -42,6 +48,7 @@ class FieldEditorBloc extends Bloc { class FieldEditorEvent with _$FieldEditorEvent { const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.updateName(String name) = _UpdateName; + const factory FieldEditorEvent.didReceiveFieldChanged(GridFieldPB field) = _DidReceiveFieldChanged; } @freezed @@ -50,13 +57,17 @@ class FieldEditorState with _$FieldEditorState { required String gridId, required String errorText, required String name, - required Option fieldContext, + required Option field, }) = _FieldEditorState; - factory FieldEditorState.initial(String gridId, String fieldName, IFieldContextLoader loader) => FieldEditorState( + factory FieldEditorState.initial( + String gridId, + String fieldName, + ) => + FieldEditorState( gridId: gridId, - fieldContext: none(), errorText: '', + field: none(), name: fieldName, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart deleted file mode 100644 index 6310452d48..0000000000 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_editor_pannel_bloc.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'dart:async'; - -import 'field_service.dart'; - -part 'field_editor_pannel_bloc.freezed.dart'; - -class FieldEditorPannelBloc extends Bloc { - final GridFieldContext _fieldContext; - void Function()? _fieldListenFn; - - FieldEditorPannelBloc(GridFieldContext fieldContext) - : _fieldContext = fieldContext, - super(FieldEditorPannelState.initial(fieldContext)) { - on( - (event, emit) async { - event.when( - initial: () { - _fieldListenFn = fieldContext.addFieldListener((field) { - add(FieldEditorPannelEvent.didReceiveFieldUpdated(field)); - }); - }, - didReceiveFieldUpdated: (field) { - emit(state.copyWith(field: field)); - }, - ); - }, - ); - } - - @override - Future close() async { - if (_fieldListenFn != null) { - _fieldContext.removeFieldListener(_fieldListenFn!); - } - return super.close(); - } -} - -@freezed -class FieldEditorPannelEvent with _$FieldEditorPannelEvent { - const factory FieldEditorPannelEvent.initial() = _Initial; - const factory FieldEditorPannelEvent.didReceiveFieldUpdated(Field field) = _DidReceiveFieldUpdated; -} - -@freezed -class FieldEditorPannelState with _$FieldEditorPannelState { - const factory FieldEditorPannelState({ - required Field field, - }) = _FieldEditorPannelState; - - factory FieldEditorPannelState.initial(GridFieldContext fieldContext) => FieldEditorPannelState( - field: fieldContext.field, - ); -} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart index 21bd5befeb..d3b35e2bfc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_listener.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -typedef UpdateFieldNotifiedValue = Either; +typedef UpdateFieldNotifiedValue = Either; class SingleFieldListener { final String fieldId; @@ -31,7 +31,7 @@ class SingleFieldListener { switch (ty) { case GridNotification.DidUpdateField: result.fold( - (payload) => _updateFieldNotifier?.value = left(Field.fromBuffer(payload)), + (payload) => _updateFieldNotifier?.value = left(GridFieldPB.fromBuffer(payload)), (error) => _updateFieldNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart index 99fef626c0..a0c6fc7d21 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart @@ -1,4 +1,5 @@ import 'package:dartz/dartz.dart'; +import 'package:flowy_infra/notifier.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; @@ -9,6 +10,10 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:protobuf/protobuf.dart'; part 'field_service.freezed.dart'; +/// FieldService consists of lots of event functions. We define the events in the backend(Rust), +/// you can find the corresponding event implementation in event_map.rs of the corresponding crate. +/// +/// You could check out the rust-lib/flowy-grid/event_map.rs for more information. class FieldService { final String gridId; final String fieldId; @@ -16,10 +21,10 @@ class FieldService { FieldService({required this.gridId, required this.fieldId}); Future> moveField(int fromIndex, int toIndex) { - final payload = MoveItemPayload.create() + final payload = MoveItemPayloadPB.create() ..gridId = gridId ..itemId = fieldId - ..ty = MoveItemType.MoveField + ..ty = MoveItemTypePB.MoveField ..fromIndex = fromIndex ..toIndex = toIndex; @@ -34,7 +39,7 @@ class FieldService { double? width, List? typeOptionData, }) { - var payload = FieldChangesetPayload.create() + var payload = FieldChangesetPayloadPB.create() ..gridId = gridId ..fieldId = fieldId; @@ -68,11 +73,11 @@ class FieldService { // Create the field if it does not exist. Otherwise, update the field. static Future> insertField({ required String gridId, - required Field field, + required GridFieldPB field, List? typeOptionData, String? startFieldId, }) { - var payload = InsertFieldPayload.create() + var payload = InsertFieldPayloadPB.create() ..gridId = gridId ..field_2 = field ..typeOptionData = typeOptionData ?? []; @@ -89,7 +94,7 @@ class FieldService { required String fieldId, required List typeOptionData, }) { - var payload = UpdateFieldTypeOptionPayload.create() + var payload = UpdateFieldTypeOptionPayloadPB.create() ..gridId = gridId ..fieldId = fieldId ..typeOptionData = typeOptionData; @@ -98,7 +103,7 @@ class FieldService { } Future> deleteField() { - final payload = FieldIdentifierPayload.create() + final payload = GridFieldIdentifierPayloadPB.create() ..gridId = gridId ..fieldId = fieldId; @@ -106,17 +111,17 @@ class FieldService { } Future> duplicateField() { - final payload = FieldIdentifierPayload.create() + final payload = GridFieldIdentifierPayloadPB.create() ..gridId = gridId ..fieldId = fieldId; return GridEventDuplicateField(payload).send(); } - Future> getFieldTypeOptionData({ + Future> getFieldTypeOptionData({ required FieldType fieldType, }) { - final payload = EditFieldPayload.create() + final payload = EditFieldPayloadPB.create() ..gridId = gridId ..fieldId = fieldId ..fieldType = fieldType; @@ -133,16 +138,16 @@ class FieldService { class GridFieldCellContext with _$GridFieldCellContext { const factory GridFieldCellContext({ required String gridId, - required Field field, + required GridFieldPB field, }) = _GridFieldCellContext; } -abstract class IFieldContextLoader { +abstract class IFieldTypeOptionLoader { String get gridId; - Future> load(); + Future> load(); - Future> switchToField(String fieldId, FieldType fieldType) { - final payload = EditFieldPayload.create() + Future> switchToField(String fieldId, FieldType fieldType) { + final payload = EditFieldPayloadPB.create() ..gridId = gridId ..fieldId = fieldId ..fieldType = fieldType; @@ -151,16 +156,16 @@ abstract class IFieldContextLoader { } } -class NewFieldContextLoader extends IFieldContextLoader { +class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader { @override final String gridId; - NewFieldContextLoader({ + NewFieldTypeOptionLoader({ required this.gridId, }); @override - Future> load() { - final payload = EditFieldPayload.create() + Future> load() { + final payload = EditFieldPayloadPB.create() ..gridId = gridId ..fieldType = FieldType.RichText; @@ -168,19 +173,19 @@ class NewFieldContextLoader extends IFieldContextLoader { } } -class FieldContextLoader extends IFieldContextLoader { +class FieldTypeOptionLoader extends IFieldTypeOptionLoader { @override final String gridId; - final Field field; + final GridFieldPB field; - FieldContextLoader({ + FieldTypeOptionLoader({ required this.gridId, required this.field, }); @override - Future> load() { - final payload = EditFieldPayload.create() + Future> load() { + final payload = EditFieldPayloadPB.create() ..gridId = gridId ..fieldId = field.id ..fieldType = field.fieldType; @@ -189,16 +194,16 @@ class FieldContextLoader extends IFieldContextLoader { } } -class GridFieldContext { +class TypeOptionDataController { final String gridId; - final IFieldContextLoader _loader; + final IFieldTypeOptionLoader _loader; - late FieldTypeOptionData _data; - ValueNotifier? _fieldNotifier; + late FieldTypeOptionDataPB _data; + final PublishNotifier _fieldNotifier = PublishNotifier(); - GridFieldContext({ + TypeOptionDataController({ required this.gridId, - required IFieldContextLoader loader, + required IFieldTypeOptionLoader loader, }) : _loader = loader; Future> loadData() async { @@ -207,13 +212,7 @@ class GridFieldContext { (data) { data.freeze(); _data = data; - - if (_fieldNotifier == null) { - _fieldNotifier = ValueNotifier(data.field_2); - } else { - _fieldNotifier?.value = data.field_2; - } - + _fieldNotifier.value = data.field_2; return left(unit); }, (err) { @@ -223,9 +222,9 @@ class GridFieldContext { ); } - Field get field => _data.field_2; + GridFieldPB get field => _data.field_2; - set field(Field field) { + set field(GridFieldPB field) { _updateData(newField: field); } @@ -239,7 +238,7 @@ class GridFieldContext { _updateData(newTypeOptionData: typeOptionData); } - void _updateData({String? newName, Field? newField, List? newTypeOptionData}) { + void _updateData({String? newName, GridFieldPB? newField, List? newTypeOptionData}) { _data = _data.rebuild((rebuildData) { if (newName != null) { rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) { @@ -256,9 +255,7 @@ class GridFieldContext { } }); - if (_data.field_2 != _fieldNotifier?.value) { - _fieldNotifier?.value = _data.field_2; - } + _fieldNotifier.value = _data.field_2; FieldService.insertField( gridId: gridId, @@ -283,16 +280,16 @@ class GridFieldContext { }); } - void Function() addFieldListener(void Function(Field) callback) { + void Function() addFieldListener(void Function(GridFieldPB) callback) { listener() { callback(field); } - _fieldNotifier?.addListener(listener); + _fieldNotifier.addListener(listener); return listener; } void removeFieldListener(void Function() listener) { - _fieldNotifier?.removeListener(listener); + _fieldNotifier.removeListener(listener); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_type_option_edit_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_type_option_edit_bloc.dart new file mode 100644 index 0000000000..e098f87d86 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_type_option_edit_bloc.dart @@ -0,0 +1,57 @@ +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; + +import 'field_service.dart'; + +part 'field_type_option_edit_bloc.freezed.dart'; + +class FieldTypeOptionEditBloc extends Bloc { + final TypeOptionDataController _dataController; + void Function()? _fieldListenFn; + + FieldTypeOptionEditBloc(TypeOptionDataController dataController) + : _dataController = dataController, + super(FieldTypeOptionEditState.initial(dataController)) { + on( + (event, emit) async { + event.when( + initial: () { + _fieldListenFn = dataController.addFieldListener((field) { + add(FieldTypeOptionEditEvent.didReceiveFieldUpdated(field)); + }); + }, + didReceiveFieldUpdated: (field) { + emit(state.copyWith(field: field)); + }, + ); + }, + ); + } + + @override + Future close() async { + if (_fieldListenFn != null) { + _dataController.removeFieldListener(_fieldListenFn!); + } + return super.close(); + } +} + +@freezed +class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent { + const factory FieldTypeOptionEditEvent.initial() = _Initial; + const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(GridFieldPB field) = _DidReceiveFieldUpdated; +} + +@freezed +class FieldTypeOptionEditState with _$FieldTypeOptionEditState { + const factory FieldTypeOptionEditState({ + required GridFieldPB field, + }) = _FieldTypeOptionEditState; + + factory FieldTypeOptionEditState.initial(TypeOptionDataController fieldContext) => FieldTypeOptionEditState( + field: fieldContext.field, + ); +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart b/frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart index 00e94bb3e1..67bec17be7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/grid_listenr.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -typedef UpdateFieldNotifiedValue = Either; +typedef UpdateFieldNotifiedValue = Either; class GridFieldsListener { final String gridId; @@ -27,7 +27,7 @@ class GridFieldsListener { switch (ty) { case GridNotification.DidUpdateGridField: result.fold( - (payload) => updateFieldsNotifier?.value = left(GridFieldChangeset.fromBuffer(payload)), + (payload) => updateFieldsNotifier?.value = left(GridFieldChangesetPB.fromBuffer(payload)), (error) => updateFieldsNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart index 8784422c54..00deff56e7 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/date_bloc.dart @@ -1,14 +1,15 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; import 'package:protobuf/protobuf.dart'; part 'date_bloc.freezed.dart'; -typedef DateTypeOptionContext = TypeOptionContext; +typedef DateTypeOptionContext = TypeOptionWidgetContext; -class DateTypeOptionDataBuilder extends TypeOptionDataBuilder { +class DateTypeOptionDataParser extends TypeOptionDataParser { @override DateTypeOption fromBuffer(List buffer) { return DateTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart index 2854fcf199..9f1dd4dd1d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/edit_select_option_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -7,7 +7,7 @@ import 'package:dartz/dartz.dart'; part 'edit_select_option_bloc.freezed.dart'; class EditSelectOptionBloc extends Bloc { - EditSelectOptionBloc({required SelectOption option}) : super(EditSelectOptionState.initial(option)) { + EditSelectOptionBloc({required SelectOptionPB option}) : super(EditSelectOptionState.initial(option)) { on( (event, emit) async { event.map( @@ -30,14 +30,14 @@ class EditSelectOptionBloc extends Bloc deleted, }) = _EditSelectOptionState; - factory EditSelectOptionState.initial(SelectOption option) => EditSelectOptionState( + factory EditSelectOptionState.initial(SelectOptionPB option) => EditSelectOptionState( option: option, deleted: none(), ); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart index bb72277f0b..e0e242305b 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/multi_select_type_option.dart @@ -1,26 +1,28 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'dart:async'; import 'package:protobuf/protobuf.dart'; import 'select_option_type_option_bloc.dart'; import 'type_option_service.dart'; -class MultiSelectTypeOptionContext extends TypeOptionContext with SelectOptionTypeOptionAction { +class MultiSelectTypeOptionContext extends TypeOptionWidgetContext + with SelectOptionTypeOptionAction { final TypeOptionService service; MultiSelectTypeOptionContext({ - required MultiSelectTypeOptionDataBuilder dataBuilder, - required GridFieldContext fieldContext, + required MultiSelectTypeOptionWidgetDataParser dataBuilder, + required TypeOptionDataController dataController, }) : service = TypeOptionService( - gridId: fieldContext.gridId, - fieldId: fieldContext.field.id, + gridId: dataController.gridId, + fieldId: dataController.field.id, ), - super(dataBuilder: dataBuilder, fieldContext: fieldContext); + super(dataParser: dataBuilder, dataController: dataController); @override - List Function(SelectOption) get deleteOption { - return (SelectOption option) { + List Function(SelectOptionPB) get deleteOption { + return (SelectOptionPB option) { typeOption.freeze(); typeOption = typeOption.rebuild((typeOption) { final index = typeOption.options.indexWhere((element) => element.id == option.id); @@ -33,7 +35,7 @@ class MultiSelectTypeOptionContext extends TypeOptionContext> Function(String) get insertOption { + Future> Function(String) get insertOption { return (String optionName) { return service.newOption(name: optionName).then((result) { return result.fold( @@ -55,8 +57,8 @@ class MultiSelectTypeOptionContext extends TypeOptionContext Function(SelectOption) get udpateOption { - return (SelectOption option) { + List Function(SelectOptionPB) get udpateOption { + return (SelectOptionPB option) { typeOption.freeze(); typeOption = typeOption.rebuild((typeOption) { final index = typeOption.options.indexWhere((element) => element.id == option.id); @@ -69,7 +71,7 @@ class MultiSelectTypeOptionContext extends TypeOptionContext { +class MultiSelectTypeOptionWidgetDataParser extends TypeOptionDataParser { @override MultiSelectTypeOption fromBuffer(List buffer) { return MultiSelectTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart index a708668066..804ce3ee11 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/number_bloc.dart @@ -8,9 +8,9 @@ import 'package:protobuf/protobuf.dart'; part 'number_bloc.freezed.dart'; -typedef NumberTypeOptionContext = TypeOptionContext; +typedef NumberTypeOptionContext = TypeOptionWidgetContext; -class NumberTypeOptionDataBuilder extends TypeOptionDataBuilder { +class NumberTypeOptionWidgetDataParser extends TypeOptionDataParser { @override NumberTypeOption fromBuffer(List buffer) { return NumberTypeOption.fromBuffer(buffer); diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart index d4290cd8ff..b77b9b86cd 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/select_option_type_option_bloc.dart @@ -1,4 +1,4 @@ -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -6,25 +6,25 @@ import 'package:dartz/dartz.dart'; part 'select_option_type_option_bloc.freezed.dart'; abstract class SelectOptionTypeOptionAction { - Future> Function(String) get insertOption; + Future> Function(String) get insertOption; - List Function(SelectOption) get deleteOption; + List Function(SelectOptionPB) get deleteOption; - List Function(SelectOption) get udpateOption; + List Function(SelectOptionPB) get udpateOption; } class SelectOptionTypeOptionBloc extends Bloc { final SelectOptionTypeOptionAction typeOptionAction; SelectOptionTypeOptionBloc({ - required List options, + required List options, required this.typeOptionAction, }) : super(SelectOptionTypeOptionState.initial(options)) { on( (event, emit) async { await event.when( createOption: (optionName) async { - final List options = await typeOptionAction.insertOption(optionName); + final List options = await typeOptionAction.insertOption(optionName); emit(state.copyWith(options: options)); }, addingOption: () { @@ -34,11 +34,11 @@ class SelectOptionTypeOptionBloc extends Bloc options = typeOptionAction.udpateOption(option); + final List options = typeOptionAction.udpateOption(option); emit(state.copyWith(options: options)); }, deleteOption: (option) { - final List options = typeOptionAction.deleteOption(option); + final List options = typeOptionAction.deleteOption(option); emit(state.copyWith(options: options)); }, ); @@ -57,19 +57,19 @@ class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent { const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption; const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption; const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption; - const factory SelectOptionTypeOptionEvent.updateOption(SelectOption option) = _UpdateOption; - const factory SelectOptionTypeOptionEvent.deleteOption(SelectOption option) = _DeleteOption; + const factory SelectOptionTypeOptionEvent.updateOption(SelectOptionPB option) = _UpdateOption; + const factory SelectOptionTypeOptionEvent.deleteOption(SelectOptionPB option) = _DeleteOption; } @freezed class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState { const factory SelectOptionTypeOptionState({ - required List options, + required List options, required bool isEditingOption, required Option newOptionName, }) = _SelectOptionTyepOptionState; - factory SelectOptionTypeOptionState.initial(List options) => SelectOptionTypeOptionState( + factory SelectOptionTypeOptionState.initial(List options) => SelectOptionTypeOptionState( options: options, isEditingOption: false, newOptionName: none(), diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart index 91d8de7a28..857838de38 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/single_select_type_option.dart @@ -1,27 +1,28 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart'; import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart'; import 'dart:async'; import 'package:protobuf/protobuf.dart'; import 'select_option_type_option_bloc.dart'; import 'type_option_service.dart'; -class SingleSelectTypeOptionContext extends TypeOptionContext +class SingleSelectTypeOptionContext extends TypeOptionWidgetContext with SelectOptionTypeOptionAction { final TypeOptionService service; SingleSelectTypeOptionContext({ - required SingleSelectTypeOptionDataBuilder dataBuilder, - required GridFieldContext fieldContext, + required SingleSelectTypeOptionWidgetDataParser dataBuilder, + required TypeOptionDataController fieldContext, }) : service = TypeOptionService( gridId: fieldContext.gridId, fieldId: fieldContext.field.id, ), - super(dataBuilder: dataBuilder, fieldContext: fieldContext); + super(dataParser: dataBuilder, dataController: fieldContext); @override - List Function(SelectOption) get deleteOption { - return (SelectOption option) { + List Function(SelectOptionPB) get deleteOption { + return (SelectOptionPB option) { typeOption.freeze(); typeOption = typeOption.rebuild((typeOption) { final index = typeOption.options.indexWhere((element) => element.id == option.id); @@ -34,7 +35,7 @@ class SingleSelectTypeOptionContext extends TypeOptionContext> Function(String) get insertOption { + Future> Function(String) get insertOption { return (String optionName) { return service.newOption(name: optionName).then((result) { return result.fold( @@ -56,8 +57,8 @@ class SingleSelectTypeOptionContext extends TypeOptionContext Function(SelectOption) get udpateOption { - return (SelectOption option) { + List Function(SelectOptionPB) get udpateOption { + return (SelectOptionPB option) { typeOption.freeze(); typeOption = typeOption.rebuild((typeOption) { final index = typeOption.options.indexWhere((element) => element.id == option.id); @@ -70,9 +71,9 @@ class SingleSelectTypeOptionContext extends TypeOptionContext { +class SingleSelectTypeOptionWidgetDataParser extends TypeOptionDataParser { @override - SingleSelectTypeOption fromBuffer(List buffer) { - return SingleSelectTypeOption.fromBuffer(buffer); + SingleSelectTypeOptionPB fromBuffer(List buffer) { + return SingleSelectTypeOptionPB.fromBuffer(buffer); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart index c6abc71cbb..b45cc6f869 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/field/type_option/type_option_service.dart @@ -6,7 +6,7 @@ import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:protobuf/protobuf.dart'; class TypeOptionService { @@ -18,14 +18,14 @@ class TypeOptionService { required this.fieldId, }); - Future> newOption({ + Future> newOption({ required String name, }) { - final fieldIdentifier = FieldIdentifierPayload.create() + final fieldIdentifier = GridFieldIdentifierPayloadPB.create() ..gridId = gridId ..fieldId = fieldId; - final payload = CreateSelectOptionPayload.create() + final payload = CreateSelectOptionPayloadPB.create() ..optionName = name ..fieldIdentifier = fieldIdentifier; @@ -33,36 +33,36 @@ class TypeOptionService { } } -abstract class TypeOptionDataBuilder { +abstract class TypeOptionDataParser { T fromBuffer(List buffer); } -class TypeOptionContext { +class TypeOptionWidgetContext { T? _typeOptionObject; - final GridFieldContext _fieldContext; - final TypeOptionDataBuilder dataBuilder; + final TypeOptionDataController _dataController; + final TypeOptionDataParser dataParser; - TypeOptionContext({ - required this.dataBuilder, - required GridFieldContext fieldContext, - }) : _fieldContext = fieldContext; + TypeOptionWidgetContext({ + required this.dataParser, + required TypeOptionDataController dataController, + }) : _dataController = dataController; - String get gridId => _fieldContext.gridId; + String get gridId => _dataController.gridId; - Field get field => _fieldContext.field; + GridFieldPB get field => _dataController.field; T get typeOption { if (_typeOptionObject != null) { return _typeOptionObject!; } - final T object = dataBuilder.fromBuffer(_fieldContext.typeOptionData); + final T object = dataParser.fromBuffer(_dataController.typeOptionData); _typeOptionObject = object; return object; } set typeOption(T typeOption) { - _fieldContext.typeOptionData = typeOption.writeToBuffer(); + _dataController.typeOptionData = typeOption.writeToBuffer(); _typeOptionObject = typeOption; } } @@ -74,10 +74,10 @@ abstract class TypeOptionFieldDelegate { class TypeOptionContext2 { final String gridId; - final Field field; + final GridFieldPB field; final FieldService _fieldService; T? _data; - final TypeOptionDataBuilder dataBuilder; + final TypeOptionDataParser dataBuilder; TypeOptionContext2({ required this.gridId, diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart index 49e90b6a9e..19c4049224 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart @@ -7,7 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'block/block_service.dart'; +import 'block/block_cache.dart'; import 'grid_service.dart'; import 'row/row_service.dart'; import 'dart:collection'; @@ -20,17 +20,17 @@ class GridBloc extends Bloc { final GridFieldCache fieldCache; // key: the block id - final LinkedHashMap _blocks; + final LinkedHashMap _blocks; - List get rows { - final List rows = []; + List get rowInfos { + final List rows = []; for (var block in _blocks.values) { rows.addAll(block.rows); } return rows; } - GridBloc({required View view}) + GridBloc({required ViewPB view}) : gridId = view.id, _blocks = LinkedHashMap.identity(), _gridService = GridService(gridId: view.id), @@ -46,11 +46,11 @@ class GridBloc extends Bloc { createRow: () { _gridService.createRow(); }, - didReceiveRowUpdate: (rows, reason) { - emit(state.copyWith(rows: rows, reason: reason)); + didReceiveRowUpdate: (newRowInfos, reason) { + emit(state.copyWith(rowInfos: newRowInfos, reason: reason)); }, didReceiveFieldUpdate: (fields) { - emit(state.copyWith(rows: rows, fields: GridFieldEquatable(fields))); + emit(state.copyWith(rowInfos: rowInfos, fields: GridFieldEquatable(fields))); }, ); }, @@ -68,8 +68,8 @@ class GridBloc extends Bloc { return super.close(); } - GridRowCacheService? getRowCache(String blockId, String rowId) { - final GridBlockCacheService? blockCache = _blocks[blockId]; + GridRowCache? getRowCache(String blockId, String rowId) { + final GridBlockCache? blockCache = _blocks[blockId]; return blockCache?.rowCache; } @@ -93,8 +93,8 @@ class GridBloc extends Bloc { ); } - Future _loadFields(Grid grid, Emitter emit) async { - final result = await _gridService.getFields(fieldOrders: grid.fieldOrders); + Future _loadFields(GridPB grid, Emitter emit) async { + final result = await _gridService.getFields(fieldIds: grid.fields); return Future( () => result.fold( (fields) { @@ -103,7 +103,7 @@ class GridBloc extends Bloc { emit(state.copyWith( grid: Some(grid), fields: GridFieldEquatable(fieldCache.fields), - rows: rows, + rowInfos: rowInfos, loadingState: GridLoadingState.finish(left(unit)), )); }, @@ -112,14 +112,14 @@ class GridBloc extends Bloc { ); } - void _initialBlocks(List blocks) { + void _initialBlocks(List blocks) { for (final block in blocks) { if (_blocks[block.id] != null) { Log.warn("Intial duplicate block's cache: ${block.id}"); return; } - final cache = GridBlockCacheService( + final cache = GridBlockCache( gridId: gridId, block: block, fieldCache: fieldCache, @@ -127,7 +127,7 @@ class GridBloc extends Bloc { cache.addListener( listenWhen: () => !isClosed, - onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rows, reason)), + onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rowInfos, reason)), ); _blocks[block.id] = cache; @@ -139,24 +139,25 @@ class GridBloc extends Bloc { class GridEvent with _$GridEvent { const factory GridEvent.initial() = InitialGrid; const factory GridEvent.createRow() = _CreateRow; - const factory GridEvent.didReceiveRowUpdate(List rows, GridRowChangeReason listState) = _DidReceiveRowUpdate; - const factory GridEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; + const factory GridEvent.didReceiveRowUpdate(List rows, GridRowChangeReason listState) = + _DidReceiveRowUpdate; + const factory GridEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; } @freezed class GridState with _$GridState { const factory GridState({ required String gridId, - required Option grid, + required Option grid, required GridFieldEquatable fields, - required List rows, + required List rowInfos, required GridLoadingState loadingState, required GridRowChangeReason reason, }) = _GridState; factory GridState.initial(String gridId) => GridState( fields: const GridFieldEquatable([]), - rows: [], + rowInfos: [], grid: none(), gridId: gridId, loadingState: const _Loading(), @@ -171,8 +172,8 @@ class GridLoadingState with _$GridLoadingState { } class GridFieldEquatable extends Equatable { - final List _fields; - const GridFieldEquatable(List fields) : _fields = fields; + final List _fields; + const GridFieldEquatable(List fields) : _fields = fields; @override List get props { @@ -182,5 +183,5 @@ class GridFieldEquatable extends Equatable { ]; } - UnmodifiableListView get value => UnmodifiableListView(_fields); + UnmodifiableListView get value => UnmodifiableListView(_fields); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart index 3e4db25b3d..b34064da13 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart @@ -34,7 +34,7 @@ class GridHeaderBloc extends Bloc { } Future _moveField(_MoveField value, Emitter emit) async { - final fields = List.from(state.fields); + final fields = List.from(state.fields); fields.insert(value.toIndex, fields.removeAt(value.fromIndex)); emit(state.copyWith(fields: fields)); @@ -62,16 +62,16 @@ class GridHeaderBloc extends Bloc { @freezed class GridHeaderEvent with _$GridHeaderEvent { const factory GridHeaderEvent.initial() = _InitialHeader; - const factory GridHeaderEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; - const factory GridHeaderEvent.moveField(Field field, int fromIndex, int toIndex) = _MoveField; + const factory GridHeaderEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; + const factory GridHeaderEvent.moveField(GridFieldPB field, int fromIndex, int toIndex) = _MoveField; } @freezed class GridHeaderState with _$GridHeaderState { - const factory GridHeaderState({required List fields}) = _GridHeaderState; + const factory GridHeaderState({required List fields}) = _GridHeaderState; - factory GridHeaderState.initial(List fields) { - // final List newFields = List.from(fields); + factory GridHeaderState.initial(List fields) { + // final List newFields = List.from(fields); // newFields.retainWhere((field) => field.visibility); return GridHeaderState(fields: fields); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart index 391a5d13a2..1f8b4336fc 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/grid_service.dart @@ -19,55 +19,54 @@ class GridService { required this.gridId, }); - Future> loadGrid() async { - await FolderEventSetLatestView(ViewId(value: gridId)).send(); + Future> loadGrid() async { + await FolderEventSetLatestView(ViewIdPB(value: gridId)).send(); - final payload = GridId(value: gridId); + final payload = GridIdPB(value: gridId); return GridEventGetGrid(payload).send(); } - Future> createRow({Option? startRowId}) { - CreateRowPayload payload = CreateRowPayload.create()..gridId = gridId; + Future> createRow({Option? startRowId}) { + CreateRowPayloadPB payload = CreateRowPayloadPB.create()..gridId = gridId; startRowId?.fold(() => null, (id) => payload.startRowId = id); return GridEventCreateRow(payload).send(); } - Future> getFields({required List fieldOrders}) { - final payload = QueryFieldPayload.create() + Future> getFields({required List fieldIds}) { + final payload = QueryFieldPayloadPB.create() ..gridId = gridId - ..fieldOrders = RepeatedFieldOrder(items: fieldOrders); + ..fieldIds = RepeatedGridFieldIdPB(items: fieldIds); return GridEventGetFields(payload).send(); } Future> closeGrid() { - final request = ViewId(value: gridId); + final request = ViewIdPB(value: gridId); return FolderEventCloseView(request).send(); } } class FieldsNotifier extends ChangeNotifier { - List _fields = []; + List _fields = []; - set fields(List fields) { + set fields(List fields) { _fields = fields; notifyListeners(); } - List get fields => _fields; + List get fields => _fields; } -typedef FieldChangesetCallback = void Function(GridFieldChangeset); -typedef FieldsCallback = void Function(List); +typedef FieldChangesetCallback = void Function(GridFieldChangesetPB); +typedef FieldsCallback = void Function(List); class GridFieldCache { final String gridId; - late final GridFieldsListener _fieldListener; + final GridFieldsListener _fieldListener; FieldsNotifier? _fieldNotifier = FieldsNotifier(); final Map _fieldsCallbackMap = {}; final Map _changesetCallbackMap = {}; - GridFieldCache({required this.gridId}) { - _fieldListener = GridFieldsListener(gridId: gridId); + GridFieldCache({required this.gridId}) : _fieldListener = GridFieldsListener(gridId: gridId) { _fieldListener.start(onFieldsChanged: (result) { result.fold( (changeset) { @@ -89,11 +88,11 @@ class GridFieldCache { _fieldNotifier = null; } - UnmodifiableListView get unmodifiableFields => UnmodifiableListView(_fieldNotifier?.fields ?? []); + UnmodifiableListView get unmodifiableFields => UnmodifiableListView(_fieldNotifier?.fields ?? []); - List get fields => [..._fieldNotifier?.fields ?? []]; + List get fields => [..._fieldNotifier?.fields ?? []]; - set fields(List fields) { + set fields(List fields) { _fieldNotifier?.fields = [...fields]; } @@ -142,12 +141,12 @@ class GridFieldCache { } } - void _deleteFields(List deletedFields) { + void _deleteFields(List deletedFields) { if (deletedFields.isEmpty) { return; } - final List newFields = fields; - final Map deletedFieldMap = { + final List newFields = fields; + final Map deletedFieldMap = { for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder }; @@ -155,11 +154,11 @@ class GridFieldCache { _fieldNotifier?.fields = newFields; } - void _insertFields(List insertedFields) { + void _insertFields(List insertedFields) { if (insertedFields.isEmpty) { return; } - final List newFields = fields; + final List newFields = fields; for (final indexField in insertedFields) { if (newFields.length > indexField.index) { newFields.insert(indexField.index, indexField.field_1); @@ -170,11 +169,11 @@ class GridFieldCache { _fieldNotifier?.fields = newFields; } - void _updateFields(List updatedFields) { + void _updateFields(List updatedFields) { if (updatedFields.isEmpty) { return; } - final List newFields = fields; + final List newFields = fields; for (final updatedField in updatedFields) { final index = newFields.indexWhere((field) => field.id == updatedField.id); if (index != -1) { @@ -186,14 +185,14 @@ class GridFieldCache { } } -class GridRowCacheDelegateImpl extends GridRowCacheDelegate { +class GridRowCacheFieldNotifierImpl extends GridRowCacheFieldNotifier { final GridFieldCache _cache; FieldChangesetCallback? _onChangesetFn; FieldsCallback? _onFieldFn; - GridRowCacheDelegateImpl(GridFieldCache cache) : _cache = cache; + GridRowCacheFieldNotifierImpl(GridFieldCache cache) : _cache = cache; @override - UnmodifiableListView get fields => _cache.unmodifiableFields; + UnmodifiableListView get fields => _cache.unmodifiableFields; @override void onFieldsChanged(VoidCallback callback) { @@ -202,8 +201,8 @@ class GridRowCacheDelegateImpl extends GridRowCacheDelegate { } @override - void onFieldUpdated(void Function(Field) callback) { - _onChangesetFn = (GridFieldChangeset changeset) { + void onFieldChanged(void Function(GridFieldPB) callback) { + _onChangesetFn = (GridFieldChangesetPB changeset) { for (final updatedField in changeset.updatedFields) { callback(updatedField); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/prelude.dart b/frontend/app_flowy/lib/workspace/application/grid/prelude.dart index 6ea198f303..2a19ca1134 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/prelude.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/prelude.dart @@ -4,18 +4,18 @@ export 'row/row_service.dart'; export 'grid_service.dart'; export 'grid_header_bloc.dart'; -// Field +// GridFieldPB export 'field/field_service.dart'; export 'field/field_action_sheet_bloc.dart'; export 'field/field_editor_bloc.dart'; -export 'field/field_editor_pannel_bloc.dart'; +export 'field/field_type_option_edit_bloc.dart'; -// Field Type Option +// GridFieldPB Type Option export 'field/type_option/date_bloc.dart'; export 'field/type_option/number_bloc.dart'; export 'field/type_option/single_select_type_option.dart'; -// Cell +// GridCellPB export 'cell/text_cell_bloc.dart'; export 'cell/number_cell_bloc.dart'; export 'cell/select_option_cell_bloc.dart'; diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart index 8754917223..9fe12f3ff5 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart @@ -11,11 +11,11 @@ part 'row_action_sheet_bloc.freezed.dart'; class RowActionSheetBloc extends Bloc { final RowService _rowService; - RowActionSheetBloc({required GridRow rowData}) + RowActionSheetBloc({required GridRowInfo rowData}) : _rowService = RowService( gridId: rowData.gridId, blockId: rowData.blockId, - rowId: rowData.rowId, + rowId: rowData.id, ), super(RowActionSheetState.initial(rowData)) { on( @@ -53,10 +53,10 @@ class RowActionSheetEvent with _$RowActionSheetEvent { @freezed class RowActionSheetState with _$RowActionSheetState { const factory RowActionSheetState({ - required GridRow rowData, + required GridRowInfo rowData, }) = _RowActionSheetState; - factory RowActionSheetState.initial(GridRow rowData) => RowActionSheetState( + factory RowActionSheetState.initial(GridRowInfo rowData) => RowActionSheetState( rowData: rowData, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart index 7f565b500a..1d7224383d 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart @@ -11,19 +11,19 @@ part 'row_bloc.freezed.dart'; class RowBloc extends Bloc { final RowService _rowService; - final GridRowCacheService _rowCache; + final GridRowCache _rowCache; void Function()? _rowListenFn; RowBloc({ - required GridRow rowData, - required GridRowCacheService rowCache, + required GridRowInfo rowInfo, + required GridRowCache rowCache, }) : _rowService = RowService( - gridId: rowData.gridId, - blockId: rowData.blockId, - rowId: rowData.rowId, + gridId: rowInfo.gridId, + blockId: rowInfo.blockId, + rowId: rowInfo.id, ), _rowCache = rowCache, - super(RowState.initial(rowData, rowCache.loadGridCells(rowData.rowId))) { + super(RowState.initial(rowInfo, rowCache.loadGridCells(rowInfo.id))) { on( (event, emit) async { await event.map( @@ -58,7 +58,7 @@ class RowBloc extends Bloc { Future _startListening() async { _rowListenFn = _rowCache.addListener( - rowId: state.rowData.rowId, + rowId: state.rowInfo.id, onCellUpdated: (cellDatas, reason) => add(RowEvent.didReceiveCellDatas(cellDatas, reason)), listenWhen: () => !isClosed, ); @@ -76,23 +76,23 @@ class RowEvent with _$RowEvent { @freezed class RowState with _$RowState { const factory RowState({ - required GridRow rowData, + required GridRowInfo rowInfo, required GridCellMap gridCellMap, required UnmodifiableListView snapshots, GridRowChangeReason? changeReason, }) = _RowState; - factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState( - rowData: rowData, + factory RowState.initial(GridRowInfo rowInfo, GridCellMap cellDataMap) => RowState( + rowInfo: rowInfo, gridCellMap: cellDataMap, snapshots: UnmodifiableListView(cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()), ); } class GridCellEquatable extends Equatable { - final Field _field; + final GridFieldPB _field; - const GridCellEquatable(Field field) : _field = field; + const GridCellEquatable(GridFieldPB field) : _field = field; @override List get props => [ diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart index f17f0f4cb6..966310fe8c 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart @@ -7,13 +7,13 @@ import 'row_service.dart'; part 'row_detail_bloc.freezed.dart'; class RowDetailBloc extends Bloc { - final GridRow rowData; - final GridRowCacheService _rowCache; + final GridRowInfo rowInfo; + final GridRowCache _rowCache; void Function()? _rowListenFn; RowDetailBloc({ - required this.rowData, - required GridRowCacheService rowCache, + required this.rowInfo, + required GridRowCache rowCache, }) : _rowCache = rowCache, super(RowDetailState.initial()) { on( @@ -41,14 +41,14 @@ class RowDetailBloc extends Bloc { Future _startListening() async { _rowListenFn = _rowCache.addListener( - rowId: rowData.rowId, + rowId: rowInfo.id, onCellUpdated: (cellDatas, reason) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())), listenWhen: () => !isClosed, ); } Future _loadCellData() async { - final cellDataMap = _rowCache.loadGridCells(rowData.rowId); + final cellDataMap = _rowCache.loadGridCells(rowInfo.id); if (!isClosed) { add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList())); } @@ -58,13 +58,13 @@ class RowDetailBloc extends Bloc { @freezed class RowDetailEvent with _$RowDetailEvent { const factory RowDetailEvent.initial() = _Initial; - const factory RowDetailEvent.didReceiveCellDatas(List gridCells) = _DidReceiveCellDatas; + const factory RowDetailEvent.didReceiveCellDatas(List gridCells) = _DidReceiveCellDatas; } @freezed class RowDetailState with _$RowDetailState { const factory RowDetailState({ - required List gridCells, + required List gridCells, }) = _RowDetailState; factory RowDetailState.initial() => RowDetailState( diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart index 98fddaeccf..9aa829d617 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart @@ -8,8 +8,8 @@ import 'dart:typed_data'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -typedef UpdateRowNotifiedValue = Either; -typedef UpdateFieldNotifiedValue = Either, FlowyError>; +typedef UpdateRowNotifiedValue = Either; +typedef UpdateFieldNotifiedValue = Either, FlowyError>; class RowListener { final String rowId; @@ -26,7 +26,7 @@ class RowListener { switch (ty) { case GridNotification.DidUpdateRow: result.fold( - (payload) => updateRowNotifier?.value = left(Row.fromBuffer(payload)), + (payload) => updateRowNotifier?.value = left(GridRowPB.fromBuffer(payload)), (error) => updateRowNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart index c31a0660aa..f8f9c7ee3c 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart @@ -1,5 +1,4 @@ import 'dart:collection'; - import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; @@ -15,118 +14,135 @@ part 'row_service.freezed.dart'; typedef RowUpdateCallback = void Function(); -abstract class GridRowCacheDelegate with GridCellCacheDelegate { - UnmodifiableListView get fields; - void onFieldsChanged(void Function() callback); +abstract class GridRowCacheFieldNotifier { + UnmodifiableListView get fields; + void onFieldsChanged(VoidCallback callback); + void onFieldChanged(void Function(GridFieldPB) callback); void dispose(); } -class GridRowCacheService { +/// Cache the rows in memory +/// Insert / delete / update row +/// +/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information. + +class GridRowCache { final String gridId; - final GridBlock block; - final _Notifier _notifier; - List _rows = []; - final HashMap _rowByRowId; - final GridRowCacheDelegate _delegate; - final GridCellCacheService _cellCache; + final GridBlockPB block; - List get rows => _rows; - GridCellCacheService get cellCache => _cellCache; + /// _rows containers the current block's rows + /// Use List to reverse the order of the GridRow. + List _rowInfos = []; - GridRowCacheService({ + /// Use Map for faster access the raw row data. + final HashMap _rowByRowId; + + final GridCellCache _cellCache; + final GridRowCacheFieldNotifier _fieldNotifier; + final _GridRowChangesetNotifier _rowChangeReasonNotifier; + + UnmodifiableListView get rows => UnmodifiableListView(_rowInfos); + GridCellCache get cellCache => _cellCache; + + GridRowCache({ required this.gridId, required this.block, - required GridRowCacheDelegate delegate, - }) : _cellCache = GridCellCacheService(gridId: gridId, delegate: delegate), + required GridRowCacheFieldNotifier notifier, + }) : _cellCache = GridCellCache(gridId: gridId), _rowByRowId = HashMap(), - _notifier = _Notifier(), - _delegate = delegate { + _rowChangeReasonNotifier = _GridRowChangesetNotifier(), + _fieldNotifier = notifier { // - delegate.onFieldsChanged(() => _notifier.receive(const GridRowChangeReason.fieldDidChange())); - _rows = block.rowInfos.map((rowInfo) => buildGridRow(rowInfo)).toList(); + notifier.onFieldsChanged(() => _rowChangeReasonNotifier.receive(const GridRowChangeReason.fieldDidChange())); + notifier.onFieldChanged((field) => _cellCache.remove(field.id)); + _rowInfos = block.rows.map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble())).toList(); } Future dispose() async { - _delegate.dispose(); - _notifier.dispose(); + _fieldNotifier.dispose(); + _rowChangeReasonNotifier.dispose(); await _cellCache.dispose(); } - void applyChangesets(List changesets) { + void applyChangesets(List changesets) { for (final changeset in changesets) { _deleteRows(changeset.deletedRows); _insertRows(changeset.insertedRows); _updateRows(changeset.updatedRows); + _hideRows(changeset.hideRows); + _showRows(changeset.visibleRows); } } - void _deleteRows(List deletedRows) { + void _deleteRows(List deletedRows) { if (deletedRows.isEmpty) { return; } - final List newRows = []; + final List newRows = []; final DeletedIndexs deletedIndex = []; - final Map deletedRowByRowId = {for (var e in deletedRows) e.rowId: e}; + final Map deletedRowByRowId = {for (var rowId in deletedRows) rowId: rowId}; - _rows.asMap().forEach((index, row) { - if (deletedRowByRowId[row.rowId] == null) { + _rowInfos.asMap().forEach((index, row) { + if (deletedRowByRowId[row.id] == null) { newRows.add(row); } else { + _rowByRowId.remove(row.id); deletedIndex.add(DeletedIndex(index: index, row: row)); } }); - _rows = newRows; - _notifier.receive(GridRowChangeReason.delete(deletedIndex)); + _rowInfos = newRows; + _rowChangeReasonNotifier.receive(GridRowChangeReason.delete(deletedIndex)); } - void _insertRows(List insertRows) { + void _insertRows(List insertRows) { if (insertRows.isEmpty) { return; } InsertedIndexs insertIndexs = []; - final List newRows = _rows; for (final insertRow in insertRows) { final insertIndex = InsertedIndex( index: insertRow.index, - rowId: insertRow.rowInfo.rowId, + rowId: insertRow.rowId, ); insertIndexs.add(insertIndex); - newRows.insert(insertRow.index, (buildGridRow(insertRow.rowInfo))); + _rowInfos.insert(insertRow.index, (buildGridRow(insertRow.rowId, insertRow.height.toDouble()))); } - _notifier.receive(GridRowChangeReason.insert(insertIndexs)); + _rowChangeReasonNotifier.receive(GridRowChangeReason.insert(insertIndexs)); } - void _updateRows(List updatedRows) { + void _updateRows(List updatedRows) { if (updatedRows.isEmpty) { return; } final UpdatedIndexs updatedIndexs = UpdatedIndexs(); - final List newRows = _rows; for (final updatedRow in updatedRows) { - final rowOrder = updatedRow.rowInfo; - final rowId = updatedRow.rowInfo.rowId; - final index = newRows.indexWhere((row) => row.rowId == rowId); + final rowId = updatedRow.rowId; + final index = _rowInfos.indexWhere((row) => row.id == rowId); if (index != -1) { _rowByRowId[rowId] = updatedRow.row; - newRows.removeAt(index); - newRows.insert(index, buildGridRow(rowOrder)); + _rowInfos.removeAt(index); + _rowInfos.insert(index, buildGridRow(rowId, updatedRow.row.height.toDouble())); updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId); } } - _notifier.receive(GridRowChangeReason.update(updatedIndexs)); + _rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs)); } + void _hideRows(List hideRows) {} + + void _showRows(List visibleRows) {} + void onRowsChanged( void Function(GridRowChangeReason) onRowChanged, ) { - _notifier.addListener(() { - onRowChanged(_notifier._reason); + _rowChangeReasonNotifier.addListener(() { + onRowChanged(_rowChangeReasonNotifier.reason); }); } @@ -145,12 +161,12 @@ class GridRowCacheService { final row = _rowByRowId[rowId]; if (row != null) { final GridCellMap cellDataMap = _makeGridCells(rowId, row); - onCellUpdated(cellDataMap, _notifier._reason); + onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason); } } } - _notifier._reason.whenOrNull( + _rowChangeReasonNotifier.reason.whenOrNull( update: (indexs) { if (indexs[rowId] != null) notifyUpdate(); }, @@ -158,16 +174,16 @@ class GridRowCacheService { ); } - _notifier.addListener(listenrHandler); + _rowChangeReasonNotifier.addListener(listenrHandler); return listenrHandler; } void removeRowListener(VoidCallback callback) { - _notifier.removeListener(callback); + _rowChangeReasonNotifier.removeListener(callback); } GridCellMap loadGridCells(String rowId) { - final Row? data = _rowByRowId[rowId]; + final GridRowPB? data = _rowByRowId[rowId]; if (data == null) { _loadRow(rowId); } @@ -175,7 +191,7 @@ class GridRowCacheService { } Future _loadRow(String rowId) async { - final payload = GridRowIdPayload.create() + final payload = GridRowIdPayloadPB.create() ..gridId = gridId ..blockId = block.id ..rowId = rowId; @@ -187,11 +203,11 @@ class GridRowCacheService { ); } - GridCellMap _makeGridCells(String rowId, Row? row) { + GridCellMap _makeGridCells(String rowId, GridRowPB? row) { var cellDataMap = GridCellMap.new(); - for (final field in _delegate.fields) { + for (final field in _fieldNotifier.fields) { if (field.visibility) { - cellDataMap[field.id] = GridCell( + cellDataMap[field.id] = GridCellIdentifier( rowId: rowId, gridId: gridId, field: field, @@ -201,7 +217,7 @@ class GridRowCacheService { return cellDataMap; } - void _refreshRow(OptionalRow optionRow) { + void _refreshRow(OptionalRowPB optionRow) { if (!optionRow.hasRow()) { return; } @@ -209,41 +225,41 @@ class GridRowCacheService { updatedRow.freeze(); _rowByRowId[updatedRow.id] = updatedRow; - final index = _rows.indexWhere((gridRow) => gridRow.rowId == updatedRow.id); + final index = _rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id); if (index != -1) { // update the corresponding row in _rows if they are not the same - if (_rows[index].data != updatedRow) { - final row = _rows.removeAt(index).copyWith(data: updatedRow); - _rows.insert(index, row); + if (_rowInfos[index].rawRow != updatedRow) { + final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow); + _rowInfos.insert(index, row); // Calculate the update index final UpdatedIndexs updatedIndexs = UpdatedIndexs(); - updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId); + updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id); // - _notifier.receive(GridRowChangeReason.update(updatedIndexs)); + _rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs)); } } } - GridRow buildGridRow(BlockRowInfo rowInfo) { - return GridRow( + GridRowInfo buildGridRow(String rowId, double rowHeight) { + return GridRowInfo( gridId: gridId, blockId: block.id, - fields: _delegate.fields, - rowId: rowInfo.rowId, - height: rowInfo.height.toDouble(), + fields: _fieldNotifier.fields, + id: rowId, + height: rowHeight, ); } } -class _Notifier extends ChangeNotifier { - GridRowChangeReason _reason = const InitialListState(); +class _GridRowChangesetNotifier extends ChangeNotifier { + GridRowChangeReason reason = const InitialListState(); - _Notifier(); + _GridRowChangesetNotifier(); - void receive(GridRowChangeReason reason) { - _reason = reason; + void receive(GridRowChangeReason newReason) { + reason = newReason; reason.map( insert: (_) => notifyListeners(), delete: (_) => notifyListeners(), @@ -261,8 +277,8 @@ class RowService { RowService({required this.gridId, required this.blockId, required this.rowId}); - Future> createRow() { - CreateRowPayload payload = CreateRowPayload.create() + Future> createRow() { + CreateRowPayloadPB payload = CreateRowPayloadPB.create() ..gridId = gridId ..startRowId = rowId; @@ -270,18 +286,18 @@ class RowService { } Future> moveRow(String rowId, int fromIndex, int toIndex) { - final payload = MoveItemPayload.create() + final payload = MoveItemPayloadPB.create() ..gridId = gridId ..itemId = rowId - ..ty = MoveItemType.MoveRow + ..ty = MoveItemTypePB.MoveRow ..fromIndex = fromIndex ..toIndex = toIndex; return GridEventMoveItem(payload).send(); } - Future> getRow() { - final payload = GridRowIdPayload.create() + Future> getRow() { + final payload = GridRowIdPayloadPB.create() ..gridId = gridId ..blockId = blockId ..rowId = rowId; @@ -290,7 +306,7 @@ class RowService { } Future> deleteRow() { - final payload = GridRowIdPayload.create() + final payload = GridRowIdPayloadPB.create() ..gridId = gridId ..blockId = blockId ..rowId = rowId; @@ -299,7 +315,7 @@ class RowService { } Future> duplicateRow() { - final payload = GridRowIdPayload.create() + final payload = GridRowIdPayloadPB.create() ..gridId = gridId ..blockId = blockId ..rowId = rowId; @@ -309,15 +325,15 @@ class RowService { } @freezed -class GridRow with _$GridRow { - const factory GridRow({ +class GridRowInfo with _$GridRowInfo { + const factory GridRowInfo({ required String gridId, required String blockId, - required String rowId, - required UnmodifiableListView fields, + required String id, + required UnmodifiableListView fields, required double height, - Row? data, - }) = _GridRow; + GridRowPB? rawRow, + }) = _GridRowInfo; } typedef InsertedIndexs = List; @@ -344,7 +360,7 @@ class InsertedIndex { class DeletedIndex { final int index; - final GridRow row; + final GridRowInfo row; DeletedIndex({ required this.index, required this.row, diff --git a/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart index 10c59559d3..ee16eb0455 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart @@ -10,7 +10,7 @@ part 'property_bloc.freezed.dart'; class GridPropertyBloc extends Bloc { final GridFieldCache _fieldCache; - Function(List)? _onFieldsFn; + Function(List)? _onFieldsFn; GridPropertyBloc({required String gridId, required GridFieldCache fieldCache}) : _fieldCache = fieldCache, @@ -62,7 +62,7 @@ class GridPropertyBloc extends Bloc { class GridPropertyEvent with _$GridPropertyEvent { const factory GridPropertyEvent.initial() = _Initial; const factory GridPropertyEvent.setFieldVisibility(String fieldId, bool visibility) = _SetFieldVisibility; - const factory GridPropertyEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; + const factory GridPropertyEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; const factory GridPropertyEvent.moveField(int fromIndex, int toIndex) = _MoveField; } @@ -70,10 +70,10 @@ class GridPropertyEvent with _$GridPropertyEvent { class GridPropertyState with _$GridPropertyState { const factory GridPropertyState({ required String gridId, - required List fields, + required List fields, }) = _GridPropertyState; - factory GridPropertyState.initial(String gridId, List fields) => GridPropertyState( + factory GridPropertyState.initial(String gridId, List fields) => GridPropertyState( gridId: gridId, fields: fields, ); diff --git a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart index 4b840634f5..af637ea1c7 100644 --- a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart @@ -3,7 +3,7 @@ import 'package:app_flowy/workspace/application/edit_pannel/edit_context.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart' show CurrentWorkspaceSetting; +import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart' show CurrentWorkspaceSettingPB; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -13,45 +13,51 @@ part 'home_bloc.freezed.dart'; class HomeBloc extends Bloc { final UserWorkspaceListener _listener; - HomeBloc(UserProfile user, CurrentWorkspaceSetting workspaceSetting) + HomeBloc(UserProfilePB user, CurrentWorkspaceSettingPB workspaceSetting) : _listener = UserWorkspaceListener(userProfile: user), super(HomeState.initial(workspaceSetting)) { - on((event, emit) async { - await event.map( - initial: (_Initial value) { - _listener.start( - onAuthChanged: (result) => _authDidChanged(result), - onSettingUpdated: (result) { - result.fold( - (setting) => add(HomeEvent.didReceiveWorkspaceSetting(setting)), - (r) => Log.error(r), - ); - }, - ); - }, - showLoading: (e) async { - emit(state.copyWith(isLoading: e.isLoading)); - }, - setEditPannel: (e) async { - emit(state.copyWith(pannelContext: some(e.editContext))); - }, - dismissEditPannel: (value) async { - emit(state.copyWith(pannelContext: none())); - }, - forceCollapse: (e) async { - emit(state.copyWith(forceCollapse: e.forceCollapse)); - }, - didReceiveWorkspaceSetting: (_DidReceiveWorkspaceSetting value) { - emit(state.copyWith(workspaceSetting: value.setting)); - }, - unauthorized: (_Unauthorized value) { - emit(state.copyWith(unauthorized: true)); - }, - collapseMenu: (e) { - emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed)); - }, - ); - }); + on( + (event, emit) async { + await event.map( + initial: (_Initial value) { + _listener.start( + onAuthChanged: (result) => _authDidChanged(result), + onSettingUpdated: (result) { + result.fold( + (setting) => add(HomeEvent.didReceiveWorkspaceSetting(setting)), + (r) => Log.error(r), + ); + }, + ); + }, + showLoading: (e) async { + emit(state.copyWith(isLoading: e.isLoading)); + }, + setEditPannel: (e) async { + emit(state.copyWith(pannelContext: some(e.editContext))); + }, + dismissEditPannel: (value) async { + emit(state.copyWith(pannelContext: none())); + }, + forceCollapse: (e) async { + emit(state.copyWith(forceCollapse: e.forceCollapse)); + }, + didReceiveWorkspaceSetting: (_DidReceiveWorkspaceSetting value) { + emit(state.copyWith(workspaceSetting: value.setting)); + }, + unauthorized: (_Unauthorized value) { + emit(state.copyWith(unauthorized: true)); + }, + collapseMenu: (e) { + emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed)); + }, + editPannelResized: (e) { + final newOffset = (state.resizeOffset + e.offset).clamp(-50, 200).toDouble(); + emit(state.copyWith(resizeOffset: newOffset)); + }, + ); + }, + ); } @override @@ -76,9 +82,10 @@ class HomeEvent with _$HomeEvent { const factory HomeEvent.forceCollapse(bool forceCollapse) = _ForceCollapse; const factory HomeEvent.setEditPannel(EditPannelContext editContext) = _ShowEditPannel; const factory HomeEvent.dismissEditPannel() = _DismissEditPannel; - const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSetting setting) = _DidReceiveWorkspaceSetting; + const factory HomeEvent.didReceiveWorkspaceSetting(CurrentWorkspaceSettingPB setting) = _DidReceiveWorkspaceSetting; const factory HomeEvent.unauthorized(String msg) = _Unauthorized; const factory HomeEvent.collapseMenu() = _CollapseMenu; + const factory HomeEvent.editPannelResized(double offset) = _EditPannelResized; } @freezed @@ -87,17 +94,19 @@ class HomeState with _$HomeState { required bool isLoading, required bool forceCollapse, required Option pannelContext, - required CurrentWorkspaceSetting workspaceSetting, + required CurrentWorkspaceSettingPB workspaceSetting, required bool unauthorized, required bool isMenuCollapsed, + required double resizeOffset, }) = _HomeState; - factory HomeState.initial(CurrentWorkspaceSetting workspaceSetting) => HomeState( + factory HomeState.initial(CurrentWorkspaceSettingPB workspaceSetting) => HomeState( isLoading: false, forceCollapse: false, pannelContext: none(), workspaceSetting: workspaceSetting, unauthorized: false, isMenuCollapsed: false, + resizeOffset: 0, ); } diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart index b3885d7b6b..21d51f5a93 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart @@ -41,7 +41,7 @@ class MenuBloc extends Bloc { if (state.apps.length > value.fromIndex) { final app = state.apps[value.fromIndex]; _workspaceService.moveApp(appId: app.id, fromIndex: value.fromIndex, toIndex: value.toIndex); - final apps = List.from(state.apps); + final apps = List.from(state.apps); apps.insert(value.toIndex, apps.removeAt(value.fromIndex)); emit(state.copyWith(apps: apps)); } @@ -79,7 +79,7 @@ class MenuBloc extends Bloc { )); } - void _handleAppsOrFail(Either, FlowyError> appsOrFail) { + void _handleAppsOrFail(Either, FlowyError> appsOrFail) { appsOrFail.fold( (apps) => add(MenuEvent.didReceiveApps(left(apps))), (error) => add(MenuEvent.didReceiveApps(right(error))), @@ -93,13 +93,13 @@ class MenuEvent with _$MenuEvent { const factory MenuEvent.openPage(Plugin plugin) = _OpenPage; const factory MenuEvent.createApp(String name, {String? desc}) = _CreateApp; const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp; - const factory MenuEvent.didReceiveApps(Either, FlowyError> appsOrFail) = _ReceiveApps; + const factory MenuEvent.didReceiveApps(Either, FlowyError> appsOrFail) = _ReceiveApps; } @freezed class MenuState with _$MenuState { const factory MenuState({ - required List apps, + required List apps, required Either successOrFailure, required Plugin plugin, }) = _MenuState; diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_user_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_user_bloc.dart index d590621e2d..e6cadcfe3a 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_user_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_user_bloc.dart @@ -14,7 +14,7 @@ class MenuUserBloc extends Bloc { final UserService _userService; final UserListener _userListener; final UserWorkspaceListener _userWorkspaceListener; - final UserProfile userProfile; + final UserProfilePB userProfile; MenuUserBloc(this.userProfile) : _userListener = UserListener(userProfile: userProfile), @@ -31,7 +31,7 @@ class MenuUserBloc extends Bloc { fetchWorkspaces: () async { // }, - didReceiveUserProfile: (UserProfile newUserProfile) { + didReceiveUserProfile: (UserProfilePB newUserProfile) { emit(state.copyWith(userProfile: newUserProfile)); }, ); @@ -50,14 +50,14 @@ class MenuUserBloc extends Bloc { result.fold((l) => null, (error) => Log.error(error)); } - void _profileUpdated(Either userProfileOrFailed) { + void _profileUpdated(Either userProfileOrFailed) { userProfileOrFailed.fold( (newUserProfile) => add(MenuUserEvent.didReceiveUserProfile(newUserProfile)), (err) => Log.error(err), ); } - void _workspaceListUpdated(Either, FlowyError> workspacesOrFailed) { + void _workspaceListUpdated(Either, FlowyError> workspacesOrFailed) { // Do nothing by now } } @@ -66,18 +66,19 @@ class MenuUserBloc extends Bloc { class MenuUserEvent with _$MenuUserEvent { const factory MenuUserEvent.initial() = _Initial; const factory MenuUserEvent.fetchWorkspaces() = _FetchWorkspaces; - const factory MenuUserEvent.didReceiveUserProfile(UserProfile newUserProfile) = _DidReceiveUserProfile; + const factory MenuUserEvent.updateUserName(String name) = _UpdateUserName; + const factory MenuUserEvent.didReceiveUserProfile(UserProfilePB newUserProfile) = _DidReceiveUserProfile; } @freezed class MenuUserState with _$MenuUserState { const factory MenuUserState({ - required UserProfile userProfile, - required Option> workspaces, + required UserProfilePB userProfile, + required Option> workspaces, required Either successOrFailure, }) = _MenuUserState; - factory MenuUserState.initial(UserProfile userProfile) => MenuUserState( + factory MenuUserState.initial(UserProfilePB userProfile) => MenuUserState( userProfile: userProfile, workspaces: none(), successOrFailure: left(unit), diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart index 6d9f4ce9ef..e70dfd179a 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart @@ -62,7 +62,7 @@ class ViewSectionBloc extends Bloc { Future _moveView(_MoveView value, Emitter emit) async { if (value.fromIndex < state.views.length) { final viewId = state.views[value.fromIndex].id; - final views = List.from(state.views); + final views = List.from(state.views); views.insert(value.toIndex, views.removeAt(value.fromIndex)); emit(state.copyWith(views: views)); @@ -92,16 +92,16 @@ class ViewSectionBloc extends Bloc { @freezed class ViewSectionEvent with _$ViewSectionEvent { const factory ViewSectionEvent.initial() = _Initial; - const factory ViewSectionEvent.setSelectedView(View? view) = _SetSelectedView; + const factory ViewSectionEvent.setSelectedView(ViewPB? view) = _SetSelectedView; const factory ViewSectionEvent.moveView(int fromIndex, int toIndex) = _MoveView; - const factory ViewSectionEvent.didReceiveViewUpdated(List views) = _DidReceiveViewUpdated; + const factory ViewSectionEvent.didReceiveViewUpdated(List views) = _DidReceiveViewUpdated; } @freezed class ViewSectionState with _$ViewSectionState { const factory ViewSectionState({ - required List views, - View? selectedView, + required List views, + ViewPB? selectedView, }) = _ViewSectionState; factory ViewSectionState.initial(AppViewDataContext appViewData) => ViewSectionState( diff --git a/frontend/app_flowy/lib/workspace/application/trash/trash_bloc.dart b/frontend/app_flowy/lib/workspace/application/trash/trash_bloc.dart index 8c83cd4d2f..a7bcf0588c 100644 --- a/frontend/app_flowy/lib/workspace/application/trash/trash_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/trash/trash_bloc.dart @@ -45,7 +45,7 @@ class TrashBloc extends Bloc { )); } - void _listenTrashUpdated(Either, FlowyError> trashOrFailed) { + void _listenTrashUpdated(Either, FlowyError> trashOrFailed) { trashOrFailed.fold( (trash) { add(TrashEvent.didReceiveTrash(trash)); @@ -66,9 +66,9 @@ class TrashBloc extends Bloc { @freezed class TrashEvent with _$TrashEvent { const factory TrashEvent.initial() = Initial; - const factory TrashEvent.didReceiveTrash(List trash) = ReceiveTrash; + const factory TrashEvent.didReceiveTrash(List trash) = ReceiveTrash; const factory TrashEvent.putback(String trashId) = Putback; - const factory TrashEvent.delete(Trash trash) = Delete; + const factory TrashEvent.delete(TrashPB trash) = Delete; const factory TrashEvent.restoreAll() = RestoreAll; const factory TrashEvent.deleteAll() = DeleteAll; } @@ -76,7 +76,7 @@ class TrashEvent with _$TrashEvent { @freezed class TrashState with _$TrashState { const factory TrashState({ - required List objects, + required List objects, required Either successOrFailure, }) = _TrashState; diff --git a/frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart b/frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart index 51244e23bb..0b4e142058 100644 --- a/frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart @@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart'; import 'package:flowy_sdk/rust_stream.dart'; -typedef TrashUpdatedCallback = void Function(Either, FlowyError> trashOrFailed); +typedef TrashUpdatedCallback = void Function(Either, FlowyError> trashOrFailed); class TrashListener { StreamSubscription? _subscription; @@ -27,7 +27,7 @@ class TrashListener { if (_trashUpdated != null) { result.fold( (payload) { - final repeatedTrash = RepeatedTrash.fromBuffer(payload); + final repeatedTrash = RepeatedTrashPB.fromBuffer(payload); _trashUpdated!(left(repeatedTrash.items)); }, (error) => _trashUpdated!(right(error)), diff --git a/frontend/app_flowy/lib/workspace/application/trash/trash_service.dart b/frontend/app_flowy/lib/workspace/application/trash/trash_service.dart index a2b6d47147..e782120a74 100644 --- a/frontend/app_flowy/lib/workspace/application/trash/trash_service.dart +++ b/frontend/app_flowy/lib/workspace/application/trash/trash_service.dart @@ -5,24 +5,24 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart'; class TrashService { - Future> readTrash() { + Future> readTrash() { return FolderEventReadTrash().send(); } Future> putback(String trashId) { - final id = TrashId.create()..id = trashId; + final id = TrashIdPB.create()..id = trashId; return FolderEventPutbackTrash(id).send(); } Future> deleteViews(List> trashList) { final items = trashList.map((trash) { - return TrashId.create() + return TrashIdPB.create() ..id = trash.value1 ..ty = trash.value2; }); - final ids = RepeatedTrashId(items: items); + final ids = RepeatedTrashIdPB(items: items); return FolderEventDeleteTrash(ids).send(); } diff --git a/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart b/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart index f87e863d45..bf3cfee6e3 100644 --- a/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart @@ -11,7 +11,7 @@ part 'view_bloc.freezed.dart'; class ViewBloc extends Bloc { final ViewService service; final ViewListener listener; - final View view; + final ViewPB view; ViewBloc({ required this.view, @@ -81,18 +81,18 @@ class ViewEvent with _$ViewEvent { const factory ViewEvent.rename(String newName) = Rename; const factory ViewEvent.delete() = Delete; const factory ViewEvent.duplicate() = Duplicate; - const factory ViewEvent.viewDidUpdate(Either result) = ViewDidUpdate; + const factory ViewEvent.viewDidUpdate(Either result) = ViewDidUpdate; } @freezed class ViewState with _$ViewState { const factory ViewState({ - required View view, + required ViewPB view, required bool isEditing, required Either successOrFailure, }) = _ViewState; - factory ViewState.init(View view) => ViewState( + factory ViewState.init(ViewPB view) => ViewState( view: view, isEditing: false, successOrFailure: left(unit), diff --git a/frontend/app_flowy/lib/workspace/application/view/view_ext.dart b/frontend/app_flowy/lib/workspace/application/view/view_ext.dart index 3987226a20..a54d5ecf40 100644 --- a/frontend/app_flowy/lib/workspace/application/view/view_ext.dart +++ b/frontend/app_flowy/lib/workspace/application/view/view_ext.dart @@ -32,7 +32,7 @@ extension FlowyPluginExtension on FlowyPlugin { } } -extension ViewExtension on View { +extension ViewExtension on ViewPB { Widget renderThumbnail({Color? iconColor}) { String thumbnail = "file_icon"; diff --git a/frontend/app_flowy/lib/workspace/application/view/view_listener.dart b/frontend/app_flowy/lib/workspace/application/view/view_listener.dart index 4acb5e021f..2b080ec194 100644 --- a/frontend/app_flowy/lib/workspace/application/view/view_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/view/view_listener.dart @@ -9,9 +9,9 @@ import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart'; import 'package:flowy_sdk/rust_stream.dart'; import 'package:flowy_infra/notifier.dart'; -typedef DeleteViewNotifyValue = Either; -typedef UpdateViewNotifiedValue = Either; -typedef RestoreViewNotifiedValue = Either; +typedef DeleteViewNotifyValue = Either; +typedef UpdateViewNotifiedValue = Either; +typedef RestoreViewNotifiedValue = Either; class ViewListener { StreamSubscription? _subscription; @@ -19,7 +19,7 @@ class ViewListener { final PublishNotifier _deletedNotifier = PublishNotifier(); final PublishNotifier _restoredNotifier = PublishNotifier(); FolderNotificationParser? _parser; - View view; + ViewPB view; ViewListener({ required this.view, @@ -62,19 +62,19 @@ class ViewListener { switch (ty) { case FolderNotification.ViewUpdated: result.fold( - (payload) => _updatedViewNotifier.value = left(View.fromBuffer(payload)), + (payload) => _updatedViewNotifier.value = left(ViewPB.fromBuffer(payload)), (error) => _updatedViewNotifier.value = right(error), ); break; case FolderNotification.ViewDeleted: result.fold( - (payload) => _deletedNotifier.value = left(View.fromBuffer(payload)), + (payload) => _deletedNotifier.value = left(ViewPB.fromBuffer(payload)), (error) => _deletedNotifier.value = right(error), ); break; case FolderNotification.ViewRestored: result.fold( - (payload) => _restoredNotifier.value = left(View.fromBuffer(payload)), + (payload) => _restoredNotifier.value = left(ViewPB.fromBuffer(payload)), (error) => _restoredNotifier.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/workspace/application/view/view_service.dart b/frontend/app_flowy/lib/workspace/application/view/view_service.dart index c8edd37782..b73cf25cad 100644 --- a/frontend/app_flowy/lib/workspace/application/view/view_service.dart +++ b/frontend/app_flowy/lib/workspace/application/view/view_service.dart @@ -5,13 +5,13 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; class ViewService { - Future> readView({required String viewId}) { - final request = ViewId(value: viewId); + Future> readView({required String viewId}) { + final request = ViewIdPB(value: viewId); return FolderEventReadView(request).send(); } - Future> updateView({required String viewId, String? name, String? desc}) { - final request = UpdateViewPayload.create()..viewId = viewId; + Future> updateView({required String viewId, String? name, String? desc}) { + final request = UpdateViewPayloadPB.create()..viewId = viewId; if (name != null) { request.name = name; @@ -25,12 +25,12 @@ class ViewService { } Future> delete({required String viewId}) { - final request = RepeatedViewId.create()..items.add(viewId); + final request = RepeatedViewIdPB.create()..items.add(viewId); return FolderEventDeleteView(request).send(); } Future> duplicate({required String viewId}) { - final request = ViewId(value: viewId); + final request = ViewIdPB(value: viewId); return FolderEventDuplicateView(request).send(); } } diff --git a/frontend/app_flowy/lib/workspace/application/workspace/welcome_bloc.dart b/frontend/app_flowy/lib/workspace/application/workspace/welcome_bloc.dart index dd3edb27c1..49c3ba19be 100644 --- a/frontend/app_flowy/lib/workspace/application/workspace/welcome_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/workspace/welcome_bloc.dart @@ -52,7 +52,7 @@ class WelcomeBloc extends Bloc { )); } - Future _openWorkspace(Workspace workspace, Emitter emit) async { + Future _openWorkspace(WorkspacePB workspace, Emitter emit) async { final result = await userService.openWorkspace(workspace.id); emit(result.fold( (workspaces) => state.copyWith(successOrFailure: left(unit)), @@ -82,8 +82,8 @@ class WelcomeEvent with _$WelcomeEvent { const factory WelcomeEvent.initial() = Initial; // const factory WelcomeEvent.fetchWorkspaces() = FetchWorkspace; const factory WelcomeEvent.createWorkspace(String name, String desc) = CreateWorkspace; - const factory WelcomeEvent.openWorkspace(Workspace workspace) = OpenWorkspace; - const factory WelcomeEvent.workspacesReveived(Either, FlowyError> workspacesOrFail) = + const factory WelcomeEvent.openWorkspace(WorkspacePB workspace) = OpenWorkspace; + const factory WelcomeEvent.workspacesReveived(Either, FlowyError> workspacesOrFail) = WorkspacesReceived; } @@ -91,7 +91,7 @@ class WelcomeEvent with _$WelcomeEvent { class WelcomeState with _$WelcomeState { const factory WelcomeState({ required bool isLoading, - required List workspaces, + required List workspaces, required Either successOrFailure, }) = _WelcomeState; diff --git a/frontend/app_flowy/lib/workspace/application/workspace/workspace_listener.dart b/frontend/app_flowy/lib/workspace/application/workspace/workspace_listener.dart index 1ee8de94fd..2d7a100e8b 100644 --- a/frontend/app_flowy/lib/workspace/application/workspace/workspace_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/workspace/workspace_listener.dart @@ -3,21 +3,21 @@ import 'dart:typed_data'; import 'package:app_flowy/core/folder_notification.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_infra/notifier.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart'; -typedef AppListNotifyValue = Either, FlowyError>; -typedef WorkspaceNotifyValue = Either; +typedef AppListNotifyValue = Either, FlowyError>; +typedef WorkspaceNotifyValue = Either; class WorkspaceListener { PublishNotifier? _appsChangedNotifier = PublishNotifier(); PublishNotifier? _workspaceUpdatedNotifier = PublishNotifier(); FolderNotificationListener? _listener; - final UserProfile user; + final UserProfilePB user; final String workspaceId; WorkspaceListener({ @@ -47,13 +47,13 @@ class WorkspaceListener { switch (ty) { case FolderNotification.WorkspaceUpdated: result.fold( - (payload) => _workspaceUpdatedNotifier?.value = left(Workspace.fromBuffer(payload)), + (payload) => _workspaceUpdatedNotifier?.value = left(WorkspacePB.fromBuffer(payload)), (error) => _workspaceUpdatedNotifier?.value = right(error), ); break; case FolderNotification.WorkspaceAppsChanged: result.fold( - (payload) => _appsChangedNotifier?.value = left(RepeatedApp.fromBuffer(payload).items), + (payload) => _appsChangedNotifier?.value = left(RepeatedAppPB.fromBuffer(payload).items), (error) => _appsChangedNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/workspace/application/workspace/workspace_service.dart b/frontend/app_flowy/lib/workspace/application/workspace/workspace_service.dart index 678794b594..4f68d4776a 100644 --- a/frontend/app_flowy/lib/workspace/application/workspace/workspace_service.dart +++ b/frontend/app_flowy/lib/workspace/application/workspace/workspace_service.dart @@ -5,7 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart' show MoveFolderItemPayload, MoveFolderItemType; +import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart' show MoveFolderItemPayloadPB, MoveFolderItemType; import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; @@ -15,16 +15,16 @@ class WorkspaceService { WorkspaceService({ required this.workspaceId, }); - Future> createApp({required String name, required String desc}) { - final payload = CreateAppPayload.create() + Future> createApp({required String name, required String desc}) { + final payload = CreateAppPayloadPB.create() ..name = name ..workspaceId = workspaceId ..desc = desc; return FolderEventCreateApp(payload).send(); } - Future> getWorkspace() { - final payload = WorkspaceId.create()..value = workspaceId; + Future> getWorkspace() { + final payload = WorkspaceIdPB.create()..value = workspaceId; return FolderEventReadWorkspaces(payload).send().then((result) { return result.fold( (workspaces) { @@ -41,8 +41,8 @@ class WorkspaceService { }); } - Future, FlowyError>> getApps() { - final payload = WorkspaceId.create()..value = workspaceId; + Future, FlowyError>> getApps() { + final payload = WorkspaceIdPB.create()..value = workspaceId; return FolderEventReadWorkspaceApps(payload).send().then((result) { return result.fold( (apps) => left(apps.items), @@ -56,7 +56,7 @@ class WorkspaceService { required int fromIndex, required int toIndex, }) { - final payload = MoveFolderItemPayload.create() + final payload = MoveFolderItemPayloadPB.create() ..itemId = appId ..from = fromIndex ..to = toIndex diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_layout.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_layout.dart index 0405279851..e7e02b9767 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_layout.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_layout.dart @@ -27,6 +27,8 @@ class HomeLayout { menuWidth = Sizes.sideBarLg; } + menuWidth += homeBlocState.resizeOffset; + if (forceCollapse) { showMenu = false; } else { diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart index f205daabac..abe89c14cb 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart @@ -5,8 +5,9 @@ import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_b import 'package:app_flowy/startup/startup.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_infra_ui/style_widget/container.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -18,8 +19,8 @@ import 'home_stack.dart'; import 'menu/menu.dart'; class HomeScreen extends StatefulWidget { - final UserProfile user; - final CurrentWorkspaceSetting workspaceSetting; + final UserProfilePB user; + final CurrentWorkspaceSettingPB workspaceSetting; const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key); @override @@ -27,7 +28,7 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { - View? initialView; + ViewPB? initialView; @override void initState() { @@ -87,6 +88,7 @@ class _HomeScreenState extends State { context: context, state: state, ); + final homeMenuResizer = _buildHomeMenuResizer(context: context); final editPannel = _buildEditPannel( homeState: state, layout: layout, @@ -99,6 +101,7 @@ class _HomeScreenState extends State { homeMenu: menu, editPannel: editPannel, bubble: bubble, + homeMenuResizer: homeMenuResizer, ); }, ); @@ -122,7 +125,10 @@ class _HomeScreenState extends State { ); final latestView = workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null; - getIt().latestOpenView = latestView; + if (getIt().latestOpenView == null) { + /// AppFlowy will open the view that the last time the user opened it. The _buildHomeMenu will get called when AppFlowy's screen resizes. So we only set the latestOpenView when it's null. + getIt().latestOpenView = latestView; + } return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu)); } @@ -147,12 +153,31 @@ class _HomeScreenState extends State { ); } + Widget _buildHomeMenuResizer({ + required BuildContext context, + }) { + return MouseRegion( + cursor: SystemMouseCursors.resizeLeftRight, + child: GestureDetector( + dragStartBehavior: DragStartBehavior.down, + onPanUpdate: ((details) { + context.read().add(HomeEvent.editPannelResized(details.delta.dx)); + }), + behavior: HitTestBehavior.translucent, + child: SizedBox( + width: 10, + height: MediaQuery.of(context).size.height, + )), + ); + } + Widget _layoutWidgets({ required HomeLayout layout, required Widget homeMenu, required Widget homeStack, required Widget editPannel, required Widget bubble, + required Widget homeMenuResizer, }) { return Stack( children: [ @@ -167,6 +192,7 @@ class _HomeScreenState extends State { .constrained(minWidth: 500) .positioned(left: layout.homePageLOffset, right: layout.homePageROffset, bottom: 0, top: 0, animate: true) .animate(layout.animDuration, Curves.easeOut), + homeMenuResizer.positioned(left: layout.homePageLOffset - 5), bubble .positioned( right: 20, diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart index 2d26aabc0b..dbeb2248cc 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart @@ -19,7 +19,7 @@ import 'add_button.dart'; import 'right_click_action.dart'; class MenuAppHeader extends StatelessWidget { - final App app; + final AppPB app; const MenuAppHeader( this.app, { Key? key, @@ -85,7 +85,7 @@ class MenuAppHeader extends StatelessWidget { anchorDirection: AnchorDirection.bottomWithCenterAligned, ); }, - child: BlocSelector( + child: BlocSelector( selector: (state) => state.app, builder: (context, app) => FlowyText.medium( app.name, diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart index 462ce1ba8a..e636c87d9a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart @@ -10,7 +10,7 @@ import 'package:provider/provider.dart'; import 'section/section.dart'; class MenuApp extends StatefulWidget { - final App app; + final AppPB app; const MenuApp(this.app, {Key? key}) : super(key: key); @override diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart index f5de13892f..97ae1dae52 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart @@ -21,8 +21,8 @@ import 'disclosure_action.dart'; // ignore: must_be_immutable class ViewSectionItem extends StatelessWidget { final bool isSelected; - final View view; - final void Function(View) onSelected; + final ViewPB view; + final void Function(ViewPB) onSelected; ViewSectionItem({ Key? key, diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart index 01debf6196..3976598b7b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart @@ -10,7 +10,7 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart'; import 'package:flutter/material.dart'; @@ -32,8 +32,8 @@ import 'menu_user.dart'; class HomeMenu extends StatelessWidget { final PublishNotifier _collapsedNotifier; - final UserProfile user; - final CurrentWorkspaceSetting workspaceSetting; + final UserProfilePB user; + final CurrentWorkspaceSettingPB workspaceSetting; const HomeMenu({ Key? key, @@ -155,19 +155,21 @@ class HomeMenu extends StatelessWidget { } class MenuSharedState { - final ValueNotifier _latestOpenView = ValueNotifier(null); + final ValueNotifier _latestOpenView = ValueNotifier(null); - MenuSharedState({View? view}) { + MenuSharedState({ViewPB? view}) { _latestOpenView.value = view; } - View? get latestOpenView => _latestOpenView.value; + ViewPB? get latestOpenView => _latestOpenView.value; - set latestOpenView(View? view) { - _latestOpenView.value = view; + set latestOpenView(ViewPB? view) { + if (_latestOpenView.value != view) { + _latestOpenView.value = view; + } } - VoidCallback addLatestViewListener(void Function(View?) callback) { + VoidCallback addLatestViewListener(void Function(ViewPB?) callback) { listener() { callback(_latestOpenView.value); } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart index 4bc2ce29c2..d7b8dce4af 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart @@ -6,14 +6,14 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfile; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; class MenuUser extends StatelessWidget { - final UserProfile user; + final UserProfilePB user; MenuUser(this.user, {Key? key}) : super(key: ValueKey(user.id)); @override diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart index b8692168c6..ab58176e50 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart @@ -9,7 +9,7 @@ import 'src/board_page.dart'; class BoardPluginBuilder implements PluginBuilder { @override Plugin build(dynamic data) { - if (data is View) { + if (data is ViewPB) { return BoardPlugin(pluginType: pluginType, view: data); } else { throw FlowyPluginException.invalidData; @@ -32,11 +32,11 @@ class BoardPluginConfig implements PluginConfig { } class BoardPlugin extends Plugin { - final View _view; + final ViewPB _view; final PluginType _pluginType; BoardPlugin({ - required View view, + required ViewPB view, required PluginType pluginType, }) : _pluginType = pluginType, _view = view; @@ -52,8 +52,8 @@ class BoardPlugin extends Plugin { } class GridPluginDisplay extends PluginDisplay { - final View _view; - GridPluginDisplay({required View view, Key? key}) : _view = view; + final ViewPB _view; + GridPluginDisplay({required ViewPB view, Key? key}) : _view = view; @override Widget get leftBarItem => ViewLeftBarItem(view: _view); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/board/src/board_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/board/src/board_page.dart index 1cdfea5480..612e7c6770 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/board/src/board_page.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/board/src/board_page.dart @@ -4,9 +4,9 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter/material.dart'; class BoardPage extends StatelessWidget { - final View _view; + final ViewPB _view; - const BoardPage({required View view, Key? key}) + const BoardPage({required ViewPB view, Key? key}) : _view = view, super(key: key); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart index 0759894844..c09843873d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/document.dart @@ -36,7 +36,7 @@ export './src/widget/toolbar/toolbar_icon_button.dart'; class DocumentPluginBuilder extends PluginBuilder { @override Plugin build(dynamic data) { - if (data is View) { + if (data is ViewPB) { return DocumentPlugin(pluginType: pluginType, view: data); } else { throw FlowyPluginException.invalidData; @@ -54,11 +54,11 @@ class DocumentPluginBuilder extends PluginBuilder { } class DocumentPlugin implements Plugin { - late View _view; + late ViewPB _view; ViewListener? _listener; late PluginType _pluginType; - DocumentPlugin({required PluginType pluginType, required View view, Key? key}) : _view = view { + DocumentPlugin({required PluginType pluginType, required ViewPB view, Key? key}) : _view = view { _pluginType = pluginType; _listener = getIt(param1: view); _listener?.start(onViewUpdated: (result) { @@ -90,9 +90,9 @@ class DocumentPlugin implements Plugin { class DocumentPluginDisplay extends PluginDisplay with NavigationItem { final PublishNotifier _displayNotifier = PublishNotifier(); - final View _view; + final ViewPB _view; - DocumentPluginDisplay({required View view, Key? key}) : _view = view; + DocumentPluginDisplay({required ViewPB view, Key? key}) : _view = view; @override Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id)); @@ -111,7 +111,7 @@ class DocumentPluginDisplay extends PluginDisplay with NavigationItem { } class DocumentShareButton extends StatelessWidget { - final View view; + final ViewPB view; DocumentShareButton({Key? key, required this.view}) : super(key: ValueKey(view.hashCode)); @override @@ -160,7 +160,7 @@ class DocumentShareButton extends StatelessWidget { ); } - void _handleExportData(ExportData exportData) { + void _handleExportData(ExportDataPB exportData) { switch (exportData.exportType) { case ExportType.Link: break; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/document_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/document_page.dart index a02289a555..3598c5a5ac 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/document_page.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/doc/src/document_page.dart @@ -14,7 +14,7 @@ import 'styles.dart'; import 'widget/banner.dart'; class DocumentPage extends StatefulWidget { - final View view; + final ViewPB view; DocumentPage({Key? key, required this.view}) : super(key: ValueKey(view.id)); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid.dart index da634d8833..776dd833ea 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/grid.dart @@ -11,7 +11,7 @@ import 'src/grid_page.dart'; class GridPluginBuilder implements PluginBuilder { @override Plugin build(dynamic data) { - if (data is View) { + if (data is ViewPB) { return GridPlugin(pluginType: pluginType, view: data); } else { throw FlowyPluginException.invalidData; @@ -34,11 +34,11 @@ class GridPluginConfig implements PluginConfig { } class GridPlugin extends Plugin { - final View _view; + final ViewPB _view; final PluginType _pluginType; GridPlugin({ - required View view, + required ViewPB view, required PluginType pluginType, }) : _pluginType = pluginType, _view = view; @@ -54,8 +54,8 @@ class GridPlugin extends Plugin { } class GridPluginDisplay extends PluginDisplay { - final View _view; - GridPluginDisplay({required View view, Key? key}) : _view = view; + final ViewPB _view; + GridPluginDisplay({required ViewPB view, Key? key}) : _view = view; @override Widget get leftBarItem => ViewLeftBarItem(view: _view); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart index 6daf2c50fa..82b13867a4 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart @@ -19,7 +19,7 @@ import 'widgets/shortcuts.dart'; import 'widgets/toolbar/grid_toolbar.dart'; class GridPage extends StatefulWidget { - final View view; + final ViewPB view; GridPage({Key? key, required this.view}) : super(key: ValueKey(view.id)); @@ -212,10 +212,10 @@ class _GridRowsState extends State<_GridRows> { builder: (context, state) { return SliverAnimatedList( key: _key, - initialItemCount: context.read().state.rows.length, + initialItemCount: context.read().state.rowInfos.length, itemBuilder: (BuildContext context, int index, Animation animation) { - final GridRow rowData = context.read().state.rows[index]; - return _renderRow(context, rowData, animation); + final GridRowInfo rowInfo = context.read().state.rowInfos[index]; + return _renderRow(context, rowInfo, animation); }, ); }, @@ -224,17 +224,19 @@ class _GridRowsState extends State<_GridRows> { Widget _renderRow( BuildContext context, - GridRow rowData, + GridRowInfo rowInfo, Animation animation, ) { - final rowCache = context.read().getRowCache(rowData.blockId, rowData.rowId); + final rowCache = context.read().getRowCache(rowInfo.blockId, rowInfo.id); + final fieldCache = context.read().fieldCache; if (rowCache != null) { return SizeTransition( sizeFactor: animation, child: GridRowWidget( - rowData: rowData, + rowData: rowInfo, rowCache: rowCache, - key: ValueKey(rowData.rowId), + fieldCache: fieldCache, + key: ValueKey(rowInfo.id), ), ); } else { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/layout.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/layout.dart index 36c603b7b4..0b289ecd4b 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/layout.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/layout.dart @@ -2,7 +2,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'sizes.dart'; class GridLayout { - static double headerWidth(List fields) { + static double headerWidth(List fields) { if (fields.isEmpty) return 0; final fieldsWidth = fields.map((field) => field.width.toDouble()).reduce((value, element) => value + element); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index 9f3c7a632e..f785672f71 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart @@ -1,4 +1,5 @@ import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/workspace/application/grid/grid_service.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -12,28 +13,39 @@ import 'select_option_cell/select_option_cell.dart'; import 'text_cell.dart'; import 'url_cell/url_cell.dart'; -GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCacheService cellCache, {GridCellStyle? style}) { - final key = ValueKey(gridCell.cellId()); +class GridCellBuilder { + final GridCellCache cellCache; + final GridFieldCache fieldCache; + GridCellBuilder({ + required this.cellCache, + required this.fieldCache, + }); - final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache); - - switch (gridCell.field.fieldType) { - case FieldType.Checkbox: - return CheckboxCell(cellContextBuilder: cellContextBuilder, key: key); - case FieldType.DateTime: - return DateCell(cellContextBuilder: cellContextBuilder, key: key, style: style); - case FieldType.SingleSelect: - return SingleSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key); - case FieldType.MultiSelect: - return MultiSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key); - case FieldType.Number: - return NumberCell(cellContextBuilder: cellContextBuilder, key: key); - case FieldType.RichText: - return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key); - case FieldType.URL: - return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key); + GridCellWidget build(GridCellIdentifier cell, {GridCellStyle? style}) { + final cellControllerBuilder = GridCellControllerBuilder( + cellId: cell, + cellCache: cellCache, + fieldCache: fieldCache, + ); + final key = cell.key(); + switch (cell.fieldType) { + case FieldType.Checkbox: + return GridCheckboxCell(cellControllerBuilder: cellControllerBuilder, key: key); + case FieldType.DateTime: + return GridDateCell(cellControllerBuilder: cellControllerBuilder, key: key, style: style); + case FieldType.SingleSelect: + return GridSingleSelectCell(cellContorllerBuilder: cellControllerBuilder, style: style, key: key); + case FieldType.MultiSelect: + return GridMultiSelectCell(cellContorllerBuilder: cellControllerBuilder, style: style, key: key); + case FieldType.Number: + return GridNumberCell(cellContorllerBuilder: cellControllerBuilder, key: key); + case FieldType.RichText: + return GridTextCell(cellContorllerBuilder: cellControllerBuilder, style: style, key: key); + case FieldType.URL: + return GridURLCell(cellContorllerBuilder: cellControllerBuilder, style: style, key: key); + } + throw UnimplementedError; } - throw UnimplementedError; } class BlankCell extends StatelessWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index 384d85737f..9fcbaf751f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -6,23 +6,23 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -class CheckboxCell extends GridCellWidget { - final GridCellContextBuilder cellContextBuilder; - CheckboxCell({ - required this.cellContextBuilder, +class GridCheckboxCell extends GridCellWidget { + final GridCellControllerBuilder cellControllerBuilder; + GridCheckboxCell({ + required this.cellControllerBuilder, Key? key, }) : super(key: key); @override - GridCellState createState() => _CheckboxCellState(); + GridCellState createState() => _CheckboxCellState(); } -class _CheckboxCellState extends GridCellState { +class _CheckboxCellState extends GridCellState { late CheckboxCellBloc _cellBloc; @override void initState() { - final cellContext = widget.cellContextBuilder.build(); + final cellContext = widget.cellControllerBuilder.build(); _cellBloc = getIt(param1: cellContext)..add(const CheckboxCellEvent.initial()); super.initState(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart index 3e7d40c796..78d18a50e8 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart @@ -18,13 +18,13 @@ abstract class GridCellDelegate { GridCellDelegate get delegate; } -class DateCell extends GridCellWidget { - final GridCellContextBuilder cellContextBuilder; +class GridDateCell extends GridCellWidget { + final GridCellControllerBuilder cellControllerBuilder; late final DateCellStyle? cellStyle; - DateCell({ + GridDateCell({ GridCellStyle? style, - required this.cellContextBuilder, + required this.cellControllerBuilder, Key? key, }) : super(key: key) { if (style != null) { @@ -35,15 +35,15 @@ class DateCell extends GridCellWidget { } @override - GridCellState createState() => _DateCellState(); + GridCellState createState() => _DateCellState(); } -class _DateCellState extends GridCellState { +class _DateCellState extends GridCellState { late DateCellBloc _cellBloc; @override void initState() { - final cellContext = widget.cellContextBuilder.build(); + final cellContext = widget.cellControllerBuilder.build(); _cellBloc = getIt(param1: cellContext)..add(const DateCellEvent.initial()); super.initState(); } @@ -80,7 +80,7 @@ class _DateCellState extends GridCellState { final calendar = DateCellEditor(onDismissed: () => widget.onCellEditing.value = false); calendar.show( context, - cellContext: bloc.cellContext.clone(), + cellController: bloc.cellContext.clone(), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart index 93d304cd1a..36ad22ec53 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_editor.dart @@ -31,16 +31,16 @@ class DateCellEditor with FlowyOverlayDelegate { Future show( BuildContext context, { - required GridDateCellContext cellContext, + required GridDateCellController cellController, }) async { DateCellEditor.remove(context); - final result = await cellContext.getTypeOptionData(); + final result = await cellController.getFieldTypeOption(DateTypeOptionDataParser()); result.fold( - (data) { + (dateTypeOption) { final calendar = _CellCalendarWidget( - cellContext: cellContext, - dateTypeOption: DateTypeOption.fromBuffer(data.typeOptionData), + cellContext: cellController, + dateTypeOption: dateTypeOption, ); FlowyOverlay.of(context).insertWithAnchor( @@ -75,7 +75,7 @@ class DateCellEditor with FlowyOverlayDelegate { } class _CellCalendarWidget extends StatelessWidget { - final GridDateCellContext cellContext; + final GridDateCellController cellContext; final DateTypeOption dateTypeOption; const _CellCalendarWidget({ diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index 7d16b16ef0..573a4168c9 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart @@ -6,26 +6,26 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -class NumberCell extends GridCellWidget { - final GridCellContextBuilder cellContextBuilder; +class GridNumberCell extends GridCellWidget { + final GridCellControllerBuilder cellContorllerBuilder; - NumberCell({ - required this.cellContextBuilder, + GridNumberCell({ + required this.cellContorllerBuilder, Key? key, }) : super(key: key); @override - GridFocusNodeCellState createState() => _NumberCellState(); + GridFocusNodeCellState createState() => _NumberCellState(); } -class _NumberCellState extends GridFocusNodeCellState { +class _NumberCellState extends GridFocusNodeCellState { late NumberCellBloc _cellBloc; late TextEditingController _controller; Timer? _delayOperation; @override void initState() { - final cellContext = widget.cellContextBuilder.build(); + final cellContext = widget.cellContorllerBuilder.build(); _cellBloc = getIt(param1: cellContext)..add(const NumberCellEvent.initial()); _controller = TextEditingController(text: contentFromState(_cellBloc.state)); super.initState(); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart index 27e36ad46d..6946993bae 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart @@ -1,33 +1,33 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -extension SelectOptionColorExtension on SelectOptionColor { +extension SelectOptionColorExtension on SelectOptionColorPB { Color make(BuildContext context) { final theme = context.watch(); switch (this) { - case SelectOptionColor.Purple: + case SelectOptionColorPB.Purple: return theme.tint1; - case SelectOptionColor.Pink: + case SelectOptionColorPB.Pink: return theme.tint2; - case SelectOptionColor.LightPink: + case SelectOptionColorPB.LightPink: return theme.tint3; - case SelectOptionColor.Orange: + case SelectOptionColorPB.Orange: return theme.tint4; - case SelectOptionColor.Yellow: + case SelectOptionColorPB.Yellow: return theme.tint5; - case SelectOptionColor.Lime: + case SelectOptionColorPB.Lime: return theme.tint6; - case SelectOptionColor.Green: + case SelectOptionColorPB.Green: return theme.tint7; - case SelectOptionColor.Aqua: + case SelectOptionColorPB.Aqua: return theme.tint8; - case SelectOptionColor.Blue: + case SelectOptionColorPB.Blue: return theme.tint9; default: throw ArgumentError; @@ -36,23 +36,23 @@ extension SelectOptionColorExtension on SelectOptionColor { String optionName() { switch (this) { - case SelectOptionColor.Purple: + case SelectOptionColorPB.Purple: return LocaleKeys.grid_selectOption_purpleColor.tr(); - case SelectOptionColor.Pink: + case SelectOptionColorPB.Pink: return LocaleKeys.grid_selectOption_pinkColor.tr(); - case SelectOptionColor.LightPink: + case SelectOptionColorPB.LightPink: return LocaleKeys.grid_selectOption_lightPinkColor.tr(); - case SelectOptionColor.Orange: + case SelectOptionColorPB.Orange: return LocaleKeys.grid_selectOption_orangeColor.tr(); - case SelectOptionColor.Yellow: + case SelectOptionColorPB.Yellow: return LocaleKeys.grid_selectOption_yellowColor.tr(); - case SelectOptionColor.Lime: + case SelectOptionColorPB.Lime: return LocaleKeys.grid_selectOption_limeColor.tr(); - case SelectOptionColor.Green: + case SelectOptionColorPB.Green: return LocaleKeys.grid_selectOption_greenColor.tr(); - case SelectOptionColor.Aqua: + case SelectOptionColorPB.Aqua: return LocaleKeys.grid_selectOption_aquaColor.tr(); - case SelectOptionColor.Blue: + case SelectOptionColorPB.Blue: return LocaleKeys.grid_selectOption_blueColor.tr(); default: throw ArgumentError; @@ -75,7 +75,7 @@ class SelectOptionTag extends StatelessWidget { factory SelectOptionTag.fromSelectOption({ required BuildContext context, - required SelectOption option, + required SelectOptionPB option, VoidCallback? onSelected, bool isSelected = false, }) { @@ -107,8 +107,8 @@ class SelectOptionTag extends StatelessWidget { class SelectOptionTagCell extends StatelessWidget { final List children; - final void Function(SelectOption) onSelected; - final SelectOption option; + final void Function(SelectOptionPB) onSelected; + final SelectOptionPB option; const SelectOptionTagCell({ required this.option, required this.onSelected, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart index e878ac2c58..f822e503d2 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart @@ -5,7 +5,7 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; // ignore: unused_import import 'package:flowy_sdk/log.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,12 +20,12 @@ class SelectOptionCellStyle extends GridCellStyle { }); } -class SingleSelectCell extends GridCellWidget { - final GridCellContextBuilder cellContextBuilder; +class GridSingleSelectCell extends GridCellWidget { + final GridCellControllerBuilder cellContorllerBuilder; late final SelectOptionCellStyle? cellStyle; - SingleSelectCell({ - required this.cellContextBuilder, + GridSingleSelectCell({ + required this.cellContorllerBuilder, GridCellStyle? style, Key? key, }) : super(key: key) { @@ -37,15 +37,15 @@ class SingleSelectCell extends GridCellWidget { } @override - State createState() => _SingleSelectCellState(); + State createState() => _SingleSelectCellState(); } -class _SingleSelectCellState extends State { +class _SingleSelectCellState extends State { late SelectOptionCellBloc _cellBloc; @override void initState() { - final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext; + final cellContext = widget.cellContorllerBuilder.build() as GridSelectOptionCellController; _cellBloc = getIt(param1: cellContext)..add(const SelectOptionCellEvent.initial()); super.initState(); } @@ -60,7 +60,7 @@ class _SingleSelectCellState extends State { selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, onFocus: (value) => widget.onCellEditing.value = value, - cellContextBuilder: widget.cellContextBuilder); + cellContorllerBuilder: widget.cellContorllerBuilder); }, ), ); @@ -74,12 +74,12 @@ class _SingleSelectCellState extends State { } //---------------------------------------------------------------- -class MultiSelectCell extends GridCellWidget { - final GridCellContextBuilder cellContextBuilder; +class GridMultiSelectCell extends GridCellWidget { + final GridCellControllerBuilder cellContorllerBuilder; late final SelectOptionCellStyle? cellStyle; - MultiSelectCell({ - required this.cellContextBuilder, + GridMultiSelectCell({ + required this.cellContorllerBuilder, GridCellStyle? style, Key? key, }) : super(key: key) { @@ -91,15 +91,15 @@ class MultiSelectCell extends GridCellWidget { } @override - State createState() => _MultiSelectCellState(); + State createState() => _MultiSelectCellState(); } -class _MultiSelectCellState extends State { +class _MultiSelectCellState extends State { late SelectOptionCellBloc _cellBloc; @override void initState() { - final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext; + final cellContext = widget.cellContorllerBuilder.build() as GridSelectOptionCellController; _cellBloc = getIt(param1: cellContext)..add(const SelectOptionCellEvent.initial()); super.initState(); } @@ -114,7 +114,7 @@ class _MultiSelectCellState extends State { selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, onFocus: (value) => widget.onCellEditing.value = value, - cellContextBuilder: widget.cellContextBuilder); + cellContorllerBuilder: widget.cellContorllerBuilder); }, ), ); @@ -128,15 +128,15 @@ class _MultiSelectCellState extends State { } class _SelectOptionCell extends StatelessWidget { - final List selectOptions; + final List selectOptions; final void Function(bool) onFocus; final SelectOptionCellStyle? cellStyle; - final GridCellContextBuilder cellContextBuilder; + final GridCellControllerBuilder cellContorllerBuilder; const _SelectOptionCell({ required this.selectOptions, required this.onFocus, required this.cellStyle, - required this.cellContextBuilder, + required this.cellContorllerBuilder, Key? key, }) : super(key: key); @@ -172,7 +172,7 @@ class _SelectOptionCell extends StatelessWidget { InkWell( onTap: () { onFocus(true); - final cellContext = cellContextBuilder.build() as GridSelectOptionCellContext; + final cellContext = cellContorllerBuilder.build() as GridSelectOptionCellController; SelectOptionCellEditor.show(context, cellContext, () => onFocus(false)); }, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart index 51c550ccb2..95f2bf1e03 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_editor.dart @@ -10,8 +10,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -24,11 +24,11 @@ import 'text_field.dart'; const double _editorPannelWidth = 300; class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { - final GridSelectOptionCellContext cellContext; + final GridSelectOptionCellController cellController; final VoidCallback onDismissed; const SelectOptionCellEditor({ - required this.cellContext, + required this.cellController, required this.onDismissed, Key? key, }) : super(key: key); @@ -37,7 +37,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { Widget build(BuildContext context) { return BlocProvider( create: (context) => SelectOptionCellEditorBloc( - cellContext: cellContext, + cellController: cellController, )..add(const SelectOptionEditorEvent.initial()), child: BlocBuilder( builder: (context, state) { @@ -59,12 +59,12 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { static void show( BuildContext context, - GridSelectOptionCellContext cellContext, + GridSelectOptionCellController cellContext, VoidCallback onDismissed, ) { SelectOptionCellEditor.remove(context); final editor = SelectOptionCellEditor( - cellContext: cellContext, + cellController: cellContext, onDismissed: onDismissed, ); @@ -146,7 +146,7 @@ class _TextField extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final optionMap = LinkedHashMap.fromIterable(state.selectedOptions, + final optionMap = LinkedHashMap.fromIterable(state.selectedOptions, key: (option) => option.name, value: (option) => option); return SizedBox( @@ -216,7 +216,7 @@ class _CreateOptionCell extends StatelessWidget { } class _SelectOptionCell extends StatelessWidget { - final SelectOption option; + final SelectOptionPB option; final bool isSelected; const _SelectOptionCell(this.option, this.isSelected, {Key? key}) : super(key: key); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart index 398d98a994..10b04cfb58 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/text_field.dart @@ -2,7 +2,7 @@ import 'dart:collection'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; @@ -15,8 +15,8 @@ class SelectOptionTextField extends StatelessWidget { final FocusNode _focusNode; final TextEditingController _controller; final TextfieldTagsController tagController; - final List options; - final LinkedHashMap selectedOptionMap; + final List options; + final LinkedHashMap selectedOptionMap; final double distanceToText; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index 1bece5a3d7..55bf757ba4 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -14,10 +14,10 @@ class GridTextCellStyle extends GridCellStyle { } class GridTextCell extends GridCellWidget { - final GridCellContextBuilder cellContextBuilder; + final GridCellControllerBuilder cellContorllerBuilder; late final GridTextCellStyle? cellStyle; GridTextCell({ - required this.cellContextBuilder, + required this.cellContorllerBuilder, GridCellStyle? style, Key? key, }) : super(key: key) { @@ -39,7 +39,7 @@ class _GridTextCellState extends GridFocusNodeCellState { @override void initState() { - final cellContext = widget.cellContextBuilder.build(); + final cellContext = widget.cellContorllerBuilder.build(); _cellBloc = getIt(param1: cellContext); _cellBloc.add(const TextCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart index f4da18be86..b6bc8daa21 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart @@ -7,21 +7,21 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate { - final GridURLCellContext cellContext; + final GridURLCellController cellController; final VoidCallback completed; - const URLCellEditor({required this.cellContext, required this.completed, Key? key}) : super(key: key); + const URLCellEditor({required this.cellController, required this.completed, Key? key}) : super(key: key); @override State createState() => _URLCellEditorState(); static void show( BuildContext context, - GridURLCellContext cellContext, + GridURLCellController cellContext, VoidCallback completed, ) { FlowyOverlay.of(context).remove(identifier()); final editor = URLCellEditor( - cellContext: cellContext, + cellController: cellContext, completed: completed, ); @@ -62,7 +62,7 @@ class _URLCellEditorState extends State { @override void initState() { - _cellBloc = URLCellEditorBloc(cellContext: widget.cellContext); + _cellBloc = URLCellEditorBloc(cellContext: widget.cellController); _cellBloc.add(const URLCellEditorEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart index e37dca6632..9cc14fbbda 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart @@ -31,10 +31,10 @@ enum GridURLCellAccessoryType { } class GridURLCell extends GridCellWidget { - final GridCellContextBuilder cellContextBuilder; + final GridCellControllerBuilder cellContorllerBuilder; late final GridURLCellStyle? cellStyle; GridURLCell({ - required this.cellContextBuilder, + required this.cellContorllerBuilder, GridCellStyle? style, Key? key, }) : super(key: key) { @@ -51,11 +51,11 @@ class GridURLCell extends GridCellWidget { GridCellAccessory accessoryFromType(GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) { switch (ty) { case GridURLCellAccessoryType.edit: - final cellContext = cellContextBuilder.build() as GridURLCellContext; + final cellContext = cellContorllerBuilder.build() as GridURLCellController; return _EditURLAccessory(cellContext: cellContext, anchorContext: buildContext.anchorContext); case GridURLCellAccessoryType.copyURL: - final cellContext = cellContextBuilder.build() as GridURLCellContext; + final cellContext = cellContorllerBuilder.build() as GridURLCellController; return _CopyURLAccessory(cellContext: cellContext); } } @@ -83,7 +83,7 @@ class _GridURLCellState extends GridCellState { @override void initState() { - final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + final cellContext = widget.cellContorllerBuilder.build() as GridURLCellController; _cellBloc = URLCellBloc(cellContext: cellContext); _cellBloc.add(const URLCellEvent.initial()); super.initState(); @@ -132,7 +132,7 @@ class _GridURLCellState extends GridCellState { if (url.isNotEmpty && await canLaunchUrl(uri)) { await launchUrl(uri); } else { - final cellContext = widget.cellContextBuilder.build() as GridURLCellContext; + final cellContext = widget.cellContorllerBuilder.build() as GridURLCellController; widget.onCellEditing.value = true; URLCellEditor.show(context, cellContext, () { widget.onCellEditing.value = false; @@ -155,7 +155,7 @@ class _GridURLCellState extends GridCellState { } class _EditURLAccessory extends StatelessWidget with GridCellAccessory { - final GridURLCellContext cellContext; + final GridURLCellController cellContext; final BuildContext anchorContext; const _EditURLAccessory({ required this.cellContext, @@ -176,7 +176,7 @@ class _EditURLAccessory extends StatelessWidget with GridCellAccessory { } class _CopyURLAccessory extends StatelessWidget with GridCellAccessory { - final GridURLCellContext cellContext; + final GridURLCellController cellContext; const _CopyURLAccessory({required this.cellContext, Key? key}) : super(key: key); @override @@ -187,7 +187,7 @@ class _CopyURLAccessory extends StatelessWidget with GridCellAccessory { @override void onTap() { - final content = cellContext.getCellData(loadIfNoCache: false)?.content ?? ""; + final content = cellContext.getCellData(loadIfNotExist: false)?.content ?? ""; Clipboard.setData(ClipboardData(text: content)); showMessageToast(LocaleKeys.grid_row_copyProperty.tr()); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart index 9237c71be0..8202a3537f 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart @@ -65,7 +65,7 @@ class GridFieldCell extends StatelessWidget { FieldEditor( gridId: state.gridId, fieldName: field.name, - contextLoader: FieldContextLoader( + typeOptionLoader: FieldTypeOptionLoader( gridId: state.gridId, field: field, ), @@ -135,7 +135,7 @@ class _DragToExpandLine extends StatelessWidget { class FieldCellButton extends StatelessWidget { final VoidCallback onTap; - final Field field; + final GridFieldPB field; const FieldCellButton({ required this.field, required this.onTap, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart index 3390351663..3a857a9fb4 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart @@ -8,17 +8,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'field_name_input.dart'; -import 'field_editor_pannel.dart'; +import 'field_type_option_editor.dart'; class FieldEditor extends StatelessWidget with FlowyOverlayDelegate { final String gridId; final String fieldName; - final IFieldContextLoader contextLoader; + final IFieldTypeOptionLoader typeOptionLoader; const FieldEditor({ required this.gridId, required this.fieldName, - required this.contextLoader, + required this.typeOptionLoader, Key? key, }) : super(key: key); @@ -28,7 +28,7 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate { create: (context) => FieldEditorBloc( gridId: gridId, fieldName: fieldName, - fieldContextLoader: contextLoader, + loader: typeOptionLoader, )..add(const FieldEditorEvent.initial()), child: BlocBuilder( buildWhen: (p, c) => false, @@ -38,9 +38,9 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate { children: [ FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12), const VSpace(10), - const _FieldNameTextField(), + const _FieldNameCell(), const VSpace(10), - const _FieldPannel(), + const _FieldTypeOptionCell(), ], ); }, @@ -74,25 +74,28 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate { bool asBarrier() => true; } -class _FieldPannel extends StatelessWidget { - const _FieldPannel({Key? key}) : super(key: key); +class _FieldTypeOptionCell extends StatelessWidget { + const _FieldTypeOptionCell({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (p, c) => p.fieldContext != c.fieldContext, + buildWhen: (p, c) => p.field != c.field, builder: (context, state) { - return state.fieldContext.fold( + return state.field.fold( () => const SizedBox(), - (fieldContext) => FieldEditorPannel(fieldContext: fieldContext), + (fieldContext) { + final dataController = context.read().dataController; + return FieldTypeOptionEditor(dataController: dataController); + }, ); }, ); } } -class _FieldNameTextField extends StatelessWidget { - const _FieldNameTextField({Key? key}) : super(key: key); +class _FieldNameCell extends StatelessWidget { + const _FieldNameCell({Key? key}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart deleted file mode 100644 index 7ec5144458..0000000000 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'dart:typed_data'; - -import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart'; -import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart'; -import 'package:dartz/dartz.dart' show Either; -import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:app_flowy/workspace/application/grid/prelude.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart'; -import 'field_type_extension.dart'; -import 'type_option/multi_select.dart'; -import 'type_option/number.dart'; -import 'type_option/rich_text.dart'; -import 'type_option/single_select.dart'; -import 'type_option/url.dart'; - -typedef UpdateFieldCallback = void Function(Field, Uint8List); -typedef SwitchToFieldCallback = Future> Function( - String fieldId, - FieldType fieldType, -); - -class FieldEditorPannel extends StatefulWidget { - final GridFieldContext fieldContext; - - const FieldEditorPannel({ - required this.fieldContext, - Key? key, - }) : super(key: key); - - @override - State createState() => _FieldEditorPannelState(); -} - -class _FieldEditorPannelState extends State { - String? currentOverlayIdentifier; - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => FieldEditorPannelBloc(widget.fieldContext)..add(const FieldEditorPannelEvent.initial()), - child: BlocBuilder( - builder: (context, state) { - List children = [_switchFieldTypeButton(context, widget.fieldContext.field)]; - final typeOptionWidget = _typeOptionWidget(context: context, state: state); - - if (typeOptionWidget != null) { - children.add(typeOptionWidget); - } - - return ListView( - shrinkWrap: true, - children: children, - ); - }, - ), - ); - } - - Widget _switchFieldTypeButton(BuildContext context, Field field) { - final theme = context.watch(); - return SizedBox( - height: GridSize.typeOptionItemHeight, - child: FlowyButton( - text: FlowyText.medium(field.fieldType.title(), fontSize: 12), - margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - hoverColor: theme.hover, - onTap: () { - final list = FieldTypeList(onSelectField: (newFieldType) { - widget.fieldContext.switchToField(newFieldType); - }); - _showOverlay(context, list); - }, - leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor), - rightIcon: svgWidget("grid/more", color: theme.iconColor), - ), - ); - } - - Widget? _typeOptionWidget({ - required BuildContext context, - required FieldEditorPannelState state, - }) { - final overlayDelegate = TypeOptionOverlayDelegate( - showOverlay: _showOverlay, - hideOverlay: _hideOverlay, - ); - - final builder = _makeTypeOptionBuild( - typeOptionContext: _makeTypeOptionContext(widget.fieldContext), - overlayDelegate: overlayDelegate, - ); - - return builder.customWidget; - } - - void _showOverlay(BuildContext context, Widget child, {VoidCallback? onRemoved}) { - final identifier = child.toString(); - if (currentOverlayIdentifier != null) { - FlowyOverlay.of(context).remove(currentOverlayIdentifier!); - } - - currentOverlayIdentifier = identifier; - FlowyOverlay.of(context).insertWithAnchor( - widget: OverlayContainer( - child: child, - constraints: BoxConstraints.loose(const Size(460, 440)), - ), - identifier: identifier, - anchorContext: context, - anchorDirection: AnchorDirection.leftWithCenterAligned, - style: FlowyOverlayStyle(blur: false), - anchorOffset: const Offset(-20, 0), - ); - } - - void _hideOverlay(BuildContext context) { - if (currentOverlayIdentifier != null) { - FlowyOverlay.of(context).remove(currentOverlayIdentifier!); - } - } -} - -abstract class TypeOptionBuilder { - Widget? get customWidget; -} - -TypeOptionBuilder _makeTypeOptionBuild({ - required TypeOptionContext typeOptionContext, - required TypeOptionOverlayDelegate overlayDelegate, -}) { - switch (typeOptionContext.field.fieldType) { - case FieldType.Checkbox: - return CheckboxTypeOptionBuilder( - typeOptionContext as CheckboxTypeOptionContext, - ); - case FieldType.DateTime: - return DateTypeOptionBuilder( - typeOptionContext as DateTypeOptionContext, - overlayDelegate, - ); - case FieldType.SingleSelect: - return SingleSelectTypeOptionBuilder( - typeOptionContext as SingleSelectTypeOptionContext, - overlayDelegate, - ); - case FieldType.MultiSelect: - return MultiSelectTypeOptionBuilder( - typeOptionContext as MultiSelectTypeOptionContext, - overlayDelegate, - ); - case FieldType.Number: - return NumberTypeOptionBuilder( - typeOptionContext as NumberTypeOptionContext, - overlayDelegate, - ); - case FieldType.RichText: - return RichTextTypeOptionBuilder( - typeOptionContext as RichTextTypeOptionContext, - ); - - case FieldType.URL: - return URLTypeOptionBuilder( - typeOptionContext as URLTypeOptionContext, - ); - } - throw UnimplementedError; -} - -TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) { - switch (fieldContext.field.fieldType) { - case FieldType.Checkbox: - return CheckboxTypeOptionContext( - fieldContext: fieldContext, - dataBuilder: CheckboxTypeOptionDataBuilder(), - ); - case FieldType.DateTime: - return DateTypeOptionContext( - fieldContext: fieldContext, - dataBuilder: DateTypeOptionDataBuilder(), - ); - case FieldType.MultiSelect: - return MultiSelectTypeOptionContext( - fieldContext: fieldContext, - dataBuilder: MultiSelectTypeOptionDataBuilder(), - ); - case FieldType.Number: - return NumberTypeOptionContext( - fieldContext: fieldContext, - dataBuilder: NumberTypeOptionDataBuilder(), - ); - case FieldType.RichText: - return RichTextTypeOptionContext( - fieldContext: fieldContext, - dataBuilder: RichTextTypeOptionDataBuilder(), - ); - case FieldType.SingleSelect: - return SingleSelectTypeOptionContext( - fieldContext: fieldContext, - dataBuilder: SingleSelectTypeOptionDataBuilder(), - ); - - case FieldType.URL: - return URLTypeOptionContext( - fieldContext: fieldContext, - dataBuilder: URLTypeOptionDataBuilder(), - ); - } - - throw UnimplementedError(); -} - -abstract class TypeOptionWidget extends StatelessWidget { - const TypeOptionWidget({Key? key}) : super(key: key); -} - -typedef TypeOptionData = Uint8List; -typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData); -typedef ShowOverlayCallback = void Function( - BuildContext anchorContext, - Widget child, { - VoidCallback? onRemoved, -}); -typedef HideOverlayCallback = void Function(BuildContext anchorContext); - -class TypeOptionOverlayDelegate { - ShowOverlayCallback showOverlay; - HideOverlayCallback hideOverlay; - TypeOptionOverlayDelegate({ - required this.showOverlay, - required this.hideOverlay, - }); -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_option_editor.dart new file mode 100644 index 0000000000..95dd39bae9 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_option_editor.dart @@ -0,0 +1,127 @@ +import 'dart:typed_data'; +import 'package:dartz/dartz.dart' show Either; +import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart'; +import 'field_type_extension.dart'; +import 'type_option/builder.dart'; + +typedef UpdateFieldCallback = void Function(GridFieldPB, Uint8List); +typedef SwitchToFieldCallback = Future> Function( + String fieldId, + FieldType fieldType, +); + +class FieldTypeOptionEditor extends StatefulWidget { + final TypeOptionDataController dataController; + + const FieldTypeOptionEditor({ + required this.dataController, + Key? key, + }) : super(key: key); + + @override + State createState() => _FieldTypeOptionEditorState(); +} + +class _FieldTypeOptionEditorState extends State { + String? currentOverlayIdentifier; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + FieldTypeOptionEditBloc(widget.dataController)..add(const FieldTypeOptionEditEvent.initial()), + child: BlocBuilder( + builder: (context, state) { + List children = [_switchFieldTypeButton(context, widget.dataController.field)]; + final typeOptionWidget = _typeOptionWidget(context: context, state: state); + + if (typeOptionWidget != null) { + children.add(typeOptionWidget); + } + + return ListView( + shrinkWrap: true, + children: children, + ); + }, + ), + ); + } + + Widget _switchFieldTypeButton(BuildContext context, GridFieldPB field) { + final theme = context.watch(); + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: FlowyButton( + text: FlowyText.medium(field.fieldType.title(), fontSize: 12), + margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + hoverColor: theme.hover, + onTap: () { + final list = FieldTypeList(onSelectField: (newFieldType) { + widget.dataController.switchToField(newFieldType); + }); + _showOverlay(context, list); + }, + leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor), + rightIcon: svgWidget("grid/more", color: theme.iconColor), + ), + ); + } + + Widget? _typeOptionWidget({ + required BuildContext context, + required FieldTypeOptionEditState state, + }) { + final overlayDelegate = TypeOptionOverlayDelegate( + showOverlay: _showOverlay, + hideOverlay: _hideOverlay, + ); + + return makeTypeOptionWidget( + context: context, + dataController: widget.dataController, + overlayDelegate: overlayDelegate, + ); + } + + void _showOverlay(BuildContext context, Widget child, {VoidCallback? onRemoved}) { + final identifier = child.toString(); + if (currentOverlayIdentifier != null) { + FlowyOverlay.of(context).remove(currentOverlayIdentifier!); + } + + currentOverlayIdentifier = identifier; + FlowyOverlay.of(context).insertWithAnchor( + widget: OverlayContainer( + child: child, + constraints: BoxConstraints.loose(const Size(460, 440)), + ), + identifier: identifier, + anchorContext: context, + anchorDirection: AnchorDirection.leftWithCenterAligned, + style: FlowyOverlayStyle(blur: false), + anchorOffset: const Offset(-20, 0), + ); + } + + void _hideOverlay(BuildContext context) { + if (currentOverlayIdentifier != null) { + FlowyOverlay.of(context).remove(currentOverlayIdentifier!); + } + } +} + +abstract class TypeOptionWidget extends StatelessWidget { + const TypeOptionWidget({Key? key}) : super(key: key); +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart index cc968d15cb..5a888129b5 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart @@ -151,7 +151,7 @@ class CreateFieldButton extends StatelessWidget { onTap: () => FieldEditor( gridId: gridId, fieldName: "", - contextLoader: NewFieldContextLoader(gridId: gridId), + typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId), ).show(context), leftIcon: svgWidget("home/add"), ); @@ -160,7 +160,7 @@ class CreateFieldButton extends StatelessWidget { class SliverHeaderDelegateImplementation extends SliverPersistentHeaderDelegate { final String gridId; - final List fields; + final List fields; SliverHeaderDelegateImplementation({required this.gridId, required this.fields}); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/builder.dart new file mode 100644 index 0000000000..63fa761d98 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/builder.dart @@ -0,0 +1,108 @@ +import 'dart:typed_data'; + +import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart'; +import 'package:app_flowy/workspace/application/grid/prelude.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter/material.dart'; +import 'date.dart'; +import 'multi_select.dart'; +import 'number.dart'; +import 'rich_text.dart'; +import 'single_select.dart'; +import 'url.dart'; + +typedef TypeOptionData = Uint8List; +typedef TypeOptionDataCallback = void Function(TypeOptionData typeOptionData); +typedef ShowOverlayCallback = void Function( + BuildContext anchorContext, + Widget child, { + VoidCallback? onRemoved, +}); +typedef HideOverlayCallback = void Function(BuildContext anchorContext); + +class TypeOptionOverlayDelegate { + ShowOverlayCallback showOverlay; + HideOverlayCallback hideOverlay; + TypeOptionOverlayDelegate({ + required this.showOverlay, + required this.hideOverlay, + }); +} + +abstract class TypeOptionWidgetBuilder { + Widget? build(BuildContext context); +} + +Widget? makeTypeOptionWidget({ + required BuildContext context, + required TypeOptionDataController dataController, + required TypeOptionOverlayDelegate overlayDelegate, +}) { + final builder = makeTypeOptionWidgetBuilder(dataController, overlayDelegate); + return builder.build(context); +} + +TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder( + TypeOptionDataController dataController, + TypeOptionOverlayDelegate overlayDelegate, +) { + switch (dataController.field.fieldType) { + case FieldType.Checkbox: + final context = CheckboxTypeOptionContext( + dataController: dataController, + dataParser: CheckboxTypeOptionWidgetDataParser(), + ); + return CheckboxTypeOptionWidgetBuilder(context); + case FieldType.DateTime: + final context = DateTypeOptionContext( + dataController: dataController, + dataParser: DateTypeOptionDataParser(), + ); + return DateTypeOptionWidgetBuilder( + context, + overlayDelegate, + ); + case FieldType.SingleSelect: + final context = SingleSelectTypeOptionContext( + fieldContext: dataController, + dataBuilder: SingleSelectTypeOptionWidgetDataParser(), + ); + return SingleSelectTypeOptionWidgetBuilder( + context, + overlayDelegate, + ); + case FieldType.MultiSelect: + final context = MultiSelectTypeOptionContext( + dataController: dataController, + dataBuilder: MultiSelectTypeOptionWidgetDataParser(), + ); + return MultiSelectTypeOptionWidgetBuilder( + context, + overlayDelegate, + ); + case FieldType.Number: + final context = NumberTypeOptionContext( + dataController: dataController, + dataParser: NumberTypeOptionWidgetDataParser(), + ); + return NumberTypeOptionWidgetBuilder( + context, + overlayDelegate, + ); + case FieldType.RichText: + final context = RichTextTypeOptionContext( + dataController: dataController, + dataParser: RichTextTypeOptionWidgetDataParser(), + ); + return RichTextTypeOptionWidgetBuilder(context); + + case FieldType.URL: + final context = URLTypeOptionContext( + dataController: dataController, + dataParser: URLTypeOptionWidgetDataParser(), + ); + return URLTypeOptionWidgetBuilder(context); + } + throw UnimplementedError; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart index c1e202f358..beca8acd09 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/checkbox.dart @@ -1,20 +1,20 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart'; import 'package:flutter/material.dart'; +import 'builder.dart'; -typedef CheckboxTypeOptionContext = TypeOptionContext; +typedef CheckboxTypeOptionContext = TypeOptionWidgetContext; -class CheckboxTypeOptionDataBuilder extends TypeOptionDataBuilder { +class CheckboxTypeOptionWidgetDataParser extends TypeOptionDataParser { @override CheckboxTypeOption fromBuffer(List buffer) { return CheckboxTypeOption.fromBuffer(buffer); } } -class CheckboxTypeOptionBuilder extends TypeOptionBuilder { - CheckboxTypeOptionBuilder(CheckboxTypeOptionContext typeOptionContext); +class CheckboxTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { + CheckboxTypeOptionWidgetBuilder(CheckboxTypeOptionContext typeOptionContext); @override - Widget? get customWidget => null; + Widget? build(BuildContext context) => null; } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart index f4031dc9ce..39408c9ee3 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart @@ -1,6 +1,6 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/date_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_option_editor.dart'; import 'package:easy_localization/easy_localization.dart' hide DateFormat; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flowy_infra/image.dart'; @@ -9,14 +9,15 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'builder.dart'; -class DateTypeOptionBuilder extends TypeOptionBuilder { +class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { final DateTypeOptionWidget _widget; - DateTypeOptionBuilder( + DateTypeOptionWidgetBuilder( DateTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, ) : _widget = DateTypeOptionWidget( @@ -25,7 +26,9 @@ class DateTypeOptionBuilder extends TypeOptionBuilder { ); @override - Widget? get customWidget => _widget; + Widget? build(BuildContext context) { + return _widget; + } } class DateTypeOptionWidget extends TypeOptionWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart index fc310410f9..8c7ab40a6f 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/multi_select.dart @@ -1,13 +1,14 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/multi_select_type_option.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_option_editor.dart'; import 'package:flutter/material.dart'; +import 'builder.dart'; import 'select_option.dart'; -class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { +class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { final MultiSelectTypeOptionWidget _widget; - MultiSelectTypeOptionBuilder( + MultiSelectTypeOptionWidgetBuilder( MultiSelectTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, ) : _widget = MultiSelectTypeOptionWidget( @@ -16,7 +17,7 @@ class MultiSelectTypeOptionBuilder extends TypeOptionBuilder { ); @override - Widget? get customWidget => _widget; + Widget? build(BuildContext context) => _widget; } class MultiSelectTypeOptionWidget extends TypeOptionWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart index bca17e1cd8..e15abb1728 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/number.dart @@ -2,7 +2,7 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/number_bl import 'package:app_flowy/workspace/application/grid/field/type_option/number_format_bloc.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_option_editor.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -15,10 +15,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart' hide NumberFormat; import 'package:app_flowy/generated/locale_keys.g.dart'; -class NumberTypeOptionBuilder extends TypeOptionBuilder { +import 'builder.dart'; + +class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { final NumberTypeOptionWidget _widget; - NumberTypeOptionBuilder( + NumberTypeOptionWidgetBuilder( NumberTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, ) : _widget = NumberTypeOptionWidget( @@ -27,7 +29,7 @@ class NumberTypeOptionBuilder extends TypeOptionBuilder { ); @override - Widget? get customWidget => _widget; + Widget? build(BuildContext context) => _widget; } class NumberTypeOptionWidget extends TypeOptionWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart index 03f9ed347c..2375918f11 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/rich_text.dart @@ -1,21 +1,20 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart'; - import 'package:flutter/material.dart'; +import 'builder.dart'; -typedef RichTextTypeOptionContext = TypeOptionContext; +typedef RichTextTypeOptionContext = TypeOptionWidgetContext; -class RichTextTypeOptionDataBuilder extends TypeOptionDataBuilder { +class RichTextTypeOptionWidgetDataParser extends TypeOptionDataParser { @override RichTextTypeOption fromBuffer(List buffer) { return RichTextTypeOption.fromBuffer(buffer); } } -class RichTextTypeOptionBuilder extends TypeOptionBuilder { - RichTextTypeOptionBuilder(RichTextTypeOptionContext typeOptionContext); +class RichTextTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { + RichTextTypeOptionWidgetBuilder(RichTextTypeOptionContext typeOptionContext); @override - Widget? get customWidget => null; + Widget? build(BuildContext context) => null; } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart index b308995dd1..c0d54159e8 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option.dart @@ -2,22 +2,22 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/select_op import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/extension.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/common/text_field.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'builder.dart'; import 'select_option_editor.dart'; class SelectOptionTypeOptionWidget extends StatelessWidget { - final List options; + final List options; final VoidCallback beginEdit; final TypeOptionOverlayDelegate overlayDelegate; final SelectOptionTypeOptionAction typeOptionAction; @@ -131,7 +131,7 @@ class _OptionList extends StatelessWidget { ); } - _OptionCell _makeOptionCell(BuildContext context, SelectOption option) { + _OptionCell _makeOptionCell(BuildContext context, SelectOptionPB option) { return _OptionCell( option: option, onSelected: (option) { @@ -154,8 +154,8 @@ class _OptionList extends StatelessWidget { } class _OptionCell extends StatelessWidget { - final SelectOption option; - final Function(SelectOption) onSelected; + final SelectOptionPB option; + final Function(SelectOptionPB) onSelected; const _OptionCell({ required this.option, required this.onSelected, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart index cec32bd99e..5381342d20 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/select_option_editor.dart @@ -8,16 +8,16 @@ import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; class SelectOptionTypeOptionEditor extends StatelessWidget { - final SelectOption option; + final SelectOptionPB option; final VoidCallback onDeleted; - final Function(SelectOption) onUpdated; + final Function(SelectOptionPB) onUpdated; const SelectOptionTypeOptionEditor({ required this.option, required this.onDeleted, @@ -110,12 +110,12 @@ class _OptionNameTextField extends StatelessWidget { } class SelectOptionColorList extends StatelessWidget { - final SelectOptionColor selectedColor; + final SelectOptionColorPB selectedColor; const SelectOptionColorList({required this.selectedColor, Key? key}) : super(key: key); @override Widget build(BuildContext context) { - final cells = SelectOptionColor.values.map((color) { + final cells = SelectOptionColorPB.values.map((color) { return _SelectOptionColorCell(color: color, isSelected: selectedColor == color); }).toList(); @@ -152,7 +152,7 @@ class SelectOptionColorList extends StatelessWidget { } class _SelectOptionColorCell extends StatelessWidget { - final SelectOptionColor color; + final SelectOptionColorPB color; final bool isSelected; const _SelectOptionColorCell({required this.color, required this.isSelected, Key? key}) : super(key: key); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart index fedddec11d..a1ed4e857a 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/single_select.dart @@ -1,12 +1,13 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/single_select_type_option.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; +import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_option_editor.dart'; import 'package:flutter/material.dart'; +import 'builder.dart'; import 'select_option.dart'; -class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { +class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { final SingleSelectTypeOptionWidget _widget; - SingleSelectTypeOptionBuilder( + SingleSelectTypeOptionWidgetBuilder( SingleSelectTypeOptionContext typeOptionContext, TypeOptionOverlayDelegate overlayDelegate, ) : _widget = SingleSelectTypeOptionWidget( @@ -15,7 +16,7 @@ class SingleSelectTypeOptionBuilder extends TypeOptionBuilder { ); @override - Widget? get customWidget => _widget; + Widget? build(BuildContext context) => _widget; } class SingleSelectTypeOptionWidget extends TypeOptionWidget { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart index f4e73f7fdc..97c0db0814 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/url.dart @@ -1,20 +1,20 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; -import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart'; import 'package:flutter/material.dart'; +import 'builder.dart'; -typedef URLTypeOptionContext = TypeOptionContext; +typedef URLTypeOptionContext = TypeOptionWidgetContext; -class URLTypeOptionDataBuilder extends TypeOptionDataBuilder { +class URLTypeOptionWidgetDataParser extends TypeOptionDataParser { @override URLTypeOption fromBuffer(List buffer) { return URLTypeOption.fromBuffer(buffer); } } -class URLTypeOptionBuilder extends TypeOptionBuilder { - URLTypeOptionBuilder(URLTypeOptionContext typeOptionContext); +class URLTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { + URLTypeOptionWidgetBuilder(URLTypeOptionContext typeOptionContext); @override - Widget? get customWidget => null; + Widget? build(BuildContext context) => null; } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index de7f117d51..c3e2e17b7e 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart @@ -10,19 +10,25 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; -import 'row_action_sheet.dart'; +import 'row_action_sheet.dart'; import 'row_detail.dart'; class GridRowWidget extends StatefulWidget { - final GridRow rowData; - final GridRowCacheService rowCache; + final GridRowInfo rowData; + final GridRowCache rowCache; + final GridCellBuilder cellBuilder; - const GridRowWidget({ + GridRowWidget({ required this.rowData, required this.rowCache, + required GridFieldCache fieldCache, Key? key, - }) : super(key: key); + }) : cellBuilder = GridCellBuilder( + cellCache: rowCache.cellCache, + fieldCache: fieldCache, + ), + super(key: key); @override State createState() => _GridRowWidgetState(); @@ -34,7 +40,7 @@ class _GridRowWidgetState extends State { @override void initState() { _rowBloc = RowBloc( - rowData: widget.rowData, + rowInfo: widget.rowData, rowCache: widget.rowCache, ); _rowBloc.add(const RowEvent.initial()); @@ -47,12 +53,16 @@ class _GridRowWidgetState extends State { value: _rowBloc, child: _RowEnterRegion( child: BlocBuilder( - buildWhen: (p, c) => p.rowData.height != c.rowData.height, + buildWhen: (p, c) => p.rowInfo.height != c.rowInfo.height, builder: (context, state) { return Row( children: [ const _RowLeading(), - Expanded(child: _RowCells(cellCache: widget.rowCache.cellCache, onExpand: () => _expandRow(context))), + Expanded( + child: _RowCells( + builder: widget.cellBuilder, + onExpand: () => _expandRow(context), + )), const _RowTrailing(), ], ); @@ -70,8 +80,9 @@ class _GridRowWidgetState extends State { void _expandRow(BuildContext context) { final page = RowDetailPage( - rowData: widget.rowData, + rowInfo: widget.rowData, rowCache: widget.rowCache, + cellBuilder: widget.cellBuilder, ); page.show(context); } @@ -137,7 +148,7 @@ class _DeleteRowButton extends StatelessWidget { width: 20, height: 30, onPressed: () => GridRowActionSheet( - rowData: context.read().state.rowData, + rowData: context.read().state.rowInfo, ).show(context), iconPadding: const EdgeInsets.all(3), icon: svgWidget("editor/details"), @@ -146,9 +157,13 @@ class _DeleteRowButton extends StatelessWidget { } class _RowCells extends StatelessWidget { - final GridCellCacheService cellCache; final VoidCallback onExpand; - const _RowCells({required this.cellCache, required this.onExpand, Key? key}) : super(key: key); + final GridCellBuilder builder; + const _RowCells({ + required this.builder, + required this.onExpand, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -168,13 +183,12 @@ class _RowCells extends StatelessWidget { List _makeCells(BuildContext context, GridCellMap gridCellMap) { return gridCellMap.values.map( - (gridCell) { - final GridCellWidget child = buildGridCellWidget(gridCell, cellCache); - + (cellId) { + final GridCellWidget child = builder.build(cellId); accessoryBuilder(GridCellAccessoryBuildContext buildContext) { final builder = child.accessoryBuilder; List accessories = []; - if (gridCell.field.isPrimary) { + if (cellId.field.isPrimary) { accessories.add(PrimaryCellAccessory( onTapCallback: onExpand, isCellEditing: buildContext.isCellEditing, @@ -188,7 +202,7 @@ class _RowCells extends StatelessWidget { } return CellContainer( - width: gridCell.field.width.toDouble(), + width: cellId.field.width.toDouble(), child: child, rowStateNotifier: Provider.of(context, listen: false), accessoryBuilder: accessoryBuilder, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart index 1d7886c86c..06e9c68d19 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart @@ -14,7 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class GridRowActionSheet extends StatelessWidget { - final GridRow rowData; + final GridRowInfo rowData; const GridRowActionSheet({required this.rowData, Key? key}) : super(key: key); @override diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index d1c2f96e75..a01f97b847 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -19,15 +19,16 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:window_size/window_size.dart'; class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { - final GridRow rowData; - final GridRowCacheService rowCache; + final GridRowInfo rowInfo; + final GridRowCache rowCache; + final GridCellBuilder cellBuilder; const RowDetailPage({ - required this.rowData, + required this.rowInfo, required this.rowCache, + required this.cellBuilder, Key? key, }) : super(key: key); @@ -35,8 +36,8 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { State createState() => _RowDetailPageState(); void show(BuildContext context) async { - final window = await getWindowInfo(); - final size = Size(window.frame.size.width * 0.7, window.frame.size.height * 0.7); + final windowSize = MediaQuery.of(context).size; + final size = windowSize * 0.7; FlowyOverlay.of(context).insertWithRect( widget: OverlayContainer( child: this, @@ -44,7 +45,7 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { ), identifier: RowDetailPage.identifier(), anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0), - anchorSize: window.frame.size, + anchorSize: windowSize, anchorDirection: AnchorDirection.center, style: FlowyOverlayStyle(blur: false), delegate: this, @@ -61,7 +62,7 @@ class _RowDetailPageState extends State { Widget build(BuildContext context) { return BlocProvider( create: (context) { - final bloc = RowDetailBloc(rowData: widget.rowData, rowCache: widget.rowCache); + final bloc = RowDetailBloc(rowInfo: widget.rowInfo, rowCache: widget.rowCache); bloc.add(const RowDetailEvent.initial()); return bloc; }, @@ -75,7 +76,7 @@ class _RowDetailPageState extends State { children: const [Spacer(), _CloseButton()], ), ), - Expanded(child: _PropertyList(cellCache: widget.rowCache.cellCache)), + Expanded(child: _PropertyList(cellBuilder: widget.cellBuilder)), ], ), ), @@ -99,10 +100,10 @@ class _CloseButton extends StatelessWidget { } class _PropertyList extends StatelessWidget { - final GridCellCacheService cellCache; + final GridCellBuilder cellBuilder; final ScrollController _scrollController; _PropertyList({ - required this.cellCache, + required this.cellBuilder, Key? key, }) : _scrollController = ScrollController(), super(key: key); @@ -121,8 +122,8 @@ class _PropertyList extends StatelessWidget { itemCount: state.gridCells.length, itemBuilder: (BuildContext context, int index) { return _RowDetailCell( - gridCell: state.gridCells[index], - cellCache: cellCache, + cellId: state.gridCells[index], + cellBuilder: cellBuilder, ); }, separatorBuilder: (BuildContext context, int index) { @@ -136,19 +137,19 @@ class _PropertyList extends StatelessWidget { } class _RowDetailCell extends StatelessWidget { - final GridCell gridCell; - final GridCellCacheService cellCache; + final GridCellIdentifier cellId; + final GridCellBuilder cellBuilder; const _RowDetailCell({ - required this.gridCell, - required this.cellCache, + required this.cellId, + required this.cellBuilder, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); - final style = _customCellStyle(theme, gridCell.field.fieldType); - final cell = buildGridCellWidget(gridCell, cellCache, style: style); + final style = _customCellStyle(theme, cellId.fieldType); + final cell = cellBuilder.build(cellId, style: style); final gesture = GestureDetector( behavior: HitTestBehavior.translucent, @@ -168,7 +169,7 @@ class _RowDetailCell extends StatelessWidget { children: [ SizedBox( width: 150, - child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), + child: FieldCellButton(field: cellId.field, onTap: () => _showFieldEditor(context)), ), const HSpace(10), Expanded(child: gesture), @@ -180,11 +181,11 @@ class _RowDetailCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { FieldEditor( - gridId: gridCell.gridId, - fieldName: gridCell.field.name, - contextLoader: FieldContextLoader( - gridId: gridCell.gridId, - field: gridCell.field, + gridId: cellId.gridId, + fieldName: cellId.field.name, + typeOptionLoader: FieldTypeOptionLoader( + gridId: cellId.gridId, + field: cellId.field, ), ).show(context); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart index e19c90ecea..b34007493b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart @@ -75,7 +75,7 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate { } class _GridPropertyCell extends StatelessWidget { - final Field field; + final GridFieldPB field; final String gridId; const _GridPropertyCell({required this.gridId, required this.field, Key? key}) : super(key: key); @@ -116,7 +116,7 @@ class _GridPropertyCell extends StatelessWidget { FieldEditor( gridId: gridId, fieldName: field.name, - contextLoader: FieldContextLoader(gridId: gridId, field: field), + typeOptionLoader: FieldTypeOptionLoader(gridId: gridId, field: field), ).show(context, anchorDirection: AnchorDirection.bottomRight); }, ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/trash/src/trash_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/trash/src/trash_cell.dart index 92b1f61cd0..4d6604e4af 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/trash/src/trash_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/trash/src/trash_cell.dart @@ -14,7 +14,7 @@ import 'sizes.dart'; class TrashCell extends StatelessWidget { final VoidCallback onRestore; final VoidCallback onDelete; - final Trash object; + final TrashPB object; const TrashCell({required this.object, required this.onRestore, required this.onDelete, Key? key}) : super(key: key); @override diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/trash/trash.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/trash/trash.dart index 324b676fc3..e098eddf96 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/trash/trash.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/trash/trash.dart @@ -31,7 +31,7 @@ class TrashPluginBuilder extends PluginBuilder { } @override - String get menuName => "Trash"; + String get menuName => "TrashPB"; @override PluginType get pluginType => DefaultPlugin.trash.type(); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart index bd9f1441e8..0d06ec56ce 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/widgets/left_bar_item.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ViewLeftBarItem extends StatefulWidget { - final View view; + final ViewPB view; ViewLeftBarItem({required this.view, Key? key}) : super(key: ValueKey(view.hashCode)); @@ -20,7 +20,7 @@ class _ViewLeftBarItemState extends State { final _focusNode = FocusNode(); late ViewService _viewService; late ViewListener _viewListener; - late View view; + late ViewPB view; @override void initState() { diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart index 8bb45b51cd..b5d364f67e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart @@ -1632,7 +1632,7 @@ final Map activities = Map.fromIterables([ 'Flying Disc', 'Bowling', 'Cricket Game', - 'Field Hockey', + 'GridFieldPB Hockey', 'Ice Hockey', 'Lacrosse', 'Ping Pong', diff --git a/frontend/app_flowy/packages/flowy_infra/pubspec.lock b/frontend/app_flowy/packages/flowy_infra/pubspec.lock deleted file mode 100644 index 256c2dac3a..0000000000 --- a/frontend/app_flowy/packages/flowy_infra/pubspec.lock +++ /dev/null @@ -1,231 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - url: "https://pub.dartlang.org" - source: hosted - version: "0.22.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - path_drawing: - dependency: transitive - description: - name: path_drawing - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.8" - textstyle_extensions: - dependency: "direct main" - description: - name: textstyle_extensions - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0-nullsafety" - time: - dependency: "direct main" - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - uuid: - dependency: "direct main" - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.4" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "5.2.0" -sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=1.24.0-7.0" diff --git a/frontend/app_flowy/packages/flowy_infra_ui/example/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/example/pubspec.lock deleted file mode 100644 index 0a91c0b58c..0000000000 --- a/frontend/app_flowy/packages/flowy_infra_ui/example/pubspec.lock +++ /dev/null @@ -1,334 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - animations: - dependency: transitive - description: - name: animations - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - dartz: - dependency: transitive - description: - name: dartz - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.0-nullsafety.2" - equatable: - dependency: transitive - description: - name: equatable - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - flowy_infra: - dependency: transitive - description: - path: "../../flowy_infra" - relative: true - source: path - version: "0.0.1" - flowy_infra_ui: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.1" - flowy_infra_ui_platform_interface: - dependency: transitive - description: - path: "../flowy_infra_ui_platform_interface" - relative: true - source: path - version: "0.0.1" - flowy_infra_ui_web: - dependency: transitive - description: - path: "../flowy_infra_ui_web" - relative: true - source: path - version: "0.0.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - flutter_svg: - dependency: transitive - description: - name: flutter_svg - url: "https://pub.dartlang.org" - source: hosted - version: "0.22.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.3" - lint: - dependency: transitive - description: - name: lint - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.3" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - loading_indicator: - dependency: transitive - description: - name: loading_indicator - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - nested: - dependency: transitive - description: - name: nested - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - path_drawing: - dependency: transitive - description: - name: path_drawing - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.1+1" - path_parsing: - dependency: transitive - description: - name: path_parsing - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "4.4.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - provider: - dependency: transitive - description: - name: provider - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - styled_widget: - dependency: transitive - description: - name: styled_widget - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.1+2" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.3" - textstyle_extensions: - dependency: transitive - description: - name: textstyle_extensions - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0-nullsafety" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.4" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "5.3.1" -sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=2.0.0" diff --git a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.lock deleted file mode 100644 index a2ed8a8fe8..0000000000 --- a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.lock +++ /dev/null @@ -1,168 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.6.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.10" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - plugin_platform_interface: - dependency: "direct main" - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" -sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.17.0" diff --git a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.lock deleted file mode 100644 index 804b67b5b9..0000000000 --- a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.lock +++ /dev/null @@ -1,187 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.6.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - flowy_infra_ui_platform_interface: - dependency: "direct main" - description: - path: "../flowy_infra_ui_platform_interface" - relative: true - source: path - version: "0.0.1" - flutter: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.3" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.10" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" -sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.17.0" diff --git a/frontend/app_flowy/packages/flowy_infra_ui/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/pubspec.lock deleted file mode 100644 index 8ff7b9870b..0000000000 --- a/frontend/app_flowy/packages/flowy_infra_ui/pubspec.lock +++ /dev/null @@ -1,320 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - animations: - dependency: "direct main" - description: - name: animations - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - dartz: - dependency: "direct main" - description: - name: dartz - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.0-nullsafety.2" - equatable: - dependency: "direct main" - description: - name: equatable - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - flowy_infra: - dependency: "direct main" - description: - path: "../flowy_infra" - relative: true - source: path - version: "0.0.1" - flowy_infra_ui_platform_interface: - dependency: "direct main" - description: - path: flowy_infra_ui_platform_interface - relative: true - source: path - version: "0.0.1" - flowy_infra_ui_web: - dependency: "direct main" - description: - path: flowy_infra_ui_web - relative: true - source: path - version: "0.0.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - flutter_svg: - dependency: transitive - description: - name: flutter_svg - url: "https://pub.dartlang.org" - source: hosted - version: "0.22.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.3" - lint: - dependency: transitive - description: - name: lint - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.3" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - loading_indicator: - dependency: "direct main" - description: - name: loading_indicator - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - nested: - dependency: transitive - description: - name: nested - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - path_drawing: - dependency: transitive - description: - name: path_drawing - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.1+1" - path_parsing: - dependency: transitive - description: - name: path_parsing - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "4.4.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - provider: - dependency: "direct main" - description: - name: provider - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - styled_widget: - dependency: "direct main" - description: - name: styled_widget - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.1+2" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.3" - textstyle_extensions: - dependency: "direct main" - description: - name: textstyle_extensions - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0-nullsafety" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.4" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "5.3.1" -sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=2.0.0" diff --git a/frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock b/frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock deleted file mode 100644 index 921bd4fb2b..0000000000 --- a/frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock +++ /dev/null @@ -1,309 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.6" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - dartz: - dependency: transitive - description: - name: dartz - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.0-nullsafety.2" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - ffi: - dependency: transitive - description: - name: ffi - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.2" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flowy_sdk: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_driver: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.1" - fuchsia_remote_debug_protocol: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - integration_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - isolates: - dependency: transitive - description: - name: isolates - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.3+8" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.1" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - logger: - dependency: transitive - description: - name: logger - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - platform: - dependency: transitive - description: - name: platform - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - process: - dependency: transitive - description: - name: process - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.4" - protobuf: - dependency: transitive - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - sync_http: - dependency: transitive - description: - name: sync_http - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.8" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - vm_service: - dependency: transitive - description: - name: vm_service - url: "https://pub.dartlang.org" - source: hosted - version: "7.5.0" - webdriver: - dependency: transitive - description: - name: webdriver - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" -sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=1.17.0" diff --git a/frontend/app_flowy/packages/flowy_sdk/pubspec.lock b/frontend/app_flowy/packages/flowy_sdk/pubspec.lock deleted file mode 100644 index 1f5af870d9..0000000000 --- a/frontend/app_flowy/packages/flowy_sdk/pubspec.lock +++ /dev/null @@ -1,504 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - url: "https://pub.dartlang.org" - source: hosted - version: "20.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.0" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.0" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.7" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.10" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "1.12.2" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.12" - built_collection: - dependency: transitive - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.0" - built_value: - dependency: transitive - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "8.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "3.6.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - dartz: - dependency: "direct main" - description: - name: dartz - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.0-nullsafety.2" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - ffi: - dependency: "direct main" - description: - name: ffi - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.0" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - freezed: - dependency: "direct dev" - description: - name: freezed - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.1+2" - freezed_annotation: - dependency: "direct main" - description: - name: freezed_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.1" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.4" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.4" - isolates: - dependency: "direct main" - description: - name: isolates - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.3+8" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.3" - json_annotation: - dependency: transitive - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.1" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - logger: - dependency: "direct main" - description: - name: logger - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.11.0" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.0" - protobuf: - dependency: "direct main" - description: - name: protobuf - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.9" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.3" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_gen: - dependency: transitive - description: - name: source_gen - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.8" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+3" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" -sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=1.17.0" diff --git a/frontend/app_flowy/test/util/test_env.dart b/frontend/app_flowy/test/util/test_env.dart index 40006295a4..1743e4b920 100644 --- a/frontend/app_flowy/test/util/test_env.dart +++ b/frontend/app_flowy/test/util/test_env.dart @@ -14,7 +14,7 @@ class FlowyTest { return FlowyTest(); } - Future signIn() async { + Future signIn() async { final authService = getIt(); const password = "AppFlowy123@"; final uid = uuid(); diff --git a/frontend/app_flowy/test/workspace_bloc_test.dart b/frontend/app_flowy/test/workspace_bloc_test.dart index 680726b401..d0b898cc42 100644 --- a/frontend/app_flowy/test/workspace_bloc_test.dart +++ b/frontend/app_flowy/test/workspace_bloc_test.dart @@ -7,7 +7,7 @@ import 'package:bloc_test/bloc_test.dart'; import 'util/test_env.dart'; void main() { - UserProfile? userInfo; + UserProfilePB? userInfo; setUpAll(() async { final flowyTest = await FlowyTest.setup(); userInfo = await flowyTest.signIn(); diff --git a/frontend/rust-lib/flowy-database/migrations/2022-06-10-140131_revision-snapshot/down.sql b/frontend/rust-lib/flowy-database/migrations/2022-06-10-140131_revision-snapshot/down.sql new file mode 100644 index 0000000000..01a87a1e99 --- /dev/null +++ b/frontend/rust-lib/flowy-database/migrations/2022-06-10-140131_revision-snapshot/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE rev_snapshot; \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database/migrations/2022-06-10-140131_revision-snapshot/up.sql b/frontend/rust-lib/flowy-database/migrations/2022-06-10-140131_revision-snapshot/up.sql new file mode 100644 index 0000000000..9656dabc3c --- /dev/null +++ b/frontend/rust-lib/flowy-database/migrations/2022-06-10-140131_revision-snapshot/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE rev_snapshot ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + object_id TEXT NOT NULL DEFAULT '', + rev_id BIGINT NOT NULL DEFAULT 0, + data BLOB NOT NULL DEFAULT (x'') +); \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database/src/schema.rs b/frontend/rust-lib/flowy-database/src/schema.rs index b131adf05f..e41fd6d865 100644 --- a/frontend/rust-lib/flowy-database/src/schema.rs +++ b/frontend/rust-lib/flowy-database/src/schema.rs @@ -49,6 +49,15 @@ table! { } } +table! { + rev_snapshot (id) { + id -> Integer, + object_id -> Text, + rev_id -> BigInt, + data -> Binary, + } +} + table! { rev_table (id) { id -> Integer, @@ -116,6 +125,7 @@ allow_tables_to_appear_in_same_query!( grid_meta_rev_table, grid_rev_table, kv_table, + rev_snapshot, rev_table, trash_table, user_table, diff --git a/frontend/rust-lib/flowy-folder/src/entities/app.rs b/frontend/rust-lib/flowy-folder/src/entities/app.rs index 1c705d5b0e..3a5dc3099e 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/app.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/app.rs @@ -3,7 +3,7 @@ use crate::{ app::{AppColorStyle, AppIdentify, AppName}, workspace::WorkspaceIdentify, }, - entities::view::RepeatedView, + entities::view::RepeatedViewPB, errors::ErrorCode, impl_def_and_def_mut, }; @@ -12,7 +12,7 @@ use flowy_folder_data_model::revision::AppRevision; use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct App { +pub struct AppPB { #[pb(index = 1)] pub id: String, @@ -26,7 +26,7 @@ pub struct App { pub desc: String, #[pb(index = 5)] - pub belongings: RepeatedView, + pub belongings: RepeatedViewPB, #[pb(index = 6)] pub version: i64, @@ -38,9 +38,9 @@ pub struct App { pub create_time: i64, } -impl std::convert::From for App { +impl std::convert::From for AppPB { fn from(app_serde: AppRevision) -> Self { - App { + AppPB { id: app_serde.id, workspace_id: app_serde.workspace_id, name: app_serde.name, @@ -53,21 +53,21 @@ impl std::convert::From for App { } } #[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)] -pub struct RepeatedApp { +pub struct RepeatedAppPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl_def_and_def_mut!(RepeatedApp, App); +impl_def_and_def_mut!(RepeatedAppPB, AppPB); -impl std::convert::From> for RepeatedApp { +impl std::convert::From> for RepeatedAppPB { fn from(values: Vec) -> Self { - let items = values.into_iter().map(|value| value.into()).collect::>(); - RepeatedApp { items } + let items = values.into_iter().map(|value| value.into()).collect::>(); + RepeatedAppPB { items } } } #[derive(ProtoBuf, Default)] -pub struct CreateAppPayload { +pub struct CreateAppPayloadPB { #[pb(index = 1)] pub workspace_id: String, @@ -78,31 +78,24 @@ pub struct CreateAppPayload { pub desc: String, #[pb(index = 4)] - pub color_style: ColorStyle, + pub color_style: ColorStylePB, } #[derive(ProtoBuf, Default, Debug, Clone)] -pub struct ColorStyle { +pub struct ColorStylePB { #[pb(index = 1)] pub theme_color: String, } -#[derive(ProtoBuf, Default, Debug)] +#[derive(Debug)] pub struct CreateAppParams { - #[pb(index = 1)] pub workspace_id: String, - - #[pb(index = 2)] pub name: String, - - #[pb(index = 3)] pub desc: String, - - #[pb(index = 4)] - pub color_style: ColorStyle, + pub color_style: ColorStylePB, } -impl TryInto for CreateAppPayload { +impl TryInto for CreateAppPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -119,21 +112,21 @@ impl TryInto for CreateAppPayload { } } -impl std::convert::From for ColorStyle { +impl std::convert::From for ColorStylePB { fn from(data: AppColorStyle) -> Self { - ColorStyle { + ColorStylePB { theme_color: data.theme_color, } } } #[derive(ProtoBuf, Default, Clone, Debug)] -pub struct AppId { +pub struct AppIdPB { #[pb(index = 1)] pub value: String, } -impl AppId { +impl AppIdPB { pub fn new(app_id: &str) -> Self { Self { value: app_id.to_string(), @@ -142,7 +135,7 @@ impl AppId { } #[derive(ProtoBuf, Default)] -pub struct UpdateAppPayload { +pub struct UpdateAppPayloadPB { #[pb(index = 1)] pub app_id: String, @@ -153,27 +146,22 @@ pub struct UpdateAppPayload { pub desc: Option, #[pb(index = 4, one_of)] - pub color_style: Option, + pub color_style: Option, #[pb(index = 5, one_of)] pub is_trash: Option, } -#[derive(ProtoBuf, Default, Clone, Debug)] +#[derive(Debug, Clone)] pub struct UpdateAppParams { - #[pb(index = 1)] pub app_id: String, - #[pb(index = 2, one_of)] pub name: Option, - #[pb(index = 3, one_of)] pub desc: Option, - #[pb(index = 4, one_of)] - pub color_style: Option, + pub color_style: Option, - #[pb(index = 5, one_of)] pub is_trash: Option, } @@ -181,7 +169,10 @@ impl UpdateAppParams { pub fn new(app_id: &str) -> Self { Self { app_id: app_id.to_string(), - ..Default::default() + name: None, + desc: None, + color_style: None, + is_trash: None, } } @@ -201,7 +192,7 @@ impl UpdateAppParams { } } -impl TryInto for UpdateAppPayload { +impl TryInto for UpdateAppPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-folder/src/entities/trash.rs b/frontend/rust-lib/flowy-folder/src/entities/trash.rs index c332dded18..15358ba3e5 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/trash.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/trash.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::Formatter; #[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)] -pub struct Trash { +pub struct TrashPB { #[pb(index = 1)] pub id: String, @@ -22,9 +22,9 @@ pub struct Trash { pub ty: TrashType, } -impl std::convert::From for Trash { +impl std::convert::From for TrashPB { fn from(trash_rev: TrashRevision) -> Self { - Trash { + TrashPB { id: trash_rev.id, name: trash_rev.name, modified_time: trash_rev.modified_time, @@ -34,8 +34,8 @@ impl std::convert::From for Trash { } } -impl std::convert::From for TrashRevision { - fn from(trash: Trash) -> Self { +impl std::convert::From for TrashRevision { + fn from(trash: TrashPB) -> Self { TrashRevision { id: trash.id, name: trash.name, @@ -46,16 +46,16 @@ impl std::convert::From for TrashRevision { } } #[derive(PartialEq, Debug, Default, ProtoBuf, Clone)] -pub struct RepeatedTrash { +pub struct RepeatedTrashPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl_def_and_def_mut!(RepeatedTrash, Trash); -impl std::convert::From> for RepeatedTrash { +impl_def_and_def_mut!(RepeatedTrashPB, TrashPB); +impl std::convert::From> for RepeatedTrashPB { fn from(trash_revs: Vec) -> Self { - let items: Vec = trash_revs.into_iter().map(|trash_rev| trash_rev.into()).collect(); - RepeatedTrash { items } + let items: Vec = trash_revs.into_iter().map(|trash_rev| trash_rev.into()).collect(); + RepeatedTrashPB { items } } } @@ -106,15 +106,15 @@ impl std::default::Default for TrashType { } #[derive(PartialEq, ProtoBuf, Default, Debug, Clone)] -pub struct RepeatedTrashId { +pub struct RepeatedTrashIdPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, #[pb(index = 2)] pub delete_all: bool, } -impl std::fmt::Display for RepeatedTrashId { +impl std::fmt::Display for RepeatedTrashIdPB { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&format!( "{:?}", @@ -123,35 +123,35 @@ impl std::fmt::Display for RepeatedTrashId { } } -impl RepeatedTrashId { - pub fn all() -> RepeatedTrashId { - RepeatedTrashId { +impl RepeatedTrashIdPB { + pub fn all() -> RepeatedTrashIdPB { + RepeatedTrashIdPB { items: vec![], delete_all: true, } } } -impl std::convert::From> for RepeatedTrashId { - fn from(items: Vec) -> Self { - RepeatedTrashId { +impl std::convert::From> for RepeatedTrashIdPB { + fn from(items: Vec) -> Self { + RepeatedTrashIdPB { items, delete_all: false, } } } -impl std::convert::From> for RepeatedTrashId { +impl std::convert::From> for RepeatedTrashIdPB { fn from(trash: Vec) -> Self { let items = trash .into_iter() - .map(|t| TrashId { + .map(|t| TrashIdPB { id: t.id, ty: t.ty.into(), }) .collect::>(); - RepeatedTrashId { + RepeatedTrashIdPB { items, delete_all: false, } @@ -159,7 +159,7 @@ impl std::convert::From> for RepeatedTrashId { } #[derive(PartialEq, ProtoBuf, Default, Debug, Clone)] -pub struct TrashId { +pub struct TrashIdPB { #[pb(index = 1)] pub id: String, @@ -167,15 +167,15 @@ pub struct TrashId { pub ty: TrashType, } -impl std::fmt::Display for TrashId { +impl std::fmt::Display for TrashIdPB { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&format!("{:?}:{}", self.ty, self.id)) } } -impl std::convert::From<&TrashRevision> for TrashId { +impl std::convert::From<&TrashRevision> for TrashIdPB { fn from(trash: &TrashRevision) -> Self { - TrashId { + TrashIdPB { id: trash.id.clone(), ty: trash.ty.clone().into(), } diff --git a/frontend/rust-lib/flowy-folder/src/entities/view.rs b/frontend/rust-lib/flowy-folder/src/entities/view.rs index ca54ec8cff..8f3a26c8fd 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/view.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/view.rs @@ -11,7 +11,7 @@ use flowy_folder_data_model::revision::{gen_view_id, ViewDataTypeRevision, ViewR use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct View { +pub struct ViewPB { #[pb(index = 1)] pub id: String, @@ -34,9 +34,9 @@ pub struct View { pub plugin_type: i32, } -impl std::convert::From for View { +impl std::convert::From for ViewPB { fn from(rev: ViewRevision) -> Self { - View { + ViewPB { id: rev.id, belong_to_id: rev.belong_to_id, name: rev.name, @@ -79,27 +79,27 @@ impl std::convert::From for ViewDataTypeRevision { } #[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)] -pub struct RepeatedView { +pub struct RepeatedViewPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl_def_and_def_mut!(RepeatedView, View); +impl_def_and_def_mut!(RepeatedViewPB, ViewPB); -impl std::convert::From> for RepeatedView { +impl std::convert::From> for RepeatedViewPB { fn from(values: Vec) -> Self { - let items = values.into_iter().map(|value| value.into()).collect::>(); - RepeatedView { items } + let items = values.into_iter().map(|value| value.into()).collect::>(); + RepeatedViewPB { items } } } #[derive(Default, ProtoBuf)] -pub struct RepeatedViewId { +pub struct RepeatedViewIdPB { #[pb(index = 1)] pub items: Vec, } #[derive(Default, ProtoBuf)] -pub struct CreateViewPayload { +pub struct CreateViewPayloadPB { #[pb(index = 1)] pub belong_to_id: String, @@ -122,34 +122,19 @@ pub struct CreateViewPayload { pub data: Vec, } -#[derive(Default, ProtoBuf, Debug, Clone)] +#[derive(Debug, Clone)] pub struct CreateViewParams { - #[pb(index = 1)] pub belong_to_id: String, - - #[pb(index = 2)] pub name: String, - - #[pb(index = 3)] pub desc: String, - - #[pb(index = 4)] pub thumbnail: String, - - #[pb(index = 5)] pub data_type: ViewDataType, - - #[pb(index = 6)] pub view_id: String, - - #[pb(index = 7)] pub data: Vec, - - #[pb(index = 8)] pub plugin_type: i32, } -impl TryInto for CreateViewPayload { +impl TryInto for CreateViewPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -175,20 +160,20 @@ impl TryInto for CreateViewPayload { } #[derive(Default, ProtoBuf, Clone, Debug)] -pub struct ViewId { +pub struct ViewIdPB { #[pb(index = 1)] pub value: String, } -impl std::convert::From<&str> for ViewId { +impl std::convert::From<&str> for ViewIdPB { fn from(value: &str) -> Self { - ViewId { + ViewIdPB { value: value.to_string(), } } } -impl std::ops::Deref for ViewId { +impl std::ops::Deref for ViewIdPB { type Target = str; fn deref(&self) -> &Self::Target { @@ -197,7 +182,7 @@ impl std::ops::Deref for ViewId { } #[derive(Default, ProtoBuf)] -pub struct UpdateViewPayload { +pub struct UpdateViewPayloadPB { #[pb(index = 1)] pub view_id: String, @@ -211,22 +196,15 @@ pub struct UpdateViewPayload { pub thumbnail: Option, } -#[derive(Default, ProtoBuf, Clone, Debug)] +#[derive(Clone, Debug)] pub struct UpdateViewParams { - #[pb(index = 1)] pub view_id: String, - - #[pb(index = 2, one_of)] pub name: Option, - - #[pb(index = 3, one_of)] pub desc: Option, - - #[pb(index = 4, one_of)] pub thumbnail: Option, } -impl TryInto for UpdateViewPayload { +impl TryInto for UpdateViewPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -269,7 +247,7 @@ impl std::default::Default for MoveFolderItemType { } #[derive(Default, ProtoBuf)] -pub struct MoveFolderItemPayload { +pub struct MoveFolderItemPayloadPB { #[pb(index = 1)] pub item_id: String, @@ -290,7 +268,7 @@ pub struct MoveFolderItemParams { pub ty: MoveFolderItemType, } -impl TryInto for MoveFolderItemPayload { +impl TryInto for MoveFolderItemPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-folder/src/entities/view_info.rs b/frontend/rust-lib/flowy-folder/src/entities/view_info.rs index a772396e9f..92ec785821 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/view_info.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/view_info.rs @@ -1,8 +1,8 @@ -use crate::entities::{RepeatedView, ViewDataType}; +use crate::entities::{RepeatedViewPB, ViewDataType}; use flowy_derive::ProtoBuf; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct ViewInfo { +pub struct ViewInfoPB { #[pb(index = 1)] pub id: String, @@ -19,7 +19,7 @@ pub struct ViewInfo { pub data_type: ViewDataType, #[pb(index = 6)] - pub belongings: RepeatedView, + pub belongings: RepeatedViewPB, #[pb(index = 7)] pub ext_data: String, diff --git a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs b/frontend/rust-lib/flowy-folder/src/entities/workspace.rs index a9f61b50c8..20c9750940 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/workspace.rs @@ -1,6 +1,6 @@ use crate::{ entities::parser::workspace::{WorkspaceDesc, WorkspaceIdentify, WorkspaceName}, - entities::{app::RepeatedApp, view::View}, + entities::{app::RepeatedAppPB, view::ViewPB}, errors::*, impl_def_and_def_mut, }; @@ -9,7 +9,7 @@ use flowy_folder_data_model::revision::WorkspaceRevision; use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)] -pub struct Workspace { +pub struct WorkspacePB { #[pb(index = 1)] pub id: String, @@ -20,7 +20,7 @@ pub struct Workspace { pub desc: String, #[pb(index = 4)] - pub apps: RepeatedApp, + pub apps: RepeatedAppPB, #[pb(index = 5)] pub modified_time: i64, @@ -29,9 +29,9 @@ pub struct Workspace { pub create_time: i64, } -impl std::convert::From for Workspace { +impl std::convert::From for WorkspacePB { fn from(workspace_serde: WorkspaceRevision) -> Self { - Workspace { + WorkspacePB { id: workspace_serde.id, name: workspace_serde.name, desc: workspace_serde.desc, @@ -42,15 +42,15 @@ impl std::convert::From for Workspace { } } #[derive(PartialEq, Debug, Default, ProtoBuf)] -pub struct RepeatedWorkspace { +pub struct RepeatedWorkspacePB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl_def_and_def_mut!(RepeatedWorkspace, Workspace); +impl_def_and_def_mut!(RepeatedWorkspacePB, WorkspacePB); #[derive(ProtoBuf, Default)] -pub struct CreateWorkspacePayload { +pub struct CreateWorkspacePayloadPB { #[pb(index = 1)] pub name: String, @@ -58,16 +58,13 @@ pub struct CreateWorkspacePayload { pub desc: String, } -#[derive(Clone, ProtoBuf, Default, Debug)] +#[derive(Clone, Debug)] pub struct CreateWorkspaceParams { - #[pb(index = 1)] pub name: String, - - #[pb(index = 2)] pub desc: String, } -impl TryInto for CreateWorkspacePayload { +impl TryInto for CreateWorkspacePayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -83,28 +80,28 @@ impl TryInto for CreateWorkspacePayload { // Read all workspaces if the workspace_id is None #[derive(Clone, ProtoBuf, Default, Debug)] -pub struct WorkspaceId { +pub struct WorkspaceIdPB { #[pb(index = 1, one_of)] pub value: Option, } -impl WorkspaceId { +impl WorkspaceIdPB { pub fn new(workspace_id: Option) -> Self { Self { value: workspace_id } } } #[derive(Default, ProtoBuf, Clone)] -pub struct CurrentWorkspaceSetting { +pub struct CurrentWorkspaceSettingPB { #[pb(index = 1)] - pub workspace: Workspace, + pub workspace: WorkspacePB, #[pb(index = 2, one_of)] - pub latest_view: Option, + pub latest_view: Option, } #[derive(ProtoBuf, Default)] -pub struct UpdateWorkspaceRequest { +pub struct UpdateWorkspacePayloadPB { #[pb(index = 1)] pub id: String, @@ -115,19 +112,14 @@ pub struct UpdateWorkspaceRequest { pub desc: Option, } -#[derive(Clone, ProtoBuf, Default, Debug)] +#[derive(Clone, Debug)] pub struct UpdateWorkspaceParams { - #[pb(index = 1)] pub id: String, - - #[pb(index = 2, one_of)] pub name: Option, - - #[pb(index = 3, one_of)] pub desc: Option, } -impl TryInto for UpdateWorkspaceRequest { +impl TryInto for UpdateWorkspacePayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-folder/src/event_map.rs b/frontend/rust-lib/flowy-folder/src/event_map.rs index abb168b5e3..71e436b848 100644 --- a/frontend/rust-lib/flowy-folder/src/event_map.rs +++ b/frontend/rust-lib/flowy-folder/src/event_map.rs @@ -1,9 +1,9 @@ use crate::{ entities::{ - app::{AppId, CreateAppParams, UpdateAppParams}, - trash::RepeatedTrashId, - view::{CreateViewParams, RepeatedViewId, UpdateViewParams, ViewId}, - workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceId}, + app::{AppIdPB, CreateAppParams, UpdateAppParams}, + trash::RepeatedTrashIdPB, + view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, + workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, }, errors::FlowyError, manager::FolderManager, @@ -84,73 +84,73 @@ pub fn create(folder: Arc) -> Module { #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum FolderEvent { - #[event(input = "CreateWorkspacePayload", output = "Workspace")] + #[event(input = "CreateWorkspacePayloadPB", output = "WorkspacePB")] CreateWorkspace = 0, - #[event(output = "CurrentWorkspaceSetting")] + #[event(output = "CurrentWorkspaceSettingPB")] ReadCurWorkspace = 1, - #[event(input = "WorkspaceId", output = "RepeatedWorkspace")] + #[event(input = "WorkspaceIdPB", output = "RepeatedWorkspacePB")] ReadWorkspaces = 2, - #[event(input = "WorkspaceId")] + #[event(input = "WorkspaceIdPB")] DeleteWorkspace = 3, - #[event(input = "WorkspaceId", output = "Workspace")] + #[event(input = "WorkspaceIdPB", output = "WorkspacePB")] OpenWorkspace = 4, - #[event(input = "WorkspaceId", output = "RepeatedApp")] + #[event(input = "WorkspaceIdPB", output = "RepeatedAppPB")] ReadWorkspaceApps = 5, - #[event(input = "CreateAppPayload", output = "App")] + #[event(input = "CreateAppPayloadPB", output = "AppPB")] CreateApp = 101, - #[event(input = "AppId")] + #[event(input = "AppIdPB")] DeleteApp = 102, - #[event(input = "AppId", output = "App")] + #[event(input = "AppIdPB", output = "AppPB")] ReadApp = 103, - #[event(input = "UpdateAppPayload")] + #[event(input = "UpdateAppPayloadPB")] UpdateApp = 104, - #[event(input = "CreateViewPayload", output = "View")] + #[event(input = "CreateViewPayloadPB", output = "ViewPB")] CreateView = 201, - #[event(input = "ViewId", output = "View")] + #[event(input = "ViewIdPB", output = "ViewPB")] ReadView = 202, - #[event(input = "UpdateViewPayload", output = "View")] + #[event(input = "UpdateViewPayloadPB", output = "ViewPB")] UpdateView = 203, - #[event(input = "RepeatedViewId")] + #[event(input = "RepeatedViewIdPB")] DeleteView = 204, - #[event(input = "ViewId")] + #[event(input = "ViewIdPB")] DuplicateView = 205, - #[event(input = "ViewId")] + #[event(input = "ViewIdPB")] CloseView = 206, - #[event(input = "ViewId", output = "ViewInfo")] + #[event(input = "ViewIdPB", output = "ViewInfoPB")] ReadViewInfo = 207, #[event()] CopyLink = 220, - #[event(input = "ViewId")] + #[event(input = "ViewIdPB")] SetLatestView = 221, - #[event(input = "MoveFolderItemPayload")] + #[event(input = "MoveFolderItemPayloadPB")] MoveFolderItem = 230, - #[event(output = "RepeatedTrash")] + #[event(output = "RepeatedTrashPB")] ReadTrash = 300, - #[event(input = "TrashId")] + #[event(input = "TrashIdPB")] PutbackTrash = 301, - #[event(input = "RepeatedTrashId")] + #[event(input = "RepeatedTrashIdPB")] DeleteTrash = 302, #[event()] @@ -170,34 +170,34 @@ pub trait FolderCouldServiceV1: Send + Sync { params: CreateWorkspaceParams, ) -> FutureResult; - fn read_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult, FlowyError>; + fn read_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult, FlowyError>; fn update_workspace(&self, token: &str, params: UpdateWorkspaceParams) -> FutureResult<(), FlowyError>; - fn delete_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult<(), FlowyError>; + fn delete_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult<(), FlowyError>; // View fn create_view(&self, token: &str, params: CreateViewParams) -> FutureResult; - fn read_view(&self, token: &str, params: ViewId) -> FutureResult, FlowyError>; + fn read_view(&self, token: &str, params: ViewIdPB) -> FutureResult, FlowyError>; - fn delete_view(&self, token: &str, params: RepeatedViewId) -> FutureResult<(), FlowyError>; + fn delete_view(&self, token: &str, params: RepeatedViewIdPB) -> FutureResult<(), FlowyError>; fn update_view(&self, token: &str, params: UpdateViewParams) -> FutureResult<(), FlowyError>; // App fn create_app(&self, token: &str, params: CreateAppParams) -> FutureResult; - fn read_app(&self, token: &str, params: AppId) -> FutureResult, FlowyError>; + fn read_app(&self, token: &str, params: AppIdPB) -> FutureResult, FlowyError>; fn update_app(&self, token: &str, params: UpdateAppParams) -> FutureResult<(), FlowyError>; - fn delete_app(&self, token: &str, params: AppId) -> FutureResult<(), FlowyError>; + fn delete_app(&self, token: &str, params: AppIdPB) -> FutureResult<(), FlowyError>; // Trash - fn create_trash(&self, token: &str, params: RepeatedTrashId) -> FutureResult<(), FlowyError>; + fn create_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; - fn delete_trash(&self, token: &str, params: RepeatedTrashId) -> FutureResult<(), FlowyError>; + fn delete_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; fn read_trash(&self, token: &str) -> FutureResult, FlowyError>; } diff --git a/frontend/rust-lib/flowy-folder/src/macros.rs b/frontend/rust-lib/flowy-folder/src/macros.rs index 6afcd95f72..11ff6b71f1 100644 --- a/frontend/rust-lib/flowy-folder/src/macros.rs +++ b/frontend/rust-lib/flowy-folder/src/macros.rs @@ -28,7 +28,7 @@ macro_rules! impl_def_and_def_mut { impl $target { #[allow(dead_code)] pub fn into_inner(&mut self) -> Vec<$item> { - ::std::mem::replace(&mut self.items, vec![]) + ::std::mem::take(&mut self.items) } #[allow(dead_code)] diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index b6555035d3..356ff15897 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -1,7 +1,8 @@ use crate::entities::view::ViewDataType; +use crate::services::folder_editor::FolderRevisionCompactor; use crate::{ dart_notification::{send_dart_notification, FolderNotification}, - entities::workspace::RepeatedWorkspace, + entities::workspace::RepeatedWorkspacePB, errors::FlowyResult, event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, services::{ @@ -13,7 +14,7 @@ use bytes::Bytes; use flowy_error::FlowyError; use flowy_folder_data_model::user_default; use flowy_revision::disk::SQLiteTextBlockRevisionPersistence; -use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket}; +use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionSnapshotPersistence}; use flowy_sync::client_document::default::{initial_quill_delta_string, initial_read_me}; use flowy_sync::{client_folder::FolderPad, entities::ws_data::ServerRevisionWSData}; use lazy_static::lazy_static; @@ -161,9 +162,20 @@ impl FolderManager { let _ = self.persistence.initialize(user_id, &folder_id).await?; let pool = self.persistence.db_pool()?; - let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool)); - let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache)); - let rev_manager = RevisionManager::new(user_id, folder_id.as_ref(), rev_persistence); + let object_id = folder_id.as_ref(); + let disk_cache = SQLiteTextBlockRevisionPersistence::new(user_id, pool.clone()); + let rev_persistence = RevisionPersistence::new(user_id, object_id, disk_cache); + let rev_compactor = FolderRevisionCompactor(); + // let history_persistence = SQLiteRevisionHistoryPersistence::new(object_id, pool.clone()); + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(object_id, pool); + let rev_manager = RevisionManager::new( + user_id, + folder_id.as_ref(), + rev_persistence, + rev_compactor, + // history_persistence, + snapshot_persistence, + ); let folder_editor = FolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; *self.folder_editor.write().await = Some(Arc::new(folder_editor)); @@ -216,7 +228,7 @@ impl DefaultFolderBuilder { let folder = FolderPad::new(vec![workspace_rev.clone()], vec![])?; let folder_id = FolderId::new(user_id); let _ = persistence.save_folder(user_id, &folder_id, folder).await?; - let repeated_workspace = RepeatedWorkspace { + let repeated_workspace = RepeatedWorkspacePB { items: vec![workspace_rev.into()], }; send_dart_notification(token, FolderNotification::UserCreateWorkspace) diff --git a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs b/frontend/rust-lib/flowy-folder/src/services/app/controller.rs index 481af85ccd..112ad052ce 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/controller.rs @@ -1,7 +1,7 @@ use crate::{ dart_notification::*, entities::{ - app::{App, CreateAppParams, *}, + app::{AppPB, CreateAppParams, *}, trash::TrashType, }, errors::*, @@ -44,12 +44,12 @@ impl AppController { } #[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name) err)] - pub(crate) async fn create_app_from_params(&self, params: CreateAppParams) -> Result { + pub(crate) async fn create_app_from_params(&self, params: CreateAppParams) -> Result { let app = self.create_app_on_server(params).await?; self.create_app_on_local(app).await } - pub(crate) async fn create_app_on_local(&self, app: AppRevision) -> Result { + pub(crate) async fn create_app_on_local(&self, app: AppRevision) -> Result { let _ = self .persistence .begin_transaction(|transaction| { @@ -61,7 +61,7 @@ impl AppController { Ok(app.into()) } - pub(crate) async fn read_app(&self, params: AppId) -> Result { + pub(crate) async fn read_app(&self, params: AppIdPB) -> Result { let app = self .persistence .begin_transaction(|transaction| { @@ -81,7 +81,7 @@ impl AppController { let changeset = AppChangeset::new(params.clone()); let app_id = changeset.id.clone(); - let app: App = self + let app: AppPB = self .persistence .begin_transaction(|transaction| { let _ = transaction.update_app(changeset)?; @@ -150,7 +150,7 @@ impl AppController { } #[tracing::instrument(level = "trace", skip(self), err)] - fn read_app_on_server(&self, params: AppId) -> Result<(), FlowyError> { + fn read_app_on_server(&self, params: AppIdPB) -> Result<(), FlowyError> { let token = self.user.token()?; let server = self.cloud_service.clone(); let persistence = self.persistence.clone(); @@ -162,7 +162,7 @@ impl AppController { .await { Ok(_) => { - let app: App = app_rev.into(); + let app: AppPB = app_rev.into(); send_dart_notification(&app.id, FolderNotification::AppUpdated) .payload(app) .send(); @@ -247,7 +247,7 @@ fn notify_apps_changed<'a>( .into_iter() .map(|app_rev| app_rev.into()) .collect(); - let repeated_app = RepeatedApp { items }; + let repeated_app = RepeatedAppPB { items }; send_dart_notification(workspace_id, FolderNotification::WorkspaceAppsChanged) .payload(repeated_app) .send(); diff --git a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs index 28885c69d7..e2087c167e 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs @@ -1,5 +1,5 @@ use crate::{ - entities::app::{App, AppId, CreateAppParams, CreateAppPayload, UpdateAppParams, UpdateAppPayload}, + entities::app::{AppIdPB, AppPB, CreateAppParams, CreateAppPayloadPB, UpdateAppParams, UpdateAppPayloadPB}, errors::FlowyError, services::{AppController, TrashController, ViewController}, }; @@ -8,9 +8,9 @@ use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::{convert::TryInto, sync::Arc}; pub(crate) async fn create_app_handler( - data: Data, + data: Data, controller: AppData>, -) -> DataResult { +) -> DataResult { let params: CreateAppParams = data.into_inner().try_into()?; let detail = controller.create_app_from_params(params).await?; @@ -18,11 +18,11 @@ pub(crate) async fn create_app_handler( } pub(crate) async fn delete_app_handler( - data: Data, + data: Data, app_controller: AppData>, trash_controller: AppData>, ) -> Result<(), FlowyError> { - let params: AppId = data.into_inner(); + let params: AppIdPB = data.into_inner(); let trash = app_controller .read_local_apps(vec![params.value]) .await? @@ -36,7 +36,7 @@ pub(crate) async fn delete_app_handler( #[tracing::instrument(level = "debug", skip(data, controller))] pub(crate) async fn update_app_handler( - data: Data, + data: Data, controller: AppData>, ) -> Result<(), FlowyError> { let params: UpdateAppParams = data.into_inner().try_into()?; @@ -46,11 +46,11 @@ pub(crate) async fn update_app_handler( #[tracing::instrument(level = "trace", skip(data, app_controller, view_controller))] pub(crate) async fn read_app_handler( - data: Data, + data: Data, app_controller: AppData>, view_controller: AppData>, -) -> DataResult { - let params: AppId = data.into_inner(); +) -> DataResult { + let params: AppIdPB = data.into_inner(); let mut app_rev = app_controller.read_app(params.clone()).await?; app_rev.belongings = view_controller.read_views_belong_to(¶ms.value).await?; diff --git a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs index 4fa40013d0..b69b303fa1 100644 --- a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs +++ b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs @@ -89,11 +89,7 @@ impl FolderEditor { &self.user_id, md5, ); - let _ = futures::executor::block_on(async { - self.rev_manager - .add_local_revision(&revision, Box::new(FolderRevisionCompactor())) - .await - })?; + let _ = futures::executor::block_on(async { self.rev_manager.add_local_revision(&revision).await })?; Ok(()) } @@ -133,7 +129,7 @@ impl FolderEditor { } } -struct FolderRevisionCompactor(); +pub struct FolderRevisionCompactor(); impl RevisionCompactor for FolderRevisionCompactor { fn bytes_from_revisions(&self, revisions: Vec) -> FlowyResult { let delta = make_delta_from_revisions::(revisions)?; diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs index 4c147ad0d4..d67d062d25 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs @@ -85,7 +85,7 @@ impl FolderMigration { return Ok(None); } let pool = self.database.db_pool()?; - let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool)); + let disk_cache = SQLiteTextBlockRevisionPersistence::new(user_id, pool); let rev_persistence = Arc::new(RevisionPersistence::new(user_id, folder_id.as_ref(), disk_cache)); let (revisions, _) = RevisionLoader { object_id: folder_id.as_ref().to_owned(), diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs index 54fcc3b522..5676db54da 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs @@ -11,9 +11,9 @@ use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use flowy_revision::disk::{RevisionRecord, RevisionState}; -use flowy_revision::mk_revision_disk_cache; -use flowy_sync::client_folder::initial_folder_delta; +use flowy_revision::mk_text_block_revision_disk_cache; use flowy_sync::{client_folder::FolderPad, entities::revision::Revision}; +use lib_ot::core::PlainTextDeltaBuilder; use std::sync::Arc; use tokio::sync::RwLock; pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*}; @@ -109,16 +109,16 @@ impl FolderPersistence { pub async fn save_folder(&self, user_id: &str, folder_id: &FolderId, folder: FolderPad) -> FlowyResult<()> { let pool = self.database.db_pool()?; - let delta_data = initial_folder_delta(&folder)?.to_delta_bytes(); - let md5 = folder.md5(); - let revision = Revision::new(folder_id.as_ref(), 0, 0, delta_data, user_id, md5); + let json = folder.to_json()?; + let delta_data = PlainTextDeltaBuilder::new().insert(&json).build().to_delta_bytes(); + let revision = Revision::initial_revision(user_id, folder_id.as_ref(), delta_data); let record = RevisionRecord { revision, state: RevisionState::Sync, write_to_disk: true, }; - let disk_cache = mk_revision_disk_cache(user_id, pool); + let disk_cache = mk_text_block_revision_disk_cache(user_id, pool); disk_cache.delete_and_insert_records(folder_id.as_ref(), None, vec![record]) } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs index 6b5f47c2b7..cf643e7dff 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs @@ -1,6 +1,6 @@ use crate::entities::{ app::UpdateAppParams, - trash::{Trash, TrashType}, + trash::{TrashPB, TrashType}, }; use crate::{errors::FlowyError, services::persistence::version_1::workspace_sql::WorkspaceTable}; use flowy_database::{ @@ -107,9 +107,9 @@ impl AppTable { } } -impl std::convert::From for Trash { +impl std::convert::From for TrashPB { fn from(table: AppTable) -> Self { - Trash { + TrashPB { id: table.id, name: table.name, modified_time: table.modified_time, diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs index ec1bbf38bf..6223cd3366 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs @@ -1,6 +1,6 @@ use crate::{ entities::{ - trash::{Trash, TrashType}, + trash::{TrashPB, TrashType}, view::UpdateViewParams, }, errors::FlowyError, @@ -133,9 +133,9 @@ impl std::convert::From for ViewRevision { } } -impl std::convert::From for Trash { +impl std::convert::From for TrashPB { fn from(table: ViewTable) -> Self { - Trash { + TrashPB { id: table.id, name: table.name, modified_time: table.modified_time, diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs b/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs index 134e8a0d84..8ac9618917 100644 --- a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs @@ -1,6 +1,6 @@ use crate::{ dart_notification::{send_anonymous_dart_notification, FolderNotification}, - entities::trash::{RepeatedTrash, RepeatedTrashId, Trash, TrashId, TrashType}, + entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB, TrashPB, TrashType}, errors::{FlowyError, FlowyResult}, event_map::{FolderCouldServiceV1, WorkspaceUser}, services::persistence::{FolderPersistence, FolderPersistenceTransaction}, @@ -49,12 +49,12 @@ impl TrashController { }) .await?; - let identifier = TrashId { + let identifier = TrashIdPB { id: trash.id, ty: trash.ty.into(), }; - let _ = self.delete_trash_on_server(RepeatedTrashId { + let _ = self.delete_trash_on_server(RepeatedTrashIdPB { items: vec![identifier.clone()], delete_all: false, })?; @@ -67,7 +67,7 @@ impl TrashController { #[tracing::instrument(level = "debug", skip(self) err)] pub async fn restore_all_trash(&self) -> FlowyResult<()> { - let trash_identifier: RepeatedTrashId = self + let trash_identifier: RepeatedTrashIdPB = self .persistence .begin_transaction(|transaction| { let trash = transaction.read_trash(None); @@ -81,14 +81,14 @@ impl TrashController { let _ = self.notify.send(TrashEvent::Putback(trash_identifier, tx)); let _ = rx.recv().await; - notify_trash_changed(RepeatedTrash { items: vec![] }); + notify_trash_changed(RepeatedTrashPB { items: vec![] }); let _ = self.delete_all_trash_on_server().await?; Ok(()) } #[tracing::instrument(level = "debug", skip(self), err)] pub async fn delete_all_trash(&self) -> FlowyResult<()> { - let all_trash_identifiers: RepeatedTrashId = self + let all_trash_identifiers: RepeatedTrashIdPB = self .persistence .begin_transaction(|transaction| transaction.read_trash(None)) .await? @@ -96,13 +96,13 @@ impl TrashController { let _ = self.delete_with_identifiers(all_trash_identifiers).await?; - notify_trash_changed(RepeatedTrash { items: vec![] }); + notify_trash_changed(RepeatedTrashPB { items: vec![] }); let _ = self.delete_all_trash_on_server().await?; Ok(()) } #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn delete(&self, trash_identifiers: RepeatedTrashId) -> FlowyResult<()> { + pub async fn delete(&self, trash_identifiers: RepeatedTrashIdPB) -> FlowyResult<()> { let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?; let trash_revs = self .persistence @@ -116,7 +116,7 @@ impl TrashController { } #[tracing::instrument(level = "debug", skip(self), fields(delete_trash_ids), err)] - pub async fn delete_with_identifiers(&self, trash_identifiers: RepeatedTrashId) -> FlowyResult<()> { + pub async fn delete_with_identifiers(&self, trash_identifiers: RepeatedTrashIdPB) -> FlowyResult<()> { let (tx, mut rx) = mpsc::channel::>(1); tracing::Span::current().record("delete_trash_ids", &format!("{}", trash_identifiers).as_str()); let _ = self.notify.send(TrashEvent::Delete(trash_identifiers.clone(), tx)); @@ -153,7 +153,7 @@ impl TrashController { pub async fn add>(&self, trash: Vec) -> Result<(), FlowyError> { let (tx, mut rx) = mpsc::channel::>(1); let trash_revs: Vec = trash.into_iter().map(|t| t.into()).collect(); - let identifiers = trash_revs.iter().map(|t| t.into()).collect::>(); + let identifiers = trash_revs.iter().map(|t| t.into()).collect::>(); tracing::Span::current().record( "trash_ids", @@ -187,8 +187,8 @@ impl TrashController { self.notify.subscribe() } - pub async fn read_trash(&self) -> Result { - let items: Vec = self + pub async fn read_trash(&self) -> Result { + let items: Vec = self .persistence .begin_transaction(|transaction| transaction.read_trash(None)) .await? @@ -197,7 +197,7 @@ impl TrashController { .collect(); let _ = self.read_trash_on_server()?; - Ok(RepeatedTrash { items }) + Ok(RepeatedTrashPB { items }) } pub fn read_trash_ids<'a>( @@ -215,7 +215,7 @@ impl TrashController { impl TrashController { #[tracing::instrument(level = "trace", skip(self, trash), err)] - fn create_trash_on_server>(&self, trash: T) -> FlowyResult<()> { + fn create_trash_on_server>(&self, trash: T) -> FlowyResult<()> { let token = self.user.token()?; let trash_identifiers = trash.into(); let server = self.cloud_service.clone(); @@ -230,7 +230,7 @@ impl TrashController { } #[tracing::instrument(level = "trace", skip(self, trash), err)] - fn delete_trash_on_server>(&self, trash: T) -> FlowyResult<()> { + fn delete_trash_on_server>(&self, trash: T) -> FlowyResult<()> { let token = self.user.token()?; let trash_identifiers = trash.into(); let server = self.cloud_service.clone(); @@ -277,12 +277,12 @@ impl TrashController { async fn delete_all_trash_on_server(&self) -> FlowyResult<()> { let token = self.user.token()?; let server = self.cloud_service.clone(); - server.delete_trash(&token, RepeatedTrashId::all()).await + server.delete_trash(&token, RepeatedTrashIdPB::all()).await } } #[tracing::instrument(level = "debug", skip(repeated_trash), fields(n_trash))] -fn notify_trash_changed>(repeated_trash: T) { +fn notify_trash_changed>(repeated_trash: T) { let repeated_trash = repeated_trash.into(); tracing::Span::current().record("n_trash", &repeated_trash.len()); send_anonymous_dart_notification(FolderNotification::TrashUpdated) @@ -292,9 +292,9 @@ fn notify_trash_changed>(repeated_trash: T) { #[derive(Clone)] pub enum TrashEvent { - NewTrash(RepeatedTrashId, mpsc::Sender>), - Putback(RepeatedTrashId, mpsc::Sender>), - Delete(RepeatedTrashId, mpsc::Sender>), + NewTrash(RepeatedTrashIdPB, mpsc::Sender>), + Putback(RepeatedTrashIdPB, mpsc::Sender>), + Delete(RepeatedTrashIdPB, mpsc::Sender>), } impl std::fmt::Debug for TrashEvent { diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs index b1853faaf5..0034745253 100644 --- a/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs @@ -1,5 +1,5 @@ use crate::{ - entities::trash::{RepeatedTrash, RepeatedTrashId, TrashId}, + entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB}, errors::FlowyError, services::TrashController, }; @@ -9,14 +9,14 @@ use std::sync::Arc; #[tracing::instrument(level = "debug", skip(controller), err)] pub(crate) async fn read_trash_handler( controller: AppData>, -) -> DataResult { +) -> DataResult { let repeated_trash = controller.read_trash().await?; data_result(repeated_trash) } #[tracing::instrument(level = "debug", skip(identifier, controller), err)] pub(crate) async fn putback_trash_handler( - identifier: Data, + identifier: Data, controller: AppData>, ) -> Result<(), FlowyError> { let _ = controller.putback(&identifier.id).await?; @@ -25,7 +25,7 @@ pub(crate) async fn putback_trash_handler( #[tracing::instrument(level = "debug", skip(identifiers, controller), err)] pub(crate) async fn delete_trash_handler( - identifiers: Data, + identifiers: Data, controller: AppData>, ) -> Result<(), FlowyError> { let _ = controller.delete(identifiers.into_inner()).await?; diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs index d573b096f6..df1f668941 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs @@ -1,11 +1,11 @@ pub use crate::entities::view::ViewDataType; -use crate::entities::ViewInfo; +use crate::entities::ViewInfoPB; use crate::manager::{ViewDataProcessor, ViewDataProcessorMap}; use crate::{ dart_notification::{send_dart_notification, FolderNotification}, entities::{ - trash::{RepeatedTrashId, TrashType}, - view::{CreateViewParams, RepeatedView, UpdateViewParams, View, ViewId}, + trash::{RepeatedTrashIdPB, TrashType}, + view::{CreateViewParams, RepeatedViewPB, UpdateViewParams, ViewIdPB, ViewPB}, }, errors::{FlowyError, FlowyResult}, event_map::{FolderCouldServiceV1, WorkspaceUser}, @@ -17,7 +17,7 @@ use crate::{ use bytes::Bytes; use flowy_database::kv::KV; use flowy_folder_data_model::revision::{gen_view_id, ViewRevision}; -use flowy_sync::entities::text_block::TextBlockId; +use flowy_sync::entities::text_block::TextBlockIdPB; use futures::{FutureExt, StreamExt}; use std::{collections::HashSet, sync::Arc}; @@ -106,7 +106,7 @@ impl ViewController { } #[tracing::instrument(level = "debug", skip(self, view_id), fields(view_id = %view_id.value), err)] - pub(crate) async fn read_view(&self, view_id: ViewId) -> Result { + pub(crate) async fn read_view(&self, view_id: ViewIdPB) -> Result { let view_rev = self .persistence .begin_transaction(|transaction| { @@ -123,25 +123,25 @@ impl ViewController { } #[tracing::instrument(level = "debug", skip(self, view_id), fields(view_id = %view_id.value), err)] - pub(crate) async fn read_view_info(&self, view_id: ViewId) -> Result { + pub(crate) async fn read_view_info(&self, view_id: ViewIdPB) -> Result { let view_info = self .persistence .begin_transaction(|transaction| { let view_rev = transaction.read_view(&view_id.value)?; - let items: Vec = view_rev + let items: Vec = view_rev .belongings .into_iter() .map(|view_rev| view_rev.into()) .collect(); - let view_info = ViewInfo { + let view_info = ViewInfoPB { id: view_rev.id, belong_to_id: view_rev.belong_to_id, name: view_rev.name, desc: view_rev.desc, data_type: view_rev.data_type.into(), - belongings: RepeatedView { items }, + belongings: RepeatedViewPB { items }, ext_data: view_rev.ext_data, }; Ok(view_info) @@ -177,7 +177,7 @@ impl ViewController { } #[tracing::instrument(level = "debug", skip(self,params), fields(doc_id = %params.value), err)] - pub(crate) async fn delete_view(&self, params: TextBlockId) -> Result<(), FlowyError> { + pub(crate) async fn delete_view(&self, params: TextBlockIdPB) -> Result<(), FlowyError> { if let Some(view_id) = KV::get_str(LATEST_VIEW_ID) { if view_id == params.value { let _ = KV::remove(LATEST_VIEW_ID); @@ -245,7 +245,7 @@ impl ViewController { .begin_transaction(|transaction| { let _ = transaction.update_view(changeset)?; let view_rev = transaction.read_view(&view_id)?; - let view: View = view_rev.clone().into(); + let view: ViewPB = view_rev.clone().into(); send_dart_notification(&view_id, FolderNotification::ViewUpdated) .payload(view) .send(); @@ -297,7 +297,7 @@ impl ViewController { } #[tracing::instrument(level = "debug", skip(self), err)] - fn read_view_on_server(&self, params: ViewId) -> Result<(), FlowyError> { + fn read_view_on_server(&self, params: ViewIdPB) -> Result<(), FlowyError> { let token = self.user.token()?; let server = self.cloud_service.clone(); let persistence = self.persistence.clone(); @@ -310,7 +310,7 @@ impl ViewController { .await { Ok(_) => { - let view: View = view_rev.into(); + let view: ViewPB = view_rev.into(); send_dart_notification(&view.id, FolderNotification::ViewUpdated) .payload(view) .send(); @@ -464,7 +464,7 @@ fn get_data_processor( } fn read_local_views_with_transaction<'a>( - identifiers: RepeatedTrashId, + identifiers: RepeatedTrashIdPB, transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result, FlowyError> { let mut view_revs = vec![]; @@ -474,7 +474,7 @@ fn read_local_views_with_transaction<'a>( Ok(view_revs) } -fn notify_dart(view: View, notification: FolderNotification) { +fn notify_dart(view: ViewPB, notification: FolderNotification) { send_dart_notification(&view.id, notification).payload(view).send(); } @@ -489,13 +489,13 @@ fn notify_views_changed<'a>( trash_controller: Arc, transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult<()> { - let items: Vec = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)? + let items: Vec = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)? .into_iter() .map(|view_rev| view_rev.into()) .collect(); tracing::Span::current().record("view_count", &format!("{}", items.len()).as_str()); - let repeated_view = RepeatedView { items }; + let repeated_view = RepeatedViewPB { items }; send_dart_notification(belong_to_id, FolderNotification::AppViewsChanged) .payload(repeated_view) .send(); diff --git a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs index f970905f30..925ece6ba2 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs @@ -1,12 +1,13 @@ -use crate::entities::view::{MoveFolderItemParams, MoveFolderItemPayload, MoveFolderItemType}; -use crate::entities::ViewInfo; +use crate::entities::view::{MoveFolderItemParams, MoveFolderItemPayloadPB, MoveFolderItemType}; +use crate::entities::ViewInfoPB; use crate::manager::FolderManager; use crate::services::{notify_workspace_setting_did_change, AppController}; use crate::{ entities::{ - trash::Trash, + trash::TrashPB, view::{ - CreateViewParams, CreateViewPayload, RepeatedViewId, UpdateViewParams, UpdateViewPayload, View, ViewId, + CreateViewParams, CreateViewPayloadPB, RepeatedViewIdPB, UpdateViewParams, UpdateViewPayloadPB, ViewIdPB, + ViewPB, }, }, errors::FlowyError, @@ -17,35 +18,35 @@ use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::{convert::TryInto, sync::Arc}; pub(crate) async fn create_view_handler( - data: Data, + data: Data, controller: AppData>, -) -> DataResult { +) -> DataResult { let params: CreateViewParams = data.into_inner().try_into()?; let view_rev = controller.create_view_from_params(params).await?; data_result(view_rev.into()) } pub(crate) async fn read_view_handler( - data: Data, + data: Data, controller: AppData>, -) -> DataResult { - let view_id: ViewId = data.into_inner(); +) -> DataResult { + let view_id: ViewIdPB = data.into_inner(); let view_rev = controller.read_view(view_id.clone()).await?; data_result(view_rev.into()) } pub(crate) async fn read_view_info_handler( - data: Data, + data: Data, controller: AppData>, -) -> DataResult { - let view_id: ViewId = data.into_inner(); +) -> DataResult { + let view_id: ViewIdPB = data.into_inner(); let view_info = controller.read_view_info(view_id.clone()).await?; data_result(view_info) } #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn update_view_handler( - data: Data, + data: Data, controller: AppData>, ) -> Result<(), FlowyError> { let params: UpdateViewParams = data.into_inner().try_into()?; @@ -55,11 +56,11 @@ pub(crate) async fn update_view_handler( } pub(crate) async fn delete_view_handler( - data: Data, + data: Data, view_controller: AppData>, trash_controller: AppData>, ) -> Result<(), FlowyError> { - let params: RepeatedViewId = data.into_inner(); + let params: RepeatedViewIdPB = data.into_inner(); for view_id in ¶ms.items { let _ = view_controller.delete_view(view_id.into()).await; } @@ -72,35 +73,35 @@ pub(crate) async fn delete_view_handler( let trash_rev: TrashRevision = view.into(); trash_rev.into() }) - .collect::>(); + .collect::>(); let _ = trash_controller.add(trash).await?; Ok(()) } pub(crate) async fn set_latest_view_handler( - data: Data, + data: Data, folder: AppData>, controller: AppData>, ) -> Result<(), FlowyError> { - let view_id: ViewId = data.into_inner(); + let view_id: ViewIdPB = data.into_inner(); let _ = controller.set_latest_view(&view_id.value)?; let _ = notify_workspace_setting_did_change(&folder, &view_id).await?; Ok(()) } pub(crate) async fn close_view_handler( - data: Data, + data: Data, controller: AppData>, ) -> Result<(), FlowyError> { - let view_id: ViewId = data.into_inner(); + let view_id: ViewIdPB = data.into_inner(); let _ = controller.close_view(&view_id.value).await?; Ok(()) } #[tracing::instrument(level = "debug", skip_all, err)] pub(crate) async fn move_item_handler( - data: Data, + data: Data, view_controller: AppData>, app_controller: AppData>, ) -> Result<(), FlowyError> { @@ -120,10 +121,10 @@ pub(crate) async fn move_item_handler( #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn duplicate_view_handler( - data: Data, + data: Data, controller: AppData>, ) -> Result<(), FlowyError> { - let view_id: ViewId = data.into_inner(); + let view_id: ViewIdPB = data.into_inner(); let _ = controller.duplicate_view(&view_id.value).await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs index 9863cafdc5..42f3f65fa1 100644 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs @@ -52,7 +52,7 @@ impl WorkspaceController { .into_iter() .map(|workspace_rev| workspace_rev.into()) .collect(); - let repeated_workspace = RepeatedWorkspace { items: workspaces }; + let repeated_workspace = RepeatedWorkspacePB { items: workspaces }; send_dart_notification(&token, FolderNotification::UserCreateWorkspace) .payload(repeated_workspace) .send(); @@ -99,7 +99,7 @@ impl WorkspaceController { Ok(()) } - pub(crate) async fn open_workspace(&self, params: WorkspaceId) -> Result { + pub(crate) async fn open_workspace(&self, params: WorkspaceIdPB) -> Result { let user_id = self.user.user_id()?; if let Some(workspace_id) = params.value { let workspace = self @@ -131,14 +131,14 @@ impl WorkspaceController { workspace_id: Option, user_id: &str, transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result { + ) -> Result { let workspace_id = workspace_id.to_owned(); let workspaces = transaction .read_workspaces(user_id, workspace_id)? .into_iter() .map(|workspace_rev| workspace_rev.into()) .collect(); - Ok(RepeatedWorkspace { items: workspaces }) + Ok(RepeatedWorkspacePB { items: workspaces }) } pub(crate) fn read_local_workspace<'a>( @@ -146,7 +146,7 @@ impl WorkspaceController { workspace_id: String, user_id: &str, transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result { + ) -> Result { let mut workspace_revs = transaction.read_workspaces(user_id, Some(workspace_id.clone()))?; if workspace_revs.is_empty() { return Err(FlowyError::record_not_found().context(format!("{} workspace not found", workspace_id))); @@ -155,7 +155,7 @@ impl WorkspaceController { let workspace = workspace_revs .drain(..1) .map(|workspace_rev| workspace_rev.into()) - .collect::>() + .collect::>() .pop() .unwrap(); Ok(workspace) @@ -186,7 +186,7 @@ impl WorkspaceController { #[tracing::instrument(level = "trace", skip(self), err)] fn delete_workspace_on_server(&self, workspace_id: &str) -> Result<(), FlowyError> { - let params = WorkspaceId { + let params = WorkspaceIdPB { value: Some(workspace_id.to_string()), }; let (token, server) = (self.user.token()?, self.cloud_service.clone()); @@ -221,11 +221,11 @@ pub async fn notify_workspace_setting_did_change( )?; let setting = match transaction.read_view(view_id) { - Ok(latest_view) => CurrentWorkspaceSetting { + Ok(latest_view) => CurrentWorkspaceSettingPB { workspace, latest_view: Some(latest_view.into()), }, - Err(_) => CurrentWorkspaceSetting { + Err(_) => CurrentWorkspaceSettingPB { workspace, latest_view: None, }, diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs index 02fb37a12b..234fa3b7de 100644 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs @@ -1,7 +1,7 @@ use crate::entities::{ - app::RepeatedApp, - view::View, - workspace::{CurrentWorkspaceSetting, RepeatedWorkspace, WorkspaceId, *}, + app::RepeatedAppPB, + view::ViewPB, + workspace::{CurrentWorkspaceSettingPB, RepeatedWorkspacePB, WorkspaceIdPB, *}, }; use crate::{ dart_notification::{send_dart_notification, FolderNotification}, @@ -14,9 +14,9 @@ use std::{convert::TryInto, sync::Arc}; #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn create_workspace_handler( - data: Data, + data: Data, controller: AppData>, -) -> DataResult { +) -> DataResult { let controller = controller.get_ref().clone(); let params: CreateWorkspaceParams = data.into_inner().try_into()?; let workspace_rev = controller.create_workspace_from_params(params).await?; @@ -26,33 +26,33 @@ pub(crate) async fn create_workspace_handler( #[tracing::instrument(level = "debug", skip(controller), err)] pub(crate) async fn read_workspace_apps_handler( controller: AppData>, -) -> DataResult { +) -> DataResult { let items = controller .read_current_workspace_apps() .await? .into_iter() .map(|app_rev| app_rev.into()) .collect(); - let repeated_app = RepeatedApp { items }; + let repeated_app = RepeatedAppPB { items }; data_result(repeated_app) } #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn open_workspace_handler( - data: Data, + data: Data, controller: AppData>, -) -> DataResult { - let params: WorkspaceId = data.into_inner(); +) -> DataResult { + let params: WorkspaceIdPB = data.into_inner(); let workspaces = controller.open_workspace(params).await?; data_result(workspaces) } #[tracing::instrument(level = "debug", skip(data, folder), err)] pub(crate) async fn read_workspaces_handler( - data: Data, + data: Data, folder: AppData>, -) -> DataResult { - let params: WorkspaceId = data.into_inner(); +) -> DataResult { + let params: WorkspaceIdPB = data.into_inner(); let user_id = folder.user.user_id()?; let workspace_controller = folder.workspace_controller.clone(); @@ -79,10 +79,10 @@ pub(crate) async fn read_workspaces_handler( #[tracing::instrument(level = "debug", skip(folder), err)] pub async fn read_cur_workspace_handler( folder: AppData>, -) -> DataResult { +) -> DataResult { let workspace_id = get_current_workspace()?; let user_id = folder.user.user_id()?; - let params = WorkspaceId { + let params = WorkspaceIdPB { value: Some(workspace_id.clone()), }; @@ -95,13 +95,13 @@ pub async fn read_cur_workspace_handler( }) .await?; - let latest_view: Option = folder + let latest_view: Option = folder .view_controller .latest_visit_view() .await .unwrap_or(None) .map(|view_rev| view_rev.into()); - let setting = CurrentWorkspaceSetting { workspace, latest_view }; + let setting = CurrentWorkspaceSettingPB { workspace, latest_view }; let _ = read_workspaces_on_server(folder, user_id, params); data_result(setting) } @@ -110,7 +110,7 @@ pub async fn read_cur_workspace_handler( fn read_workspaces_on_server( folder_manager: AppData>, user_id: String, - params: WorkspaceId, + params: WorkspaceIdPB, ) -> Result<(), FlowyError> { let (token, server) = (folder_manager.user.token()?, folder_manager.cloud_service.clone()); let persistence = folder_manager.persistence.clone(); @@ -145,7 +145,7 @@ fn read_workspaces_on_server( }) .await?; - let repeated_workspace = RepeatedWorkspace { + let repeated_workspace = RepeatedWorkspacePB { items: workspace_revs .into_iter() .map(|workspace_rev| workspace_rev.into()) diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs b/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs index 86c3dc7478..f17986b484 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs @@ -1,6 +1,6 @@ use crate::script::{invalid_workspace_name_test_case, FolderScript::*, FolderTest}; use flowy_folder::entities::view::ViewDataType; -use flowy_folder::entities::workspace::CreateWorkspacePayload; +use flowy_folder::entities::workspace::CreateWorkspacePayloadPB; use flowy_revision::disk::RevisionState; use flowy_test::{event_builder::*, FlowySDKTest}; @@ -63,7 +63,7 @@ async fn workspace_create_with_apps() { async fn workspace_create_with_invalid_name() { for (name, code) in invalid_workspace_name_test_case() { let sdk = FlowySDKTest::default(); - let request = CreateWorkspacePayload { + let request = CreateWorkspacePayloadPB { name, desc: "".to_owned(), }; diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs index b461730cf9..ed16664d90 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs @@ -1,23 +1,23 @@ -use flowy_folder::entities::view::{RepeatedViewId, ViewId}; -use flowy_folder::entities::workspace::WorkspaceId; +use flowy_folder::entities::view::{RepeatedViewIdPB, ViewIdPB}; +use flowy_folder::entities::workspace::WorkspaceIdPB; use flowy_folder::entities::{ - app::{App, RepeatedApp}, - trash::Trash, - view::{RepeatedView, View, ViewDataType}, - workspace::Workspace, + app::{AppIdPB, CreateAppPayloadPB, UpdateAppPayloadPB}, + trash::{RepeatedTrashPB, TrashIdPB, TrashType}, + view::{CreateViewPayloadPB, UpdateViewPayloadPB}, + workspace::{CreateWorkspacePayloadPB, RepeatedWorkspacePB}, }; use flowy_folder::entities::{ - app::{AppId, CreateAppPayload, UpdateAppPayload}, - trash::{RepeatedTrash, TrashId, TrashType}, - view::{CreateViewPayload, UpdateViewPayload}, - workspace::{CreateWorkspacePayload, RepeatedWorkspace}, + app::{AppPB, RepeatedAppPB}, + trash::TrashPB, + view::{RepeatedViewPB, ViewDataType, ViewPB}, + workspace::WorkspacePB, }; use flowy_folder::event_map::FolderEvent::*; use flowy_folder::{errors::ErrorCode, services::folder_editor::FolderEditor}; use flowy_revision::disk::RevisionState; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; -use flowy_sync::entities::text_block::TextBlockInfo; +use flowy_sync::entities::text_block::DocumentPB; use flowy_test::{event_builder::*, FlowySDKTest}; use std::{sync::Arc, time::Duration}; use tokio::time::sleep; @@ -30,7 +30,7 @@ pub enum FolderScript { desc: String, }, // AssertWorkspaceRevisionJson(String), - AssertWorkspace(Workspace), + AssertWorkspace(WorkspacePB), ReadWorkspace(Option), // App @@ -39,7 +39,7 @@ pub enum FolderScript { desc: String, }, // AssertAppRevisionJson(String), - AssertApp(App), + AssertApp(AppPB), ReadApp(String), UpdateApp { name: Option, @@ -53,7 +53,7 @@ pub enum FolderScript { desc: String, data_type: ViewDataType, }, - AssertView(View), + AssertView(ViewPB), ReadView(String), UpdateView { name: Option, @@ -79,11 +79,11 @@ pub enum FolderScript { pub struct FolderTest { pub sdk: FlowySDKTest, - pub all_workspace: Vec, - pub workspace: Workspace, - pub app: App, - pub view: View, - pub trash: Vec, + pub all_workspace: Vec, + pub workspace: WorkspacePB, + pub app: AppPB, + pub view: ViewPB, + pub trash: Vec, // pub folder_editor: } @@ -101,11 +101,11 @@ impl FolderTest { ViewDataType::TextBlock, ) .await; - app.belongings = RepeatedView { + app.belongings = RepeatedViewPB { items: vec![view.clone()], }; - workspace.apps = RepeatedApp { + workspace.apps = RepeatedAppPB { items: vec![app.clone()], }; Self { @@ -247,8 +247,8 @@ pub fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> { ] } -pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> Workspace { - let request = CreateWorkspacePayload { +pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB { + let request = CreateWorkspacePayloadPB { name: name.to_owned(), desc: desc.to_owned(), }; @@ -258,18 +258,18 @@ pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> Wor .payload(request) .async_send() .await - .parse::(); + .parse::(); workspace } -pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option) -> Vec { - let request = WorkspaceId { value: workspace_id }; +pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option) -> Vec { + let request = WorkspaceIdPB { value: workspace_id }; let mut repeated_workspace = FolderEventBuilder::new(sdk.clone()) .event(ReadWorkspaces) .payload(request.clone()) .async_send() .await - .parse::(); + .parse::(); let workspaces; if let Some(workspace_id) = &request.value { @@ -277,7 +277,7 @@ pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option) -> .into_inner() .into_iter() .filter(|workspace| &workspace.id == workspace_id) - .collect::>(); + .collect::>(); debug_assert_eq!(workspaces.len(), 1); } else { workspaces = repeated_workspace.items; @@ -286,8 +286,8 @@ pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option) -> workspaces } -pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc: &str) -> App { - let create_app_request = CreateAppPayload { +pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc: &str) -> AppPB { + let create_app_request = CreateAppPayloadPB { workspace_id: workspace_id.to_owned(), name: name.to_string(), desc: desc.to_string(), @@ -299,12 +299,12 @@ pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc .payload(create_app_request) .async_send() .await - .parse::(); + .parse::(); app } -pub async fn read_app(sdk: &FlowySDKTest, app_id: &str) -> App { - let request = AppId { +pub async fn read_app(sdk: &FlowySDKTest, app_id: &str) -> AppPB { + let request = AppIdPB { value: app_id.to_owned(), }; @@ -313,13 +313,13 @@ pub async fn read_app(sdk: &FlowySDKTest, app_id: &str) -> App { .payload(request) .async_send() .await - .parse::(); + .parse::(); app } pub async fn update_app(sdk: &FlowySDKTest, app_id: &str, name: Option, desc: Option) { - let request = UpdateAppPayload { + let request = UpdateAppPayloadPB { app_id: app_id.to_string(), name, desc, @@ -335,7 +335,7 @@ pub async fn update_app(sdk: &FlowySDKTest, app_id: &str, name: Option, } pub async fn delete_app(sdk: &FlowySDKTest, app_id: &str) { - let request = AppId { + let request = AppIdPB { value: app_id.to_string(), }; @@ -346,8 +346,8 @@ pub async fn delete_app(sdk: &FlowySDKTest, app_id: &str) { .await; } -pub async fn create_view(sdk: &FlowySDKTest, app_id: &str, name: &str, desc: &str, data_type: ViewDataType) -> View { - let request = CreateViewPayload { +pub async fn create_view(sdk: &FlowySDKTest, app_id: &str, name: &str, desc: &str, data_type: ViewDataType) -> ViewPB { + let request = CreateViewPayloadPB { belong_to_id: app_id.to_string(), name: name.to_string(), desc: desc.to_string(), @@ -361,22 +361,22 @@ pub async fn create_view(sdk: &FlowySDKTest, app_id: &str, name: &str, desc: &st .payload(request) .async_send() .await - .parse::(); + .parse::(); view } -pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> View { - let view_id: ViewId = view_id.into(); +pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> ViewPB { + let view_id: ViewIdPB = view_id.into(); FolderEventBuilder::new(sdk.clone()) .event(ReadView) .payload(view_id) .async_send() .await - .parse::() + .parse::() } pub async fn update_view(sdk: &FlowySDKTest, view_id: &str, name: Option, desc: Option) { - let request = UpdateViewPayload { + let request = UpdateViewPayloadPB { view_id: view_id.to_string(), name, desc, @@ -390,7 +390,7 @@ pub async fn update_view(sdk: &FlowySDKTest, view_id: &str, name: Option } pub async fn delete_view(sdk: &FlowySDKTest, view_ids: Vec) { - let request = RepeatedViewId { items: view_ids }; + let request = RepeatedViewIdPB { items: view_ids }; FolderEventBuilder::new(sdk.clone()) .event(DeleteView) .payload(request) @@ -399,26 +399,26 @@ pub async fn delete_view(sdk: &FlowySDKTest, view_ids: Vec) { } #[allow(dead_code)] -pub async fn set_latest_view(sdk: &FlowySDKTest, view_id: &str) -> TextBlockInfo { - let view_id: ViewId = view_id.into(); +pub async fn set_latest_view(sdk: &FlowySDKTest, view_id: &str) -> DocumentPB { + let view_id: ViewIdPB = view_id.into(); FolderEventBuilder::new(sdk.clone()) .event(SetLatestView) .payload(view_id) .async_send() .await - .parse::() + .parse::() } -pub async fn read_trash(sdk: &FlowySDKTest) -> RepeatedTrash { +pub async fn read_trash(sdk: &FlowySDKTest) -> RepeatedTrashPB { FolderEventBuilder::new(sdk.clone()) .event(ReadTrash) .async_send() .await - .parse::() + .parse::() } pub async fn restore_app_from_trash(sdk: &FlowySDKTest, app_id: &str) { - let id = TrashId { + let id = TrashIdPB { id: app_id.to_owned(), ty: TrashType::TrashApp, }; @@ -430,7 +430,7 @@ pub async fn restore_app_from_trash(sdk: &FlowySDKTest, app_id: &str) { } pub async fn restore_view_from_trash(sdk: &FlowySDKTest, view_id: &str) { - let id = TrashId { + let id = TrashIdPB { id: view_id.to_owned(), ty: TrashType::TrashView, }; diff --git a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs index 5ff7fea0f4..df2192c77d 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs @@ -1,4 +1,3 @@ -use crate::entities::GridRowId; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; @@ -6,25 +5,96 @@ use flowy_grid_data_model::revision::RowRevision; use std::sync::Arc; #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct GridBlock { +pub struct GridBlockPB { #[pb(index = 1)] pub id: String, #[pb(index = 2)] - pub row_infos: Vec, + pub rows: Vec, } -impl GridBlock { - pub fn new(block_id: &str, row_orders: Vec) -> Self { +impl GridBlockPB { + pub fn new(block_id: &str, rows: Vec) -> Self { Self { id: block_id.to_owned(), - row_infos: row_orders, + rows, } } } #[derive(Debug, Default, Clone, ProtoBuf)] -pub struct BlockRowInfo { +pub struct GridRowPB { + #[pb(index = 1)] + pub block_id: String, + + #[pb(index = 2)] + pub id: String, + + #[pb(index = 3)] + pub height: i32, +} + +impl GridRowPB { + pub fn row_id(&self) -> &str { + &self.id + } + + pub fn block_id(&self) -> &str { + &self.block_id + } +} + +impl std::convert::From<&RowRevision> for GridRowPB { + fn from(rev: &RowRevision) -> Self { + Self { + block_id: rev.block_id.clone(), + id: rev.id.clone(), + height: rev.height, + } + } +} + +impl std::convert::From<&Arc> for GridRowPB { + fn from(rev: &Arc) -> Self { + Self { + block_id: rev.block_id.clone(), + id: rev.id.clone(), + height: rev.height, + } + } +} + +#[derive(Debug, Default, ProtoBuf)] +pub struct OptionalRowPB { + #[pb(index = 1, one_of)] + pub row: Option, +} + +#[derive(Debug, Default, ProtoBuf)] +pub struct RepeatedRowPB { + #[pb(index = 1)] + pub items: Vec, +} + +impl std::convert::From> for RepeatedRowPB { + fn from(items: Vec) -> Self { + Self { items } + } +} +#[derive(Debug, Default, ProtoBuf)] +pub struct RepeatedGridBlockPB { + #[pb(index = 1)] + pub items: Vec, +} + +impl std::convert::From> for RepeatedGridBlockPB { + fn from(items: Vec) -> Self { + Self { items } + } +} + +#[derive(Debug, Clone, Default, ProtoBuf)] +pub struct InsertedRowPB { #[pb(index = 1)] pub block_id: String, @@ -33,161 +103,99 @@ pub struct BlockRowInfo { #[pb(index = 3)] pub height: i32, -} -impl BlockRowInfo { - pub fn row_id(&self) -> &str { - &self.row_id - } - - pub fn block_id(&self) -> &str { - &self.block_id - } -} - -impl std::convert::From<&RowRevision> for BlockRowInfo { - fn from(rev: &RowRevision) -> Self { - Self { - block_id: rev.block_id.clone(), - row_id: rev.id.clone(), - height: rev.height, - } - } -} - -impl std::convert::From<&Arc> for BlockRowInfo { - fn from(rev: &Arc) -> Self { - Self { - block_id: rev.block_id.clone(), - row_id: rev.id.clone(), - height: rev.height, - } - } -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct Row { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub height: i32, -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct OptionalRow { - #[pb(index = 1, one_of)] - pub row: Option, -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct RepeatedRow { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::convert::From> for RepeatedRow { - fn from(items: Vec) -> Self { - Self { items } - } -} -#[derive(Debug, Default, ProtoBuf)] -pub struct RepeatedGridBlock { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::convert::From> for RepeatedGridBlock { - fn from(items: Vec) -> Self { - Self { items } - } -} - -#[derive(Debug, Clone, Default, ProtoBuf)] -pub struct IndexRowOrder { - #[pb(index = 1)] - pub row_info: BlockRowInfo, - - #[pb(index = 2, one_of)] + #[pb(index = 4, one_of)] pub index: Option, } #[derive(Debug, Default, ProtoBuf)] -pub struct UpdatedRowOrder { +pub struct UpdatedRowPB { #[pb(index = 1)] - pub row_info: BlockRowInfo, + pub block_id: String, #[pb(index = 2)] - pub row: Row, + pub row_id: String, + + #[pb(index = 3)] + pub row: GridRowPB, } -impl UpdatedRowOrder { - pub fn new(row_rev: &RowRevision, row: Row) -> Self { +impl UpdatedRowPB { + pub fn new(row_rev: &RowRevision, row: GridRowPB) -> Self { Self { - row_info: BlockRowInfo::from(row_rev), + row_id: row_rev.id.clone(), + block_id: row_rev.block_id.clone(), row, } } } -impl std::convert::From for IndexRowOrder { - fn from(row_info: BlockRowInfo) -> Self { - Self { row_info, index: None } +impl std::convert::From for InsertedRowPB { + fn from(row_info: GridRowPB) -> Self { + Self { + row_id: row_info.id, + block_id: row_info.block_id, + height: row_info.height, + index: None, + } } } -impl std::convert::From<&RowRevision> for IndexRowOrder { +impl std::convert::From<&RowRevision> for InsertedRowPB { fn from(row: &RowRevision) -> Self { - let row_order = BlockRowInfo::from(row); + let row_order = GridRowPB::from(row); Self::from(row_order) } } #[derive(Debug, Default, ProtoBuf)] -pub struct GridRowsChangeset { +pub struct GridBlockChangesetPB { #[pb(index = 1)] pub block_id: String, #[pb(index = 2)] - pub inserted_rows: Vec, + pub inserted_rows: Vec, #[pb(index = 3)] - pub deleted_rows: Vec, + pub deleted_rows: Vec, #[pb(index = 4)] - pub updated_rows: Vec, + pub updated_rows: Vec, + + #[pb(index = 5)] + pub visible_rows: Vec, + + #[pb(index = 6)] + pub hide_rows: Vec, } -impl GridRowsChangeset { - pub fn insert(block_id: &str, inserted_rows: Vec) -> Self { +impl GridBlockChangesetPB { + pub fn insert(block_id: &str, inserted_rows: Vec) -> Self { Self { block_id: block_id.to_owned(), inserted_rows, - deleted_rows: vec![], - updated_rows: vec![], + ..Default::default() } } - pub fn delete(block_id: &str, deleted_rows: Vec) -> Self { + pub fn delete(block_id: &str, deleted_rows: Vec) -> Self { Self { block_id: block_id.to_owned(), - inserted_rows: vec![], deleted_rows, - updated_rows: vec![], + ..Default::default() } } - pub fn update(block_id: &str, updated_rows: Vec) -> Self { + pub fn update(block_id: &str, updated_rows: Vec) -> Self { Self { block_id: block_id.to_owned(), - inserted_rows: vec![], - deleted_rows: vec![], updated_rows, + ..Default::default() } } } #[derive(ProtoBuf, Default)] -pub struct QueryGridBlocksPayload { +pub struct QueryGridBlocksPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -200,7 +208,7 @@ pub struct QueryGridBlocksParams { pub block_ids: Vec, } -impl TryInto for QueryGridBlocksPayload { +impl TryInto for QueryGridBlocksPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs index 391a63fa3f..11cbdeb88e 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs @@ -1,4 +1,4 @@ -use crate::entities::{FieldIdentifier, FieldIdentifierPayload}; +use crate::entities::{FieldIdentifierParams, GridFieldIdentifierPayloadPB}; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; @@ -6,28 +6,28 @@ use flowy_grid_data_model::revision::{CellRevision, RowMetaChangeset}; use std::collections::HashMap; #[derive(ProtoBuf, Default)] -pub struct CreateSelectOptionPayload { +pub struct CreateSelectOptionPayloadPB { #[pb(index = 1)] - pub field_identifier: FieldIdentifierPayload, + pub field_identifier: GridFieldIdentifierPayloadPB, #[pb(index = 2)] pub option_name: String, } pub struct CreateSelectOptionParams { - pub field_identifier: FieldIdentifier, + pub field_identifier: FieldIdentifierParams, pub option_name: String, } impl std::ops::Deref for CreateSelectOptionParams { - type Target = FieldIdentifier; + type Target = FieldIdentifierParams; fn deref(&self) -> &Self::Target { &self.field_identifier } } -impl TryInto for CreateSelectOptionPayload { +impl TryInto for CreateSelectOptionPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -41,7 +41,7 @@ impl TryInto for CreateSelectOptionPayload { } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct CellIdentifierPayload { +pub struct GridCellIdentifierPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -52,20 +52,20 @@ pub struct CellIdentifierPayload { pub row_id: String, } -pub struct CellIdentifier { +pub struct CellIdentifierParams { pub grid_id: String, pub field_id: String, pub row_id: String, } -impl TryInto for CellIdentifierPayload { +impl TryInto for GridCellIdentifierPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - Ok(CellIdentifier { + Ok(CellIdentifierParams { grid_id: grid_id.0, field_id: field_id.0, row_id: row_id.0, @@ -73,7 +73,7 @@ impl TryInto for CellIdentifierPayload { } } #[derive(Debug, Default, ProtoBuf)] -pub struct Cell { +pub struct GridCellPB { #[pb(index = 1)] pub field_id: String, @@ -81,7 +81,7 @@ pub struct Cell { pub data: Vec, } -impl Cell { +impl GridCellPB { pub fn new(field_id: &str, data: Vec) -> Self { Self { field_id: field_id.to_owned(), @@ -98,32 +98,32 @@ impl Cell { } #[derive(Debug, Default, ProtoBuf)] -pub struct RepeatedCell { +pub struct RepeatedCellPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl std::ops::Deref for RepeatedCell { - type Target = Vec; +impl std::ops::Deref for RepeatedCellPB { + type Target = Vec; fn deref(&self) -> &Self::Target { &self.items } } -impl std::ops::DerefMut for RepeatedCell { +impl std::ops::DerefMut for RepeatedCellPB { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items } } -impl std::convert::From> for RepeatedCell { - fn from(items: Vec) -> Self { +impl std::convert::From> for RepeatedCellPB { + fn from(items: Vec) -> Self { Self { items } } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct CellChangeset { +pub struct CellChangesetPB { #[pb(index = 1)] pub grid_id: String, @@ -134,15 +134,15 @@ pub struct CellChangeset { pub field_id: String, #[pb(index = 4, one_of)] - pub cell_content_changeset: Option, + pub content: Option, } -impl std::convert::From for RowMetaChangeset { - fn from(changeset: CellChangeset) -> Self { +impl std::convert::From for RowMetaChangeset { + fn from(changeset: CellChangesetPB) -> Self { let mut cell_by_field_id = HashMap::with_capacity(1); let field_id = changeset.field_id; let cell_rev = CellRevision { - data: changeset.cell_content_changeset.unwrap_or_else(|| "".to_owned()), + data: changeset.content.unwrap_or_else(|| "".to_owned()), }; cell_by_field_id.insert(field_id, cell_rev); diff --git a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs index 137693e13a..c769b4f08b 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString}; #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct Field { +pub struct GridFieldPB { #[pb(index = 1)] pub id: String, @@ -35,7 +35,7 @@ pub struct Field { pub is_primary: bool, } -impl std::convert::From for Field { +impl std::convert::From for GridFieldPB { fn from(field_rev: FieldRevision) -> Self { Self { id: field_rev.id, @@ -50,31 +50,31 @@ impl std::convert::From for Field { } } -impl std::convert::From> for Field { +impl std::convert::From> for GridFieldPB { fn from(field_rev: Arc) -> Self { let field_rev = field_rev.as_ref().clone(); - Field::from(field_rev) + GridFieldPB::from(field_rev) } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct FieldOrder { +pub struct GridFieldIdPB { #[pb(index = 1)] pub field_id: String, } -impl std::convert::From<&str> for FieldOrder { +impl std::convert::From<&str> for GridFieldIdPB { fn from(s: &str) -> Self { - FieldOrder { field_id: s.to_owned() } + GridFieldIdPB { field_id: s.to_owned() } } } -impl std::convert::From for FieldOrder { +impl std::convert::From for GridFieldIdPB { fn from(s: String) -> Self { - FieldOrder { field_id: s } + GridFieldIdPB { field_id: s } } } -impl std::convert::From<&Arc> for FieldOrder { +impl std::convert::From<&Arc> for GridFieldIdPB { fn from(field_rev: &Arc) -> Self { Self { field_id: field_rev.id.clone(), @@ -82,22 +82,22 @@ impl std::convert::From<&Arc> for FieldOrder { } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct GridFieldChangeset { +pub struct GridFieldChangesetPB { #[pb(index = 1)] pub grid_id: String, #[pb(index = 2)] - pub inserted_fields: Vec, + pub inserted_fields: Vec, #[pb(index = 3)] - pub deleted_fields: Vec, + pub deleted_fields: Vec, #[pb(index = 4)] - pub updated_fields: Vec, + pub updated_fields: Vec, } -impl GridFieldChangeset { - pub fn insert(grid_id: &str, inserted_fields: Vec) -> Self { +impl GridFieldChangesetPB { + pub fn insert(grid_id: &str, inserted_fields: Vec) -> Self { Self { grid_id: grid_id.to_owned(), inserted_fields, @@ -106,7 +106,7 @@ impl GridFieldChangeset { } } - pub fn delete(grid_id: &str, deleted_fields: Vec) -> Self { + pub fn delete(grid_id: &str, deleted_fields: Vec) -> Self { Self { grid_id: grid_id.to_string(), inserted_fields: vec![], @@ -115,7 +115,7 @@ impl GridFieldChangeset { } } - pub fn update(grid_id: &str, updated_fields: Vec) -> Self { + pub fn update(grid_id: &str, updated_fields: Vec) -> Self { Self { grid_id: grid_id.to_string(), inserted_fields: vec![], @@ -126,25 +126,25 @@ impl GridFieldChangeset { } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct IndexField { +pub struct IndexFieldPB { #[pb(index = 1)] - pub field: Field, + pub field: GridFieldPB, #[pb(index = 2)] pub index: i32, } -impl IndexField { +impl IndexFieldPB { pub fn from_field_rev(field_rev: &Arc, index: usize) -> Self { Self { - field: Field::from(field_rev.as_ref().clone()), + field: GridFieldPB::from(field_rev.as_ref().clone()), index: index as i32, } } } #[derive(Debug, Default, ProtoBuf)] -pub struct GetEditFieldContextPayload { +pub struct GetEditFieldContextPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -156,7 +156,7 @@ pub struct GetEditFieldContextPayload { } #[derive(Debug, Default, ProtoBuf)] -pub struct EditFieldPayload { +pub struct EditFieldPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -176,7 +176,7 @@ pub struct EditFieldParams { pub field_type: FieldType, } -impl TryInto for EditFieldPayload { +impl TryInto for EditFieldPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -195,7 +195,7 @@ pub struct CreateFieldParams { pub field_type: FieldType, } -impl TryInto for EditFieldPayload { +impl TryInto for EditFieldPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -209,87 +209,75 @@ impl TryInto for EditFieldPayload { } #[derive(Debug, Default, ProtoBuf)] -pub struct FieldTypeOptionContext { +pub struct FieldTypeOptionDataPB { #[pb(index = 1)] pub grid_id: String, #[pb(index = 2)] - pub grid_field: Field, + pub field: GridFieldPB, #[pb(index = 3)] pub type_option_data: Vec, } #[derive(Debug, Default, ProtoBuf)] -pub struct FieldTypeOptionData { +pub struct RepeatedGridFieldPB { #[pb(index = 1)] - pub grid_id: String, - - #[pb(index = 2)] - pub field: Field, - - #[pb(index = 3)] - pub type_option_data: Vec, + pub items: Vec, } - -#[derive(Debug, Default, ProtoBuf)] -pub struct RepeatedField { - #[pb(index = 1)] - pub items: Vec, -} -impl std::ops::Deref for RepeatedField { - type Target = Vec; +impl std::ops::Deref for RepeatedGridFieldPB { + type Target = Vec; fn deref(&self) -> &Self::Target { &self.items } } -impl std::ops::DerefMut for RepeatedField { +impl std::ops::DerefMut for RepeatedGridFieldPB { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items } } -impl std::convert::From> for RepeatedField { - fn from(items: Vec) -> Self { +impl std::convert::From> for RepeatedGridFieldPB { + fn from(items: Vec) -> Self { Self { items } } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct RepeatedFieldOrder { +pub struct RepeatedGridFieldIdPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl std::ops::Deref for RepeatedFieldOrder { - type Target = Vec; +impl std::ops::Deref for RepeatedGridFieldIdPB { + type Target = Vec; fn deref(&self) -> &Self::Target { &self.items } } -impl std::convert::From> for RepeatedFieldOrder { - fn from(field_orders: Vec) -> Self { - RepeatedFieldOrder { items: field_orders } +impl std::convert::From> for RepeatedGridFieldIdPB { + fn from(items: Vec) -> Self { + RepeatedGridFieldIdPB { items } } } -impl std::convert::From for RepeatedFieldOrder { +impl std::convert::From for RepeatedGridFieldIdPB { fn from(s: String) -> Self { - RepeatedFieldOrder { - items: vec![FieldOrder::from(s)], + RepeatedGridFieldIdPB { + items: vec![GridFieldIdPB::from(s)], } } } #[derive(ProtoBuf, Default)] -pub struct InsertFieldPayload { +pub struct InsertFieldPayloadPB { #[pb(index = 1)] pub grid_id: String, #[pb(index = 2)] - pub field: Field, + pub field: GridFieldPB, #[pb(index = 3)] pub type_option_data: Vec, @@ -301,12 +289,12 @@ pub struct InsertFieldPayload { #[derive(Clone)] pub struct InsertFieldParams { pub grid_id: String, - pub field: Field, + pub field: GridFieldPB, pub type_option_data: Vec, pub start_field_id: Option, } -impl TryInto for InsertFieldPayload { +impl TryInto for InsertFieldPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -328,7 +316,7 @@ impl TryInto for InsertFieldPayload { } #[derive(ProtoBuf, Default)] -pub struct UpdateFieldTypeOptionPayload { +pub struct UpdateFieldTypeOptionPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -346,7 +334,7 @@ pub struct UpdateFieldTypeOptionParams { pub type_option_data: Vec, } -impl TryInto for UpdateFieldTypeOptionPayload { +impl TryInto for UpdateFieldTypeOptionPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -362,33 +350,33 @@ impl TryInto for UpdateFieldTypeOptionPayload { } #[derive(ProtoBuf, Default)] -pub struct QueryFieldPayload { +pub struct QueryFieldPayloadPB { #[pb(index = 1)] pub grid_id: String, #[pb(index = 2)] - pub field_orders: RepeatedFieldOrder, + pub field_ids: RepeatedGridFieldIdPB, } pub struct QueryFieldParams { pub grid_id: String, - pub field_orders: RepeatedFieldOrder, + pub field_ids: RepeatedGridFieldIdPB, } -impl TryInto for QueryFieldPayload { +impl TryInto for QueryFieldPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; Ok(QueryFieldParams { grid_id: grid_id.0, - field_orders: self.field_orders, + field_ids: self.field_ids, }) } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct FieldChangesetPayload { +pub struct FieldChangesetPayloadPB { #[pb(index = 1)] pub field_id: String, @@ -417,7 +405,7 @@ pub struct FieldChangesetPayload { pub type_option_data: Option>, } -impl TryInto for FieldChangesetPayload { +impl TryInto for FieldChangesetPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -458,6 +446,8 @@ impl TryInto for FieldChangesetPayload { Serialize_repr, Deserialize_repr, )] +/// The order of the enum can't be changed. If you want to add a new type, +/// it would be better to append it to the end of the list. #[repr(u8)] pub enum FieldType { RichText = 0, @@ -567,7 +557,7 @@ impl std::convert::From for FieldType { } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct FieldIdentifierPayload { +pub struct GridFieldIdentifierPayloadPB { #[pb(index = 1)] pub field_id: String, @@ -575,18 +565,18 @@ pub struct FieldIdentifierPayload { pub grid_id: String, } -pub struct FieldIdentifier { +pub struct FieldIdentifierParams { pub field_id: String, pub grid_id: String, } -impl TryInto for FieldIdentifierPayload { +impl TryInto for GridFieldIdentifierPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(FieldIdentifier { + Ok(FieldIdentifierParams { grid_id: grid_id.0, field_id: field_id.0, }) diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs deleted file mode 100644 index 747c7787a8..0000000000 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities.rs +++ /dev/null @@ -1,393 +0,0 @@ -use crate::entities::FieldType; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision}; -use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams}; -use std::convert::TryInto; -use std::sync::Arc; - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridFilter { - #[pb(index = 1)] - pub id: String, -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct RepeatedGridFilter { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::convert::From<&Arc> for GridFilter { - fn from(rev: &Arc) -> Self { - Self { id: rev.id.clone() } - } -} - -impl std::convert::From<&Vec>> for RepeatedGridFilter { - fn from(revs: &Vec>) -> Self { - RepeatedGridFilter { - items: revs.iter().map(|rev| rev.into()).collect(), - } - } -} - -impl std::convert::From> for RepeatedGridFilter { - fn from(items: Vec) -> Self { - Self { items } - } -} - -#[derive(ProtoBuf, Debug, Default, Clone)] -pub struct DeleteFilterPayload { - #[pb(index = 1)] - pub filter_id: String, - - #[pb(index = 2)] - pub field_type: FieldType, -} - -impl TryInto for DeleteFilterPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let filter_id = NotEmptyStr::parse(self.filter_id) - .map_err(|_| ErrorCode::UnexpectedEmptyString)? - .0; - Ok(DeleteFilterParams { - filter_id, - field_type_rev: self.field_type.into(), - }) - } -} - -#[derive(ProtoBuf, Debug, Default, Clone)] -pub struct CreateGridFilterPayload { - #[pb(index = 1)] - pub field_id: String, - - #[pb(index = 2)] - pub field_type: FieldType, - - #[pb(index = 3)] - pub condition: i32, - - #[pb(index = 4, one_of)] - pub content: Option, -} - -impl CreateGridFilterPayload { - #[allow(dead_code)] - pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self { - Self { - field_id: field_rev.id.clone(), - field_type: field_rev.field_type_rev.into(), - condition: condition.into(), - content, - } - } -} - -impl TryInto for CreateGridFilterPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - let condition = self.condition as u8; - match self.field_type { - FieldType::RichText | FieldType::URL => { - let _ = TextFilterCondition::try_from(condition)?; - } - FieldType::Checkbox => { - let _ = CheckboxCondition::try_from(condition)?; - } - FieldType::Number => { - let _ = NumberFilterCondition::try_from(condition)?; - } - FieldType::DateTime => { - let _ = DateFilterCondition::try_from(condition)?; - } - FieldType::SingleSelect | FieldType::MultiSelect => { - let _ = SelectOptionCondition::try_from(condition)?; - } - } - - Ok(CreateGridFilterParams { - field_id, - field_type_rev: self.field_type.into(), - condition, - content: self.content, - }) - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridTextFilter { - #[pb(index = 1)] - pub condition: TextFilterCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum TextFilterCondition { - Is = 0, - IsNot = 1, - Contains = 2, - DoesNotContain = 3, - StartsWith = 4, - EndsWith = 5, - TextIsEmpty = 6, - TextIsNotEmpty = 7, -} -impl std::convert::From for i32 { - fn from(value: TextFilterCondition) -> Self { - value as i32 - } -} - -impl std::default::Default for TextFilterCondition { - fn default() -> Self { - TextFilterCondition::Is - } -} -impl std::convert::TryFrom for TextFilterCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(TextFilterCondition::Is), - 1 => Ok(TextFilterCondition::IsNot), - 2 => Ok(TextFilterCondition::Contains), - 3 => Ok(TextFilterCondition::DoesNotContain), - 4 => Ok(TextFilterCondition::StartsWith), - 5 => Ok(TextFilterCondition::EndsWith), - 6 => Ok(TextFilterCondition::TextIsEmpty), - 7 => Ok(TextFilterCondition::TextIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridTextFilter { - fn from(rev: Arc) -> Self { - GridTextFilter { - condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridNumberFilter { - #[pb(index = 1)] - pub condition: NumberFilterCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum NumberFilterCondition { - Equal = 0, - NotEqual = 1, - GreaterThan = 2, - LessThan = 3, - GreaterThanOrEqualTo = 4, - LessThanOrEqualTo = 5, - NumberIsEmpty = 6, - NumberIsNotEmpty = 7, -} -impl std::default::Default for NumberFilterCondition { - fn default() -> Self { - NumberFilterCondition::Equal - } -} - -impl std::convert::From for i32 { - fn from(value: NumberFilterCondition) -> Self { - value as i32 - } -} -impl std::convert::TryFrom for NumberFilterCondition { - type Error = ErrorCode; - - fn try_from(n: u8) -> Result { - match n { - 0 => Ok(NumberFilterCondition::Equal), - 1 => Ok(NumberFilterCondition::NotEqual), - 2 => Ok(NumberFilterCondition::GreaterThan), - 3 => Ok(NumberFilterCondition::LessThan), - 4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo), - 5 => Ok(NumberFilterCondition::LessThanOrEqualTo), - 6 => Ok(NumberFilterCondition::NumberIsEmpty), - 7 => Ok(NumberFilterCondition::NumberIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridNumberFilter { - fn from(rev: Arc) -> Self { - GridNumberFilter { - condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridSelectOptionFilter { - #[pb(index = 1)] - pub condition: SelectOptionCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum SelectOptionCondition { - OptionIs = 0, - OptionIsNot = 1, - OptionIsEmpty = 2, - OptionIsNotEmpty = 3, -} - -impl std::convert::From for i32 { - fn from(value: SelectOptionCondition) -> Self { - value as i32 - } -} - -impl std::default::Default for SelectOptionCondition { - fn default() -> Self { - SelectOptionCondition::OptionIs - } -} - -impl std::convert::TryFrom for SelectOptionCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(SelectOptionCondition::OptionIs), - 1 => Ok(SelectOptionCondition::OptionIsNot), - 2 => Ok(SelectOptionCondition::OptionIsEmpty), - 3 => Ok(SelectOptionCondition::OptionIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridSelectOptionFilter { - fn from(rev: Arc) -> Self { - GridSelectOptionFilter { - condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridDateFilter { - #[pb(index = 1)] - pub condition: DateFilterCondition, - - #[pb(index = 2, one_of)] - pub content: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum DateFilterCondition { - DateIs = 0, - DateBefore = 1, - DateAfter = 2, - DateOnOrBefore = 3, - DateOnOrAfter = 4, - DateWithIn = 5, - DateIsEmpty = 6, -} - -impl std::default::Default for DateFilterCondition { - fn default() -> Self { - DateFilterCondition::DateIs - } -} - -impl std::convert::TryFrom for DateFilterCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(DateFilterCondition::DateIs), - 1 => Ok(DateFilterCondition::DateBefore), - 2 => Ok(DateFilterCondition::DateAfter), - 3 => Ok(DateFilterCondition::DateOnOrBefore), - 4 => Ok(DateFilterCondition::DateOnOrAfter), - 5 => Ok(DateFilterCondition::DateWithIn), - 6 => Ok(DateFilterCondition::DateIsEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} -impl std::convert::From> for GridDateFilter { - fn from(rev: Arc) -> Self { - GridDateFilter { - condition: DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs), - content: rev.content.clone(), - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridCheckboxFilter { - #[pb(index = 1)] - pub condition: CheckboxCondition, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum CheckboxCondition { - IsChecked = 0, - IsUnChecked = 1, -} - -impl std::convert::From for i32 { - fn from(value: CheckboxCondition) -> Self { - value as i32 - } -} - -impl std::default::Default for CheckboxCondition { - fn default() -> Self { - CheckboxCondition::IsChecked - } -} - -impl std::convert::TryFrom for CheckboxCondition { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(CheckboxCondition::IsChecked), - 1 => Ok(CheckboxCondition::IsUnChecked), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl std::convert::From> for GridCheckboxFilter { - fn from(rev: Arc) -> Self { - GridCheckboxFilter { - condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked), - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs new file mode 100644 index 0000000000..bb31b7a3be --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs @@ -0,0 +1,49 @@ +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridCheckboxFilter { + #[pb(index = 1)] + pub condition: CheckboxCondition, +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum CheckboxCondition { + IsChecked = 0, + IsUnChecked = 1, +} + +impl std::convert::From for i32 { + fn from(value: CheckboxCondition) -> Self { + value as i32 + } +} + +impl std::default::Default for CheckboxCondition { + fn default() -> Self { + CheckboxCondition::IsChecked + } +} + +impl std::convert::TryFrom for CheckboxCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(CheckboxCondition::IsChecked), + 1 => Ok(CheckboxCondition::IsUnChecked), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridCheckboxFilter { + fn from(rev: Arc) -> Self { + GridCheckboxFilter { + condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs new file mode 100644 index 0000000000..936b95216c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs @@ -0,0 +1,142 @@ +use crate::entities::FieldType; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::parser::NotEmptyStr; +use flowy_grid_data_model::revision::GridFilterRevision; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridDateFilter { + #[pb(index = 1)] + pub condition: DateFilterCondition, + + #[pb(index = 2, one_of)] + pub start: Option, + + #[pb(index = 3, one_of)] + pub end: Option, +} + +#[derive(ProtoBuf, Default, Clone, Debug)] +pub struct CreateGridDateFilterPayload { + #[pb(index = 1)] + pub field_id: String, + + #[pb(index = 2)] + pub field_type: FieldType, + + #[pb(index = 3)] + pub condition: DateFilterCondition, + + #[pb(index = 4, one_of)] + pub start: Option, + + #[pb(index = 5, one_of)] + pub end: Option, +} + +pub struct CreateGridDateFilterParams { + pub field_id: String, + + pub field_type: FieldType, + + pub condition: DateFilterCondition, + + pub start: Option, + + pub end: Option, +} + +impl TryInto for CreateGridDateFilterPayload { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + Ok(CreateGridDateFilterParams { + field_id, + condition: self.condition, + start: self.start, + field_type: self.field_type, + end: self.end, + }) + } +} + +#[derive(Serialize, Deserialize, Default)] +struct DateRange { + start: Option, + end: Option, +} + +impl ToString for DateRange { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) + } +} + +impl FromStr for DateRange { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum DateFilterCondition { + DateIs = 0, + DateBefore = 1, + DateAfter = 2, + DateOnOrBefore = 3, + DateOnOrAfter = 4, + DateWithIn = 5, + DateIsEmpty = 6, +} + +impl std::default::Default for DateFilterCondition { + fn default() -> Self { + DateFilterCondition::DateIs + } +} + +impl std::convert::TryFrom for DateFilterCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(DateFilterCondition::DateIs), + 1 => Ok(DateFilterCondition::DateBefore), + 2 => Ok(DateFilterCondition::DateAfter), + 3 => Ok(DateFilterCondition::DateOnOrBefore), + 4 => Ok(DateFilterCondition::DateOnOrAfter), + 5 => Ok(DateFilterCondition::DateWithIn), + 6 => Ok(DateFilterCondition::DateIsEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} +impl std::convert::From> for GridDateFilter { + fn from(rev: Arc) -> Self { + let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs); + let mut filter = GridDateFilter { + condition, + ..Default::default() + }; + + if let Some(range) = rev + .content + .as_ref() + .and_then(|content| DateRange::from_str(content).ok()) + { + filter.start = range.start; + filter.end = range.end; + }; + + filter + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs new file mode 100644 index 0000000000..bb033f5600 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs @@ -0,0 +1,13 @@ +mod checkbox_filter; +mod date_filter; +mod number_filter; +mod select_option_filter; +mod text_filter; +mod util; + +pub use checkbox_filter::*; +pub use date_filter::*; +pub use number_filter::*; +pub use select_option_filter::*; +pub use text_filter::*; +pub use util::*; diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs new file mode 100644 index 0000000000..097ff5330a --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs @@ -0,0 +1,65 @@ +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; + +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridNumberFilter { + #[pb(index = 1)] + pub condition: NumberFilterCondition, + + #[pb(index = 2, one_of)] + pub content: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum NumberFilterCondition { + Equal = 0, + NotEqual = 1, + GreaterThan = 2, + LessThan = 3, + GreaterThanOrEqualTo = 4, + LessThanOrEqualTo = 5, + NumberIsEmpty = 6, + NumberIsNotEmpty = 7, +} + +impl std::default::Default for NumberFilterCondition { + fn default() -> Self { + NumberFilterCondition::Equal + } +} + +impl std::convert::From for i32 { + fn from(value: NumberFilterCondition) -> Self { + value as i32 + } +} +impl std::convert::TryFrom for NumberFilterCondition { + type Error = ErrorCode; + + fn try_from(n: u8) -> Result { + match n { + 0 => Ok(NumberFilterCondition::Equal), + 1 => Ok(NumberFilterCondition::NotEqual), + 2 => Ok(NumberFilterCondition::GreaterThan), + 3 => Ok(NumberFilterCondition::LessThan), + 4 => Ok(NumberFilterCondition::GreaterThanOrEqualTo), + 5 => Ok(NumberFilterCondition::LessThanOrEqualTo), + 6 => Ok(NumberFilterCondition::NumberIsEmpty), + 7 => Ok(NumberFilterCondition::NumberIsNotEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridNumberFilter { + fn from(rev: Arc) -> Self { + GridNumberFilter { + condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal), + content: rev.content.clone(), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs new file mode 100644 index 0000000000..9eb4ff3fe9 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs @@ -0,0 +1,58 @@ +use crate::services::field::SelectOptionIds; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridSelectOptionFilter { + #[pb(index = 1)] + pub condition: SelectOptionCondition, + + #[pb(index = 2)] + pub option_ids: Vec, +} +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum SelectOptionCondition { + OptionIs = 0, + OptionIsNot = 1, + OptionIsEmpty = 2, + OptionIsNotEmpty = 3, +} + +impl std::convert::From for i32 { + fn from(value: SelectOptionCondition) -> Self { + value as i32 + } +} + +impl std::default::Default for SelectOptionCondition { + fn default() -> Self { + SelectOptionCondition::OptionIs + } +} + +impl std::convert::TryFrom for SelectOptionCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(SelectOptionCondition::OptionIs), + 1 => Ok(SelectOptionCondition::OptionIsNot), + 2 => Ok(SelectOptionCondition::OptionIsEmpty), + 3 => Ok(SelectOptionCondition::OptionIsNotEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridSelectOptionFilter { + fn from(rev: Arc) -> Self { + let ids = SelectOptionIds::from(rev.content.clone()); + GridSelectOptionFilter { + condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs), + option_ids: ids.into_inner(), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs new file mode 100644 index 0000000000..7335e89129 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs @@ -0,0 +1,64 @@ +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::ErrorCode; +use flowy_grid_data_model::revision::GridFilterRevision; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridTextFilter { + #[pb(index = 1)] + pub condition: TextFilterCondition, + + #[pb(index = 2, one_of)] + pub content: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[repr(u8)] +pub enum TextFilterCondition { + Is = 0, + IsNot = 1, + Contains = 2, + DoesNotContain = 3, + StartsWith = 4, + EndsWith = 5, + TextIsEmpty = 6, + TextIsNotEmpty = 7, +} + +impl std::convert::From for i32 { + fn from(value: TextFilterCondition) -> Self { + value as i32 + } +} + +impl std::default::Default for TextFilterCondition { + fn default() -> Self { + TextFilterCondition::Is + } +} +impl std::convert::TryFrom for TextFilterCondition { + type Error = ErrorCode; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(TextFilterCondition::Is), + 1 => Ok(TextFilterCondition::IsNot), + 2 => Ok(TextFilterCondition::Contains), + 3 => Ok(TextFilterCondition::DoesNotContain), + 4 => Ok(TextFilterCondition::StartsWith), + 5 => Ok(TextFilterCondition::EndsWith), + 6 => Ok(TextFilterCondition::TextIsEmpty), + 7 => Ok(TextFilterCondition::TextIsNotEmpty), + _ => Err(ErrorCode::InvalidData), + } + } +} + +impl std::convert::From> for GridTextFilter { + fn from(rev: Arc) -> Self { + GridTextFilter { + condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is), + content: rev.content.clone(), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs new file mode 100644 index 0000000000..03cc3d6111 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs @@ -0,0 +1,135 @@ +use crate::entities::{ + CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition, + TextFilterCondition, +}; +use flowy_derive::ProtoBuf; +use flowy_error::ErrorCode; +use flowy_grid_data_model::parser::NotEmptyStr; +use flowy_grid_data_model::revision::{FieldRevision, GridFilterRevision}; +use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams}; +use std::convert::TryInto; +use std::sync::Arc; + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridFilter { + #[pb(index = 1)] + pub id: String, +} + +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct RepeatedGridFilterPB { + #[pb(index = 1)] + pub items: Vec, +} + +impl std::convert::From<&GridFilterRevision> for GridFilter { + fn from(rev: &GridFilterRevision) -> Self { + Self { id: rev.id.clone() } + } +} + +impl std::convert::From>> for RepeatedGridFilterPB { + fn from(revs: Vec>) -> Self { + RepeatedGridFilterPB { + items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), + } + } +} + +impl std::convert::From> for RepeatedGridFilterPB { + fn from(items: Vec) -> Self { + Self { items } + } +} + +#[derive(ProtoBuf, Debug, Default, Clone)] +pub struct DeleteFilterPayloadPB { + #[pb(index = 1)] + pub field_id: String, + + #[pb(index = 2)] + pub filter_id: String, + + #[pb(index = 3)] + pub field_type: FieldType, +} + +impl TryInto for DeleteFilterPayloadPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + let filter_id = NotEmptyStr::parse(self.filter_id) + .map_err(|_| ErrorCode::UnexpectedEmptyString)? + .0; + Ok(DeleteFilterParams { + field_id, + filter_id, + field_type_rev: self.field_type.into(), + }) + } +} + +#[derive(ProtoBuf, Debug, Default, Clone)] +pub struct CreateGridFilterPayloadPB { + #[pb(index = 1)] + pub field_id: String, + + #[pb(index = 2)] + pub field_type: FieldType, + + #[pb(index = 3)] + pub condition: i32, + + #[pb(index = 4, one_of)] + pub content: Option, +} + +impl CreateGridFilterPayloadPB { + #[allow(dead_code)] + pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self { + Self { + field_id: field_rev.id.clone(), + field_type: field_rev.field_type_rev.into(), + condition: condition.into(), + content, + } + } +} + +impl TryInto for CreateGridFilterPayloadPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + let condition = self.condition as u8; + match self.field_type { + FieldType::RichText | FieldType::URL => { + let _ = TextFilterCondition::try_from(condition)?; + } + FieldType::Checkbox => { + let _ = CheckboxCondition::try_from(condition)?; + } + FieldType::Number => { + let _ = NumberFilterCondition::try_from(condition)?; + } + FieldType::DateTime => { + let _ = DateFilterCondition::try_from(condition)?; + } + FieldType::SingleSelect | FieldType::MultiSelect => { + let _ = SelectOptionCondition::try_from(condition)?; + } + } + + Ok(CreateGridFilterParams { + field_id, + field_type_rev: self.field_type.into(), + condition, + content: self.content, + }) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs index c05df11414..1be0412503 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs @@ -1,69 +1,69 @@ -use crate::entities::{FieldOrder, GridBlock}; +use crate::entities::{GridBlockPB, GridFieldIdPB}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct Grid { +pub struct GridPB { #[pb(index = 1)] pub id: String, #[pb(index = 2)] - pub field_orders: Vec, + pub fields: Vec, #[pb(index = 3)] - pub blocks: Vec, + pub blocks: Vec, } #[derive(ProtoBuf, Default)] -pub struct CreateGridPayload { +pub struct CreateGridPayloadPB { #[pb(index = 1)] pub name: String, } #[derive(Clone, ProtoBuf, Default, Debug)] -pub struct GridId { +pub struct GridIdPB { #[pb(index = 1)] pub value: String, } -impl AsRef for GridId { +impl AsRef for GridIdPB { fn as_ref(&self) -> &str { &self.value } } #[derive(Clone, ProtoBuf, Default, Debug)] -pub struct GridBlockId { +pub struct GridBlockIdPB { #[pb(index = 1)] pub value: String, } -impl AsRef for GridBlockId { +impl AsRef for GridBlockIdPB { fn as_ref(&self) -> &str { &self.value } } -impl std::convert::From<&str> for GridBlockId { +impl std::convert::From<&str> for GridBlockIdPB { fn from(s: &str) -> Self { - GridBlockId { value: s.to_owned() } + GridBlockIdPB { value: s.to_owned() } } } #[derive(Debug, Clone, ProtoBuf_Enum)] -pub enum MoveItemType { +pub enum MoveItemTypePB { MoveField = 0, MoveRow = 1, } -impl std::default::Default for MoveItemType { +impl std::default::Default for MoveItemTypePB { fn default() -> Self { - MoveItemType::MoveField + MoveItemTypePB::MoveField } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct MoveItemPayload { +pub struct MoveItemPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -77,7 +77,7 @@ pub struct MoveItemPayload { pub to_index: i32, #[pb(index = 5)] - pub ty: MoveItemType, + pub ty: MoveItemTypePB, } #[derive(Clone)] @@ -86,10 +86,10 @@ pub struct MoveItemParams { pub item_id: String, pub from_index: i32, pub to_index: i32, - pub ty: MoveItemType, + pub ty: MoveItemTypePB, } -impl TryInto for MoveItemPayload { +impl TryInto for MoveItemPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs index dc090e6ea1..dda624fc67 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities.rs @@ -4,9 +4,10 @@ use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::revision::GridGroupRevision; use flowy_sync::entities::grid::CreateGridGroupParams; use std::convert::TryInto; +use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridGroup { +pub struct GridGroupPB { #[pb(index = 1)] pub id: String, @@ -17,9 +18,9 @@ pub struct GridGroup { pub sub_group_field_id: Option, } -impl std::convert::From<&GridGroupRevision> for GridGroup { +impl std::convert::From<&GridGroupRevision> for GridGroupPB { fn from(rev: &GridGroupRevision) -> Self { - GridGroup { + GridGroupPB { id: rev.id.clone(), group_field_id: rev.field_id.clone(), sub_group_field_id: rev.sub_field_id.clone(), @@ -28,27 +29,27 @@ impl std::convert::From<&GridGroupRevision> for GridGroup { } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct RepeatedGridGroup { +pub struct RepeatedGridGroupPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } -impl std::convert::From> for RepeatedGridGroup { - fn from(items: Vec) -> Self { +impl std::convert::From> for RepeatedGridGroupPB { + fn from(items: Vec) -> Self { Self { items } } } -impl std::convert::From<&Vec> for RepeatedGridGroup { - fn from(revs: &Vec) -> Self { - RepeatedGridGroup { - items: revs.iter().map(|rev| rev.into()).collect(), +impl std::convert::From>> for RepeatedGridGroupPB { + fn from(revs: Vec>) -> Self { + RepeatedGridGroupPB { + items: revs.iter().map(|rev| rev.as_ref().into()).collect(), } } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct CreateGridGroupPayload { +pub struct CreateGridGroupPayloadPB { #[pb(index = 1, one_of)] pub field_id: Option, @@ -56,7 +57,7 @@ pub struct CreateGridGroupPayload { pub sub_field_id: Option, } -impl TryInto for CreateGridGroupPayload { +impl TryInto for CreateGridGroupPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs index 4b3c8a7a78..f66e1c1d06 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs @@ -3,7 +3,7 @@ use flowy_error::ErrorCode; use flowy_grid_data_model::parser::NotEmptyStr; #[derive(ProtoBuf, Default)] -pub struct GridRowIdPayload { +pub struct GridRowIdPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -15,7 +15,7 @@ pub struct GridRowIdPayload { } #[derive(Debug, Default, Clone, ProtoBuf)] -pub struct GridRowId { +pub struct GridRowIdPB { #[pb(index = 1)] pub grid_id: String, @@ -26,15 +26,15 @@ pub struct GridRowId { pub row_id: String, } -impl TryInto for GridRowIdPayload { +impl TryInto for GridRowIdPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let block_id = NotEmptyStr::parse(self.block_id).map_err(|_| ErrorCode::BlockIdIsEmpty)?; let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - Ok(GridRowId { + Ok(GridRowIdPB { grid_id: grid_id.0, block_id: block_id.0, row_id: row_id.0, @@ -43,7 +43,7 @@ impl TryInto for GridRowIdPayload { } #[derive(Debug, Default, Clone, ProtoBuf)] -pub struct BlockRowId { +pub struct BlockRowIdPB { #[pb(index = 1)] pub block_id: String, @@ -52,7 +52,7 @@ pub struct BlockRowId { } #[derive(ProtoBuf, Default)] -pub struct CreateRowPayload { +pub struct CreateRowPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -66,7 +66,7 @@ pub struct CreateRowParams { pub start_row_id: Option, } -impl TryInto for CreateRowPayload { +impl TryInto for CreateRowPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs index ce2d623b0c..9272cfe46d 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs @@ -1,6 +1,6 @@ use crate::entities::{ - CreateGridFilterPayload, CreateGridGroupPayload, CreateGridSortPayload, DeleteFilterPayload, RepeatedGridFilter, - RepeatedGridGroup, RepeatedGridSort, + CreateGridFilterPayloadPB, CreateGridGroupPayloadPB, CreateGridSortPayloadPB, DeleteFilterPayloadPB, + RepeatedGridFilterPB, RepeatedGridGroupPB, RepeatedGridSortPB, }; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; @@ -9,49 +9,45 @@ use flowy_grid_data_model::revision::GridLayoutRevision; use flowy_sync::entities::grid::GridSettingChangesetParams; use std::collections::HashMap; use std::convert::TryInto; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridSetting { +pub struct GridSettingPB { #[pb(index = 1)] - pub filters_by_layout_ty: HashMap, + pub layouts: Vec, #[pb(index = 2)] - pub groups_by_layout_ty: HashMap, + pub current_layout_type: GridLayoutType, #[pb(index = 3)] - pub sorts_by_layout_ty: HashMap, + pub filters_by_field_id: HashMap, + + #[pb(index = 4)] + pub groups_by_field_id: HashMap, + + #[pb(index = 5)] + pub sorts_by_field_id: HashMap, } -// -// impl std::convert::From<&GridSettingRevision> for GridSetting { -// fn from(rev: &GridSettingRevision) -> Self { -// let filters_by_layout_ty: HashMap = rev -// .filters -// .iter() -// .map(|(layout_rev, filter_revs)| (layout_rev.to_string(), filter_revs.into())) -// .collect(); -// -// let groups_by_layout_ty: HashMap = rev -// .groups -// .iter() -// .map(|(layout_rev, group_revs)| (layout_rev.to_string(), group_revs.into())) -// .collect(); -// -// let sorts_by_layout_ty: HashMap = rev -// .sorts -// .iter() -// .map(|(layout_rev, sort_revs)| (layout_rev.to_string(), sort_revs.into())) -// .collect(); -// -// GridSetting { -// filters_by_layout_ty, -// groups_by_layout_ty, -// sorts_by_layout_ty, -// } -// } -// } -// -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct GridLayoutPB { + #[pb(index = 1)] + ty: GridLayoutType, +} + +impl GridLayoutPB { + pub fn all() -> Vec { + let mut layouts = vec![]; + for layout_ty in GridLayoutType::iter() { + layouts.push(GridLayoutPB { ty: layout_ty }) + } + + layouts + } +} + +#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumIter)] #[repr(u8)] pub enum GridLayoutType { Table = 0, @@ -83,7 +79,7 @@ impl std::convert::From for GridLayoutRevision { } #[derive(Default, ProtoBuf)] -pub struct GridSettingChangesetPayload { +pub struct GridSettingChangesetPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -91,25 +87,25 @@ pub struct GridSettingChangesetPayload { pub layout_type: GridLayoutType, #[pb(index = 3, one_of)] - pub insert_filter: Option, + pub insert_filter: Option, #[pb(index = 4, one_of)] - pub delete_filter: Option, + pub delete_filter: Option, #[pb(index = 5, one_of)] - pub insert_group: Option, + pub insert_group: Option, #[pb(index = 6, one_of)] pub delete_group: Option, #[pb(index = 7, one_of)] - pub insert_sort: Option, + pub insert_sort: Option, #[pb(index = 8, one_of)] pub delete_sort: Option, } -impl TryInto for GridSettingChangesetPayload { +impl TryInto for GridSettingChangesetPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs index 1f03808fa8..b630b000c5 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/sort_entities.rs @@ -4,6 +4,7 @@ use flowy_grid_data_model::parser::NotEmptyStr; use flowy_grid_data_model::revision::GridSortRevision; use flowy_sync::entities::grid::CreateGridSortParams; use std::convert::TryInto; +use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct GridSort { @@ -25,32 +26,32 @@ impl std::convert::From<&GridSortRevision> for GridSort { } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct RepeatedGridSort { +pub struct RepeatedGridSortPB { #[pb(index = 1)] pub items: Vec, } -impl std::convert::From<&Vec> for RepeatedGridSort { - fn from(revs: &Vec) -> Self { - RepeatedGridSort { - items: revs.iter().map(|rev| rev.into()).collect(), +impl std::convert::From>> for RepeatedGridSortPB { + fn from(revs: Vec>) -> Self { + RepeatedGridSortPB { + items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } } } -impl std::convert::From> for RepeatedGridSort { +impl std::convert::From> for RepeatedGridSortPB { fn from(items: Vec) -> Self { Self { items } } } #[derive(ProtoBuf, Debug, Default, Clone)] -pub struct CreateGridSortPayload { +pub struct CreateGridSortPayloadPB { #[pb(index = 1, one_of)] pub field_id: Option, } -impl TryInto for CreateGridSortPayload { +impl TryInto for CreateGridSortPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index cbe43c8f82..4c4bfe5559 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -1,7 +1,13 @@ use crate::entities::*; use crate::manager::GridManager; -use crate::services::field::type_options::*; -use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_json_str}; +use crate::services::cell::AnyCellData; +use crate::services::field::{ + default_type_option_builder_from_type, select_option_operation, type_option_builder_from_json_str, + DateChangesetParams, DateChangesetPayloadPB, SelectOptionCellChangeset, SelectOptionCellChangesetParams, + SelectOptionCellChangesetPayloadPB, SelectOptionCellDataPB, SelectOptionChangeset, SelectOptionChangesetPayloadPB, + SelectOptionPB, +}; +use crate::services::row::make_row_from_row_rev; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::revision::FieldRevision; use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams}; @@ -10,10 +16,10 @@ use std::sync::Arc; #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_grid_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let grid_id: GridId = data.into_inner(); +) -> DataResult { + let grid_id: GridIdPB = data.into_inner(); let editor = manager.open_grid(grid_id).await?; let grid = editor.get_grid_data().await?; data_result(grid) @@ -21,10 +27,10 @@ pub(crate) async fn get_grid_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_grid_setting_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let grid_id: GridId = data.into_inner(); +) -> DataResult { + let grid_id: GridIdPB = data.into_inner(); let editor = manager.open_grid(grid_id).await?; let grid_setting = editor.get_grid_setting().await?; data_result(grid_setting) @@ -32,7 +38,7 @@ pub(crate) async fn get_grid_setting_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_grid_setting_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: GridSettingChangesetParams = data.into_inner().try_into()?; @@ -43,9 +49,9 @@ pub(crate) async fn update_grid_setting_handler( #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_grid_blocks_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: QueryGridBlocksParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; let repeated_grid_block = editor.get_blocks(Some(params.block_ids)).await?; @@ -54,25 +60,25 @@ pub(crate) async fn get_grid_blocks_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_fields_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: QueryFieldParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; let field_orders = params - .field_orders + .field_ids .items .into_iter() .map(|field_order| field_order.field_id) .collect(); let field_revs = editor.get_field_revs(Some(field_orders)).await?; - let repeated_field: RepeatedField = field_revs.into_iter().map(Field::from).collect::>().into(); + let repeated_field: RepeatedGridFieldPB = field_revs.into_iter().map(GridFieldPB::from).collect::>().into(); data_result(repeated_field) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let changeset: FieldChangesetParams = data.into_inner().try_into()?; @@ -83,7 +89,7 @@ pub(crate) async fn update_field_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn insert_field_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: InsertFieldParams = data.into_inner().try_into()?; @@ -94,7 +100,7 @@ pub(crate) async fn insert_field_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_type_option_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: UpdateFieldTypeOptionParams = data.into_inner().try_into()?; @@ -107,10 +113,10 @@ pub(crate) async fn update_field_type_option_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn delete_field_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { - let params: FieldIdentifier = data.into_inner().try_into()?; + let params: FieldIdentifierParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; let _ = editor.delete_field(¶ms.field_id).await?; Ok(()) @@ -118,9 +124,9 @@ pub(crate) async fn delete_field_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn switch_to_field_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: EditFieldParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; editor @@ -134,7 +140,7 @@ pub(crate) async fn switch_to_field_handler( .unwrap_or(Arc::new(editor.next_field_rev(¶ms.field_type).await?)); let type_option_data = get_type_option_data(&field_rev, ¶ms.field_type).await?; - let data = FieldTypeOptionData { + let data = FieldTypeOptionDataPB { grid_id: params.grid_id, field: field_rev.into(), type_option_data, @@ -145,10 +151,10 @@ pub(crate) async fn switch_to_field_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn duplicate_field_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { - let params: FieldIdentifier = data.into_inner().try_into()?; + let params: FieldIdentifierParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; let _ = editor.duplicate_field(¶ms.field_id).await?; Ok(()) @@ -157,9 +163,9 @@ pub(crate) async fn duplicate_field_handler( /// Return the FieldTypeOptionData if the Field exists otherwise return record not found error. #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_field_type_option_data_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: EditFieldParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_field_rev(¶ms.field_id).await { @@ -167,7 +173,7 @@ pub(crate) async fn get_field_type_option_data_handler( Some(field_rev) => { let field_type = field_rev.field_type_rev.into(); let type_option_data = get_type_option_data(&field_rev, &field_type).await?; - let data = FieldTypeOptionData { + let data = FieldTypeOptionDataPB { grid_id: params.grid_id, field: field_rev.into(), type_option_data, @@ -180,16 +186,16 @@ pub(crate) async fn get_field_type_option_data_handler( /// Create FieldMeta and save it. Return the FieldTypeOptionData. #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn create_field_type_option_data_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: CreateFieldParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; let field_rev = editor.create_next_field_rev(¶ms.field_type).await?; let field_type: FieldType = field_rev.field_type_rev.into(); let type_option_data = get_type_option_data(&field_rev, &field_type).await?; - data_result(FieldTypeOptionData { + data_result(FieldTypeOptionDataPB { grid_id: params.grid_id, field: field_rev.into(), type_option_data, @@ -198,7 +204,7 @@ pub(crate) async fn create_field_type_option_data_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn move_item_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: MoveItemParams = data.into_inner().try_into()?; @@ -221,23 +227,25 @@ async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType) #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_row_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let params: GridRowId = data.into_inner().try_into()?; +) -> DataResult { + let params: GridRowIdPB = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; - let row = OptionalRow { - row: editor.get_row(¶ms.row_id).await?, - }; - data_result(row) + let row = editor + .get_row_rev(¶ms.row_id) + .await? + .and_then(make_row_from_row_rev); + + data_result(OptionalRowPB { row }) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn delete_row_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { - let params: GridRowId = data.into_inner().try_into()?; + let params: GridRowIdPB = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; let _ = editor.delete_row(¶ms.row_id).await?; Ok(()) @@ -245,10 +253,10 @@ pub(crate) async fn delete_row_handler( #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn duplicate_row_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { - let params: GridRowId = data.into_inner().try_into()?; + let params: GridRowIdPB = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; let _ = editor.duplicate_row(¶ms.row_id).await?; Ok(()) @@ -256,7 +264,7 @@ pub(crate) async fn duplicate_row_handler( #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn create_row_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: CreateRowParams = data.into_inner().try_into()?; @@ -267,23 +275,23 @@ pub(crate) async fn create_row_handler( // #[tracing::instrument(level = "debug", skip_all, err)] pub(crate) async fn get_cell_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let params: CellIdentifier = data.into_inner().try_into()?; +) -> DataResult { + let params: CellIdentifierParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_cell(¶ms).await { - None => data_result(Cell::empty(¶ms.field_id)), + None => data_result(GridCellPB::empty(¶ms.field_id)), Some(cell) => data_result(cell), } } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_cell_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { - let changeset: CellChangeset = data.into_inner(); + let changeset: CellChangesetPB = data.into_inner(); let editor = manager.get_grid_editor(&changeset.grid_id)?; let _ = editor.update_cell(changeset).await?; Ok(()) @@ -291,9 +299,9 @@ pub(crate) async fn update_cell_handler( #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn new_select_option_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: CreateSelectOptionParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_field_rev(¶ms.field_id).await { @@ -308,7 +316,7 @@ pub(crate) async fn new_select_option_handler( #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let changeset: SelectOptionChangeset = data.into_inner().try_into()?; @@ -320,7 +328,7 @@ pub(crate) async fn update_select_option_handler( let mut cell_content_changeset = None; if let Some(option) = changeset.insert_option { - cell_content_changeset = Some(SelectOptionCellContentChangeset::from_insert(&option.id).to_str()); + cell_content_changeset = Some(SelectOptionCellChangeset::from_insert(&option.id).to_str()); type_option.insert_option(option); } @@ -329,18 +337,18 @@ pub(crate) async fn update_select_option_handler( } if let Some(option) = changeset.delete_option { - cell_content_changeset = Some(SelectOptionCellContentChangeset::from_delete(&option.id).to_str()); + cell_content_changeset = Some(SelectOptionCellChangeset::from_delete(&option.id).to_str()); type_option.delete_option(option); } mut_field_rev.insert_type_option_entry(&*type_option); let _ = editor.replace_field(field_rev).await?; - let changeset = CellChangeset { + let changeset = CellChangesetPB { grid_id: changeset.cell_identifier.grid_id, row_id: changeset.cell_identifier.row_id, field_id: changeset.cell_identifier.field_id, - cell_content_changeset, + content: cell_content_changeset, }; let _ = editor.update_cell(changeset).await?; } @@ -349,20 +357,28 @@ pub(crate) async fn update_select_option_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_select_option_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let params: CellIdentifier = data.into_inner().try_into()?; +) -> DataResult { + let params: CellIdentifierParams = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; match editor.get_field_rev(¶ms.field_id).await { None => { tracing::error!("Can't find the select option field with id: {}", params.field_id); - data_result(SelectOptionCellData::default()) + data_result(SelectOptionCellDataPB::default()) } Some(field_rev) => { + // let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?; let type_option = select_option_operation(&field_rev)?; - let option_context = type_option.select_option_cell_data(&cell_rev); + let any_cell_data: AnyCellData = match cell_rev { + None => AnyCellData { + data: "".to_string(), + field_type: field_rev.field_type_rev.into(), + }, + Some(cell_rev) => cell_rev.try_into()?, + }; + let option_context = type_option.selected_select_option(any_cell_data.into()); data_result(option_context) } } @@ -370,7 +386,7 @@ pub(crate) async fn get_select_option_handler( #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_cell_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?; @@ -381,7 +397,7 @@ pub(crate) async fn update_select_option_cell_handler( #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_date_cell_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: DateChangesetParams = data.into_inner().try_into()?; diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index 8739be5257..5abda48c1b 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -45,78 +45,78 @@ pub fn create(grid_manager: Arc) -> Module { #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum GridEvent { - #[event(input = "GridId", output = "Grid")] + #[event(input = "GridIdPB", output = "GridPB")] GetGrid = 0, - #[event(input = "QueryGridBlocksPayload", output = "RepeatedGridBlock")] + #[event(input = "QueryGridBlocksPayloadPB", output = "RepeatedGridBlockPB")] GetGridBlocks = 1, - #[event(input = "GridId", output = "GridSetting")] + #[event(input = "GridIdPB", output = "GridSettingPB")] GetGridSetting = 2, - #[event(input = "GridId", input = "GridSettingChangesetPayload")] + #[event(input = "GridIdPB", input = "GridSettingChangesetPayloadPB")] UpdateGridSetting = 3, - #[event(input = "QueryFieldPayload", output = "RepeatedField")] + #[event(input = "QueryFieldPayloadPB", output = "RepeatedGridFieldPB")] GetFields = 10, - #[event(input = "FieldChangesetPayload")] + #[event(input = "FieldChangesetPayloadPB")] UpdateField = 11, - #[event(input = "UpdateFieldTypeOptionPayload")] + #[event(input = "UpdateFieldTypeOptionPayloadPB")] UpdateFieldTypeOption = 12, - #[event(input = "InsertFieldPayload")] + #[event(input = "InsertFieldPayloadPB")] InsertField = 13, - #[event(input = "FieldIdentifierPayload")] + #[event(input = "GridFieldIdentifierPayloadPB")] DeleteField = 14, - #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")] + #[event(input = "EditFieldPayloadPB", output = "FieldTypeOptionDataPB")] SwitchToField = 20, - #[event(input = "FieldIdentifierPayload")] + #[event(input = "GridFieldIdentifierPayloadPB")] DuplicateField = 21, - #[event(input = "MoveItemPayload")] + #[event(input = "MoveItemPayloadPB")] MoveItem = 22, - #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")] + #[event(input = "EditFieldPayloadPB", output = "FieldTypeOptionDataPB")] GetFieldTypeOption = 23, - #[event(input = "EditFieldPayload", output = "FieldTypeOptionData")] + #[event(input = "EditFieldPayloadPB", output = "FieldTypeOptionDataPB")] CreateFieldTypeOption = 24, - #[event(input = "CreateSelectOptionPayload", output = "SelectOption")] + #[event(input = "CreateSelectOptionPayloadPB", output = "SelectOptionPB")] NewSelectOption = 30, - #[event(input = "CellIdentifierPayload", output = "SelectOptionCellData")] + #[event(input = "GridCellIdentifierPayloadPB", output = "SelectOptionCellDataPB")] GetSelectOptionCellData = 31, - #[event(input = "SelectOptionChangesetPayload")] + #[event(input = "SelectOptionChangesetPayloadPB")] UpdateSelectOption = 32, - #[event(input = "CreateRowPayload", output = "Row")] + #[event(input = "CreateRowPayloadPB", output = "GridRowPB")] CreateRow = 50, - #[event(input = "GridRowIdPayload", output = "OptionalRow")] + #[event(input = "GridRowIdPayloadPB", output = "OptionalRowPB")] GetRow = 51, - #[event(input = "GridRowIdPayload")] + #[event(input = "GridRowIdPayloadPB")] DeleteRow = 52, - #[event(input = "GridRowIdPayload")] + #[event(input = "GridRowIdPayloadPB")] DuplicateRow = 53, - #[event(input = "CellIdentifierPayload", output = "Cell")] + #[event(input = "GridCellIdentifierPayloadPB", output = "GridCellPB")] GetCell = 70, - #[event(input = "CellChangeset")] + #[event(input = "CellChangesetPB")] UpdateCell = 71, - #[event(input = "SelectOptionCellChangesetPayload")] + #[event(input = "SelectOptionCellChangesetPayloadPB")] UpdateSelectOptionCell = 72, - #[event(input = "DateChangesetPayload")] + #[event(input = "DateChangesetPayloadPB")] UpdateDateCell = 80, } diff --git a/frontend/rust-lib/flowy-grid/src/macros.rs b/frontend/rust-lib/flowy-grid/src/macros.rs index 8433964477..aee8ca2b68 100644 --- a/frontend/rust-lib/flowy-grid/src/macros.rs +++ b/frontend/rust-lib/flowy-grid/src/macros.rs @@ -30,7 +30,7 @@ macro_rules! impl_type_option { ($target: ident, $field_type:expr) => { impl std::convert::From<&FieldRevision> for $target { fn from(field_rev: &FieldRevision) -> $target { - match field_rev.get_type_option_entry::<$target, _>(&$field_type) { + match field_rev.get_type_option_entry::<$target>($field_type.into()) { None => $target::default(), Some(target) => target, } @@ -39,7 +39,7 @@ macro_rules! impl_type_option { impl std::convert::From<&std::sync::Arc> for $target { fn from(field_rev: &std::sync::Arc) -> $target { - match field_rev.get_type_option_entry::<$target, _>(&$field_type) { + match field_rev.get_type_option_entry::<$target>($field_type.into()) { None => $target::default(), Some(target) => target, } diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index 28fa7b0c9e..320f18b560 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -1,6 +1,8 @@ -use crate::services::grid_editor::GridRevisionEditor; +use crate::services::block_revision_editor::GridBlockRevisionCompactor; +use crate::services::grid_editor::{GridRevisionCompactor, GridRevisionEditor}; use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::kv::GridKVPersistence; +use crate::services::persistence::migration::GridMigration; use crate::services::persistence::GridDatabase; use crate::services::tasks::GridTaskScheduler; use bytes::Bytes; @@ -8,9 +10,9 @@ use dashmap::DashMap; use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{BuildGridContext, GridRevision}; -use flowy_revision::disk::{SQLiteGridBlockMetaRevisionPersistence, SQLiteGridRevisionPersistence}; -use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket}; -use flowy_sync::client_grid::{make_block_meta_delta, make_grid_delta}; +use flowy_revision::disk::{SQLiteGridBlockRevisionPersistence, SQLiteGridRevisionPersistence}; +use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionSnapshotPersistence}; +use flowy_sync::client_grid::{make_grid_block_delta, make_grid_delta}; use flowy_sync::entities::revision::{RepeatedRevision, Revision}; use std::sync::Arc; use tokio::sync::RwLock; @@ -30,6 +32,7 @@ pub struct GridManager { #[allow(dead_code)] kv_persistence: Arc, task_scheduler: GridTaskSchedulerRwLock, + migration: GridMigration, } impl GridManager { @@ -40,17 +43,27 @@ impl GridManager { ) -> Self { let grid_editors = Arc::new(DashMap::new()); let kv_persistence = Arc::new(GridKVPersistence::new(database.clone())); - let block_index_cache = Arc::new(BlockIndexCache::new(database)); + let block_index_cache = Arc::new(BlockIndexCache::new(database.clone())); let task_scheduler = GridTaskScheduler::new(); + let migration = GridMigration::new(grid_user.clone(), database); Self { grid_editors, grid_user, kv_persistence, block_index_cache, task_scheduler, + migration, } } + pub async fn initialize_with_new_user(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { + Ok(()) + } + + pub async fn initialize(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { + Ok(()) + } + #[tracing::instrument(level = "debug", skip_all, err)] pub async fn create_grid>(&self, grid_id: T, revisions: RepeatedRevision) -> FlowyResult<()> { let grid_id = grid_id.as_ref(); @@ -61,14 +74,10 @@ impl GridManager { } #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn create_grid_block_meta>( - &self, - block_id: T, - revisions: RepeatedRevision, - ) -> FlowyResult<()> { + pub async fn create_grid_block>(&self, block_id: T, revisions: RepeatedRevision) -> FlowyResult<()> { let block_id = block_id.as_ref(); let db_pool = self.grid_user.db_pool()?; - let rev_manager = self.make_grid_block_meta_rev_manager(block_id, db_pool)?; + let rev_manager = self.make_grid_block_rev_manager(block_id, db_pool)?; let _ = rev_manager.reset_object(revisions).await?; Ok(()) } @@ -77,6 +86,7 @@ impl GridManager { pub async fn open_grid>(&self, grid_id: T) -> FlowyResult> { let grid_id = grid_id.as_ref(); tracing::Span::current().record("grid_id", &grid_id); + let _ = self.migration.migration_grid_if_need(grid_id).await; self.get_or_create_grid_editor(grid_id).await } @@ -113,7 +123,7 @@ impl GridManager { None => { tracing::trace!("Create grid editor with id: {}", grid_id); let db_pool = self.grid_user.db_pool()?; - let editor = self.make_grid_editor(grid_id, db_pool).await?; + let editor = self.make_grid_rev_editor(grid_id, db_pool).await?; if self.grid_editors.contains_key(grid_id) { tracing::warn!("Grid:{} already exists in cache", grid_id); @@ -127,7 +137,7 @@ impl GridManager { } #[tracing::instrument(level = "trace", skip(self, pool), err)] - async fn make_grid_editor( + async fn make_grid_rev_editor( &self, grid_id: &str, pool: Arc, @@ -147,22 +157,22 @@ impl GridManager { pub fn make_grid_rev_manager(&self, grid_id: &str, pool: Arc) -> FlowyResult { let user_id = self.grid_user.user_id()?; - - let disk_cache = Arc::new(SQLiteGridRevisionPersistence::new(&user_id, pool)); - let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, grid_id, disk_cache)); - let rev_manager = RevisionManager::new(&user_id, grid_id, rev_persistence); + let disk_cache = SQLiteGridRevisionPersistence::new(&user_id, pool.clone()); + let rev_persistence = RevisionPersistence::new(&user_id, grid_id, disk_cache); + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(grid_id, pool); + let rev_compactor = GridRevisionCompactor(); + let rev_manager = RevisionManager::new(&user_id, grid_id, rev_persistence, rev_compactor, snapshot_persistence); Ok(rev_manager) } - fn make_grid_block_meta_rev_manager( - &self, - block_d: &str, - pool: Arc, - ) -> FlowyResult { + fn make_grid_block_rev_manager(&self, block_id: &str, pool: Arc) -> FlowyResult { let user_id = self.grid_user.user_id()?; - let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool)); - let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, block_d, disk_cache)); - let rev_manager = RevisionManager::new(&user_id, block_d, rev_persistence); + let disk_cache = SQLiteGridBlockRevisionPersistence::new(&user_id, pool.clone()); + let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache); + let rev_compactor = GridBlockRevisionCompactor(); + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(block_id, pool); + let rev_manager = + RevisionManager::new(&user_id, block_id, rev_persistence, rev_compactor, snapshot_persistence); Ok(rev_manager) } } @@ -181,13 +191,11 @@ pub async fn make_grid_view_data( }); // Create grid's block - let grid_block_meta_delta = make_block_meta_delta(block_meta_data); - let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes(); + let grid_block_delta = make_grid_block_delta(block_meta_data); + let block_delta_data = grid_block_delta.to_delta_bytes(); let repeated_revision: RepeatedRevision = - Revision::initial_revision(user_id, block_id, block_meta_delta_data).into(); - let _ = grid_manager - .create_grid_block_meta(&block_id, repeated_revision) - .await?; + Revision::initial_revision(user_id, block_id, block_delta_data).into(); + let _ = grid_manager.create_grid_block(&block_id, repeated_revision).await?; } let grid_rev = GridRevision::from_build_context(view_id, build_context); diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs index 9594beab33..c83bfcc2cd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs @@ -1,7 +1,7 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::{BlockRowInfo, CellChangeset, GridRowId, GridRowsChangeset, IndexRowOrder, Row, UpdatedRowOrder}; +use crate::entities::{CellChangesetPB, GridBlockChangesetPB, GridRowPB, InsertedRowPB, UpdatedRowPB}; use crate::manager::GridUser; -use crate::services::block_revision_editor::GridBlockRevisionEditor; +use crate::services::block_revision_editor::{GridBlockRevisionCompactor, GridBlockRevisionEditor}; use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::{block_from_row_orders, GridBlockSnapshot}; use dashmap::DashMap; @@ -9,8 +9,8 @@ use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{ GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision, }; -use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence; -use flowy_revision::{RevisionManager, RevisionPersistence}; +use flowy_revision::disk::SQLiteGridBlockRevisionPersistence; +use flowy_revision::{RevisionManager, RevisionPersistence, SQLiteRevisionSnapshotPersistence}; use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; @@ -71,12 +71,12 @@ impl GridBlockManager { let _ = self.persistence.insert(&row_rev.block_id, &row_rev.id)?; let editor = self.get_editor(&row_rev.block_id).await?; - let mut index_row_order = IndexRowOrder::from(&row_rev); + let mut index_row_order = InsertedRowPB::from(&row_rev); let (row_count, row_index) = editor.create_row(row_rev, start_row_id).await?; index_row_order.index = row_index; let _ = self - .notify_did_update_block(block_id, GridRowsChangeset::insert(block_id, vec![index_row_order])) + .notify_did_update_block(block_id, GridBlockChangesetPB::insert(block_id, vec![index_row_order])) .await?; Ok(row_count) } @@ -92,7 +92,7 @@ impl GridBlockManager { let mut row_count = 0; for row in row_revs { let _ = self.persistence.insert(&row.block_id, &row.id)?; - let mut row_order = IndexRowOrder::from(&row); + let mut row_order = InsertedRowPB::from(&row); let (count, index) = editor.create_row(row, None).await?; row_count = count; row_order.index = index; @@ -101,7 +101,7 @@ impl GridBlockManager { changesets.push(GridBlockMetaRevisionChangeset::from_row_count(&block_id, row_count)); let _ = self - .notify_did_update_block(&block_id, GridRowsChangeset::insert(&block_id, inserted_row_orders)) + .notify_did_update_block(&block_id, GridBlockChangesetPB::insert(&block_id, inserted_row_orders)) .await?; } @@ -110,7 +110,7 @@ impl GridBlockManager { pub async fn update_row(&self, changeset: RowMetaChangeset, row_builder: F) -> FlowyResult<()> where - F: FnOnce(Arc) -> Option, + F: FnOnce(Arc) -> Option, { let editor = self.get_editor_from_row_id(&changeset.row_id).await?; let _ = editor.update_row(changeset.clone()).await?; @@ -118,8 +118,8 @@ impl GridBlockManager { None => tracing::error!("Internal error: can't find the row with id: {}", changeset.row_id), Some(row_rev) => { if let Some(row) = row_builder(row_rev.clone()) { - let row_order = UpdatedRowOrder::new(&row_rev, row); - let block_order_changeset = GridRowsChangeset::update(&editor.block_id, vec![row_order]); + let row_order = UpdatedRowPB::new(&row_rev, row); + let block_order_changeset = GridBlockChangesetPB::update(&editor.block_id, vec![row_order]); let _ = self .notify_did_update_block(&editor.block_id, block_order_changeset) .await?; @@ -137,14 +137,8 @@ impl GridBlockManager { None => {} Some(row_info) => { let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?; - - let row_identifier = GridRowId { - grid_id: self.grid_id.clone(), - block_id: row_info.block_id, - row_id: row_info.row_id, - }; let _ = self - .notify_did_update_block(&block_id, GridRowsChangeset::delete(&block_id, vec![row_identifier])) + .notify_did_update_block(&block_id, GridBlockChangesetPB::delete(&block_id, vec![row_info.id])) .await?; } } @@ -154,13 +148,13 @@ impl GridBlockManager { pub(crate) async fn delete_rows( &self, - row_orders: Vec, + row_orders: Vec, ) -> FlowyResult> { let mut changesets = vec![]; for grid_block in block_from_row_orders(row_orders) { let editor = self.get_editor(&grid_block.id).await?; let row_ids = grid_block - .row_infos + .rows .into_iter() .map(|row_info| Cow::Owned(row_info.row_id().to_owned())) .collect::>>(); @@ -179,22 +173,18 @@ impl GridBlockManager { match editor.get_row_revs(Some(vec![Cow::Borrowed(row_id)])).await?.pop() { None => {} Some(row_rev) => { - let row_info = BlockRowInfo::from(&row_rev); - let insert_row = IndexRowOrder { - row_info: row_info.clone(), + let insert_row = InsertedRowPB { + block_id: row_rev.block_id.clone(), + row_id: row_rev.id.clone(), index: Some(to as i32), + height: row_rev.height, }; - let deleted_row = GridRowId { - grid_id: self.grid_id.clone(), - block_id: row_info.block_id, - row_id: row_info.row_id, - }; - let notified_changeset = GridRowsChangeset { + let notified_changeset = GridBlockChangesetPB { block_id: editor.block_id.clone(), inserted_rows: vec![insert_row], - deleted_rows: vec![deleted_row], - updated_rows: vec![], + deleted_rows: vec![row_rev.id.clone()], + ..Default::default() }; let _ = self @@ -206,9 +196,9 @@ impl GridBlockManager { Ok(()) } - pub async fn update_cell(&self, changeset: CellChangeset, row_builder: F) -> FlowyResult<()> + pub async fn update_cell(&self, changeset: CellChangesetPB, row_builder: F) -> FlowyResult<()> where - F: FnOnce(Arc) -> Option, + F: FnOnce(Arc) -> Option, { let row_changeset: RowMetaChangeset = changeset.clone().into(); let _ = self.update_row(row_changeset, row_builder).await?; @@ -227,7 +217,7 @@ impl GridBlockManager { } } - pub async fn get_row_orders(&self, block_id: &str) -> FlowyResult> { + pub async fn get_row_orders(&self, block_id: &str) -> FlowyResult> { let editor = self.get_editor(block_id).await?; editor.get_row_infos::<&str>(None).await } @@ -257,14 +247,14 @@ impl GridBlockManager { Ok(snapshots) } - async fn notify_did_update_block(&self, block_id: &str, changeset: GridRowsChangeset) -> FlowyResult<()> { + async fn notify_did_update_block(&self, block_id: &str, changeset: GridBlockChangesetPB) -> FlowyResult<()> { send_dart_notification(block_id, GridNotification::DidUpdateGridBlock) .payload(changeset) .send(); Ok(()) } - async fn notify_did_update_cell(&self, changeset: CellChangeset) -> FlowyResult<()> { + async fn notify_did_update_cell(&self, changeset: CellChangesetPB) -> FlowyResult<()> { let id = format!("{}:{}", changeset.row_id, changeset.field_id); send_dart_notification(&id, GridNotification::DidUpdateCell).send(); Ok(()) @@ -290,8 +280,10 @@ async fn make_block_editor(user: &Arc, block_id: &str) -> FlowyRes let user_id = user.user_id()?; let pool = user.db_pool()?; - let disk_cache = Arc::new(SQLiteGridBlockMetaRevisionPersistence::new(&user_id, pool)); - let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, block_id, disk_cache)); - let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence); + let disk_cache = SQLiteGridBlockRevisionPersistence::new(&user_id, pool.clone()); + let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache); + let rev_compactor = GridBlockRevisionCompactor(); + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(block_id, pool); + let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence, rev_compactor, snapshot_persistence); GridBlockRevisionEditor::new(&user_id, &token, block_id, rev_manager).await } diff --git a/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs index 6506b0a367..f74075ec6e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_revision_editor.rs @@ -1,4 +1,4 @@ -use crate::entities::BlockRowInfo; +use crate::entities::GridRowPB; use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{CellRevision, GridBlockRevision, RowMetaChangeset, RowRevision}; @@ -26,7 +26,7 @@ impl GridBlockRevisionEditor { block_id: &str, mut rev_manager: RevisionManager, ) -> FlowyResult { - let cloud = Arc::new(GridBlockMetaRevisionCloudService { + let cloud = Arc::new(GridBlockRevisionCloudService { token: token.to_owned(), }); let block_meta_pad = rev_manager.load::(Some(cloud)).await?; @@ -123,12 +123,12 @@ impl GridBlockRevisionEditor { Ok(cell_revs) } - pub async fn get_row_info(&self, row_id: &str) -> FlowyResult> { + pub async fn get_row_info(&self, row_id: &str) -> FlowyResult> { let row_ids = Some(vec![Cow::Borrowed(row_id)]); Ok(self.get_row_infos(row_ids).await?.pop()) } - pub async fn get_row_infos(&self, row_ids: Option>>) -> FlowyResult> + pub async fn get_row_infos(&self, row_ids: Option>>) -> FlowyResult> where T: AsRef + ToOwned + ?Sized, { @@ -138,8 +138,8 @@ impl GridBlockRevisionEditor { .await .get_row_revs(row_ids)? .iter() - .map(BlockRowInfo::from) - .collect::>(); + .map(GridRowPB::from) + .collect::>(); Ok(row_infos) } @@ -170,20 +170,17 @@ impl GridBlockRevisionEditor { &user_id, md5, ); - let _ = self - .rev_manager - .add_local_revision(&revision, Box::new(GridBlockMetaRevisionCompactor())) - .await?; + let _ = self.rev_manager.add_local_revision(&revision).await?; Ok(()) } } -struct GridBlockMetaRevisionCloudService { +struct GridBlockRevisionCloudService { #[allow(dead_code)] token: String, } -impl RevisionCloudService for GridBlockMetaRevisionCloudService { +impl RevisionCloudService for GridBlockRevisionCloudService { #[tracing::instrument(level = "trace", skip(self))] fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { FutureResult::new(async move { Ok(vec![]) }) @@ -200,8 +197,8 @@ impl RevisionObjectBuilder for GridBlockMetaPadBuilder { } } -struct GridBlockMetaRevisionCompactor(); -impl RevisionCompactor for GridBlockMetaRevisionCompactor { +pub struct GridBlockRevisionCompactor(); +impl RevisionCompactor for GridBlockRevisionCompactor { fn bytes_from_revisions(&self, revisions: Vec) -> FlowyResult { let delta = make_delta_from_revisions::(revisions)?; Ok(delta.to_delta_bytes()) diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs b/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs new file mode 100644 index 0000000000..8ebffcbedc --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs @@ -0,0 +1,168 @@ +use crate::entities::FieldType; +use crate::services::cell::{CellData, FromCellString}; +use bytes::Bytes; +use flowy_error::{internal_error, FlowyError, FlowyResult}; +use flowy_grid_data_model::revision::CellRevision; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +/// AnyCellData is a generic CellData, you can parse the cell_data according to the field_type. +/// When the type of field is changed, it's different from the field_type of AnyCellData. +/// So it will return an empty data. You could check the CellDataOperation trait for more information. +#[derive(Debug, Serialize, Deserialize)] +pub struct AnyCellData { + pub data: String, + pub field_type: FieldType, +} + +impl std::str::FromStr for AnyCellData { + type Err = FlowyError; + + fn from_str(s: &str) -> Result { + let type_option_cell_data: AnyCellData = serde_json::from_str(s)?; + Ok(type_option_cell_data) + } +} + +impl std::convert::TryInto for String { + type Error = FlowyError; + + fn try_into(self) -> Result { + AnyCellData::from_str(&self) + } +} + +impl std::convert::TryFrom<&CellRevision> for AnyCellData { + type Error = FlowyError; + + fn try_from(value: &CellRevision) -> Result { + Self::from_str(&value.data) + } +} + +impl std::convert::TryFrom for AnyCellData { + type Error = FlowyError; + + fn try_from(value: CellRevision) -> Result { + Self::try_from(&value) + } +} + +impl std::convert::From for CellData +where + T: FromCellString, +{ + fn from(any_call_data: AnyCellData) -> Self { + CellData::from(any_call_data.data) + } +} + +impl AnyCellData { + pub fn new(content: String, field_type: FieldType) -> Self { + AnyCellData { + data: content, + field_type, + } + } + + pub fn json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) + } + + pub fn is_number(&self) -> bool { + self.field_type == FieldType::Number + } + + pub fn is_text(&self) -> bool { + self.field_type == FieldType::RichText + } + + pub fn is_checkbox(&self) -> bool { + self.field_type == FieldType::Checkbox + } + + pub fn is_date(&self) -> bool { + self.field_type == FieldType::DateTime + } + + pub fn is_single_select(&self) -> bool { + self.field_type == FieldType::SingleSelect + } + + pub fn is_multi_select(&self) -> bool { + self.field_type == FieldType::MultiSelect + } + + pub fn is_url(&self) -> bool { + self.field_type == FieldType::URL + } + + pub fn is_select_option(&self) -> bool { + self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect + } +} + +/// The data is encoded by protobuf or utf8. You should choose the corresponding decode struct to parse it. +/// +/// For example: +/// +/// * Use DateCellData to parse the data when the FieldType is Date. +/// * Use URLCellData to parse the data when the FieldType is URL. +/// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox. +/// * Check out the implementation of CellDataOperation trait for more information. +#[derive(Default)] +pub struct CellBytes(pub Bytes); + +pub trait CellBytesParser { + type Object; + fn parse(&self, bytes: &Bytes) -> FlowyResult; +} + +impl CellBytes { + pub fn new>(data: T) -> Self { + let bytes = Bytes::from(data.as_ref().to_vec()); + Self(bytes) + } + + pub fn from>(bytes: T) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + let bytes = bytes.try_into().map_err(internal_error)?; + Ok(Self(bytes)) + } + + pub fn with_parser

(&self, parser: P) -> FlowyResult + where + P: CellBytesParser, + { + parser.parse(&self.0) + } + + // pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult + // where + // >::Error: std::fmt::Debug, + // { + // T::try_from(self.0.as_ref()).map_err(internal_error) + // } +} + +impl ToString for CellBytes { + fn to_string(&self) -> String { + match String::from_utf8(self.0.to_vec()) { + Ok(s) => s, + Err(e) => { + tracing::error!("DecodedCellData to string failed: {:?}", e); + "".to_string() + } + } + } +} + +impl std::ops::Deref for CellBytes { + type Target = Bytes; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs new file mode 100644 index 0000000000..e34f3a8430 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs @@ -0,0 +1,217 @@ +use crate::entities::FieldType; +use crate::services::cell::{AnyCellData, CellBytes}; +use crate::services::field::*; + +use flowy_error::{ErrorCode, FlowyError, FlowyResult}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, FieldTypeRevision}; + +/// This trait is used when doing filter/search on the grid. +pub trait CellFilterOperation { + /// Return true if any_cell_data match the filter condition. + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &T) -> FlowyResult; +} + +/// Return object that describes the cell. +pub trait CellDisplayable { + fn display_data( + &self, + cell_data: CellData, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult; +} + +// CD: Short for CellData. This type is the type return by apply_changeset function. +// CS: Short for Changeset. Parse the string into specific Changeset type. +pub trait CellDataOperation { + /// The cell_data is able to parse into the specific data if CD impl the FromCellData trait. + /// For example: + /// URLCellData, DateCellData. etc. + fn decode_cell_data( + &self, + cell_data: CellData, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult; + + /// The changeset is able to parse into the specific data if CS impl the FromCellChangeset trait. + /// For example: + /// SelectOptionCellChangeset,DateCellChangeset. etc. + fn apply_changeset(&self, changeset: CellDataChangeset, cell_rev: Option) -> FlowyResult; +} + +/// changeset: It will be deserialized into specific data base on the FieldType. +/// For example, +/// FieldType::RichText => String +/// FieldType::SingleSelect => SelectOptionChangeset +/// +/// cell_rev: It will be None if the cell does not contain any data. +pub fn apply_cell_data_changeset>( + changeset: C, + cell_rev: Option, + field_rev: T, +) -> Result { + let field_rev = field_rev.as_ref(); + let changeset = changeset.to_string(); + let field_type = field_rev.field_type_rev.into(); + let s = match field_type { + FieldType::RichText => RichTextTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev), + FieldType::Number => NumberTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev), + FieldType::DateTime => DateTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev), + FieldType::SingleSelect => { + SingleSelectTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev) + } + FieldType::MultiSelect => MultiSelectTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev), + FieldType::Checkbox => CheckboxTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev), + FieldType::URL => URLTypeOption::from(field_rev).apply_changeset(changeset.into(), cell_rev), + }?; + + Ok(AnyCellData::new(s, field_type).json()) +} + +pub fn decode_any_cell_data>(data: T, field_rev: &FieldRevision) -> CellBytes { + if let Ok(any_cell_data) = data.try_into() { + let AnyCellData { data, field_type } = any_cell_data; + let to_field_type = field_rev.field_type_rev.into(); + match try_decode_cell_data(data.into(), field_rev, &field_type, &to_field_type) { + Ok(cell_bytes) => cell_bytes, + Err(e) => { + tracing::error!("Decode cell data failed, {:?}", e); + CellBytes::default() + } + } + } else { + tracing::error!("Decode type option data failed"); + CellBytes::default() + } +} + +pub fn try_decode_cell_data( + cell_data: CellData, + field_rev: &FieldRevision, + s_field_type: &FieldType, + t_field_type: &FieldType, +) -> FlowyResult { + let cell_data = cell_data.try_into_inner()?; + let get_cell_data = || { + let field_type: FieldTypeRevision = t_field_type.into(); + let data = match t_field_type { + FieldType::RichText => field_rev + .get_type_option_entry::(field_type)? + .decode_cell_data(cell_data.into(), s_field_type, field_rev), + FieldType::Number => field_rev + .get_type_option_entry::(field_type)? + .decode_cell_data(cell_data.into(), s_field_type, field_rev), + FieldType::DateTime => field_rev + .get_type_option_entry::(field_type)? + .decode_cell_data(cell_data.into(), s_field_type, field_rev), + FieldType::SingleSelect => field_rev + .get_type_option_entry::(field_type)? + .decode_cell_data(cell_data.into(), s_field_type, field_rev), + FieldType::MultiSelect => field_rev + .get_type_option_entry::(field_type)? + .decode_cell_data(cell_data.into(), s_field_type, field_rev), + FieldType::Checkbox => field_rev + .get_type_option_entry::(field_type)? + .decode_cell_data(cell_data.into(), s_field_type, field_rev), + FieldType::URL => field_rev + .get_type_option_entry::(field_type)? + .decode_cell_data(cell_data.into(), s_field_type, field_rev), + }; + Some(data) + }; + + match get_cell_data() { + Some(Ok(data)) => Ok(data), + Some(Err(err)) => { + tracing::error!("{:?}", err); + Ok(CellBytes::default()) + } + None => Ok(CellBytes::default()), + } +} + +/// If the cell data is not String type, it should impl this trait. +/// Deserialize the String into cell specific data type. +pub trait FromCellString { + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized; +} + +/// CellData is a helper struct. String will be parser into Option only if the T impl the FromCellString trait. +pub struct CellData(pub Option); +impl CellData { + pub fn try_into_inner(self) -> FlowyResult { + match self.0 { + None => Err(ErrorCode::InvalidData.into()), + Some(data) => Ok(data), + } + } +} + +impl std::convert::From for CellData +where + T: FromCellString, +{ + fn from(s: String) -> Self { + match T::from_cell_str(&s) { + Ok(inner) => CellData(Some(inner)), + Err(e) => { + tracing::error!("Deserialize Cell Data failed: {}", e); + CellData(None) + } + } + } +} + +impl std::convert::From for CellData { + fn from(val: T) -> Self { + CellData(Some(val)) + } +} + +impl std::convert::From> for String { + fn from(p: CellData) -> Self { + p.try_into_inner().unwrap_or_else(|_| String::new()) + } +} + +/// If the changeset applying to the cell is not String type, it should impl this trait. +/// Deserialize the string into cell specific changeset. +pub trait FromCellChangeset { + fn from_changeset(changeset: String) -> FlowyResult + where + Self: Sized; +} + +pub struct CellDataChangeset(pub Option); + +impl CellDataChangeset { + pub fn try_into_inner(self) -> FlowyResult { + match self.0 { + None => Err(ErrorCode::InvalidData.into()), + Some(data) => Ok(data), + } + } +} + +impl std::convert::From for CellDataChangeset +where + T: FromCellChangeset, +{ + fn from(changeset: C) -> Self { + match T::from_changeset(changeset.to_string()) { + Ok(data) => CellDataChangeset(Some(data)), + Err(e) => { + tracing::error!("Deserialize CellDataChangeset failed: {}", e); + CellDataChangeset(None) + } + } + } +} +impl std::convert::From for CellDataChangeset { + fn from(s: String) -> Self { + CellDataChangeset(Some(s)) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/mod.rs b/frontend/rust-lib/flowy-grid/src/services/cell/mod.rs new file mode 100644 index 0000000000..bc8ada929c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/cell/mod.rs @@ -0,0 +1,5 @@ +mod any_cell_data; +mod cell_operation; + +pub use any_cell_data::*; +pub use cell_operation::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs index 352d3e7f5c..de1f37b04f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs @@ -1,4 +1,4 @@ -use crate::entities::{Field, FieldType}; +use crate::entities::{FieldType, GridFieldPB}; use crate::services::field::type_options::*; use bytes::Bytes; use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry}; @@ -28,7 +28,7 @@ impl FieldBuilder { Self::new(type_option_builder) } - pub fn from_field(field: Field, type_option_builder: Box) -> Self { + pub fn from_field(field: GridFieldPB, type_option_builder: Box) -> Self { let field_rev = FieldRevision { id: field.id, name: field.name, @@ -93,7 +93,7 @@ pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box RichTextTypeOption::default().into(), FieldType::Number => NumberTypeOption::default().into(), FieldType::DateTime => DateTypeOption::default().into(), - FieldType::SingleSelect => SingleSelectTypeOption::default().into(), + FieldType::SingleSelect => SingleSelectTypeOptionPB::default().into(), FieldType::MultiSelect => MultiSelectTypeOption::default().into(), FieldType::Checkbox => CheckboxTypeOption::default().into(), FieldType::URL => URLTypeOption::default().into(), diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs deleted file mode 100644 index 807f3d1fb7..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::entities::{FieldType, GridCheckboxFilter}; -use crate::impl_type_option; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; -use bytes::Bytes; -use flowy_derive::ProtoBuf; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; -use serde::{Deserialize, Serialize}; - -#[derive(Default)] -pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption); -impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption); - -impl CheckboxTypeOptionBuilder { - pub fn set_selected(mut self, is_selected: bool) -> Self { - self.0.is_selected = is_selected; - self - } -} - -impl TypeOptionBuilder for CheckboxTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::Checkbox - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] -pub struct CheckboxTypeOption { - #[pb(index = 1)] - pub is_selected: bool, -} -impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); - -const YES: &str = "Yes"; -const NO: &str = "No"; - -impl CellDataOperation for CheckboxTypeOption { - fn decode_cell_data( - &self, - encoded_data: T, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { - if !decoded_field_type.is_checkbox() { - return Ok(DecodedCellData::default()); - } - - let encoded_data = encoded_data.into(); - if encoded_data == YES || encoded_data == NO { - return Ok(DecodedCellData::new(encoded_data)); - } - - Ok(DecodedCellData::default()) - } - - fn apply_filter(&self, _filter: GridCheckboxFilter) -> bool { - todo!() - } - - fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result - where - C: Into, - { - let changeset = changeset.into(); - let s = match string_to_bool(&changeset) { - true => YES, - false => NO, - }; - Ok(s.to_string()) - } -} - -fn string_to_bool(bool_str: &str) -> bool { - let lower_case_str: &str = &bool_str.to_lowercase(); - match lower_case_str { - "1" => true, - "true" => true, - "yes" => true, - "0" => false, - "false" => false, - "no" => false, - _ => false, - } -} - -#[cfg(test)] -mod tests { - use crate::services::field::type_options::checkbox_type_option::{NO, YES}; - - use crate::services::field::FieldBuilder; - use crate::services::row::{apply_cell_data_changeset, decode_cell_data}; - - use crate::entities::FieldType; - - #[test] - fn checkout_box_description_test() { - let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build(); - let data = apply_cell_data_changeset("true", None, &field_rev).unwrap(); - assert_eq!(decode_cell_data(data, &field_rev).to_string(), YES); - - let data = apply_cell_data_changeset("1", None, &field_rev).unwrap(); - assert_eq!(decode_cell_data(data, &field_rev,).to_string(), YES); - - let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap(); - assert_eq!(decode_cell_data(data, &field_rev,).to_string(), YES); - - let data = apply_cell_data_changeset("false", None, &field_rev).unwrap(); - assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO); - - let data = apply_cell_data_changeset("no", None, &field_rev).unwrap(); - assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO); - - let data = apply_cell_data_changeset("12", None, &field_rev).unwrap(); - assert_eq!(decode_cell_data(data, &field_rev,).to_string(), NO); - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs new file mode 100644 index 0000000000..1a60a0a5a8 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs @@ -0,0 +1,30 @@ +#[cfg(test)] +mod tests { + use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data}; + use crate::services::field::type_options::checkbox_type_option::{NO, YES}; + use crate::services::field::FieldBuilder; + + use crate::entities::FieldType; + + #[test] + fn checkout_box_description_test() { + let field_rev = FieldBuilder::from_field_type(&FieldType::Checkbox).build(); + let data = apply_cell_data_changeset("true", None, &field_rev).unwrap(); + assert_eq!(decode_any_cell_data(data, &field_rev).to_string(), YES); + + let data = apply_cell_data_changeset("1", None, &field_rev).unwrap(); + assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES); + + let data = apply_cell_data_changeset("yes", None, &field_rev).unwrap(); + assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), YES); + + let data = apply_cell_data_changeset("false", None, &field_rev).unwrap(); + assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO); + + let data = apply_cell_data_changeset("no", None, &field_rev).unwrap(); + assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), NO); + + let data = apply_cell_data_changeset("12", None, &field_rev).unwrap(); + assert_eq!(decode_any_cell_data(data, &field_rev,).to_string(), ""); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs new file mode 100644 index 0000000000..155965f409 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs @@ -0,0 +1,76 @@ +use crate::entities::FieldType; +use crate::impl_type_option; +use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionBuilder}; +use bytes::Bytes; +use flowy_derive::ProtoBuf; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +#[derive(Default)] +pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption); +impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOption); + +impl CheckboxTypeOptionBuilder { + pub fn set_selected(mut self, is_selected: bool) -> Self { + self.0.is_selected = is_selected; + self + } +} + +impl TypeOptionBuilder for CheckboxTypeOptionBuilder { + fn field_type(&self) -> FieldType { + FieldType::Checkbox + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] +pub struct CheckboxTypeOption { + #[pb(index = 1)] + pub is_selected: bool, +} +impl_type_option!(CheckboxTypeOption, FieldType::Checkbox); + +impl CellDisplayable for CheckboxTypeOption { + fn display_data( + &self, + cell_data: CellData, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult { + let cell_data = cell_data.try_into_inner()?; + Ok(CellBytes::new(cell_data)) + } +} + +impl CellDataOperation for CheckboxTypeOption { + fn decode_cell_data( + &self, + cell_data: CellData, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult { + if !decoded_field_type.is_checkbox() { + return Ok(CellBytes::default()); + } + + self.display_data(cell_data, decoded_field_type, field_rev) + } + + fn apply_changeset( + &self, + changeset: CellDataChangeset, + _cell_rev: Option, + ) -> Result { + let changeset = changeset.try_into_inner()?; + let cell_data = CheckboxCellData::from_str(&changeset)?; + Ok(cell_data.to_string()) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs new file mode 100644 index 0000000000..233c481eb6 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs @@ -0,0 +1,69 @@ +use crate::services::cell::{CellBytesParser, FromCellString}; +use bytes::Bytes; +use flowy_error::{FlowyError, FlowyResult}; +use std::str::FromStr; + +pub const YES: &str = "Yes"; +pub const NO: &str = "No"; + +pub struct CheckboxCellData(String); + +impl CheckboxCellData { + pub fn is_check(&self) -> bool { + self.0 == YES + } +} + +impl AsRef<[u8]> for CheckboxCellData { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl FromStr for CheckboxCellData { + type Err = FlowyError; + + fn from_str(s: &str) -> Result { + let lower_case_str: &str = &s.to_lowercase(); + let val = match lower_case_str { + "1" => Some(true), + "true" => Some(true), + "yes" => Some(true), + "0" => Some(false), + "false" => Some(false), + "no" => Some(false), + _ => None, + }; + + match val { + Some(true) => Ok(Self(YES.to_string())), + Some(false) => Ok(Self(NO.to_string())), + None => Ok(Self("".to_string())), + } + } +} + +impl FromCellString for CheckboxCellData { + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + Self::from_str(s) + } +} + +impl ToString for CheckboxCellData { + fn to_string(&self) -> String { + self.0.clone() + } +} +pub struct CheckboxCellDataParser(); +impl CellBytesParser for CheckboxCellDataParser { + type Object = CheckboxCellData; + fn parse(&self, bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => CheckboxCellData::from_str(&s), + Err(_) => Ok(CheckboxCellData("".to_string())), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs new file mode 100644 index 0000000000..ebe5d1a6a8 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs @@ -0,0 +1,7 @@ +#![allow(clippy::module_inception)] +mod checkbox_tests; +mod checkbox_type_option; +mod checkbox_type_option_entities; + +pub use checkbox_type_option::*; +pub use checkbox_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs deleted file mode 100644 index a0bfd33cd2..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs +++ /dev/null @@ -1,636 +0,0 @@ -use crate::entities::{CellChangeset, FieldType, GridDateFilter}; -use crate::entities::{CellIdentifier, CellIdentifierPayload}; -use crate::impl_type_option; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; -use bytes::Bytes; -use chrono::format::strftime::StrftimeItems; -use chrono::{NaiveDateTime, Timelike}; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; -use serde::{Deserialize, Serialize}; -use strum_macros::EnumIter; - -// Date -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct DateTypeOption { - #[pb(index = 1)] - pub date_format: DateFormat, - - #[pb(index = 2)] - pub time_format: TimeFormat, - - #[pb(index = 3)] - pub include_time: bool, -} -impl_type_option!(DateTypeOption, FieldType::DateTime); - -impl DateTypeOption { - #[allow(dead_code)] - pub fn new() -> Self { - Self::default() - } - - fn today_desc_from_timestamp(&self, timestamp: i64) -> DateCellData { - let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0); - self.date_from_native(native) - } - - fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellData { - if native.timestamp() == 0 { - return DateCellData::default(); - } - - let time = native.time(); - let has_time = time.hour() != 0 || time.second() != 0; - - let utc = self.utc_date_time_from_native(native); - let fmt = self.date_format.format_str(); - let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt))); - - let mut time = "".to_string(); - if has_time { - let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str()); - time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, ""); - } - - let timestamp = native.timestamp(); - DateCellData { date, time, timestamp } - } - - fn date_fmt(&self, time: &Option) -> String { - if self.include_time { - match time.as_ref() { - None => self.date_format.format_str().to_string(), - Some(time_str) => { - if time_str.is_empty() { - self.date_format.format_str().to_string() - } else { - format!("{} {}", self.date_format.format_str(), self.time_format.format_str()) - } - } - } - } else { - self.date_format.format_str().to_string() - } - } - - fn timestamp_from_utc_with_time( - &self, - utc: &chrono::DateTime, - time: &Option, - ) -> FlowyResult { - if let Some(time_str) = time.as_ref() { - if !time_str.is_empty() { - let date_str = format!( - "{}{}", - utc.format_with_items(StrftimeItems::new(self.date_format.format_str())), - &time_str - ); - - return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) { - Ok(native) => { - let utc = self.utc_date_time_from_native(native); - Ok(utc.timestamp()) - } - Err(_e) => { - let msg = format!("Parse {} failed", date_str); - Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)) - } - }; - } - } - - Ok(utc.timestamp()) - } - - fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime { - let native = NaiveDateTime::from_timestamp(timestamp, 0); - self.utc_date_time_from_native(native) - } - - fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime { - chrono::DateTime::::from_utc(naive, chrono::Utc) - } -} - -impl CellDataOperation for DateTypeOption { - fn decode_cell_data( - &self, - encoded_data: T, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { - // Return default data if the type_option_cell_data is not FieldType::DateTime. - // It happens when switching from one field to another. - // For example: - // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. - if !decoded_field_type.is_date() { - return Ok(DecodedCellData::default()); - } - - let timestamp = encoded_data.into().parse::().unwrap_or(0); - let date = self.today_desc_from_timestamp(timestamp); - DecodedCellData::try_from_bytes(date) - } - - fn apply_filter(&self, _filter: GridDateFilter) -> bool { - todo!() - } - - fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result - where - C: Into, - { - let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?; - let cell_data = match content_changeset.date_timestamp() { - None => 0, - Some(date_timestamp) => match (self.include_time, content_changeset.time) { - (true, Some(time)) => { - let time = Some(time.trim().to_uppercase()); - let utc = self.utc_date_time_from_timestamp(date_timestamp); - self.timestamp_from_utc_with_time(&utc, &time)? - } - _ => date_timestamp, - }, - }; - - Ok(cell_data.to_string()) - } -} - -#[derive(Default)] -pub struct DateTypeOptionBuilder(DateTypeOption); -impl_into_box_type_option_builder!(DateTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption); - -impl DateTypeOptionBuilder { - pub fn date_format(mut self, date_format: DateFormat) -> Self { - self.0.date_format = date_format; - self - } - - pub fn time_format(mut self, time_format: TimeFormat) -> Self { - self.0.time_format = time_format; - self - } -} -impl TypeOptionBuilder for DateTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::DateTime - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)] -pub enum DateFormat { - Local = 0, - US = 1, - ISO = 2, - Friendly = 3, -} -impl std::default::Default for DateFormat { - fn default() -> Self { - DateFormat::Friendly - } -} - -impl std::convert::From for DateFormat { - fn from(value: i32) -> Self { - match value { - 0 => DateFormat::Local, - 1 => DateFormat::US, - 2 => DateFormat::ISO, - 3 => DateFormat::Friendly, - _ => { - tracing::error!("Unsupported date format, fallback to friendly"); - DateFormat::Friendly - } - } - } -} - -impl DateFormat { - pub fn value(&self) -> i32 { - *self as i32 - } - // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html - pub fn format_str(&self) -> &'static str { - match self { - DateFormat::Local => "%Y/%m/%d", - DateFormat::US => "%Y/%m/%d", - DateFormat::ISO => "%Y-%m-%d", - DateFormat::Friendly => "%b %d,%Y", - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)] -pub enum TimeFormat { - TwelveHour = 0, - TwentyFourHour = 1, -} - -impl std::convert::From for TimeFormat { - fn from(value: i32) -> Self { - match value { - 0 => TimeFormat::TwelveHour, - 1 => TimeFormat::TwentyFourHour, - _ => { - tracing::error!("Unsupported time format, fallback to TwentyFourHour"); - TimeFormat::TwentyFourHour - } - } - } -} - -impl TimeFormat { - pub fn value(&self) -> i32 { - *self as i32 - } - - // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html - pub fn format_str(&self) -> &'static str { - match self { - TimeFormat::TwelveHour => "%I:%M %p", - TimeFormat::TwentyFourHour => "%R", - } - } -} - -impl std::default::Default for TimeFormat { - fn default() -> Self { - TimeFormat::TwentyFourHour - } -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct DateCellData { - #[pb(index = 1)] - pub date: String, - - #[pb(index = 2)] - pub time: String, - - #[pb(index = 3)] - pub timestamp: i64, -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct DateChangesetPayload { - #[pb(index = 1)] - pub cell_identifier: CellIdentifierPayload, - - #[pb(index = 2, one_of)] - pub date: Option, - - #[pb(index = 3, one_of)] - pub time: Option, -} - -pub struct DateChangesetParams { - pub cell_identifier: CellIdentifier, - pub date: Option, - pub time: Option, -} - -impl TryInto for DateChangesetPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?; - Ok(DateChangesetParams { - cell_identifier, - date: self.date, - time: self.time, - }) - } -} - -impl std::convert::From for CellChangeset { - fn from(params: DateChangesetParams) -> Self { - let changeset = DateCellContentChangeset { - date: params.date, - time: params.time, - }; - let s = serde_json::to_string(&changeset).unwrap(); - CellChangeset { - grid_id: params.cell_identifier.grid_id, - row_id: params.cell_identifier.row_id, - field_id: params.cell_identifier.field_id, - cell_content_changeset: Some(s), - } - } -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct DateCellContentChangeset { - pub date: Option, - pub time: Option, -} - -impl DateCellContentChangeset { - pub fn date_timestamp(&self) -> Option { - if let Some(date) = &self.date { - match date.parse::() { - Ok(date_timestamp) => Some(date_timestamp), - Err(_) => None, - } - } else { - None - } - } -} - -impl std::convert::From for CellContentChangeset { - fn from(changeset: DateCellContentChangeset) -> Self { - let s = serde_json::to_string(&changeset).unwrap(); - CellContentChangeset::from(s) - } -} - -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::field::FieldBuilder; - use crate::services::field::{DateCellContentChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat}; - use crate::services::row::CellDataOperation; - use flowy_grid_data_model::revision::FieldRevision; - use strum::IntoEnumIterator; - - #[test] - fn date_type_option_invalid_input_test() { - let type_option = DateTypeOption::default(); - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some("1e".to_string()), - time: Some("23:00".to_owned()), - }, - &field_type, - &field_rev, - "", - ); - } - - #[test] - fn date_type_option_date_format_test() { - let mut type_option = DateTypeOption::default(); - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - for date_format in DateFormat::iter() { - type_option.date_format = date_format; - match date_format { - DateFormat::Friendly => { - assert_decode_timestamp(1647251762, &type_option, &field_rev, "Mar 14,2022"); - } - DateFormat::US => { - assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14"); - } - DateFormat::ISO => { - assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022-03-14"); - } - DateFormat::Local => { - assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14"); - } - } - } - } - - #[test] - fn date_type_option_time_format_test() { - let mut type_option = DateTypeOption::default(); - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for time_format in TimeFormat::iter() { - type_option.time_format = time_format; - type_option.include_time = true; - match time_format { - TimeFormat::TwentyFourHour => { - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }, - &field_type, - &field_rev, - "May 27,2022", - ); - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("23:00".to_owned()), - }, - &field_type, - &field_rev, - "May 27,2022 23:00", - ); - } - TimeFormat::TwelveHour => { - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: None, - }, - &field_type, - &field_rev, - "May 27,2022", - ); - // - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("".to_owned()), - }, - &field_type, - &field_rev, - "May 27,2022", - ); - - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(1653609600.to_string()), - time: Some("11:23 pm".to_owned()), - }, - &field_type, - &field_rev, - "May 27,2022 11:23 PM", - ); - } - } - } - } - - #[test] - fn date_type_option_apply_changeset_test() { - let mut type_option = DateTypeOption::new(); - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - let date_timestamp = "1653609600".to_owned(); - - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: None, - }, - &field_type, - &field_rev, - "May 27,2022", - ); - - type_option.include_time = true; - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: None, - }, - &field_type, - &field_rev, - "May 27,2022", - ); - - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:00".to_owned()), - }, - &field_type, - &field_rev, - "May 27,2022 01:00", - ); - - type_option.time_format = TimeFormat::TwelveHour; - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(date_timestamp), - time: Some("1:00 am".to_owned()), - }, - &field_type, - &field_rev, - "May 27,2022 01:00 AM", - ); - } - - #[test] - #[should_panic] - fn date_type_option_apply_changeset_error_test() { - let mut type_option = DateTypeOption::new(); - type_option.include_time = true; - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - let date_timestamp = "1653609600".to_owned(); - - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(date_timestamp.clone()), - time: Some("1:".to_owned()), - }, - &FieldType::DateTime, - &field_rev, - "May 27,2022 01:00", - ); - - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(date_timestamp), - time: Some("1:00".to_owned()), - }, - &FieldType::DateTime, - &field_rev, - "May 27,2022 01:00", - ); - } - - #[test] - #[should_panic] - fn date_type_option_twelve_hours_to_twenty_four_hours() { - let mut type_option = DateTypeOption::new(); - type_option.include_time = true; - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - let date_timestamp = "1653609600".to_owned(); - - assert_changeset_result( - &type_option, - DateCellContentChangeset { - date: Some(date_timestamp), - time: Some("1:00 am".to_owned()), - }, - &FieldType::DateTime, - &field_rev, - "May 27,2022 01:00", - ); - } - - fn assert_changeset_result( - type_option: &DateTypeOption, - changeset: DateCellContentChangeset, - _field_type: &FieldType, - field_rev: &FieldRevision, - expected: &str, - ) { - let encoded_data = type_option.apply_changeset(changeset, None).unwrap(); - assert_eq!( - expected.to_owned(), - decode_cell_data(encoded_data, type_option, field_rev) - ); - } - - fn assert_decode_timestamp( - timestamp: i64, - type_option: &DateTypeOption, - field_rev: &FieldRevision, - expected: &str, - ) { - let encoded_data = type_option - .apply_changeset( - DateCellContentChangeset { - date: Some(timestamp.to_string()), - time: None, - }, - None, - ) - .unwrap(); - - assert_eq!( - expected.to_owned(), - decode_cell_data(encoded_data, type_option, field_rev) - ); - } - - fn decode_cell_data>( - encoded_data: T, - type_option: &DateTypeOption, - field_rev: &FieldRevision, - ) -> String { - let decoded_data = type_option - .decode_cell_data(encoded_data, &FieldType::DateTime, field_rev) - .unwrap() - .parse::() - .unwrap(); - - if type_option.include_time { - format!("{}{}", decoded_data.date, decoded_data.time) - } else { - decoded_data.date - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs new file mode 100644 index 0000000000..5bb2b1c415 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs @@ -0,0 +1,272 @@ +#[cfg(test)] +mod tests { + use crate::entities::FieldType; + use crate::services::cell::{CellDataChangeset, CellDataOperation}; + use crate::services::field::*; + // use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat}; + use flowy_grid_data_model::revision::FieldRevision; + use strum::IntoEnumIterator; + + #[test] + fn date_type_option_invalid_input_test() { + let type_option = DateTypeOption::default(); + let field_type = FieldType::DateTime; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some("1e".to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_rev, + "", + ); + } + + #[test] + fn date_type_option_date_format_test() { + let mut type_option = DateTypeOption::default(); + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + for date_format in DateFormat::iter() { + type_option.date_format = date_format; + match date_format { + DateFormat::Friendly => { + assert_decode_timestamp(1647251762, &type_option, &field_rev, "Mar 14,2022"); + } + DateFormat::US => { + assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14"); + } + DateFormat::ISO => { + assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022-03-14"); + } + DateFormat::Local => { + assert_decode_timestamp(1647251762, &type_option, &field_rev, "2022/03/14"); + } + } + } + } + + #[test] + fn date_type_option_time_format_test() { + let mut type_option = DateTypeOption::default(); + let field_type = FieldType::DateTime; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + + for time_format in TimeFormat::iter() { + type_option.time_format = time_format; + type_option.include_time = true; + match time_format { + TimeFormat::TwentyFourHour => { + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_rev, + "May 27,2022", + ); + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(1653609600.to_string()), + time: Some("23:00".to_owned()), + }, + &field_type, + &field_rev, + "May 27,2022 23:00", + ); + } + TimeFormat::TwelveHour => { + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(1653609600.to_string()), + time: None, + }, + &field_type, + &field_rev, + "May 27,2022", + ); + // + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(1653609600.to_string()), + time: Some("".to_owned()), + }, + &field_type, + &field_rev, + "May 27,2022", + ); + + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(1653609600.to_string()), + time: Some("11:23 pm".to_owned()), + }, + &field_type, + &field_rev, + "May 27,2022 11:23 PM", + ); + } + } + } + } + + #[test] + fn date_type_option_apply_changeset_test() { + let mut type_option = DateTypeOption::new(); + let field_type = FieldType::DateTime; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + let date_timestamp = "1653609600".to_owned(); + + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_rev, + "May 27,2022", + ); + + type_option.include_time = true; + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(date_timestamp.clone()), + time: None, + }, + &field_type, + &field_rev, + "May 27,2022", + ); + + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(date_timestamp.clone()), + time: Some("1:00".to_owned()), + }, + &field_type, + &field_rev, + "May 27,2022 01:00", + ); + + type_option.time_format = TimeFormat::TwelveHour; + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(date_timestamp), + time: Some("1:00 am".to_owned()), + }, + &field_type, + &field_rev, + "May 27,2022 01:00 AM", + ); + } + + #[test] + #[should_panic] + fn date_type_option_apply_changeset_error_test() { + let mut type_option = DateTypeOption::new(); + type_option.include_time = true; + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let date_timestamp = "1653609600".to_owned(); + + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(date_timestamp.clone()), + time: Some("1:".to_owned()), + }, + &FieldType::DateTime, + &field_rev, + "May 27,2022 01:00", + ); + + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(date_timestamp), + time: Some("1:00".to_owned()), + }, + &FieldType::DateTime, + &field_rev, + "May 27,2022 01:00", + ); + } + + #[test] + #[should_panic] + fn date_type_option_twelve_hours_to_twenty_four_hours() { + let mut type_option = DateTypeOption::new(); + type_option.include_time = true; + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + let date_timestamp = "1653609600".to_owned(); + + assert_changeset_result( + &type_option, + DateCellChangesetPB { + date: Some(date_timestamp), + time: Some("1:00 am".to_owned()), + }, + &FieldType::DateTime, + &field_rev, + "May 27,2022 01:00", + ); + } + + fn assert_changeset_result( + type_option: &DateTypeOption, + changeset: DateCellChangesetPB, + _field_type: &FieldType, + field_rev: &FieldRevision, + expected: &str, + ) { + let changeset = CellDataChangeset(Some(changeset)); + let encoded_data = type_option.apply_changeset(changeset, None).unwrap(); + assert_eq!( + expected.to_owned(), + decode_cell_data(encoded_data, type_option, field_rev) + ); + } + + fn assert_decode_timestamp( + timestamp: i64, + type_option: &DateTypeOption, + field_rev: &FieldRevision, + expected: &str, + ) { + let s = serde_json::to_string(&DateCellChangesetPB { + date: Some(timestamp.to_string()), + time: None, + }) + .unwrap(); + let encoded_data = type_option.apply_changeset(s.into(), None).unwrap(); + + assert_eq!( + expected.to_owned(), + decode_cell_data(encoded_data, type_option, field_rev) + ); + } + + fn decode_cell_data(encoded_data: String, type_option: &DateTypeOption, field_rev: &FieldRevision) -> String { + let decoded_data = type_option + .decode_cell_data(encoded_data.into(), &FieldType::DateTime, field_rev) + .unwrap() + .with_parser(DateCellDataParser()) + .unwrap(); + + if type_option.include_time { + format!("{}{}", decoded_data.date, decoded_data.time) + } else { + decoded_data.date + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs new file mode 100644 index 0000000000..17b897b1f0 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs @@ -0,0 +1,195 @@ +use crate::entities::FieldType; +use crate::impl_type_option; +use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::field::{ + BoxTypeOptionBuilder, DateCellChangesetPB, DateCellDataPB, DateFormat, DateTimestamp, TimeFormat, TypeOptionBuilder, +}; +use bytes::Bytes; +use chrono::format::strftime::StrftimeItems; +use chrono::{NaiveDateTime, Timelike}; +use flowy_derive::ProtoBuf; +use flowy_error::{ErrorCode, FlowyError, FlowyResult}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use serde::{Deserialize, Serialize}; + +// Date +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct DateTypeOption { + #[pb(index = 1)] + pub date_format: DateFormat, + + #[pb(index = 2)] + pub time_format: TimeFormat, + + #[pb(index = 3)] + pub include_time: bool, +} +impl_type_option!(DateTypeOption, FieldType::DateTime); + +impl DateTypeOption { + #[allow(dead_code)] + pub fn new() -> Self { + Self::default() + } + + fn today_desc_from_timestamp>(&self, timestamp: T) -> DateCellDataPB { + let timestamp = *timestamp.as_ref(); + let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0); + self.date_from_native(native) + } + + fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellDataPB { + if native.timestamp() == 0 { + return DateCellDataPB::default(); + } + + let time = native.time(); + let has_time = time.hour() != 0 || time.second() != 0; + + let utc = self.utc_date_time_from_native(native); + let fmt = self.date_format.format_str(); + let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt))); + + let mut time = "".to_string(); + if has_time { + let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str()); + time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, ""); + } + + let timestamp = native.timestamp(); + DateCellDataPB { date, time, timestamp } + } + + fn date_fmt(&self, time: &Option) -> String { + if self.include_time { + match time.as_ref() { + None => self.date_format.format_str().to_string(), + Some(time_str) => { + if time_str.is_empty() { + self.date_format.format_str().to_string() + } else { + format!("{} {}", self.date_format.format_str(), self.time_format.format_str()) + } + } + } + } else { + self.date_format.format_str().to_string() + } + } + + fn timestamp_from_utc_with_time( + &self, + utc: &chrono::DateTime, + time: &Option, + ) -> FlowyResult { + if let Some(time_str) = time.as_ref() { + if !time_str.is_empty() { + let date_str = format!( + "{}{}", + utc.format_with_items(StrftimeItems::new(self.date_format.format_str())), + &time_str + ); + + return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) { + Ok(native) => { + let utc = self.utc_date_time_from_native(native); + Ok(utc.timestamp()) + } + Err(_e) => { + let msg = format!("Parse {} failed", date_str); + Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)) + } + }; + } + } + + Ok(utc.timestamp()) + } + + fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime { + let native = NaiveDateTime::from_timestamp(timestamp, 0); + self.utc_date_time_from_native(native) + } + + fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime { + chrono::DateTime::::from_utc(naive, chrono::Utc) + } +} + +impl CellDisplayable for DateTypeOption { + fn display_data( + &self, + cell_data: CellData, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult { + let timestamp = cell_data.try_into_inner()?; + let date_cell_data = self.today_desc_from_timestamp(timestamp); + CellBytes::from(date_cell_data) + } +} + +impl CellDataOperation for DateTypeOption { + fn decode_cell_data( + &self, + cell_data: CellData, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult { + // Return default data if the type_option_cell_data is not FieldType::DateTime. + // It happens when switching from one field to another. + // For example: + // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. + if !decoded_field_type.is_date() { + return Ok(CellBytes::default()); + } + self.display_data(cell_data, decoded_field_type, field_rev) + } + + fn apply_changeset( + &self, + changeset: CellDataChangeset, + _cell_rev: Option, + ) -> Result { + let changeset = changeset.try_into_inner()?; + let cell_data = match changeset.date_timestamp() { + None => 0, + Some(date_timestamp) => match (self.include_time, changeset.time) { + (true, Some(time)) => { + let time = Some(time.trim().to_uppercase()); + let utc = self.utc_date_time_from_timestamp(date_timestamp); + self.timestamp_from_utc_with_time(&utc, &time)? + } + _ => date_timestamp, + }, + }; + + Ok(cell_data.to_string()) + } +} + +#[derive(Default)] +pub struct DateTypeOptionBuilder(DateTypeOption); +impl_into_box_type_option_builder!(DateTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOption); + +impl DateTypeOptionBuilder { + pub fn date_format(mut self, date_format: DateFormat) -> Self { + self.0.date_format = date_format; + self + } + + pub fn time_format(mut self, time_format: TimeFormat) -> Self { + self.0.time_format = time_format; + self + } +} +impl TypeOptionBuilder for DateTypeOptionBuilder { + fn field_type(&self) -> FieldType { + FieldType::DateTime + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs new file mode 100644 index 0000000000..aa8fab221a --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs @@ -0,0 +1,210 @@ +use crate::entities::CellChangesetPB; +use crate::entities::{CellIdentifierParams, GridCellIdentifierPayloadPB}; +use crate::services::cell::{CellBytesParser, FromCellChangeset, FromCellString}; +use bytes::Bytes; + +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::{internal_error, ErrorCode, FlowyResult}; + +use serde::{Deserialize, Serialize}; +use strum_macros::EnumIter; + +#[derive(Clone, Debug, Default, ProtoBuf)] +pub struct DateCellDataPB { + #[pb(index = 1)] + pub date: String, + + #[pb(index = 2)] + pub time: String, + + #[pb(index = 3)] + pub timestamp: i64, +} + +#[derive(Clone, Debug, Default, ProtoBuf)] +pub struct DateChangesetPayloadPB { + #[pb(index = 1)] + pub cell_identifier: GridCellIdentifierPayloadPB, + + #[pb(index = 2, one_of)] + pub date: Option, + + #[pb(index = 3, one_of)] + pub time: Option, +} + +pub struct DateChangesetParams { + pub cell_identifier: CellIdentifierParams, + pub date: Option, + pub time: Option, +} + +impl TryInto for DateChangesetPayloadPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let cell_identifier: CellIdentifierParams = self.cell_identifier.try_into()?; + Ok(DateChangesetParams { + cell_identifier, + date: self.date, + time: self.time, + }) + } +} + +impl std::convert::From for CellChangesetPB { + fn from(params: DateChangesetParams) -> Self { + let changeset = DateCellChangesetPB { + date: params.date, + time: params.time, + }; + let s = serde_json::to_string(&changeset).unwrap(); + CellChangesetPB { + grid_id: params.cell_identifier.grid_id, + row_id: params.cell_identifier.row_id, + field_id: params.cell_identifier.field_id, + content: Some(s), + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct DateCellChangesetPB { + pub date: Option, + pub time: Option, +} + +impl DateCellChangesetPB { + pub fn date_timestamp(&self) -> Option { + if let Some(date) = &self.date { + match date.parse::() { + Ok(date_timestamp) => Some(date_timestamp), + Err(_) => None, + } + } else { + None + } + } +} + +impl FromCellChangeset for DateCellChangesetPB { + fn from_changeset(changeset: String) -> FlowyResult + where + Self: Sized, + { + serde_json::from_str::(&changeset).map_err(internal_error) + } +} +pub struct DateTimestamp(i64); +impl AsRef for DateTimestamp { + fn as_ref(&self) -> &i64 { + &self.0 + } +} + +impl std::convert::From for i64 { + fn from(timestamp: DateTimestamp) -> Self { + timestamp.0 + } +} + +impl FromCellString for DateTimestamp { + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + let num = s.parse::().unwrap_or(0); + Ok(DateTimestamp(num)) + } +} + +#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)] +pub enum DateFormat { + Local = 0, + US = 1, + ISO = 2, + Friendly = 3, +} +impl std::default::Default for DateFormat { + fn default() -> Self { + DateFormat::Friendly + } +} + +impl std::convert::From for DateFormat { + fn from(value: i32) -> Self { + match value { + 0 => DateFormat::Local, + 1 => DateFormat::US, + 2 => DateFormat::ISO, + 3 => DateFormat::Friendly, + _ => { + tracing::error!("Unsupported date format, fallback to friendly"); + DateFormat::Friendly + } + } + } +} + +impl DateFormat { + pub fn value(&self) -> i32 { + *self as i32 + } + // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html + pub fn format_str(&self) -> &'static str { + match self { + DateFormat::Local => "%Y/%m/%d", + DateFormat::US => "%Y/%m/%d", + DateFormat::ISO => "%Y-%m-%d", + DateFormat::Friendly => "%b %d,%Y", + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)] +pub enum TimeFormat { + TwelveHour = 0, + TwentyFourHour = 1, +} + +impl std::convert::From for TimeFormat { + fn from(value: i32) -> Self { + match value { + 0 => TimeFormat::TwelveHour, + 1 => TimeFormat::TwentyFourHour, + _ => { + tracing::error!("Unsupported time format, fallback to TwentyFourHour"); + TimeFormat::TwentyFourHour + } + } + } +} + +impl TimeFormat { + pub fn value(&self) -> i32 { + *self as i32 + } + + // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html + pub fn format_str(&self) -> &'static str { + match self { + TimeFormat::TwelveHour => "%I:%M %p", + TimeFormat::TwentyFourHour => "%R", + } + } +} + +impl std::default::Default for TimeFormat { + fn default() -> Self { + TimeFormat::TwentyFourHour + } +} + +pub struct DateCellDataParser(); +impl CellBytesParser for DateCellDataParser { + type Object = DateCellDataPB; + + fn parse(&self, bytes: &Bytes) -> FlowyResult { + DateCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs new file mode 100644 index 0000000000..395f2c9104 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs @@ -0,0 +1,7 @@ +#![allow(clippy::module_inception)] +mod date_tests; +mod date_type_option; +mod date_type_option_entities; + +pub use date_type_option::*; +pub use date_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs index 3cfe390b38..32fd9ef0a4 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/mod.rs @@ -1,9 +1,9 @@ -mod checkbox_type_option; -mod date_type_option; -mod number_type_option; -mod selection_type_option; -mod text_type_option; -mod url_type_option; +pub mod checkbox_type_option; +pub mod date_type_option; +pub mod number_type_option; +pub mod selection_type_option; +pub mod text_type_option; +pub mod url_type_option; mod util; pub use checkbox_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs index fffbad97bf..4b2bcc1ecd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs @@ -1,6 +1,9 @@ #![allow(clippy::module_inception)] mod format; +mod number_tests; mod number_type_option; +mod number_type_option_entities; pub use format::*; pub use number_type_option::*; +pub use number_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs new file mode 100644 index 0000000000..6ea1e8f302 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs @@ -0,0 +1,139 @@ +#[cfg(test)] +mod tests { + use crate::entities::FieldType; + use crate::services::cell::CellDataOperation; + use crate::services::field::FieldBuilder; + use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOption}; + use flowy_grid_data_model::revision::FieldRevision; + use strum::IntoEnumIterator; + + #[test] + fn number_type_option_invalid_input_test() { + let type_option = NumberTypeOption::default(); + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_equal(&type_option, "", "", &field_type, &field_rev); + assert_equal(&type_option, "abc", "", &field_type, &field_rev); + } + + #[test] + fn number_type_option_strip_symbol_test() { + let mut type_option = NumberTypeOption::new(); + type_option.format = NumberFormat::USD; + assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned()); + + type_option.format = NumberFormat::Yuan; + assert_eq!(strip_currency_symbol("$0.2"), "0.2".to_owned()); + } + + #[test] + fn number_type_option_format_number_test() { + let mut type_option = NumberTypeOption::default(); + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Num => { + assert_equal(&type_option, "18443", "18443", &field_type, &field_rev); + } + NumberFormat::USD => { + assert_equal(&type_option, "18443", "$18,443", &field_type, &field_rev); + } + NumberFormat::Yen => { + assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_rev); + } + NumberFormat::Yuan => { + assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_rev); + } + NumberFormat::EUR => { + assert_equal(&type_option, "18443", "€18.443", &field_type, &field_rev); + } + _ => {} + } + } + } + + #[test] + fn number_type_option_format_str_test() { + let mut type_option = NumberTypeOption::default(); + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Num => { + assert_equal(&type_option, "18443", "18443", &field_type, &field_rev); + assert_equal(&type_option, "0.2", "0.2", &field_type, &field_rev); + } + NumberFormat::USD => { + assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_rev); + assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_rev); + assert_equal(&type_option, "", "", &field_type, &field_rev); + assert_equal(&type_option, "abc", "", &field_type, &field_rev); + } + NumberFormat::Yen => { + assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev); + assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_rev); + } + NumberFormat::Yuan => { + assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_rev); + assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev); + } + NumberFormat::EUR => { + assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_rev); + assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_rev); + assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_rev); + } + _ => {} + } + } + } + + #[test] + fn number_description_sign_test() { + let mut type_option = NumberTypeOption { + sign_positive: false, + ..Default::default() + }; + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Num => { + assert_equal(&type_option, "18443", "18443", &field_type, &field_rev); + } + NumberFormat::USD => { + assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_rev); + } + NumberFormat::Yen => { + assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_rev); + } + NumberFormat::EUR => { + assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_rev); + } + _ => {} + } + } + } + + fn assert_equal( + type_option: &NumberTypeOption, + cell_data: &str, + expected_str: &str, + field_type: &FieldType, + field_rev: &FieldRevision, + ) { + assert_eq!( + type_option + .decode_cell_data(cell_data.to_owned().into(), field_type, field_rev) + .unwrap() + .to_string(), + expected_str.to_owned() + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs index f912879ad9..26d1d64248 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs @@ -1,14 +1,15 @@ +use crate::entities::FieldType; use crate::impl_type_option; - -use crate::entities::{FieldType, GridNumberFilter}; +use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation}; use crate::services::field::type_options::number_type_option::format::*; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData}; +use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBuilder}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; + use rust_decimal::Decimal; + use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -74,24 +75,13 @@ impl NumberTypeOption { Self::default() } - fn cell_content_from_number_str(&self, s: &str) -> FlowyResult { + pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult { match self.format { - NumberFormat::Num => { - if let Ok(v) = s.parse::() { - return Ok(v.to_string()); - } - - if let Ok(v) = s.parse::() { - return Ok(v.to_string()); - } - - Ok("".to_string()) - } - NumberFormat::Percent => { - let content = s.parse::().map_or(String::new(), |v| v.to_string()); - Ok(content) - } - _ => self.money_from_number_str(s), + NumberFormat::Num | NumberFormat::Percent => match Decimal::from_str(s) { + Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), + Err(_) => Ok(NumberCellData::new()), + }, + _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format), } } @@ -99,97 +89,45 @@ impl NumberTypeOption { self.format = format; self.symbol = format.symbol(); } - - fn money_from_number_str(&self, s: &str) -> FlowyResult { - let mut number = self.strip_currency_symbol(s); - - if s.is_empty() { - return Ok("".to_string()); - } - - match Decimal::from_str(&number) { - Ok(mut decimal) => { - decimal.set_sign_positive(self.sign_positive); - let money = rusty_money::Money::from_decimal(decimal, self.format.currency()).to_string(); - Ok(money) - } - Err(_) => match rusty_money::Money::from_str(&number, self.format.currency()) { - Ok(money) => Ok(money.to_string()), - Err(_) => { - number.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); - if number.chars().all(char::is_numeric) { - self.money_from_number_str(&number) - } else { - Err(FlowyError::invalid_data().context("Should only contain numbers")) - } - } - }, - } - } - - fn strip_currency_symbol(&self, s: T) -> String { - let mut s = s.to_string(); - for symbol in CURRENCY_SYMBOL.iter() { - if s.starts_with(symbol) { - s = s.strip_prefix(symbol).unwrap_or("").to_string(); - break; - } - } - s - } } -impl CellDataOperation for NumberTypeOption { - fn decode_cell_data( +pub(crate) fn strip_currency_symbol(s: T) -> String { + let mut s = s.to_string(); + for symbol in CURRENCY_SYMBOL.iter() { + if s.starts_with(symbol) { + s = s.strip_prefix(symbol).unwrap_or("").to_string(); + break; + } + } + s +} + +impl CellDataOperation for NumberTypeOption { + fn decode_cell_data( &self, - encoded_data: T, + cell_data: CellData, decoded_field_type: &FieldType, _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { + ) -> FlowyResult { if decoded_field_type.is_date() { - return Ok(DecodedCellData::default()); + return Ok(CellBytes::default()); } - let cell_data = encoded_data.into(); - match self.format { - NumberFormat::Num => { - if let Ok(v) = cell_data.parse::() { - return Ok(DecodedCellData::new(v.to_string())); - } - - if let Ok(v) = cell_data.parse::() { - return Ok(DecodedCellData::new(v.to_string())); - } - - Ok(DecodedCellData::default()) - } - NumberFormat::Percent => { - let content = cell_data.parse::().map_or(String::new(), |v| v.to_string()); - Ok(DecodedCellData::new(content)) - } - _ => { - let content = self - .money_from_number_str(&cell_data) - .unwrap_or_else(|_| "".to_string()); - Ok(DecodedCellData::new(content)) - } + let cell_data: String = cell_data.try_into_inner()?; + match self.format_cell_data(&cell_data) { + Ok(num) => Ok(CellBytes::new(num.to_string())), + Err(_) => Ok(CellBytes::default()), } } - fn apply_filter(&self, _filter: GridNumberFilter) -> bool { - todo!() - } - - fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result - where - C: Into, - { - let changeset = changeset.into(); + fn apply_changeset( + &self, + changeset: CellDataChangeset, + _cell_rev: Option, + ) -> Result { + let changeset = changeset.try_into_inner()?; let data = changeset.trim().to_string(); - let _ = self.cell_content_from_number_str(&data)?; + let _ = self.format_cell_data(&data)?; Ok(data) } } @@ -207,143 +145,3 @@ impl std::default::Default for NumberTypeOption { } } } - -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::field::FieldBuilder; - use crate::services::field::{NumberFormat, NumberTypeOption}; - use crate::services::row::CellDataOperation; - use flowy_grid_data_model::revision::FieldRevision; - use strum::IntoEnumIterator; - - #[test] - fn number_type_option_invalid_input_test() { - let type_option = NumberTypeOption::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_equal(&type_option, "", "", &field_type, &field_rev); - assert_equal(&type_option, "abc", "", &field_type, &field_rev); - } - - #[test] - fn number_type_option_strip_symbol_test() { - let mut type_option = NumberTypeOption::new(); - type_option.format = NumberFormat::USD; - assert_eq!(type_option.strip_currency_symbol("$18,443"), "18,443".to_owned()); - - type_option.format = NumberFormat::Yuan; - assert_eq!(type_option.strip_currency_symbol("$0.2"), "0.2".to_owned()); - } - - #[test] - fn number_type_option_format_number_test() { - let mut type_option = NumberTypeOption::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_equal(&type_option, "18443", "18443", &field_type, &field_rev); - } - NumberFormat::USD => { - assert_equal(&type_option, "18443", "$18,443", &field_type, &field_rev); - } - NumberFormat::Yen => { - assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_rev); - } - NumberFormat::Yuan => { - assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_rev); - } - NumberFormat::EUR => { - assert_equal(&type_option, "18443", "€18.443", &field_type, &field_rev); - } - _ => {} - } - } - } - - #[test] - fn number_type_option_format_str_test() { - let mut type_option = NumberTypeOption::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_equal(&type_option, "18443", "18443", &field_type, &field_rev); - assert_equal(&type_option, "0.2", "0.2", &field_type, &field_rev); - } - NumberFormat::USD => { - assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_rev); - assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_rev); - assert_equal(&type_option, "", "", &field_type, &field_rev); - assert_equal(&type_option, "abc", "", &field_type, &field_rev); - } - NumberFormat::Yen => { - assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev); - assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_rev); - } - NumberFormat::Yuan => { - assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_rev); - assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev); - } - NumberFormat::EUR => { - assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_rev); - assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_rev); - assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_rev); - } - _ => {} - } - } - } - - #[test] - fn number_description_sign_test() { - let mut type_option = NumberTypeOption { - sign_positive: false, - ..Default::default() - }; - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_equal(&type_option, "18443", "18443", &field_type, &field_rev); - } - NumberFormat::USD => { - assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_rev); - } - NumberFormat::Yen => { - assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_rev); - } - NumberFormat::EUR => { - assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_rev); - } - _ => {} - } - } - } - - fn assert_equal( - type_option: &NumberTypeOption, - cell_data: &str, - expected_str: &str, - field_type: &FieldType, - field_rev: &FieldRevision, - ) { - assert_eq!( - type_option - .decode_cell_data(cell_data, field_type, field_rev) - .unwrap() - .to_string(), - expected_str.to_owned() - ); - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option_entities.rs new file mode 100644 index 0000000000..6297114a07 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option_entities.rs @@ -0,0 +1,105 @@ +use crate::services::cell::CellBytesParser; +use crate::services::field::number_currency::Currency; +use crate::services::field::{strip_currency_symbol, NumberFormat, STRIP_SYMBOL}; +use bytes::Bytes; +use flowy_error::{FlowyError, FlowyResult}; +use rust_decimal::Decimal; +use rusty_money::Money; +use std::str::FromStr; + +#[derive(Default)] +pub struct NumberCellData { + decimal: Option, + money: Option, +} + +impl NumberCellData { + pub fn new() -> Self { + Self { + decimal: Default::default(), + money: None, + } + } + + pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult { + let mut num_str = strip_currency_symbol(s); + let currency = format.currency(); + if num_str.is_empty() { + return Ok(Self::default()); + } + match Decimal::from_str(&num_str) { + Ok(mut decimal) => { + decimal.set_sign_positive(sign_positive); + let money = Money::from_decimal(decimal, currency); + Ok(Self::from_money(money)) + } + Err(_) => match Money::from_str(&num_str, currency) { + Ok(money) => Ok(NumberCellData::from_money(money)), + Err(_) => { + num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); + if num_str.chars().all(char::is_numeric) { + Self::from_format_str(&num_str, sign_positive, format) + } else { + Err(FlowyError::invalid_data().context("Should only contain numbers")) + } + } + }, + } + } + + pub fn from_decimal(decimal: Decimal) -> Self { + Self { + decimal: Some(decimal), + money: None, + } + } + + pub fn from_money(money: Money) -> Self { + Self { + decimal: Some(*money.amount()), + money: Some(money.to_string()), + } + } + + pub fn decimal(&self) -> &Option { + &self.decimal + } + + pub fn is_empty(&self) -> bool { + self.decimal.is_none() + } +} + +// impl FromStr for NumberCellData { +// type Err = FlowyError; +// +// fn from_str(s: &str) -> Result { +// if s.is_empty() { +// return Ok(Self::default()); +// } +// let decimal = Decimal::from_str(s).map_err(internal_error)?; +// Ok(Self::from_decimal(decimal)) +// } +// } + +impl ToString for NumberCellData { + fn to_string(&self) -> String { + match &self.money { + None => match self.decimal { + None => String::default(), + Some(decimal) => decimal.to_string(), + }, + Some(money) => money.to_string(), + } + } +} +pub struct NumberCellDataParser(pub NumberFormat); +impl CellBytesParser for NumberCellDataParser { + type Object = NumberCellData; + fn parse(&self, bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => NumberCellData::from_format_str(&s, true, &self.0), + Err(_) => Ok(NumberCellData::default()), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs deleted file mode 100644 index f4e0bd24e2..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs +++ /dev/null @@ -1,648 +0,0 @@ -use crate::entities::{CellChangeset, FieldType, GridSelectOptionFilter}; -use crate::entities::{CellIdentifier, CellIdentifierPayload}; -use crate::impl_type_option; -use crate::services::field::type_options::util::get_cell_data; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData}; -use bytes::Bytes; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -pub const SELECTION_IDS_SEPARATOR: &str = ","; - -pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync { - fn insert_option(&mut self, new_option: SelectOption) { - let options = self.mut_options(); - if let Some(index) = options - .iter() - .position(|option| option.id == new_option.id || option.name == new_option.name) - { - options.remove(index); - options.insert(index, new_option); - } else { - options.insert(0, new_option); - } - } - - fn delete_option(&mut self, delete_option: SelectOption) { - let options = self.mut_options(); - if let Some(index) = options.iter().position(|option| option.id == delete_option.id) { - options.remove(index); - } - } - - fn create_option(&self, name: &str) -> SelectOption { - let color = select_option_color_from_index(self.options().len()); - SelectOption::with_color(name, color) - } - - fn select_option_cell_data(&self, cell_rev: &Option) -> SelectOptionCellData; - - fn options(&self) -> &Vec; - - fn mut_options(&mut self) -> &mut Vec; -} - -pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult> { - let field_type: FieldType = field_rev.field_type_rev.into(); - match &field_type { - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOption::from(field_rev); - Ok(Box::new(type_option)) - } - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOption::from(field_rev); - Ok(Box::new(type_option)) - } - ty => { - tracing::error!("Unsupported field type: {:?} for this handler", ty); - Err(ErrorCode::FieldInvalidOperation.into()) - } - } -} - -// Single select -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct SingleSelectTypeOption { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub disable_color: bool, -} -impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect); - -impl SelectOptionOperation for SingleSelectTypeOption { - fn select_option_cell_data(&self, cell_rev: &Option) -> SelectOptionCellData { - let select_options = make_select_context_from(cell_rev, &self.options); - SelectOptionCellData { - options: self.options.clone(), - select_options, - } - } - - fn options(&self) -> &Vec { - &self.options - } - - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } -} - -impl CellDataOperation for SingleSelectTypeOption { - fn decode_cell_data( - &self, - encoded_data: T, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { - if !decoded_field_type.is_select_option() { - return Ok(DecodedCellData::default()); - } - - let encoded_data = encoded_data.into(); - let mut cell_data = SelectOptionCellData { - options: self.options.clone(), - select_options: vec![], - }; - if let Some(option_id) = select_option_ids(encoded_data).first() { - if let Some(option) = self.options.iter().find(|option| &option.id == option_id) { - cell_data.select_options.push(option.clone()); - } - } - - DecodedCellData::try_from_bytes(cell_data) - } - - fn apply_filter(&self, _filter: GridSelectOptionFilter) -> bool { - todo!() - } - - fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result - where - C: Into, - { - let changeset = changeset.into(); - let select_option_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset)?; - let new_cell_data: String; - if let Some(insert_option_id) = select_option_changeset.insert_option_id { - tracing::trace!("Insert single select option: {}", &insert_option_id); - new_cell_data = insert_option_id; - } else { - tracing::trace!("Delete single select option"); - new_cell_data = "".to_string() - } - - Ok(new_cell_data) - } -} - -#[derive(Default)] -pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOption); -impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOption); - -impl SingleSelectTypeOptionBuilder { - pub fn option(mut self, opt: SelectOption) -> Self { - self.0.options.push(opt); - self - } -} - -impl TypeOptionBuilder for SingleSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::SingleSelect - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -// Multiple select -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct MultiSelectTypeOption { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub disable_color: bool, -} -impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect); - -impl SelectOptionOperation for MultiSelectTypeOption { - fn select_option_cell_data(&self, cell_rev: &Option) -> SelectOptionCellData { - let select_options = make_select_context_from(cell_rev, &self.options); - SelectOptionCellData { - options: self.options.clone(), - select_options, - } - } - - fn options(&self) -> &Vec { - &self.options - } - - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } -} - -impl CellDataOperation for MultiSelectTypeOption { - fn decode_cell_data( - &self, - encoded_data: T, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { - if !decoded_field_type.is_select_option() { - return Ok(DecodedCellData::default()); - } - - let encoded_data = encoded_data.into(); - let select_options = select_option_ids(encoded_data) - .into_iter() - .flat_map(|option_id| self.options.iter().find(|option| option.id == option_id).cloned()) - .collect::>(); - - let cell_data = SelectOptionCellData { - options: self.options.clone(), - select_options, - }; - - DecodedCellData::try_from_bytes(cell_data) - } - - fn apply_filter(&self, _filter: GridSelectOptionFilter) -> bool { - todo!() - } - - fn apply_changeset(&self, changeset: T, cell_rev: Option) -> Result - where - T: Into, - { - let content_changeset: SelectOptionCellContentChangeset = serde_json::from_str(&changeset.into())?; - let new_cell_data: String; - match cell_rev { - None => { - new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned()); - } - Some(cell_rev) => { - let cell_data = get_cell_data(&cell_rev); - let mut selected_options = select_option_ids(cell_data); - if let Some(insert_option_id) = content_changeset.insert_option_id { - tracing::trace!("Insert multi select option: {}", &insert_option_id); - if selected_options.contains(&insert_option_id) { - selected_options.retain(|id| id != &insert_option_id); - } else { - selected_options.push(insert_option_id); - } - } - - if let Some(delete_option_id) = content_changeset.delete_option_id { - tracing::trace!("Delete multi select option: {}", &delete_option_id); - selected_options.retain(|id| id != &delete_option_id); - } - - new_cell_data = selected_options.join(SELECTION_IDS_SEPARATOR); - tracing::trace!("Multi select cell data: {}", &new_cell_data); - } - } - - Ok(new_cell_data) - } -} - -#[derive(Default)] -pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOption); -impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption); -impl MultiSelectTypeOptionBuilder { - pub fn option(mut self, opt: SelectOption) -> Self { - self.0.options.push(opt); - self - } -} - -impl TypeOptionBuilder for MultiSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::MultiSelect - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -fn select_option_ids(data: String) -> Vec { - data.split(SELECTION_IDS_SEPARATOR) - .map(|id| id.to_string()) - .collect::>() -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] -pub struct SelectOption { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub name: String, - - #[pb(index = 3)] - pub color: SelectOptionColor, -} - -impl SelectOption { - pub fn new(name: &str) -> Self { - SelectOption { - id: nanoid!(4), - name: name.to_owned(), - color: SelectOptionColor::default(), - } - } - - pub fn with_color(name: &str, color: SelectOptionColor) -> Self { - SelectOption { - id: nanoid!(4), - name: name.to_owned(), - color, - } - } -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionChangesetPayload { - #[pb(index = 1)] - pub cell_identifier: CellIdentifierPayload, - - #[pb(index = 2, one_of)] - pub insert_option: Option, - - #[pb(index = 3, one_of)] - pub update_option: Option, - - #[pb(index = 4, one_of)] - pub delete_option: Option, -} - -pub struct SelectOptionChangeset { - pub cell_identifier: CellIdentifier, - pub insert_option: Option, - pub update_option: Option, - pub delete_option: Option, -} - -impl TryInto for SelectOptionChangesetPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier = self.cell_identifier.try_into()?; - Ok(SelectOptionChangeset { - cell_identifier, - insert_option: self.insert_option, - update_option: self.update_option, - delete_option: self.delete_option, - }) - } -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionCellChangesetPayload { - #[pb(index = 1)] - pub cell_identifier: CellIdentifierPayload, - - #[pb(index = 2, one_of)] - pub insert_option_id: Option, - - #[pb(index = 3, one_of)] - pub delete_option_id: Option, -} - -pub struct SelectOptionCellChangesetParams { - pub cell_identifier: CellIdentifier, - pub insert_option_id: Option, - pub delete_option_id: Option, -} - -impl std::convert::From for CellChangeset { - fn from(params: SelectOptionCellChangesetParams) -> Self { - let changeset = SelectOptionCellContentChangeset { - insert_option_id: params.insert_option_id, - delete_option_id: params.delete_option_id, - }; - let s = serde_json::to_string(&changeset).unwrap(); - CellChangeset { - grid_id: params.cell_identifier.grid_id, - row_id: params.cell_identifier.row_id, - field_id: params.cell_identifier.field_id, - cell_content_changeset: Some(s), - } - } -} - -impl TryInto for SelectOptionCellChangesetPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier: CellIdentifier = self.cell_identifier.try_into()?; - let insert_option_id = match self.insert_option_id { - None => None, - Some(insert_option_id) => Some( - NotEmptyStr::parse(insert_option_id) - .map_err(|_| ErrorCode::OptionIdIsEmpty)? - .0, - ), - }; - - let delete_option_id = match self.delete_option_id { - None => None, - Some(delete_option_id) => Some( - NotEmptyStr::parse(delete_option_id) - .map_err(|_| ErrorCode::OptionIdIsEmpty)? - .0, - ), - }; - - Ok(SelectOptionCellChangesetParams { - cell_identifier, - insert_option_id, - delete_option_id, - }) - } -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct SelectOptionCellContentChangeset { - pub insert_option_id: Option, - pub delete_option_id: Option, -} - -impl SelectOptionCellContentChangeset { - pub fn from_insert(option_id: &str) -> Self { - SelectOptionCellContentChangeset { - insert_option_id: Some(option_id.to_string()), - delete_option_id: None, - } - } - - pub fn from_delete(option_id: &str) -> Self { - SelectOptionCellContentChangeset { - insert_option_id: None, - delete_option_id: Some(option_id.to_string()), - } - } - - pub fn to_str(&self) -> String { - serde_json::to_string(self).unwrap() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct SelectOptionCellData { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub select_options: Vec, -} - -#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] -#[repr(u8)] -pub enum SelectOptionColor { - Purple = 0, - Pink = 1, - LightPink = 2, - Orange = 3, - Yellow = 4, - Lime = 5, - Green = 6, - Aqua = 7, - Blue = 8, -} - -pub fn select_option_color_from_index(index: usize) -> SelectOptionColor { - match index % 8 { - 0 => SelectOptionColor::Purple, - 1 => SelectOptionColor::Pink, - 2 => SelectOptionColor::LightPink, - 3 => SelectOptionColor::Orange, - 4 => SelectOptionColor::Yellow, - 5 => SelectOptionColor::Lime, - 6 => SelectOptionColor::Green, - 7 => SelectOptionColor::Aqua, - 8 => SelectOptionColor::Blue, - _ => SelectOptionColor::Purple, - } -} - -impl std::default::Default for SelectOptionColor { - fn default() -> Self { - SelectOptionColor::Purple - } -} - -fn make_select_context_from(cell_rev: &Option, options: &[SelectOption]) -> Vec { - match cell_rev { - None => vec![], - Some(cell_rev) => { - if let Ok(type_option_cell_data) = TypeOptionCellData::from_str(&cell_rev.data) { - select_option_ids(type_option_cell_data.data) - .into_iter() - .flat_map(|option_id| options.iter().find(|option| option.id == option_id).cloned()) - .collect() - } else { - vec![] - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::field::FieldBuilder; - use crate::services::field::{ - MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset, - SelectOptionCellData, SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR, - }; - use crate::services::row::CellDataOperation; - use flowy_grid_data_model::revision::FieldRevision; - - #[test] - fn single_select_test() { - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); - let twitter_option = SelectOption::new("Twitter"); - let single_select = SingleSelectTypeOptionBuilder::default() - .option(google_option.clone()) - .option(facebook_option.clone()) - .option(twitter_option); - - let field_rev = FieldBuilder::new(single_select) - .name("Platform") - .visibility(true) - .build(); - - let type_option = SingleSelectTypeOption::from(&field_rev); - - let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); - let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option.clone()]); - - let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option]); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) - .unwrap(); - assert_single_select_options(cell_data, &type_option, &field_rev, vec![]); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None) - .unwrap(); - - assert_single_select_options(cell_data, &type_option, &field_rev, vec![]); - - // Invalid changeset - assert!(type_option.apply_changeset("123", None).is_err()); - } - - #[test] - fn multi_select_test() { - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); - let twitter_option = SelectOption::new("Twitter"); - let multi_select = MultiSelectTypeOptionBuilder::default() - .option(google_option.clone()) - .option(facebook_option.clone()) - .option(twitter_option); - - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - let type_option = MultiSelectTypeOption::from(&field_rev); - - let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); - let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_multi_select_options( - cell_data, - &type_option, - &field_rev, - vec![google_option.clone(), facebook_option], - ); - - let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str(); - let cell_data = type_option.apply_changeset(data, None).unwrap(); - assert_multi_select_options(cell_data, &type_option, &field_rev, vec![google_option]); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None) - .unwrap(); - assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]); - - // Invalid option id - let cell_data = type_option - .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None) - .unwrap(); - assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]); - - // Invalid changeset - assert!(type_option.apply_changeset("123", None).is_err()); - } - - fn assert_multi_select_options( - cell_data: String, - type_option: &MultiSelectTypeOption, - field_rev: &FieldRevision, - expected: Vec, - ) { - let field_type: FieldType = field_rev.field_type_rev.into(); - assert_eq!( - expected, - type_option - .decode_cell_data(cell_data, &field_type, field_rev) - .unwrap() - .parse::() - .unwrap() - .select_options, - ); - } - - fn assert_single_select_options( - cell_data: String, - type_option: &SingleSelectTypeOption, - field_rev: &FieldRevision, - expected: Vec, - ) { - let field_type: FieldType = field_rev.field_type_rev.into(); - assert_eq!( - expected, - type_option - .decode_cell_data(cell_data, &field_type, field_rev) - .unwrap() - .parse::() - .unwrap() - .select_options, - ); - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs new file mode 100644 index 0000000000..45d2b96e83 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs @@ -0,0 +1,7 @@ +mod multi_select_type_option; +mod select_option; +mod single_select_type_option; + +pub use multi_select_type_option::*; +pub use select_option::*; +pub use single_select_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs new file mode 100644 index 0000000000..187b81d060 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs @@ -0,0 +1,188 @@ +use crate::entities::FieldType; +use crate::impl_type_option; +use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::field::type_options::util::get_cell_data; +use crate::services::field::{ + make_selected_select_options, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, + SelectOptionIds, SelectOptionOperation, SelectOptionPB, TypeOptionBuilder, SELECTION_IDS_SEPARATOR, +}; +use bytes::Bytes; +use flowy_derive::ProtoBuf; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use serde::{Deserialize, Serialize}; + +// Multiple select +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct MultiSelectTypeOption { + #[pb(index = 1)] + pub options: Vec, + + #[pb(index = 2)] + pub disable_color: bool, +} +impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect); + +impl SelectOptionOperation for MultiSelectTypeOption { + fn selected_select_option(&self, cell_data: CellData) -> SelectOptionCellDataPB { + let select_options = make_selected_select_options(cell_data, &self.options); + SelectOptionCellDataPB { + options: self.options.clone(), + select_options, + } + } + + fn options(&self) -> &Vec { + &self.options + } + + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } +} + +impl CellDataOperation for MultiSelectTypeOption { + fn decode_cell_data( + &self, + cell_data: CellData, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult { + if !decoded_field_type.is_select_option() { + return Ok(CellBytes::default()); + } + + self.display_data(cell_data, decoded_field_type, field_rev) + } + + fn apply_changeset( + &self, + changeset: CellDataChangeset, + cell_rev: Option, + ) -> Result { + let content_changeset = changeset.try_into_inner()?; + let new_cell_data: String; + match cell_rev { + None => { + new_cell_data = content_changeset.insert_option_id.unwrap_or_else(|| "".to_owned()); + } + Some(cell_rev) => { + let cell_data = get_cell_data(&cell_rev); + let mut select_ids: SelectOptionIds = cell_data.into(); + if let Some(insert_option_id) = content_changeset.insert_option_id { + tracing::trace!("Insert multi select option: {}", &insert_option_id); + if select_ids.contains(&insert_option_id) { + select_ids.retain(|id| id != &insert_option_id); + } else { + select_ids.push(insert_option_id); + } + } + + if let Some(delete_option_id) = content_changeset.delete_option_id { + tracing::trace!("Delete multi select option: {}", &delete_option_id); + select_ids.retain(|id| id != &delete_option_id); + } + + new_cell_data = select_ids.join(SELECTION_IDS_SEPARATOR); + tracing::trace!("Multi select cell data: {}", &new_cell_data); + } + } + + Ok(new_cell_data) + } +} + +#[derive(Default)] +pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOption); +impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOption); +impl MultiSelectTypeOptionBuilder { + pub fn option(mut self, opt: SelectOptionPB) -> Self { + self.0.options.push(opt); + self + } +} + +impl TypeOptionBuilder for MultiSelectTypeOptionBuilder { + fn field_type(&self) -> FieldType { + FieldType::MultiSelect + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} +#[cfg(test)] +mod tests { + use crate::entities::FieldType; + use crate::services::cell::CellDataOperation; + use crate::services::field::type_options::selection_type_option::*; + use crate::services::field::FieldBuilder; + use crate::services::field::{MultiSelectTypeOption, MultiSelectTypeOptionBuilder}; + use flowy_grid_data_model::revision::FieldRevision; + + #[test] + fn multi_select_test() { + let google_option = SelectOptionPB::new("Google"); + let facebook_option = SelectOptionPB::new("Facebook"); + let twitter_option = SelectOptionPB::new("Twitter"); + let multi_select = MultiSelectTypeOptionBuilder::default() + .option(google_option.clone()) + .option(facebook_option.clone()) + .option(twitter_option); + + let field_rev = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + + let type_option = MultiSelectTypeOption::from(&field_rev); + + let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); + let data = SelectOptionCellChangeset::from_insert(&option_ids).to_str(); + let cell_data = type_option.apply_changeset(data.into(), None).unwrap(); + assert_multi_select_options( + cell_data, + &type_option, + &field_rev, + vec![google_option.clone(), facebook_option], + ); + + let data = SelectOptionCellChangeset::from_insert(&google_option.id).to_str(); + let cell_data = type_option.apply_changeset(data.into(), None).unwrap(); + assert_multi_select_options(cell_data, &type_option, &field_rev, vec![google_option]); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellChangeset::from_insert("").to_str().into(), None) + .unwrap(); + assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellChangeset::from_insert("123,456").to_str().into(), None) + .unwrap(); + assert_multi_select_options(cell_data, &type_option, &field_rev, vec![]); + + // Invalid changeset + assert!(type_option.apply_changeset("123".to_owned().into(), None).is_err()); + } + + fn assert_multi_select_options( + cell_data: String, + type_option: &MultiSelectTypeOption, + field_rev: &FieldRevision, + expected: Vec, + ) { + let field_type: FieldType = field_rev.field_type_rev.into(); + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data.into(), &field_type, field_rev) + .unwrap() + .with_parser(SelectOptionCellDataParser()) + .unwrap() + .select_options, + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs new file mode 100644 index 0000000000..dbffe79f56 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs @@ -0,0 +1,380 @@ +use crate::entities::{CellChangesetPB, CellIdentifierParams, FieldType, GridCellIdentifierPayloadPB}; +use crate::services::cell::{CellBytes, CellBytesParser, CellData, CellDisplayable, FromCellChangeset, FromCellString}; +use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOptionPB}; +use bytes::Bytes; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_error::{internal_error, ErrorCode, FlowyResult}; +use flowy_grid_data_model::parser::NotEmptyStr; +use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry}; +use nanoid::nanoid; +use serde::{Deserialize, Serialize}; + +pub const SELECTION_IDS_SEPARATOR: &str = ","; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] +pub struct SelectOptionPB { + #[pb(index = 1)] + pub id: String, + + #[pb(index = 2)] + pub name: String, + + #[pb(index = 3)] + pub color: SelectOptionColorPB, +} + +impl SelectOptionPB { + pub fn new(name: &str) -> Self { + SelectOptionPB { + id: nanoid!(4), + name: name.to_owned(), + color: SelectOptionColorPB::default(), + } + } + + pub fn with_color(name: &str, color: SelectOptionColorPB) -> Self { + SelectOptionPB { + id: nanoid!(4), + name: name.to_owned(), + color, + } + } +} + +#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] +#[repr(u8)] +pub enum SelectOptionColorPB { + Purple = 0, + Pink = 1, + LightPink = 2, + Orange = 3, + Yellow = 4, + Lime = 5, + Green = 6, + Aqua = 7, + Blue = 8, +} + +impl std::default::Default for SelectOptionColorPB { + fn default() -> Self { + SelectOptionColorPB::Purple + } +} + +pub fn make_selected_select_options( + cell_data: CellData, + options: &[SelectOptionPB], +) -> Vec { + if let Ok(ids) = cell_data.try_into_inner() { + ids.iter() + .flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned()) + .collect() + } else { + vec![] + } +} + +pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync { + fn insert_option(&mut self, new_option: SelectOptionPB) { + let options = self.mut_options(); + if let Some(index) = options + .iter() + .position(|option| option.id == new_option.id || option.name == new_option.name) + { + options.remove(index); + options.insert(index, new_option); + } else { + options.insert(0, new_option); + } + } + + fn delete_option(&mut self, delete_option: SelectOptionPB) { + let options = self.mut_options(); + if let Some(index) = options.iter().position(|option| option.id == delete_option.id) { + options.remove(index); + } + } + + fn create_option(&self, name: &str) -> SelectOptionPB { + let color = select_option_color_from_index(self.options().len()); + SelectOptionPB::with_color(name, color) + } + + fn selected_select_option(&self, cell_data: CellData) -> SelectOptionCellDataPB; + + fn options(&self) -> &Vec; + + fn mut_options(&mut self) -> &mut Vec; +} + +impl CellDisplayable for T +where + T: SelectOptionOperation, +{ + fn display_data( + &self, + cell_data: CellData, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult { + CellBytes::from(self.selected_select_option(cell_data)) + } +} + +pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult> { + let field_type: FieldType = field_rev.field_type_rev.into(); + match &field_type { + FieldType::SingleSelect => { + let type_option = SingleSelectTypeOptionPB::from(field_rev); + Ok(Box::new(type_option)) + } + FieldType::MultiSelect => { + let type_option = MultiSelectTypeOption::from(field_rev); + Ok(Box::new(type_option)) + } + ty => { + tracing::error!("Unsupported field type: {:?} for this handler", ty); + Err(ErrorCode::FieldInvalidOperation.into()) + } + } +} + +pub fn select_option_color_from_index(index: usize) -> SelectOptionColorPB { + match index % 8 { + 0 => SelectOptionColorPB::Purple, + 1 => SelectOptionColorPB::Pink, + 2 => SelectOptionColorPB::LightPink, + 3 => SelectOptionColorPB::Orange, + 4 => SelectOptionColorPB::Yellow, + 5 => SelectOptionColorPB::Lime, + 6 => SelectOptionColorPB::Green, + 7 => SelectOptionColorPB::Aqua, + 8 => SelectOptionColorPB::Blue, + _ => SelectOptionColorPB::Purple, + } +} +pub struct SelectOptionIds(Vec); + +impl SelectOptionIds { + pub fn into_inner(self) -> Vec { + self.0 + } +} + +impl FromCellString for SelectOptionIds { + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + Ok(Self::from(s.to_owned())) + } +} + +impl std::convert::From for SelectOptionIds { + fn from(s: String) -> Self { + let ids = s + .split(SELECTION_IDS_SEPARATOR) + .map(|id| id.to_string()) + .collect::>(); + Self(ids) + } +} + +impl std::convert::From> for SelectOptionIds { + fn from(s: Option) -> Self { + match s { + None => Self { 0: vec![] }, + Some(s) => Self::from(s), + } + } +} + +impl std::ops::Deref for SelectOptionIds { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for SelectOptionIds { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +pub struct SelectOptionIdsParser(); +impl CellBytesParser for SelectOptionIdsParser { + type Object = SelectOptionIds; + fn parse(&self, bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => Ok(SelectOptionIds::from(s)), + Err(_) => Ok(SelectOptionIds::from("".to_owned())), + } + } +} + +pub struct SelectOptionCellDataParser(); +impl CellBytesParser for SelectOptionCellDataParser { + type Object = SelectOptionCellDataPB; + + fn parse(&self, bytes: &Bytes) -> FlowyResult { + SelectOptionCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) + } +} + +#[derive(Clone, Debug, Default, ProtoBuf)] +pub struct SelectOptionCellChangesetPayloadPB { + #[pb(index = 1)] + pub cell_identifier: GridCellIdentifierPayloadPB, + + #[pb(index = 2, one_of)] + pub insert_option_id: Option, + + #[pb(index = 3, one_of)] + pub delete_option_id: Option, +} + +pub struct SelectOptionCellChangesetParams { + pub cell_identifier: CellIdentifierParams, + pub insert_option_id: Option, + pub delete_option_id: Option, +} + +impl std::convert::From for CellChangesetPB { + fn from(params: SelectOptionCellChangesetParams) -> Self { + let changeset = SelectOptionCellChangeset { + insert_option_id: params.insert_option_id, + delete_option_id: params.delete_option_id, + }; + let s = serde_json::to_string(&changeset).unwrap(); + CellChangesetPB { + grid_id: params.cell_identifier.grid_id, + row_id: params.cell_identifier.row_id, + field_id: params.cell_identifier.field_id, + content: Some(s), + } + } +} + +impl TryInto for SelectOptionCellChangesetPayloadPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let cell_identifier: CellIdentifierParams = self.cell_identifier.try_into()?; + let insert_option_id = match self.insert_option_id { + None => None, + Some(insert_option_id) => Some( + NotEmptyStr::parse(insert_option_id) + .map_err(|_| ErrorCode::OptionIdIsEmpty)? + .0, + ), + }; + + let delete_option_id = match self.delete_option_id { + None => None, + Some(delete_option_id) => Some( + NotEmptyStr::parse(delete_option_id) + .map_err(|_| ErrorCode::OptionIdIsEmpty)? + .0, + ), + }; + + Ok(SelectOptionCellChangesetParams { + cell_identifier, + insert_option_id, + delete_option_id, + }) + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct SelectOptionCellChangeset { + pub insert_option_id: Option, + pub delete_option_id: Option, +} + +impl FromCellChangeset for SelectOptionCellChangeset { + fn from_changeset(changeset: String) -> FlowyResult + where + Self: Sized, + { + serde_json::from_str::(&changeset).map_err(internal_error) + } +} + +impl SelectOptionCellChangeset { + pub fn from_insert(option_id: &str) -> Self { + SelectOptionCellChangeset { + insert_option_id: Some(option_id.to_string()), + delete_option_id: None, + } + } + + pub fn from_delete(option_id: &str) -> Self { + SelectOptionCellChangeset { + insert_option_id: None, + delete_option_id: Some(option_id.to_string()), + } + } + + pub fn to_str(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct SelectOptionCellDataPB { + #[pb(index = 1)] + pub options: Vec, + + #[pb(index = 2)] + pub select_options: Vec, +} + +#[derive(Clone, Debug, Default, ProtoBuf)] +pub struct SelectOptionChangesetPayloadPB { + #[pb(index = 1)] + pub cell_identifier: GridCellIdentifierPayloadPB, + + #[pb(index = 2, one_of)] + pub insert_option: Option, + + #[pb(index = 3, one_of)] + pub update_option: Option, + + #[pb(index = 4, one_of)] + pub delete_option: Option, +} + +pub struct SelectOptionChangeset { + pub cell_identifier: CellIdentifierParams, + pub insert_option: Option, + pub update_option: Option, + pub delete_option: Option, +} + +impl TryInto for SelectOptionChangesetPayloadPB { + type Error = ErrorCode; + + fn try_into(self) -> Result { + let cell_identifier = self.cell_identifier.try_into()?; + Ok(SelectOptionChangeset { + cell_identifier, + insert_option: self.insert_option, + update_option: self.update_option, + delete_option: self.delete_option, + }) + } +} + +pub struct SelectedSelectOptions { + pub(crate) options: Vec, +} + +impl std::convert::From for SelectedSelectOptions { + fn from(data: SelectOptionCellDataPB) -> Self { + Self { + options: data.select_options, + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs new file mode 100644 index 0000000000..553425222e --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs @@ -0,0 +1,170 @@ +use crate::entities::FieldType; +use crate::impl_type_option; +use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::field::{ + make_selected_select_options, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, + SelectOptionOperation, SelectOptionPB, +}; +use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; +use bytes::Bytes; +use flowy_derive::ProtoBuf; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use serde::{Deserialize, Serialize}; + +// Single select +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct SingleSelectTypeOptionPB { + #[pb(index = 1)] + pub options: Vec, + + #[pb(index = 2)] + pub disable_color: bool, +} +impl_type_option!(SingleSelectTypeOptionPB, FieldType::SingleSelect); + +impl SelectOptionOperation for SingleSelectTypeOptionPB { + fn selected_select_option(&self, cell_data: CellData) -> SelectOptionCellDataPB { + let mut select_options = make_selected_select_options(cell_data, &self.options); + // only keep option in single select + select_options.truncate(1); + SelectOptionCellDataPB { + options: self.options.clone(), + select_options, + } + } + + fn options(&self) -> &Vec { + &self.options + } + + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } +} + +impl CellDataOperation for SingleSelectTypeOptionPB { + fn decode_cell_data( + &self, + cell_data: CellData, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult { + if !decoded_field_type.is_select_option() { + return Ok(CellBytes::default()); + } + + self.display_data(cell_data, decoded_field_type, field_rev) + } + + fn apply_changeset( + &self, + changeset: CellDataChangeset, + _cell_rev: Option, + ) -> Result { + let select_option_changeset = changeset.try_into_inner()?; + let new_cell_data: String; + if let Some(insert_option_id) = select_option_changeset.insert_option_id { + tracing::trace!("Insert single select option: {}", &insert_option_id); + new_cell_data = insert_option_id; + } else { + tracing::trace!("Delete single select option"); + new_cell_data = "".to_string() + } + + Ok(new_cell_data) + } +} + +#[derive(Default)] +pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOptionPB); +impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOptionPB); + +impl SingleSelectTypeOptionBuilder { + pub fn option(mut self, opt: SelectOptionPB) -> Self { + self.0.options.push(opt); + self + } +} + +impl TypeOptionBuilder for SingleSelectTypeOptionBuilder { + fn field_type(&self) -> FieldType { + FieldType::SingleSelect + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::entities::FieldType; + use crate::services::cell::CellDataOperation; + + use crate::services::field::type_options::*; + use crate::services::field::FieldBuilder; + use flowy_grid_data_model::revision::FieldRevision; + + #[test] + fn single_select_test() { + let google_option = SelectOptionPB::new("Google"); + let facebook_option = SelectOptionPB::new("Facebook"); + let twitter_option = SelectOptionPB::new("Twitter"); + let single_select = SingleSelectTypeOptionBuilder::default() + .option(google_option.clone()) + .option(facebook_option.clone()) + .option(twitter_option); + + let field_rev = FieldBuilder::new(single_select) + .name("Platform") + .visibility(true) + .build(); + + let type_option = SingleSelectTypeOptionPB::from(&field_rev); + + let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR); + let data = SelectOptionCellChangeset::from_insert(&option_ids).to_str(); + let cell_data = type_option.apply_changeset(data.into(), None).unwrap(); + assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option.clone()]); + + let data = SelectOptionCellChangeset::from_insert(&google_option.id).to_str(); + let cell_data = type_option.apply_changeset(data.into(), None).unwrap(); + assert_single_select_options(cell_data, &type_option, &field_rev, vec![google_option]); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellChangeset::from_insert("").to_str().into(), None) + .unwrap(); + assert_single_select_options(cell_data, &type_option, &field_rev, vec![]); + + // Invalid option id + let cell_data = type_option + .apply_changeset(SelectOptionCellChangeset::from_insert("123").to_str().into(), None) + .unwrap(); + + assert_single_select_options(cell_data, &type_option, &field_rev, vec![]); + + // Invalid changeset + assert!(type_option.apply_changeset("123".to_owned().into(), None).is_err()); + } + + fn assert_single_select_options( + cell_data: String, + type_option: &SingleSelectTypeOptionPB, + field_rev: &FieldRevision, + expected: Vec, + ) { + let field_type: FieldType = field_rev.field_type_rev.into(); + assert_eq!( + expected, + type_option + .decode_cell_data(cell_data.into(), &field_type, field_rev) + .unwrap() + .with_parser(SelectOptionCellDataParser()) + .unwrap() + .select_options, + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs new file mode 100644 index 0000000000..c7e518f103 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs @@ -0,0 +1,3 @@ +#![allow(clippy::module_inception)] +mod text_type_option; +pub use text_type_option::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs similarity index 55% rename from frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs index 2d9a9d129e..b11bd026d2 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs @@ -1,7 +1,10 @@ -use crate::entities::{FieldType, GridTextFilter}; +use crate::entities::FieldType; use crate::impl_type_option; +use crate::services::cell::{ + try_decode_cell_data, CellBytes, CellBytesParser, CellData, CellDataChangeset, CellDataOperation, CellDisplayable, + FromCellString, +}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{try_decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; @@ -30,41 +33,73 @@ pub struct RichTextTypeOption { } impl_type_option!(RichTextTypeOption, FieldType::RichText); -impl CellDataOperation for RichTextTypeOption { - fn decode_cell_data( +impl CellDisplayable for RichTextTypeOption { + fn display_data( &self, - encoded_data: T, + cell_data: CellData, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult { + let cell_str: String = cell_data.try_into_inner()?; + Ok(CellBytes::new(cell_str)) + } +} + +impl CellDataOperation for RichTextTypeOption { + fn decode_cell_data( + &self, + cell_data: CellData, decoded_field_type: &FieldType, field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into, - { + ) -> FlowyResult { if decoded_field_type.is_date() || decoded_field_type.is_single_select() || decoded_field_type.is_multi_select() || decoded_field_type.is_number() { - try_decode_cell_data(encoded_data.into(), field_rev, decoded_field_type, decoded_field_type) + try_decode_cell_data(cell_data, field_rev, decoded_field_type, decoded_field_type) } else { - let cell_data = encoded_data.into(); - Ok(DecodedCellData::new(cell_data)) + self.display_data(cell_data, decoded_field_type, field_rev) } } - fn apply_filter(&self, _filter: GridTextFilter) -> bool { - todo!() - } - - fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result - where - C: Into, - { - let data = changeset.into(); + fn apply_changeset( + &self, + changeset: CellDataChangeset, + _cell_rev: Option, + ) -> Result { + let data = changeset.try_into_inner()?; if data.len() > 10000 { Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) } else { - Ok(data.0) + Ok(data) + } + } +} + +pub struct TextCellData(pub String); +impl AsRef for TextCellData { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl FromCellString for TextCellData { + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + Ok(TextCellData(s.to_owned())) + } +} + +pub struct TextCellDataParser(); +impl CellBytesParser for TextCellDataParser { + type Object = TextCellData; + fn parse(&self, bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => Ok(TextCellData(s)), + Err(_) => Ok(TextCellData("".to_owned())), } } } @@ -72,9 +107,10 @@ impl CellDataOperation for RichTextTypeOption { #[cfg(test)] mod tests { use crate::entities::FieldType; + use crate::services::cell::CellDataOperation; + use crate::services::field::FieldBuilder; use crate::services::field::*; - use crate::services::row::CellDataOperation; #[test] fn text_description_test() { @@ -86,46 +122,52 @@ mod tests { assert_eq!( type_option - .decode_cell_data(1647251762.to_string(), &field_type, &date_time_field_rev) + .decode_cell_data(1647251762.to_string().into(), &field_type, &date_time_field_rev) .unwrap() - .parse::() + .with_parser(DateCellDataParser()) .unwrap() .date, "Mar 14,2022".to_owned() ); // Single select - let done_option = SelectOption::new("Done"); + let done_option = SelectOptionPB::new("Done"); let done_option_id = done_option.id.clone(); let single_select = SingleSelectTypeOptionBuilder::default().option(done_option.clone()); let single_select_field_rev = FieldBuilder::new(single_select).build(); assert_eq!( type_option - .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_rev) + .decode_cell_data( + done_option_id.into(), + &FieldType::SingleSelect, + &single_select_field_rev + ) .unwrap() - .parse::() + .with_parser(SelectOptionCellDataParser()) .unwrap() .select_options, vec![done_option], ); // Multiple select - let google_option = SelectOption::new("Google"); - let facebook_option = SelectOption::new("Facebook"); + let google_option = SelectOptionPB::new("Google"); + let facebook_option = SelectOptionPB::new("Facebook"); let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR); - let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str(); + let cell_data_changeset = SelectOptionCellChangeset::from_insert(&ids).to_str(); let multi_select = MultiSelectTypeOptionBuilder::default() .option(google_option.clone()) .option(facebook_option.clone()); let multi_select_field_rev = FieldBuilder::new(multi_select).build(); let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_rev); - let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap(); + let cell_data = multi_type_option + .apply_changeset(cell_data_changeset.into(), None) + .unwrap(); assert_eq!( type_option - .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_rev) + .decode_cell_data(cell_data.into(), &FieldType::MultiSelect, &multi_select_field_rev) .unwrap() - .parse::() + .with_parser(SelectOptionCellDataParser()) .unwrap() .select_options, vec![google_option, facebook_option] @@ -136,7 +178,7 @@ mod tests { let number_field_rev = FieldBuilder::new(number).build(); assert_eq!( type_option - .decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_rev) + .decode_cell_data("18443".to_owned().into(), &FieldType::Number, &number_field_rev) .unwrap() .to_string(), "$18,443".to_owned() diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs deleted file mode 100644 index beb0ce623a..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::entities::{FieldType, GridTextFilter}; -use crate::impl_type_option; -use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; -use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData}; -use bytes::Bytes; -use fancy_regex::Regex; -use flowy_derive::ProtoBuf; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -#[derive(Default)] -pub struct URLTypeOptionBuilder(URLTypeOption); -impl_into_box_type_option_builder!(URLTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption); - -impl TypeOptionBuilder for URLTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::URL - } - - fn entry(&self) -> &dyn TypeOptionDataEntry { - &self.0 - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] -pub struct URLTypeOption { - #[pb(index = 1)] - data: String, //It's not used yet. -} -impl_type_option!(URLTypeOption, FieldType::URL); - -impl CellDataOperation, GridTextFilter> for URLTypeOption { - fn decode_cell_data( - &self, - encoded_data: T, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into>, - { - if !decoded_field_type.is_url() { - return Ok(DecodedCellData::default()); - } - let cell_data = encoded_data.into().try_into_inner()?; - DecodedCellData::try_from_bytes(cell_data) - } - - fn apply_filter(&self, _filter: GridTextFilter) -> bool { - todo!() - } - - fn apply_changeset(&self, changeset: C, _cell_rev: Option) -> Result - where - C: Into, - { - let changeset = changeset.into(); - let mut url = "".to_string(); - if let Ok(Some(m)) = URL_REGEX.find(&changeset) { - url = auto_append_scheme(m.as_str()); - } - URLCellData { - url, - content: changeset.to_string(), - } - .to_json() - } -} - -fn auto_append_scheme(s: &str) -> String { - // Only support https scheme by now - match url::Url::parse(s) { - Ok(url) => { - if url.scheme() == "https" { - url.into() - } else { - format!("https://{}", s) - } - } - Err(_) => { - format!("https://{}", s) - } - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct URLCellData { - #[pb(index = 1)] - pub url: String, - - #[pb(index = 2)] - pub content: String, -} - -impl URLCellData { - pub fn new(s: &str) -> Self { - Self { - url: "".to_string(), - content: s.to_string(), - } - } - - fn to_json(&self) -> FlowyResult { - serde_json::to_string(self).map_err(internal_error) - } -} - -impl FromStr for URLCellData { - type Err = FlowyError; - - fn from_str(s: &str) -> Result { - serde_json::from_str::(s).map_err(internal_error) - } -} - -lazy_static! { - static ref URL_REGEX: Regex = Regex::new( - "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)" - ) - .unwrap(); -} - -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::field::FieldBuilder; - use crate::services::field::{URLCellData, URLTypeOption}; - use crate::services::row::{CellDataOperation, EncodedCellData}; - use flowy_grid_data_model::revision::FieldRevision; - - #[test] - fn url_type_option_test_no_url() { - let type_option = URLTypeOption::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_changeset(&type_option, "123", &field_type, &field_rev, "123", ""); - } - - #[test] - fn url_type_option_test_contains_url() { - let type_option = URLTypeOption::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_changeset( - &type_option, - "AppFlowy website - https://www.appflowy.io", - &field_type, - &field_rev, - "AppFlowy website - https://www.appflowy.io", - "https://www.appflowy.io/", - ); - - assert_changeset( - &type_option, - "AppFlowy website appflowy.io", - &field_type, - &field_rev, - "AppFlowy website appflowy.io", - "https://appflowy.io", - ); - } - - fn assert_changeset( - type_option: &URLTypeOption, - cell_data: &str, - field_type: &FieldType, - field_rev: &FieldRevision, - expected: &str, - expected_url: &str, - ) { - let encoded_data = type_option.apply_changeset(cell_data, None).unwrap(); - let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type); - assert_eq!(expected.to_owned(), decode_cell_data.content); - assert_eq!(expected_url.to_owned(), decode_cell_data.url); - } - - fn decode_cell_data>>( - encoded_data: T, - type_option: &URLTypeOption, - field_rev: &FieldRevision, - field_type: &FieldType, - ) -> URLCellData { - type_option - .decode_cell_data(encoded_data, field_type, field_rev) - .unwrap() - .parse::() - .unwrap() - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs new file mode 100644 index 0000000000..8f6cb884df --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs @@ -0,0 +1,7 @@ +#![allow(clippy::module_inception)] +mod url_tests; +mod url_type_option; +mod url_type_option_entities; + +pub use url_type_option::*; +pub use url_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs new file mode 100644 index 0000000000..77f4ea767e --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs @@ -0,0 +1,67 @@ +#[cfg(test)] +mod tests { + use crate::entities::FieldType; + use crate::services::cell::{CellData, CellDataOperation}; + use crate::services::field::{FieldBuilder, URLCellDataParser}; + use crate::services::field::{URLCellDataPB, URLTypeOption}; + use flowy_grid_data_model::revision::FieldRevision; + + #[test] + fn url_type_option_test_no_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset(&type_option, "123", &field_type, &field_rev, "123", ""); + } + + #[test] + fn url_type_option_test_contains_url() { + let type_option = URLTypeOption::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_changeset( + &type_option, + "AppFlowy website - https://www.appflowy.io", + &field_type, + &field_rev, + "AppFlowy website - https://www.appflowy.io", + "https://www.appflowy.io/", + ); + + assert_changeset( + &type_option, + "AppFlowy website appflowy.io", + &field_type, + &field_rev, + "AppFlowy website appflowy.io", + "https://appflowy.io", + ); + } + + fn assert_changeset( + type_option: &URLTypeOption, + cell_data: &str, + field_type: &FieldType, + field_rev: &FieldRevision, + expected: &str, + expected_url: &str, + ) { + let encoded_data = type_option.apply_changeset(cell_data.to_owned().into(), None).unwrap(); + let decode_cell_data = decode_cell_data(encoded_data, type_option, field_rev, field_type); + assert_eq!(expected.to_owned(), decode_cell_data.content); + assert_eq!(expected_url.to_owned(), decode_cell_data.url); + } + + fn decode_cell_data>>( + encoded_data: T, + type_option: &URLTypeOption, + field_rev: &FieldRevision, + field_type: &FieldType, + ) -> URLCellDataPB { + type_option + .decode_cell_data(encoded_data.into(), field_type, field_rev) + .unwrap() + .with_parser(URLCellDataParser()) + .unwrap() + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs new file mode 100644 index 0000000000..3fa49dac09 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs @@ -0,0 +1,95 @@ +use crate::entities::FieldType; +use crate::impl_type_option; +use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder, URLCellDataPB}; +use bytes::Bytes; +use fancy_regex::Regex; +use flowy_derive::ProtoBuf; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; + +#[derive(Default)] +pub struct URLTypeOptionBuilder(URLTypeOption); +impl_into_box_type_option_builder!(URLTypeOptionBuilder); +impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption); + +impl TypeOptionBuilder for URLTypeOptionBuilder { + fn field_type(&self) -> FieldType { + FieldType::URL + } + + fn entry(&self) -> &dyn TypeOptionDataEntry { + &self.0 + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] +pub struct URLTypeOption { + #[pb(index = 1)] + data: String, //It's not used yet. +} +impl_type_option!(URLTypeOption, FieldType::URL); + +impl CellDisplayable for URLTypeOption { + fn display_data( + &self, + cell_data: CellData, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult { + let cell_data: URLCellDataPB = cell_data.try_into_inner()?; + CellBytes::from(cell_data) + } +} + +impl CellDataOperation for URLTypeOption { + fn decode_cell_data( + &self, + cell_data: CellData, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult { + if !decoded_field_type.is_url() { + return Ok(CellBytes::default()); + } + self.display_data(cell_data, decoded_field_type, field_rev) + } + + fn apply_changeset( + &self, + changeset: CellDataChangeset, + _cell_rev: Option, + ) -> Result { + let content = changeset.try_into_inner()?; + let mut url = "".to_string(); + if let Ok(Some(m)) = URL_REGEX.find(&content) { + url = auto_append_scheme(m.as_str()); + } + URLCellDataPB { url, content }.to_json() + } +} + +fn auto_append_scheme(s: &str) -> String { + // Only support https scheme by now + match url::Url::parse(s) { + Ok(url) => { + if url.scheme() == "https" { + url.into() + } else { + format!("https://{}", s) + } + } + Err(_) => { + format!("https://{}", s) + } + } +} + +lazy_static! { + static ref URL_REGEX: Regex = Regex::new( + "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)" + ) + .unwrap(); +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs new file mode 100644 index 0000000000..6ff77cea9a --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option_entities.rs @@ -0,0 +1,42 @@ +use crate::services::cell::{CellBytesParser, FromCellString}; +use bytes::Bytes; +use flowy_derive::ProtoBuf; +use flowy_error::{internal_error, FlowyResult}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] +pub struct URLCellDataPB { + #[pb(index = 1)] + pub url: String, + + #[pb(index = 2)] + pub content: String, +} + +impl URLCellDataPB { + pub fn new(s: &str) -> Self { + Self { + url: "".to_string(), + content: s.to_string(), + } + } + + pub(crate) fn to_json(&self) -> FlowyResult { + serde_json::to_string(self).map_err(internal_error) + } +} + +pub struct URLCellDataParser(); +impl CellBytesParser for URLCellDataParser { + type Object = URLCellDataPB; + + fn parse(&self, bytes: &Bytes) -> FlowyResult { + URLCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) + } +} + +impl FromCellString for URLCellDataPB { + fn from_cell_str(s: &str) -> FlowyResult { + serde_json::from_str::(s).map_err(internal_error) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs similarity index 68% rename from frontend/rust-lib/flowy-grid/src/services/field/type_options/util.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs index 70cfd2db00..549f502bb0 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs @@ -1,9 +1,9 @@ -use crate::services::row::TypeOptionCellData; +use crate::services::cell::AnyCellData; use flowy_grid_data_model::revision::CellRevision; use std::str::FromStr; pub fn get_cell_data(cell_rev: &CellRevision) -> String { - match TypeOptionCellData::from_str(&cell_rev.data) { + match AnyCellData::from_str(&cell_rev.data) { Ok(type_option) => type_option.data, Err(_) => String::new(), } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs new file mode 100644 index 0000000000..dfadd1c5a0 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/mod.rs @@ -0,0 +1,3 @@ +mod cell_data_util; + +pub use cell_data_util::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs new file mode 100644 index 0000000000..a1f6d4cbbf --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs @@ -0,0 +1,161 @@ +use crate::entities::{ + FieldType, GridCheckboxFilter, GridDateFilter, GridNumberFilter, GridSelectOptionFilter, GridTextFilter, +}; +use dashmap::DashMap; +use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use flowy_sync::client_grid::GridRevisionPad; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +type RowId = String; + +#[derive(Default)] +pub(crate) struct FilterResultCache { + // key: row id + inner: DashMap, +} + +impl FilterResultCache { + pub fn new() -> Arc { + let this = Self::default(); + Arc::new(this) + } +} + +impl std::ops::Deref for FilterResultCache { + type Target = DashMap; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[derive(Default)] +pub(crate) struct FilterResult { + #[allow(dead_code)] + pub(crate) row_index: i32, + pub(crate) visible_by_field_id: HashMap, +} + +impl FilterResult { + pub(crate) fn new(index: i32, _row_rev: &RowRevision) -> Self { + Self { + row_index: index, + visible_by_field_id: HashMap::new(), + } + } + + pub(crate) fn is_visible(&self) -> bool { + for visible in self.visible_by_field_id.values() { + if visible == &false { + return false; + } + } + true + } +} + +#[derive(Default)] +pub(crate) struct FilterCache { + pub(crate) text_filter: DashMap, + pub(crate) url_filter: DashMap, + pub(crate) number_filter: DashMap, + pub(crate) date_filter: DashMap, + pub(crate) select_option_filter: DashMap, + pub(crate) checkbox_filter: DashMap, +} + +impl FilterCache { + pub(crate) async fn from_grid_pad(grid_pad: &Arc>) -> Arc { + let this = Arc::new(Self::default()); + let _ = refresh_filter_cache(this.clone(), None, grid_pad).await; + this + } + + pub(crate) fn remove(&self, filter_id: &FilterId) { + let _ = match filter_id.field_type { + FieldType::RichText => { + let _ = self.text_filter.remove(filter_id); + } + FieldType::Number => { + let _ = self.number_filter.remove(filter_id); + } + FieldType::DateTime => { + let _ = self.date_filter.remove(filter_id); + } + FieldType::SingleSelect => { + let _ = self.select_option_filter.remove(filter_id); + } + FieldType::MultiSelect => { + let _ = self.select_option_filter.remove(filter_id); + } + FieldType::Checkbox => { + let _ = self.checkbox_filter.remove(filter_id); + } + FieldType::URL => { + let _ = self.url_filter.remove(filter_id); + } + }; + } +} + +/// Refresh the filter according to the field id. +pub(crate) async fn refresh_filter_cache( + cache: Arc, + field_ids: Option>, + grid_pad: &Arc>, +) { + let grid_pad = grid_pad.read().await; + let filters_revs = grid_pad.get_filters(None, field_ids).unwrap_or_default(); + + for filter_rev in filters_revs { + match grid_pad.get_field_rev(&filter_rev.field_id) { + None => {} + Some((_, field_rev)) => { + let filter_id = FilterId::from(field_rev); + let field_type: FieldType = field_rev.field_type_rev.into(); + match &field_type { + FieldType::RichText => { + let _ = cache.text_filter.insert(filter_id, GridTextFilter::from(filter_rev)); + } + FieldType::Number => { + let _ = cache + .number_filter + .insert(filter_id, GridNumberFilter::from(filter_rev)); + } + FieldType::DateTime => { + let _ = cache.date_filter.insert(filter_id, GridDateFilter::from(filter_rev)); + } + FieldType::SingleSelect | FieldType::MultiSelect => { + let _ = cache + .select_option_filter + .insert(filter_id, GridSelectOptionFilter::from(filter_rev)); + } + FieldType::Checkbox => { + let _ = cache + .checkbox_filter + .insert(filter_id, GridCheckboxFilter::from(filter_rev)); + } + FieldType::URL => { + let _ = cache.url_filter.insert(filter_id, GridTextFilter::from(filter_rev)); + } + } + } + } + } +} +#[derive(Hash, Eq, PartialEq)] +pub(crate) struct FilterId { + pub(crate) field_id: String, + pub(crate) field_type: FieldType, +} + +impl std::convert::From<&Arc> for FilterId { + fn from(rev: &Arc) -> Self { + Self { + field_id: rev.id.clone(), + field_type: rev.field_type_rev.into(), + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs index 54e2984bd9..f3b1d72fb8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs @@ -1,7 +1,14 @@ -use crate::entities::{ - FieldType, GridCheckboxFilter, GridDateFilter, GridNumberFilter, GridSelectOptionFilter, GridTextFilter, -}; +use crate::dart_notification::{send_dart_notification, GridNotification}; +use crate::entities::{FieldType, GridBlockChangesetPB}; use crate::services::block_manager::GridBlockManager; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{ + CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption, + SingleSelectTypeOptionPB, URLTypeOption, +}; +use crate::services::filter::filter_cache::{ + refresh_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache, +}; use crate::services::grid_editor_task::GridServiceTaskScheduler; use crate::services::row::GridBlockSnapshot; use crate::services::tasks::{FilterTaskContext, Task, TaskContent}; @@ -9,6 +16,7 @@ use flowy_error::FlowyResult; use flowy_grid_data_model::revision::{CellRevision, FieldId, FieldRevision, RowRevision}; use flowy_sync::client_grid::GridRevisionPad; use flowy_sync::entities::grid::GridSettingChangesetParams; +use rayon::prelude::*; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; @@ -19,8 +27,8 @@ pub(crate) struct GridFilterService { scheduler: Arc, grid_pad: Arc>, block_manager: Arc, - filter_cache: Arc>, - filter_result_cache: Arc>, + filter_cache: Arc, + filter_result_cache: Arc, } impl GridFilterService { pub async fn new( @@ -29,13 +37,14 @@ impl GridFilterService { scheduler: S, ) -> Self { let grid_id = grid_pad.read().await.grid_id(); - let filter_cache = Arc::new(RwLock::new(FilterCache::from_grid_pad(&grid_pad).await)); - let filter_result_cache = Arc::new(RwLock::new(FilterResultCache::default())); + let scheduler = Arc::new(scheduler); + let filter_cache = FilterCache::from_grid_pad(&grid_pad).await; + let filter_result_cache = FilterResultCache::new(); Self { grid_id, grid_pad, block_manager, - scheduler: Arc::new(scheduler), + scheduler, filter_cache, filter_result_cache, } @@ -51,20 +60,48 @@ impl GridFilterService { .map(|field_rev| (field_rev.id.clone(), field_rev)) .collect::>>(); - let mut show_rows = vec![]; - let mut hide_rows = vec![]; - for block in task_context.blocks { - block.row_revs.iter().for_each(|row_rev| { - let result = filter_row(row_rev, &self.filter_cache, &self.filter_result_cache, &field_revs); + let mut changesets = vec![]; + for (index, block) in task_context.blocks.into_iter().enumerate() { + // The row_ids contains the row that its visibility was changed. + let row_ids = block + .row_revs + .par_iter() + .flat_map(|row_rev| { + let filter_result_cache = self.filter_result_cache.clone(); + let filter_cache = self.filter_cache.clone(); + filter_row(index, row_rev, filter_cache, filter_result_cache, &field_revs) + }) + .collect::>(); - if result.is_row_hidden() { - hide_rows.push(result.row_id); + let mut visible_rows = vec![]; + let mut hide_rows = vec![]; + + // Query the filter result from the cache + for row_id in row_ids { + if self + .filter_result_cache + .get(&row_id) + .map(|result| result.is_visible()) + .unwrap_or(false) + { + visible_rows.push(row_id); } else { - show_rows.push(result.row_id); + hide_rows.push(row_id); } - }); + } + + let changeset = GridBlockChangesetPB { + block_id: block.block_id, + hide_rows, + visible_rows, + ..Default::default() + }; + + // Save the changeset for each block + changesets.push(changeset); } - self.notify(hide_rows, show_rows).await; + + self.notify(changesets).await; Ok(()) } @@ -74,18 +111,17 @@ impl GridFilterService { } if let Some(filter_id) = &changeset.insert_filter { - let mut cache = self.filter_cache.write().await; let field_ids = Some(vec![filter_id.field_id.clone()]); - reload_filter_cache(&mut cache, field_ids, &self.grid_pad).await; + refresh_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await; } if let Some(filter_id) = &changeset.delete_filter { - self.filter_cache.write().await.remove(filter_id); + self.filter_cache.remove(filter_id); } if let Ok(blocks) = self.block_manager.get_block_snapshots(None).await { - let task = self.gen_task(blocks).await; - let _ = self.scheduler.register_task(task).await; + let _task = self.gen_task(blocks).await; + // let _ = self.scheduler.register_task(task).await; } } @@ -94,32 +130,135 @@ impl GridFilterService { let handler_id = self.grid_pad.read().await.grid_id(); let context = FilterTaskContext { blocks }; - Task { - handler_id, - id: task_id, - content: TaskContent::Filter(context), - } + Task::new(&handler_id, task_id, TaskContent::Filter(context)) } - async fn notify(&self, _hide_rows: Vec, _show_rows: Vec) { - // let notification = GridNotification {}; - // send_dart_notification(grid_id, GridNotification::DidUpdateGridBlock) - // .payload(notification) - // .send(); + async fn notify(&self, changesets: Vec) { + for changeset in changesets { + send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridBlock) + .payload(changeset) + .send(); + } } } +// Return None if there is no change in this row after applying the filter fn filter_row( + index: usize, row_rev: &Arc, - _filter_cache: &Arc>, - _filter_result_cache: &Arc>, - _field_revs: &HashMap>, -) -> FilterResult { - let filter_result = FilterResult::new(row_rev); - row_rev.cells.iter().for_each(|(_k, cell_rev)| { - let _cell_rev: &CellRevision = cell_rev; - }); - filter_result + filter_cache: Arc, + filter_result_cache: Arc, + field_revs: &HashMap>, +) -> Option { + let mut result = filter_result_cache + .entry(row_rev.id.clone()) + .or_insert(FilterResult::new(index as i32, row_rev)); + + for (field_id, cell_rev) in row_rev.cells.iter() { + match filter_cell(field_revs, result.value_mut(), &filter_cache, field_id, cell_rev) { + None => {} + Some(_) => { + return Some(row_rev.id.clone()); + } + } + } + None +} + +// Return None if there is no change in this cell after applying the filter +fn filter_cell( + field_revs: &HashMap>, + filter_result: &mut FilterResult, + filter_cache: &Arc, + field_id: &str, + cell_rev: &CellRevision, +) -> Option<()> { + let field_rev = field_revs.get(field_id)?; + let field_type = FieldType::from(field_rev.field_type_rev); + let field_type_rev = field_type.clone().into(); + let filter_id = FilterId { + field_id: field_id.to_owned(), + field_type, + }; + let any_cell_data = AnyCellData::try_from(cell_rev).ok()?; + let is_visible = match &filter_id.field_type { + FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option_entry::(field_type_rev)? + .apply_filter(any_cell_data, filter.value()) + .ok(), + ) + }), + FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option_entry::(field_type_rev)? + .apply_filter(any_cell_data, filter.value()) + .ok(), + ) + }), + FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option_entry::(field_type_rev)? + .apply_filter(any_cell_data, filter.value()) + .ok(), + ) + }), + FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option_entry::(field_type_rev)? + .apply_filter(any_cell_data, filter.value()) + .ok(), + ) + }), + FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option_entry::(field_type_rev)? + .apply_filter(any_cell_data, filter.value()) + .ok(), + ) + }), + FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option_entry::(field_type_rev)? + .apply_filter(any_cell_data, filter.value()) + .ok(), + ) + }), + FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option_entry::(field_type_rev)? + .apply_filter(any_cell_data, filter.value()) + .ok(), + ) + }), + }?; + + let is_visible = !is_visible.unwrap_or(true); + match filter_result.visible_by_field_id.get(&filter_id) { + None => { + if is_visible { + None + } else { + filter_result.visible_by_field_id.insert(filter_id, is_visible); + Some(()) + } + } + Some(old_is_visible) => { + if old_is_visible != &is_visible { + filter_result.visible_by_field_id.insert(filter_id, is_visible); + Some(()) + } else { + None + } + } + } } pub struct GridFilterChangeset { @@ -150,144 +289,3 @@ impl std::convert::From<&GridSettingChangesetParams> for GridFilterChangeset { } } } - -#[derive(Default)] -struct FilterResultCache { - #[allow(dead_code)] - rows: HashMap, -} - -impl FilterResultCache { - #[allow(dead_code)] - fn insert(&mut self, row_id: &str, result: FilterResult) { - self.rows.insert(row_id.to_owned(), result); - } -} - -#[derive(Default)] -struct FilterResult { - row_id: String, - #[allow(dead_code)] - cell_by_field_id: HashMap, -} - -impl FilterResult { - fn new(row_rev: &RowRevision) -> Self { - Self { - row_id: row_rev.id.clone(), - cell_by_field_id: row_rev.cells.iter().map(|(k, _)| (k.clone(), true)).collect(), - } - } - - #[allow(dead_code)] - fn update_cell(&mut self, cell_id: &str, exist: bool) { - self.cell_by_field_id.insert(cell_id.to_owned(), exist); - } - - fn is_row_hidden(&self) -> bool { - todo!() - } -} - -#[derive(Default)] -struct FilterCache { - text_filter: HashMap, - url_filter: HashMap, - number_filter: HashMap, - date_filter: HashMap, - select_option_filter: HashMap, - checkbox_filter: HashMap, -} - -impl FilterCache { - async fn from_grid_pad(grid_pad: &Arc>) -> Self { - let mut this = Self::default(); - let _ = reload_filter_cache(&mut this, None, grid_pad).await; - this - } - - fn remove(&mut self, filter_id: &FilterId) { - let _ = match filter_id.field_type { - FieldType::RichText => { - let _ = self.text_filter.remove(filter_id); - } - FieldType::Number => { - let _ = self.number_filter.remove(filter_id); - } - FieldType::DateTime => { - let _ = self.date_filter.remove(filter_id); - } - FieldType::SingleSelect => { - let _ = self.select_option_filter.remove(filter_id); - } - FieldType::MultiSelect => { - let _ = self.select_option_filter.remove(filter_id); - } - FieldType::Checkbox => { - let _ = self.checkbox_filter.remove(filter_id); - } - FieldType::URL => { - let _ = self.url_filter.remove(filter_id); - } - }; - } -} - -async fn reload_filter_cache( - cache: &mut FilterCache, - field_ids: Option>, - grid_pad: &Arc>, -) { - let grid_pad = grid_pad.read().await; - let filters_revs = grid_pad.get_filters(None, field_ids).unwrap_or_default(); - - for filter_rev in filters_revs { - match grid_pad.get_field_rev(&filter_rev.field_id) { - None => {} - Some((_, field_rev)) => { - let filter_id = FilterId::from(field_rev); - let field_type: FieldType = field_rev.field_type_rev.into(); - match &field_type { - FieldType::RichText => { - let _ = cache.text_filter.insert(filter_id, GridTextFilter::from(filter_rev)); - } - FieldType::Number => { - let _ = cache - .number_filter - .insert(filter_id, GridNumberFilter::from(filter_rev)); - } - FieldType::DateTime => { - let _ = cache.date_filter.insert(filter_id, GridDateFilter::from(filter_rev)); - } - FieldType::SingleSelect | FieldType::MultiSelect => { - let _ = cache - .select_option_filter - .insert(filter_id, GridSelectOptionFilter::from(filter_rev)); - } - FieldType::Checkbox => { - let _ = cache - .checkbox_filter - .insert(filter_id, GridCheckboxFilter::from(filter_rev)); - } - FieldType::URL => { - let _ = cache.url_filter.insert(filter_id, GridTextFilter::from(filter_rev)); - } - } - } - } - } -} -#[derive(Hash, Eq, PartialEq)] -struct FilterId { - field_id: String, - field_type: FieldType, -} - -impl std::convert::From<&Arc> for FilterId { - fn from(rev: &Arc) -> Self { - Self { - field_id: rev.id.clone(), - field_type: rev.field_type_rev.into(), - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs new file mode 100644 index 0000000000..24e21bbeb7 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs @@ -0,0 +1,54 @@ +use crate::entities::{CheckboxCondition, GridCheckboxFilter}; +use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; +use crate::services::field::{CheckboxCellData, CheckboxTypeOption}; +use flowy_error::FlowyResult; + +impl GridCheckboxFilter { + pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool { + let is_check = cell_data.is_check(); + match self.condition { + CheckboxCondition::IsChecked => is_check, + CheckboxCondition::IsUnChecked => !is_check, + } + } +} + +impl CellFilterOperation for CheckboxTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridCheckboxFilter) -> FlowyResult { + if !any_cell_data.is_checkbox() { + return Ok(true); + } + let cell_data: CellData = any_cell_data.into(); + let checkbox_cell_data = cell_data.try_into_inner()?; + Ok(filter.is_visible(&checkbox_cell_data)) + } +} + +#[cfg(test)] +mod tests { + use crate::entities::{CheckboxCondition, GridCheckboxFilter}; + use crate::services::field::CheckboxCellData; + use std::str::FromStr; + + #[test] + fn checkbox_filter_is_check_test() { + let checkbox_filter = GridCheckboxFilter { + condition: CheckboxCondition::IsChecked, + }; + for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] { + let data = CheckboxCellData::from_str(value).unwrap(); + assert_eq!(checkbox_filter.is_visible(&data), visible); + } + } + + #[test] + fn checkbox_filter_is_uncheck_test() { + let checkbox_filter = GridCheckboxFilter { + condition: CheckboxCondition::IsUnChecked, + }; + for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] { + let data = CheckboxCellData::from_str(value).unwrap(); + assert_eq!(checkbox_filter.is_visible(&data), visible); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs new file mode 100644 index 0000000000..46e2571438 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs @@ -0,0 +1,108 @@ +use crate::entities::{DateFilterCondition, GridDateFilter}; +use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; +use crate::services::field::{DateTimestamp, DateTypeOption}; +use flowy_error::FlowyResult; + +impl GridDateFilter { + pub fn is_visible>(&self, cell_timestamp: T) -> bool { + if self.start.is_none() { + return false; + } + let cell_timestamp = cell_timestamp.into(); + let start_timestamp = *self.start.as_ref().unwrap(); + // We assume that the cell_timestamp doesn't contain hours, just day. + match self.condition { + DateFilterCondition::DateIs => cell_timestamp == start_timestamp, + DateFilterCondition::DateBefore => cell_timestamp < start_timestamp, + DateFilterCondition::DateAfter => cell_timestamp > start_timestamp, + DateFilterCondition::DateOnOrBefore => cell_timestamp <= start_timestamp, + DateFilterCondition::DateOnOrAfter => cell_timestamp >= start_timestamp, + DateFilterCondition::DateWithIn => { + if let Some(end_timestamp) = self.end.as_ref() { + cell_timestamp >= start_timestamp && cell_timestamp <= *end_timestamp + } else { + false + } + } + DateFilterCondition::DateIsEmpty => cell_timestamp == 0_i64, + } + } +} + +impl CellFilterOperation for DateTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridDateFilter) -> FlowyResult { + if !any_cell_data.is_date() { + return Ok(true); + } + let cell_data: CellData = any_cell_data.into(); + let timestamp = cell_data.try_into_inner()?; + Ok(filter.is_visible(timestamp)) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{DateFilterCondition, GridDateFilter}; + + #[test] + fn date_filter_is_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateIs, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(123, true), (12, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_before_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateBefore, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(123, false), (122, true)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_before_or_on_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateOnOrBefore, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(123, true), (122, true)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_after_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateAfter, + start: Some(123), + end: None, + }; + + for (val, visible) in vec![(1234, true), (122, false), (0, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_within_test() { + let filter = GridDateFilter { + condition: DateFilterCondition::DateWithIn, + start: Some(123), + end: Some(130), + }; + + for (val, visible) in vec![(123, true), (130, true), (132, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs new file mode 100644 index 0000000000..6fe93ae58c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs @@ -0,0 +1,13 @@ +mod checkbox_filter; +mod date_filter; +mod number_filter; +mod select_option_filter; +mod text_filter; +mod url_filter; + +pub use checkbox_filter::*; +pub use date_filter::*; +pub use number_filter::*; +pub use select_option_filter::*; +pub use text_filter::*; +pub use url_filter::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs new file mode 100644 index 0000000000..f44c1d2d62 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs @@ -0,0 +1,92 @@ +use crate::entities::{GridNumberFilter, NumberFilterCondition}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{NumberCellData, NumberTypeOption}; +use flowy_error::FlowyResult; +use rust_decimal::prelude::Zero; +use rust_decimal::Decimal; +use std::str::FromStr; + +impl GridNumberFilter { + pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool { + if self.content.is_none() { + return false; + } + + let content = self.content.as_ref().unwrap(); + let zero_decimal = Decimal::zero(); + let cell_decimal = num_cell_data.decimal().as_ref().unwrap_or(&zero_decimal); + match Decimal::from_str(content) { + Ok(decimal) => match self.condition { + NumberFilterCondition::Equal => cell_decimal == &decimal, + NumberFilterCondition::NotEqual => cell_decimal != &decimal, + NumberFilterCondition::GreaterThan => cell_decimal > &decimal, + NumberFilterCondition::LessThan => cell_decimal < &decimal, + NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal, + NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal, + NumberFilterCondition::NumberIsEmpty => num_cell_data.is_empty(), + NumberFilterCondition::NumberIsNotEmpty => !num_cell_data.is_empty(), + }, + Err(_) => false, + } + } +} + +impl CellFilterOperation for NumberTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridNumberFilter) -> FlowyResult { + if !any_cell_data.is_number() { + return Ok(true); + } + + let cell_data = any_cell_data.data; + let num_cell_data = self.format_cell_data(&cell_data)?; + + Ok(filter.is_visible(&num_cell_data)) + } +} + +#[cfg(test)] +mod tests { + use crate::entities::{GridNumberFilter, NumberFilterCondition}; + use crate::services::field::{NumberCellData, NumberFormat}; + #[test] + fn number_filter_equal_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::Equal, + content: Some("123".to_owned()), + }; + + for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + + let format = NumberFormat::USD; + for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &format).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } + #[test] + fn number_filter_greater_than_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::GreaterThan, + content: Some("12".to_owned()), + }; + for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } + + #[test] + fn number_filter_less_than_test() { + let number_filter = GridNumberFilter { + condition: NumberFilterCondition::LessThan, + content: Some("100".to_owned()), + }; + for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", true)] { + let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs new file mode 100644 index 0000000000..e6cb9ff846 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs @@ -0,0 +1,109 @@ +#![allow(clippy::needless_collect)] + +use crate::entities::{GridSelectOptionFilter, SelectOptionCondition}; +use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOptionPB}; +use crate::services::field::{SelectOptionOperation, SelectedSelectOptions}; +use flowy_error::FlowyResult; + +impl GridSelectOptionFilter { + pub fn is_visible(&self, selected_options: &SelectedSelectOptions) -> bool { + let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect(); + match self.condition { + SelectOptionCondition::OptionIs => { + if self.option_ids.len() != selected_option_ids.len() { + return true; + } + + // if selected options equal to filter's options, then the required_options will be empty. + let required_options = self + .option_ids + .iter() + .filter(|id| !selected_option_ids.contains(id)) + .collect::>(); + + // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect + !required_options.is_empty() + } + SelectOptionCondition::OptionIsNot => { + for option_id in selected_option_ids { + if self.option_ids.contains(option_id) { + return true; + } + } + false + } + SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(), + SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(), + } + } +} + +impl CellFilterOperation for MultiSelectTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult { + if !any_cell_data.is_multi_select() { + return Ok(true); + } + + let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data.into())); + Ok(filter.is_visible(&selected_options)) + } +} + +impl CellFilterOperation for SingleSelectTypeOptionPB { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult { + if !any_cell_data.is_single_select() { + return Ok(true); + } + let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data.into())); + Ok(filter.is_visible(&selected_options)) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{GridSelectOptionFilter, SelectOptionCondition}; + use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions}; + + #[test] + fn select_option_filter_is_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let option_3 = SelectOptionPB::new("C"); + + let filter_1 = GridSelectOptionFilter { + condition: SelectOptionCondition::OptionIs, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone()], + }), + false + ); + + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone(), option_3.clone()], + }), + true + ); + + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_3.clone()], + }), + true + ); + + assert_eq!(filter_1.is_visible(&SelectedSelectOptions { options: vec![] }), true); + assert_eq!( + filter_1.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone()], + }), + true, + ); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs new file mode 100644 index 0000000000..25f3902ceb --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs @@ -0,0 +1,101 @@ +use crate::entities::{GridTextFilter, TextFilterCondition}; +use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; +use crate::services::field::{RichTextTypeOption, TextCellData}; +use flowy_error::FlowyResult; + +impl GridTextFilter { + pub fn is_visible>(&self, cell_data: T) -> bool { + let cell_data = cell_data.as_ref(); + let s = cell_data.to_lowercase(); + if let Some(content) = self.content.as_ref() { + match self.condition { + TextFilterCondition::Is => &s == content, + TextFilterCondition::IsNot => &s != content, + TextFilterCondition::Contains => s.contains(content), + TextFilterCondition::DoesNotContain => !s.contains(content), + TextFilterCondition::StartsWith => s.starts_with(content), + TextFilterCondition::EndsWith => s.ends_with(content), + TextFilterCondition::TextIsEmpty => s.is_empty(), + TextFilterCondition::TextIsNotEmpty => !s.is_empty(), + } + } else { + false + } + } +} + +impl CellFilterOperation for RichTextTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { + if !any_cell_data.is_text() { + return Ok(true); + } + + let cell_data: CellData = any_cell_data.into(); + let text_cell_data = cell_data.try_into_inner()?; + Ok(filter.is_visible(text_cell_data)) + } +} +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{GridTextFilter, TextFilterCondition}; + + #[test] + fn text_filter_equal_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::Is, + content: Some("appflowy".to_owned()), + }; + + assert!(text_filter.is_visible("AppFlowy")); + assert_eq!(text_filter.is_visible("appflowy"), true); + assert_eq!(text_filter.is_visible("Appflowy"), true); + assert_eq!(text_filter.is_visible("AppFlowy.io"), false); + } + #[test] + fn text_filter_start_with_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::StartsWith, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible("AppFlowy.io"), true); + assert_eq!(text_filter.is_visible(""), false); + assert_eq!(text_filter.is_visible("https"), false); + } + + #[test] + fn text_filter_end_with_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::EndsWith, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); + assert_eq!(text_filter.is_visible("App"), false); + assert_eq!(text_filter.is_visible("appflowy.io"), false); + } + #[test] + fn text_filter_empty_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::TextIsEmpty, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible(""), true); + assert_eq!(text_filter.is_visible("App"), false); + } + #[test] + fn text_filter_contain_test() { + let text_filter = GridTextFilter { + condition: TextFilterCondition::Contains, + content: Some("appflowy".to_owned()), + }; + + assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); + assert_eq!(text_filter.is_visible("AppFlowy"), true); + assert_eq!(text_filter.is_visible("App"), false); + assert_eq!(text_filter.is_visible(""), false); + assert_eq!(text_filter.is_visible("github"), false); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs new file mode 100644 index 0000000000..15254d4713 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs @@ -0,0 +1,16 @@ +use crate::entities::GridTextFilter; +use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; +use crate::services::field::{TextCellData, URLTypeOption}; +use flowy_error::FlowyResult; + +impl CellFilterOperation for URLTypeOption { + fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridTextFilter) -> FlowyResult { + if !any_cell_data.is_url() { + return Ok(true); + } + + let cell_data: CellData = any_cell_data.into(); + let text_cell_data = cell_data.try_into_inner()?; + Ok(filter.is_visible(&text_cell_data)) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs index 647885e527..8a0067ff96 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs @@ -1,3 +1,5 @@ +mod filter_cache; mod filter_service; +mod impls; pub(crate) use filter_service::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index d973b8a190..572665bd0c 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -1,13 +1,16 @@ use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::CellIdentifier; +use crate::entities::CellIdentifierParams; +use crate::entities::*; use crate::manager::{GridTaskSchedulerRwLock, GridUser}; use crate::services::block_manager::GridBlockManager; +use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes}; use crate::services::field::{default_type_option_builder_from_type, type_option_builder_from_bytes, FieldBuilder}; use crate::services::filter::{GridFilterChangeset, GridFilterService}; use crate::services::persistence::block_index::BlockIndexCache; -use crate::services::row::*; - -use crate::entities::*; +use crate::services::row::{ + make_grid_blocks, make_row_from_row_rev, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder, +}; +use crate::services::setting::make_grid_setting; use bytes::Bytes; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::revision::*; @@ -185,8 +188,8 @@ impl GridRevisionEditor { pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> { let _ = self.modify(|grid_pad| Ok(grid_pad.delete_field_rev(field_id)?)).await?; - let field_order = FieldOrder::from(field_id); - let notified_changeset = GridFieldChangeset::delete(&self.grid_id, vec![field_order]); + let field_order = GridFieldIdPB::from(field_id); + let notified_changeset = GridFieldChangesetPB::delete(&self.grid_id, vec![field_order]); let _ = self.notify_did_update_grid(notified_changeset).await?; Ok(()) } @@ -265,14 +268,13 @@ impl GridRevisionEditor { Ok(()) } - pub async fn create_row(&self, start_row_id: Option) -> FlowyResult { + pub async fn create_row(&self, start_row_id: Option) -> FlowyResult { let field_revs = self.grid_pad.read().await.get_field_revs(None)?; let block_id = self.block_id().await?; // insert empty row below the row whose id is upper_row_id - let row_rev_ctx = CreateRowRevisionBuilder::new(&field_revs).build(); - let row_rev = make_row_rev_from_context(&block_id, row_rev_ctx); - let row_order = BlockRowInfo::from(&row_rev); + let row_rev = RowRevisionBuilder::new(&field_revs).build(&block_id); + let row_order = GridRowPB::from(&row_rev); // insert the row let row_count = self.block_manager.create_row(&block_id, row_rev, start_row_id).await?; @@ -283,13 +285,12 @@ impl GridRevisionEditor { Ok(row_order) } - pub async fn insert_rows(&self, contexts: Vec) -> FlowyResult> { + pub async fn insert_rows(&self, row_revs: Vec) -> FlowyResult> { let block_id = self.block_id().await?; let mut rows_by_block_id: HashMap> = HashMap::new(); let mut row_orders = vec![]; - for ctx in contexts { - let row_rev = make_row_rev_from_context(&block_id, ctx); - row_orders.push(BlockRowInfo::from(&row_rev)); + for row_rev in row_revs { + row_orders.push(GridRowPB::from(&row_rev)); rows_by_block_id .entry(block_id.clone()) .or_insert_with(Vec::new) @@ -303,13 +304,10 @@ impl GridRevisionEditor { } pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> { - let field_revs = self.get_field_revs(None).await?; - self.block_manager - .update_row(changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev)) - .await + self.block_manager.update_row(changeset, make_row_from_row_rev).await } - pub async fn get_rows(&self, block_id: &str) -> FlowyResult { + pub async fn get_rows(&self, block_id: &str) -> FlowyResult { let block_ids = vec![block_id.to_owned()]; let mut grid_block_snapshot = self.grid_block_snapshots(Some(block_ids)).await?; @@ -318,26 +316,20 @@ impl GridRevisionEditor { debug_assert_eq!(grid_block_snapshot.len(), 1); if grid_block_snapshot.len() == 1 { let snapshot = grid_block_snapshot.pop().unwrap(); - let field_revs = self.get_field_revs(None).await?; - let rows = make_rows_from_row_revs(&field_revs, &snapshot.row_revs); + let rows = make_rows_from_row_revs(&snapshot.row_revs); Ok(rows.into()) } else { Ok(vec![].into()) } } - pub async fn get_row(&self, row_id: &str) -> FlowyResult> { + pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult>> { match self.block_manager.get_row_rev(row_id).await? { None => Ok(None), - Some(row_rev) => { - let field_revs = self.get_field_revs(None).await?; - let row_revs = vec![row_rev]; - let mut rows = make_rows_from_row_revs(&field_revs, &row_revs); - debug_assert!(rows.len() == 1); - Ok(rows.pop()) - } + Some(row_rev) => Ok(Some(row_rev)), } } + pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { let _ = self.block_manager.delete_row(row_id).await?; Ok(()) @@ -347,13 +339,17 @@ impl GridRevisionEditor { Ok(()) } - pub async fn get_cell(&self, params: &CellIdentifier) -> Option { + pub async fn get_cell(&self, params: &CellIdentifierParams) -> Option { + let cell_bytes = self.get_cell_bytes(params).await?; + Some(GridCellPB::new(¶ms.field_id, cell_bytes.to_vec())) + } + + pub async fn get_cell_bytes(&self, params: &CellIdentifierParams) -> Option { let field_rev = self.get_field_rev(¶ms.field_id).await?; let row_rev = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??; let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone(); - let data = decode_cell_data(cell_rev.data, &field_rev).data; - Some(Cell::new(¶ms.field_id, data)) + Some(decode_any_cell_data(cell_rev.data, &field_rev)) } pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult> { @@ -368,16 +364,16 @@ impl GridRevisionEditor { } #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn update_cell(&self, cell_changeset: CellChangeset) -> FlowyResult<()> { - if cell_changeset.cell_content_changeset.as_ref().is_none() { + pub async fn update_cell(&self, cell_changeset: CellChangesetPB) -> FlowyResult<()> { + if cell_changeset.content.as_ref().is_none() { return Ok(()); } - let CellChangeset { + let CellChangesetPB { grid_id, row_id, field_id, - mut cell_content_changeset, + mut content, } = cell_changeset; match self.grid_pad.read().await.get_field_rev(&field_id) { @@ -386,32 +382,27 @@ impl GridRevisionEditor { Err(FlowyError::internal().context(msg)) } Some((_, field_rev)) => { - tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, cell_content_changeset); + tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, content); let cell_rev = self.get_cell_rev(&row_id, &field_id).await?; // Update the changeset.data property with the return value. - cell_content_changeset = Some(apply_cell_data_changeset( - cell_content_changeset.unwrap(), - cell_rev, - field_rev, - )?); - let field_revs = self.get_field_revs(None).await?; - let cell_changeset = CellChangeset { + content = Some(apply_cell_data_changeset(content.unwrap(), cell_rev, field_rev)?); + let cell_changeset = CellChangesetPB { grid_id, row_id, field_id, - cell_content_changeset, + content, }; let _ = self .block_manager - .update_cell(cell_changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev)) + .update_cell(cell_changeset, make_row_from_row_rev) .await?; Ok(()) } } } - pub async fn get_blocks(&self, block_ids: Option>) -> FlowyResult { + pub async fn get_blocks(&self, block_ids: Option>) -> FlowyResult { let block_snapshots = self.grid_block_snapshots(block_ids.clone()).await?; make_grid_blocks(block_ids, block_snapshots) } @@ -421,7 +412,7 @@ impl GridRevisionEditor { Ok(block_meta_revs) } - pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { + pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { let changesets = self.block_manager.delete_rows(row_orders).await?; for changeset in changesets { let _ = self.update_block(changeset).await?; @@ -429,42 +420,46 @@ impl GridRevisionEditor { Ok(()) } - pub async fn get_grid_data(&self) -> FlowyResult { + pub async fn get_grid_data(&self) -> FlowyResult { let pad_read_guard = self.grid_pad.read().await; let field_orders = pad_read_guard .get_field_revs(None)? .iter() - .map(FieldOrder::from) + .map(GridFieldIdPB::from) .collect(); let mut block_orders = vec![]; for block_rev in pad_read_guard.get_block_meta_revs() { let row_orders = self.block_manager.get_row_orders(&block_rev.block_id).await?; - let block_order = GridBlock { + let block_order = GridBlockPB { id: block_rev.block_id.clone(), - row_infos: row_orders, + rows: row_orders, }; block_orders.push(block_order); } - Ok(Grid { + Ok(GridPB { id: self.grid_id.clone(), - field_orders, + fields: field_orders, blocks: block_orders, }) } - pub async fn get_grid_setting(&self) -> FlowyResult { - // let read_guard = self.grid_pad.read().await; - // let grid_setting_rev = read_guard.get_grid_setting_rev(); - // Ok(grid_setting_rev.into()) - todo!() + pub async fn get_grid_setting(&self) -> FlowyResult { + let read_guard = self.grid_pad.read().await; + let grid_setting_rev = read_guard.get_grid_setting_rev(); + let field_revs = read_guard.get_field_revs(None)?; + let grid_setting = make_grid_setting(grid_setting_rev, &field_revs); + Ok(grid_setting) } pub async fn get_grid_filter(&self, layout_type: &GridLayoutType) -> FlowyResult> { let read_guard = self.grid_pad.read().await; let layout_rev = layout_type.clone().into(); match read_guard.get_filters(Some(&layout_rev), None) { - Some(filter_revs) => Ok(filter_revs.iter().map(GridFilter::from).collect::>()), + Some(filter_revs) => Ok(filter_revs + .iter() + .map(|filter_rev| filter_rev.as_ref().into()) + .collect::>()), None => Ok(vec![]), } } @@ -500,11 +495,11 @@ impl GridRevisionEditor { pub async fn move_item(&self, params: MoveItemParams) -> FlowyResult<()> { match params.ty { - MoveItemType::MoveField => { + MoveItemTypePB::MoveField => { self.move_field(¶ms.item_id, params.from_index, params.to_index) .await } - MoveItemType::MoveRow => self.move_row(¶ms.item_id, params.from_index, params.to_index).await, + MoveItemTypePB::MoveRow => self.move_row(¶ms.item_id, params.from_index, params.to_index).await, } } @@ -513,9 +508,9 @@ impl GridRevisionEditor { .modify(|grid_pad| Ok(grid_pad.move_field(field_id, from as usize, to as usize)?)) .await?; if let Some((index, field_rev)) = self.grid_pad.read().await.get_field_rev(field_id) { - let delete_field_order = FieldOrder::from(field_id); - let insert_field = IndexField::from_field_rev(field_rev, index); - let notified_changeset = GridFieldChangeset { + let delete_field_order = GridFieldIdPB::from(field_id); + let insert_field = IndexFieldPB::from_field_rev(field_rev, index); + let notified_changeset = GridFieldChangesetPB { grid_id: self.grid_id.clone(), inserted_fields: vec![insert_field], deleted_fields: vec![delete_field_order], @@ -557,7 +552,7 @@ impl GridRevisionEditor { drop(grid_pad); Ok(BuildGridContext { - field_revs: duplicated_fields, + field_revs: duplicated_fields.into_iter().map(Arc::new).collect(), blocks: duplicated_blocks, blocks_meta_data, }) @@ -587,10 +582,7 @@ impl GridRevisionEditor { &user_id, md5, ); - let _ = self - .rev_manager - .add_local_revision(&revision, Box::new(GridRevisionCompactor())) - .await?; + let _ = self.rev_manager.add_local_revision(&revision).await?; Ok(()) } @@ -604,8 +596,8 @@ impl GridRevisionEditor { #[tracing::instrument(level = "trace", skip_all, err)] async fn notify_did_insert_grid_field(&self, field_id: &str) -> FlowyResult<()> { if let Some((index, field_rev)) = self.grid_pad.read().await.get_field_rev(field_id) { - let index_field = IndexField::from_field_rev(field_rev, index); - let notified_changeset = GridFieldChangeset::insert(&self.grid_id, vec![index_field]); + let index_field = IndexFieldPB::from_field_rev(field_rev, index); + let notified_changeset = GridFieldChangesetPB::insert(&self.grid_id, vec![index_field]); let _ = self.notify_did_update_grid(notified_changeset).await?; } Ok(()) @@ -620,8 +612,8 @@ impl GridRevisionEditor { .get_field_rev(field_id) .map(|(index, field)| (index, field.clone())) { - let updated_field = Field::from(field_rev); - let notified_changeset = GridFieldChangeset::update(&self.grid_id, vec![updated_field.clone()]); + let updated_field = GridFieldPB::from(field_rev); + let notified_changeset = GridFieldChangesetPB::update(&self.grid_id, vec![updated_field.clone()]); let _ = self.notify_did_update_grid(notified_changeset).await?; send_dart_notification(field_id, GridNotification::DidUpdateField) @@ -632,7 +624,7 @@ impl GridRevisionEditor { Ok(()) } - async fn notify_did_update_grid(&self, changeset: GridFieldChangeset) -> FlowyResult<()> { + async fn notify_did_update_grid(&self, changeset: GridFieldChangesetPB) -> FlowyResult<()> { send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridField) .payload(changeset) .send(); @@ -651,8 +643,8 @@ pub struct GridPadBuilder(); impl RevisionObjectBuilder for GridPadBuilder { type Output = GridRevisionPad; - fn build_object(object_id: &str, revisions: Vec) -> FlowyResult { - let pad = GridRevisionPad::from_revisions(object_id, revisions)?; + fn build_object(_object_id: &str, revisions: Vec) -> FlowyResult { + let pad = GridRevisionPad::from_revisions(revisions)?; Ok(pad) } } @@ -669,7 +661,7 @@ impl RevisionCloudService for GridRevisionCloudService { } } -struct GridRevisionCompactor(); +pub struct GridRevisionCompactor(); impl RevisionCompactor for GridRevisionCompactor { fn bytes_from_revisions(&self, revisions: Vec) -> FlowyResult { let delta = make_delta_from_revisions::(revisions)?; diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs index 1b3a364833..0338730818 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs @@ -1,23 +1,23 @@ use crate::manager::GridTaskSchedulerRwLock; use crate::services::grid_editor::GridRevisionEditor; -use crate::services::tasks::{GridTaskHandler, Task, TaskContent, TaskHandlerId, TaskId}; +use crate::services::tasks::{GridTaskHandler, Task, TaskContent, TaskId}; use flowy_error::FlowyError; use futures::future::BoxFuture; use lib_infra::future::BoxResultFuture; pub(crate) trait GridServiceTaskScheduler: Send + Sync + 'static { fn gen_task_id(&self) -> BoxFuture; - fn register_task(&self, task: Task) -> BoxFuture<()>; + fn add_task(&self, task: Task) -> BoxFuture<()>; } impl GridTaskHandler for GridRevisionEditor { - fn handler_id(&self) -> &TaskHandlerId { + fn handler_id(&self) -> &str { &self.grid_id } - fn process_task(&self, task: Task) -> BoxResultFuture<(), FlowyError> { + fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError> { Box::pin(async move { - match task.content { + match content { TaskContent::Snapshot => {} TaskContent::Filter(context) => self.filter_service.process(context).await?, } @@ -32,10 +32,10 @@ impl GridServiceTaskScheduler for GridTaskSchedulerRwLock { Box::pin(async move { this.read().await.next_task_id() }) } - fn register_task(&self, task: Task) -> BoxFuture<()> { + fn add_task(&self, task: Task) -> BoxFuture<()> { let this = self.clone(); Box::pin(async move { - this.write().await.register_task(task); + this.write().await.add_task(task); }) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index e051544839..6e670bae8c 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -2,6 +2,7 @@ mod util; mod block_manager; pub mod block_revision_editor; +pub mod cell; pub mod field; mod filter; pub mod grid_editor; diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs index c62dc502ad..798819fb98 100644 --- a/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/block_index.rs @@ -7,6 +7,7 @@ use flowy_database::{ use flowy_error::FlowyResult; use std::sync::Arc; +/// Allow getting the block id from row id. pub struct BlockIndexCache { database: Arc, } diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/migration.rs new file mode 100644 index 0000000000..026ba3bc20 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/migration.rs @@ -0,0 +1,113 @@ +use crate::manager::GridUser; + +use crate::services::persistence::GridDatabase; +use flowy_database::kv::KV; +use flowy_error::FlowyResult; +use flowy_grid_data_model::revision::GridRevision; +use flowy_revision::disk::{RevisionRecord, SQLiteGridRevisionPersistence}; +use flowy_revision::{mk_grid_block_revision_disk_cache, RevisionLoader, RevisionPersistence}; +use flowy_sync::client_grid::{make_grid_rev_json_str, GridRevisionPad}; +use flowy_sync::entities::revision::Revision; + +use lib_ot::core::PlainTextDeltaBuilder; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use std::sync::Arc; + +pub(crate) struct GridMigration { + user: Arc, + database: Arc, +} + +impl GridMigration { + pub fn new(user: Arc, database: Arc) -> Self { + Self { user, database } + } + + pub async fn migration_grid_if_need(&self, grid_id: &str) -> FlowyResult<()> { + match KV::get_str(grid_id) { + None => { + let _ = self.reset_grid_rev(grid_id).await?; + let _ = self.save_migrate_record(grid_id)?; + } + Some(s) => { + let mut record = MigrationGridRecord::from_str(&s)?; + let empty_json = self.empty_grid_rev_json()?; + if record.len < empty_json.len() { + let _ = self.reset_grid_rev(grid_id).await?; + record.len = empty_json.len(); + KV::set_str(grid_id, record.to_string()); + } + } + } + Ok(()) + } + + async fn reset_grid_rev(&self, grid_id: &str) -> FlowyResult<()> { + let user_id = self.user.user_id()?; + let pool = self.database.db_pool()?; + let grid_rev_pad = self.get_grid_revision_pad(grid_id).await?; + let json = grid_rev_pad.json_str()?; + let delta_data = PlainTextDeltaBuilder::new().insert(&json).build().to_delta_bytes(); + let revision = Revision::initial_revision(&user_id, grid_id, delta_data); + let record = RevisionRecord::new(revision); + // + let disk_cache = mk_grid_block_revision_disk_cache(&user_id, pool); + let _ = disk_cache.delete_and_insert_records(grid_id, None, vec![record]); + Ok(()) + } + + fn save_migrate_record(&self, grid_id: &str) -> FlowyResult<()> { + let empty_json_str = self.empty_grid_rev_json()?; + let record = MigrationGridRecord { + grid_id: grid_id.to_owned(), + len: empty_json_str.len(), + }; + KV::set_str(grid_id, record.to_string()); + Ok(()) + } + + fn empty_grid_rev_json(&self) -> FlowyResult { + let empty_grid_rev = GridRevision::default(); + let empty_json = make_grid_rev_json_str(&empty_grid_rev)?; + Ok(empty_json) + } + + async fn get_grid_revision_pad(&self, grid_id: &str) -> FlowyResult { + let pool = self.database.db_pool()?; + let user_id = self.user.user_id()?; + let disk_cache = SQLiteGridRevisionPersistence::new(&user_id, pool); + let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, grid_id, disk_cache)); + let (revisions, _) = RevisionLoader { + object_id: grid_id.to_owned(), + user_id, + cloud: None, + rev_persistence, + } + .load() + .await?; + + let pad = GridRevisionPad::from_revisions(revisions)?; + Ok(pad) + } +} + +#[derive(Serialize, Deserialize)] +struct MigrationGridRecord { + grid_id: String, + len: usize, +} + +impl FromStr for MigrationGridRecord { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str::(s) + } +} + +impl ToString for MigrationGridRecord { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs index d6167cf8e6..7bd196acc7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs @@ -4,6 +4,7 @@ use std::sync::Arc; pub mod block_index; pub mod kv; +pub mod migration; pub trait GridDatabase: Send + Sync { fn db_pool(&self) -> Result, FlowyError>; diff --git a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs b/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs deleted file mode 100644 index 99787f8550..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs +++ /dev/null @@ -1,271 +0,0 @@ -use crate::entities::FieldType; -use crate::services::field::*; -use bytes::Bytes; -use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision}; -use serde::{Deserialize, Serialize}; -use std::fmt::Formatter; -use std::str::FromStr; - -pub trait CellDataOperation { - fn decode_cell_data( - &self, - encoded_data: T, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult - where - T: Into; - - fn apply_filter(&self, filter: F) -> bool; - - fn apply_changeset>( - &self, - changeset: C, - cell_rev: Option, - ) -> FlowyResult; -} - -#[derive(Debug)] -pub struct CellContentChangeset(pub String); - -impl std::fmt::Display for CellContentChangeset { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.0) - } -} - -impl> std::convert::From for CellContentChangeset { - fn from(s: T) -> Self { - let s = s.as_ref().to_owned(); - CellContentChangeset(s) - } -} - -impl std::ops::Deref for CellContentChangeset { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TypeOptionCellData { - pub data: String, - pub field_type: FieldType, -} - -impl std::str::FromStr for TypeOptionCellData { - type Err = FlowyError; - - fn from_str(s: &str) -> Result { - let type_option_cell_data: TypeOptionCellData = serde_json::from_str(s)?; - Ok(type_option_cell_data) - } -} - -impl std::convert::TryInto for String { - type Error = FlowyError; - - fn try_into(self) -> Result { - TypeOptionCellData::from_str(&self) - } -} - -impl TypeOptionCellData { - pub fn new(data: T, field_type: FieldType) -> Self { - TypeOptionCellData { - data: data.to_string(), - field_type, - } - } - - pub fn json(&self) -> String { - serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) - } - - pub fn is_number(&self) -> bool { - self.field_type == FieldType::Number - } - - pub fn is_text(&self) -> bool { - self.field_type == FieldType::RichText - } - - pub fn is_checkbox(&self) -> bool { - self.field_type == FieldType::Checkbox - } - - pub fn is_date(&self) -> bool { - self.field_type == FieldType::DateTime - } - - pub fn is_single_select(&self) -> bool { - self.field_type == FieldType::SingleSelect - } - - pub fn is_multi_select(&self) -> bool { - self.field_type == FieldType::MultiSelect - } - - pub fn is_select_option(&self) -> bool { - self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect - } -} - -/// The changeset will be deserialized into specific data base on the FieldType. -/// For example, it's String on FieldType::RichText, and SelectOptionChangeset on FieldType::SingleSelect -pub fn apply_cell_data_changeset, T: AsRef>( - changeset: C, - cell_rev: Option, - field_rev: T, -) -> Result { - let field_rev = field_rev.as_ref(); - let field_type = field_rev.field_type_rev.into(); - let s = match field_type { - FieldType::RichText => RichTextTypeOption::from(field_rev).apply_changeset(changeset, cell_rev), - FieldType::Number => NumberTypeOption::from(field_rev).apply_changeset(changeset, cell_rev), - FieldType::DateTime => DateTypeOption::from(field_rev).apply_changeset(changeset, cell_rev), - FieldType::SingleSelect => SingleSelectTypeOption::from(field_rev).apply_changeset(changeset, cell_rev), - FieldType::MultiSelect => MultiSelectTypeOption::from(field_rev).apply_changeset(changeset, cell_rev), - FieldType::Checkbox => CheckboxTypeOption::from(field_rev).apply_changeset(changeset, cell_rev), - FieldType::URL => URLTypeOption::from(field_rev).apply_changeset(changeset, cell_rev), - }?; - - Ok(TypeOptionCellData::new(s, field_type).json()) -} - -pub fn decode_cell_data>(data: T, field_rev: &FieldRevision) -> DecodedCellData { - if let Ok(type_option_cell_data) = data.try_into() { - let TypeOptionCellData { data, field_type } = type_option_cell_data; - let to_field_type = field_rev.field_type_rev.into(); - match try_decode_cell_data(data, field_rev, &field_type, &to_field_type) { - Ok(cell_data) => cell_data, - Err(e) => { - tracing::error!("Decode cell data failed, {:?}", e); - DecodedCellData::default() - } - } - } else { - tracing::error!("Decode type option data failed"); - DecodedCellData::default() - } -} - -pub fn try_decode_cell_data( - encoded_data: String, - field_rev: &FieldRevision, - s_field_type: &FieldType, - t_field_type: &FieldType, -) -> FlowyResult { - let get_cell_data = || { - let data = match t_field_type { - FieldType::RichText => field_rev - .get_type_option_entry::(t_field_type)? - .decode_cell_data(encoded_data, s_field_type, field_rev), - FieldType::Number => field_rev - .get_type_option_entry::(t_field_type)? - .decode_cell_data(encoded_data, s_field_type, field_rev), - FieldType::DateTime => field_rev - .get_type_option_entry::(t_field_type)? - .decode_cell_data(encoded_data, s_field_type, field_rev), - FieldType::SingleSelect => field_rev - .get_type_option_entry::(t_field_type)? - .decode_cell_data(encoded_data, s_field_type, field_rev), - FieldType::MultiSelect => field_rev - .get_type_option_entry::(t_field_type)? - .decode_cell_data(encoded_data, s_field_type, field_rev), - FieldType::Checkbox => field_rev - .get_type_option_entry::(t_field_type)? - .decode_cell_data(encoded_data, s_field_type, field_rev), - FieldType::URL => field_rev - .get_type_option_entry::(t_field_type)? - .decode_cell_data(encoded_data, s_field_type, field_rev), - }; - Some(data) - }; - - match get_cell_data() { - Some(Ok(data)) => Ok(data), - Some(Err(err)) => { - tracing::error!("{:?}", err); - Ok(DecodedCellData::default()) - } - None => Ok(DecodedCellData::default()), - } -} - -pub(crate) struct EncodedCellData(pub Option); - -impl EncodedCellData { - pub fn try_into_inner(self) -> FlowyResult { - match self.0 { - None => Err(ErrorCode::InvalidData.into()), - Some(data) => Ok(data), - } - } -} - -impl std::convert::From for EncodedCellData -where - T: FromStr, -{ - fn from(s: String) -> Self { - match T::from_str(&s) { - Ok(inner) => EncodedCellData(Some(inner)), - Err(e) => { - tracing::error!("Deserialize Cell Data failed: {}", e); - EncodedCellData(None) - } - } - } -} - -/// The data is encoded by protobuf or utf8. You should choose the corresponding decode struct to parse it. -/// -/// For example: -/// -/// * Use DateCellData to parse the data when the FieldType is Date. -/// * Use URLCellData to parse the data when the FieldType is URL. -/// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox. -/// * Check out the implementation of CellDataOperation trait for more information. -#[derive(Default)] -pub struct DecodedCellData { - pub data: Vec, -} - -impl DecodedCellData { - pub fn new>(data: T) -> Self { - Self { - data: data.as_ref().to_vec(), - } - } - - pub fn try_from_bytes>(bytes: T) -> FlowyResult - where - >::Error: std::fmt::Debug, - { - let bytes = bytes.try_into().map_err(internal_error)?; - Ok(Self { data: bytes.to_vec() }) - } - - pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult - where - >::Error: std::fmt::Debug, - { - T::try_from(self.data.as_ref()).map_err(internal_error) - } -} - -impl ToString for DecodedCellData { - fn to_string(&self) -> String { - match String::from_utf8(self.data.clone()) { - Ok(s) => s, - Err(e) => { - tracing::error!("DecodedCellData to string failed: {:?}", e); - "".to_string() - } - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/row/mod.rs b/frontend/rust-lib/flowy-grid/src/services/row/mod.rs index e2727cdf34..60c8421d15 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/mod.rs @@ -1,7 +1,5 @@ -mod cell_data_operation; mod row_builder; mod row_loader; -pub use cell_data_operation::*; pub use row_builder::*; pub(crate) use row_loader::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs index 568c8ed12a..43153f1311 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs @@ -1,22 +1,22 @@ -use crate::services::field::SelectOptionCellContentChangeset; -use crate::services::row::apply_cell_data_changeset; +use crate::services::cell::apply_cell_data_changeset; +use crate::services::field::SelectOptionCellChangeset; use flowy_error::{FlowyError, FlowyResult}; use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; use indexmap::IndexMap; use std::collections::HashMap; use std::sync::Arc; -pub struct CreateRowRevisionBuilder<'a> { - field_rev_map: HashMap<&'a String, &'a Arc>, +pub struct RowRevisionBuilder<'a> { + field_rev_map: HashMap<&'a String, Arc>, payload: CreateRowRevisionPayload, } -impl<'a> CreateRowRevisionBuilder<'a> { +impl<'a> RowRevisionBuilder<'a> { pub fn new(fields: &'a [Arc]) -> Self { let field_rev_map = fields .iter() - .map(|field| (&field.id, field)) - .collect::>>(); + .map(|field| (&field.id, field.clone())) + .collect::>>(); let payload = CreateRowRevisionPayload { row_id: gen_row_id(), @@ -28,14 +28,14 @@ impl<'a> CreateRowRevisionBuilder<'a> { Self { field_rev_map, payload } } - pub fn add_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { + pub fn insert_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { match self.field_rev_map.get(&field_id.to_owned()) { None => { - let msg = format!("Invalid field_id: {}", field_id); + let msg = format!("Can't find the field with id: {}", field_id); Err(FlowyError::internal().context(msg)) } Some(field_rev) => { - let data = apply_cell_data_changeset(&data, None, field_rev)?; + let data = apply_cell_data_changeset(data, None, field_rev)?; let cell = CellRevision::new(data); self.payload.cell_by_field_id.insert(field_id.to_owned(), cell); Ok(()) @@ -43,15 +43,15 @@ impl<'a> CreateRowRevisionBuilder<'a> { } } - pub fn add_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { + pub fn insert_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { match self.field_rev_map.get(&field_id.to_owned()) { None => { let msg = format!("Invalid field_id: {}", field_id); Err(FlowyError::internal().context(msg)) } Some(field_rev) => { - let cell_data = SelectOptionCellContentChangeset::from_insert(&data).to_str(); - let data = apply_cell_data_changeset(&cell_data, None, field_rev)?; + let cell_data = SelectOptionCellChangeset::from_insert(&data).to_str(); + let data = apply_cell_data_changeset(cell_data, None, field_rev)?; let cell = CellRevision::new(data); self.payload.cell_by_field_id.insert(field_id.to_owned(), cell); Ok(()) @@ -71,18 +71,14 @@ impl<'a> CreateRowRevisionBuilder<'a> { self } - pub fn build(self) -> CreateRowRevisionPayload { - self.payload - } -} - -pub fn make_row_rev_from_context(block_id: &str, payload: CreateRowRevisionPayload) -> RowRevision { - RowRevision { - id: payload.row_id, - block_id: block_id.to_owned(), - cells: payload.cell_by_field_id, - height: payload.height, - visibility: payload.visibility, + pub fn build(self, block_id: &str) -> RowRevision { + RowRevision { + id: self.payload.row_id, + block_id: block_id.to_owned(), + cells: self.payload.cell_by_field_id, + height: self.payload.height, + visibility: self.payload.visibility, + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index 96da30e574..3a5aa58c6d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -1,6 +1,6 @@ -use crate::entities::{BlockRowInfo, GridBlock, RepeatedGridBlock, Row}; +use crate::entities::{GridBlockPB, GridRowPB, RepeatedGridBlockPB}; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use flowy_grid_data_model::revision::RowRevision; use std::collections::HashMap; use std::sync::Arc; @@ -9,15 +9,15 @@ pub struct GridBlockSnapshot { pub row_revs: Vec>, } -pub(crate) fn block_from_row_orders(row_orders: Vec) -> Vec { - let mut map: HashMap = HashMap::new(); +pub(crate) fn block_from_row_orders(row_orders: Vec) -> Vec { + let mut map: HashMap = HashMap::new(); row_orders.into_iter().for_each(|row_info| { // Memory Optimization: escape clone block_id let block_id = row_info.block_id().to_owned(); let cloned_block_id = block_id.clone(); map.entry(block_id) - .or_insert_with(|| GridBlock::new(&cloned_block_id, vec![])) - .row_infos + .or_insert_with(|| GridBlockPB::new(&cloned_block_id, vec![])) + .rows .push(row_info); }); map.into_values().collect::>() @@ -35,32 +35,19 @@ pub(crate) fn block_from_row_orders(row_orders: Vec) -> Vec]) -> Vec { - row_revs.iter().map(BlockRowInfo::from).collect::>() +pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc]) -> Vec { + row_revs.iter().map(GridRowPB::from).collect::>() } -pub(crate) fn make_row_from_row_rev(fields: &[Arc], row_rev: Arc) -> Option { - make_rows_from_row_revs(fields, &[row_rev]).pop() +pub(crate) fn make_row_from_row_rev(row_rev: Arc) -> Option { + make_rows_from_row_revs(&[row_rev]).pop() } -pub(crate) fn make_rows_from_row_revs(_fields: &[Arc], row_revs: &[Arc]) -> Vec { - // let field_rev_map = fields - // .iter() - // .map(|field_rev| (&field_rev.id, field_rev)) - // .collect::>(); - - let make_row = |row_rev: &Arc| { - // let cell_by_field_id = row_rev - // .cells - // .clone() - // .into_iter() - // .flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev)) - // .collect::>(); - - Row { - id: row_rev.id.clone(), - height: row_rev.height, - } +pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc]) -> Vec { + let make_row = |row_rev: &Arc| GridRowPB { + block_id: row_rev.block_id.clone(), + id: row_rev.id.clone(), + height: row_rev.height, }; row_revs.iter().map(make_row).collect::>() @@ -69,15 +56,15 @@ pub(crate) fn make_rows_from_row_revs(_fields: &[Arc], row_revs: pub(crate) fn make_grid_blocks( block_ids: Option>, block_snapshots: Vec, -) -> FlowyResult { +) -> FlowyResult { match block_ids { None => Ok(block_snapshots .into_iter() .map(|snapshot| { let row_orders = make_row_orders_from_row_revs(&snapshot.row_revs); - GridBlock::new(&snapshot.block_id, row_orders) + GridBlockPB::new(&snapshot.block_id, row_orders) }) - .collect::>() + .collect::>() .into()), Some(block_ids) => { let block_meta_data_map: HashMap<&String, &Vec>> = block_snapshots @@ -91,7 +78,7 @@ pub(crate) fn make_grid_blocks( None => {} Some(row_revs) => { let row_orders = make_row_orders_from_row_revs(row_revs); - grid_blocks.push(GridBlock::new(&block_id, row_orders)); + grid_blocks.push(GridBlockPB::new(&block_id, row_orders)); } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs index 866b0a51e6..be283981cf 100644 --- a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs @@ -1,5 +1,10 @@ -use crate::entities::GridLayoutType; +use crate::entities::{ + GridLayoutPB, GridLayoutType, GridSettingPB, RepeatedGridFilterPB, RepeatedGridGroupPB, RepeatedGridSortPB, +}; +use flowy_grid_data_model::revision::{FieldRevision, GridSettingRevision}; use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams}; +use std::collections::HashMap; +use std::sync::Arc; pub struct GridSettingChangesetBuilder { params: GridSettingChangesetParams, @@ -34,3 +39,42 @@ impl GridSettingChangesetBuilder { self.params } } + +pub fn make_grid_setting(grid_setting_rev: &GridSettingRevision, field_revs: &[Arc]) -> GridSettingPB { + let current_layout_type: GridLayoutType = grid_setting_rev.layout.clone().into(); + let filters_by_field_id = grid_setting_rev + .get_all_filter(field_revs) + .map(|filters_by_field_id| { + filters_by_field_id + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>() + }) + .unwrap_or_default(); + let groups_by_field_id = grid_setting_rev + .get_all_group() + .map(|groups_by_field_id| { + groups_by_field_id + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>() + }) + .unwrap_or_default(); + let sorts_by_field_id = grid_setting_rev + .get_all_sort() + .map(|sorts_by_field_id| { + sorts_by_field_id + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect::>() + }) + .unwrap_or_default(); + + GridSettingPB { + layouts: GridLayoutPB::all(), + current_layout_type, + filters_by_field_id, + groups_by_field_id, + sorts_by_field_id, + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs index 537f789f9b..1ba97afd9a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs @@ -20,7 +20,12 @@ impl GridTaskQueue { } pub(crate) fn push(&mut self, task: &Task) { - let task_type = match task.content { + if task.content.is_none() { + tracing::warn!("Ignore task: {} with empty content", task.id); + return; + } + + let task_type = match task.content.as_ref().unwrap() { TaskContent::Snapshot => TaskType::Snapshot, TaskContent::Filter { .. } => TaskType::Filter, }; @@ -28,7 +33,7 @@ impl GridTaskQueue { ty: task_type, id: task.id, }; - match self.index_tasks.entry("1".to_owned()) { + match self.index_tasks.entry(task.handler_id.clone()) { Entry::Occupied(entry) => { let mut list = entry.get().borrow_mut(); assert!(list.peek().map(|old_id| pending_task.id >= old_id.id).unwrap_or(true)); @@ -44,6 +49,11 @@ impl GridTaskQueue { } } + #[allow(dead_code)] + pub(crate) fn clear(&mut self) { + self.queue.clear(); + } + pub(crate) fn mut_head(&mut self, mut f: F) -> Option where F: FnMut(&mut TaskList) -> Option, diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs index 713fc15c86..121329edfb 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs @@ -1,4 +1,5 @@ use crate::services::tasks::scheduler::GridTaskScheduler; + use std::sync::Arc; use std::time::Duration; use tokio::sync::{watch, RwLock}; @@ -7,13 +8,13 @@ use tokio::time::interval; pub struct GridTaskRunner { scheduler: Arc>, debounce_duration: Duration, - notifier: Option>, + notifier: Option>, } impl GridTaskRunner { pub fn new( scheduler: Arc>, - notifier: watch::Receiver<()>, + notifier: watch::Receiver, debounce_duration: Duration, ) -> Self { Self { @@ -34,12 +35,13 @@ impl GridTaskRunner { // The runner will be stopped if the corresponding Sender drop. break; } + + if *notifier.borrow() { + break; + } let mut interval = interval(self.debounce_duration); interval.tick().await; - - if let Err(e) = self.scheduler.write().await.process_next_task().await { - tracing::error!("{:?}", e); - } + let _ = self.scheduler.write().await.process_next_task().await; } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs index 14494842ec..73ba298d9b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs @@ -3,8 +3,8 @@ use crate::services::tasks::runner::GridTaskRunner; use crate::services::tasks::store::GridTaskStore; use crate::services::tasks::task::Task; -use crate::services::tasks::TaskId; -use flowy_error::{FlowyError, FlowyResult}; +use crate::services::tasks::{TaskContent, TaskId, TaskStatus}; +use flowy_error::FlowyError; use lib_infra::future::BoxResultFuture; use std::collections::HashMap; use std::sync::Arc; @@ -12,21 +12,21 @@ use std::time::Duration; use tokio::sync::{watch, RwLock}; pub(crate) trait GridTaskHandler: Send + Sync + 'static { - fn handler_id(&self) -> &TaskHandlerId; + fn handler_id(&self) -> &str; - fn process_task(&self, task: Task) -> BoxResultFuture<(), FlowyError>; + fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError>; } pub struct GridTaskScheduler { queue: GridTaskQueue, store: GridTaskStore, - notifier: watch::Sender<()>, + notifier: watch::Sender, handlers: HashMap>, } impl GridTaskScheduler { pub(crate) fn new() -> Arc> { - let (notifier, rx) = watch::channel(()); + let (notifier, rx) = watch::channel(false); let scheduler = Self { queue: GridTaskQueue::new(), @@ -57,25 +57,38 @@ impl GridTaskScheduler { let _ = self.handlers.remove(handler_id.as_ref()); } - pub(crate) async fn process_next_task(&mut self) -> FlowyResult<()> { - let mut get_next_task = || { - let pending_task = self.queue.mut_head(|list| list.pop())?; - let task = self.store.remove_task(&pending_task.id)?; - Some(task) - }; - - if let Some(task) = get_next_task() { - match self.handlers.get(&task.handler_id) { - None => {} - Some(handler) => { - let _ = handler.process_task(task).await; - } - } - } - Ok(()) + #[allow(dead_code)] + pub(crate) fn stop(&mut self) { + let _ = self.notifier.send(true); + self.queue.clear(); + self.store.clear(); } - pub(crate) fn register_task(&mut self, task: Task) { + pub(crate) async fn process_next_task(&mut self) -> Option<()> { + let pending_task = self.queue.mut_head(|list| list.pop())?; + let mut task = self.store.remove_task(&pending_task.id)?; + let handler = self.handlers.get(&task.handler_id)?; + + let ret = task.ret.take()?; + let content = task.content.take()?; + + task.set_status(TaskStatus::Processing); + let _ = match handler.process_content(content).await { + Ok(_) => { + task.set_status(TaskStatus::Done); + let _ = ret.send(task.into()); + } + Err(e) => { + tracing::error!("Process task failed: {:?}", e); + task.set_status(TaskStatus::Failure); + let _ = ret.send(task.into()); + } + }; + self.notify(); + None + } + + pub(crate) fn add_task(&mut self, task: Task) { assert!(!task.is_finished()); self.queue.push(&task); self.store.insert_task(task); @@ -87,6 +100,87 @@ impl GridTaskScheduler { } pub(crate) fn notify(&self) { - let _ = self.notifier.send(()); + let _ = self.notifier.send(false); + } +} + +#[cfg(test)] +mod tests { + use crate::services::grid_editor_task::GridServiceTaskScheduler; + use crate::services::tasks::{GridTaskHandler, GridTaskScheduler, Task, TaskContent, TaskStatus}; + use flowy_error::FlowyError; + use lib_infra::future::BoxResultFuture; + use std::sync::Arc; + use std::time::Duration; + use tokio::time::interval; + + #[tokio::test] + async fn task_scheduler_snapshot_task_test() { + let scheduler = GridTaskScheduler::new(); + scheduler + .write() + .await + .register_handler(Arc::new(MockGridTaskHandler())); + + let task_id = scheduler.gen_task_id().await; + let mut task = Task::new("1", task_id, TaskContent::Snapshot); + let rx = task.rx.take().unwrap(); + scheduler.write().await.add_task(task); + assert_eq!(rx.await.unwrap().status, TaskStatus::Done); + } + + #[tokio::test] + async fn task_scheduler_snapshot_task_cancel_test() { + let scheduler = GridTaskScheduler::new(); + scheduler + .write() + .await + .register_handler(Arc::new(MockGridTaskHandler())); + + let task_id = scheduler.gen_task_id().await; + let mut task = Task::new("1", task_id, TaskContent::Snapshot); + let rx = task.rx.take().unwrap(); + scheduler.write().await.add_task(task); + scheduler.write().await.stop(); + + assert_eq!(rx.await.unwrap().status, TaskStatus::Cancel); + } + + #[tokio::test] + async fn task_scheduler_multi_task_test() { + let scheduler = GridTaskScheduler::new(); + scheduler + .write() + .await + .register_handler(Arc::new(MockGridTaskHandler())); + + let task_id = scheduler.gen_task_id().await; + let mut task_1 = Task::new("1", task_id, TaskContent::Snapshot); + let rx_1 = task_1.rx.take().unwrap(); + + let task_id = scheduler.gen_task_id().await; + let mut task_2 = Task::new("1", task_id, TaskContent::Snapshot); + let rx_2 = task_2.rx.take().unwrap(); + + scheduler.write().await.add_task(task_1); + scheduler.write().await.add_task(task_2); + + assert_eq!(rx_1.await.unwrap().status, TaskStatus::Done); + assert_eq!(rx_2.await.unwrap().status, TaskStatus::Done); + } + struct MockGridTaskHandler(); + impl GridTaskHandler for MockGridTaskHandler { + fn handler_id(&self) -> &str { + "1" + } + + fn process_content(&self, _content: TaskContent) -> BoxResultFuture<(), FlowyError> { + Box::pin(async move { + let mut interval = interval(Duration::from_secs(1)); + interval.tick().await; + interval.tick().await; + Ok(()) + }) + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs index 21aae60bc4..9f14889e4d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs @@ -1,6 +1,7 @@ use crate::services::tasks::task::Task; -use crate::services::tasks::TaskId; +use crate::services::tasks::{TaskId, TaskStatus}; use std::collections::HashMap; +use std::mem; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering::SeqCst; @@ -25,6 +26,18 @@ impl GridTaskStore { self.tasks.remove(task_id) } + #[allow(dead_code)] + pub(crate) fn clear(&mut self) { + let tasks = mem::take(&mut self.tasks); + tasks.into_values().for_each(|mut task| { + if task.ret.is_some() { + let ret = task.ret.take().unwrap(); + task.set_status(TaskStatus::Cancel); + let _ = ret.send(task.into()); + } + }); + } + pub(crate) fn next_task_id(&self) -> TaskId { let _ = self.task_id_counter.fetch_add(1, SeqCst); self.task_id_counter.load(SeqCst) diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs index 92575dabdc..92950b02aa 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs +++ b/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs @@ -1,3 +1,5 @@ +#![allow(clippy::all)] +#![allow(dead_code)] use crate::services::row::GridBlockSnapshot; use crate::services::tasks::queue::TaskHandlerId; use std::cmp::Ordering; @@ -60,14 +62,59 @@ pub(crate) enum TaskContent { Filter(FilterTaskContext), } +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum TaskStatus { + Pending, + Processing, + Done, + Failure, + Cancel, +} + pub(crate) struct Task { - pub handler_id: TaskHandlerId, pub id: TaskId, - pub content: TaskContent, + pub handler_id: TaskHandlerId, + pub content: Option, + status: TaskStatus, + pub ret: Option>, + pub rx: Option>, +} + +pub(crate) struct TaskResult { + pub id: TaskId, + pub(crate) status: TaskStatus, +} + +impl std::convert::From for TaskResult { + fn from(task: Task) -> Self { + TaskResult { + id: task.id, + status: task.status, + } + } } impl Task { + pub fn new(handler_id: &str, id: TaskId, content: TaskContent) -> Self { + let (ret, rx) = tokio::sync::oneshot::channel(); + Self { + handler_id: handler_id.to_owned(), + id, + content: Some(content), + ret: Some(ret), + rx: Some(rx), + status: TaskStatus::Pending, + } + } + + pub fn set_status(&mut self, status: TaskStatus) { + self.status = status; + } + pub fn is_finished(&self) -> bool { - todo!() + match self.status { + TaskStatus::Done => true, + _ => false, + } } } diff --git a/frontend/rust-lib/flowy-grid/src/util.rs b/frontend/rust-lib/flowy-grid/src/util.rs index e0055d09b6..3b48e313a9 100644 --- a/frontend/rust-lib/flowy-grid/src/util.rs +++ b/frontend/rust-lib/flowy-grid/src/util.rs @@ -4,29 +4,29 @@ use flowy_grid_data_model::revision::BuildGridContext; use flowy_sync::client_grid::GridBuilder; pub fn make_default_grid() -> BuildGridContext { + let mut grid_builder = GridBuilder::new(); // text let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) .name("Name") .visibility(true) .primary(true) .build(); + grid_builder.add_field(text_field); // single select let single_select = SingleSelectTypeOptionBuilder::default(); let single_select_field = FieldBuilder::new(single_select).name("Type").visibility(true).build(); + grid_builder.add_field(single_select_field); // checkbox let checkbox_field = FieldBuilder::from_field_type(&FieldType::Checkbox) .name("Done") .visibility(true) .build(); + grid_builder.add_field(checkbox_field); - GridBuilder::default() - .add_field(text_field) - .add_field(single_select_field) - .add_field(checkbox_field) - .add_empty_row() - .add_empty_row() - .add_empty_row() - .build() + grid_builder.add_empty_row(); + grid_builder.add_empty_row(); + grid_builder.add_empty_row(); + grid_builder.build() } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/block_test.rs similarity index 82% rename from frontend/rust-lib/flowy-grid/tests/grid/block_test.rs rename to frontend/rust-lib/flowy-grid/tests/grid/block_test/block_test.rs index 51094e2b3f..6ded1f7060 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/block_test.rs @@ -1,5 +1,6 @@ -use crate::grid::script::EditorScript::*; -use crate::grid::script::*; +use crate::grid::block_test::script::GridRowTest; +use crate::grid::block_test::script::RowScript::*; + use flowy_grid_data_model::revision::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset}; #[tokio::test] @@ -10,7 +11,7 @@ async fn grid_create_block() { CreateBlock { block: block_meta_rev }, AssertBlockCount(2), ]; - GridEditorTest::new().await.run_scripts(scripts).await; + GridRowTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -36,5 +37,5 @@ async fn grid_update_block() { block: cloned_grid_block, }, ]; - GridEditorTest::new().await.run_scripts(scripts).await; + GridRowTest::new().await.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/mod.rs new file mode 100644 index 0000000000..c982869655 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/mod.rs @@ -0,0 +1,5 @@ +#![allow(clippy::module_inception)] +mod block_test; +mod row_test; +mod script; +pub mod util; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs new file mode 100644 index 0000000000..7b64a44b48 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs @@ -0,0 +1,135 @@ +use crate::grid::block_test::script::RowScript::*; +use crate::grid::block_test::script::{CreateRowScriptBuilder, GridRowTest}; +use crate::grid::grid_editor::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER}; +use flowy_grid::entities::FieldType; +use flowy_grid::services::field::{NO, SELECTION_IDS_SEPARATOR}; +use flowy_grid_data_model::revision::RowMetaChangeset; + +#[tokio::test] +async fn grid_create_row_count_test() { + let mut test = GridRowTest::new().await; + let scripts = vec![ + AssertRowCount(5), + CreateEmptyRow, + CreateEmptyRow, + CreateRow { + row_rev: test.row_builder().build(), + }, + AssertRowCount(8), + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_update_row() { + let mut test = GridRowTest::new().await; + let row_rev = test.row_builder().build(); + let changeset = RowMetaChangeset { + row_id: row_rev.id.clone(), + height: None, + visibility: None, + cell_by_field_id: Default::default(), + }; + + let scripts = vec![AssertRowCount(5), CreateRow { row_rev }, UpdateRow { changeset }]; + test.run_scripts(scripts).await; + + let expected_row = test.last_row().unwrap(); + let scripts = vec![AssertRow { expected_row }, AssertRowCount(6)]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_delete_row() { + let mut test = GridRowTest::new().await; + let row_1 = test.row_builder().build(); + let row_2 = test.row_builder().build(); + let row_ids = vec![row_1.id.clone(), row_2.id.clone()]; + let scripts = vec![ + AssertRowCount(5), + CreateRow { row_rev: row_1 }, + CreateRow { row_rev: row_2 }, + AssertBlockCount(1), + AssertBlock { + block_index: 0, + row_count: 7, + start_row_index: 0, + }, + DeleteRows { row_ids }, + AssertBlock { + block_index: 0, + row_count: 5, + start_row_index: 0, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_row_add_cells_test() { + let mut test = GridRowTest::new().await; + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert(FieldType::RichText, "hello world", "hello world"); + builder.insert(FieldType::DateTime, "1647251762", "2022/03/14"); + builder.insert(FieldType::Number, "18,443", "$18,443.00"); + builder.insert(FieldType::Checkbox, "false", NO); + builder.insert(FieldType::URL, "https://appflowy.io", "https://appflowy.io"); + builder.insert_single_select_cell(|mut options| options.remove(0), COMPLETED); + builder.insert_multi_select_cell( + |options| options, + &vec![GOOGLE, FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), + ); + let scripts = builder.build(); + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_row_insert_number_test() { + let mut test = GridRowTest::new().await; + for (val, expected) in &[("1647251762", "2022/03/14"), ("2022/03/14", ""), ("", "")] { + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert(FieldType::DateTime, val, expected); + let scripts = builder.build(); + test.run_scripts(scripts).await; + } +} + +#[tokio::test] +async fn grid_row_insert_date_test() { + let mut test = GridRowTest::new().await; + for (val, expected) in &[ + ("18,443", "$18,443.00"), + ("0", "$0.00"), + ("100000", "$100,000.00"), + ("$100,000.00", "$100,000.00"), + ("", ""), + ] { + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert(FieldType::Number, val, expected); + let scripts = builder.build(); + test.run_scripts(scripts).await; + } +} +#[tokio::test] +async fn grid_row_insert_single_select_test() { + let mut test = GridRowTest::new().await; + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert_single_select_cell(|mut options| options.pop().unwrap(), PAUSED); + let scripts = builder.build(); + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_row_insert_multi_select_test() { + let mut test = GridRowTest::new().await; + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert_multi_select_cell( + |mut options| { + options.remove(0); + options + }, + &vec![FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), + ); + let scripts = builder.build(); + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs new file mode 100644 index 0000000000..33548f97c5 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs @@ -0,0 +1,373 @@ +use crate::grid::block_test::script::RowScript::{AssertCell, CreateRow}; +use crate::grid::block_test::util::GridRowTestBuilder; +use crate::grid::grid_editor::GridEditorTest; + +use flowy_grid::entities::{CellIdentifierParams, FieldType, GridRowPB}; +use flowy_grid::services::field::*; +use flowy_grid_data_model::revision::{ + GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision, +}; +use std::collections::HashMap; +use std::sync::Arc; +use strum::IntoEnumIterator; + +pub enum RowScript { + CreateEmptyRow, + CreateRow { + row_rev: RowRevision, + }, + UpdateRow { + changeset: RowMetaChangeset, + }, + AssertRow { + expected_row: RowRevision, + }, + DeleteRows { + row_ids: Vec, + }, + AssertCell { + row_id: String, + field_id: String, + field_type: FieldType, + expected: String, + }, + AssertRowCount(usize), + CreateBlock { + block: GridBlockMetaRevision, + }, + UpdateBlock { + changeset: GridBlockMetaRevisionChangeset, + }, + AssertBlockCount(usize), + AssertBlock { + block_index: usize, + row_count: i32, + start_row_index: i32, + }, + AssertBlockEqual { + block_index: usize, + block: GridBlockMetaRevision, + }, +} + +pub struct GridRowTest { + inner: GridEditorTest, +} + +impl GridRowTest { + pub async fn new() -> Self { + let editor_test = GridEditorTest::new().await; + Self { inner: editor_test } + } + + pub fn last_row(&self) -> Option { + self.row_revs.last().map(|a| a.clone().as_ref().clone()) + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub fn row_builder(&self) -> GridRowTestBuilder { + GridRowTestBuilder::new(self.block_id(), &self.field_revs) + } + + pub async fn run_script(&mut self, script: RowScript) { + match script { + RowScript::CreateEmptyRow => { + let row_order = self.editor.create_row(None).await.unwrap(); + self.row_order_by_row_id + .insert(row_order.row_id().to_owned(), row_order); + self.row_revs = self.get_row_revs().await; + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + } + RowScript::CreateRow { row_rev } => { + let row_orders = self.editor.insert_rows(vec![row_rev]).await.unwrap(); + for row_order in row_orders { + self.row_order_by_row_id + .insert(row_order.row_id().to_owned(), row_order); + } + self.row_revs = self.get_row_revs().await; + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + } + RowScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(), + RowScript::DeleteRows { row_ids } => { + let row_orders = row_ids + .into_iter() + .map(|row_id| self.row_order_by_row_id.get(&row_id).unwrap().clone()) + .collect::>(); + + self.editor.delete_rows(row_orders).await.unwrap(); + self.row_revs = self.get_row_revs().await; + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + } + RowScript::AssertCell { + row_id, + field_id, + field_type, + expected, + } => { + let id = CellIdentifierParams { + grid_id: self.grid_id.clone(), + field_id, + row_id, + }; + self.compare_cell_content(id, field_type, expected).await; + } + RowScript::AssertRow { expected_row } => { + let row = &*self + .row_revs + .iter() + .find(|row| row.id == expected_row.id) + .cloned() + .unwrap(); + assert_eq!(&expected_row, row); + } + RowScript::AssertRowCount(expected_row_count) => { + assert_eq!(expected_row_count, self.row_revs.len()); + } + RowScript::CreateBlock { block } => { + self.editor.create_block(block).await.unwrap(); + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + } + RowScript::UpdateBlock { changeset: change } => { + self.editor.update_block(change).await.unwrap(); + } + RowScript::AssertBlockCount(count) => { + assert_eq!(self.editor.get_block_meta_revs().await.unwrap().len(), count); + } + RowScript::AssertBlock { + block_index, + row_count, + start_row_index, + } => { + assert_eq!(self.block_meta_revs[block_index].row_count, row_count); + assert_eq!(self.block_meta_revs[block_index].start_row_index, start_row_index); + } + RowScript::AssertBlockEqual { block_index, block } => { + let blocks = self.editor.get_block_meta_revs().await.unwrap(); + let compared_block = blocks[block_index].clone(); + assert_eq!(compared_block, Arc::new(block)); + } + } + } + + async fn compare_cell_content(&self, cell_id: CellIdentifierParams, field_type: FieldType, expected: String) { + match field_type { + FieldType::RichText => { + let cell_data = self + .editor + .get_cell_bytes(&cell_id) + .await + .unwrap() + .with_parser(TextCellDataParser()) + .unwrap(); + + assert_eq!(cell_data.as_ref(), &expected); + } + FieldType::Number => { + let field_rev = self.editor.get_field_rev(&cell_id.field_id).await.unwrap(); + let number_type_option = field_rev + .get_type_option_entry::(FieldType::Number.into()) + .unwrap(); + let cell_data = self + .editor + .get_cell_bytes(&cell_id) + .await + .unwrap() + .with_parser(NumberCellDataParser(number_type_option.format)) + .unwrap(); + assert_eq!(cell_data.to_string(), expected); + } + FieldType::DateTime => { + let cell_data = self + .editor + .get_cell_bytes(&cell_id) + .await + .unwrap() + .with_parser(DateCellDataParser()) + .unwrap(); + + assert_eq!(cell_data.date, expected); + } + FieldType::SingleSelect => { + let cell_data = self + .editor + .get_cell_bytes(&cell_id) + .await + .unwrap() + .with_parser(SelectOptionCellDataParser()) + .unwrap(); + let select_option = cell_data.select_options.first().unwrap(); + assert_eq!(select_option.name, expected); + } + FieldType::MultiSelect => { + let cell_data = self + .editor + .get_cell_bytes(&cell_id) + .await + .unwrap() + .with_parser(SelectOptionCellDataParser()) + .unwrap(); + + let s = cell_data + .select_options + .into_iter() + .map(|option| option.name) + .collect::>() + .join(SELECTION_IDS_SEPARATOR); + + assert_eq!(s, expected); + } + + FieldType::Checkbox => { + let cell_data = self + .editor + .get_cell_bytes(&cell_id) + .await + .unwrap() + .with_parser(CheckboxCellDataParser()) + .unwrap(); + assert_eq!(cell_data.to_string(), expected); + } + FieldType::URL => { + let cell_data = self + .editor + .get_cell_bytes(&cell_id) + .await + .unwrap() + .with_parser(URLCellDataParser()) + .unwrap(); + + assert_eq!(cell_data.content, expected); + // assert_eq!(cell_data.url, expected); + } + } + } +} + +impl std::ops::Deref for GridRowTest { + type Target = GridEditorTest; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for GridRowTest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +pub struct CreateRowScriptBuilder<'a> { + builder: GridRowTestBuilder<'a>, + data_by_field_type: HashMap, + output_by_field_type: HashMap, +} + +impl<'a> CreateRowScriptBuilder<'a> { + pub fn new(test: &'a GridRowTest) -> Self { + Self { + builder: test.row_builder(), + data_by_field_type: HashMap::new(), + output_by_field_type: HashMap::new(), + } + } + + pub fn insert(&mut self, field_type: FieldType, input: &str, expected: &str) { + self.data_by_field_type.insert( + field_type, + CellTestData { + input: input.to_string(), + expected: expected.to_owned(), + }, + ); + } + + pub fn insert_single_select_cell(&mut self, f: F, expected: &str) + where + F: Fn(Vec) -> SelectOptionPB, + { + let field_id = self.builder.insert_single_select_cell(f); + self.output_by_field_type.insert( + FieldType::SingleSelect, + CellTestOutput { + field_id, + expected: expected.to_owned(), + }, + ); + } + + pub fn insert_multi_select_cell(&mut self, f: F, expected: &str) + where + F: Fn(Vec) -> Vec, + { + let field_id = self.builder.insert_multi_select_cell(f); + self.output_by_field_type.insert( + FieldType::MultiSelect, + CellTestOutput { + field_id, + expected: expected.to_owned(), + }, + ); + } + + pub fn build(mut self) -> Vec { + let mut scripts = vec![]; + let output_by_field_type = &mut self.output_by_field_type; + + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; + if let Some(data) = self.data_by_field_type.get(&field_type) { + let field_id = match field_type { + FieldType::RichText => self.builder.insert_text_cell(&data.input), + FieldType::Number => self.builder.insert_number_cell(&data.input), + FieldType::DateTime => self.builder.insert_date_cell(&data.input), + FieldType::Checkbox => self.builder.insert_checkbox_cell(&data.input), + FieldType::URL => self.builder.insert_url_cell(&data.input), + _ => "".to_owned(), + }; + + if !field_id.is_empty() { + output_by_field_type.insert( + field_type, + CellTestOutput { + field_id, + expected: data.expected.clone(), + }, + ); + } + } + } + + let row_rev = self.builder.build(); + let row_id = row_rev.id.clone(); + scripts.push(CreateRow { row_rev }); + + for field_type in FieldType::iter() { + if let Some(data) = output_by_field_type.get(&field_type) { + let script = AssertCell { + row_id: row_id.clone(), + field_id: data.field_id.clone(), + field_type, + expected: data.expected.clone(), + }; + scripts.push(script); + } + } + scripts + } +} + +pub struct CellTestData { + pub input: String, + pub expected: String, +} + +struct CellTestOutput { + field_id: String, + expected: String, +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs new file mode 100644 index 0000000000..216a4d4acd --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs @@ -0,0 +1,134 @@ +use flowy_grid::entities::FieldType; +use std::sync::Arc; + +use flowy_grid::services::field::{ + DateCellChangesetPB, MultiSelectTypeOption, SelectOptionPB, SingleSelectTypeOptionPB, SELECTION_IDS_SEPARATOR, +}; +use flowy_grid::services::row::RowRevisionBuilder; +use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; + +use strum::EnumCount; + +pub struct GridRowTestBuilder<'a> { + block_id: String, + field_revs: &'a [Arc], + inner_builder: RowRevisionBuilder<'a>, +} + +impl<'a> GridRowTestBuilder<'a> { + pub fn new(block_id: &str, field_revs: &'a [Arc]) -> Self { + assert_eq!(field_revs.len(), FieldType::COUNT); + let inner_builder = RowRevisionBuilder::new(field_revs); + Self { + block_id: block_id.to_owned(), + field_revs, + inner_builder, + } + } + + pub fn insert_text_cell(&mut self, data: &str) -> String { + let text_field = self.field_rev_with_type(&FieldType::RichText); + self.inner_builder + .insert_cell(&text_field.id, data.to_string()) + .unwrap(); + + text_field.id.clone() + } + + pub fn insert_number_cell(&mut self, data: &str) -> String { + let number_field = self.field_rev_with_type(&FieldType::Number); + self.inner_builder + .insert_cell(&number_field.id, data.to_string()) + .unwrap(); + number_field.id.clone() + } + + pub fn insert_date_cell(&mut self, data: &str) -> String { + let value = serde_json::to_string(&DateCellChangesetPB { + date: Some(data.to_string()), + time: None, + }) + .unwrap(); + let date_field = self.field_rev_with_type(&FieldType::DateTime); + self.inner_builder.insert_cell(&date_field.id, value).unwrap(); + date_field.id.clone() + } + + pub fn insert_checkbox_cell(&mut self, data: &str) -> String { + let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox); + self.inner_builder + .insert_cell(&checkbox_field.id, data.to_string()) + .unwrap(); + + checkbox_field.id.clone() + } + + pub fn insert_url_cell(&mut self, data: &str) -> String { + let url_field = self.field_rev_with_type(&FieldType::URL); + self.inner_builder.insert_cell(&url_field.id, data.to_string()).unwrap(); + url_field.id.clone() + } + + pub fn insert_single_select_cell(&mut self, f: F) -> String + where + F: Fn(Vec) -> SelectOptionPB, + { + let single_select_field = self.field_rev_with_type(&FieldType::SingleSelect); + let type_option = SingleSelectTypeOptionPB::from(&single_select_field); + let option = f(type_option.options); + self.inner_builder + .insert_select_option_cell(&single_select_field.id, option.id) + .unwrap(); + + single_select_field.id.clone() + } + + pub fn insert_multi_select_cell(&mut self, f: F) -> String + where + F: Fn(Vec) -> Vec, + { + let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect); + let type_option = MultiSelectTypeOption::from(&multi_select_field); + let options = f(type_option.options); + let ops_ids = options + .iter() + .map(|option| option.id.clone()) + .collect::>() + .join(SELECTION_IDS_SEPARATOR); + self.inner_builder + .insert_select_option_cell(&multi_select_field.id, ops_ids) + .unwrap(); + + multi_select_field.id.clone() + } + + pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision { + self.field_revs + .iter() + .find(|field_rev| { + let t_field_type: FieldType = field_rev.field_type_rev.into(); + &t_field_type == field_type + }) + .unwrap() + .as_ref() + .clone() + } + + pub fn build(self) -> RowRevision { + self.inner_builder.build(&self.block_id) + } +} + +impl<'a> std::ops::Deref for GridRowTestBuilder<'a> { + type Target = RowRevisionBuilder<'a>; + + fn deref(&self) -> &Self::Target { + &self.inner_builder + } +} + +impl<'a> std::ops::DerefMut for GridRowTestBuilder<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner_builder + } +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/cell_test/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/mod.rs new file mode 100644 index 0000000000..63d424afaf --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/mod.rs @@ -0,0 +1,2 @@ +mod script; +mod test; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/cell_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/script.rs new file mode 100644 index 0000000000..4def72696d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/script.rs @@ -0,0 +1,61 @@ +use crate::grid::grid_editor::GridEditorTest; +use flowy_grid::entities::CellChangesetPB; + +pub enum CellScript { + UpdateCell { changeset: CellChangesetPB, is_err: bool }, +} + +pub struct GridCellTest { + inner: GridEditorTest, +} + +impl GridCellTest { + pub async fn new() -> Self { + let inner = GridEditorTest::new().await; + Self { inner } + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: CellScript) { + // let grid_manager = self.sdk.grid_manager.clone(); + // let pool = self.sdk.user_session.db_pool().unwrap(); + let rev_manager = self.editor.rev_manager(); + let _cache = rev_manager.revision_cache().await; + + match script { + CellScript::UpdateCell { changeset, is_err } => { + let result = self.editor.update_cell(changeset).await; + if is_err { + assert!(result.is_err()) + } else { + let _ = result.unwrap(); + self.row_revs = self.get_row_revs().await; + } + } // CellScript::AssertGridRevisionPad => { + // sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; + // let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap(); + // let grid_pad = grid_rev_manager.load::(None).await.unwrap(); + // println!("{}", grid_pad.delta_str()); + // } + } + } +} + +impl std::ops::Deref for GridCellTest { + type Target = GridEditorTest; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for GridCellTest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs similarity index 62% rename from frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs rename to frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs index 98b50436ab..e8435c2d00 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/cell_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs @@ -1,12 +1,13 @@ -use crate::grid::field_util::make_date_cell_string; -use crate::grid::script::EditorScript::*; -use crate::grid::script::*; -use flowy_grid::entities::{CellChangeset, FieldType}; -use flowy_grid::services::field::{MultiSelectTypeOption, SelectOptionCellContentChangeset, SingleSelectTypeOption}; +use crate::grid::cell_test::script::CellScript::*; +use crate::grid::cell_test::script::GridCellTest; +use crate::grid::field_test::util::make_date_cell_string; +use flowy_grid::entities::{CellChangesetPB, FieldType}; +use flowy_grid::services::field::selection_type_option::SelectOptionCellChangeset; +use flowy_grid::services::field::{MultiSelectTypeOption, SingleSelectTypeOptionPB}; #[tokio::test] async fn grid_cell_update() { - let mut test = GridEditorTest::new().await; + let mut test = GridCellTest::new().await; let field_revs = &test.field_revs; let row_revs = &test.row_revs; let grid_blocks = &test.block_meta_revs; @@ -23,23 +24,23 @@ async fn grid_cell_update() { FieldType::Number => "123".to_string(), FieldType::DateTime => make_date_cell_string("123"), FieldType::SingleSelect => { - let type_option = SingleSelectTypeOption::from(field_rev); - SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() + let type_option = SingleSelectTypeOptionPB::from(field_rev); + SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() } FieldType::MultiSelect => { let type_option = MultiSelectTypeOption::from(field_rev); - SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() + SelectOptionCellChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() } FieldType::Checkbox => "1".to_string(), FieldType::URL => "1".to_string(), }; scripts.push(UpdateCell { - changeset: CellChangeset { + changeset: CellChangesetPB { grid_id: block_id.to_string(), row_id: row_rev.id.clone(), field_id: field_rev.id.clone(), - cell_content_changeset: Some(data), + content: Some(data), }, is_err: false, }); diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/mod.rs new file mode 100644 index 0000000000..5ac4da9f24 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/mod.rs @@ -0,0 +1,3 @@ +mod script; +mod test; +pub mod util; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs new file mode 100644 index 0000000000..6d910f3516 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs @@ -0,0 +1,94 @@ +use crate::grid::grid_editor::GridEditorTest; +use flowy_grid::entities::InsertFieldParams; +use flowy_grid_data_model::revision::FieldRevision; +use flowy_sync::entities::grid::FieldChangesetParams; + +pub enum FieldScript { + CreateField { + params: InsertFieldParams, + }, + UpdateField { + changeset: FieldChangesetParams, + }, + DeleteField { + field_rev: FieldRevision, + }, + AssertFieldCount(usize), + AssertFieldEqual { + field_index: usize, + field_rev: FieldRevision, + }, +} + +pub struct GridFieldTest { + inner: GridEditorTest, +} + +impl GridFieldTest { + pub async fn new() -> Self { + let editor_test = GridEditorTest::new().await; + Self { inner: editor_test } + } + + pub fn grid_id(&self) -> String { + self.grid_id.clone() + } + + pub fn field_count(&self) -> usize { + self.field_count + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: FieldScript) { + match script { + FieldScript::CreateField { params } => { + if !self.editor.contain_field(¶ms.field.id).await { + self.field_count += 1; + } + + self.editor.insert_field(params).await.unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + assert_eq!(self.field_count, self.field_revs.len()); + } + FieldScript::UpdateField { changeset: change } => { + self.editor.update_field(change).await.unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + } + FieldScript::DeleteField { field_rev } => { + if self.editor.contain_field(&field_rev.id).await { + self.field_count -= 1; + } + + self.editor.delete_field(&field_rev.id).await.unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + assert_eq!(self.field_count, self.field_revs.len()); + } + FieldScript::AssertFieldCount(count) => { + assert_eq!(self.editor.get_field_revs(None).await.unwrap().len(), count); + } + FieldScript::AssertFieldEqual { field_index, field_rev } => { + let field_revs = self.editor.get_field_revs(None).await.unwrap(); + assert_eq!(field_revs[field_index].as_ref(), &field_rev); + } + } + } +} + +impl std::ops::Deref for GridFieldTest { + type Target = GridEditorTest; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for GridFieldTest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs similarity index 67% rename from frontend/rust-lib/flowy-grid/tests/grid/field_test.rs rename to frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs index c8eb1a541a..98cb3d0324 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs @@ -1,29 +1,30 @@ -use crate::grid::field_util::*; -use crate::grid::script::EditorScript::*; -use crate::grid::script::*; -use flowy_grid::services::field::{SelectOption, SingleSelectTypeOption}; +use crate::grid::field_test::script::FieldScript::*; +use crate::grid::field_test::script::GridFieldTest; +use crate::grid::field_test::util::*; +use flowy_grid::services::field::selection_type_option::SelectOptionPB; +use flowy_grid::services::field::SingleSelectTypeOptionPB; use flowy_grid_data_model::revision::TypeOptionDataEntry; use flowy_sync::entities::grid::FieldChangesetParams; #[tokio::test] async fn grid_create_field() { - let mut test = GridEditorTest::new().await; - let (params, field_rev) = create_text_field(&test.grid_id); + let mut test = GridFieldTest::new().await; + let (params, field_rev) = create_text_field(&test.grid_id()); let scripts = vec![ CreateField { params }, AssertFieldEqual { - field_index: test.field_count, + field_index: test.field_count(), field_rev, }, ]; test.run_scripts(scripts).await; - let (params, field_rev) = create_single_select_field(&test.grid_id); + let (params, field_rev) = create_single_select_field(&test.grid_id()); let scripts = vec![ CreateField { params }, AssertFieldEqual { - field_index: test.field_count, + field_index: test.field_count(), field_rev, }, ]; @@ -32,9 +33,9 @@ async fn grid_create_field() { #[tokio::test] async fn grid_create_duplicate_field() { - let mut test = GridEditorTest::new().await; - let (params, _) = create_text_field(&test.grid_id); - let field_count = test.field_count; + let mut test = GridFieldTest::new().await; + let (params, _) = create_text_field(&test.grid_id()); + let field_count = test.field_count(); let expected_field_count = field_count + 1; let scripts = vec![ CreateField { params: params.clone() }, @@ -46,11 +47,11 @@ async fn grid_create_duplicate_field() { #[tokio::test] async fn grid_update_field_with_empty_change() { - let mut test = GridEditorTest::new().await; - let (params, field_rev) = create_single_select_field(&test.grid_id); + let mut test = GridFieldTest::new().await; + let (params, field_rev) = create_single_select_field(&test.grid_id()); let changeset = FieldChangesetParams { field_id: field_rev.id.clone(), - grid_id: test.grid_id.clone(), + grid_id: test.grid_id(), ..Default::default() }; @@ -58,7 +59,7 @@ async fn grid_update_field_with_empty_change() { CreateField { params }, UpdateField { changeset }, AssertFieldEqual { - field_index: test.field_count, + field_index: test.field_count(), field_rev, }, ]; @@ -67,14 +68,14 @@ async fn grid_update_field_with_empty_change() { #[tokio::test] async fn grid_update_field() { - let mut test = GridEditorTest::new().await; - let (params, single_select_field) = create_single_select_field(&test.grid_id); + let mut test = GridFieldTest::new().await; + let (params, single_select_field) = create_single_select_field(&test.grid_id()); - let mut single_select_type_option = SingleSelectTypeOption::from(&single_select_field); - single_select_type_option.options.push(SelectOption::new("Unknown")); + let mut single_select_type_option = SingleSelectTypeOptionPB::from(&single_select_field); + single_select_type_option.options.push(SelectOptionPB::new("Unknown")); let changeset = FieldChangesetParams { field_id: single_select_field.id.clone(), - grid_id: test.grid_id.clone(), + grid_id: test.grid_id(), frozen: Some(true), width: Some(1000), type_option_data: Some(single_select_type_option.protobuf_bytes().to_vec()), @@ -91,7 +92,7 @@ async fn grid_update_field() { CreateField { params }, UpdateField { changeset }, AssertFieldEqual { - field_index: test.field_count, + field_index: test.field_count(), field_rev: expected_field_rev, }, ]; @@ -100,9 +101,9 @@ async fn grid_update_field() { #[tokio::test] async fn grid_delete_field() { - let mut test = GridEditorTest::new().await; - let original_field_count = test.field_count; - let (params, text_field_rev) = create_text_field(&test.grid_id); + let mut test = GridFieldTest::new().await; + let original_field_count = test.field_count(); + let (params, text_field_rev) = create_text_field(&test.grid_id()); let scripts = vec![ CreateField { params }, DeleteField { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs similarity index 80% rename from frontend/rust-lib/flowy-grid/tests/grid/field_util.rs rename to frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs index 79b6b35ae3..01424cef74 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_util.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs @@ -1,6 +1,6 @@ -use flowy_grid::services::field::*; - use flowy_grid::entities::*; +use flowy_grid::services::field::selection_type_option::SelectOptionPB; +use flowy_grid::services::field::*; use flowy_grid_data_model::revision::*; pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) { @@ -12,12 +12,12 @@ pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) { let cloned_field_rev = field_rev.clone(); let type_option_data = field_rev - .get_type_option_entry::(field_rev.field_type_rev) + .get_type_option_entry::(field_rev.field_type_rev) .unwrap() .protobuf_bytes() .to_vec(); - let field = Field { + let field = GridFieldPB { id: field_rev.id, name: field_rev.name, desc: field_rev.desc, @@ -39,23 +39,22 @@ pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) { pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) { let single_select = SingleSelectTypeOptionBuilder::default() - .option(SelectOption::new("Done")) - .option(SelectOption::new("Progress")); + .option(SelectOptionPB::new("Done")) + .option(SelectOptionPB::new("Progress")); let field_rev = FieldBuilder::new(single_select).name("Name").visibility(true).build(); let cloned_field_rev = field_rev.clone(); - let field_type: FieldType = field_rev.field_type_rev.into(); let type_option_data = field_rev - .get_type_option_entry::(&field_type) + .get_type_option_entry::(field_rev.field_type_rev) .unwrap() .protobuf_bytes() .to_vec(); - let field = Field { + let field = GridFieldPB { id: field_rev.id, name: field_rev.name, desc: field_rev.desc, - field_type, + field_type: field_rev.field_type_rev.into(), frozen: field_rev.frozen, visibility: field_rev.visibility, width: field_rev.width, @@ -74,7 +73,7 @@ pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRev // The grid will contains all existing field types and there are three empty rows in this grid. pub fn make_date_cell_string(s: &str) -> String { - serde_json::to_string(&DateCellContentChangeset { + serde_json::to_string(&DateCellChangesetPB { date: Some(s.to_string()), time: None, }) diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs deleted file mode 100644 index aad4cb1e7d..0000000000 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::grid::script::EditorScript::*; -use crate::grid::script::*; -use flowy_grid::entities::CreateGridFilterPayload; - -#[tokio::test] -async fn grid_filter_create_test() { - let test = GridEditorTest::new().await; - let field_rev = test.text_field(); - let payload = CreateGridFilterPayload::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); - let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; - GridEditorTest::new().await.run_scripts(scripts).await; -} - -#[tokio::test] -#[should_panic] -async fn grid_filter_invalid_condition_panic_test() { - let test = GridEditorTest::new().await; - let field_rev = test.text_field(); - - // 100 is not a valid condition, so this test should be panic. - let payload = CreateGridFilterPayload::new(field_rev, 100, Some("abc".to_owned())); - let scripts = vec![InsertGridTableFilter { payload }]; - GridEditorTest::new().await.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_delete_test() { - let mut test = GridEditorTest::new().await; - let field_rev = test.text_field().clone(); - let payload = CreateGridFilterPayload::new(&field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); - let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; - test.run_scripts(scripts).await; - - let filter = test.grid_filters().await.pop().unwrap(); - test.run_scripts(vec![ - DeleteGridTableFilter { - filter_id: filter.id, - field_type: field_rev.field_type.clone(), - }, - AssertTableFilterCount { count: 0 }, - ]) - .await; -} - -#[tokio::test] -async fn grid_filter_get_rows_test() {} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs new file mode 100644 index 0000000000..4c6980c527 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs @@ -0,0 +1,2 @@ +mod script; +mod text_filter_test; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs new file mode 100644 index 0000000000..267cb570eb --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs @@ -0,0 +1,97 @@ +#![cfg_attr(rustfmt, rustfmt::skip)] +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use flowy_grid::entities::{CreateGridFilterPayloadPB, GridLayoutType, GridSettingPB}; +use flowy_grid::services::setting::GridSettingChangesetBuilder; +use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision}; +use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams}; +use crate::grid::grid_editor::GridEditorTest; + +pub enum FilterScript { + #[allow(dead_code)] + UpdateGridSetting { + params: GridSettingChangesetParams, + }, + InsertGridTableFilter { + payload: CreateGridFilterPayloadPB, + }, + AssertTableFilterCount { + count: i32, + }, + DeleteGridTableFilter { + filter_id: String, + field_rev: FieldRevision, + }, + #[allow(dead_code)] + AssertGridSetting { + expected_setting: GridSettingPB, + }, +} + +pub struct GridFilterTest { + inner: GridEditorTest, +} + +impl GridFilterTest { + pub async fn new() -> Self { + let editor_test = GridEditorTest::new().await; + Self { + inner: editor_test + } + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: FilterScript) { + match script { + FilterScript::UpdateGridSetting { params } => { + let _ = self.editor.update_grid_setting(params).await.unwrap(); + } + FilterScript::InsertGridTableFilter { payload } => { + let params: CreateGridFilterParams = payload.try_into().unwrap(); + let layout_type = GridLayoutType::Table; + let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type) + .insert_filter(params) + .build(); + let _ = self.editor.update_grid_setting(params).await.unwrap(); + } + FilterScript::AssertTableFilterCount { count } => { + let layout_type = GridLayoutType::Table; + let filters = self.editor.get_grid_filter(&layout_type).await.unwrap(); + assert_eq!(count as usize, filters.len()); + } + FilterScript::DeleteGridTableFilter { filter_id, field_rev} => { + let layout_type = GridLayoutType::Table; + let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type) + .delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.field_type_rev }) + .build(); + let _ = self.editor.update_grid_setting(params).await.unwrap(); + } + FilterScript::AssertGridSetting { expected_setting } => { + let setting = self.editor.get_grid_setting().await.unwrap(); + assert_eq!(expected_setting, setting); + } + } + } +} + + +impl std::ops::Deref for GridFilterTest { + type Target = GridEditorTest; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for GridFilterTest { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs new file mode 100644 index 0000000000..3e45a4053d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs @@ -0,0 +1,51 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::*; +use flowy_grid::entities::{CreateGridFilterPayloadPB, FieldType, TextFilterCondition}; +use flowy_grid_data_model::revision::FieldRevision; + +#[tokio::test] +async fn grid_filter_create_test() { + let mut test = GridFilterTest::new().await; + let field_rev = test.get_field_rev(FieldType::RichText); + let payload = CreateGridFilterPayloadPB::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); + let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +#[should_panic] +async fn grid_filter_invalid_condition_panic_test() { + let mut test = GridFilterTest::new().await; + let field_rev = test.get_field_rev(FieldType::RichText).clone(); + + // 100 is not a valid condition, so this test should be panic. + let payload = CreateGridFilterPayloadPB::new(&field_rev, 100, Some("".to_owned())); + let scripts = vec![InsertGridTableFilter { payload }]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_delete_test() { + let mut test = GridFilterTest::new().await; + let field_rev = test.get_field_rev(FieldType::RichText).clone(); + let payload = create_filter(&field_rev, TextFilterCondition::TextIsEmpty, "abc"); + let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; + test.run_scripts(scripts).await; + + let filter = test.grid_filters().await.pop().unwrap(); + test.run_scripts(vec![ + DeleteGridTableFilter { + filter_id: filter.id, + field_rev: field_rev.as_ref().clone(), + }, + AssertTableFilterCount { count: 0 }, + ]) + .await; +} + +#[tokio::test] +async fn grid_filter_get_rows_test() {} + +fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> CreateGridFilterPayloadPB { + CreateGridFilterPayloadPB::new(field_rev, condition, Some(s.to_owned())) +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs new file mode 100644 index 0000000000..f924b3e803 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs @@ -0,0 +1,258 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +use crate::grid::block_test::util::GridRowTestBuilder; +use bytes::Bytes; +use flowy_grid::entities::*; +use flowy_grid::services::field::SelectOptionPB; +use flowy_grid::services::field::*; +use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor}; +use flowy_grid::services::row::{CreateRowRevisionPayload, RowRevisionBuilder}; +use flowy_grid::services::setting::GridSettingChangesetBuilder; +use flowy_grid_data_model::revision::*; +use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; +use flowy_sync::client_grid::GridBuilder; +use flowy_sync::entities::grid::{ + CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams, +}; +use flowy_test::helper::ViewTest; +use flowy_test::FlowySDKTest; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; +use strum::EnumCount; +use strum::IntoEnumIterator; +use tokio::time::sleep; + +pub struct GridEditorTest { + pub sdk: FlowySDKTest, + pub grid_id: String, + pub editor: Arc, + pub field_revs: Vec>, + pub block_meta_revs: Vec>, + pub row_revs: Vec>, + pub field_count: usize, + pub row_order_by_row_id: HashMap, +} + +impl GridEditorTest { + pub async fn new() -> Self { + let sdk = FlowySDKTest::default(); + let _ = sdk.init_user().await; + let build_context = make_test_grid(); + let view_data: Bytes = build_context.into(); + let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await; + let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap(); + let field_revs = editor.get_field_revs(None).await.unwrap(); + let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); + let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs; + assert_eq!(block_meta_revs.len(), 1); + + // It seems like you should add the field in the make_test_grid() function. + // Because we assert the initialize count of the fields is equal to FieldType::COUNT. + assert_eq!(field_revs.len(), FieldType::COUNT); + + let grid_id = test.view.id; + Self { + sdk, + grid_id, + editor, + field_revs, + block_meta_revs, + row_revs, + field_count: FieldType::COUNT, + row_order_by_row_id: HashMap::default(), + } + } + + pub async fn get_row_revs(&self) -> Vec> { + self.editor + .grid_block_snapshots(None) + .await + .unwrap() + .pop() + .unwrap() + .row_revs + } + + pub async fn grid_filters(&self) -> Vec { + let layout_type = GridLayoutType::Table; + self.editor.get_grid_filter(&layout_type).await.unwrap() + } + + pub fn get_field_rev(&self, field_type: FieldType) -> &Arc { + self.field_revs + .iter() + .filter(|field_rev| { + let t_field_type: FieldType = field_rev.field_type_rev.into(); + t_field_type == field_type + }) + .collect::>() + .pop() + .unwrap() + } + + pub fn block_id(&self) -> &str { + &self.block_meta_revs.last().unwrap().block_id + } +} + +pub const GOOGLE: &str = "Google"; +pub const FACEBOOK: &str = "Facebook"; +pub const TWITTER: &str = "Twitter"; + +pub const COMPLETED: &str = "Completed"; +pub const PLANNED: &str = "Planned"; +pub const PAUSED: &str = "Paused"; + +// This grid is assumed to contain all the Fields. +fn make_test_grid() -> BuildGridContext { + let mut grid_builder = GridBuilder::new(); + // Iterate through the FieldType to create the corresponding Field. + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; + + // The + match field_type { + FieldType::RichText => { + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .build(); + grid_builder.add_field(text_field); + } + FieldType::Number => { + // Number + let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); + let number_field = FieldBuilder::new(number).name("Price").visibility(true).build(); + grid_builder.add_field(number_field); + } + FieldType::DateTime => { + // Date + let date = DateTypeOptionBuilder::default() + .date_format(DateFormat::US) + .time_format(TimeFormat::TwentyFourHour); + let date_field = FieldBuilder::new(date).name("Time").visibility(true).build(); + grid_builder.add_field(date_field); + } + FieldType::SingleSelect => { + // Single Select + let single_select = SingleSelectTypeOptionBuilder::default() + .option(SelectOptionPB::new(COMPLETED)) + .option(SelectOptionPB::new(PLANNED)) + .option(SelectOptionPB::new(PAUSED)); + let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build(); + grid_builder.add_field(single_select_field); + } + FieldType::MultiSelect => { + // MultiSelect + let multi_select = MultiSelectTypeOptionBuilder::default() + .option(SelectOptionPB::new(GOOGLE)) + .option(SelectOptionPB::new(FACEBOOK)) + .option(SelectOptionPB::new(TWITTER)); + let multi_select_field = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + grid_builder.add_field(multi_select_field); + } + FieldType::Checkbox => { + // Checkbox + let checkbox = CheckboxTypeOptionBuilder::default(); + let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build(); + grid_builder.add_field(checkbox_field); + } + FieldType::URL => { + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + grid_builder.add_field(url_field); + } + } + } + + // We have many assumptions base on the number of the rows, so do not change the number of the loop. + for i in 0..5 { + let block_id = grid_builder.block_id().to_owned(); + let field_revs = grid_builder.field_revs(); + let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs); + match i { + 0 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("A"), + FieldType::Number => row_builder.insert_number_cell("1"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + } + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + _ => "".to_owned(), + }; + } + } + 1 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("B"), + FieldType::Number => row_builder.insert_number_cell("2"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + } + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + _ => "".to_owned(), + }; + } + } + 2 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("C"), + FieldType::Number => row_builder.insert_number_cell("3"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(1)) + } + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + } + 3 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("D"), + FieldType::Number => row_builder.insert_number_cell("4"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(1)) + } + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + } + 4 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("E"), + FieldType::Number => row_builder.insert_number_cell("5"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(2)) + } + + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + } + _ => {} + } + + let row_rev = row_builder.build(); + grid_builder.add_row(row_rev); + } + grid_builder.build() +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs new file mode 100644 index 0000000000..63bea4cc3d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -0,0 +1,381 @@ +use crate::grid::script::EditorScript::*; +use crate::grid::script::*; +use chrono::NaiveDateTime; +use flowy_grid::services::field::{ + DateCellContentChangeset, DateCellData, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset, + SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, +}; +use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder}; +use flowy_grid_data_model::entities::{ + CellChangeset, FieldChangesetParams, FieldType, GridBlockInfoChangeset, GridBlockMetaSnapshot, RowMetaChangeset, + TypeOptionDataEntry, +}; + +#[tokio::test] +async fn grid_create_field() { + let mut test = GridEditorTest::new().await; + let (text_field_params, text_field_meta) = create_text_field(&test.grid_id); + let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id); + let scripts = vec![ + CreateField { + params: text_field_params, + }, + AssertFieldEqual { + field_index: test.field_count, + field_meta: text_field_meta, + }, + ]; + test.run_scripts(scripts).await; + + let scripts = vec![ + CreateField { + params: single_select_params, + }, + AssertFieldEqual { + field_index: test.field_count, + field_meta: single_select_field, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_create_duplicate_field() { + let mut test = GridEditorTest::new().await; + let (params, _) = create_text_field(&test.grid_id); + let field_count = test.field_count; + let expected_field_count = field_count + 1; + let scripts = vec![ + CreateField { params: params.clone() }, + CreateField { params }, + AssertFieldCount(expected_field_count), + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_update_field_with_empty_change() { + let mut test = GridEditorTest::new().await; + let (params, field_meta) = create_single_select_field(&test.grid_id); + let changeset = FieldChangesetParams { + field_id: field_meta.id.clone(), + grid_id: test.grid_id.clone(), + ..Default::default() + }; + + let scripts = vec![ + CreateField { params }, + UpdateField { changeset }, + AssertFieldEqual { + field_index: test.field_count, + field_meta, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_update_field() { + let mut test = GridEditorTest::new().await; + let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id); + let mut cloned_field = single_select_field.clone(); + + let mut single_select_type_option = SingleSelectTypeOption::from(&single_select_field); + single_select_type_option.options.push(SelectOption::new("Unknown")); + let changeset = FieldChangesetParams { + field_id: single_select_field.id.clone(), + grid_id: test.grid_id.clone(), + frozen: Some(true), + width: Some(1000), + type_option_data: Some(single_select_type_option.protobuf_bytes().to_vec()), + ..Default::default() + }; + + cloned_field.frozen = true; + cloned_field.width = 1000; + cloned_field.insert_type_option_entry(&single_select_type_option); + + let scripts = vec![ + CreateField { + params: single_select_params, + }, + UpdateField { changeset }, + AssertFieldEqual { + field_index: test.field_count, + field_meta: cloned_field, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_delete_field() { + let mut test = GridEditorTest::new().await; + let expected_field_count = test.field_count; + let (text_params, text_field) = create_text_field(&test.grid_id); + let scripts = vec![ + CreateField { params: text_params }, + DeleteField { field_meta: text_field }, + AssertFieldCount(expected_field_count), + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_create_block() { + let grid_block = GridBlockMetaSnapshot::new(); + let scripts = vec![ + AssertBlockCount(1), + CreateBlock { block: grid_block }, + AssertBlockCount(2), + ]; + GridEditorTest::new().await.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_update_block() { + let grid_block = GridBlockMetaSnapshot::new(); + let mut cloned_grid_block = grid_block.clone(); + let changeset = GridBlockInfoChangeset { + block_id: grid_block.block_id.clone(), + start_row_index: Some(2), + row_count: Some(10), + }; + + cloned_grid_block.start_row_index = 2; + cloned_grid_block.row_count = 10; + + let scripts = vec![ + AssertBlockCount(1), + CreateBlock { block: grid_block }, + UpdateBlock { changeset }, + AssertBlockCount(2), + AssertBlockEqual { + block_index: 1, + block: cloned_grid_block, + }, + ]; + GridEditorTest::new().await.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_create_row() { + let scripts = vec![AssertRowCount(3), CreateEmptyRow, CreateEmptyRow, AssertRowCount(5)]; + GridEditorTest::new().await.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_create_row2() { + let mut test = GridEditorTest::new().await; + let create_row_context = CreateRowMetaBuilder::new(&test.field_metas).build(); + let scripts = vec![ + AssertRowCount(3), + CreateRow { + context: create_row_context, + }, + AssertRowCount(4), + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_update_row() { + let mut test = GridEditorTest::new().await; + let context = CreateRowMetaBuilder::new(&test.field_metas).build(); + let changeset = RowMetaChangeset { + row_id: context.row_id.clone(), + height: None, + visibility: None, + cell_by_field_id: Default::default(), + }; + + let scripts = vec![ + AssertRowCount(3), + CreateRow { context }, + UpdateRow { + changeset: changeset.clone(), + }, + AssertRow { changeset }, + AssertRowCount(4), + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_delete_row() { + let mut test = GridEditorTest::new().await; + let context_1 = CreateRowMetaBuilder::new(&test.field_metas).build(); + let context_2 = CreateRowMetaBuilder::new(&test.field_metas).build(); + let row_ids = vec![context_1.row_id.clone(), context_2.row_id.clone()]; + let scripts = vec![ + AssertRowCount(3), + CreateRow { context: context_1 }, + CreateRow { context: context_2 }, + AssertBlockCount(1), + AssertBlock { + block_index: 0, + row_count: 5, + start_row_index: 0, + }, + DeleteRow { row_ids }, + AssertBlock { + block_index: 0, + row_count: 3, + start_row_index: 0, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_row_add_cells_test() { + let mut test = GridEditorTest::new().await; + let mut builder = CreateRowMetaBuilder::new(&test.field_metas); + for field in &test.field_metas { + match field.field_type { + FieldType::RichText => { + builder.add_cell(&field.id, "hello world".to_owned()).unwrap(); + } + FieldType::Number => { + builder.add_cell(&field.id, "18,443".to_owned()).unwrap(); + } + FieldType::DateTime => { + builder + .add_cell(&field.id, make_date_cell_string("1647251762")) + .unwrap(); + } + FieldType::SingleSelect => { + let type_option = SingleSelectTypeOption::from(field); + let option = type_option.options.first().unwrap(); + builder.add_select_option_cell(&field.id, option.id.clone()).unwrap(); + } + FieldType::MultiSelect => { + let type_option = MultiSelectTypeOption::from(field); + let ops_ids = type_option + .options + .iter() + .map(|option| option.id.clone()) + .collect::>() + .join(SELECTION_IDS_SEPARATOR); + builder.add_select_option_cell(&field.id, ops_ids).unwrap(); + } + FieldType::Checkbox => { + builder.add_cell(&field.id, "false".to_string()).unwrap(); + } + FieldType::URL => { + builder.add_cell(&field.id, "1".to_string()).unwrap(); + } + } + } + let context = builder.build(); + let scripts = vec![CreateRow { context }, AssertGridMetaPad]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_row_add_date_cell_test() { + let mut test = GridEditorTest::new().await; + let mut builder = CreateRowMetaBuilder::new(&test.field_metas); + let mut date_field = None; + let timestamp = 1647390674; + for field in &test.field_metas { + if field.field_type == FieldType::DateTime { + date_field = Some(field.clone()); + NaiveDateTime::from_timestamp(123, 0); + // The data should not be empty + assert!(builder.add_cell(&field.id, "".to_string()).is_err()); + assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok()); + assert!(builder + .add_cell(&field.id, make_date_cell_string(×tamp.to_string())) + .is_ok()); + } + } + let context = builder.build(); + let date_field = date_field.unwrap(); + let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); + assert_eq!( + decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type) + .parse::() + .unwrap() + .date, + "2022/03/16", + ); + let scripts = vec![CreateRow { context }]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_cell_update() { + let mut test = GridEditorTest::new().await; + let field_metas = &test.field_metas; + let row_metas = &test.row_metas; + let grid_blocks = &test.grid_blocks; + assert_eq!(row_metas.len(), 3); + assert_eq!(grid_blocks.len(), 1); + + let block_id = &grid_blocks.first().unwrap().block_id; + let mut scripts = vec![]; + for (index, row_meta) in row_metas.iter().enumerate() { + for field_meta in field_metas { + if index == 0 { + let data = match field_meta.field_type { + FieldType::RichText => "".to_string(), + FieldType::Number => "123".to_string(), + FieldType::DateTime => make_date_cell_string("123"), + FieldType::SingleSelect => { + let type_option = SingleSelectTypeOption::from(field_meta); + SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() + } + FieldType::MultiSelect => { + let type_option = MultiSelectTypeOption::from(field_meta); + SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str() + } + FieldType::Checkbox => "1".to_string(), + FieldType::URL => "1".to_string(), + }; + + scripts.push(UpdateCell { + changeset: CellChangeset { + grid_id: block_id.to_string(), + row_id: row_meta.id.clone(), + field_id: field_meta.id.clone(), + cell_content_changeset: Some(data), + }, + is_err: false, + }); + } + + if index == 1 { + let (data, is_err) = match field_meta.field_type { + FieldType::RichText => ("1".to_string().repeat(10001), true), + FieldType::Number => ("abc".to_string(), true), + FieldType::DateTime => ("abc".to_string(), true), + FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), + FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false), + FieldType::Checkbox => ("2".to_string(), false), + FieldType::URL => ("2".to_string(), false), + }; + + scripts.push(UpdateCell { + changeset: CellChangeset { + grid_id: block_id.to_string(), + row_id: row_meta.id.clone(), + field_id: field_meta.id.clone(), + cell_content_changeset: Some(data), + }, + is_err, + }); + } + } + } + + test.run_scripts(scripts).await; +} + +fn make_date_cell_string(s: &str) -> String { + serde_json::to_string(&DateCellContentChangeset { + date: Some(s.to_string()), + time: None, + }) + .unwrap() +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/mod.rs index 38cdb25b99..8865bf01c2 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/mod.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/mod.rs @@ -1,8 +1,5 @@ mod block_test; mod cell_test; mod field_test; -mod field_util; -// mod filter_test; -mod row_test; -mod row_util; -mod script; +mod filter_test; +mod grid_editor; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs deleted file mode 100644 index f833dff8a6..0000000000 --- a/frontend/rust-lib/flowy-grid/tests/grid/row_test.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::grid::field_util::*; -use crate::grid::row_util::GridRowTestBuilder; -use crate::grid::script::EditorScript::*; -use crate::grid::script::*; -use chrono::NaiveDateTime; -use flowy_grid::entities::FieldType; -use flowy_grid::services::field::{ - DateCellData, MultiSelectTypeOption, SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, -}; -use flowy_grid::services::row::{decode_cell_data, CreateRowRevisionBuilder}; -use flowy_grid_data_model::revision::RowMetaChangeset; - -#[tokio::test] -async fn grid_create_row_count_test() { - let test = GridEditorTest::new().await; - let scripts = vec![ - AssertRowCount(3), - CreateEmptyRow, - CreateEmptyRow, - CreateRow { - payload: GridRowTestBuilder::new(&test).build(), - }, - AssertRowCount(6), - ]; - GridEditorTest::new().await.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_update_row() { - let mut test = GridEditorTest::new().await; - let payload = GridRowTestBuilder::new(&test).build(); - let changeset = RowMetaChangeset { - row_id: payload.row_id.clone(), - height: None, - visibility: None, - cell_by_field_id: Default::default(), - }; - - let scripts = vec![AssertRowCount(3), CreateRow { payload }, UpdateRow { changeset }]; - test.run_scripts(scripts).await; - - let expected_row = (&*test.row_revs.last().cloned().unwrap()).clone(); - let scripts = vec![AssertRow { expected_row }, AssertRowCount(4)]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_delete_row() { - let mut test = GridEditorTest::new().await; - let payload1 = GridRowTestBuilder::new(&test).build(); - let payload2 = GridRowTestBuilder::new(&test).build(); - let row_ids = vec![payload1.row_id.clone(), payload2.row_id.clone()]; - let scripts = vec![ - AssertRowCount(3), - CreateRow { payload: payload1 }, - CreateRow { payload: payload2 }, - AssertBlockCount(1), - AssertBlock { - block_index: 0, - row_count: 5, - start_row_index: 0, - }, - DeleteRows { row_ids }, - AssertBlock { - block_index: 0, - row_count: 3, - start_row_index: 0, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_row_add_cells_test() { - let mut test = GridEditorTest::new().await; - let mut builder = CreateRowRevisionBuilder::new(&test.field_revs); - for field in &test.field_revs { - let field_type: FieldType = field.field_type_rev.into(); - match field_type { - FieldType::RichText => { - builder.add_cell(&field.id, "hello world".to_owned()).unwrap(); - } - FieldType::Number => { - builder.add_cell(&field.id, "18,443".to_owned()).unwrap(); - } - FieldType::DateTime => { - builder - .add_cell(&field.id, make_date_cell_string("1647251762")) - .unwrap(); - } - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOption::from(field); - let option = type_option.options.first().unwrap(); - builder.add_select_option_cell(&field.id, option.id.clone()).unwrap(); - } - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOption::from(field); - let ops_ids = type_option - .options - .iter() - .map(|option| option.id.clone()) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - builder.add_select_option_cell(&field.id, ops_ids).unwrap(); - } - FieldType::Checkbox => { - builder.add_cell(&field.id, "false".to_string()).unwrap(); - } - FieldType::URL => { - builder.add_cell(&field.id, "1".to_string()).unwrap(); - } - } - } - let context = builder.build(); - let scripts = vec![CreateRow { payload: context }, AssertGridRevisionPad]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_row_add_date_cell_test() { - let mut test = GridEditorTest::new().await; - let mut builder = CreateRowRevisionBuilder::new(&test.field_revs); - let mut date_field = None; - let timestamp = 1647390674; - for field in &test.field_revs { - let field_type: FieldType = field.field_type_rev.into(); - if field_type == FieldType::DateTime { - date_field = Some(field.clone()); - NaiveDateTime::from_timestamp(123, 0); - // The data should not be empty - assert!(builder.add_cell(&field.id, "".to_string()).is_err()); - assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok()); - assert!(builder - .add_cell(&field.id, make_date_cell_string(×tamp.to_string())) - .is_ok()); - } - } - let context = builder.build(); - let date_field = date_field.unwrap(); - let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone(); - assert_eq!( - decode_cell_data(cell_data.data.clone(), &date_field) - .parse::() - .unwrap() - .date, - "2022/03/16", - ); - let scripts = vec![CreateRow { payload: context }]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/row_util.rs b/frontend/rust-lib/flowy-grid/tests/grid/row_util.rs deleted file mode 100644 index ee4750bd19..0000000000 --- a/frontend/rust-lib/flowy-grid/tests/grid/row_util.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::grid::script::GridEditorTest; -use flowy_grid::entities::FieldType; -use flowy_grid::services::field::DateCellContentChangeset; -use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload}; -use flowy_grid_data_model::revision::FieldRevision; -use strum::EnumCount; - -pub struct GridRowTestBuilder<'a> { - test: &'a GridEditorTest, - inner_builder: CreateRowRevisionBuilder<'a>, -} - -impl<'a> GridRowTestBuilder<'a> { - pub fn new(test: &'a GridEditorTest) -> Self { - assert_eq!(test.field_revs.len(), FieldType::COUNT); - - let inner_builder = CreateRowRevisionBuilder::new(&test.field_revs); - Self { test, inner_builder } - } - #[allow(dead_code)] - pub fn update_text_cell(mut self, data: String) -> Self { - let text_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.add_cell(&text_field.id, data).unwrap(); - self - } - - #[allow(dead_code)] - pub fn update_number_cell(mut self, data: String) -> Self { - let number_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.add_cell(&number_field.id, data).unwrap(); - self - } - - #[allow(dead_code)] - pub fn update_date_cell(mut self, value: i64) -> Self { - let value = serde_json::to_string(&DateCellContentChangeset { - date: Some(value.to_string()), - time: None, - }) - .unwrap(); - let date_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.add_cell(&date_field.id, value).unwrap(); - self - } - - #[allow(dead_code)] - pub fn update_checkbox_cell(mut self, data: bool) -> Self { - let number_field = self.field_rev_with_type(&FieldType::Checkbox); - self.inner_builder.add_cell(&number_field.id, data.to_string()).unwrap(); - self - } - - #[allow(dead_code)] - pub fn update_url_cell(mut self, data: String) -> Self { - let number_field = self.field_rev_with_type(&FieldType::Checkbox); - self.inner_builder.add_cell(&number_field.id, data).unwrap(); - self - } - - pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision { - self.test - .field_revs - .iter() - .find(|field_rev| { - let t_field_type: FieldType = field_rev.field_type_rev.into(); - &t_field_type == field_type - }) - .unwrap() - .as_ref() - .clone() - } - - pub fn build(self) -> CreateRowRevisionPayload { - self.inner_builder.build() - } -} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index 0ca04024a6..c1fa47c7a9 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -1,14 +1,12 @@ -#![cfg_attr(rustfmt, rustfmt::skip)] -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] use bytes::Bytes; use flowy_grid::services::field::*; -use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor}; -use flowy_grid::services::row::CreateRowRevisionPayload; -use flowy_grid::services::setting::GridSettingChangesetBuilder; -use flowy_grid::entities::*; -use flowy_grid_data_model::revision::*; +use flowy_grid::services::grid_meta_editor::{GridMetaEditor, GridPadBuilder}; +use flowy_grid::services::row::CreateRowMetaPayload; +use flowy_grid_data_model::entities::{ + BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, + GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset, RowOrder, + TypeOptionDataEntry, +}; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; use flowy_sync::client_grid::GridBuilder; use flowy_test::helper::ViewTest; @@ -18,7 +16,6 @@ use std::sync::Arc; use std::time::Duration; use strum::EnumCount; use tokio::time::sleep; -use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, FieldChangesetParams, GridSettingChangesetParams}; pub enum EditorScript { CreateField { @@ -28,18 +25,18 @@ pub enum EditorScript { changeset: FieldChangesetParams, }, DeleteField { - field_rev: FieldRevision, + field_meta: FieldMeta, }, AssertFieldCount(usize), AssertFieldEqual { field_index: usize, - field_rev: FieldRevision, + field_meta: FieldMeta, }, CreateBlock { - block: GridBlockMetaRevision, + block: GridBlockMetaSnapshot, }, UpdateBlock { - changeset: GridBlockMetaRevisionChangeset, + changeset: GridBlockInfoChangeset, }, AssertBlockCount(usize), AssertBlock { @@ -49,19 +46,19 @@ pub enum EditorScript { }, AssertBlockEqual { block_index: usize, - block: GridBlockMetaRevision, + block: GridBlockMetaSnapshot, }, CreateEmptyRow, CreateRow { - payload: CreateRowRevisionPayload, + context: CreateRowMetaPayload, }, UpdateRow { changeset: RowMetaChangeset, }, AssertRow { - expected_row: RowRevision, + changeset: RowMetaChangeset, }, - DeleteRows { + DeleteRow { row_ids: Vec, }, UpdateCell { @@ -69,65 +66,42 @@ pub enum EditorScript { is_err: bool, }, AssertRowCount(usize), - #[allow(dead_code)] - UpdateGridSetting { - params: GridSettingChangesetParams, - }, - InsertGridTableFilter { - payload: CreateGridFilterPayload, - }, - AssertTableFilterCount { - count: i32, - }, - DeleteGridTableFilter { - filter_id: String, - field_type: FieldType, - }, - #[allow(dead_code)] - AssertGridSetting { - expected_setting: GridSetting, - }, - AssertGridRevisionPad, + // AssertRowEqual{ row_index: usize, row: RowMeta}, + AssertGridMetaPad, } pub struct GridEditorTest { pub sdk: FlowySDKTest, pub grid_id: String, - pub editor: Arc, - pub field_revs: Vec>, - pub block_meta_revs: Vec>, - pub row_revs: Vec>, + pub editor: Arc, + pub field_metas: Vec, + pub grid_blocks: Vec, + pub row_metas: Vec>, pub field_count: usize, - pub row_order_by_row_id: HashMap, + pub row_order_by_row_id: HashMap, } impl GridEditorTest { pub async fn new() -> Self { let sdk = FlowySDKTest::default(); let _ = sdk.init_user().await; - let build_context = make_all_field_test_grid(); + let build_context = make_template_1_grid(); let view_data: Bytes = build_context.into(); let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await; let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap(); - let field_revs = editor.get_field_revs(None).await.unwrap(); - let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); - let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs; - assert_eq!(row_revs.len(), 3); - assert_eq!(block_meta_revs.len(), 1); - - // It seems like you should add the field in the make_test_grid() function. - // Because we assert the initialize count of the fields is equal to FieldType::COUNT. - assert_eq!(field_revs.len(), FieldType::COUNT); + let field_metas = editor.get_field_metas::(None).await.unwrap(); + let grid_blocks = editor.get_block_metas().await.unwrap(); + let row_metas = get_row_metas(&editor).await; let grid_id = test.view.id; Self { sdk, grid_id, editor, - field_revs, - block_meta_revs, - row_revs, + field_metas, + grid_blocks, + row_metas, field_count: FieldType::COUNT, row_order_by_row_id: HashMap::default(), } @@ -152,95 +126,93 @@ impl GridEditorTest { } self.editor.insert_field(params).await.unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - assert_eq!(self.field_count, self.field_revs.len()); + self.field_metas = self.editor.get_field_metas::(None).await.unwrap(); + assert_eq!(self.field_count, self.field_metas.len()); } EditorScript::UpdateField { changeset: change } => { self.editor.update_field(change).await.unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + self.field_metas = self.editor.get_field_metas::(None).await.unwrap(); } - EditorScript::DeleteField { field_rev } => { - if self.editor.contain_field(&field_rev.id).await { + EditorScript::DeleteField { field_meta } => { + if self.editor.contain_field(&field_meta.id).await { self.field_count -= 1; } - self.editor.delete_field(&field_rev.id).await.unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - assert_eq!(self.field_count, self.field_revs.len()); + self.editor.delete_field(&field_meta.id).await.unwrap(); + self.field_metas = self.editor.get_field_metas::(None).await.unwrap(); + assert_eq!(self.field_count, self.field_metas.len()); } EditorScript::AssertFieldCount(count) => { assert_eq!( - self.editor.get_field_revs(None).await.unwrap().len(), + self.editor.get_field_metas::(None).await.unwrap().len(), count ); } - EditorScript::AssertFieldEqual { field_index, field_rev } => { - let field_revs = self.editor.get_field_revs(None).await.unwrap(); - assert_eq!(field_revs[field_index].as_ref(), &field_rev); + EditorScript::AssertFieldEqual { + field_index, + field_meta, + } => { + let field_metas = self.editor.get_field_metas::(None).await.unwrap(); + assert_eq!(field_metas[field_index].clone(), field_meta); } EditorScript::CreateBlock { block } => { self.editor.create_block(block).await.unwrap(); - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + self.grid_blocks = self.editor.get_block_metas().await.unwrap(); } EditorScript::UpdateBlock { changeset: change } => { self.editor.update_block(change).await.unwrap(); } EditorScript::AssertBlockCount(count) => { - assert_eq!(self.editor.get_block_meta_revs().await.unwrap().len(), count); + assert_eq!(self.editor.get_block_metas().await.unwrap().len(), count); } EditorScript::AssertBlock { block_index, row_count, start_row_index, } => { - assert_eq!(self.block_meta_revs[block_index].row_count, row_count); - assert_eq!(self.block_meta_revs[block_index].start_row_index, start_row_index); + assert_eq!(self.grid_blocks[block_index].row_count, row_count); + assert_eq!(self.grid_blocks[block_index].start_row_index, start_row_index); } EditorScript::AssertBlockEqual { block_index, block } => { - let blocks = self.editor.get_block_meta_revs().await.unwrap(); + let blocks = self.editor.get_block_metas().await.unwrap(); let compared_block = blocks[block_index].clone(); - assert_eq!(compared_block, Arc::new(block)); + assert_eq!(compared_block, block); } EditorScript::CreateEmptyRow => { let row_order = self.editor.create_row(None).await.unwrap(); - self.row_order_by_row_id.insert(row_order.row_id().to_owned(), row_order); - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order); + self.row_metas = self.get_row_metas().await; + self.grid_blocks = self.editor.get_block_metas().await.unwrap(); } - EditorScript::CreateRow { payload: context } => { + EditorScript::CreateRow { context } => { let row_orders = self.editor.insert_rows(vec![context]).await.unwrap(); for row_order in row_orders { - self.row_order_by_row_id.insert(row_order.row_id().to_owned(), row_order); + self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order); } - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + self.row_metas = self.get_row_metas().await; + self.grid_blocks = self.editor.get_block_metas().await.unwrap(); } EditorScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(), - EditorScript::DeleteRows { row_ids } => { + EditorScript::DeleteRow { row_ids } => { let row_orders = row_ids .into_iter() .map(|row_id| self.row_order_by_row_id.get(&row_id).unwrap().clone()) - .collect::>(); + .collect::>(); self.editor.delete_rows(row_orders).await.unwrap(); - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + self.row_metas = self.get_row_metas().await; + self.grid_blocks = self.editor.get_block_metas().await.unwrap(); } - EditorScript::AssertRow { expected_row } => { - let row = &*self - .row_revs - .iter() - .find(|row| row.id == expected_row.id) - .cloned() - .unwrap(); - assert_eq!(&expected_row, row); - // if let Some(visibility) = changeset.visibility { - // assert_eq!(row.visibility, visibility); - // } - // - // if let Some(height) = changeset.height { - // assert_eq!(row.height, height); - // } + EditorScript::AssertRow { changeset } => { + let row = self.row_metas.iter().find(|row| row.id == changeset.row_id).unwrap(); + + if let Some(visibility) = changeset.visibility { + assert_eq!(row.visibility, visibility); + } + + if let Some(height) = changeset.height { + assert_eq!(row.height, height); + } } EditorScript::UpdateCell { changeset, is_err } => { let result = self.editor.update_cell(changeset).await; @@ -248,40 +220,13 @@ impl GridEditorTest { assert!(result.is_err()) } else { let _ = result.unwrap(); - self.row_revs = self.get_row_revs().await; + self.row_metas = self.get_row_metas().await; } } - EditorScript::AssertRowCount(expected_row_count) => { - assert_eq!(expected_row_count, self.row_revs.len()); + EditorScript::AssertRowCount(count) => { + assert_eq!(self.row_metas.len(), count); } - EditorScript::UpdateGridSetting { params } => { - let _ = self.editor.update_grid_setting(params).await.unwrap(); - } - EditorScript::InsertGridTableFilter { payload } => { - let params: CreateGridFilterParams = payload.try_into().unwrap(); - let layout_type = GridLayoutType::Table; - let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type) - .insert_filter(params) - .build(); - let _ = self.editor.update_grid_setting(params).await.unwrap(); - } - EditorScript::AssertTableFilterCount { count } => { - let layout_type = GridLayoutType::Table; - let filters = self.editor.get_grid_filter(&layout_type).await.unwrap(); - assert_eq!(count as usize, filters.len()); - } - EditorScript::DeleteGridTableFilter { filter_id ,field_type} => { - let layout_type = GridLayoutType::Table; - let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type) - .delete_filter(DeleteFilterParams { filter_id, field_type_rev: field_type.into() }) - .build(); - let _ = self.editor.update_grid_setting(params).await.unwrap(); - } - EditorScript::AssertGridSetting { expected_setting } => { - let setting = self.editor.get_grid_setting().await.unwrap(); - assert_eq!(expected_setting, setting); - } - EditorScript::AssertGridRevisionPad => { + EditorScript::AssertGridMetaPad => { sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap(); let grid_pad = grid_rev_manager.load::(None).await.unwrap(); @@ -290,35 +235,89 @@ impl GridEditorTest { } } - async fn get_row_revs(&self) -> Vec> { - self.editor - .grid_block_snapshots(None) - .await - .unwrap() - .pop() - .unwrap() - .row_revs - } - - pub async fn grid_filters(&self) -> Vec { - let layout_type = GridLayoutType::Table; - self.editor.get_grid_filter(&layout_type).await.unwrap() - } - - pub fn text_field(&self) -> &FieldRevision { - self.field_revs - .iter() - .filter(|field_rev| { - let t_field_type: FieldType = field_rev.field_type_rev.into(); - t_field_type == FieldType::RichText - }) - .collect::>() - .pop() - .unwrap() + async fn get_row_metas(&self) -> Vec> { + get_row_metas(&self.editor).await } } -fn make_all_field_test_grid() -> BuildGridContext { +async fn get_row_metas(editor: &Arc) -> Vec> { + editor + .grid_block_snapshots(None) + .await + .unwrap() + .pop() + .unwrap() + .row_metas +} + +pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) { + let field_meta = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .build(); + + let cloned_field_meta = field_meta.clone(); + + let type_option_data = field_meta + .get_type_option_entry::(&field_meta.field_type) + .unwrap() + .protobuf_bytes() + .to_vec(); + + let field = Field { + id: field_meta.id, + name: field_meta.name, + desc: field_meta.desc, + field_type: field_meta.field_type, + frozen: field_meta.frozen, + visibility: field_meta.visibility, + width: field_meta.width, + is_primary: false, + }; + + let params = InsertFieldParams { + grid_id: grid_id.to_owned(), + field, + type_option_data, + start_field_id: None, + }; + (params, cloned_field_meta) +} + +pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) { + let single_select = SingleSelectTypeOptionBuilder::default() + .option(SelectOption::new("Done")) + .option(SelectOption::new("Progress")); + + let field_meta = FieldBuilder::new(single_select).name("Name").visibility(true).build(); + let cloned_field_meta = field_meta.clone(); + let type_option_data = field_meta + .get_type_option_entry::(&field_meta.field_type) + .unwrap() + .protobuf_bytes() + .to_vec(); + + let field = Field { + id: field_meta.id, + name: field_meta.name, + desc: field_meta.desc, + field_type: field_meta.field_type, + frozen: field_meta.frozen, + visibility: field_meta.visibility, + width: field_meta.width, + is_primary: false, + }; + + let params = InsertFieldParams { + grid_id: grid_id.to_owned(), + field, + type_option_data, + start_field_id: None, + }; + (params, cloned_field_meta) +} + +fn make_template_1_grid() -> BuildGridContext { let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) .name("Name") .visibility(true) diff --git a/frontend/rust-lib/flowy-net/src/http_server/document.rs b/frontend/rust-lib/flowy-net/src/http_server/document.rs index fe9f31f2ca..ca9cb47955 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/document.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/document.rs @@ -3,7 +3,7 @@ use crate::{ request::{HttpRequestBuilder, ResponseMiddleware}, }; use flowy_error::FlowyError; -use flowy_sync::entities::text_block::{CreateTextBlockParams, ResetTextBlockParams, TextBlockId, TextBlockInfo}; +use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB}; use flowy_text_block::BlockCloudService; use http_flowy::response::FlowyResponse; use lazy_static::lazy_static; @@ -27,7 +27,7 @@ impl BlockCloudService for BlockHttpCloudService { FutureResult::new(async move { create_document_request(&token, params, &url).await }) } - fn read_block(&self, token: &str, params: TextBlockId) -> FutureResult, FlowyError> { + fn read_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult, FlowyError> { let token = token.to_owned(); let url = self.config.doc_url(); FutureResult::new(async move { read_document_request(&token, params, &url).await }) @@ -52,9 +52,9 @@ pub async fn create_document_request(token: &str, params: CreateTextBlockParams, pub async fn read_document_request( token: &str, - params: TextBlockId, + params: TextBlockIdPB, url: &str, -) -> Result, FlowyError> { +) -> Result, FlowyError> { let doc = request_builder() .get(&url.to_owned()) .header(HEADER_TOKEN, token) diff --git a/frontend/rust-lib/flowy-net/src/http_server/folder.rs b/frontend/rust-lib/flowy-net/src/http_server/folder.rs index 788f7fa462..e600d617a9 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/folder.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/folder.rs @@ -1,15 +1,14 @@ use crate::{ - configuration::{ClientServerConfiguration, HEADER_TOKEN}, + configuration::ClientServerConfiguration, request::{HttpRequestBuilder, ResponseMiddleware}, }; use flowy_error::FlowyError; use flowy_folder::entities::{ - trash::RepeatedTrashId, - view::{CreateViewParams, RepeatedViewId, UpdateViewParams, ViewId}, - workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceId}, - {AppId, CreateAppParams, UpdateAppParams}, + trash::RepeatedTrashIdPB, + view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, + workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, + {AppIdPB, CreateAppParams, UpdateAppParams}, }; - use flowy_folder::event_map::FolderCouldServiceV1; use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use http_flowy::errors::ServerError; @@ -45,7 +44,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn read_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult, FlowyError> { + fn read_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult, FlowyError> { let token = token.to_owned(); let url = self.config.workspace_url(); FutureResult::new(async move { @@ -63,7 +62,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn delete_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult<(), FlowyError> { + fn delete_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult<(), FlowyError> { let token = token.to_owned(); let url = self.config.workspace_url(); FutureResult::new(async move { @@ -81,7 +80,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn read_view(&self, token: &str, params: ViewId) -> FutureResult, FlowyError> { + fn read_view(&self, token: &str, params: ViewIdPB) -> FutureResult, FlowyError> { let token = token.to_owned(); let url = self.config.view_url(); FutureResult::new(async move { @@ -90,7 +89,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn delete_view(&self, token: &str, params: RepeatedViewId) -> FutureResult<(), FlowyError> { + fn delete_view(&self, token: &str, params: RepeatedViewIdPB) -> FutureResult<(), FlowyError> { let token = token.to_owned(); let url = self.config.view_url(); FutureResult::new(async move { @@ -117,7 +116,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn read_app(&self, token: &str, params: AppId) -> FutureResult, FlowyError> { + fn read_app(&self, token: &str, params: AppIdPB) -> FutureResult, FlowyError> { let token = token.to_owned(); let url = self.config.app_url(); FutureResult::new(async move { @@ -135,7 +134,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn delete_app(&self, token: &str, params: AppId) -> FutureResult<(), FlowyError> { + fn delete_app(&self, token: &str, params: AppIdPB) -> FutureResult<(), FlowyError> { let token = token.to_owned(); let url = self.config.app_url(); FutureResult::new(async move { @@ -144,7 +143,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn create_trash(&self, token: &str, params: RepeatedTrashId) -> FutureResult<(), FlowyError> { + fn create_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { let token = token.to_owned(); let url = self.config.trash_url(); FutureResult::new(async move { @@ -153,7 +152,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { }) } - fn delete_trash(&self, token: &str, params: RepeatedTrashId) -> FutureResult<(), FlowyError> { + fn delete_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { let token = token.to_owned(); let url = self.config.trash_url(); FutureResult::new(async move { @@ -172,6 +171,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService { } } +#[allow(dead_code)] fn request_builder() -> HttpRequestBuilder { HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) } @@ -193,7 +193,7 @@ pub async fn create_workspace_request( pub async fn read_workspaces_request( _token: &str, - _params: WorkspaceId, + _params: WorkspaceIdPB, _url: &str, ) -> Result, ServerError> { // let repeated_workspace = request_builder() @@ -208,26 +208,26 @@ pub async fn read_workspaces_request( } pub async fn update_workspace_request( - token: &str, - params: UpdateWorkspaceParams, - url: &str, + _token: &str, + _params: UpdateWorkspaceParams, + _url: &str, ) -> Result<(), ServerError> { - let _ = request_builder() - .patch(&url.to_owned()) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; + // let _ = request_builder() + // .patch(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } -pub async fn delete_workspace_request(token: &str, params: WorkspaceId, url: &str) -> Result<(), ServerError> { - let _ = request_builder() - .delete(url) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; +pub async fn delete_workspace_request(_token: &str, _params: WorkspaceIdPB, _url: &str) -> Result<(), ServerError> { + // let _ = request_builder() + // .delete(url) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } @@ -247,7 +247,7 @@ pub async fn create_app_request( unimplemented!() } -pub async fn read_app_request(_token: &str, _params: AppId, _url: &str) -> Result, ServerError> { +pub async fn read_app_request(_token: &str, _params: AppIdPB, _url: &str) -> Result, ServerError> { // let app = request_builder() // .get(&url.to_owned()) // .header(HEADER_TOKEN, token) @@ -259,23 +259,23 @@ pub async fn read_app_request(_token: &str, _params: AppId, _url: &str) -> Resul unimplemented!() } -pub async fn update_app_request(token: &str, params: UpdateAppParams, url: &str) -> Result<(), ServerError> { - let _ = request_builder() - .patch(&url.to_owned()) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; +pub async fn update_app_request(_token: &str, _params: UpdateAppParams, _url: &str) -> Result<(), ServerError> { + // let _ = request_builder() + // .patch(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } -pub async fn delete_app_request(token: &str, params: AppId, url: &str) -> Result<(), ServerError> { - let _ = request_builder() - .delete(&url.to_owned()) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; +pub async fn delete_app_request(_token: &str, _params: AppIdPB, _url: &str) -> Result<(), ServerError> { + // let _ = request_builder() + // .delete(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } @@ -295,7 +295,11 @@ pub async fn create_view_request( unimplemented!() } -pub async fn read_view_request(_token: &str, _params: ViewId, _url: &str) -> Result, ServerError> { +pub async fn read_view_request( + _token: &str, + _params: ViewIdPB, + _url: &str, +) -> Result, ServerError> { // let view = request_builder() // .get(&url.to_owned()) // .header(HEADER_TOKEN, token) @@ -307,43 +311,43 @@ pub async fn read_view_request(_token: &str, _params: ViewId, _url: &str) -> Res unimplemented!() } -pub async fn update_view_request(token: &str, params: UpdateViewParams, url: &str) -> Result<(), ServerError> { - let _ = request_builder() - .patch(&url.to_owned()) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; +pub async fn update_view_request(_token: &str, _params: UpdateViewParams, _url: &str) -> Result<(), ServerError> { + // let _ = request_builder() + // .patch(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } -pub async fn delete_view_request(token: &str, params: RepeatedViewId, url: &str) -> Result<(), ServerError> { - let _ = request_builder() - .delete(&url.to_owned()) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; +pub async fn delete_view_request(_token: &str, _params: RepeatedViewIdPB, _url: &str) -> Result<(), ServerError> { + // let _ = request_builder() + // .delete(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } -pub async fn create_trash_request(token: &str, params: RepeatedTrashId, url: &str) -> Result<(), ServerError> { - let _ = request_builder() - .post(&url.to_owned()) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; +pub async fn create_trash_request(_token: &str, _params: RepeatedTrashIdPB, _url: &str) -> Result<(), ServerError> { + // let _ = request_builder() + // .post(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } -pub async fn delete_trash_request(token: &str, params: RepeatedTrashId, url: &str) -> Result<(), ServerError> { - let _ = request_builder() - .delete(&url.to_owned()) - .header(HEADER_TOKEN, token) - .protobuf(params)? - .send() - .await?; +pub async fn delete_trash_request(_token: &str, _params: RepeatedTrashIdPB, _url: &str) -> Result<(), ServerError> { + // let _ = request_builder() + // .delete(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-net/src/http_server/user.rs b/frontend/rust-lib/flowy-net/src/http_server/user.rs index faf5621161..7768c91bb6 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/user.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/user.rs @@ -1,7 +1,7 @@ use crate::{configuration::*, request::HttpRequestBuilder}; use flowy_error::FlowyError; use flowy_user::entities::{ - SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, }; use flowy_user::event_map::UserCloudService; use http_flowy::errors::ServerError; @@ -51,7 +51,7 @@ impl UserCloudService for UserHttpCloudService { }) } - fn get_user(&self, token: &str) -> FutureResult { + fn get_user(&self, token: &str) -> FutureResult { let token = token.to_owned(); let url = self.config.user_profile_url(); FutureResult::new(async move { @@ -92,7 +92,7 @@ pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), ServerE Ok(()) } -pub async fn get_user_profile_request(token: &str, url: &str) -> Result { +pub async fn get_user_profile_request(token: &str, url: &str) -> Result { let user_profile = request_builder() .get(&url.to_owned()) .header(HEADER_TOKEN, token) diff --git a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs b/frontend/rust-lib/flowy-net/src/local_server/persistence.rs index 28151af2f4..17008481b5 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/persistence.rs @@ -1,10 +1,10 @@ +use flowy_sync::entities::revision::{RepeatedRevision, Revision}; use flowy_sync::{ - entities::{folder::FolderInfo, text_block::TextBlockInfo}, + entities::{folder::FolderInfo, text_block::DocumentPB}, errors::CollaborateError, - protobuf::{RepeatedRevision as RepeatedRevisionPB, Revision as RevisionPB}, server_document::*, server_folder::FolderCloudPersistence, - util::{make_document_info_from_revisions_pb, make_folder_from_revisions_pb}, + util::{make_document_from_revision_pbs, make_folder_from_revisions_pb}, }; use lib_infra::future::BoxResultFuture; use std::{ @@ -15,17 +15,17 @@ use std::{ // For the moment, we use memory to cache the data, it will be implemented with // other storage. Like the Firestore,Dropbox.etc. pub trait RevisionCloudStorage: Send + Sync { - fn set_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError>; + fn set_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError>; fn get_revisions( &self, object_id: &str, rev_ids: Option>, - ) -> BoxResultFuture; + ) -> BoxResultFuture; fn reset_object( &self, object_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError>; } @@ -64,7 +64,7 @@ impl FolderCloudPersistence for LocalTextBlockCloudPersistence { &self, _user_id: &str, folder_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture, CollaborateError> { let folder_id = folder_id.to_owned(); let storage = self.storage.clone(); @@ -74,7 +74,7 @@ impl FolderCloudPersistence for LocalTextBlockCloudPersistence { }) } - fn save_folder_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> { + fn save_folder_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError> { let storage = self.storage.clone(); Box::pin(async move { let _ = storage.set_revisions(repeated_revision).await?; @@ -86,20 +86,19 @@ impl FolderCloudPersistence for LocalTextBlockCloudPersistence { &self, folder_id: &str, rev_ids: Option>, - ) -> BoxResultFuture, CollaborateError> { + ) -> BoxResultFuture, CollaborateError> { let folder_id = folder_id.to_owned(); let storage = self.storage.clone(); Box::pin(async move { - let mut repeated_revision = storage.get_revisions(&folder_id, rev_ids).await?; - let revisions: Vec = repeated_revision.take_items().into(); - Ok(revisions) + let repeated_revision = storage.get_revisions(&folder_id, rev_ids).await?; + Ok(repeated_revision.into_inner()) }) } fn reset_folder( &self, folder_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError> { let storage = self.storage.clone(); let folder_id = folder_id.to_owned(); @@ -111,12 +110,12 @@ impl FolderCloudPersistence for LocalTextBlockCloudPersistence { } impl TextBlockCloudPersistence for LocalTextBlockCloudPersistence { - fn read_text_block(&self, doc_id: &str) -> BoxResultFuture { + fn read_text_block(&self, doc_id: &str) -> BoxResultFuture { let storage = self.storage.clone(); let doc_id = doc_id.to_owned(); Box::pin(async move { let repeated_revision = storage.get_revisions(&doc_id, None).await?; - match make_document_info_from_revisions_pb(&doc_id, repeated_revision)? { + match make_document_from_revision_pbs(&doc_id, repeated_revision)? { Some(document_info) => Ok(document_info), None => Err(CollaborateError::record_not_found()), } @@ -126,13 +125,13 @@ impl TextBlockCloudPersistence for LocalTextBlockCloudPersistence { fn create_text_block( &self, doc_id: &str, - repeated_revision: RepeatedRevisionPB, - ) -> BoxResultFuture, CollaborateError> { + repeated_revision: RepeatedRevision, + ) -> BoxResultFuture, CollaborateError> { let doc_id = doc_id.to_owned(); let storage = self.storage.clone(); Box::pin(async move { let _ = storage.set_revisions(repeated_revision.clone()).await?; - make_document_info_from_revisions_pb(&doc_id, repeated_revision) + make_document_from_revision_pbs(&doc_id, repeated_revision) }) } @@ -140,20 +139,16 @@ impl TextBlockCloudPersistence for LocalTextBlockCloudPersistence { &self, doc_id: &str, rev_ids: Option>, - ) -> BoxResultFuture, CollaborateError> { + ) -> BoxResultFuture, CollaborateError> { let doc_id = doc_id.to_owned(); let storage = self.storage.clone(); Box::pin(async move { - let mut repeated_revision = storage.get_revisions(&doc_id, rev_ids).await?; - let revisions: Vec = repeated_revision.take_items().into(); - Ok(revisions) + let repeated_revision = storage.get_revisions(&doc_id, rev_ids).await?; + Ok(repeated_revision.into_inner()) }) } - fn save_text_block_revisions( - &self, - repeated_revision: RepeatedRevisionPB, - ) -> BoxResultFuture<(), CollaborateError> { + fn save_text_block_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError> { let storage = self.storage.clone(); Box::pin(async move { let _ = storage.set_revisions(repeated_revision).await?; @@ -161,7 +156,7 @@ impl TextBlockCloudPersistence for LocalTextBlockCloudPersistence { }) } - fn reset_text_block(&self, doc_id: &str, revisions: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> { + fn reset_text_block(&self, doc_id: &str, revisions: RepeatedRevision) -> BoxResultFuture<(), CollaborateError> { let storage = self.storage.clone(); let doc_id = doc_id.to_owned(); Box::pin(async move { @@ -174,7 +169,7 @@ impl TextBlockCloudPersistence for LocalTextBlockCloudPersistence { #[derive(Default)] struct MemoryDocumentCloudStorage {} impl RevisionCloudStorage for MemoryDocumentCloudStorage { - fn set_revisions(&self, _repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> { + fn set_revisions(&self, _repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError> { Box::pin(async move { Ok(()) }) } @@ -182,9 +177,9 @@ impl RevisionCloudStorage for MemoryDocumentCloudStorage { &self, _doc_id: &str, _rev_ids: Option>, - ) -> BoxResultFuture { + ) -> BoxResultFuture { Box::pin(async move { - let repeated_revisions = RepeatedRevisionPB::new(); + let repeated_revisions = RepeatedRevision::default(); Ok(repeated_revisions) }) } @@ -192,7 +187,7 @@ impl RevisionCloudStorage for MemoryDocumentCloudStorage { fn reset_object( &self, _doc_id: &str, - _repeated_revision: RepeatedRevisionPB, + _repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError> { Box::pin(async move { Ok(()) }) } diff --git a/frontend/rust-lib/flowy-net/src/local_server/server.rs b/frontend/rust-lib/flowy-net/src/local_server/server.rs index cdec4d0c48..338697f432 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/server.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/server.rs @@ -6,7 +6,7 @@ use flowy_folder::event_map::FolderCouldServiceV1; use flowy_sync::{ client_document::default::initial_quill_delta_string, entities::{ - text_block::{CreateTextBlockParams, ResetTextBlockParams, TextBlockId, TextBlockInfo}, + text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB}, ws_data::{ClientRevisionWSData, ClientRevisionWSDataType}, }, errors::CollaborateError, @@ -253,17 +253,17 @@ impl RevisionUser for LocalRevisionUser { } use flowy_folder::entities::{ - app::{AppId, CreateAppParams, UpdateAppParams}, - trash::RepeatedTrashId, - view::{CreateViewParams, RepeatedViewId, UpdateViewParams, ViewId}, - workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceId}, + app::{AppIdPB, CreateAppParams, UpdateAppParams}, + trash::RepeatedTrashIdPB, + view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, + workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, }; use flowy_folder_data_model::revision::{ gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision, }; use flowy_text_block::BlockCloudService; use flowy_user::entities::{ - SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, }; use flowy_user::event_map::UserCloudService; use lib_infra::{future::FutureResult, util::timestamp}; @@ -289,7 +289,7 @@ impl FolderCouldServiceV1 for LocalServer { FutureResult::new(async { Ok(workspace) }) } - fn read_workspace(&self, _token: &str, _params: WorkspaceId) -> FutureResult, FlowyError> { + fn read_workspace(&self, _token: &str, _params: WorkspaceIdPB) -> FutureResult, FlowyError> { FutureResult::new(async { Ok(vec![]) }) } @@ -297,7 +297,7 @@ impl FolderCouldServiceV1 for LocalServer { FutureResult::new(async { Ok(()) }) } - fn delete_workspace(&self, _token: &str, _params: WorkspaceId) -> FutureResult<(), FlowyError> { + fn delete_workspace(&self, _token: &str, _params: WorkspaceIdPB) -> FutureResult<(), FlowyError> { FutureResult::new(async { Ok(()) }) } @@ -320,11 +320,11 @@ impl FolderCouldServiceV1 for LocalServer { FutureResult::new(async { Ok(view) }) } - fn read_view(&self, _token: &str, _params: ViewId) -> FutureResult, FlowyError> { + fn read_view(&self, _token: &str, _params: ViewIdPB) -> FutureResult, FlowyError> { FutureResult::new(async { Ok(None) }) } - fn delete_view(&self, _token: &str, _params: RepeatedViewId) -> FutureResult<(), FlowyError> { + fn delete_view(&self, _token: &str, _params: RepeatedViewIdPB) -> FutureResult<(), FlowyError> { FutureResult::new(async { Ok(()) }) } @@ -347,7 +347,7 @@ impl FolderCouldServiceV1 for LocalServer { FutureResult::new(async { Ok(app) }) } - fn read_app(&self, _token: &str, _params: AppId) -> FutureResult, FlowyError> { + fn read_app(&self, _token: &str, _params: AppIdPB) -> FutureResult, FlowyError> { FutureResult::new(async { Ok(None) }) } @@ -355,15 +355,15 @@ impl FolderCouldServiceV1 for LocalServer { FutureResult::new(async { Ok(()) }) } - fn delete_app(&self, _token: &str, _params: AppId) -> FutureResult<(), FlowyError> { + fn delete_app(&self, _token: &str, _params: AppIdPB) -> FutureResult<(), FlowyError> { FutureResult::new(async { Ok(()) }) } - fn create_trash(&self, _token: &str, _params: RepeatedTrashId) -> FutureResult<(), FlowyError> { + fn create_trash(&self, _token: &str, _params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { FutureResult::new(async { Ok(()) }) } - fn delete_trash(&self, _token: &str, _params: RepeatedTrashId) -> FutureResult<(), FlowyError> { + fn delete_trash(&self, _token: &str, _params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { FutureResult::new(async { Ok(()) }) } @@ -405,8 +405,8 @@ impl UserCloudService for LocalServer { FutureResult::new(async { Ok(()) }) } - fn get_user(&self, _token: &str) -> FutureResult { - FutureResult::new(async { Ok(UserProfile::default()) }) + fn get_user(&self, _token: &str) -> FutureResult { + FutureResult::new(async { Ok(UserProfilePB::default()) }) } fn ws_addr(&self) -> String { @@ -419,8 +419,8 @@ impl BlockCloudService for LocalServer { FutureResult::new(async { Ok(()) }) } - fn read_block(&self, _token: &str, params: TextBlockId) -> FutureResult, FlowyError> { - let doc = TextBlockInfo { + fn read_block(&self, _token: &str, params: TextBlockIdPB) -> FutureResult, FlowyError> { + let doc = DocumentPB { block_id: params.value, text: initial_quill_delta_string(), rev_id: 0, diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_meta_rev_impl.rs b/frontend/rust-lib/flowy-revision/src/cache/disk/grid_block_meta_rev_impl.rs similarity index 97% rename from frontend/rust-lib/flowy-revision/src/cache/disk/grid_meta_rev_impl.rs rename to frontend/rust-lib/flowy-revision/src/cache/disk/grid_block_meta_rev_impl.rs index e850119c30..52b01440e6 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_meta_rev_impl.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/disk/grid_block_meta_rev_impl.rs @@ -1,6 +1,5 @@ use crate::cache::disk::RevisionDiskCache; use crate::disk::{RevisionChangeset, RevisionRecord, RevisionState}; - use bytes::Bytes; use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_database::{ @@ -16,12 +15,12 @@ use flowy_sync::{ }; use std::sync::Arc; -pub struct SQLiteGridBlockMetaRevisionPersistence { +pub struct SQLiteGridBlockRevisionPersistence { user_id: String, pub(crate) pool: Arc, } -impl RevisionDiskCache for SQLiteGridBlockMetaRevisionPersistence { +impl RevisionDiskCache for SQLiteGridBlockRevisionPersistence { type Error = FlowyError; fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { @@ -82,7 +81,7 @@ impl RevisionDiskCache for SQLiteGridBlockMetaRevisionPersistence { } } -impl SQLiteGridBlockMetaRevisionPersistence { +impl SQLiteGridBlockRevisionPersistence { pub fn new(user_id: &str, pool: Arc) -> Self { Self { user_id: user_id.to_owned(), diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_rev_impl.rs b/frontend/rust-lib/flowy-revision/src/cache/disk/grid_rev_impl.rs index d51ea8e48c..9ddb21bc8c 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_rev_impl.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/disk/grid_rev_impl.rs @@ -95,7 +95,6 @@ struct GridRevisionSql(); impl GridRevisionSql { fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records .into_iter() .map(|record| { diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/mod.rs b/frontend/rust-lib/flowy-revision/src/cache/disk/mod.rs index 945c6d707f..991d8f9b9f 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/mod.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/disk/mod.rs @@ -1,10 +1,10 @@ mod folder_rev_impl; -mod grid_meta_rev_impl; +mod grid_block_meta_rev_impl; mod grid_rev_impl; mod text_rev_impl; pub use folder_rev_impl::*; -pub use grid_meta_rev_impl::*; +pub use grid_block_meta_rev_impl::*; pub use grid_rev_impl::*; pub use text_rev_impl::*; @@ -53,6 +53,14 @@ pub struct RevisionRecord { } impl RevisionRecord { + pub fn new(revision: Revision) -> Self { + Self { + revision, + state: RevisionState::Sync, + write_to_disk: true, + } + } + pub fn ack(&mut self) { self.state = RevisionState::Ack; } @@ -64,6 +72,8 @@ pub struct RevisionChangeset { pub(crate) state: RevisionState, } +/// Sync: revision is not synced to the server +/// Ack: revision is synced to the server #[derive(Debug, Clone, Eq, PartialEq)] pub enum RevisionState { Sync = 0, diff --git a/frontend/rust-lib/flowy-revision/src/history/mod.rs b/frontend/rust-lib/flowy-revision/src/history/mod.rs new file mode 100644 index 0000000000..9de42831ed --- /dev/null +++ b/frontend/rust-lib/flowy-revision/src/history/mod.rs @@ -0,0 +1,5 @@ +mod persistence; +mod rev_history; + +pub use persistence::*; +pub use rev_history::*; diff --git a/frontend/rust-lib/flowy-revision/src/history/persistence.rs b/frontend/rust-lib/flowy-revision/src/history/persistence.rs new file mode 100644 index 0000000000..9c1bdacc9e --- /dev/null +++ b/frontend/rust-lib/flowy-revision/src/history/persistence.rs @@ -0,0 +1,78 @@ +use crate::history::RevisionHistoryDiskCache; +use flowy_database::{ + prelude::*, + schema::{rev_history, rev_history::dsl}, + ConnectionPool, +}; +use flowy_error::{internal_error, FlowyResult}; +use flowy_sync::entities::revision::Revision; +use std::sync::Arc; + +pub struct SQLiteRevisionHistoryPersistence { + object_id: String, + pool: Arc, +} + +impl SQLiteRevisionHistoryPersistence { + pub fn new(object_id: &str, pool: Arc) -> Self { + let object_id = object_id.to_owned(); + Self { object_id, pool } + } +} + +impl RevisionHistoryDiskCache for SQLiteRevisionHistoryPersistence { + fn write_history(&self, revision: Revision) -> FlowyResult<()> { + let record = ( + dsl::object_id.eq(revision.object_id), + dsl::start_rev_id.eq(revision.base_rev_id), + dsl::end_rev_id.eq(revision.rev_id), + dsl::data.eq(revision.delta_data), + ); + let conn = self.pool.get().map_err(internal_error)?; + + let _ = insert_or_ignore_into(dsl::rev_history) + .values(vec![record]) + .execute(&*conn)?; + Ok(()) + } + + fn read_histories(&self) -> FlowyResult> { + let conn = self.pool.get().map_err(internal_error)?; + let records: Vec = dsl::rev_history + .filter(dsl::object_id.eq(&self.object_id)) + .load::(&*conn)?; + + Ok(records + .into_iter() + .map(|record| record.into()) + .collect::>()) + } +} + +#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] +#[table_name = "rev_history"] +struct RevisionRecord { + id: i32, + object_id: String, + start_rev_id: i64, + end_rev_id: i64, + data: Vec, +} + +pub struct RevisionHistory { + pub object_id: String, + pub start_rev_id: i64, + pub end_rev_id: i64, + pub data: Vec, +} + +impl std::convert::From for RevisionHistory { + fn from(record: RevisionRecord) -> Self { + RevisionHistory { + object_id: record.object_id, + start_rev_id: record.start_rev_id, + end_rev_id: record.end_rev_id, + data: record.data, + } + } +} diff --git a/frontend/rust-lib/flowy-revision/src/history/rev_history.rs b/frontend/rust-lib/flowy-revision/src/history/rev_history.rs new file mode 100644 index 0000000000..71bdc0c333 --- /dev/null +++ b/frontend/rust-lib/flowy-revision/src/history/rev_history.rs @@ -0,0 +1,201 @@ +use crate::{RevisionCompactor, RevisionHistory}; +use async_stream::stream; + +use flowy_error::{FlowyError, FlowyResult}; +use flowy_sync::entities::revision::Revision; +use futures_util::future::BoxFuture; +use futures_util::stream::StreamExt; +use futures_util::FutureExt; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{mpsc, RwLock}; +use tokio::time::interval; + +pub trait RevisionHistoryDiskCache: Send + Sync { + fn write_history(&self, revision: Revision) -> FlowyResult<()>; + + fn read_histories(&self) -> FlowyResult>; +} + +pub struct RevisionHistoryManager { + user_id: String, + stop_tx: mpsc::Sender<()>, + config: RevisionHistoryConfig, + revisions: Arc>>, + disk_cache: Arc, +} + +impl RevisionHistoryManager { + pub fn new( + user_id: &str, + object_id: &str, + config: RevisionHistoryConfig, + disk_cache: Arc, + rev_compactor: Arc, + ) -> Self { + let revisions = Arc::new(RwLock::new(vec![])); + let stop_tx = + spawn_history_checkpoint_runner(user_id, object_id, &disk_cache, &revisions, rev_compactor, &config); + let user_id = user_id.to_owned(); + Self { + user_id, + stop_tx, + config, + revisions, + disk_cache, + } + } + + pub async fn add_revision(&self, revision: &Revision) { + self.revisions.write().await.push(revision.clone()); + } + + pub async fn read_revision_histories(&self) -> FlowyResult> { + self.disk_cache.read_histories() + } +} + +pub struct RevisionHistoryConfig { + check_duration: Duration, +} + +impl std::default::Default for RevisionHistoryConfig { + fn default() -> Self { + Self { + check_duration: Duration::from_secs(5), + } + } +} + +fn spawn_history_checkpoint_runner( + user_id: &str, + object_id: &str, + disk_cache: &Arc, + revisions: &Arc>>, + rev_compactor: Arc, + config: &RevisionHistoryConfig, +) -> mpsc::Sender<()> { + let user_id = user_id.to_string(); + let object_id = object_id.to_string(); + let disk_cache = disk_cache.clone(); + let revisions = revisions.clone(); + + let (checkpoint_tx, checkpoint_rx) = mpsc::channel(1); + let (stop_tx, stop_rx) = mpsc::channel(1); + let checkpoint_sender = FixedDurationCheckpointSender { + user_id, + object_id, + checkpoint_tx, + disk_cache, + revisions, + rev_compactor, + duration: config.check_duration, + }; + tokio::spawn(HistoryCheckpointRunner::new(stop_rx, checkpoint_rx).run()); + tokio::spawn(checkpoint_sender.run()); + stop_tx +} + +struct HistoryCheckpointRunner { + stop_rx: Option>, + checkpoint_rx: Option>, +} + +impl HistoryCheckpointRunner { + fn new(stop_rx: mpsc::Receiver<()>, checkpoint_rx: mpsc::Receiver) -> Self { + Self { + stop_rx: Some(stop_rx), + checkpoint_rx: Some(checkpoint_rx), + } + } + + async fn run(mut self) { + let mut stop_rx = self.stop_rx.take().expect("It should only run once"); + let mut checkpoint_rx = self.checkpoint_rx.take().expect("It should only run once"); + let stream = stream! { + loop { + tokio::select! { + result = checkpoint_rx.recv() => { + match result { + Some(checkpoint) => yield checkpoint, + None => {}, + } + }, + _ = stop_rx.recv() => { + tracing::trace!("Checkpoint runner exit"); + break + }, + }; + } + }; + + stream + .for_each(|checkpoint| async move { + checkpoint.write().await; + }) + .await; + } +} + +struct HistoryCheckpoint { + user_id: String, + object_id: String, + revisions: Vec, + disk_cache: Arc, + rev_compactor: Arc, +} + +impl HistoryCheckpoint { + async fn write(self) { + if self.revisions.is_empty() { + return; + } + + let result = || { + let revision = self + .rev_compactor + .compact(&self.user_id, &self.object_id, self.revisions)?; + let _ = self.disk_cache.write_history(revision)?; + Ok::<(), FlowyError>(()) + }; + + match result() { + Ok(_) => {} + Err(e) => tracing::error!("Write history checkout failed: {:?}", e), + } + } +} + +struct FixedDurationCheckpointSender { + user_id: String, + object_id: String, + checkpoint_tx: mpsc::Sender, + disk_cache: Arc, + revisions: Arc>>, + rev_compactor: Arc, + duration: Duration, +} + +impl FixedDurationCheckpointSender { + fn run(self) -> BoxFuture<'static, ()> { + async move { + let mut interval = interval(self.duration); + let checkpoint_revisions: Vec = self.revisions.write().await.drain(..).collect(); + let checkpoint = HistoryCheckpoint { + user_id: self.user_id.clone(), + object_id: self.object_id.clone(), + revisions: checkpoint_revisions, + disk_cache: self.disk_cache.clone(), + rev_compactor: self.rev_compactor.clone(), + }; + match self.checkpoint_tx.send(checkpoint).await { + Ok(_) => { + interval.tick().await; + self.run(); + } + Err(_) => {} + } + } + .boxed() + } +} diff --git a/frontend/rust-lib/flowy-revision/src/lib.rs b/frontend/rust-lib/flowy-revision/src/lib.rs index 05e60c00e0..b7fd8a12e6 100644 --- a/frontend/rust-lib/flowy-revision/src/lib.rs +++ b/frontend/rust-lib/flowy-revision/src/lib.rs @@ -1,13 +1,17 @@ mod cache; mod conflict_resolve; +// mod history; mod rev_manager; mod rev_persistence; +mod snapshot; mod ws_manager; pub use cache::*; pub use conflict_resolve::*; +// pub use history::*; pub use rev_manager::*; pub use rev_persistence::*; +pub use snapshot::*; pub use ws_manager::*; #[macro_use] diff --git a/frontend/rust-lib/flowy-revision/src/rev_manager.rs b/frontend/rust-lib/flowy-revision/src/rev_manager.rs index 68d8db6f27..bd4d01740d 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_manager.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_manager.rs @@ -1,5 +1,5 @@ use crate::disk::RevisionState; -use crate::{RevisionPersistence, WSDataProviderDataSource}; +use crate::{RevisionPersistence, RevisionSnapshotDiskCache, RevisionSnapshotManager, WSDataProviderDataSource}; use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; use flowy_sync::{ @@ -45,14 +45,31 @@ pub struct RevisionManager { user_id: String, rev_id_counter: RevIdCounter, rev_persistence: Arc, - + #[allow(dead_code)] + rev_snapshot: Arc, + rev_compactor: Arc, #[cfg(feature = "flowy_unit_test")] rev_ack_notifier: tokio::sync::broadcast::Sender, } impl RevisionManager { - pub fn new(user_id: &str, object_id: &str, rev_persistence: Arc) -> Self { + pub fn new( + user_id: &str, + object_id: &str, + rev_persistence: RevisionPersistence, + rev_compactor: C, + snapshot_persistence: SP, + ) -> Self + where + SP: 'static + RevisionSnapshotDiskCache, + C: 'static + RevisionCompactor, + { let rev_id_counter = RevIdCounter::new(0); + let rev_compactor = Arc::new(rev_compactor); + + let rev_persistence = Arc::new(rev_persistence); + + let rev_snapshot = Arc::new(RevisionSnapshotManager::new(user_id, object_id, snapshot_persistence)); #[cfg(feature = "flowy_unit_test")] let (revision_ack_notifier, _) = tokio::sync::broadcast::channel(1); @@ -61,7 +78,8 @@ impl RevisionManager { user_id: user_id.to_owned(), rev_id_counter, rev_persistence, - + rev_snapshot, + rev_compactor, #[cfg(feature = "flowy_unit_test")] rev_ack_notifier: revision_ack_notifier, } @@ -100,20 +118,21 @@ impl RevisionManager { } let _ = self.rev_persistence.add_ack_revision(revision).await?; + // self.rev_history.add_revision(revision).await; self.rev_id_counter.set(revision.rev_id); Ok(()) } #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn add_local_revision<'a>( - &'a self, - revision: &Revision, - compactor: Box, - ) -> Result<(), FlowyError> { + pub async fn add_local_revision(&self, revision: &Revision) -> Result<(), FlowyError> { if revision.delta_data.is_empty() { return Err(FlowyError::internal().context("Delta data should be empty")); } - let rev_id = self.rev_persistence.add_sync_revision(revision, compactor).await?; + let rev_id = self + .rev_persistence + .add_sync_revision(revision, &self.rev_compactor) + .await?; + // self.rev_history.add_revision(revision).await; self.rev_id_counter.set(rev_id); Ok(()) } diff --git a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs index 6412b26ec6..eb3da339b7 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs @@ -2,7 +2,7 @@ use crate::cache::{ disk::{RevisionChangeset, RevisionDiskCache, SQLiteTextBlockRevisionPersistence}, memory::RevisionMemoryCacheDelegate, }; -use crate::disk::{RevisionRecord, RevisionState}; +use crate::disk::{RevisionRecord, RevisionState, SQLiteGridBlockRevisionPersistence}; use crate::memory::RevisionMemoryCache; use crate::RevisionCompactor; use flowy_database::ConnectionPool; @@ -24,13 +24,13 @@ pub struct RevisionPersistence { } impl RevisionPersistence { - pub fn new( - user_id: &str, - object_id: &str, - disk_cache: Arc>, - ) -> RevisionPersistence { + pub fn new(user_id: &str, object_id: &str, disk_cache: C) -> RevisionPersistence + where + C: 'static + RevisionDiskCache, + { let object_id = object_id.to_owned(); let user_id = user_id.to_owned(); + let disk_cache = Arc::new(disk_cache) as Arc>; let sync_seq = RwLock::new(RevisionSyncSequence::new()); let memory_cache = Arc::new(RevisionMemoryCache::new(&object_id, Arc::new(disk_cache.clone()))); Self { @@ -63,7 +63,7 @@ impl RevisionPersistence { pub(crate) async fn add_sync_revision<'a>( &'a self, revision: &'a Revision, - compactor: Box, + compactor: &Arc, ) -> FlowyResult { let result = self.sync_seq.read().await.compact(); match result { @@ -214,13 +214,20 @@ impl RevisionPersistence { } } -pub fn mk_revision_disk_cache( +pub fn mk_text_block_revision_disk_cache( user_id: &str, pool: Arc, ) -> Arc> { Arc::new(SQLiteTextBlockRevisionPersistence::new(user_id, pool)) } +pub fn mk_grid_block_revision_disk_cache( + user_id: &str, + pool: Arc, +) -> Arc> { + Arc::new(SQLiteGridBlockRevisionPersistence::new(user_id, pool)) +} + impl RevisionMemoryCacheDelegate for Arc> { fn checkpoint_tick(&self, mut records: Vec) -> FlowyResult<()> { records.retain(|record| record.write_to_disk); diff --git a/frontend/rust-lib/flowy-revision/src/snapshot/mod.rs b/frontend/rust-lib/flowy-revision/src/snapshot/mod.rs new file mode 100644 index 0000000000..adc4ee2ccc --- /dev/null +++ b/frontend/rust-lib/flowy-revision/src/snapshot/mod.rs @@ -0,0 +1,5 @@ +mod persistence; +mod rev_snapshot; + +pub use persistence::*; +pub use rev_snapshot::*; diff --git a/frontend/rust-lib/flowy-revision/src/snapshot/persistence.rs b/frontend/rust-lib/flowy-revision/src/snapshot/persistence.rs new file mode 100644 index 0000000000..d8d7bae3a6 --- /dev/null +++ b/frontend/rust-lib/flowy-revision/src/snapshot/persistence.rs @@ -0,0 +1,31 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_variables)] +use crate::{RevisionSnapshotDiskCache, RevisionSnapshotInfo}; +use flowy_database::ConnectionPool; +use flowy_error::FlowyResult; +use std::sync::Arc; + +pub struct SQLiteRevisionSnapshotPersistence { + object_id: String, + pool: Arc, +} + +impl SQLiteRevisionSnapshotPersistence { + pub fn new(object_id: &str, pool: Arc) -> Self { + Self { + object_id: object_id.to_string(), + pool, + } + } +} + +impl RevisionSnapshotDiskCache for SQLiteRevisionSnapshotPersistence { + fn write_snapshot(&self, object_id: &str, rev_id: i64, data: Vec) -> FlowyResult<()> { + todo!() + } + + fn read_snapshot(&self, object_id: &str, rev_id: i64) -> FlowyResult { + todo!() + } +} diff --git a/frontend/rust-lib/flowy-revision/src/snapshot/rev_snapshot.rs b/frontend/rust-lib/flowy-revision/src/snapshot/rev_snapshot.rs new file mode 100644 index 0000000000..047d21607e --- /dev/null +++ b/frontend/rust-lib/flowy-revision/src/snapshot/rev_snapshot.rs @@ -0,0 +1,32 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_variables)] +use flowy_error::FlowyResult; +use std::sync::Arc; + +pub trait RevisionSnapshotDiskCache: Send + Sync { + fn write_snapshot(&self, object_id: &str, rev_id: i64, data: Vec) -> FlowyResult<()>; + fn read_snapshot(&self, object_id: &str, rev_id: i64) -> FlowyResult; +} + +pub struct RevisionSnapshotManager { + user_id: String, + object_id: String, + disk_cache: Arc, +} + +impl RevisionSnapshotManager { + pub fn new(user_id: &str, object_id: &str, disk_cache: D) -> Self + where + D: RevisionSnapshotDiskCache + 'static, + { + let disk_cache = Arc::new(disk_cache); + Self { + user_id: user_id.to_string(), + object_id: object_id.to_string(), + disk_cache, + } + } +} + +pub struct RevisionSnapshotInfo {} diff --git a/frontend/rust-lib/flowy-revision/src/ws_manager.rs b/frontend/rust-lib/flowy-revision/src/ws_manager.rs index 42ad39c617..eb7539c380 100644 --- a/frontend/rust-lib/flowy-revision/src/ws_manager.rs +++ b/frontend/rust-lib/flowy-revision/src/ws_manager.rs @@ -1,6 +1,5 @@ use crate::ConflictRevisionSink; use async_stream::stream; - use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; use flowy_sync::entities::{ diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs index 88073a3107..f2b862c53a 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs @@ -16,14 +16,23 @@ use std::sync::Arc; pub struct GridDepsResolver(); impl GridDepsResolver { - pub fn resolve(ws_conn: Arc, user_session: Arc) -> Arc { + pub async fn resolve(ws_conn: Arc, user_session: Arc) -> Arc { let user = Arc::new(GridUserImpl(user_session.clone())); let rev_web_socket = Arc::new(GridWebSocket(ws_conn)); - Arc::new(GridManager::new( - user, + let grid_manager = Arc::new(GridManager::new( + user.clone(), rev_web_socket, Arc::new(GridDatabaseImpl(user_session)), - )) + )); + + if let (Ok(user_id), Ok(token)) = (user.user_id(), user.token()) { + match grid_manager.initialize(&user_id, &token).await { + Ok(_) => {} + Err(e) => tracing::error!("Initialize grid manager failed: {}", e), + } + } + + grid_manager } } diff --git a/frontend/rust-lib/flowy-sdk/src/lib.rs b/frontend/rust-lib/flowy-sdk/src/lib.rs index 4a37faefef..00a3785122 100644 --- a/frontend/rust-lib/flowy-sdk/src/lib.rs +++ b/frontend/rust-lib/flowy-sdk/src/lib.rs @@ -112,7 +112,7 @@ impl FlowySDK { &config.server_config, ); - let grid_manager = GridDepsResolver::resolve(ws_conn.clone(), user_session.clone()); + let grid_manager = GridDepsResolver::resolve(ws_conn.clone(), user_session.clone()).await; let folder_manager = FolderDepsResolver::resolve( local_server.clone(), @@ -147,7 +147,7 @@ impl FlowySDK { ) })); - _start_listening(&dispatcher, &ws_conn, &user_session, &folder_manager); + _start_listening(&dispatcher, &ws_conn, &user_session, &folder_manager, &grid_manager); Self { config, @@ -171,10 +171,12 @@ fn _start_listening( ws_conn: &Arc, user_session: &Arc, folder_manager: &Arc, + grid_manager: &Arc, ) { let subscribe_user_status = user_session.notifier.subscribe_user_status(); let subscribe_network_type = ws_conn.subscribe_network_ty(); let folder_manager = folder_manager.clone(); + let grid_manager = grid_manager.clone(); let cloned_folder_manager = folder_manager.clone(); let ws_conn = ws_conn.clone(); let user_session = user_session.clone(); @@ -182,7 +184,13 @@ fn _start_listening( dispatch.spawn(async move { user_session.init(); listen_on_websocket(ws_conn.clone()); - _listen_user_status(ws_conn.clone(), subscribe_user_status, folder_manager.clone()).await; + _listen_user_status( + ws_conn.clone(), + subscribe_user_status, + folder_manager.clone(), + grid_manager.clone(), + ) + .await; }); dispatch.spawn(async move { @@ -209,6 +217,7 @@ async fn _listen_user_status( ws_conn: Arc, mut subscribe: broadcast::Receiver, folder_manager: Arc, + grid_manager: Arc, ) { while let Ok(status) = subscribe.recv().await { let result = || async { @@ -216,6 +225,7 @@ async fn _listen_user_status( UserStatus::Login { token, user_id } => { tracing::trace!("User did login"); let _ = folder_manager.initialize(&user_id, &token).await?; + let _ = grid_manager.initialize(&user_id, &token).await?; let _ = ws_conn.start(token, user_id).await?; } UserStatus::Logout { .. } => { @@ -233,6 +243,11 @@ async fn _listen_user_status( let _ = folder_manager .initialize_with_new_user(&profile.id, &profile.token) .await?; + + let _ = grid_manager + .initialize_with_new_user(&profile.id, &profile.token) + .await?; + let _ = ws_conn.start(profile.token.clone(), profile.id.clone()).await?; let _ = ret.send(()); } diff --git a/frontend/rust-lib/flowy-test/src/event_builder.rs b/frontend/rust-lib/flowy-test/src/event_builder.rs index ed6f3c3bc8..d9841d6b4a 100644 --- a/frontend/rust-lib/flowy-test/src/event_builder.rs +++ b/frontend/rust-lib/flowy-test/src/event_builder.rs @@ -1,5 +1,5 @@ use crate::FlowySDKTest; -use flowy_user::{entities::UserProfile, errors::FlowyError}; +use flowy_user::{entities::UserProfilePB, errors::FlowyError}; use lib_dispatch::prelude::{EventDispatcher, EventResponse, FromBytes, ModuleRequest, StatusCode, ToBytes, *}; use std::{ convert::TryFrom, @@ -14,7 +14,7 @@ impl FolderEventBuilder { pub fn new(sdk: FlowySDKTest) -> Self { EventBuilder::test(TestContext::new(sdk)) } - pub fn user_profile(&self) -> &Option { + pub fn user_profile(&self) -> &Option { &self.user_profile } } @@ -24,7 +24,7 @@ pub type UserModuleEventBuilder = FolderEventBuilder; #[derive(Clone)] pub struct EventBuilder { context: TestContext, - user_profile: Option, + user_profile: Option, err_phantom: PhantomData, } diff --git a/frontend/rust-lib/flowy-test/src/helper.rs b/frontend/rust-lib/flowy-test/src/helper.rs index efc4282386..1265bd695c 100644 --- a/frontend/rust-lib/flowy-test/src/helper.rs +++ b/frontend/rust-lib/flowy-test/src/helper.rs @@ -1,15 +1,15 @@ use crate::prelude::*; -use flowy_folder::entities::WorkspaceId; +use flowy_folder::entities::WorkspaceIdPB; use flowy_folder::{ entities::{ app::*, view::*, - workspace::{CreateWorkspacePayload, Workspace}, + workspace::{CreateWorkspacePayloadPB, WorkspacePB}, }, event_map::FolderEvent::{CreateWorkspace, OpenWorkspace, *}, }; use flowy_user::{ - entities::{SignInPayload, SignUpPayload, UserProfile}, + entities::{SignInPayloadPB, SignUpPayloadPB, UserProfilePB}, errors::FlowyError, event_map::UserEvent::{InitUser, SignIn, SignOut, SignUp}, }; @@ -18,9 +18,9 @@ use std::{fs, path::PathBuf, sync::Arc}; pub struct ViewTest { pub sdk: FlowySDKTest, - pub workspace: Workspace, - pub app: App, - pub view: View, + pub workspace: WorkspacePB, + pub app: AppPB, + pub view: ViewPB, } impl ViewTest { @@ -47,8 +47,8 @@ impl ViewTest { } } -async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> Workspace { - let request = CreateWorkspacePayload { +async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB { + let request = CreateWorkspacePayloadPB { name: name.to_owned(), desc: desc.to_owned(), }; @@ -58,12 +58,12 @@ async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> Workspa .payload(request) .async_send() .await - .parse::(); + .parse::(); workspace } async fn open_workspace(sdk: &FlowySDKTest, workspace_id: &str) { - let payload = WorkspaceId { + let payload = WorkspaceIdPB { value: Some(workspace_id.to_owned()), }; let _ = FolderEventBuilder::new(sdk.clone()) @@ -73,8 +73,8 @@ async fn open_workspace(sdk: &FlowySDKTest, workspace_id: &str) { .await; } -async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &str) -> App { - let create_app_request = CreateAppPayload { +async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &str) -> AppPB { + let create_app_request = CreateAppPayloadPB { workspace_id: workspace_id.to_owned(), name: name.to_string(), desc: desc.to_string(), @@ -86,12 +86,12 @@ async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &s .payload(create_app_request) .async_send() .await - .parse::(); + .parse::(); app } -async fn create_view(sdk: &FlowySDKTest, app_id: &str, data_type: ViewDataType, data: Vec) -> View { - let request = CreateViewPayload { +async fn create_view(sdk: &FlowySDKTest, app_id: &str, data_type: ViewDataType, data: Vec) -> ViewPB { + let request = CreateViewPayloadPB { belong_to_id: app_id.to_string(), name: "View A".to_string(), desc: "".to_string(), @@ -106,7 +106,7 @@ async fn create_view(sdk: &FlowySDKTest, app_id: &str, data_type: ViewDataType, .payload(request) .async_send() .await - .parse::(); + .parse::(); view } @@ -138,13 +138,13 @@ pub fn login_password() -> String { } pub struct SignUpContext { - pub user_profile: UserProfile, + pub user_profile: UserProfilePB, pub password: String, } pub fn sign_up(dispatch: Arc) -> SignUpContext { let password = login_password(); - let payload = SignUpPayload { + let payload = SignUpPayloadPB { email: random_email(), name: "app flowy".to_string(), password: password.clone(), @@ -154,7 +154,7 @@ pub fn sign_up(dispatch: Arc) -> SignUpContext { let request = ModuleRequest::new(SignUp).payload(payload); let user_profile = EventDispatcher::sync_send(dispatch, request) - .parse::() + .parse::() .unwrap() .unwrap(); @@ -164,7 +164,7 @@ pub fn sign_up(dispatch: Arc) -> SignUpContext { pub async fn async_sign_up(dispatch: Arc) -> SignUpContext { let password = login_password(); let email = random_email(); - let payload = SignUpPayload { + let payload = SignUpPayloadPB { email, name: "app flowy".to_string(), password: password.clone(), @@ -175,7 +175,7 @@ pub async fn async_sign_up(dispatch: Arc) -> SignUpContext { let request = ModuleRequest::new(SignUp).payload(payload); let user_profile = EventDispatcher::async_send(dispatch.clone(), request) .await - .parse::() + .parse::() .unwrap() .unwrap(); @@ -189,8 +189,8 @@ pub async fn init_user_setting(dispatch: Arc) { } #[allow(dead_code)] -fn sign_in(dispatch: Arc) -> UserProfile { - let payload = SignInPayload { +fn sign_in(dispatch: Arc) -> UserProfilePB { + let payload = SignInPayloadPB { email: login_email(), password: login_password(), name: "rust".to_owned(), @@ -200,7 +200,7 @@ fn sign_in(dispatch: Arc) -> UserProfile { let request = ModuleRequest::new(SignIn).payload(payload); EventDispatcher::sync_send(dispatch, request) - .parse::() + .parse::() .unwrap() .unwrap() } diff --git a/frontend/rust-lib/flowy-test/src/lib.rs b/frontend/rust-lib/flowy-test/src/lib.rs index 70159edf19..8f6aacb44d 100644 --- a/frontend/rust-lib/flowy-test/src/lib.rs +++ b/frontend/rust-lib/flowy-test/src/lib.rs @@ -4,7 +4,7 @@ pub mod helper; use crate::helper::*; use flowy_net::{get_client_server_configuration, ClientServerConfiguration}; use flowy_sdk::{FlowySDK, FlowySDKConfig}; -use flowy_user::entities::UserProfile; +use flowy_user::entities::UserProfilePB; use nanoid::nanoid; pub mod prelude { @@ -47,7 +47,7 @@ impl FlowySDKTest { context } - pub async fn init_user(&self) -> UserProfile { + pub async fn init_user(&self) -> UserProfilePB { let context = async_sign_up(self.inner.dispatcher()).await; init_user_setting(self.inner.dispatcher()).await; context.user_profile diff --git a/frontend/rust-lib/flowy-text-block/src/editor.rs b/frontend/rust-lib/flowy-text-block/src/editor.rs index 111808109d..6fed85c913 100644 --- a/frontend/rust-lib/flowy-text-block/src/editor.rs +++ b/frontend/rust-lib/flowy-text-block/src/editor.rs @@ -9,7 +9,7 @@ use flowy_error::{internal_error, FlowyResult}; use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilder, RevisionWebSocket}; use flowy_sync::entities::ws_data::ServerRevisionWSData; use flowy_sync::{ - entities::{revision::Revision, text_block::TextBlockInfo}, + entities::{revision::Revision, text_block::DocumentPB}, errors::CollaborateResult, util::make_delta_from_revisions, }; @@ -229,14 +229,14 @@ impl TextBlockEditor { struct TextBlockInfoBuilder(); impl RevisionObjectBuilder for TextBlockInfoBuilder { - type Output = TextBlockInfo; + type Output = DocumentPB; fn build_object(object_id: &str, revisions: Vec) -> FlowyResult { let (base_rev_id, rev_id) = revisions.last().unwrap().pair_rev_id(); let mut delta = make_delta_from_revisions(revisions)?; correct_delta(&mut delta); - Result::::Ok(TextBlockInfo { + Result::::Ok(DocumentPB { block_id: object_id.to_owned(), text: delta.to_delta_str(), rev_id, diff --git a/frontend/rust-lib/flowy-text-block/src/entities.rs b/frontend/rust-lib/flowy-text-block/src/entities.rs index 61dade5113..ec8767285b 100644 --- a/frontend/rust-lib/flowy-text-block/src/entities.rs +++ b/frontend/rust-lib/flowy-text-block/src/entities.rs @@ -30,7 +30,7 @@ impl std::convert::From for ExportType { } #[derive(Default, ProtoBuf)] -pub struct ExportPayload { +pub struct ExportPayloadPB { #[pb(index = 1)] pub view_id: String, @@ -44,7 +44,7 @@ pub struct ExportParams { pub export_type: ExportType, } -impl TryInto for ExportPayload { +impl TryInto for ExportPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { Ok(ExportParams { @@ -55,7 +55,7 @@ impl TryInto for ExportPayload { } #[derive(Default, ProtoBuf)] -pub struct ExportData { +pub struct ExportDataPB { #[pb(index = 1)] pub data: String, diff --git a/frontend/rust-lib/flowy-text-block/src/event_handler.rs b/frontend/rust-lib/flowy-text-block/src/event_handler.rs index 0934d20a29..dc9812d862 100644 --- a/frontend/rust-lib/flowy-text-block/src/event_handler.rs +++ b/frontend/rust-lib/flowy-text-block/src/event_handler.rs @@ -1,41 +1,41 @@ -use crate::entities::{ExportData, ExportParams, ExportPayload}; +use crate::entities::{ExportDataPB, ExportParams, ExportPayloadPB}; use crate::TextBlockManager; use flowy_error::FlowyError; -use flowy_sync::entities::text_block::{TextBlockDelta, TextBlockId}; +use flowy_sync::entities::text_block::{TextBlockDeltaPB, TextBlockIdPB}; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::convert::TryInto; use std::sync::Arc; pub(crate) async fn get_block_data_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let block_id: TextBlockId = data.into_inner(); +) -> DataResult { + let block_id: TextBlockIdPB = data.into_inner(); let editor = manager.open_block(&block_id).await?; let delta_str = editor.delta_str().await?; - data_result(TextBlockDelta { + data_result(TextBlockDeltaPB { block_id: block_id.into(), delta_str, }) } pub(crate) async fn apply_delta_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let block_delta = manager.receive_local_delta(data.into_inner()).await?; data_result(block_delta) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn export_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: ExportParams = data.into_inner().try_into()?; let editor = manager.open_block(¶ms.view_id).await?; let delta_json = editor.delta_str().await?; - data_result(ExportData { + data_result(ExportDataPB { data: delta_json, export_type: params.export_type, }) diff --git a/frontend/rust-lib/flowy-text-block/src/event_map.rs b/frontend/rust-lib/flowy-text-block/src/event_map.rs index f995fd282b..cfc06bf32c 100644 --- a/frontend/rust-lib/flowy-text-block/src/event_map.rs +++ b/frontend/rust-lib/flowy-text-block/src/event_map.rs @@ -19,12 +19,12 @@ pub fn create(block_manager: Arc) -> Module { #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum TextBlockEvent { - #[event(input = "TextBlockId", output = "TextBlockDelta")] + #[event(input = "TextBlockIdPB", output = "TextBlockDeltaPB")] GetBlockData = 0, - #[event(input = "TextBlockDelta", output = "TextBlockDelta")] + #[event(input = "TextBlockDeltaPB", output = "TextBlockDeltaPB")] ApplyDelta = 1, - #[event(input = "ExportPayload", output = "ExportData")] + #[event(input = "ExportPayloadPB", output = "ExportDataPB")] ExportDocument = 2, } diff --git a/frontend/rust-lib/flowy-text-block/src/lib.rs b/frontend/rust-lib/flowy-text-block/src/lib.rs index f470739d85..37ddf6ea1e 100644 --- a/frontend/rust-lib/flowy-text-block/src/lib.rs +++ b/frontend/rust-lib/flowy-text-block/src/lib.rs @@ -15,13 +15,13 @@ pub mod errors { pub const TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS: u64 = 1000; use crate::errors::FlowyError; -use flowy_sync::entities::text_block::{CreateTextBlockParams, ResetTextBlockParams, TextBlockId, TextBlockInfo}; +use flowy_sync::entities::text_block::{CreateTextBlockParams, DocumentPB, ResetTextBlockParams, TextBlockIdPB}; use lib_infra::future::FutureResult; pub trait BlockCloudService: Send + Sync { fn create_block(&self, token: &str, params: CreateTextBlockParams) -> FutureResult<(), FlowyError>; - fn read_block(&self, token: &str, params: TextBlockId) -> FutureResult, FlowyError>; + fn read_block(&self, token: &str, params: TextBlockIdPB) -> FutureResult, FlowyError>; fn update_block(&self, token: &str, params: ResetTextBlockParams) -> FutureResult<(), FlowyError>; } diff --git a/frontend/rust-lib/flowy-text-block/src/manager.rs b/frontend/rust-lib/flowy-text-block/src/manager.rs index ead24857be..57e875668e 100644 --- a/frontend/rust-lib/flowy-text-block/src/manager.rs +++ b/frontend/rust-lib/flowy-text-block/src/manager.rs @@ -1,13 +1,16 @@ +use crate::queue::TextBlockRevisionCompactor; use crate::{editor::TextBlockEditor, errors::FlowyError, BlockCloudService}; use bytes::Bytes; use dashmap::DashMap; use flowy_database::ConnectionPool; use flowy_error::FlowyResult; use flowy_revision::disk::SQLiteTextBlockRevisionPersistence; -use flowy_revision::{RevisionCloudService, RevisionManager, RevisionPersistence, RevisionWebSocket}; +use flowy_revision::{ + RevisionCloudService, RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionSnapshotPersistence, +}; use flowy_sync::entities::{ revision::{md5, RepeatedRevision, Revision}, - text_block::{TextBlockDelta, TextBlockId}, + text_block::{TextBlockDeltaPB, TextBlockIdPB}, ws_data::ServerRevisionWSData, }; use lib_infra::future::FutureResult; @@ -71,11 +74,11 @@ impl TextBlockManager { } #[tracing::instrument(level = "debug", skip(self, delta), fields(doc_id = %delta.block_id), err)] - pub async fn receive_local_delta(&self, delta: TextBlockDelta) -> Result { + pub async fn receive_local_delta(&self, delta: TextBlockDeltaPB) -> Result { let editor = self.get_block_editor(&delta.block_id).await?; let _ = editor.compose_local_delta(Bytes::from(delta.delta_str)).await?; let document_json = editor.delta_str().await?; - Ok(TextBlockDelta { + Ok(TextBlockDeltaPB { block_id: delta.block_id.clone(), delta_str: document_json, }) @@ -139,9 +142,20 @@ impl TextBlockManager { fn make_rev_manager(&self, doc_id: &str, pool: Arc) -> Result { let user_id = self.user.user_id()?; - let disk_cache = Arc::new(SQLiteTextBlockRevisionPersistence::new(&user_id, pool)); - let rev_persistence = Arc::new(RevisionPersistence::new(&user_id, doc_id, disk_cache)); - Ok(RevisionManager::new(&user_id, doc_id, rev_persistence)) + let disk_cache = SQLiteTextBlockRevisionPersistence::new(&user_id, pool.clone()); + let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache); + // let history_persistence = SQLiteRevisionHistoryPersistence::new(doc_id, pool.clone()); + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(doc_id, pool); + let rev_compactor = TextBlockRevisionCompactor(); + + Ok(RevisionManager::new( + &user_id, + doc_id, + rev_persistence, + rev_compactor, + // history_persistence, + snapshot_persistence, + )) } } @@ -153,7 +167,7 @@ struct TextBlockRevisionCloudService { impl RevisionCloudService for TextBlockRevisionCloudService { #[tracing::instrument(level = "trace", skip(self))] fn fetch_object(&self, user_id: &str, object_id: &str) -> FutureResult, FlowyError> { - let params: TextBlockId = object_id.to_string().into(); + let params: TextBlockIdPB = object_id.to_string().into(); let server = self.server.clone(); let token = self.token.clone(); let user_id = user_id.to_string(); diff --git a/frontend/rust-lib/flowy-text-block/src/queue.rs b/frontend/rust-lib/flowy-text-block/src/queue.rs index 7c11afd0ff..343ad11e7b 100644 --- a/frontend/rust-lib/flowy-text-block/src/queue.rs +++ b/frontend/rust-lib/flowy-text-block/src/queue.rs @@ -186,10 +186,7 @@ impl EditBlockQueue { &user_id, md5, ); - let _ = self - .rev_manager - .add_local_revision(&revision, Box::new(TextBlockRevisionCompactor())) - .await?; + let _ = self.rev_manager.add_local_revision(&revision).await?; Ok(rev_id.into()) } } diff --git a/frontend/rust-lib/flowy-user/src/entities/auth.rs b/frontend/rust-lib/flowy-user/src/entities/auth.rs index 14224ae231..f7625b37d7 100644 --- a/frontend/rust-lib/flowy-user/src/entities/auth.rs +++ b/frontend/rust-lib/flowy-user/src/entities/auth.rs @@ -4,7 +4,7 @@ use flowy_derive::ProtoBuf; use std::convert::TryInto; #[derive(ProtoBuf, Default)] -pub struct SignInPayload { +pub struct SignInPayloadPB { #[pb(index = 1)] pub email: String, @@ -42,7 +42,7 @@ pub struct SignInResponse { pub token: String, } -impl TryInto for SignInPayload { +impl TryInto for SignInPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -58,7 +58,7 @@ impl TryInto for SignInPayload { } #[derive(ProtoBuf, Default)] -pub struct SignUpPayload { +pub struct SignUpPayloadPB { #[pb(index = 1)] pub email: String, @@ -68,7 +68,7 @@ pub struct SignUpPayload { #[pb(index = 3)] pub password: String, } -impl TryInto for SignUpPayload { +impl TryInto for SignUpPayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs index 82553e8050..276894ffc8 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs @@ -7,13 +7,13 @@ use crate::{ }; #[derive(Default, ProtoBuf)] -pub struct UserToken { +pub struct UserTokenPB { #[pb(index = 1)] pub token: String, } #[derive(ProtoBuf, Default, Debug, PartialEq, Eq, Clone)] -pub struct UserProfile { +pub struct UserProfilePB { #[pb(index = 1)] pub id: String, @@ -28,7 +28,7 @@ pub struct UserProfile { } #[derive(ProtoBuf, Default)] -pub struct UpdateUserProfilePayload { +pub struct UpdateUserProfilePayloadPB { #[pb(index = 1)] pub id: String, @@ -42,7 +42,7 @@ pub struct UpdateUserProfilePayload { pub password: Option, } -impl UpdateUserProfilePayload { +impl UpdateUserProfilePayloadPB { pub fn new(id: &str) -> Self { Self { id: id.to_owned(), @@ -85,7 +85,9 @@ impl UpdateUserProfileParams { pub fn new(user_id: &str) -> Self { Self { id: user_id.to_owned(), - ..Default::default() + name: None, + email: None, + password: None, } } @@ -105,7 +107,7 @@ impl UpdateUserProfileParams { } } -impl TryInto for UpdateUserProfilePayload { +impl TryInto for UpdateUserProfilePayloadPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs index 8f7beadb4a..23c74b6d5f 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs @@ -2,22 +2,22 @@ use flowy_derive::ProtoBuf; use serde::{Deserialize, Serialize}; #[derive(ProtoBuf, Default, Debug, Clone)] -pub struct UserPreferences { +pub struct UserPreferencesPB { #[pb(index = 1)] user_id: String, #[pb(index = 2)] - appearance_setting: AppearanceSettings, + appearance_setting: AppearanceSettingsPB, } #[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)] -pub struct AppearanceSettings { +pub struct AppearanceSettingsPB { #[pb(index = 1)] pub theme: String, #[pb(index = 2)] #[serde(default)] - pub locale: LocaleSettings, + pub locale: LocaleSettingsPB, #[pb(index = 3)] #[serde(default = "DEFAULT_RESET_VALUE")] @@ -27,7 +27,7 @@ pub struct AppearanceSettings { const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT; #[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)] -pub struct LocaleSettings { +pub struct LocaleSettingsPB { #[pb(index = 1)] pub language_code: String, @@ -35,7 +35,7 @@ pub struct LocaleSettings { pub country_code: String, } -impl std::default::Default for LocaleSettings { +impl std::default::Default for LocaleSettingsPB { fn default() -> Self { Self { language_code: "en".to_owned(), @@ -47,11 +47,11 @@ impl std::default::Default for LocaleSettings { pub const APPEARANCE_DEFAULT_THEME: &str = "light"; const APPEARANCE_RESET_AS_DEFAULT: bool = true; -impl std::default::Default for AppearanceSettings { +impl std::default::Default for AppearanceSettingsPB { fn default() -> Self { - AppearanceSettings { + AppearanceSettingsPB { theme: APPEARANCE_DEFAULT_THEME.to_owned(), - locale: LocaleSettings::default(), + locale: LocaleSettingsPB::default(), reset_as_default: APPEARANCE_RESET_AS_DEFAULT, } } diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 348da177d3..ced1ede179 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -1,5 +1,5 @@ use crate::entities::{ - SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, }; use crate::{errors::FlowyError, handlers::*, services::UserSession}; use lib_dispatch::prelude::*; @@ -26,7 +26,7 @@ pub trait UserCloudService: Send + Sync { fn sign_in(&self, params: SignInParams) -> FutureResult; fn sign_out(&self, token: &str) -> FutureResult<(), FlowyError>; fn update_user(&self, token: &str, params: UpdateUserProfileParams) -> FutureResult<(), FlowyError>; - fn get_user(&self, token: &str) -> FutureResult; + fn get_user(&self, token: &str) -> FutureResult; fn ws_addr(&self) -> String; } @@ -39,27 +39,27 @@ pub enum UserEvent { #[event()] InitUser = 0, - #[event(input = "SignInPayload", output = "UserProfile")] + #[event(input = "SignInPayloadPB", output = "UserProfilePB")] SignIn = 1, - #[event(input = "SignUpPayload", output = "UserProfile")] + #[event(input = "SignUpPayloadPB", output = "UserProfilePB")] SignUp = 2, #[event(passthrough)] SignOut = 3, - #[event(input = "UpdateUserProfilePayload")] + #[event(input = "UpdateUserProfilePayloadPB")] UpdateUserProfile = 4, - #[event(output = "UserProfile")] + #[event(output = "UserProfilePB")] GetUserProfile = 5, - #[event(output = "UserProfile")] + #[event(output = "UserProfilePB")] CheckUser = 6, - #[event(input = "AppearanceSettings")] + #[event(input = "AppearanceSettingsPB")] SetAppearanceSetting = 7, - #[event(output = "AppearanceSettings")] + #[event(output = "AppearanceSettingsPB")] GetAppearanceSetting = 8, } diff --git a/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs b/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs index 22a17d887c..8ff5bdb7e2 100644 --- a/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs +++ b/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs @@ -7,9 +7,9 @@ use std::{convert::TryInto, sync::Arc}; // tracing instrument 👉🏻 https://docs.rs/tracing/0.1.26/tracing/attr.instrument.html #[tracing::instrument(level = "debug", name = "sign_in", skip(data, session), fields(email = %data.email), err)] pub async fn sign_in( - data: Data, + data: Data, session: AppData>, -) -> DataResult { +) -> DataResult { let params: SignInParams = data.into_inner().try_into()?; let user_profile = session.sign_in(params).await?; data_result(user_profile) @@ -26,9 +26,9 @@ pub async fn sign_in( err )] pub async fn sign_up( - data: Data, + data: Data, session: AppData>, -) -> DataResult { +) -> DataResult { let params: SignUpParams = data.into_inner().try_into()?; let user_profile = session.sign_up(params).await?; diff --git a/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs b/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs index 6ee62519e7..8a10dd89e2 100644 --- a/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs +++ b/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs @@ -1,5 +1,5 @@ use crate::entities::{ - AppearanceSettings, UpdateUserProfileParams, UpdateUserProfilePayload, UserProfile, APPEARANCE_DEFAULT_THEME, + AppearanceSettingsPB, UpdateUserProfileParams, UpdateUserProfilePayloadPB, UserProfilePB, APPEARANCE_DEFAULT_THEME, }; use crate::{errors::FlowyError, services::UserSession}; use flowy_database::kv::KV; @@ -13,13 +13,13 @@ pub async fn init_user_handler(session: AppData>) -> Result<(), } #[tracing::instrument(level = "debug", skip(session))] -pub async fn check_user_handler(session: AppData>) -> DataResult { +pub async fn check_user_handler(session: AppData>) -> DataResult { let user_profile = session.check_user().await?; data_result(user_profile) } #[tracing::instrument(level = "debug", skip(session))] -pub async fn get_user_profile_handler(session: AppData>) -> DataResult { +pub async fn get_user_profile_handler(session: AppData>) -> DataResult { let user_profile = session.get_user_profile().await?; data_result(user_profile) } @@ -32,7 +32,7 @@ pub async fn sign_out(session: AppData>) -> Result<(), FlowyErr #[tracing::instrument(level = "debug", skip(data, session))] pub async fn update_user_profile_handler( - data: Data, + data: Data, session: AppData>, ) -> Result<(), FlowyError> { let params: UpdateUserProfileParams = data.into_inner().try_into()?; @@ -43,7 +43,7 @@ pub async fn update_user_profile_handler( const APPEARANCE_SETTING_CACHE_KEY: &str = "appearance_settings"; #[tracing::instrument(level = "debug", skip(data), err)] -pub async fn set_appearance_setting(data: Data) -> Result<(), FlowyError> { +pub async fn set_appearance_setting(data: Data) -> Result<(), FlowyError> { let mut setting = data.into_inner(); if setting.theme.is_empty() { setting.theme = APPEARANCE_DEFAULT_THEME.to_string(); @@ -55,15 +55,15 @@ pub async fn set_appearance_setting(data: Data) -> Result<() } #[tracing::instrument(err)] -pub async fn get_appearance_setting() -> DataResult { +pub async fn get_appearance_setting() -> DataResult { match KV::get_str(APPEARANCE_SETTING_CACHE_KEY) { - None => data_result(AppearanceSettings::default()), + None => data_result(AppearanceSettingsPB::default()), Some(s) => { let setting = match serde_json::from_str(&s) { Ok(setting) => setting, Err(e) => { tracing::error!("Deserialize AppearanceSettings failed: {:?}, fallback to default", e); - AppearanceSettings::default() + AppearanceSettingsPB::default() } }; data_result(setting) diff --git a/frontend/rust-lib/flowy-user/src/services/database.rs b/frontend/rust-lib/flowy-user/src/services/database.rs index eaf5b7f6cb..64ebd705a7 100644 --- a/frontend/rust-lib/flowy-user/src/services/database.rs +++ b/frontend/rust-lib/flowy-user/src/services/database.rs @@ -1,4 +1,4 @@ -use crate::entities::{SignInResponse, SignUpResponse, UpdateUserProfileParams, UserProfile}; +use crate::entities::{SignInResponse, SignUpResponse, UpdateUserProfileParams, UserProfilePB}; use flowy_database::ConnectionPool; use flowy_database::{schema::user_table, DBConnection, Database}; use flowy_error::{ErrorCode, FlowyError}; @@ -113,9 +113,9 @@ impl std::convert::From for UserTable { } } -impl std::convert::From for UserProfile { +impl std::convert::From for UserProfilePB { fn from(table: UserTable) -> Self { - UserProfile { + UserProfilePB { id: table.id, email: table.email, name: table.name, diff --git a/frontend/rust-lib/flowy-user/src/services/notifier.rs b/frontend/rust-lib/flowy-user/src/services/notifier.rs index 9500988d99..4efdc301ac 100644 --- a/frontend/rust-lib/flowy-user/src/services/notifier.rs +++ b/frontend/rust-lib/flowy-user/src/services/notifier.rs @@ -1,4 +1,4 @@ -use crate::entities::UserProfile; +use crate::entities::UserProfilePB; use tokio::sync::{broadcast, mpsc}; #[derive(Clone)] @@ -14,7 +14,7 @@ pub enum UserStatus { token: String, }, SignUp { - profile: UserProfile, + profile: UserProfilePB, ret: mpsc::Sender<()>, }, } @@ -42,7 +42,7 @@ impl UserNotifier { }); } - pub(crate) fn notify_sign_up(&self, ret: mpsc::Sender<()>, user_profile: &UserProfile) { + pub(crate) fn notify_sign_up(&self, ret: mpsc::Sender<()>, user_profile: &UserProfilePB) { let _ = self.user_status_notifier.send(UserStatus::SignUp { profile: user_profile.clone(), ret, diff --git a/frontend/rust-lib/flowy-user/src/services/user_session.rs b/frontend/rust-lib/flowy-user/src/services/user_session.rs index 48e6ef9661..3822a8a0db 100644 --- a/frontend/rust-lib/flowy-user/src/services/user_session.rs +++ b/frontend/rust-lib/flowy-user/src/services/user_session.rs @@ -1,5 +1,5 @@ use crate::entities::{ - SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, }; use crate::{ dart_notification::*, @@ -80,7 +80,7 @@ impl UserSession { } #[tracing::instrument(level = "debug", skip(self))] - pub async fn sign_in(&self, params: SignInParams) -> Result { + pub async fn sign_in(&self, params: SignInParams) -> Result { if self.is_user_login(¶ms.email) { self.get_user_profile().await } else { @@ -88,14 +88,14 @@ impl UserSession { let session: Session = resp.clone().into(); let _ = self.set_session(Some(session))?; let user_table = self.save_user(resp.into()).await?; - let user_profile: UserProfile = user_table.into(); + let user_profile: UserProfilePB = user_table.into(); self.notifier.notify_login(&user_profile.token, &user_profile.id); Ok(user_profile) } } #[tracing::instrument(level = "debug", skip(self))] - pub async fn sign_up(&self, params: SignUpParams) -> Result { + pub async fn sign_up(&self, params: SignUpParams) -> Result { if self.is_user_login(¶ms.email) { self.get_user_profile().await } else { @@ -103,7 +103,7 @@ impl UserSession { let session: Session = resp.clone().into(); let _ = self.set_session(Some(session))?; let user_table = self.save_user(resp.into()).await?; - let user_profile: UserProfile = user_table.into(); + let user_profile: UserProfilePB = user_table.into(); let (ret, mut tx) = mpsc::channel(1); self.notifier.notify_sign_up(ret, &user_profile); @@ -143,7 +143,7 @@ impl UserSession { Ok(()) } - pub async fn check_user(&self) -> Result { + pub async fn check_user(&self) -> Result { let (user_id, token) = self.get_session()?.into_part(); let user = dsl::user_table @@ -154,7 +154,7 @@ impl UserSession { Ok(user.into()) } - pub async fn get_user_profile(&self) -> Result { + pub async fn get_user_profile(&self) -> Result { let (user_id, token) = self.get_session()?.into_part(); let user = dsl::user_table .filter(user_table::id.eq(&user_id)) diff --git a/frontend/rust-lib/flowy-user/tests/event/auth_test.rs b/frontend/rust-lib/flowy-user/tests/event/auth_test.rs index 39b22a9af0..288b9d8b08 100644 --- a/frontend/rust-lib/flowy-user/tests/event/auth_test.rs +++ b/frontend/rust-lib/flowy-user/tests/event/auth_test.rs @@ -1,13 +1,13 @@ use crate::helper::*; use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest}; -use flowy_user::entities::{SignInPayload, SignUpPayload, UserProfile}; +use flowy_user::entities::{SignInPayloadPB, SignUpPayloadPB, UserProfilePB}; use flowy_user::{errors::ErrorCode, event_map::UserEvent::*}; #[tokio::test] async fn sign_up_with_invalid_email() { for email in invalid_email_test_case() { let sdk = FlowySDKTest::default(); - let request = SignUpPayload { + let request = SignUpPayloadPB { email: email.to_string(), name: valid_name(), password: login_password(), @@ -29,7 +29,7 @@ async fn sign_up_with_invalid_email() { async fn sign_up_with_invalid_password() { for password in invalid_password_test_case() { let sdk = FlowySDKTest::default(); - let request = SignUpPayload { + let request = SignUpPayloadPB { email: random_email(), name: valid_name(), password, @@ -50,7 +50,7 @@ async fn sign_in_success() { let _ = UserModuleEventBuilder::new(test.clone()).event(SignOut).sync_send(); let sign_up_context = test.sign_up().await; - let request = SignInPayload { + let request = SignInPayloadPB { email: sign_up_context.user_profile.email.clone(), password: sign_up_context.password.clone(), name: "".to_string(), @@ -61,7 +61,7 @@ async fn sign_in_success() { .payload(request) .async_send() .await - .parse::(); + .parse::(); dbg!(&response); } @@ -69,7 +69,7 @@ async fn sign_in_success() { async fn sign_in_with_invalid_email() { for email in invalid_email_test_case() { let sdk = FlowySDKTest::default(); - let request = SignInPayload { + let request = SignInPayloadPB { email: email.to_string(), password: login_password(), name: "".to_string(), @@ -93,7 +93,7 @@ async fn sign_in_with_invalid_password() { for password in invalid_password_test_case() { let sdk = FlowySDKTest::default(); - let request = SignInPayload { + let request = SignInPayloadPB { email: random_email(), password, name: "".to_string(), diff --git a/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs b/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs index b01a533336..fa77b69509 100644 --- a/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs +++ b/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs @@ -1,6 +1,6 @@ use crate::helper::*; use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest}; -use flowy_user::entities::{UpdateUserProfilePayload, UserProfile}; +use flowy_user::entities::{UpdateUserProfilePayloadPB, UserProfilePB}; use flowy_user::{errors::ErrorCode, event_map::UserEvent::*}; use nanoid::nanoid; @@ -24,7 +24,7 @@ async fn user_profile_get() { let user = UserModuleEventBuilder::new(test.clone()) .event(GetUserProfile) .sync_send() - .parse::(); + .parse::(); assert_eq!(user_profile, user); } @@ -33,7 +33,7 @@ async fn user_update_with_name() { let sdk = FlowySDKTest::default(); let user = sdk.init_user().await; let new_name = "hello_world".to_owned(); - let request = UpdateUserProfilePayload::new(&user.id).name(&new_name); + let request = UpdateUserProfilePayloadPB::new(&user.id).name(&new_name); let _ = UserModuleEventBuilder::new(sdk.clone()) .event(UpdateUserProfile) .payload(request) @@ -43,7 +43,7 @@ async fn user_update_with_name() { .event(GetUserProfile) .assert_error() .sync_send() - .parse::(); + .parse::(); assert_eq!(user_profile.name, new_name,); } @@ -53,7 +53,7 @@ async fn user_update_with_email() { let sdk = FlowySDKTest::default(); let user = sdk.init_user().await; let new_email = format!("{}@gmail.com", nanoid!(6)); - let request = UpdateUserProfilePayload::new(&user.id).email(&new_email); + let request = UpdateUserProfilePayloadPB::new(&user.id).email(&new_email); let _ = UserModuleEventBuilder::new(sdk.clone()) .event(UpdateUserProfile) .payload(request) @@ -62,7 +62,7 @@ async fn user_update_with_email() { .event(GetUserProfile) .assert_error() .sync_send() - .parse::(); + .parse::(); assert_eq!(user_profile.email, new_email,); } @@ -72,7 +72,7 @@ async fn user_update_with_password() { let sdk = FlowySDKTest::default(); let user = sdk.init_user().await; let new_password = "H123world!".to_owned(); - let request = UpdateUserProfilePayload::new(&user.id).password(&new_password); + let request = UpdateUserProfilePayloadPB::new(&user.id).password(&new_password); let _ = UserModuleEventBuilder::new(sdk.clone()) .event(UpdateUserProfile) @@ -86,7 +86,7 @@ async fn user_update_with_invalid_email() { let test = FlowySDKTest::default(); let user = test.init_user().await; for email in invalid_email_test_case() { - let request = UpdateUserProfilePayload::new(&user.id).email(&email); + let request = UpdateUserProfilePayloadPB::new(&user.id).email(&email); assert_eq!( UserModuleEventBuilder::new(test.clone()) .event(UpdateUserProfile) @@ -104,7 +104,7 @@ async fn user_update_with_invalid_password() { let test = FlowySDKTest::default(); let user = test.init_user().await; for password in invalid_password_test_case() { - let request = UpdateUserProfilePayload::new(&user.id).password(&password); + let request = UpdateUserProfilePayloadPB::new(&user.id).password(&password); UserModuleEventBuilder::new(test.clone()) .event(UpdateUserProfile) @@ -118,7 +118,7 @@ async fn user_update_with_invalid_password() { async fn user_update_with_invalid_name() { let test = FlowySDKTest::default(); let user = test.init_user().await; - let request = UpdateUserProfilePayload::new(&user.id).name(""); + let request = UpdateUserProfilePayloadPB::new(&user.id).name(""); UserModuleEventBuilder::new(test.clone()) .event(UpdateUserProfile) .payload(request) diff --git a/frontend/rust-lib/lib-dispatch/Cargo.toml b/frontend/rust-lib/lib-dispatch/Cargo.toml index cc3615ab4b..d458c6c41b 100644 --- a/frontend/rust-lib/lib-dispatch/Cargo.toml +++ b/frontend/rust-lib/lib-dispatch/Cargo.toml @@ -22,8 +22,8 @@ thread-id = "3.3.0" lazy_static = "1.4.0" dyn-clone = "1.0" derivative = "2.2.0" -serde_json = {version = "1.0"} -serde = { version = "1.0", features = ["derive"] } +serde_json = {version = "1.0", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } dashmap = "5" #optional crate @@ -37,5 +37,5 @@ futures-util = "0.3.15" [features] default = ["use_protobuf"] -use_serde = ["bincode"] +use_serde = ["bincode", "serde_json", "serde"] use_protobuf= ["protobuf"] diff --git a/frontend/rust-lib/lib-dispatch/src/errors/errors.rs b/frontend/rust-lib/lib-dispatch/src/errors/errors.rs index dbee088802..e469b186bf 100644 --- a/frontend/rust-lib/lib-dispatch/src/errors/errors.rs +++ b/frontend/rust-lib/lib-dispatch/src/errors/errors.rs @@ -6,7 +6,6 @@ use crate::{ use bytes::Bytes; use dyn_clone::DynClone; -use serde::{Serialize, Serializer}; use std::fmt; use tokio::{sync::mpsc::error::SendError, task::JoinError}; @@ -86,11 +85,11 @@ impl From for EventResponse { err.inner_error().as_response() } } - -impl Serialize for DispatchError { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> +#[cfg(feature = "use_serde")] +impl serde::Serialize for DispatchError { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where - S: Serializer, + S: serde::Serializer, { serializer.serialize_str(&format!("{}", self)) } diff --git a/frontend/rust-lib/lib-dispatch/src/request/payload.rs b/frontend/rust-lib/lib-dispatch/src/request/payload.rs index a82e2c072a..59edcf79d2 100644 --- a/frontend/rust-lib/lib-dispatch/src/request/payload.rs +++ b/frontend/rust-lib/lib-dispatch/src/request/payload.rs @@ -4,7 +4,8 @@ use std::{fmt, fmt::Formatter}; pub enum PayloadError {} // TODO: support stream data -#[derive(Clone, serde::Serialize)] +#[derive(Clone)] +#[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub enum Payload { None, Bytes(Bytes), diff --git a/frontend/rust-lib/lib-dispatch/src/response/response.rs b/frontend/rust-lib/lib-dispatch/src/response/response.rs index 812de68e84..a745f8ee1a 100644 --- a/frontend/rust-lib/lib-dispatch/src/response/response.rs +++ b/frontend/rust-lib/lib-dispatch/src/response/response.rs @@ -8,7 +8,8 @@ use crate::{ use derivative::*; use std::{convert::TryFrom, fmt, fmt::Formatter}; -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub enum StatusCode { Ok = 0, Err = 1, @@ -16,7 +17,8 @@ pub enum StatusCode { } // serde user guide: https://serde.rs/field-attrs.html -#[derive(Debug, Clone, serde::Serialize, Derivative)] +#[derive(Debug, Clone, Derivative)] +#[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub struct EventResponse { #[derivative(Debug = "ignore")] pub payload: Payload, diff --git a/frontend/scripts/install_dev_env/install_linux.sh b/frontend/scripts/install_dev_env/install_linux.sh new file mode 100755 index 0000000000..a580c22b19 --- /dev/null +++ b/frontend/scripts/install_dev_env/install_linux.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +YELLOW="\e[93m" +GREEN="\e[32m" +RED="\e[31m" +ENDCOLOR="\e[0m" + +printMessage() { + printf "${YELLOW}AppFlowy : $1${ENDCOLOR}\n" +} + +printSuccess() { + printf "${GREEN}AppFlowy : $1${ENDCOLOR}\n" +} + +printError() { + printf "${RED}AppFlowy : $1${ENDCOLOR}\n" +} + + +# Note: This script does not install applications which are installed by the package manager. There are too many package managers out there. + +# Install Rust +printMessage "The Rust programming language is required to compile AppFlowy." +printMessage "We can install it now if you don't already have it on your system." + +read -p "$(printSuccess "Do you want to install Rust? [y/N]") " installrust + +if [ ${installrust^^} == "Y" ]; then + printMessage "Installing Rust." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + source $HOME/.cargo/env + rustup toolchain install stable + rustup default stable +else + printMessage "Skipping Rust installation." +fi + +# Enable the flutter stable channel +printMessage "Setting up Flutter" +flutter channel stable + +# Enable linux desktop +flutter config --enable-linux-desktop + +# Fix any problems reported by flutter doctor +flutter doctor + +# Add the githooks directory to your git configuration +printMessage "Setting up githooks." +git config core.hooksPath .githooks + +# Change to the frontend directory +cd frontend + +# Install cargo make +printMessage "Installing cargo-make." +cargo install --force cargo-make + +# Install duckscript +printMessage "Installing duckscript." +cargo install --force duckscript_cli + +# Install CommitLint +printMessage "Installing CommitLint." +npm install @commitlint/cli @commitlint/config-conventional --save-dev + +# Check prerequisites +printMessage "Checking prerequisites." +cargo make flowy_dev diff --git a/frontend/scripts/install_dev_env/install_macos.sh b/frontend/scripts/install_dev_env/install_macos.sh new file mode 100755 index 0000000000..edc0c40b26 --- /dev/null +++ b/frontend/scripts/install_dev_env/install_macos.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +YELLOW="\e[93m" +GREEN="\e[32m" +RED="\e[31m" +ENDCOLOR="\e[0m" + +printMessage() { + printf "${YELLOW}AppFlowy : $1${ENDCOLOR}\n" +} + +printSuccess() { + printf "${GREEN}AppFlowy : $1${ENDCOLOR}\n" +} + +printError() { + printf "${RED}AppFlowy : $1${ENDCOLOR}\n" +} + + +# Install Rust +printMessage "The Rust programming language is required to compile AppFlowy." +printMessage "We can install it now if you don't already have it on your system." + +read -p "$(printSuccess "Do you want to install Rust? [y/N]") " installrust + +if [ ${installrust^^} == "Y" ]; then + printMessage "Installing Rust." + brew install rustup-init + rustup-init -y --default-toolchain=stable +else + printMessage "Skipping Rust installation." +fi + +# Install sqllite +printMessage "Installing sqlLite3." +brew install sqlite3 + +# Enable the flutter stable channel +printMessage "Setting up Flutter" +flutter channel stable + +# Enable linux desktop +flutter config --enable-macos-desktop + +# Fix any problems reported by flutter doctor +flutter doctor + +# Add the githooks directory to your git configuration +printMessage "Setting up githooks." +git config core.hooksPath .githooks + +# Change to the frontend directory +cd frontend + +# Install cargo make +printMessage "Installing cargo-make." +cargo install --force cargo-make + +# Install duckscript +printMessage "Installing duckscript." +cargo install --force duckscript_cli + +# Install CommitLint +printMessagae "Installing CommitLint." +npm install @commitlint/cli @commitlint/config-conventional --save-dev + +# Check prerequisites +printMessage "Checking prerequisites." +cargo make flowy_dev diff --git a/frontend/scripts/makefile/githooks.toml b/frontend/scripts/makefile/githooks.toml new file mode 100644 index 0000000000..f6b066ec49 --- /dev/null +++ b/frontend/scripts/makefile/githooks.toml @@ -0,0 +1,39 @@ +[tasks.install-commitlint.mac] +script = [ + """ + brew install npm + npm install @commitlint/cli @commitlint/config-conventional --save-dev + + git config core.hooksPath .githooks + """, +] +script_runner = "@shell" + +[tasks.install-commitlint.windows] +script = [ + """ + echo "WIP" + + git config core.hooksPath .githooks + """, +] +script_runner = "@duckscript" + +[tasks.install-commitlint.linux] +script = [ + """ + if command -v apt &> /dev/null + then + echo "Installing node.js (sudo apt install nodejs)" + sudo apt install nodejs + else + echo "Installing node.js (sudo pacman -S nodejs)" + sudo pacman -S nodejs + fi + + npm install @commitlint/cli @commitlint/config-conventional --save-dev + + git config core.hooksPath .githooks + """, +] +script_runner = "@shell" diff --git a/package.json b/package.json index c891bb7d81..0f6a7fcafe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "devDependencies": { - "@commitlint/cli": "16.1.0", - "@commitlint/config-conventional": "16.0.0", - "husky": "7.0.4" + "@commitlint/cli": "^16.1.0", + "@commitlint/config-conventional": "^16.0.0" } } diff --git a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs index f0325db3b0..4fbbb1b3a7 100644 --- a/shared-lib/flowy-derive/src/proto_buf/deserialize.rs +++ b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs @@ -25,8 +25,15 @@ pub fn make_de_token_steam(ctxt: &Ctxt, ast: &ASTContainer) -> Option for #struct_ident { type Error = ::protobuf::ProtobufError; fn try_from(bytes: bytes::Bytes) -> Result { - let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(&bytes)?; - #struct_ident::try_from(pb) + Self::try_from(&bytes) + } + } + + impl std::convert::TryFrom<&bytes::Bytes> for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_from(bytes: &bytes::Bytes) -> Result { + let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; + Ok(#struct_ident::from(pb)) } } @@ -34,16 +41,15 @@ pub fn make_de_token_steam(ctxt: &Ctxt, ast: &ASTContainer) -> Option Result { let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; - #struct_ident::try_from(pb) + Ok(#struct_ident::from(pb)) } } - impl std::convert::TryFrom for #struct_ident { - type Error = ::protobuf::ProtobufError; - fn try_from(mut pb: crate::protobuf::#pb_ty) -> Result { + impl std::convert::From for #struct_ident { + fn from(mut pb: crate::protobuf::#pb_ty) -> Self { let mut o = Self::default(); #(#build_take_fields)* - Ok(o) + o } } }; @@ -70,7 +76,7 @@ fn token_stream_for_one_of(ctxt: &Ctxt, field: &ASTField) -> Option let ty = bracketed_ty_info.unwrap().ty; Some(quote! { if pb.#has_func() { - let enum_de_from_pb = #ty::try_from(&pb.#get_func()).unwrap(); + let enum_de_from_pb = #ty::from(&pb.#get_func()); o.#member = Some(enum_de_from_pb); } }) @@ -104,7 +110,7 @@ fn token_stream_for_one_of(ctxt: &Ctxt, field: &ASTField) -> Option let ty = bracketed_ty_info.unwrap().ty; Some(quote! { if pb.#has_func() { - let val = #ty::try_from(pb.#take_func()).unwrap(); + let val = #ty::from(pb.#take_func()); o.#member=Some(val); } }) @@ -138,7 +144,7 @@ fn token_stream_for_field(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type, is_ Some(quote! { let some_value = pb.#member.#take(); if some_value.is_some() { - let struct_de_from_pb = #ty::try_from(some_value.unwrap()).unwrap(); + let struct_de_from_pb = #ty::from(some_value.unwrap()); o.#member = struct_de_from_pb; } }) @@ -147,7 +153,7 @@ fn token_stream_for_field(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type, is_ TypeCategory::Enum => { let ty = ty_info.ty; Some(quote! { - let enum_de_from_pb = #ty::try_from(&pb.#member).unwrap(); + let enum_de_from_pb = #ty::from(&pb.#member); o.#member = enum_de_from_pb; }) @@ -192,7 +198,7 @@ fn token_stream_for_vec(ctxt: &Ctxt, member: &syn::Member, bracketed_type: &TyIn Some(quote! { o.#member = pb.#take_ident() .into_iter() - .map(|m| #ty::try_from(m).unwrap()) + .map(|m| #ty::from(m)) .collect(); }) } @@ -221,7 +227,7 @@ fn token_stream_for_map(ctxt: &Ctxt, member: &syn::Member, ty_info: &TyInfo) -> TypeCategory::Protobuf => Some(quote! { let mut m: std::collections::HashMap = std::collections::HashMap::new(); pb.#take_ident().into_iter().for_each(|(k,v)| { - m.insert(k.clone(), #ty::try_from(v).unwrap()); + m.insert(k.clone(), #ty::from(v)); }); o.#member = m; }), diff --git a/shared-lib/flowy-derive/src/proto_buf/enum_serde.rs b/shared-lib/flowy-derive/src/proto_buf/enum_serde.rs index 498be05e51..fb0870fde8 100644 --- a/shared-lib/flowy-derive/src/proto_buf/enum_serde.rs +++ b/shared-lib/flowy-derive/src/proto_buf/enum_serde.rs @@ -20,21 +20,19 @@ pub fn make_enum_token_stream(_ctxt: &Ctxt, cont: &ASTContainer) -> Option for #enum_ident { - type Error = String; - fn try_from(pb:&crate::protobuf::#pb_enum) -> Result { - Ok(match pb { + impl std::convert::From<&crate::protobuf::#pb_enum> for #enum_ident { + fn from(pb:&crate::protobuf::#pb_enum) -> Self { + match pb { #(#build_from_pb_enum)* - }) + } } } - impl std::convert::TryInto for #enum_ident { - type Error = String; - fn try_into(self) -> Result { - Ok(match self { + impl std::convert::From<#enum_ident> for crate::protobuf::#pb_enum{ + fn from(o: #enum_ident) -> crate::protobuf::#pb_enum { + match o { #(#build_to_pb_enum)* - }) + } } } }) diff --git a/shared-lib/flowy-derive/src/proto_buf/serialize.rs b/shared-lib/flowy-derive/src/proto_buf/serialize.rs index 324c26651b..a43f337f8f 100644 --- a/shared-lib/flowy-derive/src/proto_buf/serialize.rs +++ b/shared-lib/flowy-derive/src/proto_buf/serialize.rs @@ -19,18 +19,17 @@ pub fn make_se_token_stream(ctxt: &Ctxt, ast: &ASTContainer) -> Option Result { use protobuf::Message; - let pb: crate::protobuf::#pb_ty = self.try_into()?; + let pb: crate::protobuf::#pb_ty = self.into(); let bytes = pb.write_to_bytes()?; Ok(bytes::Bytes::from(bytes)) } } - impl std::convert::TryInto for #struct_ident { - type Error = ::protobuf::ProtobufError; - fn try_into(self) -> Result { + impl std::convert::From<#struct_ident> for crate::protobuf::#pb_ty { + fn from(mut o: #struct_ident) -> crate::protobuf::#pb_ty { let mut pb = crate::protobuf::#pb_ty::new(); #(#build_set_pb_fields)* - Ok(pb) + pb } } }; @@ -41,7 +40,7 @@ pub fn make_se_token_stream(ctxt: &Ctxt, ast: &ASTContainer) -> Option Option { if let Some(func) = &field.attrs.serialize_with() { let member = &field.member; - Some(quote! { pb.#member=self.#func(); }) + Some(quote! { pb.#member=o.#func(); }) } else if field.attrs.is_one_of() { token_stream_for_one_of(ctxt, field) } else { @@ -66,19 +65,19 @@ fn token_stream_for_one_of(ctxt: &Ctxt, field: &ASTField) -> Option match ident_category(bracketed_ty_info.unwrap().ident) { TypeCategory::Protobuf => Some(quote! { - match self.#member { - Some(s) => { pb.#set_func(s.try_into().unwrap()) } + match o.#member { + Some(s) => { pb.#set_func(s.into()) } None => {} } }), TypeCategory::Enum => Some(quote! { - match self.#member { - Some(s) => { pb.#set_func(s.try_into().unwrap()) } + match o.#member { + Some(s) => { pb.#set_func(s.into()) } None => {} } }), _ => Some(quote! { - match self.#member { + match o.#member { Some(ref s) => { pb.#set_func(s.clone()) } None => {} } @@ -100,18 +99,16 @@ fn gen_token_stream(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type, is_option TypeCategory::Str => { if is_option { Some(quote! { - match self.#member { + match o.#member { Some(ref s) => { pb.#member = s.to_string().clone(); } None => { pb.#member = String::new(); } } }) } else { - Some(quote! { pb.#member = self.#member.clone(); }) + Some(quote! { pb.#member = o.#member.clone(); }) } } - TypeCategory::Protobuf => { - Some(quote! { pb.#member = ::protobuf::SingularPtrField::some(self.#member.try_into().unwrap()); }) - } + TypeCategory::Protobuf => Some(quote! { pb.#member = ::protobuf::SingularPtrField::some(o.#member.into()); }), TypeCategory::Opt => gen_token_stream(ctxt, member, ty_info.bracket_ty_info.unwrap().ty, true), TypeCategory::Enum => { // let pb_enum_ident = format_ident!("{}", ty_info.ident.to_string()); @@ -119,10 +116,10 @@ fn gen_token_stream(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type, is_option // flowy_protobuf::#pb_enum_ident::from_i32(self.#member.value()).unwrap(); // }) Some(quote! { - pb.#member = self.#member.try_into().unwrap(); + pb.#member = o.#member.into(); }) } - _ => Some(quote! { pb.#member = self.#member; }), + _ => Some(quote! { pb.#member = o.#member; }), } } @@ -139,15 +136,15 @@ fn token_stream_for_vec(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type) -> Op match ident_category(ty_info.ident) { TypeCategory::Protobuf => Some(quote! { pb.#member = ::protobuf::RepeatedField::from_vec( - self.#member + o.#member .into_iter() - .map(|m| m.try_into().unwrap()) + .map(|m| m.into()) .collect()); }), - TypeCategory::Bytes => Some(quote! { pb.#member = self.#member.clone(); }), + TypeCategory::Bytes => Some(quote! { pb.#member = o.#member.clone(); }), _ => Some(quote! { - pb.#member = ::protobuf::RepeatedField::from_vec(self.#member.clone()); + pb.#member = ::protobuf::RepeatedField::from_vec(o.#member.clone()); }), } } @@ -166,14 +163,14 @@ fn token_stream_for_map(ctxt: &Ctxt, member: &syn::Member, ty: &syn::Type) -> Op match ident_category(ty_info.ident) { TypeCategory::Protobuf => Some(quote! { let mut m: std::collections::HashMap = std::collections::HashMap::new(); - self.#member.into_iter().for_each(|(k,v)| { - m.insert(k.clone(), v.try_into().unwrap()); + o.#member.into_iter().for_each(|(k,v)| { + m.insert(k.clone(), v.into()); }); pb.#member = m; }), _ => Some(quote! { let mut m: std::collections::HashMap = std::collections::HashMap::new(); - self.#member.iter().for_each(|(k,v)| { + o.#member.iter().for_each(|(k,v)| { m.insert(k.clone(), v.clone()); }); pb.#member = m; diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs index a214fe9aa6..2298eb485f 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs @@ -31,7 +31,7 @@ pub struct GridRevision { pub fields: Vec>, pub blocks: Vec>, - #[serde(default, skip)] + #[serde(default)] pub setting: GridSettingRevision, } @@ -48,7 +48,7 @@ impl GridRevision { pub fn from_build_context(grid_id: &str, context: BuildGridContext) -> Self { Self { grid_id: grid_id.to_owned(), - fields: context.field_revs.into_iter().map(Arc::new).collect(), + fields: context.field_revs, blocks: context.blocks.into_iter().map(Arc::new).collect(), setting: Default::default(), } @@ -168,13 +168,9 @@ impl FieldRevision { self.type_options.insert(id, entry.json_str()); } - pub fn get_type_option_entry>( - &self, - field_type: T2, - ) -> Option { - let field_type_rev = field_type.into(); + pub fn get_type_option_entry(&self, field_type_rev: FieldTypeRevision) -> Option { let id = field_type_rev.to_string(); - self.type_options.get(&id).map(|s| T1::from_json_str(s)) + self.type_options.get(&id).map(|s| T::from_json_str(s)) } pub fn insert_type_option_str(&mut self, field_type: &FieldTypeRevision, json_str: String) { @@ -244,7 +240,7 @@ impl CellRevision { #[derive(Clone, Default, Deserialize, Serialize)] pub struct BuildGridContext { - pub field_revs: Vec, + pub field_revs: Vec>, pub blocks: Vec, pub blocks_meta_data: Vec, } diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs index 3ffcedb1c4..d29d9d767b 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs +++ b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs @@ -1,8 +1,9 @@ -use crate::revision::FieldTypeRevision; +use crate::revision::{FieldRevision, FieldTypeRevision}; use indexmap::IndexMap; use nanoid::nanoid; use serde::{Deserialize, Serialize}; use serde_repr::*; +use std::collections::HashMap; use std::sync::Arc; pub fn gen_grid_filter_id() -> String { @@ -17,20 +18,33 @@ pub fn gen_grid_sort_id() -> String { nanoid!(6) } +/// Each layout contains multiple key/value. +/// Key: field_id +/// Value: this value also contains key/value. +/// Key: FieldType, +/// Value: the corresponding filter. +/// +/// This overall struct is described below: +/// GridSettingRevision +/// layout: +/// field_id: +/// FieldType: GridFilterRevision +/// FieldType: GridFilterRevision +/// field_id: +/// FieldType: GridFilterRevision +/// FieldType: GridFilterRevision +/// layout: +/// field_id: +/// FieldType: GridFilterRevision +/// FieldType: GridFilterRevision +/// +/// Group and sorts will be the same structure as filters. #[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)] pub struct GridSettingRevision { pub layout: GridLayoutRevision, - // layout: - // field_id: - // FieldType: GridFilterRevision - // FieldType: GridFilterRevision - // layout: - // field_id: - // FieldType: GridFilterRevision - // field_id: - // FieldType: GridFilterRevision + #[serde(with = "indexmap::serde_seq")] - pub filters: IndexMap>, + filters: IndexMap>, #[serde(skip, with = "indexmap::serde_seq")] pub groups: IndexMap>, @@ -39,7 +53,44 @@ pub struct GridSettingRevision { pub sorts: IndexMap>, } +pub type FiltersByFieldId = HashMap>>; +pub type GroupsByFieldId = HashMap>>; +pub type SortsByFieldId = HashMap>>; impl GridSettingRevision { + pub fn get_all_group(&self) -> Option { + None + } + + pub fn get_all_sort(&self) -> Option { + None + } + + /// Return the Filters of the current layout + pub fn get_all_filter(&self, field_revs: &[Arc]) -> Option { + let layout = &self.layout; + // Acquire the read lock of the filters. + let filter_rev_map_by_field_id = self.filters.get(layout)?; + // Get the filters according to the FieldType, so we need iterate the field_revs. + let filters_by_field_id = field_revs + .iter() + .flat_map(|field_rev| { + let field_type = &field_rev.field_type_rev; + let field_id = &field_rev.id; + + let filter_rev_map: &GridFilterRevisionMap = filter_rev_map_by_field_id.get(field_id)?; + let filters: Vec> = filter_rev_map.get(field_type)?.clone(); + Some((field_rev.id.clone(), filters)) + }) + .collect::(); + Some(filters_by_field_id) + } + + #[allow(dead_code)] + fn get_filter_rev_map(&self, layout: &GridLayoutRevision, field_id: &str) -> Option<&GridFilterRevisionMap> { + let filter_rev_map_by_field_id = self.filters.get(layout)?; + filter_rev_map_by_field_id.get(field_id) + } + pub fn get_mut_filters( &mut self, layout: &GridLayoutRevision, @@ -56,12 +107,12 @@ impl GridSettingRevision { &self, layout: &GridLayoutRevision, field_id: &str, - field_type: &FieldTypeRevision, + field_type_rev: &FieldTypeRevision, ) -> Option>> { self.filters .get(layout) .and_then(|filter_rev_map_by_field_id| filter_rev_map_by_field_id.get(field_id)) - .and_then(|filter_rev_map| filter_rev_map.get(field_type)) + .and_then(|filter_rev_map| filter_rev_map.get(field_type_rev)) .cloned() } diff --git a/shared-lib/flowy-grid-data-model/tests/serde_test.rs b/shared-lib/flowy-grid-data-model/tests/serde_test.rs index b544e10588..1dff913547 100644 --- a/shared-lib/flowy-grid-data-model/tests/serde_test.rs +++ b/shared-lib/flowy-grid-data-model/tests/serde_test.rs @@ -6,5 +6,8 @@ fn grid_default_serde_test() { let grid = GridRevision::new(&grid_id); let json = serde_json::to_string(&grid).unwrap(); - assert_eq!(json, r#"{"grid_id":"1","fields":[],"blocks":[]}"#) + assert_eq!( + json, + r#"{"grid_id":"1","fields":[],"blocks":[],"setting":{"layout":0,"filters":[]}}"# + ) } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs index 8ee18309ad..16b98dc7fc 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_block_revsion_pad.rs @@ -245,13 +245,13 @@ pub struct GridBlockMetaChange { pub md5: String, } -pub fn make_block_meta_delta(block_rev: &GridBlockRevision) -> GridBlockRevisionDelta { +pub fn make_grid_block_delta(block_rev: &GridBlockRevision) -> GridBlockRevisionDelta { let json = serde_json::to_string(&block_rev).unwrap(); PlainTextDeltaBuilder::new().insert(&json).build() } -pub fn make_block_meta_revisions(user_id: &str, grid_block_meta_data: &GridBlockRevision) -> RepeatedRevision { - let delta = make_block_meta_delta(grid_block_meta_data); +pub fn make_grid_block_revisions(user_id: &str, grid_block_meta_data: &GridBlockRevision) -> RepeatedRevision { + let delta = make_grid_block_delta(grid_block_meta_data); let bytes = delta.to_delta_bytes(); let revision = Revision::initial_revision(user_id, &grid_block_meta_data.block_id, bytes); revision.into() @@ -264,7 +264,7 @@ impl std::default::Default for GridBlockRevisionPad { rows: vec![], }; - let delta = make_block_meta_delta(&block_revision); + let delta = make_grid_block_delta(&block_revision); GridBlockRevisionPad { block_revision, delta } } } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs index d1fbed3f53..ea5d5a9332 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs @@ -26,18 +26,31 @@ impl std::default::Default for GridBuilder { } impl GridBuilder { - pub fn add_field(mut self, field: FieldRevision) -> Self { - self.build_context.field_revs.push(field); - self + pub fn new() -> Self { + Self::default() + } + pub fn add_field(&mut self, field: FieldRevision) { + self.build_context.field_revs.push(Arc::new(field)); } - pub fn add_empty_row(mut self) -> Self { - let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id); + pub fn add_row(&mut self, row_rev: RowRevision) { let block_meta_rev = self.build_context.blocks.first_mut().unwrap(); let block_rev = self.build_context.blocks_meta_data.first_mut().unwrap(); - block_rev.rows.push(Arc::new(row)); + block_rev.rows.push(Arc::new(row_rev)); block_meta_rev.row_count += 1; - self + } + + pub fn add_empty_row(&mut self) { + let row = RowRevision::new(self.block_id()); + self.add_row(row); + } + + pub fn field_revs(&self) -> &Vec> { + &self.build_context.field_revs + } + + pub fn block_id(&self) -> &str { + &self.build_context.blocks.first().unwrap().block_id } pub fn build(self) -> BuildGridContext { diff --git a/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs new file mode 100644 index 0000000000..3ef9251e4a --- /dev/null +++ b/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs @@ -0,0 +1,432 @@ +use crate::entities::revision::{md5, RepeatedRevision, Revision}; +use crate::errors::{internal_error, CollaborateError, CollaborateResult}; +use crate::util::{cal_diff, make_delta_from_revisions}; +use bytes::Bytes; +use flowy_grid_data_model::entities::{ + gen_block_id, gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockInfoChangeset, + GridBlockMetaSnapshot, GridMeta, +}; +use lib_infra::util::move_vec_element; +use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder}; +use std::collections::HashMap; +use std::sync::Arc; + +pub type GridMetaDelta = PlainTextDelta; +pub type GridDeltaBuilder = PlainTextDeltaBuilder; + +pub struct GridMetaPad { + pub(crate) grid_meta: Arc, + pub(crate) delta: GridMetaDelta, +} + +pub trait JsonDeserializer { + fn deserialize(&self, type_option_data: Vec) -> CollaborateResult; +} + +impl GridMetaPad { + pub async fn duplicate_grid_meta(&self) -> (Vec, Vec) { + let fields = self.grid_meta.fields.to_vec(); + + let blocks = self + .grid_meta + .blocks + .iter() + .map(|block| { + let mut duplicated_block = block.clone(); + duplicated_block.block_id = gen_block_id(); + duplicated_block + }) + .collect::>(); + + (fields, blocks) + } + + pub fn from_delta(delta: GridMetaDelta) -> CollaborateResult { + let s = delta.to_str()?; + let grid: GridMeta = serde_json::from_str(&s) + .map_err(|e| CollaborateError::internal().context(format!("Deserialize delta to grid failed: {}", e)))?; + + Ok(Self { + grid_meta: Arc::new(grid), + delta, + }) + } + + pub fn from_revisions(_grid_id: &str, revisions: Vec) -> CollaborateResult { + let grid_delta: GridMetaDelta = make_delta_from_revisions::(revisions)?; + Self::from_delta(grid_delta) + } + + #[tracing::instrument(level = "debug", skip_all, err)] + pub fn create_field_meta( + &mut self, + new_field_meta: FieldMeta, + start_field_id: Option, + ) -> CollaborateResult> { + self.modify_grid(|grid_meta| { + // Check if the field exists or not + if grid_meta + .fields + .iter() + .any(|field_meta| field_meta.id == new_field_meta.id) + { + tracing::error!("Duplicate grid field"); + return Ok(None); + } + + let insert_index = match start_field_id { + None => None, + Some(start_field_id) => grid_meta.fields.iter().position(|field| field.id == start_field_id), + }; + + match insert_index { + None => grid_meta.fields.push(new_field_meta), + Some(index) => grid_meta.fields.insert(index, new_field_meta), + } + Ok(Some(())) + }) + } + + pub fn delete_field_meta(&mut self, field_id: &str) -> CollaborateResult> { + self.modify_grid( + |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) { + None => Ok(None), + Some(index) => { + grid_meta.fields.remove(index); + Ok(Some(())) + } + }, + ) + } + + pub fn duplicate_field_meta( + &mut self, + field_id: &str, + duplicated_field_id: &str, + ) -> CollaborateResult> { + self.modify_grid( + |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) { + None => Ok(None), + Some(index) => { + let mut duplicate_field_meta = grid_meta.fields[index].clone(); + duplicate_field_meta.id = duplicated_field_id.to_string(); + duplicate_field_meta.name = format!("{} (copy)", duplicate_field_meta.name); + grid_meta.fields.insert(index + 1, duplicate_field_meta); + Ok(Some(())) + } + }, + ) + } + + pub fn switch_to_field( + &mut self, + field_id: &str, + field_type: FieldType, + type_option_json_builder: B, + ) -> CollaborateResult> + where + B: FnOnce(&FieldType) -> String, + { + self.modify_grid(|grid_meta| { + // + match grid_meta.fields.iter_mut().find(|field_meta| field_meta.id == field_id) { + None => { + tracing::warn!("Can not find the field with id: {}", field_id); + Ok(None) + } + Some(field_meta) => { + if field_meta.get_type_option_str(&field_type).is_none() { + let type_option_json = type_option_json_builder(&field_type); + field_meta.insert_type_option_str(&field_type, type_option_json); + } + + field_meta.field_type = field_type; + Ok(Some(())) + } + } + }) + } + + pub fn update_field_meta( + &mut self, + changeset: FieldChangesetParams, + deserializer: T, + ) -> CollaborateResult> { + let field_id = changeset.field_id.clone(); + self.modify_field(&field_id, |field| { + let mut is_changed = None; + if let Some(name) = changeset.name { + field.name = name; + is_changed = Some(()) + } + + if let Some(desc) = changeset.desc { + field.desc = desc; + is_changed = Some(()) + } + + if let Some(field_type) = changeset.field_type { + field.field_type = field_type; + is_changed = Some(()) + } + + if let Some(frozen) = changeset.frozen { + field.frozen = frozen; + is_changed = Some(()) + } + + if let Some(visibility) = changeset.visibility { + field.visibility = visibility; + is_changed = Some(()) + } + + if let Some(width) = changeset.width { + field.width = width; + is_changed = Some(()) + } + + if let Some(type_option_data) = changeset.type_option_data { + match deserializer.deserialize(type_option_data) { + Ok(json_str) => { + let field_type = field.field_type.clone(); + field.insert_type_option_str(&field_type, json_str); + is_changed = Some(()) + } + Err(err) => { + tracing::error!("Deserialize data to type option json failed: {}", err); + } + } + } + + Ok(is_changed) + }) + } + + pub fn get_field_meta(&self, field_id: &str) -> Option<(usize, &FieldMeta)> { + self.grid_meta + .fields + .iter() + .enumerate() + .find(|(_, field)| field.id == field_id) + } + + pub fn replace_field_meta(&mut self, field_meta: FieldMeta) -> CollaborateResult> { + self.modify_grid( + |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_meta.id) { + None => Ok(None), + Some(index) => { + grid_meta.fields.remove(index); + grid_meta.fields.insert(index, field_meta); + Ok(Some(())) + } + }, + ) + } + + pub fn move_field( + &mut self, + field_id: &str, + from_index: usize, + to_index: usize, + ) -> CollaborateResult> { + self.modify_grid(|grid_meta| { + match move_vec_element( + &mut grid_meta.fields, + |field| field.id == field_id, + from_index, + to_index, + ) + .map_err(internal_error)? + { + true => Ok(Some(())), + false => Ok(None), + } + }) + } + + pub fn contain_field(&self, field_id: &str) -> bool { + self.grid_meta.fields.iter().any(|field| field.id == field_id) + } + + pub fn get_field_orders(&self) -> Vec { + self.grid_meta.fields.iter().map(FieldOrder::from).collect() + } + + pub fn get_field_metas(&self, field_orders: Option>) -> CollaborateResult> { + match field_orders { + None => Ok(self.grid_meta.fields.clone()), + Some(field_orders) => { + let field_by_field_id = self + .grid_meta + .fields + .iter() + .map(|field| (&field.id, field)) + .collect::>(); + + let fields = field_orders + .iter() + .flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) { + None => { + tracing::error!("Can't find the field with id: {}", field_order.field_id); + None + } + Some(field) => Some((*field).clone()), + }) + .collect::>(); + Ok(fields) + } + } + } + + pub fn create_block_meta(&mut self, block: GridBlockMetaSnapshot) -> CollaborateResult> { + self.modify_grid(|grid_meta| { + if grid_meta.blocks.iter().any(|b| b.block_id == block.block_id) { + tracing::warn!("Duplicate grid block"); + Ok(None) + } else { + match grid_meta.blocks.last() { + None => grid_meta.blocks.push(block), + Some(last_block) => { + if last_block.start_row_index > block.start_row_index + && last_block.len() > block.start_row_index + { + let msg = "GridBlock's start_row_index should be greater than the last_block's start_row_index and its len".to_string(); + return Err(CollaborateError::internal().context(msg)) + } + grid_meta.blocks.push(block); + } + } + Ok(Some(())) + } + }) + } + + pub fn get_block_metas(&self) -> Vec { + self.grid_meta.blocks.clone() + } + + pub fn update_block_meta(&mut self, changeset: GridBlockInfoChangeset) -> CollaborateResult> { + let block_id = changeset.block_id.clone(); + self.modify_block(&block_id, |block| { + let mut is_changed = None; + + if let Some(row_count) = changeset.row_count { + block.row_count = row_count; + is_changed = Some(()); + } + + if let Some(start_row_index) = changeset.start_row_index { + block.start_row_index = start_row_index; + is_changed = Some(()); + } + + Ok(is_changed) + }) + } + + pub fn md5(&self) -> String { + md5(&self.delta.to_delta_bytes()) + } + + pub fn delta_str(&self) -> String { + self.delta.to_delta_str() + } + + pub fn delta_bytes(&self) -> Bytes { + self.delta.to_delta_bytes() + } + + pub fn fields(&self) -> &[FieldMeta] { + &self.grid_meta.fields + } + + fn modify_grid(&mut self, f: F) -> CollaborateResult> + where + F: FnOnce(&mut GridMeta) -> CollaborateResult>, + { + let cloned_grid = self.grid_meta.clone(); + match f(Arc::make_mut(&mut self.grid_meta))? { + None => Ok(None), + Some(_) => { + let old = json_from_grid(&cloned_grid)?; + let new = json_from_grid(&self.grid_meta)?; + match cal_diff::(old, new) { + None => Ok(None), + Some(delta) => { + self.delta = self.delta.compose(&delta)?; + Ok(Some(GridChangeset { delta, md5: self.md5() })) + } + } + } + } + } + + pub fn modify_block(&mut self, block_id: &str, f: F) -> CollaborateResult> + where + F: FnOnce(&mut GridBlockMetaSnapshot) -> CollaborateResult>, + { + self.modify_grid( + |grid_meta| match grid_meta.blocks.iter().position(|block| block.block_id == block_id) { + None => { + tracing::warn!("[GridMetaPad]: Can't find any block with id: {}", block_id); + Ok(None) + } + Some(index) => f(&mut grid_meta.blocks[index]), + }, + ) + } + + pub fn modify_field(&mut self, field_id: &str, f: F) -> CollaborateResult> + where + F: FnOnce(&mut FieldMeta) -> CollaborateResult>, + { + self.modify_grid( + |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) { + None => { + tracing::warn!("[GridMetaPad]: Can't find any field with id: {}", field_id); + Ok(None) + } + Some(index) => f(&mut grid_meta.fields[index]), + }, + ) + } +} + +fn json_from_grid(grid: &Arc) -> CollaborateResult { + let json = serde_json::to_string(grid) + .map_err(|err| internal_error(format!("Serialize grid to json str failed. {:?}", err)))?; + Ok(json) +} + +pub struct GridChangeset { + pub delta: GridMetaDelta, + /// md5: the md5 of the grid after applying the change. + pub md5: String, +} + +pub fn make_grid_delta(grid_meta: &GridMeta) -> GridMetaDelta { + let json = serde_json::to_string(&grid_meta).unwrap(); + PlainTextDeltaBuilder::new().insert(&json).build() +} + +pub fn make_grid_revisions(user_id: &str, grid_meta: &GridMeta) -> RepeatedRevision { + let delta = make_grid_delta(grid_meta); + let bytes = delta.to_delta_bytes(); + let revision = Revision::initial_revision(user_id, &grid_meta.grid_id, bytes); + revision.into() +} + +impl std::default::Default for GridMetaPad { + fn default() -> Self { + let grid = GridMeta { + grid_id: gen_grid_id(), + fields: vec![], + blocks: vec![], + }; + let delta = make_grid_delta(&grid); + GridMetaPad { + grid_meta: Arc::new(grid), + delta, + } + } +} diff --git a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs index 8b724d8133..eaa6f1823a 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs @@ -17,8 +17,8 @@ pub type GridRevisionDelta = PlainTextDelta; pub type GridRevisionDeltaBuilder = PlainTextDeltaBuilder; pub struct GridRevisionPad { - pub(crate) grid_rev: Arc, - pub(crate) delta: GridRevisionDelta, + grid_rev: Arc, + delta: GridRevisionDelta, } pub trait JsonDeserializer { @@ -62,7 +62,7 @@ impl GridRevisionPad { }) } - pub fn from_revisions(_grid_id: &str, revisions: Vec) -> CollaborateResult { + pub fn from_revisions(revisions: Vec) -> CollaborateResult { let grid_delta: GridRevisionDelta = make_delta_from_revisions::(revisions)?; Self::from_delta(grid_delta) } @@ -358,10 +358,9 @@ impl GridRevisionPad { if is_contain { // Only return the filters for the current fields' type. - if let Some(mut t_filter_revs) = - self.grid_rev - .setting - .get_filters(layout_ty, &field_rev.id, &field_rev.field_type_rev) + let field_id = &field_rev.id; + let field_type_rev = &field_rev.field_type_rev; + if let Some(mut t_filter_revs) = self.grid_rev.setting.get_filters(layout_ty, field_id, field_type_rev) { filter_revs.append(&mut t_filter_revs); } @@ -396,7 +395,7 @@ impl GridRevisionPad { if let Some(params) = changeset.delete_filter { match grid_rev .setting - .get_mut_filters(&layout_rev, ¶ms.filter_id, ¶ms.field_type_rev) + .get_mut_filters(&layout_rev, ¶ms.field_id, ¶ms.field_type_rev) { Some(filters) => { filters.retain(|filter| filter.id != params.filter_id); @@ -481,8 +480,8 @@ impl GridRevisionPad { match f(Arc::make_mut(&mut self.grid_rev))? { None => Ok(None), Some(_) => { - let old = json_from_grid(&cloned_grid)?; - let new = json_from_grid(&self.grid_rev)?; + let old = make_grid_rev_json_str(&cloned_grid)?; + let new = self.json_str()?; match cal_diff::(old, new) { None => Ok(None), Some(delta) => { @@ -529,9 +528,13 @@ impl GridRevisionPad { }, ) } + + pub fn json_str(&self) -> CollaborateResult { + make_grid_rev_json_str(&self.grid_rev) + } } -fn json_from_grid(grid: &Arc) -> CollaborateResult { +pub fn make_grid_rev_json_str(grid: &GridRevision) -> CollaborateResult { let json = serde_json::to_string(grid) .map_err(|err| internal_error(format!("Serialize grid to json str failed. {:?}", err)))?; Ok(json) diff --git a/shared-lib/flowy-sync/src/entities/grid.rs b/shared-lib/flowy-sync/src/entities/grid.rs index 866562ccd6..fc3c14b4fb 100644 --- a/shared-lib/flowy-sync/src/entities/grid.rs +++ b/shared-lib/flowy-sync/src/entities/grid.rs @@ -24,6 +24,7 @@ pub struct CreateGridFilterParams { } pub struct DeleteFilterParams { + pub field_id: String, pub filter_id: String, pub field_type_rev: FieldTypeRevision, } diff --git a/shared-lib/flowy-sync/src/entities/revision.rs b/shared-lib/flowy-sync/src/entities/revision.rs index 57276bba1b..d909efa3d5 100644 --- a/shared-lib/flowy-sync/src/entities/revision.rs +++ b/shared-lib/flowy-sync/src/entities/revision.rs @@ -125,6 +125,12 @@ impl std::convert::From for RepeatedRevision { } } +impl std::convert::From> for RepeatedRevision { + fn from(revisions: Vec) -> Self { + Self { items: revisions } + } +} + impl RepeatedRevision { pub fn new(mut items: Vec) -> Self { items.sort_by(|a, b| a.rev_id.cmp(&b.rev_id)); diff --git a/shared-lib/flowy-sync/src/entities/text_block.rs b/shared-lib/flowy-sync/src/entities/text_block.rs index c325fdad07..8753103369 100644 --- a/shared-lib/flowy-sync/src/entities/text_block.rs +++ b/shared-lib/flowy-sync/src/entities/text_block.rs @@ -15,7 +15,7 @@ pub struct CreateTextBlockParams { } #[derive(ProtoBuf, Default, Debug, Clone, Eq, PartialEq)] -pub struct TextBlockInfo { +pub struct DocumentPB { #[pb(index = 1)] pub block_id: String, @@ -29,14 +29,14 @@ pub struct TextBlockInfo { pub base_rev_id: i64, } -impl TextBlockInfo { +impl DocumentPB { pub fn delta(&self) -> Result { let delta = RichTextDelta::from_bytes(&self.text)?; Ok(delta) } } -impl std::convert::TryFrom for TextBlockInfo { +impl std::convert::TryFrom for DocumentPB { type Error = CollaborateError; fn try_from(revision: Revision) -> Result { @@ -48,7 +48,7 @@ impl std::convert::TryFrom for TextBlockInfo { let delta = RichTextDelta::from_bytes(&revision.delta_data)?; let doc_json = delta.to_delta_str(); - Ok(TextBlockInfo { + Ok(DocumentPB { block_id: revision.object_id, text: doc_json, rev_id: revision.rev_id, @@ -67,7 +67,7 @@ pub struct ResetTextBlockParams { } #[derive(ProtoBuf, Default, Debug, Clone)] -pub struct TextBlockDelta { +pub struct TextBlockDeltaPB { #[pb(index = 1)] pub block_id: String, @@ -76,7 +76,7 @@ pub struct TextBlockDelta { } #[derive(ProtoBuf, Default, Debug, Clone)] -pub struct NewDocUser { +pub struct NewDocUserPB { #[pb(index = 1)] pub user_id: String, @@ -88,30 +88,30 @@ pub struct NewDocUser { } #[derive(ProtoBuf, Default, Debug, Clone)] -pub struct TextBlockId { +pub struct TextBlockIdPB { #[pb(index = 1)] pub value: String, } -impl AsRef for TextBlockId { +impl AsRef for TextBlockIdPB { fn as_ref(&self) -> &str { &self.value } } -impl std::convert::From for TextBlockId { +impl std::convert::From for TextBlockIdPB { fn from(value: String) -> Self { - TextBlockId { value } + TextBlockIdPB { value } } } -impl std::convert::From for String { - fn from(block_id: TextBlockId) -> Self { +impl std::convert::From for String { + fn from(block_id: TextBlockIdPB) -> Self { block_id.value } } -impl std::convert::From<&String> for TextBlockId { +impl std::convert::From<&String> for TextBlockIdPB { fn from(s: &String) -> Self { - TextBlockId { value: s.to_owned() } + TextBlockIdPB { value: s.to_owned() } } } diff --git a/shared-lib/flowy-sync/src/server_document/document_manager.rs b/shared-lib/flowy-sync/src/server_document/document_manager.rs index 8d547a9484..59d3dd3c97 100644 --- a/shared-lib/flowy-sync/src/server_document/document_manager.rs +++ b/shared-lib/flowy-sync/src/server_document/document_manager.rs @@ -1,7 +1,8 @@ +use crate::entities::revision::{RepeatedRevision, Revision}; use crate::{ - entities::{text_block::TextBlockInfo, ws_data::ServerRevisionWSDataBuilder}, + entities::{text_block::DocumentPB, ws_data::ServerRevisionWSDataBuilder}, errors::{internal_error, CollaborateError, CollaborateResult}, - protobuf::{ClientRevisionWSData, RepeatedRevision as RepeatedRevisionPB, Revision as RevisionPB}, + protobuf::ClientRevisionWSData, server_document::document_pad::ServerDocument, synchronizer::{RevisionSyncPersistence, RevisionSyncResponse, RevisionSynchronizer, RevisionUser}, util::rev_id_from_str, @@ -18,27 +19,26 @@ use tokio::{ }; pub trait TextBlockCloudPersistence: Send + Sync + Debug { - fn read_text_block(&self, doc_id: &str) -> BoxResultFuture; + fn read_text_block(&self, doc_id: &str) -> BoxResultFuture; fn create_text_block( &self, doc_id: &str, - repeated_revision: RepeatedRevisionPB, - ) -> BoxResultFuture, CollaborateError>; + repeated_revision: RepeatedRevision, + ) -> BoxResultFuture, CollaborateError>; fn read_text_block_revisions( &self, doc_id: &str, rev_ids: Option>, - ) -> BoxResultFuture, CollaborateError>; + ) -> BoxResultFuture, CollaborateError>; - fn save_text_block_revisions(&self, repeated_revision: RepeatedRevisionPB) - -> BoxResultFuture<(), CollaborateError>; + fn save_text_block_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError>; fn reset_text_block( &self, doc_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError>; } @@ -47,18 +47,18 @@ impl RevisionSyncPersistence for Arc { &self, object_id: &str, rev_ids: Option>, - ) -> BoxResultFuture, CollaborateError> { + ) -> BoxResultFuture, CollaborateError> { (**self).read_text_block_revisions(object_id, rev_ids) } - fn save_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> { + fn save_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError> { (**self).save_text_block_revisions(repeated_revision) } fn reset_object( &self, object_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError> { (**self).reset_text_block(object_id, repeated_revision) } @@ -82,7 +82,7 @@ impl ServerDocumentManager { user: Arc, mut client_data: ClientRevisionWSData, ) -> Result<(), CollaborateError> { - let repeated_revision = client_data.take_revisions(); + let repeated_revision: RepeatedRevision = client_data.take_revisions().into(); let cloned_user = user.clone(); let ack_id = rev_id_from_str(&client_data.data_id)?; let object_id = client_data.object_id; @@ -131,9 +131,10 @@ impl ServerDocumentManager { pub async fn handle_document_reset( &self, doc_id: &str, - mut repeated_revision: RepeatedRevisionPB, + mut repeated_revision: RepeatedRevision, ) -> Result<(), CollaborateError> { - repeated_revision.mut_items().sort_by(|a, b| a.rev_id.cmp(&b.rev_id)); + repeated_revision.sort_by(|a, b| a.rev_id.cmp(&b.rev_id)); + match self.get_document_handler(doc_id).await { None => { tracing::warn!("Document:{} doesn't exist, ignore document reset", doc_id); @@ -166,7 +167,7 @@ impl ServerDocumentManager { async fn create_document( &self, doc_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> Result, CollaborateError> { match self.persistence.create_text_block(doc_id, repeated_revision).await? { None => Err(CollaborateError::internal().context("Create document info from revisions failed")), @@ -182,7 +183,7 @@ impl ServerDocumentManager { } #[tracing::instrument(level = "debug", skip(self, doc), err)] - async fn create_document_handler(&self, doc: TextBlockInfo) -> Result, CollaborateError> { + async fn create_document_handler(&self, doc: DocumentPB) -> Result, CollaborateError> { let persistence = self.persistence.clone(); let handle = spawn_blocking(|| OpenDocumentHandler::new(doc, persistence)) .await @@ -206,7 +207,7 @@ struct OpenDocumentHandler { } impl OpenDocumentHandler { - fn new(doc: TextBlockInfo, persistence: Arc) -> Result { + fn new(doc: DocumentPB, persistence: Arc) -> Result { let doc_id = doc.block_id.clone(); let (sender, receiver) = mpsc::channel(1000); let users = DashMap::new(); @@ -229,7 +230,7 @@ impl OpenDocumentHandler { async fn apply_revisions( &self, user: Arc, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> Result<(), CollaborateError> { let (ret, rx) = oneshot::channel(); self.users.insert(user.user_id(), user.clone()); @@ -252,7 +253,7 @@ impl OpenDocumentHandler { } #[tracing::instrument(level = "debug", skip(self, repeated_revision), err)] - async fn apply_document_reset(&self, repeated_revision: RepeatedRevisionPB) -> Result<(), CollaborateError> { + async fn apply_document_reset(&self, repeated_revision: RepeatedRevision) -> Result<(), CollaborateError> { let (ret, rx) = oneshot::channel(); let msg = DocumentCommand::Reset { repeated_revision, ret }; let result = self.send(msg, rx).await?; @@ -279,7 +280,7 @@ impl std::ops::Drop for OpenDocumentHandler { enum DocumentCommand { ApplyRevisions { user: Arc, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ret: oneshot::Sender>, }, Ping { @@ -288,7 +289,7 @@ enum DocumentCommand { ret: oneshot::Sender>, }, Reset { - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ret: oneshot::Sender>, }, } diff --git a/shared-lib/flowy-sync/src/server_folder/folder_manager.rs b/shared-lib/flowy-sync/src/server_folder/folder_manager.rs index 0a1a5e117a..16b753f50b 100644 --- a/shared-lib/flowy-sync/src/server_folder/folder_manager.rs +++ b/shared-lib/flowy-sync/src/server_folder/folder_manager.rs @@ -1,10 +1,11 @@ +use crate::entities::revision::{RepeatedRevision, Revision}; use crate::{ entities::{ folder::{FolderDelta, FolderInfo}, ws_data::ServerRevisionWSDataBuilder, }, errors::{internal_error, CollaborateError, CollaborateResult}, - protobuf::{ClientRevisionWSData, RepeatedRevision as RepeatedRevisionPB, Revision as RevisionPB}, + protobuf::ClientRevisionWSData, server_folder::folder_pad::ServerFolder, synchronizer::{RevisionSyncPersistence, RevisionSyncResponse, RevisionSynchronizer, RevisionUser}, util::rev_id_from_str, @@ -26,21 +27,21 @@ pub trait FolderCloudPersistence: Send + Sync + Debug { &self, user_id: &str, folder_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture, CollaborateError>; - fn save_folder_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError>; + fn save_folder_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError>; fn read_folder_revisions( &self, folder_id: &str, rev_ids: Option>, - ) -> BoxResultFuture, CollaborateError>; + ) -> BoxResultFuture, CollaborateError>; fn reset_folder( &self, folder_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError>; } @@ -49,18 +50,18 @@ impl RevisionSyncPersistence for Arc { &self, object_id: &str, rev_ids: Option>, - ) -> BoxResultFuture, CollaborateError> { + ) -> BoxResultFuture, CollaborateError> { (**self).read_folder_revisions(object_id, rev_ids) } - fn save_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError> { + fn save_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError> { (**self).save_folder_revisions(repeated_revision) } fn reset_object( &self, object_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError> { (**self).reset_folder(object_id, repeated_revision) } @@ -84,7 +85,7 @@ impl ServerFolderManager { user: Arc, mut client_data: ClientRevisionWSData, ) -> Result<(), CollaborateError> { - let repeated_revision = client_data.take_revisions(); + let repeated_revision: RepeatedRevision = client_data.take_revisions().into(); let cloned_user = user.clone(); let ack_id = rev_id_from_str(&client_data.data_id)?; let folder_id = client_data.object_id; @@ -167,7 +168,7 @@ impl ServerFolderManager { &self, user_id: &str, folder_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> Result, CollaborateError> { match self .persistence @@ -221,7 +222,7 @@ impl OpenFolderHandler { async fn apply_revisions( &self, user: Arc, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> CollaborateResult<()> { let (ret, rx) = oneshot::channel(); let msg = FolderCommand::ApplyRevisions { @@ -258,7 +259,7 @@ impl std::ops::Drop for OpenFolderHandler { enum FolderCommand { ApplyRevisions { user: Arc, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ret: oneshot::Sender>, }, Ping { diff --git a/shared-lib/flowy-sync/src/synchronizer.rs b/shared-lib/flowy-sync/src/synchronizer.rs index 1305c471b2..eca422296a 100644 --- a/shared-lib/flowy-sync/src/synchronizer.rs +++ b/shared-lib/flowy-sync/src/synchronizer.rs @@ -1,10 +1,11 @@ +use crate::entities::revision::{RepeatedRevision, Revision}; use crate::{ entities::{ revision::RevisionRange, ws_data::{ServerRevisionWSData, ServerRevisionWSDataBuilder}, }, errors::CollaborateError, - protobuf::{RepeatedRevision as RepeatedRevisionPB, Revision as RevisionPB}, + protobuf::Revision as RevisionPB, util::*, }; use lib_infra::future::BoxResultFuture; @@ -31,14 +32,14 @@ pub trait RevisionSyncPersistence: Send + Sync + 'static { &self, object_id: &str, rev_ids: Option>, - ) -> BoxResultFuture, CollaborateError>; + ) -> BoxResultFuture, CollaborateError>; - fn save_revisions(&self, repeated_revision: RepeatedRevisionPB) -> BoxResultFuture<(), CollaborateError>; + fn save_revisions(&self, repeated_revision: RepeatedRevision) -> BoxResultFuture<(), CollaborateError>; fn reset_object( &self, object_id: &str, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> BoxResultFuture<(), CollaborateError>; } @@ -87,20 +88,20 @@ where pub async fn sync_revisions( &self, user: Arc, - repeated_revision: RepeatedRevisionPB, + repeated_revision: RepeatedRevision, ) -> Result<(), CollaborateError> { let object_id = self.object_id.clone(); - if repeated_revision.get_items().is_empty() { + if repeated_revision.is_empty() { // Return all the revisions to client let revisions = self.persistence.read_revisions(&object_id, None).await?; - let repeated_revision = repeated_revision_from_revision_pbs(revisions)?; + let repeated_revision = RepeatedRevision::from(revisions); let data = ServerRevisionWSDataBuilder::build_push_message(&object_id, repeated_revision); user.receive(RevisionSyncResponse::Push(data)); return Ok(()); } let server_base_rev_id = self.rev_id.load(SeqCst); - let first_revision = repeated_revision.get_items().first().unwrap().clone(); + let first_revision = repeated_revision.first().unwrap().clone(); if self.is_applied_before(&first_revision, &self.persistence).await { // Server has received this revision before, so ignore the following revisions return Ok(()); @@ -111,7 +112,7 @@ where let server_rev_id = next(server_base_rev_id); if server_base_rev_id == first_revision.base_rev_id || server_rev_id == first_revision.rev_id { // The rev is in the right order, just compose it. - for revision in repeated_revision.get_items() { + for revision in repeated_revision.iter() { let _ = self.compose_revision(revision)?; } let _ = self.persistence.save_revisions(repeated_revision).await?; @@ -165,10 +166,10 @@ where } #[tracing::instrument(level = "debug", skip(self, repeated_revision), fields(object_id), err)] - pub async fn reset(&self, repeated_revision: RepeatedRevisionPB) -> Result<(), CollaborateError> { + pub async fn reset(&self, repeated_revision: RepeatedRevision) -> Result<(), CollaborateError> { let object_id = self.object_id.clone(); tracing::Span::current().record("object_id", &object_id.as_str()); - let revisions: Vec = repeated_revision.get_items().to_vec(); + let revisions: Vec = repeated_revision.clone().into_inner(); let (_, rev_id) = pair_rev_id_from_revision_pbs(&revisions); let delta = make_delta_from_revision_pb(revisions)?; let _ = self.persistence.reset_object(&object_id, repeated_revision).await?; @@ -181,7 +182,7 @@ where self.object.read().to_json() } - fn compose_revision(&self, revision: &RevisionPB) -> Result<(), CollaborateError> { + fn compose_revision(&self, revision: &Revision) -> Result<(), CollaborateError> { let delta = Delta::::from_bytes(&revision.delta_data)?; let _ = self.compose_delta(delta)?; let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id)); @@ -213,11 +214,7 @@ where self.rev_id.load(SeqCst) } - async fn is_applied_before( - &self, - new_revision: &RevisionPB, - persistence: &Arc, - ) -> bool { + async fn is_applied_before(&self, new_revision: &Revision, persistence: &Arc) -> bool { let rev_ids = Some(vec![new_revision.rev_id]); if let Ok(revisions) = persistence.read_revisions(&self.object_id, rev_ids).await { if let Some(revision) = revisions.first() { @@ -243,13 +240,10 @@ where tracing::trace!("{}: can not read the revisions in range {:?}", self.object_id, rev_ids); // assert_eq!(revisions.is_empty(), rev_ids.is_empty(),); } - match repeated_revision_from_revision_pbs(revisions) { - Ok(repeated_revision) => { - let data = ServerRevisionWSDataBuilder::build_push_message(&self.object_id, repeated_revision); - user.receive(RevisionSyncResponse::Push(data)); - } - Err(e) => tracing::error!("{}", e), - } + + let repeated_revision = RepeatedRevision::from(revisions); + let data = ServerRevisionWSDataBuilder::build_push_message(&self.object_id, repeated_revision); + user.receive(RevisionSyncResponse::Push(data)); } Err(e) => { tracing::error!("{}", e); diff --git a/shared-lib/flowy-sync/src/util.rs b/shared-lib/flowy-sync/src/util.rs index 3fc09c5932..c4504a806a 100644 --- a/shared-lib/flowy-sync/src/util.rs +++ b/shared-lib/flowy-sync/src/util.rs @@ -2,13 +2,9 @@ use crate::{ entities::{ folder::{FolderDelta, FolderInfo}, revision::{RepeatedRevision, Revision}, - text_block::TextBlockInfo, + text_block::DocumentPB, }, errors::{CollaborateError, CollaborateResult}, - protobuf::{ - FolderInfo as FolderInfoPB, RepeatedRevision as RepeatedRevisionPB, Revision as RevisionPB, - TextBlockInfo as TextBlockInfoPB, - }, }; use dissimilar::Chunk; use lib_ot::core::{DeltaBuilder, FlowyStr}; @@ -17,10 +13,7 @@ use lib_ot::{ rich_text::RichTextDelta, }; use serde::de::DeserializeOwned; -use std::{ - convert::TryInto, - sync::atomic::{AtomicI64, Ordering::SeqCst}, -}; +use std::sync::atomic::{AtomicI64, Ordering::SeqCst}; #[inline] pub fn find_newline(s: &str) -> Option { @@ -88,7 +81,7 @@ where Ok(delta) } -pub fn make_delta_from_revision_pb(revisions: Vec) -> CollaborateResult> +pub fn make_delta_from_revision_pb(revisions: Vec) -> CollaborateResult> where T: Attributes + DeserializeOwned, { @@ -103,26 +96,7 @@ where Ok(new_delta) } -pub fn repeated_revision_from_revision_pbs(revisions: Vec) -> CollaborateResult { - let repeated_revision_pb = repeated_revision_pb_from_revisions(revisions); - repeated_revision_from_repeated_revision_pb(repeated_revision_pb) -} - -pub fn repeated_revision_pb_from_revisions(revisions: Vec) -> RepeatedRevisionPB { - let mut repeated_revision_pb = RepeatedRevisionPB::new(); - repeated_revision_pb.set_items(revisions.into()); - repeated_revision_pb -} - -pub fn repeated_revision_from_repeated_revision_pb( - repeated_revision: RepeatedRevisionPB, -) -> CollaborateResult { - repeated_revision - .try_into() - .map_err(|e| CollaborateError::internal().context(format!("Cast repeated revision failed: {:?}", e))) -} - -pub fn pair_rev_id_from_revision_pbs(revisions: &[RevisionPB]) -> (i64, i64) { +pub fn pair_rev_id_from_revision_pbs(revisions: &[Revision]) -> (i64, i64) { let mut rev_id = 0; revisions.iter().for_each(|revision| { if rev_id < revision.rev_id { @@ -155,23 +129,9 @@ pub fn pair_rev_id_from_revisions(revisions: &[Revision]) -> (i64, i64) { #[inline] pub fn make_folder_from_revisions_pb( folder_id: &str, - revisions: RepeatedRevisionPB, + revisions: RepeatedRevision, ) -> Result, CollaborateError> { - match make_folder_pb_from_revisions_pb(folder_id, revisions)? { - None => Ok(None), - Some(pb) => { - let folder_info: FolderInfo = pb.try_into().map_err(|e| CollaborateError::internal().context(e))?; - Ok(Some(folder_info)) - } - } -} - -#[inline] -pub fn make_folder_pb_from_revisions_pb( - folder_id: &str, - mut revisions: RepeatedRevisionPB, -) -> Result, CollaborateError> { - let revisions = revisions.take_items(); + let revisions = revisions.into_inner(); if revisions.is_empty() { return Ok(None); } @@ -190,41 +150,25 @@ pub fn make_folder_pb_from_revisions_pb( } let text = folder_delta.to_delta_str(); - let mut folder_info = FolderInfoPB::new(); - folder_info.set_folder_id(folder_id.to_owned()); - folder_info.set_text(text); - folder_info.set_base_rev_id(base_rev_id); - folder_info.set_rev_id(rev_id); - Ok(Some(folder_info)) + Ok(Some(FolderInfo { + folder_id: folder_id.to_string(), + text, + rev_id, + base_rev_id, + })) } #[inline] -pub fn make_document_info_from_revisions_pb( +pub fn make_document_from_revision_pbs( doc_id: &str, - revisions: RepeatedRevisionPB, -) -> Result, CollaborateError> { - match make_document_info_pb_from_revisions_pb(doc_id, revisions)? { - None => Ok(None), - Some(pb) => { - let document_info: TextBlockInfo = pb.try_into().map_err(|e| { - CollaborateError::internal().context(format!("Deserialize document info from pb failed: {}", e)) - })?; - Ok(Some(document_info)) - } - } -} - -#[inline] -pub fn make_document_info_pb_from_revisions_pb( - doc_id: &str, - mut revisions: RepeatedRevisionPB, -) -> Result, CollaborateError> { - let revisions = revisions.take_items(); + revisions: RepeatedRevision, +) -> Result, CollaborateError> { + let revisions = revisions.into_inner(); if revisions.is_empty() { return Ok(None); } - let mut document_delta = RichTextDelta::new(); + let mut delta = RichTextDelta::new(); let mut base_rev_id = 0; let mut rev_id = 0; for revision in revisions { @@ -235,17 +179,18 @@ pub fn make_document_info_pb_from_revisions_pb( tracing::warn!("revision delta_data is empty"); } - let delta = RichTextDelta::from_bytes(revision.delta_data)?; - document_delta = document_delta.compose(&delta)?; + let new_delta = RichTextDelta::from_bytes(revision.delta_data)?; + delta = delta.compose(&new_delta)?; } - let text = document_delta.to_delta_str(); - let mut block_info = TextBlockInfoPB::new(); - block_info.set_block_id(doc_id.to_owned()); - block_info.set_text(text); - block_info.set_base_rev_id(base_rev_id); - block_info.set_rev_id(rev_id); - Ok(Some(block_info)) + let text = delta.to_delta_str(); + + Ok(Some(DocumentPB { + block_id: doc_id.to_owned(), + text, + rev_id, + base_rev_id, + })) } #[inline] diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs index 3641b4729f..5f293e5e2c 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs @@ -114,12 +114,12 @@ fn generate_dart_protobuf_files( check_pb_dart_plugin(); let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned(); paths.iter().for_each(|path| { - if cmd_lib::run_cmd! { + let result = cmd_lib::run_cmd! { ${protoc_bin_path} --dart_out=${output} --proto_path=${proto_file_output_path} ${path} - } - .is_err() - { - panic!("Generate dart pb file failed with: {}", path) + }; + + if result.is_err() { + panic!("Generate dart pb file failed with: {}, {:?}", path, result) }; });