refactor(core): move hooks to core (#5458)

* move @toeverything/hooks -> @affine/core/hooks
* delete @toeverything/hooks

hooks are all business-related logic and are deeply coupled with other parts.

Move them into the core and then reconstruct them by feature.
This commit is contained in:
EYHN
2024-01-02 08:05:46 +00:00
parent 6862b7deaf
commit 4b217e6b89
95 changed files with 99 additions and 347 deletions

View File

@@ -1,7 +1,7 @@
import { updateReadyAtom } from '@affine/core/hooks/use-app-updater';
import { apis } from '@affine/electron-api';
import type { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ResetIcon } from '@blocksuite/icons';
import { updateReadyAtom } from '@toeverything/hooks/use-app-updater';
import { registerAffineCommand } from '@toeverything/infra/command';
import type { createStore } from 'jotai';

View File

@@ -1,7 +1,7 @@
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useTheme } from 'next-themes';
import {
type FC,

View File

@@ -5,9 +5,9 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import React, { useCallback } from 'react';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';

View File

@@ -5,8 +5,8 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { type FC, useCallback } from 'react';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';

View File

@@ -7,13 +7,13 @@ import {
} from '@affine/component/auth-components';
import { pushNotificationAtom } from '@affine/component/notification-center';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import {
sendChangeEmailMutation,
sendChangePasswordEmailMutation,
sendSetPasswordEmailMutation,
} from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai/react';
import { useCallback, useState } from 'react';

View File

@@ -5,8 +5,8 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
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';

View File

@@ -4,11 +4,11 @@ import {
ModalHeader,
} from '@affine/component/auth-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { type GetUserQuery, getUserQuery } from '@affine/graphql';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowDownBigIcon, GoogleDuotoneIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { GraphQLError } from 'graphql';
import { type FC, useState } from 'react';
import { useCallback } from 'react';

View File

@@ -2,6 +2,7 @@ import { SignUpPage } from '@affine/component/auth-components';
import { AffineShapeIcon } from '@affine/component/page-list';
import { Button } from '@affine/component/ui/button';
import { Loading } from '@affine/component/ui/loading';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import type { SubscriptionRecurring } from '@affine/graphql';
import {
changePasswordMutation,
@@ -9,7 +10,6 @@ import {
subscriptionQuery,
} from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { nanoid } from 'nanoid';
import { Suspense, useCallback, useEffect, useMemo } from 'react';

View File

@@ -4,12 +4,12 @@ import {
type ConfirmModalProps,
Modal,
} from '@affine/component/ui/modal';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { DebugLogger } from '@affine/debug';
import { apis } from '@affine/electron-api';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { workspaceManagerAtom } from '@affine/workspace/atom';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { getCurrentStore } from '@toeverything/infra/atom';
import {
buildShowcaseWorkspace,

View File

@@ -1,3 +1,5 @@
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspacePage } from '@affine/core/hooks/use-block-suite-workspace-page';
import { DebugLogger } from '@affine/debug';
import {
fetchWithTraceReport,
@@ -11,8 +13,6 @@ import {
} from '@affine/workspace';
import { assertEquals } from '@blocksuite/global/utils';
import { Workspace } from '@blocksuite/store';
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import { revertUpdate } from '@toeverything/y-indexeddb';
import { useMemo } from 'react';
import useSWRImmutable from 'swr/immutable';

View File

@@ -6,13 +6,13 @@ import {
import { Button } from '@affine/component/ui/button';
import { ConfirmModal, Modal } from '@affine/component/ui/modal';
import type { PageMode } from '@affine/core/atoms';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { ToggleCollapseIcon } from '@blocksuite/icons';
import type { Page, Workspace } from '@blocksuite/store';
import * as Collapsible from '@radix-ui/react-collapsible';
import type { DialogContentProps } from '@radix-ui/react-dialog';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtom, useAtomValue } from 'jotai';
import {
Fragment,

View File

@@ -7,6 +7,7 @@ import {
} from '@affine/component/setting-components';
import { Avatar } from '@affine/component/ui/avatar';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import {
allBlobSizesQuery,
removeAvatarMutation,
@@ -15,7 +16,6 @@ import {
} from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import bytes from 'bytes';
import { useSetAtom } from 'jotai';
import {

View File

@@ -2,9 +2,9 @@ import { Switch } from '@affine/component';
import { SettingHeader } from '@affine/component/setting-components';
import { SettingRow } from '@affine/component/setting-components';
import { SettingWrapper } from '@affine/component/setting-components';
import { useAppUpdater } from '@affine/core/hooks/use-app-updater';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons';
import { useAppUpdater } from '@toeverything/hooks/use-app-updater';
import { useCallback } from 'react';
import { useAppSettingHelper } from '../../../../../hooks/affine/use-app-setting-helper';

View File

@@ -1,9 +1,9 @@
import { Loading } from '@affine/component';
import { SettingRow } from '@affine/component/setting-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAppUpdater } from '@affine/core/hooks/use-app-updater';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAppUpdater } from '@toeverything/hooks/use-app-updater';
import clsx from 'clsx';
import { useCallback, useMemo, useState } from 'react';

View File

@@ -7,6 +7,7 @@ import {
} from '@affine/component/setting-components';
import { Button, IconButton } from '@affine/component/ui/button';
import { Loading } from '@affine/component/ui/loading';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import {
createCustomerPortalMutation,
getInvoicesCountQuery,
@@ -21,7 +22,6 @@ import {
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai';
import { Suspense, useCallback, useMemo, useState } from 'react';

View File

@@ -1,9 +1,9 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import type { SubscriptionMutator } from '@affine/core/hooks/use-subscription';
import {
cancelSubscriptionMutation,
resumeSubscriptionMutation,
} from '@affine/graphql';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { nanoid } from 'nanoid';
import type { PropsWithChildren } from 'react';
import { useState } from 'react';

View File

@@ -1,5 +1,6 @@
import { Button } from '@affine/component/ui/button';
import { Tooltip } from '@affine/component/ui/tooltip';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import type {
Subscription,
SubscriptionMutator,
@@ -14,7 +15,6 @@ import {
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { DoneIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai';
import { useAtom } from 'jotai';
import { nanoid } from 'nanoid';

View File

@@ -4,6 +4,8 @@ import {
} from '@affine/component/setting-components';
import { Avatar } from '@affine/component/ui/avatar';
import { Tooltip } from '@affine/component/ui/tooltip';
import { useWorkspaceBlobObjectUrl } from '@affine/core/hooks/use-workspace-blob';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { WorkspaceMetadata } from '@affine/workspace';
@@ -12,8 +14,6 @@ import {
workspaceListAtom,
} from '@affine/workspace/atom';
import { Logo1Icon } from '@blocksuite/icons';
import { useWorkspaceBlobObjectUrl } from '@toeverything/hooks/use-workspace-blob';
import { useWorkspaceInfo } from '@toeverything/hooks/use-workspace-info';
import clsx from 'clsx';
import { useAtom, useAtomValue } from 'jotai/react';
import { type ReactElement, Suspense, useCallback } from 'react';

View File

@@ -3,12 +3,12 @@ import {
ConfirmModal,
type ConfirmModalProps,
} from '@affine/component/ui/modal';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
import { useWorkspaceInfo } from '@toeverything/hooks/use-workspace-info';
import { useCallback, useState } from 'react';
import * as styles from './style.css';

View File

@@ -1,6 +1,7 @@
import { pushNotificationAtom } from '@affine/component/notification-center';
import { SettingRow } from '@affine/component/setting-components';
import { ConfirmModal } from '@affine/component/ui/modal';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
@@ -9,7 +10,6 @@ import {
workspaceManagerAtom,
} from '@affine/workspace/atom';
import { ArrowRightSmallIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback, useState } from 'react';

View File

@@ -1,12 +1,12 @@
import { SettingRow } from '@affine/component/setting-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@affine/workspace';
import { workspaceManagerAtom } from '@affine/workspace/atom';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useWorkspaceInfo } from '@toeverything/hooks/use-workspace-info';
import { useAtomValue, useSetAtom } from 'jotai';
import { useState } from 'react';

View File

@@ -1,10 +1,10 @@
import { pushNotificationAtom } from '@affine/component/notification-center';
import { SettingRow } from '@affine/component/setting-components';
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { apis } from '@affine/electron-api';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace, WorkspaceMetadata } from '@affine/workspace';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSetAtom } from 'jotai';
import { useState } from 'react';

View File

@@ -4,10 +4,10 @@ import {
SettingWrapper,
} from '@affine/component/setting-components';
import { useSelfHosted } from '@affine/core/hooks/affine/use-server-config';
import { useWorkspace } from '@affine/core/hooks/use-workspace';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useWorkspace } from '@toeverything/hooks/use-workspace';
import { useWorkspaceInfo } from '@toeverything/hooks/use-workspace-info';
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
import { EnableCloudPanel } from './enable-cloud';

View File

@@ -3,15 +3,15 @@ import { pushNotificationAtom } from '@affine/component/notification-center';
import { Avatar } from '@affine/component/ui/avatar';
import { Button } from '@affine/component/ui/button';
import { Upload } from '@affine/core/components/pure/file-upload';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useWorkspaceBlobObjectUrl } from '@affine/core/hooks/use-workspace-blob';
import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
import { validateAndReduceImage } from '@affine/core/utils/reduce-image';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@affine/workspace';
import { SyncPeerStep } from '@affine/workspace';
import { CameraIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useWorkspaceBlobObjectUrl } from '@toeverything/hooks/use-workspace-blob';
import { useWorkspaceStatus } from '@toeverything/hooks/use-workspace-status';
import { useSetAtom } from 'jotai';
import {
type KeyboardEvent,

View File

@@ -1,8 +1,8 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { WorkspaceFlavour } from '@affine/env/workspace';
import type { Workspace } from '@affine/workspace';
import { workspaceManagerAtom } from '@affine/workspace/atom';
import type { Page } from '@blocksuite/store';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtomValue } from 'jotai';
import { useState } from 'react';

View File

@@ -1,8 +1,8 @@
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
} from '@affine/core/hooks/use-block-suite-page-meta';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import {
type FocusEvent,
type InputHTMLAttributes,

View File

@@ -5,6 +5,7 @@ import {
MenuItem,
MenuSeparator,
} from '@affine/component/ui/menu';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
@@ -19,7 +20,6 @@ import {
ImportIcon,
PageIcon,
} from '@blocksuite/icons';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtomValue } from 'jotai';
import { useCallback, useState } from 'react';

View File

@@ -1,6 +1,6 @@
import { Tooltip } from '@affine/component/ui/tooltip';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtomValue } from 'jotai';
import type { CSSProperties } from 'react';
import { useCallback, useEffect } from 'react';

View File

@@ -1,8 +1,8 @@
import { toast } from '@affine/component';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@affine/core/hooks/use-block-suite-workspace-helper';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { initEmptyPage } from '@toeverything/infra/blocksuite';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback, useMemo } from 'react';

View File

@@ -1,8 +1,8 @@
import { Avatar } from '@affine/component/ui/avatar';
import { Menu, MenuIcon, MenuItem } from '@affine/component/ui/menu';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { SignOutIcon } from '@blocksuite/icons';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';

View File

@@ -1,10 +1,10 @@
import './page-detail-editor.css';
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
import { useBlockSuiteWorkspacePage } from '@affine/core/hooks/use-block-suite-workspace-page';
import { assertExists, DisposableGroup } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { Page, Workspace } from '@blocksuite/store';
import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor';
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import { fontStyleOptions } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';

View File

@@ -1,5 +1,9 @@
import { commandScore } from '@affine/cmdk';
import { useCollectionManager } from '@affine/component/page-list';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@affine/core/hooks/use-block-suite-page-meta';
import type { Collection } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
@@ -8,10 +12,6 @@ import {
} from '@affine/workspace/atom';
import { EdgelessIcon, PageIcon, ViewLayersIcon } from '@blocksuite/icons';
import type { Page, PageMeta } from '@blocksuite/store';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { getCurrentStore } from '@toeverything/infra/atom';
import {
type AffineCommand,

View File

@@ -1,9 +1,9 @@
import { Command } from '@affine/cmdk';
import { useCommandState } from '@affine/cmdk';
import { formatDate } from '@affine/component/page-list';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
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, useAtomValue } from 'jotai';

View File

@@ -3,7 +3,7 @@ import {
appSidebarOpenAtom,
SidebarSwitch,
} from '@affine/component/app-sidebar';
import { useIsTinyScreen } from '@toeverything/hooks/use-is-tiny-screen';
import { useIsTinyScreen } from '@affine/core/hooks/use-is-tiny-screen';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
import type { ReactNode } from 'react';

View File

@@ -1,12 +1,12 @@
import { Button } from '@affine/component/ui/button';
import { ConfirmModal } from '@affine/component/ui/modal';
import { Tooltip } from '@affine/component/ui/tooltip';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils';
import { DeleteIcon, ResetIcon } from '@blocksuite/icons';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtomValue } from 'jotai';
import { useCallback, useState } from 'react';

View File

@@ -9,13 +9,13 @@ import {
} from '@affine/component/page-list';
import { RenameModal } from '@affine/component/rename-modal';
import { Button, IconButton } from '@affine/component/ui/button';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { MoreHorizontalIcon, ViewLayersIcon } from '@blocksuite/icons';
import type { PageMeta, Workspace } from '@blocksuite/store';
import { useDroppable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useCallback, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';

View File

@@ -1,10 +1,10 @@
import { MenuItem as CollectionItem } from '@affine/component/app-sidebar';
import { useBlockSuitePageReferences } from '@affine/core/hooks/use-block-suite-page-references';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import type { PageMeta, Workspace } from '@blocksuite/store';
import { useDraggable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useBlockSuitePageReferences } from '@toeverything/hooks/use-block-suite-page-references';
import { useAtomValue } from 'jotai/index';
import React, { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';

View File

@@ -1,8 +1,8 @@
import { toast } from '@affine/component';
import { RenameModal } from '@affine/component/rename-modal';
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@blocksuite/store';
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
import { useCallback, useState } from 'react';
import { AddFavouriteButton } from '../favorite/add-favourite-button';

View File

@@ -1,9 +1,9 @@
import { MenuLinkItem } from '@affine/component/app-sidebar';
import { useBlockSuitePageReferences } from '@affine/core/hooks/use-block-suite-page-references';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import { type PageMeta, type Workspace } from '@blocksuite/store';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useBlockSuitePageReferences } from '@toeverything/hooks/use-block-suite-page-references';
import { useAtomValue } from 'jotai/react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';

View File

@@ -1,8 +1,8 @@
import { IconButton } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { PlusIcon } from '@blocksuite/icons';
import type { Workspace } from '@blocksuite/store';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
import { usePageHelper } from '../../../blocksuite/block-suite-page-list/utils';

View File

@@ -1,6 +1,6 @@
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import type { PageMeta } from '@blocksuite/store';
import { useDroppable } from '@dnd-kit/core';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useMemo } from 'react';
import { getDropItemId } from '../../../../hooks/affine/use-sidebar-drag';

View File

@@ -1,9 +1,9 @@
import { MenuLinkItem } from '@affine/component/app-sidebar';
import { useBlockSuitePageReferences } from '@affine/core/hooks/use-block-suite-page-references';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import { useDraggable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useBlockSuitePageReferences } from '@toeverything/hooks/use-block-suite-page-references';
import { useAtomValue } from 'jotai/index';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';

View File

@@ -1,6 +1,10 @@
import { ScrollableContainer } from '@affine/component';
import { Divider } from '@affine/component/ui/divider';
import { WorkspaceList } from '@affine/component/workspace-list';
import {
useWorkspaceAvatar,
useWorkspaceName,
} from '@affine/core/hooks/use-workspace-info';
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { WorkspaceMetadata } from '@affine/workspace';
@@ -9,10 +13,6 @@ import {
workspaceListAtom,
} from '@affine/workspace/atom';
import type { DragEndEvent } from '@dnd-kit/core';
import {
useWorkspaceAvatar,
useWorkspaceName,
} from '@toeverything/hooks/use-workspace-info';
import { useAtomValue, useSetAtom } from 'jotai';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useSession } from 'next-auth/react';

View File

@@ -1,6 +1,8 @@
import { Avatar } from '@affine/component/ui/avatar';
import { Loading } from '@affine/component/ui/loading';
import { Tooltip } from '@affine/component/ui/tooltip';
import { useWorkspaceBlobObjectUrl } from '@affine/core/hooks/use-workspace-blob';
import { useWorkspaceInfo } from '@affine/core/hooks/use-workspace-info';
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { type SyncEngineStatus, SyncEngineStep } from '@affine/workspace';
@@ -12,8 +14,6 @@ import {
NoNetworkIcon,
UnsyncIcon,
} from '@blocksuite/icons';
import { useWorkspaceBlobObjectUrl } from '@toeverything/hooks/use-workspace-blob';
import { useWorkspaceInfo } from '@toeverything/hooks/use-workspace-info';
import { useAtomValue } from 'jotai';
import { debounce, mean } from 'lodash-es';
import {

View File

@@ -19,6 +19,7 @@ import {
} from '@affine/component/page-list';
import { Menu } from '@affine/component/ui/menu';
import { collectionsCRUDAtom } from '@affine/core/atoms/collections';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { apis, events } from '@affine/electron-api';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
@@ -26,7 +27,6 @@ import type { Workspace } from '@affine/workspace';
import { FolderIcon, SettingsIcon } from '@blocksuite/icons';
import { type Page } from '@blocksuite/store';
import { useDroppable } from '@dnd-kit/core';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtom, useAtomValue } from 'jotai';
import { nanoid } from 'nanoid';
import type { HTMLAttributes, ReactElement } from 'react';

View File

@@ -1,5 +1,5 @@
import { AppUpdaterButton } from '@affine/component/app-sidebar/app-updater-button';
import { useAppUpdater } from '@toeverything/hooks/use-app-updater';
import { useAppUpdater } from '@affine/core/hooks/use-app-updater';
export const UpdaterButton = () => {
const appUpdater = useAppUpdater();

View File

@@ -1,11 +1,11 @@
import { BrowserWarning } from '@affine/component/affine-banner';
import { LocalDemoTips } from '@affine/component/affine-banner';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@affine/workspace';
import { workspaceManagerAtom } from '@affine/workspace/atom';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback, useState } from 'react';

View File

@@ -1,13 +1,13 @@
import { AffineShapeIcon } from '@affine/component/page-list'; // TODO: import from page-list temporarily, need to defined common svg icon/images management.
import { Button } from '@affine/component/ui/button';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import {
waitForCurrentWorkspaceAtom,
workspaceManagerAtom,
} from '@affine/workspace/atom';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useWorkspaceStatus } from '@toeverything/hooks/use-workspace-status';
import { useAtomValue } from 'jotai';
import { useState } from 'react';

View File

@@ -0,0 +1,47 @@
/**
* @vitest-environment happy-dom
*/
import 'fake-indexeddb/auto';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import { Schema, Workspace } from '@blocksuite/store';
import { renderHook } from '@testing-library/react';
import { initEmptyPage } from '@toeverything/infra/blocksuite';
import { beforeEach, describe, expect, test } from 'vitest';
import { useBlockSuitePageMeta } from '../use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '../use-block-suite-workspace-helper';
let blockSuiteWorkspace: Workspace;
const schema = new Schema();
schema.register(AffineSchemas).register(__unstableSchemas);
beforeEach(async () => {
blockSuiteWorkspace = new Workspace({
id: 'test',
schema,
});
await initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
await initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
await initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
});
describe('useBlockSuiteWorkspaceHelper', () => {
test('should create page', () => {
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
const helperHook = renderHook(() =>
useBlockSuiteWorkspaceHelper(blockSuiteWorkspace)
);
const pageMetaHook = renderHook(() =>
useBlockSuitePageMeta(blockSuiteWorkspace)
);
expect(pageMetaHook.result.current.length).toBe(3);
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
const page = helperHook.result.current.createPage('page4');
expect(page.id).toBe('page4');
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(4);
pageMetaHook.rerender();
expect(pageMetaHook.result.current.length).toBe(4);
});
});

View File

@@ -0,0 +1,49 @@
/**
* @vitest-environment happy-dom
*/
import 'fake-indexeddb/auto';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import { assertExists } from '@blocksuite/global/utils';
import type { Page } from '@blocksuite/store';
import { Schema, Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import { renderHook } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';
import { beforeEach } from 'vitest';
import { useBlockSuiteWorkspacePageTitle } from '../use-block-suite-workspace-page-title';
let blockSuiteWorkspace: BlockSuiteWorkspace;
const schema = new Schema();
schema.register(AffineSchemas).register(__unstableSchemas);
beforeEach(async () => {
vi.useFakeTimers({ toFake: ['requestIdleCallback'] });
blockSuiteWorkspace = new BlockSuiteWorkspace({ id: 'test', schema });
const initPage = async (page: Page) => {
await page.waitForLoaded();
expect(page).not.toBeNull();
assertExists(page);
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(''),
});
const frameId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, frameId);
};
await initPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
await initPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
await initPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
});
describe('useBlockSuiteWorkspacePageTitle', () => {
test('basic', async () => {
const pageTitleHook = renderHook(() =>
useBlockSuiteWorkspacePageTitle(blockSuiteWorkspace, 'page0')
);
expect(pageTitleHook.result.current).toBe('Untitled');
blockSuiteWorkspace.setPageMeta('page0', { title: '1' });
pageTitleHook.rerender();
expect(pageTitleHook.result.current).toBe('1');
});
});

