mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 20:38:52 +00:00
feat(core): center peek open doc should only load doc when idle (#10023)
fix AF-2183
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const overlayRef = useRef<HTMLDivElement>(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<void>(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<void>(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<
|
||||
<PeekViewModalOverlay
|
||||
ref={overlayRef}
|
||||
className={styles.modalOverlay}
|
||||
onAnimationStart={onAnimationStart}
|
||||
onAnimationEnd={onAnimateEnd}
|
||||
data-anime-state={animeState}
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -2,7 +2,7 @@ import { toReactNode } from '@affine/component';
|
||||
import { AIChatBlockPeekViewTemplate } from '@affine/core/blocksuite/presets/ai';
|
||||
import { BlockComponent } from '@blocksuite/affine/block-std';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { ActivePeekView } from '../entities/peek-view';
|
||||
import { PeekViewService } from '../services/peek-view';
|
||||
@@ -19,12 +19,12 @@ import {
|
||||
DocPeekViewControls,
|
||||
} from './peek-view-controls';
|
||||
|
||||
function renderPeekView({ info }: ActivePeekView) {
|
||||
function renderPeekView({ info }: ActivePeekView, animating?: boolean) {
|
||||
if (info.type === 'template') {
|
||||
return toReactNode(info.template);
|
||||
}
|
||||
if (info.type === 'doc') {
|
||||
return <DocPeekPreview docRef={info.docRef} />;
|
||||
return <DocPeekPreview docRef={info.docRef} animating={animating} />;
|
||||
}
|
||||
|
||||
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<PeekViewModalContainerProps> | 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}
|
||||
</PeekViewModalContainer>
|
||||
|
||||
@@ -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<Doc | null>(null);
|
||||
const [editor, setEditor] = useState<Editor | null>(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,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user