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:
fundon
2025-05-22 01:11:03 +00:00
parent 346c0df800
commit 21ea65edc5
10 changed files with 341 additions and 114 deletions

View File

@@ -16,6 +16,7 @@ type AttachmentPageProps = {
};
const useLoadAttachment = (pageId: string, attachmentId: string) => {
const [loading, setLoading] = useState(true);
const docsService = useService(DocsService);
const docRecord = useLiveData(docsService.list.doc$(pageId));
const [doc, setDoc] = useState<Doc | null>(null);
@@ -23,6 +24,7 @@ const useLoadAttachment = (pageId: string, attachmentId: string) => {
useLayoutEffect(() => {
if (!docRecord) {
setLoading(false);
return;
}
@@ -38,44 +40,23 @@ const useLoadAttachment = (pageId: string, attachmentId: string) => {
doc
.waitForSyncReady()
.then(() => {
const block = doc.blockSuiteDoc.getBlock(attachmentId);
if (block) {
setModel(block.model as AttachmentBlockModel);
}
const model =
doc.blockSuiteDoc.getModelById<AttachmentBlockModel>(attachmentId);
setModel(model);
})
.catch(console.error);
.catch(console.error)
.finally(() => setLoading(false));
return () => {
release();
dispose();
};
}, [docRecord, docsService, pageId, attachmentId]);
}, [docRecord, docsService, pageId, attachmentId, setLoading]);
return { doc, model };
return { doc, model, loading };
};
export const AttachmentPage = ({
pageId,
attachmentId,
}: AttachmentPageProps): ReactElement => {
const { doc, model } = useLoadAttachment(pageId, attachmentId);
if (!doc) {
return <PageNotFound noPermission={false} />;
}
if (doc && model) {
return (
<FrameworkScope scope={doc.scope}>
<ViewTitle title={model.props.name} />
<ViewIcon
icon={model.props.type.endsWith('pdf') ? 'pdf' : 'attachment'}
/>
<AttachmentViewerView model={model} />
</FrameworkScope>
);
}
const Loading = () => {
return (
<div className={styles.attachmentSkeletonStyle}>
<Skeleton
@@ -109,12 +90,37 @@ export const AttachmentPage = ({
);
};
export const AttachmentPage = ({
pageId,
attachmentId,
}: AttachmentPageProps): ReactElement => {
const { doc, model, loading } = useLoadAttachment(pageId, attachmentId);
if (loading) {
return <Loading />;
}
if (doc && model) {
return (
<FrameworkScope scope={doc.scope}>
<ViewTitle title={model.props.name} />
<ViewIcon
icon={model.props.type.endsWith('pdf') ? 'pdf' : 'attachment'}
/>
<AttachmentViewerView model={model} />
</FrameworkScope>
);
}
return <PageNotFound noPermission={false} />;
};
export const Component = () => {
const { pageId, attachmentId } = useParams();
if (!pageId || !attachmentId) {
return <PageNotFound noPermission />;
if (pageId && attachmentId) {
return <AttachmentPage pageId={pageId} attachmentId={attachmentId} />;
}
return <AttachmentPage pageId={pageId} attachmentId={attachmentId} />;
return <PageNotFound noPermission />;
};