diff --git a/packages/frontend/core/src/modules/doc/services/docs.ts b/packages/frontend/core/src/modules/doc/services/docs.ts index 91312f6b1c..3520aebd13 100644 --- a/packages/frontend/core/src/modules/doc/services/docs.ts +++ b/packages/frontend/core/src/modules/doc/services/docs.ts @@ -64,6 +64,14 @@ export class DocsService extends Service { super(); } + loaded(docId: string) { + const exists = this.pool.get(docId); + if (exists) { + return { doc: exists.obj, release: exists.release }; + } + return null; + } + open(docId: string) { const docRecord = this.list.doc$(docId).value; if (!docRecord) { diff --git a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx index d0ab8e23ee..c255bf7b28 100644 --- a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx @@ -179,7 +179,13 @@ function DocPeekPreviewEditor({ ); } -export function DocPeekPreview({ docRef }: { docRef: DocReferenceInfo }) { +export function DocPeekPreview({ + docRef, + animating, +}: { + docRef: DocReferenceInfo; + animating?: boolean; +}) { const { docId, blockIds, @@ -204,7 +210,8 @@ export function DocPeekPreview({ docRef }: { docRef: DocReferenceInfo }) { databaseRowId, type: 'database', } - : undefined + : undefined, + !animating ); // if sync engine has been synced and the page is null, show 404 page. diff --git a/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx b/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx index 22e6d7a1bf..10cb7db364 100644 --- a/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx @@ -55,7 +55,7 @@ export type PeekViewModalContainerProps = PropsWithChildren<{ target?: HTMLElement; controls?: React.ReactNode; onAnimationStart?: () => void; - onAnimateEnd?: () => void; + onAnimationEnd?: () => void; mode?: PeekViewMode; animation?: PeekViewAnimation; testId?: string; @@ -76,7 +76,7 @@ export const PeekViewModalContainer = forwardRef< controls, children, onAnimationStart, - onAnimateEnd, + onAnimationEnd, animation = 'zoom', mode = 'fit', dialogFrame = true, @@ -84,9 +84,7 @@ export const PeekViewModalContainer = forwardRef< ref ) { const [vtOpen, setVtOpen] = useState(open); - const [animeState, setAnimeState] = useState<'idle' | 'ready' | 'animating'>( - 'idle' - ); + const [animeState, setAnimeState] = useState<'idle' | 'animating'>('idle'); const contentClipRef = useRef(null); const contentRef = useRef(null); const overlayRef = useRef(null); @@ -143,6 +141,7 @@ export const PeekViewModalContainer = forwardRef< if (!contentClip || !content || !target || !overlay) { resolve(); setAnimeState('idle'); + onAnimationEnd?.(); return; } const targets = contentClip; @@ -196,6 +195,7 @@ export const PeekViewModalContainer = forwardRef< complete: (ins: AnimeInstance) => { paramsMap?.contentWrapper?.complete?.(ins); setAnimeState('idle'); + onAnimationEnd?.(); overlay.style.pointerEvents = ''; if (zoomIn) { Object.assign(targets.style, { @@ -238,6 +238,7 @@ export const PeekViewModalContainer = forwardRef< */ const animateZoomIn = useCallback(() => { setAnimeState('animating'); + onAnimationStart?.(); setVtOpen(true); setTimeout(() => { zoomAnimate(true, { @@ -257,9 +258,10 @@ export const PeekViewModalContainer = forwardRef< // controls delay: to make sure the time interval for animations of dialog and controls is 150ms. 400 - 230 + 150 ); - }, [animateControls, zoomAnimate]); + }, [animateControls, onAnimationStart, zoomAnimate]); const animateZoomOut = useCallback(() => { setAnimeState('animating'); + onAnimationStart?.(); animateControls(false); zoomAnimate(false, { contentWrapper: { @@ -275,33 +277,38 @@ export const PeekViewModalContainer = forwardRef< }) .then(() => setVtOpen(false)) .catch(console.error); - }, [animateControls, zoomAnimate]); + }, [animateControls, onAnimationStart, zoomAnimate]); - const animateFade = useCallback((animateIn: boolean) => { - setAnimeState('animating'); - return new Promise(resolve => { - if (animateIn) setVtOpen(true); - setTimeout(() => { - const overlay = overlayRef.current; - const contentClip = contentClipRef.current; - if (!overlay || !contentClip) { - resolve(); - return; - } - anime({ - targets: [overlay, contentClip], - opacity: animateIn ? [0, 1] : [1, 0], - easing: 'easeOutQuad', - duration: 230, - complete: () => { - if (!animateIn) setVtOpen(false); - setAnimeState('idle'); + const animateFade = useCallback( + (animateIn: boolean) => { + setAnimeState('animating'); + onAnimationStart?.(); + return new Promise(resolve => { + if (animateIn) setVtOpen(true); + setTimeout(() => { + const overlay = overlayRef.current; + const contentClip = contentClipRef.current; + if (!overlay || !contentClip) { resolve(); - }, + return; + } + anime({ + targets: [overlay, contentClip], + opacity: animateIn ? [0, 1] : [1, 0], + easing: 'easeOutQuad', + duration: 230, + complete: () => { + if (!animateIn) setVtOpen(false); + setAnimeState('idle'); + onAnimationEnd?.(); + resolve(); + }, + }); }); }); - }); - }, []); + }, + [onAnimationEnd, onAnimationStart] + ); useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { @@ -332,8 +339,6 @@ export const PeekViewModalContainer = forwardRef<
; + return ; } if (info.type === 'attachment' && info.docRef.blockIds?.[0]) { @@ -77,13 +77,14 @@ const getMode = (info: ActivePeekView['info']) => { }; const getRendererProps = ( - activePeekView?: ActivePeekView + activePeekView?: ActivePeekView, + animating?: boolean ): Partial | undefined => { if (!activePeekView) { return; } - const preview = renderPeekView(activePeekView); + const preview = renderPeekView(activePeekView, animating); const controls = renderControls(activePeekView); return { children: preview, @@ -106,12 +107,24 @@ export const PeekViewManagerModal = () => { const activePeekView = useLiveData(peekViewEntity.active$); const show = useLiveData(peekViewEntity.show$); + const [animating, setAnimating] = useState(false); + + const onAnimationStart = useCallback(() => { + console.log('onAnimationStart'); + setAnimating(true); + }, []); + + const onAnimationEnd = useCallback(() => { + console.log('onAnimationEnd'); + setAnimating(false); + }, []); + const renderProps = useMemo(() => { if (!activePeekView) { return; } - return getRendererProps(activePeekView); - }, [activePeekView]); + return getRendererProps(activePeekView, animating); + }, [activePeekView, animating]); useEffect(() => { const subscription = peekViewEntity.show$.subscribe(() => { @@ -135,6 +148,8 @@ export const PeekViewManagerModal = () => { peekViewEntity.close(); } }} + onAnimationStart={onAnimationStart} + onAnimationEnd={onAnimationEnd} > {renderProps?.children} diff --git a/packages/frontend/core/src/modules/peek-view/view/utils.ts b/packages/frontend/core/src/modules/peek-view/view/utils.ts index ed452ef5fa..1e3f6d278a 100644 --- a/packages/frontend/core/src/modules/peek-view/view/utils.ts +++ b/packages/frontend/core/src/modules/peek-view/view/utils.ts @@ -12,11 +12,13 @@ export const useEditor = ( pageId: string, preferMode?: DocMode, preferSelector?: EditorSelector, - defaultOpenProperty?: DefaultOpenProperty + defaultOpenProperty?: DefaultOpenProperty, + canLoad?: boolean ) => { const currentWorkspace = useService(WorkspaceService).workspace; const docsService = useService(DocsService); const docRecordList = docsService.list; + const [loading, setLoading] = useState(false); const docListReady = useLiveData(docRecordList.isReady$); const docRecord = docRecordList.doc$(pageId).value; const preferModeRef = useRef(preferMode); @@ -25,16 +27,40 @@ export const useEditor = ( const [doc, setDoc] = useState(null); const [editor, setEditor] = useState(null); - useLayoutEffect(() => { + useEffect(() => { if (!docRecord) { return; } - const { doc: opened, release } = docsService.open(pageId); - setDoc(opened); + let canceled = false; + let release: () => void; + setLoading(true); + const loaded = docsService.loaded(pageId); + if (loaded) { + setDoc(loaded.doc); + release = loaded.release; + setLoading(false); + } else if (canLoad) { + requestIdleCallback( + () => { + if (canceled) { + return; + } + const { doc: opened, release: _release } = docsService.open(pageId); + setDoc(opened); + release = _release; + setLoading(false); + }, + { + timeout: 1000, + } + ); + } return () => { - release(); + canceled = true; + release?.(); + setLoading(false); }; - }, [docRecord, docsService, pageId]); + }, [canLoad, docRecord, docsService, pageId]); useLayoutEffect(() => { if (!doc) { @@ -55,5 +81,10 @@ export const useEditor = ( return currentWorkspace.engine.doc.addPriority(pageId, 10); }, [currentWorkspace, pageId]); - return { doc, editor, workspace: currentWorkspace, loading: !docListReady }; + return { + doc, + editor, + workspace: currentWorkspace, + loading: !docListReady || loading, + }; };