-
+
{state.meta.pageCount > 0 ? cursor + 1 : 0}
- /{state.meta.pageCount}
+ /{state.meta.pageCount}
:
}
@@ -299,7 +187,7 @@ function PDFViewerStatus({ pdf }: { pdf: PDF }) {
const state = useLiveData(pdf.state$);
if (state?.status !== PDFStatus.Opened) {
- return null;
+ return
;
}
return
;
@@ -317,7 +205,7 @@ export function PDFViewer({ model }: ViewerProps) {
}, [model, pdfService, setPdf]);
if (!pdf) {
- return null;
+ return
;
}
return
;
diff --git a/packages/frontend/core/src/components/attachment-viewer/styles.css.ts b/packages/frontend/core/src/components/attachment-viewer/styles.css.ts
index 0ba25f051447c..65525db4af6db 100644
--- a/packages/frontend/core/src/components/attachment-viewer/styles.css.ts
+++ b/packages/frontend/core/src/components/attachment-viewer/styles.css.ts
@@ -21,9 +21,12 @@ export const titlebar = style({
borderTopWidth: '0.5px',
borderTopStyle: 'solid',
borderTopColor: cssVarV2('layer/insideBorder/border'),
+ textWrap: 'nowrap',
+ overflow: 'hidden',
});
export const titlebarChild = style({
+ overflow: 'hidden',
selectors: {
[`${titlebar} > &`]: {
display: 'flex',
@@ -40,36 +43,10 @@ export const titlebarChild = style({
export const titlebarName = style({
display: 'flex',
-});
-
-export const body = style({
- position: 'relative',
- zIndex: 0,
- display: 'flex',
- flex: 1,
- selectors: {
- '&:before': {
- position: 'absolute',
- content: '',
- top: 0,
- right: 0,
- bottom: 0,
- left: 0,
- zIndex: -1,
- },
- '&:not(.gridding):before': {
- backgroundColor: cssVarV2('layer/background/secondary'),
- },
- '&.gridding:before': {
- opacity: 0.25,
- backgroundSize: '20px 20px',
- backgroundImage: `linear-gradient(${cssVarV2('button/grabber/default')} 1px, transparent 1px), linear-gradient(to right, ${cssVarV2('button/grabber/default')} 1px, transparent 1px)`,
- },
- },
-});
-
-export const virtuoso = style({
- width: '100%',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'pre',
+ wordWrap: 'break-word',
});
export const error = style({
@@ -101,22 +78,46 @@ export const errorBtns = style({
marginTop: '28px',
});
-export const mainItemWrapper = style({
+export const viewer = style({
+ position: 'relative',
+ zIndex: 0,
display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- margin: '20px auto',
+ flex: 1,
+ overflow: 'hidden',
+ resize: 'both',
selectors: {
- '&:first-of-type': {
- marginTop: 0,
+ '&:before': {
+ position: 'absolute',
+ content: '',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ zIndex: -1,
},
- '&:last-of-type': {
- marginBottom: 0,
+ '&:not(.gridding):before': {
+ backgroundColor: cssVarV2('layer/background/secondary'),
+ },
+ '&.gridding:before': {
+ opacity: 0.25,
+ backgroundSize: '20px 20px',
+ backgroundImage: `linear-gradient(${cssVarV2('button/grabber/default')} 1px, transparent 1px), linear-gradient(to right, ${cssVarV2('button/grabber/default')} 1px, transparent 1px)`,
},
},
});
-export const viewerPage = style({
+export const virtuoso = style({
+ width: '100%',
+});
+
+export const pdfIndicator = style({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ padding: '0 12px',
+});
+
+export const pdfPage = style({
maxWidth: 'calc(100% - 40px)',
background: cssVarV2('layer/white'),
boxSizing: 'border-box',
@@ -125,9 +126,10 @@ export const viewerPage = style({
borderColor: cssVarV2('layer/insideBorder/border'),
boxShadow:
'0px 4px 20px 0px var(--transparent-black-200, rgba(0, 0, 0, 0.10))',
+ overflow: 'hidden',
});
-export const thumbnails = style({
+export const pdfThumbnails = style({
display: 'flex',
flexDirection: 'column',
position: 'absolute',
@@ -148,13 +150,13 @@ export const thumbnails = style({
color: cssVarV2('text/secondary'),
});
-export const thumbnailsPages = style({
+export const pdfThumbnailsList = style({
position: 'relative',
display: 'flex',
flexDirection: 'column',
maxHeight: '100%',
overflow: 'hidden',
- // gap: '12px',
+ resize: 'both',
selectors: {
'&.collapsed': {
display: 'none',
@@ -165,13 +167,9 @@ export const thumbnailsPages = style({
},
});
-export const thumbnailsItemWrapper = style({
- margin: '0px 12px 12px',
-});
-
-export const thumbnailsPage = style({
+export const pdfThumbnail = style({
display: 'flex',
- overflow: 'clip',
+ overflow: 'hidden',
// width: '100%',
borderRadius: '4px',
borderWidth: '1px',
@@ -183,10 +181,3 @@ export const thumbnailsPage = style({
},
},
});
-
-export const thumbnailsIndicator = style({
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- padding: '0 12px',
-});
diff --git a/packages/frontend/core/src/components/attachment-viewer/titlebar.tsx b/packages/frontend/core/src/components/attachment-viewer/titlebar.tsx
index d694062322877..8f826513d8907 100644
--- a/packages/frontend/core/src/components/attachment-viewer/titlebar.tsx
+++ b/packages/frontend/core/src/components/attachment-viewer/titlebar.tsx
@@ -46,7 +46,6 @@ export interface TitlebarProps {
name: string;
ext: string;
size: string;
- isPDF: boolean;
zoom?: number;
}
diff --git a/packages/frontend/core/src/components/attachment-viewer/utils.ts b/packages/frontend/core/src/components/attachment-viewer/utils.ts
index 04e5a7c9479e0..fb0e5c8f8383b 100644
--- a/packages/frontend/core/src/components/attachment-viewer/utils.ts
+++ b/packages/frontend/core/src/components/attachment-viewer/utils.ts
@@ -1,4 +1,3 @@
-import type { RenderOut } from '@affine/core/modules/pdf/workers/types';
import type { AttachmentBlockModel } from '@blocksuite/affine/blocks';
import { filesize } from 'filesize';
@@ -27,83 +26,20 @@ export async function download(model: AttachmentBlockModel) {
await downloadBlob(blob, model.name);
}
-export function renderItem(
- scroller: HTMLElement | null,
- className: string,
- data: RenderOut
-) {
- if (!scroller) return;
-
- const item = scroller.querySelector(
- `[data-index="${data.index}"] > div.${className}`
- );
- if (!item) return;
- if (item.firstElementChild) return;
-
- const { width, height, buffer } = data;
-
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- if (!ctx) return;
-
- const imageData = new ImageData(buffer, width, height);
-
- canvas.width = width;
- canvas.height = height;
- canvas.style.width = '100%';
- canvas.style.height = '100%';
-
- ctx.putImageData(imageData, 0, 0);
-
- item.append(canvas);
-}
-
export function buildAttachmentProps(model: AttachmentBlockModel) {
- const isPDF = model.type.endsWith('pdf');
const pieces = model.name.split('.');
const ext = pieces.pop() || '';
const name = pieces.join('.');
const size = filesize(model.size);
- return { model, name, ext, size, isPDF };
+ return { model, name, ext, size };
}
-/**
- * Generates a set of sequences.
- *
- * 1. when `start` is `0`, returns `[0, .., 5]`
- * 2. when `end` is `total - 1`, returns `[total - 1, .., total - 5]`
- * 2. when `start > 0` and `end < total - 1`, returns `[18, 17, 19, 16, 20, 15, 21]`
- */
-export function genSeq(start: number, end: number, total: number) {
- start = Math.max(start, 0);
- end = Math.min(end, Math.max(total - 1, 0));
- let diff = end - start;
-
- if (diff < 0) return [];
-
- if (diff === 0) return [start];
-
- if (start === 0)
- return Array.from
({ length: diff })
- .fill(start)
- .map((n, i) => n + i);
-
- if (end === total - 1)
- return Array.from({ length: diff })
- .fill(end)
- .map((n, i) => n - i);
-
- diff = Math.ceil(diff / 2);
- const mid = start + diff;
-
- return Array.from<[number, number]>({ length: diff })
- .fill([mid, mid])
- .map(([s, e], i) => [s - i, e + i])
- .reduce((a, [s, e]) => {
- s = Math.max(start, s);
- e = Math.min(end, e);
- if (!a.includes(s)) a.push(s);
- if (!a.includes(e)) a.push(e);
- return a;
- }, []);
+export function calculatePageNum(el: HTMLElement, pageCount: number) {
+ const { scrollTop, scrollHeight } = el;
+ const pageHeight = scrollHeight / pageCount;
+ const n = scrollTop / pageHeight;
+ const t = n / pageCount;
+ const index = Math.floor(n + t);
+ const cursor = Math.min(index, pageCount - 1);
+ return cursor;
}
diff --git a/packages/frontend/core/src/modules/pdf/entities/pdf-page.ts b/packages/frontend/core/src/modules/pdf/entities/pdf-page.ts
new file mode 100644
index 0000000000000..b1f91b14ea172
--- /dev/null
+++ b/packages/frontend/core/src/modules/pdf/entities/pdf-page.ts
@@ -0,0 +1,39 @@
+import { DebugLogger } from '@affine/debug';
+import {
+ catchErrorInto,
+ effect,
+ Entity,
+ LiveData,
+ mapInto,
+} from '@toeverything/infra';
+import { map, switchMap } from 'rxjs';
+
+import type { RenderPageOpts } from '../renderer';
+import type { PDF } from './pdf';
+
+const logger = new DebugLogger('affine:pdf:page:render');
+
+export class PDFPage extends Entity<{ pdf: PDF; pageNum: number }> {
+ readonly pageNum: number = this.props.pageNum;
+ bitmap$ = new LiveData(null);
+ error$ = new LiveData(null);
+
+ render = effect(
+ switchMap((opts: Omit) =>
+ this.props.pdf.renderer.ob$('render', {
+ ...opts,
+ pageNum: this.pageNum,
+ })
+ ),
+ map(data => data.bitmap),
+ mapInto(this.bitmap$),
+ catchErrorInto(this.error$, error => {
+ logger.error('Failed to render page', error);
+ })
+ );
+
+ constructor() {
+ super();
+ this.disposables.push(() => this.render.unsubscribe);
+ }
+}
diff --git a/packages/frontend/core/src/modules/pdf/entities/pdf.ts b/packages/frontend/core/src/modules/pdf/entities/pdf.ts
index 3ad8a27433036..bcf12d33b77b3 100644
--- a/packages/frontend/core/src/modules/pdf/entities/pdf.ts
+++ b/packages/frontend/core/src/modules/pdf/entities/pdf.ts
@@ -1,15 +1,10 @@
import type { AttachmentBlockModel } from '@blocksuite/affine/blocks';
-import {
- effect,
- Entity,
- LiveData,
- mapInto,
- ObjectPool,
-} from '@toeverything/infra';
+import { Entity, LiveData, ObjectPool } from '@toeverything/infra';
import { catchError, from, map, of, startWith, switchMap } from 'rxjs';
-import type { PDFMeta, RenderPageOpts } from '../renderer';
-import { PDFRenderer } from '../renderer';
+import type { PDFMeta } from '../renderer';
+import { downloadBlobToBuffer, PDFRenderer } from '../renderer';
+import { PDFPage } from './pdf-page';
export enum PDFStatus {
IDLE = 0,
@@ -31,34 +26,6 @@ export type PDFRendererState =
error: Error;
};
-function resizeImageBitmap(
- imageData: ImageData,
- options: {
- resizeWidth: number;
- resizeHeight: number;
- }
-) {
- return createImageBitmap(imageData, 0, 0, imageData.width, imageData.height, {
- colorSpaceConversion: 'none',
- resizeQuality: 'pixelated',
- ...options,
- });
-}
-
-async function downloadBlobToBuffer(model: AttachmentBlockModel) {
- const sourceId = model.sourceId;
- if (!sourceId) {
- throw new Error('Attachment not found');
- }
-
- const blob = await model.doc.blobSync.get(sourceId);
- if (!blob) {
- throw new Error('Attachment not found');
- }
-
- return await blob.arrayBuffer();
-}
-
export class PDF extends Entity {
public readonly id: string = this.props.id;
readonly renderer = new PDFRenderer();
@@ -86,14 +53,14 @@ export class PDF extends Entity {
this.disposables.push(() => this.pages.clear());
}
- page(type: string, page: number) {
- const key = `${type}:${page}`;
+ page(pageNum: number, size: string) {
+ const key = `${pageNum}:${size}`;
let rc = this.pages.get(key);
if (!rc) {
rc = this.pages.put(
key,
- this.framework.createEntity(PDFPage, { pdf: this, page })
+ this.framework.createEntity(PDFPage, { pdf: this, pageNum })
);
}
@@ -105,24 +72,3 @@ export class PDF extends Entity {
super.dispose();
}
}
-
-export class PDFPage extends Entity<{ pdf: PDF; page: number }> {
- readonly page: number = this.props.page;
- bitmap$ = new LiveData(null);
-
- render = effect(
- switchMap((opts: Omit) =>
- this.props.pdf.renderer.ob$('render', {
- ...opts,
- pageNum: this.props.page,
- })
- ),
- map(data => data.bitmap),
- mapInto(this.bitmap$)
- );
-
- constructor() {
- super();
- this.disposables.push(() => this.render.unsubscribe);
- }
-}
diff --git a/packages/frontend/core/src/modules/pdf/index.ts b/packages/frontend/core/src/modules/pdf/index.ts
index 5b69edd691d4e..998173d9ce585 100644
--- a/packages/frontend/core/src/modules/pdf/index.ts
+++ b/packages/frontend/core/src/modules/pdf/index.ts
@@ -1,7 +1,8 @@
import type { Framework } from '@toeverything/infra';
import { WorkspaceScope } from '@toeverything/infra';
-import { PDF, PDFPage } from './entities/pdf';
+import { PDF } from './entities/pdf';
+import { PDFPage } from './entities/pdf-page';
import { PDFService } from './services/pdf';
export function configurePDFModule(framework: Framework) {
@@ -13,5 +14,6 @@ export function configurePDFModule(framework: Framework) {
}
export { PDF, type PDFRendererState, PDFStatus } from './entities/pdf';
+export { PDFPage } from './entities/pdf-page';
export { PDFRenderer } from './renderer';
export { PDFService } from './services/pdf';
diff --git a/packages/frontend/core/src/modules/pdf/renderer/index.ts b/packages/frontend/core/src/modules/pdf/renderer/index.ts
index f911a08237598..d3e9a83e744aa 100644
--- a/packages/frontend/core/src/modules/pdf/renderer/index.ts
+++ b/packages/frontend/core/src/modules/pdf/renderer/index.ts
@@ -1,30 +1,3 @@
-import { OpClient } from '@toeverything/infra/op';
-
-import type { ClientOps } from './ops';
-
-export class PDFRenderer extends OpClient {
- private readonly worker: Worker;
-
- constructor() {
- const worker = new Worker(
- /* webpackChunkName: "pdf.worker" */ new URL(
- './worker.ts',
- import.meta.url
- )
- );
- super(worker);
-
- this.worker = worker;
- }
-
- override destroy() {
- super.destroy();
- this.worker.terminate();
- }
-
- [Symbol.dispose]() {
- this.destroy();
- }
-}
-
+export { PDFRenderer } from './renderer';
export type { PDFMeta, RenderedPage, RenderPageOpts } from './types';
+export { downloadBlobToBuffer } from './utils';
diff --git a/packages/frontend/core/src/modules/pdf/renderer/renderer.ts b/packages/frontend/core/src/modules/pdf/renderer/renderer.ts
new file mode 100644
index 0000000000000..6ca502772a016
--- /dev/null
+++ b/packages/frontend/core/src/modules/pdf/renderer/renderer.ts
@@ -0,0 +1,28 @@
+import { OpClient } from '@toeverything/infra/op';
+
+import type { ClientOps } from './ops';
+
+export class PDFRenderer extends OpClient {
+ private readonly worker: Worker;
+
+ constructor() {
+ const worker = new Worker(
+ /* webpackChunkName: "pdf.worker" */ new URL(
+ './worker.ts',
+ import.meta.url
+ )
+ );
+ super(worker);
+
+ this.worker = worker;
+ }
+
+ override destroy() {
+ super.destroy();
+ this.worker.terminate();
+ }
+
+ [Symbol.dispose]() {
+ this.destroy();
+ }
+}
diff --git a/packages/frontend/core/src/modules/pdf/renderer/utils.ts b/packages/frontend/core/src/modules/pdf/renderer/utils.ts
new file mode 100644
index 0000000000000..d2006f8f1eaac
--- /dev/null
+++ b/packages/frontend/core/src/modules/pdf/renderer/utils.ts
@@ -0,0 +1,15 @@
+import type { AttachmentBlockModel } from '@blocksuite/affine/blocks';
+
+export async function downloadBlobToBuffer(model: AttachmentBlockModel) {
+ const sourceId = model.sourceId;
+ if (!sourceId) {
+ throw new Error('Attachment not found');
+ }
+
+ const blob = await model.doc.blobSync.get(sourceId);
+ if (!blob) {
+ throw new Error('Attachment not found');
+ }
+
+ return await blob.arrayBuffer();
+}
diff --git a/packages/frontend/core/src/modules/pdf/services/pdf.ts b/packages/frontend/core/src/modules/pdf/services/pdf.ts
index 7880300007352..141b1731a96d0 100644
--- a/packages/frontend/core/src/modules/pdf/services/pdf.ts
+++ b/packages/frontend/core/src/modules/pdf/services/pdf.ts
@@ -4,7 +4,6 @@ import { ObjectPool, Service } from '@toeverything/infra';
import { PDF } from '../entities/pdf';
// One PDF document one worker.
-// Multiple channels correspond to multiple views.
export class PDFService extends Service {
PDFs = new ObjectPool({
diff --git a/packages/frontend/core/src/modules/pdf/views/components.tsx b/packages/frontend/core/src/modules/pdf/views/components.tsx
new file mode 100644
index 0000000000000..abffd86fdcc0a
--- /dev/null
+++ b/packages/frontend/core/src/modules/pdf/views/components.tsx
@@ -0,0 +1,185 @@
+import { Scrollable } from '@affine/component';
+import clsx from 'clsx';
+import { type CSSProperties, forwardRef, memo } from 'react';
+import type { VirtuosoProps } from 'react-virtuoso';
+
+import * as styles from './styles.css';
+
+export type PDFVirtuosoContext = {
+ width: number;
+ height: number;
+ pageClassName?: string;
+ onPageSelect?: (index: number) => void;
+};
+
+export type PDFVirtuosoProps = VirtuosoProps;
+
+export const Scroller = forwardRef(
+ ({ context: _, ...props }, ref) => {
+ return (
+
+
+
+
+ );
+ }
+);
+
+Scroller.displayName = 'pdf-virtuoso-scroller';
+
+export const List = forwardRef(
+ ({ context: _, className, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+
+List.displayName = 'pdf-virtuoso-list';
+
+export const ListWithSmallGap = forwardRef(
+ ({ context: _, className, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+
+ListWithSmallGap.displayName = 'pdf-virtuoso-small-gap-list';
+
+export const Item = forwardRef(
+ ({ context: _, ...props }, ref) => {
+ return ;
+ }
+);
+
+Item.displayName = 'pdf-virtuoso-item';
+
+export const ListPadding = () => (
+
+);
+
+export const LoadingSvg = memo(function LoadingSvg({
+ style,
+ className,
+}: {
+ style?: CSSProperties;
+ className?: string;
+}) {
+ return (
+
+ );
+});
diff --git a/packages/frontend/core/src/modules/pdf/views/index.ts b/packages/frontend/core/src/modules/pdf/views/index.ts
new file mode 100644
index 0000000000000..09e3135cad0ae
--- /dev/null
+++ b/packages/frontend/core/src/modules/pdf/views/index.ts
@@ -0,0 +1,11 @@
+export {
+ Item,
+ List,
+ ListPadding,
+ ListWithSmallGap,
+ LoadingSvg,
+ type PDFVirtuosoContext,
+ type PDFVirtuosoProps,
+ Scroller,
+} from './components';
+export { PDFPageRenderer } from './page-renderer';
diff --git a/packages/frontend/core/src/modules/pdf/views/page-renderer.tsx b/packages/frontend/core/src/modules/pdf/views/page-renderer.tsx
new file mode 100644
index 0000000000000..80a3dcb6bb840
--- /dev/null
+++ b/packages/frontend/core/src/modules/pdf/views/page-renderer.tsx
@@ -0,0 +1,84 @@
+import { useI18n } from '@affine/i18n';
+import { useLiveData } from '@toeverything/infra';
+import { useEffect, useRef, useState } from 'react';
+
+import type { PDF } from '../entities/pdf';
+import type { PDFPage } from '../entities/pdf-page';
+import { LoadingSvg } from './components';
+import * as styles from './styles.css';
+
+interface PDFPageProps {
+ pdf: PDF;
+ width: number;
+ height: number;
+ pageNum: number;
+ scale?: number;
+ className?: string;
+ onSelect?: (pageNum: number) => void;
+}
+
+export const PDFPageRenderer = ({
+ pdf,
+ width,
+ height,
+ pageNum,
+ className,
+ onSelect,
+ scale = window.devicePixelRatio,
+}: PDFPageProps) => {
+ const t = useI18n();
+ const [pdfPage, setPdfPage] = useState(null);
+ const canvasRef = useRef(null);
+ const img = useLiveData(pdfPage?.bitmap$ ?? null);
+ const error = useLiveData(pdfPage?.error$ ?? null);
+ const style = { width, aspectRatio: `${width} / ${height}` };
+
+ useEffect(() => {
+ const { page, release } = pdf.page(pageNum, `${width}:${height}:${scale}`);
+ setPdfPage(page);
+
+ return release;
+ }, [pdf, width, height, pageNum, scale]);
+
+ useEffect(() => {
+ pdfPage?.render({ width, height, scale });
+
+ return pdfPage?.render.unsubscribe;
+ }, [pdfPage, width, height, scale]);
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ if (!img) return;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ canvas.width = width * scale;
+ canvas.height = height * scale;
+ ctx.drawImage(img, 0, 0);
+ }, [img, width, height, scale]);
+
+ if (error) {
+ return (
+
+
+ {t['com.affine.pdf.page.render.error']()}
+
+
+ );
+ }
+
+ return (
+ onSelect?.(pageNum)}
+ >
+ {img === null ? (
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/packages/frontend/core/src/modules/pdf/views/styles.css.ts b/packages/frontend/core/src/modules/pdf/views/styles.css.ts
new file mode 100644
index 0000000000000..7ded9648adaf7
--- /dev/null
+++ b/packages/frontend/core/src/modules/pdf/views/styles.css.ts
@@ -0,0 +1,64 @@
+import { cssVarV2 } from '@toeverything/theme/v2';
+import { style } from '@vanilla-extract/css';
+
+export const virtuoso = style({
+ width: '100%',
+});
+
+export const virtuosoList = style({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: '20px',
+ selectors: {
+ '&.small-gap': {
+ gap: '12px',
+ },
+ },
+});
+
+export const virtuosoItem = style({
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ 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',
+ justifyContent: 'center',
+ overflow: 'hidden',
+ textWrap: 'wrap',
+ width: '100%',
+ wordBreak: 'break-word',
+ fontSize: 14,
+ lineHeight: '22px',
+ fontWeight: 400,
+ color: cssVarV2('text/primary'),
+});
+
+export const pdfPageCanvas = style({
+ width: '100%',
+});
+
+export const pdfLoading = style({
+ display: 'flex',
+ alignSelf: 'center',
+ width: '100%',
+ height: '100%',
+ maxWidth: '537px',
+});
diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json
index 3fa3a54596b29..33392f7ebd1e4 100644
--- a/packages/frontend/i18n/src/resources/en.json
+++ b/packages/frontend/i18n/src/resources/en.json
@@ -1463,5 +1463,6 @@
"com.affine.m.selector.journal-menu.today-activity": "Today's activity",
"com.affine.m.selector.journal-menu.conflicts": "Duplicate Entries in Today's Journal",
"com.affine.attachment.preview.error.title": "Unable to preview this file",
- "com.affine.attachment.preview.error.subtitle": "file type not supported."
+ "com.affine.attachment.preview.error.subtitle": "file type not supported.",
+ "com.affine.pdf.page.render.error": "Failed to render page."
}
diff --git a/tests/affine-local/e2e/attachment-preview.spec.ts b/tests/affine-local/e2e/attachment-preview.spec.ts
index 092d393912de0..8b3ddf2528e07 100644
--- a/tests/affine-local/e2e/attachment-preview.spec.ts
+++ b/tests/affine-local/e2e/attachment-preview.spec.ts
@@ -48,14 +48,14 @@ test('attachment preview should be shown', async ({ page }) => {
await page.locator('affine-attachment').first().dblclick();
- const attachmentViewer = page.getByTestId('attachment-viewer');
+ const attachmentViewer = page.getByTestId('pdf-viewer');
await expect(attachmentViewer).toBeVisible();
await page.waitForTimeout(500);
- const pageCount = attachmentViewer.locator('.page-count');
+ const pageCount = attachmentViewer.locator('.page-cursor');
expect(await pageCount.textContent()).toBe('1');
- const pageTotal = attachmentViewer.locator('.page-total');
+ const pageTotal = attachmentViewer.locator('.page-count');
expect(await pageTotal.textContent()).toBe('3');
const thumbnails = attachmentViewer.locator('.thumbnails');
@@ -89,7 +89,7 @@ test('attachment preview can be expanded', async ({ page }) => {
await page.locator('affine-attachment').first().dblclick();
- const attachmentViewer = page.getByTestId('attachment-viewer');
+ const attachmentViewer = page.getByTestId('pdf-viewer');
await page.waitForTimeout(500);
@@ -99,9 +99,9 @@ test('attachment preview can be expanded', async ({ page }) => {
await page.waitForTimeout(500);
- const pageCount = attachmentViewer.locator('.page-count');
+ const pageCount = attachmentViewer.locator('.page-cursor');
expect(await pageCount.textContent()).toBe('1');
- const pageTotal = attachmentViewer.locator('.page-total');
+ const pageTotal = attachmentViewer.locator('.page-count');
expect(await pageTotal.textContent()).toBe('3');
const thumbnails = attachmentViewer.locator('.thumbnails');
diff --git a/yarn.lock b/yarn.lock
index ff08cbed163ea..03d4aa73c12ff 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -382,7 +382,7 @@ __metadata:
zod: "npm:^3.22.4"
peerDependencies:
"@blocksuite/affine": "*"
- "@blocksuite/icons": "*"
+ "@blocksuite/icons": 2.1.67
languageName: unknown
linkType: soft