View File

@@ -0,0 +1,29 @@
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<AsyncErrorHandler>(
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<T extends any[]>(
callback: (...args: T) => Promise<void>,
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
);
}

View File

@@ -3,10 +3,10 @@ import {
type AllPageListConfig,
FavoriteTag,
} from '@affine/component/page-list';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import type { PageMeta } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtomValue } from 'jotai';
import { useCallback, useMemo } from 'react';

View File

@@ -1,9 +1,9 @@
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
} from '@affine/core/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@affine/core/hooks/use-block-suite-workspace-helper';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';

View File

@@ -1,5 +1,5 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { LOCALES, useI18N } from '@affine/i18n';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useMemo } from 'react';
export function useLanguageHelper() {

View File

@@ -1,10 +1,10 @@
import { toast } from '@affine/component';
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils';
import { EdgelessIcon, HistoryIcon, PageIcon } from '@blocksuite/icons';
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
import {
PreconditionStrategy,
registerAffineCommand,

View File

@@ -1,9 +1,9 @@
import { toast } from '@affine/component';
import type { DraggableTitleCellData } from '@affine/component/page-list';
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import type { DragEndEvent, UniqueIdentifier } from '@dnd-kit/core';
import { usePageMetaHelper } from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';

View File

@@ -1,5 +1,5 @@
import { useBlockSuiteWorkspacePage } from '@affine/core/hooks/use-block-suite-workspace-page';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { useBlockSuiteWorkspacePage } from '@toeverything/hooks/use-block-suite-workspace-page';
import { useAtomValue } from 'jotai';
import { currentPageIdAtom } from '../../atoms/mode';

View File

@@ -0,0 +1,198 @@
import { apis, events, type UpdateMeta } from '@affine/electron-api';
import { isBrowser } from '@affine/env/constant';
import { appSettingAtom } from '@toeverything/infra/atom';
import { atom, useAtom, useAtomValue } from 'jotai';
import { atomWithObservable, atomWithStorage } from 'jotai/utils';
import { useCallback, useState } from 'react';
import { Observable } from 'rxjs';
import { useAsyncCallback } from './affine-async-hooks';
function rpcToObservable<
T,
H extends () => Promise<T>,
E extends (callback: (t: T) => void) => () => void,
>(
initialValue: T | null,
{
event,
handler,
onSubscribe,
}: {
event?: E;
handler?: H;
onSubscribe?: () => void;
}
): Observable<T | null> {
return new Observable<T | null>(subscriber => {
subscriber.next(initialValue);
onSubscribe?.();
if (!isBrowser || !environment.isDesktop || !event) {
subscriber.complete();
return;
}
handler?.()
.then(t => {
subscriber.next(t);
})
.catch(err => {
subscriber.error(err);
});
return event(t => {
subscriber.next(t);
});
});
}
// download complete, ready to install
export const updateReadyAtom = atomWithObservable(() => {
return rpcToObservable(null as UpdateMeta | null, {
event: events?.updater.onUpdateReady,
});
});
// update available, but not downloaded yet
export const updateAvailableAtom = atomWithObservable(() => {
return rpcToObservable(null as UpdateMeta | null, {
event: events?.updater.onUpdateAvailable,
});
});
// downloading new update
export const downloadProgressAtom = atomWithObservable(() => {
return rpcToObservable(null as number | null, {
event: events?.updater.onDownloadProgress,
});
});
export const changelogCheckedAtom = atomWithStorage<Record<string, boolean>>(
'affine:client-changelog-checked',
{}
);
export const checkingForUpdatesAtom = atom(false);
export const currentVersionAtom = atom(async () => {
if (!isBrowser) {
return null;
}
const currentVersion = await apis?.updater.currentVersion();
return currentVersion;
});
const currentChangelogUnreadAtom = atom(
async get => {
if (!isBrowser) {
return false;
}
const mapping = get(changelogCheckedAtom);
const currentVersion = await get(currentVersionAtom);
if (currentVersion) {
return !mapping[currentVersion];
}
return false;
},
async (get, set, v: boolean) => {
const currentVersion = await get(currentVersionAtom);
if (currentVersion) {
set(changelogCheckedAtom, mapping => {
return {
...mapping,
[currentVersion]: v,
};
});
}
}
);
export const useAppUpdater = () => {
const [appQuitting, setAppQuitting] = useState(false);
const updateReady = useAtomValue(updateReadyAtom);
const [setting, setSetting] = useAtom(appSettingAtom);
const downloadProgress = useAtomValue(downloadProgressAtom);
const [changelogUnread, setChangelogUnread] = useAtom(
currentChangelogUnreadAtom
);
const [checkingForUpdates, setCheckingForUpdates] = useAtom(
checkingForUpdatesAtom
);
const quitAndInstall = useCallback(() => {
if (updateReady) {
setAppQuitting(true);
apis?.updater.quitAndInstall().catch(err => {
// TODO: add error toast here
console.error(err);
});
}
}, [updateReady]);
const checkForUpdates = useCallback(async () => {
if (checkingForUpdates) {
return;
}
setCheckingForUpdates(true);
try {
const updateInfo = await apis?.updater.checkForUpdates();
return updateInfo?.version ?? false;
} catch (err) {
console.error('Error checking for updates:', err);
return null;
} finally {
setCheckingForUpdates(false);
}
}, [checkingForUpdates, setCheckingForUpdates]);
const downloadUpdate = useCallback(() => {
apis?.updater.downloadUpdate().catch(err => {
console.error('Error downloading update:', err);
});
}, []);
const toggleAutoDownload = useCallback(
(enable: boolean) => {
setSetting({
autoDownloadUpdate: enable,
});
},
[setSetting]
);
const toggleAutoCheck = useCallback(
(enable: boolean) => {
setSetting({
autoCheckUpdate: enable,
});
},
[setSetting]
);
const openChangelog = useAsyncCallback(async () => {
window.open(runtimeConfig.changelogUrl, '_blank');
await setChangelogUnread(true);
}, [setChangelogUnread]);
const dismissChangelog = useAsyncCallback(async () => {
await setChangelogUnread(true);
}, [setChangelogUnread]);
return {
quitAndInstall,
checkForUpdates,
downloadUpdate,
toggleAutoDownload,
toggleAutoCheck,
appQuitting,
checkingForUpdates,
autoCheck: setting.autoCheckUpdate,
autoDownload: setting.autoDownloadUpdate,
changelogUnread,
openChangelog,
dismissChangelog,
updateReady,
updateAvailable: useAtomValue(updateAvailableAtom),
downloadProgress,
currentVersion: useAtomValue(currentVersionAtom),
};
};

View File

@@ -0,0 +1,15 @@
import type { AffineEditorContainer } from '@blocksuite/presets';
import { atom, type SetStateAction, useAtom } from 'jotai';
const activeEditorContainerAtom = atom<AffineEditorContainer | null>(null);
export function useActiveBlocksuiteEditor(): [
AffineEditorContainer | null,
React.Dispatch<SetStateAction<AffineEditorContainer | null>>,
] {
const [editorContainer, setEditorContainer] = useAtom(
activeEditorContainerAtom
);
return [editorContainer, setEditorContainer];
}

View File

@@ -0,0 +1,59 @@
import type { PageBlockModel } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import type { PageMeta, Workspace } from '@blocksuite/store';
import type { Atom } from 'jotai';
import { atom, useAtomValue } from 'jotai';
import { useMemo } from 'react';
const weakMap = new WeakMap<Workspace, Atom<PageMeta[]>>();
export function useBlockSuitePageMeta(
blockSuiteWorkspace: Workspace
): PageMeta[] {
if (!weakMap.has(blockSuiteWorkspace)) {
const baseAtom = atom<PageMeta[]>(blockSuiteWorkspace.meta.pageMetas);
weakMap.set(blockSuiteWorkspace, baseAtom);
baseAtom.onMount = set => {
set(blockSuiteWorkspace.meta.pageMetas);
const dispose = blockSuiteWorkspace.meta.pageMetasUpdated.on(() => {
set(blockSuiteWorkspace.meta.pageMetas);
});
return () => {
dispose.dispose();
};
};
}
return useAtomValue(weakMap.get(blockSuiteWorkspace) as Atom<PageMeta[]>);
}
export function usePageMetaHelper(blockSuiteWorkspace: Workspace) {
return useMemo(
() => ({
setPageTitle: (pageId: string, newTitle: string) => {
const page = blockSuiteWorkspace.getPage(pageId);
assertExists(page);
const pageBlock = page
.getBlockByFlavour('affine:page')
.at(0) as PageBlockModel;
assertExists(pageBlock);
page.transact(() => {
pageBlock.title.delete(0, pageBlock.title.length);
pageBlock.title.insert(newTitle, 0);
});
blockSuiteWorkspace.meta.setPageMeta(pageId, { title: newTitle });
},
setPageReadonly: (pageId: string, readonly: boolean) => {
const page = blockSuiteWorkspace.getPage(pageId);
assertExists(page);
page.awarenessStore.setReadonly(page, readonly);
},
setPageMeta: (pageId: string, pageMeta: Partial<PageMeta>) => {
blockSuiteWorkspace.meta.setPageMeta(pageId, pageMeta);
},
getPageMeta: (pageId: string) => {
return blockSuiteWorkspace.meta.getPageMeta(pageId);
},
}),
[blockSuiteWorkspace]
);
}

View File

@@ -0,0 +1,45 @@
import type { Page, Workspace } from '@blocksuite/store';
import { type Atom, atom, useAtomValue } from 'jotai';
import { useBlockSuiteWorkspacePage } from './use-block-suite-workspace-page';
const weakMap = new WeakMap<Page, Atom<string[]>>();
function getPageReferences(page: Page): string[] {
return Object.values(
page.workspace.indexer.backlink.linkIndexMap[page.id] ?? {}
).flatMap(linkNodes => linkNodes.map(linkNode => linkNode.pageId));
}
const getPageReferencesAtom = (page: Page | null) => {
if (!page) {
return atom([]);
}
if (!weakMap.has(page)) {
const baseAtom = atom<string[]>([]);
baseAtom.onMount = set => {
const disposables = [
page.slots.ready.on(() => {
set(getPageReferences(page));
}),
page.workspace.indexer.backlink.slots.indexUpdated.on(() => {
set(getPageReferences(page));
}),
];
set(getPageReferences(page));
return () => {
disposables.forEach(disposable => disposable.dispose());
};
};
weakMap.set(page, baseAtom);
}
return weakMap.get(page) as Atom<string[]>;
};
export function useBlockSuitePageReferences(
blockSuiteWorkspace: Workspace,
pageId: string
): string[] {
const page = useBlockSuiteWorkspacePage(blockSuiteWorkspace, pageId);
return useAtomValue(getPageReferencesAtom(page));
}

View File

@@ -0,0 +1,13 @@
import type { Page, Workspace } from '@blocksuite/store';
import { useMemo } from 'react';
export function useBlockSuiteWorkspaceHelper(blockSuiteWorkspace: Workspace) {
return useMemo(
() => ({
createPage: (pageId?: string): Page => {
return blockSuiteWorkspace.createPage({ id: pageId });
},
}),
[blockSuiteWorkspace]
);
}

View File

@@ -0,0 +1,39 @@
import { assertExists } from '@blocksuite/global/utils';
import type { Workspace } from '@blocksuite/store';
import type { Atom } from 'jotai';
import { atom, useAtomValue } from 'jotai';
const weakMap = new WeakMap<Workspace, Map<string, Atom<string>>>();
function getAtom(w: Workspace, pageId: string): Atom<string> {
if (!weakMap.has(w)) {
weakMap.set(w, new Map());
}
const map = weakMap.get(w);
assertExists(map);
if (!map.has(pageId)) {
const baseAtom = atom<string>(w.getPage(pageId)?.meta.title || 'Untitled');
baseAtom.onMount = set => {
const disposable = w.meta.pageMetasUpdated.on(() => {
const page = w.getPage(pageId);
set(page?.meta.title || 'Untitled');
});
return () => {
disposable.dispose();
};
};
map.set(pageId, baseAtom);
return baseAtom;
} else {
return map.get(pageId) as Atom<string>;
}
}
export function useBlockSuiteWorkspacePageTitle(
blockSuiteWorkspace: Workspace,
pageId: string
) {
const titleAtom = getAtom(blockSuiteWorkspace, pageId);
assertExists(titleAtom);
return useAtomValue(titleAtom);
}

View File

@@ -0,0 +1,46 @@
import { DebugLogger } from '@affine/debug';
import { DisposableGroup } from '@blocksuite/global/utils';
import type { Page, Workspace } from '@blocksuite/store';
import { useEffect, useState } from 'react';
const logger = new DebugLogger('use-block-suite-workspace-page');
export function useBlockSuiteWorkspacePage(
blockSuiteWorkspace: Workspace,
pageId: string | null
): Page | null {
const [page, setPage] = useState(
pageId ? blockSuiteWorkspace.getPage(pageId) : null
);
useEffect(() => {
const group = new DisposableGroup();
group.add(
blockSuiteWorkspace.slots.pageAdded.on(id => {
if (pageId === id) {
setPage(blockSuiteWorkspace.getPage(id));
}
})
);
group.add(
blockSuiteWorkspace.slots.pageRemoved.on(id => {
if (pageId === id) {
setPage(null);
}
})
);
return () => {
group.dispose();
};
}, [blockSuiteWorkspace, pageId]);
useEffect(() => {
if (page && !page.loaded) {
page.load().catch(err => {
logger.error('Failed to load page', err);
});
}
}, [page]);
return page;
}

View File

@@ -0,0 +1,15 @@
import { useCallback, useSyncExternalStore } from 'react';
import type { DataSourceAdapter, Status } from 'y-provider';
type UIStatus =
| Status
| {
type: 'unknown';
};
export function useDataSourceStatus(provider: DataSourceAdapter): UIStatus {
return useSyncExternalStore(
provider.subscribeStatusChange,
useCallback(() => provider.status, [provider])
);
}

View File

@@ -1,6 +1,6 @@
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import type { GetPageInfoById } from '@affine/env/page-info';
import type { Workspace } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtomValue } from 'jotai';
import { useCallback, useMemo } from 'react';

View File

@@ -0,0 +1,73 @@
import 'foxact/use-debounced-state';
import { debounce } from 'lodash-es';
import { type RefObject, useEffect, useState } from 'react';
export function useIsTinyScreen({
container,
leftStatic,
leftSlot,
centerDom,
rightSlot,
}: {
container: HTMLElement | null;
leftStatic: RefObject<HTMLElement>;
leftSlot: RefObject<HTMLElement>[];
centerDom: RefObject<HTMLElement>;
rightSlot: RefObject<HTMLElement>[];
}) {
const [isTinyScreen, setIsTinyScreen] = useState(false);
useEffect(() => {
if (!container) {
return;
}
const handleResize = debounce(() => {
if (!centerDom.current) {
return;
}
const leftStaticWidth = leftStatic.current?.clientWidth || 0;
const leftSlotWidth = leftSlot.reduce((accWidth, dom) => {
return accWidth + (dom.current?.clientWidth || 0);
}, 0);
const rightSlotWidth = rightSlot.reduce((accWidth, dom) => {
return accWidth + (dom.current?.clientWidth || 0);
}, 0);
if (!leftSlotWidth && !rightSlotWidth) {
if (isTinyScreen) {
setIsTinyScreen(false);
}
return;
}
const containerRect = container.getBoundingClientRect();
const centerRect = centerDom.current.getBoundingClientRect();
if (
leftStaticWidth + leftSlotWidth + containerRect.left >=
centerRect.left ||
containerRect.right - centerRect.right <= rightSlotWidth
) {
setIsTinyScreen(true);
} else {
setIsTinyScreen(false);
}
}, 100);
handleResize();
const resizeObserver = new ResizeObserver(() => {
handleResize();
});
resizeObserver.observe(container);
return () => {
resizeObserver.unobserve(container);
};
}, [centerDom, isTinyScreen, leftSlot, leftStatic, container, rightSlot]);
return isTinyScreen;
}

View File

@@ -1,5 +1,5 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { type SubscriptionQuery, subscriptionQuery } from '@affine/graphql';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useSelfHosted } from './affine/use-server-config';
import { useQuery } from './use-query';

