This commit is contained in:
2025-12-11 15:14:23 +00:00
parent de464461e6
commit 382af070fc

View File

@@ -12,6 +12,7 @@ import {
} from "@design.estate/dees-element";
import { demoFunc } from "./viewer.demo.js";
import { DeDocument } from "./document.js";
import "./page.js"; // Import DePage for thumbnail rendering
import "@design.estate/dees-catalog";
@@ -91,6 +92,19 @@ export class DeDocumentViewer extends DeesElement {
@state()
accessor currentPageDisplay: string = "1 / 1";
// Sidebar state
@state()
accessor showThumbnails: boolean = true;
@state()
accessor sidebarWidth: number = 140;
@state()
accessor isResizingSidebar: boolean = false;
@state()
accessor thumbnailPages: number[] = [];
public static styles = [
cssManager.defaultStyles,
css`
@@ -107,6 +121,124 @@ export class DeDocumentViewer extends DeesElement {
--dropdown-shadow: ${cssManager.bdTheme("0 4px 16px rgba(0,0,0,0.15)", "0 4px 16px rgba(0,0,0,0.4)")};
--slider-track: ${cssManager.bdTheme("#ddd", "#444")};
--slider-fill: ${cssManager.bdTheme("#0066cc", "#4d9fff")};
--sidebar-min-width: 100px;
--sidebar-max-width: 300px;
}
/* Sidebar Styles */
.sidebar {
position: absolute;
left: 0;
top: var(--toolbar-height);
bottom: 0;
background: var(--toolbar-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-right: 1px solid var(--toolbar-border);
display: flex;
flex-direction: column;
z-index: 50;
box-sizing: border-box;
}
.sidebar--collapsed {
width: 0 !important;
overflow: hidden;
border-right: none;
}
.sidebar--resizing {
transition: none !important;
user-select: none;
}
.sidebar__header {
padding: 10px 12px;
font-size: 11px;
font-weight: 600;
color: var(--text-secondary);
border-bottom: 1px solid var(--toolbar-border);
text-transform: uppercase;
letter-spacing: 0.5px;
flex-shrink: 0;
}
.sidebar__thumbnails {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 8px;
display: flex;
flex-direction: column;
gap: 12px;
}
.thumbnail {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
cursor: pointer;
padding: 4px;
border-radius: 6px;
transition: background 0.15s ease;
flex-shrink: 0;
}
.thumbnail:hover {
background: var(--button-hover);
}
.thumbnail--active {
background: var(--button-active);
}
.thumbnail--active .thumbnail__preview {
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
.thumbnail__preview {
width: 100%;
aspect-ratio: 210 / 297;
background: ${cssManager.bdTheme("#fff", "#2a2a2a")};
box-shadow: ${cssManager.bdTheme("0 1px 4px rgba(0,0,0,0.15)", "0 1px 4px rgba(0,0,0,0.4)")};
border-radius: 2px;
overflow: hidden;
position: relative;
}
.thumbnail__label {
font-size: 11px;
color: var(--text-secondary);
font-weight: 500;
}
.sidebar__resize-handle {
position: absolute;
top: 0;
right: -4px;
bottom: 0;
width: 8px;
cursor: ew-resize;
background: transparent;
z-index: 60;
}
.sidebar__resize-handle::after {
content: '';
position: absolute;
top: 0;
left: 3px;
bottom: 0;
width: 2px;
background: transparent;
transition: background 0.15s ease;
}
.sidebar__resize-handle:hover::after,
.sidebar--resizing .sidebar__resize-handle::after {
background: var(--accent-color);
}
.maincontainer {
@@ -413,6 +545,11 @@ export class DeDocumentViewer extends DeesElement {
overflow-y: scroll;
overflow-x: auto;
overscroll-behavior: contain;
transition: left 0.2s ease;
}
.viewport--with-sidebar {
left: var(--current-sidebar-width, 140px);
}
.viewport--centered {
@@ -429,6 +566,16 @@ export class DeDocumentViewer extends DeesElement {
.spacing-slider-container {
display: none;
}
/* Collapse sidebar by default on tablet */
.sidebar {
width: 0 !important;
}
.sidebar--collapsed {
width: 0 !important;
}
.viewport--with-sidebar {
left: 0 !important;
}
`),
// Phone styles
cssManager.cssForPhone(css`
@@ -453,13 +600,30 @@ export class DeDocumentViewer extends DeesElement {
.controls__section {
gap: 2px;
}
/* Hide sidebar and toggle on phone */
.sidebar {
display: none !important;
}
.sidebar-toggle {
display: none !important;
}
.viewport--with-sidebar {
left: 0 !important;
}
`),
];
public render(): TemplateResult {
const viewportClasses = [
"viewport",
this.zoomMode !== "auto" ? "viewport--centered" : "",
this.showThumbnails ? "viewport--with-sidebar" : "",
].filter(Boolean).join(" ");
return html`
<div class="maincontainer">
<div class="viewport ${this.zoomMode !== "auto" ? "viewport--centered" : ""}">
<div class="maincontainer" style="--current-sidebar-width: ${this.sidebarWidth}px;">
${this.renderSidebar()}
<div class="${viewportClasses}">
${this.letterData
? html`
<dedocument-dedocument
@@ -480,9 +644,77 @@ export class DeDocumentViewer extends DeesElement {
`;
}
private renderSidebar(): TemplateResult {
const sidebarClasses = [
"sidebar",
!this.showThumbnails ? "sidebar--collapsed" : "",
this.isResizingSidebar ? "sidebar--resizing" : "",
].filter(Boolean).join(" ");
const thumbnailScale = this.getThumbnailScale();
const currentPage = this.getCurrentPage();
return html`
<div class="${sidebarClasses}" style="width: ${this.sidebarWidth}px;">
<div class="sidebar__header">Pages</div>
<div class="sidebar__thumbnails">
${this.thumbnailPages.map(
(_, i) => html`
<div
class="thumbnail ${currentPage === i + 1 ? "thumbnail--active" : ""}"
@click=${() => this.scrollToPage(i + 1)}
>
<div
class="thumbnail__preview"
style="--thumbnail-scale: ${thumbnailScale};"
>
<div style="
width: ${plugins.shared.A4_WIDTH}px;
height: ${plugins.shared.A4_HEIGHT}px;
transform: scale(${thumbnailScale});
transform-origin: top left;
pointer-events: none;
">
<dedocument-page
.letterData=${this.letterData}
.documentSettings=${this.documentSettings}
.pageNumber=${i + 1}
.pageTotalNumber=${this.thumbnailPages.length}
></dedocument-page>
</div>
</div>
<span class="thumbnail__label">${i + 1}</span>
</div>
`
)}
</div>
<div
class="sidebar__resize-handle"
@mousedown=${this.startSidebarResize}
></div>
</div>
`;
}
private getThumbnailScale(): number {
const previewWidth = this.sidebarWidth - 24; // Account for padding
return previewWidth / plugins.shared.A4_WIDTH;
}
private renderZoomControls(): TemplateResult {
return html`
<div class="controls__section">
<!-- Sidebar Toggle Button -->
<button
class="controls__button sidebar-toggle ${this.showThumbnails ? "controls__button--active" : ""}"
@click=${() => this.toggleThumbnails()}
title="Toggle Thumbnails"
>
<dees-icon icon="lucide:panelLeft"></dees-icon>
</button>
<div class="controls__divider sidebar-toggle"></div>
<!-- Zoom Out Button -->
<button
class="controls__button"
@@ -706,6 +938,44 @@ export class DeDocumentViewer extends DeesElement {
this.pageGap = parseInt((e.target as HTMLInputElement).value, 10);
}
// ============================================
// SIDEBAR METHODS
// ============================================
private startSidebarResize(e: MouseEvent): void {
e.preventDefault();
this.isResizingSidebar = true;
const startX = e.clientX;
const startWidth = this.sidebarWidth;
const onMouseMove = (moveEvent: MouseEvent) => {
const delta = moveEvent.clientX - startX;
const newWidth = Math.min(300, Math.max(100, startWidth + delta));
this.sidebarWidth = newWidth;
};
const onMouseUp = () => {
this.isResizingSidebar = false;
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
document.body.style.cursor = "ew-resize";
document.body.style.userSelect = "none";
}
private updateThumbnailPages(): void {
const pageCount = this.getPageCount();
if (pageCount !== this.thumbnailPages.length) {
this.thumbnailPages = Array.from({ length: pageCount }, (_, i) => i);
}
}
private handlePreviousPage(): void {
const current = this.getCurrentPage();
if (current > 1) {
@@ -861,11 +1131,16 @@ export class DeDocumentViewer extends DeesElement {
super.updated(changedProperties);
if (changedProperties.has("letterData")) {
// Update display zoom and page indicator after document renders
// Update display zoom, page indicator, and thumbnails after document renders
setTimeout(() => {
this.updateDisplayZoom();
this.updatePageIndicator();
this.updateThumbnailPages();
}, 100);
// Additional delay to ensure all pages are rendered
setTimeout(() => {
this.updateThumbnailPages();
}, 500);
}
}
@@ -896,16 +1171,21 @@ export class DeDocumentViewer extends DeesElement {
resizeObserver.observe(viewport);
this.registerGarbageFunction(() => resizeObserver.disconnect());
// Scroll listener for page indicator
// Scroll listener for page indicator and thumbnail highlighting
const scrollHandler = () => {
this.updatePageIndicator();
// Trigger re-render to update active thumbnail
this.requestUpdate();
};
viewport.addEventListener("scroll", scrollHandler);
this.registerGarbageFunction(() => viewport.removeEventListener("scroll", scrollHandler));
}
// Initial page indicator update
setTimeout(() => this.updatePageIndicator(), 300);
// Initial page indicator and thumbnail update
setTimeout(() => {
this.updatePageIndicator();
this.updateThumbnailPages();
}, 300);
}
// ============================================
@@ -1031,4 +1311,41 @@ export class DeDocumentViewer extends DeesElement {
public async print(): Promise<void> {
await this.handlePrint();
}
/**
* Toggle the thumbnails sidebar visibility
*/
public toggleThumbnails(): void {
this.showThumbnails = !this.showThumbnails;
}
/**
* Set whether the thumbnails sidebar is visible
* @param visible - Whether the sidebar should be visible
*/
public setThumbnailsVisible(visible: boolean): void {
this.showThumbnails = visible;
}
/**
* Get whether the thumbnails sidebar is visible
*/
public getThumbnailsVisible(): boolean {
return this.showThumbnails;
}
/**
* Set the sidebar width
* @param width - Width in pixels (100-300)
*/
public setSidebarWidth(width: number): void {
this.sidebarWidth = Math.min(300, Math.max(100, width));
}
/**
* Get the current sidebar width
*/
public getSidebarWidth(): number {
return this.sidebarWidth;
}
}