feat: Update PDF components to improve rendering performance and manage document lifecycle without caching
This commit is contained in:
@@ -42,10 +42,16 @@ export class DeesPdfPreview extends DeesElement {
|
||||
@property({ type: Boolean })
|
||||
private error: boolean = false;
|
||||
|
||||
private renderPagesTask: Promise<void> | null = null;
|
||||
private renderPagesQueued: boolean = false;
|
||||
|
||||
private observer: IntersectionObserver;
|
||||
private pdfDocument: any;
|
||||
private canvases: PooledCanvas[] = [];
|
||||
private renderRequestId: number;
|
||||
private resizeObserver?: ResizeObserver;
|
||||
private previewContainer: HTMLElement | null = null;
|
||||
private stackElement: HTMLElement | null = null;
|
||||
private loadedPdfUrl: string | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -118,13 +124,21 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
|
||||
public async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
await super.connectedCallback();
|
||||
this.setupIntersectionObserver();
|
||||
await this.updateComplete;
|
||||
this.cacheElements();
|
||||
this.setupResizeObserver();
|
||||
}
|
||||
|
||||
public async disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
await super.disconnectedCallback();
|
||||
this.cleanup();
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
this.resizeObserver?.disconnect();
|
||||
this.resizeObserver = undefined;
|
||||
}
|
||||
|
||||
private setupIntersectionObserver() {
|
||||
@@ -161,9 +175,11 @@ export class DeesPdfPreview extends DeesElement {
|
||||
try {
|
||||
this.pdfDocument = await PdfManager.loadDocument(this.pdfUrl);
|
||||
this.pageCount = this.pdfDocument.numPages;
|
||||
this.loadedPdfUrl = this.pdfUrl;
|
||||
|
||||
await this.updateComplete;
|
||||
await this.renderPages();
|
||||
this.cacheElements();
|
||||
await this.scheduleRenderPages();
|
||||
|
||||
this.rendered = true;
|
||||
|
||||
@@ -177,17 +193,46 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async renderPages() {
|
||||
private scheduleRenderPages(): Promise<void> {
|
||||
if (!this.pdfDocument) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.renderPagesTask) {
|
||||
this.renderPagesQueued = true;
|
||||
return this.renderPagesTask;
|
||||
}
|
||||
|
||||
this.renderPagesTask = (async () => {
|
||||
try {
|
||||
await this.performRenderPages();
|
||||
} catch (error) {
|
||||
console.error('Failed to render PDF preview pages:', error);
|
||||
}
|
||||
})().finally(() => {
|
||||
this.renderPagesTask = null;
|
||||
if (this.renderPagesQueued) {
|
||||
this.renderPagesQueued = false;
|
||||
void this.scheduleRenderPages();
|
||||
}
|
||||
});
|
||||
|
||||
return this.renderPagesTask;
|
||||
}
|
||||
|
||||
private async performRenderPages() {
|
||||
if (!this.pdfDocument) return;
|
||||
const canvasElements = this.shadowRoot?.querySelectorAll('.preview-canvas') as NodeListOf<HTMLCanvasElement>;
|
||||
const pagesToRender = Math.min(this.pageCount, this.maxPages);
|
||||
|
||||
// Release old canvases
|
||||
this.clearCanvases();
|
||||
|
||||
// Calculate available width for preview (container width minus padding and stacking offset)
|
||||
const containerWidth = 160; // 200px container - 40px padding
|
||||
const maxStackOffset = (pagesToRender - 1) * this.stackOffset;
|
||||
const availableWidth = containerWidth - maxStackOffset;
|
||||
|
||||
this.cacheElements();
|
||||
|
||||
const { availableWidth, availableHeight } = this.getAvailableStackSize(maxStackOffset);
|
||||
|
||||
// Render pages in reverse order (back to front for stacking)
|
||||
for (let i = 0; i < pagesToRender; i++) {
|
||||
@@ -197,9 +242,15 @@ export class DeesPdfPreview extends DeesElement {
|
||||
const pageNum = parseInt(canvas.dataset.page || '1');
|
||||
const page = await this.pdfDocument.getPage(pageNum);
|
||||
|
||||
// Calculate scale to fit within available width
|
||||
// Calculate scale to fit within available area while keeping aspect ratio
|
||||
const initialViewport = page.getViewport({ scale: 1 });
|
||||
const scale = Math.min(availableWidth / initialViewport.width, 0.5); // Cap at 0.5 for quality
|
||||
const scaleX = availableWidth > 0 ? availableWidth / initialViewport.width : 0;
|
||||
const scaleY = availableHeight > 0 ? availableHeight / initialViewport.height : 0;
|
||||
const scale = Math.min(scaleX || 0.5, scaleY || scaleX || 0.5, 0.75);
|
||||
if (!Number.isFinite(scale) || scale <= 0) {
|
||||
page.cleanup?.();
|
||||
continue;
|
||||
}
|
||||
const viewport = page.getViewport({ scale });
|
||||
|
||||
// Acquire canvas from pool
|
||||
@@ -231,12 +282,6 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
|
||||
private clearCanvases() {
|
||||
// Cancel any pending render
|
||||
if (this.renderRequestId) {
|
||||
cancelAnimationFrame(this.renderRequestId);
|
||||
this.renderRequestId = 0;
|
||||
}
|
||||
|
||||
// Release pooled canvases
|
||||
for (const pooledCanvas of this.canvases) {
|
||||
CanvasPool.release(pooledCanvas);
|
||||
@@ -245,18 +290,22 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
|
||||
private cleanup() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
this.clearCanvases();
|
||||
|
||||
if (this.pdfUrl && this.pdfDocument) {
|
||||
PdfManager.releaseDocument(this.pdfUrl);
|
||||
if (this.pdfDocument) {
|
||||
PdfManager.releaseDocument(this.loadedPdfUrl ?? this.pdfUrl);
|
||||
this.pdfDocument = null;
|
||||
}
|
||||
|
||||
this.renderPagesQueued = false;
|
||||
|
||||
this.pageCount = 0;
|
||||
this.previewContainer = null;
|
||||
this.stackElement = null;
|
||||
this.loadedPdfUrl = null;
|
||||
this.rendered = false;
|
||||
this.loading = false;
|
||||
this.error = false;
|
||||
}
|
||||
|
||||
private handleClick() {
|
||||
@@ -277,6 +326,10 @@ export class DeesPdfPreview extends DeesElement {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('pdfUrl') && this.pdfUrl) {
|
||||
const previousUrl = changedProperties.get('pdfUrl') as string | undefined;
|
||||
if (previousUrl) {
|
||||
PdfManager.releaseDocument(previousUrl);
|
||||
}
|
||||
this.cleanup();
|
||||
this.rendered = false;
|
||||
|
||||
@@ -288,6 +341,10 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((changedProperties.has('maxPages') || changedProperties.has('stackOffset')) && this.rendered) {
|
||||
await this.scheduleRenderPages();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,4 +408,40 @@ export class DeesPdfPreview extends DeesElement {
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
private cacheElements() {
|
||||
if (!this.previewContainer) {
|
||||
this.previewContainer = this.shadowRoot?.querySelector('.preview-container') as HTMLElement;
|
||||
}
|
||||
if (!this.stackElement) {
|
||||
this.stackElement = this.shadowRoot?.querySelector('.preview-stack') as HTMLElement;
|
||||
}
|
||||
}
|
||||
|
||||
private setupResizeObserver() {
|
||||
if (!this.previewContainer || this.resizeObserver) return;
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
if (this.rendered && this.pdfDocument && !this.loading) {
|
||||
void this.scheduleRenderPages();
|
||||
}
|
||||
});
|
||||
|
||||
this.resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
private getAvailableStackSize(maxStackOffset: number) {
|
||||
if (!this.stackElement) {
|
||||
return {
|
||||
availableWidth: 0,
|
||||
availableHeight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const rect = this.stackElement.getBoundingClientRect();
|
||||
const availableWidth = Math.max(rect.width - maxStackOffset, 0);
|
||||
const availableHeight = Math.max(rect.height - maxStackOffset, 0);
|
||||
|
||||
return { availableWidth, availableHeight };
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user