mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-15 00:56:26 +08:00
feat(core): pdf embed view component (#8671)
Closes: [AF-1445](https://linear.app/affine-design/issue/AF-1445/添加-pdf-embed-view-component) Upstreams: https://github.com/toeverything/blocksuite/pull/8658, https://github.com/toeverything/blocksuite/pull/8752 ### What's Changed * uses `IntersectionObserver` to determine lazy loading * adds `PDFViewerEmbedded` component * automatically shut down workers when out of view <div class='graphite__hidden'> <div>🎥 Video uploaded on Graphite:</div> <a href="https://app.graphite.dev/media/video/8ypiIKZXudF5a0tIgIzf/4181bec6-83fd-42f1-bfea-87276e0bd9e6.mov"> <img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/8ypiIKZXudF5a0tIgIzf/4181bec6-83fd-42f1-bfea-87276e0bd9e6.mov"> </a> </div> <video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/8ypiIKZXudF5a0tIgIzf/4181bec6-83fd-42f1-bfea-87276e0bd9e6.mov">Screen Recording 2024-11-15 at 08.14.34.mov</video>
This commit is contained in:
@@ -103,8 +103,9 @@ class PDFRendererBackend extends OpConsumer<ClientOps> {
|
||||
|
||||
if (!page) return;
|
||||
|
||||
const width = Math.ceil(opts.width * (opts.scale ?? 1));
|
||||
const height = Math.ceil(opts.height * (opts.scale ?? 1));
|
||||
const scale = opts.scale ?? 1;
|
||||
const width = Math.ceil(opts.width * scale);
|
||||
const height = Math.ceil(opts.height * scale);
|
||||
|
||||
const bitmap = viewer.createBitmap(width, height, 0);
|
||||
bitmap.fill(0, 0, width, height);
|
||||
@@ -119,9 +120,8 @@ class PDFRendererBackend extends OpConsumer<ClientOps> {
|
||||
);
|
||||
|
||||
const data = new Uint8ClampedArray(bitmap.toUint8Array());
|
||||
const imageBitmap = await createImageBitmap(
|
||||
new ImageData(data, width, height)
|
||||
);
|
||||
const imageData = new ImageData(data, width, height);
|
||||
const imageBitmap = await createImageBitmap(imageData);
|
||||
|
||||
bitmap.close();
|
||||
page.close();
|
||||
|
||||
@@ -113,3 +113,9 @@ export const LoadingSvg = memo(
|
||||
);
|
||||
|
||||
LoadingSvg.displayName = 'pdf-loading';
|
||||
|
||||
export const PDFPageCanvas = forwardRef<HTMLCanvasElement>((props, ref) => {
|
||||
return <canvas className={styles.pdfPageCanvas} ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
PDFPageCanvas.displayName = 'pdf-page-canvas';
|
||||
|
||||
@@ -4,6 +4,7 @@ export {
|
||||
ListPadding,
|
||||
ListWithSmallGap,
|
||||
LoadingSvg,
|
||||
PDFPageCanvas,
|
||||
type PDFVirtuosoContext,
|
||||
type PDFVirtuosoProps,
|
||||
Scroller,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { PDF } from '../entities/pdf';
|
||||
import type { PDFPage } from '../entities/pdf-page';
|
||||
import { LoadingSvg } from './components';
|
||||
import { LoadingSvg, PDFPageCanvas } from './components';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
interface PDFPageProps {
|
||||
@@ -34,6 +34,8 @@ export const PDFPageRenderer = ({
|
||||
const style = { width, aspectRatio: `${width} / ${height}` };
|
||||
|
||||
useEffect(() => {
|
||||
if (width * height === 0) return;
|
||||
|
||||
const { page, release } = pdf.page(pageNum, `${width}:${height}:${scale}`);
|
||||
setPdfPage(page);
|
||||
|
||||
@@ -41,6 +43,8 @@ export const PDFPageRenderer = ({
|
||||
}, [pdf, width, height, pageNum, scale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (width * height === 0) return;
|
||||
|
||||
pdfPage?.render({ width, height, scale });
|
||||
|
||||
return pdfPage?.render.unsubscribe;
|
||||
@@ -52,6 +56,7 @@ export const PDFPageRenderer = ({
|
||||
if (!img) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
if (width * height === 0) return;
|
||||
|
||||
canvas.width = width * scale;
|
||||
canvas.height = height * scale;
|
||||
@@ -74,11 +79,7 @@ export const PDFPageRenderer = ({
|
||||
style={style}
|
||||
onClick={() => onSelect?.(pageNum)}
|
||||
>
|
||||
{img === null ? (
|
||||
<LoadingSvg />
|
||||
) : (
|
||||
<canvas className={styles.pdfPageCanvas} ref={canvasRef} />
|
||||
)}
|
||||
{img === null ? <LoadingSvg /> : <PDFPageCanvas ref={canvasRef} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,18 +25,6 @@ export const virtuosoItem = style({
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
export const pdfPage = style({
|
||||
overflow: 'hidden',
|
||||
maxWidth: 'calc(100% - 40px)',
|
||||
background: cssVarV2('layer/white'),
|
||||
boxSizing: 'border-box',
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||
boxShadow:
|
||||
'0px 4px 20px 0px var(--transparent-black-200, rgba(0, 0, 0, 0.10))',
|
||||
});
|
||||
|
||||
export const pdfPageError = style({
|
||||
display: 'flex',
|
||||
alignSelf: 'center',
|
||||
@@ -62,4 +50,5 @@ export const pdfLoading = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxWidth: '537px',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
SurfaceRefBlockModel,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { AffineReference } from '@blocksuite/affine/blocks';
|
||||
import type { BlockModel } from '@blocksuite/affine/store';
|
||||
import type { Block, BlockModel } from '@blocksuite/affine/store';
|
||||
import type { AIChatBlockModel } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import type { TemplateResult } from 'lit';
|
||||
@@ -36,7 +36,8 @@ export type PeekViewElement =
|
||||
| HTMLElement
|
||||
| BlockComponent
|
||||
| AffineReference
|
||||
| HTMLAnchorElement;
|
||||
| HTMLAnchorElement
|
||||
| Block;
|
||||
|
||||
export interface PeekViewTarget {
|
||||
element?: PeekViewElement;
|
||||
@@ -184,7 +185,7 @@ function resolvePeekInfoFromPeekTarget(
|
||||
blockIds: [blockModel.id],
|
||||
},
|
||||
};
|
||||
} else if (isAIChatBlockModel(blockModel)) {
|
||||
} else if (isAIChatBlockModel(blockModel) && 'host' in element) {
|
||||
return {
|
||||
type: 'ai-chat-block',
|
||||
docRef: {
|
||||
|
||||
Reference in New Issue
Block a user