diff --git a/apps/core/package.json b/apps/core/package.json index 14854e4d11..c6ff36e808 100644 --- a/apps/core/package.json +++ b/apps/core/package.json @@ -50,7 +50,6 @@ "jotai": "^2.4.0", "jotai-devtools": "^0.6.2", "lit": "^2.8.0", - "lodash.debounce": "^4.0.8", "lottie-web": "^5.12.2", "mini-css-extract-plugin": "^2.7.6", "next-auth": "^4.22.1", @@ -77,7 +76,6 @@ "@svgr/webpack": "^8.1.0", "@swc/core": "^1.3.80", "@types/lodash-es": "^4.17.8", - "@types/lodash.debounce": "^4.0.7", "@types/webpack-env": "^1.18.1", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", diff --git a/apps/core/src/atoms/element.ts b/apps/core/src/atoms/element.ts new file mode 100644 index 0000000000..2f33acdc67 --- /dev/null +++ b/apps/core/src/atoms/element.ts @@ -0,0 +1,5 @@ +import { atom } from 'jotai/vanilla'; + +export const appHeaderAtom = atom(null); + +export const mainContainerAtom = atom(null); diff --git a/apps/core/src/components/pure/header/index.tsx b/apps/core/src/components/pure/header/index.tsx index 78eec79d44..10c27a4fbd 100644 --- a/apps/core/src/components/pure/header/index.tsx +++ b/apps/core/src/components/pure/header/index.tsx @@ -5,112 +5,40 @@ import { SidebarSwitch, } from '@affine/component/app-sidebar'; import { isDesktop } from '@affine/env/constant'; +import { useIsTinyScreen } from '@toeverything/hooks/use-is-tiny-screen'; import clsx from 'clsx'; -import { useAtomValue } from 'jotai'; -import debounce from 'lodash.debounce'; -import type { MutableRefObject, ReactNode } from 'react'; -import { useEffect, useRef, useState } from 'react'; +import { type Atom, useAtomValue } from 'jotai'; +import type { ReactElement } from 'react'; +import { forwardRef, useRef } from 'react'; import * as style from './style.css'; import { TopTip } from './top-tip'; import { WindowsAppControls } from './windows-app-controls'; + interface HeaderPros { - left?: ReactNode; - right?: ReactNode; - center?: ReactNode; + left?: ReactElement; + right?: ReactElement; + center?: ReactElement; + mainContainerAtom: Atom; } -const useIsTinyScreen = ({ - mainContainer, - leftStatic, - leftSlot, - centerDom, - rightStatic, - rightSlot, -}: { - mainContainer: HTMLElement; - leftStatic: MutableRefObject; - leftSlot: MutableRefObject[]; - centerDom: MutableRefObject; - rightStatic: MutableRefObject; - rightSlot: MutableRefObject[]; -}) => { - const [isTinyScreen, setIsTinyScreen] = useState(false); - - useEffect(() => { - 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 rightStaticWidth = rightStatic.current?.clientWidth || 0; - - const rightSlotWidth = rightSlot.reduce((accWidth, dom) => { - return accWidth + (dom.current?.clientWidth || 0); - }, 0); - - if (!leftSlotWidth && !rightSlotWidth) { - if (isTinyScreen) { - setIsTinyScreen(false); - } - return; - } - - const containerRect = mainContainer.getBoundingClientRect(); - const centerRect = centerDom.current.getBoundingClientRect(); - - if ( - leftStaticWidth + leftSlotWidth + containerRect.left >= - centerRect.left || - containerRect.right - centerRect.right <= - rightSlotWidth + rightStaticWidth - ) { - setIsTinyScreen(true); - } else { - setIsTinyScreen(false); - } - }, 100); - - handleResize(); - - const resizeObserver = new ResizeObserver(() => { - handleResize(); - }); - - resizeObserver.observe(mainContainer); - - return () => { - resizeObserver.disconnect(); - }; - }, [ - centerDom, - isTinyScreen, - leftSlot, - leftStatic, - mainContainer, - rightSlot, - rightStatic, - ]); - - return isTinyScreen; -}; - // The Header component is used to solve the following problems // 1. Manage layout issues independently of page or business logic // 2. Dynamic centered middle element (relative to the main-container), when the middle element is detected to collide with the two elements, the line wrapping process is performed -export const Header = ({ left, center, right }: HeaderPros) => { +export const Header = forwardRef(function Header( + { left, center, right, mainContainerAtom }, + ref +) { const sidebarSwitchRef = useRef(null); const leftSlotRef = useRef(null); const centerSlotRef = useRef(null); const rightSlotRef = useRef(null); const windowControlsRef = useRef(null); + const mainContainer = useAtomValue(mainContainerAtom); + const isTinyScreen = useIsTinyScreen({ - mainContainer: document.querySelector('.main-container') || document.body, + mainContainer, leftStatic: sidebarSwitchRef, leftSlot: [leftSlotRef], centerDom: centerSlotRef, @@ -130,6 +58,7 @@ export const Header = ({ left, center, right }: HeaderPros) => { data-open={open} data-sidebar-floating={appSidebarFloating} data-testid="header" + ref={ref} >
{
); -}; +}); + +Header.displayName = 'Header'; diff --git a/apps/core/src/components/workspace-header.tsx b/apps/core/src/components/workspace-header.tsx index 49b4feeac8..6b22db1be8 100644 --- a/apps/core/src/components/workspace-header.tsx +++ b/apps/core/src/components/workspace-header.tsx @@ -4,6 +4,7 @@ import { SaveCollectionButton, useCollectionManager, } from '@affine/component/page-list'; +import { Unreachable } from '@affine/env/constant'; import type { Collection } from '@affine/env/filter'; import type { PropertiesMeta } from '@affine/env/filter'; import { @@ -11,8 +12,10 @@ import { type WorkspaceHeaderProps, } from '@affine/env/workspace'; import { WorkspaceSubPath } from '@affine/env/workspace'; +import { useSetAtom } from 'jotai/react'; import { useCallback } from 'react'; +import { appHeaderAtom, mainContainerAtom } from '../atoms/element'; import { useGetPageInfoById } from '../hooks/use-get-page-info'; import { useWorkspace } from '../hooks/use-workspace'; import { SharePageModal } from './affine/share-page-modal'; @@ -76,6 +79,7 @@ export function WorkspaceHeader({ currentEntry, }: WorkspaceHeaderProps) { const setting = useCollectionManager(currentWorkspaceId); + const setAppHeader = useSetAtom(appHeaderAtom); const currentWorkspace = useWorkspace(currentWorkspaceId); const getPageInfoById = useGetPageInfoById( @@ -90,6 +94,8 @@ export function WorkspaceHeader({ return ( <>
} />; + return ( +
} + /> + ); } // route in edit page @@ -128,6 +140,8 @@ export function WorkspaceHeader({ ) : null; return (
{ const location = useLocation(); const { pageId } = useParams(); + const setMainContainer = useSetAtom(mainContainerAtom); + return ( <> {/* This DndContext is used for drag page from all-pages list into a folder in sidebar */} @@ -234,8 +237,11 @@ export const WorkspaceLayoutInner = ({ children }: PropsWithChildren) => { paths={pathGenerator} /> - }> - + }> + {children} diff --git a/apps/core/src/utils/toast.ts b/apps/core/src/utils/toast.ts index 657c391901..4e951259c1 100644 --- a/apps/core/src/utils/toast.ts +++ b/apps/core/src/utils/toast.ts @@ -1,13 +1,19 @@ import type { ToastOptions } from '@affine/component'; import { toast as basicToast } from '@affine/component'; +import { assertEquals, assertExists } from '@blocksuite/global/utils'; +import { getCurrentStore } from '@toeverything/infra/atom'; + +import { mainContainerAtom } from '../atoms/element'; export const toast = (message: string, options?: ToastOptions) => { + const mainContainer = getCurrentStore().get(mainContainerAtom); const modal = document.querySelector( '[role=presentation]' - ) as HTMLElement | null; - const mainContainer = document.querySelector( - '.main-container' - ) as HTMLElement | null; + ) as HTMLDivElement | null; + assertExists(mainContainer, 'main container should exist'); + if (modal) { + assertEquals(modal.constructor, HTMLDivElement, 'modal should be div'); + } return basicToast(message, { portal: modal || mainContainer || document.body, ...options, diff --git a/packages/component/src/components/workspace/index.tsx b/packages/component/src/components/workspace/index.tsx index e757c7fce0..157df78619 100644 --- a/packages/component/src/components/workspace/index.tsx +++ b/packages/component/src/components/workspace/index.tsx @@ -1,5 +1,6 @@ import { clsx } from 'clsx'; import type { HTMLAttributes, PropsWithChildren, ReactElement } from 'react'; +import { forwardRef } from 'react'; import { AppSidebarFallback } from '../app-sidebar'; import { appStyle, mainContainerStyle, toolStyle } from './index.css'; @@ -36,23 +37,27 @@ export interface MainContainerProps extends HTMLAttributes { padding?: boolean; } -export const MainContainer = ({ - className, - padding, - children, - ...props -}: PropsWithChildren): ReactElement => { +export const MainContainer = forwardRef< + HTMLDivElement, + PropsWithChildren +>(function MainContainer( + { className, padding, children, ...props }, + ref +): ReactElement { return (
{children}
); -}; +}); + +MainContainer.displayName = 'MainContainer'; export const ToolContainer = (props: PropsWithChildren): ReactElement => { return
{props.children}
; diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 086b476e07..aa904178f0 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -6,7 +6,8 @@ }, "private": true, "dependencies": { - "foxact": "^0.2.20" + "foxact": "^0.2.20", + "lodash.debounce": "^4.0.8" }, "devDependencies": { "@affine/env": "workspace:*", @@ -16,7 +17,8 @@ "@blocksuite/editor": "0.0.0-20230828163942-e5356e86-nightly", "@blocksuite/global": "0.0.0-20230828163942-e5356e86-nightly", "@blocksuite/lit": "0.0.0-20230828163942-e5356e86-nightly", - "@blocksuite/store": "0.0.0-20230828163942-e5356e86-nightly" + "@blocksuite/store": "0.0.0-20230828163942-e5356e86-nightly", + "@types/lodash.debounce": "^4.0.7" }, "peerDependencies": { "@affine/y-provider": "workspace:*", diff --git a/packages/hooks/src/use-is-tiny-screen.ts b/packages/hooks/src/use-is-tiny-screen.ts new file mode 100644 index 0000000000..185d5fb8b4 --- /dev/null +++ b/packages/hooks/src/use-is-tiny-screen.ts @@ -0,0 +1,86 @@ +import 'foxact/use-debounced-state'; + +import debounce from 'lodash.debounce'; +import { type RefObject, useEffect, useState } from 'react'; + +export function useIsTinyScreen({ + mainContainer, + leftStatic, + leftSlot, + centerDom, + rightStatic, + rightSlot, +}: { + mainContainer: HTMLElement | null; + leftStatic: RefObject; + leftSlot: RefObject[]; + centerDom: RefObject; + rightStatic: RefObject; + rightSlot: RefObject[]; +}) { + const [isTinyScreen, setIsTinyScreen] = useState(false); + + useEffect(() => { + if (!mainContainer) { + 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 rightStaticWidth = rightStatic.current?.clientWidth || 0; + + const rightSlotWidth = rightSlot.reduce((accWidth, dom) => { + return accWidth + (dom.current?.clientWidth || 0); + }, 0); + + if (!leftSlotWidth && !rightSlotWidth) { + if (isTinyScreen) { + setIsTinyScreen(false); + } + return; + } + + const containerRect = mainContainer.getBoundingClientRect(); + const centerRect = centerDom.current.getBoundingClientRect(); + + if ( + leftStaticWidth + leftSlotWidth + containerRect.left >= + centerRect.left || + containerRect.right - centerRect.right <= + rightSlotWidth + rightStaticWidth + ) { + setIsTinyScreen(true); + } else { + setIsTinyScreen(false); + } + }, 100); + + handleResize(); + + const resizeObserver = new ResizeObserver(() => { + handleResize(); + }); + + resizeObserver.observe(mainContainer); + + return () => { + resizeObserver.disconnect(); + }; + }, [ + centerDom, + isTinyScreen, + leftSlot, + leftStatic, + mainContainer, + rightSlot, + rightStatic, + ]); + + return isTinyScreen; +} diff --git a/yarn.lock b/yarn.lock index 7dfbf1e9fb..8d395782d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -262,7 +262,6 @@ __metadata: "@swc/core": ^1.3.80 "@toeverything/components": ^0.0.24 "@types/lodash-es": ^4.17.8 - "@types/lodash.debounce": ^4.0.7 "@types/webpack-env": ^1.18.1 async-call-rpc: ^6.3.1 cmdk: ^0.2.0 @@ -278,7 +277,6 @@ __metadata: jotai-devtools: ^0.6.2 lit: ^2.8.0 lodash-es: ^4.17.21 - lodash.debounce: ^4.0.8 lottie-web: ^5.12.2 mime-types: ^2.1.35 mini-css-extract-plugin: ^2.7.6 @@ -12662,7 +12660,9 @@ __metadata: "@blocksuite/global": 0.0.0-20230828163942-e5356e86-nightly "@blocksuite/lit": 0.0.0-20230828163942-e5356e86-nightly "@blocksuite/store": 0.0.0-20230828163942-e5356e86-nightly + "@types/lodash.debounce": ^4.0.7 foxact: ^0.2.20 + lodash.debounce: ^4.0.8 peerDependencies: "@affine/y-provider": "workspace:*" "@blocksuite/block-std": "*"