View File

@@ -0,0 +1,40 @@
import { workspaceManagerAtom } from '@affine/workspace/atom';
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
import { useAtomValue } from 'jotai';
import { useEffect, useState } from 'react';
export function useWorkspaceBlobObjectUrl(
meta?: WorkspaceMetadata,
blobKey?: string | null
) {
const workspaceManager = useAtomValue(workspaceManagerAtom);
const [blob, setBlob] = useState<string | undefined>(undefined);
useEffect(() => {
setBlob(undefined);
if (!blobKey || !meta) {
return;
}
let canceled = false;
let objectUrl: string = '';
workspaceManager
.getWorkspaceBlob(meta, blobKey)
.then(blob => {
if (blob && !canceled) {
objectUrl = URL.createObjectURL(blob);
setBlob(objectUrl);
}
})
.catch(err => {
console.error('get workspace blob error: ' + err);
});
return () => {
canceled = true;
URL.revokeObjectURL(objectUrl);
};
}, [meta, blobKey, workspaceManager]);
return blob;
}

View File

@@ -0,0 +1,38 @@
import type { WorkspaceMetadata } from '@affine/workspace';
import { workspaceManagerAtom } from '@affine/workspace/atom';
import { useAtomValue } from 'jotai';
import { useEffect, useState } from 'react';
import { useWorkspaceBlobObjectUrl } from './use-workspace-blob';
export function useWorkspaceInfo(meta: WorkspaceMetadata) {
const workspaceManager = useAtomValue(workspaceManagerAtom);
const [information, setInformation] = useState(
() => workspaceManager.list.getInformation(meta).info
);
useEffect(() => {
const information = workspaceManager.list.getInformation(meta);
setInformation(information.info);
return information.onUpdated.on(info => {
setInformation(info);
}).dispose;
}, [meta, workspaceManager]);
return information;
}
export function useWorkspaceName(meta: WorkspaceMetadata) {
const information = useWorkspaceInfo(meta);
return information?.name;
}
export function useWorkspaceAvatar(meta: WorkspaceMetadata) {
const information = useWorkspaceInfo(meta);
const avatar = useWorkspaceBlobObjectUrl(meta, information?.avatar);
return avatar;
}

