From 30bac7dce26491ae247249b7de5f309c6fc84433 Mon Sep 17 00:00:00 2001 From: Joooye_34 Date: Fri, 10 Nov 2023 18:25:59 +0800 Subject: [PATCH] ci(core): eslint errors for core (#4662) --- .eslintrc.js | 8 ++- packages/frontend/component/package.json | 1 - .../src/components/app-sidebar/index.tsx | 2 +- packages/frontend/core/package.json | 19 ++++++ .../frontend/core/src/atoms/collections.ts | 63 +++++++++++-------- .../affine/auth/after-sign-in-send-email.tsx | 3 +- .../affine/auth/after-sign-up-send-email.tsx | 3 +- .../src/components/affine/auth/send-email.tsx | 3 +- .../affine/auth/sign-in-with-password.tsx | 3 +- .../src/components/affine/auth/sign-in.tsx | 3 +- .../affine/auth/subscription-redirect.tsx | 13 ++-- .../affine/create-workspace-modal/index.tsx | 10 +-- .../enable-affine-cloud-modal/index.tsx | 2 +- .../new-workspace-setting-detail/export.tsx | 10 +-- .../new-workspace-setting-detail/profile.tsx | 4 +- .../new-workspace-setting-detail/publish.tsx | 5 +- .../setting-modal/account-setting/index.tsx | 3 +- .../general-setting/billing/index.tsx | 5 +- .../general-setting/plans/actions.tsx | 11 ++-- .../general-setting/plans/plan-card.tsx | 11 ++-- .../setting-modal/workspace-setting/index.tsx | 5 +- .../operation-menu.tsx | 3 +- .../block-suite-page-list/utils.tsx | 3 +- .../core/src/components/cloud/login-card.tsx | 13 ++-- .../core/src/components/pure/cmdk/data.tsx | 2 +- .../core/src/components/pure/cmdk/main.tsx | 20 ++++-- .../collections/collections-list.tsx | 9 +-- .../favorite/add-favourite-button.tsx | 4 +- .../user-with-workspace-list/index.tsx | 4 +- .../workspace-list/index.tsx | 2 +- .../src/components/root-app-sidebar/index.tsx | 3 +- .../core/src/components/workspace-header.tsx | 21 ++++--- .../src/hooks/affine/use-language-helper.ts | 9 +-- ...se-register-blocksuite-editor-commands.tsx | 16 ++--- .../hooks/root/use-on-transform-workspace.ts | 4 +- .../core/src/hooks/use-subscription.ts | 8 +-- .../core/src/layouts/workspace-layout.tsx | 3 +- packages/frontend/core/src/pages/404.tsx | 3 +- .../core/src/pages/workspace/collection.tsx | 11 ++-- .../core/src/providers/modal-provider.tsx | 10 ++- .../core/src/providers/session-provider.tsx | 16 ++--- packages/frontend/graphql/package.json | 1 + .../frontend/hooks/src/affine-async-hooks.ts | 27 ++++++++ yarn.lock | 25 +++++++- 44 files changed, 264 insertions(+), 140 deletions(-) create mode 100644 packages/frontend/hooks/src/affine-async-hooks.ts diff --git a/.eslintrc.js b/.eslintrc.js index e0f1244bc4..31e36813f7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -58,7 +58,7 @@ const createPattern = packageName => [ const allPackages = [ 'packages/backend/server', 'packages/frontend/component', - 'packages/frontend/web', + 'packages/frontend/core', 'packages/frontend/electron', 'packages/frontend/graphql', 'packages/frontend/hooks', @@ -255,6 +255,12 @@ const config = { ], '@typescript-eslint/no-misused-promises': ['error'], 'i/no-extraneous-dependencies': ['error'], + 'react-hooks/exhaustive-deps': [ + 'warn', + { + additionalHooks: 'useAsyncCallback', + }, + ], }, })), { diff --git a/packages/frontend/component/package.json b/packages/frontend/component/package.json index 67d2429a2b..9798d4b0c1 100644 --- a/packages/frontend/component/package.json +++ b/packages/frontend/component/package.json @@ -51,7 +51,6 @@ "jotai-effect": "^0.2.2", "jotai-scope": "^0.4.0", "lit": "^3.0.2", - "lodash": "^4.17.21", "lodash-es": "^4.17.21", "lottie-react": "^2.4.0", "lottie-web": "^5.12.2", diff --git a/packages/frontend/component/src/components/app-sidebar/index.tsx b/packages/frontend/component/src/components/app-sidebar/index.tsx index 78afc60237..2bfd9a0fed 100644 --- a/packages/frontend/component/src/components/app-sidebar/index.tsx +++ b/packages/frontend/component/src/components/app-sidebar/index.tsx @@ -2,7 +2,7 @@ import { Skeleton } from '@mui/material'; import { assignInlineVars } from '@vanilla-extract/dynamic'; import clsx from 'clsx'; import { useAtom, useAtomValue } from 'jotai'; -import debounce from 'lodash/debounce'; +import { debounce } from 'lodash-es'; import type { PropsWithChildren, ReactElement } from 'react'; import { useEffect, useRef, useState } from 'react'; diff --git a/packages/frontend/core/package.json b/packages/frontend/core/package.json index 1f2556bb4c..d9b716cf30 100644 --- a/packages/frontend/core/package.json +++ b/packages/frontend/core/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@affine-test/fixtures": "workspace:*", + "@affine/cmdk": "workspace:*", "@affine/component": "workspace:*", "@affine/debug": "workspace:*", "@affine/env": "workspace:*", @@ -31,6 +32,7 @@ "@blocksuite/icons": "2.1.35", "@blocksuite/lit": "0.0.0-20231110042432-4fdac4dc-nightly", "@blocksuite/store": "0.0.0-20231110042432-4fdac4dc-nightly", + "@blocksuite/virgo": "0.0.0-20231110042432-4fdac4dc-nightly", "@dnd-kit/core": "^6.0.8", "@dnd-kit/sortable": "^7.0.2", "@emotion/cache": "^11.11.0", @@ -39,31 +41,44 @@ "@emotion/styled": "^11.11.0", "@marsidev/react-turnstile": "^0.3.1", "@mui/material": "^5.14.14", + "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.4", + "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", "@react-hookz/web": "^23.1.0", "@toeverything/components": "^0.0.46", + "@toeverything/theme": "^0.7.20", + "@vanilla-extract/css": "^1.13.0", + "@vanilla-extract/dynamic": "^2.0.3", "async-call-rpc": "^6.3.1", "bytes": "^3.1.2", + "clsx": "^2.0.0", "css-spring": "^4.1.0", "cssnano": "^6.0.1", + "dayjs": "^1.11.10", + "foxact": "^0.2.20", "graphql": "^16.8.1", + "idb": "^7.1.1", "intl-segmenter-polyfill-rs": "^0.1.6", "jotai": "^2.4.3", "jotai-devtools": "^0.7.0", "lit": "^3.0.2", "lottie-web": "^5.12.2", "mini-css-extract-plugin": "^2.7.6", + "nanoid": "^5.0.1", "next-auth": "^4.23.2", "next-themes": "^0.2.1", "postcss-loader": "^7.3.3", "react": "18.2.0", "react-dom": "18.2.0", + "react-error-boundary": "^4.0.11", "react-is": "18.2.0", "react-resizable-panels": "^0.0.55", "react-router-dom": "^6.16.0", "rxjs": "^7.8.1", "ses": "^0.18.8", "swr": "2.2.4", + "uuid": "^9.0.1", "valtio": "^1.11.2", "y-protocols": "^1.0.6", "yjs": "^13.6.8", @@ -76,12 +91,15 @@ "@sentry/webpack-plugin": "^2.8.0", "@svgr/webpack": "^8.1.0", "@swc/core": "^1.3.93", + "@testing-library/react": "^14.0.0", "@types/bytes": "^3.1.3", "@types/lodash-es": "^4.17.9", + "@types/uuid": "^9.0.5", "@types/webpack-env": "^1.18.2", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "express": "^4.18.2", + "fake-indexeddb": "^5.0.0", "html-webpack-plugin": "^5.5.3", "lodash-es": "^4.17.21", "mime-types": "^2.1.35", @@ -91,6 +109,7 @@ "swc-loader": "^0.2.3", "swc-plugin-coverage-instrument": "^0.0.20", "thread-loader": "^4.0.2", + "vitest": "0.34.6", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1", diff --git a/packages/frontend/core/src/atoms/collections.ts b/packages/frontend/core/src/atoms/collections.ts index 6bf960229d..650048bebf 100644 --- a/packages/frontend/core/src/atoms/collections.ts +++ b/packages/frontend/core/src/atoms/collections.ts @@ -147,36 +147,49 @@ export const pageCollectionBaseAtom = return new Observable(subscriber => { const group = new DisposableGroup(); - currentWorkspacePromise.then(async currentWorkspace => { - const workspaceSetting = getWorkspaceSetting(currentWorkspace); - migrateCollectionsFromIdbData(currentWorkspace).then(collections => { - if (collections.length) { - workspaceSetting.addCollection(...collections); - } - }); - migrateCollectionsFromUserData(currentWorkspace).then(collections => { - if (collections.length) { - workspaceSetting.addCollection(...collections); - } - }); - subscriber.next({ - loading: false, - collections: workspaceSetting.collections, - }); - if (group.disposed) { - return; - } - const fn = () => { + currentWorkspacePromise + .then(async currentWorkspace => { + const workspaceSetting = getWorkspaceSetting(currentWorkspace); + migrateCollectionsFromIdbData(currentWorkspace) + .then(collections => { + if (collections.length) { + workspaceSetting.addCollection(...collections); + } + }) + .catch(error => { + console.error(error); + }); + migrateCollectionsFromUserData(currentWorkspace) + .then(collections => { + if (collections.length) { + workspaceSetting.addCollection(...collections); + } + }) + .catch(error => { + console.error(error); + }); subscriber.next({ loading: false, collections: workspaceSetting.collections, }); - }; - workspaceSetting.collectionsYArray.observe(fn); - group.add(() => { - workspaceSetting.collectionsYArray.unobserve(fn); + if (group.disposed) { + return; + } + const fn = () => { + subscriber.next({ + loading: false, + collections: workspaceSetting.collections, + }); + }; + workspaceSetting.collectionsYArray.observe(fn); + group.add(() => { + workspaceSetting.collectionsYArray.unobserve(fn); + }); + }) + .catch(error => { + subscriber.error(error); }); - }); + return () => { group.dispose(); }; diff --git a/packages/frontend/core/src/components/affine/auth/after-sign-in-send-email.tsx b/packages/frontend/core/src/components/affine/auth/after-sign-in-send-email.tsx index a272b7f5cd..db76cc27bd 100644 --- a/packages/frontend/core/src/components/affine/auth/after-sign-in-send-email.tsx +++ b/packages/frontend/core/src/components/affine/auth/after-sign-in-send-email.tsx @@ -7,6 +7,7 @@ import { import { Trans } from '@affine/i18n'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import React, { useCallback } from 'react'; import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status'; @@ -31,7 +32,7 @@ export const AfterSignInSendEmail = ({ onSignedIn?.(); } - const onResendClick = useCallback(async () => { + const onResendClick = useAsyncCallback(async () => { if (verifyToken) { await signIn(email, verifyToken, challenge); } diff --git a/packages/frontend/core/src/components/affine/auth/after-sign-up-send-email.tsx b/packages/frontend/core/src/components/affine/auth/after-sign-up-send-email.tsx index 3a2d21ff19..3841104620 100644 --- a/packages/frontend/core/src/components/affine/auth/after-sign-up-send-email.tsx +++ b/packages/frontend/core/src/components/affine/auth/after-sign-up-send-email.tsx @@ -6,6 +6,7 @@ import { } from '@affine/component/auth-components'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { type FC, useCallback } from 'react'; import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status'; @@ -29,7 +30,7 @@ export const AfterSignUpSendEmail: FC = ({ onSignedIn?.(); } - const onResendClick = useCallback(async () => { + const onResendClick = useAsyncCallback(async () => { if (verifyToken) { await signUp(email, verifyToken, challenge); } diff --git a/packages/frontend/core/src/components/affine/auth/send-email.tsx b/packages/frontend/core/src/components/affine/auth/send-email.tsx index e2abe99d06..0b3399c1d8 100644 --- a/packages/frontend/core/src/components/affine/auth/send-email.tsx +++ b/packages/frontend/core/src/components/affine/auth/send-email.tsx @@ -14,6 +14,7 @@ import { import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useMutation } from '@affine/workspace/affine/gql'; import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useSetAtom } from 'jotai/react'; import { useCallback, useState } from 'react'; @@ -146,7 +147,7 @@ export const SendEmail = ({ const buttonContent = useButtonContent(emailType); const { loading, sendEmail } = useSendEmail(emailType); - const onSendEmail = useCallback(async () => { + const onSendEmail = useAsyncCallback(async () => { // TODO: add error handler await sendEmail(email); diff --git a/packages/frontend/core/src/components/affine/auth/sign-in-with-password.tsx b/packages/frontend/core/src/components/affine/auth/sign-in-with-password.tsx index 5559e70433..9657c850c3 100644 --- a/packages/frontend/core/src/components/affine/auth/sign-in-with-password.tsx +++ b/packages/frontend/core/src/components/affine/auth/sign-in-with-password.tsx @@ -6,6 +6,7 @@ import { } from '@affine/component/auth-components'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useSession } from 'next-auth/react'; import type { FC } from 'react'; @@ -26,7 +27,7 @@ export const SignInWithPassword: FC = ({ const [password, setPassword] = useState(''); const [passwordError, setPasswordError] = useState(false); - const onSignIn = useCallback(async () => { + const onSignIn = useAsyncCallback(async () => { const res = await signInCloud('credentials', { redirect: false, email, diff --git a/packages/frontend/core/src/components/affine/auth/sign-in.tsx b/packages/frontend/core/src/components/affine/auth/sign-in.tsx index fcc68f3040..93bd0d42be 100644 --- a/packages/frontend/core/src/components/affine/auth/sign-in.tsx +++ b/packages/frontend/core/src/components/affine/auth/sign-in.tsx @@ -9,6 +9,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useMutation } from '@affine/workspace/affine/gql'; import { ArrowDownBigIcon, GoogleDuotoneIcon } from '@blocksuite/icons'; import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { GraphQLError } from 'graphql'; import { type FC, useState } from 'react'; import { useCallback } from 'react'; @@ -52,7 +53,7 @@ export const SignIn: FC = ({ onSignedIn?.(); } - const onContinue = useCallback(async () => { + const onContinue = useAsyncCallback(async () => { if (!validateEmail(email)) { setIsValidEmail(false); return; diff --git a/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx b/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx index a64eeab270..26b720b60f 100644 --- a/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx +++ b/packages/frontend/core/src/components/affine/auth/subscription-redirect.tsx @@ -10,6 +10,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useMutation, useQuery } from '@affine/workspace/affine/gql'; import { Button } from '@toeverything/components/button'; import { Loading } from '@toeverything/components/loading'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { nanoid } from 'nanoid'; import { Suspense, useCallback, useEffect, useMemo } from 'react'; @@ -33,12 +34,12 @@ const usePaymentRedirect = () => { mutation: checkoutMutation, }); - return useCallback(() => { - checkoutSubscription({ recurring, idempotencyKey }) - .then(({ checkout }) => { - window.open(checkout, '_self', 'norefferer'); - }) - .catch(e => console.error(e)); + return useAsyncCallback(async () => { + const { checkout } = await checkoutSubscription({ + recurring, + idempotencyKey, + }); + window.open(checkout, '_self', 'norefferer'); }, [recurring, idempotencyKey, checkoutSubscription]); }; diff --git a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx index 373b86a584..0ec4f8920d 100644 --- a/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx +++ b/packages/frontend/core/src/components/affine/create-workspace-modal/index.tsx @@ -9,6 +9,7 @@ import { Modal, } from '@toeverything/components/modal'; import { Tooltip } from '@toeverything/components/tooltip'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import type { LoadDBFileResult, SelectDBFileLocationResult, @@ -330,14 +331,13 @@ export const CreateWorkspaceModal = ({ ] ); - const onConfirmName = useCallback( - (name: string) => { + const onConfirmName = useAsyncCallback( + async (name: string) => { setWorkspaceName(name); // this will be the last step for web for now // fix me later - createLocalWorkspace(name).then(id => { - onCreate(id); - }); + const id = await createLocalWorkspace(name); + onCreate(id); }, [createLocalWorkspace, onCreate] ); diff --git a/packages/frontend/core/src/components/affine/enable-affine-cloud-modal/index.tsx b/packages/frontend/core/src/components/affine/enable-affine-cloud-modal/index.tsx index 62320122b0..e26990481d 100644 --- a/packages/frontend/core/src/components/affine/enable-affine-cloud-modal/index.tsx +++ b/packages/frontend/core/src/components/affine/enable-affine-cloud-modal/index.tsx @@ -19,7 +19,7 @@ export const EnableAffineCloudModal = ({ const setAuthAtom = useSetAtom(authAtom); const setOnceSignedInEvent = useSetAtom(setOnceSignedInEventAtom); - const confirm = useCallback(async () => { + const confirm = useCallback(() => { return propsOnConfirm?.(); }, [propsOnConfirm]); diff --git a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/export.tsx b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/export.tsx index 91b40a1503..592ccf09c0 100644 --- a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/export.tsx +++ b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/export.tsx @@ -1,17 +1,17 @@ import { pushNotificationAtom } from '@affine/component/notification-center'; import { SettingRow } from '@affine/component/setting-components'; -import { isDesktop } from '@affine/env/constant'; import type { AffineOfficialWorkspace } from '@affine/env/workspace'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import type { SaveDBFileResult } from '@toeverything/infra/type'; import { useSetAtom } from 'jotai'; -import { useCallback, useState } from 'react'; +import { useState } from 'react'; import type { Doc } from 'yjs'; import { encodeStateAsUpdate } from 'yjs'; async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) { - if (window.apis && isDesktop) { + if (window.apis && environment.isDesktop) { const bs = workspace.blockSuiteWorkspace.blob; const blobsInDb = await window.apis.db.getBlobKeys(workspace.id); const blobsInStorage = await bs.list(); @@ -32,7 +32,7 @@ async function syncBlobsToSqliteDb(workspace: AffineOfficialWorkspace) { } async function syncDocsToSqliteDb(workspace: AffineOfficialWorkspace) { - if (window.apis && isDesktop) { + if (window.apis && environment.isDesktop) { const workspaceId = workspace.blockSuiteWorkspace.doc.guid; const syncDoc = async (doc: Doc) => { await window.apis.db.applyDocUpdate( @@ -56,7 +56,7 @@ export const ExportPanel = ({ workspace }: ExportPanelProps) => { const t = useAFFiNEI18N(); const [syncing, setSyncing] = useState(false); const pushNotification = useSetAtom(pushNotificationAtom); - const onExport = useCallback(async () => { + const onExport = useAsyncCallback(async () => { if (syncing) { return; } diff --git a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/profile.tsx b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/profile.tsx index 04844e7df0..80eacda929 100644 --- a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/profile.tsx +++ b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/profile.tsx @@ -77,8 +77,8 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => { ); const handleUploadAvatar = useCallback( - async (file: File) => { - await update(file) + (file: File) => { + update(file) .then(() => { pushNotification({ title: 'Update workspace avatar success', diff --git a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/publish.tsx b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/publish.tsx index 0e824e842d..95652cdd91 100644 --- a/packages/frontend/core/src/components/affine/new-workspace-setting-detail/publish.tsx +++ b/packages/frontend/core/src/components/affine/new-workspace-setting-detail/publish.tsx @@ -11,9 +11,10 @@ import { WorkspaceFlavour } from '@affine/env/workspace'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { Button } from '@toeverything/components/button'; import { Tooltip } from '@toeverything/components/tooltip'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; import { noop } from 'foxact/noop'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { toast } from '../../../utils'; import { EnableAffineCloudModal } from '../enable-affine-cloud-modal'; @@ -52,7 +53,7 @@ const PublishPanelAffine = (props: PublishPanelAffineProps) => { ); }, []); - const copyUrl = useCallback(async () => { + const copyUrl = useAsyncCallback(async () => { await navigator.clipboard.writeText(shareUrl); toast(t['Copied link to clipboard']()); }, [shareUrl, t]); diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx index 3f887cdefb..2c16543363 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx @@ -16,6 +16,7 @@ import { useMutation, useQuery } from '@affine/workspace/affine/gql'; import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons'; import { Avatar } from '@toeverything/components/avatar'; import { Button } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { validateAndReduceImage } from '@toeverything/hooks/use-block-suite-workspace-avatar-url'; import bytes from 'bytes'; import { useSetAtom } from 'jotai'; @@ -50,7 +51,7 @@ export const UserAvatar = () => { mutation: removeAvatarMutation, }); - const handleUpdateUserAvatar = useCallback( + const handleUpdateUserAvatar = useAsyncCallback( async (file: File) => { try { const reducedFile = await validateAndReduceImage(file); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx index 8f44116fca..8fd6785c8c 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx @@ -22,6 +22,7 @@ import { ArrowRightSmallIcon } from '@blocksuite/icons'; import { Skeleton } from '@mui/material'; import { Button, IconButton } from '@toeverything/components/button'; import { Loading } from '@toeverything/components/loading'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useSetAtom } from 'jotai'; import { Suspense, useCallback, useMemo, useState } from 'react'; @@ -255,8 +256,8 @@ const PaymentMethodUpdater = () => { }); const t = useAFFiNEI18N(); - const update = useCallback(() => { - trigger(null, { + const update = useAsyncCallback(async () => { + await trigger(null, { onSuccess: data => { window.open(data.createCustomerPortal, '_blank', 'noopener noreferrer'); }, diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx index 7bf7c0cc7d..4b8a7c0b1d 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/actions.tsx @@ -4,9 +4,10 @@ import { resumeSubscriptionMutation, } from '@affine/graphql'; import { useMutation } from '@affine/workspace/affine/gql'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { nanoid } from 'nanoid'; import type { PropsWithChildren } from 'react'; -import { useCallback, useState } from 'react'; +import { useState } from 'react'; import { ConfirmLoadingModal, DowngradeModal } from './modals'; @@ -30,8 +31,8 @@ export const CancelAction = ({ mutation: cancelSubscriptionMutation, }); - const downgrade = useCallback(() => { - trigger( + const downgrade = useAsyncCallback(async () => { + await trigger( { idempotencyKey }, { onSuccess: data => { @@ -78,8 +79,8 @@ export const ResumeAction = ({ mutation: resumeSubscriptionMutation, }); - const resume = useCallback(() => { - trigger( + const resume = useAsyncCallback(async () => { + await trigger( { idempotencyKey }, { onSuccess: data => { diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx index 98dbc7ced6..e2e1041df4 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx @@ -15,6 +15,7 @@ import { useMutation } from '@affine/workspace/affine/gql'; import { DoneIcon } from '@blocksuite/icons'; import { Button } from '@toeverything/components/button'; import { Tooltip } from '@toeverything/components/tooltip'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useSetAtom } from 'jotai'; import { useAtom } from 'jotai'; import { nanoid } from 'nanoid'; @@ -364,7 +365,7 @@ const Upgrade = ({ }, [onSubscriptionUpdate]); const [, openPaymentDisableModal] = useAtom(openPaymentDisableAtom); - const upgrade = useCallback(() => { + const upgrade = useAsyncCallback(async () => { if (!runtimeConfig.enablePayment) { openPaymentDisableModal(true); return; @@ -373,7 +374,7 @@ const Upgrade = ({ if (newTabRef.current) { newTabRef.current.focus(); } else { - trigger( + await trigger( { recurring, idempotencyKey }, { onSuccess: data => { @@ -439,8 +440,8 @@ const ChangeRecurring = ({ mutation: updateSubscriptionMutation, }); - const change = useCallback(() => { - trigger( + const change = useAsyncCallback(async () => { + await trigger( { recurring: to, idempotencyKey }, { onSuccess: data => { @@ -492,7 +493,7 @@ const ChangeRecurring = ({ const SignUpAction = ({ children }: PropsWithChildren) => { const setOpen = useSetAtom(authAtom); - const onClickSignIn = useCallback(async () => { + const onClickSignIn = useCallback(() => { setOpen(state => ({ ...state, openModal: true, diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx index a96a0b9514..111aa96d36 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/index.tsx @@ -2,6 +2,7 @@ import { pushNotificationAtom } from '@affine/component/notification-center'; import { WorkspaceSubPath } from '@affine/env/workspace'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; import { useSetAtom } from 'jotai'; import { useAtomValue } from 'jotai'; @@ -65,7 +66,7 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => { workspaces, ]); - const handleDeleteWorkspace = useCallback(async () => { + const handleDeleteWorkspace = useAsyncCallback(async () => { closeAndJumpOut(); await deleteWorkspace(workspaceId); @@ -75,7 +76,7 @@ export const WorkspaceSetting = ({ workspaceId }: { workspaceId: string }) => { }); }, [closeAndJumpOut, deleteWorkspace, pushNotification, t, workspaceId]); - const handleLeaveWorkspace = useCallback(async () => { + const handleLeaveWorkspace = useAsyncCallback(async () => { closeAndJumpOut(); await leaveWorkspace(workspaceId, workspaceName); diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx index 4a5976724f..42c702a40c 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header-title/operation-menu.tsx @@ -18,6 +18,7 @@ import { MenuItem, MenuSeparator, } from '@toeverything/components/menu'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useBlockSuitePageMeta, usePageMetaHelper, @@ -99,7 +100,7 @@ export const PageMenu = ({ rename, pageId }: PageMenuProps) => { const exportHandler = useExportPage(currentPage); const setPageMode = useSetAtom(setPageModeAtom); - const duplicate = useCallback(async () => { + const duplicate = useAsyncCallback(async () => { const currentPageMeta = currentPage.meta; const newPage = createPage(); await newPage.waitForLoaded(); diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx index 48d6edf5ab..ea0287e157 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx @@ -1,5 +1,6 @@ import { toast } from '@affine/component'; import { WorkspaceSubPath } from '@affine/env/workspace'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper'; import { initEmptyPage } from '@toeverything/infra/blocksuite'; import { useAtomValue, useSetAtom } from 'jotai'; @@ -33,7 +34,7 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => { const createEdgelessAndOpen = useCallback(() => { return createPageAndOpen('edgeless'); }, [createPageAndOpen]); - const importFileAndOpen = useCallback(async () => { + const importFileAndOpen = useAsyncCallback(async () => { const { showImportModal } = await import('@blocksuite/blocks'); const onSuccess = (pageIds: string[], isWorkspaceFile: boolean) => { toast( diff --git a/packages/frontend/core/src/components/cloud/login-card.tsx b/packages/frontend/core/src/components/cloud/login-card.tsx index bb5107fe9f..747139f5c8 100644 --- a/packages/frontend/core/src/components/cloud/login-card.tsx +++ b/packages/frontend/core/src/components/cloud/login-card.tsx @@ -1,6 +1,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { CloudWorkspaceIcon } from '@blocksuite/icons'; import { Avatar } from '@toeverything/components/avatar'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useCurrentLoginStatus } from '../../hooks/affine/use-current-login-status'; import { useCurrentUser } from '../../hooks/affine/use-current-user'; @@ -10,16 +11,16 @@ import { StyledSignInButton } from '../pure/footer/styles'; export const LoginCard = () => { const t = useAFFiNEI18N(); const loginStatus = useCurrentLoginStatus(); + + const onSignInClick = useAsyncCallback(async () => { + await signInCloud(); + }, []); + if (loginStatus === 'authenticated') { return ; } return ( - { - signInCloud().catch(console.error); - }} - > +
{' '} diff --git a/packages/frontend/core/src/components/pure/cmdk/data.tsx b/packages/frontend/core/src/components/pure/cmdk/data.tsx index fbf4f5e31c..7c2bb695b6 100644 --- a/packages/frontend/core/src/components/pure/cmdk/data.tsx +++ b/packages/frontend/core/src/components/pure/cmdk/data.tsx @@ -24,7 +24,7 @@ import { PreconditionStrategy, } from '@toeverything/infra/command'; import { atom, useAtomValue } from 'jotai'; -import groupBy from 'lodash/groupBy'; +import { groupBy } from 'lodash-es'; import { useCallback, useMemo } from 'react'; import { diff --git a/packages/frontend/core/src/components/pure/cmdk/main.tsx b/packages/frontend/core/src/components/pure/cmdk/main.tsx index 0f6523b688..1ec2a91773 100644 --- a/packages/frontend/core/src/components/pure/cmdk/main.tsx +++ b/packages/frontend/core/src/components/pure/cmdk/main.tsx @@ -2,6 +2,7 @@ import { Command } from '@affine/cmdk'; import { formatDate } from '@affine/component/page-list'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import type { PageMeta } from '@blocksuite/store'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import type { CommandCategory } from '@toeverything/infra/command'; import clsx from 'clsx'; import { useAtom } from 'jotai'; @@ -55,6 +56,19 @@ const QuickSearchGroup = ({ const t = useAFFiNEI18N(); const i18nkey = categoryToI18nKey[category]; const [query, setQuery] = useAtom(cmdkQueryAtom); + + const onCommendSelect = useAsyncCallback( + async (command: CMDKCommand) => { + try { + await command.run(); + } finally { + setQuery(''); + onOpenChange?.(false); + } + }, + [setQuery, onOpenChange] + ); + return ( {commands.map(command => { @@ -67,11 +81,7 @@ const QuickSearchGroup = ({ return ( { - command.run(); - setQuery(''); - onOpenChange?.(false); - }} + onSelect={() => onCommendSelect(command)} value={command.value} data-is-danger={ command.id === 'editor:page-move-to-trash' || diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/collections/collections-list.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/collections/collections-list.tsx index 50941ba37b..c369cdd551 100644 --- a/packages/frontend/core/src/components/pure/workspace-slider-bar/collections/collections-list.tsx +++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/collections/collections-list.tsx @@ -18,8 +18,9 @@ import type { DragEndEvent } from '@dnd-kit/core'; import { useDroppable } from '@dnd-kit/core'; import * as Collapsible from '@radix-ui/react-collapsible'; import { IconButton } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta'; -import { useCallback, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { collectionsCRUDAtom } from '../../../../atoms/collections'; @@ -80,9 +81,9 @@ const CollectionRenderer = ({ () => new Set(collection.allowList), [collection.allowList] ); - const removeFromAllowList = useCallback( - (id: string) => { - return setting.updateCollection({ + const removeFromAllowList = useAsyncCallback( + async (id: string) => { + await setting.updateCollection({ ...collection, allowList: collection.allowList?.filter(v => v != id), }); diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/favorite/add-favourite-button.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/favorite/add-favourite-button.tsx index 919677db5d..92e04e86b8 100644 --- a/packages/frontend/core/src/components/pure/workspace-slider-bar/favorite/add-favourite-button.tsx +++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/favorite/add-favourite-button.tsx @@ -1,8 +1,8 @@ import { PlusIcon } from '@blocksuite/icons'; import type { Workspace } from '@blocksuite/store'; import { IconButton } from '@toeverything/components/button'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta'; -import { useCallback } from 'react'; import { usePageHelper } from '../../../blocksuite/block-suite-page-list/utils'; @@ -13,7 +13,7 @@ type AddFavouriteButtonProps = { export const AddFavouriteButton = ({ workspace }: AddFavouriteButtonProps) => { const { createPage } = usePageHelper(workspace); const { setPageMeta } = usePageMetaHelper(workspace); - const handleAddFavorite = useCallback(async () => { + const handleAddFavorite = useAsyncCallback(async () => { const page = createPage(); await page.waitForLoaded(); setPageMeta(page.id, { favorite: true }); diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx index e1045d593f..b5a4524051 100644 --- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx +++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/index.tsx @@ -25,7 +25,7 @@ const SignInItem = () => { const t = useAFFiNEI18N(); - const onClickSignIn = useCallback(async () => { + const onClickSignIn = useCallback(() => { if (!runtimeConfig.enableCloud) { setDisableCloudOpen(true); } else { @@ -76,7 +76,7 @@ export const UserWithWorkspaceList = ({ onEventEnd?.(); }, [onEventEnd, setOpenCreateWorkspaceModal]); - const onAddWorkspace = useCallback(async () => { + const onAddWorkspace = useCallback(() => { setOpenCreateWorkspaceModal('add'); onEventEnd?.(); }, [onEventEnd, setOpenCreateWorkspaceModal]); diff --git a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx index be17d2dcf0..9d065ec449 100644 --- a/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx +++ b/packages/frontend/core/src/components/pure/workspace-slider-bar/user-with-workspace-list/workspace-list/index.tsx @@ -193,7 +193,7 @@ export const AFFiNEWorkspaceList = ({ onEventEnd?.(); }, [onEventEnd, setOpenCreateWorkspaceModal]); - const onAddWorkspace = useCallback(async () => { + const onAddWorkspace = useCallback(() => { setOpenCreateWorkspaceModal('add'); onEventEnd?.(); }, [onEventEnd, setOpenCreateWorkspaceModal]); diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx index 1fd0d46d64..a6620301c5 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -18,6 +18,7 @@ import { FolderIcon, SettingsIcon } from '@blocksuite/icons'; import type { Page } from '@blocksuite/store'; import { useDroppable } from '@dnd-kit/core'; import { Menu } from '@toeverything/components/menu'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useAtom, useAtomValue } from 'jotai'; import type { HTMLAttributes, ReactElement } from 'react'; import { forwardRef, useCallback, useEffect, useMemo } from 'react'; @@ -107,7 +108,7 @@ export const RootAppSidebar = ({ ); const generalShortcutsInfo = useGeneralShortcuts(); - const onClickNewPage = useCallback(async () => { + const onClickNewPage = useAsyncCallback(async () => { const page = createPage(); await page.waitForLoaded(); openPage(page.id); diff --git a/packages/frontend/core/src/components/workspace-header.tsx b/packages/frontend/core/src/components/workspace-header.tsx index de7e5c3eb1..719a35b409 100644 --- a/packages/frontend/core/src/components/workspace-header.tsx +++ b/packages/frontend/core/src/components/workspace-header.tsx @@ -5,7 +5,7 @@ import { useCollectionManager, } from '@affine/component/page-list'; import { Unreachable } from '@affine/env/constant'; -import type { Collection } from '@affine/env/filter'; +import type { Collection, Filter } from '@affine/env/filter'; import type { WorkspaceFlavour, WorkspaceHeaderProps, @@ -13,6 +13,7 @@ import type { import { WorkspaceSubPath } from '@affine/env/workspace'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { DeleteIcon } from '@blocksuite/icons'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useSetAtom } from 'jotai/react'; import { useCallback } from 'react'; @@ -44,6 +45,17 @@ const FilterContainer = ({ workspaceId }: { workspaceId: string }) => { }, [setting, navigateHelper, workspaceId] ); + + const onFilterChange = useAsyncCallback( + async (filterList: Filter[]) => { + await setting.updateCollection({ + ...setting.currentCollection, + filterList, + }); + }, + [setting] + ); + if (!setting.isDefault || !setting.currentCollection.filterList.length) { return null; } @@ -54,12 +66,7 @@ const FilterContainer = ({ workspaceId }: { workspaceId: string }) => { { - return setting.updateCollection({ - ...setting.currentCollection, - filterList, - }); - }} + onChange={onFilterChange} />
diff --git a/packages/frontend/core/src/hooks/affine/use-language-helper.ts b/packages/frontend/core/src/hooks/affine/use-language-helper.ts index 442ee0393d..9ddfca62f7 100644 --- a/packages/frontend/core/src/hooks/affine/use-language-helper.ts +++ b/packages/frontend/core/src/hooks/affine/use-language-helper.ts @@ -1,5 +1,6 @@ import { LOCALES, useI18N } from '@affine/i18n'; -import { useCallback, useMemo } from 'react'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; +import { useMemo } from 'react'; export function useLanguageHelper() { const i18n = useI18N(); @@ -16,9 +17,9 @@ export function useLanguageHelper() { })), [] ); - const onLanguageChange = useCallback( - (event: string) => { - i18n.changeLanguage(event); + const onLanguageChange = useAsyncCallback( + async (event: string) => { + await i18n.changeLanguage(event); }, [i18n] ); diff --git a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx index 886383aabd..d7f6cd5134 100644 --- a/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx +++ b/packages/frontend/core/src/hooks/affine/use-register-blocksuite-editor-commands.tsx @@ -117,8 +117,8 @@ export function useRegisterBlocksuiteEditorCommands( category: `editor:${mode}`, icon: mode === 'page' ? : , label: t['Export to PDF'](), - run() { - exportHandler('pdf'); + async run() { + await exportHandler('pdf'); }, }) ); @@ -130,8 +130,8 @@ export function useRegisterBlocksuiteEditorCommands( category: `editor:${mode}`, icon: mode === 'page' ? : , label: t['Export to HTML'](), - run() { - exportHandler('html'); + async run() { + await exportHandler('html'); }, }) ); @@ -143,8 +143,8 @@ export function useRegisterBlocksuiteEditorCommands( category: `editor:${mode}`, icon: mode === 'page' ? : , label: t['Export to PNG'](), - run() { - exportHandler('png'); + async run() { + await exportHandler('png'); }, }) ); @@ -156,8 +156,8 @@ export function useRegisterBlocksuiteEditorCommands( category: `editor:${mode}`, icon: mode === 'page' ? : , label: t['Export to Markdown'](), - run() { - exportHandler('markdown'); + async run() { + await exportHandler('markdown'); }, }) ); diff --git a/packages/frontend/core/src/hooks/root/use-on-transform-workspace.ts b/packages/frontend/core/src/hooks/root/use-on-transform-workspace.ts index 6810cc5294..496b66759a 100644 --- a/packages/frontend/core/src/hooks/root/use-on-transform-workspace.ts +++ b/packages/frontend/core/src/hooks/root/use-on-transform-workspace.ts @@ -7,10 +7,10 @@ import { rootWorkspacesMetadataAtom, workspaceAdaptersAtom, } from '@affine/workspace/atom'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { currentPageIdAtom } from '@toeverything/infra/atom'; import { WorkspaceVersion } from '@toeverything/infra/blocksuite'; import { useAtomValue, useSetAtom } from 'jotai'; -import { useCallback } from 'react'; import { openSettingModalAtom } from '../../atoms'; import { useNavigateHelper } from '../use-navigate-helper'; @@ -24,7 +24,7 @@ export function useOnTransformWorkspace() { const currentPageId = useAtomValue(currentPageIdAtom); const pushNotification = useSetAtom(pushNotificationAtom); - return useCallback( + return useAsyncCallback( async ( from: From, to: To, diff --git a/packages/frontend/core/src/hooks/use-subscription.ts b/packages/frontend/core/src/hooks/use-subscription.ts index e2d7d50b12..bdc254c71d 100644 --- a/packages/frontend/core/src/hooks/use-subscription.ts +++ b/packages/frontend/core/src/hooks/use-subscription.ts @@ -1,6 +1,6 @@ import { type SubscriptionQuery, subscriptionQuery } from '@affine/graphql'; import { useQuery } from '@affine/workspace/affine/gql'; -import { useCallback } from 'react'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; export type Subscription = NonNullable< NonNullable['subscription'] @@ -16,9 +16,9 @@ export const useUserSubscription = () => { query: subscriptionQuery, }); - const set: SubscriptionMutator = useCallback( - (update?: Partial) => { - mutate(prev => { + const set: SubscriptionMutator = useAsyncCallback( + async (update?: Partial) => { + await mutate(prev => { if (!update || !prev?.currentUser?.subscription) { return; } diff --git a/packages/frontend/core/src/layouts/workspace-layout.tsx b/packages/frontend/core/src/layouts/workspace-layout.tsx index 94017fb8e1..e8779f6b0c 100644 --- a/packages/frontend/core/src/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/layouts/workspace-layout.tsx @@ -121,6 +121,7 @@ type WorkspaceLayoutProps = { function useLoadWorkspacePages() { const [currentWorkspace] = useCurrentWorkspace(); const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace); + useEffect(() => { if (currentWorkspace) { const timer = setTimeout(() => { @@ -129,7 +130,7 @@ function useLoadWorkspacePages() { .map(id => currentWorkspace.blockSuiteWorkspace.getPage(id)) .filter((p): p is Page => !!p); pages.forEach(page => { - loadPage(page, -10); + loadPage(page, -10).catch(e => console.error(e)); }); }, 10 * 1000); // load pages after 10s return () => { diff --git a/packages/frontend/core/src/pages/404.tsx b/packages/frontend/core/src/pages/404.tsx index 4afdc78a52..e569f1a09e 100644 --- a/packages/frontend/core/src/pages/404.tsx +++ b/packages/frontend/core/src/pages/404.tsx @@ -1,4 +1,5 @@ import { NotFoundPage } from '@affine/component/not-found-page'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useSession } from 'next-auth/react'; import type { ReactElement } from 'react'; @@ -22,7 +23,7 @@ export const Component = (): ReactElement => { setOpen(true); }, [setOpen]); - const onConfirmSignOut = useCallback(async () => { + const onConfirmSignOut = useAsyncCallback(async () => { setOpen(false); await signOutCloud({ callbackUrl: '/signIn', diff --git a/packages/frontend/core/src/pages/workspace/collection.tsx b/packages/frontend/core/src/pages/workspace/collection.tsx index 5f37de1ed8..ef2f330d6f 100644 --- a/packages/frontend/core/src/pages/workspace/collection.tsx +++ b/packages/frontend/core/src/pages/workspace/collection.tsx @@ -14,6 +14,7 @@ import { PageIcon, ViewLayersIcon, } from '@blocksuite/icons'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { getCurrentStore } from '@toeverything/infra/atom'; import { useAtomValue } from 'jotai'; import { useSetAtom } from 'jotai'; @@ -92,11 +93,13 @@ export const Component = function CollectionPage() { const Placeholder = ({ collection }: { collection: Collection }) => { const { updateCollection } = useCollectionManager(collectionsCRUDAtom); const { node, open } = useEditCollection(useAllPageListConfig()); - const openPageEdit = useCallback(() => { - open({ ...collection }, 'page').then(updateCollection); + const openPageEdit = useAsyncCallback(async () => { + const ret = await open({ ...collection }, 'page'); + await updateCollection(ret); }, [open, collection, updateCollection]); - const openRuleEdit = useCallback(() => { - open({ ...collection }, 'rule').then(updateCollection); + const openRuleEdit = useAsyncCallback(async () => { + const ret = await open({ ...collection }, 'rule'); + await updateCollection(ret); }, [collection, open, updateCollection]); const [showTips, setShowTips] = useState(false); useEffect(() => { diff --git a/packages/frontend/core/src/providers/modal-provider.tsx b/packages/frontend/core/src/providers/modal-provider.tsx index 6a9daa7488..3c805bae9c 100644 --- a/packages/frontend/core/src/providers/modal-provider.tsx +++ b/packages/frontend/core/src/providers/modal-provider.tsx @@ -1,5 +1,6 @@ import { WorkspaceSubPath } from '@affine/env/workspace'; import { assertExists } from '@blocksuite/global/utils'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useAtom } from 'jotai'; import type { ReactElement } from 'react'; import { lazy, Suspense, useCallback } from 'react'; @@ -154,13 +155,10 @@ export const SignOutConfirmModal = () => { const { jumpToIndex } = useNavigateHelper(); const [open, setOpen] = useAtom(openSignOutModalAtom); - const onConfirm = useCallback(async () => { + const onConfirm = useAsyncCallback(async () => { setOpen(false); - signOutCloud() - .then(() => { - jumpToIndex(); - }) - .catch(console.error); + await signOutCloud(); + jumpToIndex(); }, [jumpToIndex, setOpen]); return ( diff --git a/packages/frontend/core/src/providers/session-provider.tsx b/packages/frontend/core/src/providers/session-provider.tsx index b1ef1b0328..1135fa6468 100644 --- a/packages/frontend/core/src/providers/session-provider.tsx +++ b/packages/frontend/core/src/providers/session-provider.tsx @@ -3,6 +3,7 @@ import '@toeverything/hooks/use-affine-ipc-renderer'; import { pushNotificationAtom } from '@affine/component/notification-center'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { refreshRootMetadataAtom } from '@affine/workspace/atom'; +import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useAtom, useSetAtom } from 'jotai'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { SessionProvider, useSession } from 'next-auth/react'; @@ -24,6 +25,12 @@ const SessionDefence = (props: PropsWithChildren) => { const refreshMetadata = useSetAtom(refreshRootMetadataAtom); const onceSignedInEvents = useOnceSignedInEvents(); const t = useAFFiNEI18N(); + + const refreshAfterSignedInEvents = useAsyncCallback(async () => { + await onceSignedInEvents(); + refreshMetadata(); + }, [onceSignedInEvents, refreshMetadata]); + useEffect(() => { if (sessionInAtom !== session && session.status === 'authenticated') { setSession(session); @@ -35,11 +42,7 @@ const SessionDefence = (props: PropsWithChildren) => { prevSession.current?.status === 'unauthenticated' && session.status === 'authenticated' ) { - startTransition(() => { - onceSignedInEvents().then(() => { - refreshMetadata(); - }); - }); + startTransition(() => refreshAfterSignedInEvents()); pushNotification({ title: t['com.affine.auth.has.signed'](), message: t['com.affine.auth.has.signed.message'](), @@ -57,9 +60,8 @@ const SessionDefence = (props: PropsWithChildren) => { sessionInAtom, prevSession, setSession, - onceSignedInEvents, pushNotification, - refreshMetadata, + refreshAfterSignedInEvents, t, ]); return props.children; diff --git a/packages/frontend/graphql/package.json b/packages/frontend/graphql/package.json index 7b4d4b31f1..ee9047ad80 100644 --- a/packages/frontend/graphql/package.json +++ b/packages/frontend/graphql/package.json @@ -15,6 +15,7 @@ "@graphql-codegen/typescript": "^4.0.1", "@graphql-codegen/typescript-operations": "^4.0.1", "@types/lodash-es": "^4.17.9", + "lodash": "^4.17.21", "lodash-es": "^4.17.21", "prettier": "^3.0.3", "vitest": "0.34.6" diff --git a/packages/frontend/hooks/src/affine-async-hooks.ts b/packages/frontend/hooks/src/affine-async-hooks.ts new file mode 100644 index 0000000000..44affeda67 --- /dev/null +++ b/packages/frontend/hooks/src/affine-async-hooks.ts @@ -0,0 +1,27 @@ +import React from 'react'; + +export type AsyncErrorHandler = (error: Error) => void; + +/** + * App should provide a global error handler for async callback in the root. + */ +export const AsyncCallbackContext = React.createContext(e => + console.error(e) +); + +/** + * Translate async function to sync function and handle error automatically. + * Only accept void function, return data here is meaningless. + */ +export function useAsyncCallback( + callback: (...args: T) => Promise, + deps: any[] +): (...args: T) => void { + const handleAsyncError = React.useContext(AsyncCallbackContext); + return React.useCallback( + (...args: any) => { + callback(...args).catch(e => handleAsyncError(e)); + }, + [...deps] // eslint-disable-line react-hooks/exhaustive-deps + ); +} diff --git a/yarn.lock b/yarn.lock index 2f84cb0787..3dd47f1ab7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -178,7 +178,7 @@ __metadata: languageName: unknown linkType: soft -"@affine/cmdk@workspace:packages/common/cmdk": +"@affine/cmdk@workspace:*, @affine/cmdk@workspace:packages/common/cmdk": version: 0.0.0-use.local resolution: "@affine/cmdk@workspace:packages/common/cmdk" dependencies: @@ -242,7 +242,6 @@ __metadata: jotai-effect: "npm:^0.2.2" jotai-scope: "npm:^0.4.0" lit: "npm:^3.0.2" - lodash: "npm:^4.17.21" lodash-es: "npm:^4.17.21" lottie-react: "npm:^2.4.0" lottie-web: "npm:^5.12.2" @@ -304,6 +303,7 @@ __metadata: resolution: "@affine/core@workspace:packages/frontend/core" dependencies: "@affine-test/fixtures": "workspace:*" + "@affine/cmdk": "workspace:*" "@affine/component": "workspace:*" "@affine/debug": "workspace:*" "@affine/env": "workspace:*" @@ -319,6 +319,7 @@ __metadata: "@blocksuite/icons": "npm:2.1.35" "@blocksuite/lit": "npm:0.0.0-20231110042432-4fdac4dc-nightly" "@blocksuite/store": "npm:0.0.0-20231110042432-4fdac4dc-nightly" + "@blocksuite/virgo": "npm:0.0.0-20231110042432-4fdac4dc-nightly" "@dnd-kit/core": "npm:^6.0.8" "@dnd-kit/sortable": "npm:^7.0.2" "@emotion/cache": "npm:^11.11.0" @@ -329,24 +330,37 @@ __metadata: "@mui/material": "npm:^5.14.14" "@perfsee/webpack": "npm:^1.8.4" "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.11" + "@radix-ui/react-collapsible": "npm:^1.0.3" + "@radix-ui/react-dialog": "npm:^1.0.4" + "@radix-ui/react-scroll-area": "npm:^1.0.5" "@radix-ui/react-select": "npm:^2.0.0" "@react-hookz/web": "npm:^23.1.0" "@sentry/webpack-plugin": "npm:^2.8.0" "@svgr/webpack": "npm:^8.1.0" "@swc/core": "npm:^1.3.93" + "@testing-library/react": "npm:^14.0.0" "@toeverything/components": "npm:^0.0.46" + "@toeverything/theme": "npm:^0.7.20" "@types/bytes": "npm:^3.1.3" "@types/lodash-es": "npm:^4.17.9" + "@types/uuid": "npm:^9.0.5" "@types/webpack-env": "npm:^1.18.2" + "@vanilla-extract/css": "npm:^1.13.0" + "@vanilla-extract/dynamic": "npm:^2.0.3" async-call-rpc: "npm:^6.3.1" bytes: "npm:^3.1.2" + clsx: "npm:^2.0.0" copy-webpack-plugin: "npm:^11.0.0" css-loader: "npm:^6.8.1" css-spring: "npm:^4.1.0" cssnano: "npm:^6.0.1" + dayjs: "npm:^1.11.10" express: "npm:^4.18.2" + fake-indexeddb: "npm:^5.0.0" + foxact: "npm:^0.2.20" graphql: "npm:^16.8.1" html-webpack-plugin: "npm:^5.5.3" + idb: "npm:^7.1.1" intl-segmenter-polyfill-rs: "npm:^0.1.6" jotai: "npm:^2.4.3" jotai-devtools: "npm:^0.7.0" @@ -355,12 +369,14 @@ __metadata: lottie-web: "npm:^5.12.2" mime-types: "npm:^2.1.35" mini-css-extract-plugin: "npm:^2.7.6" + nanoid: "npm:^5.0.1" next-auth: "npm:^4.23.2" next-themes: "npm:^0.2.1" postcss-loader: "npm:^7.3.3" raw-loader: "npm:^4.0.2" react: "npm:18.2.0" react-dom: "npm:18.2.0" + react-error-boundary: "npm:^4.0.11" react-is: "npm:18.2.0" react-resizable-panels: "npm:^0.0.55" react-router-dom: "npm:^6.16.0" @@ -372,7 +388,9 @@ __metadata: swc-plugin-coverage-instrument: "npm:^0.0.20" swr: "npm:2.2.4" thread-loader: "npm:^4.0.2" + uuid: "npm:^9.0.1" valtio: "npm:^1.11.2" + vitest: "npm:0.34.6" webpack: "npm:^5.89.0" webpack-cli: "npm:^5.1.4" webpack-dev-server: "npm:^4.15.1" @@ -482,6 +500,7 @@ __metadata: "@graphql-codegen/typescript-operations": "npm:^4.0.1" "@types/lodash-es": "npm:^4.17.9" graphql: "npm:^16.8.1" + lodash: "npm:^4.17.21" lodash-es: "npm:^4.17.21" nanoid: "npm:^5.0.1" prettier: "npm:^3.0.3" @@ -12679,7 +12698,7 @@ __metadata: languageName: unknown linkType: soft -"@toeverything/theme@npm:^0.7.21, @toeverything/theme@npm:^0.7.24": +"@toeverything/theme@npm:^0.7.20, @toeverything/theme@npm:^0.7.21, @toeverything/theme@npm:^0.7.24": version: 0.7.24 resolution: "@toeverything/theme@npm:0.7.24" checksum: faa97dad2a411e895090497ff6cbb83836e9be963e608cbc7f3421c4a933d86393551250fa015d4b9060778f0abb0e122a41d12a70e6f7fb7c9eadc2324a6035