feat(dees-pdf-preview): enhance hover functionality and page indicator display
feat(dees-pdf-viewer): improve input handling and remove unused variables
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { DeesElement, property, html, customElement, domtools, type TemplateResult, css, cssManager } from '@design.estate/dees-element';
|
||||
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
||||
import { PdfManager } from '../dees-pdf-shared/PdfManager.js';
|
||||
import { CanvasPool, type PooledCanvas } from '../dees-pdf-shared/CanvasPool.js';
|
||||
import { PerformanceMonitor, throttle } from '../dees-pdf-shared/utils.js';
|
||||
import { previewStyles } from './styles.js';
|
||||
import { demo as demoFunc } from './demo.js';
|
||||
import { DeesContextmenu } from '../dees-contextmenu.js';
|
||||
import '../dees-icon.js';
|
||||
|
||||
declare global {
|
||||
@@ -22,10 +21,7 @@ export class DeesPdfPreview extends DeesElement {
|
||||
public pdfUrl: string = '';
|
||||
|
||||
@property({ type: Number })
|
||||
public maxPages: number = 3;
|
||||
|
||||
@property({ type: Number })
|
||||
public stackOffset: number = 8;
|
||||
public currentPreviewPage: number = 1;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public clickable: boolean = true;
|
||||
@@ -42,8 +38,12 @@ export class DeesPdfPreview extends DeesElement {
|
||||
@property({ type: Boolean })
|
||||
private error: boolean = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
private isHovering: boolean = false;
|
||||
|
||||
private renderPagesTask: Promise<void> | null = null;
|
||||
private renderPagesQueued: boolean = false;
|
||||
private hoverPageNumber: number = 1;
|
||||
|
||||
private observer: IntersectionObserver;
|
||||
private pdfDocument: any;
|
||||
@@ -62,6 +62,9 @@ export class DeesPdfPreview extends DeesElement {
|
||||
<div
|
||||
class="preview-container ${this.loading ? 'loading' : ''} ${this.error ? 'error' : ''} ${this.clickable ? 'clickable' : ''}"
|
||||
@click=${this.handleClick}
|
||||
@mouseenter=${this.handleMouseEnter}
|
||||
@mouseleave=${this.handleMouseLeave}
|
||||
@mousemove=${this.handleMouseMove}
|
||||
>
|
||||
${this.loading ? html`
|
||||
<div class="preview-loading">
|
||||
@@ -79,10 +82,19 @@ export class DeesPdfPreview extends DeesElement {
|
||||
|
||||
${!this.loading && !this.error ? html`
|
||||
<div class="preview-stack">
|
||||
${this.getStackedCanvases()}
|
||||
<canvas
|
||||
class="preview-canvas"
|
||||
data-page="${this.currentPreviewPage}"
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
${this.pageCount > 0 ? html`
|
||||
${this.pageCount > 1 && this.isHovering ? html`
|
||||
<div class="preview-page-indicator">
|
||||
Page ${this.currentPreviewPage} of ${this.pageCount}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${this.pageCount > 0 && !this.isHovering ? html`
|
||||
<div class="preview-info">
|
||||
<dees-icon icon="lucide:FileText"></dees-icon>
|
||||
<span class="preview-pages">${this.pageCount} page${this.pageCount > 1 ? 's' : ''}</span>
|
||||
@@ -100,27 +112,34 @@ export class DeesPdfPreview extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private getStackedCanvases(): TemplateResult[] {
|
||||
const pagesToShow = Math.min(this.pageCount, this.maxPages);
|
||||
const canvases: TemplateResult[] = [];
|
||||
private handleMouseEnter() {
|
||||
this.isHovering = true;
|
||||
}
|
||||
|
||||
for (let i = pagesToShow - 1; i >= 0; i--) {
|
||||
const offset = i * this.stackOffset;
|
||||
canvases.push(html`
|
||||
<canvas
|
||||
class="preview-canvas"
|
||||
data-page="${i + 1}"
|
||||
style="
|
||||
top: ${offset}px;
|
||||
left: ${offset}px;
|
||||
z-index: ${pagesToShow - i};
|
||||
${i > 0 ? `box-shadow: 0 2px 8px rgba(0, 0, 0, ${0.1 + (i * 0.05)});` : ''}
|
||||
"
|
||||
></canvas>
|
||||
`);
|
||||
private handleMouseLeave() {
|
||||
this.isHovering = false;
|
||||
// Reset to first page when not hovering
|
||||
if (this.currentPreviewPage !== 1) {
|
||||
this.currentPreviewPage = 1;
|
||||
void this.scheduleRenderPages();
|
||||
}
|
||||
}
|
||||
|
||||
return canvases;
|
||||
private handleMouseMove(e: MouseEvent) {
|
||||
if (!this.isHovering || this.pageCount <= 1) return;
|
||||
|
||||
const rect = this.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const width = rect.width;
|
||||
|
||||
// Calculate which page to show based on horizontal position
|
||||
const percentage = Math.max(0, Math.min(1, x / width));
|
||||
const newPage = Math.ceil(percentage * this.pageCount) || 1;
|
||||
|
||||
if (newPage !== this.currentPreviewPage) {
|
||||
this.currentPreviewPage = newPage;
|
||||
void this.scheduleRenderPages();
|
||||
}
|
||||
}
|
||||
|
||||
public async connectedCallback() {
|
||||
@@ -175,6 +194,7 @@ export class DeesPdfPreview extends DeesElement {
|
||||
try {
|
||||
this.pdfDocument = await PdfManager.loadDocument(this.pdfUrl);
|
||||
this.pageCount = this.pdfDocument.numPages;
|
||||
this.currentPreviewPage = 1;
|
||||
this.loadedPdfUrl = this.pdfUrl;
|
||||
|
||||
await this.updateComplete;
|
||||
@@ -222,35 +242,34 @@ export class DeesPdfPreview extends DeesElement {
|
||||
|
||||
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);
|
||||
|
||||
const canvas = this.shadowRoot?.querySelector('.preview-canvas') as HTMLCanvasElement;
|
||||
if (!canvas) return;
|
||||
|
||||
// Release old canvases
|
||||
this.clearCanvases();
|
||||
|
||||
const maxStackOffset = (pagesToRender - 1) * this.stackOffset;
|
||||
|
||||
this.cacheElements();
|
||||
|
||||
const { availableWidth, availableHeight } = this.getAvailableStackSize(maxStackOffset);
|
||||
// Get available size for the preview
|
||||
const { availableWidth, availableHeight } = this.getAvailableSize();
|
||||
|
||||
// Render pages in reverse order (back to front for stacking)
|
||||
for (let i = 0; i < pagesToRender; i++) {
|
||||
const canvas = canvasElements[i];
|
||||
if (!canvas) continue;
|
||||
|
||||
const pageNum = parseInt(canvas.dataset.page || '1');
|
||||
try {
|
||||
// Get the page to render
|
||||
const pageNum = this.currentPreviewPage;
|
||||
const page = await this.pdfDocument.getPage(pageNum);
|
||||
|
||||
// Calculate scale to fit within available area while keeping aspect ratio
|
||||
const initialViewport = page.getViewport({ scale: 1 });
|
||||
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);
|
||||
const scale = Math.min(scaleX || 0.5, scaleY || scaleX || 0.5, 1.0);
|
||||
|
||||
if (!Number.isFinite(scale) || scale <= 0) {
|
||||
page.cleanup?.();
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
const viewport = page.getViewport({ scale });
|
||||
|
||||
// Acquire canvas from pool
|
||||
@@ -278,6 +297,8 @@ export class DeesPdfPreview extends DeesElement {
|
||||
|
||||
// Release page to free memory
|
||||
page.cleanup();
|
||||
} catch (error) {
|
||||
console.error(`Failed to render page ${this.currentPreviewPage}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +321,8 @@ export class DeesPdfPreview extends DeesElement {
|
||||
this.renderPagesQueued = false;
|
||||
|
||||
this.pageCount = 0;
|
||||
this.currentPreviewPage = 1;
|
||||
this.isHovering = false;
|
||||
this.previewContainer = null;
|
||||
this.stackElement = null;
|
||||
this.loadedPdfUrl = null;
|
||||
@@ -332,6 +355,7 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
this.cleanup();
|
||||
this.rendered = false;
|
||||
this.currentPreviewPage = 1;
|
||||
|
||||
// Check if in viewport and render if so
|
||||
if (this.observer) {
|
||||
@@ -342,7 +366,7 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
if ((changedProperties.has('maxPages') || changedProperties.has('stackOffset')) && this.rendered) {
|
||||
if (changedProperties.has('currentPreviewPage') && this.rendered) {
|
||||
await this.scheduleRenderPages();
|
||||
}
|
||||
}
|
||||
@@ -430,7 +454,7 @@ export class DeesPdfPreview extends DeesElement {
|
||||
this.resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
private getAvailableStackSize(maxStackOffset: number) {
|
||||
private getAvailableSize() {
|
||||
if (!this.stackElement) {
|
||||
return {
|
||||
availableWidth: 0,
|
||||
@@ -439,8 +463,8 @@ export class DeesPdfPreview extends DeesElement {
|
||||
}
|
||||
|
||||
const rect = this.stackElement.getBoundingClientRect();
|
||||
const availableWidth = Math.max(rect.width - maxStackOffset, 0);
|
||||
const availableHeight = Math.max(rect.height - maxStackOffset, 0);
|
||||
const availableWidth = Math.max(rect.width, 0);
|
||||
const availableHeight = Math.max(rect.height, 0);
|
||||
|
||||
return { availableWidth, availableHeight };
|
||||
}
|
||||
|
@@ -36,18 +36,25 @@ export const previewStyles = [
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.preview-canvas {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
background: white;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214 31% 88%)', 'hsl(217 25% 30%)')};
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
box-shadow: 0 2px 8px ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(0, 0, 0, 0.2)')};
|
||||
}
|
||||
|
||||
.preview-info {
|
||||
@@ -153,6 +160,35 @@ export const previewStyles = [
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.preview-page-indicator {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
padding: 6px 10px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.75)', 'hsl(0 0% 100% / 0.85)')};
|
||||
color: ${cssManager.bdTheme('white', 'hsl(215 20% 12%)')};
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 15;
|
||||
pointer-events: none;
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive sizes */
|
||||
:host([size="small"]) .preview-container {
|
||||
width: 150px;
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import { DeesElement, property, html, customElement, domtools, type TemplateResult, type CSSResult, css, cssManager } from '@design.estate/dees-element';
|
||||
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
||||
import { keyed } from 'lit/directives/keyed.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { DeesInputBase } from '../dees-input-base.js';
|
||||
import { PdfManager } from '../dees-pdf-shared/PdfManager.js';
|
||||
import { viewerStyles } from './styles.js';
|
||||
import { demo as demoFunc } from './demo.js';
|
||||
import { DeesContextmenu } from '../dees-contextmenu.js';
|
||||
import '../dees-icon.js';
|
||||
|
||||
declare global {
|
||||
@@ -68,7 +66,6 @@ export class DeesPdfViewer extends DeesElement {
|
||||
private resizeObserver?: ResizeObserver;
|
||||
private viewportDimensions = { width: 0, height: 0 };
|
||||
private viewportMode: 'auto' | 'page-fit' | 'page-width' | 'custom' = 'auto';
|
||||
private loadedPdfUrl: string | null = null;
|
||||
private readonly MANUAL_MIN_ZOOM = 0.5;
|
||||
private readonly MANUAL_MAX_ZOOM = 3;
|
||||
private readonly ABSOLUTE_MIN_ZOOM = 0.1;
|
||||
@@ -96,7 +93,7 @@ export class DeesPdfViewer extends DeesElement {
|
||||
type="number"
|
||||
min="1"
|
||||
max="${this.totalPages}"
|
||||
.value=${this.currentPage}
|
||||
.value=${String(this.currentPage)}
|
||||
@change=${this.handlePageInput}
|
||||
class="page-input"
|
||||
/>
|
||||
@@ -334,7 +331,6 @@ export class DeesPdfViewer extends DeesElement {
|
||||
}
|
||||
|
||||
this.renderState = 'rendered';
|
||||
this.loadedPdfUrl = this.pdfUrl;
|
||||
} catch (error) {
|
||||
console.error('Error loading PDF:', error);
|
||||
this.loading = false;
|
||||
@@ -795,9 +791,6 @@ export class DeesPdfViewer extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the loaded URL reference
|
||||
this.loadedPdfUrl = null;
|
||||
|
||||
// Finally null the document reference
|
||||
this.pdfDocument = null;
|
||||
|
||||
|
Reference in New Issue
Block a user