web-mojo
Version: 
WEB-MOJO - A lightweight JavaScript framework for building data-driven web applications
3 lines (2 loc) • 22.6 kB
JavaScript
"use strict";const t=require("./WebApp-sKJf8j1s.js"),e=require("./Dialog-BmvpkgLD.js");class LightboxGallery extends t.View{constructor(t={}){super({...t,className:`lightbox-gallery ${t.className||""}`,tagName:"div"});const e=Array.isArray(t.images)?t.images:[t.images||t.src].filter(Boolean);this.images=e.map(t=>"string"==typeof t?{src:t,alt:""}:{src:t.src,alt:t.alt||""}),this.currentIndex=t.startIndex||0,this.showNavigation=!1!==t.showNavigation&&this.images.length>1,this.showCounter=!1!==t.showCounter&&this.images.length>1,this.allowKeyboard=!1!==t.allowKeyboard,this.closeOnBackdrop=!1!==t.closeOnBackdrop,this.fitToScreen=!1!==t.fitToScreen,this._keyboardHandler=this.handleKeyboard.bind(this),this.updateTemplateProperties()}updateTemplateProperties(){this.currentImage=this.images[this.currentIndex]||{src:"",alt:""},this.currentNumber=this.currentIndex+1,this.total=this.images.length,this.isFirst=0===this.currentIndex,this.isLast=this.currentIndex===this.images.length-1,this.imageStyle=this.fitToScreen?"width: 90vw; max-height: 100%; object-fit: contain; user-select: none; cursor: zoom-in;":"max-width: none; max-height: none; user-select: none; cursor: zoom-out;",this.containerStyle=this.fitToScreen?"":"overflow: auto;",this.modeIndicator=this.fitToScreen?"Fit to Screen":"Original Size"}async getTemplate(){return this.images[this.currentIndex],this.images.length,'\n      <div class="lightbox-overlay position-fixed top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"\n           style="background: rgba(0,0,0,0.9); z-index: 9999;"\n           data-action="backdrop-click">\n\n        \x3c!-- Close button --\x3e\n        <button type="button" class="btn-close btn-close-white position-absolute top-0 end-0 m-4"\n                data-action="close"\n                style="z-index: 10001;"\n                title="Close"></button>\n\n        \x3c!-- Counter --\x3e\n        {{#showCounter}}\n        <div class="lightbox-counter position-absolute top-0 start-50 translate-middle-x mt-4 text-white"\n             style="z-index: 10001; font-size: 1.1rem;">\n          {{currentNumber}} of {{total}}\n        </div>\n        {{/showCounter}}\n\n        \x3c!-- Mode Indicator --\x3e\n        <div class="lightbox-mode-indicator position-absolute bottom-0 start-50 translate-middle-x mb-4 text-white bg-dark bg-opacity-75 px-3 py-2 rounded"\n             style="z-index: 10001; font-size: 0.9rem;">\n          {{modeIndicator}} • Click image to toggle\n        </div>\n\n        \x3c!-- Navigation --\x3e\n        {{#showNavigation}}\n        <button type="button" class="btn btn-light btn-lg position-absolute start-0 top-50 translate-middle-y ms-4"\n                data-action="prev"\n                style="z-index: 10001;"\n                title="Previous"\n                {{#isFirst}}disabled{{/isFirst}}>\n          <i class="bi bi-chevron-left"></i>\n        </button>\n\n        <button type="button" class="btn btn-light btn-lg position-absolute end-0 top-50 translate-middle-y me-4"\n                data-action="next"\n                style="z-index: 10001;"\n                title="Next"\n                {{#isLast}}disabled{{/isLast}}>\n          <i class="bi bi-chevron-right"></i>\n        </button>\n        {{/showNavigation}}\n\n        \x3c!-- Image container --\x3e\n        <div class="lightbox-image-container w-100 h-100 d-flex align-items-center justify-content-center p-5"\n             style="{{containerStyle}}">\n          {{#currentImage}}\n          <img src="{{src}}"\n               alt="{{alt}}"\n               class="lightbox-image img-fluid"\n               style="{{imageStyle}}"\n               data-action="image-click">\n          {{/currentImage}}\n        </div>\n\n        \x3c!-- Loading spinner --\x3e\n        <div class="lightbox-loading position-absolute top-50 start-50 translate-middle text-white"\n             style="display: none;">\n          <div class="spinner-border" role="status">\n            <span class="visually-hidden">Loading...</span>\n          </div>\n        </div>\n      </div>\n    '}async onAfterRender(){document.body.appendChild(this.element),document.body.style.overflow="hidden",this.allowKeyboard&&document.addEventListener("keydown",this._keyboardHandler),this.preloadAdjacentImages()}async handleActionClose(){this.close()}async handleActionBackdropClick(t){this.closeOnBackdrop&&t.target===t.currentTarget&&this.close()}async handleActionPrev(){this.showPrevious()}async handleActionNext(){this.showNext()}async handleActionImageClick(){this.toggleImageMode()}showPrevious(){this.currentIndex>0&&(this.currentIndex--,this.updateImage(),this.preloadAdjacentImages())}showNext(){this.currentIndex<this.images.length-1&&(this.currentIndex++,this.updateImage(),this.preloadAdjacentImages())}goToImage(t){t>=0&&t<this.images.length&&t!==this.currentIndex&&(this.currentIndex=t,this.updateImage(),this.preloadAdjacentImages())}async updateImage(){const t=this.images[this.currentIndex],e=this.element.querySelector(".lightbox-image"),s=this.element.querySelector(".lightbox-counter"),i=this.element.querySelector('[data-action="prev"]'),n=this.element.querySelector('[data-action="next"]');e&&(this.showLoading(),e.src=t.src,e.alt=t.alt,await this.waitForImageLoad(e),this.hideLoading()),s&&(s.textContent=`${this.currentIndex+1} of ${this.images.length}`),i&&(i.disabled=0===this.currentIndex),n&&(n.disabled=this.currentIndex===this.images.length-1),this.updateTemplateProperties(),this.updateImageDisplay();const a=this.getApp()?.events;a&&a.emit("lightbox:image-changed",{gallery:this,index:this.currentIndex,image:t})}showLoading(){const t=this.element.querySelector(".lightbox-loading");t&&(t.style.display="block")}hideLoading(){const t=this.element.querySelector(".lightbox-loading");t&&(t.style.display="none")}waitForImageLoad(t){return new Promise(e=>{t.complete?e():(t.onload=e,t.onerror=e)})}preloadAdjacentImages(){const t=[];this.currentIndex>0&&t.push(this.currentIndex-1),this.currentIndex<this.images.length-1&&t.push(this.currentIndex+1),t.forEach(t=>{const e=this.images[t];(new Image).src=e.src})}handleKeyboard(t){switch(t.key){case"Escape":t.preventDefault(),this.close();break;case"ArrowLeft":t.preventDefault(),this.showPrevious();break;case"ArrowRight":t.preventDefault(),this.showNext();break;case"Home":t.preventDefault(),this.goToImage(0);break;case"End":t.preventDefault(),this.goToImage(this.images.length-1)}}toggleImageMode(){this.fitToScreen=!this.fitToScreen,this.updateTemplateProperties(),this.updateImageDisplay();const t=this.getApp()?.events;t&&t.emit("lightbox:mode-changed",{gallery:this,fitToScreen:this.fitToScreen})}updateImageDisplay(){const t=this.element.querySelector(".lightbox-image"),e=this.element.querySelector(".lightbox-image-container"),s=this.element.querySelector(".lightbox-mode-indicator");t&&(this.fitToScreen?(t.style.maxWidth="100%",t.style.maxHeight="100%",t.style.objectFit="contain",t.style.cursor="zoom-in"):(t.style.maxWidth="none",t.style.maxHeight="none",t.style.objectFit="none",t.style.cursor="zoom-out"),t.style.userSelect="none"),e&&(e.style.overflow=this.fitToScreen?"":"auto"),s&&(s.textContent=`${this.modeIndicator} • Click image to toggle`)}close(){const t=this.getApp()?.events;t&&t.emit("lightbox:closed",{gallery:this}),this.destroy()}async onBeforeDestroy(){document.body.style.overflow="",this.allowKeyboard&&document.removeEventListener("keydown",this._keyboardHandler),this.element.parentNode===document.body&&document.body.removeChild(this.element)}static show(t,e={}){const s=new LightboxGallery({images:t,...e});return s.render().then(()=>{s.mount()}),s}}window.LightboxGallery=LightboxGallery;class PDFViewer extends t.View{constructor(t={}){super({...t,className:`pdf-viewer ${t.className||""}`,tagName:"div"}),this.pdfUrl=t.pdfUrl||t.src||"",this.title=t.title||"PDF Document",this.pdfDoc=null,this.currentPage=1,this.totalPages=0,this.pageRendering=!1,this.pageNumPending=null,this.scale=1,this.minScale=.25,this.maxScale=5,this.scaleStep=.25,this.fitMode="page",this.canvas=null,this.ctx=null,this.showControls=!1!==t.showControls,this.allowZoom=!1!==t.allowZoom,this.allowNavigation=!1!==t.allowNavigation,this.showPageNumbers=!1!==t.showPageNumbers,this.isLoaded=!1,this.isLoading=!1,this.pdfjsWorkerPath=t.pdfjsWorkerPath||"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.js",this.pdfjsCMapUrl=t.pdfjsCMapUrl||"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/cmaps/",this.canvasContainer=null,this.controlsElement=null,this.statusElement=null,this.pageInput=null}async getTemplate(){return'\n      <div class="pdf-viewer-container">\n        {{#showControls}}\n        <div class="pdf-viewer-toolbar" data-container="toolbar">\n          <div class="btn-toolbar" role="toolbar">\n            \x3c!-- Navigation Controls --\x3e\n            {{#allowNavigation}}\n            <div class="btn-group me-2" role="group" aria-label="Navigation">\n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="first-page" title="First Page">\n                <i class="bi bi-chevron-double-left"></i>\n              </button>\n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="prev-page" title="Previous Page">\n                <i class="bi bi-chevron-left"></i>\n              </button>\n              \n              {{#showPageNumbers}}\n              <div class="input-group input-group-sm" style="width: 120px;">\n                <input type="number" class="form-control text-center page-input" min="1" value="1" data-change-action="page-input">\n                <span class="input-group-text page-total">/ 0</span>\n              </div>\n              {{/showPageNumbers}}\n              \n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="next-page" title="Next Page">\n                <i class="bi bi-chevron-right"></i>\n              </button>\n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="last-page" title="Last Page">\n                <i class="bi bi-chevron-double-right"></i>\n              </button>\n            </div>\n            {{/allowNavigation}}\n\n            \x3c!-- Zoom Controls --\x3e\n            {{#allowZoom}}\n            <div class="btn-group me-2" role="group" aria-label="Zoom">\n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="zoom-out" title="Zoom Out">\n                <i class="bi bi-zoom-out"></i>\n              </button>\n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="zoom-in" title="Zoom In">\n                <i class="bi bi-zoom-in"></i>\n              </button>\n              \n              <div class="btn-group" role="group">\n                <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" title="Fit">\n                  <i class="bi bi-arrows-fullscreen"></i>\n                </button>\n                <ul class="dropdown-menu">\n                  <li><a class="dropdown-item" href="#" data-action="fit-page">Fit Page</a></li>\n                  <li><a class="dropdown-item" href="#" data-action="fit-width">Fit Width</a></li>\n                  <li><a class="dropdown-item" href="#" data-action="actual-size">Actual Size</a></li>\n                </ul>\n              </div>\n            </div>\n            {{/allowZoom}}\n\n            \x3c!-- Utility Controls --\x3e\n            <div class="btn-group me-2" role="group" aria-label="Utilities">\n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="download" title="Download">\n                <i class="bi bi-download"></i>\n              </button>\n              <button type="button" class="btn btn-outline-secondary btn-sm" data-action="print" title="Print">\n                <i class="bi bi-printer"></i>\n              </button>\n            </div>\n          </div>\n        </div>\n        {{/showControls}}\n\n        \x3c!-- PDF Content Area --\x3e\n        <div class="pdf-viewer-content" data-container="content">\n          <div class="pdf-canvas-container" data-container="canvasContainer">\n            <canvas class="pdf-canvas" data-container="canvas"></canvas>\n          </div>\n\n          <div class="pdf-viewer-overlay">\n            <div class="pdf-viewer-loading">\n              <div class="spinner-border text-primary" role="status">\n                <span class="visually-hidden">Loading PDF...</span>\n              </div>\n              <div class="mt-2">Loading PDF...</div>\n            </div>\n          </div>\n\n          <div class="pdf-viewer-error" style="display: none;">\n            <div class="alert alert-danger" role="alert">\n              <i class="bi bi-exclamation-triangle"></i>\n              <strong>Error:</strong> <span class="error-message">Failed to load PDF</span>\n            </div>\n          </div>\n        </div>\n\n        \x3c!-- Status Bar --\x3e\n        <div class="pdf-viewer-status" data-container="status">\n          <small class="text-muted">\n            <span class="current-page">0</span> of <span class="total-pages">0</span> pages |\n            <span class="zoom-level">100%</span> |\n            <span class="document-title">{{title}}</span>\n          </small>\n        </div>\n      </div>\n    '}get(){return{pdfUrl:this.pdfUrl,title:this.title,showControls:this.showControls,allowZoom:this.allowZoom,allowNavigation:this.allowNavigation,showPageNumbers:this.showPageNumbers}}async onAfterRender(){this.canvas=this.element.querySelector(".pdf-canvas"),this.canvasContainer=this.element.querySelector(".pdf-canvas-container"),this.controlsElement=this.element.querySelector(".pdf-viewer-toolbar"),this.statusElement=this.element.querySelector(".pdf-viewer-status"),this.pageInput=this.element.querySelector(".page-input"),this.overlayElement=this.element.querySelector(".pdf-viewer-overlay"),this.errorElement=this.element.querySelector(".pdf-viewer-error"),this.canvas&&(this.ctx=this.canvas.getContext("2d")),this.setupEssentialEventListeners(),await this.initializePDFJS(),this.pdfUrl&&await this.loadPDF()}setupEssentialEventListeners(){const t=t=>this.handleKeyDown(t);document.addEventListener("keydown",t);let e=null;this.canvasContainer&&(e=new ResizeObserver(()=>{"auto"!==this.fitMode&&this.applyFitMode()}),e.observe(this.canvasContainer)),this._essentialListeners=[{el:document,type:"keydown",fn:t}],this._resizeObserver=e}async initializePDFJS(){try{return void 0===window.pdfjsLib&&await this.loadPDFJSLibrary(),window.pdfjsLib.GlobalWorkerOptions.workerSrc=this.pdfjsWorkerPath,!0}catch(t){return console.error("Failed to initialize PDF.js:",t),this.showError("Failed to initialize PDF viewer"),!1}}async loadPDFJSLibrary(){return new Promise((t,e)=>{const s=document.createElement("script");s.src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.js",s.onload=t,s.onerror=e,document.head.appendChild(s)})}async handleActionFirstPage(){await this.goToPage(1)}async handleActionPrevPage(){await this.goToPage(this.currentPage-1)}async handleActionNextPage(){await this.goToPage(this.currentPage+1)}async handleActionLastPage(){await this.goToPage(this.totalPages)}async onChangePageInput(t,e){const s=parseInt(e.value,10);s>=1&&s<=this.totalPages?await this.goToPage(s):e.value=this.currentPage}async handleActionZoomIn(){this.setScale(this.scale+this.scaleStep)}async handleActionZoomOut(){this.setScale(this.scale-this.scaleStep)}async handleActionFitPage(){this.setFitMode("page")}async handleActionFitWidth(){this.setFitMode("width")}async handleActionActualSize(){this.setScale(1),this.fitMode="auto"}async handleActionDownload(){this.downloadPDF()}async handleActionPrint(){window.print()}async loadPDF(){if(!this.pdfUrl||!window.pdfjsLib)return this.showError("PDF URL or PDF.js library not available"),!1;this.isLoading=!0,this.showLoading();try{const t=window.pdfjsLib.getDocument({url:this.pdfUrl,cMapUrl:this.pdfjsCMapUrl,cMapPacked:!0});this.pdfDoc=await t.promise,this.totalPages=this.pdfDoc.numPages,this.currentPage=1,this.updatePageControls(),this.updateStatus(),await this.renderPage(1),this.isLoaded=!0,this.isLoading=!1,this.element.classList.add("loaded"),this.hideLoading(),this.applyFitMode();const e=this.getApp()?.events;return e&&e.emit("pdfviewer:loaded",{viewer:this,pdfUrl:this.pdfUrl,totalPages:this.totalPages}),!0}catch(t){console.error("Error loading PDF:",t),this.isLoading=!1,this.showError("Failed to load PDF document");const e=this.getApp()?.events;return e&&e.emit("pdfviewer:error",{viewer:this,pdfUrl:this.pdfUrl,error:t.message}),!1}}async renderPage(t){if(this.pageRendering)this.pageNumPending=t;else if(this.pdfDoc&&this.canvas&&this.ctx){this.pageRendering=!0,this.currentPage=t;try{const e=await this.pdfDoc.getPage(t),s=e.getViewport({scale:this.scale});this.canvas.height=s.height,this.canvas.width=s.width,this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);const i={canvasContext:this.ctx,viewport:s},n=e.render(i);if(await n.promise,this.pageRendering=!1,null!==this.pageNumPending){const t=this.pageNumPending;this.pageNumPending=null,await this.renderPage(t)}this.updatePageControls(),this.updateStatus();const a=this.getApp()?.events;a&&a.emit("pdfviewer:page-changed",{viewer:this,currentPage:this.currentPage,totalPages:this.totalPages})}catch(e){console.error("Error rendering page:",e),this.pageRendering=!1,this.showError("Failed to render PDF page")}}}async goToPage(t){!this.pdfDoc||t<1||t>this.totalPages||await this.renderPage(t)}setScale(t){const e=this.scale;this.scale=Math.max(this.minScale,Math.min(this.maxScale,t)),this.fitMode="auto",this.isLoaded&&this.renderPage(this.currentPage);const s=this.getApp()?.events;s&&e!==this.scale&&s.emit("pdfviewer:scale-changed",{viewer:this,oldScale:e,newScale:this.scale})}setFitMode(t){const e=this.fitMode;this.fitMode=t,this.applyFitMode();const s=this.getApp()?.events;s&&s.emit("pdfviewer:fit-mode-changed",{viewer:this,oldMode:e,newMode:t})}applyFitMode(){this.isLoaded&&this.pdfDoc&&this.canvasContainer&&this.pdfDoc.getPage(this.currentPage).then(t=>{const e=this.canvasContainer.getBoundingClientRect(),s=t.getViewport({scale:1});let i;if("page"===this.fitMode){const t=(e.width-40)/s.width,n=(e.height-40)/s.height;i=Math.min(t,n)}else{if("width"!==this.fitMode)return;i=(e.width-40)/s.width}this.scale=Math.max(this.minScale,Math.min(this.maxScale,i)),this.renderPage(this.currentPage)})}handleKeyDown(t){if("INPUT"!==t.target.tagName||t.target===this.pageInput)switch(t.key){case"ArrowLeft":case"PageUp":t.preventDefault(),this.goToPage(this.currentPage-1);break;case"ArrowRight":case"PageDown":t.preventDefault(),this.goToPage(this.currentPage+1);break;case"Home":t.preventDefault(),this.goToPage(1);break;case"End":t.preventDefault(),this.goToPage(this.totalPages);break;case"+":case"=":(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.setScale(this.scale+this.scaleStep));break;case"-":(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.setScale(this.scale-this.scaleStep));break;case"0":(t.ctrlKey||t.metaKey)&&(t.preventDefault(),this.setFitMode("page"))}}updatePageControls(){this.pageInput&&(this.pageInput.value=this.currentPage);const t=this.element.querySelector(".page-total");t&&(t.textContent=`/ ${this.totalPages}`);const e=this.element.querySelector('[data-action="first-page"]'),s=this.element.querySelector('[data-action="prev-page"]'),i=this.element.querySelector('[data-action="next-page"]'),n=this.element.querySelector('[data-action="last-page"]');e&&(e.disabled=this.currentPage<=1),s&&(s.disabled=this.currentPage<=1),i&&(i.disabled=this.currentPage>=this.totalPages),n&&(n.disabled=this.currentPage>=this.totalPages);const a=this.element.querySelector('[data-action="zoom-in"]'),o=this.element.querySelector('[data-action="zoom-out"]');a&&(a.disabled=this.scale>=this.maxScale),o&&(o.disabled=this.scale<=this.minScale)}updateStatus(){if(!this.statusElement)return;const t=this.statusElement.querySelector(".current-page"),e=this.statusElement.querySelector(".total-pages"),s=this.statusElement.querySelector(".zoom-level");t&&(t.textContent=this.currentPage),e&&(e.textContent=this.totalPages),s&&(s.textContent=`${Math.round(100*this.scale)}%`)}showLoading(){this.overlayElement&&(this.overlayElement.style.display="flex")}hideLoading(){this.overlayElement&&(this.overlayElement.style.display="none")}showError(t){if(this.hideLoading(),this.errorElement){const e=this.errorElement.querySelector(".error-message");e&&(e.textContent=t),this.errorElement.style.display="block"}console.error("PDF Viewer Error:",t)}downloadPDF(){if(!this.pdfUrl)return;const t=document.createElement("a");t.href=this.pdfUrl,t.download=this.title+".pdf",t.target="_blank",t.click()}setPDF(t,e=""){const s=this.pdfUrl;this.pdfUrl=t,this.title=e||"PDF Document",this.isLoaded=!1,this.element.classList.remove("loaded"),this.pdfDoc&&(this.pdfDoc.destroy(),this.pdfDoc=null),this.currentPage=1,this.totalPages=0,this.scale=1,t&&this.loadPDF();const i=this.getApp()?.events;i&&i.emit("pdfviewer:pdf-changed",{viewer:this,oldPdfUrl:s,newPdfUrl:t})}getCurrentPage(){return this.currentPage}getTotalPages(){return this.totalPages}getCurrentScale(){return this.scale}async onBeforeDestroy(){this.pdfDoc&&(this.pdfDoc.destroy(),this.pdfDoc=null),this.pageRendering=!1,this.pageNumPending=null,this._essentialListeners&&(this._essentialListeners.forEach(({el:t,type:e,fn:s})=>{t&&t.removeEventListener(e,s)}),this._essentialListeners=null),this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null);const t=this.getApp()?.events;t&&t.emit("pdfviewer:destroyed",{viewer:this})}static async showDialog(t,s={}){const{title:i="PDF Viewer",size:n="fullscreen",showControls:a=!0,allowZoom:o=!0,allowNavigation:r=!0,showPageNumbers:l=!0,...c}=s,d=new PDFViewer({pdfUrl:t,title:i,showControls:a,allowZoom:o,allowNavigation:r,showPageNumbers:l}),h=new e.default({title:i,body:d,size:n,centered:!0,backdrop:"static",keyboard:!0,buttons:[{text:"Download",action:"download",class:"btn btn-outline-primary"},{text:"Close",action:"close",class:"btn btn-secondary",dismiss:!0}],...c});return await h.render(),document.body.appendChild(h.element),await h.mount(),h.show(),new Promise(t=>{h.on("hidden",()=>{h.destroy(),t(d)}),h.on("action:download",()=>{d.downloadPDF()}),h.on("action:close",()=>{h.hide()})})}}exports.LightboxGallery=LightboxGallery,exports.PDFViewer=PDFViewer;
//# sourceMappingURL=PDFViewer-D5SmAaQD.js.map