UNPKG

@mindfiredigital/page-builder

Version:
628 lines (622 loc) 21.7 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator['throw'](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { Canvas } from './canvas/Canvas.js'; import { Sidebar } from './sidebar/ConfigSidebar.js'; import { CustomizationSidebar } from './sidebar/CustomizationSidebar.js'; import { createSidebar } from './sidebar/CreateSidebar.js'; import { createNavbar } from './navbar/CreateNavbar.js'; import { HTMLGenerator } from './services/HTMLGenerator.js'; import { JSONStorage } from './services/JSONStorage.js'; import { showDialogBox, showNotification, syntaxHighlightCSS, syntaxHighlightHTML, } from './utils/utilityFunctions.js'; import { createZipFile } from './utils/zipGenerator.js'; import { ShortcutManager } from './services/ShortcutManager.js'; import { PreviewPanel } from './canvas/PreviewPanel.js'; import './styles/main.css'; import { svgs } from './icons/svgs.js'; import html2pdf from 'html2pdf.js'; export class PageBuilder { constructor( dynamicComponents = { Basic: [], Extra: [], Custom: {}, }, initialDesign = null, editable = true, brandTitle, showAttributeTab, layoutMode = 'absolute' ) { this.dynamicComponents = dynamicComponents; this.initialDesign = initialDesign; this.canvas = new Canvas(); this.sidebar = new Sidebar(this.canvas); this.htmlGenerator = new HTMLGenerator(this.canvas); this.jsonStorage = new JSONStorage(); this.previewPanel = new PreviewPanel(); this.editable = editable; this.brandTitle = brandTitle; this.showAttributeTab = showAttributeTab; this.layoutMode = layoutMode; this.initializeEventListeners(); } // Static method to reset header flag (called during cleanup) static resetHeaderFlag() { PageBuilder.headerInitialized = false; } initializeEventListeners() { // Re-initialize core components this.canvas = new Canvas(); this.sidebar = new Sidebar(this.canvas); this.htmlGenerator = new HTMLGenerator(this.canvas); this.jsonStorage = new JSONStorage(); this.previewPanel = new PreviewPanel(); this.setupInitialComponents(); this.setupSaveButton(); this.setupResetButton(); this.handleExport(); this.setupExportHTMLButton(); this.setupExportPDFButton(); this.setupViewButton(); this.setupPreviewModeButtons(); this.setupUndoRedoButtons(); } setupInitialComponents() { createSidebar(this.dynamicComponents, this.editable); // Pass initial design to Canvas.init Canvas.init( this.initialDesign, this.editable, this.dynamicComponents.Basic, this.layoutMode ); this.sidebar.init(); ShortcutManager.init(); CustomizationSidebar.init( this.dynamicComponents.Custom, this.editable, this.dynamicComponents.Basic, this.showAttributeTab ); // Create header logic - improved to handle re-initialization this.createHeaderIfNeeded(); } createHeaderIfNeeded() { const existingHeader = document.getElementById('page-builder-header'); // Only create header if it doesn't exist if (!existingHeader) { const appElement = document.getElementById('app'); if (appElement && appElement.parentNode) { const header = document.createElement('header'); header.id = 'page-builder-header'; header.appendChild( createNavbar(this.editable, this.brandTitle, this.showAttributeTab) ); appElement.parentNode.insertBefore(header, appElement); PageBuilder.headerInitialized = true; } else { console.error('Error: #app not found in the DOM'); } } else { // Header exists, mark as initialized PageBuilder.headerInitialized = true; } } // Rest of your methods remain the same... setupSaveButton() { const saveButton = document.getElementById('save-btn'); if (saveButton) { saveButton.addEventListener('click', () => { const layoutJSON = Canvas.getState(); this.jsonStorage.save(layoutJSON); showNotification('Saving progress...'); }); } } setupResetButton() { const resetButton = document.getElementById('reset-btn'); if (resetButton) { resetButton.addEventListener('click', () => { showDialogBox( 'Are you sure you want to reset the layout?', () => { this.jsonStorage.remove(); Canvas.clearCanvas(); showNotification('The saved layout has been successfully reset.'); }, () => { console.log('Layout reset canceled.'); } ); }); } } /** * This function handles the event on clicking the export button * It opens up a drop down with 2 options for exporting * One is for html export and another is for json object export */ handleExport() { const exportBtn = document.getElementById('export-btn'); if (exportBtn) { const dropdown = document.createElement('div'); dropdown.classList.add('export-dropdown'); const option1 = document.createElement('div'); option1.textContent = 'HTML'; option1.classList.add('export-option'); option1.id = 'export-html-btn'; const option2 = document.createElement('div'); option2.textContent = 'PDF'; option2.classList.add('export-option'); option2.id = 'export-pdf-btn'; dropdown.appendChild(option1); dropdown.appendChild(option2); exportBtn.appendChild(dropdown); exportBtn.addEventListener('click', event => { event.stopPropagation(); dropdown.classList.toggle('visible'); }); // Hide dropdown when clicking outside document.addEventListener('click', event => { if (!exportBtn.contains(event.target)) { dropdown.classList.remove('visible'); } }); } } /** * This function handles opening up the modal on clicking export to html option from drop down options * This generates expected html and css present on the canvas layout. */ setupExportHTMLButton() { const exportButton = document.getElementById('export-html-btn'); if (exportButton) { exportButton.addEventListener('click', () => { const htmlGenerator = new HTMLGenerator(new Canvas()); const html = htmlGenerator.generateHTML(); const css = htmlGenerator.generateCSS(); const highlightedHTML = syntaxHighlightHTML(html); const highlightedCSS = syntaxHighlightCSS(css); const modal = this.createExportModal( highlightedHTML, highlightedCSS, html, css ); document.body.appendChild(modal); modal.classList.add('show'); }); } } setupExportPDFButton() { const exportButton = document.getElementById('export-pdf-btn'); if (exportButton) { exportButton.addEventListener('click', () => __awaiter(this, void 0, void 0, function* () { var _a; showNotification('Generating PDF for download...'); // Blur active element and remove all selected/focus states (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur(); document.querySelectorAll('.selected').forEach(el => { el.classList.remove('selected'); }); document.querySelectorAll('.table-cell-content').forEach(el => { el.blur(); }); yield new Promise(resolve => setTimeout(resolve, 1500)); const tempContainer = document.createElement('div'); try { const worker = html2pdf(); if (!worker) { showNotification('html2pdf library not loaded'); return; } const htmlGenerator = new HTMLGenerator(new Canvas()); const contentHTML = htmlGenerator.generateHTML(); let css = htmlGenerator.generateCSS(); const canvasElement = document.getElementById('canvas'); if (!canvasElement) return; const contentWidth = canvasElement.scrollWidth; const contentHeight = canvasElement.scrollHeight; const A4_WIDTH_PX = 794; const A4_HEIGHT_PX = 1123; const MARGIN_BUFFER_PX = 40; const QUALITY_SCALE = 3; const widthScaleFactor = (A4_WIDTH_PX - MARGIN_BUFFER_PX) / contentWidth; const heightScaleFactor = (A4_HEIGHT_PX - MARGIN_BUFFER_PX) / contentHeight; const SHRINK_FACTOR = Math.min( widthScaleFactor, heightScaleFactor, 1 ); const FINAL_HTML2CANVAS_SCALE = SHRINK_FACTOR * QUALITY_SCALE; css = css.replace(/min-height:\s*100vh/gi, 'min-height: auto'); const pdfContent = ` <style> ${css} /* General Styles for isolated rendering */ * { box-sizing: border-box; } html, body, #pdf-wrapper { margin: 0; padding: 0; overflow: visible !important; font-family: Arial, sans-serif !important; background-color: white !important; } /* Remove all focus and selection styles */ *:focus { outline: none !important; box-shadow: none !important; } .selected { outline: none !important; box-shadow: none !important; border-color: inherit !important; } .table-cell-content:focus { outline: none !important; border: none !important; } /* Set wrapper to the full, non-scaled content size */ #pdf-wrapper { width: ${contentWidth}px !important; height: ${contentHeight}px !important; overflow: visible !important; transform: none !important; } #canvas.home { width: ${contentWidth}px !important; height: ${contentHeight}px !important; min-height: auto !important; transform: none !important; position: relative !important; margin: 0 !important; padding: 0 !important; overflow: visible !important; } /* Prevent breaking elements */ table, #pdf-wrapper, #canvas.home { page-break-inside: avoid !important; } </style> <div id="pdf-wrapper"> ${contentHTML} </div> `; tempContainer.innerHTML = pdfContent; tempContainer.style.cssText = ` position: absolute; left: -99999px; top: 0; width: ${contentWidth}px; height: ${contentHeight}px; overflow: visible; background-color: white; `; document.body.appendChild(tempContainer); yield new Promise(resolve => setTimeout(resolve, 100)); const sourceElement = tempContainer.querySelector('#pdf-wrapper'); if (!sourceElement) { throw new Error('PDF source element (#pdf-wrapper) not found.'); } yield worker .set({ filename: 'exported_page_download.pdf', image: { type: 'png', quality: 1 }, html2canvas: { scale: FINAL_HTML2CANVAS_SCALE, width: contentWidth, height: contentHeight, useCORS: true, logging: false, backgroundColor: null, letterRendering: true, allowTaint: true, onclone: clonedDoc => { // ✅ Clean up focus/selection styles in the cloned document clonedDoc.querySelectorAll('.selected').forEach(el => { el.classList.remove('selected'); }); clonedDoc .querySelectorAll('.table-cell-content') .forEach(el => { el.style.outline = 'none'; el.style.boxShadow = 'none'; }); }, }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait', }, }) .from(sourceElement) .save(); showNotification('PDF downloaded successfully!'); } catch (error) { console.error('PDF generation error:', error); showNotification( 'Error generating PDF. Check console for details.' ); } finally { if (document.body.contains(tempContainer)) { document.body.removeChild(tempContainer); } } }) ); } } createExportModal(highlightedHTML, highlightedCSS, html, css) { const modal = document.createElement('div'); modal.id = 'export-dialog'; modal.classList.add('modal'); const modalContent = document.createElement('div'); modalContent.classList.add('export-modal-content'); const closeButton = this.createCloseButton(modal); modalContent.appendChild(closeButton); const htmlSection = this.createCodeSection('HTML', highlightedHTML); const cssSection = this.createCodeSection('CSS', highlightedCSS); const exportButton = this.createExportToZipButton(html, css); modalContent.appendChild(htmlSection); modalContent.appendChild(cssSection); modalContent.appendChild(exportButton); const exportButtonWrapper = document.createElement('div'); exportButtonWrapper.classList.add('button-wrapper'); exportButtonWrapper.appendChild(modalContent); modal.appendChild(exportButtonWrapper); this.setupModalEventListeners(modal); return modal; } createCloseButton(modal) { const closeButton = document.createElement('button'); closeButton.textContent = '×'; closeButton.classList.add('close-btn'); closeButton.addEventListener('click', () => this.closeModal(modal)); return closeButton; } createCodeSection(title, highlightedContent) { const section = document.createElement('div'); section.classList.add('modal-section'); const titleElement = document.createElement('h2'); titleElement.textContent = title; const codeBlock = document.createElement('div'); codeBlock.classList.add('code-block'); codeBlock.setAttribute('contenteditable', 'true'); codeBlock.innerHTML = highlightedContent; section.appendChild(titleElement); section.appendChild(codeBlock); return section; } createExportToZipButton(html, css) { const exportButton = document.createElement('button'); exportButton.textContent = 'Export to ZIP'; exportButton.classList.add('export-btn'); exportButton.addEventListener('click', () => { const zipFile = createZipFile([ { name: 'index.html', content: html }, { name: 'styles.css', content: css }, ]); const link = document.createElement('a'); link.href = URL.createObjectURL(zipFile); link.download = 'exported-files.zip'; link.click(); URL.revokeObjectURL(link.href); }); return exportButton; } setupModalEventListeners(modal) { modal.addEventListener('click', event => { if (event.target === modal) { this.closeModal(modal); } }); document.addEventListener('keydown', event => { if (event.key === 'Escape') { this.closeModal(modal); } }); } closeModal(modal) { modal.classList.remove('show'); modal.classList.add('hide'); setTimeout(() => modal.remove(), 300); } setupViewButton() { const viewButton = document.getElementById('view-btn'); if (viewButton) { viewButton.addEventListener('click', () => { const html = this.htmlGenerator.generateHTML(); const fullScreenModal = this.createFullScreenPreviewModal(html); document.body.appendChild(fullScreenModal); }); } } createFullScreenPreviewModal(html) { const fullScreenModal = document.createElement('div'); fullScreenModal.id = 'preview-modal'; fullScreenModal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 10000; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; background-color: #ffffff; `; const iframe = document.createElement('iframe'); iframe.id = 'preview-iframe'; iframe.style.cssText = ` width: 100%; height: 100%; border: none; background: #fff; `; iframe.srcdoc = html; fullScreenModal.appendChild(iframe); const closeButton = this.createPreviewCloseButton(fullScreenModal); fullScreenModal.appendChild(closeButton); const responsivenessContainer = this.createResponsivenessControls(iframe); fullScreenModal.insertBefore(responsivenessContainer, iframe); return fullScreenModal; } createPreviewCloseButton(fullScreenModal) { const closeButton = document.createElement('button'); closeButton.id = 'close-modal-btn'; closeButton.innerHTML = svgs.closePreviewBtn; closeButton.style.cssText = ` position: absolute; top: 0; left:0; font-size: 20px; border: none; background: none; font:bold; color:black; cursor: pointer; `; const closeModal = () => { setTimeout(() => fullScreenModal.remove(), 300); document.removeEventListener('keydown', escKeyListener); }; closeButton.addEventListener('click', closeModal); const escKeyListener = event => { if (event.key === 'Escape') { closeModal(); } }; document.addEventListener('keydown', escKeyListener); return closeButton; } createResponsivenessControls(iframe) { const responsivenessContainer = document.createElement('div'); responsivenessContainer.style.cssText = ` gap: 10px; display: flex; justify-content: center; align-items: center; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); border-bottom: 1px solid #e2e8f0; width: 100% `; const sizes = [ { icon: svgs.mobile, title: 'Desktop', width: '375px', height: '100%', }, { icon: svgs.tablet, title: 'Tablet', width: '768px', height: '100%', }, { icon: svgs.desktop, title: 'Mobile', width: '100%', height: '100%', }, ]; sizes.forEach(size => { const button = document.createElement('button'); button.style.cssText = ` padding: 5px; border: none; background: none; cursor: pointer; display: flex; align-items: center; justify-content: center; `; button.title = size.title; const iconContainer = document.createElement('div'); iconContainer.innerHTML = size.icon; const svgElement = iconContainer.querySelector('svg'); if (svgElement) { svgElement.style.width = '24px'; svgElement.style.height = '24px'; svgElement.classList.add('component-icon'); } button.appendChild(iconContainer); button.addEventListener('click', () => { iframe.style.width = size.width; iframe.style.height = size.height; iframe.style.transition = 'all 0.5s ease'; }); responsivenessContainer.appendChild(button); }); return responsivenessContainer; } setupPreviewModeButtons() { const desktopButton = document.getElementById('preview-desktop'); const tabletButton = document.getElementById('preview-tablet'); const mobileButton = document.getElementById('preview-mobile'); if (desktopButton) { desktopButton.addEventListener('click', () => { this.previewPanel.setPreviewMode('desktop'); }); } if (tabletButton) { tabletButton.addEventListener('click', () => { this.previewPanel.setPreviewMode('tablet'); }); } if (mobileButton) { mobileButton.addEventListener('click', () => { this.previewPanel.setPreviewMode('mobile'); }); } } setupUndoRedoButtons() { const undoButton = document.getElementById('undo-btn'); const redoButton = document.getElementById('redo-btn'); if (undoButton) { undoButton.addEventListener('click', () => { Canvas.historyManager.undo(); }); } if (redoButton) { redoButton.addEventListener('click', () => { Canvas.historyManager.redo(); }); } } } PageBuilder.headerInitialized = false; PageBuilder.initialCanvasWidth = null;