View File

@@ -0,0 +1,34 @@
import type { Workspace, WorkspaceStatus } from '@affine/workspace';
import { useEffect, useState } from 'react';
export function useWorkspaceStatus<
Selector extends ((status: WorkspaceStatus) => any) | undefined | null,
Status = Selector extends (status: WorkspaceStatus) => any
? ReturnType<Selector>
: WorkspaceStatus,
>(workspace?: Workspace | null, selector?: Selector): Status | null {
// avoid re-render when selector is changed
const [cachedSelector] = useState(() => selector);
const [status, setStatus] = useState<Status | null>(() => {
if (!workspace) {
return null;
}
return cachedSelector ? cachedSelector(workspace.status) : workspace.status;
});
useEffect(() => {
if (!workspace) {
setStatus(null);
return;
}
setStatus(
cachedSelector ? cachedSelector(workspace.status) : workspace.status
);
return workspace.onStatusChange.on(status =>
setStatus(cachedSelector ? cachedSelector(status) : status)
).dispose;
}, [cachedSelector, workspace]);
return status;
}

View File

@@ -0,0 +1,28 @@
import type { Workspace } from '@affine/workspace';
import { workspaceManagerAtom } from '@affine/workspace/atom';
import type { WorkspaceMetadata } from '@affine/workspace/metadata';
import { useAtomValue } from 'jotai';
import { useEffect, useState } from 'react';
/**
* definitely be careful when using this hook, open workspace is a heavy operation
*/
export function useWorkspace(meta?: WorkspaceMetadata | null) {
const workspaceManager = useAtomValue(workspaceManagerAtom);
const [workspace, setWorkspace] = useState<Workspace | null>(null);
useEffect(() => {
if (!meta) {
setWorkspace(null); // set to null if meta is null or undefined
return;
}
const ref = workspaceManager.use(meta);
setWorkspace(ref.workspace);
return () => {
ref.release();
};
}, [meta, workspaceManager]);
return workspace;
}

