mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-18 18:41:52 +08:00
fix: surface-ref peek view (#7208)
### Change Add ref to `SurfaceRefPeekView`. It provide `fitViewportToTarget` method to fit the `surface-ref` content. Related to [AFF-1200](https://linear.app/affine-design/issue/AFF-1200/center-peek-frame-rendering-issue).
This commit is contained in:
@@ -9,7 +9,13 @@ import { DisposableGroup } from '@blocksuite/global/utils';
|
|||||||
import { type AffineEditorContainer, AIProvider } from '@blocksuite/presets';
|
import { type AffineEditorContainer, AIProvider } from '@blocksuite/presets';
|
||||||
import type { DocMode } from '@toeverything/infra';
|
import type { DocMode } from '@toeverything/infra';
|
||||||
import { DocsService, FrameworkScope, useService } from '@toeverything/infra';
|
import { DocsService, FrameworkScope, useService } from '@toeverything/infra';
|
||||||
import { forwardRef, useEffect, useState } from 'react';
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { WorkbenchService } from '../../workbench';
|
import { WorkbenchService } from '../../workbench';
|
||||||
import { PeekViewService } from '../services/peek-view';
|
import { PeekViewService } from '../services/peek-view';
|
||||||
@@ -113,6 +119,7 @@ const DocPreview = forwardRef<
|
|||||||
</AffineErrorBoundary>
|
</AffineErrorBoundary>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
DocPreview.displayName = 'DocPreview';
|
||||||
|
|
||||||
export const DocPeekView = ({
|
export const DocPeekView = ({
|
||||||
docId,
|
docId,
|
||||||
@@ -126,16 +133,42 @@ export const DocPeekView = ({
|
|||||||
return <DocPreview mode={mode} docId={docId} blockId={blockId} />;
|
return <DocPreview mode={mode} docId={docId} blockId={blockId} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SurfaceRefPeekView = ({
|
export type SurfaceRefPeekViewRef = {
|
||||||
docId,
|
fitViewportToTarget: () => void;
|
||||||
xywh,
|
};
|
||||||
}: {
|
|
||||||
docId: string;
|
export const SurfaceRefPeekView = forwardRef<
|
||||||
xywh: `[${number},${number},${number},${number}]`;
|
SurfaceRefPeekViewRef,
|
||||||
}) => {
|
{ docId: string; xywh: `[${number},${number},${number},${number}]` }
|
||||||
|
>(function SurfaceRefPeekView({ docId, xywh }, ref) {
|
||||||
const [editorRef, setEditorRef] = useState<AffineEditorContainer | null>(
|
const [editorRef, setEditorRef] = useState<AffineEditorContainer | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
const fitViewportToTarget = useCallback(() => {
|
||||||
|
if (!editorRef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewport = {
|
||||||
|
xywh: xywh,
|
||||||
|
padding: [60, 20, 20, 20] as [number, number, number, number],
|
||||||
|
};
|
||||||
|
const rootService =
|
||||||
|
editorRef.host.std.spec.getService<EdgelessRootService>('affine:page');
|
||||||
|
rootService.viewport.onResize();
|
||||||
|
rootService.viewport.setViewportByBound(
|
||||||
|
Bound.deserialize(viewport.xywh),
|
||||||
|
viewport.padding
|
||||||
|
);
|
||||||
|
}, [editorRef, xywh]);
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
fitViewportToTarget,
|
||||||
|
}),
|
||||||
|
[fitViewportToTarget]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
@@ -143,18 +176,7 @@ export const SurfaceRefPeekView = ({
|
|||||||
editorRef.host?.updateComplete
|
editorRef.host?.updateComplete
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
const viewport = {
|
fitViewportToTarget();
|
||||||
xywh: xywh,
|
|
||||||
padding: [60, 20, 20, 20] as [number, number, number, number],
|
|
||||||
};
|
|
||||||
const rootService =
|
|
||||||
editorRef.host.std.spec.getService<EdgelessRootService>(
|
|
||||||
'affine:page'
|
|
||||||
);
|
|
||||||
rootService.viewport.setViewportByBound(
|
|
||||||
Bound.deserialize(viewport.xywh),
|
|
||||||
viewport.padding
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
@@ -164,7 +186,8 @@ export const SurfaceRefPeekView = ({
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [editorRef, xywh]);
|
}, [editorRef, fitViewportToTarget]);
|
||||||
|
|
||||||
return <DocPreview ref={setEditorRef} docId={docId} mode={'edgeless'} />;
|
return <DocPreview ref={setEditorRef} docId={docId} mode={'edgeless'} />;
|
||||||
};
|
});
|
||||||
|
SurfaceRefPeekView.displayName = 'SurfaceRefPeekView';
|
||||||
|
|||||||
@@ -75,11 +75,6 @@ export const PeekViewModalContainer = ({
|
|||||||
}>) => {
|
}>) => {
|
||||||
const [{ status }, toggle] = useTransition({
|
const [{ status }, toggle] = useTransition({
|
||||||
timeout: animationTimeout,
|
timeout: animationTimeout,
|
||||||
onStateChange(event) {
|
|
||||||
if (event.current.status === 'exited' && onAnimateEnd) {
|
|
||||||
onAnimateEnd();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const [transformOrigin, setTransformOrigin] = useState<string | null>(null);
|
const [transformOrigin, setTransformOrigin] = useState<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -105,6 +100,9 @@ export const PeekViewModalContainer = ({
|
|||||||
[styles.transformOrigin]: transformOrigin,
|
[styles.transformOrigin]: transformOrigin,
|
||||||
[styles.animationTimeout]: `${animationTimeout}ms`,
|
[styles.animationTimeout]: `${animationTimeout}ms`,
|
||||||
})}
|
})}
|
||||||
|
onAnimationEnd={() => {
|
||||||
|
onAnimateEnd?.();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
data-peek-view-wrapper
|
data-peek-view-wrapper
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
|
import { BlockElement } from '@blocksuite/block-std';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import type { ActivePeekView } from '../entities/peek-view';
|
import type { ActivePeekView } from '../entities/peek-view';
|
||||||
import { PeekViewService } from '../services/peek-view';
|
import { PeekViewService } from '../services/peek-view';
|
||||||
import { DocPeekViewControls } from './doc-peek-controls';
|
import { DocPeekViewControls } from './doc-peek-controls';
|
||||||
|
import type { SurfaceRefPeekViewRef } from './doc-peek-view';
|
||||||
import { DocPeekView, SurfaceRefPeekView } from './doc-peek-view';
|
import { DocPeekView, SurfaceRefPeekView } from './doc-peek-view';
|
||||||
import { PeekViewModalContainer } from './modal-container';
|
import { PeekViewModalContainer } from './modal-container';
|
||||||
|
|
||||||
function renderPeekView({ info }: ActivePeekView) {
|
function renderPeekView(
|
||||||
|
{ info }: ActivePeekView,
|
||||||
|
refCallback: (editor: SurfaceRefPeekViewRef) => void
|
||||||
|
) {
|
||||||
if (info.mode === 'edgeless' && info.xywh) {
|
if (info.mode === 'edgeless' && info.xywh) {
|
||||||
return <SurfaceRefPeekView docId={info.docId} xywh={info.xywh} />;
|
return (
|
||||||
|
<SurfaceRefPeekView
|
||||||
|
ref={refCallback}
|
||||||
|
docId={info.docId}
|
||||||
|
xywh={info.xywh}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -31,15 +42,32 @@ export const PeekViewManagerModal = () => {
|
|||||||
const peekViewEntity = useService(PeekViewService).peekView;
|
const peekViewEntity = useService(PeekViewService).peekView;
|
||||||
const activePeekView = useLiveData(peekViewEntity.active$);
|
const activePeekView = useLiveData(peekViewEntity.active$);
|
||||||
const show = useLiveData(peekViewEntity.show$);
|
const show = useLiveData(peekViewEntity.show$);
|
||||||
|
const peekViewRef = useRef<SurfaceRefPeekViewRef | null>(null);
|
||||||
|
|
||||||
const preview = useMemo(() => {
|
const preview = useMemo(() => {
|
||||||
return activePeekView ? renderPeekView(activePeekView) : null;
|
return activePeekView
|
||||||
|
? renderPeekView(activePeekView, editor => {
|
||||||
|
peekViewRef.current = editor;
|
||||||
|
})
|
||||||
|
: null;
|
||||||
}, [activePeekView]);
|
}, [activePeekView]);
|
||||||
|
|
||||||
const controls = useMemo(() => {
|
const controls = useMemo(() => {
|
||||||
return activePeekView ? renderControls(activePeekView) : null;
|
return activePeekView ? renderControls(activePeekView) : null;
|
||||||
}, [activePeekView]);
|
}, [activePeekView]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = peekViewEntity.show$.subscribe(() => {
|
||||||
|
if (activePeekView?.target instanceof BlockElement) {
|
||||||
|
activePeekView.target.requestUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [activePeekView, peekViewEntity]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PeekViewModalContainer
|
<PeekViewModalContainer
|
||||||
open={show && !!preview}
|
open={show && !!preview}
|
||||||
@@ -49,15 +77,14 @@ export const PeekViewManagerModal = () => {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
controls={controls}
|
controls={controls}
|
||||||
// there is a bug for edgeless mode when showing the peek view during start up animation
|
|
||||||
hideOnEntering={
|
|
||||||
!activePeekView?.info.mode || activePeekView?.info.mode === 'edgeless'
|
|
||||||
}
|
|
||||||
onOpenChange={open => {
|
onOpenChange={open => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
peekViewEntity.close();
|
peekViewEntity.close();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onAnimateEnd={() => {
|
||||||
|
peekViewRef.current?.fitViewportToTarget();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{preview}
|
{preview}
|
||||||
</PeekViewModalContainer>
|
</PeekViewModalContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user