mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 10:06:17 +08:00
feat(core): add status to pdf viewer (#12349)
Closes: [BS-3439](https://linear.app/affine-design/issue/BS-3439/pdf-独立页面split-view-中的-status-组件) Related to: [BS-3143](https://linear.app/affine-design/issue/BS-3143/更新-loading-和错误样式) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a dedicated error handling and reload interface for PDF attachments, allowing users to retry loading PDFs when errors occur. - **Refactor** - Improved PDF viewer interface with clearer loading and error states. - Enhanced attachment type detection for better performance and maintainability. - Streamlined attachment preview logic for more direct and efficient model retrieval. - Simplified internal PDF metadata handling and control flow for improved clarity. - Clarified conditional rendering logic in attachment viewer components. - Introduced explicit loading state management and refined rendering logic in attachment pages. - **Style** - Updated and added styles for PDF viewer controls and error status display. - **Tests** - Added end-to-end tests validating PDF preview error handling and attachment not-found scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,49 +1,62 @@
|
||||
import type { AttachmentBlockModel } from '@blocksuite/affine/model';
|
||||
|
||||
const imageExts = new Set([
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'webp',
|
||||
'svg',
|
||||
'avif',
|
||||
'tiff',
|
||||
'bmp',
|
||||
]);
|
||||
|
||||
const audioExts = new Set(['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'opus']);
|
||||
|
||||
const videoExts = new Set([
|
||||
'mp4',
|
||||
'webm',
|
||||
'avi',
|
||||
'mov',
|
||||
'mkv',
|
||||
'mpeg',
|
||||
'ogv',
|
||||
'3gp',
|
||||
]);
|
||||
|
||||
export function getAttachmentType(model: AttachmentBlockModel) {
|
||||
const type = model.props.type;
|
||||
|
||||
// Check MIME type first
|
||||
if (model.props.type.startsWith('image/')) {
|
||||
if (type.startsWith('image/')) {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
if (model.props.type.startsWith('audio/')) {
|
||||
if (type.startsWith('audio/')) {
|
||||
return 'audio';
|
||||
}
|
||||
|
||||
if (model.props.type.startsWith('video/')) {
|
||||
if (type.startsWith('video/')) {
|
||||
return 'video';
|
||||
}
|
||||
|
||||
if (model.props.type === 'application/pdf') {
|
||||
if (type === 'application/pdf') {
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
// If MIME type doesn't match, check file extension
|
||||
const ext = model.props.name.split('.').pop()?.toLowerCase() || '';
|
||||
const ext = model.props.name.split('.').pop()?.toLowerCase() ?? '';
|
||||
|
||||
if (
|
||||
[
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'webp',
|
||||
'svg',
|
||||
'avif',
|
||||
'tiff',
|
||||
'bmp',
|
||||
].includes(ext)
|
||||
) {
|
||||
if (imageExts.has(ext)) {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
if (['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'opus'].includes(ext)) {
|
||||
if (audioExts.has(ext)) {
|
||||
return 'audio';
|
||||
}
|
||||
|
||||
if (
|
||||
['mp4', 'webm', 'avi', 'mov', 'mkv', 'mpeg', 'ogv', '3gp'].includes(ext)
|
||||
) {
|
||||
if (videoExts.has(ext)) {
|
||||
return 'video';
|
||||
}
|
||||
|
||||
@@ -55,7 +68,7 @@ export function getAttachmentType(model: AttachmentBlockModel) {
|
||||
}
|
||||
|
||||
export async function downloadBlobToBuffer(model: AttachmentBlockModel) {
|
||||
const sourceId = model.props.sourceId;
|
||||
const sourceId = model.props.sourceId$.peek();
|
||||
if (!sourceId) {
|
||||
throw new Error('Attachment not found');
|
||||
}
|
||||
@@ -65,6 +78,5 @@ export async function downloadBlobToBuffer(model: AttachmentBlockModel) {
|
||||
throw new Error('Attachment not found');
|
||||
}
|
||||
|
||||
const arrayBuffer = await blob.arrayBuffer();
|
||||
return arrayBuffer;
|
||||
return await blob.arrayBuffer();
|
||||
}
|
||||
|
||||
@@ -37,9 +37,7 @@ export class PDF extends Entity<AttachmentBlockModel> {
|
||||
readonly state$ = LiveData.from<PDFRendererState>(
|
||||
// @ts-expect-error type alias
|
||||
from(downloadBlobToBuffer(this.props)).pipe(
|
||||
switchMap(buffer => {
|
||||
return this.renderer.ob$('open', { data: buffer });
|
||||
}),
|
||||
switchMap(data => this.renderer.ob$('open', { data })),
|
||||
map(meta => ({ status: PDFStatus.Opened, meta })),
|
||||
// @ts-expect-error type alias
|
||||
startWith({ status: PDFStatus.Opening }),
|
||||
|
||||
@@ -15,5 +15,5 @@ export function configurePDFModule(framework: Framework) {
|
||||
|
||||
export { PDF, type PDFRendererState, PDFStatus } from './entities/pdf';
|
||||
export { PDFPage } from './entities/pdf-page';
|
||||
export { PDFRenderer } from './renderer';
|
||||
export { type PDFMeta, PDFRenderer } from './renderer';
|
||||
export { PDFService } from './services/pdf';
|
||||
|
||||
@@ -15,11 +15,14 @@ export const AttachmentPreviewPeekView = ({
|
||||
}: AttachmentPreviewModalProps) => {
|
||||
const { doc } = useEditor(docId);
|
||||
const blocksuiteDoc = doc?.blockSuiteDoc;
|
||||
const model = useMemo(() => {
|
||||
const model = blocksuiteDoc?.getBlock(blockId)?.model;
|
||||
if (!model) return null;
|
||||
return model as AttachmentBlockModel;
|
||||
}, [blockId, blocksuiteDoc]);
|
||||
const model = useMemo(
|
||||
() => blocksuiteDoc?.getModelById<AttachmentBlockModel>(blockId) ?? null,
|
||||
[blockId, blocksuiteDoc]
|
||||
);
|
||||
|
||||
return model === null ? null : <AttachmentViewer model={model} />;
|
||||
if (model) {
|
||||
return <AttachmentViewer model={model} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user