View File

@@ -7,6 +7,8 @@ import {
PageListDragOverlay,
} from '@affine/component/page-list';
import { MainContainer, WorkspaceFallback } from '@affine/component/workspace';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils';
import {
@@ -18,8 +20,6 @@ import {
useSensor,
useSensors,
} from '@dnd-kit/core';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useWorkspaceStatus } from '@toeverything/hooks/use-workspace-status';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import type { PropsWithChildren, ReactNode } from 'react';
import { lazy, Suspense, useCallback, useEffect, useState } from 'react';

View File

@@ -1,5 +1,5 @@
import { NotFoundPage } from '@affine/component/not-found-page';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useSession } from 'next-auth/react';
import type { ReactElement } from 'react';

View File

@@ -9,6 +9,7 @@ import {
useCollectionManager,
VirtualizedPageList,
} from '@affine/component/page-list';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
@@ -19,7 +20,6 @@ import {
ViewLayersIcon,
} from '@blocksuite/icons';
import type { PageMeta, Workspace } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import clsx from 'clsx';
import { useAtomValue, useSetAtom } from 'jotai';
import {

View File

@@ -10,6 +10,7 @@ import {
useEditCollection,
} from '@affine/component/page-list';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import type { Collection } from '@affine/env/filter';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
@@ -20,7 +21,6 @@ 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';

View File

@@ -4,13 +4,13 @@ import {
useCollectionManager,
} from '@affine/component/page-list';
import { ResizePanel } from '@affine/component/resize-panel';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useWorkspaceStatus } from '@affine/core/hooks/use-workspace-status';
import { WorkspaceSubPath } from '@affine/env/workspace';
import { globalBlockSuiteSchema, SyncEngineStep } from '@affine/workspace';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { Page, Workspace } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { useWorkspaceStatus } from '@toeverything/hooks/use-workspace-status';
import { appSettingAtom } from '@toeverything/infra/atom';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import {

View File

@@ -1,7 +1,7 @@
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
import { assertExists } from '@blocksuite/global/utils';
import { AiIcon } from '@blocksuite/icons';
import { CopilotPanel } from '@blocksuite/presets';
import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor';
import { useCallback, useRef } from 'react';
import type { EditorExtension } from '../types';

View File

@@ -1,7 +1,7 @@
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
import { assertExists } from '@blocksuite/global/utils';
import { FrameIcon } from '@blocksuite/icons';
import { FramePanel } from '@blocksuite/presets';
import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor';
import { useCallback, useRef } from 'react';
import type { EditorExtension } from '../types';

View File

@@ -1,7 +1,7 @@
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
import { assertExists } from '@blocksuite/global/utils';
import { TocIcon } from '@blocksuite/icons';
import { TOCPanel } from '@blocksuite/presets';
import { useActiveBlocksuiteEditor } from '@toeverything/hooks/use-block-suite-editor';
import { useCallback, useRef } from 'react';
import type { EditorExtension } from '../types';

View File

@@ -1,4 +1,5 @@
import { WorkspaceFallback } from '@affine/component/workspace';
import { useWorkspace } from '@affine/core/hooks/use-workspace';
import { type Workspace } from '@affine/workspace';
import {
currentWorkspaceAtom,
@@ -6,7 +7,6 @@ import {
workspaceListLoadingStatusAtom,
workspaceManagerAtom,
} from '@affine/workspace/atom';
import { useWorkspace } from '@toeverything/hooks/use-workspace';
import { useAtom, useAtomValue } from 'jotai';
import { type ReactElement, Suspense, useEffect, useMemo } from 'react';
import { Outlet, useParams } from 'react-router-dom';

View File

@@ -4,12 +4,12 @@ import {
TrashOperationCell,
VirtualizedPageList,
} from '@affine/component/page-list';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils';
import { DeleteIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { getCurrentStore } from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';

View File

@@ -1,3 +1,4 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
import {
currentWorkspaceAtom,
@@ -5,7 +6,6 @@ import {
workspaceListAtom,
} from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtom, useAtomValue } from 'jotai';
import type { ReactElement } from 'react';
import { lazy, Suspense, useCallback } from 'react';

View File

@@ -1,9 +1,8 @@
import '@toeverything/hooks/use-affine-ipc-renderer';
import { pushNotificationAtom } from '@affine/component/notification-center';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { affine } from '@affine/electron-api';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CLOUD_WORKSPACE_CHANGED_BROADCAST_CHANNEL_KEY } from '@affine/workspace';
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';
@@ -51,7 +50,7 @@ const SessionDefence = (props: PropsWithChildren) => {
});
if (environment.isDesktop) {
window.affine.ipcRenderer.send('affine:login');
affine?.ipcRenderer.send('affine:login');
}
}
prevSession.current = session;

View File

@@ -14,9 +14,6 @@
{
"path": "../../frontend/graphql"
},
{
"path": "../../frontend/hooks"
},
{
"path": "../../frontend/i18n"
},
@@ -32,6 +29,9 @@
{
"path": "../../common/env"
},
{
"path": "../../common/y-indexeddb"
},
{
"path": "./tsconfig.node.json"
},