Skip to content

Commit

Permalink
feat: pdf embed view component
Browse files Browse the repository at this point in the history
  • Loading branch information
fundon committed Nov 5, 2024
1 parent 7910f20 commit 9cdb92e
Show file tree
Hide file tree
Showing 11 changed files with 642 additions and 186 deletions.
19 changes: 18 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,23 @@
"which-typed-array": "npm:@nolyfill/which-typed-array@latest",
"@reforged/maker-appimage/@electron-forge/maker-base": "7.5.0",
"macos-alias": "npm:@napi-rs/[email protected]",
"fs-xattr": "npm:@napi-rs/xattr@latest"
"fs-xattr": "npm:@napi-rs/xattr@latest",
"@blocksuite/affine": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/all",
"@blocksuite/affine-block-embed": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/block-embed",
"@blocksuite/affine-block-list": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/block-list",
"@blocksuite/affine-block-paragraph": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/block-paragraph",
"@blocksuite/affine-block-surface": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/block-surface",
"@blocksuite/affine-components": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/components",
"@blocksuite/data-view": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/data-view",
"@blocksuite/affine-model": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/model",
"@blocksuite/affine-shared": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/shared",
"@blocksuite/affine-widget-scroll-anchoring": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/affine/widget-scroll-anchoring",
"@blocksuite/blocks": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/blocks",
"@blocksuite/block-std": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/framework/block-std",
"@blocksuite/global": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/framework/global",
"@blocksuite/inline": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/framework/inline",
"@blocksuite/store": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/framework/store",
"@blocksuite/sync": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/framework/sync",
"@blocksuite/presets": "portal:/Users/fundon/dev/toeverything/blocksuite/packages/presets"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { IconButton } from '@affine/component';
import { stopPropagation } from '@affine/core/utils';
import type { AttachmentBlockModel } from '@blocksuite/affine/blocks';
import {
ArrowDownSmallIcon,
ArrowUpSmallIcon,
AttachmentIcon,
CenterPeekIcon,
} from '@blocksuite/icons/rc';
import clsx from 'clsx';
import { useCallback, useLayoutEffect, useRef } from 'react';

import { usePDFDocState } from '../hooks';
import type { RenderKind } from '../worker/types';
import * as styles from './styles.css';

type AttachmentEmbedProps = {
model: AttachmentBlockModel;
blobUrl?: string;
};

export function AttachmentEmbedWithPDF({ model }: AttachmentEmbedProps) {
const viewerRef = useRef<HTMLDivElement | null>(null);
const pageRef = useRef<HTMLDivElement | null>(null);
const render = useCallback(
(index: number, kind: RenderKind, imageData: ImageData) => {
const el = pageRef.current;
if (!el) return;

let canvas = el.firstElementChild as HTMLCanvasElement | null;
if (!canvas) {
canvas = document.createElement('canvas');
el.append(canvas);
}

const ctx = canvas.getContext('2d');
if (!ctx) return;

canvas.dataset.index = String(index);
canvas.dataset.kind = String(kind);
canvas.width = imageData.width;
canvas.height = imageData.height;
canvas.style.width = '100%';
canvas.style.height = '100%';

ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
},
[pageRef]
);
const { start, stop, navigator, docInfoFormatter } = usePDFDocState(
model,
render
);

useLayoutEffect(() => {
const el = viewerRef.current;
if (!el) return;

let tid = 0;

function enter() {
if (tid) {
clearTimeout(tid);
tid = 0;
}
start();
}

function leave() {
tid = window.setTimeout(() => {
stop();
}, 1000);
}

el.addEventListener('pointerenter', enter);
el.addEventListener('pointerleave', leave);

return () => {
el.removeEventListener('pointerenter', enter);
el.removeEventListener('pointerleave', leave);
};
}, [viewerRef, start, stop]);

return (
<div ref={viewerRef} className={styles.embedWithPDF}>
<main className={styles.pdfMain}>
<div className={styles.pdfPage} ref={pageRef}></div>
<div className={styles.pdfControls}>
<IconButton
size={16}
className={styles.pdfControlButton}
icon={<ArrowUpSmallIcon />}
onDoubleClick={stopPropagation}
{...navigator.prev}
/>
<IconButton
size={16}
className={styles.pdfControlButton}
icon={<ArrowDownSmallIcon />}
onDoubleClick={stopPropagation}
{...navigator.next}
/>
<IconButton
size={16}
className={styles.pdfControlButton}
icon={<CenterPeekIcon />}
onDoubleClick={stopPropagation}
{...navigator.peek}
/>
</div>
</main>
<footer className={styles.pdfFooter}>
<div className={clsx([styles.pdfFooterItem, { truncate: true }])}>
<AttachmentIcon />
<span className={styles.pdfTitle}>{model.name}</span>
</div>
<div className={clsx([styles.pdfFooterItem, styles.pdfPageCount])}>
<span>{docInfoFormatter.cursor}</span>/
<span>{docInfoFormatter.total}</span>
</div>
</footer>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';

export const embedWithPDF = style({
width: '100%',
borderRadius: '8px',
borderWidth: '1px',
borderStyle: 'solid',
borderColor: cssVarV2('layer/insideBorder/border'),
background: cssVar('--affine-background-primary-color'),
userSelect: 'none',
});

export const pdfMain = style({
position: 'relative',
minHeight: '160px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '12px',
background: cssVarV2('layer/background/secondary'),
});

export const pdfPage = style({
maxWidth: 'calc(100% - 24px)',
});

export const pdfControls = style({
position: 'absolute',
bottom: '16px',
right: '14px',
display: 'flex',
flexDirection: 'column',
gap: '10px',
});

export const pdfControlButton = style({
width: '36px',
height: '36px',
borderWidth: '1px',
borderStyle: 'solid',
borderColor: cssVar('--affine-border-color'),
background: cssVar('--affine-white'),
});

export const pdfFooter = style({
display: 'flex',
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
gap: '12px',
padding: '12px',
textWrap: 'nowrap',
});

export const pdfFooterItem = style({
display: 'flex',
alignItems: 'center',
selectors: {
'&.truncate': {
overflow: 'hidden',
},
},
});

export const pdfTitle = style({
marginLeft: '8px',
fontSize: '14px',
fontWeight: 600,
lineHeight: '22px',
color: cssVarV2('text/primary'),
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});

export const pdfPageCount = style({
fontSize: '12px',
fontWeight: 400,
lineHeight: '20px',
color: cssVarV2('text/secondary'),
});
Loading

0 comments on commit 9cdb92e

Please sign in to comment.